Stageshow Docs Gallery

How we proved the port

Moving a React design kit to Svelte 5 is the easy part. Proving the result is faithful is the work.

Notes · svelte-stageshow

Stageshow is Benji Taylor's kit for making faux product UI that reads as a real screenshot: browser chrome, a cursor that types under a script, 69 worked scenes. It shipped as React. This is a port to Svelte 5, and the interesting question was never "does it compile" — it was "is it the same thing".

A port has two possible failure modes

The obvious one is that it does not work. The subtle one is that it works and looks almost right: a 3px gap where there should be 4, a cursor that lands a character early, a card that pops instead of flying. Almost right is worse, because it looks finished.

So the port needed a test that could tell "almost right" from "right", on all 69 scenes, without a human staring at two screenshots for an afternoon.

Compare at a settled mark, not at the end

The first version of the test captured each scene's final frame and diffed it against the original. It produced failures that were not real.

Animated scenes do not settle all at once. A scene can finish its last beat and still be mid-fade, mid-spring, mid-scroll. Diffing the last frame compares a settled original against a port that is one frame behind, and the score collapses for a reason that has nothing to do with the port.

Three scenes failed this way and then passed untouched: a cursor raycast, a dynamic island, and a slide transition. That is the point where you stop trusting the test.

Score at a named, settled mark in the scene's own script. Never at the last frame.

Every scene now declares a mark where it is visually at rest — after the transition finishes, before the next one starts. The gate reads that mark for both sides. The table of marks is in the repository, one row per scene.

Frames have to be reproducible before they can be compared

A comparison is meaningless if the same scene renders differently on two loads. The kit drives its animations from a script, so the port exposes a mode where CSS transitions are off and a virtual 60fps clock drives every wait, tween and keystroke. Advancing the clock to frame n must produce identical DOM, every time.

That harness ran 1,501 frames across the scenes and reported no mismatch. Only then was a screen comparison worth anything.

What the numbers actually are

measureresult
scene slugs69, every one ported
stylesheets42, carried over unchanged
brand marks44 SVGs
comparisons71, all matched at their settled mark
determinism1,501 frames, no mismatch
runtimeno React, react-dom or lucide

Seventy-one comparisons for 69 scenes, because two of them are compared twice: once at the mark, once as a still.

The port itself, briefly

Keeping the stylesheets unchanged was a deliberate constraint. If the CSS is the same, any visual difference is the port's fault, which makes the comparison mean something. It also means the design language stays Benji's rather than becoming a looser interpretation of it.

The code follows the same rule: same prop names, same defaults, CSS custom properties for the tokens. React's node props became Svelte snippets, and React's event props became callback props, because those are the language's native equivalents rather than a translation layer.

What this method does not catch

A scene can match its reference and still be a bad demo. The comparison says the port is faithful; it says nothing about whether the original was worth making. That judgment is still a person looking at the thing and deciding.

It also cannot tell you the port is usable. Matching pixels is not the same as a component that behaves well in someone else's app, with someone else's state and their own content inside it. That takes people using it, which is the next stage and the reason the package is public.

If you port something