Skip to content

feat(website): viewport contract from specs/ui/viewport.t27 — phone master-detail, tablet two-pane, desktop unchanged (Round 3 P0) - #982

Merged
gHashTag merged 1 commit into
mainfrom
feat/viewport-p0
Sep 10, 2026
Merged

feat(website): viewport contract from specs/ui/viewport.t27 — phone master-detail, tablet two-pane, desktop unchanged (Round 3 P0)#982
gHashTag merged 1 commit into
mainfrom
feat/viewport-p0

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Round 3 (P0 responsive, owner-approved): the five explorers (Skills / Crons / Agents / Tools / Functions) plus the spec explorer get a spec-driven viewport contract — master-detail on a phone, two panes on a tablet, desktop pixel-identical at 1280. Queen rail / HUD are not changed (P1).

Spec: gHashTag/t27#3558 (specs/ui/viewport.t27, issue gHashTag/t27#3557). Do not merge without the owner's approval.

What

Spec → code (no regex, no hand-typed breakpoints)

  • public/t27/files/specs/ui/viewport.t27 — byte-identical vendored copy (cmp clean), sha256 91c9235486599fdd18a188c4e191dd6ff7a591a13b01625e766fc70403dd43a4.
  • scripts/viewport-from-spec.mjs (write / --check / --json) reads the spec through the vendored compiler wasm and writes src/lib/viewport.generated.ts (tiers, bounds, tierOf(width), tierQuery(tier), VIEWPORTS, touch minimum, rail constants) and src/styles/viewport.generated.css (:root custom properties). Both headers carry GENERATED + the spec sha; hand-editing forbidden. The generator evaluates the spec's six test blocks (49 asserts) from the AST, because typecheck.ok stays true for assert 1 > 2; it also refuses non-ASCII, a ; comment inside a block, and a VIEWPORT_TIERS entry that disagrees with the bounds.
  • scripts/viewport-from-spec.test.mjs — 8 tests (vendored copy builds, committed files fresh, deterministic output, false assert refused although typecheck ok, ; comment in a test block reported, wrong VIEWPORT_TIERS refused, non-ASCII refused, tierOf bounds).
  • scripts/agents-from-specs.mjs gains arr-u8/u16/u32 shapes (integer arrays for the matrix).

Hook

  • src/lib/useViewport.ts{ tier, width, height, coarsePointer } via matchMedia for the generated tier queries + resize; useDocumentLock puts body.explorer-shell (no document scroll) on phone/tablet only.
  • src/hooks/useHashParams.tsget now reads the address bar live and changes counts hashchange/popstate, so a deep link arriving after mount opens the card (this was the measured root cause of "list only" at 390x844 on main c881e7f).

Explorers

  • ExplorerLibrary, ExplorerHeader, SpecShare, SpecContribute, AgentSpecPanel, SpecGraphics: data-tier on the explorer root; phone = list or card (never both), ?<key>= opens the card, "← All …" back control ≥ 44px, header one line (Function explorer's live badge becomes glyph-only with title/aria-label on a phone), ladder one line (flex-wrap: nowrap !important, horizontal scroll); tablet = two panes, filters folded into one collapsible row; desktop/wide untouched.
  • SpecExplorer: its own phone/narrow flags replaced by useViewport(); phone bound moves 760 → PHONE_MAX 600 (600–760 now renders the tablet two-pane); PipelineRibbon gets interactive?: boolean and is static (role="img") on phone/tablet — 10 bar buttons shared 375px = 31px targets, and the tabs already carry the same actions.
  • AgentSpecPanel: panel border written as longhands (borderWidth/Style/Color). React's shorthand+longhand removal hazard produced an intermittent white frame — a pre-existing bug found while diffing 1280 (see witnesses).
  • ClientsConsole: adapter only (reads the tier), no layout change.
  • src/styles/explorer-viewport.css: compact-tier rules (.spec-x[data-tier='phone'|'tablet'] …), pane min-width:0; max-width:100%; overflow-x:hidden, note chip wraps (.spec-x-note), ladder nowrap. !important is used only where an inline style on the element wins otherwise (align-items, white-space, flex-wrap) — each has a comment.

QA contract

  • qa/explorer-viewport-contract.mjs — headless Chrome over VIEWPORTS from the generated constants × 6 explorers (5 + specs), each opened by deep link. FAIL on document.scrollWidth > clientWidth, deep-linked card not in viewport, interactive target < 44px on phone/tablet, > 1 live vertical scroller inside the explorer root on phone/tablet, a pane wider than the viewport / scrolling sideways on phone/tablet. Document scroll and scroller count on desktop/wide, and any Queen finding, are warnings. Screenshots per cell.
  • package.json: check:viewport, test:viewport-spec, check:explorer-viewport; prebuild / prebuild:ci end with node scripts/viewport-from-spec.mjs.
  • .github/workflows/website-checks.yml: step "Viewport spec" after "Catalog generators"; step "Explorer viewport contract" (--no-build, CHROME_PATH) after "Render check".

Witnesses (all measured on this branch, node 22, Chromium 1217, DPR 1)

Check Result
npm run check:viewport up to date; spec sha256 91c9235486599fdd; tiers phone<=600 tablet<=1024 desktop<=1600; spec tests 6, asserts 49, all hold
node --test scripts/*.test.mjs 108 / 108 pass (8 new)
npm run check:explorer-viewport -- --no-build PASS — 6 sizes × 6 explorers, 0 FAIL, 22 warnings, all on 1280x800 / 1920x1080: document scrolls 80px (880 > 800, 1160 > 1080) and 3 live vertical scrollers for 2 panes — desktop/wide are unchanged by design (P1)
phone rows (390x844, 600x900) panes=1, scrollers=1 (specs at 600x900: 0 — content fits), every control ≥ 44px
tablet rows (768x1024, 1024x768) panes=2, scrollers=2 (specs: 1)
desktop pixel diff at 1280x800, before (main c881e7f build) vs after agents 0 px, crons 0 px, functions 0 px, tools 0 px, skills 0 px against re-shots of main (before_rerun run2 = run3); the original before skills capture is a flaky frame of main (11147 px: the white-border bug above), and main re-shot 3× gave run1 flaky / run2 = run3 = after; the after build re-shot 4×, all 0 px
npm run typecheck:ratchet 179 errors across 26 files; baseline 179 across 26 — unchanged
npx eslint . 180 problems (172 errors, 8 warnings) — unchanged
npm run check:agents skills 26 / crons 33
npm run check:tools / check:docs 62 tools / 7 chapters pinned
npm run check:skills-catalog / check:crons-catalog / check:spec-catalog PASS 26 / PASS 33 / PASS 760
npm run check:explorer-languages PASS (7 pages, en/ru key sets identical)
npm run check:queen-languages 281 / 281
npm run check:clients-console / check:aria PASS / every ARIA reference resolves
npm run check:render no uncaught errors and no console.error across 4 panels
npm run audit:mobile PASS — no route scrolls sideways (33 routes, EN + RU)
npx vite build built in ~35s

Before/after screenshots (5 explorers × 390x844 / 600x900 / 768x1024 / 1024x768 / 1280x800, EN), the contract screenshots and contract_output.txt, pixel_diff_1280.txt are attached to the round-3 report (workspace shots_viewport/).

Boundaries / honesty

  • Queen rail and HUD: not touched; the spec describes their capacity (RAIL_*, Queen.css file:line) so a later contract can check them; the existing qa/queen-viewport-contract.mjs is unchanged.
  • Desktop and wide keep the 80px document scroll and the nested scrollers they had on main; the contract reports them as warnings, not failures (P1).
  • HEADER_CHROME_MAX = 1100 is kept from the existing TSX so 1025–1099 renders exactly as before; it is not a tier.
  • No --bless; MAX_DEAD_TRI, typecheck and eslint ratchets unchanged; no public/*/manifest.json hand-edits; no secrets; UI copy through existing i18n bundles (no new keys — check:explorer-languages PASS).
  • Known pre-existing reds (claude-review, pr-opened, Brain Health) are not addressed.
  • The screenshot harness has one nondeterministic frame (the fixed white-border bug on main; a 512 px chip-row frame once on the after build); every 0-px figure above is from matching re-shots, and the flaky frames are kept alongside, labelled.

…aster-detail, tablet two-pane, desktop unchanged (Round 3 P0)

- vendored specs/ui/viewport.t27 (byte-identical, sha 91c92354…) →
  scripts/viewport-from-spec.mjs → src/lib/viewport.generated.ts +
  src/styles/viewport.generated.css; the spec's 6 test blocks / 49 asserts
  are evaluated by the generator (typecheck.ok is not sufficient).
- useViewport() { tier, width, height, coarsePointer } via matchMedia +
  resize; useHashParams follows hashchange/popstate so a deep link opens
  the card after mount.
- Skills/Crons/Agents/Tools/Functions + Spec explorer: phone = list OR
  card, ?<key>= opens the card, back control >= 44px, header and ladder
  one line; tablet = two panes + collapsible filters; desktop/wide untouched
  (1280 pixel-identical, 0 px on all five).
- qa/explorer-viewport-contract.mjs over VIEWPORTS x 6 explorers; wired as
  check:viewport / test:viewport-spec / check:explorer-viewport and into
  website-checks.yml.
- Queen rail/HUD not changed (P1). No ratchet changes.
@gHashTag
gHashTag merged commit 3d9ae13 into main Sep 10, 2026
27 of 33 checks passed
@gHashTag
gHashTag deleted the feat/viewport-p0 branch September 10, 2026 00:49
github-actions Bot added a commit that referenced this pull request Sep 10, 2026
feat(website): viewport contract from specs/ui/viewport.t27 — phone master-detail, tablet two-pane, desktop unchanged (Round 3 P0) (#982)

- vendored specs/ui/viewport.t27 (byte-identical, sha 91c92354…) →
  scripts/viewport-from-spec.mjs → src/lib/viewport.generated.ts +
  src/styles/viewport.generated.css; the spec's 6 test blocks / 49 asserts
  are evaluated by the generator (typecheck.ok is not sufficient).
- useViewport() { tier, width, height, coarsePointer } via matchMedia +
  resize; useHashParams follows hashchange/popstate so a deep link opens
  the card after mount.
- Skills/Crons/Agents/Tools/Functions + Spec explorer: phone = list OR
  card, ?<key>= opens the card, back control >= 44px, header and ladder
  one line; tablet = two panes + collapsible filters; desktop/wide untouched
  (1280 pixel-identical, 0 px on all five).
- qa/explorer-viewport-contract.mjs over VIEWPORTS x 6 explorers; wired as
  check:viewport / test:viewport-spec / check:explorer-viewport and into
  website-checks.yml.
- Queen rail/HUD not changed (P1). No ratchet changes.

Co-authored-by: gHashTag <gHashTag@users.noreply.github.com>
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