From 7675a268896c98c125cc6bbb60271c8fd019ae7f Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 12 May 2026 23:45:21 +0200 Subject: [PATCH] docs(backlog): grand audit cleanup plan in 8 prioritized waves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes out the dep-upgrade session by laying out the path from "deps done" → "audit-clean codebase." Maps the 534 findings in AUDIT-2026-05-12.md to concrete waves with file pointers, effort estimates, and acceptance criteria. Wave 1 — Stop-ship CRITICALs: db:migrate runner, EMAIL_REDIRECT_TO prod guard, orphan-blob fix, escape URLs in templates, replace window.confirm calls, GDPR export completeness, right-to-be-forgotten true erase, FK + onDelete on permission_overrides, resolve-identifier hardening. Wave 2 — HIGH security/observability: PII masking in audit_logs, webhook→error pipeline, admin email template subject editor wire-up, PII redaction in error pipeline, notification email worker XSS. Wave 3 — React Compiler set-state-in-effect cleanup (~41 sites). Two migration patterns from this session as templates. Wave 4 — UI/UX consistency + a11y. Wave 5 — Concurrency + Postgres FTS perf. Wave 6 — Email + Documenso depth. Wave 7 — Reporting + recommender quality. Wave 8 — Long tail (PDF, copy, onboarding, types, build). Also closes out major-version deferrals: Next 15→16 + Tailwind 3→4 now DONE; eslint 9→10 documented as upstream-blocked. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/BACKLOG.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index ef86231b..21f073a0 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -191,11 +191,93 @@ Decisions / parked: Major deferrals from §34 of audit: -- **Next 15 → 16** — codemod handles it; wait 2-4 weeks for field bugs to surface (touches `middleware.ts` → `proxy.ts` + custom server). -- **Tailwind 3 → 4** — focused-afternoon project; visual regression risk across every screen. -- **eslint 9 → 10** — locked to next 16. +- ~**Next 15 → 16**~ — **DONE 2026-05-12**. middleware.ts → proxy.ts via codemod, native flat eslint config, react-hooks v7 Compiler safety rules surfaced + triaged. +- ~**Tailwind 3 → 4**~ — **DONE 2026-05-12**. Official upgrade tool migrated 80 files; tailwind-animate → tw-animate-css; theme moved to @theme directive in globals.css. +- **eslint 9 → 10** — attempted, reverted: `eslint-config-next@16` still has a transitive on `eslint-plugin-react@7` that uses removed eslint-9 context API. Re-attempt when upstream lands eslint-plugin-react@8. - **archiver 7 → 8** — no `@types/archiver@8` published; skip indefinitely. +--- + +## H. Grand audit cleanup plan (post-deps) + +**Source:** [`docs/AUDIT-2026-05-12.md`](./AUDIT-2026-05-12.md) — 534 findings across 27 domain reports + [`docs/AUDIT-FOLLOWUPS.md`](./AUDIT-FOLLOWUPS.md) + [`docs/AUDIT-TRIAGE.md`](./AUDIT-TRIAGE.md). + +Deps work is complete (sections A-G above). Remaining audit cleanup is grouped into focused waves so it's tackleable a chunk at a time. Each wave has clear scope, file pointers, and acceptance criteria. + +### Wave 1 — Stop-ship CRITICALs (security + data integrity) + +Roughly half-day each; ship in priority order. These are the items from the audit's `## Cross-cutting priority queue` marked `[C]`: + +1. **Real `db:migrate` runner** — `0052_audit_critical_fixes.sql` uses `CREATE INDEX CONCURRENTLY` which silently never runs under `db:push`. Six composite indexes missing in prod. Build a tsx runner that reads migrations in order, splits on `--> statement-breakpoint`, executes outside a tx, tracks state in `__drizzle_migrations`. ~3-4 h. **(data-model C1)** +2. **`EMAIL_REDIRECT_TO` production guard** — `src/lib/env.ts` should refine to reject when `NODE_ENV === 'production'`; `src/lib/email/index.ts` should `logger.warn` at boot. 5-min change, prevents a very-bad-day class of incident. **(email C1)** +3. **Orphan-blob fix in `handleDocumentCompleted`** — `src/lib/services/documents.service.ts:1100-1253`. Wrap `storage.put + files.insert + documents.update` in a transaction (or saga with compensating delete). Current catch-block leaves blob in storage AND marks `status='completed'` with no `signedFileId`. ~2 h. **(services C2)** +4. **Escape URLs in email templates** — every template in `src/lib/email/templates/*` inlines `${data.link}` etc. into `href="…"` and link text without escaping. Add `escapeUrl` helper + http(s) scheme allow-list; route every template through it. ~3 h. **(email C2)** +5. **Replace 16 native `window.confirm()` calls** — destructive flows bypassing `ConfirmationDialog` / `AlertDialog`. ui-ux-auditor's C1 lists the sites (cancel signing, delete files, archive interest/company/yacht…). ~30 min per site = full day. **(ui/ux C1)** +6. **GDPR Article-15 export completeness** — `src/lib/services/gdpr-bundle-builder.ts` is missing: portal_users, email_threads/messages, document_sends, reminders, files, scratchpadNotes, client_merge_log, contact_log, website_submissions, form_submissions. Regulator-finding-level gap. ~half-day. **(gdpr C1)** +7. **Right-to-be-forgotten actually erase** — `src/lib/services/client-hard-delete.service.ts` nullifies FKs but leaves verbatim PII in `email_messages.body_html`, `files`, `document_sends.recipient_email`. Add true-wipe path. ~half-day. **(gdpr C2)** +8. **`user_permission_overrides.user_id` FK + `onDelete='set null'`** — data-model H1+H2. Single migration. ~30 min. **(data-model H1+H2)** +9. **Resolve-identifier endpoint replacement** — current rate-limited hit still echoes the real canonical email on a successful username hit. Replace with a server-side signIn proxy that takes `{identifier, password}` together and never returns canonical emails at all. ~2 h. **(security/gdpr crossover)** + +### Wave 2 — HIGH-priority security + observability (5-7 days) + +10. **`audit_logs.metadata` PII masking** — extend `maskSensitiveFields` to cover `audit_logs.metadata`; add 90-day retention cron mirroring `error_events`. ~2 h. **(gdpr H)** +11. **Webhook → error pipeline** — `src/app/api/webhooks/documenso/route.ts` bypasses `captureErrorEvent` on handler crash. Apply to every webhook route. ~2 h. **(observability H)** +12. **Admin email-template subject editor** — 5 of 8 templates ignore `overrides.subject`; admins see "Saved" with zero effect. Wire all 8. ~2 h. **(email H1+H2)** +13. **Admin signature/footer fields** — `/admin/email` writes `email_signature_html` + `email_footer_html` which the email shell never reads. Either delete the UI or wire it. ~half-day. **(email H3)** +14. **PII redaction in error pipeline** — `error_events.request_body_excerpt` sanitizer redacts password/token but not email/phone/name/dob/address. ~2 h. **(observability H + gdpr)** +15. **Notification email worker XSS** — `src/lib/queue/workers/notifications.ts:65-71` interpolates `notif.description` and `notif.link` into HTML unescaped. Apply `escapeHtml` + URL allow-list (the `isomorphic-dompurify` we shipped helps here). ~1 h. **(email H + security)** + +### Wave 3 — React Compiler set-state-in-effect cleanup (~41 sites) + +Remaining 41 `react-hooks/set-state-in-effect` warnings. Two patterns established this session as templates: + +- **List/load pattern** (`src/components/admin/tags/tag-list.tsx` is the template): `useState([]) + useEffect(fetch+setState)` → `useQuery({ queryKey, queryFn })`. Mutation paths get `useMutation` with `onSuccess: queryClient.invalidateQueries`. ~10 min per site. +- **Dialog open→reset pattern** (`src/components/clients/hard-delete-dialog.tsx` is the template): inner `` mounted only while `open`, so `useState` initializers run naturally on each open without an open→reset useEffect. ~15 min per site. + +Migrate as a focused day's work, then promote `react-hooks/set-state-in-effect` from `warn` to `error` in `eslint.config.mjs` to lock in. + +### Wave 4 — UI/UX consistency + accessibility (~3-4 days) + +- **Raw enum render via `.replace(/_/g, ' ')` (40+ sites)** — extract to `constants.ts` `formatStage`/`formatStatus`/`formatPriority` helpers. **(ui/ux H1)** +- **18 list components missing mobile `cardRender`** — DataTable already supports it; populate per-list. **(ui/ux H2)** +- **Berth status pills using ad-hoc Tailwind colors** — swap to shared `StatusPill`. **(ui/ux M1)** +- **UserList "Active"/"Disabled" badge** — align to `StatusPill` convention. **(ui/ux M2)** +- **Drawer vs Sheet usage drift** — pick one based on a documented rule (`Drawer` for mobile-bottom-sheet, `Sheet` for side panels). Standardize. **(ui/ux M11)** +- **Decorative icons missing `aria-hidden`** — sweep. ~1 h. **(ui/ux M10)** +- **Hard-coded "border-amber-300 bg-amber-50" callouts (15+ sites)** — extract `` component. **(ui/ux L5)** +- **Dashboard route `loading.tsx` coverage** — 5 of 73 routes have it; add for the rest. **(ui/ux M3)** + +### Wave 5 — Performance + reliability (~2-3 days) + +- **Concurrency races** — concurrency-auditor flagged multi-rep edit windows + partial-unique-index insert windows. Add row-level locks (`SELECT … FOR UPDATE`) on the affected paths. **(concurrency C, H)** +- **Postgres FTS for `search.service.ts`** — current `LIKE '%term%'` doesn't rank and slows at scale. Add `tsvector` GIN indexes; rewrite `searchClient/yacht/company/interest` against `to_tsquery()`. ~half-day migration + half-day service refactor. **(audit 36.K.1)** +- **`useEffect → fetch → setState` data-loading** (covered by Wave 3 above). + +### Wave 6 — Email + Documenso depth (~2-3 days) + +- **Documenso integration depth** (documenso-auditor report) — full v1/v2 audit, recipient signing URL handling, redirect URL per-port, sequential signing flag. +- **Email deliverability** (email-auditor report) — subject editor wire-up (Wave 2 #12), signature/footer wire-up (Wave 2 #13), bounce monitoring sanity check, attachment threshold UX. + +### Wave 7 — Reporting + recommender quality (~half-week) + +- **Reporting math correctness** (reporting-auditor) — verify revenue, pipeline funnel, occupancy math against hand-computed truth set. +- **Berth recommender quality** (recommender-auditor) — tier ladder edge cases, heat-score weight calibration. + +### Wave 8 — Long tail (whenever) + +- **PDF + brand asset correctness** (pdf-auditor) — verify each PDF template renders with port branding pulled correctly. +- **Customer-facing copy + terminology** (copy-auditor) — sweep for inconsistent terms ("berth/mooring", "owner/lessee", etc.). +- **Onboarding + first-run UX** (onboarding-auditor) — new-port walkthrough refinement. +- **Type-safety + drizzle leak audit** (types-auditor) — `any` casts, untyped `metadata` fields. +- **Build + deploy + prod readiness** (build-auditor) — Dockerfile review, env var validation. + +### How to use this section + +- Pick a wave; pick an item; read the linked audit section for full context. +- Each item closes with a commit in the `fix(audit-): ...` format so it's trivially greppable. +- Mark items DONE inline in this section as they ship. +- Audit-FOLLOWUPS.md tracks Wave 1-10 from an earlier sweep — items there may already be done or supplanted by AUDIT-2026-05-12. + Future PDF-related work (carry-over from §A of the PDF overhaul spec): - **AcroForm-fill admin-uploaded PDF templates** (~1 week solo): new `pdf_templates` table + admin upload UI + field-mapping editor + generalize `fill-eoi-form.ts` into a reusable `fillAcroForm()` utility. Reinstates the invoice PDF path (and any future customer-facing standardized doc).