The system at a glance
The shape (unusual: no app server)
BROWSER (React SPA, Vite + TS + shadcn/ui)
│
│ hooks (useWorkflow, useChecklistOperations, useInquiryActions…)
│ hold the WORKFLOW LOGIC and query Postgres DIRECTLY
│
├──────────────── supabase-js (anon key) ───────────────┐
│ ▼
│ ┌───────────────────────────┐
│ │ SUPABASE │
│ │ │
│ Auth (JWT, email hooks) ◀───────────────│ Postgres (RLS policies) │
│ │ ~30 tables, enums, │
│ │ SECURITY DEFINER fns, │
│ │ triggers, PGMQ queues │
│ │ │
│ Storage (case-attachments, │ Storage buckets │
│ logos, avatars, email-assets) │ │
│ │ Edge Functions (Deno): │
└──── functions.invoke() ─────────────────▶│ generate-dossier, │
│ portal-access, │
EXTERNAL PARTY (no login) ──── /portal/:token ─▶│ invite-user, │
token → portal-access edge fn │ setup-company, │
│ process-email-queue, │
│ auth-email-hook, … │
└───────────────────────────┘
│
Lovable email API (proprietary)
The defining architectural fact: there is no traditional backend. The browser is the application layer. It runs the workflow engine and talks to the database directly with the public anon key. Edge functions exist only for the handful of operations that need the service-role key (bypassing RLS): assembling the dossier, provisioning tenants/users, the external portal, and the email queue.
This is fast to prototype and the single biggest structural problem to fix — see Client-side enforcement is the whole trust model.
Tech stack
| Layer | Choice |
|---|---|
| Frontend | React 18, Vite, TypeScript, React Router, shadcn/ui (Radix + Tailwind) |
| Server state | TanStack Query (React Query) |
| Forms / validation | React Hook Form + Zod |
| Backend | Supabase — Postgres, Auth, Storage, Realtime, Edge Functions (Deno/TS) |
| Async / queues | pg_cron + PGMQ queues; email via Lovable's API |
| Email templates | React-Email (Portuguese) |
| Marketing | Remotion (a 5-scene explainer video in remotion/) |
| Tests | Vitest + Testing Library — one placeholder test, no real coverage |
| Origin | Generated by Lovable; forked from a whistleblower template |
The subsystems (each has a map)
| Subsystem | One-line | Detail |
|---|---|---|
| Workflow engine | Client-side 8-phase FSM with guards, gates, deadlines | → |
| Tenancy | Company-scoped via company_memberships + RLS | → |
| Audit | Two parallel trails + auth log | → |
| Deliverables | Two subsystems (witness vs generic) writing JSONB | → |
| Dossier | Edge function assembles a ZIP with hashes | → |
| External portal | Token access for the arguido | → |
| PGMQ queue, Lovable-coupled | → | |
| Storage | 4 buckets; case-attachments holds everything | → |
Data model in one breath
~30 Postgres tables around a central cases row: identity/tenancy (profiles, companies,
company_memberships), the case and its children (messages, attachments, team, response),
workflow (workflow_transitions, case_checklist_items, case_gates, case_deadlines,
inquiry_actions, case_phase_deliverables), evidence + audit + dossier, the portal tables,
and email infrastructure. Full map: Key tables.
How a typical action flows (example: advance a phase)
user clicks "Advance" → useWorkflow.executeTransition (browser)
├─ validate: allowed transition? checklist approved? gate passed? deadlines? (all JS)
├─ INSERT workflow_transitions ┐
├─ UPDATE cases (status + dates) │ seven separate writes,
├─ complete old deadlines │ NOT in a transaction
├─ create next-phase checklist │ (see non-atomic-transitions)
├─ auto-create gates │
├─ upsert next-phase deadlines │
└─ INSERT case_audit_events ┘ (fire-and-forget)
That this critical operation is seven un-transacted, un-authorized-server-side writes is the kind of thing the Tacit Assumptions section catalogues.