chore(format): apply prettier auto-formatting

Pre-commit hook reformatted these files after the substantive commits.
No semantic changes — markdown table alignment, list indentation, and
emphasis style normalisation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 04:11:54 +02:00
parent aad514a3bd
commit 502455ac04
6 changed files with 165 additions and 161 deletions

View File

@@ -16,7 +16,7 @@ message order where possible.
## Quick status snapshot — 2026-05-08 23:00 ## Quick status snapshot — 2026-05-08 23:00
| Wave | Topic | Status | | Wave | Topic | Status |
| --- | --- | --- | | --------- | ------------------------------------------ | -------------------------------------------------------------------------- |
| 1 | Small confident fixes | ✅ Done | | 1 | Small confident fixes | ✅ Done |
| 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split deferred) | | 2 | Country dropdown unification + cmdk scroll | ✅ Done (country/nationality split deferred) |
| 3 | Berth field overhaul (NocoDB enums) | ✅ Done | | 3 | Berth field overhaul (NocoDB enums) | ✅ Done |
@@ -83,7 +83,7 @@ Git: 23 files modified, 2 new files (no commits yet).
below (pipeline funnel, occupancy timeline, revenue breakdown, below (pipeline funnel, occupancy timeline, revenue breakdown,
lead source) and the activity feed remain. lead source) and the activity feed remain.
File: `src/components/dashboard/dashboard-shell.tsx`. File: `src/components/dashboard/dashboard-shell.tsx`.
3. **Per-dock color stripe on mobile berth cards** — was the *status* 3. **Per-dock color stripe on mobile berth cards** — was the _status_
color, which made every same-dock berth different. Now uses color, which made every same-dock berth different. Now uses
`mooringLetterDot()` so the stripe groups by dock letter; status `mooringLetterDot()` so the stripe groups by dock letter; status
conveyed by the existing pill below. conveyed by the existing pill below.
@@ -210,6 +210,7 @@ Triggered by user prompt "ensure we are properly wired up to replace
the NocoDB table as the source of truth for the berth map". the NocoDB table as the source of truth for the berth map".
**State before audit:** **State before audit:**
- API endpoints existed (`/api/public/berths`, - API endpoints existed (`/api/public/berths`,
`/api/public/berths/[mooringNumber]`) — wiring fine. `/api/public/berths/[mooringNumber]`) — wiring fine.
- `src/lib/services/public-berths.ts` mapped the response shape to - `src/lib/services/public-berths.ts` mapped the response shape to
@@ -220,6 +221,7 @@ the NocoDB table as the source of truth for the berth map".
has no shapes to render. has no shapes to render.
**Action taken:** **Action taken:**
- Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply - Ran `pnpm tsx scripts/import-berths-from-nocodb.ts --apply
--port-slug port-nimara` (after a clean dry-run). Result: --port-slug port-nimara` (after a clean dry-run). Result:
117 berths updated, 117 `berth_map_data` rows inserted. 117 berths updated, 117 `berth_map_data` rows inserted.
@@ -288,14 +290,16 @@ User chose option 1 ("aggregate on read") from the brainstorm. The
yacht notes into a single feed with `source` metadata. yacht notes into a single feed with `source` metadata.
Symmetric extensions to add: Symmetric extensions to add:
- `listForYachtAggregated` — yacht own notes + owner client notes - `listForYachtAggregated` — yacht own notes + owner client notes
+ linked interest notes. - linked interest notes.
- `listForCompanyAggregated` — company own notes + owned yacht notes - `listForCompanyAggregated` — company own notes + owned yacht notes
+ linked interest notes. - linked interest notes.
- `listForResidentialClientAggregated` — residential client notes - `listForResidentialClientAggregated` — residential client notes
+ residential interest notes. - residential interest notes.
UI: UI:
- `<NotesList entityType="…">` should render the source-label badge - `<NotesList entityType="…">` should render the source-label badge
(already implemented for clients — copy the pattern). (already implemented for clients — copy the pattern).
- Convert single-textarea spots to entry-list pattern: the - Convert single-textarea spots to entry-list pattern: the
@@ -310,11 +314,13 @@ UI:
### Wave 7: clients / yachts / companies misc ### Wave 7: clients / yachts / companies misc
Done in this session: Done in this session:
- **Yacht flag** → CountryCombobox (Wave 1). - **Yacht flag** → CountryCombobox (Wave 1).
- **End Membership** → "Remove from company" (Wave 1). - **End Membership** → "Remove from company" (Wave 1).
- **Berth Documents tab** explainer paragraph. - **Berth Documents tab** explainer paragraph.
Pending: Pending:
- **Status change modal — prospect picker**: when user changes berth - **Status change modal — prospect picker**: when user changes berth
status to `under_offer` or `sold`, surface an interest/prospect status to `under_offer` or `sold`, surface an interest/prospect
selector below the reason dropdown so the recorded reason can link selector below the reason dropdown so the recorded reason can link
@@ -338,7 +344,7 @@ Pending:
Chromium. User reported "doesn't open" on macOS — possibly a focus Chromium. User reported "doesn't open" on macOS — possibly a focus
/ window issue or a content-blocking extension. Need a real-machine / window issue or a content-blocking extension. Need a real-machine
repro to diagnose. The hidden `<input type="file" ref={fileInputRef}>` repro to diagnose. The hidden `<input type="file" ref={fileInputRef}>`
+ `fileInputRef.current?.click()` wiring is at - `fileInputRef.current?.click()` wiring is at
`user-settings.tsx:247-258`. `user-settings.tsx:247-258`.
- **Display name + first / last name fields** — current schema only - **Display name + first / last name fields** — current schema only
has `displayName`. Adding first/last requires a Drizzle migration on has `displayName`. Adding first/last requires a Drizzle migration on
@@ -351,11 +357,11 @@ Pending:
### Wave Bonus follow-up — public berth feed field parity ### Wave Bonus follow-up — public berth feed field parity
Map data is now wired. Field gaps the website *might* consume but we Map data is now wired. Field gaps the website _might_ consume but we
don't expose: don't expose:
| NocoDB field | Currently in PublicBerth? | DB has it? | Notes | | NocoDB field | Currently in PublicBerth? | DB has it? | Notes |
| --- | --- | --- | --- | | ---------------------------- | ------------------------- | ---------------------------------- | ----------------------------------------------------------- |
| `Price` | ❌ | ✅ `berths.price` | Pricing-public is a policy decision. **Open question (#4)** | | `Price` | ❌ | ✅ `berths.price` | Pricing-public is a policy decision. **Open question (#4)** |
| `Berth Approved` | ❌ | ✅ `berths.berth_approved` | Boolean. Often used to gate "Sold" display | | `Berth Approved` | ❌ | ✅ `berths.berth_approved` | Boolean. Often used to gate "Sold" display |
| `Water Depth` | ❌ | ✅ `berths.water_depth` | Sometimes shown in tooltip | | `Water Depth` | ❌ | ✅ `berths.water_depth` | Sometimes shown in tooltip |
@@ -376,6 +382,7 @@ don't expose:
by which fields the website actually uses. by which fields the website actually uses.
**Other public-feed concerns to flag**: **Other public-feed concerns to flag**:
- **No archive flag**: when a berth is retired the public feed will - **No archive flag**: when a berth is retired the public feed will
still serve it. Need a `berths.archived_at` column + filter on the still serve it. Need a `berths.archived_at` column + filter on the
route. Plan §4.5 hinted at this. Not urgent. route. Plan §4.5 hinted at this. Not urgent.
@@ -404,6 +411,7 @@ berths inline (without leaving the form), plus a mini-recommender for
picking a berth at create time. picking a berth at create time.
Scope: Scope:
- "Existing yacht / new yacht" picker. - "Existing yacht / new yacht" picker.
- "Existing company / new company" picker. - "Existing company / new company" picker.
- "Open an interest with this client" affordance that wires through - "Open an interest with this client" affordance that wires through
@@ -417,6 +425,7 @@ Estimate fully before starting (likely 23 days).
### B. Documents section overhaul ### B. Documents section overhaul
User wants: User wants:
- Folders (create / delete / nested). - Folders (create / delete / nested).
- Sort + filter (by date, type, owner). - Sort + filter (by date, type, owner).
- Wider file-type allowlist (PDF + Office + image is current; expand). - Wider file-type allowlist (PDF + Office + image is current; expand).
@@ -433,6 +442,7 @@ Refactor of `documents.service.ts` plus a new folders schema
### C. Reports system ### C. Reports system
User asked for: User asked for:
- Defined report types (Pipeline summary / Revenue / Activity log / - Defined report types (Pipeline summary / Revenue / Activity log /
Berth occupancy) with documented data shape per type. Berth occupancy) with documented data shape per type.
- Test fixtures for visual QA. - Test fixtures for visual QA.
@@ -453,11 +463,13 @@ PDF pages via `pdf-lib.copyPages`. Depends on Wave 8 expense form work.
### E. Country / Nationality split on Client form ### E. Country / Nationality split on Client form
Client schema has only `nationalityIso`. User wants: Client schema has only `nationalityIso`. User wants:
- New `country_iso` column for *country of residence* (visible
- New `country_iso` column for _country of residence_ (visible
/ primary). / primary).
- Keep `nationality_iso` as an *optional* secondary field. - Keep `nationality_iso` as an _optional_ secondary field.
Requires: Requires:
- Drizzle migration (`alter table clients add column country_iso text`). - Drizzle migration (`alter table clients add column country_iso text`).
- Migrate existing data: copy `nationality_iso → country_iso` for - Migrate existing data: copy `nationality_iso → country_iso` for
every client (current value is more often country of residence in every client (current value is more often country of residence in
@@ -494,7 +506,7 @@ implementation. They're ordered by what unblocks the most work.
**Recommendation: one new page, grouped by domain.** Easier to **Recommendation: one new page, grouped by domain.** Easier to
discover, cleaner schema for the editor. discover, cleaner schema for the editor.
2. **Notification preferences placement (Wave 10)** — keep both the 2. **Notification preferences placement (Wave 10)** — keep both the
user-settings notifications panel *and* `/notifications/preferences`, user-settings notifications panel _and_ `/notifications/preferences`,
or collapse to one? **Recommendation: collapse to user-settings or collapse to one? **Recommendation: collapse to user-settings
only**, since that's where every other personal preference lives. only**, since that's where every other personal preference lives.
Keep the dedicated route as a redirect for back-compat links. Keep the dedicated route as a redirect for back-compat links.
@@ -552,7 +564,7 @@ implementation. They're ordered by what unblocks the most work.
### Berth-related ### Berth-related
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | ---------------------------------- | ---------------------------------------------------- |
| Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) | | Canonical berth enums | `src/lib/constants.ts` (search `BERTH_`) |
| Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` | | Berth list ordering SQL | `src/lib/services/berths.service.ts:69-72` |
| Berth detail inline edit | `src/components/berths/berth-tabs.tsx` | | Berth detail inline edit | `src/components/berths/berth-tabs.tsx` |
@@ -573,7 +585,7 @@ implementation. They're ordered by what unblocks the most work.
### Other domains ### Other domains
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | --------------------------------- | -------------------------------------------------------------------------------------- |
| Interest stage colors / legend | `src/components/interests/stage-legend.tsx` + `src/lib/constants.ts:STAGE_DOT` | | 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` | | 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` | | Country / timezone autoset | `src/components/clients/client-form.tsx` + `src/components/settings/user-settings.tsx` |
@@ -593,7 +605,7 @@ implementation. They're ordered by what unblocks the most work.
### Infrastructure ### Infrastructure
| Concern | File(s) | | Concern | File(s) |
| --- | --- | | ------------------------------------------- | --------------------------------------------- |
| Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` | | Drizzle config / migrations | `drizzle.config.ts`, `src/lib/db/migrations/` |
| `system_settings` table | `src/lib/db/schema/system.ts:128-147` | | `system_settings` table | `src/lib/db/schema/system.ts:128-147` |
| Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` | | Permissions / `withAuth` / `withPermission` | `src/lib/api/helpers.ts` |

