NOTE
Next.js 16 + Supabase · 8-state task lifecycle · RAG assistant · 650+ tests on a real database.
Tasks
The whole app revolves around tasks. Each task moves through an 8-state lifecycle, and every transition is a single atomic UPDATE ... WHERE status = query. That query doubles as the lock: if two people act on the same task at once, only one of them wins, and the state never gets corrupted.
Data and auth
The stack is Next.js 16 (App Router, Server Components, Server Actions) with Supabase underneath for Postgres, Auth, Realtime, Storage, and pgvector. The business logic lives in 11 domain services, wired together by hand through a single factory function. There is no IoC container.
Supabase is used through two clients. The server client carries the service_role key, bypasses RLS, and runs all the business logic. The browser client carries the anon key, is limited by RLS to 6 of the 16 tables, and only handles Realtime subscriptions and direct storage uploads. The split is deliberate: the server client has no user JWT, so it can’t open Realtime connections, and the browser client can’t see the tables the logic needs.
Authorization is layered. RLS policies deny everything by default and even restrict columns on tasks (clients only see id and requester_id). On top of that, every service method checks ownership before writing anything. Every input goes through one of about 30 Zod schemas, and HTML is stripped with sanitize-html after validation.
AI assistant
The AI assistant was added to answer questions and explain the platform to users. It gets what it knows from the internal docs and specs through RAG. The RAG pipeline we implemented has four stages: Markdown-aware chunking by headings, Gemini embeddings at 1536 dimensions, vector search in pgvector with an IVFFlat index, and retrieval of the top 5 chunks above a 0.5 cosine threshold. Answers are generated by Gemma 4 31B through the Vercel AI SDK’s streamText(), streamed token by token.
Two things make it robust. consumeStream() removes backpressure so the model finishes its answer even if the user disconnects, and a second handler guarantees the persistence callback still fires on abort — the reply is always saved. Chat titles come from a different model (Qwen 3 32B on Groq), so an outage on the chat provider doesn’t take titles down with it.
Infra and tests
There is no job queue. The five timers (auto-complete, ghosting detection, stale expiry, reminders, review closure) run as pg_cron → pg_net → Edge Function, entirely inside Postgres and Supabase. The Edge Function reads the service role key from Vault through a SECURITY DEFINER helper, so the key never reaches the client.
The project was built with TDD: 650+ tests across 21 files, running against a real local Supabase instance. Nothing is mocked, and the tests encode the acceptance criteria straight from the spec docs.
[02] Internship Gallery