Investigate dark scheme reverting during multi-context review #507

Open
opened 2026-09-30 09:50:35 +00:00 by kayg · 3 comments
Owner

Production Calendar review on the merged tree found a non-blocking Appearance inconsistency. The screenshot harness selects Dark through Settings, waits for local settings and GET /api/v1/appearance to report dark, then selects the Tokyo Night family and returns to Calendar. At the last desktop-dark case in two runs, the strict evidence assertion reported:

{"mode":"light","palette":"tokyo-night-day","dark":false,"colorScheme":"light","storedMode":"light","storedFamily":"tokyo-night","serverMode":"light"}

The same phone and tablet cases passed. A second desktop crop context had already closed. Removing that context's duplicate theme save did not resolve the finding. The root cause is not established. Possible stale Appearance hydration needs review; this report does not claim that cause.

The merge job will use the existing shared persisted-theme capture helper, with the same strict server/storage/rendered-theme assertions, to collect review evidence. Production Appearance behavior is unchanged. This is an odd-but-harmless view preference inconsistency, not a crash, data-loss or authorization finding.

Production Calendar review on the merged tree found a non-blocking Appearance inconsistency. The screenshot harness selects Dark through Settings, waits for local settings and GET /api/v1/appearance to report dark, then selects the Tokyo Night family and returns to Calendar. At the last desktop-dark case in two runs, the strict evidence assertion reported: ``` {"mode":"light","palette":"tokyo-night-day","dark":false,"colorScheme":"light","storedMode":"light","storedFamily":"tokyo-night","serverMode":"light"} ``` The same phone and tablet cases passed. A second desktop crop context had already closed. Removing that context's duplicate theme save did not resolve the finding. The root cause is not established. Possible stale Appearance hydration needs review; this report does not claim that cause. The merge job will use the existing shared persisted-theme capture helper, with the same strict server/storage/rendered-theme assertions, to collect review evidence. Production Appearance behavior is unchanged. This is an odd-but-harmless view preference inconsistency, not a crash, data-loss or authorization finding.
Author
Owner

Starting fix-507 on job/fix-507. Branch HEAD: 558457cf32; branch base (merge-base with origin/dev): 558457cf32. I am tracing the persisted Appearance write/hydration paths and will reproduce cross-context behavior before changing it.

Starting fix-507 on job/fix-507. Branch HEAD: 558457cf32e1d429da3834a05ff2720d11284302; branch base (merge-base with origin/dev): 558457cf32e1d429da3834a05ff2720d11284302. I am tracing the persisted Appearance write/hydration paths and will reproduce cross-context behavior before changing it.
Author
Owner

Reproduced on merged tree cc25c441b7 with the production SPA and a real local server. Two isolated browser Installations for one User reproduced the race: Installation 2 held an Appearance GET whose response had auto_scheme: null and had a cached Light scheme. Installation 1 saved Dark. When Installation 2 resumed, it sent {"auto_scheme":{"mode":"light"}}; the server returned Light and ended with Light. The rendered state was mode=light, palette=tokyo-night-day, dark=false, colorScheme=light, storedMode=light.

This is reachable while the server preference is first initialized, including two tabs or two devices opening at the same time. Later explicit scheme selections must keep their current replace behavior. I will make only the legacy first-value migration conditional on the server field still being absent. Before-state and reverted-state captures are in artifacts/fix-507/.

Reproduced on merged tree cc25c441b7a974185622a1dee853cf38686d2b67 with the production SPA and a real local server. Two isolated browser Installations for one User reproduced the race: Installation 2 held an Appearance GET whose response had `auto_scheme: null` and had a cached Light scheme. Installation 1 saved Dark. When Installation 2 resumed, it sent `{"auto_scheme":{"mode":"light"}}`; the server returned Light and ended with Light. The rendered state was `mode=light`, `palette=tokyo-night-day`, `dark=false`, `colorScheme=light`, `storedMode=light`. This is reachable while the server preference is first initialized, including two tabs or two devices opening at the same time. Later explicit scheme selections must keep their current replace behavior. I will make only the legacy first-value migration conditional on the server field still being absent. Before-state and reverted-state captures are in `artifacts/fix-507/`.
Author
Owner

#507 is fixed in ede8fdc953.

Finding and fix

hydrate(null) migrated each Installation's cached mode with the normal auto_scheme replacement. When two Installations read an unset User scheme, a delayed Light migration could finish after another Installation explicitly saved Dark and replace it. This is reachable by real Users on separate devices or browser profiles, and by a tab that has stale in-memory scheme state while another tab changes it.

The client now sends initialize_auto_scheme for first-use migration. The Appearance API inserts that value only while the User has no saved scheme. Explicit auto_scheme writes still replace the saved value.

Before, a two-context real-server reproduction returned:

{"initialMode":null,"explicitMode":"dark","staleRequest":{"auto_scheme":{"mode":"light"}},"staleResponseMode":"light","rendered":{"mode":"light","palette":"tokyo-night-day","dark":false,"colorScheme":"light","storedMode":"light"},"finalServerMode":"light"}

After, the regression probe logged:

ISSUE 507 multi-Installation Appearance evidence {"request":{"initialize_auto_scheme":{"mode":"light"}},"rendered":{"mode":"dark","palette":"tokyo-night","dark":true,"colorScheme":"dark","storedMode":"dark"},"serverMode":"dark"}
PASS #507: stale first-use hydration keeps the current User scheme across Installations

