perf: first-paint CSS diet on BM14d (-639 B vs train, -252 B vs dev) #908

Closed
kayg wants to merge 0 commits from perf/first-css-diet into dev
Owner

Stacked on train BM14d (#907, head c163abae): the 5 commits on top are the diet; the rest of the diff is the train until #907 lands. Never merge before #907.

First-paint CSS, brotli q11 (bundle-size.mjs, exact bytes): dev 41f2efa3 19250, train c163abae 19637 (+387), train + diet 2bb672ff 18998 (-252 vs dev, -639 vs train). First JS unchanged (232.8 KB).

commit saving
d9f206c9 pressed/disabled/danger/done/tall-pill rules to note-bars-lazy.css -31 B
03e9e1c3 sheet-open room rule loads with clear-sheet -28 B
93585a40 drop dead bar-stack/bar-line/bar-group -92 B
a0c7130a drop unused .visually-hidden -30 B
2bb672ff picker list CSS (@/[[/#) loads after first paint; first lookup waits for it -458 B

First note, 1440 and 390, light and dark: byte-identical PNGs, train vs train+diet (train vs train repeat is identical too). 12 affected specs (link-suggest, tag-picker, glass*, pill-room, tint, note-bars*, style-sheet, sheet-geometry, bar-rest, lazy-sheet-recovery) on chromium/webkit 1440/390: 407 passed, 0 failed, 157 skipped.

Details and screenshots on #907.

🤖 Generated with Claude Code

Stacked on train BM14d (#907, head c163abae): the 5 commits on top are the diet; the rest of the diff is the train until #907 lands. Never merge before #907. First-paint CSS, brotli q11 (`bundle-size.mjs`, exact bytes): dev 41f2efa3 **19250**, train c163abae **19637** (+387), train + diet 2bb672ff **18998** (-252 vs dev, -639 vs train). First JS unchanged (232.8 KB). | commit | saving | |---|---| | d9f206c9 pressed/disabled/danger/done/tall-pill rules to note-bars-lazy.css | -31 B | | 03e9e1c3 sheet-open room rule loads with clear-sheet | -28 B | | 93585a40 drop dead bar-stack/bar-line/bar-group | -92 B | | a0c7130a drop unused .visually-hidden | -30 B | | 2bb672ff picker list CSS (`@`/`[[`/`#`) loads after first paint; first lookup waits for it | -458 B | First note, 1440 and 390, light and dark: byte-identical PNGs, train vs train+diet (train vs train repeat is identical too). 12 affected specs (link-suggest, tag-picker, glass*, pill-room, tint, note-bars*, style-sheet, sheet-geometry, bar-rest, lazy-sheet-recovery) on chromium/webkit 1440/390: 407 passed, 0 failed, 157 skipped. Details and screenshots on #907. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kayg added 219 commits 2026-11-04 05:52:05 +00:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5oTUycqAh7DT72mdSUztk
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The room under a phone's Style sheet loads with the idle screens
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
7a6d5adf4c
Presets: the thumbnail's page is not .page; backdrop, opacity and width specs follow the new Style sheet
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
c1ef1b78ed
Merge port/style-2
Some checks failed
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
a0f425ac08
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dee3e961 replaced the fixed 450 ms x4 waits with still(); a settled page
draws no frames, so the screencast held 15-19 frames instead of 40+ and
'frames > 20' failed 16/20 unloaded and 20/20 under load. Wait for the
frame count (state), threshold unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Opening the note (5 s), typing 55 keys (6 s) and two palette round trips
(6 s) at 6x CPU took 18 s of a 30 s test on a quiet machine and hit the
limit whenever the machine was shared. Loading+typing and switching modes
are now separate tests; nothing about the timeout or the shift check changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
e2e: vim :noh waits for the marks to come back; lazy-ui marks the window; links waits for the job first
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
3c1d8deb19
- vim.spec '/ searches...': Esc from a half-typed '?cher' removes the marks and puts the
  earlier search's three back about 60 ms later (probe: 0,3,3,...). :noh sent inside that gap
  clears nothing and the marks then return. Wait for the 3 before ':', and for the bar's
  field to hold the focus before typing. Flaked 4/20 on origin/dev, 1/20 on the BM14b head.
- lazy-ui.spec 'failed ... idle warming recovers': detect a reload with a mark on the window,
  not performance.timeOrigin (WebKit derives it from the wall clock; BM14b saw it move by 1 ms).
- links.spec 'Try again': wait for the server's job (15 s poll) before the 5 s URL check.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
yrs 0.28.0 counts the whole merged string again after every squash of two
string items, so a run of appends costs the block so far each time (O(n^2)).
Vendored copy with the one-line fix: add the two lengths.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
test(notes): appends cost the same early and late, UTF-16 length stays exact
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
76759d9da5
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Patch to the merged upstream commit until a release after 0.28.0 ships.
Cargo.lock follows in the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Halves of a run are noisy on a loaded machine (3.2x once in a full suite).
A 5 s bound on 6,000 appends separates the quadratic case (over a minute)
from the fixed one (well under a second) with a wide margin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An owner reading their own note never depends on a grant row. For
every credential except an app password the resource check reduces to
the note-ID size and read-scope checks, so run those directly instead
of a database round trip for each queued frame and each delivered
frame. App passwords keep the full check (revocation is in the db).
Same allow/deny outcome for every caller; non-owner paths untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The owner's own live frames paid a clone, a boxed future and a home
lookup per queued frame and again per written frame. Answer them in
Home and Notes with the same size and scope checks, for non-app-password
owner credentials only; everyone else takes the full path unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A build older than #898 could not deserialize Requirement::ExpectedOwners, so
one such record took a whole Home offline on rollback. Accept the variant in
the reader and validator; admission still refuses it (this build has no
probe). Writes are unchanged. Test reads a real record written by #898.
Deploy doc: roll back past #898 only to a build with this reader.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
style: cargo fmt
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
5f40792840
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
perf(web): leave unused remote-cursor code out of first paint (-983 B brotli)
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
809a33c2b4
yCollab only uses y-codemirror.next remote selections when it is given an
awareness, and the editor always passes null. The module still shipped.
A build-time empty stand-in drops it; yCollab's own import keeps working.

first-paint JS: brotli 238797 -> 237814 B (-983), raw 845550 -> 841512 B (-4038)
first-paint CSS: unchanged (19250 B brotli)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The upstream pin (26cc6ae3) is 18 commits past 0.28.0 and some change how
unusual input is read (e.g. rejecting malformed updates), which could make a
stored document unopenable. Vendor the release with only the squash length fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An owner reading their own note never depends on a grant row. For every
credential except an app password the resource check reduces to the
note-ID size and read-scope checks, so run those directly instead of a
database round trip for each delivered frame. App passwords keep the
full check (revocation is in the db). Same allow/deny outcome for every
caller; non-owner paths untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Answer the owner's own connection in Home with the same size and scope
checks, without cloning the caller or awaiting the sharing layer.
Everyone else takes the full path unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The probe stood in for a recipient using the owner. An owner never waits
on a grant lookup, so use a real recipient caller: same recheck, same
assertions, no dependency on the owner path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The probe stood in for a recipient using the owner. An owner never waits
on a grant lookup, so use a real recipient caller: same recheck, same
assertions, no dependency on the owner path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	crates/auth/src/sharing.rs
#	crates/notes/src/home.rs
#	crates/notes/src/room/sync.rs
Opening a note for its owner made two sharing lookups (read, then edit)
plus the delivery check, for a person who needs none: the owner's access
is fixed by the credential. Answer owner Read and Edit with the same size
and scope checks and an Owner result, for session and development
credentials; app passwords (revocable), recipients and every other
operation take the full path unchanged. The sequence is passed through as
given; owner delivery never reads it.

Access logic touched: owner Read/Edit resolution only; outcomes are the
same allow/deny set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Opening a note for its owner made two sharing lookups (read, then edit)
plus the delivery check, for a person who needs none: the owner's access
is fixed by the credential. Answer owner Read and Edit with the same size
and scope checks and an Owner result, for session and development
credentials; app passwords (revocable), recipients and every other
operation take the full path unchanged. The sequence is passed through as
given; owner delivery never reads it.

Access logic touched: owner Read/Edit resolution only; outcomes are the
same allow/deny set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
note-bars.css held every bar state's rules and was imported by main.ts, so the
first stylesheet carried the block bar, the typing strip, the kind chip and the
strip fades (5.4 KB of 8.5 KB) before anyone selects a block or types. Move
those to note-bars-lazy.css, imported by BlockBar and Strip (lazy, in the
idle-screens chunk; Vite resolves the import after its CSS, so there is no
unstyled frame). The rest bar's rules and the pill radius stay eager.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge #906 (perf/first-paint-diet @ 809a33c2) into train BM14d
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
c163abaea6
The rest bar uses none of them; the block bar and the strip (which load note-bars-lazy.css) do.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
No component, test or script names these classes any more (the two-line phone bar they drew was replaced by the note bars).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
perf(web): load the @ / [[ / # picker list's look after the first paint
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
2bb672ff3b
The list only shows once you type a trigger. Its stylesheet is warmed when the browser is idle and the picker's first lookup waits for it, so the list never shows unstyled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Landed in dev via 12103f1c97 — closing.

Landed in dev via 12103f1c973f1e810dd4a9a3bff5a705514d70b0 — closing.
kayg closed this pull request 2026-11-07 17:53:11 +00:00
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled

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!908
No description provided.