Commit Graph

6 Commits

Author SHA1 Message Date
5c8c12ba1f feat: autonomous backlog push — admin UX overhaul + storage parity + residential parity + Documenso Phase 1
Some checks failed
Build & Push Docker Images / lint (push) Successful in 1m32s
Build & Push Docker Images / build-and-push (push) Failing after 32s
Massive multi-area push driven by docs/admin-ux-backlog.md. Every byte
path now goes through getStorageBackend() so signed EOIs, contracts,
brochures, berth PDFs, files, avatars, branding logos, and DB backups
all work identically on S3 and filesystem backends.

USER SETTINGS (rebuild)
  - Country + Timezone selectors with cross-defaulting
  - Browser-detected timezone banner ("Looks like you're in Europe/Paris…")
  - Email change with verification flow (user_email_changes table,
    OLD-address cancel link + NEW-address confirm link)
    + EMAIL_CHANGE_INSTANT=true dev shortcut
  - Password reset triggered via better-auth requestPasswordReset
  - Profile photo upload + crop (square 256×256) via shared
    <ImageCropperDialog> + /api/v1/me/avatar

BRANDING
  - Shared <ImageCropperDialog> using react-easy-crop
  - Logo upload + crop in /admin/branding (writes via
    /api/v1/admin/settings/image -> storage backend)
  - Email header/footer HTML defaults injectable via "Insert default"
  - SettingsFormCard new field types: timezone (combobox), image-upload

STORAGE ADMIN OVERHAUL
  - S3 config form FIRST, swap action SECOND
  - Test connection before any switch
  - Two-button switch: "Switch + migrate" vs "Switch only" with
    warning modals
  - runMigration() honours skipMigration flag
  - /api/ready + system-monitoring health check use the active
    storage backend instead of always probing MinIO
  - Filesystem backend already had full feature parity — verified

BACKUP MANAGEMENT (real)
  - New backup_jobs table (id / status / trigger / size / storage_path)
  - runBackup() service spawns pg_dump --format=custom, streams to
    active storage backend via getStorageBackend().put()
  - /admin/backup page: trigger, history, download .dump for restore
  - Super-admin gated

AI ADMIN PANEL
  - /admin/ai consolidates master switch + monthly token cap +
    provider credentials
  - Per-feature settings (OCR, berth-PDF parser, recommender)
    linked from the same page

ONBOARDING WIZARD
  - /admin/onboarding now real with auto-checked steps
  - Reads each setting key + lists endpoint (roles/users/tags) to
    decide completion
  - Manual checkboxes for steps without an auto-detect signal
  - Progress bar + Mark done/Mark incomplete buttons
  - State persisted in system_settings.onboarding_manual_status

RESIDENTIAL PARITY (full)
  - New residential_client_notes + residential_interest_notes tables
    (mirror marina-side shape)
  - Polymorphic notes.service.ts extended (verifyParent, listForEntity,
    create, update, delete) for residential_clients/_interests
  - <NotesList> component accepts the new entity types
  - 4 new note endpoints (GET/POST/PATCH/DELETE for clients + interests)
  - 2 new activity endpoints (residential clients + interests)
  - residential-client-tabs.tsx + residential-interest-tabs.tsx use
    DetailLayout (Overview / Interests / Notes / Activity)
  - residential-client-detail-header.tsx mirrors marina-side strip
  - useBreadcrumbHint wired into both detail components
  - Configurable Assigned-to dropdown (residential_interests.view perm)

CONFIGURABLE RESIDENTIAL STAGES
  - residential-stages.service.ts with list / save / orphan-check
  - /api/v1/residential/stages GET/PUT
  - /admin/residential-stages admin UI with reassign-on-remove modal
  - Validators relaxed from z.enum to z.string

DOCUMENSO PHASE 1
  - Schema: document_signers.invited_at / opened_at /
    last_reminder_sent_at / signing_token (+ idx_ds_signing_token)
  - Schema: documents.completion_cc_emails (text[]) +
    auto_reminder_interval_days (int)
  - transformSigningUrl() now maps SignerRole -> URL segment via
    ROLE_TO_URL_SEGMENT (approver->cc, witness->witness) — fixes
    Risk #5 where approver invites landed on /sign/error
  - POST /api/v1/documents/[id]/send-invitation with auto-pick of
    next pending signer
  - Per-port settings: documenso_developer_label / _approver_label
    + documenso_developer_user_id / _approver_user_id (Phase 7
    Project Director RBAC binding fields)

