Pelorus UI Walkthrough — Action Plan & Tracker¶
Source: 2026-08-19 UI walk through for Pelorus.docx (client feedback, full walkthrough notes)
Tracked from: 2026-08-20
How to use this doc: each item has an ID and a status. As we work through items one by one, update the status in place (⬜ Not started → 🟦 In progress → ✅ Closed) rather than deleting rows, so this stays the single source of truth for what's been addressed. Items are grouped exactly as the client's document grouped them, plus one flagged conflict at the top that needs resolving before role-related items can proceed.
Summary counts¶
By status — 41 tracked action items total (the 8 "For Curo/VCL to agree" items are informational, not counted here):
| Status | Count | IDs |
|---|---|---|
| ✅ Closed | 33 | GEN-1, GEN-4, GEN-5, GEN-7, CAND-1, CAND-2, CRED-1, CRED-2, CRED-3, CRED-4, CRED-5, CRED-6, CRED-7, CRED-8, CRED-9, VCD-1, VCD-2, VCD-3, VCD-4, VCD-5, VCD-6, VCD-7, ISS-1, ISS-2, ISS-3, ISS-4, ISS-5, ISS-6, BULK-1, BULK-2, BULK-3, BULK-4, KYB-1 |
| ⬜ Ready to start | 5 | everything below not listed in the other rows |
| 🔁 Needs re-confirmation with client (may already be satisfied) | 2 | ISS-7, GEN-6 |
| ⏸ Deferred — not MVP | 1 | KYB-2 |
| Total | 41 |
By priority tier — cheapest/lowest-risk first, biggest structural change last. Use this table to pick the next item(s) to work; update the ✓ column as tiers close out.
| Tier | Focus | Milestone | Item IDs | Count | Done |
|---|---|---|---|---|---|
| 1 | Role foundation — unblocks tier 6 & most of the role conflict | M2 | GEN-5 | 1 | ✅ |
| 2 | Quick, independent UI/copy trims — no dependencies | M3 | CRED-1, CRED-3, CRED-4, CRED-6, CRED-8, CRED-9, VCD-2, VCD-4, VCD-6, VCD-7, ISS-1, ISS-3, BULK-2, BULK-3 | 14 | ✅ |
| 3 | Data-integrity gap | M3 | ISS-5 | 1 | ✅ |
| 4 | Wallet-style UI + link/GUID security (M3-17 / M3-18) | M3 | CRED-5, CRED-7, VCD-1, VCD-3, VCD-5, ISS-4, ISS-6 | 7 | ✅ 7/7 — CRED-7 and VCD-5 closed 2026-08-21, overtaken by the Copy Link/QR removal |
| 5 | Bulk CSV template + validation | M3 | BULK-1, BULK-4 | 2 | ✅ |
| 6 | Role-scoped access (needs Tier 1 done first) | M2 (GEN-7) / M3 (CRED-2) | CRED-2, GEN-7 | 2 | ✅ |
| 7 | KYB replacement approval mechanism | M2 | KYB-1 | 1 | ✅ |
| 8 | Candidate re-plumbing, then removal (in that order) | M3 | ISS-2, CAND-2, CAND-1 | 3 | ✅ |
| 9 | Infra / security | M2 (GEN-3) / general, not M2 or M3 (GEN-2) | GEN-2, GEN-3 | 2 | ☐ |
| 10 | Disclosure & Verification | M5 — excluded from current focus, zero code exists | DISC-1, DISC-2, DISC-3 | 3 | ☐ |
| — | Already closed (pre-existing, not part of the tiered sequence) | M2 (GEN-4) / general (GEN-1) | GEN-1, GEN-4 | 2 | ✅ |
| — | Needs re-confirmation with client first | M3 | ISS-7, GEN-6 | 2 | ☐ |
| — | Deferred, not MVP | M2 | KYB-2 | 1 | ☐ |
Current focus: M2 + M3 only¶
Confirmed: M5 has zero code today — only three unused TypeORM entities exist (app/backend/src/verification/entities/), no controller, no service, nothing wired up. Tier 10 (DISC-1–3) is correctly excluded from active work until the client sends the additional disclosure/verification detail they promised.
Scoped to M2 + M3 only, the active queue is Tiers 1–8 (30 items) plus GEN-3 from Tier 9 (M2-07 MFA). GEN-2 (docs behind an auth wall) doesn't cleanly belong to either milestone — it's a general infra/security item, not tied to platform admin (M2) or issuing (M3) functionality — so it's parked alongside M5 for now unless you want it pulled in.
That leaves 31 items in scope right now: Tiers 1–8 in full, plus GEN-3 — of which 25 are now done (Tiers 1–3 in full, Tier 4 5/7, Tiers 5–6 in full), 6 remain (plus the 2 permanently-blocked-for-now items in Tier 4).
✅ Tier 1 done — the role conflict from yesterday is resolved¶
GEN-5 is closed: ORG_USER is now a real, scoped role. It can issue credentials (manual + bulk) — verified live against the running backend (2026-08-21):
- GET /issuer/manual-credentials/credential-types → 200 (was 403)
- POST /issuer/credentials/bulk-issuance/upload → reaches request validation instead of 403
- GET /admin/kyb → still correctly 403 for the same org_user token — the change is scoped, not a blanket permission grant
Changed: manual-credential.controller.ts and bulk-issuance.controller.ts (ORG_USER added to every @Roles() decorator), plus a new ORG_USER entry in navConfig.tsx (Credentials / Issue Credential / Bulk Issue Credentials — same pages Org Admin uses). M2-15 and the Role Access Matrix artifact are both updated to match. "Request and verify credentials" (disclosure/verification) will get the same ORG_USER treatment once M5 is built.
This also unblocks Tier 6 (CRED-2, GEN-7) — not done yet, just no longer waiting on anything.
Original conflict note (2026-08-20, now resolved)
## 🔴 A real conflict with the previous day's confirmed role matrix This document says (under "For Curo / VCL to agree"): *"Curo / VCL to agree which user roles are needed. Roles are org admin / org user / issue / relying party / credential manager. Only need the first 2 for now?"* and, under General Fixes: *"Apply the role-based security to the org user UI. The base user will be able to issue (including bulk), request and verify credentials only."* This means **`ORG_USER` needs to be a real, functional role for MVP** — able to issue credentials (manual + bulk), raise disclosure requests, and run verification — separate from `ORG_ADMIN`. That's a direct change to what was just confirmed and published as [M2-15](../milestones/M2/M2-15-role-permission-matrix-client-confirmation.md) and the "Role Access Matrix" artifact: Org Admin as the *sole* functional role, `ORG_USER` included in "gets nothing." That confirmation was based on a marked-up matrix that predates this fuller document. **This document supersedes it.** **Recommendation:** treat M2-15 as needing a second revision — add `ORG_USER` with a scoped permission set (issue credentials manual + bulk, raise/track disclosure requests, run/view verification) alongside `ORG_ADMIN` (full access, everything). This is item **GEN-5** below; it's called out here first because several other items (credential issuance, future disclosure/verification work) depend on knowing this before they're built, not after.Candidates¶
| ID | Item | Current state (verified in code) | Status |
|---|---|---|---|
| CAND-1 | Remove the whole "Workforce" nav section (Candidate Records, Candidates) and linked pages/functionality | Fully done as originally worded, in two stages. First stage (2026-08-21, earlier) removed only the real Candidate entity's browse/search UI (/candidates), on the reasoning that "Candidate Records" (/candidate, actually Employee CRUD) was still bulk issuance's data source. Revisited the same day after re-reading your original docx wording directly ("We have no need of the Candidate data type... this is not a system of record for Candidates... Remove the whole workforce section: Candidate Records and Candidates from the menu") and confirming bulk issuance no longer uses Employee records either (see BULK-1/BULK-4 rework) — that original justification was gone, so completed the removal as literally worded: deleted the Employee entity, service, controller, DTO, all five /candidate/* pages, the EmployeeList/EmployeeForm/EmployeeDetails components, and the "Candidate Records" nav item (the whole "Workforce" section, now empty, is gone). Also dropped CredentialOffer.employeeId (a dead column — nothing had written to it since CAND-2/the bulk rework) and the orphaned employees table via a new migration. Fixed up the few places that read employee data for display — the public claim page now shows job title/department/dates sourced from the credential itself, not a deleted relation. Verified live: /candidate and /candidates both 404 on frontend and API, manual issuance, bulk issuance, and the claim page's employment-details display all still work end-to-end. Clean tsc, lint, and full builds on both sides. |
✅ Closed 2026-08-21 |
| CAND-2 | When issuing, enter recipient fields (First Name / Last Name / Email) directly — don't read from a candidate record | Replaced the required Employee-picking Autocomplete in ManualCredentialForm.tsx with a typed "Credential recipient" section (First name / Last name / Email). Updated 2026-08-21: initially kept employeeId as an alternate path for bulk issuance only, but that's now gone too (see CAND-1) — recipient is the only path for both manual and bulk issuance. A fresh Candidate row is created per issuance directly from the typed input (createCandidateFromRecipient) — intentionally not deduped against prior issuances, per your own framing that this isn't a system of record for candidates, the credential is. jobTitle/countryCode (previously auto-filled from the Employee record) live in the existing mandatory dynamic-fields section. Verified live end-to-end: POST /issuer/manual-credentials and /preview issue correctly, missing-recipient requests are rejected with 400. Clean tsc, lint, and full builds on both sides. |
✅ Closed 2026-08-21 |
Credentials (list page)¶
| ID | Item | Current state | Status |
|---|---|---|---|
| CRED-1 | Reorder submenu: Issue a Credential; Bulk Issue Credentials; Credentials History | Done. navConfig.tsx — Org Admin and Org User Credentials groups reordered; the old "Credentials" link relabeled "Credentials History" (same /credentials route). Only the nav label changed, not the page's own heading. |
✅ Closed |
| CRED-2 | Org users see only credentials they issued; Org Admins see all in the org. CR if not MVP-feasible | Done — no CR needed, it was MVP-feasible. Found CredentialOffer.issuerUserId already existed as a column but was never populated at creation (only overwritten, incorrectly, on resend/revoke — fixed that too, since it was silently reassigning "who issued this" to whoever last touched it). Now set correctly at issuance time for both manual and bulk issuance. List, export, stats, and get-by-ID all scope to "my own" for Org User, full org for Org Admin — asking for someone else's credential by ID now 404s for an Org User the same as a nonexistent one (no ownership leaked). Also gave this whole controller @Roles() for the first time — it had none before, open to any authenticated user; Revoke is Org-Admin-only (not part of "issue" per the client's own wording). Verified live end-to-end: issued a real credential as Org User, confirmed Org User's list/stats show only that one while Org Admin sees all 6 in the org, and Org User gets a clean 404 on an existing-but-not-theirs credential. |
✅ Closed |
| CRED-3 | Remove "Manage candidate Records" button | Done. Button, its handler, and the now-unused icon import removed from pages/credentials/index.tsx. |
✅ Closed |
| CRED-4 | Remove "Created" from the status filter list (may still show in table data) | Done. Removed from STATUS_OPTIONS in pages/credentials/index.tsx — still renders correctly in the table if a credential happens to be in that status. |
✅ Closed |
| CRED-5 | Remove GUIDs/JSON/big URLs from the credentials pages for org roles; keep for Platform Admin | Done, scoped to GUIDs/JSON — see VCD-1/VCD-3 (the list page itself never showed a GUID or JSON, only the detail page did). The "big URL" part of this ask is the same territory as CRED-7/VCD-5, both explicitly on hold pending your security discussion — left untouched there rather than acting on an inconsistent read of "do it here, hold off there" for the same underlying thing. |
✅ Closed (GUID/JSON portion) |
| CRED-6 | Rename "EmploymentPast v1.1" → "Past Employment" everywhere | Done. New shared credentialDisplay.ts util (credentialTypeLabel()); backend's own getCredentialTypeName() shortened to "Past Employment" too. Applied to the credentials list, type filter, detail page title, issue form preview badge, and the bulk-issuance confirm dialog. |
✅ Closed |
| CRED-7 | Rename "Deep Link" column to "Links"; tooltip options "Copy URL" / "Copy QR code" — hold off, pending the security-of-credentials discussion | Resolved 2026-08-21, overtaken by later feedback. Rather than renaming the column or adding copy-URL/copy-QR-code affordances, the whole "Deep Link" column (Copy + QR icon buttons) was removed entirely from the credentials list — a separate ask that landed after this one. There's nothing left to rename or add tooltips to, and no copy/QR affordance exists anywhere on the list page anymore, which satisfies the underlying security concern this was on hold for. Verified: zero Copy/QR references left in pages/credentials/index.tsx. |
✅ Closed (overtaken — feature removed, not renamed) |
| CRED-8 | Black icons by default, lighter/greyed icons when an action (e.g. resend) isn't available | Done. All row-action icons (view/resend/revoke/copy/QR) given an explicit dark default colour in pages/credentials/index.tsx; MUI's built-in disabled-state dimming now reads as a clear, intentional contrast rather than a subtle default-vs-default difference. |
✅ Closed |
| CRED-9 | Replace "Sent Date" column with "Status date" (date of the currently-shown status) | Done. New credentialStatusDate() helper in credentialDisplay.ts — resolves to sentAt/claimedAt/revokedAt depending on the credential's actual current status (falls back to createdAt for created/expired, which have no dedicated timestamp field). Column header renamed to "Status date." Reused on the detail page's Credential Summary too (VCD-4). |
✅ Closed |
View Credential (Credential Details)¶
| ID | Item | Current state | Status |
|---|---|---|---|
| VCD-1 | Page should look like the wallet view — no JSON for org roles; keep for Platform Admin | Done. New shared CredentialWalletCard.tsx component (navy header, credential-type badge, icon-led Recipient/Employer/Start/End rows) — same visual pattern as the candidate-facing claim page, reused rather than reinvented. Org roles see the wallet card built from credential.payload.credentialSubject; Platform Admin still sees the raw payload for debugging. Live-verified the real preview response shape matches the component's expected fields exactly. |
✅ Closed |
| VCD-2 | Page title: "Name — Credential Type" (e.g. "Etan Bernstein – Past Employment") | Done. [id].tsx title now "${candidateName} — ${credentialTypeLabel(...)}", order swapped from type-first to name-first, using the same friendly-name util as CRED-6. |
✅ Closed |
| VCD-3 | Hide the GUID below the title for all but Platform Admin; match wallet field layout/labels/presentation. Note: Velocity schema may have a usable presentation description field | Done. The raw credential UUID chip in the page subtitle is now Platform-Admin-only. | ✅ Closed |
| VCD-4 | Credential Summary section: show only email, phone, sent, and a "Status and date" blob | Done. Trimmed to Email / Phone / Sent / a combined Status row (chip + its credentialStatusDate()); removed Type, Candidate name+email combo, Candidate ID, and the separate Created/Claimed/Revoked date rows. |
✅ Closed |
| VCD-5 | Remove the big URL + QR code image for all but Platform Admin. Leave "Copy link," add "Copy QR code" — hold off, pending the security-of-credentials discussion; may end up with resend-only, no copy buttons at all | Resolved 2026-08-21, and landed on exactly the outcome you flagged as likely ("may end up with resend-only, no copy buttons at all"). A separate later ask removed the QR code image, the raw URL text, and the Copy link button from the detail page entirely — only the "Open" button remains (still role-gated the same way; Platform Admin sees nothing extra here since none of this was Platform-Admin-only to begin with). No copy-URL/copy-QR-code button was ever built. Verified: zero Copy/QR references left in pages/credentials/[id].tsx. |
✅ Closed (overtaken — landed on resend/open-only, no copy buttons) |
| VCD-6 | Timeline: remove "Created" entry, keep "Sent" onward | Done. Removed the "Created" entry from the timeline array in [id].tsx — starts at "Sent" now. |
✅ Closed |
| VCD-7 | Credentials breadcrumb in the title section isn't clickable | Done — and it wasn't PageHeader, it was the top Topbar breadcrumb. Non-final breadcrumb segments now render as real links to their own route (e.g. clicking "Credentials" from a detail page goes back to the list), matching the existing "Home" link's behaviour. |
✅ Closed |
Issue a Credential¶
| ID | Item | Current state | Status |
|---|---|---|---|
| ISS-1 | Remove the blue "issuing as" banner | Done, with a deliberate nuance. Removed the routine blue "Issuing as org · DID" info banner. Kept the warning-severity variant (an org with no Velocity DID recorded — issuing would genuinely fail) since that's a real blocker, not decoration; only the blue "for your info" banner was in scope here. Flag if you want that warning gone too. | ✅ Closed |
| ISS-2 | Remove "Select Candidate"; add a "Credential Recipient" section with First Name / Last Name / Email | Same change as CAND-2 (done together) — see CAND-2's notes above for the full detail. ManualCredentialForm.tsx now shows a "1 · Credential recipient" section with required First name / Last name / Email fields; the old Employee Autocomplete (and the Employee CRUD feature it pulled from — see CAND-1) is gone entirely. |
✅ Closed 2026-08-21 |
| ISS-3 | Default credential type to "Past Employment" for MVP; remove the template dropdown (only one template per type) | Done. Credential Type and Template selects both removed from the issuance form; type is fixed to EmploymentPastV1.1 (shown read-only as "Past Employment"), template auto-resolves to the one default as before. Dead state/loading code removed along with the dropdowns rather than left unused. |
✅ Closed |
| ISS-4 | Read the schema into two sections: mandatory fields and optional fields | Done — M3-17 implemented. Fixed the actual bug: variablesSchema for EmploymentPastV1.1 was a stale, unrelated field list (email, salary, skills, etc. with no real schema mapping) that disagreed with the true required-field set, which is what let a field show "Required" under an "optional" heading. Reconciled to the real schema — firstName/lastName/jobTitle/dates/org fields are already auto-filled from the selected employee + your org record (not re-entered here), so the only two genuinely-optional override fields left are Department and Address locality. The form now renders a "Mandatory fields" / "Optional fields" split driven by each field's own flag — for this credential type that means "Optional fields" is the only one with anything in it today, which is the honest, correct answer rather than a fabricated empty mandatory section. Also fixed validateCredentialData() server-side, which was silently checking for keys that never existed in the real payload — it now checks the real required structure and returns a specific error before the round-trip to Velocity. |
✅ Closed |
| ISS-5 | Validate Start Date ≤ End Date, and both ≤ today (Velocity won't validate this for us) | Done, both sides. New IsOnOrBeforeToday/IsOnOrAfterSiblingDate class-validator decorators applied to employmentStartDate/employmentEndDate on both CreateManualCredentialDto and PreviewCredentialDto — verified live (bad ordering and future dates both now return a clear 400; valid input passes through untouched). Matching client-side validation added to the issuance form with the same rules, plus an HTML5 max on both date pickers so the browser's own picker won't offer future dates. |
✅ Closed |
| ISS-6 | Preview should use wallet-style layout; if so, maybe drop the separate Preview button | Done, one open question left for you. The preview panel now shows the same CredentialWalletCard as VCD-1, live-approximated from the selected employee before you click Preview, and upgraded to the exact real payload after. Kept the Preview button rather than removing it — it still does real, useful work (hits the backend, runs the real template + the newly-fixed validation), so removing it would lose that safety check. If you'd still rather drop it and always trust the live approximation, say so and I'll remove it. |
✅ Closed — button kept, flagged for your call |
| ISS-7 | No separate confirmation page after issuing — show a read-only confirmation banner on the same issuing page instead | Confirmed: there's already no separate confirmation page — success today replaces the form inline on the same page (ManualCredentialForm.tsx:425-490). This item may already be satisfied; worth a quick visual check against what the client actually saw rather than assuming a rebuild is needed |
⬜ Needs re-confirmation with client — may already be done |
Bulk Issuance¶
| ID | Item | Current state | Status |
|---|---|---|---|
| BULK-1 | Button to download a blank CSV template (with an explanatory second sheet) | Done, with one scoped-down part. "Download CSV template" button on the bulk-issuance page generates a template client-side — no backend round-trip needed for something this small. Updated 2026-08-21: after comparing against the official VNF bulk-issuance CSV format the client provided, and to match the now-recipient-typed manual issuance flow (CAND-2), the template's columns changed from employeeId,startDate,endDate to the full set of fields the manual form itself captures: firstName,lastName,email,employmentStartDate,employmentEndDate,jobTitle,countryCode,department,addressLocality (last two optional), with one example row filled in. The "second sheet" part isn't implemented: a CSV is a single-sheet format by definition, so a real second sheet would mean generating an actual .xlsx workbook instead — a bigger, separate piece of work (new dependency, different file type) I didn't want to add without your sign-off. Explanatory text stays in the page's info banner. Flag if you specifically want a two-sheet Excel file rather than a CSV. |
✅ Closed (CSV template; Excel two-sheet version flagged, not built) |
| BULK-2 | Remove template dropdown; default to the single template | Done. Template Select removed from BulkIssueCredentialsForm.tsx; the credential-type select stays (not asked to remove it here) since bulk issuance could plausibly support more types before the manual form does. Auto-selects the one available template as before, just without a visible control. |
✅ Closed |
| BULK-3 | Remove the "columns" explanatory text (may still need some explanatory text, TBC) | Done — removed, nothing added in its place since replacement copy is explicitly TBC per the client's own note. Revisit if/when that copy is decided. | ✅ Closed |
| BULK-4 | Validate the upload is in the correct format | Tested live, and it "mostly worked" as you suspected — found and closed one real gap. A genuinely malformed file (wrong headers, or not a CSV at all) was parsing "successfully" and silently creating a batch with every row skipped for an identical, unhelpful reason — safe (never created bad data) but confusing. Added an upfront check that rejects immediately with a clear message naming the columns it actually found, instead of only discovering the problem row-by-row after the fact. Updated 2026-08-21: the required-column set changed along with BULK-1 (see above) now that bulk issuance no longer references an existing employee record — the upfront check now validates against firstName,lastName,email,employmentStartDate,employmentEndDate,jobTitle,countryCode, and each row is additionally checked for blank required values and invalid dates, skipping just that row (with a specific reason) rather than failing the whole batch. Verified live: missing-column upload rejected upfront, a blank required cell skips just that row, an invalid date skips just that row, and a real multi-row upload issues correctly end-to-end (including a genuine failure→retry cycle, which also fixed a latent bug where a retried row's old error message never actually cleared in the database). |
✅ Closed |
KYB¶
| ID | Item | Current state | Status |
|---|---|---|---|
| KYB-1 | Remove KYB UI — it's managed on the Registrar, nothing to duplicate here (may add a Registrar wrapper post-MVP) | Resolved 2026-08-21. Confirmed with the client: KYB already happens on the Velocity Network Registrar when an org registers there, so our own submission/review workflow was pure duplication. New organisations created in this system now default to kybStatus: APPROVED / status: KYB_APPROVED at creation time (organization.entity.ts column default + organization.service.ts's createOrganization()), with an auto-approved KybRecord kept purely as an audit trail (notes: "Auto-approved — KYB is verified by the Velocity Network Registrar at org registration"). Removed the entire submission/review UI and backend across three surfaces that existed: the standalone /kyb pages + kyb.controller.ts/kyb.service.ts, a second, separate SuperAdmin approve/reject flow on the org detail page and dashboard (superadmin-org.controller.ts's kyb-status/kyb-records endpoints), and the now-fully-decorative "KYB Documents" step in the new-org creation wizard (collected data was never even sent to the backend). Also deleted kyb-management.service.ts/kyb-management.dto.ts, found to be fully dead code (registered but never wired to any controller) during this cleanup. kyb_records table itself is kept (now an approval audit log, not a workflow); KYB-2's Registrar-link/placeholder idea remains the explicit post-MVP option if you want visibility into the Registrar's own record later. Verified live end-to-end: created a real organisation via the API and confirmed it comes back kybStatus: approved / status: kyb_approved with a matching audit KybRecord; /kyb and the old SuperAdmin KYB endpoints all 404; SuperAdmin org detail/dashboard no longer show any approve/reject controls. Clean tsc, lint, and full builds on both sides. |
✅ Closed 2026-08-21 |
| KYB-2 | Link for Org Admin to reach their Registrar record (not MVP) | Deferred per client's own note | ⬜ Deferred — not MVP |
Disclosure / Verification — TBD¶
| ID | Item | Current state | Status |
|---|---|---|---|
| DISC-1 | Send a Credential Offer/invite to submit credentials (disclosure) | Matches the existing M5 plan direction (M5-00) | ⬜ Not started — M5, awaiting more client detail |
| DISC-2 | Disclosure can be any valid credential type, not limited to Past Employment, but not configurable yet | This narrows M5's "one generic disclosure request" to mean "all types, no per-type configuration" — consistent with what M5-00 already assumed (the VNF sample fixture found during M5 research was exactly this shape: ~30 credential types, "pick at least one") | ⬜ Not started — M5-00 already aligned |
| DISC-3 | Optional verification on receipt, showing pass/fail for the 5 points per disclosed credential | Matches M5-00's plan (per-credential credentialChecks) — note the client says 5 points; Velocity's own schema surfaced only 4 fields (TRUSTED_ISSUER/UNREVOKED/UNEXPIRED/UNTAMPERED) in every example found. Worth asking the client to list their 5 explicitly so the UI grid matches exactly, rather than guessing the 5th |
⬜ Not started — M5, needs the 5th check confirmed |
Client says more detail is coming on this flow — treat DISC-1–3 as provisional until that lands, per their own "TBD."
General Fixes¶
| ID | Item | Current state | Status |
|---|---|---|---|
| GEN-1 | British English throughout ("Organization" → "organisation"); client will review all copy before release | Already done 2026-08-20 — ~250 user-facing strings changed across 58 files, verified with a full build. Client's "we will review all copy" note means treat this as done-pending-their-final-check, not untouched | ✅ Closed (pending client's own copy review) |
| GEN-2 | Project docs need protecting behind an authorisation wall | Confirmed unprotected today — the docs_rec_instance MkDocs site (port 8001) has no auth in front of it. Needs a decision on mechanism (basic auth at the reverse proxy, or behind the app's own Auth0 login) |
⬜ Not started |
| GEN-3 | MFA needs adding to the login process | Cross-ref M2-07 — already an open item there | ⬜ Not started — see M2-07 |
| GEN-4 | "Invite User Error" — failed to invite in background, page still showing; assume OK once retested with a validated email | Already resolved 2026-08-20, by removal rather than a fix — per an earlier decision, "Invite User" was determined to not be a tracked requirement and was removed entirely (dialog, buttons, service methods) in favor of "Create User" only, which does work correctly. This feedback likely predates that change (docx dated 2026-08-19). Worth confirming with the client that Create User (not a separate Invite flow) is the accepted replacement | ✅ Closed — feature removed, confirm acceptance with client |
| GEN-5 | Apply role-based security to the org user UI — base user can issue (incl. bulk), request and verify credentials only | Done for the issuance half. ORG_USER added to every @Roles() decorator on manual-credential.controller.ts and bulk-issuance.controller.ts, plus a new ORG_USER nav entry in navConfig.tsx. Verified live: manual + bulk issuance endpoints now reachable (200/validation-error, not 403); unrelated org-admin-only endpoints (e.g. KYB) still correctly 403. "Request and verify credentials" will follow the same pattern once M5 exists. M2-15 and the Role Access Matrix updated to match. |
✅ Closed (issuance scope) — disclosure/verification scope to follow once M5 is built |
| GEN-6 | Wallet landing page — remove options via configuration; for now just show the VCL Career Wallet | Confirmed: there's no multi-wallet chooser to trim today — the claim page (pages/claim/[token].tsx) already only ever shows one wallet (Velocity Career Wallet) by name, with a web-claim fallback. A planned MobileAppLinks.tsx component (which would have offered multiple wallets) was never actually built. Worth confirming with the client exactly what they saw, since this item may already be satisfied, or may refer to something not yet found |
⬜ Needs re-confirmation with client — may already be satisfied |
| GEN-7 | Once permissions are adjusted, confirm what's left in Settings for the org user (password reset etc.) | Answer: nothing, by design — and now enforced, not just assumed. All 6 tabs (Email Templates, Branding, and 4 empty placeholders) are Org Admin/Platform Admin configuration; none of it is in Org User's scope ("issue, bulk-issue, request/verify" per GEN-5). Org User's nav never had a Settings link (Tier 1), and the backend already rejected these calls for that role — but nothing stopped Org User from typing the URL directly and hitting a broken page. Added a redirect to /unauthorized for any role but Org Admin/Platform Admin. On password reset specifically (per your note): correct, that's handled entirely by Auth0's own flow, not this app — confirmed there's no in-app password-change feature at all today; the "My sessions" page only has a line of text advising you to change your password if you see an unrecognised session, it doesn't actually implement it. Self-service profile editing doesn't exist in-app yet either (no "update my display name" or similar) — noting this as a future item alongside any other self-service fields you want to add later, not something built now. |
✅ Closed |
For Curo / VCL to agree (info only — not yet actionable)¶
These aren't code tasks — they're decisions/inputs needed from Curo and/or VCL before related work can start. Listed for visibility, not tracked to "closed" the same way as above.
| ID | Item | Notes |
|---|---|---|
| INFO-1 | Overall branding for the tool (minor changes to current branding expected) | — |
| INFO-2 | Curo to provide fixed copy (Help, About, etc.) for the site, likely as left-menu link(s) | — |
| INFO-3 | Curo to set up test staffing organisations + demo email addresses | — |
| INFO-4 | Possible CR: emails should appear to come from the organisation, not us — options: we send on their behalf, or they configure their own sending | Flag as a Change Request once scoped, not MVP by default |
| INFO-5 | Curo/VCL to agree exactly which roles are needed (org admin / org user / issuer / relying party / credential manager) — "only need the first 2 for now?" | This is the source of the GEN-5 conflict above — the "first 2" (org admin, org user) matches GEN-5's ask, so treat GEN-5 as reasonably safe to start even before this is formally signed off, but confirm before going further than those two roles |
| INFO-6 | Not MVP: week/month/year toggle on the Credentials History dashboard totals | Deferred |
| INFO-7 | Not MVP: status filter that dynamically shows only statuses present in the current data subset | Deferred |
| INFO-8 | Need to test logo pulling from the Registrar | Cross-ref the Velocity-logo auto-sync work already built this session (BrandingService.syncLogoFromVelocity) — this may just need a live test, not new code |
Additional fixes (not part of the original feedback)¶
Found and fixed proactively during this engagement — not raised in the client's walkthrough docx, so not counted in the 41 tracked items above and not annotated in the .docx.
| Date | Item | Notes | Status |
|---|---|---|---|
| 2026-08-21 | Stale-session login bug: hitting / with an expired/stale session showed the authenticated sidebar around the public landing page content; refreshing then showed a fully logged-out state; clicking "Sign in" bounced back to / instead of reaching Auth0; only a full browser cache clear recovered it |
Root cause: three uncoordinated auth systems (custom localStorage tokens, the Auth0 SDK's own separate cache, and a fully dead NextAuth wiring several components still consulted). A 401 cleared the app's own tokens but never touched the Auth0 SDK's cache, and nothing reactive told already-mounted components anything had changed. Fixed with a targeted patch (not the larger NextAuth removal, which was flagged but deferred): the 401 handler now fires an event that purges the SDK's own cache via a local-only logout; the sidebar reacts to that event immediately instead of only on a full reload; both sign-in pages now require actual local token evidence, not just the SDK's possibly-stale flag, before skipping the Auth0 redirect; fixed an unrelated ordering bug in the (currently unused) LogoutButton that meant it never actually sent a session ID to the backend logout call. Verified: clean tsc/lint/build, confirmed live by the user after the fix. |
✅ Fixed 2026-08-21 |