Mobile + responsive
- berth-form full-width on phones (was 480px fixed → overflowed iPhone)
- currency-input switched to inputMode=decimal with live thousands separator
- client-form Country/Timezone/Source/Preferred-Contact full-width <sm
- contacts row restructured so Primary toggle + Remove get their own strip
- customize-dashboard footer stacks vertically on mobile; Done full-width
- interest-form client/berth pickers no longer cmdk-filter on UUID (typing
"Carlos" now returns Carlos Vega instead of "No clients found")
Data + consistency
- SOURCES + SOURCE_LABELS + formatSource() in lib/constants; 9 surfaces
now resolve interest/client source from one place
- INTEREST_OUTCOMES adds lost_other (picker, badge, timeline)
- Berth options natural-sort A1 → A2 → … → A10 via lib/utils/mooring-sort
- archiver downgraded ^8 → ^7.0.1 so the GDPR export route compiles
- TableBody last-row uses border-b-0 (not border-0); colored left-accent
on the bottom berth row now renders
- Hide Invite-to-Portal until port setting === true (was !== false default-show)
- OwnerPicker primer query resolves entity name on first paint (no more
UUID flash before the popover opens)
Terminology
- Replaced user-facing "Documenso" with "signing service" / "Generated EOI" /
"Manual EOI" in 8 components (admin/internal references kept)
- Plainer status-change copy on berth-detail-header
Forms + editing
- InlineEditableField gained a `date` variant (native picker); applied to
company incorporation date and ready for other YYYY-MM-DD plaintext fields
- Inline source picker on interest-tabs detail (was free text)
- TagPicker self-hides when port has no tags AND nothing is selected
- New ReminderDaysInput with preset chips (1d / 3d / 1wk / 2wk / 1mo / custom)
- Compose dialog follow-up is now a toggle that reveals datetime picker
Pipeline milestones
- changeStageSchema accepts optional milestoneDate; service stamps it on the
matching date column instead of always using now
- MilestoneAdvanceButton popover collects a back-date before stage advance
- Applied to every "Mark X manually" surface on the interest overview
EOI / linked-berths polish
- Add-bypass row aligned inline with toggle descriptions
- Tooltips on "Specifically pitching" / "Mark in EOI bundle" explain their
legal vs. public-map consequences
Surfaces
- Companies list now has the column picker + persisted hidden-column prefs
- NotesList aggregate flag enabled on clients, companies, residential_clients
(yachts already aggregated)
ft/m unit toggle (interim, before drift fix)
- "Berth size desired" gets a section-level ft/m toggle; per-field hint shows
the converted value. Storage stays canonical-ft for now; the drift-safe
persistence migration is the next step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New documents_show_expired_tab system setting (default true). Public
read via GET /api/v1/documents/feature-flags (gated on documents.view
so reps can read it without holding manage_settings). When off, the
Expired tab is hidden from the documents hub — useful when expired
EOIs are noise that distracts reps from active deals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the bulk of audit-pass-#1 admin gaps in one batch.
New admin pages:
- /admin/inquiries reads website_submissions with filter chips for
berth/residence/contact + payload viewer per row.
- /admin/sends reads document_sends with sent/failed filter chips and
expandable body markdown; failures surface errorReason and any
fallback-to-link reason from the SMTP retry.
- /admin/email-templates lets per-port admins override the subject of
each transactional template (8 templates catalogued in
template-catalog.ts). Body editing is a follow-on; portal_activation
+ portal_reset are wired to honor the override via loadSubjectOverride.
- /admin/reports replaces the "Coming in Layer 3" placeholder with a
KPI dashboard: 4 KPI tiles, pipeline funnel bars, berth occupancy
donut-bars, conversion %, refresh every 60s.
- backup/import/onboarding admin pages replace placeholders with
actionable guidance: backup posture + planned features, available CLI
imports + planned UI, ordered onboarding checklist linking to admin
pages.
Existing pages widened:
- settings-manager exposes the 9 berth-recommender tunables that were
previously code-only (recommender_*, heat_weight_*, fallthrough_*,
tier_ladder_hide_late_stage).
- role-form covers all 19 RolePermissions schema groups; previously
missing yachts/companies/memberships/reservations + missing
documents.edit + files.edit checkboxes. snake_case residential
labels replaced with friendly text.
portal-auth.service.ts now also writes audit_log rows for portal
invite, resend, activate, password-reset request, and reset (closes one
more audit-pass-#2 gap while we were touching the file).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).
Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
port switcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inquiry Settings + Business Rules cards used a flex-row layout that
crushed the label column into a narrow vertical stripe at 390px ("Inquiry
/ Contact / Email" wrapping one word per line) while the input took the
right side.
Stack label + helper text above the input on phone widths; restore the
side-by-side row from sm up. Same pattern as the other detail-edit rows
that were fixed in pass-2/pass-3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Per-port EOI signer config
- New `eoi_signers` system_settings key (JSON: { developer, approver },
each `{ name, email }`). Settings UI exposes it under Admin → Settings.
- getPortEoiSigners(portId) reads the setting with a typed validator;
falls back to the legacy David Mizrahi / Abbie May defaults if the
row is missing or malformed (so older ports keep working until an
admin saves a value).
- Both EOI generation pathways now read from the helper instead of
hardcoded constants:
* documenso-template path (generateAndSignViaDocumensoTemplate)
* in-app PDF-fill path (generateAndSignViaInApp)
2. Timeline upgrades
The interest detail Activity tab now distinguishes the new automation
events that arrived with sessions 1+2:
- Stage auto-advances (userId='system') get a small "Auto" pill and
carry their reason into the description (e.g. "Stage advanced to
EOI Signed (auto-advanced — EOI signed via Documenso)").
- outcome_set events show "Marked as Won" / "Marked as Lost — went
to another marina" with optional reason; trophy/X icons.
- outcome_cleared events show "Reopened to {stage}" with a refresh
icon.
- Document events humanized: "Document 'X' fully signed" instead
of "Document X: completed".
- Stage labels run through stageLabel() so the timeline shows the
human label, not the enum key.
- Timestamps switched to relative-time with full-date tooltip.
- "by system" is rendered plainly (no longer the literal user-id).
tsc clean. vitest 832/832 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 8→9 stage refresh from earlier today only updated constants.ts and the DB —
20 component/service files still hardcoded the old enum, leaving labels blank,
filter dropdowns wrong, kanban columns mismatched, and the analytics funnel
silently dropping new-stage rows. The platform also never advanced
pipelineStage on EOI lifecycle events: documents.service.ts wrote eoiStatus
but left the user-visible stage stuck.
This commit closes both gaps:
1. Single source of truth in src/lib/constants.ts — adds STAGE_LABELS,
STAGE_BADGE, STAGE_DOT, STAGE_WEIGHTS, STAGE_TRANSITIONS plus
stageLabel / stageBadgeClass / stageDotClass / safeStage /
canTransitionStage helpers. components/clients/pipeline-constants.ts
becomes a re-export shim so existing imports keep working.
2. 18 stale-enum surfaces migrated — interest list (table, card, filters,
form, stage picker), pipeline board, client card, berth interests tab,
portal client interests page, dashboard pipeline / funnel / revenue-
forecast charts, settings pipeline_weights default, dashboard.service
weights, analytics.service funnel stages, alert-rules stale-interest
filter, interest-scoring stage rank.
3. Documents tab wired into interest detail — replaced the placeholder in
interest-tabs.tsx with InterestDocumentsTab + InterestFilesTab so the
EOI launcher is back where salespeople work.
4. Auto-advance — new advanceStageIfBehind() in interests.service.ts
(forward-only, no-op if interest is already past the target). Called
from documents.service.ts on send (→ eoi_sent), Documenso completed
webhook (→ eoi_signed), and manual signed-EOI upload (→ eoi_signed).
5. Transition guard — canTransitionStage() blocks egregious skips
(e.g. completed → open, open → contract_signed). Enforced in
changeInterestStage before the DB write.
Tests updated to reflect the 9-stage model. tsc clean, vitest 832/832,
ESLint clean on every file touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Port CRUD: list, create, update with branding, currency, timezone
- System settings: upsert key-value pairs per port with known settings UI
(AI feature flags, invoice discount, pipeline weights, berth rules)
- Settings manager with toggle switches, number inputs, and JSON editors
- Replace both stub pages with real implementations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>