From eb522b311d477454fc23578be1b2f5dcb920879d Mon Sep 17 00:00:00 2001 From: gHashTag Date: Wed, 9 Sep 2026 20:49:07 +0000 Subject: [PATCH 1/2] specs/ui: the site viewport contract as a .t27 spec (tiers, panes, 44px targets, six-size QA matrix, rail capacity) - specs/ui/viewport.t27 (module ui_viewport, KIND viewport): PHONE_MAX 600, TABLET_MAX 1024, DESKTOP_MAX 1600, HEADER_CHROME_MAX 1100, one pane on a phone, 44 px touch targets, the six-size matrix with tiers, the Queen rail capacity read from Queen.css (described, not changed). - Six test blocks / 49 asserts inside the spec; the site generator evaluates them because typecheck.ok stays true for a false assert. - specs/ui/README.md, OWNERS row ui/ -> T-Queen, docs/now entry. Closes #3557 --- ...he-site-viewport-contract-as-a-t27-spec.md | 11 ++ specs/OWNERS.md | 1 + specs/ui/README.md | 71 +++++++ specs/ui/viewport.t27 | 186 ++++++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md create mode 100644 specs/ui/README.md create mode 100644 specs/ui/viewport.t27 diff --git a/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md b/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md new file mode 100644 index 000000000..74c18690f --- /dev/null +++ b/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md @@ -0,0 +1,11 @@ +# NOW -- The site viewport contract as a .t27 spec (2026-09-10) + +## The site viewport contract as a .t27 spec (Closes #3557) + +- `specs/ui/viewport.t27` (module `ui_viewport`, `KIND = "viewport"`) is the one source for how the explorers of t27.ai (`#/specs`, `#/skills`, `#/crons`, `#/agents`, `#/tools`, `#/functions`) lay themselves out: four tiers by CSS viewport width with inclusive bounds `PHONE_MAX = 600`, `TABLET_MAX = 1024`, `DESKTOP_MAX = 1600`; `HEADER_CHROME_MAX = 1100` (the threshold the explorers' TSX already used for trimming the header, kept inside the desktop tier so 1025-1099 renders as before; not a tier); `PHONE_PANES = 1` (list or card, never both; a `?=` deep link opens the card), `TABLET_PANES = 2`, `DESKTOP_PANES = 2`; `ONE_SCROLLER_PER_PANE = true`, `DOCUMENT_SCROLLS = false`; `TOUCH_TARGET_MIN_PX = 44`, `BACK_CONTROL_MIN_PX = 44`; the six-size QA matrix `390x844 600x900 768x1024 1024x768 1280x800 1920x1080` as labels and as integers, with the tier of each entry; `DESKTOP_REFERENCE_WIDTH = 1280`. Schema and rules in `specs/ui/README.md`. +- Evidence is in the file: the `SOURCE` comment records the live measurement (t27.ai, 2026-09-09, cloud Chromium, DPR 1: at 390x844 `#/tools?tool=mcp%2Fgitbutler` shows only the 62-item list, the card is not in the viewport, document scrollHeight 924 > 844; reproduced on a build of trinity main c881e7f where the hash written after mount is ignored), and the Queen rail numbers (`RAIL_*`) carry the `Queen.css` line each was read from. The rail and HUD are described, not changed: the first consumer is the explorers only (P0), and a QA contract may report the rail as warnings. +- Six `test` blocks, 49 asserts, inside the spec: monotone bounds, the 44 px minimum, every matrix entry in the tier the bounds say, the six quoted sizes, one pane on a phone, and the rail capacity arithmetic per matrix height. Measured under the vendored compiler wasm on the site: typecheck ok, nothing discarded, 49/49 asserts hold. `typecheck.ok` is lenient (true for `assert 1 > 2`), so the site's generator (`scripts/viewport-from-spec.mjs`, gHashTag/trinity) evaluates the asserts from the AST and refuses the spec otherwise; its own tests cover a false assert, a `;` comment inside a block, a matrix tier that disagrees with the bounds, a non-ASCII byte, stale generated files and determinism. +- Inside a `test` block only `//` comments are safe: the parser reads a `;` line inside a block as a statement (recorded in the README). +- English only, ASCII only, no user-visible copy in the spec (so no i18n SCOPE row); no absolute developer home path; the checkout is referred to as `T27_ROOT` or `git rev-parse --show-toplevel`. +- `specs/OWNERS.md` gains the row `ui/` -> **T-Queen**. +- Not claimed: the bootstrap compiler on `master` was not run against this file; the Queen rail capacity is a model (a CSS budget plus one consistent observation at 1024x768), not a measurement of every height; `gate-topology` and `untrusted-input` still fail on `master` for already-merged PRs and are not addressed here. diff --git a/specs/OWNERS.md b/specs/OWNERS.md index cbc356cf5..387257960 100644 --- a/specs/OWNERS.md +++ b/specs/OWNERS.md @@ -24,6 +24,7 @@ | `functions/` | **T-Queen** | The 28 Inngest functions of `999-multibots-telegraf` as `.t27` specs (`functions/.t27`, `KIND = "function"`): canonical and legacy ids and events, trigger, steps, retries, failure handling, side effects, guard and the 2026-09-09 safe-probe result; the site joins them to a vendored copy of the functions manifest | | `tools/` | **T-Queen** | The `tri` CLI commands and the MCP servers as `.t27` specs (`tools/tri/.t27`, `tools/mcp/.t27`, `KIND = "tool"`): what a command or server is for, its actions or tools, its source and witness (`source-parse` / `help-output`), and the agent letters a source evidently binds; the three older files at the top of `tools/` are ordinary corpus specs | | `docs/` | **Z-Zeta** (with **T-Queen** for `chapters/queen.t27`) | The system documentation of `t27.ai/#/docs` as `.t27` specs (`docs/system.t27`, `KIND = "docs"`; `docs/chapters/.t27`, `KIND = "docs-chapter"`): chapter ids, sections, sources, the figure and table the site generates; English prose in `docs/system/.md`, Russian via `i18n/docs-ru.t27` | +| `ui/` | **T-Queen** | The site's viewport contract as a `.t27` spec (`ui/viewport.t27`, `KIND = "viewport"`): tiers by CSS viewport width, panes per tier, the 44 px touch minimum, the six-size QA matrix and the Queen rail capacity read from `Queen.css`; the site (gHashTag/trinity) generates `viewport.generated.ts` / `.css` from a vendored copy and evaluates the spec's own test blocks | Each subtree with substantial churn should keep a local **`OWNERS.md`** (see below). diff --git a/specs/ui/README.md b/specs/ui/README.md new file mode 100644 index 000000000..dc503a88b --- /dev/null +++ b/specs/ui/README.md @@ -0,0 +1,71 @@ +# specs/ui -- the site's viewport contract as a declared spec + +> **Where this lives.** `specs/ui/` in `gHashTag/t27` is the canonical home of the +> viewport contract of t27.ai -- edit it here. `gHashTag/trinity` keeps a byte-identical +> vendored copy under `apps/website/public/t27/files/specs/ui/`; its build reads that copy +> through the vendored compiler wasm (`t27_compiler.wasm`) and generates +> `src/lib/viewport.generated.ts` and `src/styles/viewport.generated.css` from it +> (`scripts/viewport-from-spec.mjs`). Nothing is parsed with a regular expression and no +> breakpoint is typed twice. The wasm's `typecheck.ok` is necessary, not sufficient (it +> stays true for `assert 1 > 2`), so the generator also evaluates every `test` block of the +> spec against the declared constants and refuses a spec whose own tests do not hold. The +> bootstrap compiler on `master` was not run against this file in the commit that added it. +> A checkout of this repository is referred to as `T27_ROOT` (an environment variable) or +> `git rev-parse --show-toplevel`; no file here names an absolute path. + +## Files + +| File | Module | Role | +|---|---|---| +| `viewport.t27` | `ui_viewport` | `KIND = "viewport"`: the four tiers by CSS viewport width, the compact-chrome threshold, the pane rule per tier, the touch-target minimum, the six-size QA matrix with the tier of each entry, the desktop reference width, and the Queen rail capacity table (described, not changed) | + +## Fields of `viewport.t27` + +| Field | Type | Meaning | +|---|---|---| +| `KIND`, `ID`, `NAME` | `str` | `"viewport"`, `"ui/viewport"`, a display name | +| `GENERATED` | `[2]str` | The two files the site derives from this spec (relative to `apps/website`) | +| `TIERS` | `[4]str` | `phone`, `tablet`, `desktop`, `wide` | +| `PHONE_MAX`, `TABLET_MAX`, `DESKTOP_MAX` | `u16` | Inclusive upper bounds in CSS px: `phone <= PHONE_MAX < tablet <= TABLET_MAX < desktop <= DESKTOP_MAX < wide` | +| `COARSE_POINTER_QUERY` | `str` | The media feature that says the pointer cannot hover | +| `HEADER_CHROME_MAX` | `u16` | Below this width the explorer header drops subtitle and note. Not a tier; it sits strictly inside the desktop tier so 1025-1099 renders as before | +| `PHONE_PANES`, `TABLET_PANES`, `DESKTOP_PANES` | `u8` | Panes visible at once: phone shows list **or** card (master-detail), the others both | +| `ONE_SCROLLER_PER_PANE`, `DOCUMENT_SCROLLS` | `bool` | One vertical scroller per visible pane; the document itself does not scroll | +| `TOUCH_TARGET_MIN_PX`, `BACK_CONTROL_MIN_PX` | `u8` | Smallest interactive box per side on phone and tablet; the phone card's way back to the list meets the same minimum | +| `VIEWPORTS`, `VIEWPORT_WIDTHS`, `VIEWPORT_HEIGHTS`, `VIEWPORT_TIERS` | `[6]str`, `[6]u16`, `[6]u16`, `[6]str` | The QA matrix every viewport contract iterates, written as labels and as integers so a test can index them, plus the tier of each entry | +| `DESKTOP_REFERENCE_WIDTH` | `u16` | The width at which the desktop layout must stay pixel-identical across a change | +| `RAIL_*` | `u8` / `u16` / `[6]u8` | The Queen rail's tile sizes, gaps and chrome heights read from `Queen.css` (file:line in the comments) and the capacity per matrix height they predict. Described, not changed: the first consumer is the explorers only | + +## Rules + +- **Tests inside the spec.** Every invariant the site relies on (monotone bounds, the 44 px + minimum, every matrix entry in the tier the bounds say, the rail capacity arithmetic) is an + `assert` in a `test` block of the same file. The site's generator evaluates them; a failing + assert is a failed build, not a warning. +- **Evidence in the file.** The `SOURCE` comment names the measurement each number rests on + (live t27.ai, the date, the viewport, what was observed) or the `Queen.css` line it was + read from. A number without a source does not belong here. +- **Comments.** Top-level comments use `;`. Inside a `test` block use `//` only: the parser + reads a `;` line inside a block as a statement, and the site's generator reports it. +- **English only, ASCII only** (LANG-EN, L3). UI copy is not in this spec; the site's + Russian travels through its i18n bundles, which this spec does not name because it carries + no user-visible text. +- **Do not edit the generated files by hand.** Change this spec, re-vendor it byte-identical, + re-run the generator; the generated files carry the spec's sha256 in their header. +- **Not a Queen spec.** The rail and HUD numbers are recorded so a QA contract can report + them; changing the rail is a separate round with its own approval. + +## How the site uses it + +``` +T27_ROOT/specs/ui/viewport.t27 + -> trinity apps/website/public/t27/files/specs/ui/viewport.t27 (byte-identical copy) + -> node scripts/viewport-from-spec.mjs (compiler wasm, asserts evaluated) + -> src/lib/viewport.generated.ts tiers, tierOf(width), tierQuery(tier), VIEWPORTS + -> src/styles/viewport.generated.css :root custom properties (touch minimum, rail tiles) + -> src/lib/useViewport.ts { tier, width, height, coarsePointer } via matchMedia + resize + -> qa/explorer-viewport-contract.mjs the six explorers at the six sizes, headless Chrome +``` + +`npm run check:viewport` fails when the committed generated files are not the ones the +vendored spec produces; `npm run check:explorer-viewport` runs the browser contract. diff --git a/specs/ui/viewport.t27 b/specs/ui/viewport.t27 new file mode 100644 index 000000000..f94f6b852 --- /dev/null +++ b/specs/ui/viewport.t27 @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/ui/viewport.t27 -- the one viewport contract of the t27.ai site (breakpoints, touch targets, QA matrix) +; Source of truth for how the site's explorers (#/specs, #/skills, #/crons, #/agents, #/tools, +; #/functions) lay themselves out at a given viewport. The site (gHashTag/trinity, apps/website) +; vendors this file byte-identical under public/t27/files/specs/ui/ and generates +; src/lib/viewport.generated.ts and src/styles/viewport.generated.css from it with the real +; compiler (scripts/viewport-from-spec.mjs); nothing below is parsed with a regex and no +; breakpoint is typed twice. The generator also evaluates the test blocks below against the +; declared constants, because the analyzer's typecheck.ok is necessary, not sufficient +; (it stays true for `assert 1 > 2`). ASCII only (L3), English only (LANG-EN). + +; SOURCE (measured on live t27.ai, bundle index-B4FDqqXk.js, 2026-09-09 ~18:40 UTC, cloud +; Chromium, DPR 1; screenshots kept with the round-3 report): +; 390x844 #/tools?tool=mcp%2Fgitbutler only the 62-item list renders, the card +; tool_mcp_gitbutler is not in the viewport, document scrollHeight 924 > 844. +; Reproduced on a build of trinity main c881e7f: the hash written after mount is +; ignored (useHashParams reads the hash once), so the list pane stays and the +; document scrolls; a fresh navigation to the same deep link does open the card. +; 390x844 #/queen rail is a horizontal strip, 12 tiles of 44x74, about 6 visible; +; 7 interactive targets below 40 px; .queen27-hud-command.is-compact +; scrollWidth 612 > clientWidth 344. +; 1024x768 #/queen the rail of 12 does not fit: PROJECT hidden under COLLAPSE. +; 1920x1080 #/docs text column left-aligned, nested scrollers (Docs 2, Tools 3). +; Queen.css carries 36 media queries at 640/760/900/901/1100; the explorers use 760/1100 +; in TSX; qa/queen-viewport-contract.mjs checks 1280 only. There was no single source. +; The Queen rail and HUD are described here (RAIL_*) but are not changed by this spec's +; first consumer (P0 = explorers); a QA contract may report them as warnings only. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module ui_viewport; + +pub const KIND : str = "viewport"; +pub const ID : str = "ui/viewport"; +pub const NAME : str = "Site viewport contract"; +; The generated artifacts the site derives from this file (relative to apps/website). +pub const GENERATED : [2]str = ["src/lib/viewport.generated.ts", "src/styles/viewport.generated.css"]; + +; --- Tiers ----------------------------------------------------------------------------- +; Four tiers by CSS viewport width (window.innerWidth), inclusive upper bounds: +; phone : width <= PHONE_MAX +; tablet : PHONE_MAX < width <= TABLET_MAX +; desktop : TABLET_MAX < width <= DESKTOP_MAX +; wide : width > DESKTOP_MAX +pub const TIERS : [4]str = ["phone", "tablet", "desktop", "wide"]; +pub const PHONE_MAX : u16 = 600; +pub const TABLET_MAX : u16 = 1024; +pub const DESKTOP_MAX : u16 = 1600; +; A pointer that cannot hover (touch) is read from the pointer:coarse media feature. +pub const COARSE_POINTER_QUERY : str = "(pointer: coarse)"; +; Below this width the explorer header drops its subtitle and note (compact chrome). Not a +; tier: it is the 1100 the explorers' TSX already used, kept so the desktop tier renders the +; same at 1025-1099 as before this spec; it sits strictly inside the desktop tier. +pub const HEADER_CHROME_MAX : u16 = 1100; + +; --- Panes ----------------------------------------------------------------------------- +; phone: master-detail, list pane OR card pane, never both; a `?=` deep link opens the +; card pane directly and the card carries a control back to the list. +; tablet: list and card side by side, the list's filters folded into one collapsible row. +; desktop and wide: unchanged three-pane layout (header, ladder, list + card). +pub const PHONE_PANES : u8 = 1; +pub const TABLET_PANES : u8 = 2; +pub const DESKTOP_PANES : u8 = 2; +; Exactly one vertical scroller per visible pane; the document itself never scrolls. +pub const ONE_SCROLLER_PER_PANE : bool = true; +pub const DOCUMENT_SCROLLS : bool = false; + +; --- Targets --------------------------------------------------------------------------- +; Smallest interactive box (CSS px, each side) on phone and tablet; WCAG 2.5.5 uses 44. +pub const TOUCH_TARGET_MIN_PX : u8 = 44; +; The pane-switch control on a phone card ("<- list") must meet the same minimum. +pub const BACK_CONTROL_MIN_PX : u8 = 44; + +; --- QA matrix ------------------------------------------------------------------------- +; The six sizes every viewport contract iterates; widths and heights are the same six +; pairs written twice so an integer test can index them. "WxH" in CSS px. +pub const VIEWPORTS : [6]str = ["390x844", "600x900", "768x1024", "1024x768", "1280x800", "1920x1080"]; +pub const VIEWPORT_WIDTHS : [6]u16 = [390, 600, 768, 1024, 1280, 1920]; +pub const VIEWPORT_HEIGHTS : [6]u16 = [844, 900, 1024, 768, 800, 1080]; +; The tier each matrix entry falls into, by the bounds above (checked by a test below). +pub const VIEWPORT_TIERS : [6]str = ["phone", "phone", "tablet", "tablet", "desktop", "wide"]; +; The width the desktop layout must stay pixel-identical at across a change (before/after). +pub const DESKTOP_REFERENCE_WIDTH : u16 = 1280; + +; --- Queen rail capacity (described, not changed here) --------------------------------- +; Numbers read from apps/website/src/pages/Queen.css of trinity main c881e7f and from the +; measurement above; each carries where it comes from. +; RAIL_TILES 12 src/components/queenHud.ts:35 (HUD_KEYS, twelve views) +; RAIL_TILE_W_COMPACT 44 Queen.css:5311-5312 (.is-compact .queen27-hud-cmd flex 0 0 44px, min-width 44px) +; RAIL_TILE_GAP_COMPACT 4 Queen.css:5302 (.is-compact gap: 4px) +; RAIL_TILE_H_COMPACT 74 measured at 390x844 (the CSS fixes no height; padding at Queen.css:5315) +; RAIL_CLIENT_W_PHONE 344 measured clientWidth of the compact rail at 390x844 +; RAIL_TILE_H_COLUMN 48 Queen.css:3997-3998 (the rail's own budget comment: "six tile rows at ~48 px") +; RAIL_TILE_GAP_COLUMN 6 Queen.css:3868 (gap: 6px) +; RAIL_CHROME_H 148 Queen.css:4277 (hud-top height 64px) + Queen.css:4397 (hud-bottom height 84px) +; Capacity by viewport height in the one-column rail: (height - RAIL_CHROME_H) / RAIL_ROW_H_COLUMN, +; integer division. It predicts 11 tiles at 768 px, fewer than the 12 the rail carries, which +; is the 1024x768 observation above. Tag: model (a CSS budget plus one consistent observation), +; not a measurement of every height. +pub const RAIL_TILES : u8 = 12; +pub const RAIL_TILE_W_COMPACT : u8 = 44; +pub const RAIL_TILE_GAP_COMPACT : u8 = 4; +pub const RAIL_TILE_H_COMPACT : u8 = 74; +pub const RAIL_CLIENT_W_PHONE : u16 = 344; +pub const RAIL_TILE_H_COLUMN : u8 = 48; +pub const RAIL_TILE_GAP_COLUMN : u8 = 6; +pub const RAIL_CHROME_H : u16 = 148; +; One rail row: tile plus gap, for each mode (checked against the parts by a test). +pub const RAIL_ROW_H_COLUMN : u8 = 54; +pub const RAIL_ROW_W_COMPACT : u8 = 48; +; Tiles that must be visible without scrolling the rail, on every size in the matrix. +pub const RAIL_MIN_VISIBLE : u8 = 6; +; One entry per VIEWPORT_HEIGHTS, from the formula above. +pub const RAIL_CAPACITY_BY_HEIGHT : [6]u8 = [12, 13, 16, 11, 12, 17]; + +; --- Tests (typechecked by the compiler, evaluated by the site generator) --------------- + +test breakpoints_are_monotone { + assert PHONE_MAX < TABLET_MAX; + assert TABLET_MAX < DESKTOP_MAX; + assert HEADER_CHROME_MAX > TABLET_MAX; + assert HEADER_CHROME_MAX <= DESKTOP_MAX; +} + +test touch_target_is_at_least_44 { + assert TOUCH_TARGET_MIN_PX >= 44; + assert BACK_CONTROL_MIN_PX >= TOUCH_TARGET_MIN_PX; +} + +test matrix_covers_every_tier { + assert VIEWPORT_WIDTHS[0] <= PHONE_MAX; + assert VIEWPORT_WIDTHS[1] <= PHONE_MAX; + assert VIEWPORT_WIDTHS[2] > PHONE_MAX; + assert VIEWPORT_WIDTHS[2] <= TABLET_MAX; + assert VIEWPORT_WIDTHS[3] <= TABLET_MAX; + assert VIEWPORT_WIDTHS[4] > TABLET_MAX; + assert VIEWPORT_WIDTHS[4] <= DESKTOP_MAX; + assert VIEWPORT_WIDTHS[5] > DESKTOP_MAX; + assert VIEWPORT_TIERS[0] == "phone"; + assert VIEWPORT_TIERS[1] == "phone"; + assert VIEWPORT_TIERS[2] == "tablet"; + assert VIEWPORT_TIERS[3] == "tablet"; + assert VIEWPORT_TIERS[4] == "desktop"; + assert VIEWPORT_TIERS[5] == "wide"; + assert DESKTOP_REFERENCE_WIDTH == VIEWPORT_WIDTHS[4]; +} + +test matrix_is_the_six_quoted_sizes { + assert VIEWPORTS[0] == "390x844"; + assert VIEWPORTS[1] == "600x900"; + assert VIEWPORTS[2] == "768x1024"; + assert VIEWPORTS[3] == "1024x768"; + assert VIEWPORTS[4] == "1280x800"; + assert VIEWPORTS[5] == "1920x1080"; +} + +test phone_shows_one_pane { + assert PHONE_PANES == 1; + assert TABLET_PANES == 2; + assert ONE_SCROLLER_PER_PANE == true; + assert DOCUMENT_SCROLLS == false; +} + +test rail_capacity_follows_the_css_budget { + assert RAIL_ROW_H_COLUMN == RAIL_TILE_H_COLUMN + RAIL_TILE_GAP_COLUMN; + assert RAIL_ROW_W_COMPACT == RAIL_TILE_W_COMPACT + RAIL_TILE_GAP_COMPACT; + // capacity * row fits the budget, one more row does not: integer division, per height + assert RAIL_CAPACITY_BY_HEIGHT[0] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[0] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[0] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[0] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[1] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[1] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[1] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[1] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[2] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[2] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[2] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[2] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[3] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[3] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[3] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[3] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[4] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[4] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[4] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[4] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[5] * RAIL_ROW_H_COLUMN <= VIEWPORT_HEIGHTS[5] - RAIL_CHROME_H; + assert RAIL_CAPACITY_BY_HEIGHT[5] * RAIL_ROW_H_COLUMN + RAIL_ROW_H_COLUMN > VIEWPORT_HEIGHTS[5] - RAIL_CHROME_H; + // the one-column rail cannot hold all twelve at 768 px: this is the 1024x768 observation + assert RAIL_CAPACITY_BY_HEIGHT[3] < RAIL_TILES; + // every size in the matrix still shows the minimum + assert RAIL_CAPACITY_BY_HEIGHT[3] >= RAIL_MIN_VISIBLE; + // the compact strip on a phone shows the minimum without scrolling, but not all twelve + assert RAIL_MIN_VISIBLE * RAIL_ROW_W_COMPACT <= RAIL_CLIENT_W_PHONE; + assert RAIL_TILES * RAIL_ROW_W_COMPACT > RAIL_CLIENT_W_PHONE; +} From b63c3df66bdbcd64f5210e13ad7bd7cdbd2119f4 Mon Sep 17 00:00:00 2001 From: gHashTag Date: Wed, 9 Sep 2026 21:02:04 +0000 Subject: [PATCH 2/2] tools: the test-blocks pin follows the corpus, 12456 -> 12462 (six test blocks of specs/ui/viewport.t27) spec-guards on #3558 reported the drift; the movement is the six test blocks this branch adds, written next to the pin. --check exits 0. Refs #3557 --- .../2026-09-10-the-site-viewport-contract-as-a-t27-spec.md | 1 + tools/published_figures.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md b/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md index 74c18690f..a257fd822 100644 --- a/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md +++ b/docs/now/2026-09-10-the-site-viewport-contract-as-a-t27-spec.md @@ -8,4 +8,5 @@ - Inside a `test` block only `//` comments are safe: the parser reads a `;` line inside a block as a statement (recorded in the README). - English only, ASCII only, no user-visible copy in the spec (so no i18n SCOPE row); no absolute developer home path; the checkout is referred to as `T27_ROOT` or `git rev-parse --show-toplevel`. - `specs/OWNERS.md` gains the row `ui/` -> **T-Queen**. +- `tools/published_figures.py`: the pinned `test blocks` population follows the corpus, 12456 -> 12462 (the six test blocks of this spec), with the movement written next to the pin as the file's protocol asks; the other nine figures are unchanged and `--check` exits 0. - Not claimed: the bootstrap compiler on `master` was not run against this file; the Queen rail capacity is a model (a CSS budget plus one consistent observation at 1024x768), not a measurement of every height; `gate-topology` and `untrusted-input` still fail on `master` for already-merged PRs and are not addressed here. diff --git a/tools/published_figures.py b/tools/published_figures.py index 6b2eb3eda..92b2f4ee5 100755 --- a/tools/published_figures.py +++ b/tools/published_figures.py @@ -64,9 +64,10 @@ # blessed away: #3482 deleted 188 duplicate test blocks whose bodies were # byte-identical to their twin. 12644 - 188 = 12456, which is what a # re-derivation gives -- the first thing this file caught, on its first run. + # #3557 added specs/ui/viewport.t27 with six test blocks: 12456 + 6 = 12462. ("test blocks", "blocks", - r"^\s*test\s+(?:\"[^\"]*\"|[A-Za-z_][\w\-]*)\s*\{?\s*$", 12456, - "#3479 pinned 12644; #3482 removed 188"), + r"^\s*test\s+(?:\"[^\"]*\"|[A-Za-z_][\w\-]*)\s*\{?\s*$", 12462, + "#3479 pinned 12644; #3482 removed 188; #3557 added 6"), ]