Timbre
- Role
- Solo — design, frontend, backend, infra
- Period
- Jul — Aug 2026
- Stack
- Next.js 16, TypeScript, MongoDB, Socket.io, Yjs, Vercel Blob, Firebase Auth
The problem
Voice notes are treated as attachments — a recording bolted onto a text note, searchable by neither. The audio is the thing you actually captured, and every tool buries it.
What I did
Built a note-taking app where the recording is the note itself. Two editors share one document model: a spatial canvas and a linear document. Documents merge conflict-free over CRDTs, so two people can type in the same place and neither loses work, and they keep working offline and re-sync on reconnect.
What happened
Shipped v1.0, live at timbrenotes.xyz. Notes export as a single portable .vnote file with the audio inside it, and a share link plays back with no account at all. The OpenAPI reference generates from the route handlers themselves.
Why the canvas and the document share a model
Most editors pick one shape and make you live with it. Timbre lets a note be spatial or linear, chosen at creation, but both write into the same underlying document — so voice pins on a canvas and inline audio blocks in a document are the same object rendered two ways.
That decision is what made the .vnote export possible at all. A portable file needs one thing to serialise, not two.
Collaboration that survives a bad network
Presence and live cursors are the easy half. The hard half is what happens when two people edit the same paragraph on a train.
The Socket.io relay holds no state and owns no database. It forwards updates and nothing else, which means it can restart mid-session without anyone noticing.
What v1 deliberately leaves out
A five-minute recording budget per account, enforced server-side rather than in the client. Transcription is opt-in per recording instead of automatic — it costs money and most notes never need it.