Production screenshots are in the shared worktree under artifacts/fix-507/ (before-fix) and artifacts/auto-scheme/issue-507-{before,after}-hydration-{390,820,1440}.png (light and dark states at phone, tablet, and desktop widths). They are ignored and are not in Git. The scripts/fj issue comment command has no attachment option, so the artifact paths are recorded here for review.

Decisions

DESIGN §35 does not define precedence for concurrent first-use migrations. The first conditional migration that reaches the server initializes the shared User scheme; later stale migrations do nothing. Explicit selections replace the value in server write order.

Gates

cargo fmt --all -- --check
(no output; exit 0)

cargo clippy -p calternal-server --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 24.48s

cargo test -p calternal-server
    Finished `test` profile [unoptimized + debuginfo] target(s) in 7m 50s
    test result: ok. 94 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 22.79s

bun run --cwd apps/web check
svelte-check found 0 errors and 0 warnings

bun run --cwd apps/web test
 Test Files  140 passed (140)
      Tests  930 passed (930)
   Duration  190.32s (transform 63%, import 13%, environment 12%, tests 9%, setup 3%)

bash packages/api-client/check-generated.sh
(exit 0; OpenAPI and TypeScript generation produced no diff)

The production build completed. Vite emitted existing use client module-directive warnings in vendored chart files.

The adversarial run passed the #507 scenario, Location API probes, and the 48-request Appearance write burst (all 200). The full script later exited 1 at its existing Auto solar-day check: waitForFunction: Timeout 30000ms exceeded at tests/adversarial/appearance_auto_scheme.mjs:522. The same host reported load averages around 32 during the local benchmark; this unrelated Location check remains unverified under normal load.

Local Appearance performance profile (host calternal-dev, load average 32.04, 31.03, 29.99): conditional migration p50/p95 27.1/48.0 ms, mean server CPU 46.72%, mean RSS 161,974,272 bytes; eight first-use migrations p50/p95 187.1/205.7 ms, mean CPU 58.04%, mean RSS 161,619,968 bytes. The baseline Appearance API value in docs/perf/baseline.json is a GET (1.1/2.1 ms), so it is not directly comparable to these PUT results.

#507 is fixed in ede8fdc953631f6901027c41652b8eef979f9d7e. ## Finding and fix hydrate(null) migrated each Installation's cached mode with the normal auto_scheme replacement. When two Installations read an unset User scheme, a delayed Light migration could finish after another Installation explicitly saved Dark and replace it. This is reachable by real Users on separate devices or browser profiles, and by a tab that has stale in-memory scheme state while another tab changes it. The client now sends initialize_auto_scheme for first-use migration. The Appearance API inserts that value only while the User has no saved scheme. Explicit auto_scheme writes still replace the saved value. Before, a two-context real-server reproduction returned: ~~~json {"initialMode":null,"explicitMode":"dark","staleRequest":{"auto_scheme":{"mode":"light"}},"staleResponseMode":"light","rendered":{"mode":"light","palette":"tokyo-night-day","dark":false,"colorScheme":"light","storedMode":"light"},"finalServerMode":"light"} ~~~ After, the regression probe logged: ~~~json ISSUE 507 multi-Installation Appearance evidence {"request":{"initialize_auto_scheme":{"mode":"light"}},"rendered":{"mode":"dark","palette":"tokyo-night","dark":true,"colorScheme":"dark","storedMode":"dark"},"serverMode":"dark"} PASS #507: stale first-use hydration keeps the current User scheme across Installations ~~~ Production screenshots are in the shared worktree under artifacts/fix-507/ (before-fix) and artifacts/auto-scheme/issue-507-{before,after}-hydration-{390,820,1440}.png (light and dark states at phone, tablet, and desktop widths). They are ignored and are not in Git. The scripts/fj issue comment command has no attachment option, so the artifact paths are recorded here for review. ## Decisions DESIGN §35 does not define precedence for concurrent first-use migrations. The first conditional migration that reaches the server initializes the shared User scheme; later stale migrations do nothing. Explicit selections replace the value in server write order. ## Gates ~~~text cargo fmt --all -- --check (no output; exit 0) cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 24.48s cargo test -p calternal-server Finished `test` profile [unoptimized + debuginfo] target(s) in 7m 50s test result: ok. 94 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 22.79s bun run --cwd apps/web check svelte-check found 0 errors and 0 warnings bun run --cwd apps/web test Test Files 140 passed (140) Tests 930 passed (930) Duration 190.32s (transform 63%, import 13%, environment 12%, tests 9%, setup 3%) bash packages/api-client/check-generated.sh (exit 0; OpenAPI and TypeScript generation produced no diff) ~~~ The production build completed. Vite emitted existing use client module-directive warnings in vendored chart files. The adversarial run passed the #507 scenario, Location API probes, and the 48-request Appearance write burst (all 200). The full script later exited 1 at its existing Auto solar-day check: waitForFunction: Timeout 30000ms exceeded at tests/adversarial/appearance_auto_scheme.mjs:522. The same host reported load averages around 32 during the local benchmark; this unrelated Location check remains unverified under normal load. Local Appearance performance profile (host calternal-dev, load average 32.04, 31.03, 29.99): conditional migration p50/p95 27.1/48.0 ms, mean server CPU 46.72%, mean RSS 161,974,272 bytes; eight first-use migrations p50/p95 187.1/205.7 ms, mean CPU 58.04%, mean RSS 161,619,968 bytes. The baseline Appearance API value in docs/perf/baseline.json is a GET (1.1/2.1 ms), so it is not directly comparable to these PUT results.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#507
No description provided.