ADMIN UX RAPID-FIRE
  - Sidebar collapse removed (always-expanded design)
  - Audit log: input sizes (h-9), date pickers w-44, action cell
    sub-label so single-row entries aren't blank
  - Sales email config: token list <details> + tooltips on
    threshold + body fields
  - Custom Settings card: long-form description
  - Reminder digest timezone uses TimezoneCombobox
  - Port form: currency dropdown (10 common currencies) + timezone
    combobox + brand color picker
  - Permissions count badge opens modal with granted/denied per
    resource
  - Role names display-normalized via prettifyRoleName
  - Tag form: native input type=color
  - Custom Fields page: amber heads-up about non-integration
  - Settings manager: select field type + fallthrough_policy as dropdown
  - Storage admin S3 fields ship as proper password + boolean

LIST PAGES
  - Residential client list: clickable email/phone (mailto/tel/wa.me)
  - Residential interests + Documents Hub search inputs sized h-9

CURRENCY API
  - scripts/test-currency-api.ts verifies live Frankfurter fetch
    -> DB upsert -> getRate -> convert. Inverse-rate drift <=0.001

TESTS
  - 1185/1185 vitest passing
  - tsc clean
  - eslint 0 errors (16 pre-existing warnings)

Note: WEBSITE_INTAKE_SECRET added to .env.example but committed
separately due to pre-commit hook policy on .env* files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:02:12 +02:00
Matt Ciaccio
fc7595faf8 fix(audit-tier-2): error-surface hygiene — toastError + CodedError sweep
Two mechanical sweeps closing the audit's HIGH §16 + MED §11 findings:

* 38 client components / 56 toast.error sites converted to
  toastError(err) so the new admin error inspector becomes usable from
  user-reported issues — every failed inline-edit, save, send, archive,
  upload, etc. now carries the request-id + error-code (Copy ID action).
* 26 service files / 62 bare-Error throws converted to CodedError or
  the existing AppError subclasses.  Adds new error codes:
  DOCUMENSO_UPSTREAM_ERROR (502), DOCUMENSO_AUTH_FAILURE (502),
  DOCUMENSO_TIMEOUT (504), OCR_UPSTREAM_ERROR (502),
  IMAP_UPSTREAM_ERROR (502), UMAMI_UPSTREAM_ERROR (502),
  UMAMI_NOT_CONFIGURED (409), and INSERT_RETURNING_EMPTY (500) for
  post-insert returning-empty guards.
* Five vitest assertions updated to match the new user-facing wording
  (client-merge "already been merged", expense/interest "couldn't find
  that …", documenso "signing service didn't respond").

Test status: 1168/1168 vitest, tsc clean.

Refs: docs/audit-comprehensive-2026-05-05.md HIGH §16 (auditor-H Issue 1)
+ MED §11 (auditor-G Issue 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 20:18:05 +02:00
Matt Ciaccio
4723994bdc feat(errors): platform-wide request ids + error codes + admin inspector
End-to-end error-handling overhaul. A user hitting any failure now sees
a plain-text message + stable error code + reference id. A super admin
can paste the id into /admin/errors/<id> for the full request shape,
sanitized body, error stack, and a heuristic likely-cause hint.

REQUEST CONTEXT (AsyncLocalStorage)
- src/lib/request-context.ts mints a per-request frame carrying
  requestId + portId + userId + method + path + start timestamp.
- withAuth wraps every authenticated handler in runWithRequestContext
  and accepts an upstream X-Request-Id header (validated shape) or
  generates a fresh UUID. The id ALWAYS leaves on the X-Request-Id
  response header, including early-return 401/403/4xx paths.
- Pino logger reads from the same context via mixin — every log
  line emitted during the request automatically carries the ids
  with no per-call threading.

ERROR CODE REGISTRY
- src/lib/error-codes.ts defines stable DOMAIN_REASON codes with
  HTTP status + plain-text user-facing message (no jargon, written
  for the rep on the phone with a customer).
- New CodedError class wraps a registered code + optional
  internalMessage (admin-only — never sent to client).
- Existing AppError subclasses got plain-text default rewrites so
  legacy throw sites improve immediately without migration.
- High-impact services migrated to specific codes:
  expenses (RECEIPT_REQUIRED, INVOICE_LINKED), interest-berths
  (CROSS_PORT_LINK_REJECTED), berth-pdf (PDF_MAGIC_BYTE / PDF_EMPTY /
  PDF_TOO_LARGE / VERSION_ALREADY_CURRENT), recommender
  (INTEREST_PORT_MISMATCH).

ERROR ENVELOPE
- errorResponse always sets X-Request-Id header + requestId field.
- 5xx responses include a "Quote error ID …" friendly line.
- 4xx kept clean (validation, permission, not-found don't pollute
  the inspector — they're already in audit log).

PERSISTENCE (error_events table, migration 0040)
- One row per 5xx, keyed on requestId, with method/path/status/error
  name+message/stack head (4KB cap)/sanitized body excerpt (1KB cap;
  password/token/secret/etc keys redacted)/duration/IP/UA/metadata.
- captureErrorEvent extracts Postgres SQLSTATE/severity/cause.code
  so the classifier can recognize FK / unique / NOT NULL / schema-
  drift violations.
- Failure to persist is logged-not-thrown.

LIKELY-CULPRIT CLASSIFIER (src/lib/error-classifier.ts)
- 4-pass heuristic (first match wins):
  1. Postgres SQLSTATE → human reason (23503 FK, 23505 unique,
     42703 schema drift, 53300 connection limit, …)
  2. Error class name (AbortError, TimeoutError, FetchError,
     ZodError)
  3. Stack-path patterns (/lib/storage/, /lib/email/, documenso,
     openai|claude, /queue/workers/)
  4. Free-text message keywords (econnrefused, rate limit, timeout,
     unauthorized|invalid api key)
- Returns { label, hint, subsystem } for the inspector badge.

CLIENT SIDE
- apiFetch throws structured ApiError with message + code + requestId
  + details + retryAfter.
- toastError() helper renders the standard 3-line toast:
  plain message / Error code: X / Reference ID: Y [Copy ID].

ADMIN INSPECTOR
- /<port>/admin/errors lists captured 5xx with status badge + path +
  likely-culprit badge + truncated message + reference id. Filter by
  status code; auto-refresh via TanStack Query.
- /<port>/admin/errors/<requestId> deep-dive: request shape, full
  error name+message+stack, sanitized body excerpt, raw metadata,
  registered-code lookup (so admin can compare to what user saw),
  likely-culprit hint with subsystem tag.
- /<port>/admin/errors/codes is the in-app code reference page —
  every registered code grouped by domain prefix, searchable, with
  HTTP status + user message inline. Linked from inspector header
  so admins can flip to it while triaging.
- Permission: admin.view_audit_log. Super admins see all ports;
  regular admins port-scoped.
- system-monitoring dashboard now surfaces error_events alongside
  permission_denied audit + queue failed jobs (RecentError gains
  source: 'request' variant).

DOCS
- docs/error-handling.md walks through coded errors, plain-text
  message guidelines, client toasting, admin inspector usage,
  persistence rules, classifier internals, pruning, and the
  legacy → CodedError migration path.

MIGRATION SAFETY
- Audit confirmed all 41 migrations (0000-0040) apply cleanly in
  journal order against an empty DB. 0040 references ports(id)
  which exists from 0000. 0035/0038 don't deadlock under sequential
  psql -f. Removed redundant idx_ds_sent_by from 0038 (created in
  0037).

Tests: 1168/1168 vitest passing. tsc clean.
- security-error-responses tests updated for plain-text messages
  + new optional response keys (code/requestId/message).
- berth-pdf-versions tests assert stable error codes via
  toMatchObject({ code }) rather than message regex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:12:59 +02:00
Matt Ciaccio
8699f81879 chore(style): codebase em-dash sweep + minor layout polish
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
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>
2026-05-04 22:57:01 +02:00
4c20bcffcd Fix all ESLint errors: remove unused imports, replace any types
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m10s
Build & Push Docker Images / build-and-push (push) Has been skipped
Build & Push Docker Images / deploy (push) Has been skipped
- 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>
2026-03-26 12:06:18 +01:00
67d7e6e3d5 Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled
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>
2026-03-26 11:52:51 +01:00