Skip to content

fix: make the preview stage visible to session replay - #37

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixpreview-editor-canvas-is-invisible-196f5d
Draft

fix: make the preview stage visible to session replay#37
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixpreview-editor-canvas-is-invisible-196f5d

Conversation

@posthog

@posthog posthog Bot commented Aug 30, 2026

Copy link
Copy Markdown

Problem

  • We are blind to the main surface of our own product in replay — and it cost two Replay Vision scanner runs chasing a render bug that does not exist.
  • The preview <canvas> is handed to a worker with transferControlToOffscreen(), so its pixels never live on the main thread. Session replay (rrweb) can only record main-thread DOM, so it captures the editor stage blank, with just the #8b5cf6 transform overlay on top — which reads exactly like "imported media renders as purple placeholder boxes".
  • Enabling PostHog captureCanvas alone would not help: rrweb instruments the canvas element's own context, and an offscreen-transferred canvas no longer has one on the main thread.

Changes

  • Mirror the canvas into a DOM <img> replay can record. A new useCanvasReplaySnapshot hook draws the current frame through the worker's existing captureThumbnail into a low-rate JPEG data URL and writes it to an <img>.
    • The image sits behind the live canvas, which clears to opaque black in the Rust compositor — so the user keeps seeing the live preview at full rate, while replay (which reconstructs the canvas with no pixels) shows the snapshot through it.
    • bg-background moved from the <canvas> to its wrapper so the canvas stays transparent-in-replay; visually a no-op live.
    • Capture is skipped when the frame is unchanged and while playing, keeping the GPU readback off the live render path.
  • Fix a real image-render race in preview-panel.tsx (unrelated to replay, in the same file):
    • Image textures were flagged uploaded before the fire-and-forget uploadBitmap resolved — a failed upload stayed flagged and never retried. Now the upload is awaited before flagging.
    • img.onload never asked for a re-render, so a freshly decoded image could fail to appear until an unrelated edit. It now re-renders the current frame on decode (while paused).

Why

Two Replay Vision signals reported imported media never rendering on the stage. The media renders fine — our own recordings just cannot see the canvas, so the "bug" was self-inflicted replay blindness on a core render path.

Test plan

  • pnpm --filter @tooscut/ui typecheck — no new errors (only the pre-existing missing-WASM module error, present on main in this environment)
  • oxlint --type-aware and oxfmt --check clean on changed files
  • pnpm --filter @tooscut/ui test passes
  • Manual replay verification not run in this environment (no WASM/GPU/PostHog recording available). The layering and rrweb behavior were verified by reasoning: live canvas clears to opaque black (crates/compositor render_frameColor::BLACK) so the img is hidden live; the img src is a self-contained data URL that rrweb records verbatim.

Agent context

  • Considered PostHog captureCanvas — rejected: does not work for an offscreen-transferred canvas.
  • /simplify pass applied the playback-skip guard; skipped a suggested shared-helper extraction (would widen the change into use-auto-save.ts) and a wrapper-<div> removal (false positive — the wrapper is needed so the <img> aligns to the canvas content box inside the p-3 padding).

Created with PostHog Desktop from this inbox report.

The preview canvas is handed to a worker with transferControlToOffscreen(),
so its pixels never live on the main thread and session replay (rrweb) records
the editor stage blank. Every editor recording looked like media rendered as
empty/purple placeholder boxes, which sent Replay Vision scanners chasing a
render bug that does not exist.

Mirror the canvas into a DOM <img> that replay can record: a new
useCanvasReplaySnapshot hook draws the current frame through the worker's
existing captureThumbnail into a low-rate JPEG data URL. The image sits behind
the live canvas, which clears to opaque black, so the user keeps seeing the
live preview while replay (which reconstructs the canvas without pixels) shows
the snapshot. Capture is skipped while unchanged and during playback to keep it
off the live render path.

Also fix a real image-render race in preview-panel: image textures were flagged
uploaded before the fire-and-forget uploadBitmap resolved, and img.onload never
asked for a re-render, so a freshly decoded image could fail to appear until an
unrelated edit. Await the upload before flagging it, and re-render on decode.

Generated-By: PostHog Desktop
Task-Id: e9cb72a8-4128-4b8e-98b0-49a9a3c0cf04
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tooscut Ready Ready Preview Aug 30, 2026 7:58pm
tooscut-docs Ready Ready Preview Aug 30, 2026 7:58pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants