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

@@ -815,6 +815,44 @@ phase starts:
---
## Session log
### Session 2026-05-18 PM — Phases 4 / 2 wiring / 6 / CLAUDE.md
Three of the four "suggested execution order" items shipped; Phase 3b
was deferred (effort estimate exceeded remaining session time).
Recent commits leading into this session:
```
a6e7923 docs(plan): mark Phase 1+2 ☑, Phase 3-7 ◐ partial
df1594d feat(email): Phase 5 — branding chain ext'd with per-port background
9f57868 feat(post-audit): Phase 3/6/7 schema foundations + bounce parser
fb4a09e feat(reminders): Phase 4 partial — schema + service + validators
918c23f feat(post-audit): Phase 1.3 + 1.4 + Phase 2 signals + pulse admin
ee3cbb9 docs(plan): expand master plan with detailed implementation appendix
c9debce docs(plan): comprehensive 7-phase master plan for post-audit work
0f99f05 feat(post-audit): batch A+B quick-wins + audit-side residuals
4b5f85c fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish
397dbd1 docs(spec): env-to-admin migration design
```
Shipped this session:
- ☑ CLAUDE.md trimmed 27KB → ~19.5KB; added Tools/Skills/MCPs section.
- ☑ Phase 4 polish — yachtId field on `<ReminderForm>` + Ship subtitle on `<ReminderCard>` + `listReminders` filter + `getReminder` yacht relation join.
- ☑ Phase 2 risk-signal data wiring — derivation pass in `getInterestById` (3 parallel queries) populates the 3 risk-signal dates from `document_events` / `berth_reservations` / cross-interest `interest_berths`. Chosen over new schema columns; documented in CLAUDE.md.
- ☑ Phase 6 cron + UI — `imap-bounce-poller.ts` worker wired into maintenance queue at `*/15 * * * *`; matches NDRs to recent `document_sends` rows, fires `email_bounced` notification on hard/soft; admin `/admin/sends` page now shows bounce badge + reason banner.
- Quality gates: 1374/1374 vitest pass, `tsc --noEmit` clean, `pnpm lint` zero errors (37 pre-existing warnings).
Deferred:
- Phase 3b — EOI dialog override UI (combobox per field + 2 checkboxes) was the 4th item; master-plan estimate is 2-3 days and exceeded remaining session time.
- Phase 4 worker scheduler refactor (fired_at gate cron tick).
- Phase 6 interest-detail "Emails" tab — the tab surface doesn't exist yet; bounce banner will live there when the tab lands.
---
## Phase ☑/☐ tracker
- ☑ Phase 1 — Documenso completion + Supplemental form (commits df1594d, 918c23f)
@@ -822,30 +860,43 @@ phase starts:
- ☑ 1.2 Documenso Phase 2 (Webhook UX cascading invite) — already in code prior; verified
- ☑ 1.3 Documenso Phase 5 (Embedded signing) — copy made order-agnostic + developer-role branch
- ☑ 1.4 Supplemental form per-port URL — registry + getPortEmailConfig + route
- ☑ Phase 2 — Deal-pulse signals + admin config UI (918c23f)
- ☑ Phase 2 — Deal-pulse signals + admin config UI (918c23f, plus session 2026-05-18 PM)
- Compute extended with 3 positive + 3 risk signals; admin page mounted at /admin/pulse
- Data-wiring follow-up: 3 risk signals need new interest timestamp columns or derivation
from event tables (`dateDocumentDeclined`, `dateReservationCancelled`, `dateBerthSoldToOther`)
- Data-wiring: derivation pass inside `getInterestById` — runs 3 parallel queries
against `document_events` (rejected/declined), `berth_reservations`
(status='cancelled'), and other `won` interests sharing a berth via `interest_berths`.
Returns the 3 dates on the API response; `interest-detail-header` threads them
through to `<DealPulseChip>`. Chosen over new schema columns to keep the master
plan's "no new tables" promise. Documented in CLAUDE.md.
- ◐ Phase 3 — EOI field overrides (schema only; 9f57868)
- ☑ 3a — Schema migration 0073, Drizzle additions, audit_actions free-text verbs
- ☐ 3b — EOI dialog UI (combobox + 2 checkboxes per field)
- ☐ 3c — Yacht spawn from EOI (inline Sheet + YachtForm)
- ☐ 3d — Audit surfacing + client/yacht detail badges + set-primary endpoint
- ◐ Phase 4 — Reminders (schema + service + validators; fb4a09e)
- ◐ Phase 4 — Reminders (schema + service + form; fb4a09e + session 2026-05-18 PM)
- ☑ Schema migration 0072: reminders.yacht_id + fired_at + interests.reminder_note
- ☑ Service + validators accept yachtId with port-scoping check
- Dialog UI extension (yachtId field; existing form covers core)
- Dialog UI extended with YachtPicker (free-text search, no clientId scope)
- ☑ `<ReminderCard>` shows yacht subtitle (Ship icon + yacht name)
- ☑ `listReminders` now filters by query.yachtId; `getReminder` joins yacht relation
- ☐ Worker scheduler refactor (fired_at gate; cron tick)
- ☐ user_profiles.preferences.digest_time_of_day picker in /settings
- ☐ Per-entity-page `[+ Task]` buttons threading `defaultYachtId` (etc.)
- ◐ Phase 5 — Email-copy refactor (branding chain only; df1594d)
- ☑ Per-port background URL — closes the last hard-coded portnimara.com asset
- ☐ Tone rewrite across 8 templates using old-CRM Nuxt repo as reference
- ☐ Snapshot tests per template at port-nimara + 2nd test port
- ◐ Phase 6 — IMAP bounce-to-interest linking (schema + parser; 9f57868)
- ◐ Phase 6 — IMAP bounce-to-interest linking (9f57868 + session 2026-05-18 PM)
- ☑ Schema migration 0074: bounce_status/reason/detected_at on document_sends
- ☑ Parser library `src/lib/email/bounce-parser.ts` (RFC 3464 + Outlook + OOO)
- Cron worker `src/jobs/processors/imap-bounce-poller.ts`
- ☐ UI banner on interest emails tab + email_bounced notification type
- Cron worker `src/jobs/processors/imap-bounce-poller.ts` — reads IMAP\__ env,
matches NDR recipient to recent document_sends, idempotent via `bounceDetectedAt`,
fires `email_bounced` notification on hard/soft (skips OOO); state persisted to
`system_settings.bounce_poller_state` (port_id=NULL). Wired into maintenance
queue at `_/15 \* \* \* \*`.
- ☑ UI banner on `/admin/sends` (admin sends-log) + `email_bounced` notification type
- ☐ Interest-detail "Emails" tab — surface tab doesn't exist yet; bounce banner
would live there when the tab lands (deferred to a wider emails-surface session)
- ☐ Manual round-trip test against real bounced delivery
- ◐ Phase 7 — PDF template editor (field-map types only; 9f57868)
- ☑ FieldMap type definitions + Zod validators + page-count cross-validator