Merge train BM14c #897

Closed
kayg wants to merge 68 commits from train/2026-11-03-bm14c into dev
Owner

BM14c, based on origin/dev 41f2efa387b74175971a838f602535089d0d5fb4. Head b59e4a5e2155b2b71c6246b98a4e20679073cf26. Do not merge.

PR Head merged (verified before merge) Outcome
#850 af6fb861 --no-ff, clean
#851 d94771b6 --no-ff, clean
#852 656b61bf --no-ff, clean
#829 97d790f6 --no-ff, clean
#835 3755691d (current head) --no-ff, clean
#895 3c1d8deb --no-ff, clean

No conflicts. First-run gates, full matrix and perf gate results follow in comments. No fixes, no retries.

BM14c, based on origin/dev `41f2efa387b74175971a838f602535089d0d5fb4`. Head `b59e4a5e2155b2b71c6246b98a4e20679073cf26`. Do not merge. | PR | Head merged (verified before merge) | Outcome | |---|---|---| | #850 | af6fb861 | --no-ff, clean | | #851 | d94771b6 | --no-ff, clean | | #852 | 656b61bf | --no-ff, clean | | #829 | 97d790f6 | --no-ff, clean | | #835 | 3755691d (current head) | --no-ff, clean | | #895 | 3c1d8deb | --no-ff, clean | No conflicts. First-run gates, full matrix and perf gate results follow in comments. No fixes, no retries.
kayg added 181 commits 2026-11-03 13:35:21 +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>
Merge #895 (3c1d8deb) into BM14c
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
b59e4a5e21
Author
Owner

