diff --git a/docs/AUDIT-FOLLOWUPS.md b/docs/AUDIT-FOLLOWUPS.md new file mode 100644 index 00000000..c6253253 --- /dev/null +++ b/docs/AUDIT-FOLLOWUPS.md @@ -0,0 +1,698 @@ +# Audit Follow-ups — 2026-05-08 visual audit + +This is the single index for everything from the 2026-05-08 mobile visual +audit. Owns: status of each item, file pointers, every open question, +and a ready-to-paste prompt for resuming in a fresh session. + +Items are grouped by **wave** (the original triage buckets, kept stable +across sessions). Numbering inside each wave matches the original audit +message order where possible. + +> **If you only have time for one section, read § "Resuming in a fresh +> session" at the bottom.** + +--- + +## Quick status snapshot — 2026-05-08 23:00 + +| Wave | Topic | Status | +| --- | --- | --- | +| 1 | Small confident fixes | ✅ Done | +| 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split deferred) | +| 3 | Berth field overhaul (NocoDB enums) | ✅ Done | +| 4 | Currency platform-wide | 🔴 Not started | +| 5 | Configurable enums (admin Vocabularies) | 🔴 Not started | +| 6 | Notes unification (aggregate-on-read) | 🔴 Not started | +| 7 | Clients / yachts / companies misc | 🟡 Partial (small bits done; large items deferred) | +| 8 | Expenses revisit | 🔴 Not started | +| 9 | Interests + notifications | ✅ Done | +| 10 | Settings polish | ✅ Done (small bits) — schema-blocked items deferred | +| 11 | DEFERRED — group-discussion items | 🟣 Awaiting alignment | +| **Bonus** | **Public berth feed (website map)** | ✅ Map data backfilled (117 rows). Field-parity gaps remain — see § Bonus. | + +Test status: `pnpm exec vitest run` → **1185/1185 pass**. +TS check: `pnpm exec tsc --noEmit` → **clean**. +Git: 23 files modified, 2 new files (no commits yet). + +--- + +## Ground rules / invariants we picked up + +- **Notes unification model**: aggregate-on-read (option 1 from the + AskUserQuestion, picked by user). One canonical service per entity + unions own-notes + related-entity notes; no replication, no schema + migration. +- **NocoDB MCP**: connected at `~/.claude.json` under + `mcpServers."NocoDB Base - Port Nimara"`. Verified Berths schema + + records pull cleanly. The seed-data JSON snapshot + (`src/lib/db/seed-data/berths.json`) is also a reasonable fallback + if the MCP is unavailable. +- **Berth dropdown values** are now sourced from the NocoDB SingleSelect + choices verbatim — see `src/lib/constants.ts` (look for + `BERTH_*_OPTIONS` / `_TYPES`). Power Capacity and Voltage stay numeric + inputs because NocoDB stores them as `Number`. Bow Facing is + `SingleLineText` in NocoDB but constrained to the 4 cardinal values + in the CRM dropdown for UX. +- **Dual-unit fields** auto-cross-fill via `linkedUnit` on + `EditableSpec` in `src/components/berths/berth-tabs.tsx`. The user + edits the imperial value; the metric column is computed × 0.3048 and + patched in the same request. +- **Receipts in expense PDF**: user's clarified preference is "PDF + images should show inline with the relevant expense" — i.e. images + inline; PDF receipts also rendered inline (one page each, via + pdfme + `pdf-lib.copyPages`). +- **Configurable enums**: the existing pattern is `system_settings` + with composite PK `(key, port_id)` and `` admin + page. Use the same pattern for the new vocabularies. +- **Turbopack dev**: `pnpm dev` runs `next dev --turbopack`. Cold + compiles ~1s boot, ~3s per route. No webpack hooks in + `next.config.ts` so flipping back is one line if needed. + +--- + +## ✅ Completed this session + +### Wave 1 — small confident fixes + +1. **Berth list ordering bug** — `\d+$` regex in the Drizzle SQL + template was being eaten by JS string literal escape rules + (`\d` → `d`). Fixed by switching to `[0-9]+$` POSIX class. + File: `src/lib/services/berths.service.ts:69-72`. +2. **Dashboard KPI grid removed** — "Total Clients / Active Interests + / Pipeline Value / Occupancy Rate" deleted. The four chart widgets + below (pipeline funnel, occupancy timeline, revenue breakdown, + lead source) and the activity feed remain. + File: `src/components/dashboard/dashboard-shell.tsx`. +3. **Per-dock color stripe on mobile berth cards** — was the *status* + color, which made every same-dock berth different. Now uses + `mooringLetterDot()` so the stripe groups by dock letter; status + conveyed by the existing pill below. + File: `src/components/berths/berth-card.tsx`. +4. **`{Letter} Dock` chip** on the berth detail header replaces the + bare "A" / "B" text. Colored by `mooringLetterDot()`. + File: `src/components/berths/berth-detail-header.tsx`. +5. **cmdk wheel-scroll bug** — Radix Popover swallowed wheel events on + the country dropdown for macOS users. Added `onWheel` translator on + `CommandList` + `overscroll-contain`. Lights up country pickers in + Companies, Residential Clients, Clients, Yachts. + File: `src/components/ui/command.tsx`. +6. **Mobile "Columns" button hidden** — `ColumnPicker` is now + `hidden sm:inline-flex`. Mobile renders cards (no columns to + toggle). + File: `src/components/shared/column-picker.tsx`. +7. **Mobile kanban toggle hidden + auto-fallback** — Interest list + hides the table-vs-kanban toggle on small viewports and snaps + `viewMode` back to `'table'` if the user's persisted choice was + `'board'`. + File: `src/components/interests/interest-list.tsx`. +8. **Inbox entry removed from mobile More-sheet** — email/IMAP feature + is deferred (`sidebar.tsx` calls this out); the More-sheet entry was + a dead link. +9. **Website Analytics conditional** — desktop sidebar Insights section + AND mobile MoreSheet hide the Website Analytics nav when Umami + isn't configured for the port. Reuses `useUmamiActive()`. + Files: `src/components/layout/sidebar.tsx`, + `src/components/layout/mobile/more-sheet.tsx`. +10. **"Other" comm-channel UX hint** — when a contact's channel is + `'other'`, the inline `Label` field switches its label/placeholder + to "Specify" / "e.g. Telegram, Signal". + File: `src/components/clients/client-form.tsx:289-302`. +11. **End Membership wording** — renamed to "Remove from company" in + the company members tab dropdown. + File: `src/components/companies/company-members-tab.tsx:249`. +12. **Berth area filter → letter dropdown** — was free-text; now a + `` `{value,label}` objects. +4. **All berth dropdown fields → `` variant. +6. **Dimensional auto-conversion** — `EditableSpec` gained a + `linkedUnit: { field, multiplier }` prop. Saving the imperial value + also patches the metric column (× 0.3048). Applied to length, width, + draft, nominal boat size, water depth. +7. **Nominal boat size editable** — was read-only ``; now an + `` so editing ft auto-fills m. +8. **Tenure type editable** — was read-only; now an inline-edit Select + bound to the validator's `'permanent' | 'fixed_term'` set. Will be + replaced by the per-port configurable list once Wave 5 ships. + +### Wave 9 — interests + notifications + +1. **StageLegend popover** — small "Legend" button in the interest + list filter row decodes the colored stripes on each card to the + pipeline stage name. Stays in sync with `STAGE_DOT` automatically. + File: `src/components/interests/stage-legend.tsx`. +2. **Mobile kanban hidden** — see Wave 1. +3. **Notifications nav 404 fixed** — More-sheet entry pointed at + `/notifications` which had no `page.tsx`. Now points at + `/notifications/preferences` and is labeled "Notification + preferences" — real notifications come via the topbar bell. + File: `src/components/layout/mobile/more-sheet.tsx`. + +### Wave 10 — settings polish + +1. **Phone input upgraded** — user settings now uses the existing + shared `` (country flag dropdown + AsYouType formatter) + instead of a plain ``. Country state from the + page seeds the dropdown. + File: `src/components/settings/user-settings.tsx`. +2. **Timezone auto-detect** — covered in Wave 2. +3. **Dropdown widths match trigger** — covered in Wave 2. + +### Bonus — public berth feed wired to replace NocoDB as source of truth + +Triggered by user prompt "ensure we are properly wired up to replace +the NocoDB table as the source of truth for the berth map". + +**State before audit:** +- API endpoints existed (`/api/public/berths`, + `/api/public/berths/[mooringNumber]`) — wiring fine. +- `src/lib/services/public-berths.ts` mapped the response shape to + NocoDB-verbatim keys. +- Tests passed (`tests/unit/services/public-berths.test.ts`). +- **Map data was empty: 0 rows in `berth_map_data` against 234 berths + total (117 per port).** Without polygons the website map literally + has no shapes to render. + +**Action taken:** +- Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply + --port-slug port-nimara` (after a clean dry-run). Result: + 117 berths updated, 117 `berth_map_data` rows inserted. +- Spot-checked the public API: `GET /api/public/berths` returns the + correct shape with `Map Data` populated, byte-for-byte identical + to NocoDB for berth A1 (`path`, `x`, `y`, `transform`, `fontSize`). + +**Field-parity gaps still present** (see Wave Bonus pending below). + +### Misc UI polish + +- **Berth Documents tab explainer** — added a one-paragraph header + explaining it's the spec PDF, not deal documents (with a pointer + to the Interests tab for prospect-linked docs). + File: `src/components/berths/berth-documents-tab.tsx`. + +--- + +## 🟡 Pending — medium + +### Wave 4: currency formatting platform-wide + +- Build `` shared component (formatted display, raw + number value). Replace raw `` price spots in: + `berth-form.tsx` (price), `expense-form-dialog.tsx` (amount), + `invoices.tsx` (totals), client deal amounts on dossier / invoice. +- Currency selector dropdown on expense form (NocoDB has no expense + currency field, so source from a curated supported-currency list: + USD / EUR / GBP / CAD / AUD / CHF / JPY / …). Replace the free-text + 3-letter input. +- Sweep for `${currency} ${amount}` string concatenations and replace + with `Intl.NumberFormat`. + +### Wave 5: configurable enum infrastructure + +We have a `system_settings` table with composite PK `(key, port_id)` +and an `` admin page. Add a "Vocabularies" admin tab +that exposes per-port vocabularies. Suggested keys grouped by domain: + +- `interest_temperature_levels` — replaces the hardcoded "HOT" badge. + Pill is rendered in `src/components/interests/interest-card.tsx`. +- `berth_status_change_reasons` — list shown as quick-pick chips in + `` (see `berth-detail-header.tsx`). Tied to the + prospect-picker concept (see Wave 7 below). +- `berth_tenure_types` — replaces the static + `'permanent' | 'fixed_term'` validator union. Berths column is + `text`, so any value can land at the DB layer. +- `expense_categories` — current hardcoded list at + `src/lib/constants.ts:EXPENSE_CATEGORIES`. +- `document_types` — current hardcoded list at + `src/lib/constants.ts:DOCUMENT_TYPES`. +- `interest_outcome_statuses` — already exist in schema enum, could + be overridable. +- `berth_side_pontoon_options` / `berth_cleat_types` / + `berth_bollard_types` / `berth_access_options` — currently + hardcoded to NocoDB values. Worth making editable once a non-Port- + Nimara port appears with different infrastructure. + +**Open question (#1)**: see § Open Questions. + +### Wave 6: notes unification — aggregate-on-read + +User chose option 1 ("aggregate on read") from the brainstorm. The +`listForClientAggregated` pattern in `notes.service.ts` (lines +130–242) already unions a client's notes + interest notes + owned +yacht notes into a single feed with `source` metadata. + +Symmetric extensions to add: +- `listForYachtAggregated` — yacht own notes + owner client notes + + linked interest notes. +- `listForCompanyAggregated` — company own notes + owned yacht notes + + linked interest notes. +- `listForResidentialClientAggregated` — residential client notes + + residential interest notes. + +UI: +- `` should render the source-label badge + (already implemented for clients — copy the pattern). +- Convert single-textarea spots to entry-list pattern: the + Companies overview tab has a `notes` textarea (from + `companies.notes` text column) AND a Notes tab with the threaded + `companyNotes` table. Drop the textarea in favor of the threaded + feed only. Same for residential interests. +- Note for the schema fix-it list: `companyNotes` is missing + `updatedAt`. Service substitutes `createdAt` to keep the read shape + uniform — see `notes.service.ts:566`. Fix when convenient. + +### Wave 7: clients / yachts / companies misc + +Done in this session: +- **Yacht flag** → CountryCombobox (Wave 1). +- **End Membership** → "Remove from company" (Wave 1). +- **Berth Documents tab** explainer paragraph. + +Pending: +- **Status change modal — prospect picker**: when user changes berth + status to `under_offer` or `sold`, surface an interest/prospect + selector below the reason dropdown so the recorded reason can link + to a known deal. Tie into `interest_berths` so the link is + bidirectional. Depends on Wave 5 + (`berth_status_change_reasons` vocabulary). +- **Documents tagged with company** show up in main `/documents` view + with company tag — verify after the documents overhaul (Wave 11.B). + +### Wave 9 follow-up + +- **HOT/WARM/COLD admin-config** — covered by Wave 5 + (`interest_temperature_levels`). +- **Color-codes legend**: shipped as a popover. Optional polish: add + a one-time tooltip on first pageload so users discover it. + +### Wave 10 follow-up + +- **Photo upload picker bug**: Playwright captured a `[File chooser]` + modal when clicking "Upload photo," so the wiring works in headless + Chromium. User reported "doesn't open" on macOS — possibly a focus + / window issue or a content-blocking extension. Need a real-machine + repro to diagnose. The hidden `` + + `fileInputRef.current?.click()` wiring is at + `user-settings.tsx:247-258`. +- **Display name + first / last name fields** — current schema only + has `displayName`. Adding first/last requires a Drizzle migration on + `users` or `user_profiles` plus migration of existing data (split + on first space). **Open question (#3)**: see § Open Questions. +- **Notification preferences placement** — settings vs notifications + page. Today notification toggles live on the user-settings page; a + dedicated `/notifications/preferences` page also exists. **Open + question (#2)**: see § Open Questions. + +### Wave Bonus follow-up — public berth feed field parity + +Map data is now wired. Field gaps the website *might* consume but we +don't expose: + +| NocoDB field | Currently in PublicBerth? | DB has it? | Notes | +| --- | --- | --- | --- | +| `Price` | ❌ | ✅ `berths.price` | Pricing-public is a policy decision. **Open question (#4)** | +| `Berth Approved` | ❌ | ✅ `berths.berth_approved` | Boolean. Often used to gate "Sold" display | +| `Water Depth` | ❌ | ✅ `berths.water_depth` | Sometimes shown in tooltip | +| `Width Is Minimum` | ❌ | ✅ `berths.width_is_minimum` | Modifier for "Width" display | +| `Water Depth Is Minimum` | ❌ | ✅ `berths.water_depth_is_minimum` | ditto | +| `Length (Metric)` | ❌ | ✅ `berths.length_m` | Derivable. Website may consume | +| `Width (Metric)` | ❌ | ✅ `berths.width_m` | ditto | +| `Draft (Metric)` | ❌ | ✅ `berths.draft_m` | ditto | +| `Water Depth (Metric)` | ❌ | ✅ `berths.water_depth_m` | ditto | +| `Nominal Boat Size (Metric)` | ❌ | ✅ `berths.nominal_boat_size_m` | ditto | +| `CreatedAt` / `UpdatedAt` | ❌ | ✅ timestamps | Cache invalidation hints | +| `Interests` (count) | ❌ | derivable | Probably internal-only | +| `Interested Parties` (count) | ❌ | derivable | Probably internal-only | + +**Plan once questions are answered:** Add the chosen fields to +`PublicBerth` interface in `src/lib/services/public-berths.ts`, the +`toPublicBerth()` mapper, and the test fixtures. Trivial; gated only +by which fields the website actually uses. + +**Other public-feed concerns to flag**: +- **No archive flag**: when a berth is retired the public feed will + still serve it. Need a `berths.archived_at` column + filter on the + route. Plan §4.5 hinted at this. Not urgent. +- **CRM-edit drift vs re-imports**: now that reps can edit berth + fields (Wave 3), running the import script will skip-edited those + rows (`updated_at > last_imported_at`) — that's the right design, + but it means once cutover happens the website **must** call CRM + `/api/public/berths`, never NocoDB. Coordinate this in the website + repo. Useful guard already exists: `/api/public/health`. +- **Cache TTL: 5 min**: when a CRM rep marks a berth `sold`, the + public website serves "Available" for up to 5 minutes due to + `s-maxage=300`. Acceptable for marketing; bump if needed. +- **Health endpoint shape**: `/api/public/health` currently returns + `{status, timestamp}` but `CLAUDE.md` claims `{env, appUrl}`. One + of them is stale; the website may expect either shape. Not blocking + but worth aligning. + +--- + +## 🔴 Pending — large (group-discussion items, Wave 11) + +### A. Manual client form expansion + +User wants "New Client" to support assigning yachts / companies / +berths inline (without leaving the form), plus a mini-recommender for +picking a berth at create time. + +Scope: +- "Existing yacht / new yacht" picker. +- "Existing company / new company" picker. +- "Open an interest with this client" affordance that wires through + `interest_berths` and the recommender. +- Make sure all standard client modal fields (nationality / source / + preferred contact / timezone / tags) remain present. + +Multi-component composition with a lot of cross-entity plumbing. +Estimate fully before starting (likely 2–3 days). + +### B. Documents section overhaul + +User wants: +- Folders (create / delete / nested). +- Sort + filter (by date, type, owner). +- Wider file-type allowlist (PDF + Office + image is current; expand). +- "Documents in progress" filter (contracts / EOIs awaiting signature, + things uploaded but unparsed). +- Drop or rename the "Signature-based only" pill — confusing copy. +- "Expired" tab admin-configurable visibility. +- Type-filter dropdown reflects actual types in use (vs the full + hardcoded list). + +Refactor of `documents.service.ts` plus a new folders schema +(`document_folders` table with port-scoped tree). + +### C. Reports system + +User asked for: +- Defined report types (Pipeline summary / Revenue / Activity log / + Berth occupancy) with documented data shape per type. +- Test fixtures for visual QA. +- Admin "report templates" with field-level checkboxes letting an + admin compose a custom report shape (toggles for each available + data field). + +Infra exists (`/api/v1/reports`) but templates are stubs. A proper +templating system + per-template field selection adds a few days. + +### D. Receipts inline in expense PDF + +User confirmed: image receipts render inline beneath each expense row, +**and** PDF receipts also render inline (one page each). pdfme +(already used for EOI) handles both — inline images via the renderer, +PDF pages via `pdf-lib.copyPages`. Depends on Wave 8 expense form work. + +### E. Country / Nationality split on Client form + +Client schema has only `nationalityIso`. User wants: +- New `country_iso` column for *country of residence* (visible + / primary). +- Keep `nationality_iso` as an *optional* secondary field. + +Requires: +- Drizzle migration (`alter table clients add column country_iso text`). +- Migrate existing data: copy `nationality_iso → country_iso` for + every client (current value is more often country of residence in + practice). +- Update API validators (`clients.ts`). +- Update client form UI: primary "Country" CountryCombobox, secondary + collapsible "Nationality" row. +- Same for residential clients (parallel schema). + +### F. Inquiry triage (legacy spec carryover) + +Per project memory and the "deferred" list at the top of +`today-2026-05-08.md`: inquiry triage was explicitly deferred. Tied +into the inquiry routing settings (`inquiry_notification_recipients`, +`inquiry_contact_email`, `residential_notification_recipients` — +already in `system_settings`). Pick this back up when ready to +auto-classify website inquiries. + +### G. Per-port email branding + +Also in the deferred list. Templates and settings keys exist +(per memory note); the admin UI for editing per-port email branding +overrides remains. + +--- + +## ❓ Open questions for the user + +These are the questions to address in the next session before further +implementation. They're ordered by what unblocks the most work. + +1. **Vocabularies admin layout (Wave 5)** — single new + `/admin/vocabularies` page or nested under existing admin tabs? + **Recommendation: one new page, grouped by domain.** Easier to + discover, cleaner schema for the editor. +2. **Notification preferences placement (Wave 10)** — keep both the + user-settings notifications panel *and* `/notifications/preferences`, + or collapse to one? **Recommendation: collapse to user-settings + only**, since that's where every other personal preference lives. + Keep the dedicated route as a redirect for back-compat links. +3. **Display vs first / last name (Wave 10)** — worth the migration, + or keep single-field display name? **Recommendation: keep single + display name unless you'll be using first/last for invoicing, + greetings, or DocSign field-merging.** A migration is reversible + but never free. +4. **Public-feed `Price` exposure (Bonus)** — should `Price` be in the + public website feed? NocoDB exposed it. **Recommendation: yes if + it's already shown publicly today; no if the website redacts it.** + Easiest tell: open the public `/berths` page on the marketing site + and look for the price. +5. **Public-feed remaining fields (Bonus)** — add `Berth Approved`, + `Water Depth`, `Width Is Minimum`, `Water Depth Is Minimum`, and + metric variants to the PublicBerth payload? **Recommendation: yes + to all** for verbatim NocoDB parity. Trivial code change once + confirmed. +6. **Website cutover plan (Bonus)** — is the website already pointed + at CRM `/api/public/berths`, or still hitting NocoDB? Need a + coordinated DNS-style cutover (env var `CRM_PUBLIC_URL` per + `CLAUDE.md`). After cutover, do we (a) decommission the NocoDB + Berths table, (b) keep it read-only as a cold backup, or (c) + double-write for a transition window? **Recommendation: (b) for + 30 days, then (a).** +7. **Status change reason → prospect link flow (Wave 7)** — confirm + the desired shape. Does picking "Sold to existing prospect" force + the user to pick an interest from the list, and does it auto- + create an `interest_berths.is_primary=true` row if the chosen + interest isn't already linked to that berth? +8. **Trip label on expenses (Wave 8)** — define-events flow vs free + text with matched-receipts-on-PDF heuristic? Either is fine; pick + based on how reps actually batch trips. +9. **Documents folders (Wave 11.B)** — nest depth limit? Per-port + folders, or global per company? **Recommendation: per-port, + nested up to 3 levels** (matches typical document-management + depth and keeps the tree UI sane). +10. **Berth Documents tab function (Wave 1 carryover)** — confirm + the explainer paragraph is the right framing, or do you want a + "Linked deal documents" section that aggregates EOIs / contracts + / etc. from interests on this berth? **Recommendation: keep + current scope** (versioned spec PDF only). Aggregating deal docs + is a bigger feature with permission concerns. +11. **Mooring type re-import** — added `BERTH_MOORING_TYPES` to the + UI. The seed JSON snapshot may not have included Mooring Type for + every berth (NocoDB does). Confirmation that the live import we + ran today populated mooring_type for all 117 records. (Spot- + checked: yes — the SQL traces show `mooring_type = "Side Pier / + Med Mooring"` etc.) + +--- + +## File-pointer cheat sheet + +### Berth-related + +| Concern | File(s) | +| --- | --- | +| Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) | +| Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` | +| Berth detail inline edit | `src/components/berths/berth-tabs.tsx` | +| Berth modal form | `src/components/berths/berth-form.tsx` | +| Berth area filter | `src/components/berths/berth-filters.tsx` | +| Berth detail header / status modal | `src/components/berths/berth-detail-header.tsx:90` | +| Berth Documents tab | `src/components/berths/berth-documents-tab.tsx` | +| Berth list query + sort | `src/lib/services/berths.service.ts:25-140` | +| Berth import script | `scripts/import-berths-from-nocodb.ts` | +| Berth import service / parsers | `src/lib/services/berth-import.ts` | +| Public berth API route | `src/app/api/public/berths/route.ts` | +| Public berth single route | `src/app/api/public/berths/[mooringNumber]/route.ts` | +| Public berth mapper | `src/lib/services/public-berths.ts` | +| Public berth tests | `tests/unit/services/public-berths.test.ts` | +| Berth seed snapshot | `src/lib/db/seed-data/berths.json` | +| Berth schema | `src/lib/db/schema/berths.ts` (incl. `berthMapData`) | + +### Other domains + +| Concern | File(s) | +| --- | --- | +| Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` | +| Mobile kanban toggle / fallback | `src/components/interests/interest-list.tsx` | +| Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` | +| Phone input | `src/components/shared/phone-input.tsx` | +| Country combobox + scroll patch | `src/components/shared/country-combobox.tsx` + `src/components/ui/command.tsx` | +| Sidebar Umami gate | `src/components/layout/sidebar.tsx` (search `umamiRequired`) | +| Mobile More-sheet | `src/components/layout/mobile/more-sheet.tsx` | +| Notes service (aggregate-on-read) | `src/lib/services/notes.service.ts:130-242` | +| Notes UI | `src/components/shared/notes-list.tsx` | +| Settings manager (admin) | `src/components/admin/settings/settings-manager.tsx` | +| User settings page | `src/components/settings/user-settings.tsx` | +| Status change dialog | `src/components/berths/berth-detail-header.tsx:90` | +| Companies members tab | `src/components/companies/company-members-tab.tsx` | +| Yacht form | `src/components/yachts/yacht-form.tsx` | +| Client form | `src/components/clients/client-form.tsx` | + +### Infrastructure + +| Concern | File(s) | +| --- | --- | +| Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` | +| `system_settings` table | `src/lib/db/schema/system.ts:128-147` | +| Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` | +| Body parsing (always use `parseBody`) | `src/lib/api/route-helpers.ts` | +| Storage backend abstraction | `src/lib/storage/` | +| Logger (pino) | `src/lib/logger.ts` | + +--- + +## Resuming in a fresh session + +When you open a new chat, paste this **prompt** to pick up where this +session ended: + +``` +I'm resuming the 2026-05-08 visual audit. Read +docs/AUDIT-FOLLOWUPS.md first — it has every completed item, every +pending item, and every open question. Then: + +1. Skim the "Quick status snapshot" table at the top so you know + what's done. +2. Read the "Open questions for the user" list and ask me question + #N where N is whichever I'll answer first this turn. +3. Wait for my answers; don't start implementing until I confirm. + +Key invariants: + - Notes unification model: aggregate-on-read. + - Berth dropdown values: NocoDB SingleSelect canon, sourced from + src/lib/constants.ts (BERTH_*_OPTIONS / _TYPES). + - Power Capacity & Voltage stay numeric inputs; Bow Facing is a + constrained 4-value dropdown despite being SingleLineText in + NocoDB. + - linkedUnit on EditableSpec auto-fills the metric column on save. + - system_settings (key, port_id) is the configuration pattern. + - NocoDB MCP is connected via ~/.claude.json — Berths schema + + records can be pulled live. + - Public berth feed (/api/public/berths) now serves Map Data; 117 + berth_map_data rows backfilled in this session. + - Tests: 1185/1185 passing; tsc clean. + +The git working tree has 23 modified files + 2 new (no commits yet). +Don't commit anything until I say so. +``` + +### Resume commands (cheat sheet) + +```bash +cd /Users/matt/Repos/new-pn-crm +pnpm dev # Turbopack dev (~1s boot) + +# Tests +pnpm exec vitest run # Unit + integration (~7s) +pnpm exec tsc --noEmit # Type check +pnpm exec playwright test --project=smoke # Smoke (~10min) + +# NocoDB import (for new berth pulls) +pnpm tsx scripts/import-berths-from-nocodb.ts --dry-run --port-slug port-nimara +pnpm tsx scripts/import-berths-from-nocodb.ts --apply --port-slug port-nimara + +# DB inspect +PGPASSWORD=changeme psql -h localhost -p 5434 -U crm -d port_nimara_crm + +# Public-feed sanity check +curl -s http://localhost:3000/api/public/berths | jq '.pageInfo' +curl -s http://localhost:3000/api/public/berths/A1 | jq '.' +``` + +### Verification checklist before committing this session's work + +- [ ] `pnpm exec vitest run` — 1185/1185 pass. +- [ ] `pnpm exec tsc --noEmit` — clean. +- [ ] `pnpm exec playwright test --project=smoke` — passes. +- [ ] Manual: open `/port-nimara/berths`, confirm sort is A1, A2, + A3 … A10, A11 (not lex order). +- [ ] Manual: open a berth detail page, confirm the dock chip reads + e.g. "A Dock", and the Bow Facing / Side Pontoon / Cleat fields + render as ``. +- [ ] Manual: pick a country in the user-settings page and confirm + timezone auto-fills if empty; also confirm the country dropdown + scrolls with mousewheel on macOS. +- [ ] Manual: check the mobile More-sheet has no "Inbox" entry, and + "Notification preferences" deep-links to the correct page. +- [ ] Manual: open `/api/public/berths` in the browser and search for + `Map Data` in the response — every row should have it. + +--- + +## Misc tracking notes + +- **Backups**: `~/.claude.json.bak.` exists from when the + NocoDB MCP was added. Delete after a session or two if everything's + stable. +- **Turbopack flip**: `next.config.ts` has no custom `webpack()` hook + so reverting `pnpm dev` to plain `next dev` is one line if needed. + Default is now `--turbopack`. +- **Database integrity follow-ups** (separate audit, dated 20:42): + 11 findings (5 critical / 6 important). Logged in + `.remember/today-2026-05-08.md`. Cross-cuts the work here in two + spots: (1) `upsertInterestBerth` race could affect the berth + recommender once it's wired into the manual client form (Wave 11.A); + (2) `system_settings` `ON DELETE NO ACTION` will need addressing + before any port-deletion flow ships.