Skip to content

Narrow the sidebar page to the width its measured rail asks for - #136

Merged
max-sixty merged 1 commit into
mainfrom
fix/ci-33327907308
Aug 30, 2026
Merged

Narrow the sidebar page to the width its measured rail asks for#136
max-sixty merged 1 commit into
mainfrom
fix/ci-33327907308

Conversation

@leaf-agent

@leaf-agent leaf-agent commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

test_a_left_sidebar_uses_the_margin_until_the_page_needs_it_back fails on every ci run since 6a50d50 "Keep sidebar from narrowing centered content", which added a second reading at a stated 1200px window: assert tighter["column"]["right"] - tighter["column"]["left"] == 720, reported as assert (950 - 264) == 720.

The page cannot hold a whole column at that width here. --strip-l is the sidebar's 264px token, but --strip-r is --rail, which margin-layout.js sets by measuring the widest margin row and never lets shrink. That row is a control — ✎ Edit and ✓ Accept ✗ Reject? — set in --sans, so its width is the width the host's UI font sets those words in. A 1200px window leaves 936px for the two residents and the column, so the assertion holds only where the claim comes in at 216px or narrower. Measured on the runner it is 250px: 264 + 720 + 250 = 1234 against 1200, and the column gives up the 34px difference. scripts/linux-suite.sh opens by naming this exact divergence — "what the two platforms disagree about is exactly what a browser test measures: how wide a system font sets a word, and whether a scrollbar takes a gutter out of the window". Both halves of that sentence are in this test: the font is this change, the gutter is #121.

The layout itself is doing what the commit describes: --lf-sidebar-edge has already clamped to 0, so both outer gutters are spent before the column narrows. Only the window the test states is wrong.

Solution

Derive that window from the claim instead of stating it. The test now narrows to exactly strip-l + 720 + strip-r — the width at which the sidebar, a whole column and the rail fill the page with no gutter left — and asserts the column is still 720 there, with both residents on screen. That is the invariant the block was written to prove ("the sidebar and rail use the outer gutters before taking width from the reading column"), and it holds on any font rather than on one machine's. The window adds back whatever the root scrollport holds outside 100cqi, so the arithmetic is the container's either way.

The probe that already read --strip-l becomes a small measure() so the same reading returns --strip-r and 100cqi beside it.

Testing

uv run pytest tests/test_render_pages.py, and the same file with --run-nightly: 42 passed each — but only with #121 also applied. pre-commit run --files tests/test_render_pages.py passes.

This alone does not turn the test green, and this branch's ci will not say so either way. test_render_pages.py is pytestmark = pytest.mark.nightly, so it runs on the push to main and not in a pull request's everyday suite — which is why #121's own test job is green with the stated 1200px window still in its tree.

The other half is #121. The root's scrollbar-gutter: stable makes documentElement.scrollWidth 15px narrower than its clientWidth, so assert roomy["sideways"] == 0 reads -15, and the centring assertion above it compares a column centred in a 1385px container against a 1400px window. Applying only #121's chrome-style.js hunk to this commit's parent leaves this test failing at assert (950 - 264) == 720; applying only this commit leaves it failing at the centring. Both together are what turns it green — verified in both directions on 26dbf42.

Measured, on 26dbf42

The reading at a 1400px window, unpatched main:

{'strip': 264, 'rail': 250, 'pageWidth': 1385, 'viewportWidth': 1400, 'sideways': -15,
 'column': {'left': 332.5, 'right': 1052.5}, 'marginCount': 7, 'marginRight': 1326.5}

The margin rows the claim is measured from, at that window: ✎ Edit at 151px and ✓ Accept ✗ Reject? at 199px, plus the row's left margin, ceilinged — 250px. Narrowed to 1200 they condense to 103px and the claim stays 250, which is the monotonicity the block's first sentence relies on and this change keeps relying on.

With #121's chrome-style.js hunk applied and this commit reverted, at 1200: --lf-sidebar-edge 0, --lf-room 686, column 686. With both applied, at the derived 1234px page: column 720, sidebar left 0, rail on screen, sideways 0.


Automated fix for failed run

