APPEARANCE: Colour scheme 'Auto' — light at sunrise, dark at sunset #324

Closed
opened 2026-09-28 09:31:42 +00:00 by kayg · 16 comments
Owner

Owner (2026-09-28, screenshot of Settings → Appearance → Theme & UI → Colour scheme [System | Light | Dark]): 'we need another option here "Auto" which switches between light and dark modes according to sunrise and sunset.'

Build (opinionated, easy, private):

  • The segmented control becomes System · Auto · Light · Dark. Tooltip/description for Auto: 'Light from sunrise to sunset, dark at night'. The stored value joins the existing appearance setting (server is the source of truth; the local copy only avoids a flash before first paint).
  • Location without a prompt by default: derive coordinates from the user's timezone (IANA zone → its reference city's lat/long via a small bundled table, e.g. Asia/Kolkata → New Delhi, Europe/Berlin → Berlin). This is accurate to a few minutes for most people and needs no permission. An optional 'Use my precise location' toggle uses the browser Geolocation API once (a one-shot, rounded to ~0.1°, stored only as coarse coordinates in the user's settings, never sent anywhere else). Settings shows the next switch time: 'Dark at 6:12 PM'.
  • Compute locally: the NOAA solar position formula (sunrise/sunset at −0.833° altitude), in a small tested module in packages/ui (no network, no dependency). Handle polar day/night (no sunrise → stay light or dark accordingly) and DST changes.
  • Switching: one timer to the next boundary (re-armed on wake, visibility change and timezone change); the change uses the theme family's light/dark variants and the existing theme transition (reduced motion: instant). It never flips while a sheet/dialog animation runs (defer to the end of the frame).
  • The per-scheme background picture memory (#248: remembered per family+scheme) keeps working when Auto flips.
  • Tests: sunrise/sunset for several cities and dates against published tables (±2 min), polar cases, DST boundaries, a timezone-to-city table coverage test.
    Evidence: screenshots of the control and the 'next switch' line, light and dark.
Owner (2026-09-28, screenshot of Settings → Appearance → Theme & UI → Colour scheme [System | Light | Dark]): 'we need another option here "Auto" which switches between light and dark modes according to sunrise and sunset.' Build (opinionated, easy, private): - The segmented control becomes **System · Auto · Light · Dark**. Tooltip/description for Auto: 'Light from sunrise to sunset, dark at night'. The stored value joins the existing appearance setting (server is the source of truth; the local copy only avoids a flash before first paint). - **Location without a prompt by default:** derive coordinates from the user's timezone (IANA zone → its reference city's lat/long via a small bundled table, e.g. Asia/Kolkata → New Delhi, Europe/Berlin → Berlin). This is accurate to a few minutes for most people and needs no permission. An optional **'Use my precise location'** toggle uses the browser Geolocation API once (a one-shot, rounded to ~0.1°, stored only as coarse coordinates in the user's settings, never sent anywhere else). Settings shows the next switch time: 'Dark at 6:12 PM'. - **Compute locally**: the NOAA solar position formula (sunrise/sunset at −0.833° altitude), in a small tested module in packages/ui (no network, no dependency). Handle polar day/night (no sunrise → stay light or dark accordingly) and DST changes. - **Switching:** one timer to the next boundary (re-armed on wake, visibility change and timezone change); the change uses the theme family's light/dark variants and the existing theme transition (reduced motion: instant). It never flips while a sheet/dialog animation runs (defer to the end of the frame). - The per-scheme background picture memory (#248: remembered per family+scheme) keeps working when Auto flips. - Tests: sunrise/sunset for several cities and dates against published tables (±2 min), polar cases, DST boundaries, a timezone-to-city table coverage test. Evidence: screenshots of the control and the 'next switch' line, light and dark.
Author
Owner

Starting #324 on branch job/auto-scheme, based on ebc5d5f10ba475d4df75077471c1694eb0dee8c3 (dev). I am tracing the existing appearance settings, theme tokens, and UI timer conventions before implementing Auto with timezone-city defaults and optional precise coordinates.

Starting #324 on branch `job/auto-scheme`, based on `ebc5d5f10ba475d4df75077471c1694eb0dee8c3` (`dev`). I am tracing the existing appearance settings, theme tokens, and UI timer conventions before implementing Auto with timezone-city defaults and optional precise coordinates.
Author
Owner

Finding: apps/web/src/lib/stores/settings-store.ts persists the current colour scheme only in browser storage, while /api/v1/appearance already reads and writes server-owned backgrounds and fonts in the User settings file (crates/calternal-server/src/appearance.rs). Auto and its coarse optional location need to sync between Installations, so I will extend that appearance contract; the local setting remains the first-paint cache.

Finding: `apps/web/src/lib/stores/settings-store.ts` persists the current colour scheme only in browser storage, while `/api/v1/appearance` already reads and writes server-owned backgrounds and fonts in the User settings file (`crates/calternal-server/src/appearance.rs`). Auto and its coarse optional location need to sync between Installations, so I will extend that appearance contract; the local setting remains the first-paint cache.
Author
Owner

API persistence slice committed as c6d0276c.

The focused server test passed. It checked the new-user default (auto_scheme: null), persistence of Auto mode, server rounding of 28.6139/77.209 to 28.6/77.2, clearing the location, and refusal of latitude 91.

Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 41s
Running unittests src/main.rs (target/debug/deps/calternal_server-ceadddac3ff94dd3)
running 1 test
test appearance::tests::auto_scheme_settings_round_coordinates_and_reject_out_of_range_values ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 67 filtered out; finished in 0.96s
API persistence slice committed as c6d0276c. The focused server test passed. It checked the new-user default (`auto_scheme: null`), persistence of Auto mode, server rounding of 28.6139/77.209 to 28.6/77.2, clearing the location, and refusal of latitude 91. ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 41s Running unittests src/main.rs (target/debug/deps/calternal_server-ceadddac3ff94dd3) running 1 test test appearance::tests::auto_scheme_settings_round_coordinates_and_reject_out_of_range_values ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 67 filtered out; finished in 0.96s ```
Author
Owner

Finding: the timezone table labeled the India reference city “New Delhi” but retained the IANA zone.tab coordinates for Kolkata (22.53333, 88.36667). This runtime canonicalizes Asia/Kolkata to Asia/Calcutta, so the mismatch would affect the default Auto calculation for that timezone. I am changing the coordinates to New Delhi and adding a test for both the label and coordinates.

Finding: the timezone table labeled the India reference city “New Delhi” but retained the IANA `zone.tab` coordinates for Kolkata (22.53333, 88.36667). This runtime canonicalizes `Asia/Kolkata` to `Asia/Calcutta`, so the mismatch would affect the default Auto calculation for that timezone. I am changing the coordinates to New Delhi and adding a test for both the label and coordinates.
Author
Owner

Finding: the real-server probe enabled precise location. A GET first returned the server-rounded 28.6/77.2 coordinates, but a later GET returned location: null and the visible switch remained off. The Auto client can receive overlapping appearance reads while an Auto write is pending; I am guarding hydration against that stale response and adding this flow to the local probe.

Finding: the real-server probe enabled precise location. A GET first returned the server-rounded 28.6/77.2 coordinates, but a later GET returned `location: null` and the visible switch remained off. The Auto client can receive overlapping appearance reads while an Auto write is pending; I am guarding hydration against that stale response and adding this flow to the local probe.
Author
Owner

The real production browser probe found that precise-location opt-in always fell back to the timezone city, even when Chromium reported geolocation permission as granted. The server's baseline Permissions-Policy sent geolocation=(), which blocked the API before the opt-in callback could run. Auto needs same-origin geolocation only after the User enables the control, so I changed this directive to geolocation=(self) and added a baseline-header assertion. I am rebuilding the server and rerunning the probe against this policy change.

The real production browser probe found that precise-location opt-in always fell back to the timezone city, even when Chromium reported geolocation permission as granted. The server's baseline `Permissions-Policy` sent `geolocation=()`, which blocked the API before the opt-in callback could run. Auto needs same-origin geolocation only after the User enables the control, so I changed this directive to `geolocation=(self)` and added a baseline-header assertion. I am rebuilding the server and rerunning the probe against this policy change.
Author
Owner

The production probe also exposed an ordering race: a delayed first-load Auto migration could finish after the precise-location write, so an uncached API read returned location: null after briefly seeing rounded coordinates. Auto writes from one Installation now run in order, and a focused test holds the earlier write open while making a newer choice. The rerun passed the API/browser adversarial checks and captured all four requested production views.

The production probe also exposed an ordering race: a delayed first-load Auto migration could finish after the precise-location write, so an uncached API read returned `location: null` after briefly seeing rounded coordinates. Auto writes from one Installation now run in order, and a focused test holds the earlier write open while making a newer choice. The rerun passed the API/browser adversarial checks and captured all four requested production views.
Author
Owner

Implemented Auto colour scheme for Forgejo #324.

Built server-backed per-User Auto mode and optional rounded location; a timezone-city reference default; local NOAA sunrise/sunset calculation; polar season handling; and one timeout to the next solar boundary. The settings surface includes Auto, next switch, and opt-in precise location. Added appearance API validation, same-origin geolocation policy, ordered writes, and stale-read protection.

Production adversarial probe passed:
Appearance Auto/Fonts/Background burst: 48 concurrent writes, all 200
PASS Auto appearance adversarial and production captures: /home/kayg/Developer/calternal-wt/auto-scheme/artifacts/auto-scheme

Screenshots:

Gates:

  • cargo fmt --check: no output; exit 0.
  • cargo clippy --all-targets -- -D warnings:
    Finished dev profile [unoptimized + debuginfo] target(s) in 27m 40s
  • cargo test: exit_code=0 (all workspace suites passed). The first attempt lost its sccache server; retry with RUSTC_WRAPPER unset passed.
  • bun run check:
    svelte-check found 0 errors and 0 warnings
  • bun run test:
    Test Files 111 passed (111)
    Tests 726 passed (726)
  • Adversarial round: 48 concurrent appearance writes returned 200; hostile inputs, persistence, and production browser flows passed.

Head: 69e9709344
Branch: job/auto-scheme (pushed; dev merged once).
Cleanup: cargo clean removed 14.4 GiB; web build output removed. Worktree is clean.

Decisions not specified in DESIGN.md:

  • The no-permission default uses the IANA reference point for the device timezone. Precise browser location is opt-in and stored to 0.1 degree.
  • Sunrise and sunset use the NOAA apparent horizon of -0.833 degrees. DST comes from the timezone database; polar seasons schedule the next seasonal boundary.
  • An unsupported browser timezone falls back to UTC locally.

Known gaps: none.

Implemented Auto colour scheme for Forgejo #324. Built server-backed per-User Auto mode and optional rounded location; a timezone-city reference default; local NOAA sunrise/sunset calculation; polar season handling; and one timeout to the next solar boundary. The settings surface includes Auto, next switch, and opt-in precise location. Added appearance API validation, same-origin geolocation policy, ordered writes, and stale-read protection. Production adversarial probe passed: Appearance Auto/Fonts/Background burst: 48 concurrent writes, all 200 PASS Auto appearance adversarial and production captures: /home/kayg/Developer/calternal-wt/auto-scheme/artifacts/auto-scheme Screenshots: - Timezone-city default: https://git.kayg.org/attachments/cf422b92-b1d6-4772-8b2c-f59babe2e77e - Solar day / light: https://git.kayg.org/attachments/930730a0-eaaa-4d36-a880-c837f21b0be2 - Solar night / dark: https://git.kayg.org/attachments/6a81403d-69eb-40a7-b296-a2e1efe1449e - Precise location, mobile: https://git.kayg.org/attachments/7a58a026-d5c7-49de-b995-846fd61ac081 Gates: - cargo fmt --check: no output; exit 0. - cargo clippy --all-targets -- -D warnings: Finished `dev` profile [unoptimized + debuginfo] target(s) in 27m 40s - cargo test: exit_code=0 (all workspace suites passed). The first attempt lost its sccache server; retry with RUSTC_WRAPPER unset passed. - bun run check: svelte-check found 0 errors and 0 warnings - bun run test: Test Files 111 passed (111) Tests 726 passed (726) - Adversarial round: 48 concurrent appearance writes returned 200; hostile inputs, persistence, and production browser flows passed. Head: 69e97093446078b58fcb885ff67a227fde33d8ae Branch: job/auto-scheme (pushed; dev merged once). Cleanup: cargo clean removed 14.4 GiB; web build output removed. Worktree is clean. Decisions not specified in DESIGN.md: - The no-permission default uses the IANA reference point for the device timezone. Precise browser location is opt-in and stored to 0.1 degree. - Sunrise and sunset use the NOAA apparent horizon of -0.833 degrees. DST comes from the timezone database; polar seasons schedule the next seasonal boundary. - An unsupported browser timezone falls back to UTC locally. Known gaps: none.
Author
Owner

Orchestrator review: desktop is correct (Auto segment, 'Dark at 6:51 PM, Berlin for Europe/Berlin' plausible for 28 Sep). Before merge, prove the phone number: the 390 crop shows 'Light at 7:53 PM' with precise location ON. That is right only if the mocked coordinates are far from the viewer's timezone (sunrise there = 19:53 local time here). State the exact mocked lat/long and viewer TZ used for that screenshot, and add a unit test for that exact case (coordinates + TZ → expected next boundary, checked against NOAA's calculator ±2 min). If the displayed time should be in the location's local time or the viewer's, pick one and say it in the UI ('Light at 7:53 PM your time'). Also: at 390 px the Theme dropdown is cut off by the sheet bottom (the sheet must scroll it into view), and add 820 px crops.

Orchestrator review: desktop is correct (Auto segment, 'Dark at 6:51 PM, Berlin for Europe/Berlin' plausible for 28 Sep). **Before merge, prove the phone number:** the 390 crop shows '**Light at 7:53 PM**' with precise location ON. That is right only if the mocked coordinates are far from the viewer's timezone (sunrise there = 19:53 local time here). State the exact mocked lat/long and viewer TZ used for that screenshot, and add a unit test for that exact case (coordinates + TZ → expected next boundary, checked against NOAA's calculator ±2 min). If the displayed time should be in the location's local time or the viewer's, pick one and say it in the UI ('Light at 7:53 PM your time'). Also: at 390 px the Theme dropdown is cut off by the sheet bottom (the sheet must scroll it into view), and add 820 px crops.
Author
Owner

Starting work on job/auto-scheme, based on dev at 5c3068936e55c975f083d8c46b401d42db087036. I am checking the latest review items for the NOAA example, the 390 px Theme menu, and 820 px screenshot coverage.

Starting work on `job/auto-scheme`, based on `dev` at `5c3068936e55c975f083d8c46b401d42db087036`. I am checking the latest review items for the NOAA example, the 390 px Theme menu, and 820 px screenshot coverage.
Author
Owner

Finding and fix evidence: the earlier phone screenshot used latitude: 0 with a longitude calculated from Date.now() (nightLongitude in tests/adversarial/appearance_auto_scheme.mjs). The run did not log that computed value, so the 7:53 PM capture could not be reproduced from the artifact. I replaced it with the exact case latitude: 0, longitude: 178.5, viewer zone Europe/Berlin, and a fixed instant of 2026-09-28T12:00:00Z. NOAA/GML's 2026 table reports 05:53 sunrise on 29 Sep for that point in Etc/GMT-12, which is 17:53 UTC on 28 Sep and 19:53 in Berlin. The new unit test checks the exact coordinates, zone, date and next sunrise within ±2 minutes; it passes.

The switch label now says your time (Europe/Berlin) so the displayed zone is clear. ThemePicker now scrolls its trigger into the nearest sheet scrollport before opening the menu. Its focused UI test passes. The production capture script now covers 1440, 820 and 390 px in light and dark, asserts the menu and trigger stay visible, and captures both Auto phases at every width.

Focused web tests: Test Files 2 passed (2); Tests 18 passed (18).

NOAA reference: https://gml.noaa.gov/grad/solcalc/table.php?lat=0&lon=178.5&year=2026

Finding and fix evidence: the earlier phone screenshot used `latitude: 0` with a longitude calculated from `Date.now()` (`nightLongitude` in `tests/adversarial/appearance_auto_scheme.mjs`). The run did not log that computed value, so the 7:53 PM capture could not be reproduced from the artifact. I replaced it with the exact case `latitude: 0`, `longitude: 178.5`, viewer zone `Europe/Berlin`, and a fixed instant of `2026-09-28T12:00:00Z`. NOAA/GML's 2026 table reports 05:53 sunrise on 29 Sep for that point in `Etc/GMT-12`, which is 17:53 UTC on 28 Sep and 19:53 in Berlin. The new unit test checks the exact coordinates, zone, date and next sunrise within ±2 minutes; it passes. The switch label now says `your time (Europe/Berlin)` so the displayed zone is clear. ThemePicker now scrolls its trigger into the nearest sheet scrollport before opening the menu. Its focused UI test passes. The production capture script now covers 1440, 820 and 390 px in light and dark, asserts the menu and trigger stay visible, and captures both Auto phases at every width. Focused web tests: `Test Files 2 passed (2)`; `Tests 18 passed (18)`. NOAA reference: https://gml.noaa.gov/grad/solcalc/table.php?lat=0&lon=178.5&year=2026
Author
Owner

The production appearance capture stopped at apps/web/e2e/appearance-review.mjs:339 after a 20 s wait for a PUT /api/v1/appearance. That action only changes the visible Background tab; it does not save settings, so there is no PUT to observe. I removed that incorrect wait and kept the following persisted Theme choice assertion. The targeted production capture has passed Theme menus and Auto screens at 1440, 820, and 390 px; I am rerunning the full capture matrix now.

The production appearance capture stopped at `apps/web/e2e/appearance-review.mjs:339` after a 20 s wait for a `PUT /api/v1/appearance`. That action only changes the visible Background tab; it does not save settings, so there is no PUT to observe. I removed that incorrect wait and kept the following persisted Theme choice assertion. The targeted production capture has passed Theme menus and Auto screens at 1440, 820, and 390 px; I am rerunning the full capture matrix now.
Author
Owner

The required workspace cargo test gate failed once in calternal-collab integration test owner_editor_viewer_and_live_revoke (crates/calternal-collab/tests/shared_notes.rs:286). The test log reported editor Share lost after Note save from its client fixture, after repeated shared Note access denied identity checks. I did not change its expectation or rerun the full gate. All earlier suites in that run passed. This is outside the Auto appearance changes and is recorded for follow-up.

The required workspace `cargo test` gate failed once in `calternal-collab` integration test `owner_editor_viewer_and_live_revoke` (`crates/calternal-collab/tests/shared_notes.rs:286`). The test log reported `editor Share lost after Note save` from its client fixture, after repeated `shared Note access denied` identity checks. I did not change its expectation or rerun the full gate. All earlier suites in that run passed. This is outside the Auto appearance changes and is recorded for follow-up.
Author
Owner

Two review findings are recorded for owner follow-up. First, the full appearance-review.mjs run now proceeds past the display-only Background tab, then times out at captureAutoScheme line 244. That predicate expects document.documentElement.dataset.mode to be light/dark while Auto is active. apps/web/src/lib/stores/settings-store.ts:191 intentionally stores the selected preference there (auto); the resolved phase is represented by the is-dark class. I left that existing expectation unchanged per the test contract. The separate real-server review asserts the exact displayed Auto labels and captured the Theme/Auto views at all three widths.

Second, two initial adversarial attempts observed different no-store reads around precise-location opt-in: the polling GET saw rounded coordinates, then the next GET returned location: null. A request log showed one opt-in PUT with the expected rounded coordinates; the next complete adversarial run passed. I did not weaken the persistence assertion, but I also could not identify the writer that caused those two transient observations. Please review this intermittent consistency signal.

Two review findings are recorded for owner follow-up. First, the full `appearance-review.mjs` run now proceeds past the display-only Background tab, then times out at `captureAutoScheme` line 244. That predicate expects `document.documentElement.dataset.mode` to be `light`/`dark` while Auto is active. `apps/web/src/lib/stores/settings-store.ts:191` intentionally stores the selected preference there (`auto`); the resolved phase is represented by the `is-dark` class. I left that existing expectation unchanged per the test contract. The separate real-server review asserts the exact displayed Auto labels and captured the Theme/Auto views at all three widths. Second, two initial adversarial attempts observed different no-store reads around precise-location opt-in: the polling GET saw rounded coordinates, then the next GET returned `location: null`. A request log showed one opt-in PUT with the expected rounded coordinates; the next complete adversarial run passed. I did not weaken the persistence assertion, but I also could not identify the writer that caused those two transient observations. Please review this intermittent consistency signal.
Author
Owner

Finished

Branch job/auto-scheme is pushed. Head: c75f7b2932e72055cbf86308133227dd08c1b900. I merged dev once at 146685670666e987fd8467734f5ee57858166493.

Built

  • Auto switch labels now identify the viewer's timezone, for example Light at 7:53 PM your time (Europe/Berlin).
  • Added the exact solar unit case: latitude 0, longitude 178.5, Europe/Berlin, at 2026-09-28T12:00:00Z; the expected sunrise boundary is 19:53 Berlin time, within ±2 minutes. NOAA's 2026 table for these coordinates lists Sep 29 sunrise at 05:53 in UTC+12, which maps to Sep 28 17:53 UTC: https://gml.noaa.gov/grad/solcalc/table.php?lat=0&lon=178.5&year=2026
  • The Theme menu scrolls its trigger into view before opening in a narrow Settings sheet.
  • Corrected the visual-review harness so a display-only Background tab change does not wait for an API write.

Production screenshots

All are from the production SPA and a real local server. The 390/820 captures assert viewport bounds and trigger visibility; screenshot capture also checks icon/text cap-height alignment within 1 px. Screenshots are attached here and are not committed.

The targeted capture ended with:

PASS targeted appearance screenshots: /home/kayg/Developer/calternal-wt/auto-scheme/artifacts/appearance-target-review

Gates

cargo fmt --check passed with empty stdout/stderr (exit 0).

cargo clippy --all-targets -- -D warnings output:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 13s

cargo test failed in the unrelated calternal-collab integration test owner_editor_viewer_and_live_revoke:

test owner_editor_viewer_and_live_revoke ... FAILED

thread 'owner_editor_viewer_and_live_revoke' panicked at crates/calternal-collab/tests/shared_notes.rs:286:5:
error: editor Share lost after Note save

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.77s

error: test failed, to rerun pass `-p calternal-collab --test shared_notes`

bun run check output:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/auto-scheme/apps/web
Getting Svelte diagnostics...
svelte-check found 0 errors and 0 warnings

bun run test output:

Test Files  113 passed (113)
      Tests  744 passed (744)
   Start at  17:05:42
   Duration  96.20s (transform 58%, environment 16%, import 14%, tests 9%, setup 3%)

Known gaps

  • The full appearance-review.mjs run reaches Auto but times out at its existing line 244 predicate, which checks document.documentElement.dataset.mode === 'light'/'dark'. Auto intentionally leaves that field as the selected value auto; the resolved phase uses the is-dark class. I left that expectation unchanged under the test contract. The focused production captures and the Auto adversarial flow passed.
  • Two initial adversarial attempts saw a no-store read with the rounded precise coordinates followed by location: null; the captured opt-in issued one correct PUT. The next complete adversarial run passed. I kept the strict assertion and recorded this intermittent consistency signal above; its cause is unresolved.

Decisions

  • Use the device timezone ID in the switch label so the displayed clock is unambiguous across installations.
  • Before opening the anchored Theme menu, scroll the trigger only as far as needed into view with block: nearest and inline: nearest.
## Finished Branch `job/auto-scheme` is pushed. Head: `c75f7b2932e72055cbf86308133227dd08c1b900`. I merged `dev` once at `146685670666e987fd8467734f5ee57858166493`. ## Built - Auto switch labels now identify the viewer's timezone, for example `Light at 7:53 PM your time (Europe/Berlin)`. - Added the exact solar unit case: latitude `0`, longitude `178.5`, `Europe/Berlin`, at `2026-09-28T12:00:00Z`; the expected sunrise boundary is 19:53 Berlin time, within ±2 minutes. NOAA's 2026 table for these coordinates lists Sep 29 sunrise at 05:53 in UTC+12, which maps to Sep 28 17:53 UTC: https://gml.noaa.gov/grad/solcalc/table.php?lat=0&lon=178.5&year=2026 - The Theme menu scrolls its trigger into view before opening in a narrow Settings sheet. - Corrected the visual-review harness so a display-only Background tab change does not wait for an API write. ## Production screenshots All are from the production SPA and a real local server. The 390/820 captures assert viewport bounds and trigger visibility; screenshot capture also checks icon/text cap-height alignment within 1 px. Screenshots are attached here and are not committed. - 1440 light: [Theme menu](https://git.kayg.org/attachments/554c18ae-6b40-4f9e-8de7-3b77c34270a9), [Auto](https://git.kayg.org/attachments/be10ee1b-2067-41d9-b7e4-f347074f79f7) - 1440 dark: [Theme menu](https://git.kayg.org/attachments/9b99f812-4735-43eb-bbfb-b87a6c957926), [Auto](https://git.kayg.org/attachments/a77b1bb4-24bb-48c7-add8-91e9d56a0b95) - 820 light: [Theme menu](https://git.kayg.org/attachments/689c814c-c140-4ec2-a399-ddeda5579ec4), [Auto](https://git.kayg.org/attachments/1a6de1dd-fe3c-48f5-963c-65d2f8861743) - 820 dark: [Theme menu](https://git.kayg.org/attachments/23c568f0-8520-48b3-b5e9-742918306173), [Auto](https://git.kayg.org/attachments/cd39d3c1-d112-4528-9317-95db990f5df8) - 390 light: [Theme menu](https://git.kayg.org/attachments/a3ce35d3-b9a1-42fc-adfa-16254a837227), [Auto](https://git.kayg.org/attachments/7554e5b2-eaec-4e71-b9ac-266427ad4732) - 390 dark: [Theme menu](https://git.kayg.org/attachments/61bc5a02-bc36-41bd-a7f4-aad6e9b20942), [Auto](https://git.kayg.org/attachments/465e21e1-ff46-4bdc-aefe-f3e847743ea3) The targeted capture ended with: ```text PASS targeted appearance screenshots: /home/kayg/Developer/calternal-wt/auto-scheme/artifacts/appearance-target-review ``` ## Gates `cargo fmt --check` passed with empty stdout/stderr (exit 0). `cargo clippy --all-targets -- -D warnings` output: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 13s ``` `cargo test` failed in the unrelated `calternal-collab` integration test `owner_editor_viewer_and_live_revoke`: ```text test owner_editor_viewer_and_live_revoke ... FAILED thread 'owner_editor_viewer_and_live_revoke' panicked at crates/calternal-collab/tests/shared_notes.rs:286:5: error: editor Share lost after Note save test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.77s error: test failed, to rerun pass `-p calternal-collab --test shared_notes` ``` `bun run check` output: ```text $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/auto-scheme/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` output: ```text Test Files 113 passed (113) Tests 744 passed (744) Start at 17:05:42 Duration 96.20s (transform 58%, environment 16%, import 14%, tests 9%, setup 3%) ``` ## Known gaps - The full `appearance-review.mjs` run reaches Auto but times out at its existing line 244 predicate, which checks `document.documentElement.dataset.mode === 'light'/'dark'`. Auto intentionally leaves that field as the selected value `auto`; the resolved phase uses the `is-dark` class. I left that expectation unchanged under the test contract. The focused production captures and the Auto adversarial flow passed. - Two initial adversarial attempts saw a no-store read with the rounded precise coordinates followed by `location: null`; the captured opt-in issued one correct PUT. The next complete adversarial run passed. I kept the strict assertion and recorded this intermittent consistency signal above; its cause is unresolved. ## Decisions - Use the device timezone ID in the switch label so the displayed clock is unambiguous across installations. - Before opening the anchored Theme menu, scroll the trigger only as far as needed into view with `block: nearest` and `inline: nearest`.
kayg closed this issue 2026-09-28 15:23:19 +00:00
Author
Owner

One-round follow-up from the merged-dev gate run:

  • node tests/adversarial/appearance_auto_scheme.mjs ran against the merged production app and server on 2026-09-28.
  • The hostile appearance payload assertions passed, and the API accepted all 48 concurrent writes with status 200.
  • The probe then timed out at tests/adversarial/appearance_auto_scheme.mjs:183. After it saved an equatorial daytime coordinate and reloaded the settings page, it waited 30 seconds for !document.documentElement.classList.contains('is-dark'); the page remained dark and the process exited with code 1.
  • The later solar night, geolocation and production capture checks did not run. I did not repeat the probe in this one-round job.

This is separate from Calendar issue #352. Please investigate the daytime phase after a saved coordinate is loaded.

One-round follow-up from the merged-dev gate run: - `node tests/adversarial/appearance_auto_scheme.mjs` ran against the merged production app and server on 2026-09-28. - The hostile appearance payload assertions passed, and the API accepted all 48 concurrent writes with status 200. - The probe then timed out at `tests/adversarial/appearance_auto_scheme.mjs:183`. After it saved an equatorial daytime coordinate and reloaded the settings page, it waited 30 seconds for `!document.documentElement.classList.contains('is-dark')`; the page remained dark and the process exited with code 1. - The later solar night, geolocation and production capture checks did not run. I did not repeat the probe in this one-round job. This is separate from Calendar issue #352. Please investigate the daytime phase after a saved coordinate is loaded.
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#324
No description provided.