# web — Disclosure Bureau Next.js app Next.js 15 + React 19 + Tailwind + Supabase + assistant-ui. ## Quick start (local dev) ```bash # 1. Install deps npm install # 2. (Optional) Start local Supabase # Requires Docker. Skip if pointing at remote Supabase. npx supabase init # first time only — creates supabase/ folder npx supabase start # spins up Postgres/GoTrue/Storage on :54321 # 3. Configure env cp .env.local.example .env.local # Edit .env.local — paste local Supabase keys (printed by `supabase start`) # 4. Apply migrations psql postgresql://postgres:postgres@localhost:54322/postgres \ -f ../infra/supabase/migrations/0001_chat_schema.sql # 5. Start dev npm run dev # http://localhost:3030 ``` ## Without Supabase The app degrades gracefully if Supabase env vars are unset: - Wiki browsing works (read-only from filesystem) - Auth bar shows "auth: disabled (dev)" - Chat bubble shows "Auth not configured" Useful for quick UI work without spinning up Docker. ## Production (Coolify on VPS) See [`../infra/coolify/`](../infra/coolify/). Stack: - Coolify orchestrates everything - Supabase self-hosted: `db.disclosure.top`, `studio.disclosure.top` - Next.js: `disclosure.top` - Meilisearch (shared): `search.disclosure.top` - Imgproxy (shared): `img.disclosure.top` - Caddy: TLS + reverse proxy (built into Coolify) ## Architecture ``` app/ ├── page.tsx # home — 116 docs grouped by collection ├── auth/ │ ├── signin/page.tsx # magic-link form │ ├── callback/route.ts # exchanges code for session │ └── signout/route.ts ├── d/[docId]/ │ ├── page.tsx # doc detail │ └── [page]/page.tsx # page reader (OCR + entity highlights + crops + sidebar PNG) ├── api/ │ ├── me/route.ts # GET current profile │ ├── sessions/route.ts # GET list, POST new │ ├── sessions/[id]/route.ts # GET detail, PATCH, DELETE │ ├── sessions/[id]/messages/route.ts # POST send → assistant reply │ ├── documents/, pages/, entities/, tables/ # read-only data │ └── static/[...path]/route.ts # sandboxed file serve components/ ├── chat-bubble.tsx # floating Sherlock — auth-aware, session list ├── entity-modal.tsx # opens on entity click ├── reader-content.tsx # OCR + highlights + crops └── auth-bar.tsx # sign in / out + budget tracker lib/ ├── wiki.ts # markdown reader (gray-matter) ├── entity-index.ts # match loader + text segmentation └── supabase/{server,client}.ts # SSR helpers middleware.ts # session refresh on every request ``` ## Tech notes - **No RAG**: chat agent reads markdown directly. Wiki-link traversal substitutes for vector search. - **RLS-first**: Supabase Row Level Security enforces "user sees only own sessions" at the DB layer. - **Magic-link auth**: no passwords. GoTrue handles email delivery. - **Anti-abuse**: per-user budget cap (default $5) + daily message quota (default 100) enforced via `check_budget` RPC before each Claude call. ## Cost Each chat turn costs ~$0.005-0.05 depending on context size (mostly Haiku $1/M input, $5/M output).