Skip to content

Add the canvas type axis and an axis-keyed ruleset table - #235

Merged
vicmaster merged 4 commits into
masterfrom
phase-30-slice-b
Oct 31, 2026
Merged

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

Conversation

@vicmaster

Copy link
Copy Markdown
Owner

Phase 30 slice B. A canvas could say what it is for (genre) and what style it uses (preset), but not what medium it is on. This PR adds that third declaration, the canvas type (web or mobile), and moves the genre and preset relaxations into one ruleset table that all three declarations feed.

The canvas type

canvas_create accepts type and device. With type: "mobile" the artboard defaults to the device size (390×844 for iphone-390, the only profile so far), and the root gets the device's safe-area insets as safeArea: { top: 47, bottom: 34 }. An explicit width or height still wins for its side. Passing device without type: "mobile", or naming an unknown device, is an error. The result reports type, typeSource (declared or default), and for a mobile canvas device and safeArea. A canvas created without type is unchanged and is judged as web.

The renderer adds the root's safeArea to its own top and bottom padding, at exact pixels even when the authored padding scales fluidly. In-flow content therefore never sits under the status bar or the home indicator, and the root's fill paints the bands. A root without safeArea renders byte-identically to before.

A new tool, canvas_set_type, declares or clears the type on an existing canvas. It writes only the declaration. It never resizes the artboard or writes safeArea, because either would reflow content the agent already placed. When a mobile result is missing either, its hint names the exact batch_design op that adds it. The declaration is metadata, so it does not move the versionHash. State variants copy the type.

The type never changes what renders. It selects rules and generator defaults only, so a CI check of a mobile canvas's hash means the same as for a web canvas.

One ruleset table

The genre and preset relaxation tables now live in src/surfaces.ts as one table keyed by axis (base, type, genre, preset). Each row can relax cliché tells and turn rule ids on or off, and the active rules are the union of the rows that apply. A rule that one row enables and another disables stays on and is reported as a conflict, so combining declarations can never switch a guardrail off.

Every enable and disable list is empty in this slice, the mobile row included. A rule id lands in the same change as the check that honours it, and the mobile checks are slice C. Genre behaviour is unchanged: the genre report matched master byte for byte across 360 combinations of preset, stored genre and per-call genre.

The surface report

canvas_evaluate and canvas_autofix always return a surface field: { type, device, source, rulesOn, rulesOff, conflicts }. It appears on every mode and category filter, and on the empty-canvas result too. The viewer's score cache now keys on the type and device as well, so re-declaring a canvas re-scores it in the gallery.

A mobile type scale

generate_design_system on a mobile canvas pivots its type scale on the device body size: 17px for every personality except data-dense, which uses 16px. No generated size falls below 11px. An explicit baseSize still wins. The result reports the generated pivot as baseSize and baseSizeSource. A $body inherited from a workspace or project design system is still preserved by default and listed in designSystemConflicts, so the docs say to pass preserveInherited: false to take the mobile body.

Device constants

Before the profile table landed, every constant was re-checked against Apple's HIG through its JSON and Markdown endpoints. No value changed. Apple's own pages confirm the 44pt touch target, Body 17, Caption 2 at 11, and "five or fewer" labeled tabs. The insets come from secondary tables that agree with each other. The bar heights, row heights, side margin and home indicator have no Apple source that could be read, so the spec's table now marks them as house values.

Smaller fixes found on the way

  • canvas_autofix now reloads the canvas from disk in propose mode too. Before, its proposals and reports could describe a stale copy after a pull or hand-edit.
  • canvas_set_type's hint and the renderer share one rule for which safeArea values count, so the hint never treats a value the renderer ignores as reserved.

Docs

canvas_create, canvas_set_type, canvas_evaluate, canvas_autofix and generate_design_system docstrings, INSTRUCTIONS, GOTCHAS, the README, and a new "Declaring the medium" section in docs/GUIDELINES.md cover the type. They state that no mobile rule ids are registered yet. Two GUIDELINES lines that still taught width: 390, height: 844 for a phone screen now point at type: "mobile", and safeArea is listed as a node property. test-discoverability.ts reads the ruleset table directly instead of parsing evaluate.ts, and checks that every canvas type, device profile and surface field is documented. The phase spec records what was built in a slice B "As built" block.

Testing

  • New test-surface.ts (Chrome-free, 103 checks): creation defaults and errors, canvas_set_type storage, persistence and on-disk freshness, variants, ruleset union and conflicts, the surface report, safeArea rendering for every padding form, render invariance under the type, the score cache key, and the real tools over MCP stdio.
  • test-design-language.ts covers the mobile pivot for all four personalities.
  • Independent verification checked every acceptance item and contract point by running the code. Mutation testing killed every targeted mutant once the gaps it found were closed. A review found no regressions: every structure renders byte-identically to master, and persistence holds through both the global store and a bound repo.
  • npm run test:full passes (107/107), and tsc is clean.

Phase 30 slice B. canvas_create takes type/device (mobile: device artboard and
root safeArea insets the renderer adds to the root padding); canvas_set_type
re-declares without touching the root. Genre and preset relaxations move into
one ruleset table keyed by type, genre and preset that composes by union and
can enable or disable rule ids. Evaluate and autofix report the surface, and
generate_design_system pivots on the device body size on mobile.
canvas_set_type's hint now uses the renderer's safeArea validity rule, canvas_autofix reads fresh in
propose mode, and clearing a type drops an emptied metadata bag. Docs stop implying mobile rules run
yet and answer the cold-read gaps; test-surface covers the mutation survivors.
@vicmaster
vicmaster merged commit f842cf9 into master Oct 31, 2026
3 checks passed
@vicmaster
vicmaster deleted the phase-30-slice-b branch October 31, 2026 22:02
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