Tier 1.4: error_events.request_body_excerpt sanitizer now redacts
GDPR-relevant fields (email, phone, dob, address, fullName, firstName,
lastName, postcode, nationalId, etc.) on top of the existing
credential list. A 5xx in /api/v1/clients no longer lands full client
PII in the super-admin inspector.
Tier 3.10: ScanShell <main> now adds pb-[max(1.5rem, env(safe-area-
inset-bottom))]. Mobile-pwa audit caught the Save expense button sitting
flush against the iPhone 14/15 home indicator in standalone PWA mode.
Tier 6.2: dashboard widget-registry now dynamic-imports every
recharts-backed chart widget (berth status, lead source, occupancy
timeline, pipeline funnel, revenue breakdown, source conversion).
~80-150KB initial-bundle savings when reps have charts disabled.
ssr:false because recharts needs window.
Tier 6.3: DataTable wraps the assembled columns in useMemo keyed on
(columns, hasBulkActions). TanStack docs explicitly warn that
rebuilding columns every render resets the table's internal state.
Tier 7.1: Added .dockerignore (was missing — 7.6 GB context with
.env reachable via COPY . .). Excludes git, env files, node_modules,
build artefacts, IDE config, test artefacts, audit docs.
Tier 7.4: Dockerfile.dev now runs as the node user (uid 1000) — was
root. Working dir moves to /home/node/app.
Tier 7.5: docker-compose.prod.yml adds memory limits (2g postgres,
512m redis, 1g crm-app, 1g crm-worker) and json-file log rotation
(max-size, max-file) to every service.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier 2.2: revenue PDF totalCompleted now filters on outcome='won' —
setInterestOutcome forces stage='completed' for every outcome (incl.
lost + cancelled), so the stage-only filter was including those toward
"TOTAL COMPLETED REVENUE".
Tier 2.3: fetchPipelineData stageCounts adds the missing .groupBy() —
without it Postgres rejects the SELECT (per-stage breakdown was broken
or coercing to ELSE-stage row).
Tier 2.4: hot-deals widget rank ladder fixed two stage-name typos —
'in_comms' → 'in_communication', 'deposit_10' → 'deposit_10pct'. Both
stages were collapsing to the ELSE 0 branch server-side AND rendering
raw enum to the user in hot-deals-card.tsx.
Tier 3.2: portal /portal/interests no longer renders raw enum to
clients. New PORTAL_SIGNING_LABELS table maps every EOI/contract
status to plain English (e.g. "waiting_for_signatures" → "Waiting for
signatures").
Tier 4.1 (CRITICAL): permission-overrides PUT now requires caller-
superset on every `true` write. Admins with only `admin.manage_users`
could previously grant other users leaves they don't hold themselves
(permanently_delete_clients, system_backup). Super-admins bypass.
Tier 4.4: search graph-expansion re-gates every merged bucket by the
destination's view permission. A user with berths.view but no
interests.view searching "A12" no longer sees interest rows surfaced
via expansion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tier 1.6: S3Backend.put now sets ServerSideEncryption=AES256 — closes
the cleartext-at-rest gap for signed contracts, GDPR exports, pg_dumps.
Tier 3.7: New safeUrl() helper in lib/email/shell.ts. Scheme allow-list
(http/https/mailto/tel/relative only — javascript:/data:/vbscript:/file:
rewritten to about:blank) + HTML-attribute escape. Retrofitted across
all 7 transactional templates (crm-invite, portal-auth, document-signing,
notification-digest, residential-inquiry, admin-email-change).
Tier 4.2: /api/v1/alerts GET now gated on admin.view_audit_log.
Tier 4.3: Documenso webhook handler emits captureErrorEvent on catch.
Admin/errors no longer silent on webhook crashes.
Tier 4.6: Inquiry-funnel email dedup is now case-insensitive
(LOWER(value)) and stores normalized email on insert. Capital-letter
resubmissions no longer spawn duplicate client+yacht+interest rows.
Tier 5.6 + data-model H1: migration 0056 adds FK
user_permission_overrides.user_id → user(id) cascade, same for
user_port_roles.userId, plus partial unique index on
user_email_changes pending rows.
Tier 7.6: @types/node bumped from ^25 to ^20.19.0 — matches the runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>