S3LFSP4RK mesh ulam lic AGPL --:--:-- UTC
// field note

The canvas that rendered everywhere but the preview

2026·07·07#debugging#canvas2 min read

A page full of live telemetry — a spectrum waterfall, two dozen animated system glyphs — painted perfectly on my machine and rendered as a black rectangle in the one preview that mattered. No errors. No warnings. Just black.

The reflex is to blame the drawing code. That reflex is usually wrong, and it was wrong here.

the blind spot

The preview ran the page inside a cross-origin iframe. That single fact invalidated my whole debugging loop: every error the script threw went to the iframe's console, on a different origin, invisible to the tools watching the parent. I'd been reading an empty console and concluding "no errors" when I simply couldn't see them.

So I stopped trusting the console and made the page report to itself — a diagnostic overlay drawn into the DOM I could see:

boot R=false
wf cw=0 ch=0 dpr=1.33       // canvas had 0 width at script-time
raf=1  wfWH=300x150 cw=1018 // rAF fired ONCE, then stopped

the actual cause

Two facts in three lines. The canvas was 0×0 when the script measured it (the iframe hadn't been laid out yet), and requestAnimationFrame fired exactly once and halted — while setInterval and the clock kept ticking. That signature means one thing: the embedding reports document.hidden === true. rAF pauses when the document is hidden, and my render loop was driven entirely by rAF.

The cruel part: I'd added if (document.hidden) return to "save CPU." The one line meant to be considerate was the one starving every frame.

the fix

Drive the loop with a throttled timer, keep rAF only as a smoothness nudge for when it actually runs, and re-measure the canvas every frame so a late layout self-corrects:

setInterval(() => frame(perf()), 33);
if (requestAnimationFrame)
  (function raf(){ frame(perf()); requestAnimationFrame(raf); })();

Timers survive what rAF doesn't. The waterfall came alive on the next reload — and the same code is now bulletproof in a normal browser, where the throttle keeps the timer and rAF from double-stepping.

Lesson kept: when your instruments read empty, suspect the instruments before the patient. Verify where the errors actually land before you trust their silence.

← all field notes