Tabs: reduce warm Calendar to Files input-to-paint latency #1233

Open
opened 2026-10-06 21:43:51 +00:00 by kayg · 100 comments
Owner

Performance follow-up to #1124, round 2. Warm Calendar→Files Tab restore remains slow on the shared perf VM, full HDD scope, Chromium 4× CPU throttle, macOS platform. Five measured switches restore the already-loaded Files root with the same URL and four-row identity before and after; the departing Calendar is data-ready. Browser click→second painted frame p50 / p95 / max 747.5 / 995.5 / 995.5 ms. Automation click/readiness clock is 991.91 / 1300.40 / 1300.40 ms and must not replace the browser input clock. Individual browser samples: 906.8, 747.5, 654.2, 514.4, 995.5 ms.

Fixture: 5000 Files elsewhere in Home, 240 asserted Logs, 121 asserted provider Events. Profile runs through perf-run and records load. First separate API root snapshot had three rows while the completed UI had four; fixture Home setup can race that snapshot. Benchmark readiness now uses the actual completed accessible Files label, records API counts, and strictly asserts restored destination identity. Do not reuse earlier warm clocks that omitted destination preparation or departing Calendar readiness.

Investigate warm route activation, Calendar teardown and cache invalidation with a separate marked trace. The existing Files first-page cache returns its cached snapshot immediately and only revalidates after 2 seconds; its TTL alone does not prove the critical path waits on a request. No warm source flamegraph was captured in this final single-condition phase, so root cause is open. Cost should depend on the four visible rows and bounded mount/teardown, not a full-Home scan. Evidence: docs/perf/runs/2026-10-06-1124-round2-warm-tabs.json and bench/interaction-1124.mjs. SLOW-only finding; no crash or data loss found.

Performance follow-up to #1124, round 2. Warm Calendar→Files Tab restore remains slow on the shared perf VM, full HDD scope, Chromium 4× CPU throttle, macOS platform. Five measured switches restore the already-loaded Files root with the same URL and four-row identity before and after; the departing Calendar is data-ready. Browser click→second painted frame p50 / p95 / max 747.5 / 995.5 / 995.5 ms. Automation click/readiness clock is 991.91 / 1300.40 / 1300.40 ms and must not replace the browser input clock. Individual browser samples: 906.8, 747.5, 654.2, 514.4, 995.5 ms. Fixture: 5000 Files elsewhere in Home, 240 asserted Logs, 121 asserted provider Events. Profile runs through perf-run and records load. First separate API root snapshot had three rows while the completed UI had four; fixture Home setup can race that snapshot. Benchmark readiness now uses the actual completed accessible Files label, records API counts, and strictly asserts restored destination identity. Do not reuse earlier warm clocks that omitted destination preparation or departing Calendar readiness. Investigate warm route activation, Calendar teardown and cache invalidation with a separate marked trace. The existing Files first-page cache returns its cached snapshot immediately and only revalidates after 2 seconds; its TTL alone does not prove the critical path waits on a request. No warm source flamegraph was captured in this final single-condition phase, so root cause is open. Cost should depend on the four visible rows and bounded mount/teardown, not a full-Home scan. Evidence: docs/perf/runs/2026-10-06-1124-round2-warm-tabs.json and bench/interaction-1124.mjs. SLOW-only finding; no crash or data loss found.
Author
Owner

Starting tabswitch-1233 on branch job/tabswitch-1233, based on 3601719b80 (origin/dev). I am reading the existing interaction profile, then I will measure the full Tab switch matrix and post the trace-backed baseline before changing app behavior.

Starting tabswitch-1233 on branch job/tabswitch-1233, based on 3601719b80a39f712daf07b37879b22929dfffa0 (origin/dev). I am reading the existing interaction profile, then I will measure the full Tab switch matrix and post the trace-backed baseline before changing app behavior.
Author
Owner

Code inspection confirms that Settings and Search are not Tab Bar destinations in this checkout. registers the actual mode routes (including Calendar, Tasks, Notes, Files, Photos, Mail, Money, Analytics and Ask); Settings is routed under , and Search is a shared surface. The baseline will exercise the real enabled Tab Bar entries with actual tab clicks and report Settings/Search separately as outside the Tab-switch matrix. No app behavior has changed yet. The measurement harness is committed as 1e37c8f87; bun test v1.4.2 (744846f84) passed 6 tests with 0 failures.

Code inspection confirms that Settings and Search are not Tab Bar destinations in this checkout. registers the actual mode routes (including Calendar, Tasks, Notes, Files, Photos, Mail, Money, Analytics and Ask); Settings is routed under , and Search is a shared surface. The baseline will exercise the real enabled Tab Bar entries with actual tab clicks and report Settings/Search separately as outside the Tab-switch matrix. No app behavior has changed yet. The measurement harness is committed as 1e37c8f87; bun test v1.4.2 (744846f84) passed 6 tests with 0 failures.
Author
Owner

Correction to my previous progress comment: the shell stripped inline code spans from the paths and test command. The checked-in navigation registry at apps/web/src/lib/navigation.ts provides the actual Tab Bar destinations: Calendar, Tasks, Notes, Files, Photos, Mail, Analytics, Money, and Ask. Settings is reached through /settings routes, and Search is a shared surface. The baseline will exercise every enabled Tab Bar destination with real clicks; I will list Settings and Search as out of scope for a Tab-switch pair matrix. No app behavior has changed yet. The measurement harness is committed as 1e37c8f87. The focused command bun test bench/tab-switch.test.mjs passed 6 tests with 0 failures.

Correction to my previous progress comment: the shell stripped inline code spans from the paths and test command. The checked-in navigation registry at apps/web/src/lib/navigation.ts provides the actual Tab Bar destinations: Calendar, Tasks, Notes, Files, Photos, Mail, Analytics, Money, and Ask. Settings is reached through /settings routes, and Search is a shared surface. The baseline will exercise every enabled Tab Bar destination with real clicks; I will list Settings and Search as out of scope for a Tab-switch pair matrix. No app behavior has changed yet. The measurement harness is committed as 1e37c8f87. The focused command bun test bench/tab-switch.test.mjs passed 6 tests with 0 failures.
Author
Owner

The full benchmark fixture did not reach a first Tab pair during a 49 minute 45 second leased setup. The remote server was still processing the 100,000-File and 50,000-Photo fixture (server RSS about 854 MB; Home DB data about 903 MB at the last check). I stopped the server before the one-hour perf-run cap and confirmed the lease is free. No timing sample was collected from this attempt.

I added a bounded medium fixture for the matrix: 5,000 Files, 5,000 Photos, the existing 5,000 Notes, 2,000 Tasks, 20,000 transactions and three Calendar years. This matches the 5,000-File scale stated in the prior #1124 baseline and leaves enough lease time for five samples per ordered pair. The focused harness suite now passes 7 tests with 0 failures. I will continue with this fixture; app behavior is unchanged.

The full benchmark fixture did not reach a first Tab pair during a 49 minute 45 second leased setup. The remote server was still processing the 100,000-File and 50,000-Photo fixture (server RSS about 854 MB; Home DB data about 903 MB at the last check). I stopped the server before the one-hour perf-run cap and confirmed the lease is free. No timing sample was collected from this attempt. I added a bounded medium fixture for the matrix: 5,000 Files, 5,000 Photos, the existing 5,000 Notes, 2,000 Tasks, 20,000 transactions and three Calendar years. This matches the 5,000-File scale stated in the prior #1124 baseline and leaves enough lease time for five samples per ordered pair. The focused harness suite now passes 7 tests with 0 failures. I will continue with this fixture; app behavior is unchanged.
Author
Owner

The reused benchmark harness still awaits waitForLinkNavigationFixture unconditionally, even when invoked with --no-linknav. That gate indexes the #639 search/tag/note fixture, which is outside this issue's Tab-switch matrix. In the current 5k-Files run, the server had been indexing for over 12 minutes without reaching the first switch sample. I am checking whether this gate is holding setup; if confirmed, I will skip it only for --no-linknav runs.

The reused benchmark harness still awaits `waitForLinkNavigationFixture` unconditionally, even when invoked with `--no-linknav`. That gate indexes the #639 search/tag/note fixture, which is outside this issue's Tab-switch matrix. In the current 5k-Files run, the server had been indexing for over 12 minutes without reaching the first switch sample. I am checking whether this gate is holding setup; if confirmed, I will skip it only for `--no-linknav` runs.
Author
Owner

The files fixture reached fixture ready after about 15 minutes and exposed an instrumentation defect before the first matrix sample: readDestinationIdentity failed with Calendar first visit paints real content identity. Its selector only covered Agenda/Month-style .item rows; the current default week view uses the shared TimeGrid .block .b-title rows. The failed run saved artifacts/tabswitch-1233/baseline/chromium-1440-light-failure.png. I am adding selectors for the rendered Calendar views and a focused selector test, then I will retry the baseline.

The `files` fixture reached `fixture ready` after about 15 minutes and exposed an instrumentation defect before the first matrix sample: `readDestinationIdentity` failed with `Calendar first visit paints real content identity`. Its selector only covered Agenda/Month-style `.item` rows; the current default week view uses the shared TimeGrid `.block .b-title` rows. The failed run saved `artifacts/tabswitch-1233/baseline/chromium-1440-light-failure.png`. I am adding selectors for the rendered Calendar views and a focused selector test, then I will retry the baseline.
Author
Owner

The app's real Tab Bar exposes role=tab entries for mode routes. In the fixture, the available entries were Calendar, Tasks, Notes, Files, Photos, Mail and Ask; Analytics and Money were absent because their optional plugins were off. The Settings action is a route, and Search is a shared overlay; neither is a Tab Bar item or has a Tab selection identity. I enabled Money through the existing User plugin setting for the next run. I will measure all enabled mode Tabs and report Settings/Search as outside the Tab-switch surface instead of fabricating Tab entries.

The app's real Tab Bar exposes `role=tab` entries for mode routes. In the fixture, the available entries were Calendar, Tasks, Notes, Files, Photos, Mail and Ask; Analytics and Money were absent because their optional plugins were off. The Settings action is a route, and Search is a shared overlay; neither is a Tab Bar item or has a Tab selection identity. I enabled Money through the existing User plugin setting for the next run. I will measure all enabled mode Tabs and report Settings/Search as outside the Tab-switch surface instead of fabricating Tab entries.
Author
Owner

The content-wait diagnostic showed that the Calendar route is present and has 140 matching grid title nodes, but every title node is inside the shared grid's aria-hidden visual layer. The accessible row is the parent .block[aria-label] button. I will key Calendar identity from that real control label, while retaining Month/Agenda rows; this avoids counting hidden decorative text.

The content-wait diagnostic showed that the Calendar route is present and has 140 matching grid title nodes, but every title node is inside the shared grid's `aria-hidden` visual layer. The accessible row is the parent `.block[aria-label]` button. I will key Calendar identity from that real control label, while retaining Month/Agenda rows; this avoids counting hidden decorative text.
Author
Owner

The local 100-item smoke confirmed the Calendar route content was present: /calendar/week/2026-10-05 had 140 grid title nodes and 4,248 visible text characters. Those title nodes are under aria-hidden="true"; the actual accessible buttons expose the content labels. The harness now selects .calendar .block[aria-label], preserves Month/Agenda rows, and the focused harness suite passes (10 tests). Code change: 924a55a9b. The retry is waiting for the single-tenant perf lease.

The local 100-item smoke confirmed the Calendar route content was present: `/calendar/week/2026-10-05` had 140 grid title nodes and 4,248 visible text characters. Those title nodes are under `aria-hidden="true"`; the actual accessible buttons expose the content labels. The harness now selects `.calendar .block[aria-label]`, preserves Month/Agenda rows, and the focused harness suite passes (10 tests). Code change: `924a55a9b`. The retry is waiting for the single-tenant perf lease.
Author
Owner

The supplied prior-reference path docs/perf/2026-10-06-1124-round2.md and its 2026-10-06-1124-round2-warm-tabs.json artifact are not present in this checkout. The checked-in docs/perf/2026-10-06-1124.md states that the earlier warm Tab profile failed before it produced valid JSON. I will preserve the issue-provided 747/995 ms Calendar→Files figures as the historical reference and report the first complete matrix measured in this job separately.

The supplied prior-reference path `docs/perf/2026-10-06-1124-round2.md` and its `2026-10-06-1124-round2-warm-tabs.json` artifact are not present in this checkout. The checked-in `docs/perf/2026-10-06-1124.md` states that the earlier warm Tab profile failed before it produced valid JSON. I will preserve the issue-provided 747/995 ms Calendar→Files figures as the historical reference and report the first complete matrix measured in this job separately.
Author
Owner

The corrected identity selector passes a local fallback run with the same 5k-Files/366-Log fixture. Its single diagnostic pair sample confirms instrumentation: Files→Calendar recorded 291.2 ms and Calendar→Files 361.4 ms locally, with route-root teardown/remount and 4/2 critical-path requests. These are local debug values without HDD emulation and are not the baseline; the perf VM is now free for the qualifying matrix.

The corrected identity selector passes a local fallback run with the same 5k-Files/366-Log fixture. Its single diagnostic pair sample confirms instrumentation: Files→Calendar recorded 291.2 ms and Calendar→Files 361.4 ms locally, with route-root teardown/remount and 4/2 critical-path requests. These are local debug values without HDD emulation and are not the baseline; the perf VM is now free for the qualifying matrix.
Author
Owner

The full baseline reached fixture readiness on the perf VM with 8 available Tabs and all server probes returning 200. It then stopped before the first visit because the harness's setThemeContext write to PUT /api/v1/appearance timed out after 30 seconds; the payload was 32 bytes. No switch sample was recorded, and the benchmark cleanup released its lease. This is setup overhead, not a measured Tab result. I am checking whether the request path is blocked by the emulated server before retrying.

The full baseline reached fixture readiness on the perf VM with 8 available Tabs and all server probes returning 200. It then stopped before the first visit because the harness's `setThemeContext` write to `PUT /api/v1/appearance` timed out after 30 seconds; the payload was 32 bytes. No switch sample was recorded, and the benchmark cleanup released its lease. This is setup overhead, not a measured Tab result. I am checking whether the request path is blocked by the emulated server before retrying.
Author
Owner

The retry with --no-linknav still needed about 16 minutes to reach fixture readiness, close to the prior 15-minute run. Skipping the unrelated #639 readiness poll is correct for a Tab-only profile, but it did not materially change the total setup delay; I do not attribute the indexing duration to that gate.

The retry with `--no-linknav` still needed about 16 minutes to reach fixture readiness, close to the prior 15-minute run. Skipping the unrelated #639 readiness poll is correct for a Tab-only profile, but it did not materially change the total setup delay; I do not attribute the indexing duration to that gate.
Author
Owner

The timeout came from the shared setThemeContext helper using Playwright's default 30-second API request limit with no caller override. I added an optional requestTimeoutMs parameter (30 seconds by default) and set it to 120 seconds only for this perf profile. This write prepares the test User's theme before navigation and is outside the measured switch interval. The focused suite passes: bun test bench/tab-switch.test.mjs — 11 pass, 0 fail, 37 assertions. Commit: 3be77fc79.

The timeout came from the shared `setThemeContext` helper using Playwright's default 30-second API request limit with no caller override. I added an optional `requestTimeoutMs` parameter (30 seconds by default) and set it to 120 seconds only for this perf profile. This write prepares the test User's theme before navigation and is outside the measured switch interval. The focused suite passes: `bun test bench/tab-switch.test.mjs` — 11 pass, 0 fail, 37 assertions. Commit: `3be77fc79`.
Author
Owner

The requested bench/interaction-1124.mjs file is absent in this checkout. I extended the existing bench/tab-switch.mjs harness used by the warm route profile, rather than creating a second benchmark harness. The change keeps its real server fixture, Tab clicks and trace tooling while adding #1233's identity and frame metrics.

The requested `bench/interaction-1124.mjs` file is absent in this checkout. I extended the existing `bench/tab-switch.mjs` harness used by the warm route profile, rather than creating a second benchmark harness. The change keeps its real server fixture, Tab clicks and trace tooling while adding #1233's identity and frame metrics.
Author
Owner

Baseline restart: the earlier perf-VM profile reached fixture readiness, then timed out while applying the benchmark-only appearance theme before any Tab-switch sample. I extended the shared e2e theme helper with an optional setup request timeout and enabled 120 seconds for this profile; all existing callers retain the 30-second default. The focused harness suite passes (11 tests). Restarting the same 5-sample, 8-Tab profile under a fresh 60-minute perf-run lease. No application behavior has changed.

Baseline restart: the earlier perf-VM profile reached fixture readiness, then timed out while applying the benchmark-only appearance theme before any Tab-switch sample. I extended the shared e2e theme helper with an optional setup request timeout and enabled 120 seconds for this profile; all existing callers retain the 30-second default. The focused harness suite passes (11 tests). Restarting the same 5-sample, 8-Tab profile under a fresh 60-minute perf-run lease. No application behavior has changed.
Author
Owner

