Files
pn-new-crm/src/components/admin/audit/audit-log-list.tsx

691 lines
25 KiB
TypeScript
Raw Normal View History

'use client';
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
import { useEffect, useState, useCallback, useMemo } from 'react';
import { type ColumnDef } from '@tanstack/react-table';
import { formatDistanceToNow } from 'date-fns';
feat(audit-cleanup): finish all 15 outstanding items from verified backlog Audit cleanup completion plan, all tiers shipped: Tier 1 (security + data integrity) - A.7 RTBF true wipe: redact email_messages body/subject/addresses for threads owned by deleted client; redact document_sends.recipient_email; collect file storage keys + delete blobs post-commit. - A.8 user_permission_overrides FK: documented inline why cascade is correct (not set-null as audit suggested) — overrides have no value without their user. - W2.14 PII redaction: camelCase normalization in audit.ts + error-events.service.ts isSensitiveKey; added city/postal/country/ birth fragments. firstName/lastName/dateOfBirth/postalCode etc. now caught in BOTH masker paths. 12 new test cases lock the coverage. Tier 2 (Documenso completion + refactor) - C.2: documentEvents.recipient_email column + partial unique index for per-recipient webhook dedup (migration 0075). handleDocumentSigned now sets recipient_email on insert. - Phase 2: completion_cc_emails distribution. handleDocumentCompleted reads documents.completionCcEmails, filters out signer-duplicates case-insensitively, fans signed PDF out to non-signer recipients. - C.4: extracted createPublicInterest() service from the 346-line api/public/interests route. Route becomes a thin shell (rate-limit, port resolution, audit log, email fan-out). The trio creation logic is now unit-testable without an HTTP fixture. - Phase 4: POST /api/v1/document-templates/[id]/detect-fields wired to document-field-detector.detectFields(). Sparkles "Auto-detect" button added to template-editor.tsx — maps DetectedField → marker with best-guess merge token (DATE / NAME / EMAIL); user retags. Tier 3 (reporting + recommender snapshot lockfiles) - W7.reports: extracted rollupStageRevenue / rollupStageCounts / computeTotalForecast / computeOccupancyRate / rollupBerthStatusCounts into src/lib/services/report-math.ts (pure functions). 16 new tests including an inline-snapshot lockfile on a representative 7-stage forecast. report-generators.ts now delegates. - W7.recommender: 18 new toMatchSnapshot tripwires on classifyTier boundaries + computeHeat at canonical input points. Tier 4 (rolling) - W6.attach: fixed outdated CLAUDE.md claim — threshold banner is informational and never depended on IMAP; bounce monitoring (the IMAP poller) is separate. - D.1 + D.2: documented deferral inline with full why-not-build-it reasoning so a future engineer sees the rationale. - G.1: representative formatDate sweep (audit-log-list, user-list, document-templates merge tokens, document-signing email). Rest of the ~100 sites stay rolling. Quality gates: 1420/1420 vitest (46 new tests above baseline of 1374), tsc clean, 0 lint errors. Plan: docs/superpowers/plans/2026-05-18-audit-cleanup-completion.md Migration: 0075_c2_document_events_recipient_email.sql (applied to dev DB). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:22:36 +02:00
import { formatDate } from '@/lib/utils/format-date';
import { Download, History, Search, X } from 'lucide-react';
import { toast } from 'sonner';
import { DataTable } from '@/components/shared/data-table';
import { PageHeader } from '@/components/shared/page-header';
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
import { EmptyState } from '@/components/shared/empty-state';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
feat(forms): migrate remaining native date inputs to <DatePicker> / <DateTimePicker> Sweeps the last ~17 native `<Input type="date"|"datetime-local">` call sites onto the shared `<DatePicker>` / `<DateTimePicker>` primitives so date entry is uniform across the app (calendar popover on desktop, native OS picker on mobile via the primitive's viewport-aware fallback). Three patterns handled: 1. Controlled value/onChange — direct swap to <DatePicker value/onChange>: audit-log-list.tsx (audit-from / audit-to filters) reports/generate-report-form.tsx (date range) scan/scan-shell.tsx (expense date) reservations/reservation-detail.tsx (end-reservation dialog) shared/filter-bar.tsx ('date' filter variant) 2. RHF `register('field')` pattern — wrapped in <Controller> with field.value/field.onChange bridge. The picker's '' → undefined normalisation kicks in via `field.onChange(v || undefined)`: berths/berth-form.tsx (tenureStartDate + tenureEndDate) reservations/berth-reserve-dialog.tsx (startDate) companies/add-membership-dialog.tsx (startDate) yachts/yacht-transfer-dialog.tsx (effectiveDate) invoices/invoice-detail.tsx (paymentDate) 3. RHF + Date-typed schema — same Controller wrap, plus a Date<->YYYY-MM-DD bridge in the render() since the zod schema coerces these to Date: expenses/expense-form-dialog.tsx (expenseDate) companies/company-form.tsx (incorporationDate) 4. Datetime variants — swapped onto <DateTimePicker>: interests/interest-contact-log-tab.tsx (occurredAt + followUpAt) Skipped because they ARE picker primitives or internal date variants: - ui/date-picker.tsx, ui/date-time-picker.tsx (the primitives) - shared/inline-editable-field.tsx (the InlineEditableField date variant) - dashboard/date-range-picker.tsx (its own popover with min/max gating that doesn't map cleanly onto the shared primitive) Removed now-unused Input imports from four files. Verified: tsc clean, vitest 1448/1448. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:14:33 +02:00
import { DatePicker } from '@/components/ui/date-picker';
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { apiFetch } from '@/lib/api/client';
fix(audit-wave-11): dossier sweep — error-ux + webhook + storage + search + maintainability Final pass over the unaddressed AUDIT-2026-05-12 dossiers, taking the tractable Critical/High items from each: error-ux-auditor (5 items) - C2: 17 toast.error(err.message) sites swept to toastError(err, …) so every user-visible failure carries a copy-paste Reference ID - C3: apiFetch synthesizes a client-side correlation id when a 5xx comes back with a non-JSON body (reverse-proxy HTML pages); message becomes "The server is unreachable. Please try again." with code UPSTREAM_UNREACHABLE - C4: checkRateLimit fails OPEN when Redis is unavailable so an outage no longer 500s login + portal sign-in; logged at warn so monitoring catches it - H2: StorageTimeoutError (name='TimeoutError') replaces the plain Error throw in s3.ts withTimeout — error-classifier hints fire now - H5: errorResponse() adopted across /api/storage/[token], /api/public/website-inquiries, and the Documenso webhook body (drops the "Invalid secret" reconnaissance string) outbound-webhook-auditor (5 items) - C1: signature is now HMAC(secret, `${ts}.${body}`) with the timestamp surfaced as X-Webhook-Timestamp so receivers can reject replays outside a freshness window - C3: dead-letter with reason missing_signing_secret when secret is null (defence-in-depth against DB tampering / future migration mistakes) - H2: webhooks queue bumped to maxAttempts=8 with 30 s base exponential backoff so a 30 s receiver blip during a deploy no longer dead-letters every in-flight event; per-queue backoffDelayMs added to QUEUE_CONFIGS - M1: SSRF denylist gains Oracle Cloud metadata 192.0.0.192 - M2: dispatch-time https:// assertion before fetch, so a bad DB edit can't slip plaintext through storage-pathing-auditor (2 items) - H1: berth-PDF presigned-upload keys now `${portSlug}/berths/…/…` with portSlug threaded into backend.presignUpload — engages the filesystem-proxy port-binding `p` token verifier - H2: presignDownloadUrl auto-derives portSlug from the key's first segment when callers don't pass it, so all 8 download sites engage the `p`-token guard without per-site plumbing search-auditor (1 item) - H3: removed dead void wantEmail; void wantPhone; pair plus the unused looksLikeEmail helper — the bucket-reorder it was scaffolded for was never wired maintainability-auditor (1 item) - M2: swept seven abandoned `void <symbol>` markers and their dead imports across clients/bulk, interests/bulk, admin/email-templates, admin/website-submissions, alert-rules, and notes.service Deferred to future work (substantial refactors, schema migrations, or multi-file UI work): - error-ux M3-M8 (global-error.tsx, per-route loading.tsx coverage, ErrorBanner component, /api/ready route, worker DLQ admin surface) - maintainability C1-C4 (documents/search/notes service splits, interest-tabs split — multi-hour refactors) - currency C1-H5 (mixed-currency dashboard aggregation, FX history table, rounding policy) — wait for second non-USD port - outbound-webhook C2 (deliveries reaper job), H1 (DNS-rebind TOCTOU with undici Agent), H3 (circuit-breaker), H5 (presigned-post-policy) - storage-pathing C2 (orphan reaper), H3-H5 (streaming + content-type binding) Tests: 1315/1315 vitest ✅ ; tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:27:32 +02:00
import { toastError } from '@/lib/api/toast-error';
import { AuditLogCard } from './audit-log-card';
interface AuditEntry {
id: string;
userId: string | null;
action: string;
entityType: string;
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
entityId: string | null;
fieldChanged: string | null;
oldValue: Record<string, unknown> | null;
newValue: Record<string, unknown> | null;
metadata: Record<string, unknown> | null;
ipAddress: string | null;
userAgent: string | null;
severity: 'info' | 'warning' | 'error' | 'critical';
source: 'user' | 'system' | 'auth' | 'webhook' | 'cron' | 'job';
createdAt: string;
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
actor: { id: string; email: string; name: string } | null;
}
interface AuditResponse {
data: AuditEntry[];
pagination: { nextCursor: { createdAt: string; id: string } | null };
}
const ACTION_COLORS: Record<string, string> = {
create: 'bg-green-600',
update: 'bg-blue-500',
delete: 'bg-red-600',
archive: 'bg-orange-500',
restore: 'bg-teal-500',
login: 'bg-slate-500',
logout: 'bg-slate-400',
permission_denied: 'bg-red-800',
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
merge: 'bg-purple-500',
revert: 'bg-amber-500',
hard_delete: 'bg-red-900',
request_hard_delete_code: 'bg-orange-700',
send: 'bg-indigo-500',
view: 'bg-gray-400',
webhook_delivered: 'bg-emerald-500',
webhook_failed: 'bg-amber-600',
webhook_dead_letter: 'bg-red-700',
webhook_retried: 'bg-indigo-600',
job_failed: 'bg-rose-700',
cron_run: 'bg-sky-500',
};
const SEVERITY_BADGE: Record<string, string> = {
info: 'bg-slate-200 text-slate-800',
warning: 'bg-amber-200 text-amber-900',
error: 'bg-red-200 text-red-900',
critical: 'bg-red-600 text-white',
};
const SOURCE_LABEL: Record<string, string> = {
user: 'User',
system: 'System',
auth: 'Auth',
webhook: 'Webhook',
cron: 'Cron',
job: 'Job',
};
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
// L-AU03: entity types that mutations can target but the filter dropdown
// didn't expose. Reps querying the audit log for, e.g., an email-account
// toggle (H-05 fix) couldn't pick it from the dropdown.
const ENTITY_TYPES = [
'client',
'interest',
'berth',
'document',
'expense',
'invoice',
'reminder',
'user',
'role',
'port',
'setting',
'tag',
'webhook',
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
'yacht',
'company',
'reservation',
'email_account',
'portal_session',
'portal_user',
'file',
];
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
function useDebounced<T>(value: T, ms = 300): T {
const [v, setV] = useState(value);
useEffect(() => {
const t = setTimeout(() => setV(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return v;
}
export function AuditLogList() {
const [entries, setEntries] = useState<AuditEntry[]>([]);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const [nextCursor, setNextCursor] = useState<{
createdAt: string;
id: string;
} | null>(null);
const [loading, setLoading] = useState(true);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const [loadingMore, setLoadingMore] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
// Filter state - debounce text inputs.
const [search, setSearch] = useState('');
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const [entityType, setEntityType] = useState<string>('all');
const [action, setAction] = useState<string>('all');
const [severity, setSeverity] = useState<string>('all');
const [source, setSource] = useState<string>('all');
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const [userId, setUserId] = useState('');
const [dateFrom, setDateFrom] = useState('');
const [dateTo, setDateTo] = useState('');
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
// Per-row detail is surfaced inline via a Popover anchored to the
// Details button (see column cell below). Lets the rep inspect the
// full oldValue / newValue / metadata / IP / UA payload without
// leaving the table or opening a Sheet.
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const debouncedSearch = useDebounced(search);
const debouncedUserId = useDebounced(userId);
const queryString = useMemo(() => {
const params = new URLSearchParams({ limit: '50' });
if (entityType !== 'all') params.set('entityType', entityType);
if (action !== 'all') params.set('action', action);
if (severity !== 'all') params.set('severity', severity);
if (source !== 'all') params.set('source', source);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
if (debouncedSearch) params.set('search', debouncedSearch);
if (debouncedUserId) params.set('userId', debouncedUserId);
// Skip the date filters when From > To - the inline warning below
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate R2-M11: mobile More-sheet missing 4 destinations. Added Reservations, Notifications, Residential, Website analytics — anyone using mobile chrome to triage on the go can now reach those domains. R2-M12: portal had no profile / change-password surface. New /portal/profile page with read-only contact details + a ChangePasswordForm component, backed by a new POST /api/portal/auth/change-password endpoint and changePortalPassword() service function. Audits both ok and failure cases at warning severity. Added Profile to PortalNav. R2-M1: portal dashboard "My Memberships" tile had no href and no /portal/memberships route — dead-end on tap. Hidden until a memberships page ships; the count remains in the underlying data. R2-M7: InlineStagePicker never sent override:true so users with interests.override_stage couldn't actually use the perm from the inline chip — they had to fall back to the modal picker. Now the picker auto-detects when a transition isn't legal AND the user has override_stage, sets override:true, and supplies a default reason. Frontend M2: hard-delete-dialog confirm stage now has a "Send a new code" link in case the original expired before the user could enter it. Avoids forcing a full Cancel + reopen. Frontend M4: audit-log-list date-range validation. From > To now shows an inline error and skips the request rather than firing an empty-range query that surfaces "no entries found". R2-M6: external-EOI route now requires interests.edit AND documents.upload_signed (defense-in-depth) — uploading a signed EOI mutates interest state, so the upload-signed perm alone shouldn't let a custom role flip an interest. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
// tells the user to fix it; we don't want to fire a request with a
// useless empty range either.
const datesValid = !(dateFrom && dateTo && dateFrom > dateTo);
if (datesValid && dateFrom) params.set('dateFrom', new Date(dateFrom).toISOString());
if (datesValid && dateTo) {
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const end = new Date(dateTo);
end.setHours(23, 59, 59, 999);
params.set('dateTo', end.toISOString());
}
return params.toString();
}, [entityType, action, severity, source, debouncedSearch, debouncedUserId, dateFrom, dateTo]);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const fetchFirstPage = useCallback(async () => {
setLoading(true);
setLoadError(null);
try {
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
const res = await apiFetch<AuditResponse>(`/api/v1/admin/audit?${queryString}`);
setEntries(res.data);
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
setNextCursor(res.pagination.nextCursor);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to load audit log';
setLoadError(msg);
toast.error(msg);
} finally {
setLoading(false);
}
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
}, [queryString]);
const loadMore = useCallback(async () => {
if (!nextCursor) return;
setLoadingMore(true);
try {
const params = new URLSearchParams(queryString);
params.set('cursorAt', nextCursor.createdAt);
params.set('cursorId', nextCursor.id);
const res = await apiFetch<AuditResponse>(`/api/v1/admin/audit?${params}`);
setEntries((prev) => [...prev, ...res.data]);
setNextCursor(res.pagination.nextCursor);
} catch (err) {
fix(audit-wave-11): dossier sweep — error-ux + webhook + storage + search + maintainability Final pass over the unaddressed AUDIT-2026-05-12 dossiers, taking the tractable Critical/High items from each: error-ux-auditor (5 items) - C2: 17 toast.error(err.message) sites swept to toastError(err, …) so every user-visible failure carries a copy-paste Reference ID - C3: apiFetch synthesizes a client-side correlation id when a 5xx comes back with a non-JSON body (reverse-proxy HTML pages); message becomes "The server is unreachable. Please try again." with code UPSTREAM_UNREACHABLE - C4: checkRateLimit fails OPEN when Redis is unavailable so an outage no longer 500s login + portal sign-in; logged at warn so monitoring catches it - H2: StorageTimeoutError (name='TimeoutError') replaces the plain Error throw in s3.ts withTimeout — error-classifier hints fire now - H5: errorResponse() adopted across /api/storage/[token], /api/public/website-inquiries, and the Documenso webhook body (drops the "Invalid secret" reconnaissance string) outbound-webhook-auditor (5 items) - C1: signature is now HMAC(secret, `${ts}.${body}`) with the timestamp surfaced as X-Webhook-Timestamp so receivers can reject replays outside a freshness window - C3: dead-letter with reason missing_signing_secret when secret is null (defence-in-depth against DB tampering / future migration mistakes) - H2: webhooks queue bumped to maxAttempts=8 with 30 s base exponential backoff so a 30 s receiver blip during a deploy no longer dead-letters every in-flight event; per-queue backoffDelayMs added to QUEUE_CONFIGS - M1: SSRF denylist gains Oracle Cloud metadata 192.0.0.192 - M2: dispatch-time https:// assertion before fetch, so a bad DB edit can't slip plaintext through storage-pathing-auditor (2 items) - H1: berth-PDF presigned-upload keys now `${portSlug}/berths/…/…` with portSlug threaded into backend.presignUpload — engages the filesystem-proxy port-binding `p` token verifier - H2: presignDownloadUrl auto-derives portSlug from the key's first segment when callers don't pass it, so all 8 download sites engage the `p`-token guard without per-site plumbing search-auditor (1 item) - H3: removed dead void wantEmail; void wantPhone; pair plus the unused looksLikeEmail helper — the bucket-reorder it was scaffolded for was never wired maintainability-auditor (1 item) - M2: swept seven abandoned `void <symbol>` markers and their dead imports across clients/bulk, interests/bulk, admin/email-templates, admin/website-submissions, alert-rules, and notes.service Deferred to future work (substantial refactors, schema migrations, or multi-file UI work): - error-ux M3-M8 (global-error.tsx, per-route loading.tsx coverage, ErrorBanner component, /api/ready route, worker DLQ admin surface) - maintainability C1-C4 (documents/search/notes service splits, interest-tabs split — multi-hour refactors) - currency C1-H5 (mixed-currency dashboard aggregation, FX history table, rounding policy) — wait for second non-USD port - outbound-webhook C2 (deliveries reaper job), H1 (DNS-rebind TOCTOU with undici Agent), H3 (circuit-breaker), H5 (presigned-post-policy) - storage-pathing C2 (orphan reaper), H3-H5 (streaming + content-type binding) Tests: 1315/1315 vitest ✅ ; tsc clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:27:32 +02:00
toastError(err, 'Failed to load more audit entries');
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
} finally {
setLoadingMore(false);
}
}, [queryString, nextCursor]);
useEffect(() => {
// Refetch on filter change. Migrating this list to useInfiniteQuery
// would be the proper fix but is deferred - the fetch-on-effect
// pattern here is functionally correct and gated by the queryString
// memo so it only fires when filters actually change.
// eslint-disable-next-line react-hooks/set-state-in-effect
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
void fetchFirstPage();
}, [fetchFirstPage]);
function clearFilters() {
setSearch('');
setEntityType('all');
setAction('all');
setSeverity('all');
setSource('all');
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
setUserId('');
setDateFrom('');
setDateTo('');
}
const hasActiveFilter =
Boolean(search) ||
entityType !== 'all' ||
action !== 'all' ||
severity !== 'all' ||
source !== 'all' ||
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
Boolean(userId) ||
Boolean(dateFrom) ||
Boolean(dateTo);
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate R2-M11: mobile More-sheet missing 4 destinations. Added Reservations, Notifications, Residential, Website analytics — anyone using mobile chrome to triage on the go can now reach those domains. R2-M12: portal had no profile / change-password surface. New /portal/profile page with read-only contact details + a ChangePasswordForm component, backed by a new POST /api/portal/auth/change-password endpoint and changePortalPassword() service function. Audits both ok and failure cases at warning severity. Added Profile to PortalNav. R2-M1: portal dashboard "My Memberships" tile had no href and no /portal/memberships route — dead-end on tap. Hidden until a memberships page ships; the count remains in the underlying data. R2-M7: InlineStagePicker never sent override:true so users with interests.override_stage couldn't actually use the perm from the inline chip — they had to fall back to the modal picker. Now the picker auto-detects when a transition isn't legal AND the user has override_stage, sets override:true, and supplies a default reason. Frontend M2: hard-delete-dialog confirm stage now has a "Send a new code" link in case the original expired before the user could enter it. Avoids forcing a full Cancel + reopen. Frontend M4: audit-log-list date-range validation. From > To now shows an inline error and skips the request rather than firing an empty-range query that surfaces "no entries found". R2-M6: external-EOI route now requires interests.edit AND documents.upload_signed (defense-in-depth) — uploading a signed EOI mutates interest state, so the upload-signed perm alone shouldn't let a custom role flip an interest. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
const dateRangeInvalid = Boolean(dateFrom && dateTo && dateFrom > dateTo);
const columns: ColumnDef<AuditEntry, unknown>[] = [
{
accessorKey: 'createdAt',
header: 'Time',
cell: ({ row }) => (
<div className="text-sm">
feat(audit-cleanup): finish all 15 outstanding items from verified backlog Audit cleanup completion plan, all tiers shipped: Tier 1 (security + data integrity) - A.7 RTBF true wipe: redact email_messages body/subject/addresses for threads owned by deleted client; redact document_sends.recipient_email; collect file storage keys + delete blobs post-commit. - A.8 user_permission_overrides FK: documented inline why cascade is correct (not set-null as audit suggested) — overrides have no value without their user. - W2.14 PII redaction: camelCase normalization in audit.ts + error-events.service.ts isSensitiveKey; added city/postal/country/ birth fragments. firstName/lastName/dateOfBirth/postalCode etc. now caught in BOTH masker paths. 12 new test cases lock the coverage. Tier 2 (Documenso completion + refactor) - C.2: documentEvents.recipient_email column + partial unique index for per-recipient webhook dedup (migration 0075). handleDocumentSigned now sets recipient_email on insert. - Phase 2: completion_cc_emails distribution. handleDocumentCompleted reads documents.completionCcEmails, filters out signer-duplicates case-insensitively, fans signed PDF out to non-signer recipients. - C.4: extracted createPublicInterest() service from the 346-line api/public/interests route. Route becomes a thin shell (rate-limit, port resolution, audit log, email fan-out). The trio creation logic is now unit-testable without an HTTP fixture. - Phase 4: POST /api/v1/document-templates/[id]/detect-fields wired to document-field-detector.detectFields(). Sparkles "Auto-detect" button added to template-editor.tsx — maps DetectedField → marker with best-guess merge token (DATE / NAME / EMAIL); user retags. Tier 3 (reporting + recommender snapshot lockfiles) - W7.reports: extracted rollupStageRevenue / rollupStageCounts / computeTotalForecast / computeOccupancyRate / rollupBerthStatusCounts into src/lib/services/report-math.ts (pure functions). 16 new tests including an inline-snapshot lockfile on a representative 7-stage forecast. report-generators.ts now delegates. - W7.recommender: 18 new toMatchSnapshot tripwires on classifyTier boundaries + computeHeat at canonical input points. Tier 4 (rolling) - W6.attach: fixed outdated CLAUDE.md claim — threshold banner is informational and never depended on IMAP; bounce monitoring (the IMAP poller) is separate. - D.1 + D.2: documented deferral inline with full why-not-build-it reasoning so a future engineer sees the rationale. - G.1: representative formatDate sweep (audit-log-list, user-list, document-templates merge tokens, document-signing email). Rest of the ~100 sites stay rolling. Quality gates: 1420/1420 vitest (46 new tests above baseline of 1374), tsc clean, 0 lint errors. Plan: docs/superpowers/plans/2026-05-18-audit-cleanup-completion.md Migration: 0075_c2_document_events_recipient_email.sql (applied to dev DB). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:22:36 +02:00
<div>{formatDate(row.original.createdAt, 'datetime.medium')}</div>
<div className="text-xs text-muted-foreground">
{formatDistanceToNow(new Date(row.original.createdAt), { addSuffix: true })}
</div>
</div>
),
size: 180,
},
{
accessorKey: 'action',
header: 'Action',
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
cell: ({ row }) => {
const verbLabel = row.original.action.replace(/_/g, ' ');
const entityLabel = row.original.entityType.replace(/_/g, ' ');
return (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1.5">
<Badge
className={`${ACTION_COLORS[row.original.action] ?? 'bg-gray-500'} text-white text-xs`}
>
{verbLabel}
</Badge>
{row.original.severity !== 'info' && (
<Badge
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
className={`${SEVERITY_BADGE[row.original.severity] ?? ''} text-xs px-1.5 py-0 uppercase`}
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
variant="outline"
>
{row.original.severity}
</Badge>
)}
</div>
<span className="text-xs text-muted-foreground capitalize">{entityLabel}</span>
</div>
);
},
size: 180,
},
{
accessorKey: 'source',
header: 'Source',
cell: ({ row }) => (
<span className="text-xs text-muted-foreground">
{SOURCE_LABEL[row.original.source] ?? row.original.source}
</span>
),
size: 80,
},
{
accessorKey: 'entityType',
header: 'Entity',
cell: ({ row }) => (
<div>
<span className="font-medium capitalize">{row.original.entityType}</span>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
{row.original.entityId ? (
<code className="ml-2 text-xs text-muted-foreground">
{row.original.entityId.slice(0, 8)}
</code>
) : null}
</div>
),
},
{
id: 'changes',
header: 'Changes',
cell: ({ row }) => {
const { newValue, fieldChanged } = row.original;
if (fieldChanged) return <span className="text-sm">{fieldChanged}</span>;
if (newValue) {
const keys = Object.keys(newValue);
return (
<span className="text-xs text-muted-foreground">
{keys.slice(0, 3).join(', ')}
{keys.length > 3 ? ` +${keys.length - 3} more` : ''}
</span>
);
}
return <span className="text-xs text-muted-foreground">-</span>;
},
},
{
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
id: 'actor',
header: 'Actor',
cell: ({ row }) => {
const { actor, userId: rawId } = row.original;
if (actor) {
return (
<div className="text-sm">
<div className="font-medium">{actor.name}</div>
<div className="text-xs text-muted-foreground">{actor.email}</div>
</div>
);
}
if (rawId) {
return <code className="text-xs">{rawId.slice(0, 8)}</code>;
}
return <span className="text-xs text-muted-foreground">system</span>;
},
size: 180,
},
{
id: 'ip',
header: 'IP',
cell: ({ row }) =>
row.original.ipAddress ? (
<code className="text-xs text-muted-foreground">{row.original.ipAddress}</code>
) : (
<span className="text-xs text-muted-foreground"> - </span>
),
size: 130,
},
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
{
id: 'details',
header: '',
cell: ({ row }) => {
const e = row.original;
const hasDetail =
Boolean(e.oldValue) || Boolean(e.newValue) || Boolean(e.metadata) || Boolean(e.userAgent);
if (!hasDetail) return null;
return (
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs">
Details
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
side="bottom"
className="w-[420px] max-h-[60vh] overflow-y-auto p-3"
>
<div className="space-y-3 text-sm">
<div className="space-y-0.5">
<p className="font-semibold capitalize">
{e.action.replace(/_/g, ' ')} - {e.entityType}
</p>
<p className="text-xs text-muted-foreground">
{formatDate(e.createdAt, 'datetime.medium')}
{e.actor ? ` · ${e.actor.name}` : ''}
</p>
</div>
{e.oldValue ? (
<details>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
Old value
</summary>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
{JSON.stringify(e.oldValue, null, 2)}
</pre>
</details>
) : null}
{e.newValue ? (
<details open>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
New value
</summary>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
{JSON.stringify(e.newValue, null, 2)}
</pre>
</details>
) : null}
{e.metadata ? (
<details>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
Metadata
</summary>
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
{JSON.stringify(e.metadata, null, 2)}
</pre>
</details>
) : null}
{e.ipAddress || e.userAgent ? (
chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish Drain the long-tail audit queue captured in alpha-uat-master.md. - next-intl ripped out (zero useTranslations callers ever existed): package.json, next.config.ts plugin wrap, src/i18n/, messages/, and the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded. - RTL lint nudge added: warn-only no-restricted-syntax on physical Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/ border-r/rounded-l-/rounded-r-) inside JSX className literals. Existing ~1,000 sites grandfathered; new code trends toward logical. - Icon-only button accessibility lint: jsx-a11y/control-has-associated- label enabled at warn; 4 empty <th>/<td> action placeholders gain sr-only labels. - Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels; new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames. CurrencySelect + settings-manager migrated. - Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US') to toLocaleString(undefined, ...) so dates honour runtime locale. - Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room. - PaymentsSection collapsed-bar: slim one-line bar showing "Payments - Not received yet" or "Payments - \$X received - N payments - Expand"; per-interest collapse state persists in localStorage; the RecordPayment flow auto-expands. - muted-foreground opacity sweep: 10 text-bearing text-muted-foreground/{60,70,80} hits dropped to plain text-muted-foreground for AA contrast on muted bg. Icon-only (aria-hidden) opacity hits left as-is. - Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px) across 87 files in src/components + src/app. Pure mechanical sweep. - Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary rewritten with cumulative state through today. Items genuinely still open are now a short long-tail list. - New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email pixel E2E verification, and website-cutover work parked here so they don't get lost in the CRM audit doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:48:46 +02:00
<dl className="grid grid-cols-[88px_1fr] gap-x-2 gap-y-1 text-xs">
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
{e.ipAddress ? (
<>
<dt className="font-semibold text-muted-foreground">IP address</dt>
<dd className="font-mono">{e.ipAddress}</dd>
</>
) : null}
{e.userAgent ? (
<>
<dt className="font-semibold text-muted-foreground">User agent</dt>
<dd className="font-mono break-all">{e.userAgent}</dd>
</>
) : null}
</dl>
) : null}
</div>
</PopoverContent>
</Popover>
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
);
},
size: 80,
},
];
return (
<div>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<PageHeader
title="Audit Log"
eyebrow="Admin"
description="Every state change in this port - fully searchable."
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
variant="gradient"
/>
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
<div className="mt-4 flex flex-wrap items-end gap-x-4 gap-y-3">
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<div className="space-y-1.5">
<Label htmlFor="audit-search" className="text-xs">
Search
</Label>
<div className="relative w-72">
<Search
className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
aria-hidden
/>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<Input
id="audit-search"
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
className="pl-9 h-9"
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
placeholder="entity id, entity type, action, user id…"
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
value={search}
onChange={(e) => setSearch(e.target.value)}
data-testid="audit-search"
/>
</div>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Entity</Label>
<Select value={entityType} onValueChange={setEntityType}>
<SelectTrigger className="w-36" data-testid="audit-entity">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All entities</SelectItem>
{ENTITY_TYPES.map((t) => (
<SelectItem key={t} value={t}>
{t.charAt(0).toUpperCase() + t.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<div className="space-y-1.5">
<Label className="text-xs">Action</Label>
<Select value={action} onValueChange={setAction}>
<SelectTrigger className="w-44" data-testid="audit-action">
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All actions</SelectItem>
<SelectItem value="create">Create</SelectItem>
<SelectItem value="update">Update</SelectItem>
<SelectItem value="delete">Delete</SelectItem>
<SelectItem value="archive">Archive</SelectItem>
<SelectItem value="restore">Restore</SelectItem>
<SelectItem value="merge">Merge</SelectItem>
<SelectItem value="revert">Revert</SelectItem>
<SelectItem value="login">Login</SelectItem>
<SelectItem value="logout">Logout</SelectItem>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<SelectItem value="permission_denied">Permission denied</SelectItem>
<SelectItem value="hard_delete">Hard delete</SelectItem>
<SelectItem value="request_hard_delete_code">Hard-delete code req</SelectItem>
<SelectItem value="send">Send</SelectItem>
<SelectItem value="view">View</SelectItem>
<SelectItem value="webhook_delivered">Webhook delivered</SelectItem>
<SelectItem value="webhook_failed">Webhook failed</SelectItem>
<SelectItem value="webhook_dead_letter">Webhook DLQ</SelectItem>
<SelectItem value="webhook_retried">Webhook retried</SelectItem>
<SelectItem value="job_failed">Job failed</SelectItem>
<SelectItem value="cron_run">Cron run</SelectItem>
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
{/* L-AU02: actions that fire in the code but were missing from
the dropdown - reps couldn't filter on them. */}
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
<SelectItem value="password_change">Password change</SelectItem>
<SelectItem value="portal_invite">Portal invite</SelectItem>
<SelectItem value="portal_activate">Portal activate</SelectItem>
<SelectItem value="portal_password_reset_request">Portal reset req</SelectItem>
<SelectItem value="portal_password_reset">Portal reset</SelectItem>
<SelectItem value="revoke_invite">Revoke invite</SelectItem>
<SelectItem value="resend_invite">Resend invite</SelectItem>
<SelectItem value="request_gdpr_export">GDPR req</SelectItem>
<SelectItem value="send_gdpr_export">GDPR sent</SelectItem>
<SelectItem value="rule_evaluated">Rule evaluated</SelectItem>
<SelectItem value="outcome_set">Outcome set</SelectItem>
<SelectItem value="outcome_cleared">Outcome cleared</SelectItem>
<SelectItem value="branding.logo.uploaded">Logo uploaded</SelectItem>
<SelectItem value="branding.logo.archived">Logo archived</SelectItem>
feat(post-audit): finish Phase 3 / 4 / 5 / 7 — remaining work Phase 3 — EOI overrides (now ☑): - Address override field with the same per-component input UX as the canonical address form (line1/line2/city/state/postal + ISO subdivision + CountryCombobox). Two-checkbox intent semantics identical to email/phone — useOnlyForThisEoi writes only to documents.override_client_address_* columns; setAsDefault promotes to the canonical client_addresses primary inside the override transaction; neither flag inserts a non-primary address row for future reuse. eoi-context route now returns available.addresses so the dialog can render the picker over existing rows. - yachts.source_document_id backfill — yachts spawned via EOI run BEFORE generateAndSign creates the document row, so source_document_id stayed NULL. Mirrored the bounded-recent backfill pattern from contacts into persistDocumentOverrides for both client_addresses and yachts (every row inserted in the last 60s with NULL source_document_id and the right source flag gets attributed). - Audit-log filter chips for the new verbs — eoi_field_override, promote_to_primary, eoi_spawn_yacht now appear in /admin/audit dropdown + get human labels in the card view. Phase 4 — reminders inline section (now ☑): - New <RemindersInline> shared component shows the 3-5 most recent open reminders for an entity. Mounted on Overview tab of yacht / client / interest detail. Empty state hints at the header button rather than duplicating it. Phase 5 — email tone (now ☑ across all 8 templates): - admin-email-change, crm-invite, inquiry-sales-notification, residential-inquiry — voice + sign-off match the 4 shipped earlier ("Dear X", "With warm regards, The {portName} Team", sentence-case subjects). Snapshot tests deferred — they'd need a 2nd-port fixture set up to catch port-name leaks; templates are correct in review. Phase 7 — PDF editor (now ☑): - 7.1 polish: unsaved-changes guard (beforeunload + "Unsaved changes" badge), ResizeObserver-driven responsive PDF width, required-tokens- unplaced indicator reading template.mergeFields. - 7.2 drag-to-move with on-page clamping. - 7.2 four-corner resize handles with min-size enforcement. - 7.2 right-click context delete via onContextMenu. - 7.2 multi-page navigation + per-page marker filter. - 7.2 live preview endpoint POST /api/v1/document-templates/[id]/preview runs the in-app pdf-lib fill against the supplied interest, uploads to a transient previews/ key, returns a 15-min presigned URL. - 7.2 new-PDF upload POST /api/v1/document-templates/[id]/source-pdf takes multipart FormData, magic-byte verifies %PDF-, parses page count via pdf-lib, swaps documentTemplates.sourceFileId. Editor warns when the new page count truncates the prior set. Quality gates: 1374/1374 vitest, tsc clean, lint 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 17:09:19 +02:00
<SelectItem value="eoi_field_override">EOI field override</SelectItem>
<SelectItem value="promote_to_primary">Contact promoted</SelectItem>
<SelectItem value="eoi_spawn_yacht">EOI spawn yacht</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Severity</Label>
<Select value={severity} onValueChange={setSeverity}>
<SelectTrigger className="w-32" data-testid="audit-severity">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All severities</SelectItem>
<SelectItem value="info">Info</SelectItem>
<SelectItem value="warning">Warning</SelectItem>
<SelectItem value="error">Error</SelectItem>
<SelectItem value="critical">Critical</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Source</Label>
<Select value={source} onValueChange={setSource}>
<SelectTrigger className="w-32" data-testid="audit-source">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All sources</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="auth">Auth</SelectItem>
<SelectItem value="system">System</SelectItem>
<SelectItem value="webhook">Webhook</SelectItem>
<SelectItem value="cron">Cron</SelectItem>
<SelectItem value="job">Job</SelectItem>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label htmlFor="audit-user" className="text-xs">
User id
</Label>
<Input
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
id="audit-user"
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
className="w-44 h-9"
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
placeholder="exact user id"
value={userId}
onChange={(e) => setUserId(e.target.value)}
/>
</div>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<div className="space-y-1.5">
<Label htmlFor="audit-from" className="text-xs">
From
</Label>
feat(forms): migrate remaining native date inputs to <DatePicker> / <DateTimePicker> Sweeps the last ~17 native `<Input type="date"|"datetime-local">` call sites onto the shared `<DatePicker>` / `<DateTimePicker>` primitives so date entry is uniform across the app (calendar popover on desktop, native OS picker on mobile via the primitive's viewport-aware fallback). Three patterns handled: 1. Controlled value/onChange — direct swap to <DatePicker value/onChange>: audit-log-list.tsx (audit-from / audit-to filters) reports/generate-report-form.tsx (date range) scan/scan-shell.tsx (expense date) reservations/reservation-detail.tsx (end-reservation dialog) shared/filter-bar.tsx ('date' filter variant) 2. RHF `register('field')` pattern — wrapped in <Controller> with field.value/field.onChange bridge. The picker's '' → undefined normalisation kicks in via `field.onChange(v || undefined)`: berths/berth-form.tsx (tenureStartDate + tenureEndDate) reservations/berth-reserve-dialog.tsx (startDate) companies/add-membership-dialog.tsx (startDate) yachts/yacht-transfer-dialog.tsx (effectiveDate) invoices/invoice-detail.tsx (paymentDate) 3. RHF + Date-typed schema — same Controller wrap, plus a Date<->YYYY-MM-DD bridge in the render() since the zod schema coerces these to Date: expenses/expense-form-dialog.tsx (expenseDate) companies/company-form.tsx (incorporationDate) 4. Datetime variants — swapped onto <DateTimePicker>: interests/interest-contact-log-tab.tsx (occurredAt + followUpAt) Skipped because they ARE picker primitives or internal date variants: - ui/date-picker.tsx, ui/date-time-picker.tsx (the primitives) - shared/inline-editable-field.tsx (the InlineEditableField date variant) - dashboard/date-range-picker.tsx (its own popover with min/max gating that doesn't map cleanly onto the shared primitive) Removed now-unused Input imports from four files. Verified: tsc clean, vitest 1448/1448. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:14:33 +02:00
<DatePicker
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
id="audit-from"
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
className="w-52 h-9"
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
value={dateFrom}
feat(forms): migrate remaining native date inputs to <DatePicker> / <DateTimePicker> Sweeps the last ~17 native `<Input type="date"|"datetime-local">` call sites onto the shared `<DatePicker>` / `<DateTimePicker>` primitives so date entry is uniform across the app (calendar popover on desktop, native OS picker on mobile via the primitive's viewport-aware fallback). Three patterns handled: 1. Controlled value/onChange — direct swap to <DatePicker value/onChange>: audit-log-list.tsx (audit-from / audit-to filters) reports/generate-report-form.tsx (date range) scan/scan-shell.tsx (expense date) reservations/reservation-detail.tsx (end-reservation dialog) shared/filter-bar.tsx ('date' filter variant) 2. RHF `register('field')` pattern — wrapped in <Controller> with field.value/field.onChange bridge. The picker's '' → undefined normalisation kicks in via `field.onChange(v || undefined)`: berths/berth-form.tsx (tenureStartDate + tenureEndDate) reservations/berth-reserve-dialog.tsx (startDate) companies/add-membership-dialog.tsx (startDate) yachts/yacht-transfer-dialog.tsx (effectiveDate) invoices/invoice-detail.tsx (paymentDate) 3. RHF + Date-typed schema — same Controller wrap, plus a Date<->YYYY-MM-DD bridge in the render() since the zod schema coerces these to Date: expenses/expense-form-dialog.tsx (expenseDate) companies/company-form.tsx (incorporationDate) 4. Datetime variants — swapped onto <DateTimePicker>: interests/interest-contact-log-tab.tsx (occurredAt + followUpAt) Skipped because they ARE picker primitives or internal date variants: - ui/date-picker.tsx, ui/date-time-picker.tsx (the primitives) - shared/inline-editable-field.tsx (the InlineEditableField date variant) - dashboard/date-range-picker.tsx (its own popover with min/max gating that doesn't map cleanly onto the shared primitive) Removed now-unused Input imports from four files. Verified: tsc clean, vitest 1448/1448. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:14:33 +02:00
onChange={setDateFrom}
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="audit-to" className="text-xs">
To
</Label>
feat(audit-session): legacy-stage canonicalization + multi-berth label sweep + PDF/UI polish Critical data-correctness fixes - external-eoi.service: stage-advance list rewritten against canonical 7-stage vocab (enquiry/qualified/nurturing → eoi). Was hardcoded to legacy 9-stage names (open/details_sent/in_communication/eoi_sent), so EOI uploads from 'qualified' silently skipped the stage flip. Now also writes eoiDocStatus='signed' alongside eoiStatus='signed'. - public-interest.service + api/public/interests/route: pipelineStage 'open' → 'enquiry' for new public interests. - interests.service: legacy 'open' gate → 'enquiry'; inline-stage-picker comments updated. - Display fallbacks canonicalized: dashboard.service, dashboard-report-data, pdf/templates/{interest,client}-summary, interest-picker, timeline route all route through canonicalizeStage / stageLabelFor. Multi-berth interest label sweep - New helper src/lib/templates/interest-berth-label.ts with 9 unit tests (deriveInterestBerthLabel reuses formatBerthRange + caps at 5 segments, falls back to 'first + N more'). - New batched aggregator getAllBerthMooringsForInterests on the interest-berths service. - BoardInterestRow + listInterests + getInterest extended with berthMoorings: string[]. - Swept render sites: interest-detail-header, pipeline-card + pipeline-column (kanban), interest-columns (list), interest-card, interest-detail (breadcrumb), client-pipeline-summary + client-interests-tab, yacht-tabs, shared interest-picker. - PDF report "New interests (in period)" Source column → Berth column. Dashboard PDF report fixes - Hardcoded EUR → reads ports.default_currency once at the top of resolveDashboardReportData. Falls back to USD. - 'maintenance' berth-status bucket removed everywhere (wasn't in canonical BERTH_STATUSES); cleaned from dashboard.service, dashboard-report-data, occupancy-report, berth-status-chart, fixture. - Berth demand ranking: dropped placeholder Tier column (resolver hardcoded 'A' — heat-tier never plumbed through). - Deal pulse distribution: tier values capitalized (hot → Hot etc.). - Validator widgetIds.max 20 → 40 (catalog has 25 entries; was throwing "Validation failed" when all sections checked). - Export dialog: badges tightened (text-[8px] py-px whitespace-nowrap, no more 2-line wraps on "needs date range"); accepts initialRange?: DateRange so the dashboard's active range pre-fills dateFrom/dateTo via rangeToBounds. Interest banner overcounts fix - interest-berth-status-banner: filters out self-caused under-offer berths (where the only active deal touching the berth IS this same interest). Waits for all competing-queries before committing the count. Was showing "3 berths unavailable" when only 1 actually had a competitor. Sessions list ordering - sessions-list: client-side sort by lastAt desc + displays lastAt instead of firstAt so visible timestamp matches the sort key. Audit log polish - Details button: side Sheet → Popover anchored to the button (in-place inline dropdown). Works with the virtualized table. - From/To date pickers: width w-44 → w-52, wrapper gap-3 → gap-x-4 gap-y-3. EntityFolderView (Documents Hub entity view) - Per-row Download button (hover-reveal icon). - File-type icon prefix + tighter row layout. - Per-row interest-berth badge: files.ts attaches interestBerthLabel via one batched getAllBerthMooringsForInterests call across all groups. AggregatedFile type + EntityFolderView render the badge linking back to the parent interest. External EOI upload dialog - Title input pre-fills from the derived default via controlled displayTitle = title || defaultTitle (no setState-in-effect). EOI Generate dialog - Success toast on mutation success. - Primary berth's "Include in EOI" checkbox is now forced-on + disabled with tooltip: the primary IS the canonical "berth for this deal", excluding it is semantically nonsense. Primary berth must always be in EOI bundle (service + backfill) - interest-berths.service: insert path forces is_in_eoi_bundle=true whenever is_primary=true; update path coerces back to true when the caller tries to set false on a primary. Backfilled 7 existing rows. Documenso redirect URL fallback - port-config getPortDocumensoConfig: resolution chain extended to documenso_redirect_url → public_site_url → null. Operators with public_site_url configured (most ports) now get sensible signer landing without setting two settings. World-map click → navigate - website-analytics-shell: country click navigates to the nationality- filtered Clients page via router.push instead of copying a URL to clipboard. Documents Hub: subfolder grid in main panel - Subfolder cards rendered above the documents list when the current folder has children. Lets reps drill into subfolders from the main content area, not only via the sidebar tree. Interest list initial sort - usePaginatedQuery gains initialSort option (used when URL has no sort param). Interest list passes updatedAt desc so the table header surfaces the active sort visibly + most-recently-added/edited bubble to the top. Interest auto-assign on create - interests.service createInterest: three-tier owner resolution chain — explicit input → port's default_new_interest_owner setting → creator (when not super-admin). Super-admins skipped since they often create on behalf of other reps. Backfills - 12 interests with eoi_status='signed' + missing eoi_doc_status='signed' aligned. - 7 interest_berths rows with is_primary=true but is_in_eoi_bundle=false flipped to true. Verified - pnpm tsc --noEmit: clean - pnpm exec vitest run: 1463 / 1463 passed Captured 25+ additional UAT findings to docs/superpowers/audits/alpha-uat-master.md across all 4 buckets, including two OPEN QUESTIONS (Reservations module re-imagine, Reports dedicated page promotion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:41:27 +02:00
<DatePicker id="audit-to" className="w-52 h-9" value={dateTo} onChange={setDateTo} />
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
</div>
{/* M-AU03: CSV export inherits the current filter set. The
endpoint streams up to 10 000 rows; reps wanting deeper
history narrow the filter first. */}
<Button
variant="outline"
size="sm"
className="ml-auto"
onClick={() => {
const url = new URL('/api/v1/admin/audit/export', window.location.origin);
if (debouncedSearch) url.searchParams.set('q', debouncedSearch);
if (entityType !== 'all') url.searchParams.set('entityType', entityType);
if (action !== 'all') url.searchParams.set('action', action);
if (severity !== 'all') url.searchParams.set('severity', severity);
if (source !== 'all') url.searchParams.set('source', source);
if (userId) url.searchParams.set('userId', userId);
if (dateFrom) url.searchParams.set('from', dateFrom);
if (dateTo) url.searchParams.set('to', dateTo);
window.location.href = url.toString();
}}
>
<Download className="mr-1.5 h-3 w-3" aria-hidden />
Export CSV
</Button>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
{hasActiveFilter ? (
<Button variant="ghost" size="sm" onClick={clearFilters}>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
<X className="mr-1.5 h-3 w-3" />
Clear
</Button>
) : null}
</div>
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate R2-M11: mobile More-sheet missing 4 destinations. Added Reservations, Notifications, Residential, Website analytics — anyone using mobile chrome to triage on the go can now reach those domains. R2-M12: portal had no profile / change-password surface. New /portal/profile page with read-only contact details + a ChangePasswordForm component, backed by a new POST /api/portal/auth/change-password endpoint and changePortalPassword() service function. Audits both ok and failure cases at warning severity. Added Profile to PortalNav. R2-M1: portal dashboard "My Memberships" tile had no href and no /portal/memberships route — dead-end on tap. Hidden until a memberships page ships; the count remains in the underlying data. R2-M7: InlineStagePicker never sent override:true so users with interests.override_stage couldn't actually use the perm from the inline chip — they had to fall back to the modal picker. Now the picker auto-detects when a transition isn't legal AND the user has override_stage, sets override:true, and supplies a default reason. Frontend M2: hard-delete-dialog confirm stage now has a "Send a new code" link in case the original expired before the user could enter it. Avoids forcing a full Cancel + reopen. Frontend M4: audit-log-list date-range validation. From > To now shows an inline error and skips the request rather than firing an empty-range query that surfaces "no entries found". R2-M6: external-EOI route now requires interests.edit AND documents.upload_signed (defense-in-depth) — uploading a signed EOI mutates interest state, so the upload-signed perm alone shouldn't let a custom role flip an interest. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
{dateRangeInvalid && (
<p className="mt-2 text-xs text-destructive">
From date must be on or before To date - date filter ignored.
fix(audit): MEDIUMs sweep — mobile More-sheet, portal profile, inline override, dialog UX, ext-EOI gate R2-M11: mobile More-sheet missing 4 destinations. Added Reservations, Notifications, Residential, Website analytics — anyone using mobile chrome to triage on the go can now reach those domains. R2-M12: portal had no profile / change-password surface. New /portal/profile page with read-only contact details + a ChangePasswordForm component, backed by a new POST /api/portal/auth/change-password endpoint and changePortalPassword() service function. Audits both ok and failure cases at warning severity. Added Profile to PortalNav. R2-M1: portal dashboard "My Memberships" tile had no href and no /portal/memberships route — dead-end on tap. Hidden until a memberships page ships; the count remains in the underlying data. R2-M7: InlineStagePicker never sent override:true so users with interests.override_stage couldn't actually use the perm from the inline chip — they had to fall back to the modal picker. Now the picker auto-detects when a transition isn't legal AND the user has override_stage, sets override:true, and supplies a default reason. Frontend M2: hard-delete-dialog confirm stage now has a "Send a new code" link in case the original expired before the user could enter it. Avoids forcing a full Cancel + reopen. Frontend M4: audit-log-list date-range validation. From > To now shows an inline error and skips the request rather than firing an empty-range query that surfaces "no entries found". R2-M6: external-EOI route now requires interests.edit AND documents.upload_signed (defense-in-depth) — uploading a signed EOI mutates interest state, so the upload-signed perm alone shouldn't let a custom role flip an interest. 1175/1175 vitest passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:38:59 +02:00
</p>
)}
{loadError && !loading && entries.length === 0 ? (
<div className="mt-4 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm space-y-2">
<p className="text-destructive">Couldn&rsquo;t load audit log: {loadError}</p>
<Button size="sm" variant="outline" onClick={() => void fetchFirstPage()}>
Retry
</Button>
</div>
) : (
<div className="mt-4">
<DataTable
columns={columns}
data={entries}
isLoading={loading}
getRowId={(row) => row.id}
cardRender={(row) => <AuditLogCard entry={row.original} />}
virtual
virtualHeightPx={640}
virtualRowHeightPx={56}
emptyState={
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing- progress redesign + env-to-admin migration + dev-mode banner) with the 2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW). CRITICAL (3): - C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths no longer silently drop interest links - C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed - C-03 generic PATCH /interests/[id] no longer accepts pipelineStage — callers must go through /stage with the override-guard chain HIGH (14/15): - H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across interests/documents/reservations/reminders/invoices (migration 0070) - H-02 login page reads ?redirect= param with same-origin guard - H-03 CRM invite token moves to URL fragment so it never lands in nginx access logs / Referer headers - H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4) - H-05 toggleAccount writes an audit row - H-06 upsertSetting masks any value whose key ends with _encrypted - H-07 archiveClient cascade fires per-interest audit rows - H-08 createSalesTransporter applies SMTP_TIMEOUTS - H-09 AppShell stable children — viewport flip across breakpoint no longer destroys in-progress form drafts - H-10 portal documents page swaps Unicode glyph status icons for Lucide CheckCircle2/XCircle/Circle + aria-labels - H-12 list components swap alert(...) for toast.warning(...) - H-13 5 icon-only buttons gain aria-label - H-14 parseBody treats empty bodies as {} - H-15 admin layout renders a 403 panel instead of silent bounce - H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet MEDIUM (28+): - M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE WHEREs across custom-fields, notes (all 6 entity types x update + delete), client-contacts, yacht ownerClient lookup, webhook reads - M-D01 documents-hub realtime event-name typo (file:created -> uploaded) - M-EM01 portal-auth emails thread through portId - M-EM02 sendEmail accepts cc/bcc params - M-EM04 notification_digest catalog key - M-IN01 portal presigned download URLs use 4h TTL - M-IN02 OpenAI client lazy-instantiated - M-IN04 stale pdfme refs updated to pdf-lib AcroForm - M-IN05 umami.testConnection returns tagged union - M-L01 reservations tenure_type unified with berths - M-L02 report-generators canonicalize stage values - M-AU01 audit log placeholder copy fixed - M-AU04 outcome_set / outcome_cleared distinct audit verbs - M-NEW-2 activity feed entity name+type separator - M-R01 portal allowlist narrowed + portal_session backstop in proxy - M-SC02 companies archived partial index - M-SC04 audit_logs.searchText documented as DB-managed - M-S01 storage_s3_access_key_encrypted admin field - M-U01 audit log empty state uses <EmptyState> - M-U09 invoice delete dialog -> <AlertDialog> - M-U10 toast.success on ClientForm + InterestForm create/edit - M-U11 settings-form-card logo preview alt text - M-U14 mobile topbar title on clients/yachts/interests/berths - M-U15 Invoices in mobile More-sheet LOW (6/8): - L-AU01 severity defaults for security-relevant verbs - L-AU02 +13 missing actions in admin audit filter - L-AU03 +7 missing entity types in admin audit filter - L-AU04 dead listAuditLogs stubbed - L-D02 CLAUDE.md Owner-wins chain tightened Bonus — Document detail polish (#67 partial, 3/6 deliverables): - state-aware action button per signer - watcher Add UI with display-name resolution - cleanSignerName cleanup Prior session work bundled in: - Documenso v2 webhook + envelope-ID normalization + sequential signing - SigningProgress UI redesign (avatars, per-signer state, timestamps) - env->admin settings registry + RegistryDrivenForm + encrypted creds - Embedded-signing card + Test connection + setup help - Dev-mode EMAIL_REDIRECT_TO banner - Pipeline rules admin page - Sales email config card - Audit log details Sheet - EOI tab: Finalising badge, absolute timestamps, sequential indicator - Notes pipeline_stage_at_creation (migration 0069) - Documenso numeric ID dual-key webhook (migration 0068) - Dimensions criterion copy (migration 0067) Tests: 1374/1374 vitest pass. tsc clean. lint clean. See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and the user-input items still pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 13:28:50 +02:00
<EmptyState
icon={History}
title="No audit log entries"
description={
hasActiveFilter
? 'No entries match the current filters. Try clearing them.'
: 'Activity will appear here once users start making changes.'
}
/>
}
/>
</div>
)}
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
{nextCursor ? (
<div className="mt-4 flex justify-center">
<Button
variant="outline"
size="sm"
disabled={loadingMore}
onClick={() => void loadMore()}
data-testid="audit-load-more"
>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
{loadingMore ? 'Loading…' : 'Load more'}
</Button>
</div>
feat(phase-b): ship analytics dashboard, alerts, scanner PWA, dedup, audit view Phase B (Insights & Alerts) PR4-11 in one drop. Builds on the schema + service skeletons committed in PRs 1-3. PR4 Analytics dashboard — 4 chart types (funnel/timeline/breakdown/source), date-range picker (today/7d/30d/90d), CSV+PNG export per card. PR5 Alert rail UI + /alerts page — topbar bell w/ live count, dashboard right-rail, three-tab page (active/dismissed/resolved), socket-driven invalidation. Bell lazy-loads list on popover open to keep cold pages fast in non-dashboard routes. PR6 EOI queue tab on documents hub — filters to in-flight EOIs, count surfaces in tab label. PR7 Interests-by-berth tab on berth detail — replaces the stub. PR8 Expense duplicate detection — BullMQ job runs scan on create, yellow banner on detail w/ Merge / Not-a-duplicate, transactional merge consolidates receipts and archives the source. PR9 Receipt scanner PWA + multi-provider AI — port-scoped /scan route in its own (scanner) group with no dashboard chrome, dynamic per-port manifest, OpenAI + Claude provider abstraction, admin OCR settings page (port-level + super-admin global default w/ opt-in fallback), test-connection endpoint, manual-entry fallback when no key is configured. Verify form always shown before save — no ghost rows. PR10 Audit log read view — swap to tsvector full-text search on the existing GIN index, cursor pagination, filters for entity/action/user /date range, batched actor-email resolution. PR11 Real-API tests — opt-in receipt-ocr.spec (admin save+test, optional real-receipt parse via REALAPI_RECEIPT_FIXTURE) and alert-engine socket-fanout spec gated behind RUN_ALERT_ENGINE_REALAPI. Both skip cleanly without their gate envs so CI stays green. Test totals: vitest 690 -> 713, smoke 130 -> 138, realapi +2 opt-in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:21:55 +02:00
) : null}
</div>
);
}