feat(post-audit): Phase 4 polish + Phase 2 wiring + Phase 6 cron + CLAUDE.md

Three of the master plan's "suggested execution order" items shipped this
session; Phase 3b (EOI dialog overrides) deferred — estimate exceeded the
remaining session time.

- Phase 4 polish: yachtId field on <ReminderForm> via the existing
  YachtPicker, Ship-icon subtitle on <ReminderCard>, listReminders filter
  by yachtId, getReminder joins the yacht relation.
- Phase 2 risk-signal data wiring: getInterestById derives the 3 dates
  (dateDocumentDeclined / dateReservationCancelled / dateBerthSoldToOther)
  from document_events / berth_reservations / cross-interest interest_berths
  in parallel — chosen over new schema columns to keep the master plan's
  "no new tables" promise. Threaded through to DealPulseChip.
- Phase 6 cron + UI: src/jobs/processors/imap-bounce-poller.ts polls the
  configured IMAP mailbox (IMAP_* env), matches NDRs to recent
  document_sends rows via recipient + 7-day window, idempotent via
  bounceDetectedAt, fires email_bounced notifications on hard/soft
  (skips OOO). State persisted to system_settings.bounce_poller_state.
  Wired into maintenance queue at */15 * * * *. Admin /admin/sends page
  surfaces the bounce badge + reason inline.
- CLAUDE.md: trimmed 27KB → ~19.5KB (~28% smaller bytes). Prose-heavy
  Documenso webhook / v1-v2 routing / Document folders sections rewritten
  as scannable bullets. Added a new "Working in this repo — skills, MCPs,
  agents" section promoting brainstorming/TDD/debugging/frontend-design
  skills, Context7/Playwright/Serena MCPs, and the Explore/feature-dev
  agents. Documented Phase 2 derivation choice in the data-model section.

Quality gates: 1374/1374 vitest pass, tsc --noEmit clean, lint 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:38:37 +02:00
parent a6e79231f3
commit 503207ef68
13 changed files with 561 additions and 123 deletions

View File

@@ -106,6 +106,10 @@ interface InterestDetailHeaderProps {
contractDocStatus?: string | null;
/** Activity-log entries in the last 7 days — drives deal-pulse +5 signal. */
recentActivityCount?: number | null;
/** Phase 2 risk-signal dates fed into DealPulseChip. */
dateDocumentDeclined?: string | Date | null;
dateReservationCancelled?: string | Date | null;
dateBerthSoldToOther?: string | Date | null;
/** Sales rep who owns this deal — populated by the AssignedToChip. */
assignedTo?: string | null;
assignedToName?: string | null;
@@ -292,6 +296,9 @@ export function InterestDetailHeader({ portSlug, interest }: InterestDetailHeade
reservationDocStatus: interest.reservationDocStatus,
contractDocStatus: interest.contractDocStatus,
recentActivityCount: interest.recentActivityCount,
dateDocumentDeclined: interest.dateDocumentDeclined,
dateReservationCancelled: interest.dateReservationCancelled,
dateBerthSoldToOther: interest.dateBerthSoldToOther,
}}
/>
</div>

View File

@@ -69,6 +69,12 @@ interface InterestData {
reminderEnabled: boolean;
reminderDays: number | null;
reminderLastFired: string | null;
/** Phase 2 risk-signal dates derived in getInterestById from event
* tables (document_events, berth_reservations, conflicting won
* interests). Feed DealPulseChip; null when no matching event. */
dateDocumentDeclined: string | null;
dateReservationCancelled: string | null;
dateBerthSoldToOther: string | null;
notes: string | null;
archivedAt: string | null;
createdAt: string;