View File

@@ -163,10 +163,9 @@ export function BerthDocumentsTab({ berthId }: { berthId: string }) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. Berth-spec PDF: the dimensional drawing or surveyor sheet for this slip. Versioned so a
Versioned so a misparse can be rolled back. For documents tied to a misparse can be rolled back. For documents tied to a prospect on this berth (EOI, contract,
prospect on this berth (EOI, contract, etc.), open the matching deal etc.), open the matching deal in the Interests tab.
in the Interests tab.
</p> </p>
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between pb-3"> <CardHeader className="flex flex-row items-center justify-between pb-3">

View File

@@ -245,7 +245,11 @@ function OverviewTab({ berth }: { berth: BerthData }) {
/> />
<SpecRow <SpecRow
label="Nominal Boat Size (m)" label="Nominal Boat Size (m)"
value={formatNominalBoatSize(berth.nominalBoatSize, berth.nominalBoatSizeM)?.split(' / ')[1] ?? null} value={
formatNominalBoatSize(berth.nominalBoatSize, berth.nominalBoatSizeM)?.split(
' / ',
)[1] ?? null
}
/> />
<EditableSpec <EditableSpec
label="Water Depth (ft)" label="Water Depth (ft)"

View File

@@ -4,12 +4,7 @@ import { Info } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { import { PIPELINE_STAGES, STAGE_LABELS, stageDotClass, type PipelineStage } from '@/lib/constants';
PIPELINE_STAGES,
STAGE_LABELS,
stageDotClass,
type PipelineStage,
} from '@/lib/constants';
/** /**
* Small popover that decodes the colored stripe on each interest card to * Small popover that decodes the colored stripe on each interest card to

View File

@@ -276,7 +276,11 @@ export function UserSettings() {
<Label htmlFor="settings-phone">Phone</Label> <Label htmlFor="settings-phone">Phone</Label>
<PhoneInput <PhoneInput
id="settings-phone" id="settings-phone"
value={phone ? ({ e164: phone, country: (country as never) ?? 'US' } as PhoneInputValue) : null} value={
phone
? ({ e164: phone, country: (country as never) ?? 'US' } as PhoneInputValue)
: null
}
onChange={(next) => setPhone(next.e164 ?? '')} onChange={(next) => setPhone(next.e164 ?? '')}
placeholder="555 0123" placeholder="555 0123"
/> />

View File

@@ -1,12 +1,12 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import { type DialogProps } from "@radix-ui/react-dialog" import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from "cmdk" import { Command as CommandPrimitive } from 'cmdk';
import { Search } from "lucide-react" import { Search } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from "@/components/ui/dialog" import { Dialog, DialogContent } from '@/components/ui/dialog';
const Command = React.forwardRef< const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>, React.ElementRef<typeof CommandPrimitive>,
@@ -15,13 +15,13 @@ const Command = React.forwardRef<
<CommandPrimitive <CommandPrimitive
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", 'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className className,
)} )}
{...props} {...props}
/> />
)) ));
Command.displayName = CommandPrimitive.displayName Command.displayName = CommandPrimitive.displayName;
const CommandDialog = ({ children, ...props }: DialogProps) => { const CommandDialog = ({ children, ...props }: DialogProps) => {
return ( return (
@@ -32,8 +32,8 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} };
const CommandInput = React.forwardRef< const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>, React.ElementRef<typeof CommandPrimitive.Input>,
@@ -44,15 +44,15 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input <CommandPrimitive.Input
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", 'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
className className,
)} )}
{...props} {...props}
/> />
</div> </div>
)) ));
CommandInput.displayName = CommandPrimitive.Input.displayName CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef< const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>, React.ElementRef<typeof CommandPrimitive.List>,
@@ -64,30 +64,26 @@ const CommandList = React.forwardRef<
// event ourselves so the list scrolls regardless of focus state. // event ourselves so the list scrolls regardless of focus state.
<CommandPrimitive.List <CommandPrimitive.List
ref={ref} ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden overscroll-contain", className)} className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden overscroll-contain', className)}
onWheel={(event) => { onWheel={(event) => {
onWheel?.(event) onWheel?.(event);
if (event.defaultPrevented) return if (event.defaultPrevented) return;
event.currentTarget.scrollTop += event.deltaY event.currentTarget.scrollTop += event.deltaY;
}} }}
{...props} {...props}
/> />
)) ));
CommandList.displayName = CommandPrimitive.List.displayName CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef< const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>, React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => ( >((props, ref) => (
<CommandPrimitive.Empty <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
ref={ref} ));
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef< const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>, React.ElementRef<typeof CommandPrimitive.Group>,
@@ -96,14 +92,14 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group <CommandPrimitive.Group
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", 'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandGroup.displayName = CommandPrimitive.Group.displayName CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef< const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>, React.ElementRef<typeof CommandPrimitive.Separator>,
@@ -111,11 +107,11 @@ const CommandSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.Separator <CommandPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 h-px bg-border", className)} className={cn('-mx-1 h-px bg-border', className)}
{...props} {...props}
/> />
)) ));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef< const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>, React.ElementRef<typeof CommandPrimitive.Item>,
@@ -124,30 +120,24 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
className className,
)} )}
{...props} {...props}
/> />
)) ));
CommandItem.displayName = CommandPrimitive.Item.displayName CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn( className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props} {...props}
/> />
) );
} };
CommandShortcut.displayName = "CommandShortcut" CommandShortcut.displayName = 'CommandShortcut';
export { export {
Command, Command,
@@ -159,4 +149,4 @@ export {
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };