Service rewrite covers 14 entity buckets (clients, residential clients,
yachts, companies, interests, residential interests, berths, invoices,
expenses, documents, files, reminders, brochures, tags, notes, navigation)
with prefix tsquery + trigram fallback, phone-digit normalization,
and JOINs to client_contacts for email matching.
New `notes` bucket searches across the four note tables (client,
interest, yacht, company) via UNION + parent-entity label resolution
(berth mooring for interests, name for yachts/companies). Renders at
the bottom of the dropdown so broad-content matches don't crowd
entity-specific hits — per the user's "low-noise" preference.
Recently-viewed tracking persists last 20 entity views per user in
Redis sorted set; CommandSearch surfaces them as the dropdown's
default state and applies affinity ranking when the user types.
ID-resolve endpoint accepts pasted UUIDs (or invoice numbers like
`INV-2025-001`) and routes the rep straight to the entity, skipping
the normal search bucket.
Audit search service gains `entityIds[]` array filter for the new
loadClientActivityAggregated() path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit log was previously silent on authentication and on background
work. This wires:
- Login (success + failed) and logout via a wrapper around better-auth's
[...all] handler. Failed logins are severity 'warning' and carry the
attempted email so brute-force attempts surface in the inspector.
- New severity (info|warning|error|critical) and source (user|auth|
system|webhook|cron|job) columns on audit_logs. permission_denied
defaults to 'warning', hard_delete to 'critical'.
- Webhook delivery success/failure/DLQ/retry now write audit rows
alongside the webhook_deliveries detail table.
- IP address is now visible as a column in the inspector (was already
captured at the helper level).
- Audit UI: severity badges per row, severity + source dropdowns, IP
column, expanded action filter covering hard-delete, webhook events,
job/cron events.
Migration 0044 adds the two columns + their port-scoped indexes.
1175/1175 vitest passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).
Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
port switcher.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
PR1 of Phase B per docs/superpowers/specs/2026-04-28-phase-b-insights-alerts-design.md.
Lays the foundation that PRs 2-10 will fill in with behaviour.
Schema (migration 0014):
- alerts table with rule-engine fields (rule_id, severity, link,
entity_type/id, fingerprint, fired/dismissed/acknowledged/resolved
timestamps, jsonb metadata). Partial-unique fingerprint index keeps
one open row per (port, rule, entity); separate indexes power
severity-filtered and time-ordered queries.
- analytics_snapshots (port_id, metric_id) -> jsonb cache + computedAt
for the 15-min recurring refresh.
- expenses: duplicate_of self-FK, dedup_scanned_at, ocr_status/raw/
confidence; partial index on (port, vendor, amount, date) where
duplicate_of IS NULL drives the dedup heuristic.
- audit_logs.search_text: GENERATED ALWAYS tsvector over
action+entity_type+entity_id+user_id, GIN-indexed (drizzle can't
model GENERATED ALWAYS in TS yet, so the migration appends manual
ALTER + the GIN index).
Service skeletons in src/lib/services/:
- alerts.service.ts: fingerprintFor, reconcileAlertsForPort (upsert +
auto-resolve), dismiss, acknowledge, listAlertsForPort.
- alert-rules.ts: RULE_REGISTRY of 10 rule evaluators (currently no-op);
PR2 fills in the bodies.
- analytics.service.ts: readSnapshot/writeSnapshot with 15-min TTL +
no-op compute* stubs for the four chart series; PR3 fills behavior.
- expense-dedup.service.ts: scanForDuplicates + markBestDuplicate
using the partial dedup index. PR8 wires the BullMQ trigger.
- expense-ocr.service.ts: OcrResult/OcrLineItem types + ocrReceipt
stub. PR9 wires Claude Vision (Haiku 4.5 + ephemeral system-prompt
cache).
- audit-search.service.ts: tsvector @@ plainto_tsquery + cursor
pagination on (createdAt, id). PR10 wires the admin UI.
tsc clean, lint clean, vitest 675/675 (one unrelated AES random-output
flake passes solo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>