BM14c in progress. Head b59e4a5e2155b2b71c6246b98a4e20679073cf26, base origin/dev 41f2efa387b74175971a838f602535089d0d5fb4. Merged --no-ff in the requested order, every head matched the list (#835 current head 3755691d); no conflicts.

Train locks held on dev-01 and dev-02 (owner shells with EXIT trap), CI=1, SLOT_TRAIN=1, on-disk TMPDIR, --workers=1 --retries=0, df checked before each step (dev-01 ≥ 238 GiB, dev-02 ≥ 300 GiB free).

First-run gates so far (dev-01 unless noted), all pass: bun install --frozen-lockfile (both), cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -j4 -- -D warnings, cargo test --workspace -j4, cargo build -j4 (both), bun run build (both), perf/web.sh --bundle-only --gate, ci/check-case-paths.mjs, ci/check-no-evidence.mjs.

Running now: the full Playwright matrix (Chromium on dev-01, WebKit on dev-02), then apps/web bun run check, then the perf-VM comparison against origin/dev.

BM14c in progress. Head `b59e4a5e2155b2b71c6246b98a4e20679073cf26`, base origin/dev `41f2efa387b74175971a838f602535089d0d5fb4`. Merged `--no-ff` in the requested order, every head matched the list (#835 current head `3755691d`); no conflicts. Train locks held on dev-01 and dev-02 (owner shells with EXIT trap), `CI=1`, `SLOT_TRAIN=1`, on-disk TMPDIR, `--workers=1 --retries=0`, df checked before each step (dev-01 ≥ 238 GiB, dev-02 ≥ 300 GiB free). First-run gates so far (dev-01 unless noted), all pass: `bun install --frozen-lockfile` (both), `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --locked -j4 -- -D warnings`, `cargo test --workspace -j4`, `cargo build -j4` (both), `bun run build` (both), `perf/web.sh --bundle-only --gate`, `ci/check-case-paths.mjs`, `ci/check-no-evidence.mjs`. Running now: the full Playwright matrix (Chromium on dev-01, WebKit on dev-02), then `apps/web` `bun run check`, then the perf-VM comparison against origin/dev.
Author
Owner

BM14c interim (head b59e4a5e2155b2b71c6246b98a4e20679073cf26). First-run matrix is RED: 17 failing cases on Chromium (dev-01) and 17 on WebKit (dev-02), same spec set on both browsers; passed 1381 (chromium half) + 1351 (webkit half). Not merging. Throttled projects (skipped by Playwright because their chromium dependencies failed) got their first attempts separately with --no-deps on the train binary: throttled-1440 3 pass, throttled-390 5 pass, 0 fail.

Failing cases on the train (first error; all deterministic in the first run):

  • cover.spec.ts:124, :143 (chromium-1440/390, webkit-1440/390): [data-testid=style-cover] Change button / "No cover" not found.
  • width.spec.ts:48 (1440 only, timeout), :86 (1440, tab "Wide" not found), :177 (1440 and 390, tab "Narrow" not found).
  • opacity.spec.ts:122 (1440/390): test timeout 30 s.
  • style-sheet.spec.ts:26, :49 (1440/390): test timeout 30 s.
  • note-bars.spec.ts:319 (1440, timeout) and :237 (390, "the Text page is shorter than Page": 394 vs < 245).
  • chromium-1440 only vim.spec.ts:387 (Saved not visible); webkit-1440 only accent-labels.spec.ts:26 (accent radios: 0 found, expected 7).

Lead (no fix made): svelte-check on the train reports src/components/style/StyleSheet.svelte:142: Property 'noteId' is missing in type '{}' but required for <PageTab />. The merge of #850/#851/#852 renders Page tab without its noteId prop, which fits the missing Width/Cover/Opacity rows. It also reports 2 type errors in e2e/review4-signout.spec.ts:225-226 (never; check against dev pending).

apps/web bun run check fails on both train and origin/dev 41f2efa3 with src/lib/actions.gen.ts is out of date with contracts/actions.json (pre-existing, not the train). Since that stops before svelte-check, svelte-check was run directly on the train (3 errors above); the same run on origin/dev is queued.

Next: one origin/dev comparison per failing case, --repeat-each=10 on the train, then the perf table (append, server hot paths, browser first paint are running on perf-01/02).

BM14c interim (head `b59e4a5e2155b2b71c6246b98a4e20679073cf26`). **First-run matrix is RED**: 17 failing cases on Chromium (dev-01) and 17 on WebKit (dev-02), same spec set on both browsers; passed 1381 (chromium half) + 1351 (webkit half). Not merging. Throttled projects (skipped by Playwright because their chromium dependencies failed) got their first attempts separately with `--no-deps` on the train binary: throttled-1440 3 pass, throttled-390 5 pass, 0 fail. Failing cases on the train (first error; all deterministic in the first run): - `cover.spec.ts:124`, `:143` (chromium-1440/390, webkit-1440/390): `[data-testid=style-cover]` Change button / "No cover" not found. - `width.spec.ts:48` (1440 only, timeout), `:86` (1440, tab "Wide" not found), `:177` (1440 and 390, tab "Narrow" not found). - `opacity.spec.ts:122` (1440/390): test timeout 30 s. - `style-sheet.spec.ts:26`, `:49` (1440/390): test timeout 30 s. - `note-bars.spec.ts:319` (1440, timeout) and `:237` (390, "the Text page is shorter than Page": 394 vs < 245). - chromium-1440 only `vim.spec.ts:387` (`Saved` not visible); webkit-1440 only `accent-labels.spec.ts:26` (accent radios: 0 found, expected 7). Lead (no fix made): `svelte-check` on the train reports `src/components/style/StyleSheet.svelte:142`: `Property 'noteId' is missing in type '{}' but required` for `<PageTab />`. The merge of #850/#851/#852 renders Page tab without its `noteId` prop, which fits the missing Width/Cover/Opacity rows. It also reports 2 type errors in `e2e/review4-signout.spec.ts:225-226` (`never`; check against dev pending). `apps/web` `bun run check` fails on both train and origin/dev `41f2efa3` with `src/lib/actions.gen.ts is out of date with contracts/actions.json` (pre-existing, not the train). Since that stops before svelte-check, svelte-check was run directly on the train (3 errors above); the same run on origin/dev is queued. Next: one origin/dev comparison per failing case, `--repeat-each=10` on the train, then the perf table (append, server hot paths, browser first paint are running on perf-01/02).
Author
Owner

BM14c result: RED. Do not merge.

Head b59e4a5e2155b2b71c6246b98a4e20679073cf26, base origin/dev 41f2efa387b74175971a838f602535089d0d5fb4. Merged --no-ff in order, heads verified first: #850 af6fb861, #851 d94771b6, #852 656b61bf, #829 97d790f6, #835 3755691d (current), #895 3c1d8deb. No conflicts. CI=1, SLOT_TRAIN=1, train lock with EXIT trap on both VMs, on-disk TMPDIR, --workers=1 --retries=0, df before each step (dev-01 ≥ 235 GiB, dev-02 ≥ 300 GiB free). First run counted, nothing retried. No fixes made.

Gates (first run)

Gate Command Result
install (both VMs) bun install --frozen-lockfile pass
fmt cargo fmt --all --check pass
clippy cargo clippy --workspace --all-targets --locked -j4 -- -D warnings pass
Rust cargo test --workspace -j4 pass
cargo build (both) cargo build -j4 pass
web build (both) cd apps/web && bun run build pass
bundle budgets PERF_DIST=$PWD/apps/web/dist perf/web.sh --bundle-only --gate pass
case paths node ci/check-case-paths.mjs pass
evidence node ci/check-no-evidence.mjs pass
apps/web bun run check fails at actions.gen.ts is out of date with contracts/actions.json. The same failure happens on origin/dev 41f2efa3 (built the same way), so it is pre-existing, not the train.
svelte-check run directly (the check script stops before it) bunx svelte-check --tsconfig ./tsconfig.json train: 3 errors; dev: 2. The train-only error: src/components/style/StyleSheet.svelte:142:158 Property 'noteId' is missing in type '{}' but required (<PageTab />). The 2 in e2e/review4-signout.spec.ts:225-226 are on dev too.

Playwright matrix (every project, one attempt each, retry 0)

Chromium half on dev-01, WebKit half on dev-02, admin-setup on each, via the .mts wrapper as in BM14b.

VM / project Passed Failed Skipped
chromium / chromium-1440 433 10 625
chromium / chromium-390 892 7 169
chromium / throttled-1440 * 3 0 2
chromium / throttled-390 * 5 0 0
chromium / admin-chromium-1440, -390 12, 12 0 9, 9
chromium / admin-setup, flow, passkeys 1, 1, 1 0 0
chromium / prefs-chromium 7 0 0
chromium / look-chromium-1440, -390 5, 5 0 0
chromium / contrast 9 0 0
chromium / sidebar-band-chromium 3 0 0
webkit / webkit-1440 430 10 628
webkit / webkit-390 888 7 173
webkit / admin-webkit-1440, -390 9, 9 0 9, 9
webkit / admin-setup 1 0 0
webkit / prefs-webkit 3 0 4
webkit / look-webkit-1440, -390 4, 4 0 1, 1
webkit / sidebar-band-webkit 3 0 0
Total 2732 + 8* 34

* The throttled projects depend on the failed chromium projects, so Playwright skipped them. Their first attempts were run right after with --no-deps on the same train binary and dist.

Known flakes sidebar-edge.spec.ts:8, webkit-1440 editor.spec.ts:173, webkit-390 missing-links.spec.ts:245 did not occur.

Failures (34 first-run failures = 17 cases on each browser)

One comparison on origin/dev 41f2efa3 (built and run the same way, exact case, one attempt) and --repeat-each=10 --no-deps on the train. "pass n/10" is the train repeat.

Case Projects failed in matrix First error origin/dev Train repeat
cover.spec.ts:124 Style sheet Cover row all four (1440, 390, both browsers) [data-testid=style-cover] Change button not found spec does not exist on dev 0/10 on each of the four
cover.spec.ts:143 "Use this picture as the cover" all four style-cover "No cover" not found spec does not exist on dev 0/10 on each
style-sheet.spec.ts:26 all four test timeout 30 s spec does not exist on dev 0/10 on each
style-sheet.spec.ts:49 all four test timeout 30 s spec does not exist on dev 0/10 on each
note-bars.spec.ts:319 Style popover tabs 1440 chromium, webkit timeout 30 s spec does not exist on dev 0/10 on each
note-bars.spec.ts:237 Style tabs follow height 390 chromium, webkit "the Text page is shorter than Page": expected < 245, got 394 spec does not exist on dev chromium 0/10, webkit 1/10
opacity.spec.ts:122 (dev :120) slider to 0% all four timeout 30 s pass on all four 0/10 on each
width.spec.ts:48 1440 chromium, webkit timeout 30 s pass (both) 0/10 on each
width.spec.ts:86 1440 chromium, webkit tab "Wide" not found pass (both) 0/10 on each
width.spec.ts:177 all four tab "Narrow" not found (900px) pass on all four 0/10 on each
vim.spec.ts:387 (dev :380) : commands chromium-1440 Saved not visible fails on dev too (reproduces on baseline) 6/10 pass: flaky on both, not the train
accent-labels.spec.ts:26 webkit-1440 accent radios: 0 found, expected 7 pass 10/10 pass: first-run flake, not reproduced

So 10 distinct cases fail on every repeat only on the train (cover ×2, style-sheet ×2, note-bars ×2, opacity, width ×3). vim:380/387 fails on dev too; accent-labels:26 did not reproduce. Likely cause (not fixed): StyleSheet.svelte renders <PageTab /> without the required noteId after the #850/#851/#852 merge (svelte-check error above); the missing Width, Cover and Opacity rows are on that tab.

Perf (new gate: no regression vs origin/dev)

Train b59e4a5e against origin/dev 41f2efa3, release binaries built on dev-01 and relayed with matching SHA-256 on both perf VMs (head calternal 56787f47…, dev 2c6a9b93…, same calternal-perf 4631c52b…). Every run under ~/bin/perf-lock, seed 1, pairs alternating, perf VM loads quiet (0.3–5.2).

Server, perf-02 (calternal-perf --sizes 10k,100k --runs 5 --cold-runs 0 --idle-secs 0 --storm-edits 1, 3 alternating pairs; medians of per-run p50 / p95; spread = min–max of per-run p95 across the 3 runs):

Metric Home dev p50 / p95 head p50 / p95 p95 delta dev spread head spread Verdict
sync.round_trip (ms) 10k 0.34 / 0.42 0.49 / 0.64 +52% 0.39–0.46 0.63–0.65 regression (ranges do not overlap)
sync.round_trip (ms) 100k 0.35 / 0.45 0.57 / 0.71 +58% 0.42–0.50 0.68–0.81 regression
open.note (ms) 10k 0.91 / 1.84 1.28 / 2.06 +12% 1.46–2.22 1.79–2.17 within noise (p50 +0.37 ms, watch)
open.note (ms) 100k 5.64 / 12.3 6.04 / 10.6 −14% 11.2–13.4 8.45–13.1 flat
open.api_get (ms) 10k / 100k 2.94 / 10.6, 6.59 / 12.1 3.04 / 12.7, 6.16 / 13.5 +20% / +12% 6.3–11.6, 11.7–19.0 11.4–20.1, 9.6–16.5 within noise
home.page / page_title / page_next / page_tag (ms) 10k 0.88 / 1.78, 0.84 / 1.06, 0.94 / 1.27, 6.09 / 7.78 0.85 / 2.21, 0.68 / 0.99, 0.91 / 1.31, 5.75 / 7.92 +24%, −7%, +3%, +2% wide within noise
home.page / page_title / page_next / page_tag (ms) 100k 1.07 / 2.63, 0.85 / 1.71, 0.98 / 1.60, 57.9 / 65.3 1.23 / 1.83, 0.92 / 1.51, 1.08 / 1.37, 61.1 / 69.1 −30%, −11%, −15%, +6% page_tag 61.9–68.6 66.8–70.9 within noise; page_tag budget 50 ms is over on dev too
home.first_page (ms) 10k / 100k 2.86 / 5.32, 4.20 / 6.81 2.79 / 6.11, 5.35 / 9.19 +15% / +35% 3.1–6.8, 4.6–12.9 4.4–6.6, 6.5–12.1 within noise (single sample per restart)
search.query (ms, prefix/typo mix of the standard harness) 10k / 100k 1.94 / 4.56, 2.26 / 12.3 1.71 / 4.38, 2.32 / 5.94 −4% / −52% flat or better
search.first_query (ms) 10k / 100k 5.7 / 32.8, 8.9 / 52.0 5.4 / 24.8, 7.3 / 31.0 −24% / −40% flat or better
sync.save_ack (ms) 10k 2.70 / 3.15 2.93 / 4.98 +58% 3.0–3.3 3.25–7.5 one slow run; p50 +0.23 ms
sync.save_ack (ms) 100k 3.54 / 5.11 3.85 / 5.27 +3% within noise
sync.durable_write, journal.catch_up, startup, shutdown, mem.idle/peak both −5%…+4% (shutdown 10k +32%: 64 vs 84.5 ms, both runs bimodal 64/85 on dev too) flat

200 appends (30 alternating pairs, perf-01, CALTERNAL_NOTES_SAVE_MS=15, same append.py as #792): total time median dev 1.308 s, head 1.328 s = +1.50%; per-append p95 median 11.55 → 12.10 ms (+4.8%); pair ratios 0.79–1.21. Noise control, the same script with the dev binary against a copy of itself (30 pairs): +2.11%. So the +1.50% is inside run-to-run noise.

Browser, perf-01 (Chromium 4× CPU slowdown, 10k Home, perf/boot.perf.ts, list-search.perf.ts, blaze.perf.ts; 3 alternating runs per side; each side served by its own server binary and dist; WebKit not available on perf VMs):

Metric dev p50 / p95 head p50 / p95 p95 delta per-run medians dev / head Verdict
boot.cold_fcp (ms) 820 / 916 904 / 956 +4.4% (p50 +10%) 804–828 / 860–936 regression (separated)
boot.cold_note (first note painted, ms) 1051 / 1186 1118 / 1204 +1.5% (p50 +6.3%) 1017–1073 / 1113–1198 regression at p50 (separated), p95 within noise
boot.cold_list (ms) 1051 / 1186 1113 / 1199 +1.1% (p50 +5.9%) 1017–1073 / 1027–1180 within noise
boot.cold_tti / cold_cpu / cold_tbt 1893 / 2507, 1477 / 1732, 199 / 254 1980 / 2496, 1632 / 1743, 187 / 230 −0.4%, +0.6%, −9% within noise
boot.warm_note / warm_list / warm_tti (ms) 531 / 971, 357 / 707, 1496 / 1980 534 / 977, 355 / 717, 1480 / 1991 +0.6%, +1.4%, +0.5% flat
list_search.key (keystroke to paint, ms) 7.6 / 10.6 7.9 / 12.1 +14% (p50 +3.9%) 7.5–7.8 / 7.8–8.2 small regression (separated)
blaze.shown / back_shown / live (ms) 47.5 / 84.0, 44.1 / 61.1, 97.5 / 133 47.2 / 79.6, 41.2 / 61.2, 94.5 / 127 −5%, +0.2%, −4.5% flat or better
blaze long_task_max, heap_growth, requests, blank_frames, cls 207, 1.84, 52, 0, 0 204, 1.89, 52, 0, 0 flat

Bundle (brotli KB, perf/web.sh --bundle-only, deterministic): first JS 233.20 → 233.95 (+0.75, +0.3%); first CSS 18.80 → 19.59 (+0.79, +4.2%); total JS 353.3 → 364.6 (+11.3, idle-screens +10.4); all CSS 34.5 → 37.0. All inside budgets (first JS 240, first CSS 22.1) but larger than dev.

Perf verdict: RED. Beyond noise: sync.round_trip p95 +52%/+58% (≈ +0.2 ms, 10 ms budget), cold first contentful paint / first note +84 / +67 ms p50 at 4× slowdown, list_search.key p95 +14% (+1.5 ms), and a larger first CSS (+4.2%). Not bisected: sync.round_trip points at the #829/#835 room/live changes, the browser numbers at the #850–#852 style stack (bundle +11 KB). Everything else is flat or better.

Summary

Gates pass; the matrix has 10 train-only deterministic failures (Style sheet, Cover, Width, Opacity) plus vim:380 that fails on dev too and one first-run accent-labels flake that did not reproduce; perf has the regressions above. Artifacts (JSON, logs, reports) are only in these comments; VM work dirs are cleaned.

## BM14c result: RED. Do not merge. Head `b59e4a5e2155b2b71c6246b98a4e20679073cf26`, base origin/dev `41f2efa387b74175971a838f602535089d0d5fb4`. Merged `--no-ff` in order, heads verified first: #850 `af6fb861`, #851 `d94771b6`, #852 `656b61bf`, #829 `97d790f6`, #835 `3755691d` (current), #895 `3c1d8deb`. No conflicts. `CI=1`, `SLOT_TRAIN=1`, train lock with EXIT trap on both VMs, on-disk TMPDIR, `--workers=1 --retries=0`, df before each step (dev-01 ≥ 235 GiB, dev-02 ≥ 300 GiB free). First run counted, nothing retried. No fixes made. ## Gates (first run) | Gate | Command | Result | |---|---|---| | install (both VMs) | `bun install --frozen-lockfile` | pass | | fmt | `cargo fmt --all --check` | pass | | clippy | `cargo clippy --workspace --all-targets --locked -j4 -- -D warnings` | pass | | Rust | `cargo test --workspace -j4` | pass | | cargo build (both) | `cargo build -j4` | pass | | web build (both) | `cd apps/web && bun run build` | pass | | bundle budgets | `PERF_DIST=$PWD/apps/web/dist perf/web.sh --bundle-only --gate` | pass | | case paths | `node ci/check-case-paths.mjs` | pass | | evidence | `node ci/check-no-evidence.mjs` | pass | | `apps/web` `bun run check` | | **fails** at `actions.gen.ts is out of date with contracts/actions.json`. The same failure happens on origin/dev `41f2efa3` (built the same way), so it is pre-existing, not the train. | | `svelte-check` run directly (the `check` script stops before it) | `bunx svelte-check --tsconfig ./tsconfig.json` | train: 3 errors; dev: 2. The train-only error: `src/components/style/StyleSheet.svelte:142:158 Property 'noteId' is missing in type '{}' but required` (`<PageTab />`). The 2 in `e2e/review4-signout.spec.ts:225-226` are on dev too. | ## Playwright matrix (every project, one attempt each, retry 0) Chromium half on dev-01, WebKit half on dev-02, `admin-setup` on each, via the `.mts` wrapper as in BM14b. | VM / project | Passed | Failed | Skipped | |---|---:|---:|---:| | chromium / chromium-1440 | 433 | **10** | 625 | | chromium / chromium-390 | 892 | **7** | 169 | | chromium / throttled-1440 * | 3 | 0 | 2 | | chromium / throttled-390 * | 5 | 0 | 0 | | chromium / admin-chromium-1440, -390 | 12, 12 | 0 | 9, 9 | | chromium / admin-setup, flow, passkeys | 1, 1, 1 | 0 | 0 | | chromium / prefs-chromium | 7 | 0 | 0 | | chromium / look-chromium-1440, -390 | 5, 5 | 0 | 0 | | chromium / contrast | 9 | 0 | 0 | | chromium / sidebar-band-chromium | 3 | 0 | 0 | | webkit / webkit-1440 | 430 | **10** | 628 | | webkit / webkit-390 | 888 | **7** | 173 | | webkit / admin-webkit-1440, -390 | 9, 9 | 0 | 9, 9 | | webkit / admin-setup | 1 | 0 | 0 | | webkit / prefs-webkit | 3 | 0 | 4 | | webkit / look-webkit-1440, -390 | 4, 4 | 0 | 1, 1 | | webkit / sidebar-band-webkit | 3 | 0 | 0 | | **Total** | **2732 + 8\*** | **34** | | \* The throttled projects depend on the failed chromium projects, so Playwright skipped them. Their first attempts were run right after with `--no-deps` on the same train binary and dist. Known flakes `sidebar-edge.spec.ts:8`, webkit-1440 `editor.spec.ts:173`, webkit-390 `missing-links.spec.ts:245` did not occur. ## Failures (34 first-run failures = 17 cases on each browser) One comparison on origin/dev `41f2efa3` (built and run the same way, exact case, one attempt) and `--repeat-each=10 --no-deps` on the train. "pass n/10" is the train repeat. | Case | Projects failed in matrix | First error | origin/dev | Train repeat | |---|---|---|---|---| | `cover.spec.ts:124` Style sheet Cover row | all four (1440, 390, both browsers) | `[data-testid=style-cover]` Change button not found | spec does not exist on dev | 0/10 on each of the four | | `cover.spec.ts:143` "Use this picture as the cover" | all four | `style-cover` "No cover" not found | spec does not exist on dev | 0/10 on each | | `style-sheet.spec.ts:26` | all four | test timeout 30 s | spec does not exist on dev | 0/10 on each | | `style-sheet.spec.ts:49` | all four | test timeout 30 s | spec does not exist on dev | 0/10 on each | | `note-bars.spec.ts:319` Style popover tabs | 1440 chromium, webkit | timeout 30 s | spec does not exist on dev | 0/10 on each | | `note-bars.spec.ts:237` Style tabs follow height | 390 chromium, webkit | "the Text page is shorter than Page": expected < 245, got 394 | spec does not exist on dev | chromium 0/10, webkit 1/10 | | `opacity.spec.ts:122` (dev `:120`) slider to 0% | all four | timeout 30 s | **pass** on all four | 0/10 on each | | `width.spec.ts:48` | 1440 chromium, webkit | timeout 30 s | **pass** (both) | 0/10 on each | | `width.spec.ts:86` | 1440 chromium, webkit | tab "Wide" not found | **pass** (both) | 0/10 on each | | `width.spec.ts:177` | all four | tab "Narrow" not found (900px) | **pass** on all four | 0/10 on each | | `vim.spec.ts:387` (dev `:380`) `:` commands | chromium-1440 | `Saved` not visible | **fails on dev too** (reproduces on baseline) | 6/10 pass: flaky on both, not the train | | `accent-labels.spec.ts:26` | webkit-1440 | accent radios: 0 found, expected 7 | pass | 10/10 pass: first-run flake, not reproduced | So 10 distinct cases fail on every repeat only on the train (cover ×2, style-sheet ×2, note-bars ×2, opacity, width ×3). `vim:380/387` fails on dev too; `accent-labels:26` did not reproduce. Likely cause (not fixed): `StyleSheet.svelte` renders `<PageTab />` without the required `noteId` after the #850/#851/#852 merge (svelte-check error above); the missing Width, Cover and Opacity rows are on that tab. ## Perf (new gate: no regression vs origin/dev) Train `b59e4a5e` against origin/dev `41f2efa3`, release binaries built on dev-01 and relayed with matching SHA-256 on both perf VMs (head `calternal` `56787f47…`, dev `2c6a9b93…`, same `calternal-perf` `4631c52b…`). Every run under `~/bin/perf-lock`, seed 1, pairs alternating, perf VM loads quiet (0.3–5.2). **Server, perf-02** (`calternal-perf --sizes 10k,100k --runs 5 --cold-runs 0 --idle-secs 0 --storm-edits 1`, 3 alternating pairs; medians of per-run p50 / p95; spread = min–max of per-run p95 across the 3 runs): | Metric | Home | dev p50 / p95 | head p50 / p95 | p95 delta | dev spread | head spread | Verdict | |---|---|---|---|---:|---|---|---| | **sync.round_trip** (ms) | 10k | 0.34 / 0.42 | 0.49 / 0.64 | **+52%** | 0.39–0.46 | 0.63–0.65 | **regression** (ranges do not overlap) | | **sync.round_trip** (ms) | 100k | 0.35 / 0.45 | 0.57 / 0.71 | **+58%** | 0.42–0.50 | 0.68–0.81 | **regression** | | open.note (ms) | 10k | 0.91 / 1.84 | 1.28 / 2.06 | +12% | 1.46–2.22 | 1.79–2.17 | within noise (p50 +0.37 ms, watch) | | open.note (ms) | 100k | 5.64 / 12.3 | 6.04 / 10.6 | −14% | 11.2–13.4 | 8.45–13.1 | flat | | open.api_get (ms) | 10k / 100k | 2.94 / 10.6, 6.59 / 12.1 | 3.04 / 12.7, 6.16 / 13.5 | +20% / +12% | 6.3–11.6, 11.7–19.0 | 11.4–20.1, 9.6–16.5 | within noise | | home.page / page_title / page_next / page_tag (ms) | 10k | 0.88 / 1.78, 0.84 / 1.06, 0.94 / 1.27, 6.09 / 7.78 | 0.85 / 2.21, 0.68 / 0.99, 0.91 / 1.31, 5.75 / 7.92 | +24%, −7%, +3%, +2% | wide | | within noise | | home.page / page_title / page_next / page_tag (ms) | 100k | 1.07 / 2.63, 0.85 / 1.71, 0.98 / 1.60, 57.9 / 65.3 | 1.23 / 1.83, 0.92 / 1.51, 1.08 / 1.37, 61.1 / 69.1 | −30%, −11%, −15%, +6% | page_tag 61.9–68.6 | 66.8–70.9 | within noise; page_tag budget 50 ms is over on dev too | | home.first_page (ms) | 10k / 100k | 2.86 / 5.32, 4.20 / 6.81 | 2.79 / 6.11, 5.35 / 9.19 | +15% / +35% | 3.1–6.8, 4.6–12.9 | 4.4–6.6, 6.5–12.1 | within noise (single sample per restart) | | search.query (ms, prefix/typo mix of the standard harness) | 10k / 100k | 1.94 / 4.56, 2.26 / 12.3 | 1.71 / 4.38, 2.32 / 5.94 | −4% / −52% | | | flat or better | | search.first_query (ms) | 10k / 100k | 5.7 / 32.8, 8.9 / 52.0 | 5.4 / 24.8, 7.3 / 31.0 | −24% / −40% | | | flat or better | | sync.save_ack (ms) | 10k | 2.70 / 3.15 | 2.93 / 4.98 | +58% | 3.0–3.3 | 3.25–7.5 | one slow run; p50 +0.23 ms | | sync.save_ack (ms) | 100k | 3.54 / 5.11 | 3.85 / 5.27 | +3% | | | within noise | | sync.durable_write, journal.catch_up, startup, shutdown, mem.idle/peak | both | | | −5%…+4% (shutdown 10k +32%: 64 vs 84.5 ms, both runs bimodal 64/85 on dev too) | | | flat | **200 appends** (30 alternating pairs, perf-01, `CALTERNAL_NOTES_SAVE_MS=15`, same `append.py` as #792): total time median dev 1.308 s, head 1.328 s = **+1.50%**; per-append p95 median 11.55 → 12.10 ms (+4.8%); pair ratios 0.79–1.21. Noise control, the same script with the dev binary against a copy of itself (30 pairs): **+2.11%**. So the +1.50% is inside run-to-run noise. **Browser, perf-01** (Chromium 4× CPU slowdown, 10k Home, `perf/boot.perf.ts`, `list-search.perf.ts`, `blaze.perf.ts`; 3 alternating runs per side; each side served by its own server binary and dist; WebKit not available on perf VMs): | Metric | dev p50 / p95 | head p50 / p95 | p95 delta | per-run medians dev / head | Verdict | |---|---|---|---:|---|---| | **boot.cold_fcp** (ms) | 820 / 916 | 904 / 956 | **+4.4%** (p50 +10%) | 804–828 / 860–936 | **regression** (separated) | | **boot.cold_note** (first note painted, ms) | 1051 / 1186 | 1118 / 1204 | +1.5% (p50 +6.3%) | 1017–1073 / 1113–1198 | **regression** at p50 (separated), p95 within noise | | boot.cold_list (ms) | 1051 / 1186 | 1113 / 1199 | +1.1% (p50 +5.9%) | 1017–1073 / 1027–1180 | within noise | | boot.cold_tti / cold_cpu / cold_tbt | 1893 / 2507, 1477 / 1732, 199 / 254 | 1980 / 2496, 1632 / 1743, 187 / 230 | −0.4%, +0.6%, −9% | | within noise | | boot.warm_note / warm_list / warm_tti (ms) | 531 / 971, 357 / 707, 1496 / 1980 | 534 / 977, 355 / 717, 1480 / 1991 | +0.6%, +1.4%, +0.5% | | flat | | **list_search.key** (keystroke to paint, ms) | 7.6 / 10.6 | 7.9 / 12.1 | **+14%** (p50 +3.9%) | 7.5–7.8 / 7.8–8.2 | **small regression** (separated) | | blaze.shown / back_shown / live (ms) | 47.5 / 84.0, 44.1 / 61.1, 97.5 / 133 | 47.2 / 79.6, 41.2 / 61.2, 94.5 / 127 | −5%, +0.2%, −4.5% | | flat or better | | blaze long_task_max, heap_growth, requests, blank_frames, cls | 207, 1.84, 52, 0, 0 | 204, 1.89, 52, 0, 0 | | | flat | **Bundle** (brotli KB, `perf/web.sh --bundle-only`, deterministic): first JS 233.20 → 233.95 (+0.75, +0.3%); first CSS 18.80 → 19.59 (+0.79, **+4.2%**); total JS 353.3 → 364.6 (+11.3, idle-screens +10.4); all CSS 34.5 → 37.0. All inside budgets (first JS 240, first CSS 22.1) but larger than dev. **Perf verdict: RED.** Beyond noise: `sync.round_trip` p95 +52%/+58% (≈ +0.2 ms, 10 ms budget), cold first contentful paint / first note +84 / +67 ms p50 at 4× slowdown, `list_search.key` p95 +14% (+1.5 ms), and a larger first CSS (+4.2%). Not bisected: `sync.round_trip` points at the #829/#835 room/live changes, the browser numbers at the #850–#852 style stack (bundle +11 KB). Everything else is flat or better. ## Summary Gates pass; the matrix has 10 train-only deterministic failures (Style sheet, Cover, Width, Opacity) plus `vim:380` that fails on dev too and one first-run `accent-labels` flake that did not reproduce; perf has the regressions above. Artifacts (JSON, logs, reports) are only in these comments; VM work dirs are cleaned.
Author
Owner

BM14c Style stack: causes and fixes (pushed to #852 port/style-3, head 07b6770a)

Cause 1 (most of the red). #851 gave PageTab a noteId prop and #852's merge of #851/#850 took the <PageTab /> call from the #851 side of StyleSheet.svelte:142 (no noteId), while #850's side had <PageTab {noteId} />. So the Cover row got no note, and cover / Width / Opacity / style-sheet / note-bars specs lost their rows and timed out. svelte-check showed it as Property 'noteId' is missing (the only train-only error). Fix: f757dc1e passes noteId.

Cause 2 (note-bars.spec.ts:237, phone, WebKit 13/20 after cause 1 was fixed). The Page tab builds itself in steps (looks, then rows + Cover, then colour; 40 ms timers). StyleSheet dropped its held room (min-height) as soon as the tab was mounted, so the capsule shrank to the looks (307 px) and grew back; the spec's still() saw the shrunken moment and compared "Text page shorter than Page" against 307 (Text is 394). Fixes: fed76ea1 keeps the held room until PageTab says its last step is in (onready), and 07b6770a updates the held-room guesses for the taller Page tab with the Cover row (measured on the stack: page 464 phone / 553 wide, sheet 516 / 605; they were 362/451 and 414/503 from before the Cover row). No assertion, threshold or timeout changed.

Results on the merged train tree + these 3 commits (train b59e4a5e plus cherry-picks, dev-01, CI=1 --workers=1 --retries=0 --repeat-each=20)

Cases: cover.spec.ts:124, :143; style-sheet.spec.ts:26, :49; note-bars.spec.ts:319 (desktop), :237 (phone); opacity.spec.ts:122; width.spec.ts:177, :48 and :86 (1440 only). Cases that do not apply to a width are skipped by their own test.skip.

project failed passed skipped (by the spec)
chromium-1440 0 180 20
chromium-390 0 140 60
webkit-1440 0 180 20
webkit-390 0 140 60

Notes on the runs: the very first parallel runs shared one test-results dir and a port, giving ENOENT/port errors (my harness, not the app); every number above is from runs with their own --output. One webkit-1440 run beside three other 20x runs had 1 failure in 180 (note-bars.spec.ts:319, tablist not up in 5 s under heavy load); the same project alone, repeated: 180/180. svelte-check on the stack: only the 2 errors in e2e/review4-signout.spec.ts:225-226 that origin/dev has too; StyleSheet.svelte:142 is gone.

Style sheet open time (kept)

note-bars-motion.spec.ts "rest to Style sheet", throttled-390 (6x CPU), --no-deps --repeat-each=20: 20/20 pass; longest main-thread task of the open: min 80, median 92, p90 128, max 172 ms (limit 250; goal median ≤ 120). Held room did not add any task.

Not done

vim.spec.ts:387 (:w says Saved): not touched. It fails on dev too and I did not find a quick deterministic fix; left as is for #895.

## BM14c Style stack: causes and fixes (pushed to #852 `port/style-3`, head `07b6770a`) **Cause 1 (most of the red).** #851 gave `PageTab` a `noteId` prop and #852's merge of #851/#850 took the `<PageTab />` call from the #851 side of `StyleSheet.svelte:142` (no `noteId`), while #850's side had `<PageTab {noteId} />`. So the Cover row got no note, and cover / Width / Opacity / style-sheet / note-bars specs lost their rows and timed out. `svelte-check` showed it as `Property 'noteId' is missing` (the only train-only error). Fix: `f757dc1e` passes `noteId`. **Cause 2 (`note-bars.spec.ts:237`, phone, WebKit 13/20 after cause 1 was fixed).** The Page tab builds itself in steps (looks, then rows + Cover, then colour; 40 ms timers). `StyleSheet` dropped its held room (`min-height`) as soon as the tab was *mounted*, so the capsule shrank to the looks (307 px) and grew back; the spec's `still()` saw the shrunken moment and compared "Text page shorter than Page" against 307 (Text is 394). Fixes: `fed76ea1` keeps the held room until PageTab says its last step is in (`onready`), and `07b6770a` updates the held-room guesses for the taller Page tab with the Cover row (measured on the stack: page 464 phone / 553 wide, sheet 516 / 605; they were 362/451 and 414/503 from before the Cover row). No assertion, threshold or timeout changed. ## Results on the merged train tree + these 3 commits (train `b59e4a5e` plus cherry-picks, dev-01, `CI=1 --workers=1 --retries=0 --repeat-each=20`) Cases: cover.spec.ts:124, :143; style-sheet.spec.ts:26, :49; note-bars.spec.ts:319 (desktop), :237 (phone); opacity.spec.ts:122; width.spec.ts:177, :48 and :86 (1440 only). Cases that do not apply to a width are skipped by their own `test.skip`. | project | failed | passed | skipped (by the spec) | |---|---|---|---| | chromium-1440 | 0 | 180 | 20 | | chromium-390 | 0 | 140 | 60 | | webkit-1440 | 0 | 180 | 20 | | webkit-390 | 0 | 140 | 60 | Notes on the runs: the very first parallel runs shared one `test-results` dir and a port, giving ENOENT/port errors (my harness, not the app); every number above is from runs with their own `--output`. One webkit-1440 run beside three other 20x runs had 1 failure in 180 (`note-bars.spec.ts:319`, tablist not up in 5 s under heavy load); the same project alone, repeated: 180/180. `svelte-check` on the stack: only the 2 errors in `e2e/review4-signout.spec.ts:225-226` that origin/dev has too; `StyleSheet.svelte:142` is gone. ## Style sheet open time (kept) `note-bars-motion.spec.ts` "rest to Style sheet", throttled-390 (6x CPU), `--no-deps --repeat-each=20`: 20/20 pass; longest main-thread task of the open: min 80, **median 92**, p90 128, max 172 ms (limit 250; goal median ≤ 120). Held room did not add any task. ## Not done `vim.spec.ts:387` (`:w` says Saved): not touched. It fails on dev too and I did not find a quick deterministic fix; left as is for #895.
Author
Owner

Correction to cause 1: #850 passes <PageTab {noteId} />; #851's StyleSheet.svelte rewrote that call as <PageTab /> (its PageTab takes no prop); #852 made PageTab require noteId for the Cover row and kept #851's call. So the missing prop is a #851/#852 interaction, fixed on #852 by f757dc1e.

Correction to cause 1: #850 passes `<PageTab {noteId} />`; #851's `StyleSheet.svelte` rewrote that call as `<PageTab />` (its PageTab takes no prop); #852 made `PageTab` require `noteId` for the Cover row and kept #851's call. So the missing prop is a #851/#852 interaction, fixed on #852 by `f757dc1e`.
Author
Owner

BM14c perf bisect and fixes (not merged)

Method: release binaries built on dev-01, relayed to the perf VMs with matching sha256 (dev de99cfbf…, train+fixes 68dd30a0…, same dev calternal-perf for every run). Every run under ~/bin/perf-lock, seed 1, 10k Home, alternating pairs. Medians of per-run p50 / p95, [min–max of p50]. The perf VMs were shared with other agents' long lock holds, so runs spread over several windows; dev-vs-dev spread alone is 0.20–0.37 ms on round trip.

sync.round_trip bisect (10k, perf-02, ms)

Build p50 p95 runs
dev 41f2efa3 0.302–0.327 0.41–0.43 5
train through #829 (ef284146) 0.418 (+34%) 0.547 (+33%) 5
train through #835 (efa2bf37) 0.548 (+77%) 0.766 (+87%) 4
#829 + fix (72eb0eb1) vs dev same window 0.217 vs 0.235 0.337 vs 0.344 5
#835 + owner-delivery fixes only (F3) 0.351 vs 0.327 (+7%) 0.451 vs 0.417 5
train + all fixes (TF2) vs dev, 10k 0.286 vs 0.290 0.388 vs 0.401 5

Culprits: #829 (Auth::authorize_room_delivery, called per delivered frame, does a sharing-db read even for the owner's own note) and #835 (the same check per queued frame in the room, again per written frame in live.rs, plus two more reads per room join and a clone of the caller). Both are the same cause: an owner reading their own note never depends on a grant row, but every frame paid a database hop.

Final train + fixes vs dev, both sizes (3 pairs, ms; p50 / p95)

Metric Home dev train+fixes note
sync.round_trip 10k 0.214 / 0.351 0.231 / 0.341 p95 flat, p50 +8% inside range overlap
sync.round_trip 100k 0.246 / 0.353 0.288 / 0.379 p50 +17%, p95 +7%: n=3, ranges overlap, not shown flat
sync.save_ack 10k / 100k 2.63 / 4.41, 3.31 / 5.11 3.11 / 4.17, 3.27 / 4.68 flat or better
open.note 10k 0.710 / 1.183 0.787 / 1.408 +11% / +19%, n=3
open.note 100k 4.81 / 9.49 4.47 / 10.64 flat / +12%
open.api_get 10k / 100k 3.47 / 9.94, 5.04 / 12.2 3.31 / 10.95, 5.12 / 10.9 flat
Earlier 5-pair 10k run of the same binary: round_trip 0.286 / 0.388 vs dev 0.290 / 0.401, open.note 0.817 / 1.531 vs 0.815 / 1.440 (flat). I could not get more 100k pairs through the shared lock; 100k round_trip needs a longer paired run before it can be called flat.

Browser (Chromium 4x CPU, 10k Home; boot.perf.ts, list-search.perf.ts)

Pooled, interleaved dev / #850 / #851 / #852 / #852+CSS-split runs across three windows (D n=21, #850 n=7, #851 n=5, #852 n=14, split n=7):

Metric (p50 ms) dev #850 #851 #852 #852 + CSS split
boot.cold_fcp 744 780 736 760 748
boot.cold_note 938 988 927 951 968
list_search.key (p50 / p95) 5.9 / 8.2 6.2 / 8.3 5.9 / 8.6 6.2 / 8.3 6.1 / 8.9
The train's +10% cold FCP, +6% first note and +14% search-key p95 did not reproduce: in the quietest window (8 interleaved rounds) #852 was flat to dev (FCP 744 vs 756, first note 941 vs 962, search key 6.0 / 8.1 vs 5.8 / 8.0), and dev-vs-dev spread is 704–832 ms FCP. #850 shows the largest step (+2.6…4.8%, inside range overlap). I found no per-keystroke work added to the search path.
Bundle (brotli KB, bundle-size.mjs): first CSS dev 18.8 → train 19.6 → with split 19.2; first JS 233.2 → 233.9 → 233.6. The remaining +0.4 KB CSS is the rest bar's own rules, needed by the first note screen.

Fix branches pushed to the owning PRs: #829 72eb0eb1, #835 46be23e3, #850 6eb81f62. Train order check: #852 state + #829 + #835 + #895 merges cleanly (no conflicts) with all fixes; tree = train head + 5 changed files.

## BM14c perf bisect and fixes (not merged) Method: release binaries built on dev-01, relayed to the perf VMs with matching sha256 (dev `de99cfbf…`, train+fixes `68dd30a0…`, same dev `calternal-perf` for every run). Every run under `~/bin/perf-lock`, seed 1, 10k Home, alternating pairs. Medians of per-run p50 / p95, [min–max of p50]. The perf VMs were shared with other agents' long lock holds, so runs spread over several windows; dev-vs-dev spread alone is 0.20–0.37 ms on round trip. ### sync.round_trip bisect (10k, perf-02, ms) | Build | p50 | p95 | runs | |---|---|---|---| | dev `41f2efa3` | 0.302–0.327 | 0.41–0.43 | 5 | | train through #829 (`ef284146`) | 0.418 (+34%) | 0.547 (+33%) | 5 | | train through #835 (`efa2bf37`) | 0.548 (+77%) | 0.766 (+87%) | 4 | | #829 + fix (`72eb0eb1`) vs dev same window | 0.217 vs 0.235 | 0.337 vs 0.344 | 5 | | #835 + owner-delivery fixes only (F3) | 0.351 vs 0.327 (+7%) | 0.451 vs 0.417 | 5 | | **train + all fixes (TF2) vs dev, 10k** | **0.286 vs 0.290** | **0.388 vs 0.401** | 5 | Culprits: **#829** (`Auth::authorize_room_delivery`, called per delivered frame, does a sharing-db read even for the owner's own note) and **#835** (the same check per queued frame in the room, again per written frame in `live.rs`, plus two more reads per room join and a clone of the caller). Both are the same cause: an owner reading their own note never depends on a grant row, but every frame paid a database hop. ### Final train + fixes vs dev, both sizes (3 pairs, ms; p50 / p95) | Metric | Home | dev | train+fixes | note | |---|---|---|---|---| | sync.round_trip | 10k | 0.214 / 0.351 | 0.231 / 0.341 | p95 flat, p50 +8% inside range overlap | | sync.round_trip | 100k | 0.246 / 0.353 | 0.288 / 0.379 | **p50 +17%, p95 +7%: n=3, ranges overlap, not shown flat** | | sync.save_ack | 10k / 100k | 2.63 / 4.41, 3.31 / 5.11 | 3.11 / 4.17, 3.27 / 4.68 | flat or better | | open.note | 10k | 0.710 / 1.183 | 0.787 / 1.408 | +11% / +19%, n=3 | | open.note | 100k | 4.81 / 9.49 | 4.47 / 10.64 | flat / +12% | | open.api_get | 10k / 100k | 3.47 / 9.94, 5.04 / 12.2 | 3.31 / 10.95, 5.12 / 10.9 | flat | Earlier 5-pair 10k run of the same binary: round_trip 0.286 / 0.388 vs dev 0.290 / 0.401, open.note 0.817 / 1.531 vs 0.815 / 1.440 (flat). I could not get more 100k pairs through the shared lock; 100k round_trip needs a longer paired run before it can be called flat. ### Browser (Chromium 4x CPU, 10k Home; `boot.perf.ts`, `list-search.perf.ts`) Pooled, interleaved dev / #850 / #851 / #852 / #852+CSS-split runs across three windows (D n=21, #850 n=7, #851 n=5, #852 n=14, split n=7): | Metric (p50 ms) | dev | #850 | #851 | #852 | #852 + CSS split | |---|---|---|---|---|---| | boot.cold_fcp | 744 | 780 | 736 | 760 | 748 | | boot.cold_note | 938 | 988 | 927 | 951 | 968 | | list_search.key (p50 / p95) | 5.9 / 8.2 | 6.2 / 8.3 | 5.9 / 8.6 | 6.2 / 8.3 | 6.1 / 8.9 | The train's +10% cold FCP, +6% first note and +14% search-key p95 did **not** reproduce: in the quietest window (8 interleaved rounds) #852 was flat to dev (FCP 744 vs 756, first note 941 vs 962, search key 6.0 / 8.1 vs 5.8 / 8.0), and dev-vs-dev spread is 704–832 ms FCP. #850 shows the largest step (+2.6…4.8%, inside range overlap). I found no per-keystroke work added to the search path. Bundle (brotli KB, `bundle-size.mjs`): first CSS dev 18.8 → train 19.6 → with split 19.2; first JS 233.2 → 233.9 → 233.6. The remaining +0.4 KB CSS is the rest bar's own rules, needed by the first note screen. Fix branches pushed to the owning PRs: #829 `72eb0eb1`, #835 `46be23e3`, #850 `6eb81f62`. Train order check: #852 state + #829 + #835 + #895 merges cleanly (no conflicts) with all fixes; tree = train head + 5 changed files.
Author
Owner

Train superseded by #914 (BM14e), landed in dev via 12103f1c97. Closing the obsolete BM14c train.

Train superseded by #914 (BM14e), landed in dev via 12103f1c973f1e810dd4a9a3bff5a705514d70b0. Closing the obsolete BM14c train.
kayg closed this pull request 2026-11-07 17:53:14 +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!897
No description provided.