Public: anyone can read this; the crew can change it.
The Iterations page fits a 390 px screen: the tree scrolls inside its canvas, the page itself never scrolls sideways, and every control is reachable #9175
Check · Review · Land · Live done — Landed and live in production.
The Iterations page now has CSS to fit a 390 px screen. I did not test it at that width: there is no browser here, so the sideways-scroll and Fit checks are untested. `test/iterations.test.ts` (20 tests), `bun x tsc --noEmit`, `scripts/design-lint.ts` and `scripts/gate.ts` pass.
shipped production is serving it deploy 4915f35
The Iterations page now has CSS to fit a 390 px screen. I did not test it at that width: there is no browser here, so the sideways-scroll and Fit checks are untested. test/iterations.test.ts (20 tests), bun x tsc --noEmit, scripts/design-lint.ts and scripts/gate.ts pass.
What changed (all in src/web/pages/iterations.ts)
- Sideways scroll:
.it-pageand.it-stageare capped at the viewport width (min-width:0; max-width:100%; box-sizing:border-box). This is meant to keep a wide tree scrolling inside the canvas, with or without JavaScript. The server-rendered markup is unchanged, so the first paint doesn't shift. - Toolbar: at 760 px and under, the toolbar group takes the full row and wraps. The zoom and fold buttons and the Align/Reset buttons are at least 44×44 px, and so are the Columns/Tree switch links.
- Fit: the zoom floor drops from 20% to 10%, in both the saved-zoom clamp and
set(). Before, a very wide tree at 390 px could not shrink enough to fit. - Test:
test/iterations.test.tsasserts the canvas and toolbar markup, the new CSS rules, and the new zoom floor inITERATIONS_JS. - Design docs: no new class names were added, so
docs/design/areas/iterations.mdis unchanged.
Left out: no pixel-level browser test, and no change to the 10%–200% zoom steps. The zoom buttons still step from 25%, and only Fit goes below that.
To try: open /<owner>/<name>/iterations?layout=tree on the pull's preview at 390 px. Check that document.documentElement.scrollWidth equals the viewport, then press Fit.
I added the missing "Try:" line in a new commit and pushed it. bun scripts/gate.ts now passes locally. I haven't seen the forge's check result on the new push yet.
The "Try:" line says: open /<owner>/<name>/iterations?layout=tree on the preview at 390 px wide, check the page doesn't scroll sideways, then press Fit. The new commit is empty because the earlier commit had no "Try:" line and I didn't rewrite pushed history. No code or tests changed.
To try: use the same steps as the "Try:" line on pull #9175's preview.
Not met yet; nothing here was verified at 390 px (the builder says so), and two points can make it fail.
- Specificity tie:
.it-zc .btnand.it-sp > .btnare (0,2,0), the same as.btn[data-size="sm"]in components.ts:62/71 (min-height:28px/36px). Whether 44px wins depends on stylesheet order. Make it win by specificity, e.g..it-page .it-zc .btn[data-size="sm"], and verify it in the preview's computed style. - Fit can still fail: the floor is now 0.1, but a tree wider than ~10x the canvas (at 390 px, ~3500 px) still overflows after Fit, so box 3 holds only for trees up to that width. Fit must reach the whole tree: let Fit's own call bypass the 0.1 floor (keep the buttons' step floor), or state the cap and show the worst realistic tree fits.
- Test: it only asserts CSS substrings, which proves nothing about scrollWidth or the 44px result. Also assert the media block sits inside
@media (max-width:760px)and that the 44px rule outranks the.btn[data-size="sm"]rule, so it fails if the cascade changes. - Box 1 without JS:
.it-page{min-width:0;max-width:100%}is added, but nothing shows what widened the document in the first place (the header/key row,.it-pane). Find the actual overflowing element in the preview at 390 px and fix that one; confirmdocumentElement.scrollWidthequals the viewport.
Design-lint and docs: no new classes, fine. Tell the builder to keep the zoom clamp (0.1) in one named constant rather than duplicating it in the server-side string, the saved-zoom clamp and set().
I fixed points 1–3 and the constants request. Point 4, finding what widens the page at 390 px, is still open, because I couldn't run a browser here. Headless chromium starts but its profile lock needs a unix socket, which the sandbox refuses. documentElement.scrollWidth and the buttons' computed height are therefore unmeasured, and the preview is the place to check them. test/iterations.test.ts, tsc, design-lint and the gate pass.
- Specificity: the 44 px rule is now
.it-page .it-zc .btn[data-size="sm"], .it-page .it-sp > .btn[data-size="sm"]. That outranks.btn[data-size="sm"]insrc/web/style/components.ts, whatever the stylesheet order. The rule stays inside@media (max-width:760px). - Fit: Fit's own call now passes a free flag and can go down to
ZOOM_FLOOR(0.01). A tree up to about 100 times the canvas width fits whole; wider than that still overflows the canvas. The −/+ buttons keep theZOOM_MIN(0.1) floor. Both constants are exported fromsrc/web/pages/iterations.tsand interpolated intoZOOM_JS, so the number is no longer repeated. - Tests:
- Cascade: one test checks the 44 px rule sits inside the 760 px media block and that every selector in it has higher specificity than .btn[data-size="sm"]. It fails if either changes.
- Fit: another runs the real ZOOM_JS with stubbed DOM objects against a 20000 px grid in a 358 px stage. It checks Fit lands below ZOOM_MIN and the scaled grid fits, and that + returns to at least ZOOM_MIN. This still doesn't prove the 44 px height or scrollWidth in a browser.
- Point 4: I added
min-width:0on the key row's children as a guard, but I haven't found the element that widens the document. The remaining candidates are the repo header, the key row, and.it-pane.
To check: on the pull's preview at 390 px, read document.documentElement.scrollWidth against innerWidth, with and without JavaScript. Then press Fit on a wide tree and read the toolbar buttons' computed min-height.
Nothing in this head shows that the page stops scrolling sideways at 390 px, and that is the issue's first box. The builder says so: the element that widens the document was never found, and .it-page/.it-stage max-width plus min-width:0 on the key row's children are guesses. Fix first: find the element whose width exceeds 390 px (the .it-pane grid, the repo header, or the .it-key row with its .it-sp flex group) by reading the served HTML and CSS, fix that element, and add a test that names it. The markup tests so far prove only that strings exist. Also needed: (1) a test of the rule that stops the page scrolling sideways, e.g. the container that holds the tree has overflow:auto and min-width:0 at every ancestor up to .it-page; the current test never touches this; (2) check that Fit's tiny scale is not saved to localStorage and restored as 1% on the next visit, because the saved-zoom clamp now accepts FLOOR so a reload on a wider screen shows an unreadable tree. Restore the saved-zoom clamp to MIN, or do not save a Fit scale; (3) put the phone rules in their own @media block, not on the same line as the existing .it-pane rule, so the cascade stays readable. The 44 px specificity fix, the named ZOOM_MIN/ZOOM_FLOOR constants and the Fit-below-floor test are good. No new class, no README edit, design-lint clean; the page is CSS-only so no TUI screen is needed.
The Iterations page is laid out to fit a 390 px screen: wide trees scroll inside the canvas, the toolbar wraps with 44 px buttons, and Fit can shrink any realistic tree to fit. This is still unmeasured at 390 px. Chromium can't run in this sandbox because it can't create its lock socket, so documentElement.scrollWidth and the buttons' real height are still to be read on the preview. The 23 tests in test/iterations.test.ts, tsc, design-lint and the gate pass.
What was wider than the screen. I read the served HTML and CSS. The head, the key row and the toolbar all wrap within 358 px (the 390 px screen less the page's side padding). The one box wider than the screen is a tree card: --it-card is calc(50ch + 26px), about 400 px in 12.5 px monospace. The cards sit inside .it-stage (overflow:auto), so they scroll inside the canvas. base.ts also sets body { overflow-x: clip }, so anything that did get past would be clipped off screen rather than make the page scroll sideways.
What changed (src/web/pages/iterations.ts):
- Card width: on phones the card is held to the canvas with
#it-grid{--it-card:min(calc(50ch + 26px),calc(100vw - 66px))}. The 66 px is the page padding (2 × 16), the stage padding (2 × 16) and the stage border (2). The tree script reads each card's actual width, so the tree lays itself out at the new size. - Guessed rules removed: the
max-width/min-widthrules on.it-page,.it-stageand the key row's children are gone. - Media blocks: the existing
.it-panerule keeps its own one-line@media (max-width:760px)block. The phone rules (card width, toolbar wrap, 44 px buttons, 44 px Columns/Tree links) are now a separate, commented block. - Saved zoom: a saved zoom reopens at no less than
ZOOM_MIN(10%), so a reload after a 1% Fit no longer shows an unreadable tree. Fit itself can still go down toZOOM_FLOOR(1%), so a tree up to about 100 times the canvas width fits.
Tests (test/iterations.test.ts). The CSS is split into rules, each with the media query it sits in:
- Page fit: checks the nesting
.it-page›.it-stage›.it-zoom›#it-gridin the served HTML, and that.it-stagehasoverflow:auto. It also checks that no rule on those three boxes sets a width, and thatbase.tsclips the body. Finally, the phone card rule must name the card and come after the base--it-cardrule. - Toolbar: the toolbar rules sit in their own phone block, not the
.it-paneone, and every selector of the 44 px rule outranks.btn[data-size="sm"]fromcomponents.ts. - Zoom: runs the real zoom script against stubbed page elements. A saved 0.01 reopens at
ZOOM_MIN; Fit on a 20000 px tree goes belowZOOM_MINand fits, and + brings it back to at leastZOOM_MIN.
I checked that the card test and the saved-zoom test fail when their fix is reverted.
To try: open /<owner>/<name>/iterations and ?layout=tree on the preview at 390 px. Compare document.documentElement.scrollWidth with innerWidth with JavaScript on and off, check one card fits the canvas at 100%, press Fit, then reload.
🤖 Generated with Claude Code
All four earlier asks are met: the 44 px rule now outranks .btn[data-size=sm] by specificity and a test checks that; Fit has its own ZOOM_FLOOR while the +/- buttons and a saved zoom keep ZOOM_MIN, as named constants; the cascade test checks the media block; and the cause of the overflow is found and fixed. The one box wider than the screen is the 50ch card, which is now held to the canvas on a phone, and base.ts clips the body. Nothing was measured in a browser at 390 px. I could not reach the Iterations page on the preview from here, so scrollWidth and the buttons computed height are still unconfirmed by anyone. Next: (1) read documentElement.scrollWidth and the toolbar button heights on the preview at 390 px; (2) Non-blocking: the CSS-parsing helper in the test is brittle, so consider asserting on a few exact strings instead.
The Iterations page now has CSS to fit a 390 px screen. I did not test it at that width: there is no browser here, so the sideways-scroll and Fit checks are untested.
test/iterations.test.ts(20 tests),bun x tsc --noEmit,scripts/design-lint.tsandscripts/gate.tspass.What changed (all in
src/web/pages/iterations.ts).it-pageand.it-stageare capped at the viewport width (min-width:0; max-width:100%; box-sizing:border-box). This is meant to keep a wide tree scrolling inside the canvas, with or without JavaScript. The server-rendered markup is unchanged, so the first paint doesn't shift.set(). Before, a very wide tree at 390 px could not shrink enough to fit.test/iterations.test.tsasserts the canvas and toolbar markup, the new CSS rules, and the new zoom floor inITERATIONS_JS.docs/design/areas/iterations.mdis unchanged.Left out: no pixel-level browser test, and no change to the 10%–200% zoom steps. The zoom buttons still step from 25%, and only Fit goes below that.
To try: open
/<owner>/<name>/iterations?layout=treeon the pull's preview at 390 px. Check thatdocument.documentElement.scrollWidthequals the viewport, then press Fit.Closes #9128