Tab storm profile status (#1338)

Branch job/tabstorm-1338; head a7691f609. The profile is in bench/tab-storm.mjs; the method and status are in docs/perf/2026-10-07-1338-tabstorm.md.

No storm measurements were captured. At 10:38 UTC, perf-run status showed tabswitch-1233 holding the perf VM lock from 10:33:15 UTC with a 60 minute reservation, through 11:33:15 UTC. That was past this job's 11:25 UTC limit. Earlier setup-only attempts stopped at Photos Index readiness. The final queued attempt was stopped before lock acquisition. The directory-wide Photos readiness change remains unverified end to end. No storm, warm-pair run, CSV, Chrome trace, heap snapshot, or measured cause exists. No performance-cause issue was filed because no budget failure was measured.

Condition Warm ordered pairs 120 s storms p50 / p95 / max Post-GC growth Result
CPU ×4 0 / 72 0 / 3 Not measured Not measured Perf VM lock was reserved past job limit
Native CPU 0 / 72 0 / 3 Not measured Not measured Perf VM lock was reserved past job limit

Post-merge web gates:

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22544 scoped exceptions
svelte-check found 0 errors and 1 warning in 1 file
 Test Files  287 passed (287)
      Tests  2036 passed (2036)
   Duration  437.22s (environment 27%, transform 26%, import 24%, tests 17%, setup 6%)

One AttachmentDeck.svelte empty CSS ruleset warning was reported by bun run check; both commands exited 0. The historical 316 ms / 594 ms profile is not Tab storm data. Numeric choices are documented in the report: idle means at most 5% main-thread busy at the one-second sample, and a server climb means three strictly increasing post-storm CPU or RSS samples.

Tab storm profile status (#1338) Branch `job/tabstorm-1338`; head `a7691f609`. The profile is in `bench/tab-storm.mjs`; the method and status are in `docs/perf/2026-10-07-1338-tabstorm.md`. No storm measurements were captured. At 10:38 UTC, `perf-run status` showed `tabswitch-1233` holding the perf VM lock from 10:33:15 UTC with a 60 minute reservation, through 11:33:15 UTC. That was past this job's 11:25 UTC limit. Earlier setup-only attempts stopped at Photos Index readiness. The final queued attempt was stopped before lock acquisition. The directory-wide Photos readiness change remains unverified end to end. No storm, warm-pair run, CSV, Chrome trace, heap snapshot, or measured cause exists. No performance-cause issue was filed because no budget failure was measured. | Condition | Warm ordered pairs | 120 s storms | p50 / p95 / max | Post-GC growth | Result | | --- | ---: | ---: | --- | --- | --- | | CPU ×4 | 0 / 72 | 0 / 3 | Not measured | Not measured | Perf VM lock was reserved past job limit | | Native CPU | 0 / 72 | 0 / 3 | Not measured | Not measured | Perf VM lock was reserved past job limit | Post-merge web gates: ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22544 scoped exceptions svelte-check found 0 errors and 1 warning in 1 file Test Files 287 passed (287) Tests 2036 passed (2036) Duration 437.22s (environment 27%, transform 26%, import 24%, tests 17%, setup 6%) ``` One `AttachmentDeck.svelte` empty CSS ruleset warning was reported by `bun run check`; both commands exited 0. The historical 316 ms / 594 ms profile is not Tab storm data. Numeric choices are documented in the report: idle means at most 5% main-thread busy at the one-second sample, and a server climb means three strictly increasing post-storm CPU or RSS samples.
Author
Owner

Baseline finding: the 5,000-File seed completed and all eight enabled mode routes loaded, but no switch samples were collected because the harness rejected Ask's real empty state. The saved DOM diagnostics show /ask, selected Ask Tab, .ask-page, 0 matches for the current row selector, 0 headings, and 56 visible text characters. TurnHistory.svelte renders its settled empty state as .turn-history .note, which the identity selector omitted. This is a benchmark identity bug, not an application loading failure. Failure screenshot and incomplete JSON are in the worktree under artifacts/tabswitch-1233/baseline and target/tmp; I will add the settled empty-state identity and rerun.

Baseline finding: the 5,000-File seed completed and all eight enabled mode routes loaded, but no switch samples were collected because the harness rejected Ask's real empty state. The saved DOM diagnostics show `/ask`, selected Ask Tab, `.ask-page`, 0 matches for the current row selector, 0 headings, and 56 visible text characters. `TurnHistory.svelte` renders its settled empty state as `.turn-history .note`, which the identity selector omitted. This is a benchmark identity bug, not an application loading failure. Failure screenshot and incomplete JSON are in the worktree under `artifacts/tabswitch-1233/baseline` and `target/tmp`; I will add the settled empty-state identity and rerun.
Author
Owner

The first seeded run (fixture ready in about 14 minutes) stopped before pair sampling because the Ask empty-state selector was missing. I added .turn-history:not([aria-busy="true"]) .note so the benchmark waits for settled real Ask content while excluding its loading state, and added a focused test. bun test bench/tab-switch.test.mjs passes: 12 pass, 0 fail, 38 expect() calls. Restarting the unchanged 5-sample matrix under a fresh 60-minute perf-run lease.

The first seeded run (fixture ready in about 14 minutes) stopped before pair sampling because the Ask empty-state selector was missing. I added `.turn-history:not([aria-busy="true"]) .note` so the benchmark waits for settled real Ask content while excluding its loading state, and added a focused test. `bun test bench/tab-switch.test.mjs` passes: 12 pass, 0 fail, 38 expect() calls. Restarting the unchanged 5-sample matrix under a fresh 60-minute `perf-run` lease.
Author
Owner

Second baseline finding: the corrected Ask identity passes its direct first visit; the checkpoint JSON records all eight first-load samples, including Ask at 2,485 ms. The run then stopped during warm-up before the pair matrix. readDestinationIdentity timed out waiting for the selected tab-<id> state after selectMode; the saved page shows the Tasks view selected. This failure path did not include the requested Tab id or current Tab DOM state, so I will add those diagnostics before the next run. No warm-pair sample was collected.

Second baseline finding: the corrected Ask identity passes its direct first visit; the checkpoint JSON records all eight first-load samples, including Ask at 2,485 ms. The run then stopped during warm-up before the pair matrix. `readDestinationIdentity` timed out waiting for the selected `tab-<id>` state after `selectMode`; the saved page shows the Tasks view selected. This failure path did not include the requested Tab id or current Tab DOM state, so I will add those diagnostics before the next run. No warm-pair sample was collected.
Author
Owner

Diagnostic finding (local fallback, not a perf-VM baseline): with the same 5,000-File fixture and the Mac Chromium profile, one warm Calendar→Tasks sample took 417 ms to the second painted frame, with one request before that frame and 124.1 ms attributed to old-view teardown/new-view mount. Tasks→Calendar took 523.6 ms, with five requests and 290.4 ms teardown/mount. This supports the route remount hypothesis but is not used as a performance result because the local profile has no HDD emulation and only one sample per direction. The local JSON is in target/tmp/tabswitch-1233-local-debug-2.json.

Diagnostic finding (local fallback, not a perf-VM baseline): with the same 5,000-File fixture and the Mac Chromium profile, one warm Calendar→Tasks sample took 417 ms to the second painted frame, with one request before that frame and 124.1 ms attributed to old-view teardown/new-view mount. Tasks→Calendar took 523.6 ms, with five requests and 290.4 ms teardown/mount. This supports the route remount hypothesis but is not used as a performance result because the local profile has no HDD emulation and only one sample per direction. The local JSON is in `target/tmp/tabswitch-1233-local-debug-2.json`.
Author
Owner

Warm-up finding: a local, one-sample, all-mode run completed first visits for all eight enabled Tabs, then timed out switching to Ask from /tasks. Timeout diagnostics show tab-ai remained aria-selected=false and tab-tasks remained selected; the direct Ask first visit passed. This is distinct from the Ask empty-state selector issue and blocks the warm-pair matrix from reaching Ask. I added same-origin resource path/status and aria-disabled fields to this timeout report so the next focused run can tell whether the click is blocked or the client navigation fails. The run has no performance value (local fallback, no HDD emulation); its incomplete JSON is in target/tmp/tabswitch-1233-local-full-debug.json.

Warm-up finding: a local, one-sample, all-mode run completed first visits for all eight enabled Tabs, then timed out switching to Ask from `/tasks`. Timeout diagnostics show `tab-ai` remained `aria-selected=false` and `tab-tasks` remained selected; the direct Ask first visit passed. This is distinct from the Ask empty-state selector issue and blocks the warm-pair matrix from reaching Ask. I added same-origin resource path/status and `aria-disabled` fields to this timeout report so the next focused run can tell whether the click is blocked or the client navigation fails. The run has no performance value (local fallback, no HDD emulation); its incomplete JSON is in `target/tmp/tabswitch-1233-local-full-debug.json`.
Author
Owner

Perf VM setup evidence for the current #1233 baseline run: HDD emulation qualified under the perf-run lease. At queue depth 1: 124.57 IOPS, p50 7.96 ms, p99 8.36 ms, lock-held load average [1.85, 1.27, 1.26]. At queue depth 16: 200.79 IOPS, p50 95.94 ms, p99 125.30 ms, load average [1.53, 1.24, 1.25]. This is storage-profile qualification, not Tab-switch latency. The 5,000-file fixture is still being seeded and indexed.

Perf VM setup evidence for the current #1233 baseline run: HDD emulation qualified under the `perf-run` lease. At queue depth 1: 124.57 IOPS, p50 7.96 ms, p99 8.36 ms, lock-held load average [1.85, 1.27, 1.26]. At queue depth 16: 200.79 IOPS, p50 95.94 ms, p99 125.30 ms, load average [1.53, 1.24, 1.25]. This is storage-profile qualification, not Tab-switch latency. The 5,000-file fixture is still being seeded and indexed.
Author
Owner

Preliminary qualifying HDD warm-switch evidence (matrix still running; five samples per ordered pair not complete): Tasks→Money 376.3 ms, Mail→Notes 365.9 ms, Mail→Calendar 781.3 ms from the real click to the second painted frame. All three show a detached old route root and an inserted new route root; Calendar's teardown and mount markers both occurred at 494.2 ms. These are not final p50/p95 values. The full pair table and worst-pair Chrome traces will determine the fix.

Preliminary qualifying HDD warm-switch evidence (matrix still running; five samples per ordered pair not complete): Tasks→Money 376.3 ms, Mail→Notes 365.9 ms, Mail→Calendar 781.3 ms from the real click to the second painted frame. All three show a detached old route root and an inserted new route root; Calendar's teardown and mount markers both occurred at 494.2 ms. These are not final p50/p95 values. The full pair table and worst-pair Chrome traces will determine the fix.
Author
Owner

The first full matrix attempt is incomplete and is not baseline data. It completed three warm samples, then Money→Ask timed out in targetMounted: the real click returned, but the route did not match /ask within 120 s. Its saved failure screenshot shows the Tasks route selected. The harness did not save pathname/Tab-selection state at this exact wait, so I am adding that diagnostic and will reproduce the narrow pair before proceeding to app changes.

The first full matrix attempt is incomplete and is not baseline data. It completed three warm samples, then Money→Ask timed out in `targetMounted`: the real click returned, but the route did not match `/ask` within 120 s. Its saved failure screenshot shows the Tasks route selected. The harness did not save pathname/Tab-selection state at this exact wait, so I am adding that diagnostic and will reproduce the narrow pair before proceeding to app changes.
Author
Owner

Narrow local smoke reproduced Money→Ask route failure (diagnostic only; no latency numbers used): after the click, pathname was /tasks, tab-ai was aria-selected=false, and tab-tasks was selected. The last same-origin route resources were Tasks workspace CSS/module and /api/v1/notes/tasks plus /api/v1/notes/tasks/views; no request or loaded view identified Ask. The first route selection was provisional and did not commit. The updated harness now writes this path/Tab/resource state beside a timeout screenshot.

Narrow local smoke reproduced Money→Ask route failure (diagnostic only; no latency numbers used): after the click, `pathname` was `/tasks`, `tab-ai` was `aria-selected=false`, and `tab-tasks` was selected. The last same-origin route resources were Tasks workspace CSS/module and `/api/v1/notes/tasks` plus `/api/v1/notes/tasks/views`; no request or loaded view identified Ask. The first route selection was provisional and did not commit. The updated harness now writes this path/Tab/resource state beside a timeout screenshot.
Author
Owner

I tightened the existing measurement harness in commits 6635203b5 and cfa69370e. Warm samples now follow closed Tab visit routes, so one real click starts each pair sample; the focused test checks every directed pair appears once per run and routes remain continuous. Content identity now requires a row or the target Tab's own empty-state label. This fixes the harness accepting a generic heading while another route remains mounted. Focused result: 14 pass, 0 fail, 55 expect() calls. The next qualifying run will restart the complete matrix on the perf VM.

I tightened the existing measurement harness in commits `6635203b5` and `cfa69370e`. Warm samples now follow closed Tab visit routes, so one real click starts each pair sample; the focused test checks every directed pair appears once per run and routes remain continuous. Content identity now requires a row or the target Tab's own empty-state label. This fixes the harness accepting a generic heading while another route remains mounted. Focused result: `14 pass, 0 fail, 55 expect() calls`. The next qualifying run will restart the complete matrix on the perf VM.
Author
Owner

The next baseline attempt stopped during Calendar first visit before any switch samples. Its identity selector found 75 Calendar nodes, but the Playwright-serialized page function referenced a module-scope helper that is unavailable after serialization. I added an isolated serialization test and made the browser callback self-contained in commit fcbd23a4d. Focused result: 15 pass, 0 fail, 57 expect() calls. This run is discarded; no switch values were recorded.

The next baseline attempt stopped during Calendar first visit before any switch samples. Its identity selector found 75 Calendar nodes, but the Playwright-serialized page function referenced a module-scope helper that is unavailable after serialization. I added an isolated serialization test and made the browser callback self-contained in commit `fcbd23a4d`. Focused result: `15 pass, 0 fail, 57 expect() calls`. This run is discarded; no switch values were recorded.
Author
Owner

Related follow-up: #1356: Tabs: release detached Calendar page views after Tab switches. The local #1338 storm snapshots retain detached Calendar page contexts and agenda rows after Tab switches. Perf-VM confirmation is queued; I will post its measurements when complete.

Related follow-up: [#1356: Tabs: release detached Calendar page views after Tab switches](https://git.kayg.org/kayg/calternal/issues/1356). The local #1338 storm snapshots retain detached Calendar page contexts and agenda rows after Tab switches. Perf-VM confirmation is queued; I will post its measurements when complete.
Author
Owner

Status: incomplete; work stopped after the shared four-hour job cap had elapsed (the job wrapper showed more than six hours at stop).

Built and committed a benchmark-only improvement in bench/tab-switch.mjs, bench/tab-switch.test.mjs, bench/tab-switch-trace.py, and apps/web/e2e/harness.mjs. The harness now uses a closed warm route schedule, checks Tab-specific real content from a self-contained Playwright predicate, preserves slow samples, and attributes trace work to the switch stages. HEAD: fcbd23a4dfb45bd5a4aafa3eb9d4f3c33852513d.

Phase 1 did not finish. The corrected local smoke was stopped while the fixture server was generating and indexing seeded Home; it produced no latency sample or identity result. Earlier exploratory values (Tasks→Money 376.3 ms, Mail→Notes 365.9 ms, Mail→Calendar 781.3 ms) used the older harness and are not a baseline. The full pair matrix, worst-three traces, baseline document, and complete issue table were not produced.

Phases 2 and 3 did not start. No app behavior changed. There is no warm-switch regression test, before/after profile, heap/RSS result, or complete screenshot set.

Verification evidence recorded before stopping:

bun test bench/tab-switch.test.mjs
15 pass
0 fail
57 expect() calls

node --check bench/tab-switch.mjs passed. git diff --check exited 0 with no output. The final web build, bun run check, focused Vitest, Rust gates, and merge-round suites were not run. Cleanup output:

Removed 9259 files, 3.4GiB total

Decisions made where the brief and current app differed: the live Tab Bar has Calendar, Tasks, Notes, Files, Photos, Mail, Money, and Ask; Settings and Search are routes/actions rather than Tabs, and Analytics is not enabled for this User. I used the existing bench/tab-switch.mjs harness because bench/interaction-1124.mjs is absent from this checkout.

Known gaps: all measurement, trace, app fix, regression, screenshots, and final gates remain. The current branch has three atomic benchmark commits; the worktree is clean. This is not ready for merge.

Status: incomplete; work stopped after the shared four-hour job cap had elapsed (the job wrapper showed more than six hours at stop). Built and committed a benchmark-only improvement in `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`, `bench/tab-switch-trace.py`, and `apps/web/e2e/harness.mjs`. The harness now uses a closed warm route schedule, checks Tab-specific real content from a self-contained Playwright predicate, preserves slow samples, and attributes trace work to the switch stages. HEAD: `fcbd23a4dfb45bd5a4aafa3eb9d4f3c33852513d`. Phase 1 did not finish. The corrected local smoke was stopped while the fixture server was generating and indexing seeded Home; it produced no latency sample or identity result. Earlier exploratory values (Tasks→Money 376.3 ms, Mail→Notes 365.9 ms, Mail→Calendar 781.3 ms) used the older harness and are not a baseline. The full pair matrix, worst-three traces, baseline document, and complete issue table were not produced. Phases 2 and 3 did not start. No app behavior changed. There is no warm-switch regression test, before/after profile, heap/RSS result, or complete screenshot set. Verification evidence recorded before stopping: ```text bun test bench/tab-switch.test.mjs 15 pass 0 fail 57 expect() calls ``` `node --check bench/tab-switch.mjs` passed. `git diff --check` exited 0 with no output. The final web build, `bun run check`, focused Vitest, Rust gates, and merge-round suites were not run. Cleanup output: ```text Removed 9259 files, 3.4GiB total ``` Decisions made where the brief and current app differed: the live Tab Bar has Calendar, Tasks, Notes, Files, Photos, Mail, Money, and Ask; Settings and Search are routes/actions rather than Tabs, and Analytics is not enabled for this User. I used the existing `bench/tab-switch.mjs` harness because `bench/interaction-1124.mjs` is absent from this checkout. Known gaps: all measurement, trace, app fix, regression, screenshots, and final gates remain. The current branch has three atomic benchmark commits; the worktree is clean. This is not ready for merge.
Author
Owner

Starting tabswitch2 on job/tabswitch-1233. Previous head fcbd23a4d; merged origin/dev once, current head 2ead7c546e83ab59b50fe4121f8818af821bbbc9. Investigating route remounts and active-only lifetimes for #1233. Local release measurements will record host load; no Photos indexing or Mail sync wait. No push or deploy.

Starting tabswitch2 on `job/tabswitch-1233`. Previous head `fcbd23a4d`; merged origin/dev once, current head `2ead7c546e83ab59b50fe4121f8818af821bbbc9`. Investigating route remounts and active-only lifetimes for #1233. Local release measurements will record host load; no Photos indexing or Mail sync wait. No push or deploy.
Author
Owner

Code evidence for #1233: root layout rendered one router-owned child. The loading branch also removed that child. Calendar registers a 30-second interval and Files change listeners for its mount lifetime; TimeGrid, AgendaList and FileCollection register observers. The fix adds a three-Tab mounted LRU, frozen committed route context and active-only work lifetimes. Shared additions in packages/ui are required so observers follow the same activity owner. Warm pointer preloads now skip retained Tabs.

Focused boundary regression: Test Files 1 passed (1); Tests 2 passed (2). Svelte diagnostics after the first slice: svelte-check found 0 errors and 1 warning in 1 file (unchanged AttachmentDeck CSS).

Local baseline attempt 1 was incomplete: all Notes/Files/Tasks/Logs seeded, then the shared release server rejected Budget creation with starter Categories (400). No timing samples were counted. Retrying the minimal Budget request, within the 45-minute baseline limit.

Decision: retain read workspaces; keep live editors and Money import sheets router-owned until their write/flush lifetime can pause safely. This is a known scope gap, not a claimed full-Tab adoption.

Code evidence for #1233: root layout rendered one router-owned child. The loading branch also removed that child. Calendar registers a 30-second interval and Files change listeners for its mount lifetime; TimeGrid, AgendaList and FileCollection register observers. The fix adds a three-Tab mounted LRU, frozen committed route context and active-only work lifetimes. Shared additions in packages/ui are required so observers follow the same activity owner. Warm pointer preloads now skip retained Tabs. Focused boundary regression: `Test Files 1 passed (1)`; `Tests 2 passed (2)`. Svelte diagnostics after the first slice: `svelte-check found 0 errors and 1 warning in 1 file` (unchanged AttachmentDeck CSS). Local baseline attempt 1 was incomplete: all Notes/Files/Tasks/Logs seeded, then the shared release server rejected Budget creation with starter Categories (400). No timing samples were counted. Retrying the minimal Budget request, within the 45-minute baseline limit. Decision: retain read workspaces; keep live editors and Money import sheets router-owned until their write/flush lifetime can pause safely. This is a known scope gap, not a claimed full-Tab adoption.
Author
Owner

Implementation is committed in 20559cac2. Three recent audited workspace trees now stay mounted behind a shared active context. SvelteKit still commits URLs, history, and deep links. Hidden trees are hidden, inert, and aria-hidden. Calendar clocks, list observers, workspace event listeners, and portaled overlays pause while hidden. Access changes clear retained trees. The storm-profile branch is merged at b5aa7e056.

The baseline time-box ended after cold Files (1172.7 ms) and Notes (693.5 ms). Tasks did not meet the content selector within two minutes. No warm baseline is available; these partial numbers will not be presented as a complete before/after comparison. The production implementation build passed. Focused host/boundary tests passed: 2 files, 5 tests. A final watcher audit found that TaskIndex kept its Notes watcher alive after its workspace hid; its active-consumer lifetime now cancels that subscription and its queued read, with a focused regression test passing.

Decision: retain the five requested read workspaces first. Live Note/Canvas editors, import recovery, Photos, Mail, and AI keep their current router lifetime until their background/write lifetimes are audited. This scope limit is documented in DESIGN §59 rule 6 and remains a gap against retention of every Tab.

Implementation is committed in 20559cac2. Three recent audited workspace trees now stay mounted behind a shared active context. SvelteKit still commits URLs, history, and deep links. Hidden trees are hidden, inert, and aria-hidden. Calendar clocks, list observers, workspace event listeners, and portaled overlays pause while hidden. Access changes clear retained trees. The storm-profile branch is merged at b5aa7e056. The baseline time-box ended after cold Files (1172.7 ms) and Notes (693.5 ms). Tasks did not meet the content selector within two minutes. No warm baseline is available; these partial numbers will not be presented as a complete before/after comparison. The production implementation build passed. Focused host/boundary tests passed: 2 files, 5 tests. A final watcher audit found that TaskIndex kept its Notes watcher alive after its workspace hid; its active-consumer lifetime now cancels that subscription and its queued read, with a focused regression test passing. Decision: retain the five requested read workspaces first. Live Note/Canvas editors, import recovery, Photos, Mail, and AI keep their current router lifetime until their background/write lifetimes are audited. This scope limit is documented in DESIGN §59 rule 6 and remains a gap against retention of every Tab.
Author
Owner

Review findings for #1233, branch job/tabswitch-1233, head c11f6445d:

  • Real production DOM identity survived warm switches, but the first local run used an October 1 server. Tasks and Money could not settle with that contract. Those results are in artifacts/tabswitch-1233/after-old-server and are not a latency pass.
  • A current shared release binary is available at /home/kayg/build/targets/tabstorm2/release/calternal-server. Its SHA-256 matches the #1338 baseline: 719ad48964424675af2b97935cf2f5f4c03be3554116eed663d91a607f090150. The next comparison uses its real API fixture for both the new web build and its original embedded web assets.
  • Review found stale same-URL Page.state, hidden portal input during outro, delayed Calendar reads publishing after hide, Files afterNavigate handling another Tab, and session checks on every warm critical path. Fixes preserve server authorization and cold session checks; only a known session background probe moves after paint.
  • A hidden interrupted Files first load could stay loading. It now records one retry. Shared resume work runs immediately when no usable content exists, and after paint when the retained content is usable. Empty listings count as usable content.
  • The warm header now accepts the retained workspace URL at route commit. It does not wait for the deep-link overlay probe.

Current local host uptime: 17:13:08 up 4 days 7:55; load average 65.61, 56.40, 54.43. This is a busy-host local measurement, not the perf VM.

Focused regression tests and a production build are running. No Rust change, push, deploy or merge into dev.

Review findings for #1233, branch job/tabswitch-1233, head c11f6445d: - Real production DOM identity survived warm switches, but the first local run used an October 1 server. Tasks and Money could not settle with that contract. Those results are in artifacts/tabswitch-1233/after-old-server and are not a latency pass. - A current shared release binary is available at /home/kayg/build/targets/tabstorm2/release/calternal-server. Its SHA-256 matches the #1338 baseline: 719ad48964424675af2b97935cf2f5f4c03be3554116eed663d91a607f090150. The next comparison uses its real API fixture for both the new web build and its original embedded web assets. - Review found stale same-URL Page.state, hidden portal input during outro, delayed Calendar reads publishing after hide, Files afterNavigate handling another Tab, and session checks on every warm critical path. Fixes preserve server authorization and cold session checks; only a known session background probe moves after paint. - A hidden interrupted Files first load could stay loading. It now records one retry. Shared resume work runs immediately when no usable content exists, and after paint when the retained content is usable. Empty listings count as usable content. - The warm header now accepts the retained workspace URL at route commit. It does not wait for the deep-link overlay probe. Current local host uptime: 17:13:08 up 4 days 7:55; load average 65.61, 56.40, 54.43. This is a busy-host local measurement, not the perf VM. Focused regression tests and a production build are running. No Rust change, push, deploy or merge into dev.
Author
Owner

Tab storm latency from #1338

This profile used the production build, one Chromium page with macOS platform emulation, 1440×900 desktop, a 15 second 390 px touch phase in each 120-second storm, and nine warmed destinations. It measured both 4× CPU throttle and native on the perf VM through perf-run.

The supplementary warm-pair sweep timed out at Calendar→Files after two samples per condition. It therefore does not complete the 72 ordered source-target pairs and must not be treated as a full warm-path result.

Condition Measured warm pairs p50 p95 / max Status
4× CPU Calendar→Tasks and Calendar→Notes, 2 samples 1,373.6 ms 1,995 ms Incomplete; Calendar→Files timed out at 15 s
Native Calendar→Tasks and Calendar→Notes, 2 samples 323.1 ms 389.2 ms Incomplete; Calendar→Files timed out at 15 s

Final storm selection

Condition Storm 1 Storm 2 Storm 3 Correct selected content by 1 s
4× CPU Photos, 1,212.9 ms Files, 719.8 ms Files, 1,057.3 ms Yes, all three
Native Tasks, 109.6 ms Tasks, 213.8 ms Notes, 184.5 ms Yes, all three

These are final-input-to-second-paint timings inside the storms. The stricter final-content-within-100-ms assertion failed in all six storms. The selected Tab's own seeded content appeared by the next one-second sample; the profile did not observe a different Tab's content after target paint.

The perf-VM lock load at start was 0.03 1.18 2.60 for CPU4 and 6.04 5.28 4.08 for native. The corresponding one-minute load p50/p95/max values were 5.75/6.50/6.79 and 5.10/5.71/5.82. The native host started loaded.

Condition-level CPU and long-task p50/p95/max across 359 one-second samples:

Condition Renderer CPU Long-task duration Switch long-task duration Idle within 1 s after storms
4× CPU 112.99% / 131.99% / 181.51% 140 / 844 / 1,720 ms 0 / 405 / 899 ms No, all three
Native 37.00% / 87.99% / 153.98% 51 / 171 / 323 ms 0 / 111 / 313 ms No, all three

The repeated full Task reads are tracked in #1376; File/Photos image request concurrency in #1377; detached Calendar and Mail trees in #1356 and #1375. Server RSS growth and unexpected Composer dialogs are tracked in #1378 and #1380. Full tables and committed raw rows are in docs/perf/2026-10-07-1338-tabstorm.md and docs/perf/runs/2026-10-07-1338-tabstorm* on the job branch.

## Tab storm latency from #1338 This profile used the production build, one Chromium page with macOS platform emulation, 1440×900 desktop, a 15 second 390 px touch phase in each 120-second storm, and nine warmed destinations. It measured both 4× CPU throttle and native on the perf VM through `perf-run`. The supplementary warm-pair sweep timed out at Calendar→Files after two samples per condition. It therefore does not complete the 72 ordered source-target pairs and must not be treated as a full warm-path result. | Condition | Measured warm pairs | p50 | p95 / max | Status | | --- | ---: | ---: | ---: | --- | | 4× CPU | Calendar→Tasks and Calendar→Notes, 2 samples | 1,373.6 ms | 1,995 ms | Incomplete; Calendar→Files timed out at 15 s | | Native | Calendar→Tasks and Calendar→Notes, 2 samples | 323.1 ms | 389.2 ms | Incomplete; Calendar→Files timed out at 15 s | ### Final storm selection | Condition | Storm 1 | Storm 2 | Storm 3 | Correct selected content by 1 s | | --- | --- | --- | --- | --- | | 4× CPU | Photos, 1,212.9 ms | Files, 719.8 ms | Files, 1,057.3 ms | Yes, all three | | Native | Tasks, 109.6 ms | Tasks, 213.8 ms | Notes, 184.5 ms | Yes, all three | These are final-input-to-second-paint timings inside the storms. The stricter final-content-within-100-ms assertion failed in all six storms. The selected Tab's own seeded content appeared by the next one-second sample; the profile did not observe a different Tab's content after target paint. The perf-VM lock load at start was `0.03 1.18 2.60` for CPU4 and `6.04 5.28 4.08` for native. The corresponding one-minute load p50/p95/max values were 5.75/6.50/6.79 and 5.10/5.71/5.82. The native host started loaded. Condition-level CPU and long-task p50/p95/max across 359 one-second samples: | Condition | Renderer CPU | Long-task duration | Switch long-task duration | Idle within 1 s after storms | | --- | --- | --- | --- | --- | | 4× CPU | 112.99% / 131.99% / 181.51% | 140 / 844 / 1,720 ms | 0 / 405 / 899 ms | No, all three | | Native | 37.00% / 87.99% / 153.98% | 51 / 171 / 323 ms | 0 / 111 / 313 ms | No, all three | The repeated full Task reads are tracked in [#1376](https://git.kayg.org/kayg/calternal/issues/1376); File/Photos image request concurrency in [#1377](https://git.kayg.org/kayg/calternal/issues/1377); detached Calendar and Mail trees in [#1356](https://git.kayg.org/kayg/calternal/issues/1356) and [#1375](https://git.kayg.org/kayg/calternal/issues/1375). Server RSS growth and unexpected Composer dialogs are tracked in [#1378](https://git.kayg.org/kayg/calternal/issues/1378) and [#1380](https://git.kayg.org/kayg/calternal/issues/1380). Full tables and committed raw rows are in `docs/perf/2026-10-07-1338-tabstorm.md` and `docs/perf/runs/2026-10-07-1338-tabstorm*` on the job branch.
Author
Owner

Progress at b39394e05: the three-entry workspace LRU and its sidebar follower are implemented. Warm DOM identity, hidden inert/aria-hidden boundaries, route snapshot stability, active-only document title and activity cleanup have focused regression tests (3 files, 15 tests passed). The final production build passed: Compressed 921 static variants; saved 21523892 bytes.

The first complete 40-sample run exposed a second remount: the mode sidebar still replaced its explorer tree on every switch. Files→Notes and Tasks→Notes each started /api/v1/notes and /api/v1/files/entries during the measured frame window. Commit 9935fb743 makes the sidebar follow the workspace's existing three-entry LRU, with no separate cache. Commit b39394e05 fences hidden sidebar reads and old folder workers, and avoids repeating a settled Money entry check.

Earlier complete warm timings did not meet 100 ms (p95 2022–5388 ms on this shared host). Those are pre-sidebar-fix results, not a passing claim. A final paired original-router/retained-build measurement, screenshots and the imported #1338 storm profile are next. The original #1338 web build and server were removed by that job's cleanup; a private copy of the running merge-round release server is now used for both web versions. Its SHA-256 is 8dac09660d5dbeebaab69d0b4699a56e5385bfc04e0a82db62d77c93d16fa2f6. The baseline uses its embedded production router; the after run uses this branch's production web assets.

Scope decision: Calendar, Files, Notes, Tasks and Money workspaces are audited for retention. Editors, import routes and Photos/Mail/Ask keep their existing router-owned lifetime. This is a known gap, not an assertion that every Tab is retained. No Rust source changed; no push or deploy.

Progress at b39394e05: the three-entry workspace LRU and its sidebar follower are implemented. Warm DOM identity, hidden inert/aria-hidden boundaries, route snapshot stability, active-only document title and activity cleanup have focused regression tests (3 files, 15 tests passed). The final production build passed: `Compressed 921 static variants; saved 21523892 bytes.` The first complete 40-sample run exposed a second remount: the mode sidebar still replaced its explorer tree on every switch. Files→Notes and Tasks→Notes each started `/api/v1/notes` and `/api/v1/files/entries` during the measured frame window. Commit 9935fb743 makes the sidebar follow the workspace's existing three-entry LRU, with no separate cache. Commit b39394e05 fences hidden sidebar reads and old folder workers, and avoids repeating a settled Money entry check. Earlier complete warm timings did not meet 100 ms (p95 2022–5388 ms on this shared host). Those are pre-sidebar-fix results, not a passing claim. A final paired original-router/retained-build measurement, screenshots and the imported #1338 storm profile are next. The original #1338 web build and server were removed by that job's cleanup; a private copy of the running merge-round release server is now used for both web versions. Its SHA-256 is `8dac09660d5dbeebaab69d0b4699a56e5385bfc04e0a82db62d77c93d16fa2f6`. The baseline uses its embedded production router; the after run uses this branch's production web assets. Scope decision: Calendar, Files, Notes, Tasks and Money workspaces are audited for retention. Editors, import routes and Photos/Mail/Ask keep their existing router-owned lifetime. This is a known gap, not an assertion that every Tab is retained. No Rust source changed; no push or deploy.
Author
Owner

Partial delivery — NOT READY FOR MERGE

The four-hour limit is reached. The app now has retained Tab workspaces, rather than only benchmark fixtures. The required final performance and browser proof is incomplete. Do not merge this branch on this report.

Branch: job/tabswitch-1233. Head: a469644be1. Initial origin/dev merge: 2ead7c546. Final fetch and merge at 16:21 was already up to date. Authorized job/tabstorm-1338 merge: b5aa7e056. No push or deploy. No Rust changes or new dependencies.

What was built

TabHost keeps the last three audited Tab workspaces mounted. TAB_VIEW_LIMIT is the sole bound. SvelteKit remains the route source of truth. Each retained workspace has a stable page snapshot, so inactive workspaces do not react to another Tab's URL. A warm navigation reuses its workspace DOM. LRU eviction unmounts the oldest workspace. The sidebar follows the same retained-view owner and pauses when physically hidden.

RetainedTab hides inactive content with hidden, inert, aria-hidden and content-visibility. It restores focus and scroll on return. The shared viewActivity context stops timers, observers, stream consumers, requests and portal handlers on hide. Hidden changes coalesce into one refresh on resume. First loading does not wait for motion. Session end and access loss clear the retained views.

Calendar, Files, Notes, Tasks and Money use the boundary on audited read routes. Photos, Mail, Ask, editors and imports remain router-owned. Shared Calendar, Files, Menu, FloatingSurface, PopoverSurface, OverlaySurface, Inspector and TagFilterChips primitives own their lifecycle changes. Existing AppSidebar, SidebarLinks, NotesExplorer, FilesSidebar, TasksSidebar and MoneySidebar controls are reused; no local restyling was added.

Notes folder reads use four workers with cancellation epochs. TaskIndex has an active-consumer reference count. Header ownership now compares the stored reactive proxy, and releases only the owner's header on hide or unmount. Focused tests caught and verified that fix.

DESIGN §59 rule 6 documents the boundary. Tests cover DOM reuse, hidden accessibility state, route snapshots, back/forward, deep links, access loss, sidebar lifetime and header ownership. The performance scanner now proves an immutable literal array slice bound. Exact exception entries were refreshed without raising a budget.

Expected warm cost: at most three cache entries plus the destination's existing layout and paint. A clean retained workspace does not remount or await a data fetch. A dirty workspace schedules one bounded refresh after paint. These cost claims still need final production measurements.

Gate output, verbatim

Final production build, exit 0 (final-build-9.log):

Compressed 921 static variants; saved 21524408 bytes.

Last complete successful check predates the final header/test-fixture changes (final-check-5.log):

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22544 scoped exceptions
svelte-check found 0 errors and 1 warning in 1 file

The next check found two new test-fixture PageChrome.onaction type errors. Both were fixed in a469644be. Final-check-7 reached the following output, but its type-check stage was stopped at the time cap. It is NOT a completed final check:

perf-lint: PASS; 0 violations; 22544 scoped exceptions

Full bun run test --maxWorkers=2:

 Test Files  4 failed | 286 passed (290)
      Tests  4 failed | 2045 passed (2049)
   Start at  18:48:21
   Duration  1418.04s (environment 29%, transform 25%, import 25%, tests 15%, setup 6%)
error: script "test" exited with code 1

All four failures were Error: Test timed out in 5000ms. Each failing file was rerun alone. Existing expectations were not changed.

VersionHistory rerun still fails:

 Test Files  1 failed (1)
      Tests  1 failed | 2 passed (3)
   Start at  19:13:59
   Duration  134.17s (transform 79%, import 9%, tests 8%, environment 3%, setup 2%)

SystemGroup rerun:

 Test Files  1 passed (1)
      Tests  4 passed (4)
   Start at  19:16:36
   Duration  72.82s (transform 80%, environment 6%, import 6%, tests 5%, setup 3%)

KeyboardShortcutsCard rerun:

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  19:13:56
   Duration  108.11s (transform 83%, environment 5%, import 5%, tests 5%, setup 2%)

Select rerun:

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  19:15:59
   Duration  38.41s (transform 48%, environment 33%, tests 9%, setup 6%, import 4%)

Focused retained boundary/header regression rerun:

 Test Files  2 passed (2)
      Tests  13 passed (13)
   Start at  19:12:41
   Duration  67.22s (transform 78%, environment 9%, import 6%, tests 5%, setup 2%)

cargo fmt --check exited 0 with no output. No Rust crate changed, so crate clippy/test were not run. cargo clean completed:

Removed 1 file, 356B total

Measurements and known gaps

No valid final <=100 ms result exists. No final cold-load <=10% comparison exists. No final <=25% heap result exists.

The earlier pre-sidebar stage used 200 Notes, 500 Files, four Tasks, four events and one account. Forty warm DOM identity/inert checks passed. Five retained-Tab heap passes were 17,325,756 / 17,172,500 / 17,402,040 / 17,444,996 / 17,498,488 bytes (+1% first to last). This is intermediate evidence, not proof for HEAD.

Intermediate warm p50 / p95=max in ms, five samples each:

Direction p50 p95/max
Calendar → Files 1315.9 2314.3
Files → Calendar 1670.2 2671.0
Files → Notes 2309.8 3285.4
Notes → Files 2024.7 3595.2
Notes → Tasks 1780.5 3282.3
Tasks → Notes 3241.7 5387.6
Tasks → Money 2136.9 2995.7
Money → Tasks 994.0 2022.1

These runs exposed sidebar remount reads and header lifecycle work, which were then fixed. They do not meet the budget. The host load was about 65; exact uptime and raw traces are in the reports. The baseline switch_mode 316/594 ms metric uses a different interaction and is not a direct paired baseline.

A final 200/500 fixture did not finish in the available time. A reduced two-Note/two-File comparison completed 40 native baseline samples but only one after sample. I rebuilt the served assets during this attempt, which invalidated its after samples. The browser also reported effect_update_depth_exceeded. The latest header fix passed focused tests, but the production loop is NOT proved resolved. This is a blocking runtime gap, not a SLOW-only finding.

The first local storm run served the shared server's embedded web assets. CDP showed zero retained boundaries. Its three storms are invalid as after evidence. I corrected bench/tab-storm.mjs to route this worktree's production assets in local mode. The corrected storm was not run before the cap. Invalid data is in artifacts/tabswitch-1233/storm-embedded-web; the imported #1338 baseline files were restored. The copied local server SHA256 is 8dac09660d5dbeebaab69d0b4699a56e5385bfc04e0a82db62d77c93d16fa2f6; its matching source revision is not established.

UX gaps closed

Hidden views and portals leave the keyboard/accessibility tree. Focus and scroll survive retained switches. Hidden work stops, interrupted first reads resume, stale folder workers cannot publish after a rapid hide/show, and inactive views cannot own the visible title/header. Access loss clears cached views. Keyboard and pointer keep the shared motion rules.

UX gaps left

The final production runtime loop needs diagnosis and proof. Photos/Mail/Ask and write routes are not retained. Final pointer/touch/keyboard and screen-reader walks, exact deep-link browser checks, and phone/tablet screenshots are missing. The attached ten images are explicitly the PRE-SIDEBAR 1440 px stage, light and dark, with macOS emulation. They are not final visual evidence. No valid final 390/820/1440 set exists. Visual quality remains for the orchestrator to judge.

Decisions

Reuse the existing +page workspace components through a route-to-workspace host, rather than duplicate their UI. Retain one audited workspace per mode, and replace it when the workspace route class changes. Set the single LRU bound to three. Keep write routes router-owned until their persistence contracts are audited. Use the same owner for sidebar retention. Defer known signed-in warm-route background checks until after paint; keep cold, unknown and access-loss checks immediate. These implementation choices are recorded in module comments and DESIGN §59.

For the next verification / merge round

First diagnose the production effect-update loop. Freeze the production assets for the whole browser run. Use a server build with known matching source/API provenance.

Run the final gates in order:

cd apps/web
bun run build
bun run check
bun run test --maxWorkers=2

Rerun the VersionHistory failing file alone without changing its expectation. From the worktree root, with CALTERNAL_SERVER_BIN set to a known matching release server:

bun bench/tab-retention-1233.mjs after

Use the existing TAB_RETENTION_BASELINE_BUILD option for a paired immutable production baseline. Keep the normal 200-Note/500-File fixture, collect all eight directions with five samples each, both Calendar/Files traces, cold loads and five heap passes. Capture all five Tabs at 390/820/1440, light/dark, macOS emulated.

Then run the corrected local storm driver with TAB_STORM_SERVER_BINARY set to the matching release server:

bun bench/tab-storm.mjs --local

It must use the worktree assets, show retained boundaries, bound heap growth, stop aborted work, and land on the final Tab. Full e2e/adversarial merge-round suites remain with the orchestrator. No API changed here.

Files

Changed files since the initial origin/dev merge (includes the authorized storm branch and its baseline documents):

apps/web/scripts/perf-source.mjs
apps/web/scripts/perf-source.test.mjs
apps/web/src/lib/chrome.svelte.ts
apps/web/src/lib/components/app-sidebar.svelte
apps/web/src/lib/components/money/MoneySidebar.svelte
apps/web/src/lib/files/FilesBrowser.svelte
apps/web/src/lib/files/FilesSidebar.svelte
apps/web/src/lib/files/InfoPanel.svelte
apps/web/src/lib/files/RecentView.svelte
apps/web/src/lib/files/TrashView.svelte
apps/web/src/lib/navigation/RetainedTab.svelte
apps/web/src/lib/navigation/RetainedTab.svelte.test.ts
apps/web/src/lib/navigation/TabHost.svelte
apps/web/src/lib/navigation/TabHost.svelte.test.ts
apps/web/src/lib/navigation/tabContext.svelte.ts
apps/web/src/lib/navigation/test/RetainedWorkspace.svelte
apps/web/src/lib/notes/NotesExplorer.svelte
apps/web/src/lib/search/SavedSearchList.svelte
apps/web/src/lib/tasks/TaskCard.svelte
apps/web/src/lib/tasks/TaskList.svelte
apps/web/src/lib/tasks/TaskSection.svelte
apps/web/src/lib/tasks/TasksSidebar.svelte
apps/web/src/lib/tasks/TasksWorkspace.svelte
apps/web/src/lib/tasks/index.svelte.test.ts
apps/web/src/lib/tasks/index.svelte.ts
apps/web/src/routes/+layout.svelte
apps/web/src/routes/calendar/[view]/[date]/+page.svelte
apps/web/src/routes/files/+page.svelte
apps/web/src/routes/files/recent/+page.svelte
apps/web/src/routes/files/trash/+page.svelte
apps/web/src/routes/money/+layout.svelte
apps/web/src/routes/money/+page.svelte
apps/web/src/routes/money/[budget]/[month]/+page.svelte
apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte
apps/web/src/routes/notes/+page.svelte
apps/web/src/routes/shared/+page.svelte
bench/tab-retention-1233.mjs
bench/tab-storm.mjs
contracts/perf/adoption-1058.json
contracts/perf/exceptions.json
contracts/perf/registry.json
docs/DESIGN.md
docs/perf/2026-10-07-1338-tabstorm.md
docs/perf/runs/2026-10-07-1338-tabstorm-local-post-gc.csv
docs/perf/runs/2026-10-07-1338-tabstorm-local-storms.csv
docs/perf/runs/2026-10-07-1338-tabstorm-local-summary.json
docs/perf/runs/2026-10-07-1338-tabstorm-local-warm.csv
docs/perf/runs/2026-10-07-1338-tabstorm-local.csv
packages/ui/src/components/Inspector.svelte
packages/ui/src/components/OverlaySurface.svelte
packages/ui/src/components/PopoverSurface.svelte
packages/ui/src/components/TagFilterChips.svelte
packages/ui/src/components/calendar/AgendaList.svelte
packages/ui/src/components/calendar/AttachmentDeck.svelte
packages/ui/src/components/calendar/TimeGrid.svelte
packages/ui/src/components/files/FileCollection.svelte
packages/ui/src/components/files/FileName.svelte
packages/ui/src/components/menu/FloatingSurface.svelte
packages/ui/src/components/menu/Menu.svelte
packages/ui/src/index.ts
packages/ui/src/viewActivity.svelte.ts

Attached intermediate screenshots

# Partial delivery — NOT READY FOR MERGE The four-hour limit is reached. The app now has retained Tab workspaces, rather than only benchmark fixtures. The required final performance and browser proof is incomplete. Do not merge this branch on this report. Branch: job/tabswitch-1233. Head: a469644be1e557730554ae68838dc23aabdf52a5. Initial origin/dev merge: 2ead7c546. Final fetch and merge at 16:21 was already up to date. Authorized job/tabstorm-1338 merge: b5aa7e056. No push or deploy. No Rust changes or new dependencies. ## What was built TabHost keeps the last three audited Tab workspaces mounted. TAB_VIEW_LIMIT is the sole bound. SvelteKit remains the route source of truth. Each retained workspace has a stable page snapshot, so inactive workspaces do not react to another Tab's URL. A warm navigation reuses its workspace DOM. LRU eviction unmounts the oldest workspace. The sidebar follows the same retained-view owner and pauses when physically hidden. RetainedTab hides inactive content with hidden, inert, aria-hidden and content-visibility. It restores focus and scroll on return. The shared viewActivity context stops timers, observers, stream consumers, requests and portal handlers on hide. Hidden changes coalesce into one refresh on resume. First loading does not wait for motion. Session end and access loss clear the retained views. Calendar, Files, Notes, Tasks and Money use the boundary on audited read routes. Photos, Mail, Ask, editors and imports remain router-owned. Shared Calendar, Files, Menu, FloatingSurface, PopoverSurface, OverlaySurface, Inspector and TagFilterChips primitives own their lifecycle changes. Existing AppSidebar, SidebarLinks, NotesExplorer, FilesSidebar, TasksSidebar and MoneySidebar controls are reused; no local restyling was added. Notes folder reads use four workers with cancellation epochs. TaskIndex has an active-consumer reference count. Header ownership now compares the stored reactive proxy, and releases only the owner's header on hide or unmount. Focused tests caught and verified that fix. DESIGN §59 rule 6 documents the boundary. Tests cover DOM reuse, hidden accessibility state, route snapshots, back/forward, deep links, access loss, sidebar lifetime and header ownership. The performance scanner now proves an immutable literal array slice bound. Exact exception entries were refreshed without raising a budget. Expected warm cost: at most three cache entries plus the destination's existing layout and paint. A clean retained workspace does not remount or await a data fetch. A dirty workspace schedules one bounded refresh after paint. These cost claims still need final production measurements. ## Gate output, verbatim Final production build, exit 0 (final-build-9.log): ``` Compressed 921 static variants; saved 21524408 bytes. ``` Last complete successful check predates the final header/test-fixture changes (final-check-5.log): ``` PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22544 scoped exceptions svelte-check found 0 errors and 1 warning in 1 file ``` The next check found two new test-fixture PageChrome.onaction type errors. Both were fixed in a469644be. Final-check-7 reached the following output, but its type-check stage was stopped at the time cap. It is NOT a completed final check: ``` perf-lint: PASS; 0 violations; 22544 scoped exceptions ``` Full bun run test --maxWorkers=2: ``` Test Files 4 failed | 286 passed (290) Tests 4 failed | 2045 passed (2049) Start at 18:48:21 Duration 1418.04s (environment 29%, transform 25%, import 25%, tests 15%, setup 6%) error: script "test" exited with code 1 ``` All four failures were `Error: Test timed out in 5000ms.` Each failing file was rerun alone. Existing expectations were not changed. VersionHistory rerun still fails: ``` Test Files 1 failed (1) Tests 1 failed | 2 passed (3) Start at 19:13:59 Duration 134.17s (transform 79%, import 9%, tests 8%, environment 3%, setup 2%) ``` SystemGroup rerun: ``` Test Files 1 passed (1) Tests 4 passed (4) Start at 19:16:36 Duration 72.82s (transform 80%, environment 6%, import 6%, tests 5%, setup 3%) ``` KeyboardShortcutsCard rerun: ``` Test Files 1 passed (1) Tests 2 passed (2) Start at 19:13:56 Duration 108.11s (transform 83%, environment 5%, import 5%, tests 5%, setup 2%) ``` Select rerun: ``` Test Files 1 passed (1) Tests 1 passed (1) Start at 19:15:59 Duration 38.41s (transform 48%, environment 33%, tests 9%, setup 6%, import 4%) ``` Focused retained boundary/header regression rerun: ``` Test Files 2 passed (2) Tests 13 passed (13) Start at 19:12:41 Duration 67.22s (transform 78%, environment 9%, import 6%, tests 5%, setup 2%) ``` cargo fmt --check exited 0 with no output. No Rust crate changed, so crate clippy/test were not run. cargo clean completed: ``` Removed 1 file, 356B total ``` ## Measurements and known gaps No valid final <=100 ms result exists. No final cold-load <=10% comparison exists. No final <=25% heap result exists. The earlier pre-sidebar stage used 200 Notes, 500 Files, four Tasks, four events and one account. Forty warm DOM identity/inert checks passed. Five retained-Tab heap passes were 17,325,756 / 17,172,500 / 17,402,040 / 17,444,996 / 17,498,488 bytes (+1% first to last). This is intermediate evidence, not proof for HEAD. Intermediate warm p50 / p95=max in ms, five samples each: | Direction | p50 | p95/max | |---|---:|---:| | Calendar → Files | 1315.9 | 2314.3 | | Files → Calendar | 1670.2 | 2671.0 | | Files → Notes | 2309.8 | 3285.4 | | Notes → Files | 2024.7 | 3595.2 | | Notes → Tasks | 1780.5 | 3282.3 | | Tasks → Notes | 3241.7 | 5387.6 | | Tasks → Money | 2136.9 | 2995.7 | | Money → Tasks | 994.0 | 2022.1 | These runs exposed sidebar remount reads and header lifecycle work, which were then fixed. They do not meet the budget. The host load was about 65; exact uptime and raw traces are in the reports. The baseline switch_mode 316/594 ms metric uses a different interaction and is not a direct paired baseline. A final 200/500 fixture did not finish in the available time. A reduced two-Note/two-File comparison completed 40 native baseline samples but only one after sample. I rebuilt the served assets during this attempt, which invalidated its after samples. The browser also reported `effect_update_depth_exceeded`. The latest header fix passed focused tests, but the production loop is NOT proved resolved. This is a blocking runtime gap, not a SLOW-only finding. The first local storm run served the shared server's embedded web assets. CDP showed zero retained boundaries. Its three storms are invalid as after evidence. I corrected bench/tab-storm.mjs to route this worktree's production assets in local mode. The corrected storm was not run before the cap. Invalid data is in artifacts/tabswitch-1233/storm-embedded-web; the imported #1338 baseline files were restored. The copied local server SHA256 is 8dac09660d5dbeebaab69d0b4699a56e5385bfc04e0a82db62d77c93d16fa2f6; its matching source revision is not established. ## UX gaps closed Hidden views and portals leave the keyboard/accessibility tree. Focus and scroll survive retained switches. Hidden work stops, interrupted first reads resume, stale folder workers cannot publish after a rapid hide/show, and inactive views cannot own the visible title/header. Access loss clears cached views. Keyboard and pointer keep the shared motion rules. ## UX gaps left The final production runtime loop needs diagnosis and proof. Photos/Mail/Ask and write routes are not retained. Final pointer/touch/keyboard and screen-reader walks, exact deep-link browser checks, and phone/tablet screenshots are missing. The attached ten images are explicitly the PRE-SIDEBAR 1440 px stage, light and dark, with macOS emulation. They are not final visual evidence. No valid final 390/820/1440 set exists. Visual quality remains for the orchestrator to judge. ## Decisions Reuse the existing +page workspace components through a route-to-workspace host, rather than duplicate their UI. Retain one audited workspace per mode, and replace it when the workspace route class changes. Set the single LRU bound to three. Keep write routes router-owned until their persistence contracts are audited. Use the same owner for sidebar retention. Defer known signed-in warm-route background checks until after paint; keep cold, unknown and access-loss checks immediate. These implementation choices are recorded in module comments and DESIGN §59. ## For the next verification / merge round First diagnose the production effect-update loop. Freeze the production assets for the whole browser run. Use a server build with known matching source/API provenance. Run the final gates in order: ``` cd apps/web bun run build bun run check bun run test --maxWorkers=2 ``` Rerun the VersionHistory failing file alone without changing its expectation. From the worktree root, with CALTERNAL_SERVER_BIN set to a known matching release server: ``` bun bench/tab-retention-1233.mjs after ``` Use the existing TAB_RETENTION_BASELINE_BUILD option for a paired immutable production baseline. Keep the normal 200-Note/500-File fixture, collect all eight directions with five samples each, both Calendar/Files traces, cold loads and five heap passes. Capture all five Tabs at 390/820/1440, light/dark, macOS emulated. Then run the corrected local storm driver with TAB_STORM_SERVER_BINARY set to the matching release server: ``` bun bench/tab-storm.mjs --local ``` It must use the worktree assets, show retained boundaries, bound heap growth, stop aborted work, and land on the final Tab. Full e2e/adversarial merge-round suites remain with the orchestrator. No API changed here. ## Files Changed files since the initial origin/dev merge (includes the authorized storm branch and its baseline documents): ``` apps/web/scripts/perf-source.mjs apps/web/scripts/perf-source.test.mjs apps/web/src/lib/chrome.svelte.ts apps/web/src/lib/components/app-sidebar.svelte apps/web/src/lib/components/money/MoneySidebar.svelte apps/web/src/lib/files/FilesBrowser.svelte apps/web/src/lib/files/FilesSidebar.svelte apps/web/src/lib/files/InfoPanel.svelte apps/web/src/lib/files/RecentView.svelte apps/web/src/lib/files/TrashView.svelte apps/web/src/lib/navigation/RetainedTab.svelte apps/web/src/lib/navigation/RetainedTab.svelte.test.ts apps/web/src/lib/navigation/TabHost.svelte apps/web/src/lib/navigation/TabHost.svelte.test.ts apps/web/src/lib/navigation/tabContext.svelte.ts apps/web/src/lib/navigation/test/RetainedWorkspace.svelte apps/web/src/lib/notes/NotesExplorer.svelte apps/web/src/lib/search/SavedSearchList.svelte apps/web/src/lib/tasks/TaskCard.svelte apps/web/src/lib/tasks/TaskList.svelte apps/web/src/lib/tasks/TaskSection.svelte apps/web/src/lib/tasks/TasksSidebar.svelte apps/web/src/lib/tasks/TasksWorkspace.svelte apps/web/src/lib/tasks/index.svelte.test.ts apps/web/src/lib/tasks/index.svelte.ts apps/web/src/routes/+layout.svelte apps/web/src/routes/calendar/[view]/[date]/+page.svelte apps/web/src/routes/files/+page.svelte apps/web/src/routes/files/recent/+page.svelte apps/web/src/routes/files/trash/+page.svelte apps/web/src/routes/money/+layout.svelte apps/web/src/routes/money/+page.svelte apps/web/src/routes/money/[budget]/[month]/+page.svelte apps/web/src/routes/money/[budget]/accounts/[[account]]/+page.svelte apps/web/src/routes/notes/+page.svelte apps/web/src/routes/shared/+page.svelte bench/tab-retention-1233.mjs bench/tab-storm.mjs contracts/perf/adoption-1058.json contracts/perf/exceptions.json contracts/perf/registry.json docs/DESIGN.md docs/perf/2026-10-07-1338-tabstorm.md docs/perf/runs/2026-10-07-1338-tabstorm-local-post-gc.csv docs/perf/runs/2026-10-07-1338-tabstorm-local-storms.csv docs/perf/runs/2026-10-07-1338-tabstorm-local-summary.json docs/perf/runs/2026-10-07-1338-tabstorm-local-warm.csv docs/perf/runs/2026-10-07-1338-tabstorm-local.csv packages/ui/src/components/Inspector.svelte packages/ui/src/components/OverlaySurface.svelte packages/ui/src/components/PopoverSurface.svelte packages/ui/src/components/TagFilterChips.svelte packages/ui/src/components/calendar/AgendaList.svelte packages/ui/src/components/calendar/AttachmentDeck.svelte packages/ui/src/components/calendar/TimeGrid.svelte packages/ui/src/components/files/FileCollection.svelte packages/ui/src/components/files/FileName.svelte packages/ui/src/components/menu/FloatingSurface.svelte packages/ui/src/components/menu/Menu.svelte packages/ui/src/index.ts packages/ui/src/viewActivity.svelte.ts ``` ## Attached intermediate screenshots - PRE-SIDEBAR [calendar-1440-dark.png](https://git.kayg.org/attachments/a8b54df8-cd64-4b19-8699-79c48f8ba1e5) - PRE-SIDEBAR [calendar-1440-light.png](https://git.kayg.org/attachments/5281ee26-e81c-41b5-add0-03dcfacbf7bf) - PRE-SIDEBAR [files-1440-dark.png](https://git.kayg.org/attachments/285892be-d22b-4330-b4b9-cfac53b4e976) - PRE-SIDEBAR [files-1440-light.png](https://git.kayg.org/attachments/01eaa5bb-3a93-430c-9082-5aea4f732120) - PRE-SIDEBAR [money-1440-dark.png](https://git.kayg.org/attachments/cc23a6c7-e73b-45eb-a1ce-2139e8d9a8ee) - PRE-SIDEBAR [money-1440-light.png](https://git.kayg.org/attachments/6ca84864-f96b-42f4-ade9-72a784066394) - PRE-SIDEBAR [notes-1440-dark.png](https://git.kayg.org/attachments/cb1c97a1-5ca1-4859-8e95-38f96b2f7a89) - PRE-SIDEBAR [notes-1440-light.png](https://git.kayg.org/attachments/50105c28-e375-4394-a614-17c43c7b5201) - PRE-SIDEBAR [tasks-1440-dark.png](https://git.kayg.org/attachments/25378e63-4998-4a96-8339-a206ccf0e15b) - PRE-SIDEBAR [tasks-1440-light.png](https://git.kayg.org/attachments/880e53e9-d463-4427-a62f-96698f27730a)
Author
Owner

Starting tabswitch3 on job/tabswitch-1233 at a469644be1. Predecessor report read in full. Reproducing the blocking production effect loop first, then freezing assets for measurements. No push or deployment.

Starting tabswitch3 on job/tabswitch-1233 at a469644be1e557730554ae68838dc23aabdf52a5. Predecessor report read in full. Reproducing the blocking production effect loop first, then freezing assets for measurements. No push or deployment.
Author
Owner

Finding at a469644be: a fresh immutable production build completed 15 cold rotation switches, then the small real-API fixture completed 40 warm retained switches and five heap passes with zero page errors. All warm destination identities remained mounted. The reported effect-depth loop is not reproduced on this head; no speculative loop guard added. Prior failed run changed assets mid-run. Focused TabHost/RetainedTab/VersionHistory suite passes; adding explicit benchmark page-error failures and separating trace collection from budget samples. Found retained focus used plain focus(); adopting the existing quiet-focus helper.

Finding at a469644be: a fresh immutable production build completed 15 cold rotation switches, then the small real-API fixture completed 40 warm retained switches and five heap passes with zero page errors. All warm destination identities remained mounted. The reported effect-depth loop is not reproduced on this head; no speculative loop guard added. Prior failed run changed assets mid-run. Focused TabHost/RetainedTab/VersionHistory suite passes; adding explicit benchmark page-error failures and separating trace collection from budget samples. Found retained focus used plain focus(); adopting the existing quiet-focus helper.
Author
Owner

Progress: focus fix committed in 91fc6200c; benchmark validity/provenance fixes are atomic commits through 8ab21f6a3. Required origin/dev fetch and merge was already up to date. Final bun run check completed: svelte-check found 0 errors and 1 warning in 1 file; perf-lint PASS, 0 violations, 22544 scoped exceptions. cargo fmt --check passed silently. Full bun run test --maxWorkers=2 is still running. Perf VM is now seeding 200 Notes / 500 Files under perf-run; initial load 0.27 / 0.69 / 0.96. Earlier attempts failed before measurements because Chromium could not create shared memory under the staged temp directory; moved TMPDIR to writable HDD temp.

Progress: focus fix committed in 91fc6200c; benchmark validity/provenance fixes are atomic commits through 8ab21f6a3. Required origin/dev fetch and merge was already up to date. Final bun run check completed: svelte-check found 0 errors and 1 warning in 1 file; perf-lint PASS, 0 violations, 22544 scoped exceptions. cargo fmt --check passed silently. Full bun run test --maxWorkers=2 is still running. Perf VM is now seeding 200 Notes / 500 Files under perf-run; initial load 0.27 / 0.69 / 0.96. Earlier attempts failed before measurements because Chromium could not create shared memory under the staged temp directory; moved TMPDIR to writable HDD temp.
Author
Owner

Perf VM first completed timing pass: all 40 warm samples retained DOM identity and had zero page errors. Native warm p95 ranged 247.9–396.6 ms, above the 100 ms budget. Five post-GC heap passes were 21943992 / 22056392 / 22332112 / 22389088 / 22438360 bytes (+2.3% first to last). Marked main-thread traces attributed 51.3/57.3 ms to Svelte flush callbacks, 18.8/25.7 ms to layout and 18.0/17.1 ms to style recalculation for Calendar/Files. Warm Notes returns issued fresh Notes reads. Fixed state-only history updates invalidating URL/data consumers in bd9ea9697; the new regression fails on the old implementation (2 publications instead of 1) and passes after the fix. Commit 4a5fcf835 prevents a queued restore from stealing the next keyboard Tab focus. First screenshot probe picked an offscreen Calendar buffer date and panned the URL; corrected it to the visible current date. Final paired run is active under perf-run; start load 0.00 / 0.04 / 0.53. Initial full web suite passed 290 files / 2051 tests; rerunning after the new runtime fixes.

Perf VM first completed timing pass: all 40 warm samples retained DOM identity and had zero page errors. Native warm p95 ranged 247.9–396.6 ms, above the 100 ms budget. Five post-GC heap passes were 21943992 / 22056392 / 22332112 / 22389088 / 22438360 bytes (+2.3% first to last). Marked main-thread traces attributed 51.3/57.3 ms to Svelte flush callbacks, 18.8/25.7 ms to layout and 18.0/17.1 ms to style recalculation for Calendar/Files. Warm Notes returns issued fresh Notes reads. Fixed state-only history updates invalidating URL/data consumers in bd9ea9697; the new regression fails on the old implementation (2 publications instead of 1) and passes after the fix. Commit 4a5fcf835 prevents a queued restore from stealing the next keyboard Tab focus. First screenshot probe picked an offscreen Calendar buffer date and panned the URL; corrected it to the visible current date. Final paired run is active under perf-run; start load 0.00 / 0.04 / 0.53. Initial full web suite passed 290 files / 2051 tests; rerunning after the new runtime fixes.
Author
Owner

Quiet-VM finding after the route-field fix (bd9ea9697, focus fix 4a5fcf835): 40 paired baseline + 40 retained warm switches, five GC passes each, and zero production page errors. Both use the same release server at origin/dev 0419170fe; retained web assets were immutable throughout the run. All retained DOM identity/inert assertions pass. Warm Notes returns now make zero API requests (ten measured returns). The history-state regression fails on the old source and passes on the new source.

Warm p95 (baseline → retained, ms): Files→Calendar 351.9→249.3; Calendar→Files 397.4→283.8; Notes→Files 314.0→222.3; Files→Notes 367.6→205.3; Tasks→Notes 242.7→202.4; Notes→Tasks 198.4→235.7; Money→Tasks 192.2→188.0; Tasks→Money 214.2→236.8. Every retained p95 still misses 100 ms. After heap grows 2.7% across five passes; absolute retained heap is 60.8% above baseline at pass five. This is NOT READY FOR MERGE.

Two paired measurement iterations are complete. A separate screenshot-only run now saves the exact failing focus/scroll/URL witness before asserting; the VM storm is running with corrected assets and three retained boundaries required. The optional 72-pair storm sweep is explicitly omitted because this job already ran the required eight-direction paired profile; the three storms per condition remain enabled.

The latest full web test run had three 5 s timeouts in tagFilterChips, SelectionBar and Composer (287 files/2050 tests passed). The unchanged three files pass a focused rerun: Test Files 3 passed (3) / Tests 43 passed (43). A final full run is active; no assertion or timeout was changed. Head e123c9571.

Quiet-VM finding after the route-field fix (`bd9ea9697`, focus fix `4a5fcf835`): 40 paired baseline + 40 retained warm switches, five GC passes each, and zero production page errors. Both use the same release server at origin/dev `0419170fe`; retained web assets were immutable throughout the run. All retained DOM identity/inert assertions pass. Warm Notes returns now make zero API requests (ten measured returns). The history-state regression fails on the old source and passes on the new source. Warm p95 (baseline → retained, ms): Files→Calendar 351.9→249.3; Calendar→Files 397.4→283.8; Notes→Files 314.0→222.3; Files→Notes 367.6→205.3; Tasks→Notes 242.7→202.4; Notes→Tasks 198.4→235.7; Money→Tasks 192.2→188.0; Tasks→Money 214.2→236.8. Every retained p95 still misses 100 ms. After heap grows 2.7% across five passes; absolute retained heap is 60.8% above baseline at pass five. This is NOT READY FOR MERGE. Two paired measurement iterations are complete. A separate screenshot-only run now saves the exact failing focus/scroll/URL witness before asserting; the VM storm is running with corrected assets and three retained boundaries required. The optional 72-pair storm sweep is explicitly omitted because this job already ran the required eight-direction paired profile; the three storms per condition remain enabled. The latest full web test run had three 5 s timeouts in tagFilterChips, SelectionBar and Composer (287 files/2050 tests passed). The unchanged three files pass a focused rerun: `Test Files 3 passed (3)` / `Tests 43 passed (43)`. A final full run is active; no assertion or timeout was changed. Head `e123c9571`.
Author
Owner

tabswitch3 final report — NOT READY FOR MERGE

Branch: job/tabswitch-1233; base: a469644be1e557730554ae68838dc23aabdf52a5; head: 6eb9aa0444dd95de3962e754a93ec4788470d358. Required origin/dev merge was already up to date at 0419170fe456b58614befcb12d9c9d255118f1c0. No push, deploy, merge into dev/main, or issue close.

Built: field-stable retained route snapshots and facade; quiet focus restoration without stealing the next keyboard input; preservation of native keyboard focus order; pre-mutation scroll capture. Regressions for private history state and scroll fail against old source and pass now. Bench fixes verify actual assets, reject page errors, preserve evidence, and capture real production views. Every measured warm switch (40/40) has zero API reads.

Files changed in this continuation:

apps/web/src/lib/navigation/RetainedTab.svelte
apps/web/src/lib/navigation/RetainedTab.svelte.test.ts
apps/web/src/lib/navigation/TabHost.svelte
apps/web/src/lib/navigation/TabHost.svelte.test.ts
apps/web/src/lib/navigation/tabContext.svelte.ts
apps/web/src/lib/navigation/test/RetainedWorkspace.svelte
bench/tab-retention-1233.mjs
bench/tab-storm.mjs
docs/perf/2026-10-07-1233-retained-tabs-followup.md

The full report follows. It contains gate output verbatim, all eight paired directions, cold loads, five heap passes, trace costs, provenance, decisions, UX gaps closed/left and exact remaining verification commands. No measurements continued after the 90-minute measurement limit at 22:37 CEST. The original production loop did not reproduce at the supplied head; its root cause remains unattributed. This is not a claim that an exact loop regression was fixed.

Retained Tab follow-up — #1233

Status

This follow-up is not ready for merge. The production runtime loop reported
by the previous job did not occur with a fresh, fixed production build.
The warm switch budget is still not met. Storm acceptance remains incomplete. The final screenshot witnesses pass,
but visual approval belongs to the orchestrator.

Changes and cost

Keep equal URL, params, data and route fields stable when private history
state changes. Each route field has its own derived signal. A history state
update still reaches its consumers. It does not start a cached Notes read.
The sidebar uses the same committed snapshot as its workspace.

Use the existing quiet-focus helper for retained focus. A queued restore does
not take focus from the next keyboard Tab. No new visible control or local
style was added. The existing Tab Bar, workspace controls and shared focus
policy remain the UI owners.

The expected cache update cost is at most three entries, plus a shallow
comparison of route fields. A warm return keeps its DOM tree. Lists retain
their existing row limits. Test fixtures are written with four workers.

The regression test changes private history state without changing URL or
data. It fails against the old source (two publications instead of one) and
passes with the new field-stable facade. Focus tests cover quiet restoration
and the next keyboard input. A later phone probe found that hiding DOM
clamps the shared feed before ordinary effect cleanup saves its scroll. A
pre-effect now saves it before DOM mutation. Its regression fails on the
old cleanup and passes with the pre-effect and reactive boundary binding.
Remembered native controls also keep their default keyboard focus order;
only a fallback heading receives tabindex=-1. No existing assertion or
timeout was changed.

Production input

The baseline is a separate production web build of origin/dev at
0419170fe456b58614befcb12d9c9d255118f1c0. Both builds use the same shared
release server from that revision. Its SHA-256 is
42993fbd7660d35b1af91ab3a48d539666e0e67e5ae9d27c05a2f639d0680eaf.
The final measured runner source is
5f1a49633fde6209672463900e96d464049ff4d4.
The after web build ID is 2b149f9b07e6ddb702df6fd5d1f7bf3ab4db43cc.
That build started with the app changes later committed in bd9ea9697 and
4a5fcf835; its assets were fixed throughout the run. A build ID is not the
same as the complete source revision in this case.

The real API fixture has 200 Notes, 500 Files, four Tasks, three Logs, four
Events and one synthetic test account. The browser is Chromium, 1440 × 900,
with macOS platform emulation. The perf VM has no GPU. Both builds use the
same software renderer. No server or web build ran on the VM.

Each run holds perf-run calternal and records load inside the lock.
The final lock began at load 0.00 / 0.04 / 0.53. The runner recorded
0.44 / 0.14 / 0.56 at browser setup. Timing begins at the captured Tab click
and ends two animation frames after real destination content is visible.
Traces are separate from the five samples in each direction. No slow sample
was removed. With five samples, nearest-rank p95 equals maximum.

Paired warm switches

All times are milliseconds. Every direction has five samples per build.

Direction Baseline p50 Baseline p95/max After p50 After p95/max
Files → Calendar 221.7 351.9 179.0 249.3
Calendar → Files 305.8 397.4 225.7 283.8
Notes → Files 255.1 314.0 186.7 222.3
Files → Notes 250.5 367.6 182.4 205.3
Tasks → Notes 220.4 242.7 191.5 202.4
Notes → Tasks 187.7 198.4 186.0 235.7
Money → Tasks 171.2 192.2 172.2 188.0
Tasks → Money 181.9 214.2 174.1 236.8

All after p95 results exceed DESIGN §59's 100 ms warm budget. Most directions
improved against the paired baseline; Notes → Tasks and Tasks → Money have
higher p95. Do not describe this result as a budget pass.

The historical switch_mode baseline in docs/perf/baseline.json is 316 ms
p50 and 594 ms p95. It has a different timing boundary. Use the paired table
above for the direct comparison.

Cold loads and heap

Cold results are single first visits, not distributions.

Tab Baseline cold ms After cold ms
Files 596.0 454.5
Notes 348.6 259.6
Tasks 242.0 242.4
Money 325.3 311.8
Forced-GC pass Baseline bytes After bytes
1 13,152,004 21,642,156
2 13,681,628 21,825,388
3 13,757,092 22,115,344
4 14,143,024 22,177,256
5 13,827,600 22,231,672

After heap grows 2.7% from pass one to five. It is 60.8% larger than the
baseline at pass five. Stable growth does not remove the absolute memory
cost of retaining three trees. All 40 warm identity checks and hidden-tree
inert checks pass. All 40 measured warm switches make zero API requests after the route-field
fix, including all ten returns to Notes.

Traces and incomplete attempts

The final Calendar trace spans 199.5 ms. Renderer main-thread tasks use
47.5 ms of that window. Svelte microtask callbacks are the largest script
cost (12.6 and 9.6 ms); layout uses 10.5 ms and style updates 8.4 ms.
The Files trace spans 195.3 ms. Renderer main-thread tasks use 62.0 ms;
Svelte callbacks take 22.3 and 7.1 ms, layout 14.5 ms and style updates
18.5 ms. These event durations overlap: do not add inclusive totals.
The remaining wall time includes frame scheduling and compositor work.
The requested CPU-profile category produced no sample stacks, so these are
trace event costs, not source-level CPU attribution.

The first valid VM pair used the earlier focus-only app build. Its after
p95 range was 247.9–396.6 ms. It had no page errors, 40 warm samples and five
heap passes. The second pair above follows the route-field fix. Both runs
failed later at the first screenshot focus witness; their timing reports
and traces were preserved. No third paired timing run was made.

Three early browser starts failed before sampling because Chromium could
not use temporary shared-memory files under /root. A writable job-owned
temporary directory outside /root fixed the start failure. These attempts
are not latency samples.

A fresh build at the supplied a469644be head, a small fixture production
probe and both full VM pairs report no effect_update_depth_exceeded.
The previous job rebuilt assets while its page was open. This follow-up
keeps assets fixed and rejects every page error. The exact previous loop
cannot be attributed without a reproduction; no guard was added to hide it.

Storm verification

The VM follow-up reached the first storm after its setup passed. It saved
14 per-second rows, then stopped before a post-GC sample: an unexpected
Composer hid the Tab Bar while the profile sought Money. The failure
screenshot shows the real Composer. This matches the keyboard scenario
already tracked in #1380,
which was also present in #1338. The summary has zero completed conditions,
zero post-GC rows and no page errors. Startup endpoint counts are 88 Tasks
reads, seven saved-view reads and three visibility reads, all HTTP 200, with
zero failed requests. A previous corrected VM start stopped at Tasks
loading before a storm; no claim of a runtime loop follows from that timeout.
The final VM lock load was 5.54 / 3.16 / 1.97. These partial rows do not prove
bounded growth, cancellation, one-second idle or correct final Tab.

The corrected local run proved three retained
workspace boundaries with this worktree's assets. It then timed out during
startup on the loaded host, before any completed storm sample. An earlier
local attempt timed out looking for the Money Tab during the first storm.
Those runs do not prove bounded heap, DOM, request cancellation or idle.
Partial failures are now saved in the output summary instead of discarded.

The first VM storm attempt copied the web build but used embedded server
assets. Its retained-boundary assertion rejected it before sampling. Remote
asset routing now uses the same production harness as local runs, with the
app port forwarded for its server boot-policy read. The invalid attempt is
preserved separately. The corrected VM run uses a separate output and stage
directory and the shared release server. A final bounded attempt keeps trusted Enter targeted at its Tab through
Locator.press, instead of an extra controller round trip after focus.
It does not claim to fix the application issue #1380. Its result must still
record unexpected overlays and all failed budgets.
The optional 72-pair sweep is
omitted: this follow-up has the required separate eight-direction paired
profile. The three 120-second storms per condition remain enabled.

Final bounded storm attempt

The final corrected VM attempt used Locator.press and fixed production
assets. Its 1,200-second controller limit ended with exit 143. It wrote a
first-storm heap snapshot and reached a later storm phase, but did not save
a completed condition summary. The remote session and lock state could no
longer be verified when the controller stalled. These observations are not
accepted benchmark rows. No claim of concurrent valid measurement follows
from a stale phase file. The runner now saves every completed storm before
heap capture and cleanup. That evidence-save change was not measured again.

The measurement time box ended at 22:37 CEST. No further run was made.
The #1338 baseline reports first-to-third storm heap growth of 22.3%, DOM
growth of 112.3% and RSS growth of 58.8%. This job has no completed after
condition to compare with those values. The required heap and DOM bound of
25%, abandoned-request cancellation, one-second idle and final Tab checks
remain unproved. SLOW-only local startup results are not product failures.

Screenshots and gates

Final production build ID: 900d093f02b8b1139829ec36e18952d1183070c7.
All five retained Tabs have switch-away-and-back witnesses at 390, 820 and
1440 px, in light and dark, with macOS emulation. The 200-Note/500-File set
has 30 passing DOM identity, URL, focus and scroll witnesses, including
160 px of phone Calendar scroll. Its images were superseded because capture
animation disabling and an update notice made them unsuitable for review.

The clean attached set has 30 images from the same real production API with
two Notes and two Files, four Tasks, three Logs and four Events. Scroll-driven
animations remain active; real Events are in view; the update notice is
closed through its UI; the pointer is off controls. All 30 witnesses pass,
with no page errors. Pointer focus has no painted outline. Four extra
1440-px light captures show the assigned Calendar, Files, Notes and Tasks
opening shortcuts without a focus ring. Money is seventh in the default
order and has no first-five shortcut. No new shortcut was added. A diagnostic
attempt to use a seventh shortcut timed out after the complete pointer set;
the runner now records an unassigned shortcut instead. A matching baseline
visual set and Money shortcut proof are not supplied. The orchestrator must
review the attached images; this job does not approve its own visual quality.

Commands ran in apps/web. Final gate output excerpts are verbatim:

bun run check
PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22544 scoped exceptions
svelte-check found 0 errors and 1 warning in 1 file

bun run test --maxWorkers=2
 Test Files  290 passed (290)
      Tests  2055 passed (2055)
   Start at  22:12:03
   Duration  400.50s (transform 29%, environment 27%, import 22%, tests 16%, setup 5%)

bunx vitest run src/lib/navigation/RetainedTab.svelte.test.ts src/lib/navigation/TabHost.svelte.test.ts --maxWorkers=2
 Test Files  2 passed (2)
      Tests  18 passed (18)

The warning is the unchanged empty .attachment-row:focus ruleset in
packages/ui/src/components/calendar/AttachmentDeck.svelte. VersionHistory is
unchanged against origin/dev and passes; no timeout or expectation changed.
cargo fmt --check exited zero with no output. No Rust code changed.
Both bench scripts pass node --check. The production build passed:

Compressed 921 static variants; saved 21525333 bytes.

The required single origin/dev merge returned Already up to date. before
final gates. Local web build output and the baseline worktree were removed.
Cargo cleanup output:

     Removed 1 file, 356B total

UX gaps closed

Private history state no longer causes a warm Notes read. All 40 measured
warm switches have zero API requests. Restored native controls keep keyboard
focus order. A queued restore does not steal the next keyboard Tab focus.
Phone scroll is saved before the shared feed is clamped. Pointer restoration
uses the shared quiet-focus helper. Existing Tab Bar and workspace controls
remain the visible controls; no shared primitive was restyled.

Decisions and gaps

No UI design was changed. Field-level derived signals implement DESIGN §59's
stable-field requirement. The snapshot still publishes shallow history state.
The optional storm pair sweep is separate from the required paired profile;
its omission is explicit. The evidence-save change preserves partial results
without changing acceptance budgets. These are implementation choices, not
new product decisions.

Photos, Mail, Ask, editors and imports keep the predecessor's route-owned
lifetime. The paired measurements precede the final native-focus and scroll
fixes; those fixes have focused regression tests, but no third paired timing
run. The original reported production loop could not be reproduced at the
supplied head and therefore has no attributed root cause or exact loop
regression. The 100 ms budget and storm acceptance remain open. The unexpected
Composer remains tracked by #1380. The final fifth heap is 60.8% above the
paired baseline; five after passes alone do not prove storm bounds.

For the next verification round

Use the fixed production assets and shared release server. Run
bun bench/tab-storm.mjs locally and through perf-run calternal on the VM.
It must complete all conditions and prove heap/DOM growth at most 25%,
abandoned requests aborted, idle within one second and correct final Tab.
Resolve or account for #1380 without silently removing a scenario.
Run bun bench/tab-retention-1233.mjs with the 200-Note/500-File fixture and
paired origin/dev assets after a measured fix for the remaining warm costs.
It must retain all eight directions, five samples and five heap passes.
Do not treat the current timing table as a budget pass. Reproduce any claimed
production loop on fixed assets before assigning its cause.

Attached production captures

These 30 switch-return captures use the clean real API fixture described above, not placeholders. The four extra images show assigned opening shortcuts. The large 200/500 fixture has a separate complete 30-witness pass. Review artifacts stay uncommitted. The orchestrator owns visual approval. No matching baseline visual set was captured.

Opening shortcuts: Calendar, Files, Notes, Tasks

Raw logs and JSON remain in this worktree under artifacts/. Final app full-suite output is artifacts/1233-test-scroll-final.log; VM paired rows are artifacts/tabswitch-1233/perf-vm-final/report.json; clean capture witnesses are artifacts/tabswitch-1233/clean-captures/. Cargo and web build cleanup completed.

tabswitch3 final report — NOT READY FOR MERGE Branch: `job/tabswitch-1233`; base: `a469644be1e557730554ae68838dc23aabdf52a5`; head: `6eb9aa0444dd95de3962e754a93ec4788470d358`. Required origin/dev merge was already up to date at `0419170fe456b58614befcb12d9c9d255118f1c0`. No push, deploy, merge into dev/main, or issue close. Built: field-stable retained route snapshots and facade; quiet focus restoration without stealing the next keyboard input; preservation of native keyboard focus order; pre-mutation scroll capture. Regressions for private history state and scroll fail against old source and pass now. Bench fixes verify actual assets, reject page errors, preserve evidence, and capture real production views. Every measured warm switch (40/40) has zero API reads. Files changed in this continuation: ```text apps/web/src/lib/navigation/RetainedTab.svelte apps/web/src/lib/navigation/RetainedTab.svelte.test.ts apps/web/src/lib/navigation/TabHost.svelte apps/web/src/lib/navigation/TabHost.svelte.test.ts apps/web/src/lib/navigation/tabContext.svelte.ts apps/web/src/lib/navigation/test/RetainedWorkspace.svelte bench/tab-retention-1233.mjs bench/tab-storm.mjs docs/perf/2026-10-07-1233-retained-tabs-followup.md ``` The full report follows. It contains gate output verbatim, all eight paired directions, cold loads, five heap passes, trace costs, provenance, decisions, UX gaps closed/left and exact remaining verification commands. No measurements continued after the 90-minute measurement limit at 22:37 CEST. The original production loop did not reproduce at the supplied head; its root cause remains unattributed. This is not a claim that an exact loop regression was fixed. # Retained Tab follow-up — #1233 ## Status This follow-up is not ready for merge. The production runtime loop reported by the previous job did not occur with a fresh, fixed production build. The warm switch budget is still not met. Storm acceptance remains incomplete. The final screenshot witnesses pass, but visual approval belongs to the orchestrator. ## Changes and cost Keep equal URL, params, data and route fields stable when private history state changes. Each route field has its own derived signal. A history state update still reaches its consumers. It does not start a cached Notes read. The sidebar uses the same committed snapshot as its workspace. Use the existing quiet-focus helper for retained focus. A queued restore does not take focus from the next keyboard Tab. No new visible control or local style was added. The existing Tab Bar, workspace controls and shared focus policy remain the UI owners. The expected cache update cost is at most three entries, plus a shallow comparison of route fields. A warm return keeps its DOM tree. Lists retain their existing row limits. Test fixtures are written with four workers. The regression test changes private history state without changing URL or data. It fails against the old source (two publications instead of one) and passes with the new field-stable facade. Focus tests cover quiet restoration and the next keyboard input. A later phone probe found that hiding DOM clamps the shared feed before ordinary effect cleanup saves its scroll. A pre-effect now saves it before DOM mutation. Its regression fails on the old cleanup and passes with the pre-effect and reactive boundary binding. Remembered native controls also keep their default keyboard focus order; only a fallback heading receives `tabindex=-1`. No existing assertion or timeout was changed. ## Production input The baseline is a separate production web build of `origin/dev` at `0419170fe456b58614befcb12d9c9d255118f1c0`. Both builds use the same shared release server from that revision. Its SHA-256 is `42993fbd7660d35b1af91ab3a48d539666e0e67e5ae9d27c05a2f639d0680eaf`. The final measured runner source is `5f1a49633fde6209672463900e96d464049ff4d4`. The after web build ID is `2b149f9b07e6ddb702df6fd5d1f7bf3ab4db43cc`. That build started with the app changes later committed in `bd9ea9697` and `4a5fcf835`; its assets were fixed throughout the run. A build ID is not the same as the complete source revision in this case. The real API fixture has 200 Notes, 500 Files, four Tasks, three Logs, four Events and one synthetic test account. The browser is Chromium, 1440 × 900, with macOS platform emulation. The perf VM has no GPU. Both builds use the same software renderer. No server or web build ran on the VM. Each run holds `perf-run calternal` and records load inside the lock. The final lock began at load 0.00 / 0.04 / 0.53. The runner recorded 0.44 / 0.14 / 0.56 at browser setup. Timing begins at the captured Tab click and ends two animation frames after real destination content is visible. Traces are separate from the five samples in each direction. No slow sample was removed. With five samples, nearest-rank p95 equals maximum. ## Paired warm switches All times are milliseconds. Every direction has five samples per build. | Direction | Baseline p50 | Baseline p95/max | After p50 | After p95/max | | --- | ---: | ---: | ---: | ---: | | Files → Calendar | 221.7 | 351.9 | 179.0 | 249.3 | | Calendar → Files | 305.8 | 397.4 | 225.7 | 283.8 | | Notes → Files | 255.1 | 314.0 | 186.7 | 222.3 | | Files → Notes | 250.5 | 367.6 | 182.4 | 205.3 | | Tasks → Notes | 220.4 | 242.7 | 191.5 | 202.4 | | Notes → Tasks | 187.7 | 198.4 | 186.0 | 235.7 | | Money → Tasks | 171.2 | 192.2 | 172.2 | 188.0 | | Tasks → Money | 181.9 | 214.2 | 174.1 | 236.8 | All after p95 results exceed DESIGN §59's 100 ms warm budget. Most directions improved against the paired baseline; Notes → Tasks and Tasks → Money have higher p95. Do not describe this result as a budget pass. The historical `switch_mode` baseline in `docs/perf/baseline.json` is 316 ms p50 and 594 ms p95. It has a different timing boundary. Use the paired table above for the direct comparison. ## Cold loads and heap Cold results are single first visits, not distributions. | Tab | Baseline cold ms | After cold ms | | --- | ---: | ---: | | Files | 596.0 | 454.5 | | Notes | 348.6 | 259.6 | | Tasks | 242.0 | 242.4 | | Money | 325.3 | 311.8 | | Forced-GC pass | Baseline bytes | After bytes | | --- | ---: | ---: | | 1 | 13,152,004 | 21,642,156 | | 2 | 13,681,628 | 21,825,388 | | 3 | 13,757,092 | 22,115,344 | | 4 | 14,143,024 | 22,177,256 | | 5 | 13,827,600 | 22,231,672 | After heap grows 2.7% from pass one to five. It is 60.8% larger than the baseline at pass five. Stable growth does not remove the absolute memory cost of retaining three trees. All 40 warm identity checks and hidden-tree inert checks pass. All 40 measured warm switches make zero API requests after the route-field fix, including all ten returns to Notes. ## Traces and incomplete attempts The final Calendar trace spans 199.5 ms. Renderer main-thread tasks use 47.5 ms of that window. Svelte microtask callbacks are the largest script cost (12.6 and 9.6 ms); layout uses 10.5 ms and style updates 8.4 ms. The Files trace spans 195.3 ms. Renderer main-thread tasks use 62.0 ms; Svelte callbacks take 22.3 and 7.1 ms, layout 14.5 ms and style updates 18.5 ms. These event durations overlap: do not add inclusive totals. The remaining wall time includes frame scheduling and compositor work. The requested CPU-profile category produced no sample stacks, so these are trace event costs, not source-level CPU attribution. The first valid VM pair used the earlier focus-only app build. Its after p95 range was 247.9–396.6 ms. It had no page errors, 40 warm samples and five heap passes. The second pair above follows the route-field fix. Both runs failed later at the first screenshot focus witness; their timing reports and traces were preserved. No third paired timing run was made. Three early browser starts failed before sampling because Chromium could not use temporary shared-memory files under `/root`. A writable job-owned temporary directory outside `/root` fixed the start failure. These attempts are not latency samples. A fresh build at the supplied `a469644be` head, a small fixture production probe and both full VM pairs report no `effect_update_depth_exceeded`. The previous job rebuilt assets while its page was open. This follow-up keeps assets fixed and rejects every page error. The exact previous loop cannot be attributed without a reproduction; no guard was added to hide it. ## Storm verification The VM follow-up reached the first storm after its setup passed. It saved 14 per-second rows, then stopped before a post-GC sample: an unexpected Composer hid the Tab Bar while the profile sought Money. The failure screenshot shows the real Composer. This matches the keyboard scenario already tracked in [#1380](https://git.kayg.org/kayg/calternal/issues/1380), which was also present in #1338. The summary has zero completed conditions, zero post-GC rows and no page errors. Startup endpoint counts are 88 Tasks reads, seven saved-view reads and three visibility reads, all HTTP 200, with zero failed requests. A previous corrected VM start stopped at Tasks loading before a storm; no claim of a runtime loop follows from that timeout. The final VM lock load was 5.54 / 3.16 / 1.97. These partial rows do not prove bounded growth, cancellation, one-second idle or correct final Tab. The corrected local run proved three retained workspace boundaries with this worktree's assets. It then timed out during startup on the loaded host, before any completed storm sample. An earlier local attempt timed out looking for the Money Tab during the first storm. Those runs do not prove bounded heap, DOM, request cancellation or idle. Partial failures are now saved in the output summary instead of discarded. The first VM storm attempt copied the web build but used embedded server assets. Its retained-boundary assertion rejected it before sampling. Remote asset routing now uses the same production harness as local runs, with the app port forwarded for its server boot-policy read. The invalid attempt is preserved separately. The corrected VM run uses a separate output and stage directory and the shared release server. A final bounded attempt keeps trusted Enter targeted at its Tab through `Locator.press`, instead of an extra controller round trip after focus. It does not claim to fix the application issue #1380. Its result must still record unexpected overlays and all failed budgets. The optional 72-pair sweep is omitted: this follow-up has the required separate eight-direction paired profile. The three 120-second storms per condition remain enabled. ## Final bounded storm attempt The final corrected VM attempt used `Locator.press` and fixed production assets. Its 1,200-second controller limit ended with exit 143. It wrote a first-storm heap snapshot and reached a later storm phase, but did not save a completed condition summary. The remote session and lock state could no longer be verified when the controller stalled. These observations are not accepted benchmark rows. No claim of concurrent valid measurement follows from a stale phase file. The runner now saves every completed storm before heap capture and cleanup. That evidence-save change was not measured again. The measurement time box ended at 22:37 CEST. No further run was made. The #1338 baseline reports first-to-third storm heap growth of 22.3%, DOM growth of 112.3% and RSS growth of 58.8%. This job has no completed after condition to compare with those values. The required heap and DOM bound of 25%, abandoned-request cancellation, one-second idle and final Tab checks remain unproved. SLOW-only local startup results are not product failures. ## Screenshots and gates Final production build ID: `900d093f02b8b1139829ec36e18952d1183070c7`. All five retained Tabs have switch-away-and-back witnesses at 390, 820 and 1440 px, in light and dark, with macOS emulation. The 200-Note/500-File set has 30 passing DOM identity, URL, focus and scroll witnesses, including 160 px of phone Calendar scroll. Its images were superseded because capture animation disabling and an update notice made them unsuitable for review. The clean attached set has 30 images from the same real production API with two Notes and two Files, four Tasks, three Logs and four Events. Scroll-driven animations remain active; real Events are in view; the update notice is closed through its UI; the pointer is off controls. All 30 witnesses pass, with no page errors. Pointer focus has no painted outline. Four extra 1440-px light captures show the assigned Calendar, Files, Notes and Tasks opening shortcuts without a focus ring. Money is seventh in the default order and has no first-five shortcut. No new shortcut was added. A diagnostic attempt to use a seventh shortcut timed out after the complete pointer set; the runner now records an unassigned shortcut instead. A matching baseline visual set and Money shortcut proof are not supplied. The orchestrator must review the attached images; this job does not approve its own visual quality. Commands ran in `apps/web`. Final gate output excerpts are verbatim: ```text bun run check PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22544 scoped exceptions svelte-check found 0 errors and 1 warning in 1 file bun run test --maxWorkers=2 Test Files 290 passed (290) Tests 2055 passed (2055) Start at 22:12:03 Duration 400.50s (transform 29%, environment 27%, import 22%, tests 16%, setup 5%) bunx vitest run src/lib/navigation/RetainedTab.svelte.test.ts src/lib/navigation/TabHost.svelte.test.ts --maxWorkers=2 Test Files 2 passed (2) Tests 18 passed (18) ``` The warning is the unchanged empty `.attachment-row:focus` ruleset in `packages/ui/src/components/calendar/AttachmentDeck.svelte`. VersionHistory is unchanged against origin/dev and passes; no timeout or expectation changed. `cargo fmt --check` exited zero with no output. No Rust code changed. Both bench scripts pass `node --check`. The production build passed: ```text Compressed 921 static variants; saved 21525333 bytes. ``` The required single origin/dev merge returned `Already up to date.` before final gates. Local web build output and the baseline worktree were removed. Cargo cleanup output: ```text Removed 1 file, 356B total ``` ## UX gaps closed Private history state no longer causes a warm Notes read. All 40 measured warm switches have zero API requests. Restored native controls keep keyboard focus order. A queued restore does not steal the next keyboard Tab focus. Phone scroll is saved before the shared feed is clamped. Pointer restoration uses the shared quiet-focus helper. Existing Tab Bar and workspace controls remain the visible controls; no shared primitive was restyled. ## Decisions and gaps No UI design was changed. Field-level derived signals implement DESIGN §59's stable-field requirement. The snapshot still publishes shallow history state. The optional storm pair sweep is separate from the required paired profile; its omission is explicit. The evidence-save change preserves partial results without changing acceptance budgets. These are implementation choices, not new product decisions. Photos, Mail, Ask, editors and imports keep the predecessor's route-owned lifetime. The paired measurements precede the final native-focus and scroll fixes; those fixes have focused regression tests, but no third paired timing run. The original reported production loop could not be reproduced at the supplied head and therefore has no attributed root cause or exact loop regression. The 100 ms budget and storm acceptance remain open. The unexpected Composer remains tracked by #1380. The final fifth heap is 60.8% above the paired baseline; five after passes alone do not prove storm bounds. ## For the next verification round Use the fixed production assets and shared release server. Run `bun bench/tab-storm.mjs` locally and through `perf-run calternal` on the VM. It must complete all conditions and prove heap/DOM growth at most 25%, abandoned requests aborted, idle within one second and correct final Tab. Resolve or account for #1380 without silently removing a scenario. Run `bun bench/tab-retention-1233.mjs` with the 200-Note/500-File fixture and paired origin/dev assets after a measured fix for the remaining warm costs. It must retain all eight directions, five samples and five heap passes. Do not treat the current timing table as a budget pass. Reproduce any claimed production loop on fixed assets before assigning its cause. ## Attached production captures These 30 switch-return captures use the clean real API fixture described above, not placeholders. The four extra images show assigned opening shortcuts. The large 200/500 fixture has a separate complete 30-witness pass. Review artifacts stay uncommitted. The orchestrator owns visual approval. No matching baseline visual set was captured. - Calendar: [390 light](https://git.kayg.org/attachments/0b4e6977-b4b8-496e-b9c9-5a244c1269ad), [390 dark](https://git.kayg.org/attachments/84c60bee-ce33-414b-a3ac-bede9cdfeffa), [820 light](https://git.kayg.org/attachments/758e6f61-1b6f-4fe9-80f7-5beb42424f76), [820 dark](https://git.kayg.org/attachments/2782a5e1-72b4-4b94-bf69-326121f5f89d), [1440 light](https://git.kayg.org/attachments/92fc22d2-a3a9-4fa4-a06e-6d1a27b2af78), [1440 dark](https://git.kayg.org/attachments/583d57bf-d4c6-470e-b24b-71e654c7138a) - Files: [390 light](https://git.kayg.org/attachments/35088203-3f82-46e4-8931-16f0554d592f), [390 dark](https://git.kayg.org/attachments/e049546d-bf72-446c-9375-f0146d5759df), [820 light](https://git.kayg.org/attachments/65879fad-4c10-4936-969a-7d9c61ea63ed), [820 dark](https://git.kayg.org/attachments/d4fa3113-c3fb-4f0e-a53a-b0309c4c273e), [1440 light](https://git.kayg.org/attachments/e9a32645-66de-4183-bb9f-008ff8326e10), [1440 dark](https://git.kayg.org/attachments/e8a7a737-6479-4870-9708-4edd0e9d20de) - Notes: [390 light](https://git.kayg.org/attachments/aa5fd937-93c0-4b6c-a346-9687d3bc7ce5), [390 dark](https://git.kayg.org/attachments/bb398087-f8a7-41d7-9315-581cb9113213), [820 light](https://git.kayg.org/attachments/478bc64c-6a99-4f90-82d7-5c1b7629a99d), [820 dark](https://git.kayg.org/attachments/3e4e462e-4b12-4ea0-8493-31b08ad8be21), [1440 light](https://git.kayg.org/attachments/8d0b4e08-870d-42f0-96a4-886efeb044d6), [1440 dark](https://git.kayg.org/attachments/5556758f-ea3d-42f1-acf2-89f43969cb1b) - Tasks: [390 light](https://git.kayg.org/attachments/9f715c8d-b156-426e-8d76-d584422a7281), [390 dark](https://git.kayg.org/attachments/cdc2fb2d-3f35-41ee-86ea-028d58995459), [820 light](https://git.kayg.org/attachments/cab92e48-7af2-4230-8a45-41145196cc9a), [820 dark](https://git.kayg.org/attachments/4eaaeab4-e7db-4c61-9e6d-2e9f07420fbd), [1440 light](https://git.kayg.org/attachments/d2e75095-436b-4df1-9000-889003846336), [1440 dark](https://git.kayg.org/attachments/b866297a-b08b-4369-bdb7-fa55de9120c5) - Money: [390 light](https://git.kayg.org/attachments/558dd31b-43b1-4d93-bc15-b51757d49a09), [390 dark](https://git.kayg.org/attachments/f04de569-6e88-46b2-853c-45197e36cb7e), [820 light](https://git.kayg.org/attachments/73c523ef-b998-4504-8700-f2a7f2ac1dfd), [820 dark](https://git.kayg.org/attachments/fd146433-3892-4942-9066-340583a9c61b), [1440 light](https://git.kayg.org/attachments/0edc193d-e0fe-4501-a348-f43bfa2edfff), [1440 dark](https://git.kayg.org/attachments/f8d662e3-ed5a-4d30-9956-925fb415b757) Opening shortcuts: [Calendar](https://git.kayg.org/attachments/1ff14797-a177-4262-b577-8f3227d12b1f), [Files](https://git.kayg.org/attachments/2e2a6496-9f84-4b0d-9487-f356e1ef761a), [Notes](https://git.kayg.org/attachments/10d42cf0-51bc-4385-b4ed-5d94d3398933), [Tasks](https://git.kayg.org/attachments/f7ca0306-bde3-427e-b7ae-2d07a828ee8c) Raw logs and JSON remain in this worktree under `artifacts/`. Final app full-suite output is `artifacts/1233-test-scroll-final.log`; VM paired rows are `artifacts/tabswitch-1233/perf-vm-final/report.json`; clean capture witnesses are `artifacts/tabswitch-1233/clean-captures/`. Cargo and web build cleanup completed.
Author
Owner

Starting tabswitch4 on job/tabswitch-1233, base/head 6eb9aa044. Read the retained-Tab follow-up and #1233 report. Accepted heap result stays closed unless the storm finds growth. I will attribute input-to-paint wall time, bypass full navigation for retained warm Tabs, preserve history/deep links/access clearing, fix capture witnesses, and run the paired profile and required local/VM storms. No push or deploy.

Starting tabswitch4 on `job/tabswitch-1233`, base/head `6eb9aa044`. Read the retained-Tab follow-up and #1233 report. Accepted heap result stays closed unless the storm finds growth. I will attribute input-to-paint wall time, bypass full navigation for retained warm Tabs, preserve history/deep links/access clearing, fix capture witnesses, and run the paired profile and required local/VM storms. No push or deploy.
Author
Owner

Finding at c6b3843b3: marked production warm switches on the locked perf VM isolate the delay. Calendar → Files: handler 0.0 ms, beforeNavigate 0.4 ms, onNavigate 68.2 ms, retained un-hide committed 91.1 ms, visible probe 114.3 ms, next rAF 165.7 ms, second rAF 212.0 ms. Notes → Tasks: handler 0.0 ms, beforeNavigate 0.6 ms, onNavigate 67.3 ms, retained un-hide committed 104.1 ms, visible probe 123.4 ms, next rAF 165.2 ms, second rAF 224.3 ms. Both destination routes have no load function; there is no View Transition or shared-motion await on this path. The Kit load/commit pipeline takes the first ~68 ms; DOM commit adds ~23–37 ms, and the probe’s two extra frame callbacks add ~98–101 ms. The before profile completes 40 warm samples, four traces and five heap passes with zero page errors. Raw artifacts are under artifacts/tabswitch4/waterfall-before.

The shallow fix is committed (d963f01a6) with 20 passing focused tests. Required origin/dev integration is resolving both retained lifetimes and upstream UI/Status changes at ef0aa12b12. No new visible controls or styles.

Finding at `c6b3843b3`: marked production warm switches on the locked perf VM isolate the delay. Calendar → Files: handler 0.0 ms, beforeNavigate 0.4 ms, onNavigate 68.2 ms, retained un-hide committed 91.1 ms, visible probe 114.3 ms, next rAF 165.7 ms, second rAF 212.0 ms. Notes → Tasks: handler 0.0 ms, beforeNavigate 0.6 ms, onNavigate 67.3 ms, retained un-hide committed 104.1 ms, visible probe 123.4 ms, next rAF 165.2 ms, second rAF 224.3 ms. Both destination routes have no load function; there is no View Transition or shared-motion await on this path. The Kit load/commit pipeline takes the first ~68 ms; DOM commit adds ~23–37 ms, and the probe’s two extra frame callbacks add ~98–101 ms. The before profile completes 40 warm samples, four traces and five heap passes with zero page errors. Raw artifacts are under artifacts/tabswitch4/waterfall-before. The shallow fix is committed (`d963f01a6`) with 20 passing focused tests. Required origin/dev integration is resolving both retained lifetimes and upstream UI/Status changes at `ef0aa12b12`. No new visible controls or styles.
Author
Owner

Head 88b5cdc01. The production shallow-history probe exposed effect_update_depth_exceeded: deep Svelte snapshot state wrapped the private history marker in a proxy, while SvelteKit kept the same marker raw. The snapshot comparison then retriggered itself. Commit 108aed2a0 keeps committed snapshots in raw immutable arrays and scopes each marker to its loaded route. The failing production capture is proxy-repro-before.log; the fixed production probe completed 36 pointer switches with no page errors (proxy-repro-after.log). The focused suite passes 21 tests, including a raw Kit Page/marker identity regression. bun run check now reports svelte-check found 0 errors and 4 warnings in 2 files. The exact full test gate and paired VM run are in progress. No heap expansion work was added.

Head `88b5cdc01`. The production shallow-history probe exposed `effect_update_depth_exceeded`: deep Svelte snapshot state wrapped the private history marker in a proxy, while SvelteKit kept the same marker raw. The snapshot comparison then retriggered itself. Commit `108aed2a0` keeps committed snapshots in raw immutable arrays and scopes each marker to its loaded route. The failing production capture is `proxy-repro-before.log`; the fixed production probe completed 36 pointer switches with no page errors (`proxy-repro-after.log`). The focused suite passes 21 tests, including a raw Kit Page/marker identity regression. `bun run check` now reports `svelte-check found 0 errors and 4 warnings in 2 files`. The exact full test gate and paired VM run are in progress. No heap expansion work was added.
Author
Owner

Head 7547fba23 plus the tested scroll fix (22 focused tests pass). The paired VM run completed all 40 baseline and 40 shallow samples with zero page errors; Back, Forward, reload and access clearing passed. The unchanged input-to-second-callback endpoint is still above 100 ms in seven directions (five samples per direction). Calendar→Files samples: 103.9, 160.8, 184.0, 211.6, 253.1 ms; Notes→Tasks: 86.1, 110.4, 99.3, 123.9, 114.7 ms. Separate compositor traces identify the formerly unattributed gap: the software Display::DrawAndSwap costs 90.8–98.2 ms per draw for Files (root render pass id 1, 77 quads); LayerTreeHost::WaitForCommitCompletion blocks for 188.9 ms. Tasks draws cost 39.1–42.1 ms, with a 196.6 ms commit wait. These traced durations include trace overhead and are not substituted for untraced samples. No load/navigation hooks run on shallow switches.

The capture step now proves initial focus and restored pointer focus without a ring. It then found a separate product scroll regression (Files expected 160, actual 0). The fix saves departing feed state before shallow history changes the DOM and cancels the prior route reset, with a regression test. I am rebuilding for final paired captures and the required local then VM storms. The full gate before this last scroll fix passed: Test Files 296 passed (296) and Tests 2125 passed (2125); final gates are running again for the changed source. Latency acceptance remains open.

Head `7547fba23` plus the tested scroll fix (22 focused tests pass). The paired VM run completed all 40 baseline and 40 shallow samples with zero page errors; Back, Forward, reload and access clearing passed. The unchanged input-to-second-callback endpoint is still above 100 ms in seven directions (five samples per direction). Calendar→Files samples: 103.9, 160.8, 184.0, 211.6, 253.1 ms; Notes→Tasks: 86.1, 110.4, 99.3, 123.9, 114.7 ms. Separate compositor traces identify the formerly unattributed gap: the software `Display::DrawAndSwap` costs 90.8–98.2 ms per draw for Files (root render pass id 1, 77 quads); `LayerTreeHost::WaitForCommitCompletion` blocks for 188.9 ms. Tasks draws cost 39.1–42.1 ms, with a 196.6 ms commit wait. These traced durations include trace overhead and are not substituted for untraced samples. No load/navigation hooks run on shallow switches. The capture step now proves initial focus and restored pointer focus without a ring. It then found a separate product scroll regression (Files expected 160, actual 0). The fix saves departing feed state before shallow history changes the DOM and cancels the prior route reset, with a regression test. I am rebuilding for final paired captures and the required local then VM storms. The full gate before this last scroll fix passed: `Test Files 296 passed (296)` and `Tests 2125 passed (2125)`; final gates are running again for the changed source. Latency acceptance remains open.
Author
Owner

Correction to the sample count above: all eight directions have p95 above 100 ms using the existing nearest-rank protocol for five samples. Notes→Files has a 120.8 ms maximum (p95), although three of its five samples are below 100 ms. The final table will show every direction.

Correction to the sample count above: all eight directions have p95 above 100 ms using the existing nearest-rank protocol for five samples. Notes→Files has a 120.8 ms maximum (p95), although three of its five samples are below 100 ms. The final table will show every direction.
Author
Owner

Head 75a6be179. The first local three-storm run finished with no page errors, but the final Photos witness became false at one second. Code inspection found a concrete last-intent defect: a click on the already selected Tab returned while another cold goto was pending. Commit 7dd769465 fixes that guard.

The VM completed its first 120 second 4× CPU storm, then stalled in storm two for over 20 minutes with the renderer saturated. Its current URL was /tasks; a second post-GC row never arrived. I stopped the invalid session and preserved the first storm plus the stalled-run metadata. The eviction fallback called goto inside a tracked effect. goto and its synchronous beforeNavigate callbacks read/write router signals, so the effect could cancel and restart its own recovery before it committed. Commit 75a6be179 makes recovery untracked, permits one attempt per missing target, defers to a newer cold load, and sends failures through the existing route error UI. It also supersedes older cold loads after shallow Back/Forward. There are 25 passing focused regressions; the exact full gates are running for this source. A fresh local then VM storm round is required and in progress.

The VM request witness also resolves a misleading budget result: the two requests open at one second were /api/v1/files/events and /api/v1/notes/events, both shared session streams. The raw #1338 counters and budgets remain unchanged. They must not be interpreted as uncancelled per-Tab reads. The local heap rose 39.2% and DOM 85.4% from storm one to three; snapshot root counts showed no detached Calendar panels, unlike the previous #1356 signature. This does not prove bounded storm memory yet. The final report will include the complete fresh round, and will remain NOT READY if the required budgets or scenarios remain unresolved.

Head `75a6be179`. The first local three-storm run finished with no page errors, but the final Photos witness became false at one second. Code inspection found a concrete last-intent defect: a click on the already selected Tab returned while another cold goto was pending. Commit `7dd769465` fixes that guard. The VM completed its first 120 second 4× CPU storm, then stalled in storm two for over 20 minutes with the renderer saturated. Its current URL was `/tasks`; a second post-GC row never arrived. I stopped the invalid session and preserved the first storm plus the stalled-run metadata. The eviction fallback called goto inside a tracked effect. goto and its synchronous beforeNavigate callbacks read/write router signals, so the effect could cancel and restart its own recovery before it committed. Commit `75a6be179` makes recovery untracked, permits one attempt per missing target, defers to a newer cold load, and sends failures through the existing route error UI. It also supersedes older cold loads after shallow Back/Forward. There are 25 passing focused regressions; the exact full gates are running for this source. A fresh local then VM storm round is required and in progress. The VM request witness also resolves a misleading budget result: the two requests open at one second were `/api/v1/files/events` and `/api/v1/notes/events`, both shared session streams. The raw #1338 counters and budgets remain unchanged. They must not be interpreted as uncancelled per-Tab reads. The local heap rose 39.2% and DOM 85.4% from storm one to three; snapshot root counts showed no detached Calendar panels, unlike the previous #1356 signature. This does not prove bounded storm memory yet. The final report will include the complete fresh round, and will remain NOT READY if the required budgets or scenarios remain unresolved.
Author
Owner

tabswitch4 finding at 87069cb66 (product 75a6be179): all three CPU4 VM storms completed without a page error, correct final Tab at 1 s in all three. First-to-third heap +6.4%, DOM -3.9%, renderer RSS +4.3%; idle failed all three. One attached Calendar panel and zero detached panels in both snapshots.

Native setup then failed its existing Calendar Settings marker: the changed forwarded origin had no remembered Settings location, so Cmd+, opened the real Account default. The captured Settings Account screen is evidence that this was a fixture mistake. The profile now visits its exact Calendar Settings deep link before timing and can select one named condition to preserve completed CPU4 results. No product behavior, content assertion, timing limit or budget changed. Native is running again with the same fixture generator, release server and production web build.

tabswitch4 finding at 87069cb66 (product 75a6be179): all three CPU4 VM storms completed without a page error, correct final Tab at 1 s in all three. First-to-third heap +6.4%, DOM -3.9%, renderer RSS +4.3%; idle failed all three. One attached Calendar panel and zero detached panels in both snapshots. Native setup then failed its existing Calendar Settings marker: the changed forwarded origin had no remembered Settings location, so Cmd+, opened the real Account default. The captured Settings Account screen is evidence that this was a fixture mistake. The profile now visits its exact Calendar Settings deep link before timing and can select one named condition to preserve completed CPU4 results. No product behavior, content assertion, timing limit or budget changed. Native is running again with the same fixture generator, release server and production web build.
Author
Owner

tabswitch4 measured finding at cfc9c9ba6 (product 75a6be179): the final paired VM recheck completes 40 baseline + 40 after samples, five per direction, using the same baseline web build 0419170fe4 and release-server SHA256 42993fbd7660d35b1af91ab3a48d539666e0e67e5ae9d27c05a2f639d0680eaf. All 40 warm targets retain their DOM identity and skip navigation hooks. Real Back/Forward/reload/access-clear probes pass; zero page errors. Warm p95 is 118.5–184.1 ms, so none meets 100 ms. Synchronous reveal p95 is 14.5–47.0 ms. Final traced Calendar→Files and Notes→Tasks name software Display::DrawAndSwap (51.3/59.4 ms max) and LayerTreeHost::WaitForCommitCompletion (187.5/178.5 ms max); they no longer show a route-pipeline wait. Trace overhead is kept separate from the budget samples.

All required storms are complete: three local + three CPU4 VM + three native VM. CPU4 first→third heap +6.4%, DOM -3.9%, renderer RSS +4.3%; native heap +25.3%, DOM +25.1%, renderer RSS +46.4%; local heap +24.6%, DOM +61.7%. There are no page errors. CPU4 and native snapshots each keep one attached Calendar panel and zero detached Calendar panels. Inspector request records also grow, but that does not prove bounded memory. The raw memory budgets fail.

Native final Mail readiness fails the bounded post-stop poll. Its cause is not yet established: the third heap has no attached Composer or Search surface. This is a correctness gap recorded here; an earlier paint marker is not proof that the final Tab remains correct. Idle passes only native storm two; all other idle checks fail. Prior-view requests at the one-second sample are shared event streams, including two Files streams in native; ordinary reads have finished or aborted. The original counters and assertions were not relaxed. The extra readiness poll starts after the idle sample and can wait one further second, so a positive result does not prove an exact one-second deadline.

The local focus witness passes all 30 width/theme cases plus four available Mac opening-shortcut checks. A late build notice made those images unclean; a fresh VM capture now dismisses that notice before the final pointer/shortcut action and asserts its absence. Clean captures are in progress. Current status remains NOT READY: latency target, storm memory/idle, and native final readiness are unresolved.

tabswitch4 measured finding at cfc9c9ba6 (product 75a6be179): the final paired VM recheck completes 40 baseline + 40 after samples, five per direction, using the same baseline web build 0419170fe456b58614befcb12d9c9d255118f1c0 and release-server SHA256 42993fbd7660d35b1af91ab3a48d539666e0e67e5ae9d27c05a2f639d0680eaf. All 40 warm targets retain their DOM identity and skip navigation hooks. Real Back/Forward/reload/access-clear probes pass; zero page errors. Warm p95 is 118.5–184.1 ms, so none meets 100 ms. Synchronous reveal p95 is 14.5–47.0 ms. Final traced Calendar→Files and Notes→Tasks name software Display::DrawAndSwap (51.3/59.4 ms max) and LayerTreeHost::WaitForCommitCompletion (187.5/178.5 ms max); they no longer show a route-pipeline wait. Trace overhead is kept separate from the budget samples. All required storms are complete: three local + three CPU4 VM + three native VM. CPU4 first→third heap +6.4%, DOM -3.9%, renderer RSS +4.3%; native heap +25.3%, DOM +25.1%, renderer RSS +46.4%; local heap +24.6%, DOM +61.7%. There are no page errors. CPU4 and native snapshots each keep one attached Calendar panel and zero detached Calendar panels. Inspector request records also grow, but that does not prove bounded memory. The raw memory budgets fail. Native final Mail readiness fails the bounded post-stop poll. Its cause is not yet established: the third heap has no attached Composer or Search surface. This is a correctness gap recorded here; an earlier paint marker is not proof that the final Tab remains correct. Idle passes only native storm two; all other idle checks fail. Prior-view requests at the one-second sample are shared event streams, including two Files streams in native; ordinary reads have finished or aborted. The original counters and assertions were not relaxed. The extra readiness poll starts after the idle sample and can wait one further second, so a positive result does not prove an exact one-second deadline. The local focus witness passes all 30 width/theme cases plus four available Mac opening-shortcut checks. A late build notice made those images unclean; a fresh VM capture now dismisses that notice before the final pointer/shortcut action and asserts its absence. Clean captures are in progress. Current status remains NOT READY: latency target, storm memory/idle, and native final readiness are unresolved.
Author
Owner

tabswitch4 final report — NOT READY

Branch: job/tabswitch-1233. Head: d6f5b46972f509b211a69b0a936dce79d43457d1. Product source measured: 75a6be179. Start: 6eb9aa044. The authorized one-time merge of origin/dev is 9c740886b. No push, deploy or issue closure.

Built

Warm retained Tabs reveal synchronously in the input handler through flushSync, then use SvelteKit shallow history. Cold destinations keep normal navigation. A validated Page facade keeps the sidebar, shell and retained trees on the selected URL. Back/Forward, reload, access-loss clearing, retained scroll/focus, pending cold navigation cancellation and one-attempt recovery of evicted history entries have focused regressions. No shared motion duration changes. No new visible control or local styling.

Warm work is a lookup over at most three snapshots, one history update and one synchronous reactive commit. All 40 final paired warm samples retain target DOM identity and skip the navigation hooks. The real production Back, Forward, reload and access-clear probes pass. The focused suite passes 25 tests.

Files: apps/web/src/lib/navigation/{retainedNavigation.ts,tabContext.svelte.ts,RetainedTab.svelte,TabHost.svelte,TabHost.svelte.test.ts}, apps/web/src/lib/components/app-sidebar.svelte, apps/web/src/routes/+layout.svelte, apps/web/src/app.d.ts; bench/{tab-retention-1233.mjs,tab-storm.mjs}; shared capture helpers in apps/web/e2e/{animation-trace.mjs,harness.mjs,linkcards-1151.mjs}; exact existing perf-pin refreshes under scripts/perf_guards; docs/perf/2026-10-08-1233-shallow-tabs.md and its raw scalar runs under docs/perf/runs/. Lockfiles and dependencies are unchanged.

Attribution and paired measurement

The before/after waterfalls for Calendar → Files and Notes → Tasks are in the committed report. Before: goto → onNavigate took about 67–68 ms, then the retained tree committed at 91–104 ms. After: no navigation hook or route load runs. No View Transition, motion wait, timer/tick chain, font or image decode was found on this path. Remaining traced wall time is software root-render-pass composition and commit wait: maximum Display::DrawAndSwap 51.3/59.4 ms and LayerTreeHost::WaitForCommitCompletion 187.5/178.5 ms. Trace overhead is separate from the untraced numbers below. Document Paint is not element-specific Paint; the unchanged benchmark endpoint includes visibility polling and two later animation callbacks.

Same paired baseline 0419170fe456b58614befcb12d9c9d255118f1c0, same release server, fixture and protocol; five samples per direction. Measured under perf-run calternal tabswitch4-recovery-paired 25, lock load 4.55 / 4.15 / 3.86. Product source 75a6be179 is included in the web build whose ID is 7dd76946566bc17d929a87160666d50ab78ccff6 (build started before that product commit).

Direction Before p50/p95 ms After p50/p95 ms Synchronous reveal p95 ms
Files → Calendar 192.7 / 228.0 142.4 / 149.2 41.9
Calendar → Files 232.5 / 288.1 136.2 / 184.1 32.5
Notes → Files 238.3 / 279.6 136.3 / 172.0 32.5
Files → Notes 226.0 / 253.9 134.5 / 142.9 47.0
Tasks → Notes 243.9 / 268.4 122.1 / 140.7 41.8
Notes → Tasks 177.3 / 186.6 114.5 / 118.5 20.6
Money → Tasks 171.9 / 223.0 130.9 / 145.1 21.2
Tasks → Money 214.0 / 236.0 135.3 / 181.0 14.5

None meets 100 ms p95. Next cost: software composition and commit scheduling. The accepted three-tree absolute heap result is not reopened; final paired heap pass 1→5 grows 3.2%. Storm growth below remains unresolved.

Required storms: completed locally and on the VM

Three consecutive 120 s local storms and six VM storms (three CPU4 plus three native) completed on the same product and large #1338 fixture. All original budgets remain. VM sessions used perf-run calternal tabstorm-1338 35; lock-start loads were 5.50 / 5.47 / 4.22 (CPU4) and 2.67 / 2.71 / 3.45 (native). No page errors occurred.

Condition First→third post-GC heap First→third DOM Idle at 1 s Post-stop readiness
Local +24.6% +61.7% 0/3 3/3
CPU4 VM +6.4% −3.9% 0/3 3/3
Native VM +25.3% +25.1% 1/3 2/3; final Mail fails

#1338 local comparison was +22.3% heap and +112.3% DOM. Different final views do not isolate a leak. Native renderer RSS grows 46.4%; CPU4 RSS grows 4.3%. Thus bounded memory/DOM is not proved. Heap artifacts remain available for investigation. At one second ordinary prior-view reads have finished or aborted; shared SSE streams remain, including two Files streams in the native ledger. Ownership needs investigation. Known #1380 Composer events are recorded and dismissed by the harness; no product fix is claimed. Native Mail readiness cause is unknown; final native heap has no attached Composer/search overlay.

The added readiness poll begins after the one-second idle sample and waits up to one more second. A passing poll does not prove the exact one-second deadline. The legacy final_paint_within_1s flag checks marker existence, so numeric durations remain authoritative. CPU4 endpoints are 386.6, 1615.1, 1109.3 ms; native endpoints are 120.8, 255.4, 168.0 ms. Original failing limits are retained. The supplementary all-pairs warm pass is skipped because the requested eight-direction paired protocol was measured separately; it is not a passing budget claim.

Gates — verbatim

bun run check — exit 0:

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22543 scoped exceptions
svelte-check found 0 errors and 4 warnings in 2 files

Four unused-selector warnings are in Mail/Money. Existing exact pins were refreshed; no ratchet raised.

bun run test --maxWorkers=2 — exit 0:

 Test Files  296 passed (296)
      Tests  2129 passed (2129)
   Start at  01:08:46
   Duration  906.56s (transform 38%, environment 21%, import 20%, tests 16%, setup 4%)

cargo fmt --check — exit 0, no output. No Rust product changes; no Rust crate build needed. Bench/capture JavaScript syntax checks pass. Existing test expectations were not weakened. Final touched module comments were re-read. cargo clean output:

     Removed 1 file, 356B total

Local and job-owned VM web/build output is removed. Ignored screenshots, traces and heaps remain local for review.

UX gaps closed / left

Closed: synchronous warm reveal, exact shallow URL/history, access clearing, retained scroll/focus, cancellation of stale cold loads, bounded history recovery and authentic capture focus. The existing Tab Bar, sidebar, RequestState, focusQuietly and provideViewActivity are reused.

Left: warm p95 target, storm memory/DOM growth, idle failures, native final Mail readiness, prior-view shared SSE ownership, known #1380 focus retargeting. Enlarged Task rows show the existing checkbox below the title cap-height center in both baseline and target: tracked by the existing #1385 consistency sweep. No Task style changed. Claude visual review remains pending.

Decisions

  • Private shallow history marker records the last loaded URL, so later full navigation cannot carry a stale snapshot selection.
  • A warm switch while a cold load is pending uses normal navigation to cancel it; shallow history alone cannot cancel that load.
  • An evicted history entry gets one untracked recovery attempt per missing URL; failure uses the existing route-error UI.
  • Keep the paired measurement endpoint and all storm budgets unchanged; compositor traces add evidence rather than substituting a faster endpoint.
  • Prime the actual Calendar Settings deep link at each new forwarded origin before storm timing. This fixes fixture state, not product behavior.
  • Move the existing build-notice dismissal helper to the shared capture harness; dismiss before the final real pointer/shortcut action and assert absence afterward. No synthetic focus change is used to satisfy the witness.

Clean screenshot evidence

94 original-size images: five Tabs × three widths × two themes × baseline/target/pointer, plus four available opening shortcuts. Both builds emulate macOS. All 30 pointer witnesses and four shortcut witnesses pass with no painted outline. Files/Notes preserve a 160 px feed offset. Money is outside the default first five shortcuts; no new chord was invented. Every capture asserts no update notice. Only tabswitch4-clean- attachments below are valid final visual evidence. Enlarged icon/label crops were inspected; original images are unchanged.

B = paired baseline, T = target, P = immediately after pointer switch.

Tab 1440 light 1440 dark 820 light 820 dark 390 light 390 dark
Calendar B / T / P B / T / P B / T / P B / T / P B / T / P B / T / P
Files B / T / P B / T / P B / T / P B / T / P B / T / P B / T / P
Notes B / T / P B / T / P B / T / P B / T / P B / T / P B / T / P
Tasks B / T / P B / T / P B / T / P B / T / P B / T / P B / T / P
Money B / T / P B / T / P B / T / P B / T / P B / T / P B / T / P

Opening shortcut witnesses: calendar-1440-light-shortcut, files-1440-light-shortcut, notes-1440-light-shortcut, tasks-1440-light-shortcut.

For the merge round

cd apps/web && bun run test:e2e against matching production assets: combined shell, deep links, Back/Forward, access loss and keyboard/touch focus. The required local and VM storms were completed in this job; they are not deferred.

NOT READY

# tabswitch4 final report — NOT READY Branch: `job/tabswitch-1233`. Head: `d6f5b46972f509b211a69b0a936dce79d43457d1`. Product source measured: `75a6be179`. Start: `6eb9aa044`. The authorized one-time merge of `origin/dev` is `9c740886b`. No push, deploy or issue closure. ## Built Warm retained Tabs reveal synchronously in the input handler through `flushSync`, then use SvelteKit shallow history. Cold destinations keep normal navigation. A validated Page facade keeps the sidebar, shell and retained trees on the selected URL. Back/Forward, reload, access-loss clearing, retained scroll/focus, pending cold navigation cancellation and one-attempt recovery of evicted history entries have focused regressions. No shared motion duration changes. No new visible control or local styling. Warm work is a lookup over at most three snapshots, one history update and one synchronous reactive commit. All 40 final paired warm samples retain target DOM identity and skip the navigation hooks. The real production Back, Forward, reload and access-clear probes pass. The focused suite passes 25 tests. Files: `apps/web/src/lib/navigation/{retainedNavigation.ts,tabContext.svelte.ts,RetainedTab.svelte,TabHost.svelte,TabHost.svelte.test.ts}`, `apps/web/src/lib/components/app-sidebar.svelte`, `apps/web/src/routes/+layout.svelte`, `apps/web/src/app.d.ts`; `bench/{tab-retention-1233.mjs,tab-storm.mjs}`; shared capture helpers in `apps/web/e2e/{animation-trace.mjs,harness.mjs,linkcards-1151.mjs}`; exact existing perf-pin refreshes under `scripts/perf_guards`; `docs/perf/2026-10-08-1233-shallow-tabs.md` and its raw scalar runs under `docs/perf/runs/`. Lockfiles and dependencies are unchanged. ## Attribution and paired measurement The before/after waterfalls for Calendar → Files and Notes → Tasks are in the committed report. Before: `goto` → `onNavigate` took about 67–68 ms, then the retained tree committed at 91–104 ms. After: no navigation hook or route load runs. No View Transition, motion wait, timer/tick chain, font or image decode was found on this path. Remaining traced wall time is software root-render-pass composition and commit wait: maximum `Display::DrawAndSwap` 51.3/59.4 ms and `LayerTreeHost::WaitForCommitCompletion` 187.5/178.5 ms. Trace overhead is separate from the untraced numbers below. Document Paint is not element-specific Paint; the unchanged benchmark endpoint includes visibility polling and two later animation callbacks. Same paired baseline `0419170fe456b58614befcb12d9c9d255118f1c0`, same release server, fixture and protocol; five samples per direction. Measured under `perf-run calternal tabswitch4-recovery-paired 25`, lock load 4.55 / 4.15 / 3.86. Product source `75a6be179` is included in the web build whose ID is `7dd76946566bc17d929a87160666d50ab78ccff6` (build started before that product commit). | Direction | Before p50/p95 ms | After p50/p95 ms | Synchronous reveal p95 ms | | --- | ---: | ---: | ---: | | Files → Calendar | 192.7 / 228.0 | 142.4 / 149.2 | 41.9 | | Calendar → Files | 232.5 / 288.1 | 136.2 / 184.1 | 32.5 | | Notes → Files | 238.3 / 279.6 | 136.3 / 172.0 | 32.5 | | Files → Notes | 226.0 / 253.9 | 134.5 / 142.9 | 47.0 | | Tasks → Notes | 243.9 / 268.4 | 122.1 / 140.7 | 41.8 | | Notes → Tasks | 177.3 / 186.6 | 114.5 / 118.5 | 20.6 | | Money → Tasks | 171.9 / 223.0 | 130.9 / 145.1 | 21.2 | | Tasks → Money | 214.0 / 236.0 | 135.3 / 181.0 | 14.5 | **None meets 100 ms p95.** Next cost: software composition and commit scheduling. The accepted three-tree absolute heap result is not reopened; final paired heap pass 1→5 grows 3.2%. Storm growth below remains unresolved. ## Required storms: completed locally and on the VM Three consecutive 120 s local storms and six VM storms (three CPU4 plus three native) completed on the same product and large #1338 fixture. All original budgets remain. VM sessions used `perf-run calternal tabstorm-1338 35`; lock-start loads were 5.50 / 5.47 / 4.22 (CPU4) and 2.67 / 2.71 / 3.45 (native). No page errors occurred. | Condition | First→third post-GC heap | First→third DOM | Idle at 1 s | Post-stop readiness | | --- | ---: | ---: | --- | --- | | Local | +24.6% | +61.7% | 0/3 | 3/3 | | CPU4 VM | +6.4% | −3.9% | 0/3 | 3/3 | | Native VM | +25.3% | +25.1% | 1/3 | 2/3; final Mail fails | #1338 local comparison was +22.3% heap and +112.3% DOM. Different final views do not isolate a leak. Native renderer RSS grows 46.4%; CPU4 RSS grows 4.3%. Thus bounded memory/DOM is not proved. Heap artifacts remain available for investigation. At one second ordinary prior-view reads have finished or aborted; shared SSE streams remain, including two Files streams in the native ledger. Ownership needs investigation. Known #1380 Composer events are recorded and dismissed by the harness; no product fix is claimed. Native Mail readiness cause is unknown; final native heap has no attached Composer/search overlay. The added readiness poll begins after the one-second idle sample and waits up to one more second. A passing poll does not prove the exact one-second deadline. The legacy `final_paint_within_1s` flag checks marker existence, so numeric durations remain authoritative. CPU4 endpoints are 386.6, 1615.1, 1109.3 ms; native endpoints are 120.8, 255.4, 168.0 ms. Original failing limits are retained. The supplementary all-pairs warm pass is skipped because the requested eight-direction paired protocol was measured separately; it is not a passing budget claim. ## Gates — verbatim `bun run check` — exit 0: ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22543 scoped exceptions svelte-check found 0 errors and 4 warnings in 2 files ``` Four unused-selector warnings are in Mail/Money. Existing exact pins were refreshed; no ratchet raised. `bun run test --maxWorkers=2` — exit 0: ```text Test Files 296 passed (296) Tests 2129 passed (2129) Start at 01:08:46 Duration 906.56s (transform 38%, environment 21%, import 20%, tests 16%, setup 4%) ``` `cargo fmt --check` — exit 0, no output. No Rust product changes; no Rust crate build needed. Bench/capture JavaScript syntax checks pass. Existing test expectations were not weakened. Final touched module comments were re-read. `cargo clean` output: ```text Removed 1 file, 356B total ``` Local and job-owned VM web/build output is removed. Ignored screenshots, traces and heaps remain local for review. ## UX gaps closed / left Closed: synchronous warm reveal, exact shallow URL/history, access clearing, retained scroll/focus, cancellation of stale cold loads, bounded history recovery and authentic capture focus. The existing Tab Bar, sidebar, `RequestState`, `focusQuietly` and `provideViewActivity` are reused. Left: warm p95 target, storm memory/DOM growth, idle failures, native final Mail readiness, prior-view shared SSE ownership, known #1380 focus retargeting. Enlarged Task rows show the existing checkbox below the title cap-height center in both baseline and target: tracked by the existing #1385 consistency sweep. No Task style changed. Claude visual review remains pending. ## Decisions - Private shallow history marker records the last loaded URL, so later full navigation cannot carry a stale snapshot selection. - A warm switch while a cold load is pending uses normal navigation to cancel it; shallow history alone cannot cancel that load. - An evicted history entry gets one untracked recovery attempt per missing URL; failure uses the existing route-error UI. - Keep the paired measurement endpoint and all storm budgets unchanged; compositor traces add evidence rather than substituting a faster endpoint. - Prime the actual Calendar Settings deep link at each new forwarded origin before storm timing. This fixes fixture state, not product behavior. - Move the existing build-notice dismissal helper to the shared capture harness; dismiss before the final real pointer/shortcut action and assert absence afterward. No synthetic focus change is used to satisfy the witness. ## Clean screenshot evidence 94 original-size images: five Tabs × three widths × two themes × baseline/target/pointer, plus four available opening shortcuts. Both builds emulate macOS. All 30 pointer witnesses and four shortcut witnesses pass with no painted outline. Files/Notes preserve a 160 px feed offset. Money is outside the default first five shortcuts; no new chord was invented. Every capture asserts no update notice. Only `tabswitch4-clean-` attachments below are valid final visual evidence. Enlarged icon/label crops were inspected; original images are unchanged. B = paired baseline, T = target, P = immediately after pointer switch. | Tab | 1440 light | 1440 dark | 820 light | 820 dark | 390 light | 390 dark | | --- | --- | --- | --- | --- | --- | --- | | Calendar | [B](https://git.kayg.org/attachments/f02c3235-43a1-4db2-b365-efd212d1fb7c) / [T](https://git.kayg.org/attachments/f2b463ba-6faf-45d3-ab52-622bc2ffc5bb) / [P](https://git.kayg.org/attachments/834be1ee-2912-499e-918a-4ba660f4478f) | [B](https://git.kayg.org/attachments/0953ff57-ba6e-43a0-8a89-4262342f7e46) / [T](https://git.kayg.org/attachments/e597c355-c910-41b4-ab05-34d147677674) / [P](https://git.kayg.org/attachments/2235bd70-5fec-4e17-92ba-27616c70a910) | [B](https://git.kayg.org/attachments/a18e8c26-248b-4987-ae27-3b27a0f7d29a) / [T](https://git.kayg.org/attachments/1bbeb7f5-e778-41f0-9f3b-a5d0cb224098) / [P](https://git.kayg.org/attachments/bd89e3f8-e8fe-4159-ac35-9e71952837bd) | [B](https://git.kayg.org/attachments/3cc4f79b-762c-472f-87e5-303248359cff) / [T](https://git.kayg.org/attachments/1fc1df25-d9e9-49a4-903e-951b69c97f18) / [P](https://git.kayg.org/attachments/fe912362-5a44-4c29-9ae7-0cb46b992308) | [B](https://git.kayg.org/attachments/dc95582b-cd37-484b-95a8-66391643d010) / [T](https://git.kayg.org/attachments/23aec306-bdd9-448c-a396-80d02286ca82) / [P](https://git.kayg.org/attachments/4ed2354f-a9da-4a30-80b8-249f4f67c15c) | [B](https://git.kayg.org/attachments/e117596a-c9f6-4a14-92e9-30cd39c0830c) / [T](https://git.kayg.org/attachments/be51e7af-5e56-4625-a9df-6d6cce36878a) / [P](https://git.kayg.org/attachments/60bb7c9c-e946-40cc-8590-7647b97413d8) | | Files | [B](https://git.kayg.org/attachments/3d82a76f-ba17-4d24-90c0-4362ef938f42) / [T](https://git.kayg.org/attachments/b5fd2ffb-e79f-4188-8170-33e61ea95b94) / [P](https://git.kayg.org/attachments/4a28eb12-b9ab-4a29-9163-42fa99f31474) | [B](https://git.kayg.org/attachments/5ace0efe-45e0-4c28-aed2-79acbb81e05f) / [T](https://git.kayg.org/attachments/48c1534a-17e3-4abd-8763-8d71046d8ef5) / [P](https://git.kayg.org/attachments/879f36f5-4814-4f36-b190-b777bce8aa14) | [B](https://git.kayg.org/attachments/c109ccc2-b4ad-4471-880e-5606ef331da6) / [T](https://git.kayg.org/attachments/ea1ccb3c-e433-4300-8889-4c51441ddc2f) / [P](https://git.kayg.org/attachments/570fdeca-7e00-46c8-ba6b-ee6164867195) | [B](https://git.kayg.org/attachments/95c944dd-5488-4998-bd24-074dcdc1d3d1) / [T](https://git.kayg.org/attachments/a92a9605-95e6-40dd-aefd-2ae1fe6b44e9) / [P](https://git.kayg.org/attachments/6cfb1783-2775-4e85-ab1a-65d23f28eb39) | [B](https://git.kayg.org/attachments/f5e808a1-d10f-42a4-82b5-80919fec2699) / [T](https://git.kayg.org/attachments/2d51ca19-b766-4bba-9adc-4bba528f00ab) / [P](https://git.kayg.org/attachments/fd507de7-0258-4472-adc9-7e7160b3794f) | [B](https://git.kayg.org/attachments/9fa72fad-5238-4ca5-a182-76c9fc8cbe8e) / [T](https://git.kayg.org/attachments/ac863f3e-513c-49d3-86fb-ef2f8a8bcaab) / [P](https://git.kayg.org/attachments/4a57d6c3-3f3d-43cf-8a2d-a59ef7905619) | | Notes | [B](https://git.kayg.org/attachments/ea6acfcd-bfec-4083-91da-84ae39e8a872) / [T](https://git.kayg.org/attachments/68de967c-8cae-40c3-90bb-48223eaf5572) / [P](https://git.kayg.org/attachments/b190cb38-89a3-49e4-badb-95c4afb734b2) | [B](https://git.kayg.org/attachments/b5e6e8a8-3308-458c-ab9d-5c7469f509c4) / [T](https://git.kayg.org/attachments/bdd33908-e556-4c9b-938e-7b6713f0a502) / [P](https://git.kayg.org/attachments/d8bd6a3c-6221-41aa-94a2-62de85c30e6f) | [B](https://git.kayg.org/attachments/bdace311-6173-4493-8fde-b5885bdb6518) / [T](https://git.kayg.org/attachments/4d79dc48-0a08-4c8a-98cc-2b768b7039c0) / [P](https://git.kayg.org/attachments/89419566-d175-450e-b112-2b220a82925d) | [B](https://git.kayg.org/attachments/d2f0c227-e069-48bc-a0e4-5cdd3d2d6434) / [T](https://git.kayg.org/attachments/b1f01662-883f-4301-b51a-9584dc844665) / [P](https://git.kayg.org/attachments/e1130d02-ccf4-45df-9c3e-836886f13a51) | [B](https://git.kayg.org/attachments/fe60eaeb-9c7f-4b5e-bb68-c696c148a078) / [T](https://git.kayg.org/attachments/309b2295-dcc2-431a-84c3-e230a539698f) / [P](https://git.kayg.org/attachments/c6d035eb-0baf-4859-89b9-e859833d15b9) | [B](https://git.kayg.org/attachments/557f6a51-3450-4b8c-a1a3-ed715468892c) / [T](https://git.kayg.org/attachments/bc2ddf36-cc18-4ecd-ba00-d5f9385f2073) / [P](https://git.kayg.org/attachments/5d486952-102f-478e-87dc-c03ecd2fc346) | | Tasks | [B](https://git.kayg.org/attachments/b4f652a2-c07c-4d3a-ac17-ef42c1e0a9fc) / [T](https://git.kayg.org/attachments/a6b3c930-8023-4dc4-8114-6824dfdd529d) / [P](https://git.kayg.org/attachments/4a22996b-324e-48f2-8478-25c8d6d9871e) | [B](https://git.kayg.org/attachments/f656b6a0-9e16-4872-9ddd-0d3970388baa) / [T](https://git.kayg.org/attachments/48c74e4e-1d8b-4214-81ed-96027a902250) / [P](https://git.kayg.org/attachments/d1b77b0f-85df-4481-a886-537da40c707b) | [B](https://git.kayg.org/attachments/7e076b70-b483-4624-a1a1-cb49972b544e) / [T](https://git.kayg.org/attachments/24126f96-e399-48c2-ad5d-d885a8ee5956) / [P](https://git.kayg.org/attachments/154ce0d1-dfe1-49d5-8685-272c3346380b) | [B](https://git.kayg.org/attachments/58627dab-10b6-4fc8-99f1-db34db54915a) / [T](https://git.kayg.org/attachments/82c88f20-5add-4d48-a883-dec00bfde25b) / [P](https://git.kayg.org/attachments/cde5ab06-a84b-4d12-9c9e-cf85254566d7) | [B](https://git.kayg.org/attachments/facc4044-3dd0-487f-a7fd-dc03da10b034) / [T](https://git.kayg.org/attachments/e45fd469-5258-4760-8c77-3bddd0268952) / [P](https://git.kayg.org/attachments/e71a1fca-bbcc-4889-a7b5-d9be7a7a25a8) | [B](https://git.kayg.org/attachments/396f2a69-b097-4c7e-9cb3-ff0978438954) / [T](https://git.kayg.org/attachments/b1d5a896-8b3c-4e5b-9f18-6a32367df5fc) / [P](https://git.kayg.org/attachments/8f42ac8f-c80d-4d3f-be6a-3ee7efba4267) | | Money | [B](https://git.kayg.org/attachments/4bd8f2aa-29d3-4a5e-b2da-959decddda51) / [T](https://git.kayg.org/attachments/e2c5f09c-c812-4467-a87a-03e8c881a57f) / [P](https://git.kayg.org/attachments/20809142-46bb-4313-b272-fc6bd63820ee) | [B](https://git.kayg.org/attachments/3da34deb-520d-4980-8cec-8f35cc6e1317) / [T](https://git.kayg.org/attachments/ffd82219-4796-4669-8054-56ff7819e935) / [P](https://git.kayg.org/attachments/9184e57c-73ee-45b5-9614-0334ab871a6e) | [B](https://git.kayg.org/attachments/68d1b7ee-eb71-4b43-b18e-f5349ddfdabc) / [T](https://git.kayg.org/attachments/65efe2c9-db73-4d7e-b80b-d393b05d171b) / [P](https://git.kayg.org/attachments/ac526d4b-126b-418e-bc11-b0271e0c2697) | [B](https://git.kayg.org/attachments/c7531514-6462-47df-8632-d906b8116008) / [T](https://git.kayg.org/attachments/b50eddda-5b2d-4d71-bd15-4a7e49e7eecb) / [P](https://git.kayg.org/attachments/b5e2d828-3077-4fab-b4eb-9f199cc795fe) | [B](https://git.kayg.org/attachments/0079ad98-cb2c-47c6-bd8d-bd4e6c51f9f2) / [T](https://git.kayg.org/attachments/2f3e8d40-fb71-4daf-bd94-6e7dfd08415c) / [P](https://git.kayg.org/attachments/c4814a31-3af0-47ca-a08c-8fafaa7689f1) | [B](https://git.kayg.org/attachments/6f91df99-7ecd-4ad6-843a-6e78671e8355) / [T](https://git.kayg.org/attachments/48e53b30-9e13-48a3-aa61-d0afb43dbadf) / [P](https://git.kayg.org/attachments/4aa5dd17-934c-4055-8a56-45d9e5662612) | Opening shortcut witnesses: [calendar-1440-light-shortcut](https://git.kayg.org/attachments/eeea98d2-4462-477f-ab7e-61f77da0b8cc), [files-1440-light-shortcut](https://git.kayg.org/attachments/c6186567-d80e-4901-9304-95213cd75266), [notes-1440-light-shortcut](https://git.kayg.org/attachments/775f0709-8f66-439d-8532-59ca52997b65), [tasks-1440-light-shortcut](https://git.kayg.org/attachments/bc5e0cbc-b265-437c-90a1-b21a2cfd480f). ## For the merge round `cd apps/web && bun run test:e2e` against matching production assets: combined shell, deep links, Back/Forward, access loss and keyboard/touch focus. The required local and VM storms were completed in this job; they are not deferred. **NOT READY**
Author
Owner

Starting independent verification for #1233.

Branch: job/tabswitch-1233 at d6f5b46972f509b211a69b0a936dce79d43457d1.
Comparison baseline: origin/dev at d395b6e25da20d8c67b8b9fc1a593b813939c8a7.

Starting independent verification for #1233. Branch: `job/tabswitch-1233` at `d6f5b46972f509b211a69b0a936dce79d43457d1`. Comparison baseline: `origin/dev` at `d395b6e25da20d8c67b8b9fc1a593b813939c8a7`.
Author
Owner

Observed one browser-test failure while verifying #1233. With the production web build from d6f5b4697 and the shared release server at d395b6e25, bun e2e/deeplinks.mjs seeded a Note with 40 paragraphs and ## Milestones, then opened /n/<note-id>#milestones cold. The heading element mounted, but the existing headingInView assertion in apps/web/e2e/deeplinks.mjs:773 timed out after 15 seconds. The suite stopped before Files and later cases. I am comparing this against origin/dev to determine whether #1233 introduced the failure.

Observed one browser-test failure while verifying #1233. With the production web build from `d6f5b4697` and the shared release server at `d395b6e25`, `bun e2e/deeplinks.mjs` seeded a Note with 40 paragraphs and `## Milestones`, then opened `/n/<note-id>#milestones` cold. The heading element mounted, but the existing `headingInView` assertion in `apps/web/e2e/deeplinks.mjs:773` timed out after 15 seconds. The suite stopped before Files and later cases. I am comparing this against origin/dev to determine whether #1233 introduced the failure.
Author
Owner

Follow-up: the same /n/<note-id>#milestones 15-second headingInView timeout reproduced on the exact origin/dev production web build (d395b6e25) with the same shared release server and fixture. This is an inherited §33 failure, not introduced by #1233. The existing apps/web/e2e/deeplinks.mjs assertion is the failing regression test.

Follow-up: the same `/n/<note-id>#milestones` 15-second `headingInView` timeout reproduced on the exact `origin/dev` production web build (`d395b6e25`) with the same shared release server and fixture. This is an inherited §33 failure, not introduced by #1233. The existing `apps/web/e2e/deeplinks.mjs` assertion is the failing regression test.
Author
Owner

Observed a second browser-test failure in the branch continuation run. The seeded deeplink-brief.txt file opened in Quick Look at /f/<item-id>, but a cold page at /f/<item-id>?open=share did not show the expected Share deeplink-brief.txt dialog within 30 seconds (apps/web/e2e/deeplinks.mjs, file/share case). The run stopped here; zero CSP reports across 17 pages. I am comparing it with the origin/dev production build before attribution.

Observed a second browser-test failure in the branch continuation run. The seeded `deeplink-brief.txt` file opened in Quick Look at `/f/<item-id>`, but a cold page at `/f/<item-id>?open=share` did not show the expected `Share deeplink-brief.txt` dialog within 30 seconds (`apps/web/e2e/deeplinks.mjs`, file/share case). The run stopped here; zero CSP reports across 17 pages. I am comparing it with the origin/dev production build before attribution.
Author
Owner

Correction to my two earlier browser comments: I omitted CALTERNAL_E2E_ASSET_OVERRIDE=1. routeCurrentBuild() installs the production bundle route only when this variable is set. Both runs therefore used the shared release binary's embedded frontend at d395b6e25; they did not compare the worktree build with the baseline build. The Note anchor and File Share dialog timeouts are baseline-only observations, not valid #1233 branch findings. I am rerunning both builds with the override enabled.

Correction to my two earlier browser comments: I omitted `CALTERNAL_E2E_ASSET_OVERRIDE=1`. `routeCurrentBuild()` installs the production bundle route only when this variable is set. Both runs therefore used the shared release binary's embedded frontend at `d395b6e25`; they did not compare the worktree build with the baseline build. The Note anchor and File Share dialog timeouts are baseline-only observations, not valid #1233 branch findings. I am rerunning both builds with the override enabled.
Author
Owner

Corrected comparison with CALTERNAL_E2E_ASSET_OVERRIDE=1: the production bundle from d6f5b4697 and the exact origin/dev bundle at d395b6e25 both fail the same existing headingInView assertion in apps/web/e2e/deeplinks.mjs after cold /n/<note-id>#milestones. This confirms an inherited §33 failure, not a #1233 regression. The initial no-override runs were invalid comparisons, as described in my prior correction.

Corrected comparison with `CALTERNAL_E2E_ASSET_OVERRIDE=1`: the production bundle from `d6f5b4697` and the exact `origin/dev` bundle at `d395b6e25` both fail the same existing `headingInView` assertion in `apps/web/e2e/deeplinks.mjs` after cold `/n/<note-id>#milestones`. This confirms an inherited §33 failure, not a #1233 regression. The initial no-override runs were invalid comparisons, as described in my prior correction.
Author
Owner

Valid branch-bundle deep-link failures found; baseline comparison is in progress.

Using the production build from d6f5b4697 with CALTERNAL_E2E_ASSET_OVERRIDE=1 and the shared release server, the existing File Share case in a temporary continuation of apps/web/e2e/deeplinks.mjs opened /f/, then cold-loaded /f/?open=share. The accessible dialog “Share deeplink-brief.txt” did not appear within 30 seconds. The continuation then opened /p/; the Quick Look title “deeplink-photo.jpg” did not appear within 30 seconds. It stopped at that title assertion. The continuation only catches the already-reproduced Note anchor timeouts so later existing deep-link cases can run. I am checking the same bundle override against origin/dev before attributing either result to this branch.

Valid branch-bundle deep-link failures found; baseline comparison is in progress. Using the production build from d6f5b4697 with CALTERNAL_E2E_ASSET_OVERRIDE=1 and the shared release server, the existing File Share case in a temporary continuation of apps/web/e2e/deeplinks.mjs opened /f/<file-id>, then cold-loaded /f/<file-id>?open=share. The accessible dialog “Share deeplink-brief.txt” did not appear within 30 seconds. The continuation then opened /p/<photo-id>; the Quick Look title “deeplink-photo.jpg” did not appear within 30 seconds. It stopped at that title assertion. The continuation only catches the already-reproduced Note anchor timeouts so later existing deep-link cases can run. I am checking the same bundle override against origin/dev before attributing either result to this branch.
Author
Owner

Comparison update: the /f/<file-id>?open=share timeout is inherited. With CALTERNAL_E2E_ASSET_OVERRIDE=1 on both d6f5b4697 and origin/dev d395b6e25, the existing File Share deep-link case did not show its accessible dialog within 30 seconds. This is the same seeded real file and shared release server. I am still checking the Photo case and its locator against the rendered DOM before attributing it.

Comparison update: the `/f/<file-id>?open=share` timeout is inherited. With CALTERNAL_E2E_ASSET_OVERRIDE=1 on both d6f5b4697 and origin/dev d395b6e25, the existing File Share deep-link case did not show its accessible dialog within 30 seconds. This is the same seeded real file and shared release server. I am still checking the Photo case and its locator against the rendered DOM before attributing it.
Author
Owner

Baseline continuation finding (branch comparison pending): with the origin/dev production build and CALTERNAL_E2E_ASSET_OVERRIDE=1, apps/web/e2e/deeplinks.mjs stopped at the Search result action assertion (line 941). After the saved result appeared and was hovered, .results-column > .sw-action-bar had no visible Actions button within 30 seconds. This prevented the remaining saved-search and Settings cases from running. I will compare the same route on d6f5b4697 before attributing it.

Baseline continuation finding (branch comparison pending): with the origin/dev production build and CALTERNAL_E2E_ASSET_OVERRIDE=1, apps/web/e2e/deeplinks.mjs stopped at the Search result action assertion (line 941). After the saved result appeared and was hovered, `.results-column > .sw-action-bar` had no visible Actions button within 30 seconds. This prevented the remaining saved-search and Settings cases from running. I will compare the same route on d6f5b4697 before attributing it.
Author
Owner

Photo comparison correction: the first Photo timeout used the existing e2e locator .ql-title h2. That selector does not match the shared Quick Look markup (.ql-name[role="heading"][aria-level="2"]). Using the accessible heading, the branch build shows “deeplink-photo.jpg” and remains at /p/<item-id>. I classify this as a stale test locator, not a #1233 navigation failure. The original locator also timed out on origin/dev.

Photo comparison correction: the first Photo timeout used the existing e2e locator `.ql-title h2`. That selector does not match the shared Quick Look markup (`.ql-name[role="heading"][aria-level="2"]`). Using the accessible heading, the branch build shows “deeplink-photo.jpg” and remains at `/p/<item-id>`. I classify this as a stale test locator, not a #1233 navigation failure. The original locator also timed out on origin/dev.
Author
Owner

Branch observation with the production build d6f5b4697: a cold /photos/videos route rendered the Photos view and selected Photos. Clicking Photos → Files → Photos returned to /photos, not /photos/videos. The reproduction is in the temporary focused route runner; TabHost.svelte's retained workspace allowlist does not include the Photos route. I will compare this exact flow on origin/dev before attributing it to #1233.

Branch observation with the production build d6f5b4697: a cold `/photos/videos` route rendered the Photos view and selected Photos. Clicking Photos → Files → Photos returned to `/photos`, not `/photos/videos`. The reproduction is in the temporary focused route runner; `TabHost.svelte`'s retained workspace allowlist does not include the Photos route. I will compare this exact flow on origin/dev before attributing it to #1233.
Author
Owner

Branch observation with the production build d6f5b4697: cold /n/<note-id>#anchor-target renders the Note. Clicking Notes → Tasks → Notes returns to /notes; it loses the item route and block fragment. This occurs because /n/[id] is excluded from the retained workspace routes. I will check the same interaction against origin/dev. The user verification asks for exact warm deep-link restoration; DESIGN §59 says Note editors keep router-owned lifetime, so I am recording this as a behavior gap for owner review rather than changing editor retention during this verification job.

Branch observation with the production build d6f5b4697: cold `/n/<note-id>#anchor-target` renders the Note. Clicking Notes → Tasks → Notes returns to `/notes`; it loses the item route and block fragment. This occurs because `/n/[id]` is excluded from the retained workspace routes. I will check the same interaction against origin/dev. The user verification asks for exact warm deep-link restoration; DESIGN §59 says Note editors keep router-owned lifetime, so I am recording this as a behavior gap for owner review rather than changing editor retention during this verification job.
Author
Owner

Warm-route comparison update (production builds with the correct asset override):

  • #1233 preserves exact URLs on warm return for Calendar week, a saved Task view, and Files Recent; origin/dev returns to /calendar/week/2026-10-05, /tasks, and /files respectively.
  • Both builds preserve the root URLs for Notes, Mail and Money. Both pass Back/Forward across Calendar ↔ Files, Back/Forward between /files and /files/recent, reload after that navigation, and branch Copy link from Recent matches /files/recent.
  • Both builds lose /photos/videos to /photos, /n/<id>#anchor to /notes, and /tag/<tag> to the prior Calendar week route after switching away and back. The Note editor remains router-owned under DESIGN §59; these are reported scope gaps, not new regressions.
  • The warm-route runner and DOM assertions are at target/tmp/navrev-warm.mjs during this job; the source deep-link checks remain in apps/web/e2e/deeplinks.mjs.
Warm-route comparison update (production builds with the correct asset override): - #1233 preserves exact URLs on warm return for Calendar week, a saved Task view, and Files Recent; origin/dev returns to `/calendar/week/2026-10-05`, `/tasks`, and `/files` respectively. - Both builds preserve the root URLs for Notes, Mail and Money. Both pass Back/Forward across Calendar ↔ Files, Back/Forward between `/files` and `/files/recent`, reload after that navigation, and branch Copy link from Recent matches `/files/recent`. - Both builds lose `/photos/videos` to `/photos`, `/n/<id>#anchor` to `/notes`, and `/tag/<tag>` to the prior Calendar week route after switching away and back. The Note editor remains router-owned under DESIGN §59; these are reported scope gaps, not new regressions. - The warm-route runner and DOM assertions are at target/tmp/navrev-warm.mjs during this job; the source deep-link checks remain in apps/web/e2e/deeplinks.mjs.
Author
Owner

Independent macOS Chrome measurements

I measured the branch (d6f5b4697) against the production web build at origin/dev (d395b6e25) on the macOS test VM in real Chrome 154. The local fronts used the shared release server at REV=d395b6e25. CUA pressed the visible mode buttons. Each direction has 10 samples; branch and baseline were alternated. The browser clock starts at the click event and ends after the selected Tab and URL match and two animation frames pass.

Build Direction p50 p95
Branch Calendar → Files 658.4 ms 740.3 ms
origin/dev Calendar → Files 135.2 ms 172.3 ms
Branch Files → Calendar 85.0 ms 122.6 ms
origin/dev Files → Calendar 110.5 ms 214.0 ms
Branch Notes → Tasks 273.1 ms 385.3 ms
origin/dev Notes → Tasks 81.2 ms 95.8 ms
Branch Tasks → Notes 88.9 ms 102.8 ms
origin/dev Tasks → Notes 90.2 ms 112.7 ms

The Linux perf VM result in the issue was 747.5 ms p50 / 995.5 ms p95 for Calendar → Files. The wait is also present on macOS Chrome: branch Calendar → Files is 658.4 / 740.3 ms, compared with 135.2 / 172.3 ms on origin/dev. This exceeds the 100 ms warm-switch budget in DESIGN §59. Mac load average was 2.67 before and 3.87 after the run.

Rapid warm-switch regression

I added apps/web/e2e/navrev-rapid-tabs-1233.mjs. It opens real Calendar and Files workspaces, warms both, checks that selecting the current Tab does not add history, then sends 20 browser pointer/click event sequences at a 90 ms cadence and asserts the final exact URL and a 2 s limit. On the branch the 20 inputs took 2129.3 ms and the final Calendar URL was correct, so the 2 s assertion failed. On origin/dev the same test passed in 1792.6 ms, ending at /calendar/week/2026-10-04.

Safari limitation

Safari did not accept the local test certificate. security add-trusted-cert returned SecTrustSettingsSetTrustSettings: The authorization was denied since no user interaction was possible. I did not enter credentials or change the Mac profile. Safari timings remain unverified until the local test certificate is trusted or its normal browser interstitial is approved on the VM.

## Independent macOS Chrome measurements I measured the branch (`d6f5b4697`) against the production web build at `origin/dev` (`d395b6e25`) on the macOS test VM in real Chrome 154. The local fronts used the shared release server at `REV=d395b6e25`. CUA pressed the visible mode buttons. Each direction has 10 samples; branch and baseline were alternated. The browser clock starts at the click event and ends after the selected Tab and URL match and two animation frames pass. | Build | Direction | p50 | p95 | | --- | --- | ---: | ---: | | Branch | Calendar → Files | 658.4 ms | 740.3 ms | | origin/dev | Calendar → Files | 135.2 ms | 172.3 ms | | Branch | Files → Calendar | 85.0 ms | 122.6 ms | | origin/dev | Files → Calendar | 110.5 ms | 214.0 ms | | Branch | Notes → Tasks | 273.1 ms | 385.3 ms | | origin/dev | Notes → Tasks | 81.2 ms | 95.8 ms | | Branch | Tasks → Notes | 88.9 ms | 102.8 ms | | origin/dev | Tasks → Notes | 90.2 ms | 112.7 ms | The Linux perf VM result in the issue was 747.5 ms p50 / 995.5 ms p95 for Calendar → Files. The wait is also present on macOS Chrome: branch Calendar → Files is 658.4 / 740.3 ms, compared with 135.2 / 172.3 ms on origin/dev. This exceeds the 100 ms warm-switch budget in DESIGN §59. Mac load average was 2.67 before and 3.87 after the run. ## Rapid warm-switch regression I added `apps/web/e2e/navrev-rapid-tabs-1233.mjs`. It opens real Calendar and Files workspaces, warms both, checks that selecting the current Tab does not add history, then sends 20 browser pointer/click event sequences at a 90 ms cadence and asserts the final exact URL and a 2 s limit. On the branch the 20 inputs took 2129.3 ms and the final Calendar URL was correct, so the 2 s assertion failed. On origin/dev the same test passed in 1792.6 ms, ending at `/calendar/week/2026-10-04`. ## Safari limitation Safari did not accept the local test certificate. `security add-trusted-cert` returned `SecTrustSettingsSetTrustSettings: The authorization was denied since no user interaction was possible.` I did not enter credentials or change the Mac profile. Safari timings remain unverified until the local test certificate is trusted or its normal browser interstitial is approved on the VM.
Author
Owner

Correction to the rapid-switch note: apps/web/e2e/navrev-rapid-tabs-1233.mjs dispatches synthetic DOM click events on the real Tab buttons at 90 ms intervals. It does not use CUA or physical pointer input. This isolates the production click handlers from WebDriver round-trip time. In the matched run, origin/dev completed in 1792.6 ms and the branch took 2129.3 ms; both ended on the expected Calendar URL and passed the no-duplicate-history check. A later branch-only repeat during heavier shared-host load took 3859.7 ms. Treat the extra repeat as load-sensitive; the interleaved macOS Chrome samples remain the stable branch-versus-baseline measurement.

Correction to the rapid-switch note: `apps/web/e2e/navrev-rapid-tabs-1233.mjs` dispatches synthetic DOM `click` events on the real Tab buttons at 90 ms intervals. It does not use CUA or physical pointer input. This isolates the production click handlers from WebDriver round-trip time. In the matched run, origin/dev completed in 1792.6 ms and the branch took 2129.3 ms; both ended on the expected Calendar URL and passed the no-duplicate-history check. A later branch-only repeat during heavier shared-host load took 3859.7 ms. Treat the extra repeat as load-sensitive; the interleaved macOS Chrome samples remain the stable branch-versus-baseline measurement.
Author
Owner

Independent verification: #1233

  • Compared branch job/tabswitch-1233 at d6f5b46972f509b211a69b0a936dce79d43457d1 with origin/dev at d395b6e25da20d8c67b8b9fc1a593b813939c8a7.
  • The branch's rapid-switch regression is commit 9ebb86d299da5a1f2c9ee75cc2f95222abe678c9.
  • Used production web builds and the shared release server from /mnt/hdd/targets/shared-server/CURRENT (REV=d395b6e25).
  • Ran the browser checks against real seeded data. Author report/result files were not read.

Shared failures on branch and baseline

  • Cold Note block link /n/<id>#milestones: the heading mounted, but the existing headingInView assertion timed out after 15 seconds at apps/web/e2e/deeplinks.mjs:773. This reproduced with both production builds.
  • File link /f/<id>?open=share: the Share dialog did not open within 30 seconds on either build.
  • Search result Actions did not become available at the existing assertion in apps/web/e2e/deeplinks.mjs:942 on either build. The temporary continuation bypassed only that unavailable action and then verified that the saved-search URL and Copy link restored the saved search on both builds.
  • Settings /settings/account/passkeys failed at the existing 15-second waitForFunction assertion in apps/web/e2e/deeplinks.mjs:965-970 for the #opt-passkeys group on both builds. The continuation did not reach the admin alias or notifications checks.
  • The Photo detail route worked after correcting the stale locator to .ql-name[role="heading"][aria-level="2"]; the seeded photo name and /p/<id> URL were visible.

Branch and baseline comparison

  • Branch warm returns preserved Calendar week, a saved Task view, Notes workspace, Files Recent, Mail root, and a Money month. Calendar ↔ Files Back/Forward restored the exact route and content. Files root ↔ Recent Back/Forward and reload restored Recent. Copy link after returning to Recent matched the current URL.
  • Baseline lost the Calendar week canonical route, returned the saved Task view to /tasks, and returned Files Recent to /files. Copy link after the baseline Files return therefore did not match the Recent view.
  • Both builds returned Photos Videos to /photos, the Note block route to /notes, and a Tag link to the previously retained Calendar route after switching away and back. Those views are outside the current retained-workspace allowlist; they are gaps shared with baseline.
  • Cold Calendar, Log, Task, Event, Note, Files/folder, Photo, Tag, Money, search query, and saved-search routes were exercised. The Note block mounted but did not scroll to its heading. Settings Passkeys did not become visible at the expected section position on either build; admin alias and notifications were not reached.

Rapid switching

  • Added apps/web/e2e/navrev-rapid-tabs-1233.mjs. It uses a production browser, real Files data, 20 browser-dispatched click events spaced 90 ms apart, and checks the final Tab/URL. It also checks that clicking the already-selected Tab does not add history. The browser events are synthetic DOM clicks, which avoids charging each input for a separate WebDriver round trip.
  • Matched run: baseline completed in 1,792.6 ms; branch completed route/history checks but exceeded the 2,000 ms budget at 2,129.3 ms. The timing assertion is intentionally failing for the branch.
  • Later runs occurred under higher shared-host load and used a different pointer-event variant for the baseline, so they are not a matched comparison.

macOS Chrome warm-switch measurements

Actual Mac Chrome, ten interleaved samples per direction. Metric: input event to selected Tab and URL match plus two animation frames.

Build Direction p50 p95
branch Calendar → Files 658.4 ms 740.3 ms
origin/dev Calendar → Files 135.2 ms 172.3 ms
branch Files → Calendar 85.0 ms 122.6 ms
origin/dev Files → Calendar 110.5 ms 214.0 ms
branch Notes → Tasks 273.1 ms 385.3 ms
origin/dev Notes → Tasks 81.2 ms 95.8 ms
branch Tasks → Notes 88.9 ms 102.8 ms
origin/dev Tasks → Notes 90.2 ms 112.7 ms

The long Calendar → Files delay is also present in Mac Chrome (branch 658.4/740.3 ms), close to the Linux issue measurement of 747.5/995.5 ms. This input-to-paint metric shows the delay on macOS; it does not isolate compositor time from application and browser work. Mac load averages were 2.67 before and 3.87 after the sample set.

Safari was not measured. The Mac rejected adding the local test certificate without user interaction: SecTrustSettingsSetTrustSettings: The authorization was denied since no user interaction was possible. No credentials were entered.

Session and Share access

  • A temporary diagnostic copy of authflash-850.mjs removed its unused Sharp import, corrected its stale /notes/{id} helper expectation to /n/{id}, and installed the branch production bundle through routeCurrentBuild. The checked-in test and its expectations were not changed.
  • The branch passed cold signed-out redirect and passkey restoration for Note, File, and Settings links. It also passed service-worker sign-out, service-worker expiry, sign-out, and expiry scenarios for all three targets. The test observed /login and no private app chrome before returning to the exact requested link.
  • The cached-shell cross-tab sign-out case passed: signing out in the second page redirected the retained page to /login; its observer recorded no private app chrome.
  • The auth continuation exited 1 later in its separate signed-out public File preview case: the Preview button appeared, but Auth flash verification file did not become visible within 30 seconds. This did not affect the completed sign-out and expiry assertions.
  • A focused two-User real-server probe revoked a Note Share while the recipient stayed on the same Notes Tab. The recipient saw “This note is not here anymore”, the private body disappeared without reload, and the Notes Tab remained selected. This passed on both branch and baseline production builds.
  • Deleted-item behavior was checked for missing Task and File links in the deep-link continuation; both showed their not-available state. The Settings admin alias and notifications links were not reached after the Passkeys section timeout.

Merge and performance contract

  • Merged origin/dev once and resolved the Tasks source conflicts by retaining the branch's retained-Tab behavior and the newer Tasks bounded-read and exact-count comments from origin/dev.
  • Refreshed the merged exact-site ledger with the documented scripts/perf-lint --adopt-7c path. Output: Adopted 9653 exact #1058 sites; ordinary future ratchet remains active and perf-lint: PASS; 0 violations; 22544 scoped exceptions.
  • The updated contract keeps 22,544 live exact scopes; 9,653 had changed exact identities/limits in the merge. No performance rules or limits were broadened beyond the exact merged findings.

Post-merge checks

  • The Rust formatter, Money crate Clippy, Money crate tests, and web check exited 0.

  • cargo fmt --check produced no output.

  • cargo clippy -p calternal-plugin-money --all-targets -- -D warnings ended with:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 50s
    
  • cargo test -p calternal-plugin-money ended with these summaries:

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 125 filtered out; finished in 47.40s
    test result: ok. 124 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 48.89s
    test result: ok. 29 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 9.55s
    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    
  • cd apps/web && bun run check exited 0. Relevant output:

    perf-lint: PASS; 0 violations; 22544 scoped exceptions
    svelte-check found 0 errors and 4 warnings in 2 files
    
  • cargo clean output:

    Removed 5770 files, 2.0GiB total
    
  • The full cd apps/web && bun run test suite is deferred to the merge round under the owner verification policy. The focused rapid-switch production-browser regression ran on both builds and intentionally fails the branch's 2-second budget. The full e2e matrix was not run.

Decisions

  • The 20-switch stress test dispatches synthetic DOM clicks every 90 ms. This runs the real Tab click handler within a two-second browser window without adding one WebDriver round trip per input. Mac latency samples used real Chrome input.
  • Note, Photo, and Tag routes that return to their root after a warm Tab switch are recorded as shared branch/baseline gaps. Note editor lifetime remains router-owned under DESIGN §59; this verification did not change product navigation.
  • The merged performance ledger used the documented #1058 exact-site adoption because the merge changed pin identities. Each pin matches a live merged finding and its actual value.

Final revision

# Independent verification: #1233 - Compared branch `job/tabswitch-1233` at `d6f5b46972f509b211a69b0a936dce79d43457d1` with `origin/dev` at `d395b6e25da20d8c67b8b9fc1a593b813939c8a7`. - The branch's rapid-switch regression is commit `9ebb86d299da5a1f2c9ee75cc2f95222abe678c9`. - Used production web builds and the shared release server from `/mnt/hdd/targets/shared-server/CURRENT` (`REV=d395b6e25`). - Ran the browser checks against real seeded data. Author report/result files were not read. ## Deep links and retained navigation ### Shared failures on branch and baseline - Cold Note block link `/n/<id>#milestones`: the heading mounted, but the existing `headingInView` assertion timed out after 15 seconds at `apps/web/e2e/deeplinks.mjs:773`. This reproduced with both production builds. - File link `/f/<id>?open=share`: the Share dialog did not open within 30 seconds on either build. - Search result Actions did not become available at the existing assertion in apps/web/e2e/deeplinks.mjs:942 on either build. The temporary continuation bypassed only that unavailable action and then verified that the saved-search URL and Copy link restored the saved search on both builds. - Settings /settings/account/passkeys failed at the existing 15-second waitForFunction assertion in apps/web/e2e/deeplinks.mjs:965-970 for the #opt-passkeys group on both builds. The continuation did not reach the admin alias or notifications checks. - The Photo detail route worked after correcting the stale locator to `.ql-name[role="heading"][aria-level="2"]`; the seeded photo name and `/p/<id>` URL were visible. ### Branch and baseline comparison - Branch warm returns preserved Calendar week, a saved Task view, Notes workspace, Files Recent, Mail root, and a Money month. Calendar ↔ Files Back/Forward restored the exact route and content. Files root ↔ Recent Back/Forward and reload restored Recent. Copy link after returning to Recent matched the current URL. - Baseline lost the Calendar week canonical route, returned the saved Task view to `/tasks`, and returned Files Recent to `/files`. Copy link after the baseline Files return therefore did not match the Recent view. - Both builds returned Photos Videos to `/photos`, the Note block route to `/notes`, and a Tag link to the previously retained Calendar route after switching away and back. Those views are outside the current retained-workspace allowlist; they are gaps shared with baseline. - Cold Calendar, Log, Task, Event, Note, Files/folder, Photo, Tag, Money, search query, and saved-search routes were exercised. The Note block mounted but did not scroll to its heading. Settings Passkeys did not become visible at the expected section position on either build; admin alias and notifications were not reached. ## Rapid switching - Added `apps/web/e2e/navrev-rapid-tabs-1233.mjs`. It uses a production browser, real Files data, 20 browser-dispatched click events spaced 90 ms apart, and checks the final Tab/URL. It also checks that clicking the already-selected Tab does not add history. The browser events are synthetic DOM clicks, which avoids charging each input for a separate WebDriver round trip. - Matched run: baseline completed in 1,792.6 ms; branch completed route/history checks but exceeded the 2,000 ms budget at 2,129.3 ms. The timing assertion is intentionally failing for the branch. - Later runs occurred under higher shared-host load and used a different pointer-event variant for the baseline, so they are not a matched comparison. ## macOS Chrome warm-switch measurements Actual Mac Chrome, ten interleaved samples per direction. Metric: input event to selected Tab and URL match plus two animation frames. | Build | Direction | p50 | p95 | | --- | --- | ---: | ---: | | branch | Calendar → Files | 658.4 ms | 740.3 ms | | origin/dev | Calendar → Files | 135.2 ms | 172.3 ms | | branch | Files → Calendar | 85.0 ms | 122.6 ms | | origin/dev | Files → Calendar | 110.5 ms | 214.0 ms | | branch | Notes → Tasks | 273.1 ms | 385.3 ms | | origin/dev | Notes → Tasks | 81.2 ms | 95.8 ms | | branch | Tasks → Notes | 88.9 ms | 102.8 ms | | origin/dev | Tasks → Notes | 90.2 ms | 112.7 ms | The long Calendar → Files delay is also present in Mac Chrome (branch 658.4/740.3 ms), close to the Linux issue measurement of 747.5/995.5 ms. This input-to-paint metric shows the delay on macOS; it does not isolate compositor time from application and browser work. Mac load averages were 2.67 before and 3.87 after the sample set. Safari was not measured. The Mac rejected adding the local test certificate without user interaction: `SecTrustSettingsSetTrustSettings: The authorization was denied since no user interaction was possible.` No credentials were entered. ## Session and Share access - A temporary diagnostic copy of authflash-850.mjs removed its unused Sharp import, corrected its stale /notes/{id} helper expectation to /n/{id}, and installed the branch production bundle through routeCurrentBuild. The checked-in test and its expectations were not changed. - The branch passed cold signed-out redirect and passkey restoration for Note, File, and Settings links. It also passed service-worker sign-out, service-worker expiry, sign-out, and expiry scenarios for all three targets. The test observed /login and no private app chrome before returning to the exact requested link. - The cached-shell cross-tab sign-out case passed: signing out in the second page redirected the retained page to /login; its observer recorded no private app chrome. - The auth continuation exited 1 later in its separate signed-out public File preview case: the Preview button appeared, but Auth flash verification file did not become visible within 30 seconds. This did not affect the completed sign-out and expiry assertions. - A focused two-User real-server probe revoked a Note Share while the recipient stayed on the same Notes Tab. The recipient saw “This note is not here anymore”, the private body disappeared without reload, and the Notes Tab remained selected. This passed on both branch and baseline production builds. - Deleted-item behavior was checked for missing Task and File links in the deep-link continuation; both showed their not-available state. The Settings admin alias and notifications links were not reached after the Passkeys section timeout. ## Merge and performance contract - Merged `origin/dev` once and resolved the Tasks source conflicts by retaining the branch's retained-Tab behavior and the newer Tasks bounded-read and exact-count comments from `origin/dev`. - Refreshed the merged exact-site ledger with the documented `scripts/perf-lint --adopt-7c` path. Output: `Adopted 9653 exact #1058 sites; ordinary future ratchet remains active` and `perf-lint: PASS; 0 violations; 22544 scoped exceptions`. - The updated contract keeps 22,544 live exact scopes; 9,653 had changed exact identities/limits in the merge. No performance rules or limits were broadened beyond the exact merged findings. ## Post-merge checks - The Rust formatter, Money crate Clippy, Money crate tests, and web check exited 0. - cargo fmt --check produced no output. - cargo clippy -p calternal-plugin-money --all-targets -- -D warnings ended with: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 50s - cargo test -p calternal-plugin-money ended with these summaries: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 125 filtered out; finished in 47.40s test result: ok. 124 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 48.89s test result: ok. 29 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 9.55s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - cd apps/web && bun run check exited 0. Relevant output: perf-lint: PASS; 0 violations; 22544 scoped exceptions svelte-check found 0 errors and 4 warnings in 2 files - cargo clean output: Removed 5770 files, 2.0GiB total - The full cd apps/web && bun run test suite is deferred to the merge round under the owner verification policy. The focused rapid-switch production-browser regression ran on both builds and intentionally fails the branch's 2-second budget. The full e2e matrix was not run. ## Decisions - The 20-switch stress test dispatches synthetic DOM clicks every 90 ms. This runs the real Tab click handler within a two-second browser window without adding one WebDriver round trip per input. Mac latency samples used real Chrome input. - Note, Photo, and Tag routes that return to their root after a warm Tab switch are recorded as shared branch/baseline gaps. Note editor lifetime remains router-owned under DESIGN §59; this verification did not change product navigation. - The merged performance ledger used the documented #1058 exact-site adoption because the merge changed pin identities. Each pin matches a live merged finding and its actual value. ## Final revision - Merge resolution head: 44c6453ad2054eebea3a476dd1e3a18aacd8fdd2.
Author
Owner

Follow-up to the #1233 verification report: the deleted-item check is now complete.

On the merged branch production build with the shared release server, I created a real File, saved its stable /f/ link, moved it to Trash, emptied Trash, and confirmed the Files API returned 404. Opening the original /f/ then showed “This item is not available”. The focused diagnostic passed; its temporary test file was not committed.

Follow-up to the #1233 verification report: the deleted-item check is now complete. On the merged branch production build with the shared release server, I created a real File, saved its stable /f/<id> link, moved it to Trash, emptied Trash, and confirmed the Files API returned 404. Opening the original /f/<id> then showed “This item is not available”. The focused diagnostic passed; its temporary test file was not committed.
Author
Owner

Additional verification limits for #1233: the Back/Forward checks confirmed route and content restoration, but did not separately measure scroll offsets. The 20-switch test checked the final Tab/URL and that clicking the selected Tab adds no history entry; it did not enumerate the full history stack created during the burst. These limits are now recorded in artifacts/navrev-findings.md.

Additional verification limits for #1233: the Back/Forward checks confirmed route and content restoration, but did not separately measure scroll offsets. The 20-switch test checked the final Tab/URL and that clicking the selected Tab adds no history entry; it did not enumerate the full history stack created during the burst. These limits are now recorded in artifacts/navrev-findings.md.
Author
Owner

Orchestrator decision: the shallow-routing branch (job/tabswitch-1233) is not merged. The independent review (navrev) measured real Chrome on the macOS test VM: Calendar → Files p50/p95 658/740 ms on the branch vs 135/172 ms on origin/dev d395b6e25. The perf VM's software compositing made the branch look faster there. On the owner's platform, dev is already close to the 100 ms target. Next step for this issue, if pursued: measure on the Mac first (real GPU) and only keep changes that win there. Deep-link and warm-restore gaps found during the review are filed as #1459 and #1460.

Orchestrator decision: the shallow-routing branch (job/tabswitch-1233) is not merged. The independent review (navrev) measured real Chrome on the macOS test VM: Calendar → Files p50/p95 658/740 ms on the branch vs 135/172 ms on origin/dev d395b6e25. The perf VM's software compositing made the branch look faster there. On the owner's platform, dev is already close to the 100 ms target. Next step for this issue, if pursued: measure on the Mac first (real GPU) and only keep changes that win there. Deep-link and warm-restore gaps found during the review are filed as #1459 and #1460.
Author
Owner

Starting #1233 on branch job/tabcomp-perf, based on 5a2c02c1300dd2f591b177395e0d8c94fc2f8df9 (origin/dev at start). I will inspect the prior #1233 attempt and its measurements, measure the requested paths on Linux only, trace the slowest interactions, retain only changes that meet the interleaved p50/p95 rule, and run the requested gates.

Starting #1233 on branch `job/tabcomp-perf`, based on `5a2c02c1300dd2f591b177395e0d8c94fc2f8df9` (`origin/dev` at start). I will inspect the prior #1233 attempt and its measurements, measure the requested paths on Linux only, trace the slowest interactions, retain only changes that meet the interleaved p50/p95 rule, and run the requested gates.
Author
Owner

Finding: the build host load was 13.64/13.65/10.74 (1m/5m/15m), above the permitted local 5-minute load of 6. The existing profile runs Chromium on the build host. On the perf VM, SSH to its own loopback and 10.69.69.63 both returned connection refused, so the remote harness cannot self-SSH. I added a perf-VM mode to run the browser and server together under perf-run, retaining the existing HDD fixture and sampling load inside the lock. Focused benchmark tests pass; I am validating the staged run next.

Finding: the build host load was 13.64/13.65/10.74 (1m/5m/15m), above the permitted local 5-minute load of 6. The existing profile runs Chromium on the build host. On the perf VM, SSH to its own loopback and 10.69.69.63 both returned `connection refused`, so the remote harness cannot self-SSH. I added a perf-VM mode to run the browser and server together under `perf-run`, retaining the existing HDD fixture and sampling load inside the lock. Focused benchmark tests pass; I am validating the staged run next.
Author
Owner

Finding: the first smoke run failed the existing HDD qualification before collecting any Tab sample. QD1 measured 82.417216 IOPS against the harness's unchanged 100–250 IOPS range. The mapped ext4 device was mounted with the configured 8 ms read/write delay. The post-run load average (1.15/0.70/0.31) is not contemporaneous with the failed sample. I kept the gate intact and added in-lock load and IOPS output before one controlled retry.

Finding: the first smoke run failed the existing HDD qualification before collecting any Tab sample. QD1 measured 82.417216 IOPS against the harness's unchanged 100–250 IOPS range. The mapped ext4 device was mounted with the configured 8 ms read/write delay. The post-run load average (1.15/0.70/0.31) is not contemporaneous with the failed sample. I kept the gate intact and added in-lock load and IOPS output before one controlled retry.
Author
Owner

Update: one controlled retry passed the unchanged HDD qualification while perf-run held the VM lock. QD1 was 108.3167 IOPS, p50 8.02816 ms, with 5-minute load 1.04; QD16 was 200.638638 IOPS with load 1.02. The smoke run has not reached Tab sampling yet; the in-lock Files index count rose from 321 to 401 in the latest checks, with Photos still at zero.

Update: one controlled retry passed the unchanged HDD qualification while `perf-run` held the VM lock. QD1 was 108.3167 IOPS, p50 8.02816 ms, with 5-minute load 1.04; QD16 was 200.638638 IOPS with load 1.02. The smoke run has not reached Tab sampling yet; the in-lock Files index count rose from 321 to 401 in the latest checks, with Photos still at zero.
Author
Owner

Finding: the aborted smoke runner requested only calendar,files but still seeded and waited for 100 Photos. The user fixture had 100 JPEG files while photos_media stayed at 0; the harness waited on that unrelated count. It also waited for a Search/Tag LinkNav fixture that a switch-only run never opens. Commit 034f33d4b now seeds Photos only when requested, keeps the Journal readiness check, and skips LinkNav readiness for switch-only runs. Focused tests pass (10 tests, 33 assertions).

Finding: the aborted smoke runner requested only `calendar,files` but still seeded and waited for 100 Photos. The user fixture had 100 JPEG files while `photos_media` stayed at 0; the harness waited on that unrelated count. It also waited for a Search/Tag LinkNav fixture that a switch-only run never opens. Commit 034f33d4b now seeds Photos only when requested, keeps the Journal readiness check, and skips LinkNav readiness for switch-only runs. Focused tests pass (10 tests, 33 assertions).
Author
Owner

The 1440 px Chromium smoke now completes on perf-test-69-63 under perf-run. Its reduced fixture was 1 year / 30 items per day / 100 Files / 20 Notes / 20 Tasks, with Photos omitted for Calendar→Files. HDD qualification passed in-lock: QD1 124.89 IOPS, p50 7.96 ms, p99 8.85 ms; QD16 200.92 IOPS, p50 100.14 ms, p99 105.38 ms. Load was 1.89 / 3.33 / 2.85 at QD1 and 1.77 / 3.23 / 2.82 at QD16.

Trusted CDP Calendar→Files smoke input-to-second-ready-frame was 363.9 ms warm and 507.7 ms cold (one sample each, not the requested baseline). The warm path exceeds DESIGN §59's 100 ms Tab budget. Earlier smoke readiness waited for Photos even when Photos was not in the requested Tabs, and waited for LinkNav data in switch-only runs; I changed the profile to omit those unrelated setup operations. I will verify full Home indexing and the complete matrix before treating any result as a baseline.

The 1440 px Chromium smoke now completes on perf-test-69-63 under `perf-run`. Its reduced fixture was 1 year / 30 items per day / 100 Files / 20 Notes / 20 Tasks, with Photos omitted for Calendar→Files. HDD qualification passed in-lock: QD1 124.89 IOPS, p50 7.96 ms, p99 8.85 ms; QD16 200.92 IOPS, p50 100.14 ms, p99 105.38 ms. Load was 1.89 / 3.33 / 2.85 at QD1 and 1.77 / 3.23 / 2.82 at QD16. Trusted CDP Calendar→Files smoke input-to-second-ready-frame was 363.9 ms warm and 507.7 ms cold (one sample each, not the requested baseline). The warm path exceeds DESIGN §59's 100 ms Tab budget. Earlier smoke readiness waited for Photos even when Photos was not in the requested Tabs, and waited for LinkNav data in switch-only runs; I changed the profile to omit those unrelated setup operations. I will verify full Home indexing and the complete matrix before treating any result as a baseline.
Author
Owner

The first full Home run did not reach a Tab sample. It seeded the requested 3-year Home (32,910 Journal entries, 100,000 Files, 5,000 Notes, 2,000 Tasks, 20,000 transactions), and the preflight Calendar API returned HTTP 200 with 267,496 bytes in 44.69 ms. The first Calendar route then failed the benchmark's visible-loading check after 120,000 ms (.tg/loading UI did not settle); the run produced zero timing samples and released the perf-run lock. This is a full-fixture readiness failure, not a latency result. I will add bounded, content-free diagnostics before deciding whether the issue is an app load or a benchmark readiness assumption.

The first full Home run did not reach a Tab sample. It seeded the requested 3-year Home (32,910 Journal entries, 100,000 Files, 5,000 Notes, 2,000 Tasks, 20,000 transactions), and the preflight Calendar API returned HTTP 200 with 267,496 bytes in 44.69 ms. The first Calendar route then failed the benchmark's visible-loading check after 120,000 ms (`.tg`/loading UI did not settle); the run produced zero timing samples and released the perf-run lock. This is a full-fixture readiness failure, not a latency result. I will add bounded, content-free diagnostics before deciding whether the issue is an app load or a benchmark readiness assumption.
Author
Owner

Full Home baseline evidence from tabcomp-1233-baseline-readiness3 (perf VM, load average observed under lock: 3.20 / 4.18 / 3.95). The fixture contained 1,097 daily-note days, 32,910 log entries, 100,000 files, 5,000 Notes, 2,000 Tasks and 20,000 transactions.

Seven trusted 1440 px light samples completed before the next cold cell failed: warm Calendar→Files 338.6 ms; cold Notes→Tasks 428.2 ms; warm Notes→Tasks 224.7 ms; cold Mail→Calendar 8,889.8 ms with 3 overlapping long tasks; warm Mail→Calendar 1,554.7 ms with 5 overlapping long tasks; cold Files→Notes 305.9 ms; warm Files→Notes 436.1 ms. All exceed the 100 ms warm Tab-switch budget where applicable. The following cold Tasks→Mail sample did not produce a second ready frame, so the profile correctly ended incomplete. The captured response log included 502/503 statuses during server restart; I am reproducing the failure against the actual sample page before deciding whether these are startup load effects or a user-visible failure.

The failed report previously captured the outer page after the cold sample page closed. I added focused, content-free diagnostics so the next run records the actual sample clock, route readiness and response statuses. No app behavior changed.

Full Home baseline evidence from `tabcomp-1233-baseline-readiness3` (perf VM, load average observed under lock: 3.20 / 4.18 / 3.95). The fixture contained 1,097 daily-note days, 32,910 log entries, 100,000 files, 5,000 Notes, 2,000 Tasks and 20,000 transactions. Seven trusted 1440 px light samples completed before the next cold cell failed: warm Calendar→Files 338.6 ms; cold Notes→Tasks 428.2 ms; warm Notes→Tasks 224.7 ms; cold Mail→Calendar 8,889.8 ms with 3 overlapping long tasks; warm Mail→Calendar 1,554.7 ms with 5 overlapping long tasks; cold Files→Notes 305.9 ms; warm Files→Notes 436.1 ms. All exceed the 100 ms warm Tab-switch budget where applicable. The following cold Tasks→Mail sample did not produce a second ready frame, so the profile correctly ended incomplete. The captured response log included 502/503 statuses during server restart; I am reproducing the failure against the actual sample page before deciding whether these are startup load effects or a user-visible failure. The failed report previously captured the outer page after the cold sample page closed. I added focused, content-free diagnostics so the next run records the actual sample clock, route readiness and response statuses. No app behavior changed.
Author
Owner

The 1440 px full-Home profile on harness commit 6611b690ad0ffd4029bcf6a12d72a02dc6705e27 and server build dbd621434d0a1133f16230cca5c6a83af0d8aed3 completed fixture setup and then failed on the first Calendar visit: waitForFunction: Timeout 120000ms exceeded. The run recorded zero first-load rows and zero switch samples, so it is incomplete.

The seeded Home was 1,097 daily Notes, 32,910 Log entries, 100,000 Files, 5,000 Notes, 2,000 Tasks, 20,000 transactions and 30 recurring CalDAV Events across 1,097 days. The HDD qualification passed (QD1 124.6 IOPS; QD16 200.7 IOPS). All four server preflight reads returned 200 in 8.6–36.6 ms, including Calendar range at 36.6 ms (Server-Timing total 9.753 ms, DB 2.446 ms, parse 2.750 ms). The Calendar UI readiness timeout is therefore separate from the sampled API response latency. The run had failure diagnostics disabled, so it did not preserve the rendered route state. The prior partial profile still has seven trusted switch samples; no additional switch data was produced here.

The 1440 px full-Home profile on harness commit `6611b690ad0ffd4029bcf6a12d72a02dc6705e27` and server build `dbd621434d0a1133f16230cca5c6a83af0d8aed3` completed fixture setup and then failed on the first Calendar visit: `waitForFunction: Timeout 120000ms exceeded.` The run recorded zero first-load rows and zero switch samples, so it is incomplete. The seeded Home was 1,097 daily Notes, 32,910 Log entries, 100,000 Files, 5,000 Notes, 2,000 Tasks, 20,000 transactions and 30 recurring CalDAV Events across 1,097 days. The HDD qualification passed (QD1 124.6 IOPS; QD16 200.7 IOPS). All four server preflight reads returned 200 in 8.6–36.6 ms, including Calendar range at 36.6 ms (`Server-Timing` total 9.753 ms, DB 2.446 ms, parse 2.750 ms). The Calendar UI readiness timeout is therefore separate from the sampled API response latency. The run had failure diagnostics disabled, so it did not preserve the rendered route state. The prior partial profile still has seven trusted switch samples; no additional switch data was produced here.
Author
Owner

390 px partial baseline (perf VM under perf-run lock, server source dbd621434d0a1133f16230cca5c6a83af0d8aed3; load average before setup 2.75/4.57/4.40; HDD QD1 124.56 IOPS, QD16 200.65 IOPS):

  • First route visits (Calendar, Tasks, Notes, Files, Mail) measured 1,516 / 1,222 / 1,398 / 1,279 / 1,384 ms.
  • Trusted input to second ready frame: warm Calendar→Files 309.5 ms (19 requests, 1 long task, 114 ms max); cold Notes→Tasks 242.3 ms (6 requests, 1 long task, 65 ms max); warm Notes→Tasks 187.4 ms (12 requests, 1 long task, 69 ms max). Both warm samples exceed the 100 ms §59 budget.
  • The run stopped before the remaining samples. The route had a trusted second-frame clock, selected Tasks, two content-root children and no visible loading UI, but the harness then timed out in a separate paint wait. I removed that post-clock wait from the requested switch metric and made source setup wait for the mounted route shell in commit 8713ec3c0; the rerun will confirm this diagnosis.

The 502/503 entries in this run overlapped the intentional cold server restart. I am not treating those as product failures.

390 px partial baseline (perf VM under `perf-run` lock, server source `dbd621434d0a1133f16230cca5c6a83af0d8aed3`; load average before setup 2.75/4.57/4.40; HDD QD1 124.56 IOPS, QD16 200.65 IOPS): - First route visits (Calendar, Tasks, Notes, Files, Mail) measured 1,516 / 1,222 / 1,398 / 1,279 / 1,384 ms. - Trusted input to second ready frame: warm Calendar→Files 309.5 ms (19 requests, 1 long task, 114 ms max); cold Notes→Tasks 242.3 ms (6 requests, 1 long task, 65 ms max); warm Notes→Tasks 187.4 ms (12 requests, 1 long task, 69 ms max). Both warm samples exceed the 100 ms §59 budget. - The run stopped before the remaining samples. The route had a trusted second-frame clock, selected Tasks, two content-root children and no visible loading UI, but the harness then timed out in a separate paint wait. I removed that post-clock wait from the requested switch metric and made source setup wait for the mounted route shell in commit `8713ec3c0`; the rerun will confirm this diagnosis. The 502/503 entries in this run overlapped the intentional cold server restart. I am not treating those as product failures.
Author
Owner

Composer baseline attempt (perf VM perf-run lock, HDD fixture ready) did not reach action samples. The harness raised ReferenceError: overlapsInterval is not defined inside browser page.evaluate; the function exists only in the Node process. It saved 1440 px first visits: Tasks 3,161 ms and Mail 1,881 ms (p50=p95=1 sample; lock-held load 1m average 5.19). I am moving that long-task filter to Node, where the existing tested interval helper is available, then I will rerun. These are harness timings, not Composer results.

Composer baseline attempt (perf VM `perf-run` lock, HDD fixture ready) did not reach action samples. The harness raised `ReferenceError: overlapsInterval is not defined` inside browser `page.evaluate`; the function exists only in the Node process. It saved 1440 px first visits: Tasks 3,161 ms and Mail 1,881 ms (p50=p95=1 sample; lock-held load 1m average 5.19). I am moving that long-task filter to Node, where the existing tested interval helper is available, then I will rerun. These are harness timings, not Composer results.
Author
Owner

The corrected Composer profile completed on the perf VM against the full Home fixture (7 samples per action and width; source server dbd621434d0a1133f16230cca5c6a83af0d8aed3). Results are trusted input to two ready frames, with warm/cold Tab profiles still pending:

Width FAB p50/p95 Mod+N p50/p95 Search action p50/p95 Close p50/p95
1440 266.3/412.4 ms 222.9/286.8 ms 723.6/960.7 ms 166.8/195.8 ms
390 73.2/153.0 ms 55.0/83.4 ms 397.8/465.8 ms 44.1/54.6 ms

The §59 action budget is 150 ms. Search exceeds it at both widths; desktop FAB, Mod+N and close also exceed it. The p50 server CPU delta for Search was 570 ms at 1440 and 460 ms at 390; the captured resource path is primarily /api/v1/calendar/preferences, with occasional concurrent /api/v1/notes/tasks page reads. The raw profile is committed as 2102c6ed6. I will trace Search and the slowest Tab paths before selecting a product change.

The corrected Composer profile completed on the perf VM against the full Home fixture (7 samples per action and width; source server `dbd621434d0a1133f16230cca5c6a83af0d8aed3`). Results are trusted input to two ready frames, with warm/cold Tab profiles still pending: | Width | FAB p50/p95 | Mod+N p50/p95 | Search action p50/p95 | Close p50/p95 | | ---: | ---: | ---: | ---: | ---: | | 1440 | 266.3/412.4 ms | 222.9/286.8 ms | 723.6/960.7 ms | 166.8/195.8 ms | | 390 | 73.2/153.0 ms | 55.0/83.4 ms | 397.8/465.8 ms | 44.1/54.6 ms | The §59 action budget is 150 ms. Search exceeds it at both widths; desktop FAB, Mod+N and close also exceed it. The p50 server CPU delta for Search was 570 ms at 1440 and 460 ms at 390; the captured resource path is primarily `/api/v1/calendar/preferences`, with occasional concurrent `/api/v1/notes/tasks` page reads. The raw profile is committed as `2102c6ed6`. I will trace Search and the slowest Tab paths before selecting a product change.
Author
Owner

Calendar first-visit finding (not yet root-caused): the full Tab profile timed out after 120 s on the direct Calendar visit before collecting switch samples. The route was /calendar/week/2026-10-05; TimeGrid reported ready and loading together, with no visible loading UI. During the wait the browser recorded 24 /api/v1/calendar/range and 24 /api/v1/calendar/items responses, all HTTP 200. Response bodies alternated between empty/300-byte and full payloads (up to 443,480 bytes); the grid never left loading. The diagnostic currently drops query strings, so I cannot yet tell whether the range changes or the cache is repeatedly invalidated. I am adding URL-level evidence before deciding if this is app behavior or the fixture/benchmark setup. No product fix is made from this evidence alone.

Calendar first-visit finding (not yet root-caused): the full Tab profile timed out after 120 s on the direct Calendar visit before collecting switch samples. The route was `/calendar/week/2026-10-05`; TimeGrid reported `ready` and `loading` together, with no visible loading UI. During the wait the browser recorded 24 `/api/v1/calendar/range` and 24 `/api/v1/calendar/items` responses, all HTTP 200. Response bodies alternated between empty/300-byte and full payloads (up to 443,480 bytes); the grid never left loading. The diagnostic currently drops query strings, so I cannot yet tell whether the range changes or the cache is repeatedly invalidated. I am adding URL-level evidence before deciding if this is app behavior or the fixture/benchmark setup. No product fix is made from this evidence alone.
Author
Owner

Perf VM finding (10 Oct): the one-sample paired-profile smoke acquired the perf-run lock and stopped before fixture reset. prepareVm() could not copy the origin/dev server to /mnt/hdd/targets/release-shared/release/calternal-server-tabswitch-549 because the file was busy. With the lock free after exit, /run/calternal-tabswitch-549/server.pid referred to no live PID, but a server process from 14:50 local remained in calternal-hdd-emu-2334748.scope on /srv/hdd-emu/tabswitch-549. This leaves the fixture unsafe to reset until the stale scope is stopped. I am checking and fixing the runner cleanup before retrying; no measured samples were produced.

Perf VM finding (10 Oct): the one-sample paired-profile smoke acquired the perf-run lock and stopped before fixture reset. `prepareVm()` could not copy the origin/dev server to `/mnt/hdd/targets/release-shared/release/calternal-server-tabswitch-549` because the file was busy. With the lock free after exit, `/run/calternal-tabswitch-549/server.pid` referred to no live PID, but a server process from 14:50 local remained in `calternal-hdd-emu-2334748.scope` on `/srv/hdd-emu/tabswitch-549`. This leaves the fixture unsafe to reset until the stale scope is stopped. I am checking and fixing the runner cleanup before retrying; no measured samples were produced.
Author
Owner

Paired-run harness smoke completed on 10 Oct under perf-run with the #549 HDD profile and origin/dev server. It produced 4 Tab samples and 8 Composer samples (1 sample per build/path), with both baseline and candidate variants on Tasks→Mail and all four Composer actions. Fixture size was 100 Files and 20 Tasks; the one-sample timings are wiring evidence only and are excluded from the seven-pair decision. HDD qualification passed: QD1 124.6 IOPS / 7.96 ms p50; QD16 200.5 IOPS / 95.94 ms p50. The benchmark server stopped and the perf lock is free. A stale server scope from an earlier profile was stopped under its own perf-run; cleanup now checks the recorded executable and uses a per-job PID file.

Paired-run harness smoke completed on 10 Oct under `perf-run` with the #549 HDD profile and origin/dev server. It produced 4 Tab samples and 8 Composer samples (1 sample per build/path), with both `baseline` and `candidate` variants on Tasks→Mail and all four Composer actions. Fixture size was 100 Files and 20 Tasks; the one-sample timings are wiring evidence only and are excluded from the seven-pair decision. HDD qualification passed: QD1 124.6 IOPS / 7.96 ms p50; QD16 200.5 IOPS / 95.94 ms p50. The benchmark server stopped and the perf lock is free. A stale server scope from an earlier profile was stopped under its own `perf-run`; cleanup now checks the recorded executable and uses a per-job PID file.
Author
Owner

#1233 progress: the full 100,000-file fixture completed, but the first Calendar visit timed out at 120 seconds before any timing samples were collected.

The sanitized page state was /calendar/week/2026-10-05; the TimeGrid had ready=true and loading=true, with no visible loading element. The browser recorded 23 /api/v1/calendar/range, 30 /api/v1/calendar/items, and 23 /api/v1/notes/tasks/day calls across two repeated date windows. All recorded API responses were HTTP 200. The same Calendar range request ran 22 times from about 2.9 s to 59.5 s after navigation, with a median duration of 2.1 s. No switch or Composer samples were produced.

The request burst ended after about 60 seconds. This suggests that the benchmark opened Calendar while full-Home indexing was still publishing changes; that cause is not confirmed yet. I am adding a bounded Files-event quiet preflight and will rerun the Calendar readiness check before changing product code.

#1233 progress: the full 100,000-file fixture completed, but the first Calendar visit timed out at 120 seconds before any timing samples were collected. The sanitized page state was `/calendar/week/2026-10-05`; the TimeGrid had `ready=true` and `loading=true`, with no visible loading element. The browser recorded 23 `/api/v1/calendar/range`, 30 `/api/v1/calendar/items`, and 23 `/api/v1/notes/tasks/day` calls across two repeated date windows. All recorded API responses were HTTP 200. The same Calendar range request ran 22 times from about 2.9 s to 59.5 s after navigation, with a median duration of 2.1 s. No switch or Composer samples were produced. The request burst ended after about 60 seconds. This suggests that the benchmark opened Calendar while full-Home indexing was still publishing changes; that cause is not confirmed yet. I am adding a bounded Files-event quiet preflight and will rerun the Calendar readiness check before changing product code.
Author
Owner

#1233 progress: the full Home’s Files event stream emitted 63 changes, then stayed open and quiet for 5 seconds after 144.834 seconds. I added this benchmark-only preflight and reran the full fixture under perf-run calternal tabcomp1233quietmatrix 60; the fixture and read probe completed, but a fresh 1440px Calendar first visit still timed out at the existing 120-second paint-ready wait. The run produced no latency samples. This means active Files events are not the sole reason the prior Calendar profile stalled. I will capture the post-quiet DOM and API diagnostics once before making any Calendar code changes. The perf lock was released when the run exited.

#1233 progress: the full Home’s Files event stream emitted 63 changes, then stayed open and quiet for 5 seconds after 144.834 seconds. I added this benchmark-only preflight and reran the full fixture under `perf-run calternal tabcomp1233quietmatrix 60`; the fixture and read probe completed, but a fresh 1440px Calendar first visit still timed out at the existing 120-second paint-ready wait. The run produced no latency samples. This means active Files events are not the sole reason the prior Calendar profile stalled. I will capture the post-quiet DOM and API diagnostics once before making any Calendar code changes. The perf lock was released when the run exited.
Author
Owner

#1233 progress: the 100,000-file full fixture did not reach a quiet Files event stream within 600 seconds. The timeout state was open SSE, 200 change events, and only 2.3 seconds since the latest event. The Files index row count was still growing, so this is startup reconciliation work, not a stable warm Home. The harness also left its setup Chromium open on this timeout and held the lock until I stopped that job-owned browser. I found the existing public /healthz startup phase, which reports files-home-reconciliation and later files-content-type-backfill. I’m switching the benchmark gate to that content-free status and adding finally cleanup for the setup Browser; this will wait for the actual Home scan instead of guessing from event silence.

#1233 progress: the 100,000-file full fixture did not reach a quiet Files event stream within 600 seconds. The timeout state was open SSE, 200 change events, and only 2.3 seconds since the latest event. The Files index row count was still growing, so this is startup reconciliation work, not a stable warm Home. The harness also left its setup Chromium open on this timeout and held the lock until I stopped that job-owned browser. I found the existing public `/healthz` startup phase, which reports `files-home-reconciliation` and later `files-content-type-backfill`. I’m switching the benchmark gate to that content-free status and adding `finally` cleanup for the setup Browser; this will wait for the actual Home scan instead of guessing from event silence.
Author
Owner

Progress: the 60-minute perf-run A/B attempt started at 2026-10-10 17:16 CEST on perf-test-69-63 with the lock held. The staged candidate build is e63681f869 and the origin/dev baseline is dbd621434d. At 14 minutes, /healthz still reports state=migrating, phase=notes-upgrade-backfills; the server is using about 70% CPU and no latency samples exist yet. The new readiness gate correctly prevents recording startup-contaminated samples. I will report the final run result after this bounded attempt.

Progress: the 60-minute perf-run A/B attempt started at 2026-10-10 17:16 CEST on perf-test-69-63 with the lock held. The staged candidate build is e63681f8698f155a018994bb07c20a2e41cbd89e and the origin/dev baseline is dbd621434d0a1133f16230cca5c6a83af0d8aed3. At 14 minutes, `/healthz` still reports `state=migrating`, `phase=notes-upgrade-backfills`; the server is using about 70% CPU and no latency samples exist yet. The new readiness gate correctly prevents recording startup-contaminated samples. I will report the final run result after this bounded attempt.
Author
Owner

A fresh 60-minute perf-run with the full 100,000-file profile and the new /healthz readiness gate exited before sampling. At the gate's 25-minute timeout, /healthz still returned state=migrating, phase=notes-upgrade-backfills; no latency samples were recorded. The VM lock is free and the benchmark's server teardown completed. This is earlier than Files reconciliation, so the quiet-event gate was not the only startup issue. The run did not produce enough evidence to identify a user-path root cause or keep a performance change.

A fresh 60-minute `perf-run` with the full 100,000-file profile and the new `/healthz` readiness gate exited before sampling. At the gate's 25-minute timeout, `/healthz` still returned `state=migrating`, `phase=notes-upgrade-backfills`; no latency samples were recorded. The VM lock is free and the benchmark's server teardown completed. This is earlier than Files reconciliation, so the quiet-event gate was not the only startup issue. The run did not produce enough evidence to identify a user-path root cause or keep a performance change.
Author
Owner

Result

Head: e63681f8698f155a018994bb07c20a2e41cbd89e.

No product performance change remains. The two Composer changes were reverted because the valid paired p50/p95 decision rule was not met. The current web app has no product-code diff from origin/dev.

Benchmark evidence

The candidate production build was e63681f8698f155a018994bb07c20a2e41cbd89e; the origin/dev baseline was dbd621434d0a1133f16230cca5c6a83af0d8aed3. I ran the requested full Home profile on perf-test-69-63 through perf-run calternal tabcomp1233finalmatrix 60, with the perf lock held. Load average inside the lock at start was 0.08 / 1.20 / 2.59. HDD qualification passed at QD1 124.466 IOPS, p50 7.963 ms, p99 8.978 ms, and QD16 200.906 IOPS, p50 100.139 ms, p99 104.333 ms.

The profile requested seven warm and cold samples for all five Tab pairs at 1440 px and 390 px, plus seven samples each for Composer FAB, shortcut, Search and close. It stopped before timing because after 25 minutes /healthz still returned state=migrating, phase=notes-upgrade-backfills. The runner exited 2, recorded no latency samples, and released the lock. The separate #1540 issue now includes this earlier startup phase as well as the Files-index readiness evidence.

An earlier incomplete origin/dev run recorded one 1440 px light sample per cell before failing: Calendar→Files warm 338.6 ms; Notes→Tasks cold/warm 428.2/224.7 ms; Mail→Calendar cold/warm 8,889.8/1,554.7 ms; Files→Notes cold/warm 305.9/436.1 ms. This is diagnostic only, not a seven-sample-per-path baseline. It found no candidate comparison. No current p50/p95 or three-path trace analysis is available, so no root cause for the slow Tab paths is claimed.

Changes

The retained changes add trusted CDP input helpers and tests, paired Tab/Composer profiling, request and long-task capture, unique benchmark PID files, and safe cleanup of the setup browser/context. The startup preflight now polls public /healthz every five seconds and accepts state=ok or phase=tag-reconciliation; it no longer guesses readiness from a quiet Files event stream. The committed Composer baseline profile remains under docs/perf/runs/.

Files: bench/cdp-input.mjs, bench/cdp-input.test.mjs, bench/notifications-read-1387-cdp.mjs, bench/tab-switch.mjs, bench/tab-switch.test.mjs, and docs/perf/runs/tabcomp-1233-composer-baseline-2026-10-10.json.

Verification

bun run check exited 0. Relevant output:

Focus policy passes: 50 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 3 warnings in 1 file

Focused benchmark tests:

17 pass
0 fail
66 expect() calls
Ran 17 tests across 1 file. [59.00ms]

Focused Composer Vitest:

Test Files  1 passed (1)
     Tests  39 passed (39)

node --check bench/tab-switch.mjs && git diff --check exited 0 with no output. No Rust crate changed, so Rust gates were not run. cargo clean output: Removed 6521 files, 202.5MiB total. The web build output was removed.

Decisions and remaining gaps

  • DESIGN §59 sets a 100 ms warm Tab-switch budget and a 150 ms input-to-visible accepted-state budget. This run produced no samples, so neither budget can be assessed for this branch.
  • DESIGN does not define a benchmark readiness signal. I chose the public /healthz phase because the prior quiet-event gate accepted a still-indexing Files fixture. The five-second poll is setup-only and bounded to 25 minutes.
  • The full matrix, seven-pair candidate comparison, and traces for the three slowest paths remain incomplete. No product fix was retained without valid paired evidence. No UI changed, so visual screenshots and UX-gap checks do not apply.
  • For the merge round, run cd apps/web && bun run test -- --maxWorkers=2 to verify the complete web suite on the combined branch.
## Result Head: `e63681f8698f155a018994bb07c20a2e41cbd89e`. No product performance change remains. The two Composer changes were reverted because the valid paired p50/p95 decision rule was not met. The current web app has no product-code diff from `origin/dev`. ## Benchmark evidence The candidate production build was `e63681f8698f155a018994bb07c20a2e41cbd89e`; the `origin/dev` baseline was `dbd621434d0a1133f16230cca5c6a83af0d8aed3`. I ran the requested full Home profile on `perf-test-69-63` through `perf-run calternal tabcomp1233finalmatrix 60`, with the perf lock held. Load average inside the lock at start was `0.08 / 1.20 / 2.59`. HDD qualification passed at QD1 `124.466 IOPS`, p50 `7.963 ms`, p99 `8.978 ms`, and QD16 `200.906 IOPS`, p50 `100.139 ms`, p99 `104.333 ms`. The profile requested seven warm and cold samples for all five Tab pairs at 1440 px and 390 px, plus seven samples each for Composer FAB, shortcut, Search and close. It stopped before timing because after 25 minutes `/healthz` still returned `state=migrating`, `phase=notes-upgrade-backfills`. The runner exited 2, recorded no latency samples, and released the lock. The separate #1540 issue now includes this earlier startup phase as well as the Files-index readiness evidence. An earlier incomplete origin/dev run recorded one 1440 px light sample per cell before failing: Calendar→Files warm 338.6 ms; Notes→Tasks cold/warm 428.2/224.7 ms; Mail→Calendar cold/warm 8,889.8/1,554.7 ms; Files→Notes cold/warm 305.9/436.1 ms. This is diagnostic only, not a seven-sample-per-path baseline. It found no candidate comparison. No current p50/p95 or three-path trace analysis is available, so no root cause for the slow Tab paths is claimed. ## Changes The retained changes add trusted CDP input helpers and tests, paired Tab/Composer profiling, request and long-task capture, unique benchmark PID files, and safe cleanup of the setup browser/context. The startup preflight now polls public `/healthz` every five seconds and accepts `state=ok` or `phase=tag-reconciliation`; it no longer guesses readiness from a quiet Files event stream. The committed Composer baseline profile remains under `docs/perf/runs/`. Files: `bench/cdp-input.mjs`, `bench/cdp-input.test.mjs`, `bench/notifications-read-1387-cdp.mjs`, `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`, and `docs/perf/runs/tabcomp-1233-composer-baseline-2026-10-10.json`. ## Verification `bun run check` exited 0. Relevant output: ``` Focus policy passes: 50 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 3 warnings in 1 file ``` Focused benchmark tests: ``` 17 pass 0 fail 66 expect() calls Ran 17 tests across 1 file. [59.00ms] ``` Focused Composer Vitest: ``` Test Files 1 passed (1) Tests 39 passed (39) ``` `node --check bench/tab-switch.mjs && git diff --check` exited 0 with no output. No Rust crate changed, so Rust gates were not run. `cargo clean` output: `Removed 6521 files, 202.5MiB total`. The web build output was removed. ## Decisions and remaining gaps - DESIGN §59 sets a 100 ms warm Tab-switch budget and a 150 ms input-to-visible accepted-state budget. This run produced no samples, so neither budget can be assessed for this branch. - DESIGN does not define a benchmark readiness signal. I chose the public `/healthz` phase because the prior quiet-event gate accepted a still-indexing Files fixture. The five-second poll is setup-only and bounded to 25 minutes. - The full matrix, seven-pair candidate comparison, and traces for the three slowest paths remain incomplete. No product fix was retained without valid paired evidence. No UI changed, so visual screenshots and UX-gap checks do not apply. - For the merge round, run `cd apps/web && bun run test -- --maxWorkers=2` to verify the complete web suite on the combined branch.
Author
Owner

Starting #1233 on branch job/tabswitch2, based on e63681f8698f155a018994bb07c20a2e41cbd89e (job/tabcomp-perf). I have read the current #1233/#1540 evidence and DESIGN §59. I will first make the full seeded Home and Index reach /healthz ready once, stop the server cleanly, snapshot both, and verify a restored restart is ready within 60 seconds. Then I will measure the requested cells on the perf VM under perf-run with the origin/dev release server and trusted CDP clock, trace the slowest paths, and keep product changes only if the paired acceptance rule passes.

Starting #1233 on branch `job/tabswitch2`, based on `e63681f8698f155a018994bb07c20a2e41cbd89e` (`job/tabcomp-perf`). I have read the current #1233/#1540 evidence and DESIGN §59. I will first make the full seeded Home and Index reach `/healthz` ready once, stop the server cleanly, snapshot both, and verify a restored restart is ready within 60 seconds. Then I will measure the requested cells on the perf VM under `perf-run` with the origin/dev release server and trusted CDP clock, trace the slowest paths, and keep product changes only if the paired acceptance rule passes.
Author
Owner

Preflight finding: the perf VM still had a #549 Tab benchmark server in calternal-hdd-emu-2349061.scope, serving /srv/hdd-emu/tabswitch-549; its executable was /mnt/hdd/targets/release-shared/release/calternal-server-tabswitch-549. The harness correctly refused to reset the marked Home while the server PID was live. I stopped the recorded server with SIGTERM inside perf-run calternal tabswitch2-stale-server-stop 1; it exited cleanly. Load average inside that lock was 4.09 1.61 2.05. No latency sample was collected in this preflight.

Preflight finding: the perf VM still had a #549 Tab benchmark server in `calternal-hdd-emu-2349061.scope`, serving `/srv/hdd-emu/tabswitch-549`; its executable was `/mnt/hdd/targets/release-shared/release/calternal-server-tabswitch-549`. The harness correctly refused to reset the marked Home while the server PID was live. I stopped the recorded server with SIGTERM inside `perf-run calternal tabswitch2-stale-server-stop 1`; it exited cleanly. Load average inside that lock was `4.09 1.61 2.05`. No latency sample was collected in this preflight.
Author
Owner

Perf-run tabswitch2-fixture-100k started under the VM lock. The server reached /readyz, then the benchmark stopped before fixture seeding at its HDD qualification guard. QD1 measured 76.595 IOPS, p50 11.075584 ms and p99 51.642368 ms at load average 2.07 / 0.79 / 1.15; the cgroup reported 7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200. The current guard requires at least 100 IOPS and a 7.5–10.5 ms p50. lsblk maps the delayed 254:0 device to backing loop 7:0.

No fixture was seeded and no Tab sample was collected. I am preserving the strict default guard and investigating a report-only run mode so the actual VM measurements remain visible without calling this device qualified.

Perf-run `tabswitch2-fixture-100k` started under the VM lock. The server reached `/readyz`, then the benchmark stopped before fixture seeding at its HDD qualification guard. QD1 measured 76.595 IOPS, p50 11.075584 ms and p99 51.642368 ms at load average 2.07 / 0.79 / 1.15; the cgroup reported `7:0 rbps=157286400 wbps=157286400 riops=200 wiops=200`. The current guard requires at least 100 IOPS and a 7.5–10.5 ms p50. `lsblk` maps the delayed `254:0` device to backing loop `7:0`. No fixture was seeded and no Tab sample was collected. I am preserving the strict default guard and investigating a report-only run mode so the actual VM measurements remain visible without calling this device qualified.
Author
Owner

The strict HDD guard remains the default. I committed an opt-in TAB_SWITCH_HDD_QUALIFICATION_MODE=record-only diagnostic mode in 0b7442f46; it records the same IOPS and latency bounds, marks each qualification sample as passed or out-of-spec, and includes the mode in the report. The focused benchmark and harness tests pass: 37 passed, 2 skipped. I am continuing the fixture run with this explicit mode so no out-of-spec environment is reported as qualified.

The strict HDD guard remains the default. I committed an opt-in `TAB_SWITCH_HDD_QUALIFICATION_MODE=record-only` diagnostic mode in `0b7442f46`; it records the same IOPS and latency bounds, marks each qualification sample as passed or out-of-spec, and includes the mode in the report. The focused benchmark and harness tests pass: 37 passed, 2 skipped. I am continuing the fixture run with this explicit mode so no out-of-spec environment is reported as qualified.
Author
Owner

The 100k fixture run seeded the Home, then the restarted server stayed in /readyz phase notes-upgrade-backfills for about 30 minutes. The runner was at waitForStartupFilesReady and collected no samples; I stopped the server/browser and recorded the evidence plus a correction on #1540. I am switching to the requested documented smaller profile: --smoke --files 10000 (10k Files, reduced non-Files fixture counts), with the same 60-second clean-restart /healthz gate.

The 100k fixture run seeded the Home, then the restarted server stayed in `/readyz` phase `notes-upgrade-backfills` for about 30 minutes. The runner was at `waitForStartupFilesReady` and collected no samples; I stopped the server/browser and recorded the evidence plus a correction on #1540. I am switching to the requested documented smaller profile: `--smoke --files 10000` (10k Files, reduced non-Files fixture counts), with the same 60-second clean-restart `/healthz` gate.
Author
Owner

I found why the 10k smoke attempt remained in post-readiness photo indexing: the Home contained 10k Files but 50,000 Photos. tab-switch-seed.py defaults to 50k, while seedHome incorrectly omitted --photos when smoke requested 100. I fixed the argument selection and added a regression test in 8494f33a5; focused tests pass (38 passed, 2 skipped). The current attempt is not a valid reduced fixture, so I am stopping it and will retry with the corrected 100-photo count.

I found why the 10k smoke attempt remained in post-readiness photo indexing: the Home contained 10k Files but 50,000 Photos. `tab-switch-seed.py` defaults to 50k, while `seedHome` incorrectly omitted `--photos` when smoke requested 100. I fixed the argument selection and added a regression test in `8494f33a5`; focused tests pass (38 passed, 2 skipped). The current attempt is not a valid reduced fixture, so I am stopping it and will retry with the corrected 100-photo count.
Author
Owner

The invalid smoke attempt’s stack confirmed the wait location: waitForPhotoIndex (bench/tab-switch.mjs:1780) after the server had reached /healthz state=ok. It was still waiting after 52 minutes with the unintended 50k Photos fixture. I stopped the server/browser; perf-run status is free and no benchmark processes remain. The next attempt uses the committed generator argument fix.

The invalid smoke attempt’s stack confirmed the wait location: `waitForPhotoIndex` (`bench/tab-switch.mjs:1780`) after the server had reached `/healthz state=ok`. It was still waiting after 52 minutes with the unintended 50k Photos fixture. I stopped the server/browser; `perf-run status` is free and no benchmark processes remain. The next attempt uses the committed generator argument fix.
Author
Owner

Result

Head: 555c4d8aa659e5bfcebfdf8b98a350b7c9deda4c (merge of origin/dev at ac7fda1a8c49e902e1b246bea9d167d64e92d58f into job/tabswitch2). No push or deploy.

The benchmark now snapshots the Home and database after one successful reconciliation, restores that snapshot for cold samples, checks /healthz state=ok within 60 seconds, returns to the requested source Tab after restart, and measures trusted CDP input through the destination's full-paint boundary. Request timing is scoped to that interval; failed requests keep only a safe path and browser error code.

No product performance change was retained. The warm trace identifies client-side Calendar activation work, and a cold trace also shows DB-backed Calendar reads. A small candidate fix with the required five interleaved A/B pairs was not available, so the acceptance rule was not met. Follow-ups: #1557 (client render) and #1560 (cold Calendar DB reads). I updated #1540 with the readiness evidence.

Measurements

The warm profile ran on perf-test-69-63 through perf-run, Chromium with macOS platform emulation, 1440 px, light theme, seven samples per directed cell. It used the pinned origin/dev release server dbd621434d0a1133f16230cca5c6a83af0d8aed3 (SHA-256 1c096b0b642bc5c7b740e0ad01c80ea3794e6aabdabc3cd6a527afe24c3ba4ca) and the 10,000-file fixture with 100 Photos, 20 Notes, 20 Tasks, 100 Transactions, 366 Daily notes and 10,980 Log entries. Load average inside the lock before setup: 3.05 / 2.48 / 2.16.

Warm Tab switch Input → full paint p50 / p95 (ms)
Tasks → Notes 357.2 / 485.0
Files → Mail 400.1 / 520.6
Money → Mail 430.3 / 486.6
Tasks → Mail 343.6 / 433.1
Files → Notes 370.2 / 389.6
Calendar → Mail 542.2 / 832.9
Notes → Tasks 444.1 / 522.2
Mail → Photos 472.0 / 543.9
Mail → Files 398.3 / 635.1
Files → Calendar 1,185.7 / 1,508.1
Notes → Files 390.7 / 651.0
Calendar → Files 561.0 / 821.6
Mail → Money 403.8 / 594.1
Photos → Mail 452.9 / 472.7
Notes → Mail 367.6 / 605.7
Mail → Calendar 1,464.9 / 1,813.5
Mail → Tasks 356.1 / 446.1
Mail → Notes 387.7 / 534.0

All measured warm p50 values exceed the DESIGN §59 100 ms budget. Mail → AI and AI → Mail were not measured.

The 10,000-file cache-dropped cold restart did not reach /healthz state=ok within 60 seconds, so it produced no cold sample. I used a separately settled 100-file snapshot for seven cold Mail → Calendar samples. Readiness took 38.704–49.695 s (p50 48.142 s); input-to-full-paint p50 / p95 was 2,223.8 / 5,122.1 ms. The initial 100-file fixture seed took 1,236,269 ms to become ready. This reduced fixture is not representative of a full Home.

Mail → Calendar finding: The seven-sample warm matrix had 0.17 ms p50 server timing. Its marked trace shows a 644.377 ms Svelte runtime FunctionCall and five long tasks, max 669 ms; the trace's observed server request took about 0.098 ms. This points to client-side Calendar activation/render work as the warm bottleneck, not slow server work or fixture startup. The one-sample cold trace took 2,408.8 ms; it also shows client long tasks up to 926.447 ms and Calendar reads with 85–269 ms of DB time each. The trace shows no evidence of Mail sync lock contention. Warm trace and request waterfall: artifacts/tab-switch-1233-mail-calendar/. Cold trace and waterfall: artifacts/tab-switch-1233-cold100-recheck/ in the worktree. The two other slowest warm cells (Files → Calendar and Calendar → Files) were not traced.

Files

Compared with origin/dev: apps/web/e2e/harness.mjs, apps/web/e2e/harness.test.mjs, bench/cdp-input.mjs, bench/cdp-input.test.mjs, bench/notifications-read-1387-cdp.mjs, bench/tab-switch.mjs, bench/tab-switch.test.mjs, and docs/perf/runs/tabcomp-1233-composer-baseline-2026-10-10.json.

Gates

cd apps/web && bun run check (after bun install --frozen-lockfile; no lockfile changes):

perf-lint: PASS; 0 violations; 22247 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, 134 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/tabswitch2/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

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

 Test Files  314 passed (314)
      Tests  2301 passed (2301)
   Start at  21:58:39
   Duration  241.15s (environment 28%, transform 24%, import 21%, tests 21%, setup 5%)

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

 25 pass
 0 fail
 88 expect() calls
Ran 25 tests across 2 files. [57.00ms]

node --check bench/tab-switch.mjs and git diff --check origin/dev...HEAD both exited 0 with no output. No Rust code changed, so Rust format, clippy and test gates were not run. cargo clean output:

     Removed 1 file, 356B total

The web build and the remote job snapshot/build directory were removed. perf-run status reports free.

Known gaps and decisions

  • The 100,000-file fixture remained in notes-upgrade-backfills beyond the setup window. The 10,000-file cache-dropped restart also missed the 60-second readiness gate. I used a 100-file fixture only for cold Mail → Calendar so the server was ready before each sample.
  • Only the 1440 px light warm matrix and cold Mail → Calendar were measured. The 390 px matrix, other cold cells, all three Composer contexts, AI↔Mail, and traces for Files → Calendar / Calendar → Files remain incomplete. The job passed its four-hour time box, so I stopped collecting new profiles and report these gaps.
  • No product UI changed, so visual screenshots and UX-gap checks do not apply. No Rust crate or API route changed; adversarial route testing was not applicable.
  • No product fix was selected because the trace evidence did not identify a small safe change with a valid before/after comparison. DESIGN §59's improvement threshold remains untested.
  • The only benchmark fallback not prescribed by DESIGN was the 100-file cold fixture after the larger fixtures failed their readiness gates. Its limits are recorded above and in #1540.
## Result **Head:** `555c4d8aa659e5bfcebfdf8b98a350b7c9deda4c` (merge of `origin/dev` at `ac7fda1a8c49e902e1b246bea9d167d64e92d58f` into `job/tabswitch2`). No push or deploy. The benchmark now snapshots the Home and database after one successful reconciliation, restores that snapshot for cold samples, checks `/healthz state=ok` within 60 seconds, returns to the requested source Tab after restart, and measures trusted CDP input through the destination's full-paint boundary. Request timing is scoped to that interval; failed requests keep only a safe path and browser error code. No product performance change was retained. The warm trace identifies client-side Calendar activation work, and a cold trace also shows DB-backed Calendar reads. A small candidate fix with the required five interleaved A/B pairs was not available, so the acceptance rule was not met. Follow-ups: #1557 (client render) and #1560 (cold Calendar DB reads). I updated #1540 with the readiness evidence. ## Measurements The warm profile ran on `perf-test-69-63` through `perf-run`, Chromium with macOS platform emulation, 1440 px, light theme, seven samples per directed cell. It used the pinned origin/dev release server `dbd621434d0a1133f16230cca5c6a83af0d8aed3` (SHA-256 `1c096b0b642bc5c7b740e0ad01c80ea3794e6aabdabc3cd6a527afe24c3ba4ca`) and the 10,000-file fixture with 100 Photos, 20 Notes, 20 Tasks, 100 Transactions, 366 Daily notes and 10,980 Log entries. Load average inside the lock before setup: `3.05 / 2.48 / 2.16`. | Warm Tab switch | Input → full paint p50 / p95 (ms) | |---|---:| | Tasks → Notes | 357.2 / 485.0 | | Files → Mail | 400.1 / 520.6 | | Money → Mail | 430.3 / 486.6 | | Tasks → Mail | 343.6 / 433.1 | | Files → Notes | 370.2 / 389.6 | | Calendar → Mail | 542.2 / 832.9 | | Notes → Tasks | 444.1 / 522.2 | | Mail → Photos | 472.0 / 543.9 | | Mail → Files | 398.3 / 635.1 | | Files → Calendar | 1,185.7 / 1,508.1 | | Notes → Files | 390.7 / 651.0 | | Calendar → Files | 561.0 / 821.6 | | Mail → Money | 403.8 / 594.1 | | Photos → Mail | 452.9 / 472.7 | | Notes → Mail | 367.6 / 605.7 | | Mail → Calendar | 1,464.9 / 1,813.5 | | Mail → Tasks | 356.1 / 446.1 | | Mail → Notes | 387.7 / 534.0 | All measured warm p50 values exceed the DESIGN §59 100 ms budget. Mail → AI and AI → Mail were not measured. The 10,000-file cache-dropped cold restart did not reach `/healthz state=ok` within 60 seconds, so it produced no cold sample. I used a separately settled 100-file snapshot for seven cold Mail → Calendar samples. Readiness took 38.704–49.695 s (p50 48.142 s); input-to-full-paint p50 / p95 was 2,223.8 / 5,122.1 ms. The initial 100-file fixture seed took 1,236,269 ms to become ready. This reduced fixture is not representative of a full Home. **Mail → Calendar finding:** The seven-sample warm matrix had 0.17 ms p50 server timing. Its marked trace shows a 644.377 ms Svelte runtime `FunctionCall` and five long tasks, max 669 ms; the trace's observed server request took about 0.098 ms. This points to client-side Calendar activation/render work as the warm bottleneck, not slow server work or fixture startup. The one-sample cold trace took 2,408.8 ms; it also shows client long tasks up to 926.447 ms and Calendar reads with 85–269 ms of DB time each. The trace shows no evidence of Mail sync lock contention. Warm trace and request waterfall: `artifacts/tab-switch-1233-mail-calendar/`. Cold trace and waterfall: `artifacts/tab-switch-1233-cold100-recheck/` in the worktree. The two other slowest warm cells (Files → Calendar and Calendar → Files) were not traced. ## Files Compared with `origin/dev`: `apps/web/e2e/harness.mjs`, `apps/web/e2e/harness.test.mjs`, `bench/cdp-input.mjs`, `bench/cdp-input.test.mjs`, `bench/notifications-read-1387-cdp.mjs`, `bench/tab-switch.mjs`, `bench/tab-switch.test.mjs`, and `docs/perf/runs/tabcomp-1233-composer-baseline-2026-10-10.json`. ## Gates `cd apps/web && bun run check` (after `bun install --frozen-lockfile`; no lockfile changes): ```text perf-lint: PASS; 0 violations; 22247 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, 134 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/tabswitch2/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bun run test -- --maxWorkers=2`: ```text Test Files 314 passed (314) Tests 2301 passed (2301) Start at 21:58:39 Duration 241.15s (environment 28%, transform 24%, import 21%, tests 21%, setup 5%) ``` `bun test bench/tab-switch.test.mjs bench/cdp-input.test.mjs`: ```text 25 pass 0 fail 88 expect() calls Ran 25 tests across 2 files. [57.00ms] ``` `node --check bench/tab-switch.mjs` and `git diff --check origin/dev...HEAD` both exited 0 with no output. No Rust code changed, so Rust format, clippy and test gates were not run. `cargo clean` output: ```text Removed 1 file, 356B total ``` The web build and the remote job snapshot/build directory were removed. `perf-run status` reports `free`. ## Known gaps and decisions - The 100,000-file fixture remained in `notes-upgrade-backfills` beyond the setup window. The 10,000-file cache-dropped restart also missed the 60-second readiness gate. I used a 100-file fixture only for cold Mail → Calendar so the server was ready before each sample. - Only the 1440 px light warm matrix and cold Mail → Calendar were measured. The 390 px matrix, other cold cells, all three Composer contexts, AI↔Mail, and traces for Files → Calendar / Calendar → Files remain incomplete. The job passed its four-hour time box, so I stopped collecting new profiles and report these gaps. - No product UI changed, so visual screenshots and UX-gap checks do not apply. No Rust crate or API route changed; adversarial route testing was not applicable. - No product fix was selected because the trace evidence did not identify a small safe change with a valid before/after comparison. DESIGN §59's improvement threshold remains untested. - The only benchmark fallback not prescribed by DESIGN was the 100-file cold fixture after the larger fixtures failed their readiness gates. Its limits are recorded above and in #1540.
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#1233
No description provided.