Say a command hub roster name once, keep it on the printed sheet, and read three margin fixtures at the layout the examples now have - #239
Conversation
…ee margin fixtures at the layout the examples now have The nightly leg of ci went red on bf9dd47 with thirteen cases. Seven are the ones #236 already fixes. Of the other six, three reproduce on every local run and are what this changes; the remaining three pass locally and are reported on the pull request rather than guessed at. A command's fleet roster labelled each row with the worker's own <strong> and marked that label as the page speaking. Both copies stand fenced — the roster item is generated words, the row's name sits between the state chip and the remit — so two passages carried the same text and the same empty context and neither could be told from the other: a drag across a worker's name detached instead of anchoring, on the roster and on the row alike. The link is a route to the row now, like the contents sidebar's links, and the words stay quotable where the page says them. Ship review stands a contents map since bf9dd47, and a sidebar claims the opposite strip, so its thread margin waits for 1472px of shell rather than 1208px. The two fixtures that open its shipped thread beside its source read that floor now. At the floor the card is 426px rather than the 447px the same floor affords a page with no sidebar, because the strip is exactly --thread-margin there and the marker, the gutter, main's padding and the card's own inset all come out of it. The gallery's core surfaces open on a decision since #237, so the page's first ask is that decision rather than the first suggestion. The ask-address fixture presses a twice and says what each press reaches.
|
The nightly leg of The three cases this PR declined as race-shaped all passed on that run. They were the whole of the difference from the thirteen on The ten cases and which branch owns eachVerified against the branch diffs rather than the PR bodies:
The ring walk failed on the ship-review precondition — The three that did not recur, and one thing that is not a problem
The two branches merge cleanly despite both editing the No fix PR for this run, since both causes are already proposed. |
leaf-agent
left a comment
There was a problem hiding this comment.
The anchoring fix is the right shape — a copied label is a route, not a second place the page says the words, and findQuote detaching on two same-text/same-context passages is exactly the fault. But dropping data-lf-said is not a free demotion: it is also what paper reads, and the fleet roster and the stopped-work list now print with no subject at all.
Measured on examples/command-hub.html in Chrome, disclosures open, emulate_media(media="print"):
on e0a0549b |
on 466473cf |
|
|---|---|---|
| stopped row | Choose the additive schema · 12d — awaiting review; 5 downstream goals unreachable |
· 12d — awaiting review; 5 downstream goals unreachable |
| fleet row | atlas-lead · working · project-wide remit |
· working · project-wide remit |
All ten rows, both lists. The contents-sidebar precedent the comment cites does not carry: the sidebar is .lf-chrome and leaves the sheet whole, where these rows stay on the sheet and lose their only identity.
Nothing in the suite sees it, and that is structural rather than a gap to fill with one more case. paperWords skips text inside [data-lf-offer] in both readings by construction — "Declared offers are excluded because paper has nothing to press" — so the gate's screen-vs-paper comparison cannot report a word that was only ever inside an offer. test_paper_takes_the_press_off_everything_it_cannot_press asks about dress, not words, and passes. I ran test_render_margin.py, test_render_commands.py, test_render_export.py and the paper cases green, and test_render_anchors.py + test_render_controls.py with only the four failures #236 owns.
leaf.js's own design note records this failure shape as already-fixed once: "Keying print on .lf-ui instead cost a printed decision the only words that stated it". The two markers answer two questions — is this the page speaking (anchoring), and are these words worth keeping on a sheet — and until now those had the same answer everywhere. A copy of another element's words is the first case where they come apart: it must not anchor, and it must still print. skills/leaf/CLAUDE.md closes the widget-level escape deliberately — "A wrong offer/said declaration is fixed where the label is created, not by naming its widget in print CSS" — so a @media print rule in command-hub/theme.css is not the remedy here. That makes this a call about whether the layer gains a third declaration, which is a maintainer's rather than something to settle in this PR.
Two smaller notes, neither a fix request:
- The change reaches the stopped-work list too —
button(goal.title, goal.element)inrenderStopped— where the lost words are goal titles. The code comment covers it ("a route to a row"), but the PR title and Solution section name only the fleet roster, and the printed stopped list is the more visible half. - The new
skills/leaf/CLAUDE.mdparagraph states the rule layer-wide ("a contents link, a roster row naming a worker, any generated index entry stays chrome"). Written as it stands, following it puts every future generated index under the same print rule, so whatever answer the point above gets belongs in that paragraph.
The three fixture changes read correctly to me. The 1208/1472 floors and the > aside.sidebar condition are theme.css as written, ship review carries the sidebar on the base, and the two card-width floors match the arithmetic in the new comments. The --thread-margin shortfall you flagged is real on the file as it stands (54 + 8 + 460 + 8 = 530 against 520) and is worth its own look rather than a change here.
A roster row names its target in the target's own words, so the name must not anchor — two fenced passages with the same text and the same empty context detach — and it must still print, being the only thing the row is about. data-lf-said answered both questions until now; `says: "echo"` is the third answer, writing data-lf-echo: out of the says reading, and keeping the paper bargain data-lf-said strikes.
|
All three points are in The layer gains the third declaration. I settled it here rather than leaving it open, because the code supplies the answer and the alternative that keeps both halves does not exist: reverting the demotion puts back the detaching drag this PR was opened to fix. The print block's own sentence — "what stays is a control whose label is one of the page's own words" — already covers a name a row is copied from; what it could not say was "these words, and no passage." So Two facts that fell out of the change rather than being designed for: a copy is not affected either way, because The stopped list is named now. Title and the Solution section both say roster and stopped list; the printed example in the second paragraph is a stopped row, which is the more visible half as you said. The gate gap stands, and it is not one more case. I tried widening Verification and the one failure that is neither branch's are in the description's Testing section. |
leaf-agent
left a comment
There was a problem hiding this comment.
The print half is closed. I read it back rather than taking the note for it: on 1e8d386 the ten rows print with their subjects — Choose the additive schema · 12d — awaiting review; 5 downstream goals unreachable, atlas-lead · working · project-wide remit — and with says: "echo" swapped back to says: false in button() the same assertion reports exactly the loss the last review measured (· working · project-wide remit, and the four w-* rows the same way). So the new case is a real reading and not a restatement of the fix. -k "paper or print or bake or standalone or export" --run-nightly is green across the suite (40 cases), and the everyday ci gate is green on this head.
The two other notes are answered: the stopped list is named in the title and the Solution section, and the skills/leaf/CLAUDE.md paragraph now separates the route chrome builds from the route a control builds, so a future generated index reads the rule that applies to it.
One observation on the new declaration's reach, not a fix request. The design note says a copy is not the medium this divides, "because bake removes presses by the marker's value". That holds for what ships, because offer("a", …) writes the empty-valued marker and the export's removal pass selects [data-lf-offer]:not([data-lf-offer='']):not([data-lf-said]) in render-checks/standalone.js — the echoed route slips both that pass and the [data-lf-offer][data-lf-said] static-ising pass after it, and stays a live fragment link. But the value is a function of the tag, not of the declaration: an echo written on an offer("button", …) carries data-lf-offer="button", so that same pass would take the control out of the copy with its words inside it. The paper bargain data-lf-echo strikes would then hold on a sheet and not in an export, and nothing would report it — data-lf-said is the marker that pass reads. Unreachable today, since button() is the only caller and it builds an a; it is the seam the third answer opens, and worth knowing about before a second widget echoes a label.
`relabel`'s note claimed a copy is not the medium the third answer divides,
because bake removes presses by the marker's value. That is true of the one
caller — `button()` builds an `a`, whose marker is empty-valued, so the echoed
route slips the removal pass and stays a live fragment link — and false in
general: the value is a function of the tag, so an echo on an `offer("button",
…)` is valued, goes out of the copy with its words inside it, and nothing
reports the loss, because the pass that keeps a said press's words reads
`data-lf-said` alone.
No pass changes. The condition is unreachable while `button()` is the only
caller, and a guard for it would be machinery the second echo has to be written
around anyway. What the second echo needs is the constraint stated where it is
read: beside the declaration, and in the layer's own paragraph on it.
|
The observation is right, and it is right for a reason the note as written denied, so the note is what I changed ( No pass changes. The condition is unreachable while Where the seam is, in the code
Making the copy read them as one set is not the fix, incidentally: it would catch today's echoed The change is comment and prose only — no behavior, no selector. |
# Conflicts: # tests/test_render_margin.py
… from the press (#241) ## Problem The nightly leg of `ci` failed on [run 33726571939](https://github.com/max-sixty/leaf/actions/runs/33726571939) (`e71fa11`) with ten cases. Nine are already owned by open PRs — four by #236 (`test_a_widgets_label_takes_a_comment_inside_the_control_it_labels`, `test_a_selection_around_a_targets_buttons_does_not_deaden_them`, `test_a_closed_leaf_clears_itself_off_the_tray`, `test_the_leaves_tray_takes_the_keyboard`), four by #239 (`test_every_passage_in_a_real_page_can_be_quoted[feature-gallery]`, both parameters of `test_ask_addresses_follow_the_feature_gallery_s_visible_margin_controls`, `test_the_shipped_long_thread_opens_beside_its_source_in_the_right_margin`), and the ring walk by #239's `ship-review` precondition ahead of #236 and #240. The tenth is unowned and is what this takes: `test_render_widgets.py::test_a_wait_the_reader_would_notice_says_so_and_a_short_one_says_nothing`, first seen on this run and absent from the three nightlies before it ([33722764498](https://github.com/max-sixty/leaf/actions/runs/33722764498), [33718675826](https://github.com/max-sixty/leaf/actions/runs/33718675826), [33714432132](https://github.com/max-sixty/leaf/actions/runs/33714432132)). ``` AssertionError: a wait worth noticing said nothing: [0.702361, 0.642718, 0.583432, 0.523789, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5] ``` The test took `performance.now()` just before the press and asked that every frame past 400ms had settled at 0.5. The rule it reads is `[aria-busy="true"] { animation: … 140ms linear 200ms both; }` — 340ms in all — but that clock starts at the first frame the busy attribute is *painted* in, not at the press. `#inFlight` sets the attribute synchronously and then renders the controls and updates the margin in the same task, so the animation's start time is the first frame after all of that. Measured here, the fade begins at 233ms and completes at 383ms: 17ms inside the deadline. The reported sample is that whole timeline slipped about 120ms, which is what a loaded nightly worker costs — the frames past 400ms are still mid-fade rather than absent. ## Solution Read the fade on the rule's own clock. Each sampled frame now records the animation's `currentTime` and `playState` beside the opacity. The delay is the frames before `currentTime` reaches 150ms, including the ones before the animation exists at all; the fade is the frames the animation itself reports `finished`. Both halves of the claim survive intact — nothing paints while the wait is too short to notice, and the wait that is long enough ends at 0.5 — and neither is a deadline the machine has to beat. This is `tests/CLAUDE.md`'s **A wait consumes a fact the system states** applied to a reading that was inferring from elapsed time, using the `getAnimations()` route that section names. The sampling window goes 500ms → 700ms so a start that slips still leaves frames on the far side of the fade; it is a bounded `setTimeout` inside the page, not an awaited `animation.finished`. ## Testing `uv run pytest tests/test_render_widgets.py --run-nightly` is green on this branch (82 passed) and `uv run pytest tests` was green (808 passed, 6 skipped) on the commit the run failed on. `ruff check` and `ruff format` at the pinned `v0.16.1` pass on the changed file. Not vacuous, checked from both sides against the rule in `chrome-style.js`: <details><summary>The two negations</summary> | rule changed to | what fails | | --- | --- | | `… 140ms linear 0ms both` (delay removed) | `the wait was announced before it was one: {1, 0.94, 0.88, …}` — the early half | | selector changed so nothing matches | `a wait worth noticing said nothing: []` — the late half | </details> <details><summary>The frame samples, before and after</summary> Sampled locally under three spinning cores, on the original wall clock (`ms since t0`, opacity): ``` (200.3, 1), (217.0, 1), (223.8, 0.999939), (240.5, 0.940296), … (357.1, 0.523868), (373.7, 0.5), (390.5, 0.5), (407.1, 0.5) ``` The fade runs 223→383. The assertion's threshold is 400, so the margin the reading had was one frame. On the animation's clock the same run reads `currentTime` 0 at the frame the attribute paints in, 200 where the fade starts and 340 where it ends and holds — the same numbers the rule is written with. Worth a maintainer's eye rather than a fix here: about 120ms of main thread between the press and the frame the busy attribute paints in is a lot for `SUGGESTION_PAGE`, which is a small fixture. `#inFlight` calls `#renderControls` and `this.#margin?.update()` after setting the attribute, and the animation cannot start until both have finished. The rule is designed for exactly the heavy-page case, so it still does the right thing for the reader — but the cost is the layer's, and it is not visible from any assertion that reads the rule alone. </details> --- Automated fix for [failed run](https://github.com/max-sixty/leaf/actions/runs/33726571939) Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
## Problem The nightly leg of `ci` failed on [run 33735071949](https://github.com/max-sixty/leaf/actions/runs/33735071949) (`a57ae29`) with seven cases. Six are already owned by open PRs: `test_a_widgets_label_takes_a_comment_inside_the_control_it_labels`, `test_a_closed_leaf_clears_itself_off_the_tray` and `test_the_leaves_tray_takes_the_keyboard` by #236; the ring walk by #239's `ship-review` precondition standing ahead of #240's `lf-shotframe` fault; and both of `test_page_fixture_renders[pr-walkthrough]` (the 4px sideways scroll on `pr-scale-groups`) and `test_nested_command_projections_stop_at_their_own_boundary` (`§ inner-worker`) by #243. The seventh is new with `a57ae29` and nobody's yet: ``` FAILED tests/test_render_conversations.py::test_a_coined_class_cannot_reach_the_chromes_rules AssertionError: the document-level class surface changed: widen the shared vocabulary on purpose Extra items in the left set: 'lf-workspace' ``` #242 wrote the open workspace's tint as `.lf-btn.on, .lf-btn.lf-workspace[aria-expanded="true"]` in `chrome-style.js`, at document level. `lf-workspace` is worn by three controls and all three are the chrome's own — the Threads toggle built at [`leaf.js:817`](https://github.com/max-sixty/leaf/blob/064e67d5/skills/leaf/assets/leaf.js#L817), and All leaves and Asks, which `trayIs` marks at [`trays.js:194`](https://github.com/max-sixty/leaf/blob/064e67d5/skills/leaf/assets/runtime/trays.js#L194). Every one of them is inside `chromeRoot`, the `.lf-chrome` container the private stylesheet is scoped to. So the name reached document level without being shared vocabulary, which is the seam the pinned set exists to catch — the same one #152 answered for the keycap rules. ## Solution Move the selector into the `@scope (.lf-chrome)` block, beside `.lf-banner-more[data-lf-news]`, which paints the same accent face for the same kind of banner state. `.lf-btn.on` keeps its document-level rule, unchanged and now on its own: `on` is worn out in the page and is the reason that line was there before #242. Nothing about the paint moves. Specificity is unchanged, and the one rule that could contend — `.lf-banner-menu > .lf-btn { border-color: transparent }`, for a workspace control folded into the overflow menu — is `(0,2,0)` against this selector's `(0,3,0)` on both sides of the move, so scope proximity never gets a say. Fixing it at the pinned set instead would have widened the shared vocabulary by a class no widget can wear, which is what the assertion's own message asks you not to do. ## Testing On this branch, rebased on `fdba9e1d`: - `uv run pytest tests` — 808 passed, 6 skipped - `test_a_coined_class_cannot_reach_the_chromes_rules` and both parameters of #242's `test_workspaces_replace_each_other_and_name_the_open_one` pass - `uv run pytest tests/test_render_conversations.py tests/test_render_controls.py --run-nightly` — 141 passed, and the three failures are #236's two tray cases and the ring walk, all reproducing identically on `main` Not vacuous: deleting the scoped rule fails the workspace test at both 500 and 1200 with the resting face (`rgb(238, 234, 222)` where the active face is `rgb(255, 254, 251)` under `--chip`) rather than passing by construction. `chrome-style.js` is served rather than bundled, so there is nothing to re-vendor, and no user-visible pixel changes, so there is no before/after shot to hand over. --- Automated fix for [failed run](https://github.com/max-sixty/leaf/actions/runs/33735071949) Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>
Problem
The nightly leg of
cifailed on run 33714432132 (bf9dd47) with thirteen cases. Seven are the ones #236 already fixes. This takes three of the other six — each reproduces on every local run, and each is a fixture or a defect thatc14fbd8(#237) orbf9dd47brought in. The remaining three pass locally and are reported below rather than guessed at.Solution
A roster name was said twice, so neither copy could be pointed at. A command's stopped-work list and its fleet roster label each row with the target's own
<strong>— a goal's title, a worker's name — andbutton()marked that label as the page speaking. Both copies stand fenced — the roster item is generated words, and the row's name sits between the state chip and the remit the widget builds — so two passages carried the same text and the same empty context, andfindQuotecannot tell one from the other: repeated candidates detach rather than inheriting document order. A drag across a name therefore anchored nothing, on the roster and on the row alike. Both lists' labels are routes now, like the contents sidebar's links that name every heading and say none of them, and the words stay quotable where the page says them. Latent since before #237 —atlas-leadon command hub detaches the same way onbf9dd47— but the sweep only reads blocks longer than twelve characters, andgallery-readyis the first agent name over that line.A route inside a control is still the words its row is about, so paper keeps them. Demoting those labels out of the
saysreading also took them off the sheet: the chrome stylesheet's print block hides[data-lf-offer]that carries nodata-lf-said, so all ten rows printed with no subject —· working · project-wide remit,· 12d — awaiting review; 5 downstream goals unreachable. The two markers answer two questions, and a copy of another element's words is the first case where they come apart: it must not anchor, and it must still print.relabeltakes a third answer for it,says: "echo", which writesdata-lf-echo— out of thesaysreading, and holding the same paper bargaindata-lf-saidstrikes: the press goes, the words stay. A standalone copy is unaffected either way, sincebakeremoves presses by the marker's value and an echoed route is a fragment link the browser still owns.Ship review stands a contents map now, and the thread margin waits on the sidebar.
theme.cssgrants--lf-thread-besideat 1208px of shell, or 1472px where the page has a> aside.sidebar—bf9dd47gave ship review one. At 1440 the shipped thread now opens Threads instead of a card beside its source, which is what both fixtures were reading. They read the sidebar floor now: 1600 for the walk, and 1472 where the case is the floor itself.The gallery's first ask is a decision. #237 opened the core-surfaces section with
bg-choice-ask, which stands before the suggestions, so oneano longer reachesbg-replace. The ask-address fixture presses twice and says what each press reaches.Testing
uv run pytest tests --run-nightlyovertest_render_controls.py,test_render_margin.py,test_render_projection.py,test_render_anchors.py,test_render_pages.py,test_render_export.py,test_render_commands.pyandtest_render_widgets.py, plus the everydayuv run pytest tests(808 passed) andpre-commit runover the changed files. Five cases fail, none of them this branch's: the fourtest_render_anchors.pyandtest_render_controls.pycases #236 owns, andtest_render_projection.py::test_escape_lets_go_of_the_ask_the_reader_is_standing_on, which reproduces deterministically with this branch stashed and again on a cleane0a0549worktree, so it belongs to neither branch.The paper half is measured rather than argued: with the roster labels marked
says: "echo"the ten rows read the same underemulate_media(media="print")as on screen, and withsays: falsethe same assertion reports the dropped subject.test_a_roster_row_names_its_target_without_saying_it_twiceis that reading. The render gate cannot stand in for it:paperWordsreads no text inside a declared offer, so a word that only ever stood in one is invisible to the screen-vs-paper comparison.What the two card-width floors changed to, and why
The shipped-thread case asserted a 460px card at 1440 and a 447px card at the 1208px shell floor. With a sidebar standing, the document keeps exactly
--thread-margin(520px) from the right edge at every width — the surplus a page without a sidebar splits between its two strips all goes to the sidebar's side — and the marker (54px), the gutter (8px), main's own 24px padding and the card's 8px window inset all come out of that 520. So the card is 450px at any width where main has reached its 720px measure, and 426px at the 1472px floor where main is down to its own 640px floor. The assertions are 449 and 425 now, with the arithmetic written beside them.Worth a maintainer's eye rather than a fix here:
--thread-margin: 520pxis documented as "the marker and the gutter between it and the 460px conversation", and 54 + 8 + 460 + 8 = 530. It has never quite held; only the surplus centring hid it, and the sidebar posture is the first arrangement that spends the strip exactly.The three cases this does not take
None reproduces locally — each passed on every run of its file here, on
bf9dd47and one0a0549— and all three are the same shape:round_trip(page)returned before the gesture's own POST had been seen, so the assertion read the log one event early.test_render_options.py::test_a_pick_states_the_whole_setchooseevents where the six clicks it made are six; the sixth click's DOM assertion had already passedtest_render_widgets.py::test_a_comment_on_a_wrapped_diff_line_names_the_line_an_unwrapped_one_namestest_render_reactions.py::test_selected_reactions_keep_neutral_button_furniture[dark]aria-pressed="true"after the take-back, with "Took back your ok — recorded" already in the bannerround_tripwaits for the traffic watcher to hold nothing pending, which is empty both before a request starts and after it finishes; a gesture whoserequestevent has not reached the watcher yet passes it instantly. Stating the ordering means waiting on what the gesture produces — the event count in the log, the state the reopened control reads — rather than on the watcher being quiet, and that is a change per case rather than one knob, which is why it is not in here.One more ring fault appears once this and #236 are both in
Composed locally (
912c99d, #236's head withmainmerged, plus this branch) the ring walk gets past the thread card and pastpage-map-search, and then stops on a new one:The list runs 33px past the sheet's bottom and 1.4px over its top on the corpus, which is the same box #236 refits for the small-screen sheet — its measurement was taken at 760×900, and this is the 1200×900 walk. It is reachable only with both changes in, so neither branch's own run can see it. Left for #236 rather than duplicated here, since that PR already owns
.lf-page-map-list's sizing.Automated fix for failed run