deployment-plan.md gains a full env-var reference (CRM + website) and the cutover env-flip sequence; launch-readiness.md gets the 2026-06-02 closeout; BACKLOG.md adds the deferred integration-health-panel idea (section L).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 bite-sized TDD tasks: parseOperationalFilters (unit-tested), Area
filter threaded through the operational service + route, hasData
existence flags on all three report routes, shared ReportEmptyState
component, and per-client wiring. Verification + tracker update in the
final task.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Locked decisions from brainstorming: report-level empty states across
Sales/Operational/Financial gated on a window-independent hasData flag;
Operational gains an Area-only berth-scope filter (Status dropped as a
light filter in this report); rep/source confirmed not applicable to
Operational.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sales/Operational/Financial are built + verified; Marketing is blocked
on the website cutover (launch-readiness Init 1b), not on code. Rather
than hide the whole reports surface behind a module toggle, keep it live
for beta and 404 the one unbuilt kind so a hand-typed /reports/marketing
URL can't reach the "in development" placeholder. The landing page
already advertises only the three live reports + Custom.
Remove the UNAVAILABLE_NEW_KINDS entry when the Marketing report ships.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 2026-05-03 migration pipeline (src/lib/dedup/*) predates the 9→7
pipeline-stage refactor; its STAGE_MAP emitted invalid stages
(open/details_sent/eoi_sent/…) that would write bad pipeline_stage values
on --apply. Remap to the current PIPELINE_STAGES (enquiry/qualified/
nurturing/eoi/reservation/deposit_paid/contract) + a deposit-received →
deposit_paid override. Frozen-fixture test expectations updated (17/17 pass).
Validated: live --dry-run = 239 clients / 255 interests / 41 EOI docs
(matches independent snapshot analysis; pipeline is more conservative and
flags 3 borderline pairs for review).
Adds the migration design spec (source map, scope lock to Port Nimara +
Expenses bases, EOI coverage 48/48, in-flight Documenso state, remaining
gaps: interest eoiStatus, expenses, doc-blob backfill).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ship-what's-done prep ahead of the prod cutover (launch ~today):
- Hide Financial + Marketing report cards from the reports landing
(both were "Builder in development" placeholders gated on unbuilt
data sources). Sales/Operational/Custom + templates/scheduling/
exports remain live.
- Trim the Custom-report card copy to match the shipped basic builder
(no group-by/filters yet; the builder page header was already honest).
- Hide the Bulk Import mockup from search-nav-catalog + the admin
sections browser; /admin/import is now unreachable from the UI.
- Correct client-facing doc over-claims (waiting-list "next-in-line
notification", Import) in features-list.md + new-system-feature-summary.md.
- Un-stale BACKLOG.md (Documenso phases 2-7 confirmed shipped).
- Log decisions + deferred work (full importer, full custom-builder,
waiting-list, maintenance-log, paper-upload bug) to launch-readiness.md.
Deferred-importer design spec added at
docs/superpowers/specs/2026-06-01-bulk-import-design.md.
Verified: tsc --noEmit clean, eslint clean on changed files,
1512/1519 vitest pass (7 failures are Redis-down, unrelated).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a "Compare to prior period" toggle to the Sales report header.
When on, the API recomputes the KPI window for the equal-length window
immediately preceding the selected range (previousPeriodBounds) behind
`?compare=1`, and the five window-derived KPI tiles (Won, Lost, Win
rate, Avg time-to-close, New leads) render colour-correct "vs prior"
deltas. Point-in-time tiles (Active interests, Pipeline value) have no
prior-window analogue and intentionally show no delta. The prior-window
query runs in parallel with the main batch and resolves to null when the
toggle is off (zero cost). Toggle state persists in the saved-template
config.
Closes the spec's "period comparison on every report" gap for Sales;
Operational already rendered period-start deltas.
Pure helpers TDD'd: previousPeriodBounds (range.ts) +
computeSalesKpiComparison (sales-comparison.ts), 7 unit tests. tsc +
lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a `residential_module_enabled` port setting (default ON) that
hides/disables the entire Residential surface when an admin turns it
off, mirroring the Tenancies / Invoices / Expenses module-toggle
pattern. Disabling is a soft hide — residential clients/interests are
preserved and reappear on re-enable.
Surfaces gated:
- Route guard: new residential/layout.tsx renders ModuleDisabledPage
(covers all 5 residential pages)
- Sidebar "Residential" section + mobile more-sheet tile (SSR-resolved
residentialModuleByPort threaded layout → app-shell → sidebar)
- Global search: residential client/interest buckets early-return at
the shared chokepoint so disabled-port records don't dead-end
- Public intake: /api/public/residential-inquiries 404s when off
- Admin Switch in settings-manager (writes via settings PUT)
Service TDD'd (residential-module.test.ts, 6 tests) plus a
disabled-port rejection test on the public endpoint. tsc + lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles the rest of the in-flight work from this UAT round into one
checkpoint. Each sub-area is independent; see the headings below.
UAT polish (drained 11 findings from active-uat.md):
- Dialog primitive default bumped sm:max-w-xl/lg:max-w-3xl →
sm:max-w-2xl/lg:max-w-4xl so multi-field forms + PDF previews
aren't cramped at 1440-1920px.
- Notes tab badge aggregation: new countFor{Client,Yacht,Company}
Aggregated helpers in notes.service mirror the listFor*Aggregated
symmetric-reach joins. yacht-tabs + company-tabs render the
badge; client-tabs already had badge support.
- Supplemental-info form polish bundle: BrandedAuthShell gains a
`width: 'sm' | 'md'` prop (md uses min-h-dvh scroll instead of
fixed inset-0 pin so long forms scroll naturally). Form picks up
port branding (logoUrl + backgroundUrl + appName) via
loadByToken. Address fields completed (street + city + region +
postal + country). Port name eyebrow + success-state copy added.
- new-document-menu Upload-file landing toast: per-file completion
emits toast.success with action link to the destination entity
or folder.
- interest-tabs OverviewTab "from client" pill on Email + Phone
rows via new EditableRow `inheritedFrom` prop.
- create-document-wizard subject picker → segmented button strip
(5 types visible at once).
Launch infra:
- UTM column wiring (Init 1b step 4): migration
0089_website_submissions_utm.sql adds utm_source/medium/campaign/
term/content + composite index (port_id, utm_source, received_at)
for per-campaign rollups. website-inquiries intake accepts the
five fields. Residential intake intentionally untouched per audit
scope.
- Invoicing module gate (Init 1c spike): new
invoices-module.service + invoices layout guard + registry entry
invoices_module_enabled (default false). Audit conclusion in
launch-readiness.md: payments table is canonical money path;
/invoices flow is parallel infrastructure now hidden by default.
Smart-back navigation refactor:
- Replaced breadcrumb component with history-aware Back button.
New route-labels.ts + use-smart-back hook +
navigation-history-tracker so back falls through to the parent
route when there's no prior page in history.
- Sidebar / topbar / mobile-topbar adopt the new pattern; old
breadcrumb-store kept for back-compat consumers but the
breadcrumbs component is gone.
- 6 detail pages (admin/errors per-id + codes, invoices/
upload-receipts, reports kind, tenancies detail, analytics
metric, client detail) migrated.
Trackers + docs:
- docs/launch-readiness.md — master pre-launch tracker. Includes
the reports gap audit (cross-cutting filter set, Marketing +
Financial blockers, custom builder remaining entities, scheduled
CSV/XLSX, template scope picker).
- docs/superpowers/audits/active-uat.md — 15 findings flipped
OPEN → SHIPPED locally with fix-applied notes; 4 OPEN remaining
(each blocked on user input or cross-repo).
- CLAUDE.md — minor session notes carried forward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of the comprehensive Documenso upload audit per the
2026-05-26 locked-decisions block in docs/superpowers/audits/active-uat.md.
P1.1 — persist documensoId immediately after create
Was set only at the late `status: 'sent'` commit. Any throw between
documensoCreate and the late update left an orphaned Documenso
envelope the CRM had no link to. Now the UPDATE runs right after
documensoCreate succeeds; rollback paths can find and void the
envelope.
P1.2 — pre-flight validation hard-blocks Submit
UploadForSigningDialog computes a submissionErrors memo over
recipients + fields. Submit button disabled when errors > 0. Inline
amber summary lists every issue (missing email, invalid email,
missing name, field assigned to non-existent recipient, no fields
placed). Service layer mirrors the same email + name checks so
direct API hits reject early. No override path per locked decision.
P1.3 — cancel/delete affordance audit + sweep
Document-list per-row Delete + Send for Signing actions now:
- Wrapped in PermissionGate (documents.delete + send_for_signing).
- Surface toast on success + toastError on failure (were silently
swallowing errors).
- Use a broader predicate-based query invalidation so every doc
list across the app refreshes, not just the local key.
EOI tab Regenerate + Cancel EOI buttons + reservation/contract
tab Cancel buttons wrapped in PermissionGate (documents.edit, the
cancel route's auth check).
P1.4 — Documenso webhook URL auto-PATCH (env-gated)
scripts/update-documenso-webhook.ts written. Reads
DEV_AUTO_UPDATE_DOCUMENSO_WEBHOOK env flag (when 1, runs; otherwise
no-op). Lists every webhook on the Documenso instance via v2 (with
v1 fallback), identifies webhooks pointing at trycloudflare.com
hosts OR /api/webhooks/documenso paths, PATCHes them to the new
tunnel URL. scripts/tunnel-url.sh chains the script after the URL
print so a re-tunnel auto-rotates the webhook (when flag set).
P1.5 — state-machine refactor with rollbackTo() helper
custom-document-upload.service.ts:
- Single try around create → send → place steps.
- state.step tracks which step is current; state.documensoDocId
records the envelope id once we have it.
- rollbackTo(reason) composes the recovery: status='cancelled' on
the CRM row, documensoVoidSafe on the envelope when applicable.
Idempotent — calling twice is safe.
- Removes three independent try/catches.
P1.6 — recipient ↔ Documenso identity reconciliation
After documensoSend, validates every distinct email we sent
appears in sentDoc.recipients. If Documenso silently dropped one,
a ConflictError fires before field placement so the rollback path
triggers. Explicit message names the missing emails for the rep.
P1.7 — vitest extension + per-failure audit-log entries
- 5 new vitest cases (blank email, whitespace email, malformed
email, blank name, duplicate-emails-OK semantic).
- rollbackTo writes a structured audit_log entry with failedStep,
documensoEnvelopeId, errorClass, errorMessage. Post-mortem
investigation has structured data instead of just logger lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User answered 11 blocking + clarifying questions across the audit doc.
Decisions inlined as a summary block in the audit doc prelude so any
session reading the doc sees the answers up front before drilling into
individual findings.
Highlights:
- Documenso comprehensive audit ships as 5 discrete sub-PRs.
- Pre-flight validation hard-blocks Submit; no override path.
- `/documents/new` wizard refactor: delete upload branch, drop inapp
pathway, per-port doc-type template defaults, surface flow 3 from
dropdown, drop the route entirely.
- Automate Signing: pick-up on mid-flow enable; broadcast to all
recipients; single combined mode; manual override stays visible.
- Webhook URL auto-PATCH env-flag-gated.
- documenso_signing_order becomes a tri-state setting.
- OverviewTab inheritance writes to interest, prompt to also update
yacht record.
- Public-map flag inheritance applies across every map-flip dialog.
- Cancel/Delete affordance audit sweeps EVERY remove route.
- Orphan-scan script deferred; dev DB nuke acceptable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every UAT finding the user surfaces during a live walkthrough now lands
in docs/superpowers/audits/active-uat.md regardless of which session
captured it. Persists across sessions until the user explicitly says to
wrap a round and archive (rename to YYYY-MM-DD-uat.md, start fresh).
CLAUDE.md's "Manual UAT" guidance updated to point at the new file +
documents the status-tag taxonomy and the append-protocol detail level
(file:line, React-grab anchor, root cause, fix proposal walking each
layer, effort estimate, alternatives + rejection reasons, open
questions, bundle-with notes, cross-refs, acceptance criteria). Historical
alpha-uat-master.md retained as the previous master through 2026-05-26.
This commit seeds the doc with the full body of findings captured during
this live session — Documenso reliability work, dialog width sweep,
recipient UX, recommender card polish, tenancy + notes plumbing, the
larger Documenso upload audit and Automate Signing feature specs. Each
entry follows the detail contract documented in the file footer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drain the long-tail audit queue captured in alpha-uat-master.md.
- next-intl ripped out (zero useTranslations callers ever existed):
package.json, next.config.ts plugin wrap, src/i18n/, messages/, and
the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded.
- RTL lint nudge added: warn-only no-restricted-syntax on physical
Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/
border-r/rounded-l-/rounded-r-) inside JSX className literals.
Existing ~1,000 sites grandfathered; new code trends toward logical.
- Icon-only button accessibility lint: jsx-a11y/control-has-associated-
label enabled at warn; 4 empty <th>/<td> action placeholders gain
sr-only labels.
- Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels;
new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames.
CurrencySelect + settings-manager migrated.
- Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US')
to toLocaleString(undefined, ...) so dates honour runtime locale.
- Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a
lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room.
- PaymentsSection collapsed-bar: slim one-line bar showing
"Payments - Not received yet" or "Payments - \$X received - N payments
- Expand"; per-interest collapse state persists in localStorage; the
RecordPayment flow auto-expands.
- muted-foreground opacity sweep: 10 text-bearing
text-muted-foreground/{60,70,80} hits dropped to plain
text-muted-foreground for AA contrast on muted bg. Icon-only
(aria-hidden) opacity hits left as-is.
- Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px)
across 87 files in src/components + src/app. Pure mechanical sweep.
- Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary
rewritten with cumulative state through today. Items genuinely still
open are now a short long-tail list.
- New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email
pixel E2E verification, and website-cutover work parked here so
they don't get lost in the CRM audit doc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Webhook auto-create on signed Reservation Agreement was gating itself on
isTenanciesModuleEnabled, but autoCreatePendingTenancies never enabled
the module — so the very first tenancy on a fresh port was unreachable
even though the row-exists fallback in isTenanciesModuleEnabled was
designed exactly for this lazy auto-surface case. Drop the gate; the
inserted row now flips the module on automatically via the fallback.
docs/tenancies-design.md §"When disabled" and the P3 PR-table row
updated to reflect the new contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the UI half of the bulk-price feature (backend already shipped).
Reps with berths.update_prices can retune pricing without unlocking the
rest of the berth schema, both one-at-a-time and in bulk.
- berth-columns: PriceCell wraps InlineEditableField, gated by
can('berths', 'update_prices'). Click → input → save through
PATCH /api/v1/berths/[id]/price. stopPropagation so row click
doesn't navigate while editing.
- bulk-price-edit-sheet: right-side Sheet listing selected berths from
the React Query cache. Per-row price + currency inputs with dirty-
highlight. "Set all to" + "Adjust by %" shortcuts. Diff-only POST to
/bulk-update-prices reports updated/unchanged/missing. Body is keyed
on the selection so useState initializes fresh per open.
- berth-list: new "Update prices" bulk action gated by the same
permission, sits between Remove tag and Archive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MUST-FIX:
- src/app/api/v1/admin/users/[id]/permission-overrides/route.ts:70 — the
PUT allowlist still gated `reservations: {view,create,activate,cancel}`.
Stale: would reject valid `tenancies.{view,manage,cancel}` writes and
silently accept ghost `reservations.*` writes that never land. Replaced.
- src/lib/services/alert-rules.ts:68 — `reservation.no_agreement` alert
emitted `entityType: 'reservation'`. Every other tenancy-related
audit/socket/dashboard label is `'berth_tenancy'`. Inconsistent dedupe
+ activity-feed label miss.
- tests/e2e/exhaustive/08-portal.spec.ts:6 — hardcoded /portal/my-reservations
navigates to a 404 every run.
- tests/e2e/exhaustive/03-reservations.spec.ts — entire spec renamed to
03-tenancies.spec.ts; tab + button locators updated to match renamed UI.
SHOULD-FIX (consistency):
- src/components/clients/client-detail.tsx — useRealtimeInvalidation only
caught 3 of the 4 berth_tenancy:* events; added the `:created` listener.
- src/lib/services/client-merge.service.ts — MergeResult.movedRows.reservations
+ snapshot.reservations + local loserReservations / movedReservations
renamed to tenancies / loserTenancies / movedTenancies. No external
consumers grep-confirmed.
- src/lib/services/gdpr-bundle-builder.ts — GdprBundle.reservations field
renamed to .tenancies; user-facing HTML section "Reservations" → "Tenancies";
local reservationRows → tenancyRows.
- 6 UI copy strings: gdpr-export-button, bulk-archive-wizard,
bulk-hard-delete-dialog, hard-delete-dialog, admin-sections-browser ×2,
admin/import/page, won-status-panel — all "reservations" prose updated
to "tenancies" (occupancy-record sense).
- tests/integration/api/tenancies.test.ts — handler import aliases
`createReservationHandler` etc renamed to `createTenancyHandler` etc.
- tests/unit/services/berth-tenancies.test.ts — local helper makeReservation
→ makeTenancyLocal (avoids shadow of the renamed factory).
- scripts/audit-permissions.ts — stale allowlist entry for
/berth-reservations/[id]/route.ts removed (path no longer exists).
- docs/runbooks/permission-audit.md — stale row for same path removed.
- docs/tenancies-design.md — fixed factual error
("tenancies.service.ts" → "berth-tenancies.service.ts").
Verified: tsc clean, 1493/1493 vitest.
Dev-server note: the running `next dev` process started before P2 and
shows Turbopack cached compile errors against the renamed schema files.
Source is correct (./tenancies); restart `next dev` to clear the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the second execution pass:
- Reports P2 CRUD landed on report_runs + report_schedules.
- Form-error sweep complete platform-wide (16 remaining callsites adopted).
- Audit-doc cleanup: dock-letters / email-test / cancelMode were already
shipped earlier and should not have been listed as queued.
Total ~25 commits across this date; ~110 h still queued for follow-up
(Reports P3-P7, Tenancies P2-P7, UploadForSigning field metadata, B3 wave).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-of-doc status block summarising what landed during the autonomous
execution pass (~12 commits across Bucket 1/2/3/4) + what remains
queued for follow-up sessions. Lets future sessions skip directly to
deferred items without re-triaging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical data-correctness fixes
- external-eoi.service: stage-advance list rewritten against canonical
7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to
legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so
EOI uploads from 'qualified' silently skipped the stage flip. Now also
writes eoiDocStatus='signed' alongside eoiStatus='signed'.
- public-interest.service + api/public/interests/route: pipelineStage
'open' → 'enquiry' for new public interests.
- interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker
comments updated.
- Display fallbacks canonicalized: dashboard.service, dashboard-report-data,
pdf/templates/{interest,client}-summary, interest-picker, timeline route
all route through canonicalizeStage / stageLabelFor.
Multi-berth interest label sweep
- New helper src/lib/templates/interest-berth-label.ts with 9 unit tests
(deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments,
falls back to 'first + N more').
- New batched aggregator getAllBerthMooringsForInterests on the
interest-berths service.
- BoardInterestRow + listInterests + getInterest extended with
berthMoorings: string[].
- Swept render sites: interest-detail-header, pipeline-card +
pipeline-column (kanban), interest-columns (list), interest-card,
interest-detail (breadcrumb), client-pipeline-summary +
client-interests-tab, yacht-tabs, shared interest-picker.
- PDF report "New interests (in period)" Source column → Berth column.
Dashboard PDF report fixes
- Hardcoded EUR → reads ports.default_currency once at the top of
resolveDashboardReportData. Falls back to USD.
- 'maintenance' berth-status bucket removed everywhere (wasn't in
canonical BERTH_STATUSES); cleaned from dashboard.service,
dashboard-report-data, occupancy-report, berth-status-chart, fixture.
- Berth demand ranking: dropped placeholder Tier column (resolver
hardcoded 'A' — heat-tier never plumbed through).
- Deal pulse distribution: tier values capitalized (hot → Hot etc.).
- Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing
"Validation failed" when all sections checked).
- Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no
more 2-line wraps on "needs date range"); accepts initialRange?:
DateRange so the dashboard's active range pre-fills dateFrom/dateTo via
rangeToBounds.
Interest banner overcounts fix
- interest-berth-status-banner: filters out self-caused under-offer
berths (where the only active deal touching the berth IS this same
interest). Waits for all competing-queries before committing the
count. Was showing "3 berths unavailable" when only 1 actually had a
competitor.
Sessions list ordering
- sessions-list: client-side sort by lastAt desc + displays lastAt
instead of firstAt so visible timestamp matches the sort key.
Audit log polish
- Details button: side Sheet → Popover anchored to the button (in-place
inline dropdown). Works with the virtualized table.
- From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3.
EntityFolderView (Documents Hub entity view)
- Per-row Download button (hover-reveal icon).
- File-type icon prefix + tighter row layout.
- Per-row interest-berth badge: files.ts attaches interestBerthLabel via
one batched getAllBerthMooringsForInterests call across all groups.
AggregatedFile type + EntityFolderView render the badge linking back
to the parent interest.
External EOI upload dialog
- Title input pre-fills from the derived default via controlled
displayTitle = title || defaultTitle (no setState-in-effect).
EOI Generate dialog
- Success toast on mutation success.
- Primary berth's "Include in EOI" checkbox is now forced-on + disabled
with tooltip: the primary IS the canonical "berth for this deal",
excluding it is semantically nonsense.
Primary berth must always be in EOI bundle (service + backfill)
- interest-berths.service: insert path forces is_in_eoi_bundle=true
whenever is_primary=true; update path coerces back to true when the
caller tries to set false on a primary. Backfilled 7 existing rows.
Documenso redirect URL fallback
- port-config getPortDocumensoConfig: resolution chain extended to
documenso_redirect_url → public_site_url → null. Operators with
public_site_url configured (most ports) now get sensible signer
landing without setting two settings.
World-map click → navigate
- website-analytics-shell: country click navigates to the nationality-
filtered Clients page via router.push instead of copying a URL to
clipboard.
Documents Hub: subfolder grid in main panel
- Subfolder cards rendered above the documents list when the current
folder has children. Lets reps drill into subfolders from the main
content area, not only via the sidebar tree.
Interest list initial sort
- usePaginatedQuery gains initialSort option (used when URL has no sort
param). Interest list passes updatedAt desc so the table header
surfaces the active sort visibly + most-recently-added/edited bubble
to the top.
Interest auto-assign on create
- interests.service createInterest: three-tier owner resolution chain
— explicit input → port's default_new_interest_owner setting →
creator (when not super-admin). Super-admins skipped since they often
create on behalf of other reps.
Backfills
- 12 interests with eoi_status='signed' + missing eoi_doc_status='signed'
aligned.
- 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false
flipped to true.
Verified
- pnpm tsc --noEmit: clean
- pnpm exec vitest run: 1463 / 1463 passed
Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md
across all 4 buckets, including two OPEN QUESTIONS (Reservations module
re-imagine, Reports dedicated page promotion).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Finishes B4 #8 by completing the UI half of the per-interest filing
model. Backend foundations (files.interest_id column, ensureEntityFolder
for 'interest', upload-zone scope radio, outcome rename hook, backfill)
shipped earlier in this audit cycle.
- listFiles validator + service: optional interestId filter
- listFilesAggregatedByEntity: routes entityType='interest' to a new
helper that returns "THIS DEAL" + "FROM CLIENT" + symmetric-reach
company/yacht groups
- InterestDocumentsTab: Attachments section now renders two cohorts
via two paginated queries, with client-side de-duplication so files
filed under this deal don't double-count under "From client"
- FileRow type exposes the optional interestId so the de-dupe filter
doesn't need a re-fetch
Backend foundations were already in place ('generic' CustomDocumentType,
storage-path routing). This wires the UI surface across Documents Hub +
entity file tabs.
- UploadForSigningDialog: interestId now string | null; new entity?,
folderId?, onCreated? props. Generic path POSTs to new endpoint
/api/v1/upload-for-signing; interest-scoped paths unchanged.
- uploadDocumentForSigning service: interestId nullable; skips interest
lookup, pipeline-stage advance, doc-status flip on the generic path.
Routes file FK + auto-filed folder via either interest.clientId or the
caller-supplied entity. Validation enforces the matching invariant
(generic must be interestId=null, type-specific must carry one).
- New menu item in NewDocumentMenu ("Upload & send for signature") on
Documents Hub root + folder views.
- Upload & send-for-signature button on ClientFilesTab + CompanyFilesTab,
gated by documents.send_for_signing.
Existing unit tests for the service still pass (validation paths unchanged).
Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
Final Bucket 1 visual-audit follow-up. Audit of all 4 useIsMobile
callers:
- pipeline-chart.tsx + pipeline-funnel-chart.tsx → keep useIsMobile
(short x-axis stage labels apply on tablet too — bar charts can't
fit full "Reservation" / "Deposit Paid" text at narrow widths).
- date-picker.tsx + date-time-picker.tsx → migrate to useViewportTier.
Tablet (768-1023) has plenty of room for the desktop Popover
Calendar; only the smallest phone widths now fall back to the
native datepicker input.
1454/1454 vitest, tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PageHeader stack point + tablet topbar trigger fixes verified via
Playwright re-screenshot at 768 + 1024.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captured 2026-05-22 from a Playwright MCP pass at 5 viewports × 20
surfaces (375 / 768 / 1024 / 1440 / 1920 px). The tablet tier
infrastructure shipped in 6d665d0 + the dashboard PageHeader
stacking fix lit up the tier — these are the residual bugs the
audit surfaced.
3 Bucket 1 quick-fixes:
- Tablet topbar logo trigger doesn't render visibly (search-bar
translate shifts over leading slot + center column min-width
too wide).
- Dashboard PageHeader at exactly 1024 viewport (sidebar present +
lg:flex-row kicks in, crushing the title).
- useIsMobile call-site audit needed (kept as tier !== desktop
alias; some sites want strict mobile-only).
4 Bucket 2 mediums:
- Documents Hub folder rail truncates to 3 chars at tablet.
- Website analytics 6-KPI row too cramped at 1024.
- Pipeline Value mobile (375) per-stage rows overflow right margin.
- Berths list 1024 — only 5-6 of 14 columns fit before h-scroll.
Screenshots local at tmp/visual-audit-2026-05-22/ (gitignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes plan item 43 in the remaining-plan doc; alpha-uat-master annotated
with the SHA. Per CLAUDE.md's "annotate the master doc" rule after a
batch ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stamps the 2026-05-21 plan with the SHA of every group's landed
commit. Groups A through T are worked end-to-end across this
session; Group U (EOI bundle UX rework) is the only remaining
parked item with reasoning in its commit.
Per-group commit notes document what shipped fully vs. what stayed
parked within each group (e.g. Q57 recharts→ECharts deferred,
M43 form-template editor UI deferred, O47-O50 marketing-site
phases deferred). Vitest 1454/1454 + tsc clean across all groups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Section K documents the recommended path for multi-tenant branded auth
screens: a single Next.js app behind `*.crm.example.com` wildcard DNS
that derives the active portSlug from the Host header (instead of the
current "first active port wins" fallback in resolveAuthShellBranding).
Includes the open work: wildcard cert, parent-domain cookie scope,
middleware host-resolver, switcher UI, and bootstrap seed.
next-env.d.ts is auto-regenerated by Next typegen with double-quote
formatting; included so the diff stays clean for the next dev session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>