ui: shared Pill, Toolbar, PageToolbar and one press (#912 PR 1) #941

Closed
kayg wants to merge 0 commits from ui/pill-toolbar-912 into dev
Owner

PR 1 of the #912 plan: the shared pill primitives from the approved mockup (review/calendar-2: #892, #936) in production. Based on origin/dev @ 9377d711. First paint vs dev: JS −447 B gzip (−108 br), CSS −685 B gzip (−664 br); see the comments for the per-change table, pixel diffs, frames and gates.

What ships

  • packages/ui/src/components/Pill.svelte, Toolbar.svelte: the pill and the pill-within-a-pill. alone (floating glass, or glass={false} flat tint), wide, h, tip, style, class.
  • apps/web/src/chrome/PageToolbar.svelte: the one [Share ⋯] top-right pill (width spring, Done state), used by TopPills.
  • The one press (packages/ui/src/styles/base.css): .press, .bounce, .pill, .ib, .seg button share one rule: scale: var(--press-scale, .97), --ease-out, 150 ms, scale only, reduced motion keeps the 3 % scale and drops the glide.
  • One bar-icon size: --bar-glyph 22 px / 2.1 line on a phone, 20 px / 1.9 on a desktop. Icon with no size now reads --glyph (16 px on the page; Pill and Toolbar raise it for what they hold). optical for the share box, dots and check.
  • Guard ci/check-ui-primitives.mjs (+ .test.mjs, CI step in the web job, no continue-on-error): fails a new <button><Glass/> pill, a hand-built pill-within-pill, or a new :active { scale }. Allow-list with a reason per place; the count may only go down.
  • Specs apps/web/e2e/pill-press.spec.ts and icons-extra.spec.ts; apps/web/src/lib/icons.test.ts.
  • To pay for the shared pieces: the old block editor's dead rules leave editor.css (~5.7 KB), icons no first-paint screen draws move to packages/ui/src/lib/icons-extra.ts (registered by the idle screens), Icon's optical size is CSS variables, more local :active copies go (Inspector swatch, BackdropGrid tile, tag chips, RowActions).

Files moved onto Pill / Toolbar / the one press

file before after
chrome/TopPills.svelte hand-made back, Share+⋯ group (.grp/.gb/.check), capsule :active back = Pill alone; right = PageToolbar; capsule .line keeps its own look (words; allow-listed) and presses through .pill
chrome/PhoneFoot.svelte .pill me press, .pill srch press Pill alone ×2; the tab strip stays (tabs, not buttons)
chrome/ListDock.svelte .pill round press (+) Pill alone; search field stays (words; allow-listed)
components/TrashPreview.svelte .pill round, .pill go with local :active flat Pill alone ×2
rows/BulkBar.svelte .act:active copy the press class it already had
ui/ColorPicker, EmptyState, Selector, toast/ToastRow, web/SignIn, web/LinkCard local :active { scale .9/.96/.97 } bounce
ui/styles/chrome.css .ib:active .96, .seg button:active .96 the shared rule (.97)
ui/styles/base.css .press .97 one rule, token, reduced motion

Left alone on purpose (guard allow-list, each with a reason): CopyLink (hover grows, press shrinks), the Settings dot and the Inspector slider thumb (an inner part presses), the editor's task box, SpringCheckBox. Other hand-made buttons (AppCard, TrashList, InfoSheet, InviteSheet, PasskeySection, NoteList pick-act, BulkBar/RowActions tiles) are PR 3 (Button/ActionTile); they are not pills of a toolbar.

#875 (open): base or coordinate?

Still open. It touches ContextMenu, Float, TapCatcher, NoteList, RowActions, tokens.css, tooltip.ts. git merge-tree of this branch (which now also edits RowActions' press) with origin/port/rows (#875), port/rows-actions (#877) and port/tasks-3-s5b (#893) is clean, so this PR is based on dev and either lands first.

Decisions

  • Prod's PageToolbar carries only what prod uses (onshare, items, done): the mockup's extra, badge, onmore, .hl (press lights the whole pill), the sheet-anchor mark, Pill dense/el come with their first user, because the first paint must not carry code nothing calls.
  • Toolbar's floating class is floats, not glass: base.css has a global .glass (backdrop blur) that made the toolbar a backdrop root and cut its own Glass layer off (caught by glass.spec.ts chromium-390).
  • Nothing to lazy-load: prod has no desktop half of the page toolbar; prod has no top-right [Share ⋯] pill on a desktop (TopPills is the phone chrome and no page registers Share yet), so there is nothing to move there; adding one is a feature for the sharing PRs, not this one.
  • Tab strip, capsule and the dock's search field stay hand-made: they hold words or tabs, not an icon.

Bytes, frames and gates: see the comments below

Latest numbers are in the last comment.

🤖 Generated with Claude Code

PR 1 of the #912 plan: the shared pill primitives from the approved mockup (`review/calendar-2`: #892, #936) in production. Based on `origin/dev` @ `9377d711`. **First paint vs dev: JS −447 B gzip (−108 br), CSS −685 B gzip (−664 br)**; see the comments for the per-change table, pixel diffs, frames and gates. ## What ships - `packages/ui/src/components/Pill.svelte`, `Toolbar.svelte`: the pill and the pill-within-a-pill. `alone` (floating glass, or `glass={false}` flat tint), `wide`, `h`, `tip`, `style`, `class`. - `apps/web/src/chrome/PageToolbar.svelte`: the one `[Share ⋯]` top-right pill (width spring, Done state), used by `TopPills`. - **The one press** (`packages/ui/src/styles/base.css`): `.press`, `.bounce`, `.pill`, `.ib`, `.seg button` share one rule: `scale: var(--press-scale, .97)`, `--ease-out`, 150 ms, `scale` only, reduced motion keeps the 3 % scale and drops the glide. - **One bar-icon size**: `--bar-glyph` 22 px / 2.1 line on a phone, 20 px / 1.9 on a desktop. `Icon` with no `size` now reads `--glyph` (16 px on the page; Pill and Toolbar raise it for what they hold). `optical` for the share box, dots and check. - **Guard** `ci/check-ui-primitives.mjs` (+ `.test.mjs`, CI step in the web job, no `continue-on-error`): fails a new `<button><Glass/>` pill, a hand-built pill-within-pill, or a new `:active { scale }`. Allow-list with a reason per place; the count may only go down. - Specs `apps/web/e2e/pill-press.spec.ts` and `icons-extra.spec.ts`; `apps/web/src/lib/icons.test.ts`. - To pay for the shared pieces: the old block editor's dead rules leave `editor.css` (~5.7 KB), icons no first-paint screen draws move to `packages/ui/src/lib/icons-extra.ts` (registered by the idle screens), Icon's optical size is CSS variables, more local `:active` copies go (Inspector swatch, BackdropGrid tile, tag chips, `RowActions`). ## Files moved onto Pill / Toolbar / the one press | file | before | after | |---|---|---| | `chrome/TopPills.svelte` | hand-made back, Share+⋯ group (`.grp`/`.gb`/`.check`), capsule `:active` | back = `Pill alone`; right = `PageToolbar`; capsule `.line` keeps its own look (words; allow-listed) and presses through `.pill` | | `chrome/PhoneFoot.svelte` | `.pill me press`, `.pill srch press` | `Pill alone` ×2; the tab strip stays (tabs, not buttons) | | `chrome/ListDock.svelte` | `.pill round press` (+) | `Pill alone`; search field stays (words; allow-listed) | | `components/TrashPreview.svelte` | `.pill round`, `.pill go` with local `:active` | flat `Pill alone` ×2 | | `rows/BulkBar.svelte` | `.act:active` copy | the `press` class it already had | | `ui/ColorPicker`, `EmptyState`, `Selector`, `toast/ToastRow`, `web/SignIn`, `web/LinkCard` | local `:active { scale .9/.96/.97 }` | `bounce` | | `ui/styles/chrome.css` | `.ib:active` .96, `.seg button:active` .96 | the shared rule (.97) | | `ui/styles/base.css` | `.press` .97 | one rule, token, reduced motion | Left alone on purpose (guard allow-list, each with a reason): CopyLink (hover grows, press shrinks), the Settings dot and the Inspector slider thumb (an inner part presses), the editor's task box, SpringCheckBox. Other hand-made buttons (AppCard, TrashList, InfoSheet, InviteSheet, PasskeySection, NoteList pick-act, BulkBar/RowActions tiles) are PR 3 (`Button`/`ActionTile`); they are not pills of a toolbar. ## #875 (open): base or coordinate? Still open. It touches `ContextMenu`, `Float`, `TapCatcher`, `NoteList`, `RowActions`, `tokens.css`, `tooltip.ts`. `git merge-tree` of this branch (which now also edits `RowActions`' press) with `origin/port/rows` (#875), `port/rows-actions` (#877) and `port/tasks-3-s5b` (#893) is clean, so this PR is based on `dev` and either lands first. ## Decisions - Prod's `PageToolbar` carries only what prod uses (`onshare`, `items`, `done`): the mockup's `extra`, `badge`, `onmore`, `.hl` (press lights the whole pill), the sheet-anchor mark, `Pill` `dense`/`el` come with their first user, because the first paint must not carry code nothing calls. - Toolbar's floating class is `floats`, not `glass`: base.css has a global `.glass` (backdrop blur) that made the toolbar a backdrop root and cut its own Glass layer off (caught by `glass.spec.ts` chromium-390). - Nothing to lazy-load: prod has no desktop half of the page toolbar; prod has no top-right `[Share ⋯]` pill on a desktop (TopPills is the phone chrome and no page registers Share yet), so there is nothing to move there; adding one is a feature for the sharing PRs, not this one. - Tab strip, capsule and the dock's search field stay hand-made: they hold words or tabs, not an icon. ## Bytes, frames and gates: see the comments below Latest numbers are in the last comment. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kayg added 19 commits 2026-11-04 19:06:52 +00:00
A plain Icon now reads --glyph / --glyph-stroke (16 px, 1.6 on the page), so a Pill or Toolbar can raise them to the bar-icon size for what it holds. An explicit size or stroke is unchanged. Adds the optical prop.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
.press, .bounce, .pill, .ib and .seg button share the rule in base.css; the .ib and .seg local copies (0.96) are removed. Adds the desktop bar-icon size (20 px).

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>
Profile, search and new-note pills are Pill alone; Back and Restore are flat Pills; the bulk tile's local :active copy is gone (its press class is the rule).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ColorPicker swatches, EmptyState action, Selector options, toast action, sign-in Continue, link-card actions. 0.9/0.96/0.97 -> 0.97.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
extra, badge, onmore and dense arrive with their first user.

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>
fix(ui): the dock's + keeps the accent colour over Pill's own (specificity, #912)
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
45394e39e3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Verdict (head 45394e39, base = dev 9377d711)

  • Bytes: NOT flat. First paint grows: JS +656 B gzip (+666 br, +1336 raw), CSS +291 B gzip (+218 br, +1335 raw). Total JS gzip +689 B, CSS +166 B. The plan's "new ≈ +1 KB, removed copies ≈ −1 KB" did not hold: Pill + Toolbar + PageToolbar + the Icon change add ≈ 2.8 KB minified to the first-paint chunk (per sourcemap: PageToolbar 1.6 KB, Pill 0.6, Toolbar 0.4, Icon 0.1) and the moved code removes ≈ 1.2 KB (TopPills −1.06, ListDock −0.08). I already cut the first version by about a third (dropped the mockup's extra/badge/onmore/.hl/dense/el/sheet-anchor mark, hoisted constants, one style string, TopPills back as a Pill). I do not have a clean way to offset the rest inside this scope. This PR should not merge under the no-regression rule unless kayg accepts about 1 KB gz of first-paint growth, or it lands after a diet PR (#906/#908) that pays for it. Measured with apps/web/scripts/bundle-size.mjs on bun run build of both trees.
  • Frames: flat. Mac VM (WebKit, 60 Hz, real GPU), 30 presses per target, ABAB × 3 rounds, frames counted only from pointer-down to 400 ms after release. Every target: p50 17 ms both sides, p95 22–24 ms both sides; frames over 25 ms: ⋯ 3.0 % → 2.6 %, back 3.3 → 2.2, new-note + 2.7 → 2.7, profile 2.8 → 2.2, search 2.6 → 2.6, .ib (1440) 6.8 → 6.4 (single-frame outliers up to several hundred ms on both sides are the test driver). Scale reaches 0.97 on every target (the .ib was 0.96). Intended change: the back pill used to snap (first scaled frame 4–8 ms after pointer-down, no transition); it now glides like the rest (22–25 ms, same as every other pill).
  • Gates: see below.

Pixel diffs (Chromium, 1440×900 and 402×874, light and dark, clock fixed, theme forced)

Files attached: base-*, head-*, diff-* (red = more than 8/255 off). 22 screens × 2 trees; screens: home, list, list ⋯ menu (phone), select mode, Trash preview, note.

Intended differences (all of them; anything not listed is identical):

screen what changes why
402 list, ⋯ menu, note, select back chevron 28 → 22 px (line 2.6 → 2.1); ⋯ box 26 → 22 (same line); + 24 → 22; dock search magnifier 21 → 22 one bar-icon size
402 select "Select all" pill ends: elliptical (border-radius: 50% on a 91 px pill) → true semicircles Pill is a full pill
402 home search glyph 21 → 22 one bar-icon size
402 and 1440 Trash preview Back: glass-flat fill + edge shadow → the flat Pill tint; chevron 28 → 22 (phone) / 20 (desktop) shared flat Pill, bar-icon size
1440 select mode the three bulk-bar tiles' icons 22 → 20 px, labels move up 1–2 px desktop bar-icon size
1440 list, note, home (some) a few pixels of the clock minute digit in the row subtitle (note mtime, differs per run) run noise, not the PR

Not different: 1440 sidebar, list, editor, bars (dark home is byte-identical); the Share button is not drawn anywhere in prod yet (no page registers Share), so the toolbar is the round ⋯ only, 44×44, as before.

Found and fixed while diffing: dock search icon fell back to 16 px, profile pill's avatar sat left, Restore lost its 18 px padding, the + lost its accent colour (specificity).

Gates

  • svelte-check: same 2 errors and 15 warnings as dev (all in review4-signout.spec.ts and others untouched), none new. web vitest 96 + ui primitives 55 passed. Guard test 4/4; guard clean on this tree.
  • Playwright, Chromium 390 + 1440, CI=1 --retries=0 --workers=1, 36 specs (list-chrome, bar-stays, phone-foot, phone-card-geometry, phone-bars, phone-targets, phone-edges, glass, glass-roots, chrome-blur, page-chrome, pill-room, note-pills, home-screen, select-rows, touch-select, trash, trash-header-geometry, trash-search, selector-fit, toast-bar, empty, tooltips, menu-first-press, safe-areas, signout, text-size, links, links-look, opacity, no-hscroll, tabs, settings-fit, swipe-rows, list-fit, list-search) + the new pill-press.spec.ts: 784 passed, 0 failed, 576 skipped at 45394e39. (An earlier run caught glass.spec chromium-390: Toolbar's class glass collided with the global .glass; fixed to floats.)
  • WebKit (dev-02), same set at 79859878: 773 passed, 4 failed. home-screen 320x568 (beforeAll hook 30 s timeout under load), phone-bars 390 settings plain dark night, signout offline, toast-bar leaves its screen. Repeat-each=10 on dev and on head (webkit-1440 for the three wide ones, webkit-390 for phone-bars): phone-bars 10/10 pass on both; the 20-run batch of the others had no failure on dev and one on head, the toast-bar one (the -g pattern matched two of the three titles, so home-screen 320x568 was not repeated; it was a 30 s hook timeout under load); so I ran 40: 1/40 on dev and 1/40 on head, a pre-existing flake, opened as #945. A rerun of the glass, glass-roots, select-rows, pill-press, phone-foot, list-chrome, trash set on the final head is queued behind the dev-02 train; I will add its result as a comment.
## Verdict (head `45394e39`, base = dev `9377d711`) - **Bytes: NOT flat.** First paint grows: JS **+656 B gzip** (+666 br, +1336 raw), CSS **+291 B gzip** (+218 br, +1335 raw). Total JS gzip +689 B, CSS +166 B. The plan's "new ≈ +1 KB, removed copies ≈ −1 KB" did not hold: Pill + Toolbar + PageToolbar + the Icon change add ≈ 2.8 KB minified to the first-paint chunk (per sourcemap: PageToolbar 1.6 KB, Pill 0.6, Toolbar 0.4, Icon 0.1) and the moved code removes ≈ 1.2 KB (TopPills −1.06, ListDock −0.08). I already cut the first version by about a third (dropped the mockup's `extra`/`badge`/`onmore`/`.hl`/`dense`/`el`/sheet-anchor mark, hoisted constants, one style string, `TopPills` back as a Pill). I do not have a clean way to offset the rest inside this scope. **This PR should not merge under the no-regression rule unless kayg accepts about 1 KB gz of first-paint growth, or it lands after a diet PR (#906/#908) that pays for it.** Measured with `apps/web/scripts/bundle-size.mjs` on `bun run build` of both trees. - **Frames: flat.** Mac VM (WebKit, 60 Hz, real GPU), 30 presses per target, ABAB × 3 rounds, frames counted only from pointer-down to 400 ms after release. Every target: p50 17 ms both sides, p95 22–24 ms both sides; frames over 25 ms: ⋯ 3.0 % → 2.6 %, back 3.3 → 2.2, new-note + 2.7 → 2.7, profile 2.8 → 2.2, search 2.6 → 2.6, `.ib` (1440) 6.8 → 6.4 (single-frame outliers up to several hundred ms on both sides are the test driver). Scale reaches 0.97 on every target (the `.ib` was 0.96). Intended change: the back pill used to snap (first scaled frame 4–8 ms after pointer-down, no transition); it now glides like the rest (22–25 ms, same as every other pill). - **Gates:** see below. ## Pixel diffs (Chromium, 1440×900 and 402×874, light and dark, clock fixed, theme forced) Files attached: `base-*`, `head-*`, `diff-*` (red = more than 8/255 off). 22 screens × 2 trees; screens: home, list, list ⋯ menu (phone), select mode, Trash preview, note. Intended differences (all of them; anything not listed is identical): | screen | what changes | why | |---|---|---| | 402 list, ⋯ menu, note, select | back chevron 28 → 22 px (line 2.6 → 2.1); ⋯ box 26 → 22 (same line); + 24 → 22; dock search magnifier 21 → 22 | one bar-icon size | | 402 select | "Select all" pill ends: elliptical (`border-radius: 50%` on a 91 px pill) → true semicircles | `Pill` is a full pill | | 402 home | search glyph 21 → 22 | one bar-icon size | | 402 and 1440 Trash preview | Back: glass-flat fill + edge shadow → the flat Pill tint; chevron 28 → 22 (phone) / 20 (desktop) | shared flat `Pill`, bar-icon size | | 1440 select mode | the three bulk-bar tiles' icons 22 → 20 px, labels move up 1–2 px | desktop bar-icon size | | 1440 list, note, home (some) | a few pixels of the clock minute digit in the row subtitle (note mtime, differs per run) | run noise, not the PR | Not different: 1440 sidebar, list, editor, bars (dark home is byte-identical); the Share button is not drawn anywhere in prod yet (no page registers Share), so the toolbar is the round ⋯ only, 44×44, as before. Found and fixed while diffing: dock search icon fell back to 16 px, profile pill's avatar sat left, Restore lost its 18 px padding, the + lost its accent colour (specificity). ## Gates - svelte-check: same 2 errors and 15 warnings as dev (all in `review4-signout.spec.ts` and others untouched), none new. web vitest 96 + ui primitives 55 passed. Guard test 4/4; guard clean on this tree. - Playwright, Chromium 390 + 1440, `CI=1 --retries=0 --workers=1`, 36 specs (list-chrome, bar-stays, phone-foot, phone-card-geometry, phone-bars, phone-targets, phone-edges, glass, glass-roots, chrome-blur, page-chrome, pill-room, note-pills, home-screen, select-rows, touch-select, trash, trash-header-geometry, trash-search, selector-fit, toast-bar, empty, tooltips, menu-first-press, safe-areas, signout, text-size, links, links-look, opacity, no-hscroll, tabs, settings-fit, swipe-rows, list-fit, list-search) + the new `pill-press.spec.ts`: **784 passed, 0 failed, 576 skipped** at `45394e39`. (An earlier run caught `glass.spec` chromium-390: `Toolbar`'s class `glass` collided with the global `.glass`; fixed to `floats`.) - WebKit (dev-02), same set at `79859878`: 773 passed, 4 failed. `home-screen 320x568` (beforeAll hook 30 s timeout under load), `phone-bars 390 settings plain dark night`, `signout offline`, `toast-bar leaves its screen`. Repeat-each=10 on dev and on head (webkit-1440 for the three wide ones, webkit-390 for phone-bars): `phone-bars` 10/10 pass on both; the 20-run batch of the others had no failure on dev and one on head, the `toast-bar` one (the `-g` pattern matched two of the three titles, so `home-screen 320x568` was not repeated; it was a 30 s hook timeout under load); so I ran 40: **1/40 on dev and 1/40 on head**, a pre-existing flake, opened as #945. A rerun of the glass, glass-roots, select-rows, pill-press, phone-foot, list-chrome, trash set on the final head is queued behind the dev-02 train; I will add its result as a comment.
Author
Owner

WebKit on the final head (45394e39, dev-02)

CI=1 --retries=0 --workers=1, webkit-390 + webkit-1440:

  • pill-press, phone-foot, list-chrome, trash, trash-header-geometry, bar-stays, phone-targets, phone-card-geometry, selector-fit, empty, links: 65 passed, 0 failed (53 skipped).
  • glass, glass-roots, select-rows: 55 passed, 0 failed (13 skipped).
    Earlier WebKit failures were toast-bar (pre-existing flake, 1/40 on dev and on head, #945), a 30 s hook timeout under load, and test-results clashes from two runs sharing one worktree.

Chromium on the same head: 784 passed, 0 failed (previous comment). Throwaway capture and probe scripts were not committed; the dev-02, dev-01 and Mac worktrees and builds I made are removed.

## WebKit on the final head (`45394e39`, dev-02) `CI=1 --retries=0 --workers=1`, webkit-390 + webkit-1440: - pill-press, phone-foot, list-chrome, trash, trash-header-geometry, bar-stays, phone-targets, phone-card-geometry, selector-fit, empty, links: **65 passed, 0 failed** (53 skipped). - glass, glass-roots, select-rows: **55 passed, 0 failed** (13 skipped). Earlier WebKit failures were `toast-bar` (pre-existing flake, 1/40 on dev and on head, #945), a 30 s hook timeout under load, and `test-results` clashes from two runs sharing one worktree. Chromium on the same head: 784 passed, 0 failed (previous comment). Throwaway capture and probe scripts were not committed; the dev-02, dev-01 and Mac worktrees and builds I made are removed.
Author
Owner

Update: first paint is now smaller than dev (head 83f4e377, base dev 9377d711)

first paint, vs dev raw gzip brotli
JS −1,723 B −447 B −108 B
CSS −4,631 B −685 B −664 B

apps/web/scripts/bundle-size.mjs on bun run build of both trees, same numbers on dev-01 and dev-02. perf/web.sh --bundle-only --gate passes (first JS 233.1 KB br ≤ 240, first CSS 18.15 KB br ≤ 22.1, total JS 354.4 KB br ≤ 404.2). Brotli moves ±100 B between neighbouring commits from the hashed file names in the chunk, so treat the JS brotli figure as "≤ 0 with about 100 B of margin"; gzip and raw are not affected by that.

Not first paint: all chunks together are JS +1,035 B gzip (+1,100 br): the icons that left the first paint now sit in the idle-screens chunk (47.7 → 49.2 KB gz; budget 50.5 KB br is met) and that chunk loads at idle. All CSS is −820 B gzip.

Per change (first-paint deltas, cumulative on the branch; JS gz/br, CSS gz/br)

step (commit) JS raw JS gz JS br CSS raw CSS gz CSS br
where #941 stood (45394e39) +1336 +656 +666 +1335 +291 +218
29 unused/lazy-only icons leave the Icon table (icons-extra) + Pill drops wide and the aria tint (25b81662) −2446 −867 −599 −203 −43 −36
the old block editor's rules out of editor.css: .block, .check, .handle, margin controls, drop lines (75778aad); Chromium CSS coverage over a session matched none of them, and nothing draws those classes 0 0 +51 −5719 −942 −819
Icon's optical size is two CSS variables, not a prop (37afd456) −195 −63 −136 +69 +31 +31
Inspector swatch, BackdropGrid tile, tag chip, RowActions buttons use the shared press (5a54eafc) +14 +7 −141 −129 −22 −28
five Settings-only icons (signout keyboard palette device update) move to icons-extra (071d3974) −480 −188 +30 0 0 −30
icons-extra registered by a call (a side-effect import is dropped by the build) and moved to lib/ (d53c55c1) +48 +8 +21 0 0 0
now −1723 −447 −108 −4631 −685 −664
(the numbers between rows are the differences of the measured totals; commits that only fixed lint/types/tests do not move the bytes.)

What I did, and what I did not

  1. Removed more copies: the local :active { scale } of the Inspector swatch, BackdropGrid tile, tag chips and RowActions buttons are gone (bounce/press); editor.css lost ~5.7 KB of the old block editor's rules. The guard's allow-list shrank: editor.css, BackdropGrid, RowActions entries removed, Inspector 2 → 1, cm.css 3 → 2.
  2. Kept shared pieces small: Icon's optical sizes are CSS variables; Pill lost wide and the pressed/expanded tint; icons that no first-paint screen draws are not in the table (a registry icons-extra.ts in the idle-screens chunk; the screens that draw them call registerExtraIcons() first; a spec checks Settings draws its own shapes, and icons.test.ts checks every literal <Icon name> exists and a name is in one table only).
  3. Left alone, with reasons (guard allow-list): CopyLink (hover grows 1.1 and press shrinks: on a shared rule the hover would win), the Settings dot and the slider thumb (an inner part presses, not the button), the editor's task box (.cm-task, its own 0.86 tick), SpringCheckBox's own spring press.
  4. #875: RowActions is migrated; git merge-tree of this branch with origin/port/rows (#875), port/rows-actions (#877) and port/tasks-3-s5b (#893) is clean, so no rebase on #875 was needed.
  5. "Lazy-load PageToolbar's desktop half": there is none. Prod's [Share ⋯] is the phone top row only (TopPills); no desktop top-right pill exists yet and no page registers Share. Pill, Toolbar and PageToolbar are first-paint code on phones. I did not lazy-load the phone chrome: it would add a request to a phone's first paint (and a 19th lazy import, which the build refuses).
  6. Bugs found on the way and fixed: icons-extra imported for its side effect was dropped by the build (package sideEffects is CSS only) and Settings drew the notes icon; PageToolbar lacked the WebKit-safe resting filter: blur(0) (the filter-motion lint caught it); Pill/Toolbar class typing.

Pixel diffs (final head; Chromium, 1440×900 and 402×874, light and dark; clock fixed, theme forced)

Same intended list as before and nothing new: bar-icon sizes (back 28 → 22, ⋯ 26 → 22, + 24 → 22, search 21 → 22 on a phone; 20 on a desktop's Trash preview Back and bulk-bar tiles), "Select all" pill ends elliptical → semicircles, Trash-preview Back is the flat Pill tint, plus the row-subtitle clock digit (run noise). Settings (every row icon) at 1440 is identical to dev (no pixel off by more than 3/255, from the live sky). Images attached: base-*, head-*, diff-*.

Frames (Mac VM, WebKit, 30 presses per target, ABAB × 3, head 071d3974): flat

p50 17 ms everywhere; p95 within 2 ms of dev on every target; frames over 25 ms, dev → head: ⋯ 1.7 → 1.6 %, back 2.3 → 2.1, + 2.2 → 2.6, profile 3.0 → 3.0, search 2.8 → 2.8, .ib 1440 5.7 → 6.1 (runs of the same comparison earlier went the other way by similar amounts: noise). Scale 0.97 on all (the .ib was 0.96); the back pill now glides (first scaled frame 24 ms, was a snap at 6 ms). Later commits (d53c55c1, 83f4e377) change registration and tests only.

Gates

  • svelte-check 2 errors / 15 warnings = dev; web vitest 98 passed (incl. icons.test.ts); ui primitives 55 passed; guard test 4/4 and guard clean; bun run lint (filter-motion) clean.
  • Chromium 390 + 1440, the 36-spec affected set plus pill-press and icons-extra, CI=1 --retries=0: 784 passed, 0 failed at d53c55c1; the only failure of that run was pill-press measuring the back chevron at 21.9 px while it springs in (a timing bug in my spec, fixed in 83f4e377; pill-press ×8 on both projects = 48/48).
  • WebKit 390 + 1440 at d53c55c1: 780 passed, 3 failed, none reproduce as a regression: signout offline is a known flake (#949: dev 4/20, head 5/20); glass-roots link card and list-search heading order timed out at 30 s under load and passed 10/10 on head and on dev in a repeat (×10 each). Earlier WebKit flakes (#945, #951: dev fails at least as often as head) are filed.
## Update: first paint is now smaller than dev (head `83f4e377`, base dev `9377d711`) | first paint, vs dev | raw | gzip | brotli | |---|---|---|---| | **JS** | −1,723 B | **−447 B** | **−108 B** | | **CSS** | −4,631 B | **−685 B** | **−664 B** | `apps/web/scripts/bundle-size.mjs` on `bun run build` of both trees, same numbers on dev-01 and dev-02. `perf/web.sh --bundle-only --gate` passes (first JS 233.1 KB br ≤ 240, first CSS 18.15 KB br ≤ 22.1, total JS 354.4 KB br ≤ 404.2). Brotli moves ±100 B between neighbouring commits from the hashed file names in the chunk, so treat the JS brotli figure as "≤ 0 with about 100 B of margin"; gzip and raw are not affected by that. **Not first paint:** all chunks together are JS **+1,035 B gzip** (+1,100 br): the icons that left the first paint now sit in the `idle-screens` chunk (47.7 → 49.2 KB gz; budget 50.5 KB br is met) and that chunk loads at idle. All CSS is −820 B gzip. ### Per change (first-paint deltas, cumulative on the branch; JS gz/br, CSS gz/br) | step (commit) | JS raw | JS gz | JS br | CSS raw | CSS gz | CSS br | |---|---|---|---|---|---|---| | where #941 stood (`45394e39`) | +1336 | +656 | +666 | +1335 | +291 | +218 | | 29 unused/lazy-only icons leave the Icon table (`icons-extra`) + Pill drops `wide` and the aria tint (`25b81662`) | −2446 | **−867** | −599 | −203 | −43 | −36 | | the old block editor's rules out of `editor.css`: `.block`, `.check`, `.handle`, margin controls, drop lines (`75778aad`); Chromium CSS coverage over a session matched none of them, and nothing draws those classes | 0 | 0 | +51 | −5719 | **−942** | **−819** | | Icon's optical size is two CSS variables, not a prop (`37afd456`) | −195 | −63 | −136 | +69 | +31 | +31 | | Inspector swatch, BackdropGrid tile, tag chip, `RowActions` buttons use the shared press (`5a54eafc`) | +14 | +7 | −141 | −129 | −22 | −28 | | five Settings-only icons (`signout keyboard palette device update`) move to `icons-extra` (`071d3974`) | −480 | −188 | +30 | 0 | 0 | −30 | | `icons-extra` registered by a call (a side-effect import is dropped by the build) and moved to `lib/` (`d53c55c1`) | +48 | +8 | +21 | 0 | 0 | 0 | | **now** | **−1723** | **−447** | **−108** | **−4631** | **−685** | **−664** | (the numbers between rows are the differences of the measured totals; commits that only fixed lint/types/tests do not move the bytes.) ### What I did, and what I did not 1. **Removed more copies:** the local `:active { scale }` of the Inspector swatch, BackdropGrid tile, tag chips and `RowActions` buttons are gone (`bounce`/`press`); `editor.css` lost ~5.7 KB of the old block editor's rules. The guard's allow-list **shrank**: `editor.css`, `BackdropGrid`, `RowActions` entries removed, `Inspector` 2 → 1, `cm.css` 3 → 2. 2. **Kept shared pieces small:** Icon's optical sizes are CSS variables; `Pill` lost `wide` and the pressed/expanded tint; icons that no first-paint screen draws are not in the table (a registry `icons-extra.ts` in the `idle-screens` chunk; the screens that draw them call `registerExtraIcons()` first; a spec checks Settings draws its own shapes, and `icons.test.ts` checks every literal `<Icon name>` exists and a name is in one table only). 3. **Left alone, with reasons (guard allow-list):** `CopyLink` (hover grows 1.1 and press shrinks: on a shared rule the hover would win), the Settings dot and the slider thumb (an inner part presses, not the button), the editor's task box (`.cm-task`, its own 0.86 tick), SpringCheckBox's own spring press. 4. **#875:** `RowActions` is migrated; `git merge-tree` of this branch with `origin/port/rows` (#875), `port/rows-actions` (#877) and `port/tasks-3-s5b` (#893) is clean, so no rebase on #875 was needed. 5. **"Lazy-load PageToolbar's desktop half":** there is none. Prod's `[Share ⋯]` is the phone top row only (`TopPills`); no desktop top-right pill exists yet and no page registers Share. Pill, Toolbar and PageToolbar are first-paint code on phones. I did not lazy-load the phone chrome: it would add a request to a phone's first paint (and a 19th lazy import, which the build refuses). 6. Bugs found on the way and fixed: `icons-extra` imported for its side effect was dropped by the build (package `sideEffects` is CSS only) and Settings drew the notes icon; PageToolbar lacked the WebKit-safe resting `filter: blur(0)` (the `filter-motion` lint caught it); `Pill`/`Toolbar` class typing. ## Pixel diffs (final head; Chromium, 1440×900 and 402×874, light and dark; clock fixed, theme forced) Same intended list as before and nothing new: bar-icon sizes (back 28 → 22, ⋯ 26 → 22, + 24 → 22, search 21 → 22 on a phone; 20 on a desktop's Trash preview Back and bulk-bar tiles), "Select all" pill ends elliptical → semicircles, Trash-preview Back is the flat Pill tint, plus the row-subtitle clock digit (run noise). Settings (every row icon) at 1440 is identical to dev (no pixel off by more than 3/255, from the live sky). Images attached: `base-*`, `head-*`, `diff-*`. ## Frames (Mac VM, WebKit, 30 presses per target, ABAB × 3, head `071d3974`): flat p50 17 ms everywhere; p95 within 2 ms of dev on every target; frames over 25 ms, dev → head: ⋯ 1.7 → 1.6 %, back 2.3 → 2.1, + 2.2 → 2.6, profile 3.0 → 3.0, search 2.8 → 2.8, `.ib` 1440 5.7 → 6.1 (runs of the same comparison earlier went the other way by similar amounts: noise). Scale 0.97 on all (the `.ib` was 0.96); the back pill now glides (first scaled frame 24 ms, was a snap at 6 ms). Later commits (`d53c55c1`, `83f4e377`) change registration and tests only. ## Gates - svelte-check 2 errors / 15 warnings = dev; web vitest 98 passed (incl. `icons.test.ts`); ui primitives 55 passed; guard test 4/4 and guard clean; `bun run lint` (filter-motion) clean. - **Chromium 390 + 1440, the 36-spec affected set plus `pill-press` and `icons-extra`, `CI=1 --retries=0`: 784 passed, 0 failed** at `d53c55c1`; the only failure of that run was `pill-press` measuring the back chevron at 21.9 px while it springs in (a timing bug in my spec, fixed in `83f4e377`; `pill-press` ×8 on both projects = 48/48). - **WebKit 390 + 1440 at `d53c55c1`: 780 passed, 3 failed**, none reproduce as a regression: `signout offline` is a known flake (#949: dev 4/20, head 5/20); `glass-roots link card` and `list-search heading order` timed out at 30 s under load and passed 10/10 on head and on dev in a repeat (×10 each). Earlier WebKit flakes (#945, #951: dev fails at least as often as head) are filed.
Author
Owner

#941 per-PR gates: GREEN at 5662cd00

Head = 3ff49fb4 + merge of origin/dev b5e8dcb2 (no conflicts). Branch alone vs dev; CI=1 --retries=0 --workers=1 through ~/bin/slot.

gate result
lint / svelte-check / vitest / ui primitives pass / 2 errors, 16 warnings = dev's / 109 + 55 pass
Chromium (dev-01) 1440+390, 22 specs (bar-stays, icons-extra, list-chrome, pill-press, note-pills, note-bars, note-bars-fit, bar-rest, bar-focus, bar-taps, chrome-blur, glass, sheet-geometry, toast-bar, select-rows, phone-bars, phone-foot, pill-room, page-chrome, swipe-rows, trash-search, keyboard) 478 passed, 352 skipped (project-specific), 0 failed
WebKit (dev-02) same specs 474 passed, 356 skipped, 0 failed
perf/web.sh --bundle-only --gate pass. First-paint JS brotli 232.479 KB vs dev 232.862 (−383 B), first CSS 17.970 vs 18.571 (−601 B)
node ci/check-ui-primitives.mjs 3 findings, all already in dev's files (dev alone has 25): styles/note-bars-lazy.css:63,122, styles/note-bars.css:55 (BM14e's local :active { scale }). None are new from this PR, but they make the new CI job red as soon as it lands: they need bounce or the shared press (owner's call)
Rust perf n/a

For the PRs behind this one: against this check #903 (styles/code-block.css:89), #832 (tasks/TasksScreen.svelte:69) and #841 (contacts/ContactsScreen.svelte:90) each add a NEW finding; they move to <Pill alone> / the shared press once #941 is in dev.

## #941 per-PR gates: GREEN at `5662cd00` Head = `3ff49fb4` + merge of origin/dev `b5e8dcb2` (no conflicts). Branch alone vs dev; `CI=1 --retries=0 --workers=1` through `~/bin/slot`. | gate | result | |---|---| | lint / svelte-check / vitest / ui primitives | pass / 2 errors, 16 warnings = dev's / 109 + 55 pass | | Chromium (dev-01) 1440+390, 22 specs (bar-stays, icons-extra, list-chrome, pill-press, note-pills, note-bars, note-bars-fit, bar-rest, bar-focus, bar-taps, chrome-blur, glass, sheet-geometry, toast-bar, select-rows, phone-bars, phone-foot, pill-room, page-chrome, swipe-rows, trash-search, keyboard) | 478 passed, 352 skipped (project-specific), 0 failed | | WebKit (dev-02) same specs | 474 passed, 356 skipped, 0 failed | | `perf/web.sh --bundle-only --gate` | pass. First-paint JS brotli **232.479 KB vs dev 232.862 (−383 B)**, first CSS **17.970 vs 18.571 (−601 B)** | | `node ci/check-ui-primitives.mjs` | **3 findings, all already in dev's files** (dev alone has 25): `styles/note-bars-lazy.css:63,122`, `styles/note-bars.css:55` (BM14e's local `:active { scale }`). None are new from this PR, but they make the new CI job red as soon as it lands: they need `bounce` or the shared press (owner's call) | | Rust perf | n/a | For the PRs behind this one: against this check #903 (`styles/code-block.css:89`), #832 (`tasks/TasksScreen.svelte:69`) and #841 (`contacts/ContactsScreen.svelte:90`) each add a NEW finding; they move to `<Pill alone>` / the shared press once #941 is in dev.
Author
Owner

Orchestrator: GREEN at 5662cd00, but the merge now conflicts in apps/web/src/chrome/TopPills.svelte with #926, which has just merged into dev. Merge dev in, resolve the conflict keeping both #926 (pills held while typing) and the shared Pill, then rerun the TopPills / typing-pills / pill-press specs in both browsers.

Orchestrator: GREEN at 5662cd00, but the merge now conflicts in `apps/web/src/chrome/TopPills.svelte` with #926, which has just merged into dev. Merge dev in, resolve the conflict keeping both #926 (pills held while typing) and the shared Pill, then rerun the TopPills / typing-pills / pill-press specs in both browsers.
Author
Owner

#941 re-gated after merging dev 789fdaa9 (#926 + #946 + #932 + #967): GREEN at 01d4a61c

Conflicts resolved, both intents kept: TopPills.svelte (#926's holdAtVisualTop, held only while data-held, with the shared Pill; dropped imports the Pill made dead), InfoSheet / InsertSheet / StyleSheet (sheetDialog() from #946 and registerExtraIcons()), base.css (#946's .visually-hidden comment + the shared press), Icon.svelte (history stays in the first table), editor.css (the dead block-editor rules stay removed), vite.config.ts (dialog-focus and icons-extra both in idle-screens).
Also in this PR, for its new CI job: the 3 findings in note-bars.css:55 / note-bars-lazy.css:63,122 now use the shared press (bounce on .nb-act, .nb-btn, .nb-chip; the local :active { scale } rules are gone).

gate result
lint / svelte-check / vitest / ui primitives pass / 2 errors, 16 warnings = dev's / 109 + 56 pass
node ci/check-ui-primitives.mjs clean: "no hand-made pills or local presses"
perf/web.sh --bundle-only --gate pass. First-paint JS brotli 232.565 vs dev 232.953 KB (−388 B), first CSS 17.952 vs 18.376 (−424 B)
Chromium (dev-01) 1440+390, 25 specs (the 22 earlier + a11y-high, search-palette, task-check, style-presets) all pass except a11y-high:107 "Style: labelled…" (1440, 390): the focus-loop spec that is being redesigned; it fails on dev the same way. Run split in two (the first hit its 2 h unit limit while queued behind the daily train); second part: 410 passed, 0 failed
WebKit (dev-02) same specs 513 passed, 368 skipped, 1 failed: the same a11y-high:107 Style spec (390)
Rust perf n/a

One budget move, no raise: idle-screens line 50.5 → 51.3 (icons-extra and #946's dialog-focus share that chunk, 51.245 KB measured) taken from the index line 240 → 239.2 (it measures 232.5). Total stays 404.2.

## #941 re-gated after merging dev `789fdaa9` (#926 + #946 + #932 + #967): GREEN at `01d4a61c` Conflicts resolved, both intents kept: `TopPills.svelte` (#926's `holdAtVisualTop`, held only while `data-held`, with the shared Pill; dropped imports the Pill made dead), `InfoSheet` / `InsertSheet` / `StyleSheet` (`sheetDialog()` from #946 and `registerExtraIcons()`), `base.css` (#946's `.visually-hidden` comment + the shared press), `Icon.svelte` (`history` stays in the first table), `editor.css` (the dead block-editor rules stay removed), `vite.config.ts` (`dialog-focus` and `icons-extra` both in `idle-screens`). Also in this PR, for its new CI job: the 3 findings in `note-bars.css:55` / `note-bars-lazy.css:63,122` now use the shared press (`bounce` on `.nb-act`, `.nb-btn`, `.nb-chip`; the local `:active { scale }` rules are gone). | gate | result | |---|---| | lint / svelte-check / vitest / ui primitives | pass / 2 errors, 16 warnings = dev's / 109 + 56 pass | | `node ci/check-ui-primitives.mjs` | **clean: "no hand-made pills or local presses"** | | `perf/web.sh --bundle-only --gate` | pass. First-paint JS brotli **232.565 vs dev 232.953 KB (−388 B)**, first CSS **17.952 vs 18.376 (−424 B)** | | Chromium (dev-01) 1440+390, 25 specs (the 22 earlier + a11y-high, search-palette, task-check, style-presets) | all pass except `a11y-high:107` "Style: labelled…" (1440, 390): the focus-loop spec that is being redesigned; it fails on dev the same way. Run split in two (the first hit its 2 h unit limit while queued behind the daily train); second part: 410 passed, 0 failed | | WebKit (dev-02) same specs | 513 passed, 368 skipped, 1 failed: the same `a11y-high:107` Style spec (390) | | Rust perf | n/a | One budget move, no raise: `idle-screens` line 50.5 → 51.3 (icons-extra and #946's dialog-focus share that chunk, 51.245 KB measured) taken from the `index` line 240 → 239.2 (it measures 232.5). Total stays 404.2.
Author
Owner

Merged into dev by the orchestrator (U1 GREEN at 01d4a61c).

Merged into dev by the orchestrator (U1 GREEN at 01d4a61c).
kayg closed this pull request 2026-11-05 20:23:35 +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!941
No description provided.