Two halves of one pipeline¶
nimbus-record collects the audio. A contributor opens the page with ?contributor=<email>, gets a queue of prompts to read aloud, and records each one in-browser as lossless WAV via an AudioWorklet. Every take is normalised and quality-checked server-side before it is stored, and a reviewer can play takes back and accept them or send them back with feedback.
nimbus-annotate labels it. An annotator opens the page with ?annotator=<name>, listens to a conversation, and edits per-turn {start, end, transcript} segments against the waveform.
They are separate repositories because they are separate jobs, usually done by different people, often by different vendors.
The part worth copying¶
Both apps treat their data sources as pluggable connectors rather than a database. Tasks, storage, input and output are each swappable, which means the demo mode needs no backend at all — REC_TASK_SOURCE=demo bun run dev and the thing runs with sample prompts and local files.
That sounds like a small detail and it is the whole design. Speech data collection is nearly always a contractor arrangement, and every client wants their audio to land somewhere different — Supabase, Airtable, S3, a folder on a machine you do not control. Hard-coding storage means rewriting the app per client. Making it a connector means the UI never changes, and a new client is a config file.