Commit Graph

75 Commits

Author SHA1 Message Date
c9debce442 docs(plan): comprehensive 7-phase master plan for post-audit work
Single source of truth for all remaining audit + feature work:
Documenso completion, deal-pulse signals + admin config, EOI overrides,
Reminders, email-copy refactor, IMAP bounce linking, PDF editor.

Each phase carries goal, scope, schema, API/UI surfaces, acceptance
criteria, test plan, effort estimate, and a sub-task tracker that
fresh sessions tick through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:43:12 +02:00
0f99f054b3 feat(post-audit): batch A+B quick-wins + audit-side residuals
Bundles the user-prioritised follow-ups from the post-audit punch-list.

Batch A — pipeline + EOI safety:
 - §1.1 timeline buildAuditDescription renders diff fields ("leadCategory → hot_lead").
 - §4.13 EOI rejection cascade: notification to assigned rep + audit row + rose banner.
 - §4.10b finish doc-detail: SigningProgress reuse, linked-entity names (server-resolved),
   per-event icons + tooltips + show-more in activity panel.
 - §7.2 stage guidance card replaces empty Payments slot pre-reservation.
 - §4.15 deal-pulse trigger audit (docs/deal-pulse-trigger-audit.md).

Batch B — UX consistency + docs:
 - §1.4 quick log-contact button on interest header.
 - §2.1 contact-log compose: Dialog → Sheet.
 - §7.1 docs/deal-pulse explainer page; /docs/ in PUBLIC_PATHS.
 - DocumentStatus now includes 'rejected' + 'declined' across constants, labels, tone maps.

Audit-side residuals:
 - M-NEW-1 /me/ports skips port-context requirement.
 - M-AU03 audit log CSV export endpoint + UI button.
 - M-IN03 dead receipt-scanner.ts deleted; live path already per-port.
 - M-P01 pg_trgm GIN indexes (migration 0071).
 - §10.1 webhook tests verified passing (was stale).

Deferred per user direction:
 - §11.3 email copy refactor (needs old-CRM reference).
 - M-EM03 IMAP bounce-to-interest linking.

Tests: 1374/1374. tsc + lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:22:11 +02:00
4b5f85cb7d fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish
Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing-
progress redesign + env-to-admin migration + dev-mode banner) with the
2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW).

CRITICAL (3):
 - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths
   no longer silently drop interest links
 - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed
 - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage —
   callers must go through /stage with the override-guard chain

HIGH (14/15):
 - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across
   interests/documents/reservations/reminders/invoices (migration 0070)
 - H-02 login page reads ?redirect= param with same-origin guard
 - H-03 CRM invite token moves to URL fragment so it never lands in
   nginx access logs / Referer headers
 - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4)
 - H-05 toggleAccount writes an audit row
 - H-06 upsertSetting masks any value whose key ends with _encrypted
 - H-07 archiveClient cascade fires per-interest audit rows
 - H-08 createSalesTransporter applies SMTP_TIMEOUTS
 - H-09 AppShell stable children — viewport flip across breakpoint no
   longer destroys in-progress form drafts
 - H-10 portal documents page swaps Unicode glyph status icons for
   Lucide CheckCircle2/XCircle/Circle + aria-labels
 - H-12 list components swap alert(...) for toast.warning(...)
 - H-13 5 icon-only buttons gain aria-label
 - H-14 parseBody treats empty bodies as {}
 - H-15 admin layout renders a 403 panel instead of silent bounce
 - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet

MEDIUM (28+):
 - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE
   WHEREs across custom-fields, notes (all 6 entity types x update +
   delete), client-contacts, yacht ownerClient lookup, webhook reads
 - M-D01 documents-hub realtime event-name typo (file:created -> uploaded)
 - M-EM01 portal-auth emails thread through portId
 - M-EM02 sendEmail accepts cc/bcc params
 - M-EM04 notification_digest catalog key
 - M-IN01 portal presigned download URLs use 4h TTL
 - M-IN02 OpenAI client lazy-instantiated
 - M-IN04 stale pdfme refs updated to pdf-lib AcroForm
 - M-IN05 umami.testConnection returns tagged union
 - M-L01 reservations tenure_type unified with berths
 - M-L02 report-generators canonicalize stage values
 - M-AU01 audit log placeholder copy fixed
 - M-AU04 outcome_set / outcome_cleared distinct audit verbs
 - M-NEW-2 activity feed entity name+type separator
 - M-R01 portal allowlist narrowed + portal_session backstop in proxy
 - M-SC02 companies archived partial index
 - M-SC04 audit_logs.searchText documented as DB-managed
 - M-S01 storage_s3_access_key_encrypted admin field
 - M-U01 audit log empty state uses <EmptyState>
 - M-U09 invoice delete dialog -> <AlertDialog>
 - M-U10 toast.success on ClientForm + InterestForm create/edit
 - M-U11 settings-form-card logo preview alt text
 - M-U14 mobile topbar title on clients/yachts/interests/berths
 - M-U15 Invoices in mobile More-sheet

LOW (6/8):
 - L-AU01 severity defaults for security-relevant verbs
 - L-AU02 +13 missing actions in admin audit filter
 - L-AU03 +7 missing entity types in admin audit filter
 - L-AU04 dead listAuditLogs stubbed
 - L-D02 CLAUDE.md Owner-wins chain tightened

