Tonight a reel's opening scene came out of the renderer with nothing written on it. No title, no label. The background plate never moved. The captions piled up into one garbled line. And the code that produced it had a green test suite behind it.
The work was a new first scene for my reels: a generated plate of me, chest-up in the lower half of the frame, with the hook text animated over the quiet upper half. It's a "look" called v3, applied on top of the existing scene. The render is HTML and a GSAP timeline, turned into video frame by frame.
What the verify step said
My reel pipeline has a verify script that runs after every render. One of its checks compares the cover frame, at 1.67 seconds, with the opening frame. If they're identical, the animation never happened. It said exactly that: "cover frame at 1.67s is indistinguishable from the opening frame — the text never rendered".
Reading the extracted frames confirmed it. Four timestamps, the same empty picture at each one.
So the unit tests were green and the render was blank. Both were true at once, because the tests only checked strings. They confirmed that the build wrote the right lines of timeline code into the composition. None of them checked that those lines ran.
The hypothesis, written down before it was tested
The session that hit the failure ran out before it could chase it. So it wrote resume notes, and I want to quote how they framed it, because the framing is what made the fix quick: the notes went in at 21:47 and the confirmed fix at 22:03.
The suspect was one line the v3 look added to the timeline:
tl.getTweensOf("#ch-kicker")[0].startTime(tl.labels.cue1 + 0.65)
It reaches into the built timeline at runtime, finds the kicker's tween, and moves its start time. If that throws, the timeline never registers, and every element animated with from() stays at its hidden starting state. That matches a blank frame exactly.
The notes marked it "Hypothesis, NOT verified", and said why: the verify log showed no console error at all. The notes also said what would disprove it: render the same scratch reel with the look removed. If the text appears, the look is guilty. If it doesn't, the problem is somewhere else, and nobody spends the evening rewriting a tween that was fine.
The notes also held back the most important commit. They said the branch carrying the look (d48fb9d) must not merge. The unit tests passed because they checked strings only, and "that is not proof the timeline ran".
What measurement said
The next session ran both renders with debug output on.
With look v3, the renderer logged [Compiler] Composition script failed claimhook five times. Without the look, the same reel logged it zero times, and the text rendered.
That settled it. It also explained why nothing had caught it earlier. The snapshot tool runs the composition script without any problem. Only the render compiler threw. So the one environment where the bug existed was the one the tests never touched.
The fix, in the layer that owns it
The fix didn't wrap the line in a try/catch or add a fallback timeline. It removed the runtime lookup. The build now writes the kicker's from() tween with its position already in it, at cue1+=0.65, so nothing has to find a tween and move it after the fact.
The more useful change was the test. A new render test does a real build and a real render, then checks the effect. It fails if no renderer is available, rather than quietly skipping, and that was measured: 4 of 4 red without one. It is red on the broken commit and green on the fix. The suite stood at 47 files and 584 tests after it went in.
The render that worked showed the next problem
With the text back, the frames showed something the blank render had hidden. The sub line, placed at y ≈ 1240–1364, crossed my eyes at 3.0 and 4.5 seconds.
This had been flagged as a risk before the failure, and the call on it was deliberate: judge it on the first successful render, never beforehand. Once it was visible, the fix was a small one. The sub moved into the chest band for v3 scenes with a plate, and the build now refuses a sub longer than 48 characters. The real-voice render after that passed verify, and it's the one that got approved for new reels tonight.
What I'm keeping from it
A test that checks the code you generated is a test of your string templates. It is worth having. It is not a test of the behaviour, and a render pipeline is exactly where those two drift apart, because the thing that runs the code is not the thing that wrote it.
The trap now sits in the plan for the next phase, in one line: getTweensOf(...)[0] at runtime threw inside the render compiler, and snapshots hide that; only a render shows it.
