# Handoff prompt for new Claude Code session Copy everything below the `---` line into the new chat as your first message. --- I'm continuing work on a comprehensive multi-feature push that was fully designed in a prior session but not yet implemented. The complete plan lives at `docs/berth-recommender-and-pdf-plan.md` (~1030 lines). **Read that file end-to-end before doing anything else — every design decision, schema change, edge case, and confirmed answer to a product question is captured there.** Don't re-litigate decisions; if something seems unclear, the answer is almost certainly in the plan. ## What the project is A multi-tenant marina/port-management CRM at `/Users/matt/Repos/new-pn-crm`. Next.js 15 App Router, React 19, TypeScript strict, Drizzle ORM on Postgres, MinIO for files, BullMQ on Redis, better-auth, shadcn/ui, Tailwind. See `CLAUDE.md` for the conventions. ## What we're building (high level) The plan bundles 8 capabilities into one branch (`feat/berth-recommender`): 1. **/clients + /interests list-column fix** (the original bug — list views show `-` everywhere because the service didn't join contacts/yachts) 2. **Full NocoDB Berths import** + seeding + mooring-number normalization (current CRM has `A-01..E-18`; canonical is `A1..E18`) 3. **Schema refactor** to many-to-many `interest_berths` with role flags (`is_primary`, `is_specific_interest`, `is_in_eoi_bundle`) 4. **Berth recommender** (SQL ranking, tier ladder, heat scoring, UI panel) — no AI; pure SQL 5. **EOI bundle** support (multi-berth EOIs + range formatter for the Documenso PDF: `["A1","A2","A3","B5","B6"]` → `"A1-A3, B5-B6"`) 6. **Pluggable storage backend** (s3-compatible OR local filesystem) so admins can run without MinIO if they want 7. **Per-berth PDFs** (versioned uploads, OCR-based reverse parser, conflict-resolution diff dialog) 8. **Sales send-out emails** (berth PDF + brochure) with full audit + size-aware fallback to download links ## Phase ordering (from plan §2) ``` Phase 0: Full NocoDB berth import + mooring normalization + 5 new pricing columns Phase 1: /clients + /interests list column fix Phase 2: M:M interest_berths schema refactor + desired dimensions on interests Phase 3: CRM /api/public/berths endpoint + website cutover Phase 4: Recommender SQL + tier ladder + heat + UI panel Phase 5: EOI bundle + range formatter Phase 6a: Pluggable storage backend + migration CLI + admin UI Phase 6b: Per-berth PDF storage (versioned) + reverse parser Phase 7: Sales send-outs + brochure admin + email-from settings Phase 8: CLAUDE.md updates + final validation ``` **Start with Phase 0**. ## Working tree state at handoff - Branch: `main` (you'll create `feat/berth-recommender` from here) - Recent commits (already pushed): - `8699f81 chore(style): codebase em-dash sweep + minor layout polish` - `d62822c fix(migration): NocoDB import safety + dedup helpers + lead-source backfill` - `089f4a6 feat(receipts): upload guide page + scanner head-tag fix` - `77ad10c feat(dashboard): custom date range + KPI port-hydration gate` - `e598cc0 feat(layout): unified Inbox + UserMenu extraction` - `f5772ce feat(analytics): Umami integration with per-port admin settings` - `49d34e0 feat(website-intake): dual-write endpoint + migration chain repair` - Untracked / uncommitted at handoff: - `docs/berth-recommender-and-pdf-plan.md` (the plan — read this first) - `docs/berth-feature-handoff-prompt.md` (this file) - `berth_pdf_example/` (two reference files — see below) - `.env.example` (modified — adds `WEBSITE_INTAKE_SECRET=`; pre-commit hook blocks `.env*` files so user adds this manually) - Dev DB state: - 245 clients (210 with no `nationality_iso` — Phase 1 backfills from primary phone's `value_country`) - 4 test rows in `website_submissions` (from a previous live audit; safe to ignore) - 90 berths with `mooring_number` in `A-01` format (Phase 0 normalizes to `A1`) - vitest: 956 tests passing - tsc: clean (one pre-existing issue in `scripts/smoke-test-redirect.ts` that's unrelated) ## Reference files - `berth_pdf_example/Berth_Spec_Sheet_A1.pdf` (358 KB) — sample per-berth PDF. **0 AcroForm fields** (confirmed via pdf-lib) so OCR with positional heuristics is the primary parser tier; the AcroForm tier is built defensively. Plan §9.2 captures the layout structure. - `berth_pdf_example/Port-Nimara-Brochure-March-2025_5nT92g.pdf` (10.26 MB) — sample brochure. Sized so it ships as an attachment under the 15 MB threshold. Plan §11.1 covers brochure handling. ## NocoDB access You have `mcp__NocoDB_Base_-_Port_Nimara__*` tools available. Tables you'll touch most: - `mczgos9hr3oa9qc` — Berths (Phase 0 imports from here; mooring numbers are stored as `A1..E18`) - `mbs9hjauug4eseo` — Interests (the combined client+deal table the old system used) ## Branch & commit conventions - Create the branch: `git checkout -b feat/berth-recommender` - Commit messages match recent history style: `(): ` lowercase, terse subject, body explains why not what. - **Pre-commit hook blocks any `.env*` file** including `.env.example`. If you need to update `.env.example`, leave it staged and tell the user to commit manually with `--no-verify` (they're aware of this). - **Don't push without explicit user permission.** Commits are fine; pushes need approval. - **Don't run `git rebase`, `git push --force`, or anything destructive without checking.** The branch is solo-owned but the repo's `main` is shared. ## User communication preferences (from prior session) - Direct, no fluff. If something is a bad idea, say so — don't sycophant. - When proposing changes, include trade-offs explicitly. - For multi-question decisions, use `AskUserQuestion` rather than long bulleted lists. - Run validation (vitest + tsc) at logical checkpoints. Don't ship a commit with regressions. - The user prefers small focused commits over mega-commits. Within Phase 0 alone there will probably be 2-3 commits (e.g. mooring normalization, schema additions, NocoDB import script). ## Critical rules (from plan §14) Eleven 🔴 critical items requiring tests before their phase ships: 1. NocoDB mooring collisions → unique constraint + ON CONFLICT 2. Non-PDF disguised upload → magic-byte check 3. Recipient email typos → pre-send confirmation 4. XSS in email body markdown → DOMPurify + payload tests 5. SMTP credentials silently failing → loud error + failed `document_sends` row 6. Wrong-environment `CRM_PUBLIC_URL` → health-check env match 7. Mooring format drift breaking `/berths/A1` URLs → Phase 0 normalization gates Phase 3 8. Multi-port isolation in recommender → explicit `port_id` filter + cross-port test 9. Permission escalation on SMTP creds → per-port admin only, no rep visibility 10. Filesystem backend in multi-node deployment → refuse to start; documented + health-check enforced 11. Path traversal via storage key in filesystem mode → strict regex validation + path realpath check ## Pending items (from plan §9) These are non-blocking but worth knowing: - Sample brochure already provided (the 10.26 MB file above). - SMTP app password for `sales@portnimara.com` — not yet obtained; expected close to production cutover. Phase 7 ships the admin UI immediately and the credential gets entered when available. - `CRM_PUBLIC_URL` confirmed as `https://crm.portnimara.com` once live; configurable via env. - GDPR cascade behavior for `document_sends` (delete vs. anonymize-PII vs. keep) — left `OPEN` in §14.10, default lean: anonymize-PII. Revisit when Phase 7 schema lands. ## Scope reminder - **No prod data depends on the current CRM schema** — refactors don't need backwards-compatibility shims. But every schema change still ships as a Drizzle migration with `pnpm db:generate`. - **Pluggable storage** rejects Postgres `bytea` as an option (§4.7a). The two backends are s3-compatible (MinIO/AWS/B2/R2/etc.) and local filesystem. Filesystem is single-node only. ## What to do first 1. Read `docs/berth-recommender-and-pdf-plan.md` end-to-end. Don't skim. The edge-case audit in §14 alone is critical context. 2. Confirm you've understood the plan by stating back the 8-phase outline and the 11 critical items, then ask the user if they want to proceed with Phase 0. 3. Once approved, create `feat/berth-recommender` and start Phase 0. Phase 0 deliverables (per plan): - One commit normalizing existing CRM mooring numbers from `A-01` → `A1` form (via `regexp_replace` migration). Delete the offending `scripts/load-berths-to-port-nimara.ts`. - One commit adding the 5 new berth columns (`weekly_rate_high_usd`, `weekly_rate_low_usd`, `daily_rate_high_usd`, `daily_rate_low_usd`, `pricing_valid_until`, `last_imported_at`). Run `pnpm db:generate`. Verify `meta/_journal.json` prevId chain stays contiguous. - One commit adding `scripts/import-berths-from-nocodb.ts` — the idempotent NocoDB import (handles updates, preserves CRM-side edits via `last_imported_at vs updated_at` check, `pg_advisory_lock`, dry-run flag, etc. per §4.1 and §14.1). - Update `src/lib/db/seed-data.ts` with the imported berth set so fresh installs get them. - Final vitest + tsc validation at the end of Phase 0. ## Don't - Don't push to remote during this session (user will batch the push later). - Don't commit `.env*` files (hook blocks them anyway). - Don't edit `.gitignore` to exclude generated artifacts; the repo's existing ignores are correct. - Don't add documentation files unless the plan asks for them — the plan itself is the doc. - Don't add features not in the plan. If something seems missing, ask. - Don't use AI for the recommender (plan §1 + §13). Pure SQL ranking. Once you've read the plan and confirmed understanding, ask me whether to proceed with Phase 0.