LIGHT MODE glass: grey-on-grey, weak contrast; brighter material, light scrim, measured contrast (dark mode untouched) #588

Open
opened 2026-10-01 07:23:04 +00:00 by kayg · 47 comments
Owner

Owner report (2026-10-01, Settings screenshots in dark vs light)

"This glass looks absolutely brilliant in dark mode but in light mode, the contrast/readability is very meh!"
In light mode, the Settings sheet reads as grey on grey: muddy mid-grey cards, a grey sidebar, and purple-grey text with weak contrast. Dark mode is approved as is: do not change dark-mode values.
Cause (tokens, packages/ui/src/tokens.css ~957–1000):

  • The glass alphas are the same for both schemes (--glass-overlay-alpha: 36%, card = overlay alpha, chrome 70%, scrim 85% of --glass-base).
  • A 36 % light tint over a dimmed, blurred backdrop lands on mid-grey.
  • The overlay dim behind sheets darkens the page in light mode, which drags every light surface toward grey.
  • Secondary text (--glass-text-muted, --muted) is a purple-grey that drops below 4.5:1 on that grey.
    Fix: light-scheme glass, the way Apple does it (iOS 26/macOS 26 light Liquid Glass: brighter, whiter material, more vibrancy, near-black labels). Light-scheme overrides only, in the token file, no per-component styling:
  1. Brighter material: raise light-scheme glass alphas (overlay/card/chrome) so surfaces read as frosted white, not grey (start near 70–80 % for cards and sheets, 85 % for chrome; tune by measurement). Add a slight brightness/saturation lift in the light backdrop-filter (saturate(180%) brightness(1.05) style) and a 1 px inner top highlight + a slightly darker hairline so cards separate from the page.
  2. Light scrim: the dim behind sheets in light mode is a light veil (white with a low alpha + blur), never a grey/dark dim. Dark mode keeps its dark scrim.
  3. Ink:
    • primary text near-black in light themes (system-label-like);
    • secondary ≥ 4.5:1 and tertiary ≥ 3:1 measured on the actual glass, worst case over a picture background;
    • the accent link colour ≥ 4.5:1 on light glass.
      Keep each theme's hue, adjusting only lightness.
  4. Apply the same to every light theme family (Paper, Catppuccin Latte, Mono light, …), through tokens.
    Prove it (no eyeballing):
  • An automated contrast audit for light themes. For each text role on each glass surface (sheet, card, inner card, sidebar, chrome pills, toasts, menus), render the production build over (a) the theme colour background and (b) 3 picture backgrounds (bright, busy, dark). Sample the composited pixels behind the text (screenshot, then average the pixels under the text box minus the glyphs) and assert WCAG ratios: body 4.5:1, large 3:1, UI icons 3:1.
  • Fail the gate if a light token change drops any role below them.
  • Dark mode: assert the computed token values are unchanged (snapshot).
  • Attach before/after screenshots of Settings (Appearance, Maintenance), Files, Calendar and a sheet, at 1440 and 390, for 3 light themes × 3 backgrounds.
    Web gates. Tokens only (#287 guard).
## Owner report (2026-10-01, Settings screenshots in dark vs light) "This glass looks absolutely brilliant in dark mode but in light mode, the contrast/readability is very meh!" In light mode, the Settings sheet reads as grey on grey: muddy mid-grey cards, a grey sidebar, and purple-grey text with weak contrast. Dark mode is approved as is: **do not change dark-mode values.** **Cause (tokens, packages/ui/src/tokens.css ~957–1000):** - The glass alphas are the same for both schemes (`--glass-overlay-alpha: 36%`, card = overlay alpha, chrome 70%, scrim 85% of `--glass-base`). - A 36 % light tint over a dimmed, blurred backdrop lands on mid-grey. - The overlay dim behind sheets darkens the page in light mode, which drags every light surface toward grey. - Secondary text (`--glass-text-muted`, `--muted`) is a purple-grey that drops below 4.5:1 on that grey. **Fix: light-scheme glass, the way Apple does it (iOS 26/macOS 26 light Liquid Glass: brighter, whiter material, more vibrancy, near-black labels). Light-scheme overrides only, in the token file, no per-component styling:** 1. **Brighter material:** raise light-scheme glass alphas (overlay/card/chrome) so surfaces read as frosted white, not grey (start near 70–80 % for cards and sheets, 85 % for chrome; tune by measurement). Add a slight brightness/saturation lift in the light backdrop-filter (`saturate(180%) brightness(1.05)` style) and a 1 px inner top highlight + a slightly darker hairline so cards separate from the page. 2. **Light scrim:** the dim behind sheets in light mode is a **light** veil (white with a low alpha + blur), never a grey/dark dim. Dark mode keeps its dark scrim. 3. **Ink:** - primary text near-black in light themes (system-label-like); - secondary ≥ 4.5:1 and tertiary ≥ 3:1 **measured on the actual glass**, worst case over a picture background; - the accent link colour ≥ 4.5:1 on light glass. Keep each theme's hue, adjusting only lightness. 4. Apply the same to every light theme family (Paper, Catppuccin Latte, Mono light, …), through tokens. **Prove it (no eyeballing):** - An automated contrast audit for light themes. For each text role on each glass surface (sheet, card, inner card, sidebar, chrome pills, toasts, menus), render the production build over (a) the theme colour background and (b) 3 picture backgrounds (bright, busy, dark). Sample the composited pixels behind the text (screenshot, then average the pixels under the text box minus the glyphs) and assert WCAG ratios: body 4.5:1, large 3:1, UI icons 3:1. - Fail the gate if a light token change drops any role below them. - Dark mode: assert the computed token values are unchanged (snapshot). - Attach before/after screenshots of Settings (Appearance, Maintenance), Files, Calendar and a sheet, at 1440 and 390, for 3 light themes × 3 backgrounds. Web gates. Tokens only (#287 guard).
Author
Owner

Starting #588 on branch job/lightglass-588, based on 98131fafe66e06959d61142555fa34d586ccbf10 (working tree clean). I am tracing the existing glass and theme contrast tests before making a light-token-only change. The issue requires an automated production-build audit and light/dark screenshot evidence; I will record the exact coverage available from this branch.

Starting #588 on branch `job/lightglass-588`, based on `98131fafe66e06959d61142555fa34d586ccbf10` (working tree clean). I am tracing the existing glass and theme contrast tests before making a light-token-only change. The issue requires an automated production-build audit and light/dark screenshot evidence; I will record the exact coverage available from this branch.
Author
Owner

Confirmed the issue in the current source: packages/ui/src/tokens.css assigns 36% to the light glass tint, while apps/web/src/calternal-app.css applies the same 45% charcoal --scrim-tint in both schemes. The production glass audit samples Settings card headings/body and toast labels, but its background matrix is only the theme mesh plus one uploaded photo and its theme set is Paper and Tokyo Night. It does not cover Maintenance or the requested three light families and bright/busy/dark image cases. I will extend the existing audit and keep all shipped CSS changes in the shared token file.

Confirmed the issue in the current source: `packages/ui/src/tokens.css` assigns 36% to the light glass tint, while `apps/web/src/calternal-app.css` applies the same 45% charcoal `--scrim-tint` in both schemes. The production glass audit samples Settings card headings/body and toast labels, but its background matrix is only the theme mesh plus one uploaded photo and its theme set is Paper and Tokyo Night. It does not cover Maintenance or the requested three light families and bright/busy/dark image cases. I will extend the existing audit and keep all shipped CSS changes in the shared token file.
Author
Owner

Progress update (2026-10-01)

Committed the light-only shared glass token change as 539dba897 (Brighten light glass and audit rendered contrast). The override raises the light surface tint to 78%, chrome to 85%, adds a brighter saturated filter, white scrim veils, a top highlight and darker hairline, and adjusts primary, secondary and accent text lightness. Dark tokens remain unchanged and now have a production computed-style snapshot assertion.

The source contrast model now resolves the light OKLCH roles and each scheme's actual scrim. It checks primary and secondary text and light accent text against theme meshes. The production audit now samples rendered glyph and icon backdrops on Settings Appearance and Maintenance, Files, Calendar, a theme menu, toast and Composer sheet over Paper, Catppuccin Latte and Mono with theme, bright, busy and dark backgrounds.

Focused test output:

 Test Files  1 passed (1)
      Tests  74 passed (74)
   Start at  11:20:18
   Duration  12.93s (transform 60%, tests 32%, import 7%)

bun run check output:

svelte-check found 0 errors and 0 warnings

I am building the local server to run the before/after production capture and post-change browser audit. No web gate is complete yet.

Progress update (2026-10-01) Committed the light-only shared glass token change as `539dba897` (`Brighten light glass and audit rendered contrast`). The override raises the light surface tint to 78%, chrome to 85%, adds a brighter saturated filter, white scrim veils, a top highlight and darker hairline, and adjusts primary, secondary and accent text lightness. Dark tokens remain unchanged and now have a production computed-style snapshot assertion. The source contrast model now resolves the light OKLCH roles and each scheme's actual scrim. It checks primary and secondary text and light accent text against theme meshes. The production audit now samples rendered glyph and icon backdrops on Settings Appearance and Maintenance, Files, Calendar, a theme menu, toast and Composer sheet over Paper, Catppuccin Latte and Mono with theme, bright, busy and dark backgrounds. Focused test output: ``` Test Files 1 passed (1) Tests 74 passed (74) Start at 11:20:18 Duration 12.93s (transform 60%, tests 32%, import 7%) ``` `bun run check` output: ``` svelte-check found 0 errors and 0 warnings ``` I am building the local server to run the before/after production capture and post-change browser audit. No web gate is complete yet.
Author
Owner

Audit harness finding (2026-10-01): the first production baseline run opened Settings successfully, then the token snapshot failed because Chromium's getComputedStyle resolves --glass-card-alpha: var(--glass-overlay-alpha) to 36%; the check expected the authored var(...) string. I changed it to assert the resolved light card percentage (36% baseline, 78% after) against the matching scheme, ran node --check and git diff --check, and committed 0f277dc8e. No application behavior was involved in this failure.

Audit harness finding (2026-10-01): the first production baseline run opened Settings successfully, then the token snapshot failed because Chromium's `getComputedStyle` resolves `--glass-card-alpha: var(--glass-overlay-alpha)` to `36%`; the check expected the authored `var(...)` string. I changed it to assert the resolved light card percentage (36% baseline, 78% after) against the matching scheme, ran `node --check` and `git diff --check`, and committed `0f277dc8e`. No application behavior was involved in this failure.
Author
Owner

Production audit finding (2026-10-01): the second baseline run confirmed Chromium serializes the computed charcoal scrim custom property as #0a0e0c73. The screenshot-backed check compared authored rgb(...) text, so it stopped before a capture. The snapshot now compares normalized RGB channels and alpha (including 8-digit hex), which accepts CSS serialization changes while still asserting charcoal and 45% for the baseline, and white and 14% after. node --check and git diff --check passed. Commit: eb11ef740.

Production audit finding (2026-10-01): the second baseline run confirmed Chromium serializes the computed charcoal scrim custom property as `#0a0e0c73`. The screenshot-backed check compared authored `rgb(...)` text, so it stopped before a capture. The snapshot now compares normalized RGB channels and alpha (including 8-digit hex), which accepts CSS serialization changes while still asserting charcoal and 45% for the baseline, and white and 14% after. `node --check` and `git diff --check` passed. Commit: `eb11ef740`.
Author
Owner

Production contrast audit finding (2026-10-01): the baseline screenshots rendered correctly, but the first icon probe sampled three fixed points inside each SVG viewbox. For real paths that miss those points, the recorded foreground and backdrop matched (for example, the Settings Maintenance sheet reported a 1.00:1 icon minimum), even though no icon pixels were sampled. I replaced this with two screenshots: one with text hidden and icons visible, and one with the same paths hidden. The audit compares their pixels inside each path's bounds, then measures the actual backdrop at changed pixels. This avoids treating empty viewbox points as icons. The production gate after the change will assert the 3:1 icon target. node --check and git diff --check passed. Commit: 125f63898.

Production contrast audit finding (2026-10-01): the baseline screenshots rendered correctly, but the first icon probe sampled three fixed points inside each SVG viewbox. For real paths that miss those points, the recorded foreground and backdrop matched (for example, the Settings Maintenance sheet reported a 1.00:1 icon minimum), even though no icon pixels were sampled. I replaced this with two screenshots: one with text hidden and icons visible, and one with the same paths hidden. The audit compares their pixels inside each path's bounds, then measures the actual backdrop at changed pixels. This avoids treating empty viewbox points as icons. The production gate after the change will assert the 3:1 icon target. `node --check` and `git diff --check` passed. Commit: `125f63898`.
Author
Owner

Production audit finding (2026-10-01): the old-build matrix saved the first three 390px Settings/Files screenshots, then timed out after 30 seconds waiting for .block.plan on Calendar. The code navigated to the Calendar route and queried the event without waiting for the app's .tg.ready state. I added that existing readiness marker before the event lookup in both light and dark capture paths. node --check and git diff --check passed. Commit: 3cf308a2e.

Production audit finding (2026-10-01): the old-build matrix saved the first three 390px Settings/Files screenshots, then timed out after 30 seconds waiting for `.block.plan` on Calendar. The code navigated to the Calendar route and queried the event without waiting for the app's `.tg.ready` state. I added that existing readiness marker before the event lookup in both light and dark capture paths. `node --check` and `git diff --check` passed. Commit: `3cf308a2e`.
Author
Owner

Production audit finding (2026-10-01): the focused Paper/390 run stopped on chrome-pill-0 icon has composited backdrop samples. Settings leaves the mode tray mounted behind the sheet; the tray's ancestor opacity can be zero while its descendants still have nonzero boxes. The role enumerator and glyph sampler now reject elements under a fully transparent ancestor. The tray is still sampled on the Files and Calendar pages when it is visible. node --check and git diff --check passed. Commit: 556fabebf.

Production audit finding (2026-10-01): the focused Paper/390 run stopped on `chrome-pill-0 icon has composited backdrop samples`. Settings leaves the mode tray mounted behind the sheet; the tray's ancestor opacity can be zero while its descendants still have nonzero boxes. The role enumerator and glyph sampler now reject elements under a fully transparent ancestor. The tray is still sampled on the Files and Calendar pages when it is visible. `node --check` and `git diff --check` passed. Commit: `556fabebf`.
Author
Owner

Production audit finding (2026-10-01): the focused baseline passed the rendered Settings sheet icon samples (6.84:1 minimum) and card-0 icons (6.84:1), then stopped at a card containing an SVG shape with no changed screenshot pixels. That was an empty/nonpainting path rather than a visible icon. The audit now omits icon roles with no rendered path pixels and still requires at least one measured glyph or icon pixel per audited surface; all actual icon pixels remain subject to the 3:1 assertion. node --check and git diff --check passed. Commit: 6a8dccd4c.

Production audit finding (2026-10-01): the focused baseline passed the rendered Settings sheet icon samples (6.84:1 minimum) and card-0 icons (6.84:1), then stopped at a card containing an SVG shape with no changed screenshot pixels. That was an empty/nonpainting path rather than a visible icon. The audit now omits icon roles with no rendered path pixels and still requires at least one measured glyph or icon pixel per audited surface; all actual icon pixels remain subject to the 3:1 assertion. `node --check` and `git diff --check` passed. Commit: `6a8dccd4c`.
Author
Owner

Production audit finding (2026-10-01): waiting for .tg.ready still did not make the provider Event appear in the Calendar screenshot. The mode indicates that the Calendar grid mounted, not that the background CalDAV sync returned its Events. I added a wait for the real /api/v1/calendar/range response to contain the fixture title after connecting the test provider, following the existing event-tint E2E setup. The page now waits for backend data before capturing. node --check and git diff --check passed. Commit: bf45c0dae.

Production audit finding (2026-10-01): waiting for `.tg.ready` still did not make the provider Event appear in the Calendar screenshot. The mode indicates that the Calendar grid mounted, not that the background CalDAV sync returned its Events. I added a wait for the real `/api/v1/calendar/range` response to contain the fixture title after connecting the test provider, following the existing event-tint E2E setup. The page now waits for backend data before capturing. `node --check` and `git diff --check` passed. Commit: `bf45c0dae`.
Author
Owner

Production audit finding (2026-10-01): the focused baseline reached the dark token check and found that the original snapshot compared authored CSS strings, while Chromium returned resolved values: --glass-card-alpha was 30% (not its var(...) alias), scrims were 8-digit hex colors, and the tabbar filter was blur(16px) saturate(1.5) while the old build had no --glass-overlay-filter variable. I updated the snapshot to pin the resolved Dark values and charcoal RGB/alpha; the source contract separately pins the unchanged Dark declarations. node --check and git diff --check passed. Commit: 92e5e213a.

Production audit finding (2026-10-01): the focused baseline reached the dark token check and found that the original snapshot compared authored CSS strings, while Chromium returned resolved values: `--glass-card-alpha` was `30%` (not its `var(...)` alias), scrims were 8-digit hex colors, and the tabbar filter was `blur(16px) saturate(1.5)` while the old build had no `--glass-overlay-filter` variable. I updated the snapshot to pin the resolved Dark values and charcoal RGB/alpha; the source contract separately pins the unchanged Dark declarations. `node --check` and `git diff --check` passed. Commit: `92e5e213a`.
Author
Owner

Production audit finding (2026-10-01): after the test provider's /api/v1/calendar/range returned the requested Event, the phone capture still timed out on hasText('Attack on Titan'). Calendar can hide an event's title text at narrow widths while it keeps the rendered plan block. The capture now selects the first real .block.plan after the API fixture wait, which keeps the screen backed by the real provider data without depending on responsive title visibility. Both Light and Dark screenshot paths use the same selector. node --check and git diff --check passed. Commit: ece1ba62b.

Production audit finding (2026-10-01): after the test provider's `/api/v1/calendar/range` returned the requested Event, the phone capture still timed out on `hasText('Attack on Titan')`. Calendar can hide an event's title text at narrow widths while it keeps the rendered plan block. The capture now selects the first real `.block.plan` after the API fixture wait, which keeps the screen backed by the real provider data without depending on responsive title visibility. Both Light and Dark screenshot paths use the same selector. `node --check` and `git diff --check` passed. Commit: `ece1ba62b`.
Author
Owner

The 820px production Files capture exposed a selector problem in the audit harness: the real fixture was in /api/v1/files/entries, while the rendered row shortened its visible label (…dit context proof.txt) and retained the full filename as its accessible option name. I changed the capture to select the exact accessible name so tablet evidence uses the real row.

The 820px production Files capture exposed a selector problem in the audit harness: the real fixture was in `/api/v1/files/entries`, while the rendered row shortened its visible label (`…dit context proof.txt`) and retained the full filename as its accessible option name. I changed the capture to select the exact accessible name so tablet evidence uses the real row.
Author
Owner

The full old-production-build contrast sweep completed for 3 light theme families × 4 backgrounds × 3 viewport widths, plus the Dark snapshots. It captured 195 screenshots. The pixel audit reported 238 light-role samples below their target. Examples: Paper Settings Appearance app-sidebar body at 820px over the bright photo measured 1.00:1 (target 4.5:1); Composer sheet icons in Latte measured as low as 1.35:1 (target 3:1). The complete baseline output and captures are in this worktree under artifacts/glass-588/before/.

The full old-production-build contrast sweep completed for 3 light theme families × 4 backgrounds × 3 viewport widths, plus the Dark snapshots. It captured 195 screenshots. The pixel audit reported 238 light-role samples below their target. Examples: Paper Settings Appearance app-sidebar body at 820px over the bright photo measured 1.00:1 (target 4.5:1); Composer sheet icons in Latte measured as low as 1.35:1 (target 3:1). The complete baseline output and captures are in this worktree under `artifacts/glass-588/before/`.
Author
Owner

The existing Theme submenu profile initially returned HTTP 422 before measurement: auto_scheme.location is no longer accepted by the Appearance request schema (expected mode). I updated the bench setup to send the current mode-only request contract. This only fixes the profile setup; the glass implementation and server API remain unchanged.

The existing Theme submenu profile initially returned HTTP 422 before measurement: `auto_scheme.location` is no longer accepted by the Appearance request schema (`expected mode`). I updated the bench setup to send the current mode-only request contract. This only fixes the profile setup; the glass implementation and server API remain unchanged.
Author
Owner

The merged theme contrast model found two Light edge cases before the production rebuild: Paper muted text measured 4.447:1 over a black photo under the 78% material, and Latte accent measured 4.430:1. I lowered only the Light muted and accent OKLCH lightness factors to 0.80; hue and all Dark token values stay unchanged. The focused all-theme glass contrast test now passes over the theme meshes and black/white photo extremes.

The merged theme contrast model found two Light edge cases before the production rebuild: Paper muted text measured 4.447:1 over a black photo under the 78% material, and Latte accent measured 4.430:1. I lowered only the Light muted and accent OKLCH lightness factors to 0.80; hue and all Dark token values stay unchanged. The focused all-theme glass contrast test now passes over the theme meshes and black/white photo extremes.
Author
Owner

Finding from the rendered contrast audit: filling Composer Entry text raised five Svelte state_unsafe_mutation page errors. The source-mapped stack points to Composer.svelte:166, where the textarea selection mirror writes reactive state from its input listener. This is outside the token-only scope, so the audit now leaves the field empty and omits its disabled Send control under the WCAG inactive-control contrast exemption.

Finding from the rendered contrast audit: filling Composer `Entry text` raised five Svelte `state_unsafe_mutation` page errors. The source-mapped stack points to `Composer.svelte:166`, where the textarea selection mirror writes reactive state from its input listener. This is outside the token-only scope, so the audit now leaves the field empty and omits its disabled Send control under the WCAG inactive-control contrast exemption.
Author
Owner

The 820 px pixel audit found a real boundary case: Paper Files metadata was 4.31:1 over the dark-photo glass because transparent rows inherited the unadjusted --ink-muted value. Light --ink-muted now uses the shared contrast-safe glass-muted token; the focused rerun measured 6.78:1 for that role. The earlier 1.06:1 Today-button sample was the Settings sheet covering the app sidebar, so the audit now drops glyphs whose rendered pixels are occluded and checks the app sidebar on Files and Calendar instead.

The 820 px pixel audit found a real boundary case: Paper Files metadata was 4.31:1 over the dark-photo glass because transparent rows inherited the unadjusted `--ink-muted` value. Light `--ink-muted` now uses the shared contrast-safe glass-muted token; the focused rerun measured 6.78:1 for that role. The earlier 1.06:1 Today-button sample was the Settings sheet covering the app sidebar, so the audit now drops glyphs whose rendered pixels are occluded and checks the app sidebar on Files and Calendar instead.
Author
Owner

Complete — light glass readability (#588)

Branch: job/lightglass-588
Base: merged origin/dev once at b561de501
Head: 947210529e0e6465f69ee1ca2268fb560ac16119

Built

The shared Light token layer now uses a brighter 78% glass tint, 85% chrome, a white low-alpha scrim, stronger saturation, and a small brightness lift. Light ink tokens lower each theme's OKLCH lightness while preserving hue. The shared muted ink token also covers transparent file rows over photos. Cards use a top highlight and darker hairline. Dark token values remain unchanged.

The production screenshot audit measures visible text and painted icon pixels on Settings Appearance, Settings Maintenance, Files, Calendar, and a sheet. It covers Paper, Catppuccin Latte, and Mono over each theme background and bright, busy, and dark photos at 390, 820, and 1440 px. It passed all 835 role checks at the required thresholds: 4.5:1 for body text, 3:1 for large text and UI icons. The computed Dark token snapshot passed. There are 195 before and 195 after captures; the after set also includes 15 Dark screenshots.

Screenshot bundles

Each bundle contains the listed theme's production screenshots at 390, 820, and 1440 px.

Files

  • packages/ui/src/tokens.css — shared Light glass, ink, filter, border, and scrim tokens.
  • apps/web/src/lib/themes.test.ts — token and composite contrast contracts.
  • apps/web/e2e/glass-audit.mjs — production pixel audit, Dark token snapshot, and screenshot matrix.
  • apps/web/e2e/theme-variants-506.mjs — seed theme preferences through the user-scoped test seam.
  • apps/web/src/lib/components/analytics/BklitTooltipMaterial.test.ts — follow the shared filter token alias.
  • docs/DESIGN.md — §34 material and contrast decisions.

Gates

bun run check passed. Output:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-588/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test passed after updating the old tooltip assertion to match the shared filter alias:

 Test Files  148 passed (148)
      Tests  1011 passed (1011)
   Start at  15:11:39
   Duration  88.90s (transform 50%, environment 19%, import 15%, tests 12%, setup 4%)

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

No Rust crate changed, so Rust gates did not apply. cargo clean completed: Removed 7362 files, 5.3GiB total. Web build output was removed.

Performance

Used the existing bench/theme-picker-506.mjs profile on local calternal-dev; docs/perf/baseline.json has no Theme menu profile. Before → after average p50/p95 was 201.4/627.5 → 170.5/259.1 ms at 390 px, and 42.5/217.9 → 16.5/18.9 ms at 1440 px. After average / burst CPU was 5.75 s / 9.17 s at 390 px and 0.84 s / 1.56 s at 1440 px; average / peak RSS was 496,578,560 / 511,393,792 bytes and 637,181,952 / 645,140,480 bytes. Host load differed between runs (before [15.55, 13.11, 17.21], after [4.34, 7.31, 9.42]), so the measurements are not a controlled performance comparison.

Known gap

Typing into Composer's Entry text triggers Svelte state_unsafe_mutation page errors at apps/web/src/lib/composer/Composer.svelte:166. This is outside the token-only change; the audit leaves that field empty and skips the disabled Send control. Evidence is recorded in the earlier issue comment.

Decisions not covered by DESIGN

  • Used relative OKLCH lightness transforms to preserve each light theme's hue. --ink-muted uses the Light glass muted token globally because transparent rows can reveal photo backgrounds without a glass class.
  • The composited-pixel matrix uses Chromium because Linux WebKit omits backdrop-filter pixels from screenshots; the general shared-glass sweep still checks both engines.
  • The merged origin/dev includes shared text-shadow: none behavior for glass surfaces. The Dark token snapshot is unchanged, but this shared no-halo behavior also applies to Dark.

All final web gates passed; issue left open for owner review.

## Complete — light glass readability (#588) **Branch:** `job/lightglass-588` **Base:** merged `origin/dev` once at `b561de501` **Head:** `947210529e0e6465f69ee1ca2268fb560ac16119` ### Built The shared Light token layer now uses a brighter 78% glass tint, 85% chrome, a white low-alpha scrim, stronger saturation, and a small brightness lift. Light ink tokens lower each theme's OKLCH lightness while preserving hue. The shared muted ink token also covers transparent file rows over photos. Cards use a top highlight and darker hairline. Dark token values remain unchanged. The production screenshot audit measures visible text and painted icon pixels on Settings Appearance, Settings Maintenance, Files, Calendar, and a sheet. It covers Paper, Catppuccin Latte, and Mono over each theme background and bright, busy, and dark photos at 390, 820, and 1440 px. It passed all 835 role checks at the required thresholds: 4.5:1 for body text, 3:1 for large text and UI icons. The computed Dark token snapshot passed. There are 195 before and 195 after captures; the after set also includes 15 Dark screenshots. ### Screenshot bundles Each bundle contains the listed theme's production screenshots at 390, 820, and 1440 px. - Before Paper: [download](https://git.kayg.org/attachments/9ec5b926-5507-4b60-ba40-c9048853eb27) - Before Catppuccin Latte: [download](https://git.kayg.org/attachments/1b211d40-f3ef-42f1-a2ef-dc32d9d7a8fd) - Before Mono: [download](https://git.kayg.org/attachments/e3577c5a-3464-4c59-9e44-7b5b2a51f9e8) - After Paper: [download](https://git.kayg.org/attachments/8b2b6222-217f-4e21-820e-9853f4504d60) - After Catppuccin Latte: [download](https://git.kayg.org/attachments/f05081bc-4efe-4bbd-bc76-25e62590baa4) - After Mono: [download](https://git.kayg.org/attachments/d48d3aa9-46fc-4808-a22f-7837b43b02d5) - After Dark: [download](https://git.kayg.org/attachments/d12e42dc-9375-4b6f-97ab-8bb15d0265f9) ### Files - `packages/ui/src/tokens.css` — shared Light glass, ink, filter, border, and scrim tokens. - `apps/web/src/lib/themes.test.ts` — token and composite contrast contracts. - `apps/web/e2e/glass-audit.mjs` — production pixel audit, Dark token snapshot, and screenshot matrix. - `apps/web/e2e/theme-variants-506.mjs` — seed theme preferences through the user-scoped test seam. - `apps/web/src/lib/components/analytics/BklitTooltipMaterial.test.ts` — follow the shared filter token alias. - `docs/DESIGN.md` — §34 material and contrast decisions. ### Gates `bun run check` passed. Output: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-588/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` passed after updating the old tooltip assertion to match the shared filter alias: ```text Test Files 148 passed (148) Tests 1011 passed (1011) Start at 15:11:39 Duration 88.90s (transform 50%, environment 19%, import 15%, tests 12%, setup 4%) Environment |component| jsdom was created 46 times · 94.48s total, 27% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` No Rust crate changed, so Rust gates did not apply. `cargo clean` completed: `Removed 7362 files, 5.3GiB total`. Web build output was removed. ### Performance Used the existing `bench/theme-picker-506.mjs` profile on local `calternal-dev`; `docs/perf/baseline.json` has no Theme menu profile. Before → after average p50/p95 was 201.4/627.5 → 170.5/259.1 ms at 390 px, and 42.5/217.9 → 16.5/18.9 ms at 1440 px. After average / burst CPU was 5.75 s / 9.17 s at 390 px and 0.84 s / 1.56 s at 1440 px; average / peak RSS was 496,578,560 / 511,393,792 bytes and 637,181,952 / 645,140,480 bytes. Host load differed between runs (before `[15.55, 13.11, 17.21]`, after `[4.34, 7.31, 9.42]`), so the measurements are not a controlled performance comparison. ### Known gap Typing into Composer's `Entry text` triggers Svelte `state_unsafe_mutation` page errors at `apps/web/src/lib/composer/Composer.svelte:166`. This is outside the token-only change; the audit leaves that field empty and skips the disabled Send control. Evidence is recorded in the earlier issue comment. ### Decisions not covered by DESIGN - Used relative OKLCH lightness transforms to preserve each light theme's hue. `--ink-muted` uses the Light glass muted token globally because transparent rows can reveal photo backgrounds without a glass class. - The composited-pixel matrix uses Chromium because Linux WebKit omits `backdrop-filter` pixels from screenshots; the general shared-glass sweep still checks both engines. - The merged `origin/dev` includes shared `text-shadow: none` behavior for glass surfaces. The Dark token snapshot is unchanged, but this shared no-halo behavior also applies to Dark. All final web gates passed; issue left open for owner review.
Author
Owner

Supplement: the 15 baseline Dark screenshots are attached as well: download. Together with the previously attached bundles, all 195 before and 195 after screenshots are available, including the Dark comparison at 390, 820, and 1440 px.

Supplement: the 15 baseline Dark screenshots are attached as well: [download](https://git.kayg.org/attachments/5b1a0684-76ce-49f6-b716-33ffd71349c3). Together with the previously attached bundles, all 195 before and 195 after screenshots are available, including the Dark comparison at 390, 820, and 1440 px.
Author
Owner

Starting #588 on branch job/lightglass-r2, based on c4a61e8cf0 (origin/dev). The repo contract and DESIGN §34/§35 glass rules are read. I am sweeping the existing production contrast audit against the requested Light backgrounds; Dark tokens will remain byte-identical.

Starting #588 on branch job/lightglass-r2, based on c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). The repo contract and DESIGN §34/§35 glass rules are read. I am sweeping the existing production contrast audit against the requested Light backgrounds; Dark tokens will remain byte-identical.
Author
Owner

Starting #588 on branch job/lightglass-r2, based at c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev at job start). I have read CLAUDE.md, CONTEXT.md, and the glass/appearance sections in docs/DESIGN.md. I am auditing the shared material paths and comparing the deployed dark surfaces before tuning the light alpha sweep.

Starting #588 on branch `job/lightglass-r2`, based at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (origin/dev at job start). I have read `CLAUDE.md`, `CONTEXT.md`, and the glass/appearance sections in `docs/DESIGN.md`. I am auditing the shared material paths and comparing the deployed dark surfaces before tuning the light alpha sweep.
Author
Owner

Production comparison finding: I built 687ff7031 and c4a61e8cf0 and ran the surface audit through the same server data and Chromium. Both Dark builds resolve the Settings photo sheet to blur(16px) saturate(1.5), 30% material, with a 0.053 backdrop variance ratio; the open theme menu captures are byte-identical. On the Settings photo capture, fixed RGB samples at (100,200), (300,300), (500,500) were identical: 48,52,64; 34,36,48; 26,30,40. The glass-related source diff in those commits is confined to tokens.css; the Sidebar, OverlaySurface, menus and cards do not add a second fill in that range. The broader current audit also passed the surface-only run; its default interaction run stops later in an unrelated Notes editor toolbar wait. I’m continuing with measured Light alpha sweeps and the shared token/guard changes.

Production comparison finding: I built 687ff7031 and c4a61e8cf090170f35b1bed3350d9de20c83ecd5 and ran the surface audit through the same server data and Chromium. Both Dark builds resolve the Settings photo sheet to blur(16px) saturate(1.5), 30% material, with a 0.053 backdrop variance ratio; the open theme menu captures are byte-identical. On the Settings photo capture, fixed RGB samples at (100,200), (300,300), (500,500) were identical: 48,52,64; 34,36,48; 26,30,40. The glass-related source diff in those commits is confined to tokens.css; the Sidebar, OverlaySurface, menus and cards do not add a second fill in that range. The broader current audit also passed the surface-only run; its default interaction run stops later in an unrelated Notes editor toolbar wait. I’m continuing with measured Light alpha sweeps and the shared token/guard changes.
Author
Owner

Light alpha sweep finding (real production SPA, Chromium 1440, Paper; Settings sheet/card/sidebar and menu, with solid white and near-black fills plus theme mesh and bright/busy/dark photos): the existing text/icon pixel audit passed every tested candidate. Material: 36/45/50/55/60/70/78% all PASS. Chrome: 36/45/50/55/60/70/85% all PASS. The Settings body minimum was 6.91:1 at material 55% on the dark photo; icons were at least 7.39:1, and the theme-menu body minimum was 8.27:1 at 36%. I am checking the white sheet scrim next, then will verify the selected material/chrome/scrim across all curated Light families.

Light alpha sweep finding (real production SPA, Chromium 1440, Paper; Settings sheet/card/sidebar and menu, with solid white and near-black fills plus theme mesh and bright/busy/dark photos): the existing text/icon pixel audit passed every tested candidate. Material: 36/45/50/55/60/70/78% all PASS. Chrome: 36/45/50/55/60/70/85% all PASS. The Settings body minimum was 6.91:1 at material 55% on the dark photo; icons were at least 7.39:1, and the theme-menu body minimum was 8.27:1 at 36%. I am checking the white sheet scrim next, then will verify the selected material/chrome/scrim across all curated Light families.
Author
Owner

The first full audit run found a stale inner-card expectation in auditExpandedSurfaces: it expected 92% (the old 78% Light sheet plus 14 points), while the current shared 36% material role rendered the card at 50%. This was an audit assertion mismatch, not a rendering failure. I changed the check to read the shared material alpha and add the explicit 14-point card step; the rerun is progressing through the Light and Dark surface checks.

The first full audit run found a stale inner-card expectation in `auditExpandedSurfaces`: it expected 92% (the old 78% Light sheet plus 14 points), while the current shared 36% material role rendered the card at 50%. This was an audit assertion mismatch, not a rendering failure. I changed the check to read the shared material alpha and add the explicit 14-point card step; the rerun is progressing through the Light and Dark surface checks.
Author
Owner

The all-theme sweep exposed a contrast-audit parsing defect for Light relative text roles. Chromium returned the computed color as oklch(0.408018 0.162302 354.823), while the probe treated its hue (355) as an RGB blue channel (foreground [0,0,355]). A Chromium reproduction rasterized that color to sRGB [134,8,78]; its contrast on the recorded [232,228,221] backdrop is 7.68:1. I corrected the probe to convert computed OKLCH to sRGB and clip out-of-range color(srgb) channels. The 4.5:1 and 3:1 thresholds are unchanged. A Bloom-only rerun passed all seven material candidates on all seven backgrounds.

The all-theme sweep exposed a contrast-audit parsing defect for Light relative text roles. Chromium returned the computed color as `oklch(0.408018 0.162302 354.823)`, while the probe treated its hue (`355`) as an RGB blue channel (`foreground [0,0,355]`). A Chromium reproduction rasterized that color to sRGB `[134,8,78]`; its contrast on the recorded `[232,228,221]` backdrop is 7.68:1. I corrected the probe to convert computed OKLCH to sRGB and clip out-of-range `color(srgb)` channels. The 4.5:1 and 3:1 thresholds are unchanged. A Bloom-only rerun passed all seven material candidates on all seven backgrounds.
Author
Owner

Resuming work on branch job/lightglass-r2 from origin/dev base c4a61e8cf0. Existing commits: 9c294e4fc, c1eccb30c, db69fb22a, da43b145b. One uncommitted extension remains in apps/web/e2e/theme-variants-506.mjs. I am checking the Dark production comparison and completing the audit/screenshots before final gates.

Resuming work on branch job/lightglass-r2 from origin/dev base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Existing commits: 9c294e4fc, c1eccb30c, db69fb22a, da43b145b. One uncommitted extension remains in apps/web/e2e/theme-variants-506.mjs. I am checking the Dark production comparison and completing the audit/screenshots before final gates.
Author
Owner

Measured finding: the Light material alpha at 0% fails the rendered audit. On the Meridian theme at 1440 px, body text on the Settings Appearance sheet measured 1.07:1 (target 4.5:1); the worst sample was at x=528, y=154.4 over the rendered note surface. The pixel-backed material sweep is continuing across the photo and fill backgrounds to find the lowest passing value.

Measured finding: the Light material alpha at 0% fails the rendered audit. On the Meridian theme at 1440 px, body text on the Settings Appearance sheet measured 1.07:1 (target 4.5:1); the worst sample was at x=528, y=154.4 over the rendered note surface. The pixel-backed material sweep is continuing across the photo and fill backgrounds to find the lowest passing value.
Author
Owner

Additional rendered contrast finding for #588: at 0% Light material alpha, Alucard over the busy photo fails the body-text threshold. Settings Appearance measured 2.34:1 at the worst sample (text RGB [67, 65, 59], backdrop RGB [69, 136, 53], point x=641.5,y=557.1; source p.note.svelte-asittu), below 4.5:1. The 0% candidate now has two failures; 20% and above continue to pass on completed theme/background cases. The all-theme sweep is still running.

Additional rendered contrast finding for #588: at 0% Light material alpha, Alucard over the busy photo fails the body-text threshold. Settings Appearance measured 2.34:1 at the worst sample (text RGB [67, 65, 59], backdrop RGB [69, 136, 53], point x=641.5,y=557.1; source p.note.svelte-asittu), below 4.5:1. The 0% candidate now has two failures; 20% and above continue to pass on completed theme/background cases. The all-theme sweep is still running.
Author
Owner

Static material evidence for #588 from rev-consistency (#427), origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5:

  • The main overlay filter is centralised in packages/ui/src/tokens.css:1184-1189. This review found no second authored overlay blur/saturation recipe in the app. Progressive edge filters are a different role; their duplicate recipe is now #799.
  • apps/web/src/lib/components/analytics/Dashboard.svelte:148-151 sets --glass-shadow: none on every .analytics-card to remove drop shadows. The shared light --glass-shadow at packages/ui/src/tokens.css:1042 includes both the drop shadow and the inner top highlight. This local override suppresses the new light highlight as well. The cards use glass-overlay; the shared material reads box-shadow: var(--glass-shadow) at lines 1180-1182. The comment still describes the earlier no-drop-shadow rule.
  • apps/web/src/lib/ai/TurnPanel.svelte:299-311 adds an opaque var(--surface) background and a local coloured shadow for its desktop dialog. Its comment says opacity is needed because only one live blur is allowed. DESIGN §34 now allows bounded glass in the shared top-level layer above the scrim, so that reason is stale. The sheet and desktop branches have different material rules.

Keep the overlay material fix in #588. Use a shared no-elevation material role if Analytics must stay flat, while retaining its light inner highlight. Verify TurnPanel through the shared surface before changing it. Check both surfaces over bright, busy and dark pictures in light/dark at 390/820/1440 px on a macOS-emulated browser. These are source findings; no rendered contrast claim is made. No duplicate issue was filed.

Static material evidence for #588 from rev-consistency (#427), `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`: - The main overlay filter is centralised in `packages/ui/src/tokens.css:1184-1189`. This review found no second authored overlay blur/saturation recipe in the app. Progressive edge filters are a different role; their duplicate recipe is now #799. - `apps/web/src/lib/components/analytics/Dashboard.svelte:148-151` sets `--glass-shadow: none` on every `.analytics-card` to remove drop shadows. The shared light `--glass-shadow` at `packages/ui/src/tokens.css:1042` includes both the drop shadow and the inner top highlight. This local override suppresses the new light highlight as well. The cards use `glass-overlay`; the shared material reads `box-shadow: var(--glass-shadow)` at lines 1180-1182. The comment still describes the earlier no-drop-shadow rule. - `apps/web/src/lib/ai/TurnPanel.svelte:299-311` adds an opaque `var(--surface)` background and a local coloured shadow for its desktop dialog. Its comment says opacity is needed because only one live blur is allowed. DESIGN §34 now allows bounded glass in the shared top-level layer above the scrim, so that reason is stale. The sheet and desktop branches have different material rules. Keep the overlay material fix in #588. Use a shared no-elevation material role if Analytics must stay flat, while retaining its light inner highlight. Verify TurnPanel through the shared surface before changing it. Check both surfaces over bright, busy and dark pictures in light/dark at 390/820/1440 px on a macOS-emulated browser. These are source findings; no rendered contrast claim is made. No duplicate issue was filed.
Author
Owner

Light material alpha sweep complete on the production SPA, macOS-platform Chromium at 1440 px. The audit sampled Settings Appearance (sheet, inner card, sidebar and visible chrome) and the theme dropdown over seven backgrounds for all 16 curated Light themes. At the selected 20% material alpha, the worst measured ratios across all samples were 5.77:1 body (Ayu Light, solid-dark), 7.43:1 large text (Ayu Light, solid-dark), and 6.52:1 icons (Ayu Light, solid-color). The thresholds are 4.5:1 for body and 3:1 for large text and icons.

Material alpha sweep (PASS/FAIL across all 16 themes):

Material Theme Solid light Solid dark Solid color Bright photo Busy photo Dark photo
0% FAIL (1) PASS PASS PASS PASS FAIL (1) PASS
20% PASS PASS PASS PASS PASS PASS PASS
30% PASS PASS PASS PASS PASS PASS PASS
36% PASS PASS PASS PASS PASS PASS PASS
45% PASS PASS PASS PASS PASS PASS PASS
50% PASS PASS PASS PASS PASS PASS PASS
55% PASS PASS PASS PASS PASS PASS PASS
60% PASS PASS PASS PASS PASS PASS PASS
70% PASS PASS PASS PASS PASS PASS PASS
78% PASS PASS PASS PASS PASS PASS PASS

At 0%, the two failures were Meridian on its theme background (1.07:1 body) and Alucard over the busy photo (2.34:1 body). Therefore 20% is the lowest passing candidate in the measured sweep. The Light text roles, blur and saturation remain unchanged; the next sweeps measure Light chrome and the white scrim.

Light material alpha sweep complete on the production SPA, macOS-platform Chromium at 1440 px. The audit sampled Settings Appearance (sheet, inner card, sidebar and visible chrome) and the theme dropdown over seven backgrounds for all 16 curated Light themes. At the selected 20% material alpha, the worst measured ratios across all samples were 5.77:1 body (Ayu Light, solid-dark), 7.43:1 large text (Ayu Light, solid-dark), and 6.52:1 icons (Ayu Light, solid-color). The thresholds are 4.5:1 for body and 3:1 for large text and icons. Material alpha sweep (PASS/FAIL across all 16 themes): | Material | Theme | Solid light | Solid dark | Solid color | Bright photo | Busy photo | Dark photo | |---:|---|---|---|---|---|---|---| | 0% | FAIL (1) | PASS | PASS | PASS | PASS | FAIL (1) | PASS | | 20% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 30% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 36% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 45% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 50% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 55% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 60% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 70% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | | 78% | PASS | PASS | PASS | PASS | PASS | PASS | PASS | At 0%, the two failures were Meridian on its theme background (1.07:1 body) and Alucard over the busy photo (2.34:1 body). Therefore 20% is the lowest passing candidate in the measured sweep. The Light text roles, blur and saturation remain unchanged; the next sweeps measure Light chrome and the white scrim.
Author
Owner

Source citation correction for my material review comment: the shared box-shadow: var(--glass-shadow) used by the Analytics card is at packages/ui/src/tokens.css:1142 (selector group includes .glass-card and .glass-overlay), not lines 1180-1182. The light highlight is at line 1042; Dashboard.svelte:151 still overrides the variable to none. The finding and review snapshot are unchanged.

Source citation correction for my material review comment: the shared `box-shadow: var(--glass-shadow)` used by the Analytics card is at `packages/ui/src/tokens.css:1142` (selector group includes `.glass-card` and `.glass-overlay`), not lines 1180-1182. The light highlight is at line 1042; `Dashboard.svelte:151` still overrides the variable to `none`. The finding and review snapshot are unchanged.
Author
Owner

Measurement status for the remaining Light roles: the fresh Chrome sweep did not start because the local media sandbox rejected setup at 4,379 user threads; its hard limit is 4,090 (too many local threads for the bounded media sandbox test: 4379). The earlier #588 sweep measured Paper Chrome candidates 36–85% across its background matrix; the completed all-16-theme material run also sampled the current 36% Chrome role on every background and passed its contrast audit. Keep Light Chrome at 36%, the lowest previously measured passing candidate.

Light scrim is 0%, the lowest possible alpha. The completed material sweep used that 0% scrim with the selected 20% material and passed body, large-text and icon thresholds across all seven backgrounds and all 16 Light themes. The final before/after captures will show the page content through the Settings sheet. A separate higher-alpha scrim sweep was not needed to establish that the minimum alpha passes.

Measurement status for the remaining Light roles: the fresh Chrome sweep did not start because the local media sandbox rejected setup at 4,379 user threads; its hard limit is 4,090 (`too many local threads for the bounded media sandbox test: 4379`). The earlier #588 sweep measured Paper Chrome candidates 36–85% across its background matrix; the completed all-16-theme material run also sampled the current 36% Chrome role on every background and passed its contrast audit. Keep Light Chrome at 36%, the lowest previously measured passing candidate. Light scrim is 0%, the lowest possible alpha. The completed material sweep used that 0% scrim with the selected 20% material and passed body, large-text and icon thresholds across all seven backgrounds and all 16 Light themes. The final before/after captures will show the page content through the Settings sheet. A separate higher-alpha scrim sweep was not needed to establish that the minimum alpha passes.
Author
Owner

Coordination for #799: job/lightglass-r2 currently has active glass-token edits in packages/ui/src/tokens.css and docs/DESIGN.md. The #799 change will preserve those material/token decisions and limit overlap in tokens.css to the obsolete .glass-progressive-blur > span family. I will keep the four direction masks in ProgressiveBlur.svelte and consolidate the approved literal filter recipe there, then reconcile both sides before final gates.

Coordination for #799: `job/lightglass-r2` currently has active glass-token edits in `packages/ui/src/tokens.css` and `docs/DESIGN.md`. The #799 change will preserve those material/token decisions and limit overlap in `tokens.css` to the obsolete `.glass-progressive-blur > span` family. I will keep the four direction masks in `ProgressiveBlur.svelte` and consolidate the approved literal filter recipe there, then reconcile both sides before final gates.
Author
Owner

Coordination from #718: overscroll work will read the existing shared --paper CSS token for html and the light/dark theme-color media pair. It will not add or change background tokens, so #588 remains the only token owner.

Coordination from #718: overscroll work will read the existing shared --paper CSS token for html and the light/dark theme-color media pair. It will not add or change background tokens, so #588 remains the only token owner.
Author
Owner

Finished: light glass tuning and shared glass roles

Head: e7854d9da (docs(ui): clarify derived glass fill). The branch also contains the measured alpha selection, shared-role refactor, guard, and review/profile harness updates. No push, deploy, or merge was done.

Changes

  • Set Light material to 20%, chrome to 36%, and sheet scrim to 0%. Kept the darker Light text roles, 16px blur, 1.8 saturation, brightness lift, and existing 14-point inner-card step.
  • Kept Dark material/chrome/scrim at 30%/70%/68%, with 16px blur and 1.5 saturation. The :root.is-dark token block is byte-identical to c4a61e8cf.
  • Centralized material/chrome/scrim alpha, blur, and saturation in the five Light and five Dark switchboard values in packages/ui/src/tokens.css. Moved surface recipes, progressive filters, scrim filters, and filter suspension rules there. Nested Settings cards derive from the shared material role.
  • Added check-glass-tokens.mjs to reject local glass alpha, translucent fills, and backdrop filters outside the shared token layer. Added the chosen roles to the existing contrast tests and glass profile.

Material alpha audit

This is the captured glass-audit.mjs table. Each cell represents 16 curated Light themes at 1440px; FAIL means at least one Settings sheet/card/sidebar, Tab Bar, or Theme menu sample missed its WCAG threshold.

value\ttheme\tsolid-light\tsolid-dark\tsolid-color\tbright\tbusy\tdark
0%\tFAIL (1)\tPASS\tPASS\tPASS\tPASS\tFAIL (1)\tPASS
20%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
30%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
36%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
45%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
50%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
55%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
60%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
70%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS
78%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS

At 0%, Meridian on the theme background measured 1.07:1 body contrast; Alucard on the busy photo measured 2.34:1. At 20%, the measured minima were 5.77:1 body, 7.43:1 large text, and 6.52:1 icons (thresholds: 4.5:1 body and 3:1 large text/icons).

Chrome was 36% during the full 16-theme/background matrix and passed. Earlier Paper chrome samples at 36–85% also passed. The separate Chrome candidate sweep was blocked by the local server thread guard, so 36% is a verified passing value, not a measured minimum. Scrim 0% was exercised during the complete matrix and is the least tint possible.

Gates (verbatim output excerpts)

bun run check:

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.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test exited 1:

Test Files  11 failed | 142 passed (153)
     Tests  19 failed | 1034 passed (1053)
   Start at  15:44:23
   Duration  833.52s (transform 68%, environment 12%, import 11%, tests 6%, setup 3%)

  Transform  |component| transforming modules took 2816.28s · 61% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

The changed theme contrast case timed out at 30s. Most other failures were timeouts; one search-provider failure cascaded after its first timeout. I did not change test expectations or rerun the suite.

bun run build exited 0; output ended with:

Wrote site to "build"
✔ done

git diff --check exited 0 with no output. cargo clean completed:

Removed 53823 files, 5.2GiB total

No Rust files changed, so Rust gates were not run. Web build output was removed.

Visual and performance evidence gaps

The production screenshot and profile server could not start: the bounded media sandbox rejected the host at 4,379 local threads (limit 4,090). The host later reported over 6,000 threads, so I did not retry or bypass the guard. No before/after screenshot set is attached. The requested Settings Appearance, Calendar sidebar, menu, and Tab Bar captures (1440px and 390px; Light on color and photo, Dark on photo) remain pending, including final-branch Dark pixel samples. Earlier 687ff7031 vs c4a61e8cf Dark menu pixel evidence and Settings samples are already in this issue; current-branch runtime pixels remain unverified. The audit/profile contexts now emulate macOS platform values, but the screenshots were not produced. The macOS VM remains offline.

The existing theme-picker profile now records material/chrome/scrim/blur/saturation roles and includes Dark desktop. It was not run, so there are no performance numbers to compare with docs/perf/baseline.json.

UX gaps closed / left

  • Closed: Light Settings and related glass surfaces now show the backdrop at the lowest passing material candidate; cards inside sheets use the shared fill recipe, and contrast remains above threshold in the audited matrix. The 14-point card step remains shared.
  • Left: screenshot-based visual review at the required device widths/backgrounds and current-branch Dark pixel comparison are pending because of the thread guard. Performance profile numbers are also pending. No interaction behavior changed in this visual-material job.

Decisions not specified in DESIGN

  • Chose 20% as the lowest passing tested Light material candidate; intermediate values between 0% and 20% were not swept.
  • Kept Light Chrome at 36% because it passed the current full background matrix and earlier Paper sweep. The blocked isolated sweep means lower Chrome values remain unmeasured.
  • Set Light sheet scrim to 0% to preserve page visibility; the full matrix passed with it.
## Finished: light glass tuning and shared glass roles Head: `e7854d9da` (`docs(ui): clarify derived glass fill`). The branch also contains the measured alpha selection, shared-role refactor, guard, and review/profile harness updates. No push, deploy, or merge was done. ### Changes - Set Light material to 20%, chrome to 36%, and sheet scrim to 0%. Kept the darker Light text roles, 16px blur, 1.8 saturation, brightness lift, and existing 14-point inner-card step. - Kept Dark material/chrome/scrim at 30%/70%/68%, with 16px blur and 1.5 saturation. The `:root.is-dark` token block is byte-identical to `c4a61e8cf`. - Centralized material/chrome/scrim alpha, blur, and saturation in the five Light and five Dark switchboard values in `packages/ui/src/tokens.css`. Moved surface recipes, progressive filters, scrim filters, and filter suspension rules there. Nested Settings cards derive from the shared material role. - Added `check-glass-tokens.mjs` to reject local glass alpha, translucent fills, and backdrop filters outside the shared token layer. Added the chosen roles to the existing contrast tests and glass profile. ### Material alpha audit This is the captured `glass-audit.mjs` table. Each cell represents 16 curated Light themes at 1440px; FAIL means at least one Settings sheet/card/sidebar, Tab Bar, or Theme menu sample missed its WCAG threshold. ``` value\ttheme\tsolid-light\tsolid-dark\tsolid-color\tbright\tbusy\tdark 0%\tFAIL (1)\tPASS\tPASS\tPASS\tPASS\tFAIL (1)\tPASS 20%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 30%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 36%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 45%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 50%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 55%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 60%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 70%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS 78%\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS\tPASS ``` At 0%, Meridian on the theme background measured 1.07:1 body contrast; Alucard on the busy photo measured 2.34:1. At 20%, the measured minima were 5.77:1 body, 7.43:1 large text, and 6.52:1 icons (thresholds: 4.5:1 body and 3:1 large text/icons). Chrome was 36% during the full 16-theme/background matrix and passed. Earlier Paper chrome samples at 36–85% also passed. The separate Chrome candidate sweep was blocked by the local server thread guard, so 36% is a verified passing value, not a measured minimum. Scrim 0% was exercised during the complete matrix and is the least tint possible. ### Gates (verbatim output excerpts) `bun run check`: ``` 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. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` exited 1: ``` Test Files 11 failed | 142 passed (153) Tests 19 failed | 1034 passed (1053) Start at 15:44:23 Duration 833.52s (transform 68%, environment 12%, import 11%, tests 6%, setup 3%) Transform |component| transforming modules took 2816.28s · 61% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` The changed theme contrast case timed out at 30s. Most other failures were timeouts; one search-provider failure cascaded after its first timeout. I did not change test expectations or rerun the suite. `bun run build` exited 0; output ended with: ``` Wrote site to "build" ✔ done ``` `git diff --check` exited 0 with no output. `cargo clean` completed: ``` Removed 53823 files, 5.2GiB total ``` No Rust files changed, so Rust gates were not run. Web build output was removed. ### Visual and performance evidence gaps The production screenshot and profile server could not start: the bounded media sandbox rejected the host at 4,379 local threads (limit 4,090). The host later reported over 6,000 threads, so I did not retry or bypass the guard. No before/after screenshot set is attached. The requested Settings Appearance, Calendar sidebar, menu, and Tab Bar captures (1440px and 390px; Light on color and photo, Dark on photo) remain pending, including final-branch Dark pixel samples. Earlier 687ff7031 vs c4a61e8cf Dark menu pixel evidence and Settings samples are already in this issue; current-branch runtime pixels remain unverified. The audit/profile contexts now emulate macOS platform values, but the screenshots were not produced. The macOS VM remains offline. The existing theme-picker profile now records material/chrome/scrim/blur/saturation roles and includes Dark desktop. It was not run, so there are no performance numbers to compare with `docs/perf/baseline.json`. ### UX gaps closed / left - Closed: Light Settings and related glass surfaces now show the backdrop at the lowest passing material candidate; cards inside sheets use the shared fill recipe, and contrast remains above threshold in the audited matrix. The 14-point card step remains shared. - Left: screenshot-based visual review at the required device widths/backgrounds and current-branch Dark pixel comparison are pending because of the thread guard. Performance profile numbers are also pending. No interaction behavior changed in this visual-material job. ### Decisions not specified in DESIGN - Chose 20% as the lowest passing tested Light material candidate; intermediate values between 0% and 20% were not swept. - Kept Light Chrome at 36% because it passed the current full background matrix and earlier Paper sweep. The blocked isolated sweep means lower Chrome values remain unmeasured. - Set Light sheet scrim to 0% to preserve page visibility; the full matrix passed with it.
Author
Owner

Starting round 3 in job/lightglass-r2, based on origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Current head: e7854d9da994b5a28345b08680b59ab847764b32.

I am tracing the pre-round-6 glass build, then I will capture the requested macOS-emulated before/after set and compare the rendered sheet luminance variance over one busy Picture background. I will check the real Safari Settings view on the Mac VM and rerun the web tests with worker and timeout overrides for this run only.

Starting round 3 in `job/lightglass-r2`, based on `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Current head: `e7854d9da994b5a28345b08680b59ab847764b32`. I am tracing the pre-round-6 glass build, then I will capture the requested macOS-emulated before/after set and compare the rendered sheet luminance variance over one busy Picture background. I will check the real Safari Settings view on the Mac VM and rerun the web tests with worker and timeout overrides for this run only.
Author
Owner

Finding for round 3: the checked-in overlay-photo-cc0.jpg is a broad, muted mountain-and-lake scene. It has large smooth regions and does not provide the requested busy field of strong colour edges. I generated a 1536×1024 flower-and-produce market photo for the real Picture-background audit; it is kept in ignored review artifacts and will not be committed.

Finding for round 3: the checked-in `overlay-photo-cc0.jpg` is a broad, muted mountain-and-lake scene. It has large smooth regions and does not provide the requested busy field of strong colour edges. I generated a 1536×1024 flower-and-produce market photo for the real Picture-background audit; it is kept in ignored review artifacts and will not be committed.
Author
Owner

Round 3 capture finding: the first targeted baseline attempt reached the real Calendar data, then a valid Files TUS upload returned HTTP 503 (the unchanged assertion expects 201), so it stopped before screenshots. A minimal authenticated TUS upload against the same prebuilt server returned 201. Its redacted server diagnostics showed SQLite pool waits up to 18.0 seconds. This points to load during the dense audit setup rather than a repeatable upload failure. I reduced only the Round 3 fixture to the one Event needed for the real Calendar route and will retry the baseline capture once.

Round 3 capture finding: the first targeted baseline attempt reached the real Calendar data, then a valid Files TUS upload returned HTTP 503 (the unchanged assertion expects 201), so it stopped before screenshots. A minimal authenticated TUS upload against the same prebuilt server returned 201. Its redacted server diagnostics showed SQLite pool waits up to 18.0 seconds. This points to load during the dense audit setup rather than a repeatable upload failure. I reduced only the Round 3 fixture to the one Event needed for the real Calendar route and will retry the baseline capture once.
Author
Owner

Round 3 follow-up: after reducing the Calendar seed, the baseline pass passed the valid Files upload but returned HTTP 503 when creating an empty folder used only by the full audit's context-menu checks. Round 3 does not open that menu or use the full audit's extra Photos and attachment fixtures, so I narrowed this mode to the real file, Picture background and Search Note that its five requested surfaces use. The existing assertions remain in the normal full audit. I am making one more targeted baseline attempt.

Round 3 follow-up: after reducing the Calendar seed, the baseline pass passed the valid Files upload but returned HTTP 503 when creating an empty folder used only by the full audit's context-menu checks. Round 3 does not open that menu or use the full audit's extra Photos and attachment fixtures, so I narrowed this mode to the real file, Picture background and Search Note that its five requested surfaces use. The existing assertions remain in the normal full audit. I am making one more targeted baseline attempt.
Author
Owner

Round 3 runner finding: after the fixture reduction, setup reached the isolated capture context but it was still at about:blank; the first relative Appearance API request could not resolve. I fixed the targeted path to navigate to the authenticated Calendar route before API setup and committed it as 1b8567d15. No screenshots were written in that attempt. I will use the corrected path for the next capture pass.

Round 3 runner finding: after the fixture reduction, setup reached the isolated capture context but it was still at `about:blank`; the first relative Appearance API request could not resolve. I fixed the targeted path to navigate to the authenticated Calendar route before API setup and committed it as 1b8567d15. No screenshots were written in that attempt. I will use the corrected path for the next capture pass.
Author
Owner

Round 3 finding: the first vivid market fixture was not too smooth. The real readability system set its Picture veil to 95% in Light and 81% in Dark (leastVeil, 24×24 image samples), before the scrim and glass were applied. The Settings sheet therefore showed little of the image even though the glass tokens matched their expected alpha. I changed the audit fixture to use separate high-key Light and low-key Dark generated Pictures. This keeps the production readability calculation active and isolates the actual glass contribution. The focused rerun is in progress; these are test-only changes.

Round 3 finding: the first vivid market fixture was not too smooth. The real readability system set its Picture veil to 95% in Light and 81% in Dark (`leastVeil`, 24×24 image samples), before the scrim and glass were applied. The Settings sheet therefore showed little of the image even though the glass tokens matched their expected alpha. I changed the audit fixture to use separate high-key Light and low-key Dark generated Pictures. This keeps the production readability calculation active and isolates the actual glass contribution. The focused rerun is in progress; these are test-only changes.
Author
Owner

Safari check finding for #588: the Mac rejected the lab TLS endpoint. The served leaf has SAN calternal.lab, is date-valid, and verifies against its local issuer, but Safari reported “This Connection Is Not Private”. The served root is calternal macinterop lab root (SHA-256 953354D84345EE89EE464A814C69E337922706AEFCF1849217117539A8ADAADD). The Mac System keychain lists calternal macverify root and calternal macdav-verify lab root, with different fingerprints; it has no matching root. I stopped without bypassing the certificate warning or changing Mac trust settings. The captured warning pages are not being attached as Safari product evidence. To complete the real Safari check, install the matching lab CA through the Mac System keychain authorization flow or provide a leaf/key signed by an already trusted lab CA.

Safari check finding for #588: the Mac rejected the lab TLS endpoint. The served leaf has SAN `calternal.lab`, is date-valid, and verifies against its local issuer, but Safari reported “This Connection Is Not Private”. The served root is `calternal macinterop lab root` (SHA-256 `953354D84345EE89EE464A814C69E337922706AEFCF1849217117539A8ADAADD`). The Mac System keychain lists `calternal macverify root` and `calternal macdav-verify lab root`, with different fingerprints; it has no matching root. I stopped without bypassing the certificate warning or changing Mac trust settings. The captured warning pages are not being attached as Safari product evidence. To complete the real Safari check, install the matching lab CA through the Mac System keychain authorization flow or provide a leaf/key signed by an already trusted lab CA.
Author
Owner

Forgejo #588 — Round 3 report

Branch: job/lightglass-r2
Head: 4bee5602231b26301b8c84e6a9b3b0bee8d9ddcc

What changed

The production glass appearance stayed intact. The audit now waits for the selected Picture identity before capturing a surface; this closes a race that showed the prior Light Picture in the 390 px Dark dropdown. It uses a separate generated, high-detail Picture for each scheme so the live contrast calculation does not apply an 81–95% veil to both themes. The audit continues to use the real API and production build. Playwright reports macOS platform and shortcut glyphs.

The initial “flat glass” result came from the readability veil over the first shared photo. With the scheme-specific photos, the app reported veil 0.75 for Light and 0 for Dark. The market tile pattern is visible through the Settings sheet in both themes.

Luminance variance

Variance uses linear luminance over the full Settings sheet crop. After is at least the pre-round-six Dark baseline in each theme and viewport:

View Before Light After Light Before Dark After Dark
1440 px 1366.13 1080.11 217.84 217.84
390 px 2269.53 1322.35 304.15 304.15

Dark Settings captures are pixel-identical before and after at both widths (mean absolute RGB delta 0.000; zero channels differ by more than 1). No Dark restoration was needed. The Dark dropdown differs in only 0.005% of channels at 390 px and 0.001% at 1440 px. Across the other Dark surfaces, the maximum mean RGB delta is 1.195/255.

Pair attachments

Each PNG places Before on the left and After on the right, with Light/Dark rows at 1440/390 px.

macOS Safari

The real Safari check is blocked by the Mac trust store. Safari showed “This Connection Is Not Private” for calternal.lab. The served leaf passed local chain verification, but its calternal macinterop lab root fingerprint is absent from the Mac System keychain. I stopped without bypassing the warning or changing Mac trust settings. No failed warning-page captures are attached. To finish this check, install the matching lab CA through the Mac System keychain authorization flow or supply a leaf/key signed by an already trusted CA.

UX gaps

  • Closed: the stale background race in the Dark phone dropdown; both theme captures now wait for the selected image identity.
  • Left: real Safari Settings captures, pending a trusted matching lab certificate on the Mac.

Files

Tracked branch files: docs/DESIGN.md; apps/web/e2e/glass-audit.mjs, harness.mjs, theme-variants-506.mjs; apps/web/package.json; apps/web/scripts/check-glass-tokens.mjs; apps/web/src/calternal-app.css, lib/appearance/readability.ts, lib/components/analytics/BklitTooltipMaterial.test.ts, lib/photos/TimelineScrubber.svelte, lib/styles/scrim.css, lib/themeColor.ts, routes/settings/apps/CalendarFeedsGroup.svelte; packages/ui/src/components/OverlaySurface.svelte, ProgressiveBlur.svelte, packages/ui/src/themes.css, tokens.css.

Gates

bun run check (exit 0), output verbatim:

$ node scripts/check-user-storage.mjs && node scripts/check-glass-tokens.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
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.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test --maxWorkers=2 --testTimeout=60000 (exit 0), summary output verbatim:

$ vitest run "--maxWorkers=2" "--testTimeout=60000"

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  20:19:16
   Duration  233.54s (transform 36%, import 25%, environment 20%, tests 15%, setup 4%)

The test log also contains 44 Not implemented: Window's scrollTo() method warnings and one Could not parse CSS stylesheet warning. No test failed. No Rust crate changed, so Rust crate gates were not run. cargo clean output: Removed 0 files.

Decisions

  • Use separate Light and Dark Pictures for this audit because the production readability veil on a shared vivid image hid the glass material.
  • Keep the pre-round-six Dark appearance because rendered Settings pixels and Dark variance match the baseline.
# Forgejo #588 — Round 3 report **Branch:** `job/lightglass-r2` **Head:** `4bee5602231b26301b8c84e6a9b3b0bee8d9ddcc` ## What changed The production glass appearance stayed intact. The audit now waits for the selected Picture identity before capturing a surface; this closes a race that showed the prior Light Picture in the 390 px Dark dropdown. It uses a separate generated, high-detail Picture for each scheme so the live contrast calculation does not apply an 81–95% veil to both themes. The audit continues to use the real API and production build. Playwright reports macOS platform and shortcut glyphs. The initial “flat glass” result came from the readability veil over the first shared photo. With the scheme-specific photos, the app reported veil `0.75` for Light and `0` for Dark. The market tile pattern is visible through the Settings sheet in both themes. ## Luminance variance Variance uses linear luminance over the full Settings sheet crop. After is at least the pre-round-six Dark baseline in each theme and viewport: | View | Before Light | After Light | Before Dark | After Dark | |---|---:|---:|---:|---:| | 1440 px | 1366.13 | 1080.11 | 217.84 | 217.84 | | 390 px | 2269.53 | 1322.35 | 304.15 | 304.15 | Dark Settings captures are pixel-identical before and after at both widths (mean absolute RGB delta `0.000`; zero channels differ by more than 1). No Dark restoration was needed. The Dark dropdown differs in only 0.005% of channels at 390 px and 0.001% at 1440 px. Across the other Dark surfaces, the maximum mean RGB delta is `1.195/255`. ## Pair attachments Each PNG places Before on the left and After on the right, with Light/Dark rows at 1440/390 px. - [Settings](https://git.kayg.org/attachments/87c28bbe-7ee7-433e-9257-b4aa282a5e30) - [Search palette](https://git.kayg.org/attachments/5a3ff520-8b5a-4a44-8a44-39f393750287) - [Dropdown](https://git.kayg.org/attachments/f2b4812e-35e7-41c3-9573-0ca3f97a9be6) - [Inspector](https://git.kayg.org/attachments/9eb004ed-be4b-4d46-8b10-ca6546a9c062) - [Toast](https://git.kayg.org/attachments/1ff22e99-bd12-4c17-8342-e48d9f3d8d37) ## macOS Safari The real Safari check is blocked by the Mac trust store. Safari showed “This Connection Is Not Private” for `calternal.lab`. The served leaf passed local chain verification, but its `calternal macinterop lab root` fingerprint is absent from the Mac System keychain. I stopped without bypassing the warning or changing Mac trust settings. No failed warning-page captures are attached. To finish this check, install the matching lab CA through the Mac System keychain authorization flow or supply a leaf/key signed by an already trusted CA. ## UX gaps - **Closed:** the stale background race in the Dark phone dropdown; both theme captures now wait for the selected image identity. - **Left:** real Safari Settings captures, pending a trusted matching lab certificate on the Mac. ## Files Tracked branch files: `docs/DESIGN.md`; `apps/web/e2e/glass-audit.mjs`, `harness.mjs`, `theme-variants-506.mjs`; `apps/web/package.json`; `apps/web/scripts/check-glass-tokens.mjs`; `apps/web/src/calternal-app.css`, `lib/appearance/readability.ts`, `lib/components/analytics/BklitTooltipMaterial.test.ts`, `lib/photos/TimelineScrubber.svelte`, `lib/styles/scrim.css`, `lib/themeColor.ts`, `routes/settings/apps/CalendarFeedsGroup.svelte`; `packages/ui/src/components/OverlaySurface.svelte`, `ProgressiveBlur.svelte`, `packages/ui/src/themes.css`, `tokens.css`. ## Gates `bun run check` (exit 0), output verbatim: ```text $ node scripts/check-user-storage.mjs && node scripts/check-glass-tokens.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json 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. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test --maxWorkers=2 --testTimeout=60000` (exit 0), summary output verbatim: ```text $ vitest run "--maxWorkers=2" "--testTimeout=60000" RUN v5.0.1 /home/kayg/Developer/calternal-wt/lightglass-r2/apps/web Test Files 153 passed (153) Tests 1053 passed (1053) Start at 20:19:16 Duration 233.54s (transform 36%, import 25%, environment 20%, tests 15%, setup 4%) ``` The test log also contains 44 `Not implemented: Window's scrollTo() method` warnings and one `Could not parse CSS stylesheet` warning. No test failed. No Rust crate changed, so Rust crate gates were not run. `cargo clean` output: `Removed 0 files`. ## Decisions - Use separate Light and Dark Pictures for this audit because the production readability veil on a shared vivid image hid the glass material. - Keep the pre-round-six Dark appearance because rendered Settings pixels and Dark variance match the baseline.
Author
Owner

Starting the reuse screenshot and glass-coordination round on job/reuse. The branch was at b5544daedac6d8027a323167d9b188a5262d6eb5 before this round; origin/dev was c4faf184df726a9375ae0c13bdfb6018ac2cf57e and is now merged as ad8d300ce3a8f50712a28dc08d435fd64df88ff0.

Forgejo reports kayg/calternal#0 as not found, so I cannot post to that task identifier. For the shared glass overlap, I recommend merging job/reuse first, then job/lightglass-r2: the latter can put the shared progressive filter recipe in tokens.css after the reuse consolidation, with one stack and no duplicate component filters.

Starting the reuse screenshot and glass-coordination round on `job/reuse`. The branch was at `b5544daedac6d8027a323167d9b188a5262d6eb5` before this round; `origin/dev` was `c4faf184df726a9375ae0c13bdfb6018ac2cf57e` and is now merged as `ad8d300ce3a8f50712a28dc08d435fd64df88ff0`. Forgejo reports `kayg/calternal#0` as not found, so I cannot post to that task identifier. For the shared glass overlap, I recommend merging `job/reuse` first, then `job/lightglass-r2`: the latter can put the shared progressive filter recipe in `tokens.css` after the reuse consolidation, with one stack and no duplicate component filters.
Author
Owner

Evidence: the local server build failed while sccache wrote dependency metadata under a temporary directory in another worktree (advfind-664/target/tmp), which had already been removed. I will retry this local build with the cache wrapper disabled. No source change is needed for this infrastructure race.

Evidence: the local server build failed while `sccache` wrote dependency metadata under a temporary directory in another worktree (`advfind-664/target/tmp`), which had already been removed. I will retry this local build with the cache wrapper disabled. No source change is needed for this infrastructure race.
Author
Owner

Finished on job/reuse.

Head: cc43ce2a337678ae5601bbc675c571b6dcb77370.

Built

  • Centralized the ProgressiveBlur filter recipe in packages/ui/src/tokens.css. ProgressiveBlur.svelte owns its masks, and the overlay still uses the shared scrim.
  • Captured the before and after production surfaces at 390, 820 and 1440 px, in light and dark, with macOS emulation. Each archive has 48 captures: Tab Bar, Search actions, Settings sheet edge, Composer mode, Files size and download, Admin storage, and Mail attachment size.
  • Fixed the capture runner to select Files rows on touch, use the visible “Show info” action, and dismiss nested Composer overlays before opening the next surface.

Files changed for this work include packages/ui/src/tokens.css, packages/ui/src/components/ProgressiveBlur.svelte, packages/ui/src/components/OverlaySurface.svelte, packages/ui/src/themes.css, docs/DESIGN.md, Files/Downloads/Mail UI code and tests, and apps/web/e2e/harness.mjs, search.mjs, files.mjs, maintenance-links.mjs, glass-audit.mjs, and reuse-screenshots.mjs.

Screenshots

Both archives are attached to issue #588:

Verification

bun run check output:

$ node scripts/check-user-storage.mjs && node scripts/check-glass-tokens.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
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.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/reuse/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Focused Vitest output (17 changed files):


 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/reuse/apps/web

Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method

 Test Files  17 passed (17)
      Tests  203 passed (203)
   Start at  00:31:37
   Duration  151.48s (transform 57%, environment 20%, import 12%, tests 8%, setup 3%)

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

The local screenshot server build completed:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 56m 44s

cargo clean output:

     Removed 7238 files, 4.6GiB total

Rust format, Clippy and test gates were not run because this work changed no Rust source. node --check apps/web/e2e/reuse-screenshots.mjs and git diff --check passed with no output. The full Web suite is deferred to the merge round.

UX gaps closed

The phone and tablet evidence flow now uses the touch selection gesture before opening Files Info. It closes the nested Composer mode menu and Composer before moving to the next surface. No product UI behavior changed in this screenshot round.

UX gaps left

No interaction gap remained in the capture flow. The screenshots are attached for the orchestrator’s visual review.

Known gaps and merge-round work

  • kayg/calternal#0 was not found by Forgejo, so this final report and the archives are on #588, where the glass coordination took place.
  • The full Web suite was not run in this job, per the 2026-10-02 verification policy. Merge round: from apps/web, run bun run test --maxWorkers=2 and confirm the combined branch passes.

Decisions

No new UI decision outside docs/DESIGN.md and issue #588 was made. The existing merge-order note stands: merge job/reuse first, then job/lightglass-r2, so both branches keep one token-owned blur definition.

Finished on `job/reuse`. Head: `cc43ce2a337678ae5601bbc675c571b6dcb77370`. ## Built - Centralized the ProgressiveBlur filter recipe in `packages/ui/src/tokens.css`. `ProgressiveBlur.svelte` owns its masks, and the overlay still uses the shared scrim. - Captured the before and after production surfaces at 390, 820 and 1440 px, in light and dark, with macOS emulation. Each archive has 48 captures: Tab Bar, Search actions, Settings sheet edge, Composer mode, Files size and download, Admin storage, and Mail attachment size. - Fixed the capture runner to select Files rows on touch, use the visible “Show info” action, and dismiss nested Composer overlays before opening the next surface. Files changed for this work include `packages/ui/src/tokens.css`, `packages/ui/src/components/ProgressiveBlur.svelte`, `packages/ui/src/components/OverlaySurface.svelte`, `packages/ui/src/themes.css`, `docs/DESIGN.md`, Files/Downloads/Mail UI code and tests, and `apps/web/e2e/harness.mjs`, `search.mjs`, `files.mjs`, `maintenance-links.mjs`, `glass-audit.mjs`, and `reuse-screenshots.mjs`. ## Screenshots Both archives are attached to issue #588: - [Before matrix (48 captures)](https://git.kayg.org/attachments/38980e6e-4cce-463e-8a66-8b9a4af6decc) - [After matrix (48 captures)](https://git.kayg.org/attachments/9ff89885-0968-40de-9d94-c1d84f6460b9) ## Verification `bun run check` output: ```text $ node scripts/check-user-storage.mjs && node scripts/check-glass-tokens.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json 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. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/reuse/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Focused Vitest output (17 changed files): ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/reuse/apps/web Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Test Files 17 passed (17) Tests 203 passed (203) Start at 00:31:37 Duration 151.48s (transform 57%, environment 20%, import 12%, tests 8%, setup 3%) Environment |component| jsdom was created 4 times · 54.15s total, 26% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` The local screenshot server build completed: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 56m 44s ``` `cargo clean` output: ```text Removed 7238 files, 4.6GiB total ``` Rust format, Clippy and test gates were not run because this work changed no Rust source. `node --check apps/web/e2e/reuse-screenshots.mjs` and `git diff --check` passed with no output. The full Web suite is deferred to the merge round. ## UX gaps closed The phone and tablet evidence flow now uses the touch selection gesture before opening Files Info. It closes the nested Composer mode menu and Composer before moving to the next surface. No product UI behavior changed in this screenshot round. ## UX gaps left No interaction gap remained in the capture flow. The screenshots are attached for the orchestrator’s visual review. ## Known gaps and merge-round work - `kayg/calternal#0` was not found by Forgejo, so this final report and the archives are on #588, where the glass coordination took place. - The full Web suite was not run in this job, per the 2026-10-02 verification policy. Merge round: from `apps/web`, run `bun run test --maxWorkers=2` and confirm the combined branch passes. ## Decisions No new UI decision outside `docs/DESIGN.md` and issue #588 was made. The existing merge-order note stands: merge `job/reuse` first, then `job/lightglass-r2`, so both branches keep one token-owned blur definition.
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#588
No description provided.