Files
pn-new-crm/docs/BACKLOG.md
Matt 60365dc3de
Some checks failed
Build & Push Docker Images / lint (push) Successful in 1m37s
Build & Push Docker Images / build-and-push (push) Failing after 24s
fix(audit): backlog sweep — partial archived indexes, custom-fields per-entity gate, polish
Wave through the 2026-05-07 backlog of small/concrete audit-final-deferred
items (deferring the Documenso Phases 2-7 build and items needing design
decisions or live external instances).

DB schema:
- Migration 0046 converts 5 composite (port_id, archived_at) indexes to
  partial WHERE archived_at IS NULL — clients, interests, yachts, and
  both residential tables. Smaller, faster planner choice for the
  dominant list-query shape.

Multi-tenant isolation:
- document_sends now verifies recipient.interestId belongs to the port
  before landing on the audit row (the surrounding clientId check was
  already port-scoped; interestId pollution was the gap).

Routes / API:
- /api/v1/custom-fields/[entityId] requires entityType query param and
  gates on the matching resource permission (clients/interests/berths/
  yachts/companies). Fixes the cross-resource gap where a user with
  clients.view could read company custom-field values.
- Admin user list trash button wrapped in PermissionGate (edit was
  already gated; remove was not).

Service polish:
- berth-recommender accepts string-shaped JSONB booleans
  ('true'/'false') so admin UIs that wrap values as strings don't
  silently fall through to defaults.
- expense-pdf renderReceiptHeader anchors all text positions to a
  captured baseY rather than reading mutating doc.y after rect+stroke.
  Headers no longer drift on the first receipt page after a soft page
  break.
- berth-pdf apply: collect non-finite numeric coercion drops + warn-log
  them so partial silent drops are observable (was invisible because
  the no-fields-supplied check only fires when ALL drop).
- Storage cache fingerprint comment documenting the encrypted-secret
  invariant + the explicit invalidation hook.

UI polish:
- invoice-detail typed: replaced two `any` casts with a proper
  InvoiceDetailData / LineItem / LinkedExpense interface set.
- YachtForm now accepts initialOwner prop. Wired through:
  - client-yachts-tab passes { type: 'client', id: clientId }
  - interest-form passes { type: 'client', id: selectedClientId }
- Interest-form yacht picker now includes company-owned yachts where
  the selected client is a member (fetches client.companies and feeds
  YachtPicker an array filter). Plus an inline "Add new" button that
  opens YachtForm pre-bound to the client.
- YachtPicker accepts ownerFilter as single OR array for "match any"
  semantics.

BACKLOG.md updated with what landed vs what's still deferred (and why
each deferred item is genuinely larger than this push warrants).

Tests: 1185/1185 vitest, tsc clean.
2026-05-07 21:45:42 +02:00

15 KiB
Raw Blame History

Master backlog index

Single source of truth for everything outstanding. Start here when asking "what's left to build/fix?". Items are grouped by source doc; each entry links back to the original spec for full context.

Last updated: 2026-05-07 (after the audit-final-deferred sweep — partial archived indexes, document_sends interestId port-verify, custom-fields per-entity permission gate, recommender bool parsing, expense PDF cursor math, berth PDF silent-drop logging, YachtForm preset-owner + interest form member-company yacht filter + add-new shortcut, invoice detail typed). Many older items in §C and §F were already resolved by earlier fix-audit commit waves; the audit doc was stale.


A. Documenso build (deferred for later)

Source: docs/documenso-build-plan.md — full phase plan with locked decisions (Q1Q10). Tracker delta: docs/admin-ux-backlog.md — what landed in Phase 1.

Phase 1 (EOI generate flow polish + APPROVER-as-CC + per-port settings + signing-URL fix) is DONE and committed.

Remaining phases — explicitly back-burnered by the user on 2026-05-07:

