Commit Graph

3 Commits

Author SHA1 Message Date
Matt Ciaccio
5fc68a5f34 fix(audit): frontend HIGHs — surface fetch errors, kill href=#, invalidate queries, toast over alert
R2-H10: webhook-delivery-log and audit-log-list both swallowed fetch
errors silently — failed loads showed spinner forever or stale data.
Both now set a loadError state, show an inline retry banner, and fire
a toast.error. Same applies to audit-log loadMore.

R2-H11: audit-log-card rendered as `<a href="#">` — tapping on mobile
inserted `#` in the URL and scrolled to top (back-button trap).
ListCard now treats `href` as optional and renders a non-link `<div>`
when omitted; audit-log-card no longer passes href.

R2-H12: smart-archive-dialog only invalidated ['clients'] / ['berths']
/ ['interests']. Detail header kept showing Archived=false until hard
reload. Now also invalidates ['clients', clientId] and removes the
['client-archive-dossier', clientId] cache so re-open re-fetches.

R2-H13: client-list bulk mutation used native alert() on partial
failure (blocking the page) and had no onError handler. Replaced with
toast.warning / toast.success / toast.error.

1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:18:14 +02:00
Matt Ciaccio
94331bd6ec fix(audit): reliability HIGHs — smart-restore re-link, TOCTOU lock, bulk wrong-interest, ext-EOI tx, bulk idempotency
R2-H1: smart-restore's berth_released auto-reversal was a no-op while
the wizard claimed success. Now uses the persisted interestId from
the decision detail to re-insert the interest_berths link and flip
the berth status back to under_offer. Verifies the interest still
exists and isn't archived before re-linking.

R2-H2: smart-archive berth status update had a TOCTOU race — read
outside tx, write inside without a lock. Now selects-for-update the
berths row inside the tx and re-checks status against the locked row
before flipping to available, preventing concurrent archive+sale
from un-selling a berth.

R2-H3: bulk-archive's berth→interest lookup fell back to
dossier.interests[0]?.interestId ?? '' which sent empty-string
interestIds that silently matched zero rows. Dossier now exposes
linkedInterestIds[] per berth (authoritative interest_berths join);
bulk + single-client wizard both use it and skip berths with no
linked interest. Affected:
- src/lib/services/client-archive-dossier.service.ts (DossierBerth)
- src/app/api/v1/clients/bulk/route.ts
- src/components/clients/smart-archive-dialog.tsx

R2-H4: external-EOI ran storage upload + 4 DB writes outside a
transaction. Now wraps file/document/event/interest writes in a
single tx; storage upload stays before the tx (S3 isn't
transactional), orphan-object on tx failure is acceptable.

R2-H5: bulk archive double-submit treated already-archived clients as
per-row failures. Bulk callback now early-returns success when the
dossier shows archivedAt is set, making the endpoint idempotent.

1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:11:00 +02:00
Matt Ciaccio
e95316bd8a feat(client-archive): single-client smart-archive dialog + CSP/middleware fixups
UI side of the smart-archive backend that shipped in d07f1ed.

- SmartArchiveDialog renders the dossier as a sectioned modal:
  Pipeline interests, Berths (with next-in-line listed), Yachts,
  Active reservations, Outstanding invoices, In-flight Documenso
  envelopes, Auto-handled summary. Each section has a per-row decision
  dropdown with sensible defaults (release for available/under-offer
  berths, retain for sold berths and yachts, cancel for active
  reservations, leave for invoices and documents).
- High-stakes deals show an amber warning panel + require a reason in
  the textarea before the Archive button enables. Signed-document
  acknowledgment checkbox blocks submission until checked.
- Wires into client-detail-header in place of the previous dumb
  ArchiveConfirmDialog (the simple confirm dialog is kept for the
  restore case until the smart-restore wizard ships).
- Pre-flight blocker banner surfaces dossier.blockers (e.g. active
  reservation on a sold berth) and disables the Archive button entirely.

Two side fixes from CSP rollout:
- next.config CSP allows unpkg.com in dev so the react-grab devtool
  loads. Stripped in prod via the existing isProd flag.
- middleware whitelist now passes /manifest.json + icon-*.png +
  apple-touch-icon through unauthenticated, so PWA installability
  isn't blocked by the auth redirect.

Bulk variant + restore wizard + hard-delete-with-email-code land in
follow-on commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:19:34 +02:00