feat(website): viewport contract from specs/ui/viewport.t27 — phone master-detail, tablet two-pane, desktop unchanged (Round 3 P0) - #982
Merged
Conversation
…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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 (cmpclean), sha25691c9235486599fdd18a188c4e191dd6ff7a591a13b01625e766fc70403dd43a4.scripts/viewport-from-spec.mjs(write /--check/--json) reads the spec through the vendored compiler wasm and writessrc/lib/viewport.generated.ts(tiers, bounds,tierOf(width),tierQuery(tier),VIEWPORTS, touch minimum, rail constants) andsrc/styles/viewport.generated.css(:rootcustom properties). Both headers carryGENERATED+ the spec sha; hand-editing forbidden. The generator evaluates the spec's sixtestblocks (49 asserts) from the AST, becausetypecheck.okstays true forassert 1 > 2; it also refuses non-ASCII, a;comment inside a block, and aVIEWPORT_TIERSentry 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, wrongVIEWPORT_TIERSrefused, non-ASCII refused,tierOfbounds).scripts/agents-from-specs.mjsgainsarr-u8/u16/u32shapes (integer arrays for the matrix).Hook
src/lib/useViewport.ts→{ tier, width, height, coarsePointer }viamatchMediafor the generated tier queries +resize;useDocumentLockputsbody.explorer-shell(no document scroll) on phone/tablet only.src/hooks/useHashParams.ts—getnow reads the address bar live andchangescountshashchange/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-tieron 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 withtitle/aria-labelon 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 ownphone/narrowflags replaced byuseViewport(); phone bound moves 760 →PHONE_MAX600 (600–760 now renders the tablet two-pane);PipelineRibbongetsinteractive?: booleanand 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'] …), panemin-width:0; max-width:100%; overflow-x:hidden, note chip wraps (.spec-x-note), ladder nowrap.!importantis 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 overVIEWPORTSfrom the generated constants × 6 explorers (5 + specs), each opened by deep link. FAIL ondocument.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:ciend withnode 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)
npm run check:viewport91c9235486599fdd; tiers phone<=600 tablet<=1024 desktop<=1600; spec tests 6, asserts 49, all holdnode --test scripts/*.test.mjsnpm run check:explorer-viewport -- --no-buildpanes=1,scrollers=1(specs at 600x900: 0 — content fits), every control ≥ 44pxpanes=2,scrollers=2(specs: 1)before_rerunrun2 = run3); the originalbeforeskills 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 pxnpm run typecheck:ratchetnpx eslint .npm run check:agentsnpm run check:tools/check:docsnpm run check:skills-catalog/check:crons-catalog/check:spec-catalognpm run check:explorer-languagesnpm run check:queen-languagesnpm run check:clients-console/check:arianpm run check:rendernpm run audit:mobilenpx vite buildBefore/after screenshots (5 explorers × 390x844 / 600x900 / 768x1024 / 1024x768 / 1280x800, EN), the contract screenshots and
contract_output.txt,pixel_diff_1280.txtare attached to the round-3 report (workspaceshots_viewport/).Boundaries / honesty
RAIL_*,Queen.cssfile:line) so a later contract can check them; the existingqa/queen-viewport-contract.mjsis unchanged.HEADER_CHROME_MAX = 1100is kept from the existing TSX so 1025–1099 renders exactly as before; it is not a tier.--bless; MAX_DEAD_TRI, typecheck and eslint ratchets unchanged; nopublic/*/manifest.jsonhand-edits; no secrets; UI copy through existing i18n bundles (no new keys —check:explorer-languagesPASS).claude-review,pr-opened,Brain Health) are not addressed.