Capture
A capture is any take a device makes in one unrepeatable instant — audio, video, MIDI, or other. The core was abstracted out of gmtermux, where the lifecycle, the naming rule and the provenance sidecar had each been written more than once. Four packages now carry it: the lifecycle, the HTTP envelope, the deployable, and the record button written once.
One seam per layer
The lifecycle, the name, the provenance
captured — a device, an instant, one chance, irreplaceable. Its transcription is derived, regenerable for exactly as long as the voice is kept. A sidecar whose origin fails the guard is refused rather than defaulted, because defaulting to derived marks an unrepeatable take safe to delete.Nine routes, and a port law
Routes are born under /api/captures/* — this service never carries the old /api/recordings name. It runs on the gaia server and, one day, on the Android device: same service, same routes, different drivers.
| Verb | Route | Answers |
|---|---|---|
| start | POST /api/captures/start | {success, filename, filepath, startedAt, context} |
| pause | POST /api/captures/pause | {success, segments} |
| resume | POST /api/captures/resume | {success, segment} |
| stop | POST /api/captures/stop | {success, filename, tlid, segments, durationSeconds, capture, belonging, registration} |
| transcribe | POST /api/captures/transcribe | {success, filename, cached, transcription} |
| status | GET /api/captures/status | RecorderStatus + {driver, takesDir, device, pendingRegistrations} |
| list | GET /api/captures | {takes: [{filename, kind, sizeBytes, modifiedAt, hasProvenance, hasTranscription}]} |
| audio | GET /api/captures/audio/<filename> | the bytes; honors Range: (206/416) for a seeking player |
| describe | GET / | service identity and the route table |
MIADI_CAPTURE_PORT defaults to 8770 and the service refuses to boot on 8768 — that port belongs to the pixel prototype and dies with it. Registration is queued durably: a take is durable before the registry hears about it.Client in, callbacks out
Every surface — forgewright, the studio app, this one — renders the same lifecycle and restyles it freely: minimal markup with capture-* classnames and data-* attributes as styling hooks. Two rules make that possible: no component fetches episode lists (episodes arrive as props) and no component holds a service URL (the host constructs the client and passes the instance in).
| Export | What it is |
|---|---|
| useCaptureLifecycle | The lifecycle as a hook: idle → recording ⇄ paused → idle, elapsed time, error surface, belonging carried per the timing ruling. |
| RecordButton | One <button>: Record when idle, Stop when rolling. |
| RecorderPanel | State, elapsed, take in flight, pause/resume/stop, error alert. |
| TakeList | Filename, kind badge, origin, duration, belonging or INBOX marker, transcription marker. |
| InboxView | TakeList filtered to takes with no declared belonging, each row offering assignment. |
| AssignToEpisodePicker | Assigns one take to a host-provided episode via client.register(), always carrying the take's ORIGINAL id. |
episode_path and composition. A take with neither lives in the inbox: whole and complete, awaiting the human's later choice. Nothing is lost by not deciding at the moment of recording.Capture is not composition
One package used to carry all three, and they were ruled fully separate — otherwise they “will always be confused”. A voice memo, a video take, a MIDI capture are takes in the capture domain; what a studio composes of them is another domain entirely; and carrying the old world across is a third, transitional one.