Bonus — Document detail polish (#67 partial, 3/6 deliverables):
 - state-aware action button per signer
 - watcher Add UI with display-name resolution
 - cleanSignerName cleanup

Prior session work bundled in:
 - Documenso v2 webhook + envelope-ID normalization + sequential signing
 - SigningProgress UI redesign (avatars, per-signer state, timestamps)
 - env->admin settings registry + RegistryDrivenForm + encrypted creds
 - Embedded-signing card + Test connection + setup help
 - Dev-mode EMAIL_REDIRECT_TO banner
 - Pipeline rules admin page
 - Sales email config card
 - Audit log details Sheet
 - EOI tab: Finalising badge, absolute timestamps, sequential indicator
 - Notes pipeline_stage_at_creation (migration 0069)
 - Documenso numeric ID dual-key webhook (migration 0068)
 - Dimensions criterion copy (migration 0067)

Tests: 1374/1374 vitest pass. tsc clean. lint clean.

See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and
the user-input items still pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
397dbd1490 docs(spec): env-to-admin migration design
Design spec for moving tenant-configurable env vars into the per-port
admin UI via a settings registry. Covers scope decisions, registry
shape, resolver, encryption, admin UI generation, env catalog by
disposition, migration plan, and testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:22:39 +02:00
d15f5509ad docs(audit): progress report for the 2026-05-15 fix wave
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m6s
Build & Push Docker Images / build-and-push (push) Successful in 1m13s
11 of 13 known issues (A1-A20) fixed and verified; legacy-stage rank
tables in clients.service.ts + berth-recommender.service.ts purged of
9-stage enum keys. 1373/1373 vitest pass.

Remaining catalog (300+ checks) listed by section so it's clear what's
covered vs. still on the to-do list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:22:14 +02:00
3b3ac287e0 docs(audit): comprehensive 320+ check catalog organized by area
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m6s
Build & Push Docker Images / build-and-push (push) Successful in 22s
Companion to the 2026-05-15 sweep findings. Catalogues every audit-worthy
surface across 19 areas:

  0. Already-known issues (A1-A20 cross-reference)
  1. Legacy stage enum bleed (the deposit_10pct class) — 20 checks
  2. Routes / page reachability — 30 checks
  3. UX consistency (forms, lists, tables, badges, modals, mobile) — 100 checks
  4. Sales workflows happy + edge cases — 52 checks
  5. Admin workflows — 60 checks
  6. Multi-tenancy port isolation — 11 checks
  7. Security — 30 checks
  8. Realtime / sockets — 9 checks
  9. Performance — 14 checks
  10. Documents / files — 22 checks
  11. Audit log surface — 14 checks
  12. Email / SMTP / IMAP — 19 checks
  13. Integrations (Documenso, NocoDB, S3, AI, BullMQ) — 29 checks
  14. Schema / migration — 15 checks
  15. i18n / l10n — 8 checks
  16. Browser / device — 7 checks
  17. Specific behavioral correctness (legacy stage drift, A1 hard-delete fallout, etc) — 22 checks
  18. Data clean-up jobs — 5 checks
  19. CI / dev experience — 13 checks

Each check tagged with effort (XS/S/M/L), severity (🔴/🟠/🟡/🟢), and
current coverage (/⚠️//). Recommended priority tiering at the bottom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:54:08 +02:00
ff5e71092e docs(audit): 2026-05-15 comprehensive Playwright sweep findings
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m7s
Build & Push Docker Images / build-and-push (push) Successful in 22s
Covers super-admin, sales-rep, viewer, portal, catch-up wizard, and the
single-tree responsive shell. 13 findings catalogued with reproduction +
effort estimates, plus a positive-findings section confirming what
shipped is working end-to-end:
  - F22/F23/F25/F44 verified live
  - #67 catch-up wizard runs full transaction (client+interest+clear-override)
  - #26 single-tree shell verified at 390px and 1440px viewports
  - permission gating holds for sales-agent and viewer

Critical issues found:
  - A4 New Client form silently rejects submit when an empty contact row is present (F19 filter runs in mutationFn, too late)
  - A16 file upload at documents-hub root fails: client sends nulls, validator wants strings or absent
  - A17 /api/v1/admin/ports is super-admin-only but apiFetch uses it to bootstrap port-slug→port-id resolution for every user

See docs/audit-2026-05-15.md for the full list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:44:51 +02:00
85bd0d82e1 docs: capture post-audit fix plan from two-round Playwright sweep
24 fixes + 1 new feature, tiered by priority. T0 already shipped in the
previous commit; T1-T4 batches sequenced with effort estimates and file
pointers. Includes the manual-berth-status catch-up workflow design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:38:02 +02:00
a8607ecc9e docs(plan): close Step 9 — recommender simulator deferred
Some checks failed
Build & Push Docker Images / lint (push) Failing after 36s
Build & Push Docker Images / build-and-push (push) Has been skipped
NocoDB inspection (via MCP) confirms the legacy Interests table carries
only the current Sales Process Level value plus point-in-time event
timestamps as text fields — no dedicated stage-change history table.
That isn't enough resolution to replay stages-over-time through the
recommender's tier-ladder + heat-score weights. Simulator deferred
until ~10+ real wins accumulate under the new pipeline, then we can
simulate against actual CRM history.

The existing /admin/berth-recommender heat-weight tuning UI is
sufficient for v1 launch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 15:55:17 +02:00
f86f511e7b docs(plan): lock pre-deploy plan from 2026-05-14 planning session
Single source of truth for everything between today and initial VPS
deploy. Captures every decision reached across the 2026-05-14 rounds:

- Hot-path correctness: canonical active-interest definition, currency-
  aware pipeline value, occupancy=sold, two-card revenue PDF, multi-
  berth EOI mooring rendering via existing Berth Number form field.
- Security gate: portal activation/reset URLs switch to URL fragment.
- Email refactor: drop signature field, per-category send-from routing,
  per-port IMAP bounce monitoring, compose-UI attachment-threshold
  banner.
- Schema: berths.archived_at, clients.metadata.source_inquiry_id,
  email_bounces table.
- UX: externally-signed mark, contract paper-upload endpoint, inquiry
  P-4.5 linkage, quick brochure/PDF download, per-user digest schedule,
  documents-tab N+1 batch fix.
- Bulk berth wizard for new-port setup.
- Four investor charts as toggleable dashboard widgets.
- Mechanical "deal" -> "interest" sweep incl. route rename.

Implementation order + deferred items + operator deploy checklist all
captured. Future agents resuming this work start here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:49:13 +02:00
c44d818144 docs(backlog): mark set-state-in-effect migration as DONE
Wave 3 of the 2026-05-12 audit cleared all ~45 useEffect→fetch→
setState sites; eslint.config.mjs promoted the rule to error in the
same sweep. BACKLOG's "next pass" entry was stale from before that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:51:30 +02:00
bd432fc6c7 docs(backlog): document the deferred-refactor list with rationale
Five engineering refactors and six mechanical service splits the
AUDIT-2026-05-12 dossiers flagged. Assessed against today's reality
(no active webhook subscribers, small DB, low-frequency storage
paths) and explicitly deferred. Listed here so future-me doesn't
re-research them when triaging the audit.

Each entry carries its cost estimate and the trigger condition that
should bring it back onto the roadmap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:18:58 +02:00
4d1fbcd469 feat(documenso-phase-5): pin transformSigningUrl + document website-side coordination
Phase 5 is mostly coordination + verification rather than a code
build — the embedded signing pages live in a different repo. What
lands here:

1. transformSigningUrl hardening — routes through extractSigningToken
   so a bare URL like `https://sig.example.com` no longer produces
   the malformed `<host>/sign/<role>/sig.example.com`. The token
   validator (≥8 URL-safe chars) rejects malformed tails so the
   function falls back to returning the raw URL.

2. 10 unit tests pin the role-segment mapping so a future refactor
   can't silently break the contract with the marketing website's
   /sign/[type]/[token] page. Covers:
     - all five SignerRole → URL segment mappings
     - trailing-slash normalization on the host
     - null host fallback (single-tenant / staging)
     - rejection of non-token-shaped tails

3. docs/documenso-integration-audit.md updated with:
     - Phase 2/3/4/7 landed-work summary (replacing the old
       "deferred" list that was now stale)
     - Phase 5 coordination tracker for the marketing-website side
       (the four edits the website team needs to make — listed
       here so the CRM stays the source of truth on the contract)
     - Phase 6 polish backlog (auto-send delay, document expiration,
       per-document message, reminder display, failed-webhook UI,
       field metadata panel, zoom controls, recipient drag-reorder)

Tests: 21 new transformSigningUrl + signers tests across two files;
full suite 1340 → 1350 ; tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:11:50 +02:00
ebdd8408bf fix(audit-wave-11): dossier sweep — error-ux + webhook + storage + search + maintainability
Final pass over the unaddressed AUDIT-2026-05-12 dossiers, taking the
tractable Critical/High items from each:

error-ux-auditor (5 items)
- C2: 17 toast.error(err.message) sites swept to toastError(err, …) so
  every user-visible failure carries a copy-paste Reference ID
- C3: apiFetch synthesizes a client-side correlation id when a 5xx
  comes back with a non-JSON body (reverse-proxy HTML pages); message
  becomes "The server is unreachable. Please try again." with code
  UPSTREAM_UNREACHABLE
- C4: checkRateLimit fails OPEN when Redis is unavailable so an outage
  no longer 500s login + portal sign-in; logged at warn so monitoring
  catches it
- H2: StorageTimeoutError (name='TimeoutError') replaces the plain
  Error throw in s3.ts withTimeout — error-classifier hints fire now
- H5: errorResponse() adopted across /api/storage/[token],
  /api/public/website-inquiries, and the Documenso webhook body (drops
  the "Invalid secret" reconnaissance string)

outbound-webhook-auditor (5 items)
- C1: signature is now HMAC(secret, `${ts}.${body}`) with the
  timestamp surfaced as X-Webhook-Timestamp so receivers can reject
  replays outside a freshness window
- C3: dead-letter with reason missing_signing_secret when secret is
  null (defence-in-depth against DB tampering / future migration
  mistakes)
- H2: webhooks queue bumped to maxAttempts=8 with 30 s base
  exponential backoff so a 30 s receiver blip during a deploy no
  longer dead-letters every in-flight event; per-queue
  backoffDelayMs added to QUEUE_CONFIGS
- M1: SSRF denylist gains Oracle Cloud metadata 192.0.0.192
- M2: dispatch-time https:// assertion before fetch, so a bad DB edit
  can't slip plaintext through

storage-pathing-auditor (2 items)
- H1: berth-PDF presigned-upload keys now `${portSlug}/berths/…/…`
  with portSlug threaded into backend.presignUpload — engages the
  filesystem-proxy port-binding `p` token verifier
- H2: presignDownloadUrl auto-derives portSlug from the key's first
  segment when callers don't pass it, so all 8 download sites engage
  the `p`-token guard without per-site plumbing

search-auditor (1 item)
- H3: removed dead void wantEmail; void wantPhone; pair plus the
  unused looksLikeEmail helper — the bucket-reorder it was scaffolded
  for was never wired

maintainability-auditor (1 item)
- M2: swept seven abandoned `void <symbol>` markers and their dead
  imports across clients/bulk, interests/bulk, admin/email-templates,
  admin/website-submissions, alert-rules, and notes.service

Deferred to future work (substantial refactors, schema migrations, or
multi-file UI work):
- error-ux M3-M8 (global-error.tsx, per-route loading.tsx coverage,
  ErrorBanner component, /api/ready route, worker DLQ admin surface)
- maintainability C1-C4 (documents/search/notes service splits,
  interest-tabs split — multi-hour refactors)
- currency C1-H5 (mixed-currency dashboard aggregation, FX history
  table, rounding policy) — wait for second non-USD port
- outbound-webhook C2 (deliveries reaper job), H1 (DNS-rebind TOCTOU
  with undici Agent), H3 (circuit-breaker), H5 (presigned-post-policy)
- storage-pathing C2 (orphan reaper), H3-H5 (streaming + content-type
  binding)

Tests: 1315/1315 vitest  ; tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:27:32 +02:00
bc54ea2c3e docs(backlog): mark Wave 10 items DONE; final grand-audit status
Wave 10 closed four more audit dossiers via:
- 10.1 types-auditor (Tx type, BerthDetailData, parseBody, toAuditJson)
- 10.2 build-auditor (server externals, healthcheck, NEXT_PUBLIC_APP_URL)
- 10.3 concurrency-auditor (handleDocumentCompleted TOCTOU, moveFolder
  cycle race, upsertInterestBerth + username 23505 mapping)
- 10.4 aria-hidden mechanical sweep across 267 files

Grand-audit status: every dossier from AUDIT-2026-05-12.md (27 reports)
has either its CRITICAL+HIGH items shipped or is explicitly back-
burnered for the four user-deferred reasons (Documenso phases 2-7,
bounce monitor, manual QA, BullMQ jobId plumbing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:38:31 +02:00
b397f6049d docs(backlog): mark Wave 9 items DONE in master backlog
Wave 9 closed eight focused commits across the ui/ux, pdf, copy, and
onboarding audit findings:
- 9.1 Drawer vs Sheet doctrine
- 9.2 StatusPill adoption
- 9.3 Custom-fields token picker
- 9.4 Mobile cardRender for admin lists
- 9.5 Dashboard loading.tsx coverage
- 9.6 PDF + brand asset correctness
- 9.7 Copy/terminology sweep
- 9.8 Onboarding + first-run UX

Remaining audit work is now: aria-hidden sweep (#69), broader
concurrency lock audit (#72), bounce monitor + Documenso v2 templates
(#75), manual QA of reporting/recommender (#77), types-auditor +
build-auditor (both no-active-trigger).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:16:34 +02:00
153f6ac797 fix(audit-wave-9): unified template token picker with custom-field group
Build a shared <TemplateTokenPicker> that renders the canonical
MERGE_FIELDS catalog grouped by scope, plus a dynamically-fetched
"Custom (port-specific)" group surfaced from /api/v1/admin/custom-fields.
The custom group is filtered to entity types the resolver actually
expands at send time (client/interest/berth - see
mergeCustomFieldValues in document-sends.service).

Wire it into both consumers:
- admin/document-templates/template-form.tsx (replaces TEMPLATE_VARIABLES
  list which had drifted from the canonical catalog)
- admin/sales-email-config-card.tsx (replaces flat alphabetical dump)

Closes custom-fields §B "UI surfacing of {{custom.…}} tokens".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:57:37 +02:00
4233aa3ac3 fix(audit-wave-9): standardize on Sheet for previews; doctrine in CLAUDE.md
Swap the one outlier (client-interests-tab.tsx) from Vaul Drawer to
Sheet side=right so every detail-preview surface uses the same
primitive. Document the doctrine: Sheet for side panels on both desktop
and mobile; Vaul Drawer reserved for mobile-only bottom-sheet UX
(currently just MoreSheet).

Closes ui/ux M11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:50:07 +02:00
7675a26889 docs(backlog): grand audit cleanup plan in 8 prioritized waves
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) <noreply@anthropic.com>
2026-05-12 23:45:21 +02:00
4329db7fc3 fix(compiler): React Compiler safety triage — 5 categories cleared
Cleared 4 rule buckets (37 violations, including 5 real bugs) and
silenced 1 informational bucket from the Next 16 / react-hooks v7
upgrade. Cleared rules promoted from `warn` back to `error` so new
regressions block CI.

Real bug fixes:
- `interest-contact-log-tab.tsx`: `useMemo` used for side effects
  (5 setState calls inside a memo body); converted to `useEffect`.
- `PieChart.tsx`: cumulative `let angle` mutation in a render-phase
  `map`; converted to `reduce` so the slice array is built without
  re-assignment.
- `documents-hub.tsx`: `useMemo(() => ({ count: 0 }))` used as a
  mutable drag counter; converted to `useRef`.
- `notes-list.tsx`: `Date.now()` read during render for note-edit
  countdown (impure) → pinned to a `now` state ticked every 30s.
- `onboarding-checklist.tsx` / `user-profile.tsx` /
  `user-settings.tsx`: `useEffect(() => void load(), [])` with the
  `load` function declared AFTER the effect — relied on hoisting,
  trips Compiler's "access before declared" rule. Declared inside
  the effect.

Pattern fixes (intentional cache-via-ref → state or layout-effect):
- 6 `ref.current = x` writes during render moved into layout
  effects (`use-realtime-invalidation`, `settings-form-card`,
  `inbox`).
- 3 `ref.current` reads during render (search totals cache,
  scanner file ref) rewritten to backed-by-state.
- `use-is-mobile.ts` rewritten on `useSyncExternalStore` to avoid
  the SSR-then-rehydrate setState dance.
- `use-notifications.ts` rewritten to write socket pushes directly
  into the React Query cache via `setQueryData`, removing a local
  state mirror.

Rule config (`eslint.config.mjs`):
- `react-hooks/purity` → error (was warn, cleared)
- `react-hooks/set-state-in-render` → error (was warn, cleared)
- `react-hooks/immutability` → error (was warn, cleared)
- `react-hooks/refs` → error (was warn, cleared)
- `react-hooks/incompatible-library` → off (informational only)
- `react-hooks/set-state-in-effect` → warn (51 remaining, all the
  useEffect→fetch→setState data-fetch pattern; migration to
  useQuery tracked in BACKLOG)

Verified: tsc clean, eslint 0 errors / 69 warnings (down from 105),
vitest 1315/1315, next build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:14:16 +02:00
92975e6bf5 feat(deps): @sentry/nextjs error tracking (DSN-gated, dormant by default)
Wires the Sentry SDK shipped-but-dormant: no-op unless
`NEXT_PUBLIC_SENTRY_DSN` is set in the environment. Production opts
in via the deploy env; dev + CI stay quiet.

- `sentry.client.config.ts` / `sentry.server.config.ts` /
  `sentry.edge.config.ts` — runtime init, each guards on the DSN.
- `instrumentation.ts` — Next 13.4+ instrumentation hook that lazy-
  imports the server + edge configs when the DSN is present.
- `next.config.ts` — withSentryConfig only wraps the config when
  the DSN is set, so dev builds skip source-map upload + middleware
  injection.
- `src/lib/env.ts` — added optional NEXT_PUBLIC_SENTRY_DSN +
  SENTRY_ENVIRONMENT + SENTRY_TRACES_SAMPLE_RATE (defaults to 0.1).

Env vars to add to .env.example (blocked from this commit by the
.env hook — apply manually):

    # Sentry (optional — SDK is a no-op without a DSN)
    NEXT_PUBLIC_SENTRY_DSN=
    SENTRY_ENVIRONMENT=
    # Defaults to 0.1 (10%) when unset
    SENTRY_TRACES_SAMPLE_RATE=

Replay is opt-in only — disabled by default for now; we'd need to
audit privacy implications (PII redaction, GDPR) before enabling it.

Verified: tsc clean, vitest 1315/1315, next build green with DSN
unset (Sentry plumbing intact, runtime no-op).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:38:18 +02:00
4879b17cff feat(deps): Next 15 → 16 (proxy.ts rename + native flat ESLint config)
Applied @next/codemod migrations:
- middleware-to-proxy: src/middleware.ts → src/proxy.ts + function rename
- remove-experimental-ppr: no hits
- remove-unstable-prefix: no hits

tsconfig.json picked up Next 16's autofixes:
- jsx: 'preserve' → 'react-jsx'
- include .next/dev/types/**/*.ts (dev-mode route types)
- next-env.d.ts: triple-slash reference → ES import (TS 6 / Next 16 style)

eslint-config-next@16 ships a native flat config, so dropped the
@eslint/eslintrc + FlatCompat shim. eslint.config.mjs now imports
eslint-config-next/core-web-vitals + eslint-config-prettier/flat
directly.

Note on ESLint 10: bumped + reverted. eslint-config-next@16 still
has a transitive eslint-plugin-react@7 that uses the eslint-9
context API (getFilename on context); breaks under eslint 10.
Audit anticipated lockstep — but the transitive isn't ready yet.
Holding at eslint 9.x until upstream lands. Tracked in BACKLOG.

React Compiler safety rules (react-hooks v7) shipped with config-
next 16 surfaced ~89 legitimate findings (set-state-in-effect,
ref-during-render, immutability). Demoted the new rules to `warn`
so the codebase isn't blocked; triage tracked in BACKLOG §G.

Verified: tsc 0 errors, eslint 0 errors / 105 warnings (89 new
Compiler-rule warns + 16 pre-existing), next build clean, custom
server build clean, vitest 1315/1315.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:24:51 +02:00
3147923d91 docs(backlog): close out dep adoption — reject upstash/faker/msw with rationale
Three audit-flagged deps rejected on inspection (not parked-pending-
decision):

- @upstash/ratelimit — audit said "4 hand-rolled rate limiters"; actual
  state is one centralized sliding-window limiter with 14 named policies.
- @faker-js/faker — both seed files are hand-curated specs keyed to test
  selectors, not random fake data; faker would mean ADDING a factory.
- msw — vi.mock at the service-module boundary already gives determinism;
  msw only helps when tests hit fetch() directly.

Adds tsc-staged.mjs to the done list. Updates parked list with concrete
rationale per item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:02:13 +02:00
7cc80512da docs(backlog): session wrap — full dependency/refactor roadmap shipped
Closes the 2026-05-12 push through the audit roadmap. Every item from
docs/AUDIT-2026-05-12.md §§34-36 is either shipped, deferred with
rationale, or parked behind a concrete UX/product trigger.

Wins this session (in commit order from 73184c5 onward):
  1. PDF stack overhaul (9 commits + design spec)
  2. react-email migration for all 7 remaining templates
  3. browser-image-compression in scan-shell
  4. @axe-core/playwright smoke a11y gate
  5. ts-pattern + bug-fix in search.service.ts
  6. p-limit on 3 mass-op fan-outs
  7. formatDate helper + 17 unit tests + sample sweep
  8. opt-in react-virtual in DataTable

Also nudges:
  - src/lib/pdf/brand-kit/Header.tsx — eslint-disable on react-pdf
    <Image> for a false-positive jsx-a11y/alt-text warning (PDFs
    don't follow the HTML img alt contract).
  - docs/BACKLOG.md §G — rewritten to reflect what's done + the
    remaining opportunistic work (mostly "migrate as you touch the
    file" callsite sweeps).

Comprehensive audit passing:
  - tsc --noEmit: 0 errors
  - vitest: 1315/1315 passing
  - eslint src/: 0 errors, 16 pre-existing warnings (none new)
  - next build: all routes compile, no broken imports
  - playwright --list: 162 tests across 33 files (incl. the new
    a11y spec)

Branch is shippable; remaining items are opportunistic callsite
sweeps the team can pick up when each file is otherwise being
touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:42:51 +02:00
63220ad072 docs(backlog): update with PDF/react-email/scan-compress/a11y wins + remainder
Adds a new §G (dependencies / audit roadmap) documenting what landed
in the 2026-05-12 session (PDF stack overhaul, react-email migration,
browser-image-compression, axe-core) and what's left in roughly
decreasing impact-per-hour order. Each remaining item gets an estimate,
a "pattern proven?" note, and a one-line action plan so a future
session can resume without re-reading the entire audit doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:25:03 +02:00
81a98c6695 docs(superpowers): pdf stack overhaul design (react-pdf + unpdf)
Lays out the plan to replace pdfme with @react-pdf/renderer, add unpdf
for berth-PDF tier-2 rasterization, and add port-level logo upload
(sharp normalization + react-image-crop UI + svgo sanitization +
rasterize-SVG-to-PNG-on-upload).

Scope locked to internal-only PDFs (reports, expenses, record exports).
Invoice + admin TipTap-to-PDF removed entirely; in-app EOI pathway
(pdf-lib AcroForm fill) stays untouched.

14 commits planned. Single source of truth for tokens. Three orthogonal
PDF paths post-migration with no overlap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:36:54 +02:00
d3960af340 feat: warm-up deps — ts-reset, web-vitals, RHF devtool, query-broadcast
Four low-risk adds before the Zod 4 / drizzle-zod headliner:

- @total-typescript/ts-reset: tightens TS stdlib types globally (JSON.parse
  → unknown, fetch().json() → unknown, .filter(Boolean) narrows, Set
  literals respect typed Set targets). Caught 179 latent type errors;
  fixed all production sites (8 files) and added `any` cast escape hatch
  in test files (ESLint exemption scoped to tests/).
- web-vitals + /api/v1/internal/vitals endpoint + WebVitalsReporter
  client component: establishes Core Web Vitals baseline (LCP/INP/CLS/
  FCP/TTFB) via navigator.sendBeacon. Required before optimisation work.
- @hookform/devtools + FormDevtool wrapper: dev-only RHF state inspector,
  lazy-loaded via next/dynamic so the chunk is excluded from prod
  bundles entirely.
- @tanstack/query-broadcast-client-experimental: cross-tab cache sync
  via BroadcastChannel — wired in query-provider.tsx, 1-liner.

Audit doc updated with sections 35 + 36 (PDF stack overhaul + comprehensive
second-pass package sweep) covering ~20 package adoption candidates and
4-5 deprecation candidates.

Verified: tsc clean, vitest 1293/1293 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:16:18 +02:00
82049eea92 deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28
Successfully bumped:
- bullmq 5.76.6 → 5.76.8
- @tanstack/react-query 5.100.9 → 5.100.10
- @tanstack/react-query-devtools 5.100.9 → 5.100.10
- better-auth 1.6.9 → 1.6.10
- @playwright/test 1.59.1 → 1.60.0
- libphonenumber-js 1.12.43 → 1.13.1
- tailwind-merge 3.5.0 → 3.6.0
- vitest 4.1.5 → 4.1.6
- @vitest/coverage-v8 4.1.5 → 4.1.6
- lint-staged 17.0.3 → 17.0.4
- esbuild 0.27.7 → 0.28.0
- react-grab 0.1.33 → 0.1.34
- react-day-picker 9.14.0 → 10.0.0

react-day-picker 10 verified safe: probed v10 release notes against
src/components/ui/calendar.tsx — we use only v9-canonical APIs that
v10 preserves. Removed the `table` className entry from the wrapper
(v10 dropped it since the renderer is now CSS-grid, not table-based).

Tried + rolled back:
- @hookform/resolvers 3 → 5: stricter input/output inference broke
  every form using <{schema}, any, {schema}> implicit shape. Needs
  per-form refactor; parked.

Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities;
pnpm exec tsc --noEmit clean; vitest 1293/1293 pass.

Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34):
- next/eslint-config-next 15 → 16 (2-4 wk wait)
- zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed)
- tailwindcss 3 → 4 (focused-afternoon project)
- @types/node ^20.19 stays pinned to match runtime (audit decision)
- archiver 7 stays (no @types/archiver@8 published)
- eslint 9 stays (locked to eslint-config-next 15)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
a7d0dd95e2 audit: append Context7-assisted dependency upgrade analysis (§34)
Companion to the deps-auditor report. Per-major pros/cons informed by
upstream changelogs queried via Context7. Sequencing:

- Tier A (patches + esbuild minor) → do now
- Zod 4 + @hookform/resolvers 5 → couple together, codemod-able
- Next 15 → 16 (incl. middleware → proxy.ts rename) → 2-4 weeks
- Tailwind 4 → afternoon project, visual review pages
- Defer indefinitely: archiver 8 (failed last try), react-day-picker 10

Top-line baseline: 0 known vulns; no GPL/AGPL; lockfile reproducible.
Everything below is dev-experience/perf, not security-blocker.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:23:19 +02:00
bfed1543b7 audit: Tier 5.4 — wrap moveFolder cycle check + write in a tx
Concurrency-auditor HIGH: the cycle walk + UPDATE used to run as
separate statements. Two concurrent moves (A→B and B→A) could each
pass the walk against the pre-move tree and both write, leaving an
A↔B cycle. Whole sequence now runs inside one db.transaction().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:19:24 +02:00
ad74e4a174 audit: Tier 1/3/6/7 batch — PII redaction, mobile safe-area, perf, build hardening
Tier 1.4: error_events.request_body_excerpt sanitizer now redacts
GDPR-relevant fields (email, phone, dob, address, fullName, firstName,
lastName, postcode, nationalId, etc.) on top of the existing
credential list. A 5xx in /api/v1/clients no longer lands full client
PII in the super-admin inspector.

Tier 3.10: ScanShell <main> now adds pb-[max(1.5rem, env(safe-area-
inset-bottom))]. Mobile-pwa audit caught the Save expense button sitting
flush against the iPhone 14/15 home indicator in standalone PWA mode.

Tier 6.2: dashboard widget-registry now dynamic-imports every
recharts-backed chart widget (berth status, lead source, occupancy
timeline, pipeline funnel, revenue breakdown, source conversion).
~80-150KB initial-bundle savings when reps have charts disabled.
ssr:false because recharts needs window.

Tier 6.3: DataTable wraps the assembled columns in useMemo keyed on
(columns, hasBulkActions). TanStack docs explicitly warn that
rebuilding columns every render resets the table's internal state.

Tier 7.1: Added .dockerignore (was missing — 7.6 GB context with
.env reachable via COPY . .). Excludes git, env files, node_modules,
build artefacts, IDE config, test artefacts, audit docs.

Tier 7.4: Dockerfile.dev now runs as the node user (uid 1000) — was
root. Working dir moves to /home/node/app.

Tier 7.5: docker-compose.prod.yml adds memory limits (2g postgres,
512m redis, 1g crm-app, 1g crm-worker) and json-file log rotation
(max-size, max-file) to every service.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:18:35 +02:00
50f48a8b6a audit: Tier 2/3/4 batch — reports math, portal copy, authz escalation guard
Tier 2.2: revenue PDF totalCompleted now filters on outcome='won' —
setInterestOutcome forces stage='completed' for every outcome (incl.
lost + cancelled), so the stage-only filter was including those toward
"TOTAL COMPLETED REVENUE".

Tier 2.3: fetchPipelineData stageCounts adds the missing .groupBy() —
without it Postgres rejects the SELECT (per-stage breakdown was broken
or coercing to ELSE-stage row).

Tier 2.4: hot-deals widget rank ladder fixed two stage-name typos —
'in_comms' → 'in_communication', 'deposit_10' → 'deposit_10pct'. Both
stages were collapsing to the ELSE 0 branch server-side AND rendering
raw enum to the user in hot-deals-card.tsx.

Tier 3.2: portal /portal/interests no longer renders raw enum to
clients. New PORTAL_SIGNING_LABELS table maps every EOI/contract
status to plain English (e.g. "waiting_for_signatures" → "Waiting for
signatures").

Tier 4.1 (CRITICAL): permission-overrides PUT now requires caller-
superset on every `true` write. Admins with only `admin.manage_users`
could previously grant other users leaves they don't hold themselves
(permanently_delete_clients, system_backup). Super-admins bypass.

Tier 4.4: search graph-expansion re-gates every merged bucket by the
destination's view permission. A user with berths.view but no
interests.view searching "A12" no longer sees interest rows surfaced
via expansion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:13:04 +02:00
16ef609e1b audit: Tier 1/3/4/5/7 batch — SSE, gates, dedup, URL escape, FK constraints
Tier 1.6: S3Backend.put now sets ServerSideEncryption=AES256 — closes
the cleartext-at-rest gap for signed contracts, GDPR exports, pg_dumps.

Tier 3.7: New safeUrl() helper in lib/email/shell.ts. Scheme allow-list
(http/https/mailto/tel/relative only — javascript:/data:/vbscript:/file:
rewritten to about:blank) + HTML-attribute escape. Retrofitted across
all 7 transactional templates (crm-invite, portal-auth, document-signing,
notification-digest, residential-inquiry, admin-email-change).

Tier 4.2: /api/v1/alerts GET now gated on admin.view_audit_log.

Tier 4.3: Documenso webhook handler emits captureErrorEvent on catch.
Admin/errors no longer silent on webhook crashes.

Tier 4.6: Inquiry-funnel email dedup is now case-insensitive
(LOWER(value)) and stores normalized email on insert. Capital-letter
resubmissions no longer spawn duplicate client+yacht+interest rows.

Tier 5.6 + data-model H1: migration 0056 adds FK
user_permission_overrides.user_id → user(id) cascade, same for
user_port_roles.userId, plus partial unique index on
user_email_changes pending rows.

Tier 7.6: @types/node bumped from ^25 to ^20.19.0 — matches the runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:09:14 +02:00
0baca41693 audit: Tier 0 quick wins — EMAIL_REDIRECT_TO prod guard + storage routing + metadata masking
Tier 0.2: src/lib/env.ts now refuses boot when NODE_ENV=production AND
EMAIL_REDIRECT_TO is set. Sendmail logs the rewrite at warn (was debug)
so dev/staging windows where someone forgets to unset are immediately
visible.

Tier 0.6: backup_jobs.storage_path added to TABLES_WITH_STORAGE_KEYS in
src/lib/storage/migrate.ts. Flipping the storage backend used to
silently orphan every pg_dump artefact — last-resort recovery path is
now actually portable.

Tier 1.7: createAuditLog now runs metadata through maskSensitiveFields
(was only applied to old/new value diffs). Portal-auth, crm-invite,
hard-delete and email-accounts services were writing raw emails into
this column unbounded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:02:10 +02:00
a7b72801be audit: importance-grouped triage companion to AUDIT-2026-05-12.md
10 tiers, every finding cross-referenced. Tier 0 = stop-ship, 8 =
already-fixed, 9 = nice-to-haves. Pick a tier and dig.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:58:51 +02:00
bdc9c019a8 audit: append storage-pathing report — all 33 agents now inlined
docs/AUDIT-2026-05-12.md now contains every audit verbatim
(6488 lines). Last to land was the S3-vs-internal-DB routing audit
covering the storage-backend boundary, presigned-URL round-trip,
magic-byte verification on both paths, migrate-storage coverage,
and orphan-blob risk on transaction failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:53:16 +02:00
4b9743a594 audit: 33-agent comprehensive audit + critical fixes
Full team audit run, all reports verbatim in docs/AUDIT-2026-05-12.md
(5900+ lines, 30+ critical findings). Already-fixed this commit:
- permission-overrides PUT: self-target block + RolePermissions allow-list + cross-tenant guard
- /api/auth/resolve-identifier: rate-limit + synthetic miss-email kill enumeration
- admin email-change: rotates account.accountId + revokes sessions
- middleware: token-gated email confirm/cancel routes whitelisted
- NAV_CATALOG: 10 dead-link sweeps to existing /admin/<x> targets

Feature work landing same commit: optional username sign-in
(migration 0054), per-user permission overrides (0055) with three-state
matrix tabbed inside UserForm, user disable button, role + outcome +
stage label normalisation across the platform, admin email-change
with auto-notification template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:52:35 +02:00
638000bb58 chore: prettier format audit report markdown
Lint-staged reformat after the previous commit added the file. Same
content, prettier's preferred line wrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:59:43 +02:00
1bdc856589 feat(documents-hub): NewDocumentMenu dropdown + FolderDropZone drag-drop
Replaces the bare "+ New document" Button on the documents hub with a
NewDocumentMenu dropdown so reps explicitly pick between:
- "Upload file" → opens a Dialog with FileUploadZone scoped to the
  current folder + entity context. No signing flow attached.
- "Generate document for signing" → navigates to /documents/new wizard.

Avoids the prior ambiguity where reps clicked "+ New document" intending
to attach a file and were dropped into the Documenso signer wizard.

Also adds FolderDropZone wrapping FlatFolderListing and EntityFolderView.
Dragging files from the OS over the current folder shows a drop overlay;
drop fires N parallel uploads carrying the folder + entity context.
Mirrors the per-entity Files tab UX but works in-place on the hub.

Both surfaces hit /api/v1/files/upload with folderId + entityType/Id +
the legacy clientId/companyId/yachtId FKs so files land on the right
entity AND inside the correct folder.

Also includes the in-flight prettier reformat from lint-staged on a
few previously-touched files (create-document-wizard, file-upload-zone,
admin/documenso/page) and adds the standalone prod-readiness audit
report to docs/superpowers/audits/ for permanent reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:59:34 +02:00
0e8feb1073 chore: prettier format pass on branch files
Auto-format all files modified during the documents-hub-split feature
branch that were not yet aligned with the project's Prettier config
(single quotes, semicolons, trailing commas).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:01:47 +02:00
40e3db237d docs(plans): documents hub split + auto-filed client folders
19-task implementation plan layering on top of Wave 11.B. Builds three
system-managed roots (Clients/Companies/Yachts), per-entity auto-
subfolders, Documenso auto-deposit on completion, owner-aggregated
projection (symmetric reach, file-FK source of truth, defense-in-depth
port_id), and the hub UI rebuild. Hard cutover; backfill via idempotent
script.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:57:46 +02:00
5422f11747 chore: prettier formatter drift across recent commits
Prettier reformatting on files touched in the wave 11.B sequence —
markdown italics _underscore-style_, single-line conditionals, minor
whitespace fixes. No semantic changes. .env.example reformatting left
unstaged (blocked by pre-commit hook).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:57:37 +02:00
286eb51f81 docs(specs): documents hub split + auto-filed client folders
Design for unifying /documents and /documents/files under a single hub
with stacked Signing/Files sections, owner-grouped aggregation across
the relationship graph, and three system-managed entity-folder roots
(Clients/Companies/Yachts) with lazy per-entity subfolders. Documents
hub stays anchored on document_folders; files gain folder_id; signed
PDFs auto-deposit on Documenso completion. Includes 14+ edge-case
decisions, schema deltas, backfill plan, and implementation surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:50:31 +02:00
f286c4ef5f docs(plan): progress snapshot at Task 7 — backend complete, UI next
Tasks 1-7 done in subagent-driven mode (11 commits 5bed62da0ffa1b).
The entire DB + service + API layer for folders is shipped: schema,
manage_folders perm, listTree/createFolder/renameFolder/moveFolder/
deleteFolderSoftRescue, validators, all 4 folder routes, the per-doc
move endpoint, and the listDocuments folder filter (with descendant
expansion). Reps can already manage folders end-to-end via direct
API calls.

Records the design decisions made mid-execution: hybrid storage
strategy (UUID-flat + path-style download URLs), permission split,
soft-rescue delete semantics, cycle prevention with port-scoped
ancestor walk, PATCH-body exclusivity via .strict(), and the
updatedAt bump rule (per-doc move yes, bulk soft-rescue no).

Tests at pause: 1213/1213 vitest, tsc clean. Resume prompt + task
ordering for Task 8 onwards included so a fresh session can pick up
without context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:08:28 +02:00
9f3e739c76 docs(plan): add Tasks 18-19 (path-style URLs + organized-bucket importer)
User chose the hybrid storage strategy after reviewing the cost
analysis: storage paths stay UUID-flat (preserves the established
pattern across brochures, berth PDFs, invoices, reports, templates,
expense receipts, and the migrate-storage byte-verbatim copy), but
documents gain a path-style download URL so reps see meaningful
paths in shared links and browser tabs.

Task 18 wires the new /api/v1/documents/[id]/download/[...slug]
catch-all route + a downloadUrl field on list/detail responses.
The slug is validated for truth so a hand-edited URL with a
stale path 404s instead of silently serving the wrong file.

Task 19 is the importer the user mentioned: a one-shot script
that walks an organized legacy bucket, creates matching folder
tree + document rows pointing at existing storage keys verbatim.
Idempotent via the sibling-uniqueness index.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:50:28 +02:00
51a60c1b9e docs(plan): documents folders implementation plan
17-task TDD plan covering schema + migration, service (listTree,
create, rename, move with cycle prevention, soft-rescue delete),
validators, API routes, hook, sidebar tree, breadcrumb, actions menu,
move-to-folder dialog, hub wiring (drop signature-only pill, In-
progress tab, dynamic type chips), admin-configurable Expired tab,
Playwright smoke, and CLAUDE.md update. Decisions locked: port-wide
tree, hub tabs stay flat, documents.manage_folders new perm, soft-
rescue on delete (never CASCADE), folder watchers out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:59:30 +02:00
1bfed587b5 docs: website cutover runbook + post-execution status snapshot
Captures the agreed cutover plan (Q6 in the decisions log: double-write
transition window, ~30 days, then NocoDB decommission). The CRM side
is wired today — public berth feed, website-inquiries intake, dual-mode
health probe, WEBSITE_INTAKE_SECRET env var. The runbook documents the
website-repo checklist and rollback path so we can pick it back up
when prep for prod begins.

Refreshes the audit-followups status snapshot to reflect what shipped
this session. Wave 11 is now broken out into A-G subitems so the
remaining group-discussion work is enumerated rather than collapsed.

Note: .env.example separately needs WEBSITE_INTAKE_SECRET added (see
runbook §Endpoints). The husky pre-commit hook blocks .env* files
intentionally — pass via a separate workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:38:46 +02:00
7804e9bb17 docs(audit-followups): record 11 decisions from 2026-05-09 review
Replace the open-questions section with a Decisions log capturing the
chosen direction for vocabularies admin, notification prefs, name
fields, public-feed parity, website cutover, status-change prospect
link, trip-label UX, documents folders, and the berth Documents tab
split. Quick-status snapshot updated to reflect that Waves 4-10 are
now ready to start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:34:59 +02:00
502455ac04 chore(format): apply prettier auto-formatting
Pre-commit hook reformatted these files after the substantive commits.
No semantic changes — markdown table alignment, list indentation, and
emphasis style normalisation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:11:54 +02:00
aad514a3bd docs(audit): 2026-05-08 mobile audit follow-ups index
Single source of truth for the 2026-05-08 visual-audit work. Owns
status of each item, file pointers, every open question, and a
ready-to-paste prompt for resuming in a fresh session. Items grouped
by wave (the original triage buckets, kept stable across sessions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 04:11:24 +02:00