a11y: task checkbox, sheet focus, palette semantics (#935 High) #946

Closed
kayg wants to merge 0 commits from fix/a11y-high-935 into dev
Owner

Fixes the three High findings of the prod UX audit (#935). No new UI; behaviour and markup only.

What changed

1. Task checkbox in the Notes editor (packages/editor/src/syntax/widgets.ts, transforms/tasks.ts, apps/web/src/styles/cm.css)

  • The box is a Tab stop (tabindex=0); Space and Enter flip it. The key is handled on the box itself, so Enter never splits the list line, and typing is untouched. A keyboard flip is instant (no spring); a pointer press still plays the spring check.
  • Name = the task's words: Task: Renew passport, Cancelled task: … (links and inline marks reduced to text, cut at 80 characters). It is read when the box gets focus and when it is drawn, never per keystroke.
  • ≥44 pt hit area on touch / phone widths through an invisible ::after (inset: -14px); the drawn box stays 18 px. Accent focus ring like every other control (hidden after a pointer press, as elsewhere).

2. Sheets (packages/ui/src/components/Morph.svelte, new packages/ui/src/lib/dialog-focus.ts)

  • Every sheet is the one Morph capsule, so the fix lives there: while its role is dialog it gets aria-modal="true", aria-labelledby (a hidden title that follows the sheet's name), focus moves in, Tab/Shift+Tab stay inside, and on close focus returns to the opener (the tool button is found again after the bar swaps its layers). Esc is still the app's (BottomBar).
  • The rules are lib/dialog-focus.ts, called by each lazy sheet and placed in the idle-screens chunk, so the first paint carries only a 3-line hook that records the opener.
  • Phone, by design (#268): when the text holds focus as a sheet opens, it keeps it (keyboard stays up, bar mode does not flip) and is not refocused on close; a sheet opened from a button takes focus. Tab pulls focus in on a phone too.

3. Search palette and phone list search (composer/FoundList.svelte, ComposerBody.svelte, components/ListSearch.svelte)

  • The <button> is now the role=option itself (li role=none), so no option contains anything interactive (axe nested-interactive). The field is the combobox: aria-autocomplete="list", aria-controls, aria-expanded, and aria-activedescendant = the highlighted option (cmp-opt-N / ls-N).

Decisions

  • Added axe-core as an apps/web devDependency (specs only, not shipped).
  • The lazy-import check stays at 18: the sheet focus rules are called by each lazy sheet, not imported.
  • Existing specs updated for the new names: task-check (Cancelled task: cancelled), search-palette (the option is the button).
  • Chose not to run the new focus rules for the pointer-open case on a phone (see above) because bar-focus / lazy-styled encode #268.

Tests

apps/web/e2e/a11y-high.spec.ts (Chromium + WebKit, 1440 and a 402 pt phone), with axe on the markup each fix changed:

  • task box: Tab reaches it, Space/Enter flip it (server text checked), Enter does not split the line, name from the words, typing unaffected, axe; touch: every point of a 44 × 44 square around the box hits it and a tap in the padding flips it;
  • sheets (Note info, Style, Insert): aria-modal, aria-labelledby resolves to the name, focus inside after open, 40 Tab + 40 Shift+Tab never leave, axe, Esc closes and the opener button is focused; desktop pointer open returns focus to the editor;
  • palette: aria-activedescendant follows ArrowDown to an aria-selected option, focus stays in the field, axe (nested-interactive and the ARIA structure rules); phone list search the same.

Also in this PR (to offset the first-paint bytes): the editor baseTheme, mockup-only CSS and two unused icons are removed; details per step and gates are in the comments below.

Fixes the three High findings of the prod UX audit (#935). No new UI; behaviour and markup only. ## What changed **1. Task checkbox in the Notes editor** (`packages/editor/src/syntax/widgets.ts`, `transforms/tasks.ts`, `apps/web/src/styles/cm.css`) - The box is a Tab stop (`tabindex=0`); **Space and Enter** flip it. The key is handled on the box itself, so Enter never splits the list line, and typing is untouched. A keyboard flip is instant (no spring); a pointer press still plays the spring check. - Name = the task's words: `Task: Renew passport`, `Cancelled task: …` (links and inline marks reduced to text, cut at 80 characters). It is read when the box gets focus and when it is drawn, never per keystroke. - ≥44 pt hit area on touch / phone widths through an invisible `::after` (`inset: -14px`); the drawn box stays 18 px. Accent focus ring like every other control (hidden after a pointer press, as elsewhere). **2. Sheets** (`packages/ui/src/components/Morph.svelte`, new `packages/ui/src/lib/dialog-focus.ts`) - Every sheet is the one `Morph` capsule, so the fix lives there: while its role is `dialog` it gets `aria-modal="true"`, `aria-labelledby` (a hidden title that follows the sheet's name), focus moves in, **Tab/Shift+Tab stay inside**, and on close **focus returns to the opener** (the tool button is found again after the bar swaps its layers). Esc is still the app's (BottomBar). - The rules are `lib/dialog-focus.ts`, called by each lazy sheet and placed in the `idle-screens` chunk, so the first paint carries only a 3-line hook that records the opener. - Phone, by design (#268): when the text holds focus as a sheet opens, it keeps it (keyboard stays up, bar mode does not flip) and is not refocused on close; a sheet opened from a button takes focus. Tab pulls focus in on a phone too. **3. Search palette and phone list search** (`composer/FoundList.svelte`, `ComposerBody.svelte`, `components/ListSearch.svelte`) - The `<button>` is now the `role=option` itself (`li role=none`), so no option contains anything interactive (axe `nested-interactive`). The field is the combobox: `aria-autocomplete="list"`, `aria-controls`, `aria-expanded`, and `aria-activedescendant` = the highlighted option (`cmp-opt-N` / `ls-N`). ## Decisions - Added `axe-core` as an `apps/web` **devDependency** (specs only, not shipped). - The lazy-import check stays at 18: the sheet focus rules are called by each lazy sheet, not imported. - Existing specs updated for the new names: `task-check` (`Cancelled task: cancelled`), `search-palette` (the option is the button). - Chose not to run the new focus rules for the pointer-open case on a phone (see above) because `bar-focus` / `lazy-styled` encode #268. ## Tests `apps/web/e2e/a11y-high.spec.ts` (Chromium + WebKit, 1440 and a 402 pt phone), with axe on the markup each fix changed: - task box: Tab reaches it, Space/Enter flip it (server text checked), Enter does not split the line, name from the words, typing unaffected, axe; touch: every point of a 44 × 44 square around the box hits it and a tap in the padding flips it; - sheets (Note info, Style, Insert): `aria-modal`, `aria-labelledby` resolves to the name, focus inside after open, 40 Tab + 40 Shift+Tab never leave, axe, Esc closes and the **opener button is focused**; desktop pointer open returns focus to the editor; - palette: `aria-activedescendant` follows ArrowDown to an `aria-selected` option, focus stays in the field, axe (`nested-interactive` and the ARIA structure rules); phone list search the same. Also in this PR (to offset the first-paint bytes): the editor `baseTheme`, mockup-only CSS and two unused icons are removed; details per step and gates are in the comments below.
kayg added 19 commits 2026-11-04 21:16:22 +00:00
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>
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>
perf(ui): Morph keeps only a hook; the dialog attributes and title come with the lazy focus rules (#935)
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
cb007ba469
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Gate results (head cb007ba4, dev-01, CI=1 --retries=0 --workers=1, through ~/bin/slot; backend binary built from this tree)

Gate Result
New spec e2e/a11y-high.spec.ts + 26 existing specs touching bars, sheets, palette, tasks, editor, lazy-ui (--project=chromium-1440 chromium-390) 327 passed, 0 failed
Same list, webkit-1440 webkit-390 319 passed, 1 failed (below)
vitest web 96, editor 511, ui 89 all pass
node --test scripts/test/*.test.mjs and filter-motion.mjs pass
svelte-check 2 errors + 15 warnings, identical to origin/dev (errors are in review4-signout.spec.ts; none in changed files)
action-types.mjs --check fails the same way on origin/dev ("actions.gen.ts is out of date"), not touched here

Specs run: a11y-high task-check keys-tab keys-rings dialog-stable bar-focus bar-taps bar-stays bar-stable search-palette composer composer-tasks list-search info sheet-geometry lazy-styled lazy-sheet-recovery lazy-ui lazy-offline preload link-suggest picker phone-targets glass-roots shortcuts editor toast-bar keys-no-motion. WebKit ran on dev-01 (Playwright WebKit), not dev-02, to avoid an aarch64 backend build; phone project width 402 inside the spec (project default 390).

The one WebKit failure: composer-tasks.spec.ts:115 "a line about a person makes a contact card, Undo deletes it" (webkit-1440; Undo button never shows). --repeat-each=10 on this branch: 10/10 fail; on origin/dev with the same binary: 10/10 fail. So it is not caused by this PR; it needs its own issue (contacts.create toast on WebKit).

First-paint bytes vs origin/dev (bundle-size.mjs, brotli q11)

dev this PR delta
first-paint JS (official, hashes as built) 238 797 239 189 +392 B
first-paint JS (asset hashes normalised) 238 804 238 937 +133 B
first-paint JS raw 845 550 846 106 +556 B
first-paint CSS 19 250 19 269 +19 B
idle-screens (lazy) 40 129 grows (holds dialog-focus) lazy, not first paint

Verdict: first paint grows slightly (+133 B real code, +392 B as built; +19 B CSS). It stays inside the budgets (240 KB JS, 22.1 KB CSS) and I did not touch budgets. About 260 B of the official JS figure is hash noise: rebuilding dev with the lazy-chunk files unchanged but different content hashes moves the index chunk by ~180 B with identical raw bytes. I cut it down from +782 B by moving the sheet rules into the lazy idle-screens chunk, drawing the task mark from one parsed string, and keeping only a hook in Morph. The remaining growth is the task box's keys and name (index) and the Morph hook; I could not remove it without dropping a requested behaviour. Needs your call, or an offset from the first-paint diet PRs (#906/#908).

Typing and frame times: the task name is computed on focus only, nothing was added to the per-key path. I did not run the perf VMs or the Mac frame harness (no animation or typing path changed; the spring check CSS is unchanged).

## Gate results (head `cb007ba4`, dev-01, `CI=1 --retries=0 --workers=1`, through `~/bin/slot`; backend binary built from this tree) | Gate | Result | |---|---| | New spec `e2e/a11y-high.spec.ts` + 26 existing specs touching bars, sheets, palette, tasks, editor, lazy-ui (`--project=chromium-1440 chromium-390`) | **327 passed, 0 failed** | | Same list, `webkit-1440 webkit-390` | **319 passed, 1 failed** (below) | | `vitest` web 96, editor 511, ui 89 | all pass | | `node --test scripts/test/*.test.mjs` and `filter-motion.mjs` | pass | | `svelte-check` | 2 errors + 15 warnings, **identical to origin/dev** (errors are in `review4-signout.spec.ts`; none in changed files) | | `action-types.mjs --check` | fails the same way on origin/dev ("actions.gen.ts is out of date"), not touched here | Specs run: a11y-high task-check keys-tab keys-rings dialog-stable bar-focus bar-taps bar-stays bar-stable search-palette composer composer-tasks list-search info sheet-geometry lazy-styled lazy-sheet-recovery lazy-ui lazy-offline preload link-suggest picker phone-targets glass-roots shortcuts editor toast-bar keys-no-motion. WebKit ran on dev-01 (Playwright WebKit), not dev-02, to avoid an aarch64 backend build; phone project width 402 inside the spec (project default 390). **The one WebKit failure:** `composer-tasks.spec.ts:115` "a line about a person makes a contact card, Undo deletes it" (webkit-1440; Undo button never shows). `--repeat-each=10` on this branch: 10/10 fail; on origin/dev with the same binary: **10/10 fail**. So it is not caused by this PR; it needs its own issue (contacts.create toast on WebKit). ## First-paint bytes vs origin/dev (`bundle-size.mjs`, brotli q11) | | dev | this PR | delta | |---|---|---|---| | first-paint JS (official, hashes as built) | 238 797 | 239 189 | **+392 B** | | first-paint JS (asset hashes normalised) | 238 804 | 238 937 | **+133 B** | | first-paint JS raw | 845 550 | 846 106 | +556 B | | first-paint CSS | 19 250 | 19 269 | **+19 B** | | idle-screens (lazy) | 40 129 | grows (holds dialog-focus) | lazy, not first paint | **Verdict: first paint grows slightly (+133 B real code, +392 B as built; +19 B CSS). It stays inside the budgets (240 KB JS, 22.1 KB CSS) and I did not touch budgets.** About 260 B of the official JS figure is hash noise: rebuilding dev with the lazy-chunk files unchanged but different content hashes moves the index chunk by ~180 B with identical raw bytes. I cut it down from +782 B by moving the sheet rules into the lazy `idle-screens` chunk, drawing the task mark from one parsed string, and keeping only a hook in `Morph`. The remaining growth is the task box's keys and name (index) and the Morph hook; I could not remove it without dropping a requested behaviour. Needs your call, or an offset from the first-paint diet PRs (#906/#908). Typing and frame times: the task name is computed on focus only, nothing was added to the per-key path. I did not run the perf VMs or the Mac frame harness (no animation or typing path changed; the spring check CSS is unchanged).
Author
Owner

Round 2: rule fixes (head 8812570f, base origin/dev = 9377d711)

Note: you named 41f2efa3 as the dev base. That commit is "Merge train BM13", an ancestor of the dev tip; origin/dev is 9377d711 and this branch is on it, so all numbers below are against 9377d711 (built the same way, same host).

1. Lazy-import count: back to 18

apps/web/scripts/lazy-ui-plugin.ts is identical to dev again. The sheet focus rules are no longer imported with import(): each lazy sheet (Inspector, InsertSheet, InfoSheet, NotePicker, all in idle-screens) calls sheetDialog() from packages/ui/src/lib/dialog-focus.ts, and that file is placed in idle-screens by the existing group in vite.config.ts (the regex now lists it next to move-to-note). Morph only records who had focus when a sheet opens ($effect.pre, 3 lines, in lib/dialog-opener.ts).

2. First-paint bytes vs dev (bundle-size.mjs, brotli q11 / gzip 9; same host, same Node)

dev this PR delta
first-paint JS raw 845 550 845 107 −443
first-paint JS gzip 281 549 281 545 −4
first-paint JS brotli, hashes normalised 238 804 238 705 −99
first-paint JS brotli, as built 238 797 238 818 +21
first-paint CSS raw 109 391 107 931 −1 460
first-paint CSS gzip 21 990 21 734 −256
first-paint CSS brotli 19 250 19 009 −241

So raw, gzip, CSS in every unit, and brotli with the asset-name hashes held constant are all at or below dev. As-built JS brotli is +21 B, which I could not close: brotli q11 on an 845 KB chunk jitters by about ±150 B between builds whose content differs by a few bytes (table below: a change that removes 17 raw bytes moved as-built brotli by +33 B and a change that removes 230 raw moved it by +270 B). I stopped trimming at the point where each further saving was smaller than that noise. If you want it strictly ≤ 0 as built, the one big lever left is the 6 mockup-only icons in the shared Icon.svelte (about 0.5 KB br), which I did not take because the mockup still draws them.

What I took out, and each step's number

(raw / gzip not shown for brevity where equal; "norm" = brotli with hashes normalised; negative is smaller; JS first-paint)

step commit what raw as-built br norm br CSS br
start cb007ba4 round 1 head +556 +392 +133 +19
1 f22ad723 no import(): sheets call sheetDialog() +378 +204 +75 +19
2 1acd998f task box uses the global keyboard ring (my own ring rule was redundant: base.css already rings :focus-visible under data-input=keyboard), role/tabindex in the template, no stopPropagation +360 +257 +221 −27
3 bf29a483 drop editor/src/theme.ts baseTheme: every rule in it (.cm-task, .cm-bullet, .cm-block-pill, .cm-block-handle) is overridden by cm.css under .page; the only other consumer (mockup) styles none of those classes −68 +152 −53
4 64b3f0c3 drop CSS for classes the product never renders (mockup leftovers): .margin-controls*, .handle*, .drop-line*, .block.dragged/.drop-target, .cm-rule −68 +13 −53 −241 (raw −1 460)
5 f7fa2dad task box drawn and named once; no redundant guard −90 +24 +29
6 6d2b81ea Morph enter/exit share 3 fade functions instead of 7 copies −215 +294 −45
7 3f45040d drop icons star and swatch (referenced nowhere, mockup or any open branch) −526 +108 −81
final 8812570f opener read as the sheet opens again (the sheets' chunk can arrive after the first open) −443 +21 −99 −241

(I tried one more trim, "read the mark from the line in hand": −17 raw, +33 as-built, so it was reverted.)

3. Gates (dev-01, CI=1 --retries=0 --workers=1, via ~/bin/slot, backend built from this tree; same 27-spec list as before plus lazy-offline and preload)

  • a11y-high.spec.ts, Chromium + WebKit, 1440 + 402: 28 passed (this run also checks that the focused task box wears a solid keyboard ring).
  • Whole list, Chromium 1440 + 390: 327 passed, 0 failed.
  • Whole list, WebKit 1440 + 390: 316 passed, 4 failed, all the same symptom ("Undo" toast gone before it could be pressed): composer-tasks.spec:51 ×2, composer-tasks.spec:115, composer.spec:51. Flaky rule, --repeat-each=10 on webkit-1440: this branch 2 of 30 fail, origin/dev 10 of 30 fail, so they are not from this PR; the cause is the toast clock and is fixed in #950 (issue #948), where composer-tasks:115 goes from 10/10 failing to 10/10 passing.
  • vitest web 96, editor 511, ui 89 pass; node --test scripts/test/*.test.mjs and filter-motion.mjs pass; svelte-check 2 errors + 15 warnings, identical to dev (none in changed files).
## Round 2: rule fixes (head `8812570f`, base `origin/dev` = `9377d711`) Note: you named `41f2efa3` as the dev base. That commit is "Merge train BM13", an ancestor of the dev tip; `origin/dev` is `9377d711` and this branch is on it, so all numbers below are against `9377d711` (built the same way, same host). ### 1. Lazy-import count: back to 18 `apps/web/scripts/lazy-ui-plugin.ts` is identical to dev again. The sheet focus rules are no longer imported with `import()`: each lazy sheet (`Inspector`, `InsertSheet`, `InfoSheet`, `NotePicker`, all in `idle-screens`) calls `sheetDialog()` from `packages/ui/src/lib/dialog-focus.ts`, and that file is placed in `idle-screens` by the existing group in `vite.config.ts` (the regex now lists it next to `move-to-note`). `Morph` only records who had focus when a sheet opens (`$effect.pre`, 3 lines, in `lib/dialog-opener.ts`). ### 2. First-paint bytes vs dev (`bundle-size.mjs`, brotli q11 / gzip 9; same host, same Node) | | dev | this PR | delta | |---|---|---|---| | first-paint JS raw | 845 550 | 845 107 | **−443** | | first-paint JS gzip | 281 549 | 281 545 | **−4** | | first-paint JS brotli, hashes normalised | 238 804 | 238 705 | **−99** | | first-paint JS brotli, as built | 238 797 | 238 818 | **+21** | | first-paint CSS raw | 109 391 | 107 931 | **−1 460** | | first-paint CSS gzip | 21 990 | 21 734 | **−256** | | first-paint CSS brotli | 19 250 | 19 009 | **−241** | So raw, gzip, CSS in every unit, and brotli with the asset-name hashes held constant are all at or below dev. **As-built JS brotli is +21 B**, which I could not close: brotli q11 on an 845 KB chunk jitters by about ±150 B between builds whose content differs by a few bytes (table below: a change that removes 17 raw bytes moved as-built brotli by +33 B and a change that removes 230 raw moved it by +270 B). I stopped trimming at the point where each further saving was smaller than that noise. If you want it strictly ≤ 0 as built, the one big lever left is the 6 mockup-only icons in the shared `Icon.svelte` (about 0.5 KB br), which I did not take because the mockup still draws them. ### What I took out, and each step's number (raw / gzip not shown for brevity where equal; "norm" = brotli with hashes normalised; negative is smaller; JS first-paint) | step | commit | what | raw | as-built br | norm br | CSS br | |---|---|---|---|---|---|---| | start | cb007ba4 | round 1 head | +556 | +392 | +133 | +19 | | 1 | f22ad723 | no `import()`: sheets call `sheetDialog()` | +378 | +204 | +75 | +19 | | 2 | 1acd998f | task box uses the **global keyboard ring** (my own ring rule was redundant: `base.css` already rings `:focus-visible` under `data-input=keyboard`), `role`/`tabindex` in the template, no `stopPropagation` | +360 | +257 | +221 | −27 | | 3 | bf29a483 | drop `editor/src/theme.ts` `baseTheme`: every rule in it (`.cm-task`, `.cm-bullet`, `.cm-block-pill`, `.cm-block-handle`) is overridden by `cm.css` under `.page`; the only other consumer (mockup) styles none of those classes | −68 | +152 | −53 | | | 4 | 64b3f0c3 | drop CSS for classes the product never renders (mockup leftovers): `.margin-controls*`, `.handle*`, `.drop-line*`, `.block.dragged/.drop-target`, `.cm-rule` | −68 | +13 | −53 | **−241** (raw −1 460) | | 5 | f7fa2dad | task box drawn and named once; no redundant guard | −90 | +24 | +29 | | | 6 | 6d2b81ea | `Morph` enter/exit share 3 fade functions instead of 7 copies | −215 | +294 | −45 | | | 7 | 3f45040d | drop icons `star` and `swatch` (referenced nowhere, mockup or any open branch) | −526 | +108 | −81 | | | final | 8812570f | opener read as the sheet opens again (the sheets' chunk can arrive after the first open) | −443 | +21 | −99 | −241 | (I tried one more trim, "read the mark from the line in hand": −17 raw, +33 as-built, so it was reverted.) ### 3. Gates (dev-01, `CI=1 --retries=0 --workers=1`, via `~/bin/slot`, backend built from this tree; same 27-spec list as before plus `lazy-offline` and `preload`) - `a11y-high.spec.ts`, Chromium + WebKit, 1440 + 402: **28 passed** (this run also checks that the focused task box wears a solid keyboard ring). - Whole list, Chromium 1440 + 390: **327 passed, 0 failed**. - Whole list, WebKit 1440 + 390: **316 passed, 4 failed**, all the same symptom ("Undo" toast gone before it could be pressed): `composer-tasks.spec:51` ×2, `composer-tasks.spec:115`, `composer.spec:51`. Flaky rule, `--repeat-each=10` on webkit-1440: **this branch 2 of 30 fail, origin/dev 10 of 30 fail**, so they are not from this PR; the cause is the toast clock and is fixed in **#950** (issue #948), where `composer-tasks:115` goes from 10/10 failing to 10/10 passing. - `vitest` web 96, editor 511, ui 89 pass; `node --test scripts/test/*.test.mjs` and `filter-motion.mjs` pass; `svelte-check` 2 errors + 15 warnings, identical to dev (none in changed files).
Author
Owner

(Reading the round-2 table: every number is cumulative against dev at that commit, not the step's own change. The jitter is why single steps look backwards: e.g. step 6 is −125 raw yet +270 as built.)

(Reading the round-2 table: every number is cumulative against dev at that commit, not the step's own change. The jitter is why single steps look backwards: e.g. step 6 is −125 raw yet +270 as built.)
Author
Owner

#946 per-PR gates: GREEN at c55a3b42 (Style-sheet focus-loop spec skipped, as instructed)

Head = e59eb683 + 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 / 107 + 55 pass
Chromium (dev-01) 1440+390: a11y-high, note-bars-fit, search-palette, task-check (--grep-invert "Style: labelled") 49 passed, 23 skipped, 0 failed
WebKit (dev-02) same 48 passed, 24 skipped, 0 failed
perf/web.sh --bundle-only --gate pass. First-paint JS brotli 232.805 vs dev 232.862 (−57 B), first CSS 18.373 vs 18.571 (−198 B)
check-ui-primitives (from #941, run on this tree) no finding that dev does not already have
Rust perf n/a

Not covered here (skipped by design): a11y-high "Style: labelled, focus in and kept" at 1440 and 390, which fails 3 rounds and is being redesigned (sentinel pair / inert).

## #946 per-PR gates: GREEN at `c55a3b42` (Style-sheet focus-loop spec skipped, as instructed) Head = `e59eb683` + 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 / 107 + 55 pass | | Chromium (dev-01) 1440+390: a11y-high, note-bars-fit, search-palette, task-check (`--grep-invert "Style: labelled"`) | 49 passed, 23 skipped, 0 failed | | WebKit (dev-02) same | 48 passed, 24 skipped, 0 failed | | `perf/web.sh --bundle-only --gate` | pass. First-paint JS brotli **232.805 vs dev 232.862 (−57 B)**, first CSS **18.373 vs 18.571 (−198 B)** | | `check-ui-primitives` (from #941, run on this tree) | no finding that dev does not already have | | Rust perf | n/a | Not covered here (skipped by design): `a11y-high` "Style: labelled, focus in and kept" at 1440 and 390, which fails 3 rounds and is being redesigned (sentinel pair / `inert`).
Author
Owner

Merged into dev by the orchestrator (U1 per-PR gates GREEN), dev head 34c14822.

Merged into dev by the orchestrator (U1 per-PR gates GREEN), dev head 34c14822.
kayg closed this pull request 2026-11-05 16:51:07 +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!946
No description provided.