2026-05-09 04:11:24 +02:00
|
|
|
|
# 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.**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-05-09 18:38:46 +02:00
|
|
|
|
## Quick status snapshot — 2026-05-09 (post-execution)
|
|
|
|
|
|
|
|
|
|
|
|
| Wave | Topic | Status |
|
|
|
|
|
|
| --------- | ------------------------------------------ | ----------------------------------------------------------------------- |
|
|
|
|
|
|
| 1 | Small confident fixes | ✅ Done |
|
|
|
|
|
|
| 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split still deferred — see Wave 11.E) |
|
|
|
|
|
|
| 3 | Berth field overhaul (NocoDB enums) | ✅ Done |
|
|
|
|
|
|
| 4 | Currency platform-wide | ✅ Done |
|
|
|
|
|
|
| 5 | Configurable enums (admin Vocabularies) | ✅ Admin page + read endpoint shipped; consumer wiring is owed |
|
|
|
|
|
|
| 6 | Notes unification (aggregate-on-read) | ✅ Done — yacht / company / residential aggregators + UI |
|
|
|
|
|
|
| 7 | Clients / yachts / companies misc | ✅ Status-link flow done; client form expansion still large (Wave 11.A) |
|
|
|
|
|
|
| 8 | Expenses revisit | ✅ Done — trip-label combobox (free text + past suggestions) |
|
|
|
|
|
|
| 9 | Interests + notifications | ✅ Done |
|
|
|
|
|
|
| 10 | Settings polish | ✅ Done — first/last name + collapse notif prefs |
|
|
|
|
|
|
| 11.A | Manual client form expansion | 🔴 Not started (large) |
|
|
|
|
|
|
| 11.B | Documents folders (unlimited nesting) | 🔴 Not started — needs deep design (sidebar tree + breadcrumb) |
|
|
|
|
|
|
| 11.C | Reports system + templates | 🔴 Not started |
|
|
|
|
|
|
| 11.D | Receipts inline in expense PDF | 🔴 Not started |
|
|
|
|
|
|
| 11.E | Country / Nationality split on Client form | 🔴 Not started |
|
|
|
|
|
|
| 11.F | Inquiry triage | 🔴 Deferred |
|
|
|
|
|
|
| 11.G | Per-port email branding admin UI | 🔴 Deferred |
|
|
|
|
|
|
| **Bonus** | **Public berth feed (website map)** | ✅ Parity fields shipped; cutover deferred (see runbook) |
|
|
|
|
|
|
| **Bonus** | **Website cutover runbook** | ✅ Doc shipped (`docs/website-cutover-runbook.md`); execution deferred |
|
|
|
|
|
|
| **Bonus** | **Berth Documents tab → Spec + Deal** | ✅ Done |
|
|
|
|
|
|
|
|
|
|
|
|
Test status: `pnpm exec vitest run` → **1187/1187 pass**.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
TS check: `pnpm exec tsc --noEmit` → **clean**.
|
2026-05-09 18:38:46 +02:00
|
|
|
|
Git: 9 commits this session (Waves 4-10 + admin Vocabularies + status-change link + Berth Documents tab split + decisions log).
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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 `<SettingsManager>` 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`.
|
2026-05-09 04:11:54 +02:00
|
|
|
|
3. **Per-dock color stripe on mobile berth cards** — was the _status_
|
2026-05-09 04:11:24 +02:00
|
|
|
|
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
|
2026-05-09 04:11:54 +02:00
|
|
|
|
`'other'`, the inline `Label` field switches its label/placeholder
|
|
|
|
|
|
to "Specify" / "e.g. Telegram, Signal".
|
|
|
|
|
|
File: `src/components/clients/client-form.tsx:289-302`.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
11. **End Membership wording** — renamed to "Remove from company" in
|
2026-05-09 04:11:54 +02:00
|
|
|
|
the company members tab dropdown.
|
|
|
|
|
|
File: `src/components/companies/company-members-tab.tsx:249`.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
12. **Berth area filter → letter dropdown** — was free-text; now a
|
2026-05-09 04:11:54 +02:00
|
|
|
|
`<Select>` constrained to `A / B / C / D / E`. Label changed to
|
|
|
|
|
|
"Dock" to match how the user refers to it.
|
|
|
|
|
|
File: `src/components/berths/berth-filters.tsx`.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
13. **Yacht flag → CountryCombobox** — was a free-text 2-letter input
|
2026-05-09 04:11:54 +02:00
|
|
|
|
(`placeholder="e.g. MT"`); now uses the same country picker as
|
|
|
|
|
|
client / residential.
|
|
|
|
|
|
File: `src/components/yachts/yacht-form.tsx`.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
### Wave 2 — country dropdown unification
|
|
|
|
|
|
|
|
|
|
|
|
1. **cmdk wheel-scroll** — covered in Wave 1 (single shared command).
|
|
|
|
|
|
2. **Country → timezone auto-set** in client form: when nationality is
|
|
|
|
|
|
picked and timezone empty, the primary IANA zone is pre-filled. Skips
|
|
|
|
|
|
when the user already chose a zone explicitly.
|
|
|
|
|
|
File: `src/components/clients/client-form.tsx` (look for
|
|
|
|
|
|
`primaryTimezoneFor`).
|
|
|
|
|
|
3. **Browser-detected timezone fallback** in user settings: timezone
|
|
|
|
|
|
pre-populates from `Intl.DateTimeFormat().resolvedOptions().timeZone`
|
|
|
|
|
|
on first load (was empty before).
|
|
|
|
|
|
File: `src/components/settings/user-settings.tsx`.
|
|
|
|
|
|
4. **Country → timezone auto-fill** also fires in user settings when
|
|
|
|
|
|
the country changes with no zone set.
|
|
|
|
|
|
5. **Dropdown widths match trigger** — `CountryCombobox` and
|
|
|
|
|
|
`TimezoneCombobox` popover content set to
|
|
|
|
|
|
`w-[var(--radix-popper-anchor-width)]` with sensible `min-w-*`
|
|
|
|
|
|
floors so wide triggers get wide popovers.
|
|
|
|
|
|
6. **DEFERRED: country/nationality split** on the client form — needs
|
|
|
|
|
|
a Drizzle migration (`alter table clients add column country_iso
|
2026-05-09 04:11:54 +02:00
|
|
|
|
text`) plus a copy-on-migrate of existing `nationality_iso` values.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
See § Wave 11 / pending — large.
|
|
|
|
|
|
|
|
|
|
|
|
### Wave 3 — berth field overhaul (NocoDB enums)
|
|
|
|
|
|
|
|
|
|
|
|
1. **Live NocoDB pull via MCP** — confirmed canonical SingleSelect
|
|
|
|
|
|
choices for: Side Pontoon (10 values), Mooring Type (5),
|
|
|
|
|
|
Cleat Type (2), Cleat Capacity (2), Bollard Type (2),
|
|
|
|
|
|
Bollard Capacity (2), Access (5), Area (A–E). Power Capacity and
|
|
|
|
|
|
Voltage are `Number` fields (not enums). Bow Facing is
|
|
|
|
|
|
`SingleLineText` (we still use a 4-value dropdown for UX).
|
|
|
|
|
|
2. **`BERTH_BOW_FACING_OPTIONS`** added to `src/lib/constants.ts`
|
|
|
|
|
|
alongside the existing `BERTH_*_OPTIONS` constants.
|
|
|
|
|
|
3. **`toSelectOptions()` helper** added to `src/lib/constants.ts` for
|
|
|
|
|
|
mapping readonly tuples → shadcn `<Select>` `{value,label}` objects.
|
|
|
|
|
|
4. **All berth dropdown fields → `<Select>`** in both the modal form
|
|
|
|
|
|
(`berth-form.tsx`) and the inline-edit detail tabs
|
|
|
|
|
|
(`berth-tabs.tsx`). Bow facing / side pontoon / mooring type /
|
|
|
|
|
|
access / cleat type / cleat capacity / bollard type / bollard
|
|
|
|
|
|
capacity / area / tenure type.
|
|
|
|
|
|
5. **Inline-edit `EditableSpec`** in `berth-tabs.tsx` now supports
|
|
|
|
|
|
`selectOptions: readonly string[]` to render a `<Select>` 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 `<SpecRow>`; now an
|
|
|
|
|
|
`<EditableSpec numeric linkedUnit>` 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 `<PhoneInput>` (country flag dropdown + AsYouType formatter)
|
|
|
|
|
|
instead of a plain `<Input type="tel">`. 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:**
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- 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:**
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply
|
2026-05-09 04:11:54 +02:00
|
|
|
|
--port-slug port-nimara` (after a clean dry-run). Result:
|
2026-05-09 04:11:24 +02:00
|
|
|
|
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 `<CurrencyInput>` shared component (formatted display, raw
|
|
|
|
|
|
number value). Replace raw `<Input type="number">` 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 `<SettingsManager>` 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
|
|
|
|
|
|
`<StatusChangeDialog>` (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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- `listForYachtAggregated` — yacht own notes + owner client notes
|
2026-05-09 04:11:54 +02:00
|
|
|
|
- linked interest notes.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- `listForCompanyAggregated` — company own notes + owned yacht notes
|
2026-05-09 04:11:54 +02:00
|
|
|
|
- linked interest notes.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- `listForResidentialClientAggregated` — residential client notes
|
2026-05-09 04:11:54 +02:00
|
|
|
|
- residential interest notes.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
UI:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- `<NotesList entityType="…">` 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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- **Yacht flag** → CountryCombobox (Wave 1).
|
|
|
|
|
|
- **End Membership** → "Remove from company" (Wave 1).
|
|
|
|
|
|
- **Berth Documents tab** explainer paragraph.
|
|
|
|
|
|
|
|
|
|
|
|
Pending:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- **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 `<input type="file" ref={fileInputRef}>`
|
2026-05-09 04:11:54 +02:00
|
|
|
|
- `fileInputRef.current?.click()` wiring is at
|
|
|
|
|
|
`user-settings.tsx:247-258`.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- **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
|
|
|
|
|
|
|
2026-05-09 04:11:54 +02:00
|
|
|
|
Map data is now wired. Field gaps the website _might_ consume but we
|
2026-05-09 04:11:24 +02:00
|
|
|
|
don't expose:
|
|
|
|
|
|
|
2026-05-09 04:11:54 +02:00
|
|
|
|
| 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 |
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
**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**:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- **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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- "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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- 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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- 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:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
|
|
|
|
|
- New `country_iso` column for _country of residence_ (visible
|
2026-05-09 04:11:24 +02:00
|
|
|
|
/ primary).
|
2026-05-09 04:11:54 +02:00
|
|
|
|
- Keep `nationality_iso` as an _optional_ secondary field.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
Requires:
|
2026-05-09 04:11:54 +02:00
|
|
|
|
|
2026-05-09 04:11:24 +02:00
|
|
|
|
- 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.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-05-09 18:34:59 +02:00
|
|
|
|
## ✅ Decisions log — 2026-05-09
|
|
|
|
|
|
|
|
|
|
|
|
All 11 open questions answered. Implementation implications inline.
|
|
|
|
|
|
|
|
|
|
|
|
1. **Vocabularies admin layout (Wave 5)** → **New `/admin/vocabularies`
|
|
|
|
|
|
page, grouped by domain, admin-only.** User considered exposing to
|
|
|
|
|
|
non-admins (since reps use them daily) but settled on admin-only as
|
|
|
|
|
|
the safer default for now. Implementation: new top-level admin
|
|
|
|
|
|
route + page, reuse `system_settings` `(key, port_id)` composite
|
|
|
|
|
|
PK. Each vocabulary key gets its own card section (interest temps,
|
|
|
|
|
|
status-change reasons, tenure types, expense categories, document
|
|
|
|
|
|
types, etc.).
|
|
|
|
|
|
2. **Notification preferences placement (Wave 10)** → **Collapse to
|
|
|
|
|
|
user-settings only.** Keep `/notifications/preferences` as a
|
|
|
|
|
|
server-side redirect to the user-settings notifications panel for
|
|
|
|
|
|
back-compat links.
|
|
|
|
|
|
3. **Display name vs first/last (Wave 10)** → **Add `first_name` and
|
|
|
|
|
|
`last_name` columns.** Don't worry about migrations during dev (we
|
|
|
|
|
|
can iterate freely), but write the migration carefully so it
|
|
|
|
|
|
applies cleanly when we eventually deploy. Keep `display_name` as
|
|
|
|
|
|
a derived/optional override.
|
|
|
|
|
|
4. **Public-feed `Price` exposure (Bonus)** → **No — keep Price
|
|
|
|
|
|
internal.** Don't add to PublicBerth payload.
|
|
|
|
|
|
5. **Public-feed remaining fields (Bonus)** → **Yes, add all.** Add
|
|
|
|
|
|
Berth Approved, Water Depth, Width Is Minimum, Water Depth Is
|
|
|
|
|
|
Minimum, all four metric variants, plus CreatedAt/UpdatedAt to
|
|
|
|
|
|
PublicBerth + mapper + tests. User noted "not sure if we'll use
|
|
|
|
|
|
all of them but best to keep them in" — verbatim NocoDB parity.
|
|
|
|
|
|
6. **Website cutover plan (Bonus)** → **Double-write transition
|
|
|
|
|
|
window.** Keep both feeds live, write to both for the transition
|
|
|
|
|
|
period, then decommission NocoDB. Coordinate with website repo
|
|
|
|
|
|
(`CRM_PUBLIC_URL`).
|
|
|
|
|
|
7. **Status-change modal → prospect link (Wave 7)** → **Force
|
|
|
|
|
|
interest pick + auto-create primary `interest_berths` row.**
|
|
|
|
|
|
When status moves to `under_offer` or `sold`, the modal surfaces
|
|
|
|
|
|
an interest selector below the reason dropdown. Picking an
|
|
|
|
|
|
interest creates an `interest_berths` row with `is_primary=true`
|
|
|
|
|
|
if one doesn't already exist for that pair. Depends on Wave 5
|
|
|
|
|
|
`berth_status_change_reasons` vocabulary.
|
|
|
|
|
|
8. **Trip label on expenses (Wave 8)** → **Combobox: free-text on
|
|
|
|
|
|
first entry, dropdown of existing labels on subsequent entries.**
|
|
|
|
|
|
No new entity. Source the dropdown from
|
|
|
|
|
|
`SELECT DISTINCT trip_label FROM expenses WHERE port_id=?`
|
|
|
|
|
|
ordered by recency. UI is a `<Combobox>` with "Create
|
|
|
|
|
|
'<typed value>'" affordance.
|
|
|
|
|
|
9. **Documents folders (Wave 11.B)** → **Per-port, unlimited
|
|
|
|
|
|
nesting depth — but render carefully.** User wants flexibility;
|
|
|
|
|
|
we owe a UI design that handles deep trees gracefully (likely
|
|
|
|
|
|
collapsed-by-default with a breadcrumb header inside the folder
|
|
|
|
|
|
view rather than always-expanded sidebar tree).
|
|
|
|
|
|
10. **Berth Documents tab (Wave 1 carryover)** → **Split into two
|
|
|
|
|
|
tabs: "Spec" (versioned spec PDF) and "Deal Documents"
|
|
|
|
|
|
(aggregated EOIs/contracts from interests on this berth).**
|
|
|
|
|
|
Permission scoping: deal docs only show entries the viewer can
|
|
|
|
|
|
already see via the linked interest.
|
|
|
|
|
|
11. **Mooring type re-import** → ✅ **Verified.** All 117 records
|
|
|
|
|
|
have `mooring_type` populated post-import (e.g. "Side Pier / Med
|
|
|
|
|
|
Mooring"). No action needed.
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## File-pointer cheat sheet
|
|
|
|
|
|
|
|
|
|
|
|
### Berth-related
|
|
|
|
|
|
|
2026-05-09 04:11:54 +02:00
|
|
|
|
| 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`) |
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
### Other domains
|
|
|
|
|
|
|
2026-05-09 04:11:54 +02:00
|
|
|
|
| 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` |
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
### Infrastructure
|
|
|
|
|
|
|
2026-05-09 04:11:54 +02:00
|
|
|
|
| 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` |
|
2026-05-09 04:11:24 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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 `<Select>` not `<Input>`.
|
|
|
|
|
|
- [ ] 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.<timestamp>` 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.
|