Reduce client work during Calendar Tab activation #1557

Open
opened 2026-10-10 19:54:33 +00:00 by kayg · 129 comments
Owner

Evidence

The #1233 profile measured the origin/dev release server (dbd621434d0a1133f16230cca5c6a83af0d8aed3) with trusted CDP input, Chromium, macOS platform emulation, 1440 px and light theme. Seven warm Mail→Calendar switches took 1,464.9 ms p50 and 1,813.5 ms p95 from trusted input through full paint. DESIGN §59 budgets 100 ms for a warm Tab switch.

A separate marked warm trace on the 10,000-file diagnostic fixture reached full paint in about 1.75 s. The browser trace shows a 644.377 ms FunctionCall in the Svelte runtime bundle (BIPLcumV.js) and five long tasks, max 669 ms. The measured server timing was about 0.098 ms for the observed request, so server work did not explain that warm sample. Trace: artifacts/tab-switch-1233-mail-calendar/mail-calendar-warm-trace.json; request waterfall: artifacts/tab-switch-1233-mail-calendar/mail-calendar-warm-network.json in the #1233 worktree.

A one-sample cold trace on the reduced 100-file fixture took 2,408.8 ms input-to-full-paint. It included Svelte runtime work of 592.642 ms and main-thread tasks of 926.447 ms and 816.967 ms. Calendar API timing also showed DB-backed reads around 0.19–0.30 s. This confirms the client activation cost is a warm-path bottleneck; cold Calendar fetch cost is additional and needs a separate measured change.

Trace: artifacts/tab-switch-1233-cold100-recheck/mail-calendar-cold-trace.json; request waterfall: artifacts/tab-switch-1233-cold100-recheck/mail-calendar-cold-network.json in the #1233 worktree. These are diagnostic single traces, not a candidate A/B comparison.

Request

Profile Calendar Tab activation and reduce the main-thread Svelte work after a warm switch. Keep server fetch work separate so each change has a clear measured effect. Add a regression profile for Mail→Calendar and compare five interleaved A/B pairs; require p50 and p95 to improve by at least 10% without another cell's p95 regressing by more than 10%.

## Evidence The #1233 profile measured the origin/dev release server (`dbd621434d0a1133f16230cca5c6a83af0d8aed3`) with trusted CDP input, Chromium, macOS platform emulation, 1440 px and light theme. Seven warm Mail→Calendar switches took 1,464.9 ms p50 and 1,813.5 ms p95 from trusted input through full paint. DESIGN §59 budgets 100 ms for a warm Tab switch. A separate marked warm trace on the 10,000-file diagnostic fixture reached full paint in about 1.75 s. The browser trace shows a 644.377 ms `FunctionCall` in the Svelte runtime bundle (`BIPLcumV.js`) and five long tasks, max 669 ms. The measured server timing was about 0.098 ms for the observed request, so server work did not explain that warm sample. Trace: `artifacts/tab-switch-1233-mail-calendar/mail-calendar-warm-trace.json`; request waterfall: `artifacts/tab-switch-1233-mail-calendar/mail-calendar-warm-network.json` in the #1233 worktree. A one-sample cold trace on the reduced 100-file fixture took 2,408.8 ms input-to-full-paint. It included Svelte runtime work of 592.642 ms and main-thread tasks of 926.447 ms and 816.967 ms. Calendar API timing also showed DB-backed reads around 0.19–0.30 s. This confirms the client activation cost is a warm-path bottleneck; cold Calendar fetch cost is additional and needs a separate measured change. Trace: `artifacts/tab-switch-1233-cold100-recheck/mail-calendar-cold-trace.json`; request waterfall: `artifacts/tab-switch-1233-cold100-recheck/mail-calendar-cold-network.json` in the #1233 worktree. These are diagnostic single traces, not a candidate A/B comparison. ## Request Profile Calendar Tab activation and reduce the main-thread Svelte work after a warm switch. Keep server fetch work separate so each change has a clear measured effect. Add a regression profile for Mail→Calendar and compare five interleaved A/B pairs; require p50 and p95 to improve by at least 10% without another cell's p95 regressing by more than 10%.
Author
Owner

Starting #1557 on job/calact-1557. The worktree is clean at 0c712b98c164b6734d54f63485012bd1a11005b4, a direct child of the requested 4ebeba0b8e13b06223bc2b24ee7a398a0933bda0; the newer commit only adds the weekly benchmark run artifacts. I am bringing in the #1233 profiling harness before profiling Calendar activation.

Starting #1557 on `job/calact-1557`. The worktree is clean at `0c712b98c164b6734d54f63485012bd1a11005b4`, a direct child of the requested `4ebeba0b8e13b06223bc2b24ee7a398a0933bda0`; the newer commit only adds the weekly benchmark run artifacts. I am bringing in the #1233 profiling harness before profiling Calendar activation.
Author
Owner

The source-mapped profile setup exceeded the harness default startup limit: after 25 minutes, /healthz still reported migrating / notes-upgrade-backfills. The runner stopped cleanly before a switch was measured or a fixture snapshot was saved. I am retrying with the harness-supported 60-minute readiness limit so the 10k-file Home can settle before profiling.

The source-mapped profile setup exceeded the harness default startup limit: after 25 minutes, `/healthz` still reported `migrating / notes-upgrade-backfills`. The runner stopped cleanly before a switch was measured or a fixture snapshot was saved. I am retrying with the harness-supported 60-minute readiness limit so the 10k-file Home can settle before profiling.
Author
Owner

Baseline visual evidence is captured in the worktree at artifacts/calact-1557/before/: 12 production screenshots covering Calendar Week and Month at 390, 820, and 1440 px in light and dark, with Chromium macOS emulation. The screenshot script verifies a real CalDAV Event is returned by the Calendar API, waits for it in each view, and confirms the update notice is absent. The script is committed as cfad824f.

Baseline visual evidence is captured in the worktree at `artifacts/calact-1557/before/`: 12 production screenshots covering Calendar Week and Month at 390, 820, and 1440 px in light and dark, with Chromium macOS emulation. The screenshot script verifies a real CalDAV Event is returned by the Calendar API, waits for it in each view, and confirms the update notice is absent. The script is committed as `cfad824f`.
Author
Owner

The 10k-file full fixture did not reach server readiness: /healthz remained migrating / notes-upgrade-backfills after 50 minutes of seeded-server time (52 minutes in the perf-run call). No Calendar sample was measured. I am stopping this attempt and will keep the 10k Files scale while using the harness smoke seed for unrelated Notes, Tasks, and Money data, identically for source profiling and both sides of the A/B comparison.

The 10k-file full fixture did not reach server readiness: `/healthz` remained `migrating / notes-upgrade-backfills` after 50 minutes of seeded-server time (52 minutes in the `perf-run` call). No Calendar sample was measured. I am stopping this attempt and will keep the 10k Files scale while using the harness smoke seed for unrelated Notes, Tasks, and Money data, identically for source profiling and both sides of the A/B comparison.
Author
Owner

The smoke-level startup attempt did not reach Calendar profiling. /healthz stayed at migrating / prepare-data-directory; after the harness's 5-minute TAB_SWITCH_START_TIMEOUT_MS, startRemoteServer failed with: native media command failed; check runtime and service limits for launcher failures cause=Launch. I stopped the runner and server and verified the perf lock is free. I am checking the server startup constraint and will retry with a longer start timeout.

The smoke-level startup attempt did not reach Calendar profiling. `/healthz` stayed at `migrating / prepare-data-directory`; after the harness's 5-minute `TAB_SWITCH_START_TIMEOUT_MS`, `startRemoteServer` failed with: `native media command failed; check runtime and service limits for launcher failures cause=Launch`. I stopped the runner and server and verified the perf lock is free. I am checking the server startup constraint and will retry with a longer start timeout.
Author
Owner

Operational note: a read-only disk diagnostic (du plus file count) ran outside perf-run and overlapped the lock held by job rec1540b-a1. It scanned 486 files (26 MB under the benchmark data directory) and took about 11 seconds. It may have added light I/O to that run. I stopped further perf-VM checks and will hold the lock for all next diagnostics and benchmarks.

Operational note: a read-only disk diagnostic (`du` plus file count) ran outside `perf-run` and overlapped the lock held by job `rec1540b-a1`. It scanned 486 files (26 MB under the benchmark `data` directory) and took about 11 seconds. It may have added light I/O to that run. I stopped further perf-VM checks and will hold the lock for all next diagnostics and benchmarks.
Author
Owner

Finding for #1557: the imported Tab-switch harness uses /mnt/hdd/targets/jobs/tabswitch2/fixture-snapshot for perf-VM snapshots. Creating this issue's 10,000-File fixture there could replace another job's saved Home. I added TAB_SWITCH_FIXTURE_SNAPSHOT_DIR so this job can use /mnt/hdd/targets/jobs/calact-1557/fixture-snapshot while preserving existing defaults. Focused check: bun test bench/tab-switch.test.mjs — 24 pass, 0 fail.

Finding for #1557: the imported Tab-switch harness uses /mnt/hdd/targets/jobs/tabswitch2/fixture-snapshot for perf-VM snapshots. Creating this issue's 10,000-File fixture there could replace another job's saved Home. I added TAB_SWITCH_FIXTURE_SNAPSHOT_DIR so this job can use /mnt/hdd/targets/jobs/calact-1557/fixture-snapshot while preserving existing defaults. Focused check: bun test bench/tab-switch.test.mjs — 24 pass, 0 fail.
Author
Owner

Source-map profile finding (local fallback while the perf VM was held):

  • A 1440 px light warm Mail→Calendar switch completed with input→two-frames 1168.5 ms, input→fully-painted 1485.2 ms, and one 607 ms main-thread long task. This is a single diagnostic sample on the build host at load average 1/5m 2.87/5.50, so it is not an A/B result.
  • The 596.8 ms FunctionCall maps to Svelte’s DOM task flush. Its stack enters the Calendar keyed each-block and GridColumn.svelte; measured self frames include TimeGrid.svelte measure (about 101 ms in native scrollTo and 62 ms in clientWidth), ModeHeader.svelte measure/offset (about 99 ms), then smaller GridColumn.svelte attachment and calendar/layout.ts item-slot work.
  • This is a real 10,000-file / 20-note / 20-task smoke Home with 366 Daily Notes and 10,980 Log entries; it is not representative of average Home size. I am checking whether the activation-time layout reads/scroll write can be made redundant without changing Calendar pixels or scroll restoration.
Source-map profile finding (local fallback while the perf VM was held): - A 1440 px light warm Mail→Calendar switch completed with input→two-frames 1168.5 ms, input→fully-painted 1485.2 ms, and one 607 ms main-thread long task. This is a single diagnostic sample on the build host at load average 1/5m 2.87/5.50, so it is not an A/B result. - The 596.8 ms FunctionCall maps to Svelte’s DOM task flush. Its stack enters the Calendar keyed each-block and `GridColumn.svelte`; measured self frames include `TimeGrid.svelte` measure (about 101 ms in native `scrollTo` and 62 ms in `clientWidth`), `ModeHeader.svelte` measure/offset (about 99 ms), then smaller `GridColumn.svelte` attachment and `calendar/layout.ts` item-slot work. - This is a real 10,000-file / 20-note / 20-task smoke Home with 366 Daily Notes and 10,980 Log entries; it is not representative of average Home size. I am checking whether the activation-time layout reads/scroll write can be made redundant without changing Calendar pixels or scroll restoration.
Author
Owner

Fix finding: the first TimeGrid.measure() set --colw and called scrollTo, then its immediately scheduled initialization frame set the same anchor again with scrollLeft. The first scroll write forced layout inside the activation flush. I now skip that write until the grid is initialized; resize and later navigation paths still use the existing scroll behavior. The focused component test checks that the grid reaches ready with its measured track and no initial scrollTo call. bunx vitest run src/lib/calendar/TimeGrid.svelte.test.ts --maxWorkers=2 passed (20 tests). The pinned production build completed successfully. The perf VM became busy again before the paired run; I am staging its candidate and fixture locally and will resume measurements when perf-run status is free.

Fix finding: the first `TimeGrid.measure()` set `--colw` and called `scrollTo`, then its immediately scheduled initialization frame set the same anchor again with `scrollLeft`. The first scroll write forced layout inside the activation flush. I now skip that write until the grid is initialized; resize and later navigation paths still use the existing scroll behavior. The focused component test checks that the grid reaches `ready` with its measured track and no initial `scrollTo` call. `bunx vitest run src/lib/calendar/TimeGrid.svelte.test.ts --maxWorkers=2` passed (20 tests). The pinned production build completed successfully. The perf VM became busy again before the paired run; I am staging its candidate and fixture locally and will resume measurements when `perf-run status` is free.
Author
Owner

The web check found stale exact TimeGrid pins after the measured change. I compared the 38 live TimeGrid findings with both perf ledgers; all 38 symbols still exist. I refreshed only their changed syntax hashes in contracts/perf/exceptions.json and contracts/perf/adoption-1058.json. Exception counts, limits and contracts/perf/ratchet.json are unchanged. bun run check then passed, and the full requested web test command passed: 318 files and 2,317 tests.

The web check found stale exact TimeGrid pins after the measured change. I compared the 38 live TimeGrid findings with both perf ledgers; all 38 symbols still exist. I refreshed only their changed syntax hashes in `contracts/perf/exceptions.json` and `contracts/perf/adoption-1058.json`. Exception counts, limits and `contracts/perf/ratchet.json` are unchanged. `bun run check` then passed, and the full requested web test command passed: 318 files and 2,317 tests.
Author
Owner

#1557 performance run finding: the first perf VM attempt passed both HDD qualification probes, restored the settled fixture, and reached /healthz state ok in 42.6 s. Chromium then returned Target crashed from CDP while waitForPhotoIndex checked fixture readiness, before the timing loop started. This attempt produced no switch samples and is not counted. The server and browser processes were cleaned up. The VM has since been reserved by another rec1540b-b2-repeat run; I will retry when perf-run status reports it free. Head: 1c6b0148478b5547f89b6bc5b60c1532ee89dfb5.

#1557 performance run finding: the first perf VM attempt passed both HDD qualification probes, restored the settled fixture, and reached `/healthz` state `ok` in 42.6 s. Chromium then returned `Target crashed` from CDP while `waitForPhotoIndex` checked fixture readiness, before the timing loop started. This attempt produced no switch samples and is not counted. The server and browser processes were cleaned up. The VM has since been reserved by another `rec1540b-b2-repeat` run; I will retry when `perf-run status` reports it free. Head: `1c6b0148478b5547f89b6bc5b60c1532ee89dfb5`.
Author
Owner

#1557 performance run finding: the single retry reproduced the same Chromium CDP Target crashed during waitForPhotoIndex at the Photos readiness page.evaluate, after the snapshot reached /healthz state ok and both HDD qualification probes passed. It failed before the timing loop, so both attempts have zero switch samples and no A/B result. I will not retry the A/B loop again. A minimal Chromium data: page probe is queued behind the current VM reservation through perf-run to distinguish browser instability from the fixture readiness path. Head: 1c6b0148478b5547f89b6bc5b60c1532ee89dfb5.

#1557 performance run finding: the single retry reproduced the same Chromium CDP `Target crashed` during `waitForPhotoIndex` at the Photos readiness `page.evaluate`, after the snapshot reached `/healthz` state `ok` and both HDD qualification probes passed. It failed before the timing loop, so both attempts have zero switch samples and no A/B result. I will not retry the A/B loop again. A minimal Chromium `data:` page probe is queued behind the current VM reservation through `perf-run` to distinguish browser instability from the fixture readiness path. Head: `1c6b0148478b5547f89b6bc5b60c1532ee89dfb5`.
Author
Owner

#1557 diagnostic: Chromium 153.0.8010.12 launched successfully under perf-run; a data:text/html,ok page completed a CDP Runtime.evaluate and returned ok. This rules out a general browser-launch/CDP failure. The reproducible crash is limited to the benchmark’s authenticated readiness page/API path at waitForPhotoIndex; its exact cause remains unknown. No A/B retry was made, so the relative latency acceptance rule remains unmeasured. Head: 1c6b0148478b5547f89b6bc5b60c1532ee89dfb5.

#1557 diagnostic: Chromium `153.0.8010.12` launched successfully under `perf-run`; a `data:text/html,ok` page completed a CDP `Runtime.evaluate` and returned `ok`. This rules out a general browser-launch/CDP failure. The reproducible crash is limited to the benchmark’s authenticated readiness page/API path at `waitForPhotoIndex`; its exact cause remains unknown. No A/B retry was made, so the relative latency acceptance rule remains unmeasured. Head: `1c6b0148478b5547f89b6bc5b60c1532ee89dfb5`.
Author
Owner

calact-1557 report

Built

The Calendar TimeGrid.measure() initial sizing path no longer writes a duplicate instant scrollTo. Its already scheduled initialization frame sets the same final horizontal anchor. Later resize and navigation measurements keep their existing scroll behavior. This removes one DOM scroll write per initial Week/Day grid mount, an O(1) change. The UI and Calendar view are unchanged.

A source-mapped production trace showed a 596.8 ms Svelte FunctionCall in the dom/task.js → batch.flush → keyed each update path. The largest application/native self-time frames were TimeGrid.measure() → native scrollTo (100.9 ms), ModeHeader.offset() from ModeHeader.measure() (99.1 ms), and TimeGrid.measure()’s clientWidth read (61.6 ms). Smaller work included Calendar GridColumn attachment, calendar/attachments.ts, and calendar/layout.ts.

The reported issue baseline was 1,465/1,814 ms p50/p95. The required perf-VM A/B did not reach its sample loop. Both attempts restored the settled fixture, reached /healthz state ok (42.6 s and 43.6 s), and passed the two HDD qualification probes. Chromium then returned CDP Target crashed from waitForPhotoIndex at the Photos readiness page.evaluate. Neither attempt recorded a switch sample. A separate locked probe passed on Chromium 153.0.8010.12 with a data: page and CDP evaluation, so the exact failure is limited to the authenticated readiness page/API path; its cause is unknown. No more A/B retries were run.

A/B acceptance: unmeasured. The required 5 interleaved baseline/candidate samples for each of Mail→Calendar, Files→Calendar, Calendar→Files and Notes→Tasks are not available. This report does not claim the ≥10% Mail→Calendar p50/p95 improvement or the ≤10% other-cell p95 regression rule passed. The §59 100 ms Warm Tab switch budget also remains unverified. Fixture plan: 10,000 Files, 366 Daily Notes, 10,980 Log entries, 30 recurring Events, 20 Notes and 20 Tasks; 1440 px, light, warm, Chromium. VM load was recorded inside perf-run; the two failed attempts started at load averages 2.66/2.52/2.41 and 3.08/2.68/2.59.

The before/after production screenshots below use a real CalDAV-backed Event, Chromium with macOS platform emulation, matching web/server build IDs, both themes and all required widths. Week and Month each have phone, tablet and desktop captures. The reviewer should judge the visuals.

View and width Before After
Week, 390 px light dark light dark
Week, 820 px light dark light dark
Week, 1440 px light dark light dark
Month, 390 px light dark light dark
Month, 820 px light dark light dark
Month, 1440 px light dark light dark

Files and commits

  • packages/ui/src/components/calendar/TimeGrid.svelte — initial scroll-write optimization and invariant comment.
  • apps/web/src/lib/calendar/TimeGrid.svelte.test.ts — regression test for the initial measurement frame.
  • bench/tab-switch.mjs, bench/tab-switch.test.mjs, bench/cdp-input.mjs, bench/cdp-input.test.mjs — focused #1233 comparison and trusted-input harness.
  • apps/web/e2e/harness.mjs, apps/web/e2e/harness.test.mjs, apps/web/e2e/calendar-tab-switch-1557.mjs — fixture isolation, platform emulation regression and screenshot capture.
  • contracts/perf/exceptions.json, contracts/perf/adoption-1058.json — refreshed exact TimeGrid source pins; exception counts and limits are unchanged; contracts/perf/ratchet.json did not change.

Commits: 3e45f6f89, cfad824f6, b4d56f816, ab1193f1f, 1c6b01484. Head: 1c6b0148478b5547f89b6bc5b60c1532ee89dfb5.

Gates (verbatim)

cd apps/web && bun run check:

perf-lint: PASS; 0 violations; 22128 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact-1557/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

cd apps/web && bun run test -- --maxWorkers=2:

 Test Files  318 passed (318)
      Tests  2317 passed (2317)
   Start at  03:58:49
   Duration  225.19s (environment 28%, transform 23%, import 22%, tests 20%, setup 5%)

Environment  |component| jsdom was created 131 times · 96.62s total, 32% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

Rust gates were not run because no Rust crate changed. cargo clean completed and apps/web/build plus apps/web/.svelte-kit were removed.

Gaps and decisions

  • Known gap: resolve the reproducible Chromium failure at the authenticated waitForPhotoIndex readiness path, then run the prescribed perf-VM A/B matrix. Its result must establish the fixed acceptance rule.
  • UX gaps closed/left: no visible controls or user flow changed; none were identified. Visual review of the attached captures remains for the orchestrator.
  • Decision: skip the first scrollTo because the next initialization frame already writes the equivalent anchor; retain later resize/navigation writes. No visual design choice outside DESIGN §59 was needed.
  • The worktree was already based on origin/dev 0c712b98c164b6734d54f63485012bd1a11005b4, while the job prompt named 4ebeba0b8. This was disclosed at job start; the A/B comparison build and prebuilt API server were pinned to 4ebeba0b8e13b06223bc2b24ee7a398a0933bda0. The required one-time merge of origin/dev was already up to date.
# calact-1557 report ## Built The Calendar `TimeGrid.measure()` initial sizing path no longer writes a duplicate instant `scrollTo`. Its already scheduled initialization frame sets the same final horizontal anchor. Later resize and navigation measurements keep their existing scroll behavior. This removes one DOM scroll write per initial Week/Day grid mount, an O(1) change. The UI and Calendar view are unchanged. A source-mapped production trace showed a 596.8 ms Svelte `FunctionCall` in the `dom/task.js` → `batch.flush` → keyed each update path. The largest application/native self-time frames were `TimeGrid.measure()` → native `scrollTo` (100.9 ms), `ModeHeader.offset()` from `ModeHeader.measure()` (99.1 ms), and `TimeGrid.measure()`’s `clientWidth` read (61.6 ms). Smaller work included Calendar `GridColumn` attachment, `calendar/attachments.ts`, and `calendar/layout.ts`. The reported issue baseline was 1,465/1,814 ms p50/p95. The required perf-VM A/B did not reach its sample loop. Both attempts restored the settled fixture, reached `/healthz` state `ok` (42.6 s and 43.6 s), and passed the two HDD qualification probes. Chromium then returned CDP `Target crashed` from `waitForPhotoIndex` at the Photos readiness `page.evaluate`. Neither attempt recorded a switch sample. A separate locked probe passed on Chromium 153.0.8010.12 with a `data:` page and CDP evaluation, so the exact failure is limited to the authenticated readiness page/API path; its cause is unknown. No more A/B retries were run. **A/B acceptance: unmeasured.** The required 5 interleaved baseline/candidate samples for each of Mail→Calendar, Files→Calendar, Calendar→Files and Notes→Tasks are not available. This report does not claim the ≥10% Mail→Calendar p50/p95 improvement or the ≤10% other-cell p95 regression rule passed. The §59 100 ms Warm Tab switch budget also remains unverified. Fixture plan: 10,000 Files, 366 Daily Notes, 10,980 Log entries, 30 recurring Events, 20 Notes and 20 Tasks; 1440 px, light, warm, Chromium. VM load was recorded inside `perf-run`; the two failed attempts started at load averages 2.66/2.52/2.41 and 3.08/2.68/2.59. The before/after production screenshots below use a real CalDAV-backed Event, Chromium with macOS platform emulation, matching web/server build IDs, both themes and all required widths. Week and Month each have phone, tablet and desktop captures. The reviewer should judge the visuals. | View and width | Before | After | | --- | --- | --- | | Week, 390 px | [light](<https://git.kayg.org/attachments/57f81d7b-47e4-41c6-9a8b-3bc8c4a963e6>) [dark](<https://git.kayg.org/attachments/d7c443bb-091a-4d53-b723-355d41b9668f>) | [light](<https://git.kayg.org/attachments/06733a35-4760-4df8-86c4-54c11c7b7a0b>) [dark](<https://git.kayg.org/attachments/29380d8c-bf99-40bf-912e-3e6381e54724>) | | Week, 820 px | [light](<https://git.kayg.org/attachments/b81b06ab-5391-4cf4-9684-583283e1c0bd>) [dark](<https://git.kayg.org/attachments/2305fbcd-4464-40ef-bdec-49829766447d>) | [light](<https://git.kayg.org/attachments/d67674f5-3ae5-4da4-ac27-c4f6bd55c527>) [dark](<https://git.kayg.org/attachments/0f761c7d-60c5-4e2e-a8e1-a2e4b7512538>) | | Week, 1440 px | [light](<https://git.kayg.org/attachments/126aacbb-276e-41ab-a172-e5a17b02eb15>) [dark](<https://git.kayg.org/attachments/1cb94a02-9c33-47f0-967d-b72729311786>) | [light](<https://git.kayg.org/attachments/15ac41a1-725a-46f1-b505-3b5453b1c059>) [dark](<https://git.kayg.org/attachments/680172a3-69aa-4111-b2c4-bfef48f44ce0>) | | Month, 390 px | [light](<https://git.kayg.org/attachments/e685cc63-c0cf-44d7-9fbe-b1256dbe0afd>) [dark](<https://git.kayg.org/attachments/26ccc95a-ba25-4917-a22e-34c7f8968b97>) | [light](<https://git.kayg.org/attachments/81574cbc-80ca-4576-93c6-22f6da310b7b>) [dark](<https://git.kayg.org/attachments/1be5c568-ea51-4d78-912e-7bf8f35ea724>) | | Month, 820 px | [light](<https://git.kayg.org/attachments/9ea40150-5496-4c6c-b566-daca8dcc525e>) [dark](<https://git.kayg.org/attachments/7f527cf1-6c43-47e9-b7b8-de7c742c1fb1>) | [light](<https://git.kayg.org/attachments/7f3d3c72-31bf-45bc-b9b3-5dd332a50f82>) [dark](<https://git.kayg.org/attachments/f3d334e5-9baa-462d-86ea-e4a0a78182f5>) | | Month, 1440 px | [light](<https://git.kayg.org/attachments/61c0ee4d-d613-498b-8780-051bc30698cd>) [dark](<https://git.kayg.org/attachments/a7efe432-1821-4ed1-af0b-816b6ffd7f26>) | [light](<https://git.kayg.org/attachments/8965ff11-0173-44f0-a20c-40bc73071c5b>) [dark](<https://git.kayg.org/attachments/eda34fde-5895-4222-b28a-877cd64ab18a>) | ## Files and commits - `packages/ui/src/components/calendar/TimeGrid.svelte` — initial scroll-write optimization and invariant comment. - `apps/web/src/lib/calendar/TimeGrid.svelte.test.ts` — regression test for the initial measurement frame. - `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`, `bench/cdp-input.mjs`, `bench/cdp-input.test.mjs` — focused #1233 comparison and trusted-input harness. - `apps/web/e2e/harness.mjs`, `apps/web/e2e/harness.test.mjs`, `apps/web/e2e/calendar-tab-switch-1557.mjs` — fixture isolation, platform emulation regression and screenshot capture. - `contracts/perf/exceptions.json`, `contracts/perf/adoption-1058.json` — refreshed exact TimeGrid source pins; exception counts and limits are unchanged; `contracts/perf/ratchet.json` did not change. Commits: `3e45f6f89`, `cfad824f6`, `b4d56f816`, `ab1193f1f`, `1c6b01484`. Head: `1c6b0148478b5547f89b6bc5b60c1532ee89dfb5`. ## Gates (verbatim) `cd apps/web && bun run check`: ```text perf-lint: PASS; 0 violations; 22128 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact-1557/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bun run test -- --maxWorkers=2`: ```text Test Files 318 passed (318) Tests 2317 passed (2317) Start at 03:58:49 Duration 225.19s (environment 28%, transform 23%, import 22%, tests 20%, setup 5%) Environment |component| jsdom was created 131 times · 96.62s total, 32% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` Rust gates were not run because no Rust crate changed. `cargo clean` completed and `apps/web/build` plus `apps/web/.svelte-kit` were removed. ## Gaps and decisions - **Known gap:** resolve the reproducible Chromium failure at the authenticated `waitForPhotoIndex` readiness path, then run the prescribed perf-VM A/B matrix. Its result must establish the fixed acceptance rule. - **UX gaps closed/left:** no visible controls or user flow changed; none were identified. Visual review of the attached captures remains for the orchestrator. - **Decision:** skip the first `scrollTo` because the next initialization frame already writes the equivalent anchor; retain later resize/navigation writes. No visual design choice outside DESIGN §59 was needed. - The worktree was already based on `origin/dev` `0c712b98c164b6734d54f63485012bd1a11005b4`, while the job prompt named `4ebeba0b8`. This was disclosed at job start; the A/B comparison build and prebuilt API server were pinned to `4ebeba0b8e13b06223bc2b24ee7a398a0933bda0`. The required one-time merge of `origin/dev` was already up to date.
Author
Owner

Starting #1557 on branch job/calact2 at base 1c6b0148478b5547f89b6bc5b60c1532ee89dfb5. I will first reproduce the perf VM renderer crash with a one-pair smoke, then reduce Calendar activation layout work and rerun the requested trace, A/B profile, screenshots, and web gates.

Starting #1557 on branch `job/calact2` at base `1c6b0148478b5547f89b6bc5b60c1532ee89dfb5`. I will first reproduce the perf VM renderer crash with a one-pair smoke, then reduce Calendar activation layout work and rerun the requested trace, A/B profile, screenshots, and web gates.
Author
Owner

Finding for #1557: waitForPhotoIndex fetches /api/v1/photos/timeline/buckets inside the Chromium renderer and calls response.json() on the full day-bucket list, then keeps only total. The API returns a days row for every captured day (for 100k Photos, the route comment says this can be a few thousand rows). This makes renderer-side response parsing a concrete crash candidate, but does not prove it caused the perf VM crash. I am moving the readiness poll to authenticated BrowserContext.request and will verify with a one-pair smoke.

Finding for #1557: `waitForPhotoIndex` fetches `/api/v1/photos/timeline/buckets` inside the Chromium renderer and calls `response.json()` on the full day-bucket list, then keeps only `total`. The API returns a `days` row for every captured day (for 100k Photos, the route comment says this can be a few thousand rows). This makes renderer-side response parsing a concrete crash candidate, but does not prove it caused the perf VM crash. I am moving the readiness poll to authenticated `BrowserContext.request` and will verify with a one-pair smoke.
Author
Owner

Screenshot finding: the baseline set completed with 18 captures, but the candidate set stopped at its first view because the candidate production build ID differs from the pinned 4ebeba0b8 server. The app showed “Update available”, and the capture assertion failed. I am updating the screenshot context to report the selected asset build ID from /api/v1/version, matching the existing paired-benchmark behavior; this only affects the review harness.

Screenshot finding: the baseline set completed with 18 captures, but the candidate set stopped at its first view because the candidate production build ID differs from the pinned 4ebeba0b8 server. The app showed “Update available”, and the capture assertion failed. I am updating the screenshot context to report the selected asset build ID from `/api/v1/version`, matching the existing paired-benchmark behavior; this only affects the review harness.
Author
Owner

The post-merge bun run check found a stale exact performance exception after the ModeHeader ResizeObserver scope changed: layout.observer-purpose in packages/ui/src/components/ModeHeader.svelte reported the prior symbol <module>:45618a0c0c0ddf2f as unused or changed. I am refreshing the exact live entry and will not increase the committed ratchet.

The post-merge `bun run check` found a stale exact performance exception after the ModeHeader ResizeObserver scope changed: `layout.observer-purpose` in `packages/ui/src/components/ModeHeader.svelte` reported the prior symbol `<module>:45618a0c0c0ddf2f` as unused or changed. I am refreshing the exact live entry and will not increase the committed ratchet.
Author
Owner

The corrected bun run check found the paired adoption checkpoint also retained the old identities: contracts/perf/adoption-1058.json reported “adoption checkpoint differs from its exact live ledger entry” for the same four ModeHeader observers. I am refreshing those four checkpoint identities to the live entries; counts and the ratchet remain unchanged.

The corrected `bun run check` found the paired adoption checkpoint also retained the old identities: `contracts/perf/adoption-1058.json` reported “adoption checkpoint differs from its exact live ledger entry” for the same four ModeHeader observers. I am refreshing those four checkpoint identities to the live entries; counts and the ratchet remain unchanged.
Author
Owner

The perf-lint check then found another stale exact pin in the same edited module: render.bound for packages/ui/src/components/ModeHeader.svelte#each (old scope 473edc9628bc3230). I am checking all live exceptions in both changed UI modules at once and will refresh only identities that still have matching live findings.

The perf-lint check then found another stale exact pin in the same edited module: `render.bound` for `packages/ui/src/components/ModeHeader.svelte#each` (old scope `473edc9628bc3230`). I am checking all live exceptions in both changed UI modules at once and will refresh only identities that still have matching live findings.
Author
Owner

Progress: the fixed one-pair smoke has not reached waitForPhotoIndex yet. At 2026-10-11 03:56 UTC, the perf VM's public /healthz still reported state=migrating, phase notes-upgrade-backfills, after the seeded server had been up for about 9 minutes. Chromium remained alive at ~100 MB RSS with no renderer crash. I am letting startup continue within the requested 45-minute perf-VM troubleshooting window; if it does not advance, I will use the local HDD profile and label those measurements.

Progress: the fixed one-pair smoke has not reached `waitForPhotoIndex` yet. At 2026-10-11 03:56 UTC, the perf VM's public `/healthz` still reported `state=migrating`, phase `notes-upgrade-backfills`, after the seeded server had been up for about 9 minutes. Chromium remained alive at ~100 MB RSS with no renderer crash. I am letting startup continue within the requested 45-minute perf-VM troubleshooting window; if it does not advance, I will use the local HDD profile and label those measurements.
Author
Owner

