5a2dabea056e0c9ec98f9dccc0ca0c110243ea65
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 4b5f85cb7d |
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish
Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing-
progress redesign + env-to-admin migration + dev-mode banner) with the
2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW).
CRITICAL (3):
- C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths
no longer silently drop interest links
- C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed
- C-03 generic PATCH /interests/[id] no longer accepts pipelineStage —
callers must go through /stage with the override-guard chain
HIGH (14/15):
- H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across
interests/documents/reservations/reminders/invoices (migration 0070)
- H-02 login page reads ?redirect= param with same-origin guard
- H-03 CRM invite token moves to URL fragment so it never lands in
nginx access logs / Referer headers
- H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4)
- H-05 toggleAccount writes an audit row
- H-06 upsertSetting masks any value whose key ends with _encrypted
- H-07 archiveClient cascade fires per-interest audit rows
- H-08 createSalesTransporter applies SMTP_TIMEOUTS
- H-09 AppShell stable children — viewport flip across breakpoint no
longer destroys in-progress form drafts
- H-10 portal documents page swaps Unicode glyph status icons for
Lucide CheckCircle2/XCircle/Circle + aria-labels
- H-12 list components swap alert(...) for toast.warning(...)
- H-13 5 icon-only buttons gain aria-label
- H-14 parseBody treats empty bodies as {}
- H-15 admin layout renders a 403 panel instead of silent bounce
- H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet
MEDIUM (28+):
- M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE
WHEREs across custom-fields, notes (all 6 entity types x update +
delete), client-contacts, yacht ownerClient lookup, webhook reads
- M-D01 documents-hub realtime event-name typo (file:created -> uploaded)
- M-EM01 portal-auth emails thread through portId
- M-EM02 sendEmail accepts cc/bcc params
- M-EM04 notification_digest catalog key
- M-IN01 portal presigned download URLs use 4h TTL
- M-IN02 OpenAI client lazy-instantiated
- M-IN04 stale pdfme refs updated to pdf-lib AcroForm
- M-IN05 umami.testConnection returns tagged union
- M-L01 reservations tenure_type unified with berths
- M-L02 report-generators canonicalize stage values
- M-AU01 audit log placeholder copy fixed
- M-AU04 outcome_set / outcome_cleared distinct audit verbs
- M-NEW-2 activity feed entity name+type separator
- M-R01 portal allowlist narrowed + portal_session backstop in proxy
- M-SC02 companies archived partial index
- M-SC04 audit_logs.searchText documented as DB-managed
- M-S01 storage_s3_access_key_encrypted admin field
- M-U01 audit log empty state uses <EmptyState>
- M-U09 invoice delete dialog -> <AlertDialog>
- M-U10 toast.success on ClientForm + InterestForm create/edit
- M-U11 settings-form-card logo preview alt text
- M-U14 mobile topbar title on clients/yachts/interests/berths
- M-U15 Invoices in mobile More-sheet
LOW (6/8):
- L-AU01 severity defaults for security-relevant verbs
- L-AU02 +13 missing actions in admin audit filter
- L-AU03 +7 missing entity types in admin audit filter
- L-AU04 dead listAuditLogs stubbed
- L-D02 CLAUDE.md Owner-wins chain tightened
Bonus — Document detail polish (#67 partial, 3/6 deliverables):
- state-aware action button per signer
- watcher Add UI with display-name resolution
- cleanSignerName cleanup
Prior session work bundled in:
- Documenso v2 webhook + envelope-ID normalization + sequential signing
- SigningProgress UI redesign (avatars, per-signer state, timestamps)
- env->admin settings registry + RegistryDrivenForm + encrypted creds
- Embedded-signing card + Test connection + setup help
- Dev-mode EMAIL_REDIRECT_TO banner
- Pipeline rules admin page
- Sales email config card
- Audit log details Sheet
- EOI tab: Finalising badge, absolute timestamps, sequential indicator
- Notes pipeline_stage_at_creation (migration 0069)
- Documenso numeric ID dual-key webhook (migration 0068)
- Dimensions criterion copy (migration 0067)
Tests: 1374/1374 vitest pass. tsc clean. lint clean.
See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and
the user-input items still pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| e7e498dedd |
fix(T3): copy + entry points + recommender alias
Batch of small fixes from the post-audit plan:
F11 — "Mark as won" dialog copy
Was: "This will move the interest to Completed and stamp the outcome."
Completed was retired in the 7-stage refactor; copy now reads
"marks Won; stage stays where it is" with a parallel Lost variant.
F13 — Bulk-add berths wizard had no UI entry point
Page existed at /[portSlug]/admin/berths/bulk-add but nothing linked
to it. Added a "Bulk add" button on the Berths list toolbar, gated
on `berths.import`. Also fixed the API route's permission key
(was `berths.create`, a phantom — switched to `berths.import` to
match seed-permissions).
F14 — Audit Log nav entry
Sidebar Admin section now lists "Audit Log" → /admin/audit, gated
by the adminRequired group rule.
F18 — Recommender `limit` param ignored
POST /interests/[id]/recommend-berths now accepts `limit` as an
alias for `topN`. Audit sent `{limit:3}` and silently got 8 rows
back; both names now resolve.
Tests still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| 6b28459c45 |
feat(pipeline): 9→7 stage refactor + v1.1 hardening wave
Replaces the legacy 9-stage pipeline with 7 canonical stages
(enquiry → qualified → eoi → reservation → deposit_paid → contract →
nurturing) plus three doc sub-status columns (eoi_doc_status,
reservation_doc_status, contract_doc_status) that track sent/signed
within a single stage instead of branching it.
Schema (migration 0062):
- interests gains assigned_to, deposit_expected_amount/currency,
three doc-status columns, two documenso-id columns, and
date_reservation_signed.
- New tables: qualification_criteria (per-port admin-configurable),
interest_qualifications (per-interest state), payments (deposit /
balance / refund records keyed to interest + client).
- Default qualification criteria seeded for every existing port.
- Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into
the new stage + doc-status + outcome shape.
Migration 0063 adds interest_contact_log.voice_transcript and
template_used columns for v1.1-A/B (quick-template buttons + voice
transcription via Web Speech API).
v1.1 phase work bundled here:
- A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on
the contact-log compose dialog (useVoiceTranscription hook).
- C: berth-rules-engine wraps state writes in pg_advisory_xact_lock
with an idempotent re-read; emits rule_evaluated audit traces.
- D: Documenso webhook: reservation/contract sub-status stamping
moved out of the PDF-download try-block so a download failure
no longer swallows the stamp. New integration test coverage.
- E: /admin/qualification-criteria CRUD page + admin component.
- F: default_new_interest_owner exposed in System Settings.
- G: recentActivityCount + active_engagement deal-pulse signal
surfaced as a chip on interests + hot-deals card.
- H: interest_assigned notification on assignedTo change (skips
self-assign, uses a dedupe key).
Plus the supporting components: AssignedToChip, DealPulseChip,
PaymentsSection, QualificationChecklist, MultiEoiChip,
SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner,
SupplementalInfoRequestButton, UserPicker.
Tests: 1370/1370 vitest pass (added deal-health unit suite +
expanded constants/validators/pipeline-transitions coverage). tsc
clean, eslint clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
| a8dec0bada |
fix(audit-wave-9): onboarding + first-run UX fixes (onboarding-auditor)
Address the CRITICAL and high-leverage HIGH items from the onboarding-auditor report: **C1 — checklist auto-checks were reading the wrong setting keys** A port that had actually been configured still showed three steps as incomplete, permanently capping the checklist at < 70 %. - email step: `sales_email_smtp_host` → `smtp_host_override` (the key the email admin page actually persists). - documenso step: `documenso_api_url` → compound gate `documenso_api_url_override` + `documenso_developer_email` + `documenso_approver_email` + `documenso_eoi_template_id`. All four are required for `buildDocumensoPayload` not to error out; checking only the URL falsely greenlit the step until a rep tried to send an EOI and Documenso 404'd. - settings step: `recommender_top_n_default` → `heat_weight_recency`. The defaults are layered (port > global > built-in), so a port using the built-ins never writes the `top_n_default` row — old key was an unreachable green. heat_weight_recency genuinely means "admin tuned the recommender". **C2 — forms step href was broken** `STEPS[8].href = '../'` resolved through the Link template to the dashboard, not `/admin/forms`. Fixed to `'forms'`. **C3 — EOI signer-identity gate** Folded into the new compound-gate logic on the documenso step (see C1). Now matches what the EOI pipeline actually requires before it can send. **C4 — ensureSystemRoots failure mode poisoned port creation** `ports.service.createPort` awaited `ensureSystemRoots` after the port row had committed, so a throw bubbled out as a 500 even though the inline comment said "non-fatal if this throws". Wrap in try/catch + logger.warn — the row stays live, the next admin action self-heals via `ensureEntityFolder`, and the operator doesn't retry into a 409. **H5 — berth-list empty-state copy misleads fresh ports** "Berths are imported from external sources. Adjust your filters..." implied data existed but was hidden. Branch on whether any filter is active: with none, suggest running `import-berths-from-nocodb.ts`; with filters, the original "adjust filters" message. **M4 — admin-sections-browser description was wrong** "Setup checklist for fresh ports (read-only references)" implied the page was read-only when it has working manual-completion checkboxes and discouraged clicking in. Reworded. Additionally, the OnboardingStep type gains an optional `autoCheckSettingKeysAll` field for compound gates (used by the documenso step), and the auto-detected hint shows all keys when the gate is compound. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
|||
| 3ffee79f3f |
feat(ui): broad consistency sweep — sources, dates, comboboxes, milestones
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> |
|||
| 3e4d9d6310 |
feat(interests): EOI/contract/reservation tabs + contact log + berth interest milestone + interest list overhaul
Major interest workflow expansion driven by the rapid-fire UX session.
EOI / Contract / Reservation tabs replace the generic Documents tab when
the deal is at the relevant stage — workspace pattern with active-doc
hero, signing progress, paper-signed upload, and history strip. Stage-
conditional visibility wired through interest-tabs.tsx so the tab set
shrinks/expands as the deal moves through the pipeline.
Contact log: per-interaction structured log (channel/direction/summary/
optional follow-up reminder). New `interest_contact_log` table + service
+ tab UI (timeline with channel-coded icons + compose dialog).
auto-creates a reminder when followUpAt is set.
Berth Interest milestone: first milestone in the OverviewTab's pipeline
strip, completes the moment any berth is linked via the junction. Drives
the "have we captured what they want?" sanity check for general_interest
leads before they move to EOI.
Stage-conditional milestones: past phases collapse into a one-liner
strip, current phase expands, future phases hide behind a "Show
upcoming" toggle. Inline stage picker now defers reason capture to an
override-confirm view (only required for illegal transitions, not the
default flow).
Notes blob → threaded: dropped `interests.notes` column entirely; the
threaded `interest_notes` table is the single source of truth. Latest-
note teaser on Overview links into the dedicated Notes tab. Polymorphic
notes service gains aggregated client view (unions client + interest +
yacht notes with source chips and group-by-source toggle).
Berth interest list overhaul:
- Configurable columns via ColumnPicker (18 toggleable, 5 default-on)
- Natural-sort SQL ORDER BY on mooring number (A1, A2, A10 not A10, A2)
- Per-letter row tinting via colored left-border accent + dot in cell
- Documents tab merged Files (single attachments section)
Topbar improvements:
- Always-visible back arrow on detail pages (path depth > 2)
- Breadcrumb-hint store + useBreadcrumbHint hook so detail pages can
push their entity hierarchy (Clients › Mary Smith › Interest › B17)
- Tighter spacing, softer separators, 160px crumb truncation
DataTable upgrades:
- Page-size selector with All option (validator cap raised to 1000)
- getRowClassName slot for per-row styling (used by berth tinting)
- Fixed Radix SelectItem crash on empty-string values via __any__
sentinel (was crashing every list page that opened a select filter)
Interest list:
- Configurable columns picker
- Stage cell clickable into detail
- TagPicker + SavedViewsDropdown sized h-8 to match adjacent buttons
- Save view moved into ColumnPicker menu; Views button hidden when
no views are saved
- Pipeline kanban board endpoint at /api/v1/interests/board with
minimal projection, 5000-row cap + truncated banner, filter
pass-through
Mobile chrome + sidebar collapse removed (always-expanded design choice).
User management lists super-admins (was inner-joined on user_port_roles
which excluded global super-admins).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|||
|
|
8699f81879 |
chore(style): codebase em-dash sweep + minor layout polish
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> |
||
|
|
722491a9dd |
feat(mobile): mobile cards for yachts, companies, berths, invoices, expenses
Five new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender. Desktop view
(lg+) is unchanged.
- YachtCard: Ship icon, owner subtitle (User/Building2 icon by
ownerType), dimensions in meters preferred, hull #,
status pill. No accent bar (status is free-text).
- CompanyCard: Building2 icon, legalName subtitle, country (MapPin)
+ tax id (Hash) meta, member/yacht count line.
- BerthCard: Anchor icon, area subtitle (MapPin), dimensions
meta, status pill. Status-encoded accent bar
(emerald=available, amber=under_offer, slate=sold).
- InvoiceCard: FileText icon, client subtitle, due date (Calendar)
meta, prominent currency-formatted amount. Status
accent bar (emerald=paid, orange=overdue, ...).
- ExpenseCard: Receipt icon, category subtitle, expense date meta,
prominent amount, payment-status pill, "Possible
duplicate" pill when duplicateOf is set. Accent bar
by paymentStatus, overridden to amber when flagged
as duplicate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
38cd36a616 |
style(lists): apply gradient PageHeader to client/interest/yacht/company/berth lists
Pulls the polished gradient hero strip into the five primary list surfaces. PR10b-e (detail polish, dashboard/admin polish, email + notifications polish, mobile responsive sweep) deferred to a follow-up release per spec risk register since visual baseline regen needs hands- on iteration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
| 4c20bcffcd |
Fix all ESLint errors: remove unused imports, replace any types
- Remove ~60 unused imports and variables across 88 files - Replace ~80 `any` type annotations with proper types (unknown, Record<string, unknown>, or specific types) - Prefix unused callback args with underscore - Fix unescaped JSX entities - Lint now passes cleanly (0 errors, 2 intentional img warnings) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|||
| 67d7e6e3d5 |
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |