super-prototyping
super-prototyping
An agent plugin for rebuilding and designing product UI as self-contained HTML artboards on a local tldraw canvas, with the measuring toolkit and the agent skills that drive the work. Install it into any project; your boards stay in your project and the plugin upgrades around them.
The point of it is a replica you can defend. Every colour and every metric on a cloned board traces back to a measurement of the source capture, and the capture itself is parked on the canvas directly under the replica, so the two are one glance apart rather than one memory apart.
How you use it: install the plugin, start the canvas, then hand Claude Code
your screenshots and ask for the clone-prototype skill. It grids the capture,
samples it region by region, writes one measured token block, generates every
board from a single gen.py, then re-renders those boards and diffs them
against the capture until the numbers hold. new-ui-mock does the same for
screens that have no reference to measure. Both write .html files into
mockups/canvases/<board>/, and the canvas picks them up as shapes with no
registry, no build step and no design tool.
Five worked examples
Five of the app folders in mockups/canvases/. That folder's own
README.md lists them all. Each is a real clone-prototype run, rebuilt
from measured samples with the evidence recorded for every token. Open any
of them with ?canvas=<slug>, and one board of it with
?canvas=<slug>#<file>. The address follows whatever is open, the page and
the board in the inspector, so the URL in the bar is always the link to share.
duolingo-ios, eight screens that are mostly picture
Replica on top, its source capture directly below it. The captures are cropped to the same 393 × 852 screen and masked to the same 52pt corner radius, so the two rows line up pixel for pixel. Six screens of the learning path and the two modal sheets.
luma-ios, twelve screens and the process behind them
Six of the twelve. The replica draws a Dynamic Island the capture does not have: the source composites it out, the iOS frame spec draws it, and this run keeps the frame and excludes the top 56pt from its numbers.
notion-ios, eighteen screens
Replica on top, its source capture directly below it. @3x captures, same crop and same scale.
claude-ios, fifteen screens across four flows
Six of the fifteen. Home, a streaming answer, voice mode, an artifact card, the Add to Chat sheet and a photo answer. The serif answer column is set in Georgia standing in for Tiempos, matched on cap height and about 11% wider.
raycast-ios, eleven screens across three flows
Replica on top, source capture directly below it. Same crop, same scale, so the two rows line up pixel for pixel. The Models sheet and Presets flows; the six "Ask AI" screens are on the same board.
Install
On macOS, Homebrew installs the app:
brew install --cask ReScienceLab/tap/super-prototyping
Then open Super Prototyping. The cask installs the same
Super-Prototyping-<version>-<arch>.dmg every
release
attaches, so downloading that instead gives the same app, signed and notarised
from v1.5.2 on.
On Windows, releases after v1.5.2 attach Super-Prototyping-<version>-x64.exe.
It installs for the current user and asks for no administrator. It is not
signed, so the first time it runs Windows says "Windows protected your PC":
choose More info, then Run anyway.
From then on the app updates itself. On launch it looks for a newer release,
downloads it in the background and asks once to restart; Later installs it
when you quit. v1.5.3 and earlier do not, so update those once by hand, with
brew upgrade --cask super-prototyping or the new installer.
The app is the canvas in a window, and it needs no terminal and no bun. It
opens by asking which agent you will work with, Claude Code or Codex, then
asks you to open a project or to name a new one, which goes in
Documents/Super Prototyping. It shows that project's mockups/canvases with
the example canvases beside them, read-only until you clone one into the
project. The agent you picked gets the skills copied into the project, each
copy marked with the app's version and refreshed on a later open when it is
behind the app's version. The app does not install the toolkit below. The
skills tell whichever agent you run to install it the first time one calls for
refkit, artgen or sp.
Without the app, on any platform, it installs in two halves, in every product. The plugin holds the three skills and the canvas app, and comes from your product's own install command. The toolkit the skills call by name is one more command, once per machine.
| Your agent | Install the plugin |
|---|---|
| Claude Code | /plugin marketplace add ReScienceLab/super-prototyping/plugin install super-prototyping@super-prototyping |
| Codex | codex plugin marketplace add ReScienceLab/super-prototypingcodex plugin add super-prototyping@super-prototyping |
| WorkBuddy / CodeBuddy | codebuddy plugin marketplace add ReScienceLab/super-prototypingcodebuddy plugin install super-prototyping --scope user |
| Hermes | hermes plugins install ReScienceLab/super-prototyping --enable |
| Pi | pi install git:github.com/ReScienceLab/super-prototyping@super-prototyping--v<version> |
Trae, and anything else that reads SKILL.md | npx skills add ReScienceLab/super-prototyping |
| Any of those except Claude Code, from a clone you control | scripts/install-skills.sh |
Then the toolkit, whichever product you came from:
uv tool install "git+https://github.com/ReScienceLab/super-prototyping#subdirectory=tools"
It puts refkit, artgen and sp on PATH, and sp start is how the canvas
runs in a browser: see Run the canvas.
One skills tree, a thin manifest per product, so a skill is never forked to be
ported: .claude-plugin/ for Claude Code, .codex-plugin/ plus the
.agents/plugins/marketplace.json catalogue for Codex, .codebuddy-plugin/ for
WorkBuddy, and a root plugin.json in the portable
Agent Plugins v1 format, which is what
Hermes installs. Pi and npx skills read skills/*/SKILL.md directly and need
no manifest at all.
/plugin update super-prototyping picks up a new release. The others are
codex plugin add again, codebuddy plugin install again, hermes plugins update super-prototyping, npx skills update, and for Pi another pi install
naming the new tag, since Pi pins the ref you gave it and never moves it on its
own. Re-run the uv tool install line with --force to move the toolkit too.
Both halves carry the same version, and sp start prints the line to run
when they drift apart. To hold the toolkit at a release rather than at the
default branch, name that release's tag. They are listed under
Releases:
uv tool install --force "git+https://github.com/ReScienceLab/super-prototyping@super-prototyping--v<version>#subdirectory=tools"
A smaller install. The full one is about 430 MB, because this repo is also
the workspace whose worked example boards the skills read, and a
marketplace install downloads the repo and then copies its worktree into the
plugin cache. If you only want the canvas and the toolkit, declare the
marketplace in ~/.claude/settings.json with sparsePaths and Claude Code
clones just those directories, cone mode:
{
"extraKnownMarketplaces": {
"super-prototyping": {
"source": {
"source": "github",
"repo": "ReScienceLab/super-prototyping",
"sparsePaths": [".claude-plugin", "skills", "canvas", "tools",
"mockups/canvases/templates"]
}
}
}
}
Measured at 6.7 MB installed, against about 430 MB. Add
mockups/canvases/duolingo-ios to that list to keep the one example
clone-prototype reads most, or drop the key entirely to get everything.
Per product, the parts worth knowing. codex plugin marketplace upgrade
refreshes the catalogue before codex plugin add moves you to the new version.
Codex has the sparse field too, spelled --sparse, but as of 0.145 a plugin
cannot be installed from a marketplace added with it. The install re-clones the
sparse snapshot and git cannot read the objects that were left out, so the small
install above is Claude Code's for now. Hermes leaves an installed plugin
disabled until you say otherwise, which is what --enable is for; it also takes
this repo as a skill tap (hermes skills tap add ReScienceLab/super-prototyping) if you want the skills without the plugin. Pi
pins whatever ref you install, so name a release tag rather than a branch. npx skills add asks which agents and whether to install globally, and knows Trae,
Trae CN, CodeBuddy, Hermes, Pi and Codex by name; -a trae -g answers both
questions up front.
No install command, or you want one checkout behind all of them. Clone once, then link:
git clone https://github.com/ReScienceLab/super-prototyping.git ~/.super-prototyping
~/.super-prototyping/scripts/install-skills.sh
It installs the toolkit and symlinks skills/* into every product skill root it
finds (~/.codex/skills, ~/.codebuddy/skills, ~/.hermes/skills,
~/.pi/agent/skills, ~/.trae/skills, ~/.trae-cn/skills). The skills are
links, not copies, so git pull in that checkout updates every product at once.
The toolkit is a copy, so re-run the script after a pull to move refkit,
artgen and sp with it. --list shows what it would do and changes
nothing. What it cannot give you is a version. A linked checkout is whatever you
last pulled, where a marketplace install is a release.
Start a project
Your project holds boards and nothing else — no canvas app, no toolkit, no skills to keep in step:
mkdir -p my-product-design/mockups/canvases && cd my-product-design
cp -r "$(sp root)/mockups/canvases/templates" mockups/canvases/<slug>
python3 mockups/canvases/<slug>/gen.py
sp root prints wherever the plugin landed. Every worked example above
is in there to copy from too.
Run the canvas
sp start # this project
sp start ~/my-app # any project, from anywhere
On first run it downloads the canvas app built for your version of the
plugin into ~/.cache/super-prototyping/, then serves it on 127.0.0.1:5173
against the project's mockups/canvases with node or bun, opens the browser,
and prints the address. --canvases DIR points it at another boards folder, --port N (or
SP_CANVAS_PORT) moves it, sp status and sp stop do what
they say. sp paths lists the two directories it writes, and
sp clean removes them.
Deep-link a page with ?canvas=<slug>, and one board of it with
?canvas=<slug>#<file>: it opens in the inspector with the camera on it, and
clicking any board writes that link into the address bar. Right-clicking the
canvas offers Force refresh; choose it after editing a layout.json. A board
folder added after boot appears on its own.
The workflow
Four skills, in skills/ (which .claude/skills/ and .agents/skills/
symlink to, so this checkout loads what an install does):
| Skill | Use it for |
|---|---|
| clone-prototype | Copying a real app's screens. Grid the reference, sample colours visually, name the type face, derive one measured token block, generate the artboards, verify by re-rendering, park the reference underneath. |
| new-ui-mock | Designing new screens with no reference, built on existing tokens, including the empty/loading/error states and side-by-side proposals. |
| prototype-canvas | Running and operating the canvas: boards, layout.json, the window.snapCanvas bridge, annotated-screenshot review, the force-refresh. |
| brand-kit | Collecting a product's own brand and promotional material -- the company's own brand or press kit, store listings, verified social accounts, the newsroom -- and turning it into the image rows of a canvas folder, each asset carrying its source and whether the company published it. |
The rule the whole thing is built around: every colour and every metric in a cloned artboard traces to a measurement. Grid the reference image, look at it, name the element, then write the token. Values that "look about right" are how a replica quietly stops being one.
clone-prototype, phase by phase
Never skip ahead. Sampling before tokens, tokens before HTML.
| Phase | What actually happens | Looks like |
|---|---|---|
| 0Collectreferences | Save every capture to a scratch dir first, because image caches rotate mid-task. Record the capture scale once, in px per design pt, and cross-check it against height. A 0.76 px/pt strip cannot settle thin ink, so get one native @3x capture of any screen in the same app.Out: p1.png … pN.png, and one number: 300 / 393 = 0.7634. | One settings row, both scales. The divider survives only one of them. |
| 1aGrid,then look | refkit grid p4.png -o g04.png --zoom 3 --minor 10 --major 50 draws a labelled grid onto the pixels. Then you read g04.png as an image and name the element each region belongs to before measuring anything. Coordinates picked blind produce numbers with no element attached, and those are the ones that land in the wrong token. Gutters, row pitch, insets and radii come off the same red labels.Out: a named region list, in design pt. | Cyan every 10pt, red every 50. The preset rows land 64 apart. Read, not guessed. |
| 1bSample,region byregion | refkit sample p4.png 76 646 132 668 --pt 3 runs a census over one named region; --pt keeps both halves in design pt, so you type the numbers you just read off the red labels. Which line of the census you believe depends on what you pointed at:• page, card, sheet → flat fills. A pixel equal to all four neighbours is a real fill, not an antialiased edge• badge, dot, brand mark → all pixels, top entry, on a core-only crop; too small to have a flat interior• text → ink core, the darkest few percent. The mode of a text region is its background: 93% of that Mistral box is #F2F2F2• pitch, edges, radii → bands / bbox / scan• 1pt divider or border → refkit hairline instead; a hairline never reaches full coverage in a downscaled capture, so solve it from the ink deficit rather than picking it. A solve within ~2 of the page background means the real UI has no divider there.Out: a token table with an evidence column. No evidence, no token. | Three named regions, three techniques, one crop of the Presets list. The label's own census is 93% background. The ink is the darkest 2%. |
| 1cName theface | refkit font ref.png 17.3 139 78.7 152 Libraries --pt 3 --fonts brand/ renders that word in every candidate face and ranks the glyph shapes at a common cap height. A closed set of ~20 faces already on disk is the right problem: the published classifiers solve a 3,000-class Google-Fonts one and so structurally cannot answer SF Pro. Under a 0.05 top-two margin it reports no call rather than naming a lookalike.Out: the one token nothing else could measure: --x-font, with evidence. Why not a model. | One word, two candidate sets. Slack ships Lato, which is not a system face, so the left column refuses, and --fonts turns it into an answer. |
| 2Designsystem | One :root block: the measured font stack, colour ramp, radii per component class, composite font: shorthands, geometry constants. Built as the first artboard, because it is the contract every screen is checked against.Out: 00-design-tokens.html. | Every swatch carries its hex and the element it was sampled from. |
| 3aOnegenerator | A single gen.py emits every screen, inlining that :root byte-identically. Artboards are output, never source. Hand-edit one and the next run reverts it.Out: NN-<slug>.html × N, layout.json. | Four boards out of one script. 478 × 980 each, self-contained, no shared stylesheet. |
| 3bSource theartwork | Every picture already on the capture is cropped out of the capture at its own measured box, keyed by id in a crops.json the generator reads: cut() writes assets/art/<id>.png, art() places the <img> back at the same pt numbers, so an asset cannot drift from where it was measured and a box correction is one edit rather than two. A crop is the reference's own pixels, so it scores Δ 0 by construction, and that is the whole argument for preferring it. Generate only what no capture contains, and when you do, hand the model the answer's geometry: pack the assets into a grid, each in its own cell at the size and position it must come back at, so it upscales in place instead of composing. That is worth 18.41 → 3.96 on the same six assets. artgen runs it end to end and scores each asset against the crop it came from. Density is free (77 assets in one call beat 6); native size is not, so anything under ~128px stays CSS or SVG.Out: crops.json and a committed assets/art/. | One asset, four ways to get it. Generated alone it is a good drawing and a bad measurement; generated in a grid it is 4.6× closer and still not the crop. |
| 4Verify byrendering | shoot --crop-phone --check-overflow renders and de-frames, diff --regions puts your fill next to the reference's, tokens audits the :root. Fan the looking out, one read-only subagent per screen, and keep a single writer for the generator.Out: a Δ per region, in numbers. | Two boards, one token apart. Nothing to see; six values to fix. |
| 5Park thereference | Each source capture goes into its own ref-NN-*.html as a data: URI, listed as a third layout.json row in the same order as the replicas. Rows lay out at index × (w + gap), so item N lands under item N.Out: every replica sits directly above its source. | Both rows as the canvas renders them. The reference artboard is the raw capture plus its attribution line. No bezel, nothing redrawn. |
The loop is 1a → 4 → 1a. A diff that disagrees sends you back to the grid, not
to the CSS. A correction you have not re-rendered is not a correction.
Constraints on every artboard
Boards render in <iframe srcDoc sandbox="">:
- Fully self-contained: no external CSS, JS, fonts or images.
data:URIs and inline SVG only. - The shape box is 478 × 980; overflow is silently clipped.
- iPhone frame is 393 × 852 pt at 1pt = 1px (54px status bar, 125 × 36 Dynamic Island, 139 × 5 home indicator).
See skills/prototype-canvas/references/layout.md for layout.json rows and
captions.
Toolkit
refkit, artgen and sp install together as
super-prototyping-tools. shoot additionally needs Google Chrome; on
Windows, Edge will do.
refkit grid ref.png -o grid.png --zoom 3 # overlay to read by eye
refkit sample ref.png 40 120 300 160 --pt 3 # fills, modes, ink core
refkit bands ref.png 30 120 60 780 --pt 3 # ink bands and their pitch
refkit scan ref.png col 196 380 410 --pt 3 # colour runs -> exact edge
refkit hairline ref.png 40 200 300 204 --bg FFFFFF --scale 0.7634
refkit font ref.png 17 139 79 152 Libraries --pt 3 \
--fonts ./brand-fonts # name the type face
refkit shoot mockups/canvases/my-app/*.html -o mine \
--scale 3 --crop-phone --check-overflow # render, de-frame, fail if clipped
refkit diff mine/01.png ref.png --pt 3 -o d.png # side by side + numbers
refkit tokens mockups/canvases/my-app # one :root, no undefined var()
refkit --version # which release you are on
Working on the plugin itself
cd canvas && bun run lint && bun run test && bun run build
uv run --with pillow --with numpy python tools/test_refkit.py
uv run python tools/test_sp_canvas.py
(cd desktop && bun install && bun test && bun run build) # the macOS app
scripts/bump-version.sh --check # every manifest agrees on one version
claude plugin validate . --strict # and the manifests are what they claim
The Validate workflow runs all of that on every pull request.
Releasing. The version is not bookkeeping: it is the cache key that
/plugin update and codex plugin marketplace upgrade compare against an
install, so commits on main reach nobody until it moves. Dispatch the
Release workflow with the new version — it runs the gates, moves every
manifest with scripts/bump-version.sh, and opens a release PR, because
"Protect main" wants a pull request and nothing bypasses it. Write that
version's section in RELEASE-NOTES.md, then merge: the tag
super-prototyping--v<version> and the GitHub Release follow from the merge.
The whole procedure, including what to do when a step fails, is under "Cutting a
release" in CONTRIBUTING.md.
Licence
This repo is Apache-2.0 (see LICENSE).
The tldraw SDK it depends on is not. tldraw ships under the tldraw licence: free to use with the tldraw watermark visible, paid business licence to remove it. Apache-2.0 here covers this repo's own code only. Anyone running the canvas is bound by tldraw's terms, and the watermark must stay.