Finding: the perf-VM smoke exited before waitForPhotoIndex after waitForStartupFilesReady timed out at 1,500,000 ms. /healthz stayed at migrating / notes-upgrade-backfills; Chromium stayed alive near 100 MB RSS, so this attempt did not reproduce the renderer crash. The smoke seed was larger than needed for a one-pair readiness check: one year of daily Notes with 30 log entries per day (924 files total in the test Home). I am reducing only the smoke fixture to one day and a few entries, then I will use the local fallback if the perf VM remains unavailable at the 45-minute limit. Full A/B fixture settings stay unchanged.

Finding: the perf-VM smoke exited before `waitForPhotoIndex` after `waitForStartupFilesReady` timed out at 1,500,000 ms. `/healthz` stayed at `migrating / notes-upgrade-backfills`; Chromium stayed alive near 100 MB RSS, so this attempt did not reproduce the renderer crash. The smoke seed was larger than needed for a one-pair readiness check: one year of daily Notes with 30 log entries per day (924 files total in the test Home). I am reducing only the smoke fixture to one day and a few entries, then I will use the local fallback if the perf VM remains unavailable at the 45-minute limit. Full A/B fixture settings stay unchanged.
Author
Owner

Follow-up: e602bf5d4 now keeps only the --smoke fixture to one Journal day, two Log entries, five Notes and two Tasks; the full A/B fixture is unchanged. The seed generator completed for the reduced data. bun test bench/tab-switch.test.mjs passed 30 tests and node --check plus git diff --check passed. The perf-VM lock is held by another job through its declared slot, which ends after this run's 45-minute troubleshooting window, so I will use the local profile at the cutoff.

Follow-up: `e602bf5d4` now keeps only the `--smoke` fixture to one Journal day, two Log entries, five Notes and two Tasks; the full A/B fixture is unchanged. The seed generator completed for the reduced data. `bun test bench/tab-switch.test.mjs` passed 30 tests and `node --check` plus `git diff --check` passed. The perf-VM lock is held by another job through its declared slot, which ends after this run's 45-minute troubleshooting window, so I will use the local profile at the cutoff.
Author
Owner

One-pair smoke passed on the local profile after the perf-VM window elapsed. With the reduced one-day fixture, startup reached /healthz state ok, the request-context Photos wait saw all 100 seeded Photos, and one warm Photos→Calendar switch completed without a Chromium crash (741.6 ms input-to-full-paint; smoke-only, not an A/B cell). The local profile has no HDD emulation; the upcoming comparison will be labeled local and include its host load.

One-pair smoke passed on the local profile after the perf-VM window elapsed. With the reduced one-day fixture, startup reached `/healthz` state `ok`, the request-context Photos wait saw all 100 seeded Photos, and one warm Photos→Calendar switch completed without a Chromium crash (741.6 ms input-to-full-paint; smoke-only, not an A/B cell). The local profile has no HDD emulation; the upcoming comparison will be labeled local and include its host load.
Author
Owner

Local A/B matrix completed: 5 interleaved samples per requested cell, Chromium, 1440 px, light, warm, 10,000 Files. The one-day smoke fixture supplied 1 Journal, 2 Log entries, 5 Notes and 2 Tasks. This ran locally without HDD emulation; load average before setup was 17.83/22.60/20.76 (1/5/15 minutes).

Input-to-full-paint p50/p95 ms, baseline → candidate:

  • Mail→Calendar: 824.1/1223.2 → 1036.3/1360.7 (+25.7%/+11.2%).
  • Files→Calendar: 991.4/1057.8 → 1260.5/1621.4 (+27.1%/+53.3%).
  • Calendar→Files: 799.1/1117.6 → 865.7/1207.3 (+8.3%/+8.0%).
  • Notes→Tasks: 649.4/805.2 → 523.1/695.5 (-19.4%/-13.6%).

The required Mail→Calendar improvement and no-regression thresholds did not pass in this local run. I am collecting the requested source-mapped candidate trace next; the high host load limits these comparison numbers.

Local A/B matrix completed: 5 interleaved samples per requested cell, Chromium, 1440 px, light, warm, 10,000 Files. The one-day smoke fixture supplied 1 Journal, 2 Log entries, 5 Notes and 2 Tasks. This ran locally without HDD emulation; load average before setup was 17.83/22.60/20.76 (1/5/15 minutes). Input-to-full-paint p50/p95 ms, baseline → candidate: - Mail→Calendar: 824.1/1223.2 → 1036.3/1360.7 (+25.7%/+11.2%). - Files→Calendar: 991.4/1057.8 → 1260.5/1621.4 (+27.1%/+53.3%). - Calendar→Files: 799.1/1117.6 → 865.7/1207.3 (+8.3%/+8.0%). - Notes→Tasks: 649.4/805.2 → 523.1/695.5 (-19.4%/-13.6%). The required Mail→Calendar improvement and no-regression thresholds did not pass in this local run. I am collecting the requested source-mapped candidate trace next; the high host load limits these comparison numbers.
Author
Owner

Trace attempt did not capture a trace: --trace-only used the normal Chromium+WebKit engine list and failed when the profiler requested a CDP session from WebKit (CDP session is only available in Chromium). I am retrying the trace with Chromium explicitly selected, 1440 px/light only, and the --trace capture flag.

Trace attempt did not capture a trace: `--trace-only` used the normal Chromium+WebKit engine list and failed when the profiler requested a CDP session from WebKit (`CDP session is only available in Chromium`). I am retrying the trace with Chromium explicitly selected, 1440 px/light only, and the `--trace` capture flag.
Author
Owner