@max-sixty
max-sixty merged commit e63866a into main Aug 30, 2026
3 checks passed
@max-sixty
max-sixty deleted the fix/ci-33327907308 branch August 30, 2026 20:55
max-sixty pushed a commit that referenced this pull request Aug 30, 2026
… after the chrome change (#138)

## Problem

[Run
33327310444](https://github.com/max-sixty/leaf/actions/runs/33327310444)
on d2dd825 failed with 34 tests red. Most of them are owned: #121 has
the `scrollbar-gutter` set, #132 the generic CONTROL contract, #134 the
catalog stills, #136 the sidebar width, #137 the versions menu, and #133
tracks the digest. What was left unowned is five tests that all fail on
one surface — what the panel's opening does to the page around it — and
each is a reading
[335bf9a](335bf9a)
"Delegate page chrome to browser standards" moved without a second
reading moving with it. All five pass at 335bf9a's parent; each was
reproduced at `-n0` before it was touched.

## Solution

Four readings, each corrected where it is taken. No assertion is
loosened and no test is changed.

**The margin's 900px floor asks the window again.** That number is half
of a pair: `@container lf-shell (min-width: 900px)` grants the rail and
the marker's strip, and `@media screen and (max-width: 899px)` stops
drawing the living margin at all. 335bf9a converted the grant to a
container query and left the hide a media query, so the two came apart
the moment the panel narrowed body: at 1200px with the panel open the
shell is 765px, the grant lapsed, and the markers stayed on screen with
no strip reserved for them. Measured on `SUGGESTION_PAGE`, `--strip-r`
resolved to `max(0px, 0px, 0px, 0px)` and every suggestion's control row
docked beside an empty rail. The floors below it stay container queries:
1152 and 1416 ask how much room is left beside the panel, which is the
question the runtime's old `data-lf-cramped` veto asked, and their
answer is supposed to move with the panel.

**Body's inline size is back under the layout observer.** 335bf9a
dropped `layoutSizes.observe(document.body)`, and with it the only thing
that re-ran `syncLayout` while the panel's eased margin was still
narrowing the page. `setPanel` runs one synchronous pass at the press,
reads the wide box, and returns; nothing reads the narrow one. So a
composer placed in a wide window's margin kept a place the narrowed page
no longer had — an absolute child past body's client box, which is
sideways-scrollable overflow, with the box standing on the panel that
displaced it. Restored with the content-box width filter the removed
code carried, so ordinary document-height growth still does not feed
back into a writer that reserves flow content.

**The panel layer is shown before anything is rendered into it.** The
panel is a `<dialog>` now, and a dialog nobody has shown is `display:
none` — but `setPanel` called `renderPanel()` first and
`syncPanelLayer()` (which calls `show()`) second. `renderPanel` is where
the anchor pass runs for the threads it draws, and a mark hangs on the
boxes its element shows through, so a widget an agent sent in a reply
resolved to an element measuring 0×0, took no mark, and never got one
afterwards: the thread stayed open in the panel pointing at nothing on
either side. Swapping the two lines is the whole fix; the focus target
is `threadsBox`, which the render fills rather than replaces.

**The margin preview shows without naming the marker as its source.**
`showPopover({source})` puts the card in the sequential focus order
directly after the control that invoked it — but this card is not
invoked. It opens on the marker merely taking focus, so a reader walking
the margin was made to Tab through a close button and two preview
actions they had not asked for, three stops between one suggestion's
controls and the next, with the card's contents changing under them as
they went. Its position comes from the `anchor-name` the code already
writes on the button, so the placement asks nothing of `source`.

## Testing

- Everyday suite green on this branch: `uv run pytest tests` — 723
passed, 6 skipped.
- The five: `test_suggestion_controls_stay_out_of_the_column`,
`test_suggestions_sharing_a_block_keep_source_and_keyboard_order`,
`test_a_settlement_in_a_reply_leaves_its_own_anchor_on_the_page`,
`test_a_mark_in_the_layer_promises_no_press_the_layer_will_not_take`,
`test_a_float_the_panel_displaces_hands_the_page_no_sideways_scroll` —
all pass with `--run-nightly`.
- Whole files: `test_render_margin.py`, `test_render_conversations.py`,
`test_render_widgets.py`, `test_render_outbox.py`,
`test_render_navigation.py` — the only failures left in them are ones
that also fail on `origin/main` unchanged, at identical values.
- `prettier --check` and `node --check` pass on both JavaScript files;
CSS is prettier-excluded here by `.pre-commit-config.yaml`.

<details><summary>How each was measured</summary>

The margin floor, on `SUGGESTION_PAGE` at 1200×900, before and after
pressing Threads:

```
BEFORE  bodyRight 1185  main 768  --strip-r "max(0px, 250px, 0px, 0px)"  --claim-rail 250px
AFTER   bodyRight  765  main 765  --strip-r "max(0px, 0px, 0px, 0px)"    --claim-rail ""
```

The same probe at 335bf9a's parent reports `--claim-rail: 250px` in both
postures, with `data-lf-cramped` standing — d226ee2 had already taken
that veto off the rail claim, so the 900 floor was the window's question
and nothing else.

The float, on `LONG_PAGE` at 1600×900 with the composer in the margin
and the panel opened:

```
body.scrollWidth 1487   body.clientWidth 1165   composer.left 1166.9   composer.right 1486.9
```

The mark, on `REPLY_TRAVEL_PAGE` with an `lf-options` frozen into a
reply: `#tv-decision` is present and `checkVisibility()` is true once
the panel is open, its box 355×32, and `.lf-mark-el` is on nothing — at
+6s as much as at +0. The same probe at 335bf9a's parent has the mark
the moment the panel opens.

The tab walk from a suggestion's Accept, before: accept → reject →
marker → **preview close → preview action → preview action** → next
row's accept.

</details>

## What this does not fix

Two of the four groups #127 recorded are still open, and both are a
product decision rather than a stale reading.

- **A covering panel is a modal dialog now**, so the document behind it
is inert:
`test_selection_hints_do_not_name_page_content_behind_a_covering_panel`
presses `s` at 700×900 and gets no hints because the page cannot hear
the key. Whether a covering panel should take the page out of reach
entirely, or cover it and leave it working, is the call —
`test_coarse_pointer_chrome_gives_its_compact_controls_humane_aims`,
`test_workspaces_replace_each_other_instead_of_stacking[500]` and
`test_coarse_pointer_resize_reach_stays_reachable_without_trapping_scroll`
fail as `<dialog … class="lf-ui lf-panel open"> intercepts pointer
events` on the same fact.
- **Escape belongs to whichever layer the browser is holding** — the
four `test_render_anchors.py` failures and
`test_esc_hands_the_page_back_after_it_has_closed_the_last_panel`. #137
is open on this.

Separately, [0d7e0d0](0d7e0d0)
"Open threads beside their source" landed while this was being verified
and brought two of its own new tests red on `main` —
`test_the_shipped_long_thread_opens_beside_its_source_in_the_right_margin`
and
`test_the_full_thread_posture_follows_the_page_container_and_left_claims`.
They fail identically with and without this branch's changes; noted here
so the next run does not re-derive them.

---
Automated fix for [failed
run](https://github.com/max-sixty/leaf/actions/runs/33327310444)

---------

Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
max-sixty pushed a commit that referenced this pull request Aug 30, 2026
)

## Problem

`test_render_gate.py::test_example_renders[corpus]` has been red on
every `ci` run since
[26dbf42](26dbf42)
"Add external link treatment", in both colour schemes, and it still
reproduces deterministically on `1ca4f40` in 11 seconds. #132 diagnosed
it as "a different finding, from 26dbf42's external-link treatment
rather than from 335bf9a" and left it; no PR or issue owns it.

```
assert ['[light] <lf-tab id=corpus-pr-walkthrough> puts "opens in a new tab" under .lf-ui, where no comment can reach it',
        '[dark] ...same...'] == []
```

The subject is not the example. `render_version` is the pass `version
check --render` runs on an agent-authored page, so any page with a
cross-origin link inside a widget now fails handover on a word the
reader is never shown.


[`unreachableWords`](https://github.com/max-sixty/leaf/blob/03b4a0b2776102041a2d48580046fbf03b5e3446/skills/leaf/scripts/leaf/render-checks/reachability.js#L208)
walks every text node under a widget's `.lf-ui` and reports the ones no
declaration claims. Its excuse list held `.lf-quiet` and nothing else.
26dbf42's `renderExternalLinks` writes a note beside each external link
— `class="lf-ui lf-external-note"`, `hidden`, textContent "opens in a
new tab" — as the `aria-describedby` target for the link, and the walk
found it: not `data-lf-said`, not inside the link's own `CONTROL` box (a
describedby target is referenced by id, so it sits beside the link
rather than in it), not `.lf-quiet`.

## Solution

Excuse `[hidden]` beside `.lf-quiet`, one line and its comment. The
check's own opening sentence is "Words the page shows that no user can
select", and a `[hidden]` subtree shows none.

The note cannot simply become a `.lf-quiet` word instead — which would
have been the tidier answer, since `quietWord` is the repo's one idiom
for "a word for a reader listening, silent on screen".
`renderExternalLinks` is enrolled on declared shadow roots as well as
the document, and the note is placed inside whatever root its link
stands in, while `.lf-quiet`'s clip is a rule in the document stylesheet
that no shadow tree adopts (only `MARK_RULES` is adopted, in
`shadow.js`). A `.lf-quiet` note in an `x-shadow` widget would render as
visible text next to the link. `hidden` is the silence available in
every root.

`[hidden]` is safe for this reading to trust rather than a hole for
hidden-but-revealable words: the browser removes the attribute when it
reveals `hidden="until-found"` content, so a word the reader finds is a
word this check sees again, at the moment it is on screen. The two
sibling gate readings that ask what a box shows already spell the same
pair —
[`widgets.js`](https://github.com/max-sixty/leaf/blob/03b4a0b2776102041a2d48580046fbf03b5e3446/skills/leaf/scripts/leaf/render-checks/widgets.js#L146)
and
[`standalone.js`](https://github.com/max-sixty/leaf/blob/03b4a0b2776102041a2d48580046fbf03b5e3446/skills/leaf/scripts/leaf/render-checks/standalone.js#L34).

No assertion is loosened and no runtime file is touched.
`skills/leaf/CLAUDE.md`'s row for this gate already reads "**visible**
page words remain in reachable flow".

## Testing

- `test_example_renders[corpus]` and
`test_example_renders[pr-walkthrough]` — pass with `--run-nightly`; the
first fails on `origin/main` unchanged at the two findings above.
- `test_render_reports_words_a_widget_puts_out_of_reach` gains the pair:
the same `.lf-ui` note inside `lf-option` reports nothing when `hidden`
and is still reported when shown. Bug-back: with the `[hidden]` excuse
reverted, the hidden half fails with exactly the corpus's finding.
- `uv run pytest tests` — 723 passed, 6 skipped, on this branch rebased
onto `c7b3706`.
- `uv run pytest tests/test_render_commands.py tests/test_render_gate.py
--run-nightly` — 77 passed, 4 failed, and all four are in the failed
run's own list and owned elsewhere:
`test_the_render_gate_measures_sideways_room_at_the_root_scrollport`,
`test_the_reader_draws_an_edge_to_the_width_they_want[comments]` and
`test_a_window_with_no_room_for_a_chosen_width_does_not_un_choose_it[comments]`
are #121's `-15`, and
`test_a_reader_arrives_at_what_they_left_rather_than_watching_it_arrive`
is the intermittent one #121 documents.
- `node --check`, pinned `prettier --check`, `ruff check` and `ruff
format --check` pass on the two touched files. `pre-commit` is not on
this sandbox's PATH, so its remaining hooks went unrun here.

The preview digest is unmoved: `capture_input_files()` hashes
`examples/`, `skills/leaf/assets`, the selected packages and three site
scripts, and `skills/leaf/scripts/` is in none of them.

## What this does not fix

The failed run had 32 red. Everything else in it sits with an open PR or
issue — #121, #132, #134, #136, #137, #138, #140, #141, and issues #133
and #139 — with two exceptions I did not reach:

-
`test_render_controls.py::test_every_ring_the_layer_draws_is_shown_whole_somewhere_in_the_corpus`
reports one unlit ring, `version-diff
(.lf-version-diff:is(:focus-visible, .lf-focus-visible))`. That control
is inside the versions menu #137 is rewriting, so it is likely
downstream of that branch rather than its own cause; I have not verified
it against #137's tree.
-
`test_render_navigation.py::test_a_widget_that_renames_its_role_keeps_the_press_offer_gave_it`
is the generic-press question #132 raised and deliberately left as a
product decision.

---
Automated fix for [failed
run](https://github.com/max-sixty/leaf/actions/runs/33330752582)

Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
max-sixty pushed a commit that referenced this pull request Aug 30, 2026
…p's own press (#140)

## Problem

Two leftovers from
[335bf9a](335bf9a)
"Delegate page chrome to browser standards", which moved control
activation off a page-wide `CONTROL` scope and into rows declared where
each control is. Both are on `ci`'s red list for [the run this was
dispatched
for](https://github.com/max-sixty/leaf/actions/runs/33328596524) or
reachable from the same change, and both still reproduce at `1ca4f40`.

**`lf-worktree`'s head names fewer keys than work it.** The commit gave
the head a row binding `PRESS` — Enter and Space — over a button wearing
`aria-expanded`, which is ARIA's disclosure pattern.
`skills/leaf/CLAUDE.md` states what that costs: "every row over a
disclosure reads [`DISCLOSE`] — this scope's, and a widget's own row
re-wording the same press. Two rows naming different sets is not two
promises but one: `lineRows` prints the nearer row and drops the other
whole, so a widget naming one key fewer takes the rest off the line".
The widget row is nearer, so the arrow that opens the evidence went on
working and stopped being named — on the key line and in
`aria-keyshortcuts`, which is written once when the scope is declared
and so froze at `Enter Space`. Nothing tested it; the identical bug in
`lf-options` is what [#132](#132)
is for, and that PR does not reach this package.

**`test_a_widget_that_renames_its_role_keeps_the_press_offer_gave_it`
still asks for the deleted scope's word.** It asserts the key line reads
`press it` over a focused tab. `press it` was `CONTROL`'s `line`, and
335bf9a removed the scope in the same commit that gave `lf-tabs` its own
`tab.activate` row reading `open the tab`. The commit updated several
tests in that file and dropped
`test_the_reference_names_the_space_that_works_a_control` outright; this
assertion was missed, so the test fails before reaching the two facts it
exists to hold.

## Solution

`lf-worktree`'s head takes its keys from `DISCLOSE(head)`, the way
`lf-diff` and `lf-draft` already do, and keeps its own `run`. `DISCLOSE`
hands over only the arrow that changes the state, so the press stays a
direction rather than a second toggle; it also answers for where the
head stands, and the runtime's disclosure scope stops at the chrome. The
head is a span from `selectableOffer`, so a copy frozen into thread
markup has no platform pair underneath it the way a `details > summary`
does — the row's own press is the only thing there, which is the shape
[#132](#132) keeps for the sibling
widget. Its `line` reads the way the tree is standing, and
`aria-expanded` is written at birth so the head is never briefly a
control the runtime cannot place — without that write `DISCLOSE` hands
back both arrows, and that is the value `aria-keyshortcuts` is written
with when the scope is declared. The repaint that keeps the line and
`aria-keyshortcuts` together is the document's disclosure watch, which
[#132](#132) moved from
`paintHere()` to `paintKeys()` while this branch was open; the widget
owes none of its own, which is what `skills/leaf/CLAUDE.md` now says in
as many words.

The tabs test asserts `open the tab` — the strip's own word for the same
press — and its docstring now describes where that press is declared
rather than the scope that used to supply it. Everything after the
assertion is unchanged: Enter still has to re-select the focused tab,
and Space still has to be consumed rather than scrolling the page out
from under it.

New coverage in `test_render_projection.py` for the worktree head, since
nothing exercised it. It holds the row rather than the repaint: on the
page, both surfaces naming the row's keys — the line and
`aria-keyshortcuts` — plus a second `→` on an open tree, so a scope
answering nothing at all could not pass it. In a message: the same head
in the threads panel, where `aria-keyshortcuts` reads `Enter Space`, the
arrow moves nothing, and Enter and Space fold it. The repaint behind
both surfaces is held by
`test_a_widgets_native_control_names_the_press_the_platform_makes`,
which #132 added.

## Testing

`uv run pytest tests` at `c292244` — 723 passed, 6 skipped. After
merging `main` (which brought #132 and #143) and taking the repaint out
at `890e7ff`, `test_render_projection.py`, `test_render_navigation.py`,
and `test_render_anchors.py` — 193 passed, 4 failed, all four the
version-menu group [#137](#137)
owns and all four failing identically on `origin/main` in a clean
worktree. With `--run-nightly`, `test_render_projection.py` and
`test_render_navigation.py` — 137 passed, 1 failed, that one being
`test_the_arrows_say_which_way_the_section_under_the_reader_goes`, which
is #132's settled ask and untouched here; it fails the same way at the
merge base `1ca4f40`.

Each half of the new test was run against the code it holds and fails
there: the page half at `assert 'Enter Space' == 'Enter Space
ArrowRight'` on `main`'s `lf-worktree.js`, and the thread-markup half at
`aria-expanded` staying `"false"` under Enter with the row's `run`
removed.

<details><summary>What this does not fix in that run</summary>

The run reported 34 failures. Most of the rest already have a PR open:
the `-15` root scrollbar-gutter family is
[#121](#121) (`assert -15 == 0`,
the drawn edge landing a bar's width off the hand, the covering panel's
blank strip), the version-menu group is
[#137](#137), the settled ask's
disclosure keys were [#132](#132),
now merged, the margin and float placements are
[#138](#138), the gallery stills
are [#134](#134), and the
sidebar's stated window is
[#136](#136).

No guard stops the third widget from declaring a fixed key pair over a
disclosure. `DISCLOSE` exists so the two surfaces cannot come apart, but
a widget has to remember to call it, and two of the four widgets over a
disclosure did not. A corpus-wide check — every focusable disclosure
names the keys `DISCLOSE` gives it — would catch the next one, and is
left out of this PR because it belongs beside the corpus checks rather
than in a fix for two widgets.

</details>

---
Automated fix for [failed
run](https://github.com/max-sixty/leaf/actions/runs/33328596524)

---------

Co-authored-by: leaf-agent <318509791+leaf-agent@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.

2 participants