DocumentationDecision recordsADR-0029
Stored video media
The workspace blob store, AttachImage operations, and the media library were built exclusively for images (PNG, JPEG, GIF, WebP).
accepted
Context
The workspace blob store, AttachImage operations, and the media library were
built exclusively for images (PNG, JPEG, GIF, WebP). Videos could only be
embedded by remote URL through the media node, which meant local recordings
had no durable, workspace-owned home and did not participate in usage
accounting or export.
Decision
Videos (MP4, WebM) become first-class stored media, reusing the existing image plumbing instead of adding a parallel pipeline:
skriuw-imagessniffs MP4 (ftypbox) and WebM (EBML header with awebmDocType) magic bytes and maps their extensions. The store remains content-addressed and format-validated at the trust boundary.WorkspaceImagerecords andAttachImageoperations carry video attachments unchanged;validate_mime_typeacceptsvideo/*alongsideimage/*.width/heightstay null for videos.- The editor
medianode gains an optionalrefIdattribute binding it to a workspace attachment. Canonical markdown serializes stored media as[title](images/<refId>)<!--skriuw-media:video-->, so exports reuse the image path-rewriting and file-bundling machinery. document_image_ids(Rust) and its renderer mirrors collectmedia.refIdnext toimage_ref.id. Detach-on-save, the unused sweep's live set, the media library's usage accounting, and "Remove all unused" therefore treat video references exactly like image references.- Videos are excluded from the note-cover picker; covers remain images.
Consequences
- A stored video that loses its last
mediareference is detached and becomes sweepable, identical to images. - Video playback picks its transport per platform. WebKitGTK delegates media
fetches to GStreamer, which resolves only http(s) and data URLs itself:
measured on WebKitGTK 2.52, asset-protocol video sources fail instantly
with
MEDIA_ERR_SRC_NOT_SUPPORTED(whilefetch()of the same URL returns 206 with correct ranges) and blob-URL videos stalled or errored on roughly half of 1–4 MB loads. Data URLs played 9/9 through 20 MB (~180 ms encode), so Linux desktop plays videos from a data URL and gives up streaming. macOS/Windows webviews stream custom schemes correctly and use the asset protocol (scope extended at runtime because the blobs directory is only known then). The browser runtime serves an object URL backed by an OPFSFilehandle. All retain a full-read blob-URL fallback. Images keep the byte-copy path. - The browser runtime gained its own media blob store
(
apps/workspace/src/bridge/browser-media.ts): the same content-addressed<sha256>.<ext>layout in one flat OPFS directory, with a TypeScript mirror of the magic-byte sniffer. Its unused sweep takes the live hash set from the renderer (the browser has no backend-side attachment view) and keeps the one-minute safety margin. - The
skriuw-imagescrate name andWorkspaceImagecontract name are kept despite now covering video, to avoid a contract rename with no behavioral benefit.
On this page