A Demo for Every Feature — and the Bug Where Two Voices Talked Over Each Other
We have a rule: nothing is "done" until there's a sixty-to-ninety-second video of it actually working. Not a screenshot. Not a description. A real recording — the terminal or the browser, doing the thing, narrated.
That rule does something sneaky. It turns "is this finished?" from an opinion into a deliverable. You can argue about whether a feature is complete. You can't argue with a video that either shows it working or doesn't. The demo is the acceptance test that a human can watch.
How the pipeline is built
The whole thing is a function: a small storyboard file goes in, a finished mp4 comes out. The storyboard describes a sequence of cards — a title card, a few terminal or browser segments showing the real flow, a closing card — each with a caption and a line of narration.
Under the hood it's deterministic shell-outs, not magic:
- Terminal segments are recorded through a scripted virtual terminal, so the "typing" is reproducible and never depends on a human hitting keys. (Workers have no screen to record — the demo has to be capturable headless.)
- Browser segments are driven by a real browser under automation, recording itself as it clicks through the flow.
- Narration is generated text-to-speech, one short clip per card.
- Cards and captions are rendered as images and composited on top.
ffmpegstitches it all together — segments end to end, narration mixed in, captions burned on.
One command, resumable. If text-to-speech hiccups on one card — and cloud TTS throws the occasional transient error — you rerun and it skips everything already done, retrying only the part that failed.
The bug: two voices, one timeline
Here's the gotcha that cost us a confusing afternoon of "why does this demo sound haunted."
Each card has a duration, and each card has a narration clip placed at the card's start. The mux step takes every narration clip, delays it to its card's start offset, and mixes them all onto one audio track. Straightforward.
Except: the mux step trusts your durations completely. It does not stretch a card to fit its narration, and it does not trim narration to fit its card.
So if a card is three seconds long but its narration is five seconds long, those extra two seconds of audio don't get cut. They bleed forward — and start playing on top of the next card's narration. Two voices, talking over each other, slightly out of phase. It sounds exactly as bad as it reads.
The fix is a one-line invariant we now enforce on every storyboard:
Each card's duration must be ≥ its narration length (plus about a second of pad).
The pipeline already logs the length of every narration clip and warns when a clip outruns its card — we'd just been ignoring the warning. Now we read it. Terminal segments are usually long enough on their own; it's the short title-and-transition cards, paired with a wordy line of narration, that overrun.
The "burn the captions in" tax
One more constraint worth naming, because it shaped the design. The box these demos render on can't draw text directly onto video frames. So captions and lower-thirds can't be a live ffmpeg text filter — they have to be rendered as images first and overlaid.
That sounds like a limitation, and it is, but it forced a discipline we'd keep anyway: every caption is a real composited image, styled consistently, on-brand, every time. There's no "eh, the default font is fine." The captions are designed, because they have to be made deliberately to exist at all. The final pass always burns them in and trims dead air, and someone watches the whole thing through once before it counts.
Why bother
Because a demo per feature changes the incentives. When "done" means "I produced a watchable proof," people stop hand-waving the last ten percent — the ten percent that's usually where the bug is. The video catches the thing the unit test didn't, because the video is the integration test a human can sit through.
And the meta-joke writes itself: the most common bug in our demo pipeline was an audio race condition. Even the tool that proves your features work has features that can break. So we demo the demo tool, too.
We're documenting these as we go. If you're trying to make "show me it works" a default instead of an afterthought, we're happy to compare notes.
Found this useful?
Related posts
We Gave Every Agent Its Own Worktree. They Still Shared One Database.
We spent weeks making sure no two agents could touch the same files. Then two of them quietly corrupted the one thing we forgot they shared: the database. Here's the bug, the fix, and the rule we now live by.
The Self-Verifying Loop: Making "Builder ≠ Verifier" a Mechanical Property
There is a failure mode that every team running agents eventually hits, and almost nobody catches early: the agent that grades its own homework. You ask a model to do the work and to confirm the work is done, and it does…
A Green Check Is Not Proof: How We Learned Our Quality Gates Were Lying to Us
We run an autonomous software company where AI agents build and ship the work, and a set of "quality gates" decides when a piece of work is allowed to advance. For a long time those gates told us everything was fine. The…
The Queue That Resurrected Dead Work
We killed a product. The queue didn't get the memo — so it kept building it anyway, across three or four teams at once. Here's the bug, the fix, and the rule we now live by.