Phase Scope Estimate Notes
Phase 2 Webhook handler enhancement: cascading "your turn" emails, on-completion PDF distribution, token-based recipient matching, idempotency lock ~34h Schema columns already in place from Phase 1 (document_signers.invited_at / opened_at / signing_token, documents.completion_cc_emails).
Phase 3 Custom doc upload-to-Documenso: custom-document-upload.service.ts + POST /api/v1/interests/[id]/upload-for-signing ~68h Depends on Phase 2 webhook UX in anger before locking the upload UX.
Phase 4 Field placement UI: react-pdf + dnd-kit overlay + auto-detect anchor scanner via pdfjs getTextContent ~1014h Largest piece. Plan locked in build-plan Phase 4 — regexes, anchors, type-to-bbox sizing all spelled out. Best done in a focused session with the user watching.
Phase 5 Embedded signing URL emission verification: confirm website's /sign/<type>/<token> page handles every signer-role × documentType combination; update signerMessages map; apply nginx CORS block from integration audit ~12h
Phase 6 Polish: auto-send delay, audit-log additions, per-document customisation, document expiration, reminder rate-limit display, failed-webhook recovery UI each ~23h All deferred until Phases 14 ship.
Phase 7 Project Director RBAC — UI binding for the developer-user fields. Add "Linked to CRM user" dropdown in /admin/documenso/page.tsx; auto-fill name/email; webhook handler matches against linked user's email for in-CRM signing-status updates. Schema + setting keys (documenso_developer_user_id, documenso_approver_user_id, _label) already in place from Phase 1. ~1h Smallest piece; could be picked off independently of Phase 2.
Risk #4 v2 webhook payload audit against a live v2 instance (payload.documentId vs payload.id, recipient.token vs recipient.recipientId) before relying on Phase 2 cascading emails ~1h Needs a live v2 instance.

B. Custom-fields hardening (~ongoing, deferred)

Source: docs/admin-ux-backlog.md §7.

Custom Settings page already shows the amber warning banner. Remediation work:

  • Search index — extend the GIN tsvector to include customFieldValues content
  • Audit diff — extend diffEntity to walk the customFieldValues blob
  • Merge tokens — add {{custom.<fieldName>}} handling at template-render time, plus surface them in the merge-tokens UI

C. Audit-final deferred items

Source: docs/audit-final-deferred.md — pre-merge + post-merge audit findings explicitly carried over.

The 2026-05-07 backlog sweep landed every small/concrete item. Remaining entries are deferred because they need design decisions, live external instances, or cross-cutting refactors:

