Skip to content

Show canvases at their artboard proportions and phones in a device frame - #239

Merged
vicmaster merged 4 commits into
masterfrom
phase-30-slice-f
Nov 1, 2026
Merged

vicmaster merged 4 commits into
masterfrom
phase-30-slice-f

Conversation

@vicmaster

Copy link
Copy Markdown
Owner

Phase 30 slice F. Slices B to E made a phone screen something framesmith can declare, build and judge, but the viewer still showed every canvas as a desktop page. Gallery thumbnails were hardcoded to a 1440×900 iframe at scale(0.222), so a 390×844 phone canvas filled about a quarter of its card. The detail view offered breakpoint buttons hardcoded to 390 and 768 and had no way to present a phone as a phone. This PR fixes both.

Thumbnails at the artboard's proportions

  • Each gallery and archive card now renders its iframe at the canvas's own artboard size and takes the artboard's aspect ratio, so a phone screen is a portrait card.
  • The aspect ratio is clamped between a phone held upright (390:844) and 2:1. Without the clamp, a 1440×6000 landing page would become a card about 1,300px tall. A longer artboard shows its top, as every artboard taller than 16:10 did before.
  • A small script scales each thumbnail to its card's rendered width and keeps it fitted when the window resizes. Before this, the fixed 0.222 scale left a blank strip in any card wider than 320px and cropped any card narrower.
  • Cards in a row align to their top, so a portrait card does not stretch a landscape one beside it.

Device mode for mobile canvases

  • A canvas declared mobile opens at 1:1 inside a CSS device frame: a dark bezel and a rounded screen, with no images and no notch. The canvas scrolls inside the screen, so pinned chrome such as a tab bar stays put while the content moves, as on the phone.
  • A Safe area toggle shades the top and bottom insets over the screen. Each side uses the root's safeArea when it sets one, and the device's inset otherwise (47 and 34 on iphone-390). A canvas made mobile with canvas_set_type, which never writes safeArea, therefore still shows the bands its content has to clear. A side whose inset is 0 draws no band.
  • A mobile canvas has no Mobile, Tablet, Desktop or Compare buttons and no Fit button, because a phone canvas has one device and is shown at its real size. Comment, JSON, Dark, Archive and Delete work as before, and comment mode places notes inside the device screen.
  • Device mode is presentation only. The /canvas/:id/html render route is unchanged, so screenshots and the versionHash are unaffected. A verifier compared the route's output before and after for five canvases and three query variants, and every one was byte-identical.

Changes to existing behaviour

  • On a web canvas, the Mobile button and the Compare view's mobile cell read their size from the device profile table instead of a hardcoded 390×844. Tablet stays 768×1024, because no tablet profile ships.
  • The highlighted breakpoint is now the button that was pressed. Before, the highlight was inferred from the width, so pressing Desktop on a 390-wide web canvas highlighted Mobile.
  • Switching breakpoints no longer clears the Dark, JSON and Comment buttons' state.
  • Switching breakpoints with Dark on keeps the dark render. The frame used to drop back to light while the button still read dark.
  • An open detail page now reloads when the canvas's type, root size or root safeArea changes. Before, the two-second poll refreshed only the iframes, so a canvas_set_type call left the old toolbar and frame in place until a manual reload. A content-only edit still refreshes just the iframes.
  • A preview wider than its pane can now be scrolled to its left edge. A 1440-wide mobile canvas, the device in a window under about 460px, or the Compare grid in a narrow pane used to start at a negative offset that no scroll could reach.
  • The viewer reads a root width or height only when it is a finite number above 0, and falls back to 1440×900 otherwise. Previously 0 or Infinity could reach the CSS as a zero-width iframe or an infinite scale.

Known limits, recorded in the spec

  • Reloading the detail page on a type or size change resets the page's own state: Dark, the JSON panel and the overlay toggle.
  • Gallery cards still refresh only when a project's canvas count changes, as before. A resized or re-declared canvas shows its new card shape on the next page load.

Docs

The README viewer paragraph no longer says every canvas has breakpoints. It, the README routes list, and a new bullet in the "Declaring the medium" section of docs/GUIDELINES.md describe the card clamp, device mode, the overlay and its fallback insets, and what an undeclared 390px canvas looks like. The spec's slice F build contract and its "As built" block record each departure. No MCP tool changed, so the docstrings, INSTRUCTIONS and GOTCHAS needed no edits; the docs audit confirmed none of them describe the viewer's sizing.

Testing

  • New test-viewer-surface.ts (no browser) checks the card markup (sizes, aspect ratios, clamps, scale, the archive page), the mobile detail page (frame, bands, toolbar), profile-driven web breakpoints, the zero-inset and invalid-size cases, and the reload and theme wiring.
  • New test-viewer-surface-browser.ts (full tier) drives the real viewer in Chrome. It measures that thumbnails fill their cards at two window widths, toggles the overlay and measures the bands at the screen edges, and checks breakpoint state. It also checks that an open page switches to the device frame after canvas_set_type, redraws the bands after a safeArea edit, and keeps a wide device's left edge reachable.
  • Two verifiers, the docs steward and a cold reader checked the first build. Both new tests failed against the pre-slice viewer, and six of seven targeted mutations were caught. The seventh, hardcoding 390×844, is equivalent while the profile is 390×844. The tests do fail if the profile changes.
  • The review found the stale page after a type change, the unreachable left edge, the dropped dark render, the 1px zero-inset band, sub-pixel fit rounding and the Infinity size. A second-round verifier confirmed each fix and found the stale bands after a safeArea edit, which the last commit fixes. Every new check was shown failing on the commit before its fix.
  • npm run test:full: 115/115.

@vicmaster
vicmaster merged commit 6c54a99 into master Nov 1, 2026
3 checks passed
@vicmaster
vicmaster deleted the phase-30-slice-f branch November 1, 2026 20:47
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.

1 participant