Files
pn-new-crm/package.json

185 lines
6.3 KiB
JSON
Raw Normal View History

{
"name": "port-nimara-crm",
"version": "0.1.0",
"private": true,
fix(audit-tier-6): validation, perms, ops/infra, per-port webhook secret Final audit polish — closes the remaining LOW + MED items the previous tiers didn't reach: * Validation hardening: me.preferences uses .strict() + 8KB cap instead of unbounded .passthrough(); files.uploadFile gains magic-byte verification (jpeg/png/gif/webp/pdf/doc/xlsx); OCR scan endpoint enforces 10MB cap + magic-byte check on receipt images; port logoUrl + me.avatarUrl reject javascript:/data: schemes via a shared httpUrl refinement. * Permission gates: document-sends/{brochure,berth-pdf} now require email.send (was withAuth-only); document-sends/{preview,list} on email.view; ai/email-draft on email.send; documents/[id]/send uses send_for_signing (was create); expenses/export/parent-company flips from hard isSuperAdmin to expenses.export for parity; admin/users/options gated on reminders.assign_others (was withAuth). * Envelope hygiene: auth/set-password switches the third {message} variant to errorResponse + {data: {email}}; ai/email-draft wraps jobId in {data: {jobId}}. * UI polish: reports-list.handleDownload surfaces failures via toastError (was console-only). * Ops/infra: pin pnpm@10.33.2 across all three Dockerfiles + packageManager field in package.json; Dockerfile.worker re-orders user creation BEFORE pnpm install so node_modules / .cache dirs are worker-owned (fixes tesseract.js + sharp EACCES at first PDF parse); add Redis-ping HEALTHCHECK to the worker container. * Public health endpoint: returns full env+appUrl payload only when the caller presents X-Intake-Secret, otherwise a minimal {status} so generic uptime monitors still work but anonymous internet doesn't get deployment fingerprints. * Per-port Documenso webhook secret: new system_settings key + listDocumensoWebhookSecrets() helper. The webhook receiver iterates every configured per-port secret with timing-safe comparison + falls back to env, then forwards the resolved portId into handleDocumentExpired so two ports sharing a documensoId cannot cross-mutate. Deferred (handled in dedicated follow-up PRs): * Tier 5.1 — direct service tests for portal-auth / users / email-accounts / document-sends / sales-email-config. MED, large test-writing scope. * The {ok: true} → {data: null} envelope migration across alerts/expenses/admin-ocr-settings/storage routes. Mechanical but needs coordinated client + test updates. * CSP-nonce migration (drop unsafe-inline) — needs middleware-level nonce generation that the Next 15 router has to thread through. * Idempotency-Key header on Documenso createDocument. Requires schema column on documents to persist the key; deferred so it doesn't bundle a migration into this commit. * The 16 better-auth user_id FKs — separate dedicated migration with care (some columns are NOT NULL today and cascade decisions matter). * PermissionGate / Skeleton / EmptyState wraps across 5 admin lists (auditor-H §§36–37) and the residential-clients filter bar. Test status: 1175/1175 vitest, tsc clean. Refs: docs/audit-comprehensive-2026-05-05.md MED §§28,29,30 + LOW §§32–43 + HIGH §9 (Documenso secrets follow-up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:03:31 +02:00
"packageManager": "pnpm@10.33.2",
"scripts": {
"dev": "next dev --turbopack -H 0.0.0.0",
"build": "next build && pnpm build:server",
"build:server": "esbuild src/server.ts --bundle --platform=node --target=node20 --format=cjs --outdir=dist --packages=external --tsconfig=tsconfig.server.json",
"build:worker": "esbuild src/worker.ts --bundle --platform=node --target=node20 --format=cjs --outdir=dist --packages=external --tsconfig=tsconfig.server.json",
"start": "next start",
"lint": "eslint .",
"format": "prettier --write \"src/**/*.{ts,tsx,json,css}\"",
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
feat(audit-wave-1): real db:migrate runner with CONCURRENTLY support Closes Wave 1.1 (CRITICAL): the production-grade migration runner the audit flagged as missing. Why drizzle-kit migrate alone wasn't enough: - Wraps every migration in a single transaction. Postgres forbids CREATE INDEX CONCURRENTLY inside a transaction (25001), so the 6 composite indexes in 0052_audit_critical_fixes.sql never landed in prod. - db:push silently diverges from migration-tracked truth on DDL the kit can't infer from the schema (CHECK constraints, partial unique indexes, the berth-pdf circular FK). scripts/db-migrate.ts: - Reads journal-ordered migrations from src/lib/db/migrations. - Tracks applied state in drizzle.__drizzle_migrations (same schema Drizzle's own tools use). - Splits each migration on `--> statement-breakpoint`. - Classifies each statement: CREATE/REINDEX/DROP INDEX CONCURRENTLY → outside transaction; everything else → batched in one tx per migration. Transactional batch runs first, CONCURRENTLY second. Three modes: - `pnpm db:migrate` — apply pending migrations - `pnpm db:migrate:status` — diff applied vs disk - `pnpm db:migrate:baseline` — mark all as applied without running them. Use ONCE per env when schema was bootstrapped via db:push. Also fixes scripts/tsc-staged.mjs: temp tsconfig now lives in `node_modules/.cache/tsc-staged/` (was /tmp) AND explicitly lists `types: [node, react, react-dom]` so @types/* auto-resolution works when `include: []` short-circuits TS's default discovery. For the existing prod cutover: After `db:migrate:baseline`, manually verify 0052's composite indexes exist: SELECT indexname FROM pg_indexes WHERE indexname IN ('idx_files_port_client', 'idx_files_port_company', 'idx_files_port_yacht', 'idx_docs_port_client', 'idx_docs_port_company', 'idx_docs_port_yacht'); If missing, paste 0052's CREATE INDEX CONCURRENTLY statements into a `psql` session directly (each runs OUTSIDE a transaction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:04:52 +02:00
"db:migrate": "tsx scripts/db-migrate.ts apply",
"db:migrate:status": "tsx scripts/db-migrate.ts status",
"db:migrate:baseline": "tsx scripts/db-migrate.ts baseline",
"db:studio": "drizzle-kit studio",
"db:seed": "tsx src/lib/db/seed.ts",
"db:seed:realistic": "tsx src/lib/db/seed.ts",
"db:seed:synthetic": "tsx src/lib/db/seed-synthetic.ts",
"db:seed:wide-synthetic": "tsx src/lib/db/seed-wide-synthetic.ts",
"db:reset": "tsx scripts/db-reset.ts --confirm",
"db:reseed:realistic": "pnpm db:reset && pnpm db:seed:realistic",
"db:reseed:synthetic": "pnpm db:reset && pnpm db:seed:synthetic",
"db:backfill:doc-folders": "tsx scripts/backfill-document-folders.ts",
test(e2e): exhaustive click-through suite + destructive narrow tests PR 14: adds a tier-3.5 Playwright pass that opens every refactored page, clicks every visible button/link/role=button, and asserts no console errors, no app-side network 4xx/5xx, and no click-time exceptions. Helper: - tests/helpers/click-everything.ts — shared `clickEverythingOnPage` with default skips for destructive selectors (archive, delete, transfer, sign-out), auto-closing of dialogs, and return-to-start after navigation. Exhaustive specs (tests/e2e/exhaustive/): - 01-yachts: list + detail + transfer dialog - 02-companies: list + detail + add-membership dialog - 03-reservations: berth list + detail reservations tab + reserve dialog - 04-client-detail: list + detail walking every tab - 05-eoi-generate: generate dialog opens with Documenso option - 06-invoice-form: new-invoice dialog billing-entity toggle - 07-berths: list + detail walking every tab - 08-portal: client portal yachts / memberships / reservations - 09-navigation: every primary nav target loads cleanly Destructive specs (tests/e2e/destructive/): - 01-yacht-archive: create-via-API → archive via UI → assert removed. Skips with a clear message when the global setup does not seed an owner client (avoids brittle failures while the full destructive fixture lands). Playwright config: testDir hoisted to ./tests/e2e; new `exhaustive` and `destructive` projects share the existing setup project. New scripts test:e2e / test:e2e:smoke / test:e2e:exhaustive / test:e2e:destructive in package.json drive each project independently. CI integration deferred — no .github/workflows/* exists in this repo yet, so the PR 14 task to wire a separate CI job is N/A. The new projects will pick up automatically when a workflow lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 14:06:10 +02:00
"test:e2e": "playwright test",
"test:e2e:smoke": "playwright test --project=smoke",
"test:e2e:exhaustive": "playwright test --project=exhaustive",
"test:e2e:destructive": "playwright test --project=destructive",
"prepare": "husky || true"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@formkit/auto-animate": "^0.9.0",
feat(deps): bump zod 3→4 + @hookform/resolvers 3→5 Resolved 65 type errors across the codebase via these v4 migration patterns: - `ZodError.errors` renamed to `ZodError.issues` (4 call sites in auth routes + central error handler). - `z.record(value)` now requires explicit key type: `z.record(z.string(), value)`. Updated 7 sites across templates / forms / saved-views / website-inquiries. - `.refine(check, msgFn)` second-arg shape changed — now requires an `{ error: (issue) => ... }` object form. Updated `mergeFieldsSchema` in document-templates validator. - `.transform(...).default(...)` chains: v4 enforces default value type matches transform OUTPUT. Reordered to `.default(...).transform(...)` in list-query / company-memberships handlers. - `z.coerce.*()` INPUT type widened to `unknown` in v4. Service signatures using `z.input<typeof schema>` (kept for caller flexibility around defaults) now re-parse via `schema.parse(data)` to recover the post-coercion shape Drizzle needs. Done in berth-reservations service. Invoice service narrows `lineItems` locally with a typed cast since re-parsing would double-validate. - `.optional().transform(...)` no longer propagates the optional marker through v4's new ZodPipe. Moved `.optional()` to the END of chain in `optionalDesiredDimSchema` (interests) and documents list query (folderId, signatureOnly). - ZodIssue subtype shapes simplified: `received` removed from invalid_type, `type` renamed to `origin` on too_small. Test fixtures updated. - @hookform/resolvers v5 splits Resolver into 3-generic form (Input, Context, Output). useForm calls in 6 forms (client, yacht, berth, interest, expense, invoices-new-page) now pass explicit generics: `useForm<z.input<typeof schema>, unknown, z.infer<typeof schema>>`. Verified: tsc clean (0 errors), vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:29:03 +02:00
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-email/components": "^1.0.12",
"@react-pdf/renderer": "^4.5.1",
"@sentry/nextjs": "^10.53.1",
"@socket.io/redis-adapter": "^8.3.0",
"@tanstack/query-broadcast-client-experimental": "^5.100.10",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"@tanstack/react-query": "^5.100.10",
"@tanstack/react-query-devtools": "^5.100.10",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.24",
feat(expenses): streaming expense-PDF export + receipt-less expense flag + audit-3 fixes Replaces the legacy text-only expense PDF (was just dumping rows into a single pdfme text field — no images, no pagination) with a proper streaming export modelled on the legacy Nuxt client-portal but re-architected for memory safety. The legacy implementation OOM'd on hundreds of receipts because it: - buffered every receipt image into memory simultaneously - accumulated PDF chunks into an array, concat'd at end - base64-encoded the whole PDF into a JSON response (3x peak memory) - had no image downscaling The new design: - `streamExpensePdf()` (src/lib/services/expense-pdf.service.ts): pdfkit pipes bytes directly to the HTTP response (no Buffer accumulation). Receipts are processed serially so peak heap is one image at a time. Sharp downscales any receipt > 500 KB or > 1500 px to JPEG q80 — typical 8 MB phone photo collapses to ~250 KB. For a 500-receipt export, peak RSS stays under ~100 MB; legacy needed >2 GB for the same input. - Pages: cover summary box (count, totals, currency equiv, optional processing fee), grouped expense table (groupBy=none|payer|category| date), one-page-per-receipt with header (establishment, amount, date, payer, category, file name) and full-bleed image. - Storage backend abstraction — receipts stream from `getStorageBackend().get(storageKey)`, works on MinIO/S3/filesystem. - Route: POST /api/v1/expenses/export/pdf streams binary application/pdf with cache-control:no-store. Validator caps expenseIds at 1000 to prevent runaway loops. Receipt-less expense flow (per user request): - Schema: 0033 migration adds `expenses.no_receipt_acknowledged` boolean (default false). - Validator: createExpenseSchema requires either receiptFileIds OR noReceiptAcknowledged=true; the .refine() error message tells the rep exactly what to do. updateExpenseSchema is partial and skips the rule (existing rows can be edited without re-acknowledging). - PDF: receiptless expenses get an inline red "(no receipt)" tag in the establishment cell + a red footer warning in the summary box showing the count and at-risk amount. - The legacy parent-company reimbursement queue may refuse to pay receiptless expenses, so the warning is load-bearing for ops. Audit-3 fixes piggy-backed: - 🔴 Tesseract OCR runtime now races a 30s timeout (CPU-bomb DoS protection — a crafted PDF rasterizing to high-res noise could pin the worker indefinitely). - 🟠 brochures.service.ts:listBrochures dropped a wasted query (the legacy single-brochure fast-path was discarding its result on the multi-brochure branch). - 🟠 berth-pdf.service.ts:listBerthPdfVersions now Promise.all's the presignDownload calls instead of awaiting each in a for-loop — 20-version berths went from 20× round-trip to 1×. - 🟡 public berths route no longer logs the full `row` object on enum drift (was dumping price + amenity columns into ops logs). - 🟡 dropped the dead `void sql` import from public berths route. Tests still 1163/1163. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 04:38:32 +02:00
"@types/pdfkit": "^0.17.6",
"@umami/node": "^0.4.0",
feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion Installs all five Tier 2 polish deps the audit flagged. Each integrates where it adds concrete value today: - **embla-carousel-react** — shadcn-style `<Carousel>` primitive in `src/components/ui/carousel.tsx`. Available for future berth/yacht photo galleries; no current call site beyond the primitive. - **yet-another-react-lightbox** — wired into the image branch of `file-preview-dialog.tsx`. Clicking the preview image now opens a fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the ~50kb only ships when a user actually previews an image. - **@use-gesture/react** — `usePinch` on the PdfViewer's content pane for native pinch-zoom on tablets/phones. Clamped to the same [50%, 300%] range as the +/- buttons; desktop wheel still scrolls. - **react-virtuoso** — installed but NOT wired. Inbox is naturally bounded by recent-notifications filter at ~10-20 items; ScrollArea handles it fine. Reserve for actual scale issues (admin audit log archive, etc.). - **motion** — installed but NOT wired. Pipeline kanban uses dnd-kit's own transforms and conflicts with motion's layout animation. @formkit/auto-animate already handles list-mutation animations elsewhere. Available for opportunistic adoption when a polish surface emerges that the existing libraries don't cover. Verified: tsc clean, vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:29:22 +02:00
"@use-gesture/react": "^10.3.1",
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>
2026-05-12 14:50:58 +02:00
"archiver": "^7.0.1",
"better-auth": "^1.6.11",
feat(scan): compress phone-photo receipts before upload (browser-image-compression) Phase 3 — wires `browser-image-compression` into the scan-shell so 4-12 MB phone photos get crushed to ~500 KB in a WebWorker before any other work happens. Receipts come back from tesseract + the AI parse much faster on mobile bandwidth, and the server's sharp pipeline has less to chew on. compressReceiptIfHeavy(file): - Pass-through for SVGs / PDFs / non-images - Pass-through for files already under 1 MB - Otherwise: imageCompression with maxSizeMB: 0.5, maxWidthOrHeight: 2000, useWebWorker: true, preserveExif: false (auto-rotate to EXIF orientation then strip metadata so the receipt isn't sideways) - PNG → JPEG transcode (smaller for natural photo content) - Initial quality 0.85 — Tesseract's sweet spot for receipt text - Lazy-loaded import: the WebWorker bundle isn't on the critical path - try/catch fallback: if compression itself throws, fall through to the original file so a corner-case bug never blocks a save Wired into handleFile(rawFile) before tesseract runs and before the receipt is sent to /api/v1/expenses/scan-receipt. Downstream upload through handleSubmit() also benefits because the same compressed File flows through. Concrete impact for a 12 MP iPhone receipt (~8 MB): Before: 8 MB upload, 8 MB tesseract input After: ~500 KB upload, 2000px max edge tesseract input Bandwidth + battery + perceived latency win on the mobile expense scanner path. No behaviour change for desktop file uploads under 1 MB. 1298/1298 vitest green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:21:37 +02:00
"browser-image-compression": "^2.0.2",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"bullmq": "^5.76.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"country-flag-icons": "^1.6.17",
fix(audit-wave-11): asset hygiene + datetime correctness **asset-auditor C1+C2+H1+H3 — image normalization** Add `src/lib/services/image-normalize.ts` and wire it into `uploadFile()` so every accepted image is re-encoded via sharp before hitting storage: - Strips EXIF (GPS coords, device serial, photographer) so uploaded photos don't leak per-pixel PII to anyone with a download URL (C1). - Caps dimensions at 4096px via `resize({fit:'inside',withoutEnlargement:true})` so a 30000×30000 palette PNG can't decompression-bomb a downstream sharp decode (C2). - Re-encode drops polyglot trailers (PDF+JPEG sandwiches that beat the prefix-only magic-byte check) (H1). - Freezes animated GIFs to first frame (H3). Avatar route already funnels through uploadFile so it's covered by the single change. **asset-auditor M2 — sanitizeFilename strips RTL/zero-width** Add Unicode NFC + a strip of bidi-control (U+202A-U+202E, U+2066-U+2069) + zero-width chars (U+200B-U+200F, U+FEFF) to `sanitizeFilename`. Closes the classic Windows-icon-spoof vector (`invoice_‮fdp.exe` displaying as `invoice_exe.pdf`) plus folder-listing collision spoofs. **datetime-auditor C1 — reminder dueAt drift on every save** The `<input type="datetime-local">` round-trip in reminder-form.tsx used `iso.slice(0,16)` (load) and `new Date(value).toISOString()` (submit). The slice drops the `Z` so a UTC instant is mis-interpreted as local on load, then converted back to UTC on save — every save of an existing Warsaw reminder drifted backwards by 2h (CEST). After two saves the reminder appears at 06:00 instead of 10:00. Add `toLocalDatetimeLocal(d: Date)` helper that builds the local YYYY-MM-DDTHH:MM string from getter methods so the round-trip is TZ-safe. snooze-dialog already did this correctly; the contact-log dialog also uses the correct localIsoString pattern. **datetime-auditor C2 — BullMQ cron in UTC, not port-local** `upsertJobScheduler` defaulted `tz` to UTC. Patterns like `0 8 * * *` were intended as "8 AM Warsaw" but fired at 09:00 winter / 10:00 summer. Pass `tz: process.env.SCHEDULER_TZ ?? 'Europe/Warsaw'`. Sub-hourly / hourly patterns are TZ-invariant and stay UTC. **datetime-auditor C3 — report-scheduler never advanced next_run_at** The minutely scheduler selected `nextRunAt <= now()` and enqueued generate-report — but never bumped nextRunAt. For weekly/monthly reports this meant the job re-fired every single minute until a human zeroed the row out, flooding recipients with dupes. Now uses `cron-parser` (added as a dep) to compute the next fire from `report.schedule` and UPDATEs the row BEFORE the enqueue. Malformed cron expressions disable the row instead of re-attempting every minute. Tests 1315/1315. Migration 0058 applied via psql. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:58:58 +02:00
"cron-parser": "^5.5.0",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.45.2",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.6",
feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion Installs all five Tier 2 polish deps the audit flagged. Each integrates where it adds concrete value today: - **embla-carousel-react** — shadcn-style `<Carousel>` primitive in `src/components/ui/carousel.tsx`. Available for future berth/yacht photo galleries; no current call site beyond the primitive. - **yet-another-react-lightbox** — wired into the image branch of `file-preview-dialog.tsx`. Clicking the preview image now opens a fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the ~50kb only ships when a user actually previews an image. - **@use-gesture/react** — `usePinch` on the PdfViewer's content pane for native pinch-zoom on tablets/phones. Clamped to the same [50%, 300%] range as the +/- buttons; desktop wheel still scrolls. - **react-virtuoso** — installed but NOT wired. Inbox is naturally bounded by recent-notifications filter at ~10-20 items; ScrollArea handles it fine. Reserve for actual scale issues (admin audit log archive, etc.). - **motion** — installed but NOT wired. Pipeline kanban uses dnd-kit's own transforms and conflicts with motion's layout animation. @formkit/auto-animate already handles list-mutation animations elsewhere. Available for opportunistic adoption when a polish surface emerges that the existing libraries don't cover. Verified: tsc clean, vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:29:22 +02:00
"embla-carousel-react": "^8.6.0",
feat(launch-readiness-batch): UAT drains, navigation refactor, launch infra, trackers 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>
2026-05-27 22:42:37 +02:00
"exceljs": "^4.4.0",
"imapflow": "^1.3.3",
"ioredis": "^5.10.1",
feat(i18n): country/phone/timezone/subdivision primitives + form wiring Cross-cutting i18n polish for forms across the marina + residential + company domains. Introduces a single source of truth for country/phone/timezone/ subdivision data and replaces every nationality-as-free-text and timezone- as-string Input with a dedicated combobox. PR1 Countries — ALL_COUNTRY_CODES (~250 ISO-3166-1 alpha-2), Intl.DisplayNames for localized labels, detectDefaultCountry() with navigator-region fallback to US, CountryCombobox with regional-indicator flag glyphs + compact mode for inline use. PR2 Phone — libphonenumber-js wrapper (parsePhone / formatAsYouType / callingCodeFor), PhoneInput with flag dropdown + national-format AsYouType + paste-detect that flips the country dropdown for pasted international strings. PR3 Timezones — country->IANA map (250 entries, multi-zone for AU/BR/CA/CD/ ID/KZ/MN/MX/RU/US), formatTimezoneLabel ("Europe/London (UTC+1)"), TimezoneCombobox with Suggested/All grouping driven by countryHint. PR4 Subdivisions — wraps the iso-3166-2 npm package (~5000 ISO 3166-2 codes for every country), per-country cache, SubdivisionCombobox with "Pick a country first" / "No regions available" empty states. PR5 Schema deltas (migration 0015) — clients.nationality_iso, clientContacts {value_e164, value_country}, clientAddresses {country_iso, subdivision_iso}, residentialClients {phone_e164, phone_country, nationality_iso, timezone, place_of_residence_country_iso, subdivision_iso}, companies {incorporation_ country_iso, incorporation_subdivision_iso}, companyAddresses {country_iso, subdivision_iso}. Plus shared zod validators (validators/i18n.ts) used by every entity validator + route handler. PR6 ClientForm + ClientDetail — CountryCombobox replaces nationality Input, TimezoneCombobox replaces timezone Input (driven by nationalityIso hint), PhoneInput conditionally rendered for phone/whatsapp contacts. Inline editors (InlineCountryField / InlineTimezoneField / InlinePhoneField) for the detail-page overview rows + ContactsEditor. PR7 Residential client form + detail — phone -> PhoneInput, nationality/ timezone/place-of-residence-country/subdivision rows in both create sheet and inline-editable detail view. Subdivision wipes when country flips since codes are country-scoped. PR8 Company form + detail — incorporation country -> CountryCombobox, incorporation region -> SubdivisionCombobox in both modes. PR9 Public inquiry endpoint — accepts pre-normalized phoneE164/phoneCountry and i18n fields from newer website builds, server-side parsePhone() fallback for legacy raw-international submissions. Old Nuxt builds keep working unchanged. Tests: 4 unit suites for the primitives (25 tests), 1 integration spec for the public phone-normalization path (3 tests), 1 smoke spec asserting the combobox triggers render in all three create sheets. Test totals: vitest 713 -> 741 (+28). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:13:08 +02:00
"iso-3166-2": "^1.0.0",
"isomorphic-dompurify": "^3.12.0",
"jose": "^6.2.3",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"libphonenumber-js": "^1.13.1",
fix(types): unblock catch-all routes under stricter Next 15.5 typing + Phase 2B deps Two changes bundled (build was failing on the type fix; deps came along on the same branch). 1. RouteHandler / withAuth / withPermission are now generic over the route's params shape. Default stays `Record<string, string>` for the common `[id]`-style routes (no caller changes needed). Catch-all routes like `[...path]` declare their narrow shape via a type-arg: export const PATCH = withAuth<{ path: string[] }>( withPermission<{ path: string[] }>('files', 'manage_folders', async (req, ctx, params) => { /* params.path: string[] */ } ), ); Without this, Next.js 15.5+'s stricter route-type checking rejected the build because the inferred `params: Promise<{ path: string[] }>` for `[...path]` doesn't satisfy `Promise<Record<string, string>>`. Updated `src/app/api/v1/files/folders/[...path]/route.ts` (the only catch-all in the tree right now) to use the new generic. 2. Phase 2B deps (within-major-jump where the API didn't actually break): - @pdfme/common, @pdfme/generator, @pdfme/schemas: 5.5.10 → 6.1.2 (closes 3 mod XSS/SSRF/decompression-bomb advisories) - lucide-react: 0.460.0 → 1.14.0 - sonner: 1.7.4 → 2.0.7 - tailwind-merge: 2.6.1 → 3.5.0 Tests: 1185/1185 vitest. tsc clean. Local `next build` succeeds. Reverted (deferred to a focused PR): - @hookform/resolvers 5: Resolver<T> typing change requires per-form useForm migration - eslint 10: incompatible with @rushstack/eslint-patch (pulled in by eslint-config-next) - react-day-picker 10: ClassNames removed `table`; needs calendar.tsx migration - zod 4: 94 type errors cascading through drizzle insert types; needs comprehensive migration Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:07:07 +02:00
"lucide-react": "^1.14.0",
"mailparser": "^3.9.8",
"minio": "^8.0.7",
feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion Installs all five Tier 2 polish deps the audit flagged. Each integrates where it adds concrete value today: - **embla-carousel-react** — shadcn-style `<Carousel>` primitive in `src/components/ui/carousel.tsx`. Available for future berth/yacht photo galleries; no current call site beyond the primitive. - **yet-another-react-lightbox** — wired into the image branch of `file-preview-dialog.tsx`. Clicking the preview image now opens a fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the ~50kb only ships when a user actually previews an image. - **@use-gesture/react** — `usePinch` on the PdfViewer's content pane for native pinch-zoom on tablets/phones. Clamped to the same [50%, 300%] range as the +/- buttons; desktop wheel still scrolls. - **react-virtuoso** — installed but NOT wired. Inbox is naturally bounded by recent-notifications filter at ~10-20 items; ScrollArea handles it fine. Reserve for actual scale issues (admin audit log archive, etc.). - **motion** — installed but NOT wired. Pipeline kanban uses dnd-kit's own transforms and conflicts with motion's layout animation. @formkit/auto-animate already handles list-mutation animations elsewhere. Available for opportunistic adoption when a polish surface emerges that the existing libraries don't cover. Verified: tsc clean, vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:29:22 +02:00
"motion": "^12.38.0",
2026-05-12 22:24:51 +02:00
"next": "16.2.6",
"next-themes": "^0.4.6",
"nodemailer": "^8.0.7",
"openai": "^6.37.0",
"p-limit": "^7.3.0",
"p-queue": "^9.2.0",
"p-retry": "^8.0.0",
"papaparse": "^5.5.3",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "^5.7.284",
feat(expenses): streaming expense-PDF export + receipt-less expense flag + audit-3 fixes Replaces the legacy text-only expense PDF (was just dumping rows into a single pdfme text field — no images, no pagination) with a proper streaming export modelled on the legacy Nuxt client-portal but re-architected for memory safety. The legacy implementation OOM'd on hundreds of receipts because it: - buffered every receipt image into memory simultaneously - accumulated PDF chunks into an array, concat'd at end - base64-encoded the whole PDF into a JSON response (3x peak memory) - had no image downscaling The new design: - `streamExpensePdf()` (src/lib/services/expense-pdf.service.ts): pdfkit pipes bytes directly to the HTTP response (no Buffer accumulation). Receipts are processed serially so peak heap is one image at a time. Sharp downscales any receipt > 500 KB or > 1500 px to JPEG q80 — typical 8 MB phone photo collapses to ~250 KB. For a 500-receipt export, peak RSS stays under ~100 MB; legacy needed >2 GB for the same input. - Pages: cover summary box (count, totals, currency equiv, optional processing fee), grouped expense table (groupBy=none|payer|category| date), one-page-per-receipt with header (establishment, amount, date, payer, category, file name) and full-bleed image. - Storage backend abstraction — receipts stream from `getStorageBackend().get(storageKey)`, works on MinIO/S3/filesystem. - Route: POST /api/v1/expenses/export/pdf streams binary application/pdf with cache-control:no-store. Validator caps expenseIds at 1000 to prevent runaway loops. Receipt-less expense flow (per user request): - Schema: 0033 migration adds `expenses.no_receipt_acknowledged` boolean (default false). - Validator: createExpenseSchema requires either receiptFileIds OR noReceiptAcknowledged=true; the .refine() error message tells the rep exactly what to do. updateExpenseSchema is partial and skips the rule (existing rows can be edited without re-acknowledging). - PDF: receiptless expenses get an inline red "(no receipt)" tag in the establishment cell + a red footer warning in the summary box showing the count and at-risk amount. - The legacy parent-company reimbursement queue may refuse to pay receiptless expenses, so the warning is load-bearing for ops. Audit-3 fixes piggy-backed: - 🔴 Tesseract OCR runtime now races a 30s timeout (CPU-bomb DoS protection — a crafted PDF rasterizing to high-res noise could pin the worker indefinitely). - 🟠 brochures.service.ts:listBrochures dropped a wasted query (the legacy single-brochure fast-path was discarding its result on the multi-brochure branch). - 🟠 berth-pdf.service.ts:listBerthPdfVersions now Promise.all's the presignDownload calls instead of awaiting each in a for-loop — 20-version berths went from 20× round-trip to 1×. - 🟡 public berths route no longer logs the full `row` object on enum drift (was dumping price + amenity columns into ops logs). - 🟡 dropped the dead `void sql` import from public berths route. Tests still 1163/1163. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 04:38:32 +02:00
"pdfkit": "^0.18.0",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"postgres": "^3.4.9",
"react": "^19.2.6",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"react-day-picker": "^10.0.0",
"react-dom": "^19.2.6",
feat: autonomous backlog push — admin UX overhaul + storage parity + residential parity + Documenso Phase 1 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
"react-easy-crop": "^5.5.7",
"react-email": "^6.1.3",
"react-hook-form": "^7.75.0",
"react-image-crop": "^11.0.10",
"react-number-format": "^5.4.5",
"react-pdf": "^10.4.1",
"react-resizable-panels": "^3.0.6",
feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion Installs all five Tier 2 polish deps the audit flagged. Each integrates where it adds concrete value today: - **embla-carousel-react** — shadcn-style `<Carousel>` primitive in `src/components/ui/carousel.tsx`. Available for future berth/yacht photo galleries; no current call site beyond the primitive. - **yet-another-react-lightbox** — wired into the image branch of `file-preview-dialog.tsx`. Clicking the preview image now opens a fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the ~50kb only ships when a user actually previews an image. - **@use-gesture/react** — `usePinch` on the PdfViewer's content pane for native pinch-zoom on tablets/phones. Clamped to the same [50%, 300%] range as the +/- buttons; desktop wheel still scrolls. - **react-virtuoso** — installed but NOT wired. Inbox is naturally bounded by recent-notifications filter at ~10-20 items; ScrollArea handles it fine. Reserve for actual scale issues (admin audit log archive, etc.). - **motion** — installed but NOT wired. Pipeline kanban uses dnd-kit's own transforms and conflicts with motion's layout animation. @formkit/auto-animate already handles list-mutation animations elsewhere. Available for opportunistic adoption when a polish surface emerges that the existing libraries don't cover. Verified: tsc clean, vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:29:22 +02:00
"react-virtuoso": "^4.18.7",
"recharts": "^3.8.1",
feat(expenses): streaming expense-PDF export + receipt-less expense flag + audit-3 fixes Replaces the legacy text-only expense PDF (was just dumping rows into a single pdfme text field — no images, no pagination) with a proper streaming export modelled on the legacy Nuxt client-portal but re-architected for memory safety. The legacy implementation OOM'd on hundreds of receipts because it: - buffered every receipt image into memory simultaneously - accumulated PDF chunks into an array, concat'd at end - base64-encoded the whole PDF into a JSON response (3x peak memory) - had no image downscaling The new design: - `streamExpensePdf()` (src/lib/services/expense-pdf.service.ts): pdfkit pipes bytes directly to the HTTP response (no Buffer accumulation). Receipts are processed serially so peak heap is one image at a time. Sharp downscales any receipt > 500 KB or > 1500 px to JPEG q80 — typical 8 MB phone photo collapses to ~250 KB. For a 500-receipt export, peak RSS stays under ~100 MB; legacy needed >2 GB for the same input. - Pages: cover summary box (count, totals, currency equiv, optional processing fee), grouped expense table (groupBy=none|payer|category| date), one-page-per-receipt with header (establishment, amount, date, payer, category, file name) and full-bleed image. - Storage backend abstraction — receipts stream from `getStorageBackend().get(storageKey)`, works on MinIO/S3/filesystem. - Route: POST /api/v1/expenses/export/pdf streams binary application/pdf with cache-control:no-store. Validator caps expenseIds at 1000 to prevent runaway loops. Receipt-less expense flow (per user request): - Schema: 0033 migration adds `expenses.no_receipt_acknowledged` boolean (default false). - Validator: createExpenseSchema requires either receiptFileIds OR noReceiptAcknowledged=true; the .refine() error message tells the rep exactly what to do. updateExpenseSchema is partial and skips the rule (existing rows can be edited without re-acknowledging). - PDF: receiptless expenses get an inline red "(no receipt)" tag in the establishment cell + a red footer warning in the summary box showing the count and at-risk amount. - The legacy parent-company reimbursement queue may refuse to pay receiptless expenses, so the warning is load-bearing for ops. Audit-3 fixes piggy-backed: - 🔴 Tesseract OCR runtime now races a 30s timeout (CPU-bomb DoS protection — a crafted PDF rasterizing to high-res noise could pin the worker indefinitely). - 🟠 brochures.service.ts:listBrochures dropped a wasted query (the legacy single-brochure fast-path was discarding its result on the multi-brochure branch). - 🟠 berth-pdf.service.ts:listBerthPdfVersions now Promise.all's the presignDownload calls instead of awaiting each in a for-loop — 20-version berths went from 20× round-trip to 1×. - 🟡 public berths route no longer logs the full `row` object on enum drift (was dumping price + amenity columns into ops logs). - 🟡 dropped the dead `void sql` import from public berths route. Tests still 1163/1163. tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 04:38:32 +02:00
"sharp": "^0.34.5",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
fix(types): unblock catch-all routes under stricter Next 15.5 typing + Phase 2B deps Two changes bundled (build was failing on the type fix; deps came along on the same branch). 1. RouteHandler / withAuth / withPermission are now generic over the route's params shape. Default stays `Record<string, string>` for the common `[id]`-style routes (no caller changes needed). Catch-all routes like `[...path]` declare their narrow shape via a type-arg: export const PATCH = withAuth<{ path: string[] }>( withPermission<{ path: string[] }>('files', 'manage_folders', async (req, ctx, params) => { /* params.path: string[] */ } ), ); Without this, Next.js 15.5+'s stricter route-type checking rejected the build because the inferred `params: Promise<{ path: string[] }>` for `[...path]` doesn't satisfy `Promise<Record<string, string>>`. Updated `src/app/api/v1/files/folders/[...path]/route.ts` (the only catch-all in the tree right now) to use the new generic. 2. Phase 2B deps (within-major-jump where the API didn't actually break): - @pdfme/common, @pdfme/generator, @pdfme/schemas: 5.5.10 → 6.1.2 (closes 3 mod XSS/SSRF/decompression-bomb advisories) - lucide-react: 0.460.0 → 1.14.0 - sonner: 1.7.4 → 2.0.7 - tailwind-merge: 2.6.1 → 3.5.0 Tests: 1185/1185 vitest. tsc clean. Local `next build` succeeds. Reverted (deferred to a focused PR): - @hookform/resolvers 5: Resolver<T> typing change requires per-form useForm migration - eslint 10: incompatible with @rushstack/eslint-patch (pulled in by eslint-config-next) - react-day-picker 10: ClassNames removed `table`; needs calendar.tsx migration - zod 4: 94 type errors cascading through drizzle insert types; needs comprehensive migration Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:07:07 +02:00
"sonner": "^2.0.7",
"svgo": "^4.0.1",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"tailwind-merge": "^3.6.0",
"tesseract.js": "^7.0.0",
"ts-pattern": "^5.9.0",
"tw-animate-css": "^1.4.0",
"unpdf": "^1.6.2",
"vaul": "^1.1.2",
"web-vitals": "^5.2.0",
feat(deps): Tier 2 UX polish — embla, lightbox, gestures, virtuoso, motion Installs all five Tier 2 polish deps the audit flagged. Each integrates where it adds concrete value today: - **embla-carousel-react** — shadcn-style `<Carousel>` primitive in `src/components/ui/carousel.tsx`. Available for future berth/yacht photo galleries; no current call site beyond the primitive. - **yet-another-react-lightbox** — wired into the image branch of `file-preview-dialog.tsx`. Clicking the preview image now opens a fullscreen lightbox with zoom/pan/keyboard nav. Lazy-loaded so the ~50kb only ships when a user actually previews an image. - **@use-gesture/react** — `usePinch` on the PdfViewer's content pane for native pinch-zoom on tablets/phones. Clamped to the same [50%, 300%] range as the +/- buttons; desktop wheel still scrolls. - **react-virtuoso** — installed but NOT wired. Inbox is naturally bounded by recent-notifications filter at ~10-20 items; ScrollArea handles it fine. Reserve for actual scale issues (admin audit log archive, etc.). - **motion** — installed but NOT wired. Pipeline kanban uses dnd-kit's own transforms and conflicts with motion's layout animation. @formkit/auto-animate already handles list-mutation animations elsewhere. Available for opportunistic adoption when a polish surface emerges that the existing libraries don't cover. Verified: tsc clean, vitest 1315/1315, next build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:29:22 +02:00
"yet-another-react-lightbox": "^3.32.0",
feat(deps): bump zod 3→4 + @hookform/resolvers 3→5 Resolved 65 type errors across the codebase via these v4 migration patterns: - `ZodError.errors` renamed to `ZodError.issues` (4 call sites in auth routes + central error handler). - `z.record(value)` now requires explicit key type: `z.record(z.string(), value)`. Updated 7 sites across templates / forms / saved-views / website-inquiries. - `.refine(check, msgFn)` second-arg shape changed — now requires an `{ error: (issue) => ... }` object form. Updated `mergeFieldsSchema` in document-templates validator. - `.transform(...).default(...)` chains: v4 enforces default value type matches transform OUTPUT. Reordered to `.default(...).transform(...)` in list-query / company-memberships handlers. - `z.coerce.*()` INPUT type widened to `unknown` in v4. Service signatures using `z.input<typeof schema>` (kept for caller flexibility around defaults) now re-parse via `schema.parse(data)` to recover the post-coercion shape Drizzle needs. Done in berth-reservations service. Invoice service narrows `lineItems` locally with a typed cast since re-parsing would double-validate. - `.optional().transform(...)` no longer propagates the optional marker through v4's new ZodPipe. Moved `.optional()` to the END of chain in `optionalDesiredDimSchema` (interests) and documents list query (folderId, signatureOnly). - ZodIssue subtype shapes simplified: `received` removed from invalid_type, `type` renamed to `origin` on too_small. Test fixtures updated. - @hookform/resolvers v5 splits Resolver into 3-generic form (Input, Context, Output). useForm calls in 6 forms (client, yacht, berth, interest, expense, invoices-new-page) now pass explicit generics: `useForm<z.input<typeof schema>, unknown, z.infer<typeof schema>>`. Verified: tsc clean (0 errors), vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:29:03 +02:00
"zod": "^4.4.3",
"zustand": "^5.0.13"
},
"devDependencies": {
"@axe-core/playwright": "^4.11.3",
"@faker-js/faker": "^10.4.0",
"@hookform/devtools": "^4.4.0",
"@next/bundle-analyzer": "^16.2.6",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"@playwright/test": "^1.60.0",
"@tailwindcss/postcss": "^4.3.0",
"@total-typescript/ts-reset": "^0.6.1",
feat(gdpr): staff-triggered client-data export bundle (Article 15) Adds a full GDPR Article 15 (right of access) workflow. Staff trigger an export from the client detail; a BullMQ worker assembles every row keyed to that client (profile, contacts, addresses, notes, tags, yachts, company memberships, interests, reservations, invoices, documents, last 500 audit events) into JSON + a self-contained HTML report, ZIPs them, uploads to MinIO, and optionally emails the client a 7-day signed download link. - New table gdpr_exports tracks lifecycle (pending → building → ready → sent / failed) with a 30-day cleanup target - Bundle builder (gdpr-bundle-builder.ts) — pure read-side, tenant- scoped, with HTML escaping to block injection from rogue field values - Worker hook in export queue dispatches on job name 'gdpr-export' - New audit actions: 'request_gdpr_export', 'send_gdpr_export' - API: POST/GET /api/v1/clients/:id/gdpr-export (admin-gated, exports rate-limit, Article-15 audit on POST); GET /:exportId returns a fresh signed URL - UI: <GdprExportButton> dialog on client detail header — admin-only, shows recent exports, supports email-to-client + override recipient, polls every 5s while open - Validation: refuses email-to-client when no primary email + no override (rather than silently dropping the send) Tests: 778/778 vitest (was 771) — +7 covering builder happy path, HTML escaping, tenant isolation, empty client, request-flow validation, and audit / queue interaction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:06:31 +02:00
"@types/archiver": "^7.0.0",
"@types/geojson": "^7946.0.16",
feat(i18n): country/phone/timezone/subdivision primitives + form wiring Cross-cutting i18n polish for forms across the marina + residential + company domains. Introduces a single source of truth for country/phone/timezone/ subdivision data and replaces every nationality-as-free-text and timezone- as-string Input with a dedicated combobox. PR1 Countries — ALL_COUNTRY_CODES (~250 ISO-3166-1 alpha-2), Intl.DisplayNames for localized labels, detectDefaultCountry() with navigator-region fallback to US, CountryCombobox with regional-indicator flag glyphs + compact mode for inline use. PR2 Phone — libphonenumber-js wrapper (parsePhone / formatAsYouType / callingCodeFor), PhoneInput with flag dropdown + national-format AsYouType + paste-detect that flips the country dropdown for pasted international strings. PR3 Timezones — country->IANA map (250 entries, multi-zone for AU/BR/CA/CD/ ID/KZ/MN/MX/RU/US), formatTimezoneLabel ("Europe/London (UTC+1)"), TimezoneCombobox with Suggested/All grouping driven by countryHint. PR4 Subdivisions — wraps the iso-3166-2 npm package (~5000 ISO 3166-2 codes for every country), per-country cache, SubdivisionCombobox with "Pick a country first" / "No regions available" empty states. PR5 Schema deltas (migration 0015) — clients.nationality_iso, clientContacts {value_e164, value_country}, clientAddresses {country_iso, subdivision_iso}, residentialClients {phone_e164, phone_country, nationality_iso, timezone, place_of_residence_country_iso, subdivision_iso}, companies {incorporation_ country_iso, incorporation_subdivision_iso}, companyAddresses {country_iso, subdivision_iso}. Plus shared zod validators (validators/i18n.ts) used by every entity validator + route handler. PR6 ClientForm + ClientDetail — CountryCombobox replaces nationality Input, TimezoneCombobox replaces timezone Input (driven by nationalityIso hint), PhoneInput conditionally rendered for phone/whatsapp contacts. Inline editors (InlineCountryField / InlineTimezoneField / InlinePhoneField) for the detail-page overview rows + ContactsEditor. PR7 Residential client form + detail — phone -> PhoneInput, nationality/ timezone/place-of-residence-country/subdivision rows in both create sheet and inline-editable detail view. Subdivision wipes when country flips since codes are country-scoped. PR8 Company form + detail — incorporation country -> CountryCombobox, incorporation region -> SubdivisionCombobox in both modes. PR9 Public inquiry endpoint — accepts pre-normalized phoneE164/phoneCountry and i18n fields from newer website builds, server-side parsePhone() fallback for legacy raw-international submissions. Old Nuxt builds keep working unchanged. Tests: 4 unit suites for the primitives (25 tests), 1 integration spec for the public phone-normalization path (3 tests), 1 smoke spec asserting the combobox triggers render in all three create sheets. Test totals: vitest 713 -> 741 (+28). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:13:08 +02:00
"@types/iso-3166-2": "^1.0.4",
"@types/mailparser": "^3.4.6",
"@types/node": "^20.19.0",
"@types/nodemailer": "^8.0.0",
"@types/papaparse": "^5.5.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/topojson-client": "^3.1.5",
"@vitejs/plugin-react": "^6.0.1",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"@vitest/coverage-v8": "^4.1.6",
"dotenv": "^17.4.2",
"drizzle-kit": "^0.31.10",
feat(deps): bump zod 3→4 + @hookform/resolvers 3→5 Resolved 65 type errors across the codebase via these v4 migration patterns: - `ZodError.errors` renamed to `ZodError.issues` (4 call sites in auth routes + central error handler). - `z.record(value)` now requires explicit key type: `z.record(z.string(), value)`. Updated 7 sites across templates / forms / saved-views / website-inquiries. - `.refine(check, msgFn)` second-arg shape changed — now requires an `{ error: (issue) => ... }` object form. Updated `mergeFieldsSchema` in document-templates validator. - `.transform(...).default(...)` chains: v4 enforces default value type matches transform OUTPUT. Reordered to `.default(...).transform(...)` in list-query / company-memberships handlers. - `z.coerce.*()` INPUT type widened to `unknown` in v4. Service signatures using `z.input<typeof schema>` (kept for caller flexibility around defaults) now re-parse via `schema.parse(data)` to recover the post-coercion shape Drizzle needs. Done in berth-reservations service. Invoice service narrows `lineItems` locally with a typed cast since re-parsing would double-validate. - `.optional().transform(...)` no longer propagates the optional marker through v4's new ZodPipe. Moved `.optional()` to the END of chain in `optionalDesiredDimSchema` (interests) and documents list query (folderId, signatureOnly). - ZodIssue subtype shapes simplified: `received` removed from invalid_type, `type` renamed to `origin` on too_small. Test fixtures updated. - @hookform/resolvers v5 splits Resolver into 3-generic form (Input, Context, Output). useForm calls in 6 forms (client, yacht, berth, interest, expense, invoices-new-page) now pass explicit generics: `useForm<z.input<typeof schema>, unknown, z.infer<typeof schema>>`. Verified: tsc clean (0 errors), vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:29:03 +02:00
"drizzle-zod": "^0.8.3",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"esbuild": "^0.28.0",
"eslint": "^9.39.4",
2026-05-12 22:24:51 +02:00
"eslint-config-next": "16.2.6",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"lint-staged": "^17.0.4",
"postcss": "^8.5.14",
"prettier": "^3.8.3",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"react-grab": "^0.1.34",
"tailwindcss": "^4.3.0",
"tsx": "^4.21.0",
"type-fest": "^5.6.0",
"typescript": "^6.0.3",
deps: bump Tier-A patches + react-day-picker 10 + esbuild 0.28 Successfully bumped: - bullmq 5.76.6 → 5.76.8 - @tanstack/react-query 5.100.9 → 5.100.10 - @tanstack/react-query-devtools 5.100.9 → 5.100.10 - better-auth 1.6.9 → 1.6.10 - @playwright/test 1.59.1 → 1.60.0 - libphonenumber-js 1.12.43 → 1.13.1 - tailwind-merge 3.5.0 → 3.6.0 - vitest 4.1.5 → 4.1.6 - @vitest/coverage-v8 4.1.5 → 4.1.6 - lint-staged 17.0.3 → 17.0.4 - esbuild 0.27.7 → 0.28.0 - react-grab 0.1.33 → 0.1.34 - react-day-picker 9.14.0 → 10.0.0 react-day-picker 10 verified safe: probed v10 release notes against src/components/ui/calendar.tsx — we use only v9-canonical APIs that v10 preserves. Removed the `table` className entry from the wrapper (v10 dropped it since the renderer is now CSS-grid, not table-based). Tried + rolled back: - @hookform/resolvers 3 → 5: stricter input/output inference broke every form using <{schema}, any, {schema}> implicit shape. Needs per-form refactor; parked. Verified clean: pnpm audit (prod + dev) = 0 vulnerabilities; pnpm exec tsc --noEmit clean; vitest 1293/1293 pass. Remaining outdated (deliberately deferred — see docs/AUDIT-2026-05-12.md §34): - next/eslint-config-next 15 → 16 (2-4 wk wait) - zod 3 → 4 (couple with @hookform/resolvers 5; codemod-needed) - tailwindcss 3 → 4 (focused-afternoon project) - @types/node ^20.19 stays pinned to match runtime (audit decision) - archiver 7 stays (no @types/archiver@8 published) - eslint 9 stays (locked to eslint-config-next 15) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:33:24 +02:00
"vitest": "^4.1.6"
},
"pnpm": {
"overrides": {
"vite": "8.0.5",
"esbuild": ">=0.25.0",
"postcss": ">=8.5.10"
}
}
}