Deferred — needs design or larger refactor

  • Storage proxy token does not bind to port_idsrc/lib/storage/filesystem.ts:73-84. Adding a p (portId) claim is mechanical; the meaningful security gain requires the proxy verifier to look up the file's owning row + assert owner.portId === payload.p. That requires either a routing prefix in the key (currently ${portSlug}/... already, so a prefix check is plausible) or a per-table lookup across all owners. Decide which approach before implementing — current state ships with validateStorageKey + per-issuer port scoping, so this is defense-in-depth rather than an open hole.
  • Documenso webhook does not enforce port_id on document lookupssrc/app/api/webhooks/documenso/route.ts:96-148. Adding port scope requires either including the originating Documenso instance/team id in the lookup (Documenso doesn't surface that on the webhook payload today) OR proving documents(documenso_id) is globally unique with a DB constraint and a backfill check. Pick the strategy with the audit doc open.
  • Webhook dedup vs per-recipient signed eventssrc/app/api/webhooks/documenso/route.ts:103-110. Replacing the body-hash dedup with a (documensoDocumentId, recipientEmail, eventType) composite unique requires schema column for recipient_email on documentEvents. Right place to do this is alongside Documenso Phase 2 (webhook handler enhancement) since they touch the same code.
  • v2 voidDocument endpoint shape verificationsrc/lib/services/documenso-client.ts:450-466. Needs a live Documenso 2.x instance to confirm POST /api/v2/envelope/delete body shape. Bundle with Documenso Phase 5.
  • Public POST routes bypass service layersrc/app/api/public/{interests,website-inquiries,residential-inquiries}/route.ts. Multi-route refactor extracting a shared publicInterestService.create(...). Worth doing but big enough to deserve its own session.
  • Inconsistent response shapes — most endpoints return { data: ... }, but notifications/[notificationId] returns { success: true }, website-inquiries returns { id, deduped }. Codebase-wide migration; document a convention in CLAUDE.md first.
  • systemSettings PK / unique-index driftsrc/lib/db/schema/system.ts:119-133. Schema declares uniqueIndex on (key, port_id), migration uses key as PK. port_id is nullable so (key, port_id) cannot serve as a PK with default NULLs-not-equal semantics. Reconcile by either making portId non-null with a sentinel ("global") and declaring composite PK, OR by dropping the schema-level unique index and using partial unique indexes for global vs per-port. Either path is a data migration.

Done in 2026-05-07 sweep (commits in this session)

  • Partial archived indexes (migration 0046) — clients, interests, yachts, residential_clients, residential_interests
  • document_sends interestId port-verification helper
  • Custom-fields per-entity permission gate (replaces hardcoded clients.view/edit)
  • EOI Berth Range warn log (was already in place)
  • v1 placeFields retry with backoff (was already in place)
  • S3 bucket-exists check at boot (was already in place)
  • Filesystem dev HMAC fallback warn (was already in place)
  • Storage cache fingerprint documentation comment
  • AI worker cost ledger writes (was already in place)
  • Logger redact paths covering headers, encrypted blobs, two-level nesting (was already in place)
  • loadRecommenderSettings accepts string "true"/"false" JSONB booleans
  • renderReceiptHeader cursor math anchored to captured baseY
  • Berth PDF apply: silent-drop logging for non-finite numeric coercions
  • Saved-views: confirmed by-design owner-only (existing inline doc)
  • Alerts ack/dismiss: confirmed by-design port-wide (service correctly bounded)
  • Storage admin migration toasts (already in place)
  • Invoice send/payment toasts + permission gates (already in place)
  • Admin user list edit + remove gates (added remove gate)
  • Email threads list skeleton + empty state (already in place)
  • Scan page error state for OCR failures (already in place)
  • Invoice detail typed (replaced any with InvoiceDetailData interface)
  • All FK indexes called out in audit doc (already in place — audit was stale)
  • documentSends.sentByUserId FK (already had .references(...))

Still open — small enough to bundle next time

  • berths.current_pdf_version_id lacks Drizzle FKsrc/lib/db/schema/berths.ts:83. The in-line comment fully documents why (circular FK between berthsberth_pdf_versions makes column-level .references() infeasible). FK is enforced via migration 0030. Treat as documented limitation; revisit if Drizzle adds deferred-FK support.
  • req.json() without parseBody helper — admin custom-fields routes use await req.json(); schema.parse(body) directly. Migrate for uniform 400 error shapes when the surface area calms down.

D. Inline TODOs in code (2 remaining)

File:line Note Status
client-yachts-tab.tsx:93 YachtForm preset owner prop landed 2026-05-07 (initialOwner prop)
interest-form.tsx:329 Include company-owned yachts where client is a member landed 2026-05-07 (yachtOwnerFilter array filter)
interest-form.tsx:330 "Add new yacht" inline shortcut landed 2026-05-07 (Plus button + YachtForm sheet)
src/lib/queue/scheduler.ts:44 Per-user reminder schedule configurable from user_settings Open — needs user_settings UI surface
src/lib/queue/workers/import.ts:13 Import job handlers — worker is a stub Open — entire feature surface

E. Hidden / stubbed UI tabs

  • Company Documents tabsrc/components/companies/company-tabs.tsx:229. Hidden until /api/v1/files accepts a companyId filter (schema supports it, validator doesn't).
  • Berth Waiting List + Maintenance Log tabssrc/components/berths/berth-tabs.tsx:346. Removed entirely; revisit if/when product asks.
  • Interest Contract / Reservation tabssrc/components/interests/interest-{contract,reservation}-tab.tsx. Render a "coming soon" friendly card; the real flow is gated on Documenso Phases 26.

F. Historical audit docs (mostly resolved)

These dossiers drove the audit-fix commit waves on 2026-05-05/06. Items not surfaced in §C above were resolved via the fix(audit): … commits (588f8bc, 94331bd, a8c6c07, 5fc68a5, da7ede7, c5b41ca, b4fb3b2, 0f648a9, c312cd3, 0a5f085, 1a87f28, f3143d7, 05babe5). Keep for historical context: