Commit 84aef1d
authored
Withhold an option's box-shadow ease until the page is presented (#122)
## Problem
`tests/test_render_gate.py::test_a_reader_arrives_at_what_they_left_rather_than_watching_it_arrive`
is one of the thirteen failures in [the run this was dispatched
for](https://github.com/max-sixty/leaf/actions/runs/33295489155), and
the one #121 leaves out. It is intermittent and it predates the scroller
commit: measured at
[d226ee2](d226ee2a),
26feec8's parent, it failed 3 runs in 18 at `-n0`; at `main` `1dd6976`
it failed 5 in 17.
The extra motion is `CSSTransition box-shadow 120ms` on the page's
`lf-option`s, and it is not replay writing `[chosen]` — the keyframes
are `boxShadow: "none"` → `"rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0,
0, 0) 0px 0px 0px 0px"`. Both channels are transparent, so the
transition eases from nothing to nothing and no pixel differs at either
end. It fires at ~105–155ms, before `data-lf-upgraded` and long before
`data-lf-presented`, on whichever arrangement's load happens to hit it.
The cause is the stylesheet's own arrival. `theme.css` is
render-blocking but not style-blocking, so on a load where a style
recalc falls inside its fetch (measured at 86–135ms for the composed
230KB sheet) the parser has already styled the options with box-shadow's
initial `none`. The sheet then lands and states `box-shadow:
var(--lf-ring), var(--lf-lift)` in the same breath as the `transition:
box-shadow .12s ease` two rules below it, and the value change plays.
Whether a recalc falls in that window is what makes the failure
intermittent, and it is why the arrangement named in the message differs
every time.
## Solution
Withhold the transition until the page says it is presented.
`theme.css`'s arrival guard already says "arriving is not a gesture" for
`body` and for `.lf-ui`, and its own comment names this as the trade it
leaves to the suite — "`transition` does not inherit, so what an
unreached descendant can still play is a transition of its own
declaring; that is the trade, and the check that finds one is the
suite's". This is the finding, answered in the package that declares the
transition rather than by widening the layer's guard into the universal
selector that comment measured and rejected.
Spelled as that guard is —
`html:has(script[type="module"][src="/leaf.js"])
body:not([data-lf-presented])` — so a page with no runtime behind it,
which never stamps and never held its options back, keeps the ease it
always had. A copy has no script link and is out by the same clause.
Nothing visible changes: the suppressed transition ran between two
transparent shadows, and it ran while `main` was still `visibility:
hidden` behind the presentation gate.
## Testing
The named test, 15 consecutive runs at `-n0`: all green, against 3-in-18
at the commit before the scroller moved and 5-in-17 at `main`.
`uv run pytest tests` — 721 passed, 6 skipped. `test_render_options.py`,
`test_render_gate.py`, `test_render_widgets.py`, `test_render_pages.py`,
`test_render_margin.py` with `--run-nightly` — 237 passed, and the only
failures are the seven gutter findings #121 fixes, unchanged in shape.
`pre-commit run --files skills/leaf/packages/default/theme.css` passes.
<details><summary>How the diagnosis was taken</summary>
A `transitionrun` listener installed through `page.add_init_script`
before each arrival, recording `performance.now()`, the readiness
stamps, the computed `--lf-ring`/`--lf-lift`, and
`getAnimations()[0].effect.getKeyframes()`:
```
{'t': 117, 'tag': 'lf-option', 'id': 'wait-day', 'prop': 'box-shadow',
'presented': False, 'upgraded': False,
'shadow': 'rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'ring': '0 0 #0000', 'lift': '0 0 #0000', 'joined': '1',
'settled': False, 'chosen': False,
'ready': 'interactive', 'sheets': 1, 'theme': '16-86', 'nav': '4/0'}
```
`theme` is theme.css's `startTime`-`responseEnd`, `nav` the navigation's
`responseEnd`/`domContentLoadedEventStart`: the HTML was complete at 4ms
and the sheet landed at 86ms, with the transition starting at 117ms. On
the runs where the test passes no such entry exists at all.
Hoisting the two shadow channels onto `lf-option` itself, so the `:has(>
lf-option > strong)` card rule could not be what flips the value
mid-parse, was tried first and changed nothing — the keyframes still
started at `none`, which is what pointed at the sheet rather than at a
selector inside it.
</details>
---
Independent of #121 and based on `main`, not on its branch: a separate
cause in a separate layer, revertable on its own. Together the two take
the failed run to green.
Automated fix for [failed
run](https://github.com/max-sixty/leaf/actions/runs/33295489155)
---------
Co-authored-by: leaf-agent <318509791+leaf-agent@users.noreply.github.com>1 parent d2dd825 commit 84aef1d
1 file changed
Lines changed: 24 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
360 | 384 | | |
361 | 385 | | |
362 | 386 | | |
| |||
0 commit comments