- Documents hub signer status now renders via a label map (`Pending`,
`Signed`, `Declined`, …) instead of the raw lowercase enum value.
- Invoice detail formats `dueDate` and `paymentDate` as `MMM d, yyyy`
via `date-fns` instead of leaking raw `2025-03-14` ISO strings, and
swaps the "Payment Method" free-text input for a `Select` of labelled
options (`Bank transfer`, `Credit card`, …) so we never store
`bank_transfer` from a hand-typed field again.
- Interest tabs `MilestoneSection` status badge uses a `humanizeStatus`
helper so values like `waiting_for_signatures` show as
`Waiting For Signatures` (correctly title-cased) instead of being a
lower-snake-case fragment inside an ALL-CAPS pill.
- `OUTCOME_BADGE` in the interest header now has a fall-through that
renders any unknown outcome as a closed-state badge, preventing a
closed interest from looking open just because its enum was added
upstream without a matching label entry.
- Interest timeline route joins the `user` table and returns
`userName` alongside `userId`; the client renders the resolved name
instead of a 36-char UUID. Falls back to `'a teammate'` if the user
row was deleted.
- Invoice "New / Step 3 — Review" replaces the truncated UUID display
with a server-resolved client/company name via a small `useQuery`,
so users can confirm they picked the right billing entity before
submitting.
- New `loading.tsx` for client detail renders a header / tab strip /
card skeleton during the server-component / initial-query window
that previously flashed empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comprehensive audit findings rolled up into one pass.
Bugs:
- dialog.tsx — sm-breakpoint centering classes (sm:left-[50%] /
sm:top-[50%]) were being silently stripped by tailwind-merge because
the base inset-0 + sm:inset-auto pair counted as a conflict. Replaced
with explicit per-side utilities (top-0 right-0 bottom-0 left-0 +
sm:right-auto sm:bottom-auto). Every Dialog instance now centers
correctly on desktop. (Affected 16 dialog consumers.)
- interest-documents-tab.tsx — useQuery shared the queryKey
['interests', interestId] with the parent InterestDetail's query but
returned a different shape ({ data: ... } envelope vs unwrapped).
They clobbered each other's cache on tab mount, degenerating the
parent header to "Unknown Client" / "Open" briefly. Unified the
queryFn shape so the cache stays consistent.
- interest-tabs.tsx — milestone steps now derive done-state from
PIPELINE_STAGES.indexOf(currentStage) >= step.advanceStage_idx as
well as from the date stamp. Stage truth > date truth. Seeded /
imported interests that arrived past `open` without per-step dates
now correctly show their milestone steps as checked.
- interest-detail.tsx — wires useMobileChrome so the mobile topbar
shows the client name instead of the interest UUID.
- interest-documents-tab.tsx — empty state restructured to a centered
"No documents yet — Generate EOI" CTA card instead of a small
primary button floating in the corner.
- timeline/route.ts — synthesizes a "Created at <stage>" event when
no audit-log rows exist for the interest, so the Activity tab
isn't empty for seeded interests.
- lead-source-chart.tsx — pie radii switched from fixed 90px/50px
to "70%"/"40%" so the pie scales with the container instead of
being clipped at narrow widths; reserved 40px for the legend.
Visual / clarity:
- interest-detail-header.tsx — Won/Lost rendered as branded text
buttons on desktop ("Mark won", "Close as lost") and icon-only on
mobile via `hidden sm:inline`. Edit/Archive stay icon-only. Reopen
promoted to a labeled button when the interest is closed. Added
"Last contact Xd ago" to the meta row.
- detail-header-strip.tsx — py-4 → py-3 (tighter strip).
- interest-tabs.tsx — milestone cards: the next pending milestone
gets a brand-blue ring + "NEXT" pill so the user can see at a
glance which lifecycle to act on. Its primary action gets the
filled button variant.
- interest-tabs.tsx — Deposit milestone: invoice flow promoted to
primary CTA ("Create deposit invoice"), manual stage advance
demoted to a small text link ("Mark received manually"). Reflects
the actual recommended path now that recordPayment auto-advances
on payment.
- inline-editable-field.tsx — pencil affordance shown faintly
(opacity-20) at rest so users discover that fields are editable
without having to hover-test every label. Lifts to opacity-60 on
hover.
- constants.ts — STAGE_SHORT_LABELS map for cramped contexts;
pipeline-chart.tsx + pipeline-funnel-chart.tsx use them on mobile
via useIsMobile, so the rotated 9-stage axis isn't a wall of
overlap on a 393px screen.
- client-pipeline-summary.tsx — StageStepper rebuilt as a single
segmented progress bar instead of 9 micro-dots + connectors that
rendered inconsistently at tight widths. Each stage is an equal
slice that lights up as the interest reaches it; tooltips on hover
give the full stage name. Also dropped a pre-existing dead `br`
variable.
- dashboard empty states — Lead Source, Revenue Breakdown, Pipeline
Funnel, and Recent Activity now have helpful descriptions explaining
what populates them, instead of bare "No interests in range".
- use-paginated-query.ts — reuses `&` when the endpoint already has
`?`, so callers like the documents hub don't generate
`…?tab=eoi_queue&signatureOnly=true?page=1&limit=25` (which the API
rejected as 400). Caught while testing the now-removed EOI route
but applies broadly.
tsc clean. vitest 832/832 pass. eslint 0 errors (down from 1
pre-existing) on every file touched.
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>