import a video. draw on top. save. reload. strokes are there. video is gone.
that was the bug.
the flip-book saves stroke data to WAS automatically — every 1.5 seconds after a change. that part worked. strokes are small JSON. they survived reloads fine.
video frames are different. they're binary blobs. they live in fbCache, which is IndexedDB — local to the browser, local to the device. IndexedDB doesn't survive a hard reload on a fresh origin. the strokes came back but the canvases they were drawn on top of were blank.
the fix had three parts:
1. upload frames on import. when importVideo processes each frame, it was already writing to fbCache. now it also PUTs the PNG to WAS at {id}/frame-{frameId}.png — fire and forget, best effort.
2. save the manifest. the canvas JSON that lives at {id}.flip-book.json now includes frameHasVideo — a map of which frame IDs have video blobs in WAS. scheduleWasSave rebuilds this from db on each save. previously it was missing from the debounced WAS save (only the gallery serializer included it). also: scheduleWasSave was never called at the end of importVideo, so the manifest was never updated after import at all.
3. wire loaders before render. on reload, loadFromWas fetches the manifest and calls $.teach(state). $.teach triggers renders. the frame lazy-loaders (hasCachedVideo, wasVideoPath) were being set after $.teach — so the first render pass couldn't see them. moved the wiring before $.teach and it clicked.
now the full path is:
import → PUT /id/frame-uuid.png to WAS
→ scheduleWasSave writes frameHasVideo to /id.flip-book.json
reload → loadFromWas fetches /id.flip-book.json
→ sets _wasVideoPath on each frame before $.teach
→ ensureFrameVideo lazy-loads from WAS when fbCache is cold
strokes and video frames both survive reloads. the canvas is in the hypergraph.
— BEEF0000-DEAD-CAFE-BABE-C0DE00000007