Skip to content

Add the mobile ruleset (Phase 30 slice C) - #236

Merged
vicmaster merged 5 commits into
masterfrom
phase-30-slice-c
Oct 31, 2026
Merged

vicmaster merged 5 commits into
masterfrom
phase-30-slice-c

Conversation

@vicmaster

Copy link
Copy Markdown
Owner

Phase 30 slice C. Slice B gave a canvas a declared type but no rule used it, so a phone screen was still judged as a web page. This PR adds the mobile ruleset: six rules that register in the ruleset table and run when the canvas type turns them on.

The rules

Every finding from these rules carries a new rule field naming its id. The surface report lists the ids that ran in rulesOn.

Rule Runs on Category Severity
mobile-touch-target mobile usability error
mobile-safe-area mobile usability warning
mobile-type-floor mobile typography warning
mobile-handbuilt-target mobile usability info
mobile-one-column mobile usability info
mobile-artboard every type structure warning or info
  • Touch targets. On a mobile canvas a toggle, checkbox, radio or select whose target is below 44px in either dimension is an error. A control's target extends to its row only when the row is a horizontal frame holding that one control. A row with no width stretches to its parent's width when that width is known. A lone checkbox inside a padded card is measured at its own size, because a card is not a row. Web canvases keep the 24px error and the 44px phone-width info unchanged. The slice D primitives join this rule as they land.

  • Safe areas. Content under the 47px status bar or the 34px home indicator warns. With safeArea on the root the renderer already reserves both insets, so fast mode checks only positioned content. Without it, the first and last content must clear the insets. Fast mode measures padding as the renderer draws it: padding of 32px or more scales down on a phone, so 48px renders about 13px. Detailed mode checks rendered positions and is the authority.

  • Type floor. These warn on a phone:

    • text below 11px;
    • a text whose fontSize is $body and resolves below 16px;
    • prose of five or more words below 13px.

    Long copy of twelve or more words at 13 to 15px is an info. Figures and their units do not count as words, so a metrics line stays legal at 12px.

  • Hand-built tap targets. A frame shaped like a tap target (one icon, or one icon plus a label of three words or fewer) whose box is below 44px is an info. It never blocks, because a frame cannot say it is decorative.

  • One column. Prose set side by side on a phone is an info.

  • Artboard. This rule runs on every type, so it can catch canvases the mobile ruleset is not on for. A mobile canvas 500px or wider warns. An undeclared canvas narrower than 500px warns until canvas_set_type declares it either way. A canvas declared web at that width gets an info.

Stress and the type

canvas_stress records the canvas type it ran under. The evaluate result's stress field gains type, and current is true only when both the version hash and the type still match. A FRAGILE run still blocks after a type change. The directive's note then names the earlier type instead of blaming an edit. On a mobile canvas the long-text perturbation leaves strings of three characters or fewer alone, so a monogram no longer reads as a layout break. The stress engine also gained a table of primitive text props and treats runs of sibling list-rows as lists. Both are dormant until slice D adds those node types.

Smaller changes

  • canvas_set_type now returns variantsOutOfSync, listing each state variant whose type differs from the base after the change. Variants keep the type they were created with.
  • Detailed mode lays out at depth 25 on a canvas with the safe-area rule on, and computes the layout when only usability is requested. Web canvases keep depth 10.

Disabled web rules (FR-C7)

None qualified. The original Ridgeline screen was evaluated as mobile, and no existing check fired on a correct mobile idiom. Its remaining findings were APCA advisories, the coverage demands for its run list, and the "no component instances" advisory, which slice D resolves by counting primitives as reuse. The mobile disable list stays empty.

Spec changes

The slice C build contract and its "As built" notes in docs/specs/PHASE-30-SPEC.md record every decision the FR rows left open. Three of them change what the spec said:

  • FR-C2's acceptance was wrong, not its rule. Ridgeline's icon buttons are 44×44 and its tab items 56×48, so they meet the floor. Evaluated as mobile, Ridgeline reports exactly its three 40×40 icon tiles.
  • Detailed mode also flags in-flow content that ends in the bottom inset at the end of the scroll, whether or not the root has safeArea. The root renders at a fixed height, so its bottom padding does not extend past overflowing content. A long screen therefore really does end under the home indicator.
  • The same fixed-height root shrinks a spacer frame placed directly under it on a long screen. The docs say to put spacers inside the content frame. Slice D now owns fixing the root render, and the spec's slice D plan says so.

Until slice D lands, fast mode trusts the safeArea reservation on long screens and only detailed mode catches that case. The docs say so where the rule is introduced.

Docs

The canvas_evaluate, canvas_autofix, canvas_stress and canvas_set_type docstrings, INSTRUCTIONS, GOTCHAS, the README and a new "The mobile ruleset" section in docs/GUIDELINES.md cover the rules, their thresholds and fixes. They also say how to run detailed mode. The stale "no rule ids are registered yet" text is gone. test-discoverability.ts now checks that every registered rule id and the rule field are documented.

Testing

  • New test-mobile-rules.ts (Chrome-free) covers every rule's positive, negative and boundary cases, the stress engine on mobile, the directive's type note, and the real tools over MCP stdio.
  • New test-mobile-rules-detailed.ts covers detailed-mode safe areas and the stress record's type through the real tools, including a FRAGILE run followed by a type change.
  • Independent verification checked every acceptance item by running the code, and found web canvases unchanged except for mobile-artboard. Mutation testing ran 104 mutants and the surviving gaps were closed with boundary tests. A review found a cubic slowdown on deeply nested trees, now fixed: an 800-deep chain went from about 2s to about 40ms.
  • npm run test:full passes (109/109), and tsc is clean.

Pins rule ids, severities and the measurement rules the FR rows leave open, and corrects FR-C2's acceptance (Ridgeline's targets meet the floor). Records the root-render gap for slice D.
Phase 30 slice C. Six rule ids register in the ruleset table and run when on: mobile-touch-target
(error), mobile-safe-area and mobile-type-floor (warnings), mobile-handbuilt-target and
mobile-one-column (infos), and mobile-artboard on every type. Findings carry a rule field. Stress
records the canvas type; canvas_set_type names variants left on the old type.
A spacer directly under the fixed-height root shrinks to nothing on a screen taller than its artboard; one inside the content frame keeps its height.
Stretching rows count as full width and full-height rows don't; positioned images are background;
the safe-area walks cache per tree instead of copying ancestors. Docs name detailed mode, the
padding formula and the fixes. Tests gain the mutation round's boundary fixtures.
A stretching row takes its host's known width, not the artboard's, and a positioned image is
full-bleed only when its containing block spans the artboard. Both first fixes passed small targets.
@vicmaster
vicmaster merged commit c2c71d0 into master Oct 31, 2026
3 checks passed
@vicmaster
vicmaster deleted the phase-30-slice-c branch October 31, 2026 23:38
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