Findings Catalog
The audit's concrete lists: unused components, dead code, false-success sites, flow verdicts, page scorecards, backend gaps.
Every item below was verified with file:line evidence in the lane files, which were removed from the repo on 2026-09-05 once the fix program landed (reachable through git history under apps/dashboard/docs/audit-2026-09). Finding IDs (F-xxx) are stable references.
Unused components (delete — zero imports repo-wide)
ui/aspect-ratio · ui/context-menu · ui/field · ui/input-otp · ui/menubar · ui/navigation-menu · ui/pagination · ui/resizable · ui/table/data-table-date-filter · ui/table/data-table-slider-filter (F-H2). field.tsx is the only adopt-later candidate (new shadcn form pattern, never adopted).
Dead code — safe to delete (verified repo-wide)
apps/dashboard/src/.next/— an ESLint cache committed to git; also fix the unanchored/.next/gitignore (F-H1).src/lib/dal.ts— zero importers; its header claims to be "the REAL security boundary" (F-H3).src/lib/format.ts,src/utils/collection-utils.ts— transitively dead (F-H5, F-H15).- 15 dead
src/hooks/convexexports:isValidSlug,formatJobStatus,getJobStatusColor,useR2Upload,useAvatarUpload,useModerateRating,WEBHOOK_STATUS_OPTIONS+ typesSeasonalClaim,StampHistoryEntry,NotificationDoc,NotificationPrefs,TransformedOrder,UserWithAggregates,SupportStats(F-H6). - The stamps/shake chain:
framer-motionships to zero clients;stores/loyalty-store.ts(1,203 lines, the app's only zustand store) is a husk whose actions log "use Convex hooks instead"; its single consumer (stamp-collection-container.tsx, 845 L) is itself unreachable — and contains a latent false "Stamp awarded" toast (F-A1, F-G14). - Dependencies removable after build-verify:
react-resizable-panels,input-otp,server-only,@convex-dev/r2,@projectr/backend,framer-motion(F-H8). WARP.md— byte-identical duplicate ofCLAUDE.md(F-H9). Oneconsole.logatConvexClientProvider.tsx:57(F-H13).
The false-success map (~35 sites)
The hook layer returns data only; a {success:false} denial becomes a success toast or an empty list. Modules and counts:
| Module | Sites | What the user sees today |
|---|---|---|
| Ratings moderation | 9 + bulk | denial → success toast on every single moderate/feature/delete/report-resolve |
| Issued vouchers | 4 | revoke / bulk-revoke / extend / reissue denial → "Revoked N voucher(s) successfully" |
| Loyalty campaigns | 4 | create/update redirect as if saved; toggle + bulk activate/pause fabricate success counts |
| Voucher templates | 2 + bulk | edits silently discarded with "updated successfully" |
| Products & groups | 8 | silent no-op saves; group create navigates to /products/groups/null |
| Notifications | 6 | queue process/retry/cancel + template create/save/delete → green "Admin access required" |
| Content campaigns | 3 | create/update discarded on duplicate slug; delete false-success |
| Duit Raya | 2 | campaign delete + destructive code reset false-success |
| Support / user edit | 3 | status changes + partial saves unchecked |
Root cause F-B8 (hook contract), user impact F-F1 (false-empty). Fix = T1 in the backlog.
User-flow verdicts (all 16 traced)
| Flow | Verdict | Blocking issue |
|---|---|---|
| Support inbox | INTACT | — |
| Ratings moderation | BROKEN | pagination dead (rows past 20 unreachable) + false-success on every op |
| Voucher templates | FRAGILE | denial → success toast; journey "completes" without saving |
| Loyalty campaigns | FRAGILE | same false-success on create/edit |
| Content | FRAGILE | duplicate slug silently discards create/update |
| All others (13) | DEGRADED | works, with listed friction |
Highlights: staff onboarding is broken end-to-end (/users/new delegates to public sign-up, which always dead-ends in AccessDenied — F-J3); notification compose 'all' mode promises sends >100 that the backend always rejects (MAX_BULK=100, F-I37); audit-log export only exports client-loaded rows (F-J2). Full traces: lane-j-flows.md.
Page scorecards (76 routes, all covered)
🔴 = has a P1 finding. Clusters:
| Cluster | Routes | 🔴 | Headline |
|---|---|---|---|
| Ratings | 6 | 5 | no states, dead pagination, unconfirmed delete, false-success |
| Loyalty | 8 | 4 | public campaign query, false-success create/edit, fabricated KPIs |
| Vouchers | 10 | 4 | issued-page false-success ×4, template form false-success |
| Products & groups | 7 | 3 | silent no-op saves, /groups/null navigation, eternal-loading not-found |
| Orders | 3 | 2 | client/backend tier mismatch, unconfirmed force-expire, identical badges |
| Content | 9 | 2 | dup-slug false-success, dead pagination on campaigns listing |
| Users | 5 | 0 | model citizen (one 🟡: unchecked edit-form results) |
| Auth | 3 | 0 | sign-up dead-end + orphan access-denied page (🟡) |
| Raya | 7 | 1 | destructive code reset false-success |
| Overview, profile, webhooks, audit-log, support, notifications | 14 | 0 | mostly solid; notifications has 6 false-success sites |
Plus 4 trivial routes (two redirects, two "coming soon" placeholders) — inventoried, no findings.
Backend (Convex) gaps
- Zero audit logging in three destructive families: Shopify webhook admin (7 mutations incl. delete-all), the entire ratings/moderation surface, gamificationLoyalty deletes incl. the super_admin hard-delete cascade (F-D1/2/3/4).
- Two public no-auth queries:
gamificationLoyalty.getCampaignById(leaks reward tiers with exact probabilities — callers verified: 2 dashboard components, no mobile, no HTTP route),duitRaya.getCampaignById(campaign metadata + claim stats) (F-I2-1, F-I43). - Suspended-staff write path:
triggerFullSync/syncSingleProductcheck role but notisProfileAccessActive— banned staff with a live session can still write to the live Shopify store (F-D5). - Pagination is mostly fake:
.take(500–10,000)+ JS slice dominates (silent truncation); Convexpaginatein only 4 files;exportCodestruncates at 10k of 100k (F-D8, F-D9). - Analytics cost: overview/trends collect every order + every profile twice per range while
orderTotals/usersByRoleaggregates have ~zero production reads (F-D6). - Response-shape variance (root cause of theme 1): 5 envelopes, 8 payload keys, 3 pagination styles across dashboard-facing functions (census in
lane-d-convex.md). - Bonus: dead N+1 in
dashboardProductssearch (F-D14), unboundedbulkGenerateVouchers(F-D13),markAllAsReadmega-transaction (F-D11), HTTP export counts cancelled/refunded as revenue (F-D7).
UI / shadcn debt
components.jsonis stale (wrong css path, old aliases) — blocks all CLI refresh; fix first (F-E10).button.tsx/card.tsxare pre-data-slotvintage;dialog.tsx/table.tsxare locally customized — never blind-refresh (F-E1/E2/E12).- 3
ui/files import app components and Convex hooks:image-upload-field,video-upload-field,r2-image(F-E5);channel-multi-selecthard-locks voucher domain config into the ui layer (F-E6). tiptap-editorrelies onproseclasses but the typography plugin isn't installed — editor content renders unstyled (F-E3).- No
--success/--warning/--infotokens; ~650 raw palette utilities escape the.theme-*runtime system — worst:shake-to-win-modal(55),stamp-collection-grid(46),stamp-collection-card(27); 108 redundantdark:variants (F-E11). - Consolidation: 5 hand-rolled delete dialogs vs an existing preset; 3 table archetypes; 9 date-entry implementations with two conflicting value contracts (F-E7/8/9).
Performance top items
Serial 3–4 round-trip auth chain before data (F-G1/G2) · overview full remount on every toggle (F-G5, live-confirmed) · orders double-subscription for tab counts (F-G3) · un-memoized transforms re-mapping up to 10k docs per render (F-G4) · closed media-picker holds a live 500-file subscription on every image-bearing form (F-G6) · header bell subscribes 20 docs on all routes (F-G7) · recharts statically imported on 5+ routes (F-G8) · fetch-all tables (F-G9) · media grid renders full-size R2 originals with exactly one loading="lazy" in the app (F-G10).
Next.js 16 items
No error.tsx at /dashboard — ~56 pages unwind to global-error (F-C1) · Dockerfile expects output:'standalone', never set (F-C2) · broken title-template chain → bare/duplicated titles (F-C3, live-confirmed) · CSP ships unsafe-eval/unsafe-inline despite "strict" comment (F-C4) · auth POST proxy: no upstream timeout, unbounded body, error-message leak (F-C5) · Sentry PII + 100% traces + replays on an admin tool (F-C7). Sessions live in localStorage, so a proxy-level session gate is impossible without an auth-model change — stated tradeoff, backend enforces (F-C8).
Docs drift (the audit also reconciled the docs)
TECH_DEBT.md: 3 of 4 "Pending" actives are already resolved; review 10 weeks overdue (F-H10) · README pins stale · BACKEND_API_CHECKLIST.md contradicts root CLAUDE.md on _generated handling (F-H11) · dashboard CLAUDE.md's feature-organization pattern is dead letter — the "deviant" vouchers/content layouts are the better structure; rewrite the doc (F-A9) · access-control.ts header describes a phantom MANAGER role.