Single source of truth for what's built. A new session should read this first and NOT redo completed phases. Specs:
trustops-design-system.md,trustops-phase-0-1-brief.md,trustops-master-build-spec.md.Verify the DB/RLS/RPC gates anytime with:
npm run test:rls(needs Docker — see README). Code gates:npm run typecheck,npm run lint,npm run build.
- Next.js 14 + TS + Tailwind scaffold; all design tokens in
globals.css+tailwind.config.ts - Shared libs:
lib/money.ts(kobo), supabase client/server/admin, middleware, shared types -
/components/uicanonical kit (Button, Input, Select, Card, Badge, DataTable, Modal, Toast, EmptyState, Skeleton, Money, MetricCard…) - App shell: TopBar (branch switcher, profile), Sidebar, mobile BottomTabBar + Record-sale FAB
- Auth: login, signup (company + owner via
bootstrap_companyRPC), accept-invite, middleware gating - Schema migrations
0001–0003: 8 tables, RLS on all (auth_company_id()),record_sale/record_paymentRPCs - Role-gated Settings (company, branches, staff invite/role/deactivate)
- Two-company RLS isolation proven
- Customers: list (search), add/edit sheet, detail (invoices + total spend), archive (soft delete, admin)
- Record sale: optimized form (customer pick + quick-add, line items, running total, optional payment) →
record_sale - Invoices: list (status filter + search + pills), detail (items, totals, payment history)
- Record payment modal →
record_paymentRPC;archive_invoiceRPC (0004); send-receipt stub toast - Dashboard: greeting + 3 KPI cards (today revenue + delta, unpaid total, customers) + recent sales
-
/sales→/invoicesredirect (no separate sales-list screen in spec) - Verification:
npm run test:rls= 24/24 (isolation + atomic rollback + payment lifecycle + role gating)
(See master spec PHASE 2. Payments/Phase 3+ explicitly NOT in scope.)
- Migration
0005:products,inventory,stock_movements(+invoice_items.product_id), RLS, indexes, grants - Migration
0006: inventory RPCs (create_product,update_product,adjust_stock,transfer_stock) +record_salestock integration - TS types +
database.types.ts+ inventory module (schemas/queries/actions) - Products list (search, category/active/low-stock filters) + add/edit (
StockBadgeadded to UI kit) - Product detail (stock across branches, movement ledger, edit, deactivate)
- Stock adjustment (restock/adjustment/return, required reason, admin) — modal on product detail
- Stock transfer (branch→branch, atomic) — modal on product detail
- Low-stock: products filter (
?low=1) + dashboard widget - Record sale: product picker per line item (free-text still supported); decrements stock atomically
- Nav: Products in sidebar + mobile "More" sheet
- Quality gates:
npm run test:rls= 36/36 (incl. oversell fails wholly; quantity == Σ movements; transfer conserves total; inventory cross-tenant isolation; staff can't adjust). typecheck + lint + build green.
Not built. User asked to hold off on payments; the Phase 1 manual cash/transfer
path remains the only payment route. modules/payments stub still in place.
(Master spec PHASE 4. Built after skipping Phase 3.)
- Migration
0007:notification_settings,notifications,jobs,job_runs,scheduled_jobs+ RLS + indexes + event-driven low-stock trigger oninventory - Migration
0008: queue RPCs (enqueue_job,claim_jobw/ SKIP LOCKED,complete_job,fail_jobw/ backoff+dead-letter,requeue_stuck_jobs,tick_scheduler),enqueue_notification,mark_notification_result,mark_notification_read/_all,transition_overdue_invoices; seeded schedules; shim addsservice_role - Queue tech: Postgres-backed durable queue behind
modules/jobs/queue.ts(swappable for BullMQ/pg_cron) - Messaging:
MessagingProviderinterface + WhatsApp + Email (Resend) adapters with simulated no-op fallback (no live calls until keys); templates renderer - Handlers: invoice reminders (cadence-deduped), stock alerts (event-driven), daily report, receipt delivery, overdue transition
- Worker:
modules/jobs/worker.ts(requeue→tick→drain jobs→dispatch notifications) +scripts/worker.ts+npm run worker - UI: in-app notifications bell (real, replaces placeholder), Notifications settings section (role-gated), "Send receipt" now enqueues, receipt enqueued on sale, mobile More sheet unaffected
- Quality gates:
npm run test:rls= 51/51 (idempotent enqueue, job dead-letter + job_runs log, notification retry→dead-letter, reminder cadence dedupe, overdue transition + audit, event-driven stock-alert trigger, scheduler tick, automation tenant isolation). typecheck + lint + build green.
(Master spec PHASE 5. Built after Phase 4; Phase 3 payments still deferred.)
- Migration
0009:daily_metrics(cached read model, company-wide + per-branch, unique nulls-not-distinct on company/branch/date) +expenses(soft delete) + RLS (select-only; writes are RPC-only) + indexes - RPCs:
save_expense/archive_expense(Zod + role-gated owner/manager/accountant + audit),aggregate_daily_metrics(from,to)(idempotent rebuild, payment-date revenue / invoice-date sales+COGS / spent-date expenses; service_role only),analytics_top_products/analytics_top_customers(scoped, capped leaderboards) - Aggregation wired into the Phase 4 jobs engine:
analytics_daily_metricshandler + seeded daily schedule (no new queue tech) - Analytics module (
modules/analytics): cached dashboard query (unstable_cache, 60s TTL), live "today" from source tables + historical fromdaily_metrics, aging buckets, branch comparison, expense list/categories/breakdown;getReportData+ purereportToCsv - Analytics dashboard: date-range picker, KPI cards (revenue + delta, profit, expenses, outstanding), revenue-over-time bars, top products/customers, aging, branch comparison; trend color only on the delta
- Expenses: list (category + date filters, paginated) + add/edit sheet (role-gated), soft delete
- Reports: server-rendered period summary + CSV export (
/api/reports/export, RLS-scoped route) + Save as PDF (print-isolated#print-area); nav (sidebar + mobile More) already linked - Quality gates:
npm run test:rls= 65/65 (aggregate revenue/sales/expenses reconcile vs raw source; aggregation idempotent; expense role gating; RPC-only writes; soft delete excluded from live reads; create+archive audited;daily_metrics/expensestenant isolation). Dashboard reads are cached + paginated. typecheck + lint + build green.
(Master spec PHASE 6. Built on Phases 1–5; Phase 3 payments still deferred.)
- Migration
0010:ai_settings,ai_conversations,ai_messages,ai_usage,ai_insights+ RLS. Conversations/messages/usage scoped per company AND per user (user_id = auth.uid()) — no cross-tenant or cross-user memory. RPCs:save_ai_settings(owner-only + audit),ai_month_usd_cents(tenant-scoped spend).ai_insightsseeded as a daily scheduled job. - AI provider spend is integer USD cents (not kobo — the one honest currency exception), logged per turn in
ai_usageand capped per company viaai_settings.monthly_cap_usd_cents. -
modules/ai: scoped tool surface (get_financial_summary/get_top_products/get_top_customers/get_unpaid_invoices/get_low_stock/find_customer) — each wraps an existing RLS-scoped Phase 1–5 query, so the model never gets raw SQL/DB access and cannot widen scope. Manual orchestrator loop (@anthropic-ai/sdk,claude-opus-4-8): grounding system prompt, capped steps, per-call token→cost accounting, read-only (drafts only, never mutates). - One turn = shared
prepareTurn(enabled gate → budget gate → conversation/user-message persistence → grounded system prompt + capped history) → orchestrator →persistAssistantTurn(assistant msg + usage log + bump). Used by BOTH paths so gates never drift: live SSE streaming routePOST /api/assistant/stream(token-by-token,runAssistantStream) for the chat UI, and the non-streamingsendAssistantMessageserver action as a programmatic fallback.saveAiSettingsowner-gated. NoANTHROPIC_API_KEY⇒ assistant cleanly disabled (no live calls); rest of app unaffected. - Proactive insights are rule-based over Phase 5 aggregates (revenue trend, low stock, overdue) — not freeform generation — written by the
ai_insightsjob on the Phase 4 engine and surfaced as grounded dashboard cards. - Screens: Assistant chat (history, suggested prompts, every answer shows its source figures), AI settings section (owner: enable + USD cap + month spend), dashboard Insights widget. Nav: Assistant in sidebar + mobile More.
- Quality gates:
npm run test:rls= 78/78 (AI tables tenant-isolated; per-user memory — staff can't read another user's chats; usage can't be logged under another user's id; settings owner-only + audited;ai_month_usd_centstenant-scoped). Cross-tenant prompt safety is structural: every tool runs through the same RLS client, proven by the Phase 1–5 isolation gates. typecheck + lint + build green.
modules/payments/index.ts— gateway adapter stub (Phase 3)- "Send receipt" — no-op toast (becomes real in Phase 4)
overduestatus derived at read time (Phase 4 job will flip it)products.cost_pricecaptured now, used by Phase 5 profit/COGS ✅ now consumed byaggregate_daily_metricsCOGS + analytics profit- Branch selection persisted client-side (full branch-scoped reads later)
daily_metricsalready stores per-branch rows; UI currently reads company-wide + branch-comparison only — full branch-filtered dashboards are a clean later addunstable_cache60s TTL on the analytics dashboard — swap for Redis/tagged revalidation when read pressure grows (revalidated viarevalidatePathon expense writes)analytics_top_products/analytics_top_customersreadinvoiceslive; promote todaily_metrics-backed rollups if leaderboards get heavy- Reports export = CSV (server route) + browser-print PDF; a server-rendered PDF (e.g. via a render service) is a drop-in later
- Phase 6 AI tools call the existing scoped
modules/analytics/invoices/inventory/customersqueries — ✅ done; any NEW tool must reuse a scoped query, never open an unscoped data path - AI assistant: ✅ live SSE token streaming (
/api/assistant/stream+runAssistantStream);max_tokens1024 per turn — raise if longer answers are wanted - AI tools run per-call (no prompt caching yet) — add
cache_controlon the stable system+tools prefix when traffic grows - AI insights are rule-based (revenue trend / low stock / overdue); the seam to add LLM-summarised insights stays behind the same scheduled job + grounded figures
- AI spend cap enforced per calendar month in USD cents; a hard per-turn token ceiling (
task_budget) is a later add - Phase 3 (Payments) still deferred —
modules/paymentsstub remains the only seam not yet built