Skip to content

A playground that works on a phone, and exporters that arrive when wanted - #6

Merged
JGalego merged 2 commits into
mainfrom
claude/hyperlab-retro-website-bico9i
Aug 2, 2026
Merged

JGalego merged 2 commits into
mainfrom
claude/hyperlab-retro-website-bico9i

Conversation

@JGalego

@JGalego JGalego commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Two problems the landing page's own invitation created: it sends people to
a playground that did not work on a phone, and it made everybody download
an SVG renderer to get there.

The playground on a phone

The inspector holds a fixed 290px beside a card up to 640 wide, so on a
375px screen the card was pushed off the left edge and the whole page
scrolled sideways.

Below 60rem the columns become rows, the card scales to the room it has, and
the inspector becomes a sheet the View menu opens rather than a column
that is always there. 60rem, because 640 + 290 cannot fit under about 960 —
landscape phones very much included.

The rules live in apps/web/src/mobile.css, layered over the theme rather
than folded into it: the theme belongs to the desktop, where the window is
never 375 pixels wide. The zoom comes from measuring the stage with a
ResizeObserver, not the window, because the menu bar, the status bar and
any open sheet have taken their share first — and a landscape phone has
plenty of width and almost no height.

Three attempts got this wrong before measurement caught them: a 48rem
breakpoint that let landscape phones fall through to the desktop layout; a
ResizeObserver that never attached, because App returns early with a
loading notice and the ref was still null when the effect ran; and a
sideways-scrolling menu bar, where overflow-x made a clipping context and
cut off the menu dropping out of its own button. The bar wraps now.

One shared file

Part.tsx divides a drag by the scale it measures off the part, so a part
lands where the finger left it on a scaled card. At 1:1 the divisor is one
and the desktop is unchanged — checked by driving both (zoom 0.95 → 42 card px, expected 42; zoom 1.00 → 40, expected 40). It also rounds to
whole pixels now, which it should always have done.

The exporters are fetched when somebody exports

An SVG renderer, a font database and a text shaper come with PDF and Decker
export, and together they were most of what HyperLab compiled to.

before after
runtime wasm, every visit 1.62 MB gzip 326 KB gzip
exporters in the bundle 1.62 MB gzip, first export only

They are now crates/web-export, a module of its own. The seam is the
stack's own single-file JSON, so the two modules share no state. That needed
a stack_text command distinct from save_stack_json — the latter marks the
document clean, because a download is the save, and exporting a PDF is not
saving.

Verified

CI green on 3306348. Beyond that, the built site was driven in Chromium:

  • Portrait, landscape and the 640px Cluedo card all sit inside the viewport
    with no sideways scroll; the desktop keeps its inspector, its unscaled
    card and no View menu.
  • Opening Cluedo fetches the runtime and neither the exporters nor the
    typeface. Exporting fetches both, once. The PDF still carries its artwork
    labels.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R7eaxUAqy2gKQSBNnYZoFr


Generated by Claude Code

claude added 2 commits August 2, 2026 17:38
It was not. The inspector holds a fixed 290px beside a card up to 640 wide,
which on a 375px screen pushed the card off the left edge and left the whole
page scrolling sideways — so the landing page's own invitation to open the
playground led somewhere broken.

Below 60rem the columns become rows, the card is scaled to the room it
actually has, and the inspector becomes a sheet the View menu opens rather
than a column that is always there. 60rem because the widest card a stack
ships with is 640 and the inspector wants 290 beside it, so anything under
about 960 cannot hold both — a landscape phone very much included.

The rules live in a web-only stylesheet layered over the theme rather than
in the theme itself, which belongs to the desktop, where the window is never
375 pixels wide. The zoom comes from measuring the stage, not the window:
the menu bar, the status bar and any open sheet have taken their share
first, and a landscape phone has plenty of width and almost no height.

Part.tsx now divides a drag by the scale it measures off the part, so a part
still lands where the finger left it. At 1:1 that divisor is one and the
desktop is unchanged; the drag also rounds to whole pixels now, which it
should always have done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7eaxUAqy2gKQSBNnYZoFr
An SVG renderer, a font database and a text shaper come with the PDF and
Decker exporters, and together they were most of what HyperLab compiled to:
1.6 MB over the wire, paid for by everyone who only wanted to click through
Cluedo. They are now a WebAssembly module of their own, fetched the first
time an export is asked for. The runtime that opens a stack is 326 KB.

The seam is the stack's own single-file JSON, which persistence already
reads and writes. A new stack_text command hands the text over — distinct
from save_stack_json, which marks the document clean because a download
*is* the save, and exporting a PDF is not saving. The export module parses
it and works from that, so the two modules share no state.

Driving the page confirms the split holds: opening Cluedo fetches the
runtime and neither the exporters nor the typeface; exporting fetches both,
once, and the PDF still comes out with its artwork labels.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7eaxUAqy2gKQSBNnYZoFr
@JGalego
JGalego merged commit 95bdab6 into main Aug 2, 2026
14 checks passed
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.

2 participants