Trace finding (#1557): after the TimeGrid and ModeHeader changes, the Calendar route still synchronously calls getBoundingClientRect() twice and reads route-content.clientHeight from its activation $effect (apps/web/src/routes/calendar/[view]/[date]/+page.svelte:2112-2123). The source-mapped candidate trace attributes about 20.55 ms of sampled self time to the getBoundingClientRect path. I am moving this measurement to ResizeObserver delivery so geometry reads happen after layout and the height comes from the observer entry.

Trace finding (#1557): after the TimeGrid and ModeHeader changes, the Calendar route still synchronously calls `getBoundingClientRect()` twice and reads `route-content.clientHeight` from its activation `$effect` (`apps/web/src/routes/calendar/[view]/[date]/+page.svelte:2112-2123`). The source-mapped candidate trace attributes about 20.55 ms of sampled self time to the `getBoundingClientRect` path. I am moving this measurement to ResizeObserver delivery so geometry reads happen after layout and the height comes from the observer entry.
Author
Owner

Implementation update: the route geometry reads are coalesced into one requestAnimationFrame after Tab activation, then all reads happen together before the unchanged height write. I kept the existing TimeGrid ResizeObserver entry as the source of its width and avoided adding another route observer. The original trace finding remains the reason for the change; the final source no longer reads this geometry from the activation effect.

Implementation update: the route geometry reads are coalesced into one requestAnimationFrame after Tab activation, then all reads happen together before the unchanged height write. I kept the existing TimeGrid ResizeObserver entry as the source of its width and avoided adding another route observer. The original trace finding remains the reason for the change; the final source no longer reads this geometry from the activation effect.
Author
Owner

The final A/B invocation exited before sampling: I pointed --comparison-build inside the runner's local profile, and fixture reset removed that directory before the build copy. No matrix samples were recorded. I am rebuilding the 4ebeba0b8 baseline outside the profile and will rerun the matrix.

The final A/B invocation exited before sampling: I pointed `--comparison-build` inside the runner's local profile, and fixture reset removed that directory before the build copy. No matrix samples were recorded. I am rebuilding the 4ebeba0b8 baseline outside the profile and will rerun the matrix.
Author
Owner

The final source-mapped trace removed the Calendar route's synchronous geometry path; the prior TimeGrid.clientWidth and large ModeHeader read are also absent. It still sampled 35.57 ms in get offsetLeft, called from apps/web/src/lib/actions/measuredCapsule.svelte.ts:58 during SegmentedControl selection. This helper runs on Tab activation, so I am moving its reads to the existing ResizeObserver delivery and adding a regression test.

The final source-mapped trace removed the Calendar route's synchronous geometry path; the prior `TimeGrid.clientWidth` and large `ModeHeader` read are also absent. It still sampled 35.57 ms in `get offsetLeft`, called from `apps/web/src/lib/actions/measuredCapsule.svelte.ts:58` during `SegmentedControl` selection. This helper runs on Tab activation, so I am moving its reads to the existing ResizeObserver delivery and adding a regression test.
Author
Owner

Source-mapped trace follow-up: after the earlier Calendar and shared-header changes, TimeGrid.measure() still spent 9.81 ms in a resize scrollTo immediately after writing --colw. I deferred that anchor correction to the next animation frame and cancel it when a newer scroll arrives. Added a regression test; the focused TimeGrid file passed (21 tests), bun run check passed with 0 Svelte errors/warnings and perf-lint: PASS; 0 violations; 22128 scoped exceptions, and the full web suite passed (318 files, 2320 tests). Only exact hashes on existing TimeGrid perf entries changed; ratchet counts did not change.

Source-mapped trace follow-up: after the earlier Calendar and shared-header changes, `TimeGrid.measure()` still spent 9.81 ms in a resize `scrollTo` immediately after writing `--colw`. I deferred that anchor correction to the next animation frame and cancel it when a newer scroll arrives. Added a regression test; the focused TimeGrid file passed (21 tests), `bun run check` passed with 0 Svelte errors/warnings and `perf-lint: PASS; 0 violations; 22128 scoped exceptions`, and the full web suite passed (318 files, 2320 tests). Only exact hashes on existing TimeGrid perf entries changed; ratchet counts did not change.
Author
Owner

Final source-mapped trace after cb8f01195 shows the resize TimeGrid.measure() scroll correction is no longer a costly activation frame (the remaining sampled scrollTo is 0.16 ms from date navigation). The trace still attributes 25.57 ms of getBoundingClientRect self time to apps/web/src/routes/+layout.svelte:612, where the bottom chrome's ResizeObserver and MutationObserver callbacks read the tray and write --tray-shift synchronously. I am coalescing that measurement into the next animation frame, then I will retrace once and report any remaining layout work.

Final source-mapped trace after `cb8f01195` shows the resize `TimeGrid.measure()` scroll correction is no longer a costly activation frame (the remaining sampled `scrollTo` is 0.16 ms from date navigation). The trace still attributes 25.57 ms of `getBoundingClientRect` self time to `apps/web/src/routes/+layout.svelte:612`, where the bottom chrome's ResizeObserver and MutationObserver callbacks read the tray and write `--tray-shift` synchronously. I am coalescing that measurement into the next animation frame, then I will retrace once and report any remaining layout work.
Author
Owner

Final local evidence on 919f4b5a3:

  • The 1-pair Mail→Calendar smoke restored the 10,000-file fixture and reached /healthz state ok; the renderer stayed alive. This host had load average 26.84 before the smoke, so its single latency sample is harness evidence only.
  • The final source-mapped trace no longer shows the old TimeGrid.measure() resize clientWidth/scrollTo frame or the heavy ModeHeader.offset() frame. scrollTo now maps to ordinary scrollToDate (11.28 ms). The bottom chrome getBoundingClientRect sample fell from 25.57 ms to 0.16 ms after it moved to a coalesced animation frame. The largest Svelte each.js FunctionCall was 179 ms; the trace still shows 79.08 ms in TabBar's existing capsule readSize path under this host load.
  • The 5-run A/B matrix completed for all four pairs on the local host, with 10,000 Files, 5 Notes, 2 Tasks and 30 recurring Events. Load average before setup was [17.41, 22.58, 21.26]; there was no HDD emulation. Mail→Calendar Input→2 frames was baseline 545.8/599.3 ms p50/p95 and candidate 1259.7/2291.6 ms, so the requested improvement failed. Files→Calendar p95 also regressed 36.5%; Notes→Tasks improved and Calendar→Files improved on Input→2 frames. I am reporting the measured miss; I did not raise the perf ratchet.
Final local evidence on `919f4b5a3`: - The 1-pair Mail→Calendar smoke restored the 10,000-file fixture and reached `/healthz` state `ok`; the renderer stayed alive. This host had load average 26.84 before the smoke, so its single latency sample is harness evidence only. - The final source-mapped trace no longer shows the old `TimeGrid.measure()` resize `clientWidth`/`scrollTo` frame or the heavy `ModeHeader.offset()` frame. `scrollTo` now maps to ordinary `scrollToDate` (11.28 ms). The bottom chrome `getBoundingClientRect` sample fell from 25.57 ms to 0.16 ms after it moved to a coalesced animation frame. The largest Svelte `each.js` FunctionCall was 179 ms; the trace still shows 79.08 ms in TabBar's existing capsule `readSize` path under this host load. - The 5-run A/B matrix completed for all four pairs on the local host, with 10,000 Files, 5 Notes, 2 Tasks and 30 recurring Events. Load average before setup was `[17.41, 22.58, 21.26]`; there was no HDD emulation. Mail→Calendar `Input→2 frames` was baseline 545.8/599.3 ms p50/p95 and candidate 1259.7/2291.6 ms, so the requested improvement failed. Files→Calendar p95 also regressed 36.5%; Notes→Tasks improved and Calendar→Files improved on `Input→2 frames`. I am reporting the measured miss; I did not raise the perf ratchet.
Author
Owner

Final report — #1557

Branch: job/calact2
Final head: 98677b92e851ae8761fc97b4dbb5563013f41443
Base: 1c6b01484; origin/dev was fetched and merged once and was already up to date. The final commit only cleans the app-shell module comment; the measured implementation head was 919f4b5a32ba306972554f3363b917fcede3b733.

Built

  • Calendar TimeGrid now uses ResizeObserver width entries, coalesces overflow sizing, and applies resize anchor corrections in the next animation frame. A newer scroll cancels a stale correction.
  • ModeHeader caches scroll state and reads row/inset sizes from ResizeObserver entries. Route and title changes schedule shared chrome work after the Svelte flush.
  • The shared measured capsule observes the active item and reads its geometry on observer delivery. SegmentedControl selection no longer measures layout in its activation effect.
  • Calendar route sizing and bottom tray alignment run in coalesced animation frames after DOM writes.
  • The Photos-readiness harness parses the response outside Chromium; the smoke fixture stays small while full profiles retain their full data set. Review captures route the matching production build identity to avoid update notices.
  • Existing exact performance-guard identities were refreshed. The ratchet count did not change.

Hot-path work is bounded: one coalesced measurement per affected component/frame, with no per-row observers or repeated activation reads. No visible controls or styles were added.

Files

apps/web/e2e/calendar-tab-switch-1557.mjs, apps/web/e2e/harness.mjs, apps/web/src/lib/actions/measuredCapsule.svelte.ts, apps/web/src/lib/calendar/TimeGrid.svelte.test.ts, apps/web/src/lib/components/SegmentedControl.svelte.test.ts, apps/web/src/lib/modeHeader.svelte.test.ts, apps/web/src/routes/+layout.svelte, apps/web/src/routes/calendar/[view]/[date]/+page.svelte, bench/tab-switch-seed.py, bench/tab-switch.mjs, bench/tab-switch.test.mjs, contracts/perf/adoption-1058.json, contracts/perf/exceptions.json, packages/ui/src/components/ModeHeader.svelte, packages/ui/src/components/calendar/TimeGrid.svelte.

Trace

  • The prior heavy TimeGrid.measure() clientWidth/scroll correction and ModeHeader.offset() activation paths no longer appear as heavy frames. Calendar scrollToDate is 11.28 ms in the final trace.
  • Bottom tray getBoundingClientRect fell from 25.57 ms in the earlier capture to 0.16 ms after deferral.
  • The final trace still sampled TabBar.svelte:184 getBoundingClientRect at 79.08 ms and the measured capsule offsetLeft at 9.49 ms. The TabBar sample is a remaining layout frame for follow-up; this report does not claim that all layout work is gone.
  • Largest post-click Svelte each.js FunctionCall: 179.01 ms. The earlier 597 ms Svelte-flush figure is a different trace aggregation, so this run does not support an apples-to-apples flush reduction percentage.
  • Source-mapped trace summary.

A/B performance

Five interleaved samples per direction at 1440 px, light theme, warm switch; base 4ebeba0b8e13b06223bc2b24ee7a398a0933bda0, candidate 919f4b5a32ba306972554f3363b917fcede3b733. The perf VM remained in migrating/notes-upgrade-backfills for 25 minutes, so the run used the local build host without HDD emulation. Load average before setup was 17.41, 22.58, 21.26. Fixture: 10,000 Files, 5 Notes, 2 Tasks, 30 recurring Events, no Photos.

Input-to-two-frames p50/p95 in milliseconds (base → candidate):

Switch Base p50 / p95 Candidate p50 / p95
Mail → Calendar 545.8 / 599.3 1259.7 / 2291.6
Files → Calendar 543.9 / 568.6 634.2 / 776.5
Calendar → Files 398.4 / 537.4 334.2 / 369.8
Notes → Tasks 306.8 / 372.7 235.9 / 278.4

Full-paint p50/p95 in milliseconds (base → candidate):

Switch Base p50 / p95 Candidate p50 / p95
Mail → Calendar 1318.3 / 1458.4 1840.6 / 2819.4
Files → Calendar 1129.4 / 1490.3 1707.1 / 2803.4
Calendar → Files 953.9 / 1096.0 1058.5 / 1386.6
Notes → Tasks 867.5 / 1051.8 880.4 / 953.1

Acceptance missed. Mail → Calendar did not improve; Files → Calendar p95 regressed 36.5%. Candidate Mail runs also had more requests than baseline in some samples, including an 18-request, 4.7 MB outlier. The high host load and request differences limit attribution; the numbers are reported without adjustment.

Gates

Web gates ran on the implementation head above. The final head adds only the module-comment cleanup described above.

cd apps/web && bun run check:

perf-lint: PASS; 0 violations; 22128 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact2/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

cd apps/web && bun run test -- --maxWorkers=2:

 Test Files  318 passed (318)
      Tests  2320 passed (2320)
   Start at  08:32:40
   Duration  425.50s (environment 27%, import 23%, transform 23%, tests 20%, setup 6%)

Environment  |component| jsdom was created 131 times · 178.46s total, 31% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

cargo fmt, cargo clippy, and cargo test were not run because this job changed no Rust crates or server routes/contracts. git diff --check passed. scripts/perf-lint --check reported perf-lint: PASS; 0 violations; 22128 scoped exceptions.

UX gaps closed / left

  • Closed: Calendar resize anchoring preserves the settled date and yields to newer scroll input; header title/row sizing updates from observer data; capsule layout reads leave the selection flush. The focused tests cover these paths.
  • Closed: 40 actual-size screenshots are attached: 18 baseline, 18 final and 4 interaction captures. They cover Calendar Week, Calendar Month and Files at 390, 820 and 1440 px in light and dark, using Chromium with macOS emulation. The pointer-click and Command+K captures logged :focus-visible=false for the focused controls.
  • Left: The A/B acceptance miss and the sampled TabBar geometry frame remain. The visual reviewer can use the attached captures to review pixel alignment.

Decisions

The design specifies the instant-interaction goal but not this measurement schedule. The implementation uses ResizeObserver entries for settled geometry and one coalesced animation frame for writes that depend on route or capsule changes. It preserves the existing visual and interaction contract. The local A/B fallback followed the issue instruction after the perf VM did not become usable within the time box.

Screenshots

All screenshots use macOS platform emulation in Chromium. Links point to the original full-resolution PNG attachments.

Baseline

Width Theme Calendar Week Calendar Month Files
390 light Week Month Files
390 dark Week Month Files
820 light Week Month Files
820 dark Week Month Files
1440 light Week Month Files
1440 dark Week Month Files

Final

Width Theme Calendar Week Calendar Month Files
390 light Week Month Files
390 dark Week Month Files
820 light Week Month Files
820 dark Week Month Files
1440 light Week Month Files
1440 dark Week Month Files

Interaction captures (1440 px)

## Final report — #1557 **Branch:** `job/calact2` **Final head:** `98677b92e851ae8761fc97b4dbb5563013f41443` **Base:** `1c6b01484`; `origin/dev` was fetched and merged once and was already up to date. The final commit only cleans the app-shell module comment; the measured implementation head was `919f4b5a32ba306972554f3363b917fcede3b733`. ### Built - Calendar TimeGrid now uses ResizeObserver width entries, coalesces overflow sizing, and applies resize anchor corrections in the next animation frame. A newer scroll cancels a stale correction. - ModeHeader caches scroll state and reads row/inset sizes from ResizeObserver entries. Route and title changes schedule shared chrome work after the Svelte flush. - The shared measured capsule observes the active item and reads its geometry on observer delivery. SegmentedControl selection no longer measures layout in its activation effect. - Calendar route sizing and bottom tray alignment run in coalesced animation frames after DOM writes. - The Photos-readiness harness parses the response outside Chromium; the smoke fixture stays small while full profiles retain their full data set. Review captures route the matching production build identity to avoid update notices. - Existing exact performance-guard identities were refreshed. The ratchet count did not change. Hot-path work is bounded: one coalesced measurement per affected component/frame, with no per-row observers or repeated activation reads. No visible controls or styles were added. ### Files `apps/web/e2e/calendar-tab-switch-1557.mjs`, `apps/web/e2e/harness.mjs`, `apps/web/src/lib/actions/measuredCapsule.svelte.ts`, `apps/web/src/lib/calendar/TimeGrid.svelte.test.ts`, `apps/web/src/lib/components/SegmentedControl.svelte.test.ts`, `apps/web/src/lib/modeHeader.svelte.test.ts`, `apps/web/src/routes/+layout.svelte`, `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, `bench/tab-switch-seed.py`, `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`, `contracts/perf/adoption-1058.json`, `contracts/perf/exceptions.json`, `packages/ui/src/components/ModeHeader.svelte`, `packages/ui/src/components/calendar/TimeGrid.svelte`. ### Trace - The prior heavy `TimeGrid.measure()` `clientWidth`/scroll correction and `ModeHeader.offset()` activation paths no longer appear as heavy frames. Calendar `scrollToDate` is 11.28 ms in the final trace. - Bottom tray `getBoundingClientRect` fell from 25.57 ms in the earlier capture to 0.16 ms after deferral. - The final trace still sampled `TabBar.svelte:184` `getBoundingClientRect` at 79.08 ms and the measured capsule `offsetLeft` at 9.49 ms. The TabBar sample is a remaining layout frame for follow-up; this report does not claim that all layout work is gone. - Largest post-click Svelte `each.js` FunctionCall: 179.01 ms. The earlier 597 ms Svelte-flush figure is a different trace aggregation, so this run does not support an apples-to-apples flush reduction percentage. - [Source-mapped trace summary](https://git.kayg.org/attachments/18d53b4b-0a86-4d95-b115-5cb791b45ea5). ### A/B performance Five interleaved samples per direction at 1440 px, light theme, warm switch; base `4ebeba0b8e13b06223bc2b24ee7a398a0933bda0`, candidate `919f4b5a32ba306972554f3363b917fcede3b733`. The perf VM remained in `migrating/notes-upgrade-backfills` for 25 minutes, so the run used the local build host without HDD emulation. Load average before setup was `17.41, 22.58, 21.26`. Fixture: 10,000 Files, 5 Notes, 2 Tasks, 30 recurring Events, no Photos. Input-to-two-frames p50/p95 in milliseconds (base → candidate): | Switch | Base p50 / p95 | Candidate p50 / p95 | | --- | ---: | ---: | | Mail → Calendar | 545.8 / 599.3 | 1259.7 / 2291.6 | | Files → Calendar | 543.9 / 568.6 | 634.2 / 776.5 | | Calendar → Files | 398.4 / 537.4 | 334.2 / 369.8 | | Notes → Tasks | 306.8 / 372.7 | 235.9 / 278.4 | Full-paint p50/p95 in milliseconds (base → candidate): | Switch | Base p50 / p95 | Candidate p50 / p95 | | --- | ---: | ---: | | Mail → Calendar | 1318.3 / 1458.4 | 1840.6 / 2819.4 | | Files → Calendar | 1129.4 / 1490.3 | 1707.1 / 2803.4 | | Calendar → Files | 953.9 / 1096.0 | 1058.5 / 1386.6 | | Notes → Tasks | 867.5 / 1051.8 | 880.4 / 953.1 | **Acceptance missed.** Mail → Calendar did not improve; Files → Calendar p95 regressed 36.5%. Candidate Mail runs also had more requests than baseline in some samples, including an 18-request, 4.7 MB outlier. The high host load and request differences limit attribution; the numbers are reported without adjustment. ### Gates Web gates ran on the implementation head above. The final head adds only the module-comment cleanup described above. `cd apps/web && bun run check`: ```text perf-lint: PASS; 0 violations; 22128 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact2/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bun run test -- --maxWorkers=2`: ```text Test Files 318 passed (318) Tests 2320 passed (2320) Start at 08:32:40 Duration 425.50s (environment 27%, import 23%, transform 23%, tests 20%, setup 6%) Environment |component| jsdom was created 131 times · 178.46s total, 31% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` `cargo fmt`, `cargo clippy`, and `cargo test` were not run because this job changed no Rust crates or server routes/contracts. `git diff --check` passed. `scripts/perf-lint --check` reported `perf-lint: PASS; 0 violations; 22128 scoped exceptions`. ### UX gaps closed / left - **Closed:** Calendar resize anchoring preserves the settled date and yields to newer scroll input; header title/row sizing updates from observer data; capsule layout reads leave the selection flush. The focused tests cover these paths. - **Closed:** 40 actual-size screenshots are attached: 18 baseline, 18 final and 4 interaction captures. They cover Calendar Week, Calendar Month and Files at 390, 820 and 1440 px in light and dark, using Chromium with macOS emulation. The pointer-click and Command+K captures logged `:focus-visible=false` for the focused controls. - **Left:** The A/B acceptance miss and the sampled TabBar geometry frame remain. The visual reviewer can use the attached captures to review pixel alignment. ### Decisions The design specifies the instant-interaction goal but not this measurement schedule. The implementation uses ResizeObserver entries for settled geometry and one coalesced animation frame for writes that depend on route or capsule changes. It preserves the existing visual and interaction contract. The local A/B fallback followed the issue instruction after the perf VM did not become usable within the time box. ### Screenshots All screenshots use macOS platform emulation in Chromium. Links point to the original full-resolution PNG attachments. **Baseline** | Width | Theme | Calendar Week | Calendar Month | Files | | ---: | --- | --- | --- | --- | | 390 | light | [Week](https://git.kayg.org/attachments/1185ce29-38a0-4c07-843a-2b352a073bdc) | [Month](https://git.kayg.org/attachments/7e2df2dd-256a-4c4e-bedc-a03aac6f17ca) | [Files](https://git.kayg.org/attachments/cb32c279-4962-4fae-90e6-c58ab3b6ea08) | | 390 | dark | [Week](https://git.kayg.org/attachments/aad2b963-ccbc-4e13-a20e-5cdf37855dcb) | [Month](https://git.kayg.org/attachments/6c3c1843-4a3d-4652-8ae0-b39957265d81) | [Files](https://git.kayg.org/attachments/bf172d69-3a08-4f8c-9d6f-45f75d835c67) | | 820 | light | [Week](https://git.kayg.org/attachments/f9c372dd-f51c-4f2f-a0a5-5c75d1560daa) | [Month](https://git.kayg.org/attachments/7ea16eeb-5b03-457c-872e-3b627f300a65) | [Files](https://git.kayg.org/attachments/8bf37d08-7bd1-42c5-b077-db4d9c98c657) | | 820 | dark | [Week](https://git.kayg.org/attachments/2b36d721-7159-4747-8e19-1f1a6e3e7b22) | [Month](https://git.kayg.org/attachments/c1a04a9d-839d-4051-9b92-0ff3fb06361c) | [Files](https://git.kayg.org/attachments/31e8686d-043f-4442-a967-c7ae5164acbc) | | 1440 | light | [Week](https://git.kayg.org/attachments/e4c66710-33fc-46ea-8b13-fbe250376db3) | [Month](https://git.kayg.org/attachments/7af9b159-0346-4c93-bc71-5400038d1856) | [Files](https://git.kayg.org/attachments/717ca0d9-3015-4660-8707-fa5e40fa68cb) | | 1440 | dark | [Week](https://git.kayg.org/attachments/e3efa154-f7ce-49bf-b758-a9bc9bee43c7) | [Month](https://git.kayg.org/attachments/e722bb6d-650d-4d0b-8e54-cd8fc5aee902) | [Files](https://git.kayg.org/attachments/d60d0876-10ff-4e14-8718-82e397069471) | **Final** | Width | Theme | Calendar Week | Calendar Month | Files | | ---: | --- | --- | --- | --- | | 390 | light | [Week](https://git.kayg.org/attachments/a4731db0-854e-4958-8fbb-30a7ca9714d8) | [Month](https://git.kayg.org/attachments/2eda11c8-1951-402f-b8d6-00ac14008211) | [Files](https://git.kayg.org/attachments/f2b534d9-280f-4b92-8318-c6ed395de367) | | 390 | dark | [Week](https://git.kayg.org/attachments/e451c0f5-177f-477e-ab60-ac48214681be) | [Month](https://git.kayg.org/attachments/427b977c-8fe3-4b5a-8b08-e88597926d4b) | [Files](https://git.kayg.org/attachments/6f627ed9-7367-4d77-a402-76f1bc98ac01) | | 820 | light | [Week](https://git.kayg.org/attachments/3fd871df-d225-4252-a5c9-a19a745f19e2) | [Month](https://git.kayg.org/attachments/115acab1-2eca-4cb0-a379-9a7a1444871c) | [Files](https://git.kayg.org/attachments/873b5882-0fd0-4a43-ad0b-fe958848e90f) | | 820 | dark | [Week](https://git.kayg.org/attachments/74aa4585-41e4-4d76-b9d1-fc8d91eec946) | [Month](https://git.kayg.org/attachments/cc3016c7-3da7-4081-b723-481b81250040) | [Files](https://git.kayg.org/attachments/f9f2e5c5-f6f0-437f-a30d-4832ee2f7a4e) | | 1440 | light | [Week](https://git.kayg.org/attachments/7927c55d-b9a3-47bf-a85b-372986899fad) | [Month](https://git.kayg.org/attachments/2a0e9b18-70bf-423f-aad9-21530dac9268) | [Files](https://git.kayg.org/attachments/96f493c2-3485-43b4-9a14-4ae869f55985) | | 1440 | dark | [Week](https://git.kayg.org/attachments/7b7addfa-f92e-4758-a1ab-3c21a91dd00f) | [Month](https://git.kayg.org/attachments/de391a6a-62cb-44fa-b776-2ed4f2a3abda) | [Files](https://git.kayg.org/attachments/cb3feae2-a6c5-4cef-acea-bcf88fcc587e) | **Interaction captures (1440 px)** - [Click Files Dark](https://git.kayg.org/attachments/3c4fc1e4-9192-4433-bc79-ba78990a3a4d) - [Click Files Light](https://git.kayg.org/attachments/22e4c33d-68e7-4025-9e90-758c9db63b3a) - [Command K Dark](https://git.kayg.org/attachments/e34f4db5-47e5-468e-bf8c-c2ff5535326f) - [Command K Light](https://git.kayg.org/attachments/f6d90267-0775-4f57-b5fa-ce0a477864e2)
Author
Owner

Starting round 3 on job/calact3 at 98677b92e851ae8761fc97b4dbb5563013f41443, continuing job/calact2. The measurement baseline is 4ebeba0b8e13b06223bc2b24ee7a398a0933bda0. Round 2's A/B ran on the loaded build host (load about 20), so I will not use those measurements as evidence.

Before measurement, the acceptance rule is: accept only if Mail→Calendar warm input-to-full-paint p50 improves by at least 10% against the baseline, and no p95 for any measured cell at either timing boundary (input-to-two-frames or full paint) regresses by more than 10%. I will capture five interleaved pairs at 1440 px in light theme, with warm Mail→Calendar, Files→Calendar and Calendar→Files switches, plus one Mail→Calendar Chrome trace per side. I interpret the p50 criterion against full paint because it measures when the switched view is visibly complete; input-to-two-frames remains a diagnostic boundary.

Starting round 3 on `job/calact3` at `98677b92e851ae8761fc97b4dbb5563013f41443`, continuing `job/calact2`. The measurement baseline is `4ebeba0b8e13b06223bc2b24ee7a398a0933bda0`. Round 2's A/B ran on the loaded build host (load about 20), so I will not use those measurements as evidence. Before measurement, the acceptance rule is: accept only if Mail→Calendar warm input-to-full-paint p50 improves by at least 10% against the baseline, and no p95 for any measured cell at either timing boundary (input-to-two-frames or full paint) regresses by more than 10%. I will capture five interleaved pairs at 1440 px in light theme, with warm Mail→Calendar, Files→Calendar and Calendar→Files switches, plus one Mail→Calendar Chrome trace per side. I interpret the p50 criterion against full paint because it measures when the switched view is visibly complete; input-to-two-frames remains a diagnostic boundary.
Author
Owner

The initial focused Vitest command could not load apps/web/vite.config.ts: this worktree had no node_modules, so vitest/config and @sveltejs/kit/vite were unresolved. bun --version is 1.4.2; I am installing dependencies from the committed lockfile with frozen resolution before retrying. No source or lockfile was changed by the failed start.

The initial focused Vitest command could not load `apps/web/vite.config.ts`: this worktree had no `node_modules`, so `vitest/config` and `@sveltejs/kit/vite` were unresolved. `bun --version` is 1.4.2; I am installing dependencies from the committed lockfile with frozen resolution before retrying. No source or lockfile was changed by the failed start.
Author
Owner

The focused test now passes: bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2 → 1 file and 28 tests passed. bun run check; echo exit=$? found one stale exact perf-lint pin after the TabBar edit: perf-lint: INVALID: ('layout.observer-purpose', 'packages/ui/src/components/TabBar.svelte', '<module>:dd9d7691a28135ce'): unused or changed exception. The Svelte check completed with 0 errors and 0 warnings. I am using scripts/perf-lint --adopt-7c to refresh the exact identity; contracts/perf/ratchet.json will not change.

The focused test now passes: `bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2` → 1 file and 28 tests passed. `bun run check; echo exit=$?` found one stale exact perf-lint pin after the TabBar edit: `perf-lint: INVALID: ('layout.observer-purpose', 'packages/ui/src/components/TabBar.svelte', '<module>:dd9d7691a28135ce'): unused or changed exception`. The Svelte check completed with 0 errors and 0 warnings. I am using `scripts/perf-lint --adopt-7c` to refresh the exact identity; `contracts/perf/ratchet.json` will not change.
Author
Owner

scripts/perf-lint --adopt-7c refreshed the changed TabBar identities, but it also proposed unrelated Money debt rows and swapped unrelated ModeHeader entries. I restored those unrelated ledger changes and kept only the five TabBar hash updates in each perf ledger. contracts/perf/ratchet.json is unchanged. scripts/perf-lint --check now exits 0.

`scripts/perf-lint --adopt-7c` refreshed the changed TabBar identities, but it also proposed unrelated Money debt rows and swapped unrelated ModeHeader entries. I restored those unrelated ledger changes and kept only the five TabBar hash updates in each perf ledger. `contracts/perf/ratchet.json` is unchanged. `scripts/perf-lint --check` now exits 0.
Author
Owner

Implementation slice committed: 5230953fc (perf(ui): defer Tab Bar sizing to ResizeObserver). TabBar now applies pane dimensions from ResizeObserver entries in one coalesced frame and keeps the last painted stage size for a reversed morph; it has no getBoundingClientRect, offsetWidth or offsetHeight reads. The focused regression test passed: bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2 → 1 file, 28 tests passed. bun run check; echo exit=$? completed with svelte-check found 0 errors and 0 warnings and exit=0. Only the exact TabBar hashes changed in the perf ledgers; contracts/perf/ratchet.json is unchanged. I am checking the perf-VM queue before staging the A/B run.

Implementation slice committed: `5230953fc` (`perf(ui): defer Tab Bar sizing to ResizeObserver`). TabBar now applies pane dimensions from ResizeObserver entries in one coalesced frame and keeps the last painted stage size for a reversed morph; it has no `getBoundingClientRect`, `offsetWidth` or `offsetHeight` reads. The focused regression test passed: `bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2` → 1 file, 28 tests passed. `bun run check; echo exit=$?` completed with `svelte-check found 0 errors and 0 warnings` and `exit=0`. Only the exact TabBar hashes changed in the perf ledgers; `contracts/perf/ratchet.json` is unchanged. I am checking the perf-VM queue before staging the A/B run.
Author
Owner

Visual evidence: the production screenshot harness passed and saved 18 macOS-emulated screenshots under artifacts/calact3/after/. They cover Calendar Week, Calendar Month and Files at 390, 820 and 1440 px in light and dark. The harness waited for a real CalDAV Event in Calendar, checked the Files API/empty state, and confirmed there was no update notice. These are captures for review; I made no visual-quality claim.

Visual evidence: the production screenshot harness passed and saved 18 macOS-emulated screenshots under `artifacts/calact3/after/`. They cover Calendar Week, Calendar Month and Files at 390, 820 and 1440 px in light and dark. The harness waited for a real CalDAV Event in Calendar, checked the Files API/empty state, and confirmed there was no update notice. These are captures for review; I made no visual-quality claim.
Author
Owner

Finding for #1557: the stage observer initially used contentRect, but the shortcut stage gets the shared 1 px glass border. That value is 2 px smaller than the old getBoundingClientRect() result used to preserve the in-flight outer size, so reversing a shortcut morph could start slightly undersized. I am updating the observer to prefer borderBoxSize and keep contentRect as the fallback.

Finding for #1557: the stage observer initially used `contentRect`, but the shortcut stage gets the shared 1 px glass border. That value is 2 px smaller than the old `getBoundingClientRect()` result used to preserve the in-flight outer size, so reversing a shortcut morph could start slightly undersized. I am updating the observer to prefer `borderBoxSize` and keep `contentRect` as the fallback.
Author
Owner

Resolved the border-box sizing finding for #1557 in 5eece8170. The observer now prefers borderBoxSize for the pane and stage, with contentRect fallback. I extended the focused test to inject a 100×40 outer stage box with a 98×38 content box, then verify activation preserves 100px × 40px without calling getBoundingClientRect(). bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2 passed: 1 file, 28 tests.

Resolved the border-box sizing finding for #1557 in 5eece8170. The observer now prefers `borderBoxSize` for the pane and stage, with `contentRect` fallback. I extended the focused test to inject a 100×40 outer stage box with a 98×38 content box, then verify activation preserves `100px × 40px` without calling `getBoundingClientRect()`. `bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2` passed: 1 file, 28 tests.
Author
Owner

Visual evidence attached: 18 macOS-emulated Chromium captures at phone (390 px), tablet (820 px), and desktop (1440 px), in light and dark.

Calendar Week

Calendar Month

Files

Visual evidence attached: 18 macOS-emulated Chromium captures at phone (390 px), tablet (820 px), and desktop (1440 px), in light and dark. **Calendar Week** - Phone: [light](https://git.kayg.org/attachments/cc7d81a7-7e13-4030-af0e-44b0343a8823) · [dark](https://git.kayg.org/attachments/6a4fec08-7ccc-4f26-8449-a8e72a89f8ed) - Tablet: [light](https://git.kayg.org/attachments/920f22c9-13a8-40f4-8469-e77fe472e145) · [dark](https://git.kayg.org/attachments/da469cca-956d-4794-ad8f-2d25c6bc1084) - Desktop: [light](https://git.kayg.org/attachments/f73332f9-e531-4926-85ef-704ea82a0d68) · [dark](https://git.kayg.org/attachments/e9328be9-61c4-43bd-bd17-effc5b0eee27) **Calendar Month** - Phone: [light](https://git.kayg.org/attachments/e9ef56fe-9110-4033-a1e6-e5213ef48b2d) · [dark](https://git.kayg.org/attachments/3f8f0d42-4f18-41b3-9f8a-02c2e559e614) - Tablet: [light](https://git.kayg.org/attachments/fedc2273-d086-4962-aa10-6408d747f525) · [dark](https://git.kayg.org/attachments/15c08ee5-b811-4a52-97ce-e4bacf7f8f1e) - Desktop: [light](https://git.kayg.org/attachments/1d77779c-7c01-46b2-a945-2d4a81f6afcf) · [dark](https://git.kayg.org/attachments/48fb9536-298e-49c1-9d6b-0aa7c82618bd) **Files** - Phone: [light](https://git.kayg.org/attachments/34df3b85-d73e-4103-a74e-6f8917ff1cc6) · [dark](https://git.kayg.org/attachments/d52a49a6-397d-44ff-9fa5-dfba127d605b) - Tablet: [light](https://git.kayg.org/attachments/9155fd9f-6853-443f-9d92-666c4a0f22cb) · [dark](https://git.kayg.org/attachments/b30aab05-4fdf-4fcc-ba48-30bb33240564) - Desktop: [light](https://git.kayg.org/attachments/484ff3b7-23fc-4dcb-bdf4-4cf60f45ed50) · [dark](https://git.kayg.org/attachments/87f775ee-741c-4eaf-80e8-430c1e32dfd4)
Author
Owner

Finding for #1557: the first full bun run check found one TypeScript error in the synthetic ResizeObserverEntry cast at apps/web/src/lib/tray.svelte.test.ts:503; svelte-check reported missing contentBoxSize and devicePixelContentBoxSize. Fixed the test fixture with an explicit unknown bridge in bd45ee005. The focused test passes (28 tests); the full check is running again.

Finding for #1557: the first full `bun run check` found one TypeScript error in the synthetic ResizeObserverEntry cast at `apps/web/src/lib/tray.svelte.test.ts:503`; `svelte-check` reported missing `contentBoxSize` and `devicePixelContentBoxSize`. Fixed the test fixture with an explicit `unknown` bridge in bd45ee005. The focused test passes (28 tests); the full check is running again.
Author
Owner

Perf VM wait update for #1557: perf-run status at 09:40 CEST still reports calternal startup1573-baseline-repro-cold holding the lock since 09:22 CEST (expected 45 minutes). The smoke-profile bundle is prepared. I am waiting for the lock and will not take timing measurements on the build host.

Perf VM wait update for #1557: `perf-run status` at 09:40 CEST still reports `calternal startup1573-baseline-repro-cold` holding the lock since 09:22 CEST (expected 45 minutes). The smoke-profile bundle is prepared. I am waiting for the lock and will not take timing measurements on the build host.
Author
Owner

Finding for #1557: the macOS-emulated post-shortcut pointer-close capture reported one :focus-visible target after the shortcut card returned to the Calendar Tab. Evidence: the temporary action capture failed its explicit :focus-visible count assertion after clicking .shortcut-card-close. TabBar.svelte restores the saved shortcut trigger with direct .focus() in the shortcut-return path. I am replacing that code-driven restore with the existing focusQuietly helper and adding a focused regression test.

Finding for #1557: the macOS-emulated post-shortcut pointer-close capture reported one `:focus-visible` target after the shortcut card returned to the Calendar Tab. Evidence: the temporary action capture failed its explicit `:focus-visible` count assertion after clicking `.shortcut-card-close`. `TabBar.svelte` restores the saved shortcut trigger with direct `.focus()` in the `shortcut-return` path. I am replacing that code-driven restore with the existing `focusQuietly` helper and adding a focused regression test.
Author
Owner

Resolved the pointer-close focus-ring finding in 5193a67a3. Shortcut return now uses the shared focusQuietly helper for both the saved trigger and the active Tab fallback. Added a tray regression test that checks the focus call carries focusVisible: false. bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2 passed: 1 file, 29 tests. I will recapture the post-close screenshots after rebuilding the production assets.

Resolved the pointer-close focus-ring finding in 5193a67a3. Shortcut return now uses the shared `focusQuietly` helper for both the saved trigger and the active Tab fallback. Added a tray regression test that checks the focus call carries `focusVisible: false`. `bunx vitest run src/lib/tray.svelte.test.ts --maxWorkers=2` passed: 1 file, 29 tests. I will recapture the post-close screenshots after rebuilding the production assets.
Author
Owner

Updated action captures use the production build at 5193a67a36eb3ea9138f2c2126346017ad9fd414 with macOS platform emulation in Chromium. The runner asserts no :focus-visible ring after pointer tab activation, the ? shortcut opens, and pointer close returns focus. All six 390/820/1440 px light/dark captures passed and include the loaded real Calendar Event.

Updated action captures use the production build at `5193a67a36eb3ea9138f2c2126346017ad9fd414` with macOS platform emulation in Chromium. The runner asserts no `:focus-visible` ring after pointer tab activation, the `?` shortcut opens, and pointer close returns focus. All six 390/820/1440 px light/dark captures passed and include the loaded real Calendar Event. - Phone: [light](https://git.kayg.org/attachments/842de72d-23de-4b68-89e1-6fda93f5755e) · [dark](https://git.kayg.org/attachments/e0649c03-cee9-49ec-a1d9-0f2be63e5b12) - Tablet: [light](https://git.kayg.org/attachments/3d2de56e-0403-4365-8363-9563897e8d35) · [dark](https://git.kayg.org/attachments/6ee1c694-b339-42c6-9180-c3f17569a460) - Desktop: [light](https://git.kayg.org/attachments/33999efa-d388-4360-bb56-6c7ebfb77225) · [dark](https://git.kayg.org/attachments/e9f2e935-2f68-49e0-b7ca-20b8bb15b589)
Author
Owner

Perf VM setup finding (10:39 CEST): perf-run calternal calact3-ab 30 acquired the lock at 10:20:26 with under-lock load average 1.51 2.94 3.32. HDD qualification passed at both tested depths, /healthz returns HTTP 200, and the --smoke seed wrote its 100-file Home. The runner has remained at Generating and indexing seeded Home for 19 minutes and has emitted no measured switch samples yet. The single bounded A/B session is still running; no acceptance decision is available.

Perf VM setup finding (10:39 CEST): `perf-run calternal calact3-ab 30` acquired the lock at 10:20:26 with under-lock load average `1.51 2.94 3.32`. HDD qualification passed at both tested depths, `/healthz` returns HTTP 200, and the `--smoke` seed wrote its 100-file Home. The runner has remained at `Generating and indexing seeded Home` for 19 minutes and has emitted no measured switch samples yet. The single bounded A/B session is still running; no acceptance decision is available.
Author
Owner

Perf setup diagnosis: the Tab-switch script selected its October 1 fallback server at /mnt/hdd/targets/release-shared/release/calternal-server (SHA-256 2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9). The shared server selected by /mnt/hdd/targets/shared-server/CURRENT is the 4eb build at /mnt/hdd/targets/shared-server/4ebeba0b8/calternal-server-release (SHA-256 58ca0cefe0ffe215a31f0ec2844a4c2979806fd2cc4574925959c61be0d84e99). The running fallback responds to /healthz as text/plain ok and returns 404 for /api/v1/version; the benchmark's startup probe expects the JSON phase API. No switch cell ran. I will stop this setup attempt and restart the same 5-pair profile using the prebuilt 4eb shared server, without compiling on the perf VM.

Perf setup diagnosis: the Tab-switch script selected its October 1 fallback server at `/mnt/hdd/targets/release-shared/release/calternal-server` (SHA-256 `2f3567d91c34839851247bc0acbc25a56aaacd14dca269b8f0342ddf83447ed9`). The shared server selected by `/mnt/hdd/targets/shared-server/CURRENT` is the 4eb build at `/mnt/hdd/targets/shared-server/4ebeba0b8/calternal-server-release` (SHA-256 `58ca0cefe0ffe215a31f0ec2844a4c2979806fd2cc4574925959c61be0d84e99`). The running fallback responds to `/healthz` as `text/plain` `ok` and returns 404 for `/api/v1/version`; the benchmark's startup probe expects the JSON phase API. No switch cell ran. I will stop this setup attempt and restart the same 5-pair profile using the prebuilt 4eb shared server, without compiling on the perf VM.
Author
Owner

Final report

Head: 226b40228d982449292b0957b13e1d2c921c825a

Change

Removed the Tab Bar activation-time geometry read. The existing ResizeObserver now watches the pane and animated stage, preserves the observed border-box size, and coalesces updates into one animation frame. Restoring focus after closing the shortcut card uses the shared quiet-focus helper.

Files changed:

  • packages/ui/src/components/TabBar.svelte
  • apps/web/src/lib/tray.svelte.test.ts
  • contracts/perf/adoption-1058.json
  • contracts/perf/exceptions.json

The performance pins were refreshed only for the changed Tab Bar code. No ratchet threshold changed.

Perf VM A/B

The decision rule posted before the run was: accept only if warm Mail→Calendar input-to-fully-painted p50 improves by at least 10%, and no cell’s p95 regresses by more than 10%.

Run: perf VM through perf-run, five interleaved pairs, Chromium, macOS platform emulation, 1440 px, light, warm, --smoke Home seed with the Calendar fixture retained. Baseline was 4ebeba0b8e13b06223bc2b24ee7a398a0933bda0; candidate was this head. Under-lock starting load average was 0.35 0.12 0.80. Both used the shared prebuilt server from 4eb (58ca0cefe0ffe215a31f0ec2844a4c2979806fd2cc4574925959c61be0d84e99).

All values are milliseconds; each pair is p50/p95.

Build Cell Input→2 frames Input→fully painted
Baseline Mail→Calendar 546.1 / 563.7 842.5 / 862.6
Candidate Mail→Calendar 589.5 / 1593.5 1013.4 / 1943.5
Baseline Calendar→Files 408.4 / 533.7 808.4 / 967.0
Candidate Calendar→Files 399.0 / 499.3 751.6 / 987.4
Baseline Files→Calendar 567.3 / 649.0 932.3 / 1087.4
Candidate Files→Calendar 730.4 / 826.2 1098.1 / 1359.6

Decision: fail; no further tuning, as instructed. Mail→Calendar fully-painted p50 regressed 20.3%; its fully-painted p95 regressed 125.3%, and its two-frame p95 regressed 182.6%. Files→Calendar also regressed in p50 and p95. Calendar→Files fully-painted p95 regressed 2.1%.

Raw traces:

Trace summary, sampled self time: baseline top entries were Pe (49.40 ms), scrollTo (44.00 ms), and an anonymous frame (10.56 ms); candidate top entries were mt (98.90 ms), garbage collection (38.16 ms), and w (22.58 ms). Longest sampled RunTask values were 138.86 ms baseline and 163.75 ms candidate. The top JavaScript frames are minified; these samples do not establish cause.

Gates

bun run check; echo exit=$?

svelte-check found 0 errors and 0 warnings
exit=0

bun run test -- --maxWorkers=2

Could not parse CSS stylesheet
Could not parse CSS stylesheet

 Test Files  318 passed (318)
      Tests  2322 passed (2322)
   Start at  10:08:04
   Duration  306.70s (transform 28%, environment 25%, tests 21%, import 21%, setup 5%)

Environment  |component| jsdom was created 131 times · 120.83s total, 28% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

Focused Tab Bar tests: 29 passed. scripts/perf-lint --adopt-7c passed with 0 violations.

UX gaps and decisions

Closed: activation no longer performs synchronous geometry reads; observed sizing retains the border box; shortcut-card close restores focus without showing a ring. Regression tests cover these behaviors. Screenshot evidence attached earlier in this issue covers real Calendar and Files data at phone (390 px), tablet (820 px), and desktop (1440 px), in light and dark, with macOS platform emulation. It includes mouse, shortcut-open, and close-click states.

Left: the requested performance acceptance failed. I made no tuning changes after the run. No other UX gap remains known from this change.

DESIGN did not specify Tab Bar measurement details. I reused the existing observer and frame scheduling, used borderBoxSize with the existing content-rectangle fallback, and kept the chosen focus behavior in the shared focusQuietly helper.

No Rust crates changed. No push, merge, or deploy was performed.

## Final report **Head:** `226b40228d982449292b0957b13e1d2c921c825a` ### Change Removed the Tab Bar activation-time geometry read. The existing `ResizeObserver` now watches the pane and animated stage, preserves the observed border-box size, and coalesces updates into one animation frame. Restoring focus after closing the shortcut card uses the shared quiet-focus helper. Files changed: - `packages/ui/src/components/TabBar.svelte` - `apps/web/src/lib/tray.svelte.test.ts` - `contracts/perf/adoption-1058.json` - `contracts/perf/exceptions.json` The performance pins were refreshed only for the changed Tab Bar code. No ratchet threshold changed. ### Perf VM A/B The decision rule posted before the run was: accept only if warm Mail→Calendar input-to-fully-painted p50 improves by at least 10%, and no cell’s p95 regresses by more than 10%. Run: perf VM through `perf-run`, five interleaved pairs, Chromium, macOS platform emulation, 1440 px, light, warm, `--smoke` Home seed with the Calendar fixture retained. Baseline was `4ebeba0b8e13b06223bc2b24ee7a398a0933bda0`; candidate was this head. Under-lock starting load average was `0.35 0.12 0.80`. Both used the shared prebuilt server from 4eb (`58ca0cefe0ffe215a31f0ec2844a4c2979806fd2cc4574925959c61be0d84e99`). All values are milliseconds; each pair is p50/p95. | Build | Cell | Input→2 frames | Input→fully painted | | --- | --- | ---: | ---: | | Baseline | Mail→Calendar | 546.1 / 563.7 | 842.5 / 862.6 | | Candidate | Mail→Calendar | 589.5 / 1593.5 | 1013.4 / 1943.5 | | Baseline | Calendar→Files | 408.4 / 533.7 | 808.4 / 967.0 | | Candidate | Calendar→Files | 399.0 / 499.3 | 751.6 / 987.4 | | Baseline | Files→Calendar | 567.3 / 649.0 | 932.3 / 1087.4 | | Candidate | Files→Calendar | 730.4 / 826.2 | 1098.1 / 1359.6 | **Decision: fail; no further tuning, as instructed.** Mail→Calendar fully-painted p50 regressed 20.3%; its fully-painted p95 regressed 125.3%, and its two-frame p95 regressed 182.6%. Files→Calendar also regressed in p50 and p95. Calendar→Files fully-painted p95 regressed 2.1%. Raw traces: - [Baseline Mail→Calendar Chrome trace](https://git.kayg.org/attachments/6d98f45c-bddf-4f00-a640-e40a2d445698) - [Candidate Mail→Calendar Chrome trace](https://git.kayg.org/attachments/860695a8-7789-4521-a9a4-6d948b83a1c6) Trace summary, sampled self time: baseline top entries were `Pe` (49.40 ms), `scrollTo` (44.00 ms), and an anonymous frame (10.56 ms); candidate top entries were `mt` (98.90 ms), garbage collection (38.16 ms), and `w` (22.58 ms). Longest sampled RunTask values were 138.86 ms baseline and 163.75 ms candidate. The top JavaScript frames are minified; these samples do not establish cause. ### Gates `bun run check; echo exit=$?` ```text svelte-check found 0 errors and 0 warnings exit=0 ``` `bun run test -- --maxWorkers=2` ```text Could not parse CSS stylesheet Could not parse CSS stylesheet Test Files 318 passed (318) Tests 2322 passed (2322) Start at 10:08:04 Duration 306.70s (transform 28%, environment 25%, tests 21%, import 21%, setup 5%) Environment |component| jsdom was created 131 times · 120.83s total, 28% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` Focused Tab Bar tests: 29 passed. `scripts/perf-lint --adopt-7c` passed with 0 violations. ### UX gaps and decisions Closed: activation no longer performs synchronous geometry reads; observed sizing retains the border box; shortcut-card close restores focus without showing a ring. Regression tests cover these behaviors. Screenshot evidence attached earlier in this issue covers real Calendar and Files data at phone (390 px), tablet (820 px), and desktop (1440 px), in light and dark, with macOS platform emulation. It includes mouse, shortcut-open, and close-click states. Left: the requested performance acceptance failed. I made no tuning changes after the run. No other UX gap remains known from this change. DESIGN did not specify Tab Bar measurement details. I reused the existing observer and frame scheduling, used `borderBoxSize` with the existing content-rectangle fallback, and kept the chosen focus behavior in the shared `focusQuietly` helper. No Rust crates changed. No push, merge, or deploy was performed.
Author
Owner

Per the shared performance rule, I filed the measured warm-switch regression as follow-up #1576 with the per-cell values and raw trace links. No tuning was done after the failed acceptance result.

Per the shared performance rule, I filed the measured warm-switch regression as follow-up #1576 with the per-cell values and raw trace links. No tuning was done after the failed acceptance result.
Author
Owner

Starting investigation in worktree job/calprof, based on origin/dev at 0c712b98c164b6734d54f63485012bd1a11005b4. I am reading the #549 Tab-switch profile and trace support to capture the requested three warm Mail→Calendar samples with source locations. I will make no product changes; any profiling-only helper will stay isolated and be removed or committed only if useful for repeatable investigation.

Starting investigation in worktree `job/calprof`, based on `origin/dev` at `0c712b98c164b6734d54f63485012bd1a11005b4`. I am reading the #549 Tab-switch profile and trace support to capture the requested three warm Mail→Calendar samples with source locations. I will make no product changes; any profiling-only helper will stay isolated and be removed or committed only if useful for repeatable investigation.
Author
Owner

Finding: bench/tab-switch.mjs already records CDP JavaScript sampling, but its trace-only filter and trace start predicate hard-code Photos→Calendar, and it writes one fixed trace filename. That makes the requested three warm Mail→Calendar traces impossible without a small benchmark-only extension. The profiling build is generated with CLI-only --sourcemap --minify=false; it does not change Vite or production build configuration.

Finding: `bench/tab-switch.mjs` already records CDP JavaScript sampling, but its trace-only filter and trace start predicate hard-code Photos→Calendar, and it writes one fixed trace filename. That makes the requested three warm Mail→Calendar traces impossible without a small benchmark-only extension. The profiling build is generated with CLI-only `--sourcemap --minify=false`; it does not change Vite or production build configuration.
Author
Owner

Run finding (2026-10-11): the smoke Home indexed all 100 Photos and the CalDAV fixture exposed 30 daily recurring Events across 366 days. The server read probe passed. The first browser profile attempt then timed out after 30 seconds on the fixture-only PUT /api/v1/appearance, before any Tab-switch sample or trace was captured. The profile context performs that theme setup outside the perf-run lock; I am serializing this setup write under the lock and will retry. This timeout is setup evidence, not activation timing.

Run finding (2026-10-11): the smoke Home indexed all 100 Photos and the CalDAV fixture exposed 30 daily recurring Events across 366 days. The server read probe passed. The first browser profile attempt then timed out after 30 seconds on the fixture-only PUT /api/v1/appearance, before any Tab-switch sample or trace was captured. The profile context performs that theme setup outside the perf-run lock; I am serializing this setup write under the lock and will retry. This timeout is setup evidence, not activation timing.
Author
Owner

#1557 — Calendar activation attribution

Work and method

This was an investigation only. I added an opt-in trace-only profiling path to bench/tab-switch.mjs, captured three Chrome CPU-sampling traces, and removed two invalid route-identity fields after finding they pointed at the always-present pull-status pill. No product code changed.

The web build used source maps and disabled minification only for this run:

CALTERNAL_BUILD_ID=4ebeba0b8 bunx vite build --sourcemap --minify=false

The app and server sources were the 4eb baseline. Each trace is a warm Mail→Calendar switch at 1440 px, light theme, smoke seed, with the Calendar fixture retained. The trace window starts at the trusted pointer event and ends after the benchmark’s fully-painted condition. Chrome’s CPU sampler was enabled.

The server VM load while locked was 3.04–3.28. The browser ran on the build host, whose one-minute load was 12.04–14.32. The unminified build, active sampler, and loaded browser host make these source-attribution timings unsuitable as a replacement for the production baseline.

A first attempt timed out on the fixture-only appearance write before a Tab sample. I moved that setup write under the VM lock and reran successfully. The timeout was setup-only.

Activation timings

Measure Three runs (ms) p50 / p95 (ms)
Pointer event → fully painted trace mark 2347.8, 3284.9, 2427.6 2427.6 / 3284.9
Target-ready Calendar content after input 1748.9, 1560.2, 1452.3 1560.2 / 1748.9
Benchmark start → fully painted 2598.8, 3334.4, 2609.8 2609.8 / 3334.4

The reported 4eb production baseline remains 546.1 / 563.7 ms input→two frames and 842.5 / 862.6 ms input→fully painted. Do not compare those values directly with this profiling build.

Source attribution

The CPU profile maps sampled JS to the nearest Svelte source frame. Svelte does not emit separate mount/effect duration events here, so the lifecycle column uses the sampled function and its source call sites. The costs below are sampled self time; they are not additive with browser layout events.

Component / source Sampled time across traces Why it runs on activation
packages/ui/src/components/ModeHeader.svelte:269 offset 127.6, 116.0, 133.6 ms In the 4eb source, route page/title changes run the ModeHeader effect (:357–364); route mutations also schedule title measurement from onMount (:332–341). The path reads the active scroller offset. This was the most repeatable Svelte JS cost.
packages/ui/src/components/calendar/TimeGrid.svelte:543 centreY 119.6, 1.2, 1.4 ms setZoom calls centreY (:581–585) when the hourHeight effect sees a changed saved preference (:591–596). The high cost occurred in one trace only.
packages/ui/src/components/calendar/GridColumn.svelte 23.5, 12.1, 16.8 ms Calendar day columns derive visible block placement and tag summaries while the grid mounts/updates.
packages/ui/src/routes/calendar/[view]/[date]/+page.svelte 1.9, 1.1, 3.0 ms The route component itself was a small part of sampled JS time.

Other sampled native self time included scrollTo (252–332 ms), getBoundingClientRect (207–608 ms), and clientWidth (40–68 ms). Native leaf samples do not preserve a single app call site, and these values overlap style/layout work; do not add them to component costs.

Style, layout, forced reflow, GC and network

Work Trace results Initiating code and reason
UpdateLayoutTree 47–48 events; 788.5–1092.9 ms summed event time The largest mapped initiators were TabBar.svelte:220 (deferred capsule measure), TimeGrid.svelte:892 (grid ResizeObserver measure), AppToaster.svelte:142 (toast dock update), and Svelte dom/task.js:28 (runtime flush).
Layout 24–27 events; 193.8–232.7 ms summed event time The largest mapped Calendar callback was TimeGrid.svelte:892 (34.6–72.5 ms per event). Runtime flushes also initiated layout events.
TabBar style work Largest single UpdateLayoutTree: 102.5, 195.5, 106.7 ms Its scheduled callback at TabBar.svelte:220 calls measure() and readSize() (:184–186) after Tab state changes.
TimeGrid style/layout work Largest UpdateLayoutTree: 68.6–147.6 ms; Layout: 34.6–72.5 ms The observer callback at TimeGrid.svelte:892 runs measure(), which reads the scrollport width and computed styles, then may write --colw and scroll position.
AppToaster style work Largest UpdateLayoutTree: 67.6–91.6 ms The global toast update at AppToaster.svelte:142 measures visible toast boxes at :150 and :154.
GC Trace 1: minor 30.7 ms. Trace 2: major 81.9 ms and minor 18.9 ms. Trace 3: minor 29.3 ms. The slowest trace included the only major GC. Background GC events overlap and are not added to main-thread GC time.

The trace has no distinct forced-reflow event with a direct source stack. It does show style/layout events inside mapped TabBar, TimeGrid and toaster callbacks. Treat these as layout work with those initiators, not as proof that every event was a synchronous forced reflow. The analyzer mapped 35–39 of 69–74 style/layout events to an enclosing FunctionCall; the remaining events did not retain a useful caller stack. Event durations can overlap and must not be summed with CPU samples.

The traces contain 23–27 DrawFrame events before the fully-painted marker. Median frame intervals were 89.8–92.7 ms, with p95 intervals of 144.4–309.7 ms. The first two compositor frames arrived at 54/138 ms, 172/274 ms, and 71/164 ms. Those frames do not mean Calendar content was ready: the target-ready marker took 1452–1749 ms in this loaded profiling run.

Two API requests were reported per run, 908 transferred bytes in the benchmark report, and zero server read bytes. Calendar preference response-header wait was 38.6–50.1 ms; its Server-Timing: total was 0.111–0.131 ms. This does not show a server-side activation bottleneck. Response events can appear later on the main-thread trace than the network timing’s receiveHeadersEnd, so I used the network timing field for header wait.

Route lifecycle and DESIGN §59

Calendar unmounts on a Tab switch and gets a new route instance when opened again. apps/web/src/routes/+layout.svelte:1431–1471 renders one active route child through {@render children()}; the Calendar route is not held beside Mail. The memory profile after GC measured 4,033 route descendants on Calendar and 18 on Mail. Calendar→Mail also reduced total DOM nodes by 14,983 and listeners by 1,446. Shared JS chunks and application data caches may remain loaded; this is the route DOM/component instance being removed.

DESIGN §59 allows a bounded LRU view snapshot, not a hidden mounted Calendar route:

“Keep recent views. Use one bounded least-recently-used cache for view snapshots: rows, cursors, selection and scroll position. Show the retained snapshot before one bounded refresh. Do not cover data already seen with a blank view or spinner. Scope all retained data to the User through userStorage (#555). Clear it at session end (§57). Reuse the route cache from #549.”

After GC, the smoke Calendar state used 23,687,704 bytes of JS heap, 17,305 DOM nodes, 1,934 listeners and 4,033 route descendants. Mail used 12,289,880 bytes, 2,322 DOM nodes, 488 listeners and 18 route descendants. The full mounted Calendar delta was 11,397,824 heap bytes, 14,983 DOM nodes, 1,446 listeners and 4,015 route descendants. This is a measured full-view retention cost, not the size of a row/cursor/selection/scroll snapshot. DESIGN §59 does not set a numeric snapshot byte limit; measure the snapshot before choosing one.

Why rounds 2–3 regressed

The old round-3 trace pair is attached to #1576 and remains minified. In the 2.5-second window after pointerdown, its candidate had 63 RequestAnimationFrame events and 60 FireAnimationFrame events versus 35 and 35 for baseline; it had 19 versus 15 compositor DrawFrame events. The candidate’s first two compositor frames were not later (56/136 ms versus 72/150 ms). The candidate trace had four long RunTask events with a maximum of 148.1 ms versus three with a maximum of 114.1 ms. These traces show more post-input frame work, but they do not contain a Calendar-ready/settled marker and cannot prove that a specific frame caused the p95 regression.

The source diff at candidate 226b402 gives the strongest explanation for the median regression. TimeGrid stopped measuring the initial width immediately in onMount; it waits for ResizeObserver (TimeGrid.svelte:930–934), then schedules scheduleInitialPosition() for another animation frame (:811–828) before setting initialized/.tg.ready. The benchmark’s fully-painted condition waits for .tg.ready. This inserts a frame gate on the measured Calendar-ready path. It is consistent with the +43.4 ms two-frame p50 and +170.9 ms fully-painted p50, but it cannot explain the +1,081 ms fully-painted p95 increase by itself. The minified traces do not establish the tail cause.

Ranked fix plan

  1. Remove the extra initial TimeGrid ready frame. Use the ResizeObserver width for sizing and publish the initial anchor/readiness before the next paint when the observer supplies the valid width. Expected saving: one frame (about 16.7 ms at 60 Hz; the profiled host showed much longer and noisy frame intervals). Risk: initial scroll anchoring or width may be wrong. Prove it with target-ready input→paint p50/p95, the current no-minify trace path, and checks that the correct day/columns appear without a ResizeObserver loop.
  2. Measure the TabBar observer/frame chain on the candidate before changing it again. Candidate traces show 28 more requestAnimationFrame events in this 2.5-second window. Candidate TabBar has separate transition and observed-size callbacks. Expected saving: up to one later frame if the callbacks serialize; no defensible millisecond estimate yet. Risk: capsule morph timing and final size. Prove it by recording callback/frame counts alongside the same interleaved Mail→Calendar and Calendar→Files cells.
  3. Use the §59 User-scoped snapshot for instant return content, then refresh once. Expected target: at least the issue’s 10% p50 gate (about 84 ms on the 842 ms baseline); current traces cannot promise that saving. Risk: stale rows or excess retained data. Measure snapshot bytes, first ready paint and refresh completion; scope it through userStorage and clear it at session end.

Files, gates and gaps

Files changed: bench/tab-switch.mjs, bench/tab-switch.test.mjs. No Rust or product files changed. Three source-mapped traces are attached: trace 1, trace 2, trace 3.

bun test bench/tab-switch.test.mjs
7 pass
0 fail
33 expect() calls
Ran 7 tests across 1 file. [106.00ms]

bun run check
NOTE Bun skipped 329 lock package(s) not installed in this production environment
PASS production dependency licences: 431 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22128 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calprof/apps/web
Getting Svelte diagnostics...
svelte-check found 0 errors and 0 warnings

Known gaps: the old candidate traces do not map to source and have no settled marker; this cannot attribute the long p95 tail. The current profiling timings are not production-comparable because the browser host was loaded and minification was disabled. The byte size of a §59 view snapshot remains unmeasured.

Decisions not covered by DESIGN: none required. I used a profile-only Vite command without changing production build settings and treated source-mapped timings as diagnostic, not as a new baseline.

Head: 2b6a1791eee6213e4c05e4049c61baa1877b7305. No push, merge or deploy was performed.

cargo clean output: Removed 0 files. apps/web/build, apps/web/.svelte-kit and target/tmp were removed after profiling.

## #1557 — Calendar activation attribution ### Work and method This was an investigation only. I added an opt-in trace-only profiling path to `bench/tab-switch.mjs`, captured three Chrome CPU-sampling traces, and removed two invalid route-identity fields after finding they pointed at the always-present pull-status pill. No product code changed. The web build used source maps and disabled minification only for this run: ```sh CALTERNAL_BUILD_ID=4ebeba0b8 bunx vite build --sourcemap --minify=false ``` The app and server sources were the 4eb baseline. Each trace is a warm Mail→Calendar switch at 1440 px, light theme, smoke seed, with the Calendar fixture retained. The trace window starts at the trusted pointer event and ends after the benchmark’s fully-painted condition. Chrome’s CPU sampler was enabled. The server VM load while locked was 3.04–3.28. The browser ran on the build host, whose one-minute load was 12.04–14.32. The unminified build, active sampler, and loaded browser host make these source-attribution timings unsuitable as a replacement for the production baseline. A first attempt timed out on the fixture-only appearance write before a Tab sample. I moved that setup write under the VM lock and reran successfully. The timeout was setup-only. ### Activation timings | Measure | Three runs (ms) | p50 / p95 (ms) | | --- | ---: | ---: | | Pointer event → fully painted trace mark | 2347.8, 3284.9, 2427.6 | 2427.6 / 3284.9 | | Target-ready Calendar content after input | 1748.9, 1560.2, 1452.3 | 1560.2 / 1748.9 | | Benchmark start → fully painted | 2598.8, 3334.4, 2609.8 | 2609.8 / 3334.4 | The reported 4eb production baseline remains 546.1 / 563.7 ms input→two frames and 842.5 / 862.6 ms input→fully painted. Do not compare those values directly with this profiling build. ### Source attribution The CPU profile maps sampled JS to the nearest Svelte source frame. Svelte does not emit separate mount/effect duration events here, so the lifecycle column uses the sampled function and its source call sites. The costs below are sampled self time; they are not additive with browser layout events. | Component / source | Sampled time across traces | Why it runs on activation | | --- | ---: | --- | | `packages/ui/src/components/ModeHeader.svelte:269 offset` | 127.6, 116.0, 133.6 ms | In the 4eb source, route `page`/`title` changes run the ModeHeader effect (`:357–364`); route mutations also schedule title measurement from `onMount` (`:332–341`). The path reads the active scroller offset. This was the most repeatable Svelte JS cost. | | `packages/ui/src/components/calendar/TimeGrid.svelte:543 centreY` | 119.6, 1.2, 1.4 ms | `setZoom` calls `centreY` (`:581–585`) when the `hourHeight` effect sees a changed saved preference (`:591–596`). The high cost occurred in one trace only. | | `packages/ui/src/components/calendar/GridColumn.svelte` | 23.5, 12.1, 16.8 ms | Calendar day columns derive visible block placement and tag summaries while the grid mounts/updates. | | `packages/ui/src/routes/calendar/[view]/[date]/+page.svelte` | 1.9, 1.1, 3.0 ms | The route component itself was a small part of sampled JS time. | Other sampled native self time included `scrollTo` (252–332 ms), `getBoundingClientRect` (207–608 ms), and `clientWidth` (40–68 ms). Native leaf samples do not preserve a single app call site, and these values overlap style/layout work; do not add them to component costs. ### Style, layout, forced reflow, GC and network | Work | Trace results | Initiating code and reason | | --- | --- | --- | | `UpdateLayoutTree` | 47–48 events; 788.5–1092.9 ms summed event time | The largest mapped initiators were `TabBar.svelte:220` (deferred capsule measure), `TimeGrid.svelte:892` (grid `ResizeObserver` measure), `AppToaster.svelte:142` (toast dock update), and Svelte `dom/task.js:28` (runtime flush). | | `Layout` | 24–27 events; 193.8–232.7 ms summed event time | The largest mapped Calendar callback was `TimeGrid.svelte:892` (34.6–72.5 ms per event). Runtime flushes also initiated layout events. | | `TabBar` style work | Largest single `UpdateLayoutTree`: 102.5, 195.5, 106.7 ms | Its scheduled callback at `TabBar.svelte:220` calls `measure()` and `readSize()` (`:184–186`) after Tab state changes. | | `TimeGrid` style/layout work | Largest `UpdateLayoutTree`: 68.6–147.6 ms; `Layout`: 34.6–72.5 ms | The observer callback at `TimeGrid.svelte:892` runs `measure()`, which reads the scrollport width and computed styles, then may write `--colw` and scroll position. | | `AppToaster` style work | Largest `UpdateLayoutTree`: 67.6–91.6 ms | The global toast update at `AppToaster.svelte:142` measures visible toast boxes at `:150` and `:154`. | | GC | Trace 1: minor 30.7 ms. Trace 2: major 81.9 ms and minor 18.9 ms. Trace 3: minor 29.3 ms. | The slowest trace included the only major GC. Background GC events overlap and are not added to main-thread GC time. | The trace has no distinct forced-reflow event with a direct source stack. It does show style/layout events inside mapped `TabBar`, `TimeGrid` and toaster callbacks. Treat these as layout work with those initiators, not as proof that every event was a synchronous forced reflow. The analyzer mapped 35–39 of 69–74 style/layout events to an enclosing `FunctionCall`; the remaining events did not retain a useful caller stack. Event durations can overlap and must not be summed with CPU samples. The traces contain 23–27 `DrawFrame` events before the fully-painted marker. Median frame intervals were 89.8–92.7 ms, with p95 intervals of 144.4–309.7 ms. The first two compositor frames arrived at 54/138 ms, 172/274 ms, and 71/164 ms. Those frames do not mean Calendar content was ready: the target-ready marker took 1452–1749 ms in this loaded profiling run. Two API requests were reported per run, 908 transferred bytes in the benchmark report, and zero server read bytes. Calendar preference response-header wait was 38.6–50.1 ms; its `Server-Timing: total` was 0.111–0.131 ms. This does not show a server-side activation bottleneck. Response events can appear later on the main-thread trace than the network timing’s `receiveHeadersEnd`, so I used the network timing field for header wait. ### Route lifecycle and DESIGN §59 **Calendar unmounts on a Tab switch and gets a new route instance when opened again.** `apps/web/src/routes/+layout.svelte:1431–1471` renders one active route child through `{@render children()}`; the Calendar route is not held beside Mail. The memory profile after GC measured 4,033 route descendants on Calendar and 18 on Mail. Calendar→Mail also reduced total DOM nodes by 14,983 and listeners by 1,446. Shared JS chunks and application data caches may remain loaded; this is the route DOM/component instance being removed. DESIGN §59 allows a bounded LRU **view snapshot**, not a hidden mounted Calendar route: > “Keep recent views. Use one bounded least-recently-used cache for view snapshots: rows, cursors, selection and scroll position. Show the retained snapshot before one bounded refresh. Do not cover data already seen with a blank view or spinner. Scope all retained data to the User through `userStorage` (#555). Clear it at session end (§57). Reuse the route cache from #549.” After GC, the smoke Calendar state used 23,687,704 bytes of JS heap, 17,305 DOM nodes, 1,934 listeners and 4,033 route descendants. Mail used 12,289,880 bytes, 2,322 DOM nodes, 488 listeners and 18 route descendants. The full mounted Calendar delta was 11,397,824 heap bytes, 14,983 DOM nodes, 1,446 listeners and 4,015 route descendants. This is a measured full-view retention cost, not the size of a row/cursor/selection/scroll snapshot. DESIGN §59 does not set a numeric snapshot byte limit; measure the snapshot before choosing one. ### Why rounds 2–3 regressed The old round-3 trace pair is attached to #1576 and remains minified. In the 2.5-second window after pointerdown, its candidate had 63 `RequestAnimationFrame` events and 60 `FireAnimationFrame` events versus 35 and 35 for baseline; it had 19 versus 15 compositor `DrawFrame` events. The candidate’s first two compositor frames were not later (56/136 ms versus 72/150 ms). The candidate trace had four long `RunTask` events with a maximum of 148.1 ms versus three with a maximum of 114.1 ms. These traces show more post-input frame work, but they do not contain a Calendar-ready/settled marker and cannot prove that a specific frame caused the p95 regression. The source diff at candidate `226b402` gives the strongest explanation for the median regression. `TimeGrid` stopped measuring the initial width immediately in `onMount`; it waits for `ResizeObserver` (`TimeGrid.svelte:930–934`), then schedules `scheduleInitialPosition()` for another animation frame (`:811–828`) before setting `initialized`/`.tg.ready`. The benchmark’s fully-painted condition waits for `.tg.ready`. This inserts a frame gate on the measured Calendar-ready path. It is consistent with the +43.4 ms two-frame p50 and +170.9 ms fully-painted p50, but it cannot explain the +1,081 ms fully-painted p95 increase by itself. The minified traces do not establish the tail cause. ### Ranked fix plan 1. **Remove the extra initial TimeGrid ready frame.** Use the `ResizeObserver` width for sizing and publish the initial anchor/readiness before the next paint when the observer supplies the valid width. Expected saving: one frame (about 16.7 ms at 60 Hz; the profiled host showed much longer and noisy frame intervals). Risk: initial scroll anchoring or width may be wrong. Prove it with target-ready input→paint p50/p95, the current no-minify trace path, and checks that the correct day/columns appear without a ResizeObserver loop. 2. **Measure the TabBar observer/frame chain on the candidate before changing it again.** Candidate traces show 28 more `requestAnimationFrame` events in this 2.5-second window. Candidate `TabBar` has separate transition and observed-size callbacks. Expected saving: up to one later frame if the callbacks serialize; no defensible millisecond estimate yet. Risk: capsule morph timing and final size. Prove it by recording callback/frame counts alongside the same interleaved Mail→Calendar and Calendar→Files cells. 3. **Use the §59 User-scoped snapshot for instant return content, then refresh once.** Expected target: at least the issue’s 10% p50 gate (about 84 ms on the 842 ms baseline); current traces cannot promise that saving. Risk: stale rows or excess retained data. Measure snapshot bytes, first ready paint and refresh completion; scope it through `userStorage` and clear it at session end. ### Files, gates and gaps Files changed: `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`. No Rust or product files changed. Three source-mapped traces are attached: [trace 1](https://git.kayg.org/attachments/13e69a9c-fadf-4928-87d9-aedd7327ac2a), [trace 2](https://git.kayg.org/attachments/30fbaae6-b8cd-4193-bb8c-3c061a3517b3), [trace 3](https://git.kayg.org/attachments/8e54e360-54f0-46a8-b4a4-37a8c56a23c4). ```text bun test bench/tab-switch.test.mjs 7 pass 0 fail 33 expect() calls Ran 7 tests across 1 file. [106.00ms] bun run check NOTE Bun skipped 329 lock package(s) not installed in this production environment PASS production dependency licences: 431 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22128 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calprof/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Known gaps: the old candidate traces do not map to source and have no settled marker; this cannot attribute the long p95 tail. The current profiling timings are not production-comparable because the browser host was loaded and minification was disabled. The byte size of a §59 view snapshot remains unmeasured. Decisions not covered by DESIGN: none required. I used a profile-only Vite command without changing production build settings and treated source-mapped timings as diagnostic, not as a new baseline. Head: `2b6a1791eee6213e4c05e4049c61baa1877b7305`. No push, merge or deploy was performed. `cargo clean` output: `Removed 0 files`. `apps/web/build`, `apps/web/.svelte-kit` and `target/tmp` were removed after profiling.
Author
Owner

Starting #1557 in job/calact4, based on 2b6a1791eee6213e4c05e4049c61baa1877b7305 (job/calprof, profiling bench path present).

I will implement A (Calendar Week DOM budget) and B (ModeHeader offset) as separate commits, each measured against its immediate parent. C (the §59 User-scoped view snapshot) is conditional on A and B both being accepted and time remaining. I will not use the rejected TimeGrid ResizeObserver-plus-extra-frame readiness pattern.

Decision rule before measurement: run five interleaved baseline/candidate pairs on the perf VM, with server and Chromium browser on that VM, smoke Calendar fixture, 1440 px light warm, and cells Mail→Calendar, Files→Calendar, Calendar→Files. Record input→two frames and input→fully painted p50/p95 for each cell. Keep a change only if Mail→Calendar fully-painted p50 improves by at least 10% and no cell's p95 regresses by more than 10%; otherwise revert that change. Record the load average while holding the perf-run lock.

Starting #1557 in `job/calact4`, based on `2b6a1791eee6213e4c05e4049c61baa1877b7305` (`job/calprof`, profiling bench path present). I will implement A (Calendar Week DOM budget) and B (ModeHeader offset) as separate commits, each measured against its immediate parent. C (the §59 User-scoped view snapshot) is conditional on A and B both being accepted and time remaining. I will not use the rejected TimeGrid ResizeObserver-plus-extra-frame readiness pattern. Decision rule before measurement: run five interleaved baseline/candidate pairs on the perf VM, with server and Chromium browser on that VM, smoke Calendar fixture, 1440 px light warm, and cells Mail→Calendar, Files→Calendar, Calendar→Files. Record input→two frames and input→fully painted p50/p95 for each cell. Keep a change only if Mail→Calendar fully-painted p50 improves by at least 10% and no cell's p95 regresses by more than 10%; otherwise revert that change. Record the load average while holding the `perf-run` lock.
Author
Owner

Finding: bench/tab-switch.mjs launched Chromium in prepareFixture, the auth phase and the profile loop on the build host, so its measurements did not satisfy #1557's server-and-browser-on-perf-VM requirement. Commit be11c15b634d3f1846cdf748c6146e8853a5651c adds opt-in --vm-browser: it launches the perf VM's cached Chromium, forwards CDP and the HTTPS front over the existing SSH session, records the VM load for browser samples, and stops/removes the browser profile during locked cleanup.

Evidence:

node --check bench/tab-switch.mjs
exit 0

bun test bench/tab-switch.test.mjs
7 pass
0 fail
33 expect() calls
Ran 7 tests across 1 file. [55.00ms]
Finding: `bench/tab-switch.mjs` launched Chromium in `prepareFixture`, the auth phase and the profile loop on the build host, so its measurements did not satisfy #1557's server-and-browser-on-perf-VM requirement. Commit `be11c15b634d3f1846cdf748c6146e8853a5651c` adds opt-in `--vm-browser`: it launches the perf VM's cached Chromium, forwards CDP and the HTTPS front over the existing SSH session, records the VM load for browser samples, and stops/removes the browser profile during locked cleanup. Evidence: ```text node --check bench/tab-switch.mjs exit 0 bun test bench/tab-switch.test.mjs 7 pass 0 fail 33 expect() calls Ran 7 tests across 1 file. [55.00ms] ```
Author
Owner

Finding and measurement setup: commit 5c88e8616fe3844c3ece4900fee61aa84e5d0306 adds a five-pair interleaved build comparison to bench/tab-switch.mjs. Each cell alternates parent/candidate order. Samples use trusted CDP pointer input and report input→two frames and input→fully painted p50/p95. The report computes the already-posted gate for Mail→Calendar and every cell's p95. It also captures Calendar Week from both builds at 1440/820/390 in light/dark, with macOS platform emulation and a visible real Event check.

Evidence:

node --check bench/tab-switch.mjs
exit 0

bun test bench/tab-switch.test.mjs
9 pass
0 fail
40 expect() calls
Ran 9 tests across 1 file. [40.00ms]

The perf VM's cached Chromium binary is present. Its first locked CDP startup probe did not open port 9227; I am checking the binary startup before running the full profile. No A/B sample has been taken yet.

Finding and measurement setup: commit `5c88e8616fe3844c3ece4900fee61aa84e5d0306` adds a five-pair interleaved build comparison to `bench/tab-switch.mjs`. Each cell alternates parent/candidate order. Samples use trusted CDP pointer input and report input→two frames and input→fully painted p50/p95. The report computes the already-posted gate for Mail→Calendar and every cell's p95. It also captures Calendar Week from both builds at 1440/820/390 in light/dark, with macOS platform emulation and a visible real Event check. Evidence: ```text node --check bench/tab-switch.mjs exit 0 bun test bench/tab-switch.test.mjs 9 pass 0 fail 40 expect() calls Ran 9 tests across 1 file. [40.00ms] ``` The perf VM's cached Chromium binary is present. Its first locked CDP startup probe did not open port 9227; I am checking the binary startup before running the full profile. No A/B sample has been taken yet.
Author
Owner

Remote browser startup probe succeeded under perf-run on perf VM 10.69.69.63. Cached headless Chrome for Testing 153.0.8010.12 launched and rendered about:blank; load average inside the lock was 6.44, 4.92, 3.98. This confirms the VM has a usable browser binary; the Calendar A/B startup and fixture still need verification.

Remote browser startup probe succeeded under `perf-run` on perf VM 10.69.69.63. Cached headless Chrome for Testing 153.0.8010.12 launched and rendered `about:blank`; load average inside the lock was 6.44, 4.92, 3.98. This confirms the VM has a usable browser binary; the Calendar A/B startup and fixture still need verification.
Author
Owner

Remote browser smoke exposed a concrete harness error before any browser sample: remote() returns { code, stdout, stderr }, but startVmChromium() called .trim() on that object. The run ended with TypeError: (await remote(...)).trim is not a function at bench/tab-switch.mjs:367; server setup and HDD qualification succeeded, but no browser profile or switch timing was collected. I am correcting the caller to read stdout and will rerun the smoke under the perf lock.

Remote browser smoke exposed a concrete harness error before any browser sample: `remote()` returns `{ code, stdout, stderr }`, but `startVmChromium()` called `.trim()` on that object. The run ended with `TypeError: (await remote(...)).trim is not a function` at `bench/tab-switch.mjs:367`; server setup and HDD qualification succeeded, but no browser profile or switch timing was collected. I am correcting the caller to read `stdout` and will rerun the smoke under the perf lock.
Author
Owner

Preliminary DOM finding: I read the live perf-VM Week route through CDP while the fixture/profile run was in setup. .tg has 3,327 element descendants before counting text or comment nodes, and no Event cards are present in that capture. The empty Week grid alone is over the 3,000-node target. The required component breakdown and fixture-backed count are still pending. Existing CSS grid lines and visible-window rendering remain unchanged; I will use the real A/B screenshot run to identify which components account for the excess.

Preliminary DOM finding: I read the live perf-VM Week route through CDP while the fixture/profile run was in setup. `.tg` has 3,327 element descendants before counting text or comment nodes, and no Event cards are present in that capture. The empty Week grid alone is over the 3,000-node target. The required component breakdown and fixture-backed count are still pending. Existing CSS grid lines and visible-window rendering remain unchanged; I will use the real A/B screenshot run to identify which components account for the excess.
Author
Owner

Correction to the preliminary count: the live embedded build did contain 240 Calendar Event cards; my initial .event-block selector did not match its production markup. It rendered 21 date columns from 2026-10-04 through 2026-10-24 while the root declared --visible-days: 7, and .tg had 3,327 element descendants. This run uses the server's embedded production assets, so it is a diagnostic, not the issue's paired-build DOM result. The A/B run will serve the frozen parent and candidate worktree builds and report their exact counts and per-component breakdowns.

Correction to the preliminary count: the live embedded build did contain 240 Calendar Event cards; my initial `.event-block` selector did not match its production markup. It rendered 21 date columns from 2026-10-04 through 2026-10-24 while the root declared `--visible-days: 7`, and `.tg` had 3,327 element descendants. This run uses the server's embedded production assets, so it is a diagnostic, not the issue's paired-build DOM result. The A/B run will serve the frozen parent and candidate worktree builds and report their exact counts and per-component breakdowns.
Author
Owner

Profile setup finding: my first trace invocation omitted --smoke, so it used the full three-year fixture. The isolated data/home directories reached 936 MB, and the release server was still indexing; no timing sample was collected. I stopped that run and closed its Chrome profile. During teardown, the stopped server returned HTTP 502, but waitForPhotoIndex treated every non-200 response as “still indexing” and could poll for its one-hour deadline. I added a fail-fast status check and a bench regression test (bun test bench/tab-switch.test.mjs: 13 pass, 0 fail, 50 expect() calls). I will rerun with --smoke; the aborted run is not part of the A/B results.

Profile setup finding: my first trace invocation omitted `--smoke`, so it used the full three-year fixture. The isolated data/home directories reached 936 MB, and the release server was still indexing; no timing sample was collected. I stopped that run and closed its Chrome profile. During teardown, the stopped server returned HTTP 502, but `waitForPhotoIndex` treated every non-200 response as “still indexing” and could poll for its one-hour deadline. I added a fail-fast status check and a bench regression test (`bun test bench/tab-switch.test.mjs`: 13 pass, 0 fail, 50 expect() calls). I will rerun with `--smoke`; the aborted run is not part of the A/B results.
Author
Owner

Perf-pin finding: the first bun run check after the GridColumn edits failed because the render.bound fingerprint for GridColumn.svelte#each:011c4146f38bb440:1 changed. I refreshed the exact pins with ../../scripts/perf-lint --adopt-7c: “Adopted 9746 exact #1058 sites; ordinary future ratchet remains active” and “perf-lint: PASS; 0 violations; 22128 scoped exceptions”. contracts/perf/ratchet.json is unchanged. The web check is running again.

Perf-pin finding: the first `bun run check` after the GridColumn edits failed because the `render.bound` fingerprint for `GridColumn.svelte#each:011c4146f38bb440:1` changed. I refreshed the exact pins with `../../scripts/perf-lint --adopt-7c`: “Adopted 9746 exact #1058 sites; ordinary future ratchet remains active” and “perf-lint: PASS; 0 violations; 22128 scoped exceptions”. `contracts/perf/ratchet.json` is unchanged. The web check is running again.
Author
Owner

A/B harness finding: the fixture and smoke seed completed on the perf VM (100 Photos indexed, 30 recurring Calendar Events; server read probes returned HTTP 200), but the run stopped before sampling. profileAbContext called availableTabs() on a newly created about:blank page, so the parent bundle exposed 0 Tabs and the assertion failed (0 !== 3). The perf lock was released and the remote browser/server were cleaned up. I am moving the assertion after the initial Mail route load, retaining that load as the A/B warm-up, and will rerun the same profile.

A/B harness finding: the fixture and smoke seed completed on the perf VM (100 Photos indexed, 30 recurring Calendar Events; server read probes returned HTTP 200), but the run stopped before sampling. `profileAbContext` called `availableTabs()` on a newly created `about:blank` page, so the parent bundle exposed 0 Tabs and the assertion failed (`0 !== 3`). The perf lock was released and the remote browser/server were cleaned up. I am moving the assertion after the initial Mail route load, retaining that load as the A/B warm-up, and will rerun the same profile.
Author
Owner

A/B harness finding: all 30 timing samples completed (five interleaved pairs for each of Mail→Calendar, Files→Calendar and Calendar→Files). Screenshot setup then timed out at #route-content .tg .block.plan.event-block after the 1440 light/dark captures. This shows the required 820 px Week view does not expose that exact visible TimeGrid selector. The run saved no acceptance result; the VM lock was released and cleanup completed. I am checking the existing responsive Calendar view and will update the screenshot readiness/DOM accounting to use its real Week content at 820/390, then rerun.

A/B harness finding: all 30 timing samples completed (five interleaved pairs for each of Mail→Calendar, Files→Calendar and Calendar→Files). Screenshot setup then timed out at `#route-content .tg .block.plan.event-block` after the 1440 light/dark captures. This shows the required 820 px Week view does not expose that exact visible TimeGrid selector. The run saved no acceptance result; the VM lock was released and cleanup completed. I am checking the existing responsive Calendar view and will update the screenshot readiness/DOM accounting to use its real Week content at 820/390, then rerun.
Author
Owner

Candidate A timing result (five interleaved warm pairs per cell; server and Chromium on the locked perf VM; 1440 px light): Mail→Calendar fully-painted p50 changed 3051.4 ms → 2774.4 ms (9.08% improvement; required ≥10%). Files→Calendar changed 2638.1 ms → 2388.0 ms (9.48%). Calendar→Files changed 1323.8 ms → 1334.1 ms (-0.78%). The largest p95 cell change is Calendar→Files, -11.98% (improved); no cell regressed >10%. The timing acceptance rule therefore rejects A because the Mail→Calendar p50 threshold is missed. I am completing the required responsive screenshots and DOM/listener evidence before reverting the A product commit.

Candidate A timing result (five interleaved warm pairs per cell; server and Chromium on the locked perf VM; 1440 px light): Mail→Calendar fully-painted p50 changed 3051.4 ms → 2774.4 ms (9.08% improvement; required ≥10%). Files→Calendar changed 2638.1 ms → 2388.0 ms (9.48%). Calendar→Files changed 1323.8 ms → 1334.1 ms (-0.78%). The largest p95 cell change is Calendar→Files, -11.98% (improved); no cell regressed >10%. The timing acceptance rule therefore rejects A because the Mail→Calendar p50 threshold is missed. I am completing the required responsive screenshots and DOM/listener evidence before reverting the A product commit.
Author
Owner

The scroll-to-Event retry narrowed the 820 px case: the Week grid reached .tg.ready, but the Event selector had no attached nodes within 30 seconds. The smoke fixture also contains real Journal Logs, Tasks and other Calendar items. I am changing screenshot readiness to bring the first attached real Week item into view (preferring Events when present) and recording item counts, so the capture proves visible loaded Calendar data at every width without assuming the responsive grid renders an Event at its leading column.

The scroll-to-Event retry narrowed the 820 px case: the Week grid reached `.tg.ready`, but the Event selector had no attached nodes within 30 seconds. The smoke fixture also contains real Journal Logs, Tasks and other Calendar items. I am changing screenshot readiness to bring the first attached real Week item into view (preferring Events when present) and recording item counts, so the capture proves visible loaded Calendar data at every width without assuming the responsive grid renders an Event at its leading column.
Author
Owner

Candidate A completed a second five-pair timing set, but still failed the acceptance rule: Mail→Calendar fully-painted p50 was 3162.8 ms → 3024.0 ms (4.39%); Files→Calendar was 2521.0 ms → 2227.4 ms (11.65%); Calendar→Files was 1210.0 ms → 1243.8 ms (-2.79%). Every p95 improved (changes -13.41%, -12.64%, and -12.22% respectively), but Mail→Calendar remained below the required 10% p50 improvement. I am reverting A. Screenshot capture also failed at 820 px: after waiting for .tg.ready, the Week grid had no attached Event, Log, Task or Activity item, so the all-width comparison is still incomplete. I will record grid loading state in the capture diagnostic and keep this as a known screenshot/DOM verification gap if the real Week view remains empty at that viewport.

Candidate A completed a second five-pair timing set, but still failed the acceptance rule: Mail→Calendar fully-painted p50 was 3162.8 ms → 3024.0 ms (4.39%); Files→Calendar was 2521.0 ms → 2227.4 ms (11.65%); Calendar→Files was 1210.0 ms → 1243.8 ms (-2.79%). Every p95 improved (changes -13.41%, -12.64%, and -12.22% respectively), but Mail→Calendar remained below the required 10% p50 improvement. I am reverting A. Screenshot capture also failed at 820 px: after waiting for `.tg.ready`, the Week grid had no attached Event, Log, Task or Activity item, so the all-width comparison is still incomplete. I will record grid loading state in the capture diagnostic and keep this as a known screenshot/DOM verification gap if the real Week view remains empty at that viewport.
Author
Owner

A is rejected and reverted in commit a273f76dd: the Mail→Calendar p50 improvement remained below 10%, despite p95 improvements in every cell. Its GridColumn listener/tag optimization is not in the working tree. I am moving on to the independent ModeHeader offset change from the pre-A parent. The 820 px screenshot/item gap remains under investigation in the bench capture path.

A is rejected and reverted in commit `a273f76dd`: the Mail→Calendar p50 improvement remained below 10%, despite p95 improvements in every cell. Its GridColumn listener/tag optimization is not in the working tree. I am moving on to the independent ModeHeader offset change from the pre-A parent. The 820 px screenshot/item gap remains under investigation in the bench capture path.
Author
Owner

B implementation is committed as 51c31486e. ModeHeader now caches the active scroll offset from scroll events and route/title effects read that cache; the focused test asserts a route update does not read the inner scroller's scrollTop, while horizontal scroll still uses the fast path. Focused Vitest result: Test Files 1 passed (1); Tests 21 passed (21). The required five-pair perf VM profile is queued behind startup1573-default-p1-base and will compare the frozen pre-B build with B.

B implementation is committed as `51c31486e`. ModeHeader now caches the active scroll offset from scroll events and route/title effects read that cache; the focused test asserts a route update does not read the inner scroller's `scrollTop`, while horizontal scroll still uses the fast path. Focused Vitest result: `Test Files 1 passed (1)`; `Tests 21 passed (21)`. The required five-pair perf VM profile is queued behind `startup1573-default-p1-base` and will compare the frozen pre-B build with B.
Author
Owner

Perf-run setup finding (#1557): the smoke fixture was ready (seed 549, 10,980 log entries, 366 daily notes), but the bench server phase/read probe timed out after 30,003.8 ms on GET /api/v1/notes/journal/2026-10-11. It reported 4,180 CPU ms and 527,630,336 RSS bytes; the same probe returned 200 for Calendar range (126.6 ms), Notes tasks/day (77.6 ms), and Files entries (1,319.2 ms). The A/B browser timing phase is continuing. This is outside the ModeHeader change and needs separate triage if it reproduces.

Perf-run setup finding (#1557): the smoke fixture was ready (seed 549, 10,980 log entries, 366 daily notes), but the bench server phase/read probe timed out after 30,003.8 ms on `GET /api/v1/notes/journal/2026-10-11`. It reported 4,180 CPU ms and 527,630,336 RSS bytes; the same probe returned 200 for Calendar range (126.6 ms), Notes tasks/day (77.6 ms), and Files entries (1,319.2 ms). The A/B browser timing phase is continuing. This is outside the ModeHeader change and needs separate triage if it reproduces.
Author
Owner

Candidate B result (#1557): five interleaved warm pairs on the perf VM, Chromium with macOS platform emulation, 1440 px/light. Mail→Calendar full-paint p50 improved 4.68% (3,063.3 → 2,919.8 ms), below the required 10%. Files→Calendar p50 changed by -3.17%; Calendar→Files improved 16.05%. No cell's full-paint p95 regressed: changes were -2.94%, -0.47%, and -23.25%. The rule therefore rejects B; I am reverting it.

The timing report completed, but the screenshot pass did not: after the candidate build was selected, Week reached .tg.ready with 505 nodes and 11 columns but had zero Events, Logs, Tasks, or Activities for 30 seconds. I did not treat the empty capture as visual evidence. The run's smoke fixture had 10,980 log entries and 30 recurring Events; the before build rendered 105 Event cards in its 1440 px screenshots. Report: artifacts/tab-switch-1557/B-ab.json in the calact4 worktree.

Candidate B result (#1557): five interleaved warm pairs on the perf VM, Chromium with macOS platform emulation, 1440 px/light. Mail→Calendar full-paint p50 improved 4.68% (3,063.3 → 2,919.8 ms), below the required 10%. Files→Calendar p50 changed by -3.17%; Calendar→Files improved 16.05%. No cell's full-paint p95 regressed: changes were -2.94%, -0.47%, and -23.25%. The rule therefore rejects B; I am reverting it. The timing report completed, but the screenshot pass did not: after the candidate build was selected, Week reached `.tg.ready` with 505 nodes and 11 columns but had zero Events, Logs, Tasks, or Activities for 30 seconds. I did not treat the empty capture as visual evidence. The run's smoke fixture had 10,980 log entries and 30 recurring Events; the before build rendered 105 Event cards in its 1440 px screenshots. Report: `artifacts/tab-switch-1557/B-ab.json` in the calact4 worktree.
Author
Owner

Visual evidence setup finding (#1557): the A/B harness rejected the required 820 px and 390 px light/dark screenshot matrix because its timing-only 1440 px/light validation also ran in --ab-visual-only mode. I split the timing and screenshot matrix checks and added a focused test. bun test bench/tab-switch.test.mjs: 15 pass, 0 fail, 58 expect() calls. The production capture has not rerun yet.

Visual evidence setup finding (#1557): the A/B harness rejected the required 820 px and 390 px light/dark screenshot matrix because its timing-only 1440 px/light validation also ran in `--ab-visual-only` mode. I split the timing and screenshot matrix checks and added a focused test. `bun test bench/tab-switch.test.mjs`: 15 pass, 0 fail, 58 expect() calls. The production capture has not rerun yet.
Author
Owner

Candidate A screenshot result (#1557): the real perf-VM fixture was ready (seed 549, 10,980 logs, 30 recurring Events). The parent production build rendered 105 Event cards at 1440 px in both light and dark. The candidate then reached .tg.ready with 505 TimeGrid nodes and 11 columns, but zero Events, Logs, Tasks, or Activities for 30 seconds; the harness stopped rather than saving an empty capture. artifacts/tab-switch-1557/A-visual.json records the snapshot. Candidate B had the same empty-grid symptom. The A and B timing candidates remain rejected/reverted; their timings are not treated as acceptable evidence while the candidate screenshots show different data states.

Candidate A screenshot result (#1557): the real perf-VM fixture was ready (seed 549, 10,980 logs, 30 recurring Events). The parent production build rendered 105 Event cards at 1440 px in both light and dark. The candidate then reached `.tg.ready` with 505 TimeGrid nodes and 11 columns, but zero Events, Logs, Tasks, or Activities for 30 seconds; the harness stopped rather than saving an empty capture. `artifacts/tab-switch-1557/A-visual.json` records the snapshot. Candidate B had the same empty-grid symptom. The A and B timing candidates remain rejected/reverted; their timings are not treated as acceptable evidence while the candidate screenshots show different data states.
Author
Owner

Baseline responsive capture finding (#1557): with the same parent production build on both sides and the smoke fixture ready, the Calendar Week rendered 105 Event cards at 1440 px light/dark. At 820 px, the route reached .tg.ready with 505 nodes, 11 columns, and visible_days=7, but no Events, Logs, Tasks, or Activities appeared within 30 seconds. The capture stopped before 390 px; I did not save or attach an empty image. The /api/v1/calendar/range and daily read probes all returned 200 in this run. Valid parent screenshots are available for 1440 px only. This prevents a complete responsive review set and is a baseline Calendar data/rendering issue, not a candidate result.

Follow-up to the earlier journal probe: /api/v1/notes/journal/2026-10-11 returned 200 in 81.2 ms on this retry, so the prior 30-second timeout did not reproduce.

Baseline responsive capture finding (#1557): with the same parent production build on both sides and the smoke fixture ready, the Calendar Week rendered 105 Event cards at 1440 px light/dark. At 820 px, the route reached `.tg.ready` with 505 nodes, 11 columns, and `visible_days=7`, but no Events, Logs, Tasks, or Activities appeared within 30 seconds. The capture stopped before 390 px; I did not save or attach an empty image. The `/api/v1/calendar/range` and daily read probes all returned 200 in this run. Valid parent screenshots are available for 1440 px only. This prevents a complete responsive review set and is a baseline Calendar data/rendering issue, not a candidate result. Follow-up to the earlier journal probe: `/api/v1/notes/journal/2026-10-11` returned 200 in 81.2 ms on this retry, so the prior 30-second timeout did not reproduce.
Author
Owner

Valid parent-build reference captures (1440 × 900, macOS platform emulation, smoke-fixture Events visibly loaded):

These are baseline references only. Candidate A and B did not produce valid loaded screenshots, and the 820 px baseline capture also had no Calendar Items, as noted above.

Valid parent-build reference captures (1440 × 900, macOS platform emulation, smoke-fixture Events visibly loaded): - Light: [calact4-1557-parent-1440-light.png](https://git.kayg.org/attachments/021351b6-669e-4bfc-8008-50ea3824f256) - Dark: [calact4-1557-parent-1440-dark.png](https://git.kayg.org/attachments/e9ea5540-32a6-4889-83fd-193b529552d3) These are baseline references only. Candidate A and B did not produce valid loaded screenshots, and the 820 px baseline capture also had no Calendar Items, as noted above.
Author
Owner

Status

Neither product candidate passed the #1557 acceptance rule. Candidate A and Candidate B are reverted; no user-facing optimization remains in this branch. The benchmark and capture harness changes remain.

Changes

  • Added Calendar Week DOM breakdown, item visibility checks, DOM/listener budget gates, and exact-pixel A/B screenshot capture across responsive widths and themes.
  • Visual-only A/B captures now allow 1440/820/390 px and light/dark while retaining the 1440 px/light timing cell.
  • Added focused tests for the screenshot matrix and the existing DOM/listener budgets.
  • A (e03e8b54e, reverted by a273f76dd) delegated GridColumn card events and deferred tag parsing. B (51c31486e, reverted by 4aa828ed2) cached ModeHeader scroll offsets.

Surviving changed files: bench/tab-switch.mjs, bench/tab-switch.test.mjs. Candidate product files were restored by their revert commits.

Measurements

  • A: Mail→Calendar full-paint p50 was 3,051.4→2,774.4 ms (+9.08%) in one five-pair set, then 3,162.8→3,024.0 ms (+4.39%) in the next. Both miss the required 10%; other cells' p95 values improved in both sets.
  • B: five interleaved warm pairs at 1440 px/light. Mail→Calendar p50 improved 4.68% (3,063.3→2,919.8 ms); Files→Calendar changed −3.17%; Calendar→Files improved 16.05%. Full-paint p95 changes were −2.94%, −0.47%, and −23.25%. B misses the required Mail→Calendar threshold.
  • The candidate screenshots for both A and B reached .tg.ready with 505 nodes and 11 columns but no Events, Logs, Tasks, or Activities. The parent rendered 105 Event cards. Treat the candidate timings as diagnostic only; the screenshot data states do not match.
  • The baseline also rendered no Calendar Items at 820 px and the run stopped before 390 px. The server read probes returned 200 in the retry; the earlier journal timeout did not reproduce.

Valid parent-build reference images are attached above for 1440×900 light and dark. They use macOS platform emulation and show loaded fixture Events. They are baseline references, not candidate comparisons.

Gates

bun run check; echo exit=$? (verbatim summary lines):

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22128 scoped exceptions
svelte-check found 0 errors and 0 warnings
exit=0

bun run test -- --maxWorkers=2:

 Test Files  318 passed (318)
      Tests  2316 passed (2316)
   Start at  15:33:59
   Duration  358.31s (environment 28%, import 25%, transform 23%, tests 18%, setup 6%)
exit=0

bun test bench/tab-switch.test.mjs:

 15 pass
 0 fail
 58 expect() calls
Ran 15 tests across 1 file. [106.00ms]

No Rust source changed, so Rust format, clippy, and test gates were not run. cargo clean output: Removed 1 file, 356B total. Web build output was removed.

UX gaps left and decisions

  • The required responsive visual matrix is incomplete: candidate captures are invalid because their Calendar Items are missing, and the baseline 820 px capture is empty. The 390 px capture was not reached. No candidate screenshot is presented as valid evidence.
  • The 3,000-node and 150-listener budgets were not validated with a loaded candidate state.
  • C was not attempted because neither A nor B was accepted.
  • The design doc did not define the visual-only benchmark matrix. I kept its 1440 px/light comparison and allowed the screenshot-only path to add required widths/themes.

git fetch origin && git merge --no-edit origin/dev reported Already up to date.

Head SHA: 83dc31b00d63b647e91c316c07f356bbeb0a893e.

## Status Neither product candidate passed the #1557 acceptance rule. Candidate A and Candidate B are reverted; no user-facing optimization remains in this branch. The benchmark and capture harness changes remain. ## Changes - Added Calendar Week DOM breakdown, item visibility checks, DOM/listener budget gates, and exact-pixel A/B screenshot capture across responsive widths and themes. - Visual-only A/B captures now allow 1440/820/390 px and light/dark while retaining the 1440 px/light timing cell. - Added focused tests for the screenshot matrix and the existing DOM/listener budgets. - A (`e03e8b54e`, reverted by `a273f76dd`) delegated GridColumn card events and deferred tag parsing. B (`51c31486e`, reverted by `4aa828ed2`) cached ModeHeader scroll offsets. Surviving changed files: `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`. Candidate product files were restored by their revert commits. ## Measurements - A: Mail→Calendar full-paint p50 was 3,051.4→2,774.4 ms (+9.08%) in one five-pair set, then 3,162.8→3,024.0 ms (+4.39%) in the next. Both miss the required 10%; other cells' p95 values improved in both sets. - B: five interleaved warm pairs at 1440 px/light. Mail→Calendar p50 improved 4.68% (3,063.3→2,919.8 ms); Files→Calendar changed −3.17%; Calendar→Files improved 16.05%. Full-paint p95 changes were −2.94%, −0.47%, and −23.25%. B misses the required Mail→Calendar threshold. - The candidate screenshots for both A and B reached `.tg.ready` with 505 nodes and 11 columns but no Events, Logs, Tasks, or Activities. The parent rendered 105 Event cards. Treat the candidate timings as diagnostic only; the screenshot data states do not match. - The baseline also rendered no Calendar Items at 820 px and the run stopped before 390 px. The server read probes returned 200 in the retry; the earlier journal timeout did not reproduce. Valid parent-build reference images are attached above for 1440×900 light and dark. They use macOS platform emulation and show loaded fixture Events. They are baseline references, not candidate comparisons. ## Gates `bun run check; echo exit=$?` (verbatim summary lines): ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22128 scoped exceptions svelte-check found 0 errors and 0 warnings exit=0 ``` `bun run test -- --maxWorkers=2`: ```text Test Files 318 passed (318) Tests 2316 passed (2316) Start at 15:33:59 Duration 358.31s (environment 28%, import 25%, transform 23%, tests 18%, setup 6%) exit=0 ``` `bun test bench/tab-switch.test.mjs`: ```text 15 pass 0 fail 58 expect() calls Ran 15 tests across 1 file. [106.00ms] ``` No Rust source changed, so Rust format, clippy, and test gates were not run. `cargo clean` output: `Removed 1 file, 356B total`. Web build output was removed. ## UX gaps left and decisions - The required responsive visual matrix is incomplete: candidate captures are invalid because their Calendar Items are missing, and the baseline 820 px capture is empty. The 390 px capture was not reached. No candidate screenshot is presented as valid evidence. - The 3,000-node and 150-listener budgets were not validated with a loaded candidate state. - C was not attempted because neither A nor B was accepted. - The design doc did not define the visual-only benchmark matrix. I kept its 1440 px/light comparison and allowed the screenshot-only path to add required widths/themes. `git fetch origin && git merge --no-edit origin/dev` reported `Already up to date.` Head SHA: `83dc31b00d63b647e91c316c07f356bbeb0a893e`.
Author
Owner

Starting escalation round 5 on job/calact5, base 83dc31b00d63b647e91c316c07f356bbeb0a893e. Read CLAUDE.md, CONTEXT.md, DESIGN §59, all round 2–4 reports and calprof attribution. First repair/validate the harness without product changes: parent/parent, five interleaved pairs, abort on per-sample Event-count mismatch, require Mail→Calendar full-paint p50 noise <5%. Browser and server run on perf VM under perf-run. Prior empty candidate and 820 px parent captures must be explained before measuring a candidate. Decision rule for each product change: keep only if Mail→Calendar full-paint p50 improves at least 10% and no measured cell full-paint p95 regresses more than 10%; report Week nodes/listeners and loaded screenshots at 1440/820/390 light/dark. No push or deploy; only the explicitly required origin/dev integration before final gates.

Starting escalation round 5 on `job/calact5`, base `83dc31b00d63b647e91c316c07f356bbeb0a893e`. Read CLAUDE.md, CONTEXT.md, DESIGN §59, all round 2–4 reports and calprof attribution. First repair/validate the harness without product changes: parent/parent, five interleaved pairs, abort on per-sample Event-count mismatch, require Mail→Calendar full-paint p50 noise <5%. Browser and server run on perf VM under perf-run. Prior empty candidate and 820 px parent captures must be explained before measuring a candidate. Decision rule for each product change: keep only if Mail→Calendar full-paint p50 improves at least 10% and no measured cell full-paint p95 regresses more than 10%; report Week nodes/listeners and loaded screenshots at 1440/820/390 light/dark. No push or deploy; only the explicitly required origin/dev integration before final gates.
Author
Owner

Harness findings (no product change yet):

  • Per-sample Calendar Event-count assertions and a separate parent/parent A/A noise result are committed (da49f42dd). Empty or different timing data aborts the run.
  • The inherited timer starts on pointerdown and takes several CDP round trips before recording full paint. Commits 51166a804 and ed7aff9bf start on the trusted click and resolve the paint timestamp inside Chromium. The browser-side target wait starts before dispatch, so the runner's later polling does not move that timestamp. A/A and A/B will use the same corrected boundary.
  • Live diagnostic on the perf VM, at 1440 px: Week has 11 columns, width 135.71875 px each. Dates 2026-10-09 and 10 have 60 Items each; October 11 has 83. There are 45 drawn Event cards and three overflow controls. Narrow lanes can put a whole overlap group in +N; the prior screenshot counter omits these controls.
  • showCalendarWeekItem() picks the first attached card, which can be in the off-screen date buffer, and calls scrollIntoViewIfNeeded(). This can change the horizontal window. I am changing capture positioning to the URL's leading date and vertical scrolling only, with an overflow-list capture when a narrow lane holds its Events in +N.

The parent-only responsive diagnostic is still in fixture indexing. HDD qualification passed; no A/A or candidate timing is claimed yet. Focused harness tests: 17 pass, 0 fail, 64 expect() calls. No dependency version changed.

Harness findings (no product change yet): - Per-sample Calendar Event-count assertions and a separate parent/parent A/A noise result are committed (`da49f42dd`). Empty or different timing data aborts the run. - The inherited timer starts on pointerdown and takes several CDP round trips before recording full paint. Commits `51166a804` and `ed7aff9bf` start on the trusted click and resolve the paint timestamp inside Chromium. The browser-side target wait starts before dispatch, so the runner's later polling does not move that timestamp. A/A and A/B will use the same corrected boundary. - Live diagnostic on the perf VM, at 1440 px: Week has 11 columns, width 135.71875 px each. Dates 2026-10-09 and 10 have 60 Items each; October 11 has 83. There are 45 drawn Event cards and three overflow controls. Narrow lanes can put a whole overlap group in `+N`; the prior screenshot counter omits these controls. - `showCalendarWeekItem()` picks the first attached card, which can be in the off-screen date buffer, and calls scrollIntoViewIfNeeded(). This can change the horizontal window. I am changing capture positioning to the URL's leading date and vertical scrolling only, with an overflow-list capture when a narrow lane holds its Events in `+N`. The parent-only responsive diagnostic is still in fixture indexing. HDD qualification passed; no A/A or candidate timing is claimed yet. Focused harness tests: 17 pass, 0 fail, 64 expect() calls. No dependency version changed.
Author
Owner

Root cause of round 4's "empty Calendar" evidence:

On the live parent at 820 px, .tg is ready compact, --colw is 62 px, and the 11 dates are October 9–19. The populated date columns still announce 60, 60 and 83 Items. There are 32 real button.more overlap controls, but zero drawn Event/Log/Task/Activity cards. This is the existing #969 overlap rule: a lane that cannot fit a 64 px card plus its overflow control puts the complete group in +N. It is not missing API data.

The round-4 saved failures themselves record 43 overflow controls and compact ready (A-visual.json, B-ab.json). Both artifact trees contain only before/1440-light.png and before/1440-dark.png, and captureAbScreenshots() iterates all parent widths before any candidate width. These runs therefore failed at parent 820 px, before candidate capture. The earlier comments saying the candidate capture was empty misidentify the failing variant. Neither run proves that candidate Event data was missing.

There is a second capture error: the inherited helper reveals the first attached card from the off-screen date buffer. Each reveal can move the linked Week window backwards. The repaired helper uses the linked leading date and changes only scrollTop. On narrow lanes it opens the largest real overflow group (the thirty fixture Events form that group), then asserts visible Event rows. The changed helper test is intentional: #1557 explicitly requires repair of invalid capture behavior; no product test expectation was changed.

No product change yet. Parent fixture indexing is still running; A/A follows with the corrected harness.

Root cause of round 4's "empty Calendar" evidence: On the live parent at 820 px, `.tg` is `ready compact`, `--colw` is 62 px, and the 11 dates are October 9–19. The populated date columns still announce 60, 60 and 83 Items. There are 32 real `button.more` overlap controls, but zero drawn Event/Log/Task/Activity cards. This is the existing #969 overlap rule: a lane that cannot fit a 64 px card plus its overflow control puts the complete group in `+N`. It is not missing API data. The round-4 saved failures themselves record **43 overflow controls** and `compact ready` (`A-visual.json`, `B-ab.json`). Both artifact trees contain only `before/1440-light.png` and `before/1440-dark.png`, and `captureAbScreenshots()` iterates all parent widths before any candidate width. These runs therefore failed at **parent 820 px**, before candidate capture. The earlier comments saying the candidate capture was empty misidentify the failing variant. Neither run proves that candidate Event data was missing. There is a second capture error: the inherited helper reveals the first attached card from the off-screen date buffer. Each reveal can move the linked Week window backwards. The repaired helper uses the linked leading date and changes only scrollTop. On narrow lanes it opens the largest real overflow group (the thirty fixture Events form that group), then asserts visible Event rows. The changed helper test is intentional: #1557 explicitly requires repair of invalid capture behavior; no product test expectation was changed. No product change yet. Parent fixture indexing is still running; A/A follows with the corrected harness.
Author
Owner

A/A setup found a second harness race before any timing samples: the second identical parent page returned 0 exposed Tabs versus 3 requested. firstLoad accepted server-rendered #route-content before Tab Bar hydration because it omitted the target argument to the paint boundary. It now waits for the requested Tab's selected state. Focused regression verifies that target reaches the boundary. Harness head 09fb411da; bun test bench/tab-switch.test.mjs: 18 pass, 0 fail, 70 expect() calls. No product change and no timing claim. The seeded fixture reached 100 indexed Photos before failure. Rerun is queued under perf-run; current holder is startup1573-seed.

A/A setup found a second harness race before any timing samples: the second identical parent page returned 0 exposed Tabs versus 3 requested. `firstLoad` accepted server-rendered `#route-content` before Tab Bar hydration because it omitted the target argument to the paint boundary. It now waits for the requested Tab's selected state. Focused regression verifies that target reaches the boundary. Harness head `09fb411da`; `bun test bench/tab-switch.test.mjs`: `18 pass`, `0 fail`, `70 expect() calls`. No product change and no timing claim. The seeded fixture reached 100 indexed Photos before failure. Rerun is queued under perf-run; current holder is startup1573-seed.
Author
Owner

The next A/A attempt reached the complete fixture (100 indexed Photos, all four read probes HTTP 200), then failed before warm-up at the Appearance fixture PUT with Playwright's default 30,000 ms deadline. No timing sample was recorded. This is a setup timeout, not an A/B result. setThemeContext now accepts an optional request deadline; existing callers retain 30,000 ms, while this benchmark uses 120,000 ms outside measurements. The public harness addition is only this optional test deadline. Head 560a80144 also retains content-free front counters, server exit state and a redacted server tail on failure so a repeated timeout can be attributed. Focused harness result: 18 pass, 0 fail, 70 expect() calls. Product files remain unchanged; another strict A/A is running on the perf VM.

The next A/A attempt reached the complete fixture (100 indexed Photos, all four read probes HTTP 200), then failed before warm-up at the Appearance fixture PUT with Playwright's default 30,000 ms deadline. No timing sample was recorded. This is a setup timeout, not an A/B result. `setThemeContext` now accepts an optional request deadline; existing callers retain 30,000 ms, while this benchmark uses 120,000 ms outside measurements. The public harness addition is only this optional test deadline. Head `560a80144` also retains content-free front counters, server exit state and a redacted server tail on failure so a repeated timeout can be attributed. Focused harness result: `18 pass`, `0 fail`, `70 expect() calls`. Product files remain unchanged; another strict A/A is running on the perf VM.
Author
Owner

Fixture finding: the live Calendar had 45 drawn Events while Photos remained at 0 after more than 20 minutes. Direct VM readiness returned 200 (0.48 s); HTTPS-front readiness returned 200 (1.09 s). Photos library roots were the default Home ([""]). A real PUT /api/v1/photos/library-roots with the seeded Photos folder returned 200; the next harness readiness check saw all 100 indexed Photos. This identifies a fixture refresh dependency; it does not prove which startup notification was missed. The harness now makes that explicit library-root assignment before waiting (6e4e6e5cf), outside all measured visits. For the current run I made the same assignment on its authenticated VM page before warm-up; parent and candidate will reuse that exact indexed Home. No product source changed. Focused harness result remains 18 pass, 0 fail, 70 expect() calls.

Fixture finding: the live Calendar had 45 drawn Events while Photos remained at 0 after more than 20 minutes. Direct VM readiness returned 200 (0.48 s); HTTPS-front readiness returned 200 (1.09 s). Photos library roots were the default Home (`[""]`). A real `PUT /api/v1/photos/library-roots` with the seeded `Photos` folder returned 200; the next harness readiness check saw all 100 indexed Photos. This identifies a fixture refresh dependency; it does not prove which startup notification was missed. The harness now makes that explicit library-root assignment before waiting (`6e4e6e5cf`), outside all measured visits. For the current run I made the same assignment on its authenticated VM page before warm-up; parent and candidate will reuse that exact indexed Home. No product source changed. Focused harness result remains `18 pass`, `0 fail`, `70 expect() calls`.
Author
Owner

Reservation finding: the third attempt reached its first measured parent sample (Mail→Calendar 1268.5 ms full paint, 135 drawn Events matching the parent warm-up). It then released the VM lock and waited behind consecutive startup1573 phases, including one declared at 24 minutes. That incomplete one-sample run is retained in metadata3-ab.json; it is not an A/A result and is not used for a gain claim. I cancelled only this run's queued lock request so its normal cleanup can stop its fixture.

Harness head 1a65a04df now reserves the full bounded paired timing phase through perf-run; snapshots use a separate reservation, and the candidate build wait still holds no reservation. Nested theme setup shares the phase reservation, with a regression test that rejects a second exclusive acquisition. Existing cold/LinkNav setup calls that already hold a reservation use the same flag. Decision: keep each interleaved matrix contiguous so another job cannot split a pair by many minutes. Acceptance thresholds and sample count are unchanged. Focused result: 19 pass, 0 fail, 72 expect() calls.

Reservation finding: the third attempt reached its first measured parent sample (Mail→Calendar 1268.5 ms full paint, 135 drawn Events matching the parent warm-up). It then released the VM lock and waited behind consecutive startup1573 phases, including one declared at 24 minutes. That incomplete one-sample run is retained in `metadata3-ab.json`; it is not an A/A result and is not used for a gain claim. I cancelled only this run's queued lock request so its normal cleanup can stop its fixture. Harness head `1a65a04df` now reserves the full bounded paired timing phase through perf-run; snapshots use a separate reservation, and the candidate build wait still holds no reservation. Nested theme setup shares the phase reservation, with a regression test that rejects a second exclusive acquisition. Existing cold/LinkNav setup calls that already hold a reservation use the same flag. Decision: keep each interleaved matrix contiguous so another job cannot split a pair by many minutes. Acceptance thresholds and sample count are unchanged. Focused result: `19 pass`, `0 fail`, `72 expect() calls`.
Author
Owner

The third attempt produced one incomplete parent sample: Mail→Calendar 1268.5 ms, 135 drawn Events, matching its 135-Event assertion. It is not an A/A result. Per-operation leases then queued behind another job, so I cancelled only our queued request and let fixture cleanup finish. Each validation now holds one bounded perf-run reservation through setup, all five paired samples and captures. It releases the reservation while waiting for the candidate build and reacquires for A/B validation and cleanup. This avoids repeatedly queueing within one validation. The 60-minute cap remains in effect. A fresh parent/parent run is queued; no product change has been applied.

The third attempt produced one incomplete parent sample: Mail→Calendar 1268.5 ms, 135 drawn Events, matching its 135-Event assertion. It is not an A/A result. Per-operation leases then queued behind another job, so I cancelled only our queued request and let fixture cleanup finish. Each validation now holds one bounded perf-run reservation through setup, all five paired samples and captures. It releases the reservation while waiting for the candidate build and reacquires for A/B validation and cleanup. This avoids repeatedly queueing within one validation. The 60-minute cap remains in effect. A fresh parent/parent run is queued; no product change has been applied.
Author
Owner

The fourth fixture remained at 0 Photos after an early library-root assignment. Directory listings alone were not a whole-tree readiness check: a read-only Index count showed 372 Files rows and only 1 Photo row, while 100 seeded JPEGs existed on disk. Listing year/month folders schedules asynchronous reconciliation; the first replies can be empty. I browsed only the generated Photo folders through the real Files API (at most four concurrent reads), then confirmed 100 returned JPEGs across 106 folders and made one real library refresh before warm-up. The harness then reported 100 indexed Photos and all three requested Tabs. The running A/A uses this repaired fixture; no product source changed. Commit 5d6fbe1d8 makes the same bounded priming automatic for smoke fixtures, retries incomplete API snapshots for at most five minutes, and retains the real Photo readiness check. Regression output: 21 pass, 0 fail, 76 expect() calls. Thumbnail job failures reported the public category “Thumbnails are temporarily unavailable”; no Photo Tab is measured in this job.

The fourth fixture remained at 0 Photos after an early library-root assignment. Directory listings alone were not a whole-tree readiness check: a read-only Index count showed 372 Files rows and only 1 Photo row, while 100 seeded JPEGs existed on disk. Listing year/month folders schedules asynchronous reconciliation; the first replies can be empty. I browsed only the generated Photo folders through the real Files API (at most four concurrent reads), then confirmed 100 returned JPEGs across 106 folders and made one real library refresh before warm-up. The harness then reported 100 indexed Photos and all three requested Tabs. The running A/A uses this repaired fixture; no product source changed. Commit 5d6fbe1d8 makes the same bounded priming automatic for smoke fixtures, retries incomplete API snapshots for at most five minutes, and retains the real Photo readiness check. Regression output: `21 pass`, `0 fail`, `76 expect() calls`. Thumbnail job failures reported the public category “Thumbnails are temporarily unavailable”; no Photo Tab is measured in this job.
Author
Owner

A/A result: INVALID. Both builds are frozen parent 83dc31b00. Five interleaved pairs ran per cell on the perf VM, with server and Chromium on that VM under perf-run. All 30 samples passed the per-sample 135-Event assertion. No sample is dropped.

Cell Parent A p50 / p95 (ms) Parent B p50 / p95 (ms)
mail→calendar 1608.6 / 4702.8 1780.2 / 2373.2
files→calendar 1784.5 / 2081.1 1541.1 / 2194.7
calendar→files 562.4 / 654.7 485.7 / 582.3

Mail→Calendar p50 noise: 10.67%, above the required <5%. The responsive parent captures contain Events: 45 drawn cards at 1440 px; 30 real overflow-list Events at 820/390 px, light and dark. Parent/parent 1440-light differs by 0 pixels. Parent/parent 1440-dark differs by 4 pixels, at (31,19), (32,19), (33,19), (19,33), around the Sidebar’s outer corner. The exact-pixel assertion rejected it; I did not weaken that assertion.

Commit 8254ff1d2 explicitly brings paired pages to the foreground and asserts document.visibilityState === "visible" before trusted input. This removes a harness confound; it is not a proven explanation for the observed noise. Standalone A/A timing runs now also exit with failure if the noise gate fails. Regression output: 22 pass, 0 fail, 79 expect() calls. One final foreground A/A timing-only run is queued behind the current VM reservation. No product source or perf ratchet changed; the candidate remains unpublished because Step 1 has not passed.

A/A result: INVALID. Both builds are frozen parent 83dc31b00. Five interleaved pairs ran per cell on the perf VM, with server and Chromium on that VM under perf-run. All 30 samples passed the per-sample 135-Event assertion. No sample is dropped. | Cell | Parent A p50 / p95 (ms) | Parent B p50 / p95 (ms) | |---|---:|---:| | mail→calendar | 1608.6 / 4702.8 | 1780.2 / 2373.2 | | files→calendar | 1784.5 / 2081.1 | 1541.1 / 2194.7 | | calendar→files | 562.4 / 654.7 | 485.7 / 582.3 | Mail→Calendar p50 noise: **10.67%**, above the required <5%. The responsive parent captures contain Events: 45 drawn cards at 1440 px; 30 real overflow-list Events at 820/390 px, light and dark. Parent/parent 1440-light differs by 0 pixels. Parent/parent 1440-dark differs by 4 pixels, at (31,19), (32,19), (33,19), (19,33), around the Sidebar’s outer corner. The exact-pixel assertion rejected it; I did not weaken that assertion. Commit 8254ff1d2 explicitly brings paired pages to the foreground and asserts `document.visibilityState === "visible"` before trusted input. This removes a harness confound; it is not a proven explanation for the observed noise. Standalone A/A timing runs now also exit with failure if the noise gate fails. Regression output: `22 pass`, `0 fail`, `79 expect() calls`. One final foreground A/A timing-only run is queued behind the current VM reservation. No product source or perf ratchet changed; the candidate remains unpublished because Step 1 has not passed.
Author
Owner

calact5 final report — INCOMPLETE / NOT READY FOR MERGE.

Calendar activation has no accepted improvement. Step 1 did not pass, so I kept product code unchanged. Branch job/calact5; base 83dc31b00d63b647e91c316c07f356bbeb0a893e; head 0a3c064801d04cb9b0ce0e371a640bac8c3057c9. The required origin/dev fetch and merge returned Already up to date. No push or deploy.

Built and files

  • bench/tab-switch.mjs: trusted-click timestamps captured inside Chromium; selected-Tab readiness; positive, matching Event counts on every sample; linked-date capture positioning; real overflow Events on narrow captures; strict A/A noise rejection; foreground-page activation and visibility assertions; bounded perf-run validation reservations; explicit smoke Photo Index preparation through four concurrent real Files reads; five-minute abortable setup deadline; content-free failure diagnostics; complete visual-matrix capture checkpoints with DOM/listener counts; early-failure provenance and correct requested conditions.
  • bench/tab-switch.test.mjs: regressions for readiness, data validity, A/A noise, nested reservations, bounded fixture reads, stalled requests and foreground activation.
  • apps/web/e2e/harness.mjs: optional appearance request deadline. The default stays 30 seconds; benchmark setup uses 120 seconds outside measurements.

Measurements

Five interleaved parent/parent pairs per cell ran with server and Chromium on the perf VM under perf-run. All 30 samples matched at 135 drawn Events. No sample was dropped.

Cell Parent A full-paint p50 / p95 ms Parent B full-paint p50 / p95 ms
Mail→Calendar 1608.6 / 4702.8 1780.2 / 2373.2
Files→Calendar 1784.5 / 2081.1 1541.1 / 2194.7
Calendar→Files 562.4 / 654.7 485.7 / 582.3

Mail→Calendar p50 noise was 10.67%, failing the required <5%. Parent/parent desktop light differed by 0 pixels; desktop dark differed by 4 pixels at the Sidebar outer corner. The exact-pixel gate rejected it. The final foreground A/A attempt acquired the VM lock but failed disk qualification at 70.148558 IOPS (required 100–250), before collecting any samples. No further measurement loop was run.

30-sample A/A data, pixel coordinates, final qualification failure, qualification log. The failure artifact predates the last metadata-only commit; future early failures include the loaded harness hash and selected warm/cold conditions.

The old historical baseline 1464.9 / 1813.5 ms uses a different measurement boundary and dataset window; I did not claim a gain against it. Fresh Week DOM/listener counts were lost when the earlier visual helper aborted; the new checkpoint retains them for future runs. No candidate counts exist. The supplied calprof attribution remains the prior evidence of roughly 15k nodes / 1.4k listeners, not a fresh measurement from this job.

Captures — frozen parent 83dc31b00, macOS platform emulation

These are diagnostic parent captures, not candidate proof. The dated Week capture has 45 drawn Event cards at desktop; tablet and phone show 30 real overflow-list Events. The timed default Week has 135 drawn Events. Those are different linked date windows.

Width Light Dark
1440 parent parent
820 parent parent
390 parent parent

A/A parent B desktop light, A/A parent B desktop dark. Round 4's missing tablet capture was a parent capture with populated overflow groups, not proof of an empty candidate. The corrected helper opens a real group and preserves the linked date.

Gates — verbatim summaries

cd apps/web && bun run check; echo exit=$?

perf-lint: PASS; 0 violations; 22128 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact5/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings
exit=0

cd apps/web && bun run test -- --maxWorkers=2

 Test Files  318 passed (318)
      Tests  2316 passed (2316)
   Start at  18:55:28
   Duration  214.87s (environment 29%, transform 23%, import 22%, tests 20%, setup 5%)

Environment  |component| jsdom was created 131 times · 95.39s total, 32% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

bun test bench/tab-switch.test.mjs


 24 pass
 0 fail
 84 expect() calls
Ran 24 tests across 1 file. [100.00ms]

Web check summary, full web test output, final harness test output. No Rust source changed; Rust format/clippy/test gates were not needed. No dependency or perf ratchet changed.

Known gaps / work left

  • Required <5% A/A calibration remains unproved. Calendar optimization, separate parent/candidate five-pair proof, candidate DOM/listener counts and all six candidate comparisons remain unbuilt.
  • Foreground activation removes a confound; it is not a proven explanation for the first run's noise.
  • Fixture thumbnail jobs reported “Thumbnails are temporarily unavailable.” This was recorded on this issue; no Photo Tab was measured.
  • No product UX gap was closed. The capture gaps closed are real Event visibility and preservation of the linked date. Calendar activation cost remains the product UX gap.

Decisions

All strict Event, noise, pixel and disk qualification gates remain enforced. Setup reads use four concurrent requests and a five-minute deadline. A validation holds one bounded perf-run reservation through setup, paired timings and captures, then releases it during candidate build waits. These are harness decisions; no undecided product design was built. The reviewed next candidate is duration-based metadata DOM pruning. Width-based pruning is unsafe because higher-specificity LOD/reveal selectors can restore rows hidden by compact/cramped rules; the draft was never applied.

For follow-up / merge round: rerun the strict five-pair A/A on the perf VM with the foreground fix and qualified disk; then implement one candidate, run bun run check, bun run test -- --maxWorkers=2, bun test bench/tab-switch.test.mjs, and measure five interleaved pairs per cell with matching Event counts. Keep it only for ≥10% Mail→Calendar p50 improvement and no cell p95 regression >10%; collect DOM/listener counts and all six responsive comparisons.

Cleanup completed: cargo clean reported Removed 1 file, 356B total; web build, .svelte-kit and frozen parent build removed. Capture browser profile removed, saved server PID stopped, no calact5 perf reservation remains. Mac VM was not used. Worktree is clean. Issue stays open.

calact5 final report — INCOMPLETE / NOT READY FOR MERGE. Calendar activation has no accepted improvement. Step 1 did not pass, so I kept product code unchanged. Branch `job/calact5`; base `83dc31b00d63b647e91c316c07f356bbeb0a893e`; head `0a3c064801d04cb9b0ce0e371a640bac8c3057c9`. The required origin/dev fetch and merge returned `Already up to date.` No push or deploy. **Built and files** - `bench/tab-switch.mjs`: trusted-click timestamps captured inside Chromium; selected-Tab readiness; positive, matching Event counts on every sample; linked-date capture positioning; real overflow Events on narrow captures; strict A/A noise rejection; foreground-page activation and visibility assertions; bounded perf-run validation reservations; explicit smoke Photo Index preparation through four concurrent real Files reads; five-minute abortable setup deadline; content-free failure diagnostics; complete visual-matrix capture checkpoints with DOM/listener counts; early-failure provenance and correct requested conditions. - `bench/tab-switch.test.mjs`: regressions for readiness, data validity, A/A noise, nested reservations, bounded fixture reads, stalled requests and foreground activation. - `apps/web/e2e/harness.mjs`: optional appearance request deadline. The default stays 30 seconds; benchmark setup uses 120 seconds outside measurements. **Measurements** Five interleaved parent/parent pairs per cell ran with server and Chromium on the perf VM under perf-run. All 30 samples matched at **135 drawn Events**. No sample was dropped. | Cell | Parent A full-paint p50 / p95 ms | Parent B full-paint p50 / p95 ms | |---|---:|---:| | Mail→Calendar | 1608.6 / 4702.8 | 1780.2 / 2373.2 | | Files→Calendar | 1784.5 / 2081.1 | 1541.1 / 2194.7 | | Calendar→Files | 562.4 / 654.7 | 485.7 / 582.3 | Mail→Calendar p50 noise was **10.67%**, failing the required <5%. Parent/parent desktop light differed by 0 pixels; desktop dark differed by 4 pixels at the Sidebar outer corner. The exact-pixel gate rejected it. The final foreground A/A attempt acquired the VM lock but failed disk qualification at **70.148558 IOPS** (required 100–250), before collecting any samples. No further measurement loop was run. [30-sample A/A data](https://git.kayg.org/attachments/8192cde3-5481-447e-a7c3-1d5b62b4bc72), [pixel coordinates](https://git.kayg.org/attachments/08743b2a-7d22-4367-ac5a-f9c7d13d857b), [final qualification failure](https://git.kayg.org/attachments/3fb528e2-b9fb-42f5-b4d4-f0a05512e3a7), [qualification log](https://git.kayg.org/attachments/f196b291-a5ce-4fff-9469-f60b28e4265f). The failure artifact predates the last metadata-only commit; future early failures include the loaded harness hash and selected warm/cold conditions. The old historical baseline 1464.9 / 1813.5 ms uses a different measurement boundary and dataset window; I did not claim a gain against it. Fresh Week DOM/listener counts were lost when the earlier visual helper aborted; the new checkpoint retains them for future runs. No candidate counts exist. The supplied calprof attribution remains the prior evidence of roughly 15k nodes / 1.4k listeners, not a fresh measurement from this job. **Captures — frozen parent 83dc31b00, macOS platform emulation** These are diagnostic parent captures, not candidate proof. The dated Week capture has 45 drawn Event cards at desktop; tablet and phone show 30 real overflow-list Events. The timed default Week has 135 drawn Events. Those are different linked date windows. | Width | Light | Dark | |---|---|---| | 1440 | [parent](https://git.kayg.org/attachments/951ab7ac-c508-47d7-a402-dff2770069a8) | [parent](https://git.kayg.org/attachments/bcb7c260-1258-466c-8fed-a434aa2abad4) | | 820 | [parent](https://git.kayg.org/attachments/3f1dec7d-2c84-4040-b51c-780129e319e2) | [parent](https://git.kayg.org/attachments/05d1d7db-2c29-443f-9705-5f4f07a964bf) | | 390 | [parent](https://git.kayg.org/attachments/d779c485-4498-4fc5-8fb6-42c42f27be4b) | [parent](https://git.kayg.org/attachments/ec0802a6-0386-45b9-92bd-e9d7a0b8ce23) | [A/A parent B desktop light](https://git.kayg.org/attachments/44ed2056-652a-40bd-a783-758666939fd3), [A/A parent B desktop dark](https://git.kayg.org/attachments/8c741c93-1db9-4585-8795-b43deffbc114). Round 4's missing tablet capture was a parent capture with populated overflow groups, not proof of an empty candidate. The corrected helper opens a real group and preserves the linked date. **Gates — verbatim summaries** `cd apps/web && bun run check; echo exit=$?` ``` perf-lint: PASS; 0 violations; 22128 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 132 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/calact5/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings exit=0 ``` `cd apps/web && bun run test -- --maxWorkers=2` ``` Test Files 318 passed (318) Tests 2316 passed (2316) Start at 18:55:28 Duration 214.87s (environment 29%, transform 23%, import 22%, tests 20%, setup 5%) Environment |component| jsdom was created 131 times · 95.39s total, 32% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` `bun test bench/tab-switch.test.mjs` ``` 24 pass 0 fail 84 expect() calls Ran 24 tests across 1 file. [100.00ms] ``` [Web check summary](https://git.kayg.org/attachments/58cbcb8a-dfc9-47c9-a164-0934116b5741), [full web test output](https://git.kayg.org/attachments/3fa1fa7c-c225-4234-ad76-96cd45cc52fe), [final harness test output](https://git.kayg.org/attachments/2c375cef-5fec-483d-9615-29ea6a745ecc). No Rust source changed; Rust format/clippy/test gates were not needed. No dependency or perf ratchet changed. **Known gaps / work left** - Required <5% A/A calibration remains unproved. Calendar optimization, separate parent/candidate five-pair proof, candidate DOM/listener counts and all six candidate comparisons remain unbuilt. - Foreground activation removes a confound; it is not a proven explanation for the first run's noise. - Fixture thumbnail jobs reported “Thumbnails are temporarily unavailable.” This was recorded on this issue; no Photo Tab was measured. - No product UX gap was closed. The capture gaps closed are real Event visibility and preservation of the linked date. Calendar activation cost remains the product UX gap. **Decisions** All strict Event, noise, pixel and disk qualification gates remain enforced. Setup reads use four concurrent requests and a five-minute deadline. A validation holds one bounded perf-run reservation through setup, paired timings and captures, then releases it during candidate build waits. These are harness decisions; no undecided product design was built. The reviewed next candidate is duration-based metadata DOM pruning. Width-based pruning is unsafe because higher-specificity LOD/reveal selectors can restore rows hidden by compact/cramped rules; the draft was never applied. For follow-up / merge round: rerun the strict five-pair A/A on the perf VM with the foreground fix and qualified disk; then implement one candidate, run `bun run check`, `bun run test -- --maxWorkers=2`, `bun test bench/tab-switch.test.mjs`, and measure five interleaved pairs per cell with matching Event counts. Keep it only for ≥10% Mail→Calendar p50 improvement and no cell p95 regression >10%; collect DOM/listener counts and all six responsive comparisons. Cleanup completed: cargo clean reported `Removed 1 file, 356B total`; web build, .svelte-kit and frozen parent build removed. Capture browser profile removed, saved server PID stopped, no calact5 perf reservation remains. Mac VM was not used. Worktree is clean. Issue stays open.
Author
Owner

Starting follow-up on job/calact6, base 0a3c064801d04cb9b0ce0e371a640bac8c3057c9 (calact5 head). I read CLAUDE.md, CONTEXT.md, DESIGN §59, the calprof attribution, and the calact5 report. The calact5 report has no accepted product optimization: its A/A noise was 10.67%, and its final retry failed disk qualification at 70.148558 IOPS before samples.

This round will use the updated rule from the job brief: 9 interleaved pairs for parent/parent calibration, then 9 interleaved pairs for each kept candidate, all under perf-run on the perf VM. Candidate acceptance requires Mail→Calendar full-paint p50 improvement ≥ max(10%, 2×N), at least 7/9 paired improvements in the same direction, and no cell p95 regression above max(10%, 2×N). Each run must retain the Event-count, foreground, timestamp and disk qualification checks. I will report Week node and listener counts for both builds and attach only loaded responsive captures.

Starting follow-up on `job/calact6`, base `0a3c064801d04cb9b0ce0e371a640bac8c3057c9` (calact5 head). I read CLAUDE.md, CONTEXT.md, DESIGN §59, the calprof attribution, and the calact5 report. The calact5 report has no accepted product optimization: its A/A noise was 10.67%, and its final retry failed disk qualification at 70.148558 IOPS before samples. This round will use the updated rule from the job brief: 9 interleaved pairs for parent/parent calibration, then 9 interleaved pairs for each kept candidate, all under perf-run on the perf VM. Candidate acceptance requires Mail→Calendar full-paint p50 improvement ≥ max(10%, 2×N), at least 7/9 paired improvements in the same direction, and no cell p95 regression above max(10%, 2×N). Each run must retain the Event-count, foreground, timestamp and disk qualification checks. I will report Week node and listener counts for both builds and attach only loaded responsive captures.
Author
Owner

The nine-pair measurement rule is implemented in the profile before any timing run. A/A now reports N as the median absolute paired Mail→Calendar full-paint difference; it does not use calact5's superseded 5% cutoff. Candidate acceptance records max(10%, 2×N), median paired gain, directional count, and each cell's p95 change. The existing per-sample Event, foreground, browser timestamp, and disk qualification gates remain enabled.

Regression result after this change:

24 pass
0 fail
94 expect() calls
Ran 24 tests across 1 file. [270.00ms]
The nine-pair measurement rule is implemented in the profile before any timing run. A/A now reports N as the median absolute paired Mail→Calendar full-paint difference; it does not use calact5's superseded 5% cutoff. Candidate acceptance records `max(10%, 2×N)`, median paired gain, directional count, and each cell's p95 change. The existing per-sample Event, foreground, browser timestamp, and disk qualification gates remain enabled. Regression result after this change: ```text 24 pass 0 fail 94 expect() calls Ran 24 tests across 1 file. [270.00ms] ```
Author
Owner

A/A setup attempt artifacts/tab-switch-1557/aa9.log ended before any timing sample. It failed because startHttpsFront() calls mkdtempSync() under target/tmp, and this clean worktree did not yet contain that directory. perf-run status returned free after cleanup. The ignored path matches the job's temp-output contract; I am adding directory creation to the profile setup and will rerun calibration.

A/A setup attempt `artifacts/tab-switch-1557/aa9.log` ended before any timing sample. It failed because `startHttpsFront()` calls `mkdtempSync()` under `target/tmp`, and this clean worktree did not yet contain that directory. `perf-run status` returned `free` after cleanup. The ignored path matches the job's temp-output contract; I am adding directory creation to the profile setup and will rerun calibration.
Author
Owner

A/A retry #1 stopped at HDD qualification before a timing sample. The live qualified read rate was 68.820786 IOPS; the required range is 100–250 IOPS. The harness retained the redacted server diagnostics, and perf-run status is free after cleanup. Per the posted rule, the next qualification attempt will wait at least 15 minutes. No A/A N or product timing is claimed from this run.

A/A retry #1 stopped at HDD qualification before a timing sample. The live qualified read rate was 68.820786 IOPS; the required range is 100–250 IOPS. The harness retained the redacted server diagnostics, and `perf-run status` is free after cleanup. Per the posted rule, the next qualification attempt will wait at least 15 minutes. No A/A N or product timing is claimed from this run.
Author
Owner

Retry #2 was stopped by the disk gate before timing: QD1 reported 71.376257 IOPS, below the required 100–250 IOPS range. The perf-run lock was released. I also found that the early-failure report discarded the load average captured inside the lock. Commit e18896f37 now records load, io.max, IOPS, and qualification state before validating the probe; the focused harness test passes (24 pass, 0 fail, 97 expect calls). I will wait at least 15 minutes from the failed probe before the next attempt.

Retry #2 was stopped by the disk gate before timing: QD1 reported 71.376257 IOPS, below the required 100–250 IOPS range. The perf-run lock was released. I also found that the early-failure report discarded the load average captured inside the lock. Commit `e18896f37` now records load, io.max, IOPS, and qualification state before validating the probe; the focused harness test passes (24 pass, 0 fail, 97 expect calls). I will wait at least 15 minutes from the failed probe before the next attempt.
Author
Owner

A follow-up review found that the process-level failure handler could not see main()'s local qualification array. Commit 18d9d34de moves the accumulator to module scope, so the new incomplete-report path can persist the locked load and failed IOPS measurement. bun test bench/tab-switch.test.mjs: 24 pass, 0 fail, 97 expect calls.

A follow-up review found that the process-level failure handler could not see `main()`'s local qualification array. Commit `18d9d34de` moves the accumulator to module scope, so the new incomplete-report path can persist the locked load and failed IOPS measurement. `bun test bench/tab-switch.test.mjs`: 24 pass, 0 fail, 97 expect calls.
Author
Owner

A/A attempt 3 reached HDD qualification, but QD1 was just below the lower bound at 99.386748 IOPS (required 100–250). The locked load captured in the saved JSON was [1.93, 0.55, 0.30]; fio reported p50 8.02816 ms and p99 39.583744 ms. It stopped before fixture setup, so raw timing samples remain empty. The perf-run lock is free. The failure report now retains qualification with load, io.max, IOPS and qualified: false; the original log and JSON are artifacts/tab-switch-1557/aa9-attempt3.{log,json}. I will wait at least 15 minutes before the next attempt. The server tail also recorded a native media-sandbox launch error during startup; the profile stopped at HDD qualification before any route was measured.

A/A attempt 3 reached HDD qualification, but QD1 was just below the lower bound at 99.386748 IOPS (required 100–250). The locked load captured in the saved JSON was [1.93, 0.55, 0.30]; fio reported p50 8.02816 ms and p99 39.583744 ms. It stopped before fixture setup, so raw timing samples remain empty. The perf-run lock is free. The failure report now retains `qualification` with load, io.max, IOPS and `qualified: false`; the original log and JSON are `artifacts/tab-switch-1557/aa9-attempt3.{log,json}`. I will wait at least 15 minutes before the next attempt. The server tail also recorded a native media-sandbox launch error during startup; the profile stopped at HDD qualification before any route was measured.
Author
Owner

A/A attempt 4 passed HDD qualification (QD1 110.718571 IOPS; QD16 200.611946 IOPS) with locked loads [1.95, 0.60, 0.31] and [1.80, 0.63, 0.33]. The default large fixture then stayed at 0/50,000 indexed Photos for 55 minutes. While it waited, the server logged 1.0–1.14 s WAL checkpoint statements and 2.09–3.01 s DB pool acquisitions (SLOW-only). I stopped the benchmark-owned server at 55 minutes, before the 60-minute perf-run cap; this produced the expected final HTTP 502 during teardown, not a measured route response. The incomplete JSON preserves qualification and has zero timing samples. The lock is free, the owned server is stopped, and no tab-switch browser profile remains under /var/tmp. I will try the existing --smoke fixture next: it keeps the 30 recurring Calendar Events used by this hot path and avoids the unrelated 50k Photo/100k File index that stalled setup.

A/A attempt 4 passed HDD qualification (QD1 110.718571 IOPS; QD16 200.611946 IOPS) with locked loads [1.95, 0.60, 0.31] and [1.80, 0.63, 0.33]. The default large fixture then stayed at 0/50,000 indexed Photos for 55 minutes. While it waited, the server logged 1.0–1.14 s WAL checkpoint statements and 2.09–3.01 s DB pool acquisitions (SLOW-only). I stopped the benchmark-owned server at 55 minutes, before the 60-minute perf-run cap; this produced the expected final HTTP 502 during teardown, not a measured route response. The incomplete JSON preserves qualification and has zero timing samples. The lock is free, the owned server is stopped, and no tab-switch browser profile remains under /var/tmp. I will try the existing `--smoke` fixture next: it keeps the 30 recurring Calendar Events used by this hot path and avoids the unrelated 50k Photo/100k File index that stalled setup.
Author
Owner

The first --smoke A/A attempt also failed before timing: QD1 was 71.561834 IOPS (below the 100 IOPS floor), with locked load [3.05, 4.96, 5.84], p50 12.386304 ms and p99 47.972352 ms. The incomplete report preserved this probe and zero raw timing samples. perf-run is free. I will wait at least 15 minutes from this failure before the smoke-calibration retry.

The first `--smoke` A/A attempt also failed before timing: QD1 was 71.561834 IOPS (below the 100 IOPS floor), with locked load [3.05, 4.96, 5.84], p50 12.386304 ms and p99 47.972352 ms. The incomplete report preserved this probe and zero raw timing samples. `perf-run` is free. I will wait at least 15 minutes from this failure before the smoke-calibration retry.
Author
Owner

The second smoke A/A attempt failed the strict QD1 HDD qualification before fixture timing. Evidence from aa9-smoke.json: QD1 70.32967 IOPS (p50 12.386304 ms, p99 45.350912 ms), locked load average 2.93 / 0.98 / 2.21, io.max 7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200, qualified: false, and 0 raw samples. The perf-run reservation is free after cleanup. I will wait 15 minutes before another attempt; no timing result is inferred from this run.

The second smoke A/A attempt failed the strict QD1 HDD qualification before fixture timing. Evidence from `aa9-smoke.json`: QD1 70.32967 IOPS (p50 12.386304 ms, p99 45.350912 ms), locked load average 2.93 / 0.98 / 2.21, io.max `7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200`, `qualified: false`, and 0 raw samples. The perf-run reservation is free after cleanup. I will wait 15 minutes before another attempt; no timing result is inferred from this run.
Author
Owner

Merge finding for #1557: origin/dev contains 4aa828ed2, which reverted the earlier cached ModeHeader scroll-offset optimization because it depended on cached viewport/scroller positions. The #1557 candidate keeps the current live offset() logic and defers only the PageChrome effect's measurement to the existing animation-frame scheduler. This preserves the live position read and the revert's stale-offset correction while removing the synchronous route-update read identified by calprof.

Merge finding for #1557: `origin/dev` contains 4aa828ed2, which reverted the earlier cached ModeHeader scroll-offset optimization because it depended on cached viewport/scroller positions. The #1557 candidate keeps the current live `offset()` logic and defers only the PageChrome effect's measurement to the existing animation-frame scheduler. This preserves the live position read and the revert's stale-offset correction while removing the synchronous route-update read identified by calprof.
Author
Owner

The third smoke A/A attempt failed strict QD1 qualification before fixture timing. aa9-smoke-retry3.json records QD1 82.084083 IOPS (p50 10.027008 ms, p99 43.25376 ms), locked load average 1.80 / 0.55 / 0.92, io.max 7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200, qualified: false, and 0 raw samples. The perf-run reservation is free after cleanup. I will wait 15 minutes before the next retry; no timing result is inferred from this run.

The third smoke A/A attempt failed strict QD1 qualification before fixture timing. `aa9-smoke-retry3.json` records QD1 82.084083 IOPS (p50 10.027008 ms, p99 43.25376 ms), locked load average 1.80 / 0.55 / 0.92, io.max `7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200`, `qualified: false`, and 0 raw samples. The perf-run reservation is free after cleanup. I will wait 15 minutes before the next retry; no timing result is inferred from this run.
Author
Owner

The final retry passed strict HDD qualification (QD1 103.797468 IOPS, p50 8.02816 ms, p99 36.438016; QD16 200.531915 IOPS, p50 94.896128 ms, p99 122.159104). It then spent about eight minutes waiting for the smoke Home's 100 Photos to index, although the profile selected only Mail, Files and Calendar. After the fixture reached 100/100, I stopped only the verified #1557 server to end setup and release the reservation; the resulting target Tab did not mount/HTTP 502 is controlled teardown and produced 0 timing samples. The fixture report confirms 100 Photos, 100 Files and 10,980 seeded Journal entries. I will change the smoke seeder to omit Photos when Photos is not selected, add a test, and rerun the strict A/A. This keeps the Calendar recurrence and measured Files data unchanged.

The final retry passed strict HDD qualification (QD1 103.797468 IOPS, p50 8.02816 ms, p99 36.438016; QD16 200.531915 IOPS, p50 94.896128 ms, p99 122.159104). It then spent about eight minutes waiting for the smoke Home's 100 Photos to index, although the profile selected only Mail, Files and Calendar. After the fixture reached 100/100, I stopped only the verified #1557 server to end setup and release the reservation; the resulting `target Tab did not mount`/HTTP 502 is controlled teardown and produced 0 timing samples. The fixture report confirms 100 Photos, 100 Files and 10,980 seeded Journal entries. I will change the smoke seeder to omit Photos when Photos is not selected, add a test, and rerun the strict A/A. This keeps the Calendar recurrence and measured Files data unchanged.
Author
Owner

The no-Photos A/A retry failed strict QD1 qualification before fixture setup. aa9-smoke-no-photos.json records QD1 70.848315 IOPS (p50 12.517376 ms, p99 42.205184 ms), locked load average 4.86 / 6.04 / 4.33, io.max 7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200, qualified: false, and 0 raw samples. The reservation was released; the next startup job then acquired it. I will wait the full cooldown and make one last qualification attempt. No timing result is inferred.

The no-Photos A/A retry failed strict QD1 qualification before fixture setup. `aa9-smoke-no-photos.json` records QD1 70.848315 IOPS (p50 12.517376 ms, p99 42.205184 ms), locked load average 4.86 / 6.04 / 4.33, io.max `7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200`, `qualified: false`, and 0 raw samples. The reservation was released; the next startup job then acquired it. I will wait the full cooldown and make one last qualification attempt. No timing result is inferred.
Author
Owner

The final A/A attempt failed strict QD1 qualification before fixture setup. aa9-smoke-retry5.json records QD1 65.657912 IOPS (p50 13.041664 ms, p99 55.836672 ms), locked load average 2.06 / 0.84 / 1.80, io.max 7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200, qualified: false, and 0 raw samples. The perf-run reservation is free after cleanup. This failure repeats the QD1 issue even with Photos omitted. I am ending measurement retries within the job timebox; there is no A/A noise value and no A/B acceptance claim.

The final A/A attempt failed strict QD1 qualification before fixture setup. `aa9-smoke-retry5.json` records QD1 65.657912 IOPS (p50 13.041664 ms, p99 55.836672 ms), locked load average 2.06 / 0.84 / 1.80, io.max `7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200`, `qualified: false`, and 0 raw samples. The perf-run reservation is free after cleanup. This failure repeats the QD1 issue even with Photos omitted. I am ending measurement retries within the job timebox; there is no A/A noise value and no A/B acceptance claim.
Author
Owner

#1557 report

Branch: job/calact6, based on calact5 0a3c06480. I merged origin/dev once before the final gates and resolved the benchmark conflicts with both branches' profile behavior intact. Final head: 2f07739f13d79be5f592f2b06f8c17c2e11c4ead.

Built

  • ModeHeader.svelte now coalesces its PageChrome route measurement into the existing animation-frame scheduler. The scroller/window offset choice and live read remain unchanged; the route effect no longer reads geometry during the Svelte flush.
  • TimeGrid.svelte keeps route-restored day focus without a programmatic focus ring. Keyboard navigation still uses the shared focus-visible behavior. Its test checks the focus options.
  • The tab-switch harness keeps the nine-pair A/A/A/B validity gates, saves HDD qualification evidence, and skips Photos seeding/indexing when Photos is not a selected Tab. The visual-only local path captures Mac-platform-emulated pointer and shortcut screenshots without treating local timings as perf evidence.
  • Exact perf pins were refreshed for the edited ModeHeader.svelte and TimeGrid.svelte. contracts/perf/ratchet.json did not change.

Performance evidence

The change adds O(1) scheduling work: at most one queued animation frame per route/scroll burst, followed by the existing offset read. It does not change Calendar data work, DOM size, or event handlers.

No valid A/A/A/B timing result was collected. The locked perf VM repeatedly failed strict QD1 qualification, and the one qualifying attempt stalled while indexing the large Photos fixture before timing began. Later smoke rounds still failed QD1. Reports contain no timing samples, so there is no measured p50/p95 improvement claim. The harness now avoids indexing Photos for this non-Photos profile, but I stopped perf retries after the repeated qualification failures.

A local screenshot-only capture recorded, for 1440 px light, Week DOM 5,119 nodes for both builds; Calendar route DOM 5,199 for both; page listener totals 1,076 parent and 1,072 candidate. Mail was 435 listeners in both captures, giving a Calendar listener delta of 641 and 637. These are single capture counts, not performance acceptance evidence.

Screenshots

The screenshots are from local production builds and a real local server. Chromium reported the macOS platform. Candidate screenshots cover 1440/820/390 px in light and dark, plus post-click and post-⌘-shortcut captures. The candidate interaction captures each showed 15 visible fixture Events and zero :focus-visible elements. Narrow Week captures opened the real overflow list, showing 30 fixture Events.

Parent screenshots are attached for 1440 light, 820/390 light and dark, and both interactions. I excluded parent 1440 dark because it shows the Calendar error message Calendar could not be loaded. Try again. The exact-pixel comparison therefore failed; 1440 light also crossed a minute boundary in the current-time line. The complete before/after visual matrix is a known gap.

Parent: 1440 light, 820 light, 820 dark, 390 light, 390 dark, pointer, ⌘ shortcut.

Candidate: 1440 light, 1440 dark, 820 light, 820 dark, 390 light, 390 dark, pointer, ⌘ shortcut.

UX gaps closed / left

Closed: programmatic focus after Calendar route activation no longer leaves a ring; pointer and shortcut captures have no visible focus ring; every candidate capture contains real Calendar Events.

Left: the parent 1440 px dark screenshot is unavailable as a clean capture, so the exact before/after pixel matrix is incomplete. No touch-specific capture was added because this change does not change touch interaction.

Decisions

  • I chose the smallest §59 change supported by the attribution: defer the existing ModeHeader geometry read to the next animation frame. I did not add a User-scoped view snapshot or keep a hidden Calendar route mounted.
  • I kept the live offset() read instead of caching it. The earlier cached-offset change was reverted upstream, and the deferral preserves its current route and scroll behavior.
  • I suppressed focus rings only for the code-driven day restore, consistent with the owner focus rule; keyboard movement continues to use the shared ring behavior.
  • The visual-only local fallback is for screenshots only. It is not used to claim timing results.

Gates (output excerpts, verbatim)

cd apps/web && bun run check:

perf-lint: PASS; 0 violations; 20373 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 0 warnings
exit=0

cd apps/web && bun run test -- --maxWorkers=2:

Test Files  321 passed (321)
      Tests  2340 passed (2340)
   Start at  07:44:54
   Duration  235.55s (environment 29%, transform 24%, import 21%, tests 20%, setup 5%)

bun test bench/tab-switch.test.mjs:

 49 pass
 0 fail
Ran 49 tests across 1 file. [184.00ms]

Focused ModeHeader and TimeGrid tests:

 Test Files  2 passed (2)
      Tests  41 passed (41)
   Start at  07:49:02
   Duration  12.51s (transform 63%, environment 18%, tests 9%, setup 4%, import 4%)

cargo clean:

     Removed 1 file, 356B total

No Rust source or API changed, so Rust crate gates and adversarial API probing were not run. Web build output was removed after verification. The working tree is clean.

## #1557 report Branch: `job/calact6`, based on calact5 `0a3c06480`. I merged `origin/dev` once before the final gates and resolved the benchmark conflicts with both branches' profile behavior intact. Final head: `2f07739f13d79be5f592f2b06f8c17c2e11c4ead`. ### Built - `ModeHeader.svelte` now coalesces its PageChrome route measurement into the existing animation-frame scheduler. The scroller/window offset choice and live read remain unchanged; the route effect no longer reads geometry during the Svelte flush. - `TimeGrid.svelte` keeps route-restored day focus without a programmatic focus ring. Keyboard navigation still uses the shared focus-visible behavior. Its test checks the focus options. - The tab-switch harness keeps the nine-pair A/A/A/B validity gates, saves HDD qualification evidence, and skips Photos seeding/indexing when Photos is not a selected Tab. The visual-only local path captures Mac-platform-emulated pointer and shortcut screenshots without treating local timings as perf evidence. - Exact perf pins were refreshed for the edited `ModeHeader.svelte` and `TimeGrid.svelte`. `contracts/perf/ratchet.json` did not change. ### Performance evidence The change adds O(1) scheduling work: at most one queued animation frame per route/scroll burst, followed by the existing offset read. It does not change Calendar data work, DOM size, or event handlers. No valid A/A/A/B timing result was collected. The locked perf VM repeatedly failed strict QD1 qualification, and the one qualifying attempt stalled while indexing the large Photos fixture before timing began. Later smoke rounds still failed QD1. Reports contain no timing samples, so there is no measured p50/p95 improvement claim. The harness now avoids indexing Photos for this non-Photos profile, but I stopped perf retries after the repeated qualification failures. A local screenshot-only capture recorded, for 1440 px light, Week DOM 5,119 nodes for both builds; Calendar route DOM 5,199 for both; page listener totals 1,076 parent and 1,072 candidate. Mail was 435 listeners in both captures, giving a Calendar listener delta of 641 and 637. These are single capture counts, not performance acceptance evidence. ### Screenshots The screenshots are from local production builds and a real local server. Chromium reported the macOS platform. Candidate screenshots cover 1440/820/390 px in light and dark, plus post-click and post-⌘-shortcut captures. The candidate interaction captures each showed 15 visible fixture Events and zero `:focus-visible` elements. Narrow Week captures opened the real overflow list, showing 30 fixture Events. Parent screenshots are attached for 1440 light, 820/390 light and dark, and both interactions. I excluded parent 1440 dark because it shows the Calendar error message `Calendar could not be loaded. Try again.` The exact-pixel comparison therefore failed; 1440 light also crossed a minute boundary in the current-time line. The complete before/after visual matrix is a known gap. Parent: [1440 light](https://git.kayg.org/attachments/75da4632-c005-41c8-af21-26d0b71310c1), [820 light](https://git.kayg.org/attachments/eb5daedc-2ec4-4abc-a3c2-ad3dc6a93f20), [820 dark](https://git.kayg.org/attachments/6c02ca87-1235-4136-9dae-cd99e75362e6), [390 light](https://git.kayg.org/attachments/df156862-d054-47bc-b6e6-1b51ce390530), [390 dark](https://git.kayg.org/attachments/f0180958-a42b-4f0e-93db-d9e63dc28668), [pointer](https://git.kayg.org/attachments/ad382511-e55f-4c42-af8f-c962a45dd481), [⌘ shortcut](https://git.kayg.org/attachments/6b58f1ed-2503-4852-a836-e50f93dc3b69). Candidate: [1440 light](https://git.kayg.org/attachments/737cf72e-6015-438e-b108-ff775ce3d89f), [1440 dark](https://git.kayg.org/attachments/4553d0cd-22f9-4d36-b4b9-379d5648491d), [820 light](https://git.kayg.org/attachments/553ea2ea-5c16-4e75-86b6-2c4ef8037261), [820 dark](https://git.kayg.org/attachments/367034e8-84da-4673-a9a3-af231fddd532), [390 light](https://git.kayg.org/attachments/e44e40a7-bc9b-4d7f-a6d0-9d112cb2a239), [390 dark](https://git.kayg.org/attachments/006db8fa-1cfb-4bb4-b7fb-21fa9880bcfb), [pointer](https://git.kayg.org/attachments/1e0b9c7c-a1b1-46ad-a10e-d41620d75c5f), [⌘ shortcut](https://git.kayg.org/attachments/8bbf77ec-b319-4301-a4d7-338a877b1941). ### UX gaps closed / left Closed: programmatic focus after Calendar route activation no longer leaves a ring; pointer and shortcut captures have no visible focus ring; every candidate capture contains real Calendar Events. Left: the parent 1440 px dark screenshot is unavailable as a clean capture, so the exact before/after pixel matrix is incomplete. No touch-specific capture was added because this change does not change touch interaction. ### Decisions - I chose the smallest §59 change supported by the attribution: defer the existing ModeHeader geometry read to the next animation frame. I did not add a User-scoped view snapshot or keep a hidden Calendar route mounted. - I kept the live `offset()` read instead of caching it. The earlier cached-offset change was reverted upstream, and the deferral preserves its current route and scroll behavior. - I suppressed focus rings only for the code-driven day restore, consistent with the owner focus rule; keyboard movement continues to use the shared ring behavior. - The visual-only local fallback is for screenshots only. It is not used to claim timing results. ### Gates (output excerpts, verbatim) `cd apps/web && bun run check`: ```text perf-lint: PASS; 0 violations; 20373 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 0 warnings exit=0 ``` `cd apps/web && bun run test -- --maxWorkers=2`: ```text Test Files 321 passed (321) Tests 2340 passed (2340) Start at 07:44:54 Duration 235.55s (environment 29%, transform 24%, import 21%, tests 20%, setup 5%) ``` `bun test bench/tab-switch.test.mjs`: ```text 49 pass 0 fail Ran 49 tests across 1 file. [184.00ms] ``` Focused ModeHeader and TimeGrid tests: ```text Test Files 2 passed (2) Tests 41 passed (41) Start at 07:49:02 Duration 12.51s (transform 63%, environment 18%, tests 9%, setup 4%, import 4%) ``` `cargo clean`: ```text Removed 1 file, 356B total ``` No Rust source or API changed, so Rust crate gates and adversarial API probing were not run. Web build output was removed after verification. The working tree is clean.
Author
Owner

Starting #1557 work on branch job/calact6.
Base SHA: 8e84096297 (origin/dev merge-base).
Starting HEAD: 2f07739f13.
I am checking the QD1 disk qualification and base-device latency before the interleaved measurements.

Starting #1557 work on branch job/calact6. Base SHA: 8e840962979acd2bdf58141888618ab5560f728e (origin/dev merge-base). Starting HEAD: 2f07739f13d79be5f592f2b06f8c17c2e11c4ead. I am checking the QD1 disk qualification and base-device latency before the interleaved measurements.
Author
Owner

#1557 disk qualification finding: under perf-run calternal base1557 5, fio 3.39 read the undelayed backing file /mnt/hdd/calternal-hdd-emu.img on /dev/sdb with QD1 direct random reads. The lock was held; the captured load average was 1.57 / 1.75 / 1.49. The three runs measured p50 9.371648 / 0.207872 / 0.066048 ms and 104.891377 / 226.718219 / 395.320936 IOPS. The first p50 exceeds the required 6 ms substrate ceiling and the p50 spread is about 142x, so the perf VM fails the stability precondition. I changed the qualification bounds to QD1 p50 8..14 ms and IOPS 1000/14..1000/8 (71.43..125), with the QD16 250 IOPS cap retained. This does not loosen the base-device precondition. I am stopping before A/A or A/B timing; no comparison samples are valid from this host state.

#1557 disk qualification finding: under `perf-run calternal base1557 5`, fio 3.39 read the undelayed backing file `/mnt/hdd/calternal-hdd-emu.img` on `/dev/sdb` with QD1 direct random reads. The lock was held; the captured load average was 1.57 / 1.75 / 1.49. The three runs measured p50 9.371648 / 0.207872 / 0.066048 ms and 104.891377 / 226.718219 / 395.320936 IOPS. The first p50 exceeds the required 6 ms substrate ceiling and the p50 spread is about 142x, so the perf VM fails the stability precondition. I changed the qualification bounds to QD1 p50 8..14 ms and IOPS 1000/14..1000/8 (71.43..125), with the QD16 250 IOPS cap retained. This does not loosen the base-device precondition. I am stopping before A/A or A/B timing; no comparison samples are valid from this host state.
Author
Owner

#1557 report

Branch: job/calact6
Head: fbb180a134a73e23e53b5a624981b742921418d0
git fetch origin && git merge origin/dev: already up to date.

Built

  • Recalibrated the HDD qualification from the 8 ms dm-delay and the allowed 6 ms base-device latency. QD1 now requires p50 8..14 ms and IOPS 1000/14..1000/8 (71.43..125). QD16 retains the 250 IOPS cap for riops=200 with 25% tolerance.
  • The strict qualification path now uses the same tested helper as its diagnostics. Boundary tests cover the QD1 latency and IOPS bounds and the QD16 cap.
  • Files: bench/tab-switch.mjs, bench/tab-switch.test.mjs.

Perf VM evidence

The base-device probe ran under perf-run calternal base1557 5 with fio 3.39, direct QD1 random reads against the undelayed /mnt/hdd/calternal-hdd-emu.img file on /dev/sdb. The load average captured inside the reservation was 1.57 1.75 1.49.

Run IOPS p50 latency p99 latency
1 104.891377 9.371648 ms 23.986176 ms
2 226.718219 0.207872 ms 20.840448 ms
3 395.320936 0.066048 ms 20.054016 ms

The first p50 exceeds the 6 ms substrate ceiling. The p50 spread is about 142×, above the 2× stability limit. The perf VM failed the calibration precondition, so I stopped before the delayed-device qualification and A/A/A/B timing. No tab-switch sample from this host state is valid, and I did not loosen the 6 ms limit.

The issue baseline remains 1,464.9 ms p50 / 1,813.5 ms p95. A/A noise N, parent-versus-candidate p50/p95, Week DOM nodes and listener counts remain unavailable. I did not attempt the next attribution item.

UX gaps closed / left

  • Closed: the benchmark regression now tests the delay-derived QD1 qualification bounds.
  • Left: candidate acceptance and its Week DOM/listener counts need the required A/A and A/B runs on a stable perf VM. No UI files changed in this round, so there are no new visual captures.

Decisions

  • I used three 15-second fio reads of the undelayed backing file, which the job brief allows, to check the substrate before running the delayed qualification.
  • I interpreted the QD16 requirement as a positive finite probe plus the stated 250 IOPS ceiling; I removed the old generic 100 IOPS floor because the brief specifies only the deeper-queue cap.

Gates

Verbatim summary lines from bun run check; echo exit=$? (the terminal view truncated perf-lint's large existing debt inventory):

NOTE Bun skipped 1 lock package(s) not installed in this production environment
PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 20373 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 0 warnings
exit=0

Verbatim final summary from bun run test -- --maxWorkers=2:

 Test Files  321 passed (321)
      Tests  2340 passed (2340)
   Start at  08:02:09
   Duration  287.74s (environment 28%, import 25%, transform 23%, tests 19%, setup 5%)

Verbatim result from bun test bench/tab-switch.test.mjs:

 49 pass
 0 fail
Ran 49 tests across 1 file. [104.00ms]

cargo clean:

     Removed 1 file, 356B total

Web .svelte-kit and build output were removed. No Rust source changed, so no Rust crate gates were run. No pushes, deploys or issue closure occurred.

## #1557 report Branch: `job/calact6` Head: `fbb180a134a73e23e53b5a624981b742921418d0` `git fetch origin && git merge origin/dev`: already up to date. ### Built - Recalibrated the HDD qualification from the 8 ms dm-delay and the allowed 6 ms base-device latency. QD1 now requires p50 8..14 ms and IOPS 1000/14..1000/8 (71.43..125). QD16 retains the 250 IOPS cap for `riops=200` with 25% tolerance. - The strict qualification path now uses the same tested helper as its diagnostics. Boundary tests cover the QD1 latency and IOPS bounds and the QD16 cap. - Files: `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`. ### Perf VM evidence The base-device probe ran under `perf-run calternal base1557 5` with fio 3.39, direct QD1 random reads against the undelayed `/mnt/hdd/calternal-hdd-emu.img` file on `/dev/sdb`. The load average captured inside the reservation was `1.57 1.75 1.49`. | Run | IOPS | p50 latency | p99 latency | |---|---:|---:|---:| | 1 | 104.891377 | 9.371648 ms | 23.986176 ms | | 2 | 226.718219 | 0.207872 ms | 20.840448 ms | | 3 | 395.320936 | 0.066048 ms | 20.054016 ms | The first p50 exceeds the 6 ms substrate ceiling. The p50 spread is about 142×, above the 2× stability limit. The perf VM failed the calibration precondition, so I stopped before the delayed-device qualification and A/A/A/B timing. No tab-switch sample from this host state is valid, and I did not loosen the 6 ms limit. The issue baseline remains 1,464.9 ms p50 / 1,813.5 ms p95. A/A noise N, parent-versus-candidate p50/p95, Week DOM nodes and listener counts remain unavailable. I did not attempt the next attribution item. ### UX gaps closed / left - Closed: the benchmark regression now tests the delay-derived QD1 qualification bounds. - Left: candidate acceptance and its Week DOM/listener counts need the required A/A and A/B runs on a stable perf VM. No UI files changed in this round, so there are no new visual captures. ### Decisions - I used three 15-second fio reads of the undelayed backing file, which the job brief allows, to check the substrate before running the delayed qualification. - I interpreted the QD16 requirement as a positive finite probe plus the stated 250 IOPS ceiling; I removed the old generic 100 IOPS floor because the brief specifies only the deeper-queue cap. ### Gates Verbatim summary lines from `bun run check; echo exit=$?` (the terminal view truncated perf-lint's large existing debt inventory): ```text NOTE Bun skipped 1 lock package(s) not installed in this production environment PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 20373 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 0 warnings exit=0 ``` Verbatim final summary from `bun run test -- --maxWorkers=2`: ```text Test Files 321 passed (321) Tests 2340 passed (2340) Start at 08:02:09 Duration 287.74s (environment 28%, import 25%, transform 23%, tests 19%, setup 5%) ``` Verbatim result from `bun test bench/tab-switch.test.mjs`: ```text 49 pass 0 fail Ran 49 tests across 1 file. [104.00ms] ``` `cargo clean`: ```text Removed 1 file, 356B total ``` Web `.svelte-kit` and `build` output were removed. No Rust source changed, so no Rust crate gates were run. No pushes, deploys or issue closure occurred.
Author
Owner

Starting calact8 on branch job/calact6. Worktree head: fbb180a134. Branch base: origin/dev at 8e8409629; this worktree already contains the branch's intervening commits. I will use the issue's current 9-pair interleaved measurement rule and the delayed-device-only qualification.

Starting calact8 on branch job/calact6. Worktree head: fbb180a134a73e23e53b5a624981b742921418d0. Branch base: origin/dev at 8e8409629; this worktree already contains the branch's intervening commits. I will use the issue's current 9-pair interleaved measurement rule and the delayed-device-only qualification.
Author
Owner

Qualification finding from #1587: ten locked probes of the delayed device measured p50 8.028–8.847 ms (1.10× spread). The regular-file undelayed path remains host-cache-sensitive even after , so this run now treats it as informational and gates only ten distinct-seed delayed-device QD1 probes: every p50 8–14 ms, with max/min < 1.3×. The perf VM was free when checked (load 0.24 / 0.71 / 1.38); I’m proceeding with the locked comparison.

Qualification finding from #1587: ten locked probes of the delayed device measured p50 8.028–8.847 ms (1.10× spread). The regular-file undelayed path remains host-cache-sensitive even after , so this run now treats it as informational and gates only ten distinct-seed delayed-device QD1 probes: every p50 8–14 ms, with max/min < 1.3×. The perf VM was free when checked (load 0.24 / 0.71 / 1.38); I’m proceeding with the locked comparison.
Author
Owner

Correction to my previous comment: the qualification finding is from #1587. Ten locked probes of the delayed /dev/mapper/calternal-hdd-emu device measured p50 8.028–8.847 ms (1.10× spread). The regular-file undelayed path remains host-cache-sensitive even after perf-run --quiesce, so this run treats it as informational and gates only ten distinct-seed delayed-device QD1 probes: every p50 must be 8–14 ms, with max/min < 1.3×. The perf VM was free when checked (load 0.24 / 0.71 / 1.38); I’m proceeding with the locked comparison.

Correction to my previous comment: the qualification finding is from #1587. Ten locked probes of the delayed `/dev/mapper/calternal-hdd-emu` device measured p50 8.028–8.847 ms (1.10× spread). The regular-file undelayed path remains host-cache-sensitive even after `perf-run --quiesce`, so this run treats it as informational and gates only ten distinct-seed delayed-device QD1 probes: every p50 must be 8–14 ms, with max/min < 1.3×. The perf VM was free when checked (load 0.24 / 0.71 / 1.38); I’m proceeding with the locked comparison.
Author
Owner

Harness finding: the second attempt reached remote server startup but stopped before qualification. Its redacted server log ended with Error: an outbound network setting contains an invalid CIDR; no timing sample was recorded, and perf-run status returned free. Inspection found that calendarFeedHost and calendarFeedPort were declared but never assigned, and the existing startCalendarFeedServer and addCalendarSubscriptionFixtures helpers were never called. I am wiring the read-only feed fixture before server startup and adding its subscriptions during fixture setup, so the server allowlist and real Calendar Events use the same selected address.

Harness finding: the second attempt reached remote server startup but stopped before qualification. Its redacted server log ended with `Error: an outbound network setting contains an invalid CIDR`; no timing sample was recorded, and `perf-run status` returned `free`. Inspection found that `calendarFeedHost` and `calendarFeedPort` were declared but never assigned, and the existing `startCalendarFeedServer` and `addCalendarSubscriptionFixtures` helpers were never called. I am wiring the read-only feed fixture before server startup and adding its subscriptions during fixture setup, so the server allowlist and real Calendar Events use the same selected address.
Author
Owner

The corrected #1557 run reached delayed-device qualification under perf-run calternal calact6-1557 with --quiesce. It recorded all ten QD1 p50 values: 11.993, 11.600, 9.765, 8.978, 8.585, 8.225, 8.028, 8.094, 8.094, and 8.028 ms. Every value is within 8–14 ms, but the run spread is 1.494×, so it fails the required <1.3× stability gate. Load average recorded inside the lock declined from 2.16 to 1.05. The run stopped before collecting A/A or candidate timing samples; I will not report it as a valid performance comparison.

The attempt also exposed a fixture setup issue: duplicate fixtureYears and tabSeed declarations in the setup try block shadowed the CLI values and put them in the temporal dead zone when the feed listener started. I removed those declarations so --fixture-years and --files and restored snapshot dimensions remain in force. Commits: 1dad8e28e (initialize the private Calendar feed listener and real subscription fixtures) and a06e50fcb (preserve selected fixture dimensions). Focused suite: 53 pass, 0 fail.

The corrected #1557 run reached delayed-device qualification under `perf-run calternal calact6-1557` with `--quiesce`. It recorded all ten QD1 p50 values: 11.993, 11.600, 9.765, 8.978, 8.585, 8.225, 8.028, 8.094, 8.094, and 8.028 ms. Every value is within 8–14 ms, but the run spread is 1.494×, so it fails the required <1.3× stability gate. Load average recorded inside the lock declined from 2.16 to 1.05. The run stopped before collecting A/A or candidate timing samples; I will not report it as a valid performance comparison. The attempt also exposed a fixture setup issue: duplicate `fixtureYears` and `tabSeed` declarations in the setup try block shadowed the CLI values and put them in the temporal dead zone when the feed listener started. I removed those declarations so `--fixture-years` and `--files` and restored snapshot dimensions remain in force. Commits: `1dad8e28e` (initialize the private Calendar feed listener and real subscription fixtures) and `a06e50fcb` (preserve selected fixture dimensions). Focused suite: `53 pass, 0 fail`.
Author
Owner

A visual-only local run reached the Home seed after the two earlier fixes. It then found that addCalDavAccount expects the complete CalDAV fixture, but the caller passed the anchor string and year count. The resulting failure was TypeError: undefined is not an object (evaluating 'firstDay.replaceAll') in startCalDAV. The caller now passes the already-built caldavFixture; the focused suite passes (53 pass, 0 fail). Commit: ae2e3dc3c.

A visual-only local run reached the Home seed after the two earlier fixes. It then found that `addCalDavAccount` expects the complete CalDAV fixture, but the caller passed the anchor string and year count. The resulting failure was `TypeError: undefined is not an object (evaluating 'firstDay.replaceAll')` in `startCalDAV`. The caller now passes the already-built `caldavFixture`; the focused suite passes (53 pass, 0 fail). Commit: `ae2e3dc3c`.
Author
Owner

A subsequent visual setup sent the ten fixture subscription POSTs together. The Calendar server returned HTTP 503 service_unavailable with calendar fetcher is busy; retry shortly. I changed fixture creation to submit feeds through the single active fetcher and retry only that explicit busy response with capped backoff. A focused test covers the retry and confirms other responses are not retried. Commit: 0a522080b; focused suite: 54 pass, 0 fail.

A subsequent visual setup sent the ten fixture subscription POSTs together. The Calendar server returned HTTP 503 `service_unavailable` with `calendar fetcher is busy; retry shortly`. I changed fixture creation to submit feeds through the single active fetcher and retry only that explicit busy response with capped backoff. A focused test covers the retry and confirms other responses are not retried. Commit: `0a522080b`; focused suite: `54 pass, 0 fail`.
Author
Owner

The smoke visual setup now reaches the server probe. The profile requested Calendar, Mail and Files, but the generic probe also called /api/v1/notes/journal/<date>; the inactive Notes route returned 404 and stopped setup before any screenshot. I made adjacent route probes conditional on their Tabs being selected and added focused coverage. Commit: 263a4cc9e; focused suite: 55 pass, 0 fail.

The smoke visual setup now reaches the server probe. The profile requested Calendar, Mail and Files, but the generic probe also called `/api/v1/notes/journal/<date>`; the inactive Notes route returned 404 and stopped setup before any screenshot. I made adjacent route probes conditional on their Tabs being selected and added focused coverage. Commit: `263a4cc9e`; focused suite: `55 pass, 0 fail`.
Author
Owner

After the selected-route probe fix, the local smoke fixture reached /healthz and the Calendar range probe verified 1,203 Event instances in the window (30 recurring CalDAV Events, 1,000 dated Events and 50 subscription Events in the fixture). Snapshot creation then failed because waitForHealthzReady, already called by the cold-restart and snapshot paths, had no implementation. I added the bounded /healthz poll and a test for the migrating-to-ok transition. Commit: 48f9e627c; focused suite: 56 pass, 0 fail.

After the selected-route probe fix, the local smoke fixture reached `/healthz` and the Calendar range probe verified 1,203 Event instances in the window (30 recurring CalDAV Events, 1,000 dated Events and 50 subscription Events in the fixture). Snapshot creation then failed because `waitForHealthzReady`, already called by the cold-restart and snapshot paths, had no implementation. I added the bounded `/healthz` poll and a test for the migrating-to-ok transition. Commit: `48f9e627c`; focused suite: `56 pass, 0 fail`.
Author
Owner

The restored server reported /healthz as {"status":200,"state":"ok","progress":null}. The benchmark's readiness helper expected the older phase:null shape and timed out after 60 seconds despite the server being ready. I updated the helper for the current progress contract, retained the older fixture shape for tests, and added current-contract coverage. Commit: de7915f6c; focused suite: 56 pass, 0 fail.

The restored server reported `/healthz` as `{"status":200,"state":"ok","progress":null}`. The benchmark's readiness helper expected the older `phase:null` shape and timed out after 60 seconds despite the server being ready. I updated the helper for the current `progress` contract, retained the older fixture shape for tests, and added current-contract coverage. Commit: `de7915f6c`; focused suite: `56 pass, 0 fail`.
Author
Owner

Responsive visual evidence

The local visual-only run captured the real parent and candidate production builds with macOS platform emulation. The fixture reported 1,203 Calendar Event instances in the range probe. Captures showed 45 visible Events at 1440 px and 80 at 820/390 px. The pointer-click and mode-shortcut captures both reported zero focus-visible elements. Screenshots cover 390, 820 and 1440 px in light and dark, plus the 1440 px pointer and shortcut captures.

Week DOM nodes / total page JS listeners, before → candidate:

View Week nodes Page listeners
1440 light 3,674 → 3,783 879 → 925
1440 dark 3,674 → 3,783 883 → 923
820 light 472 → 472 684 → 695
820 dark 472 → 472 684 → 696
390 light 472 → 472 673 → 688
390 dark 472 → 472 673 → 689

At 1440 light, the Mail baseline listener counts were 458 before and 460 after, so the Calendar delta was 421 → 465.

The screenshot tool's exact-pixel gate failed. It recorded differences of 16,190 pixels at 1440 light, 16,895 at 1440 dark, 181,524 at 820 light, 24,903 at 820 dark, and 80,898 in the pointer-click capture. The 390 px images were pixel-identical. The mode-shortcut capture differed by 81,110 pixels and still reported no focus-visible element. Please review the attached images for the intended visual effect; I did not treat this as an accepted timing result.

Before

Candidate

## Responsive visual evidence The local visual-only run captured the real parent and candidate production builds with macOS platform emulation. The fixture reported 1,203 Calendar Event instances in the range probe. Captures showed 45 visible Events at 1440 px and 80 at 820/390 px. The pointer-click and mode-shortcut captures both reported zero focus-visible elements. Screenshots cover 390, 820 and 1440 px in light and dark, plus the 1440 px pointer and shortcut captures. Week DOM nodes / total page JS listeners, before → candidate: | View | Week nodes | Page listeners | | --- | ---: | ---: | | 1440 light | 3,674 → 3,783 | 879 → 925 | | 1440 dark | 3,674 → 3,783 | 883 → 923 | | 820 light | 472 → 472 | 684 → 695 | | 820 dark | 472 → 472 | 684 → 696 | | 390 light | 472 → 472 | 673 → 688 | | 390 dark | 472 → 472 | 673 → 689 | At 1440 light, the Mail baseline listener counts were 458 before and 460 after, so the Calendar delta was 421 → 465. The screenshot tool's exact-pixel gate failed. It recorded differences of 16,190 pixels at 1440 light, 16,895 at 1440 dark, 181,524 at 820 light, 24,903 at 820 dark, and 80,898 in the pointer-click capture. The 390 px images were pixel-identical. The mode-shortcut capture differed by 81,110 pixels and still reported no focus-visible element. Please review the attached images for the intended visual effect; I did not treat this as an accepted timing result. **Before** - [1440 light](https://git.kayg.org/attachments/f2d7179d-e5cf-4c65-a5fc-1156b0f49938) · [1440 dark](https://git.kayg.org/attachments/1e75c7f5-95ee-4d0c-83bc-4dcb8114e6bb) - [820 light](https://git.kayg.org/attachments/9939ce52-eb08-4776-b5d8-b68fac7a332c) · [820 dark](https://git.kayg.org/attachments/0205b2c9-9cf2-47df-b563-da4c79441a4b) - [390 light](https://git.kayg.org/attachments/0255db66-65c0-4524-9d2e-87b58a1406dc) · [390 dark](https://git.kayg.org/attachments/9facf3a9-10e1-4e95-884a-1e208b11abd8) - [1440 pointer click](https://git.kayg.org/attachments/ea9b2f14-1039-43b7-aab4-face9a42faed) · [1440 mode shortcut](https://git.kayg.org/attachments/b70618cc-afc2-4f2e-b141-fba543e6c8a4) **Candidate** - [1440 light](https://git.kayg.org/attachments/4dea7581-2312-4f85-b12d-a7a81b702eba) · [1440 dark](https://git.kayg.org/attachments/fc20acb2-69a5-4711-bd52-58aad767cc7f) - [820 light](https://git.kayg.org/attachments/946a7f50-3f29-4aef-a620-eaa1644fd6e0) · [820 dark](https://git.kayg.org/attachments/12d90de5-c96a-47c9-96bb-b360b215dd1d) - [390 light](https://git.kayg.org/attachments/110eec47-f679-4274-8125-db98741ad55a) · [390 dark](https://git.kayg.org/attachments/a6203abf-6f90-4dfd-92b4-12e7a30c3927) - [1440 pointer click](https://git.kayg.org/attachments/612ac2d6-b8f3-465c-95a2-d89280653e86) · [1440 mode shortcut](https://git.kayg.org/attachments/578dba34-849e-48dc-9754-7709103f3283)
Author
Owner

Final report — #1557

Built

  • Kept delayed-device qualification on the delayed device only. The undelayed backing-file result is informational. The run uses perf-run --quiesce and retains all ten QD1 probes.
  • Repaired the Calendar activation fixture setup: initialize the private feed address before server config, pass the selected fixture sizes and CalDAV fixture through setup, retry only the server's explicit single-fetcher busy response, and poll the current /healthz progress contract.
  • Restrict adjacent API probes to the Tabs selected for the profile.
  • No new user controls or product UI were added in this continuation. The branch candidate uses the existing shared ModeHeader and TimeGrid components.

Files changed from origin/dev

apps/web/scripts/design-rules-lib.mjs; apps/web/src/lib/calendar/TimeGrid.svelte.test.ts; apps/web/src/lib/modeHeader.svelte.test.ts; bench/tab-switch.mjs; bench/tab-switch.test.mjs; contracts/perf/adoption-1058.json; contracts/perf/exceptions.json; packages/ui/src/components/ModeHeader.svelte; packages/ui/src/components/calendar/TimeGrid.svelte.

Measurement and visual evidence

The delayed-device qualification recorded p50 values 11.993, 11.600, 9.765, 8.978, 8.585, 8.225, 8.028, 8.094, 8.094 and 8.028 ms. All are within 8–14 ms, but the 1.494× run spread exceeds the required <1.3× gate. Load average inside the lock declined from 2.16 to 1.05. The run stopped before A/A or candidate timing; there is no performance result and no next attribution run.

The visual-only captures contain real Events: the range probe counted 1,203 instances; 45 Events were visible at 1440 px and 80 at 820/390 px. Pointer-click and mode-shortcut captures reported zero focus-visible elements. All 16 before/after PNGs (390/820/1440 px, light/dark, plus pointer and shortcut captures) are attached in the preceding #1557 comment.

Week DOM nodes / total page JS listeners, before → candidate:

View Week nodes Page listeners
1440 light 3,674 → 3,783 879 → 925
1440 dark 3,674 → 3,783 883 → 923
820 light 472 → 472 684 → 695
820 dark 472 → 472 684 → 696
390 light 472 → 472 673 → 688
390 dark 472 → 472 673 → 689

At 1440 light, Mail baseline listeners were 458 before and 460 after; Calendar listener delta was 421 → 465. The exact-pixel screenshot check failed at 1440 light/dark (16,190 / 16,895 pixels), 820 light/dark (181,524 / 24,903), and pointer click (80,898). The mode-shortcut comparison recorded 81,110 changed pixels; the 390 px captures were pixel-identical. The screenshots need the visual reviewer’s assessment.

Gates

bun run check (verbatim result lines):

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 20373 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 0 warnings
exit=0

bun run test -- --maxWorkers=2 Vitest summary:

 Test Files  321 passed (321)
      Tests  2340 passed (2340)
   Start at  10:00:44
   Duration  365.60s (environment 29%, import 24%, transform 23%, tests 19%, setup 6%)

The Vitest run passed. The shell wrapper then printed zsh:1: read-only variable: status because it tried to assign to zsh’s reserved status variable; this was a wrapper error after the test summary.

bun test bench/tab-switch.test.mjs:

 56 pass
 0 fail
Ran 56 tests across 1 file. [171.00ms]

No Rust source changed, so Rust crate gates were not run. cargo clean output: Removed 1 file, 356B total. git fetch origin && git merge origin/dev: Already up to date. The perf VM lock is free.

Commits and head

Head: de7915f6cd721e4250d48a7bcf9b7f3a738b0d1e (job/calact6). The work is committed in atomic steps. No push or deploy was run.

Known gaps

  • No valid A/A or parent-versus-candidate timing result because the required disk stability gate failed.
  • The exact-pixel visual gate failed at desktop and tablet sizes; owner visual review is needed.
  • No next attribution item was measured because the acceptance criteria were not met.

Decisions not covered by DESIGN.md

  • The Calendar server has one active subscription fetcher. Fixture setup retries only its explicit busy 503, with 100 ms exponential backoff capped at 1 second and 30 retries.
  • Inactive Plugin routes may return 404, so setup probes only adjacent routes for selected Tabs.
  • The live health response uses progress: null; the helper also accepts the older phase: null fixture shape.
# Final report — #1557 ## Built - Kept delayed-device qualification on the delayed device only. The undelayed backing-file result is informational. The run uses `perf-run --quiesce` and retains all ten QD1 probes. - Repaired the Calendar activation fixture setup: initialize the private feed address before server config, pass the selected fixture sizes and CalDAV fixture through setup, retry only the server's explicit single-fetcher busy response, and poll the current `/healthz` progress contract. - Restrict adjacent API probes to the Tabs selected for the profile. - No new user controls or product UI were added in this continuation. The branch candidate uses the existing shared `ModeHeader` and `TimeGrid` components. ## Files changed from `origin/dev` `apps/web/scripts/design-rules-lib.mjs`; `apps/web/src/lib/calendar/TimeGrid.svelte.test.ts`; `apps/web/src/lib/modeHeader.svelte.test.ts`; `bench/tab-switch.mjs`; `bench/tab-switch.test.mjs`; `contracts/perf/adoption-1058.json`; `contracts/perf/exceptions.json`; `packages/ui/src/components/ModeHeader.svelte`; `packages/ui/src/components/calendar/TimeGrid.svelte`. ## Measurement and visual evidence The delayed-device qualification recorded p50 values 11.993, 11.600, 9.765, 8.978, 8.585, 8.225, 8.028, 8.094, 8.094 and 8.028 ms. All are within 8–14 ms, but the 1.494× run spread exceeds the required <1.3× gate. Load average inside the lock declined from 2.16 to 1.05. The run stopped before A/A or candidate timing; there is no performance result and no next attribution run. The visual-only captures contain real Events: the range probe counted 1,203 instances; 45 Events were visible at 1440 px and 80 at 820/390 px. Pointer-click and mode-shortcut captures reported zero focus-visible elements. All 16 before/after PNGs (390/820/1440 px, light/dark, plus pointer and shortcut captures) are attached in the preceding #1557 comment. Week DOM nodes / total page JS listeners, before → candidate: | View | Week nodes | Page listeners | | --- | ---: | ---: | | 1440 light | 3,674 → 3,783 | 879 → 925 | | 1440 dark | 3,674 → 3,783 | 883 → 923 | | 820 light | 472 → 472 | 684 → 695 | | 820 dark | 472 → 472 | 684 → 696 | | 390 light | 472 → 472 | 673 → 688 | | 390 dark | 472 → 472 | 673 → 689 | At 1440 light, Mail baseline listeners were 458 before and 460 after; Calendar listener delta was 421 → 465. The exact-pixel screenshot check failed at 1440 light/dark (16,190 / 16,895 pixels), 820 light/dark (181,524 / 24,903), and pointer click (80,898). The mode-shortcut comparison recorded 81,110 changed pixels; the 390 px captures were pixel-identical. The screenshots need the visual reviewer’s assessment. ## Gates `bun run check` (verbatim result lines): ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 20373 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 0 warnings exit=0 ``` `bun run test -- --maxWorkers=2` Vitest summary: ```text Test Files 321 passed (321) Tests 2340 passed (2340) Start at 10:00:44 Duration 365.60s (environment 29%, import 24%, transform 23%, tests 19%, setup 6%) ``` The Vitest run passed. The shell wrapper then printed `zsh:1: read-only variable: status` because it tried to assign to zsh’s reserved `status` variable; this was a wrapper error after the test summary. `bun test bench/tab-switch.test.mjs`: ```text 56 pass 0 fail Ran 56 tests across 1 file. [171.00ms] ``` No Rust source changed, so Rust crate gates were not run. `cargo clean` output: `Removed 1 file, 356B total`. `git fetch origin && git merge origin/dev`: `Already up to date.` The perf VM lock is free. ## Commits and head Head: `de7915f6cd721e4250d48a7bcf9b7f3a738b0d1e` (`job/calact6`). The work is committed in atomic steps. No push or deploy was run. ## Known gaps - No valid A/A or parent-versus-candidate timing result because the required disk stability gate failed. - The exact-pixel visual gate failed at desktop and tablet sizes; owner visual review is needed. - No next attribution item was measured because the acceptance criteria were not met. ## Decisions not covered by DESIGN.md - The Calendar server has one active subscription fetcher. Fixture setup retries only its explicit busy 503, with 100 ms exponential backoff capped at 1 second and 30 retries. - Inactive Plugin routes may return 404, so setup probes only adjacent routes for selected Tabs. - The live health response uses `progress: null`; the helper also accepts the older `phase: null` fixture shape.
Author
Owner

Starting calact9 on job/calact9, base/head de7915f6cd721e4250d48a7bcf9b7f3a738b0d1e. I read CLAUDE.md, CONTEXT.md, DESIGN §59, all #1557 comments, calprof attribution and the #1587 disk report. I will drop the rejected ModeHeader change, preserve benchmark repairs, discard/log the first three disk probes as warm-up, and reduce Calendar remount work without retaining a hidden route. No Rust change is planned. Acceptance uses the posted 9-pair A/A noise and interleaved A/B rule.

Starting calact9 on `job/calact9`, base/head `de7915f6cd721e4250d48a7bcf9b7f3a738b0d1e`. I read CLAUDE.md, CONTEXT.md, DESIGN §59, all #1557 comments, calprof attribution and the #1587 disk report. I will drop the rejected ModeHeader change, preserve benchmark repairs, discard/log the first three disk probes as warm-up, and reduce Calendar remount work without retaining a hidden route. No Rust change is planned. Acceptance uses the posted 9-pair A/A noise and interleaved A/B rule.
Author
Owner

Commits f651fdb7a and 1bb4921bb remove the rejected ModeHeader candidate and log three warm-up probes before ten measured delayed-device probes. The reserved zsh status was in an ad-hoc gate wrapper; this job uses gate_rc/profile_rc and preserves the command exit status.

The first locked qualification attempt failed: measured p50 spread 1.363× (probe 4: 10.944512 ms; minimum: 8.02816 ms). It collected no Tab timing sample. All thirteen probes and the failure remain in artifacts/calact9/aa.json. I have started one final qualification attempt with unchanged bounds, not a valid result yet.

Implementation finding: Calendar already caches data, and GridColumn already paints hour rules with CSS backgrounds. The candidate adds measured geometry and complete row restoration to the shared viewSnapshotCache, fenced by Calendar invalidation generation and User identity. It removes only compact/cramped metadata that CSS always hides and delegates Event boundary hover/focus plus captured move/up/cancel. Existing event controls and styles remain. Focused TimeGrid, data, snapshot, route and drag tests passed.

Commits `f651fdb7a` and `1bb4921bb` remove the rejected ModeHeader candidate and log three warm-up probes before ten measured delayed-device probes. The reserved zsh `status` was in an ad-hoc gate wrapper; this job uses `gate_rc`/`profile_rc` and preserves the command exit status. The first locked qualification attempt failed: measured p50 spread 1.363× (probe 4: 10.944512 ms; minimum: 8.02816 ms). It collected no Tab timing sample. All thirteen probes and the failure remain in artifacts/calact9/aa.json. I have started one final qualification attempt with unchanged bounds, not a valid result yet. Implementation finding: Calendar already caches data, and GridColumn already paints hour rules with CSS backgrounds. The candidate adds measured geometry and complete row restoration to the shared viewSnapshotCache, fenced by Calendar invalidation generation and User identity. It removes only compact/cramped metadata that CSS always hides and delegates Event boundary hover/focus plus captured move/up/cancel. Existing event controls and styles remain. Focused TimeGrid, data, snapshot, route and drag tests passed.
Author
Owner

Candidate implementation is committed at 5e259f8e8; the required origin/dev sync is fe6617f13. Final web gates pass: bun run check exit 0, 321 Vitest files / 2345 tests passed, and 57 benchmark tests passed. No Rust source changed.

Responsive evidence: all six real Week image comparisons are pixel-identical at 1440/820/390 px in light/dark. The range probe contains 1,203 Event instances. At 1440, Week nodes fall from 3,783 to 993; the Calendar listener delta falls from 443 to 221 (total page listeners 923 to 702). Tablet and phone use the same overflow UI and have 472 Week nodes before/after. The pointer and shortcut images have zero focus-visible elements and zero changed grid pixels. Their exact-image gate reports 70,949 / 71,765 changed pixels in the Top row chrome; most pointer changes are one RGB step. I am recapturing after the shared chrome animations finish and removing harness hover before captures, without changing the timing boundary.

Strict timing is not accepted: the latest delayed-device run has a stable 1.016× spread but includes 7.962624 ms p50, below the required 8 ms floor. Failed probes remain in the report. I will also collect diagnostic nine-pair A/A and A/B timings using the existing record-only mode, with timing_qualified: false and acceptance forced false. These cannot satisfy the issue contract.

Screenshots attached (original strict capture):

Candidate implementation is committed at `5e259f8e8`; the required origin/dev sync is `fe6617f13`. Final web gates pass: `bun run check` exit 0, 321 Vitest files / 2345 tests passed, and 57 benchmark tests passed. No Rust source changed. Responsive evidence: all six real Week image comparisons are pixel-identical at 1440/820/390 px in light/dark. The range probe contains 1,203 Event instances. At 1440, Week nodes fall from 3,783 to 993; the Calendar listener delta falls from 443 to 221 (total page listeners 923 to 702). Tablet and phone use the same overflow UI and have 472 Week nodes before/after. The pointer and shortcut images have zero focus-visible elements and zero changed grid pixels. Their exact-image gate reports 70,949 / 71,765 changed pixels in the Top row chrome; most pointer changes are one RGB step. I am recapturing after the shared chrome animations finish and removing harness hover before captures, without changing the timing boundary. Strict timing is not accepted: the latest delayed-device run has a stable 1.016× spread but includes 7.962624 ms p50, below the required 8 ms floor. Failed probes remain in the report. I will also collect diagnostic nine-pair A/A and A/B timings using the existing record-only mode, with `timing_qualified: false` and acceptance forced false. These cannot satisfy the issue contract. Screenshots attached (original strict capture): - [after-1440-dark.png](https://git.kayg.org/attachments/aa1104c9-fb99-40a6-806f-84aa1f79c048) - [after-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/3a4be367-f657-43d2-9455-6ea1a8393324) - [after-1440-light-pointer-click.png](https://git.kayg.org/attachments/d028b706-0c84-40d6-bde1-3bc01bcaeeab) - [after-1440-light.png](https://git.kayg.org/attachments/53cc229f-caed-43b8-b97a-51a0f4af41bb) - [after-390-dark.png](https://git.kayg.org/attachments/0ad75928-6998-4f63-9355-69771974f8c0) - [after-390-light.png](https://git.kayg.org/attachments/bc505e97-ad31-4b12-a19b-81c5ae461827) - [after-820-dark.png](https://git.kayg.org/attachments/6d2c5b3c-9d19-4540-8b53-900a5f80497b) - [after-820-light.png](https://git.kayg.org/attachments/d001792f-2291-41e5-bbd5-2a3ef4f31e87) - [before-1440-dark.png](https://git.kayg.org/attachments/1331f671-607d-4a64-b425-46bc22df472f) - [before-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/599518f3-d67a-4118-bee3-3bbb70c0c43e) - [before-1440-light-pointer-click.png](https://git.kayg.org/attachments/b2f930bd-d546-4c5c-8c90-617ded300a65) - [before-1440-light.png](https://git.kayg.org/attachments/b3ff16e2-1a94-4ad4-9037-d3fbebcb6826) - [before-390-dark.png](https://git.kayg.org/attachments/9bd739f5-7c01-4650-8e9a-195947bdf674) - [before-390-light.png](https://git.kayg.org/attachments/cb2ed7e1-de7a-4f72-954a-67bc6e99fc0e) - [before-820-dark.png](https://git.kayg.org/attachments/b34e3c38-243e-490e-b223-7257f432e91e) - [before-820-light.png](https://git.kayg.org/attachments/ac34115f-a9fd-46ff-bb57-e3fba58a2b89)
Author
Owner

Candidate implementation is committed at 5e259f8e8; the required origin/dev sync is fe6617f13. Final web gates pass: bun run check exit 0, 321 Vitest files / 2345 tests passed, and 57 benchmark tests passed. No Rust source changed.

Responsive evidence: all six real Week image comparisons are pixel-identical at 1440/820/390 px in light/dark. The range probe contains 1,203 Event instances. At 1440, Week nodes fall from 3,783 to 993; the Calendar listener delta falls from 443 to 221 (total page listeners 923 to 702). Tablet and phone use the same overflow UI and have 472 Week nodes before/after. The pointer and shortcut images have zero focus-visible elements and zero changed grid pixels. Their exact-image gate reports 70,949 / 71,765 changed pixels in the Top row chrome; most pointer changes are one RGB step. I am recapturing after the shared chrome animations finish and removing harness hover before captures, without changing the timing boundary.

Strict timing is not accepted: the latest delayed-device run has a stable 1.016× spread but includes 7.962624 ms p50, below the required 8 ms floor. Failed probes remain in the report. I will also collect diagnostic nine-pair A/A and A/B timings using the existing record-only mode, with timing_qualified: false and acceptance forced false. These cannot satisfy the issue contract.

Screenshots attached (original strict capture):

Candidate implementation is committed at `5e259f8e8`; the required origin/dev sync is `fe6617f13`. Final web gates pass: `bun run check` exit 0, 321 Vitest files / 2345 tests passed, and 57 benchmark tests passed. No Rust source changed. Responsive evidence: all six real Week image comparisons are pixel-identical at 1440/820/390 px in light/dark. The range probe contains 1,203 Event instances. At 1440, Week nodes fall from 3,783 to 993; the Calendar listener delta falls from 443 to 221 (total page listeners 923 to 702). Tablet and phone use the same overflow UI and have 472 Week nodes before/after. The pointer and shortcut images have zero focus-visible elements and zero changed grid pixels. Their exact-image gate reports 70,949 / 71,765 changed pixels in the Top row chrome; most pointer changes are one RGB step. I am recapturing after the shared chrome animations finish and removing harness hover before captures, without changing the timing boundary. Strict timing is not accepted: the latest delayed-device run has a stable 1.016× spread but includes 7.962624 ms p50, below the required 8 ms floor. Failed probes remain in the report. I will also collect diagnostic nine-pair A/A and A/B timings using the existing record-only mode, with `timing_qualified: false` and acceptance forced false. These cannot satisfy the issue contract. Screenshots attached (original strict capture): - [after-1440-dark.png](https://git.kayg.org/attachments/aa1104c9-fb99-40a6-806f-84aa1f79c048) - [after-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/3a4be367-f657-43d2-9455-6ea1a8393324) - [after-1440-light-pointer-click.png](https://git.kayg.org/attachments/d028b706-0c84-40d6-bde1-3bc01bcaeeab) - [after-1440-light.png](https://git.kayg.org/attachments/53cc229f-caed-43b8-b97a-51a0f4af41bb) - [after-390-dark.png](https://git.kayg.org/attachments/0ad75928-6998-4f63-9355-69771974f8c0) - [after-390-light.png](https://git.kayg.org/attachments/bc505e97-ad31-4b12-a19b-81c5ae461827) - [after-820-dark.png](https://git.kayg.org/attachments/6d2c5b3c-9d19-4540-8b53-900a5f80497b) - [after-820-light.png](https://git.kayg.org/attachments/d001792f-2291-41e5-bbd5-2a3ef4f31e87) - [before-1440-dark.png](https://git.kayg.org/attachments/1331f671-607d-4a64-b425-46bc22df472f) - [before-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/599518f3-d67a-4118-bee3-3bbb70c0c43e) - [before-1440-light-pointer-click.png](https://git.kayg.org/attachments/b2f930bd-d546-4c5c-8c90-617ded300a65) - [before-1440-light.png](https://git.kayg.org/attachments/b3ff16e2-1a94-4ad4-9037-d3fbebcb6826) - [before-390-dark.png](https://git.kayg.org/attachments/9bd739f5-7c01-4650-8e9a-195947bdf674) - [before-390-light.png](https://git.kayg.org/attachments/cb2ed7e1-de7a-4f72-954a-67bc6e99fc0e) - [before-820-dark.png](https://git.kayg.org/attachments/b34e3c38-243e-490e-b223-7257f432e91e) - [before-820-light.png](https://git.kayg.org/attachments/ac34115f-a9fd-46ff-bb57-e3fba58a2b89)
Author
Owner

Resuming calact9 on job/calact9 at 16b218172c. The worktree is clean. Read the preserved commits, all current issue comments, DESIGN §59 and the calprof report. The rejected ModeHeader candidate is already reverted. The Calendar snapshot and DOM changes are committed, with User/time-zone fences. Prior strict disk qualifications and diagnostic A/A + A/B runs are preserved; A/B is not accepted. No additional qualification retry is planned: the previous run already used its attempts and the job time box. This continuation will explain the final pixel differences, run the required web gates on the resumed head, and post the complete report. The required origin/dev synchronization was already done at fe6617f13; no new merge is planned.

Resuming calact9 on job/calact9 at 16b218172c2d67167815ed8d7fedecc8a065d0c9. The worktree is clean. Read the preserved commits, all current issue comments, DESIGN §59 and the calprof report. The rejected ModeHeader candidate is already reverted. The Calendar snapshot and DOM changes are committed, with User/time-zone fences. Prior strict disk qualifications and diagnostic A/A + A/B runs are preserved; A/B is not accepted. No additional qualification retry is planned: the previous run already used its attempts and the job time box. This continuation will explain the final pixel differences, run the required web gates on the resumed head, and post the complete report. The required origin/dev synchronization was already done at fe6617f13; no new merge is planned.
Author
Owner

calact9: Calendar activation, issue #1557

Status: NOT READY FOR MERGE. This is a time-boxed report. The candidate is committed, but the required performance comparison is not accepted. Do not treat the DOM reduction as proof of faster full paint.

Built

  • Reverted the rejected calact6–8 ModeHeader scheduling change. Kept benchmark repairs.
  • Logged three delayed-device warm-up probes before ten measured probes. The limits remain 8–14 ms for every measured p50 and a spread below 1.3×. Gate wrappers use gate_rc, not the zsh reserved status variable.
  • Used the shared User-scoped viewSnapshotCache for completed Calendar rows, selection, scroll and measured column width. Its existing limits are four entries, 8 MiB, 2,000 row objects and two in-flight reservations. No hidden Calendar route stays mounted.
  • Fenced snapshots by opening User, Calendar generation, time zone, route, filters, width, scale, visible span and hour height. Session and access changes invalidate retained data.
  • Removed Event metadata subtrees that existing compact/cramped CSS always hides. Delegated Event hover/focus and captured drag move/up/cancel to the column. Kept the existing buttons, accessible labels, styles and action paths.
  • Repaired fixture setup, trusted-click timing, diagnostic acceptance flags and the complete A/B result shape.
  • Resume commit 47d9cf42f changes screenshot priming to real Tab switches. The previous full-document Mail navigation destroyed the in-memory snapshot. Old interaction captures cannot prove a warm snapshot return. The new capture path has not been rerun.

Shared components: TimeGrid, GridColumn, TagPill, the existing Event/Task controls and ModeHeader. No new visible control or dependency was added. No crate or package version changed. No Rust source changed. The job did not raise contracts/perf/ratchet.json.

Files

Implementation and regression tests:

  • apps/web/src/lib/calendar/{data.ts,data.test.ts,routeLoad.ts,routeLoad.test.ts,TimeGrid.svelte.test.ts}
  • apps/web/src/lib/{viewSnapshots.ts,viewSnapshots.test.ts,modeHeader.svelte.test.ts}
  • apps/web/src/routes/calendar/[view]/[date]/+page.svelte
  • packages/ui/src/components/{ModeHeader.svelte,calendar/TimeGrid.svelte,calendar/GridColumn.svelte}
  • bench/{tab-switch.mjs,tab-switch.test.mjs}
  • Exact live-site pins in contracts/perf/{adoption-1058.json,exceptions.json}
  • This report. Screenshots, logs and raw reports remain under ignored artifacts/calact9/.

Measurement

Prior production runs used the perf VM with perf-run --quiesce, Chromium on that VM and macOS platform emulation. The release server source was 8e840962979acd2bdf58141888618ab5560f728e; SHA-256 was 9c27cf74bb2196760c0d5ca7927717267a3edeeb2efd56c7db236222f317024a. The saved A/B report identifies the two production asset directories and harness hash. They are older builds, not a new measurement of the resumed head.

The last complete diagnostic A/B run logged warm-up p50 values 11.862016, 11.599872 and 10.289152 ms. Its ten measured p50 values were 9.109504, 8.159232, 8.093696, 8.093696, 8.02816, 8.02816, 8.02816, 8.02816, 8.02816 and 7.962624 ms. The last value fails the 8 ms floor. No bound was changed and no failed value was discarded. Earlier failures remain in the saved reports.

Nine diagnostic A/A pairs produced N = 29.61%. The source report has timing_qualified: false and valid: false. The later local aa-verified.json recalculation marked validity true from disk values alone; that does not supersede the diagnostic source flag. Do not use it as accepted A/A evidence. The diagnostic A/B threshold was max(10%, 2N) = 59.22%.

Cell Full-paint p50 before → after (ms) p95 before → after (ms) Median paired improvement Improved pairs
Mail → Calendar 694.9 → 779.1 1339.7 → 2442.0 -10.17% 2/9
Files → Calendar 652.6 → 527.6 1209.3 → 744.8 18.95% 6/9
Calendar → Files 515.0 → 391.2 615.4 → 528.1 34.89% 8/9

The A/B report has timing_qualified: false and accepted: false. Mail → Calendar fails the directional test and has an 82.28% p95 increase, above the diagnostic threshold. These results do not establish a production regression or improvement because the run is unqualified. No matching snapshot baseline exists in docs/perf/baseline.json. The 100 ms warm-switch budget remains unmet in these diagnostics.

The fixture had 100 Files, 20 Notes, 30 recurring CalDAV Events, 1,000 dated Events and 50 subscription Events. The range probe found 1,203 Event instances. It is the smoke fixture, not the largest realistic Home. Expected work is O(retained CalendarDay rows + their items) to restore/map and serialize on unmount, plus existing bounded chunk refreshes. A realistic-size snapshot byte/CPU/RSS measurement is still missing.

Visual evidence

The last local capture set used production builds with real Events, macOS platform emulation, 1440/820/390 px and both themes. It contains 45 grid Events at 1440 px and 80 overflow-list Events at 820/390 px. It is evidence of resting render structure, not accepted warm-return proof.

At 1440 light, Week nodes were 3,783 → 993. Total page listeners were 923 → 702; the Calendar-minus-Mail listener delta was 443 → 221. At 820/390, Week nodes remained 472. These counts belong to the saved capture set.

Five responsive full-image comparisons have zero changed pixels. Desktop dark has four changed pixels, all at the sidebar top-left control (x19–33, y19–33), with maximum RGB-channel change 6; Calendar pixels are unchanged.

The pointer capture has 1,188 changed pixels: 441 in sidebar Tag counts, 716 in the all-day row, and 31 at the current-time label. The images show different completed background data: the candidate has an all-day Task and higher Tag counts. This is a capture-state mismatch, not an explained visual-equivalence pass. The shortcut capture has 70,892 changed pixels, all in the Top row (x287–1359, y0–99); Calendar content has zero changed pixels. Most changes are one RGB step; the maximum is 49. Shared blur/transition capture state needs review. No pixel tolerance was raised. Both candidate interaction captures report zero focus-visible elements.

The visual reviewer remains the orchestrator. Captures are attached to #1557 below. The new SPA-only capture path must be rerun before claiming visual parity for snapshot return.

UX gaps closed

  • A retained return can restore the last column width, vertical scroll and stable item selection before live refresh.
  • A User switch or invalidation cannot save the old User's rows during late teardown.
  • A time-zone change cannot reuse rows under the old view identity.
  • Delegated pointer boundary events ignore movement within one card. Drag cancellation and keyboard focus use the existing controls.

UX gaps left / known gaps

  • No accepted A/A plus A/B performance result. No instant-return claim is supported.
  • Warm snapshot screenshots need a new run after the priming repair; old interaction captures have background-state and chrome differences.
  • Snapshot bytes, realistic-size average/worst-case CPU and RSS are not measured.
  • This branch contains older shared chrome than current origin/dev. The required prior sync was committed at fe6617f13; no additional merge was made on resume, in line with the brief's final no-merges instruction. Current origin/dev at resume was 016bf3d5752999223d08cd5d04a6618a50898ad5.
  • Final gates are not all green. Do not label the branch READY FOR MERGE.

Gates on resume (verbatim excerpts)

cd apps/web && bun run check:

perf-lint: PASS; 0 violations; 20373 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited.
UI transitions and animation options use shared motion tokens or documented exceptions.

The command terminated with tool exit code 143 before a Svelte result or the wrapper's exit= line. This is not a pass. Earlier successful check logs remain as historical evidence only.

cd apps/web && bun run test -- --maxWorkers=2:

New design-rule violations:
- packages/ui/src/components/ModeHeader.svelte:449: new hand-rolled button: mh-crumb-ellipsis type="button" aria-label="Show folders in this path" aria-haspopup="menu"
error: script "test" exited with code 1
exit=1

The command stopped before Vitest. The old breadcrumb button is absent from current origin/dev. No guard, ratchet or test expectation was changed to hide this failure.

Focused Calendar, data, route, snapshot and ModeHeader Vitest run:

 Test Files  5 passed (5)
      Tests  108 passed (108)
   Start at  16:06:55
   Duration  34.09s (transform 76%, tests 10%, import 6%, environment 6%, setup 2%)
exit=0

bun test bench/tab-switch.test.mjs after the resume repair:

 57 pass
 0 fail
Ran 57 tests across 1 file. [152.00ms]
exit=0

No Rust gates ran because no Rust source changed. Module and non-obvious function comments were reread. No existing assertion or fixture expectation was relaxed.

Decisions

  • Extend the shared snapshot with optional measured column width; keep its existing LRU limits. Retain row references, not DOM or component instances.
  • Serialize retained rows only on teardown to estimate UTF-16 bytes conservatively. This estimate must still be measured on a realistic Home.
  • Keep all qualification failures and force exploratory comparisons outside acceptance. Do not promote a diagnostic report through an offline recalculation.
  • Compare identical saved viewports. Use Tab switches for screenshot priming; document reloads are cold views.
  • Stop after the existing time-boxed attempts. Preserve the rejected candidate for review instead of making an unsupported acceptance claim.

Remaining verification

After the orchestrator resolves the current origin/dev changes, run cd apps/web && bun run check and bun run test -- --maxWorkers=2. They must prove both the design guard and Svelte/Vitest gates pass.

Run bun bench/tab-switch.mjs --vm-browser --reuse-fixture --aa --ab-before <parent-production-build> --ab-after <same-parent-production-build> --ab-followup <candidate-production-build> --runs 9 --conditions warm --engines chromium --viewports 1440 --themes light --tabs calendar,mail,files --calendar-activation --ab-cells mail:calendar,files:calendar,calendar:files --json <report> with the strict delayed-device qualification and perf-run --quiesce. Require valid A/A N, at least max(10%, 2N) median paired Mail → Calendar improvement, at least 7/9 improved pairs and no cell p95 increase above that threshold. Do not use read-only appearance or diagnostic flags for acceptance.

Run bun bench/tab-switch.mjs --local --reuse-fixture --runs 9 --conditions warm --engines chromium --viewports 1440,820,390 --themes light,dark --tabs calendar,mail,files --calendar-activation --ab-cells mail:calendar,files:calendar,calendar:files --ab-visual-only --ab-before <parent> --ab-after <candidate> --json <report> --screenshots <directory> with the local fixture state selected by TAB_SWITCH_FIXTURE_STATE_PATH. It must prove equal loaded data and viewports, preserved warm snapshot state, no mouse/shortcut rings, and explain every pixel difference. Run the focused Calendar interaction regression tests. No API changed, so this job did not run an API adversarial matrix.

No pushes, deploys, new merges, issue closure or macOS VM access occurred on resume. The perf VM reported free on the final read-only check.

Attached last capture set

Head and cleanup

Final branch head: 6cf6fa03fec38d89737d5465ac54b95e75a3ed99 (clean worktree).

Implementation head: 47d9cf42f527d44885b35c010fac5e470ade29f2. The documentation commit follows it; the final issue comment records that full head.

cargo clean (verbatim):

     Removed 1 file, 356B total
exit=0

Removed apps/web/build, apps/web/.svelte-kit and the archived parent/candidate web build directories. Kept review images and raw evidence in the ignored artifacts directory. No credentials were displayed.

# calact9: Calendar activation, issue #1557 Status: **NOT READY FOR MERGE**. This is a time-boxed report. The candidate is committed, but the required performance comparison is not accepted. Do not treat the DOM reduction as proof of faster full paint. ## Built - Reverted the rejected calact6–8 ModeHeader scheduling change. Kept benchmark repairs. - Logged three delayed-device warm-up probes before ten measured probes. The limits remain 8–14 ms for every measured p50 and a spread below 1.3×. Gate wrappers use `gate_rc`, not the zsh reserved `status` variable. - Used the shared User-scoped `viewSnapshotCache` for completed Calendar rows, selection, scroll and measured column width. Its existing limits are four entries, 8 MiB, 2,000 row objects and two in-flight reservations. No hidden Calendar route stays mounted. - Fenced snapshots by opening User, Calendar generation, time zone, route, filters, width, scale, visible span and hour height. Session and access changes invalidate retained data. - Removed Event metadata subtrees that existing compact/cramped CSS always hides. Delegated Event hover/focus and captured drag move/up/cancel to the column. Kept the existing buttons, accessible labels, styles and action paths. - Repaired fixture setup, trusted-click timing, diagnostic acceptance flags and the complete A/B result shape. - Resume commit `47d9cf42f` changes screenshot priming to real Tab switches. The previous full-document Mail navigation destroyed the in-memory snapshot. Old interaction captures cannot prove a warm snapshot return. The new capture path has not been rerun. Shared components: TimeGrid, GridColumn, TagPill, the existing Event/Task controls and ModeHeader. No new visible control or dependency was added. No crate or package version changed. No Rust source changed. The job did not raise `contracts/perf/ratchet.json`. ## Files Implementation and regression tests: - `apps/web/src/lib/calendar/{data.ts,data.test.ts,routeLoad.ts,routeLoad.test.ts,TimeGrid.svelte.test.ts}` - `apps/web/src/lib/{viewSnapshots.ts,viewSnapshots.test.ts,modeHeader.svelte.test.ts}` - `apps/web/src/routes/calendar/[view]/[date]/+page.svelte` - `packages/ui/src/components/{ModeHeader.svelte,calendar/TimeGrid.svelte,calendar/GridColumn.svelte}` - `bench/{tab-switch.mjs,tab-switch.test.mjs}` - Exact live-site pins in `contracts/perf/{adoption-1058.json,exceptions.json}` - This report. Screenshots, logs and raw reports remain under ignored `artifacts/calact9/`. ## Measurement Prior production runs used the perf VM with `perf-run --quiesce`, Chromium on that VM and macOS platform emulation. The release server source was `8e840962979acd2bdf58141888618ab5560f728e`; SHA-256 was `9c27cf74bb2196760c0d5ca7927717267a3edeeb2efd56c7db236222f317024a`. The saved A/B report identifies the two production asset directories and harness hash. They are older builds, not a new measurement of the resumed head. The last complete diagnostic A/B run logged warm-up p50 values 11.862016, 11.599872 and 10.289152 ms. Its ten measured p50 values were 9.109504, 8.159232, 8.093696, 8.093696, 8.02816, 8.02816, 8.02816, 8.02816, 8.02816 and 7.962624 ms. The last value fails the 8 ms floor. No bound was changed and no failed value was discarded. Earlier failures remain in the saved reports. Nine diagnostic A/A pairs produced N = 29.61%. The source report has `timing_qualified: false` and `valid: false`. The later local `aa-verified.json` recalculation marked validity true from disk values alone; that does not supersede the diagnostic source flag. Do not use it as accepted A/A evidence. The diagnostic A/B threshold was max(10%, 2N) = 59.22%. | Cell | Full-paint p50 before → after (ms) | p95 before → after (ms) | Median paired improvement | Improved pairs | | --- | --- | --- | --- | --- | | Mail → Calendar | 694.9 → 779.1 | 1339.7 → 2442.0 | -10.17% | 2/9 | | Files → Calendar | 652.6 → 527.6 | 1209.3 → 744.8 | 18.95% | 6/9 | | Calendar → Files | 515.0 → 391.2 | 615.4 → 528.1 | 34.89% | 8/9 | The A/B report has `timing_qualified: false` and `accepted: false`. Mail → Calendar fails the directional test and has an 82.28% p95 increase, above the diagnostic threshold. These results do not establish a production regression or improvement because the run is unqualified. No matching snapshot baseline exists in `docs/perf/baseline.json`. The 100 ms warm-switch budget remains unmet in these diagnostics. The fixture had 100 Files, 20 Notes, 30 recurring CalDAV Events, 1,000 dated Events and 50 subscription Events. The range probe found 1,203 Event instances. It is the smoke fixture, not the largest realistic Home. Expected work is O(retained CalendarDay rows + their items) to restore/map and serialize on unmount, plus existing bounded chunk refreshes. A realistic-size snapshot byte/CPU/RSS measurement is still missing. ## Visual evidence The last local capture set used production builds with real Events, macOS platform emulation, 1440/820/390 px and both themes. It contains 45 grid Events at 1440 px and 80 overflow-list Events at 820/390 px. It is evidence of resting render structure, not accepted warm-return proof. At 1440 light, Week nodes were 3,783 → 993. Total page listeners were 923 → 702; the Calendar-minus-Mail listener delta was 443 → 221. At 820/390, Week nodes remained 472. These counts belong to the saved capture set. Five responsive full-image comparisons have zero changed pixels. Desktop dark has four changed pixels, all at the sidebar top-left control (x19–33, y19–33), with maximum RGB-channel change 6; Calendar pixels are unchanged. The pointer capture has 1,188 changed pixels: 441 in sidebar Tag counts, 716 in the all-day row, and 31 at the current-time label. The images show different completed background data: the candidate has an all-day Task and higher Tag counts. This is a capture-state mismatch, not an explained visual-equivalence pass. The shortcut capture has 70,892 changed pixels, all in the Top row (x287–1359, y0–99); Calendar content has zero changed pixels. Most changes are one RGB step; the maximum is 49. Shared blur/transition capture state needs review. No pixel tolerance was raised. Both candidate interaction captures report zero focus-visible elements. The visual reviewer remains the orchestrator. Captures are attached to #1557 below. The new SPA-only capture path must be rerun before claiming visual parity for snapshot return. ## UX gaps closed - A retained return can restore the last column width, vertical scroll and stable item selection before live refresh. - A User switch or invalidation cannot save the old User's rows during late teardown. - A time-zone change cannot reuse rows under the old view identity. - Delegated pointer boundary events ignore movement within one card. Drag cancellation and keyboard focus use the existing controls. ## UX gaps left / known gaps - No accepted A/A plus A/B performance result. No instant-return claim is supported. - Warm snapshot screenshots need a new run after the priming repair; old interaction captures have background-state and chrome differences. - Snapshot bytes, realistic-size average/worst-case CPU and RSS are not measured. - This branch contains older shared chrome than current origin/dev. The required prior sync was committed at `fe6617f13`; no additional merge was made on resume, in line with the brief's final no-merges instruction. Current origin/dev at resume was `016bf3d5752999223d08cd5d04a6618a50898ad5`. - Final gates are not all green. Do not label the branch READY FOR MERGE. ## Gates on resume (verbatim excerpts) `cd apps/web && bun run check`: ```text perf-lint: PASS; 0 violations; 20373 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Focus policy passes: 48 focus ring rules, 133 DOM focus calls and 2 data-input writers audited. UI transitions and animation options use shared motion tokens or documented exceptions. ``` The command terminated with tool exit code 143 before a Svelte result or the wrapper's `exit=` line. This is not a pass. Earlier successful check logs remain as historical evidence only. `cd apps/web && bun run test -- --maxWorkers=2`: ```text New design-rule violations: - packages/ui/src/components/ModeHeader.svelte:449: new hand-rolled button: mh-crumb-ellipsis type="button" aria-label="Show folders in this path" aria-haspopup="menu" error: script "test" exited with code 1 exit=1 ``` The command stopped before Vitest. The old breadcrumb button is absent from current origin/dev. No guard, ratchet or test expectation was changed to hide this failure. Focused Calendar, data, route, snapshot and ModeHeader Vitest run: ```text Test Files 5 passed (5) Tests 108 passed (108) Start at 16:06:55 Duration 34.09s (transform 76%, tests 10%, import 6%, environment 6%, setup 2%) exit=0 ``` `bun test bench/tab-switch.test.mjs` after the resume repair: ```text 57 pass 0 fail Ran 57 tests across 1 file. [152.00ms] exit=0 ``` No Rust gates ran because no Rust source changed. Module and non-obvious function comments were reread. No existing assertion or fixture expectation was relaxed. ## Decisions - Extend the shared snapshot with optional measured column width; keep its existing LRU limits. Retain row references, not DOM or component instances. - Serialize retained rows only on teardown to estimate UTF-16 bytes conservatively. This estimate must still be measured on a realistic Home. - Keep all qualification failures and force exploratory comparisons outside acceptance. Do not promote a diagnostic report through an offline recalculation. - Compare identical saved viewports. Use Tab switches for screenshot priming; document reloads are cold views. - Stop after the existing time-boxed attempts. Preserve the rejected candidate for review instead of making an unsupported acceptance claim. ## Remaining verification After the orchestrator resolves the current origin/dev changes, run `cd apps/web && bun run check` and `bun run test -- --maxWorkers=2`. They must prove both the design guard and Svelte/Vitest gates pass. Run `bun bench/tab-switch.mjs --vm-browser --reuse-fixture --aa --ab-before <parent-production-build> --ab-after <same-parent-production-build> --ab-followup <candidate-production-build> --runs 9 --conditions warm --engines chromium --viewports 1440 --themes light --tabs calendar,mail,files --calendar-activation --ab-cells mail:calendar,files:calendar,calendar:files --json <report>` with the strict delayed-device qualification and `perf-run --quiesce`. Require valid A/A N, at least max(10%, 2N) median paired Mail → Calendar improvement, at least 7/9 improved pairs and no cell p95 increase above that threshold. Do not use read-only appearance or diagnostic flags for acceptance. Run `bun bench/tab-switch.mjs --local --reuse-fixture --runs 9 --conditions warm --engines chromium --viewports 1440,820,390 --themes light,dark --tabs calendar,mail,files --calendar-activation --ab-cells mail:calendar,files:calendar,calendar:files --ab-visual-only --ab-before <parent> --ab-after <candidate> --json <report> --screenshots <directory>` with the local fixture state selected by `TAB_SWITCH_FIXTURE_STATE_PATH`. It must prove equal loaded data and viewports, preserved warm snapshot state, no mouse/shortcut rings, and explain every pixel difference. Run the focused Calendar interaction regression tests. No API changed, so this job did not run an API adversarial matrix. No pushes, deploys, new merges, issue closure or macOS VM access occurred on resume. The perf VM reported `free` on the final read-only check. ## Attached last capture set - [matched-after-1440-dark.png](https://git.kayg.org/attachments/cbb1ae7e-661a-425b-b7c7-b3d2a6a70381) - [matched-after-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/d47b30ee-9898-4f08-8c92-64463ad2d786) - [matched-after-1440-light-pointer-click.png](https://git.kayg.org/attachments/724ea975-4e2f-4ad7-a0c9-14894ee1dfd0) - [matched-after-1440-light.png](https://git.kayg.org/attachments/1b63a901-090d-4c4a-b4f0-74b80378b1d5) - [matched-after-390-dark.png](https://git.kayg.org/attachments/a04a96e4-9461-4bfb-bb14-a6b10dcdc270) - [matched-after-390-light.png](https://git.kayg.org/attachments/19479e3d-8210-4a40-be87-8f0254412442) - [matched-after-820-dark.png](https://git.kayg.org/attachments/a64a4b85-38e9-4d8a-aadc-c571ca24c608) - [matched-after-820-light.png](https://git.kayg.org/attachments/e2a8bed6-b8a9-49c4-ab07-0086731c95bc) - [matched-before-1440-dark.png](https://git.kayg.org/attachments/6f18aa85-a91d-4039-abee-5b5a5b87da16) - [matched-before-1440-light-mode-shortcut.png](https://git.kayg.org/attachments/d698529d-e11d-408e-8caf-fcc6ab5223bf) - [matched-before-1440-light-pointer-click.png](https://git.kayg.org/attachments/4e9bce7c-697b-4234-9288-0ca54b52e5e1) - [matched-before-1440-light.png](https://git.kayg.org/attachments/b63ce2d6-d66a-4106-bdea-002eaac0af05) - [matched-before-390-dark.png](https://git.kayg.org/attachments/5773f26d-6d65-432a-a251-19f69d8f255b) - [matched-before-390-light.png](https://git.kayg.org/attachments/53862bdf-15a9-47d4-9c07-3fd3af4ab734) - [matched-before-820-dark.png](https://git.kayg.org/attachments/0412c80d-72f4-4acf-8152-ed2cc37416e9) - [matched-before-820-light.png](https://git.kayg.org/attachments/1ae78066-7c8b-4041-bf35-a8f5bbff73a6) ## Head and cleanup Final branch head: `6cf6fa03fec38d89737d5465ac54b95e75a3ed99` (clean worktree). Implementation head: `47d9cf42f527d44885b35c010fac5e470ade29f2`. The documentation commit follows it; the final issue comment records that full head. `cargo clean` (verbatim): ```text Removed 1 file, 356B total exit=0 ``` Removed `apps/web/build`, `apps/web/.svelte-kit` and the archived parent/candidate web build directories. Kept review images and raw evidence in the ignored artifacts directory. No credentials were displayed.
Author
Owner

Started calact10 on job/calact9 at 6cf6fa03fec38d89737d5465ac54b95e75a3ed99; merge base with fetched origin/dev is b8bb41f211312dfdf7accf21c132b5b5acf3636c (origin/dev is 016bf3d5752999223d08cd5d04a6618a50898ad5). I am reconciling the candidate with current dev first, preserving dev's ModeHeader, then will run the requested gates, real Tab-switch screenshot capture, and one qualified perf-VM A/A followed by A/B only if A/A noise passes.

Started calact10 on `job/calact9` at `6cf6fa03fec38d89737d5465ac54b95e75a3ed99`; merge base with fetched `origin/dev` is `b8bb41f211312dfdf7accf21c132b5b5acf3636c` (`origin/dev` is `016bf3d5752999223d08cd5d04a6618a50898ad5`). I am reconciling the candidate with current dev first, preserving dev's ModeHeader, then will run the requested gates, real Tab-switch screenshot capture, and one qualified perf-VM A/A followed by A/B only if A/A noise passes.
Author
Owner

Finding: after the dev merge, viewSnapshotCache was User-keyed and cleared on calternal:session-ended, calternal:auth-changed, calternal:mode-caches-clear and calternal:plugins-changed, but not on calternal:access-changed or the immediate calternal:session-expired signal. apps/web/src/lib/files/api.ts and live.ts emit calternal:access-changed; DESIGN §59 requires Share revocation to invalidate retained data. I am adding both invalidations and a regression test that dispatches the browser events against the shared cache.

Finding: after the dev merge, `viewSnapshotCache` was User-keyed and cleared on `calternal:session-ended`, `calternal:auth-changed`, `calternal:mode-caches-clear` and `calternal:plugins-changed`, but not on `calternal:access-changed` or the immediate `calternal:session-expired` signal. `apps/web/src/lib/files/api.ts` and `live.ts` emit `calternal:access-changed`; DESIGN §59 requires Share revocation to invalidate retained data. I am adding both invalidations and a regression test that dispatches the browser events against the shared cache.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1557
No description provided.