Scroll perf: the bar docking no longer reads layout on every scroll; fixes a note-bar regression in #528 (#445) #531

Closed
kayg wants to merge 8 commits from issue/445-scroll-perf into issue/445-no-bleed-3
Owner

Refs #445. Stacked on #528 (issue/445-no-bleed-3): base is that branch, so the diff is only this follow-up. Not merged.

1. A regression in #528, fixed here (db80115, 2bae5ec): please take these two into #528 too. bar-rest (installed-short-box, 4 sizes), bar-focus typing (2) and keyboard (2) fail on #528 and pass on train/2026-10-17w: the note's fixed bar sat 40px too high and did not go onto the keyboard. Cause: the document-scroll rule that shifts the pages beneath (.screen:not(.current) > :not(.topbar) { translate: 0 calc(-1 * var(--saved)) }) also matched the NOTE screen, and any translate makes a box the containing block of its fixed children, so the bar was placed against the note's box; the stack also lost its clipping box under a note. Now the shift is for .screen.on-glass only, and the growing stack only while data-edge="glass". None of my earlier spec runs included the bar/keyboard specs, which is how it got through.

2. Scroll cost: found and fixed one real cause. Profiling the 10k list scroll (CPU profile and trace, 4x) put place() in lib/keyboard.svelte.ts at the top of the JS self time: the bottom bar docks above the keyboard and ran on EVERY window scroll event, reading computed styles and boxes up the ancestor chain, writing the bar's style and offsetHeight: a forced layout and a style write per scroll frame, on every list page, though the bar is not even showing. It now does nothing on a scroll unless the visual viewport, innerHeight, the home-screen height or the focused-editable state changed (those events still call it directly). The list also no longer measures its own box on every scroll (getBoundingClientRect in the scroll handler); it is measured on resize/ResizeObserver.

Main-thread CPU time (trace tdur, immune to host load) over the same scripted scroll, before (#528) to after, four interleaved pairs: total 4162 / 4186 / 5874 / 5293 ms to 3565 / 3000 / 3920 / 4496 ms (about 15-30% less); script 3080 / 2819 / 3767 / 3521 to 2197 / 1924 / 2327 / 2714 ms. Wall-clock frame p95 with m.mjs is not usable as evidence this round: the host load average was 17-30 on 12 cores and the same build swung from 2% to 23% of frames over 20 ms between runs. I cannot honestly claim the 20 ms / 5% target from that. What remains is mostly native paint/commit/composite at 4x (about 2 s of main-thread time per 4 s), not script; the pills' blur, the surface, the grain and the + button each measured as no difference once the load was accounted for.

Tests (chromium-390, webkit-390, --no-deps; keyboard, bar-focus, bar-rest, bar-taps, bar-stays, bar-stable, toast-bar, list-chrome, page-chrome, phone-bars, home-screen, pwa): chromium-390 324 passed; webkit-390 324 passed and one 3-minute timeout in bar-taps "style sheet" that passed alone on rerun.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCcFhwwgMK66k9LMVqogNJ

Refs #445. Stacked on #528 (`issue/445-no-bleed-3`): base is that branch, so the diff is only this follow-up. Not merged. **1. A regression in #528, fixed here (`db80115`, `2bae5ec`): please take these two into #528 too.** `bar-rest` (installed-short-box, 4 sizes), `bar-focus` typing (2) and `keyboard` (2) fail on #528 and pass on `train/2026-10-17w`: the note's fixed bar sat 40px too high and did not go onto the keyboard. Cause: the document-scroll rule that shifts the pages beneath (`.screen:not(.current) > :not(.topbar) { translate: 0 calc(-1 * var(--saved)) }`) also matched the NOTE screen, and any `translate` makes a box the containing block of its fixed children, so the bar was placed against the note's box; the stack also lost its clipping box under a note. Now the shift is for `.screen.on-glass` only, and the growing stack only while `data-edge="glass"`. None of my earlier spec runs included the bar/keyboard specs, which is how it got through. **2. Scroll cost: found and fixed one real cause.** Profiling the 10k list scroll (CPU profile and trace, 4x) put `place()` in `lib/keyboard.svelte.ts` at the top of the JS self time: the bottom bar docks above the keyboard and ran on EVERY window scroll event, reading computed styles and boxes up the ancestor chain, writing the bar's style and `offsetHeight`: a forced layout and a style write per scroll frame, on every list page, though the bar is not even showing. It now does nothing on a scroll unless the visual viewport, innerHeight, the home-screen height or the focused-editable state changed (those events still call it directly). The list also no longer measures its own box on every scroll (`getBoundingClientRect` in the scroll handler); it is measured on resize/ResizeObserver. Main-thread CPU time (trace `tdur`, immune to host load) over the same scripted scroll, before (#528) to after, four interleaved pairs: total 4162 / 4186 / 5874 / 5293 ms to 3565 / 3000 / 3920 / 4496 ms (about 15-30% less); script 3080 / 2819 / 3767 / 3521 to 2197 / 1924 / 2327 / 2714 ms. Wall-clock frame p95 with `m.mjs` is not usable as evidence this round: the host load average was 17-30 on 12 cores and the same build swung from 2% to 23% of frames over 20 ms between runs. I cannot honestly claim the 20 ms / 5% target from that. What remains is mostly native paint/commit/composite at 4x (about 2 s of main-thread time per 4 s), not script; the pills' blur, the surface, the grain and the + button each measured as no difference once the load was accounted for. Tests (chromium-390, webkit-390, `--no-deps`; keyboard, bar-focus, bar-rest, bar-taps, bar-stays, bar-stable, toast-bar, list-chrome, page-chrome, phone-bars, home-screen, pwa): chromium-390 324 passed; webkit-390 324 passed and one 3-minute timeout in `bar-taps` "style sheet" that passed alone on rerun. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WCcFhwwgMK66k9LMVqogNJ
Author
Owner

Landed in train AA, #569.

Landed in train AA, #569.
kayg closed this pull request 2026-10-18 06:14:52 +00:00
Some checks failed
CI / server (pull_request) Successful in 30s
CI / rust (pull_request) Successful in 2m11s
CI / web (pull_request) Failing after 23s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!531
No description provided.