Files

42 KiB
Raw Permalink Blame History

P4 CSS foundation: evidence log

Status

Maintenance session (2026-10-07 11:40, platform maintainer): REQ Coordinator->P4 (PLAN §1.18 A14, round R2) done in docs/phase2/DESIGN2.md: §3.2 (quiet legend's dim band on window routes, R2-1; the ornament hugs its members and the laser Sort & Filter pill is the toolbar's trailing group, R2-2, also §7.3), §3.7 (menu box y 108–616, Cancel 60 with its own E-MENU line, count table 6 / 7–10 / 11–14, value menus follow it, E-MENU ≥ 320 / ≥ 280, R2-5; alert and sheet placement on the glass, outside click and default focus as recorded deviations, R2-6), §3.8 (source card 0 with a glow, R2-3; dimming from SG-5's measurement, R2-4); §0.3 register now A1–A14. FD-7 re-run below. No token or CSS change (R2-7 needed none). Open REQs to P4: none.

Complete, R1 review fixed (2026-10-07 10:08 local, session 3). Every review finding is fixed and re-tested live, or answered (table "R1 review fixes" below); every acceptance test FD-1 to FD-7 still passes; all REQs to P4 answered (P9's DESIGN2 one this session). M1 and M3 were reached in sessions 1 and 2.

  • M1 (G-PERF) fixed: the hook rules' subjects are generated flat host lists (:where(<host>)::after|::before, one entry per host, per family and per kind) instead of *::after / *::before. Scroll A/B on /library/tab/AllGames: listed hooks −1.5 % fps against no hooks, the old universal subject −27 %. The generator is python docs/phase2/fontkit.py --hooks (any package may run it after adding a hook selector; --hooks-check exits 1 when stale); the gate probe is python docs/phase2/fontkit.py --perf-js (contract §2). The whole theme is still ~25 % under stock on that route, which is not P4's share (noted for the coordinator, C2c, P10).
  • M2 fixed: the FocusRing plate paints nothing when the focused element is an --lgs-ill host (generated :has(.gpfocus:where(<hosts' whole selectors>))); live on Settings › Notifications: no plate on C4a's segments, label #0d0e12. A dark crescent at the selected segment's ends is C4a's fill clip (REQ P4->C4a).
  • Minors: m1 lobe arc on row/nav (ratio 0.89 → 0.00), m2 lens contract corrected, m3 SteamVR ring tokens ring-free + documented, m4 --lgs-t-* = P5 tokens (G-MOTION 0 findings), m5 mode-keyed in-transitions, m6 HC dial + --lgs-mat-window-tint (REQ C1a), m7 P-03 measured and tuned (--lgs-row-hover-add .06, --lgs-nav-hover-spot .16; all +12.8..+23.1 L), m8 window rim dims under a scrim only, m9 DESIGN2, m10 kit, m11, m12 (REQ V1), m13 scratch deleted. Plus: nav hover was invisible in High Contrast (now +19.6 L).
  • Open, outside P4: REQ P4->C1a (HC window tint), P4->C4a (segment fill shape), P4->C5b and P4->C4b (SteamVR hover-rim restatements), P4->P10 (check-theme warns on stale hook lists), P4->V1 (input-mode default). A next P4 session: answer new REQs; rerun fontkit.py --hooks-check and --perf-js if areas add many hook selectors.

Session 2 summary (still true unless the R1 table says otherwise):

Complete (2026-10-07 06:13 local, session 2). M1 reached in session 1; M3 at 05:40; every acceptance test FD-1 to FD-7 now PASS on the live device. P4 is CSS-only (T1, no runtime module), so M2/M3 mean tokens, font, materials and states complete and verified live in both input modes (pad: G-FOCUS; laser: a stale .gpfocus and Steam's FocusRing paint nothing).

  • Tokens theme/00-tokens.nowrap.css: D2 §16 + PLAN §1, focus add .32 final (P4-D8), selection recipe, edge lobe, T1 materials --lgs-mat-*, --lgs-white-glow 22/8/.55 with parts, --lgs-text-on-color, --lgs-fill-danger, --lgs-shadow-5mm; state numbers and hook switches registered (--lgs-edge-z); Phase 1 aliases kept (motion aliases follow P5).
  • Font theme/01-font.nowrap.css (generated by docs/phase2/fontkit.py), live in every Steam window and, through P8's binary FontFace load, in the SteamVR pages.
  • Materials theme/03-material.css: --lgs-edge hook (conic + lobe, rim ×1.5 on focus, --lgs-edge-z, the window's rim above its header), .lgs-glass[data-lgs-mat], plate, media dim, scroll edges, High Contrast stroke.
  • States theme/04-states.css: input-mode keyed state numbers, --lgs-ill hook (raised, recessed, row, nav, white, card), FocusRing light plate (beats Phase 1's ring; glow 22/6/.44; opacity: 0 in laser mode), disabled focus, Reduce Motion, High Contrast (outer stroke on white fills).
  • SteamVR theme/vr/00-vr-tokens.css; kit parity tool (fontkit.py --parity) and kit/theme parity; DESIGN2 A1–A13 (§0.3).

Tests: FD-1, FD-2 (Steam windows and SteamVR pages), FD-3, FD-4 (window, CONFIRM alert and fixture, with P4's material applied in a test step: C1a and C1c have not adopted the hooks yet), FD-5 (C4a's live primitives on /zoo/buttons and /zoo/toggles), FD-6, FD-7: all PASS. All REQs to P4 answered (C1a ×2, C1c, C3a, C4a, P3, P5).

Open at the end of session 2 (since resolved): C1a removed vr/10-systemui.css §0, C4a removed Phase 1's §10 ring, and C1a/C1c set --lgs-edge (FD-4 integrated passes, review R1). Still open: the check-circle bloom needs P3's .lgs-ring-check tag (only for check circles without a hook). Areas' mockups that use the kit's is-focus / lgk-glass / lgk-menu / liquid glass render differently since this session (contract §8): re-render before cmp. A next session only answers new REQs.

Decisions taken without the user (PLAN §1.17 rule: safe default, noted)

# Decision Why
P4-D1 Phase 1 names that Phase 2 redefines with the same meaning (radii, colours, text levels, tints, selected fill, scrim, well shadow, separator .08) take the Phase 2 value; every other Phase 1 name keeps its Phase 1 value D2 §16 says "values replace the Phase 1 tokens of the same name"; these changes only move looks toward the target
P4-D2 --lgs-glass-bg, --lgs-thick-bg, --lgs-glass-blur, --lgs-thick-blur keep Phase 1 values; Phase 2 material values live under new names --lgs-mat-<m>-bg/-blur/-shade (D2 §16 amended) Phase 1 files use them inside linear-gradient(x, x) and background-color, where D2's new gradient values would be invalid and make glass transparent
P4-D3 T1 window tint keeps the Phase 1 dial formula .60 + dial × .24 (.72 at the default dial, range .60–.84); PLAN §1.2's ".74" is inside the range .72 is the value Phase 1's CONTRAST audits passed with; a dial of .58 gives .74
P4-D4 Panel tint .67 + dial × .22 (= .78 at the default dial, PLAN §1.6) Matches the plan's default exactly
P4-D5 The illumination layer uses normal alpha compositing, not plus-lighter VP D-2's "+48 L for .28" is normal compositing; a blend mode on every hooked control would force isolated groups (GPU cost)
P4-D6 Hooks (--lgs-ill, --lgs-edge, --lgs-scroll-band) are CSS style queries on the host's ::before / ::after. R1: the subject is a generated host list, not * (see the R1 table: the universal subject doubled the style-recalc cost of the poster grid) Areas cannot add classes to Steam nodes in T1; CSS has no mixins, and a custom-property "recipe" cannot carry per-element state (a var() inside a custom property is substituted where it is declared). Proven on CEF 126 (SQ-1); cost measured in R1
P4-D7 The kit/theme token parity check (FD-3) is a mode of P4's own tool: python docs/phase2/fontkit.py --parity PLAN §2.6 gives P4 no tools/ path; fontkit.py is P4's tool file. It compares every kit --lg-X with its theme twin --lgs-X (00-tokens + P5's 02-motion) after normalising number spelling
P4-D8 Final --lgs-focus-add = .32 (top of .28–.32). First set at .28 (every criterion passed on P4's fixture, min P-14 +45.7 L on L 85 glass), then raised after REQ C1a: on the tab bar over glass at L ~95 (inside D2's 55–110 band) .28 gives P-14 +35.2 and P-15 +7.6 (FAIL), .30 +39.1 / +11.6 (FAIL), .32 +42.5 / +15.1 (PASS) The focus look must pass G-FOCUS on every glass in the band, and focus vs the .18 selection only has the headroom above the glass; .32 is the plan's ceiling. Glare is bounded: the add is uniform over controls, never over large areas
P4-D9 --lgs-white-glow = 22/8/.55 (C3a's measured value) instead of PLAN §1.4's 18/2/.30 PLAN's literal measures +4 to +12 L in the P-16 band (C1c, C3a); 22/8/.55 measures +25 to +49 L live (fixture)
P4-D10 The kit's illumination layer switched from plus-lighter to normal compositing and the theme's values; kit edges use the E3 lobe Parity with the theme (FD-3) and DESIGN2 A4/A5. Mockups that use is-focus render brighter (.32 instead of .14) and need a re-render before cmp (changelog in contracts/tokens.md §8)
P4-D11 The FocusRing plate is written at html.lgs-on.lgs-on and its glow is the white-glow token at 3/4 spread and 4/5 alpha (22/6/.44), not CTL §4.5's 18/2/.22 Phase 1's §10 ring (C4a's file, later in the bundle, same specificity) still drew the ring; CTL's glow measured +8 L in P-16's band on a white radio segment, 22/6/.44 measures +34 L
P4-D12 --lgs-edge-z (registered, non-inherited) controls the edge layer's stacking; window defaults to 20000 The window's content (header, art) covered a ::before edge (FD-4 first try); Phase 1's window rim used z 20000 for the same reason; non-inherited so a window's value never reaches the controls inside
P4-D13 --lgs-focus-arc stays .55 (PLAN §1.4's "arc ×1.5" of the .22 rest arc would be .33); on row and nav kinds the arc is drawn as the E3 lobe (26 % of the width, fading both ways) instead of a uniform inset line The review's finding (m1) is the uniform full-width line on wide rows; the lobe removes it. On buttons and circles (≤ ~300 px) the .55 arc reads as the control's lit lip, and five area files use the token directly in looks their reviews accepted (31-cc, 32-launcher, 41-home, vr/10-systemui); changing the value would move their looks without a measured reason
P4-D14 Any package may run python docs/phase2/fontkit.py --hooks (contract §2): it rewrites only the generated lgs-hosts blocks of 03-material.css / 04-states.css, deterministically from theme/ A new hook selector paints nothing until it is listed; waiting for a P4 session would block areas. The blocks are machine output (no hand edits), so a run by another package cannot conflict with P4's work. --hooks-check (REQ P4->P10 for check-theme) reports staleness

Evidence

Steam build 11094443 (Chrome/126.0.6478.183 in the popups).

Test Command Result Date
SQ-1 style queries on pseudo-elements in CEF 126 python glass.py js "<scratch probe: a test <style> + 3 off-screen divs in main, removed in finally>" p4a:"Q"/0.5 | p4c:none/1 | p4f:"Q"/1 | p4b:none/1: ::after queries its originating element; a non-inherited registered property reaches the pseudo through inherit; children are not matched. linear() and plus-lighter supported. Probe nodes removed (verified) 2026-10-07
M1 load python glass.py check-theme; python glass.py sync; python glass.py reload check-theme … PASS; unresolved: [], ambiguous: [], no skipped files 2026-10-07
M1 CSSOM python glass.py js "<count CSSContainerRule / CSSPropertyRule in the lgs sheet of main; read --lgs-* on .gpfocus>" 16 container rules, 9 @property rules parsed; .gpfocus element has --lgs-focus: 1, --lgs-lift: 1; --lgs-focus-add: .28 2026-10-07
REQ glow + Reduce Motion aliases python glass.py check-theme; python glass.py sync; python glass.py reload check-theme … PASS; unresolved: [], ambiguous: [] 2026-10-07 05:10
FD-2 Steam windows python glass.py js "$(cat fd2.js)" (scratch probe: per window document.fonts.load/check('500 24px "LGS Inter"'), FontFace status, canvas widths, computed font-family of the first visible label and of a form control, size of the 01-font.nowrap.css section in the live <style>) PASS on main, bar, barpopup, keyboard, notifications, plus tooltip, frame.menu, floatingfooter, volumelevel: face loaded, check true, no load error (so no CSP block); Latin width 276.4 (Inter) vs 289.7 (Motiva): Inter is used; labels and INPUT compute "LGS Inter", "Motiva Sans", Arial, sans-serif; CJK 日本語のテキスト中文한국어 measures 306.2 with our stack = 306.2 with Steam's stack (falls through to Steam's CJK font; tofu would be 152.6); font section in the bundle 163,530 bytes (main bundle 481,450) 2026-10-07 05:12
FD-2 perf python glass.py js "$(cat fd2perf.js)": on /library/home, L.perf('main', 4000) scrolling the largest scroller, alternating with a temporary override style that sets Steam's stack on everything (removed in finally, verified) font 87.5 / 89.0 / 90.0 fps, no font 89.0 / 86.8 / 87.4 fps; median frame 11.1 ms in all six; mean +1.3 % with the font: PASS (within 5 %) 2026-10-07 05:14
FD-2 SteamVR pages the same probe --in vr:systemui, --in vr:controllerbindingui; CSP read with fetch(location.href) BLOCKED (P8): face status error (NetworkError): the pages' CSP default-src 'self' 'unsafe-eval' blocks font-src data:. Text falls back to Steam's stack (nothing breaks). A binary new FontFace(…, bytes) of the same data loads (loaded, width 276.4; probe face deleted again). REQ P4->P8 2026-10-07 05:13
G-FOCUS (PLAN §1.4) on the P4 state fixture python glass.py shot main p2_p4_states_<backing> --route /library/home --back --mode pad --settle 1.5 --pre "(window.__P4FX = {base: 'rgb(B B B)'}, <fixture>)" for B = 134 (bright lounge), 51 (dim room), 250 (glass at L 85); the fixture is a .lgs-glass[data-lgs-mat=window] slab with 20 hooked controls (--lgs-ill kinds, real .gpfocus class, inline --lgs-hover/--lgs-press), removed by its own 20 s timer; luma pairs with P10's tools/p2/edge_profile.py (lum, region_mean, 601 weights, pills inset 6 shot px) PASS on all three at focus add .28: P-14 raised +54.5 / +60.8 / +45.7, recessed +71.7 / +76.8 / +64.0, row pill +66.5 / +74.1 / +55.6; P-15 focused vs selected nav +30.7 / +33.7 / +25.8, selected+focused vs selected +56.3 / +62.6 / +47.2; selected vs hovered +22.3 / +25.4 / +18.6; disabled+focus +19.2 / +21.5 / +16.0; P-16 glow band 8–16 CSS px: white +29.6 / +33.0 / +24.8, green +29.9 / +33.3 / +25.0. Shots p2_p4_states_bright.png, _dim.png, _l85.png 2026-10-07 05:21
G-FOCUS first frame the same with {pause0: true} (every lgs-focus-in paused at t = 0), p2_p4_states_bright_t0.png focus − rest at t0 = +33.4 L vs +54.5 final: 61.3 % ≥ 60 % PASS 2026-10-07 05:25
FD-1 python glass.py status PASS: unresolved: [], ambiguous: [] on the Steam bundle (481,450 bytes) and both SteamVR pages (277,901 bytes) 2026-10-07 05:26
FD-3 kit parity python docs/phase2/fontkit.py --parity PASS: kit 163 tokens, theme 312; 160 shared, 160 equal, 0 different; kit-only --lg-fill-focus, --lg-fill-hover, --lg-fill-selected (derived from parity tokens). Kit re-rendered: python tools/mockshot.py docs/phase2/mockups/_example.html shots/p2_example.png (A1 values: glass 656, ornament 628, title x 100, search 520, no mic; menu +10 mm, 40 px header; laser hover only) viewed 2026-10-07 05:24
FD-7 DESIGN2 review grep -o "\[A<n>[],]" docs/phase2/DESIGN2.md per n; read §0.3 PASS: §0.3 register lists A1–A13 with sections and sources; in-place tags A1 ×9, A2 ×4, A3 ×13, A4 ×6, A5 ×11, A6 ×3, A7 ×2, A8 ×3, A9 ×1 (new §10.5), A10 ×1, A11 ×1, A12 ×3, A13 ×3; §16 annotated with the as-built token differences (P4-D2) 2026-10-07 05:22
FD-7 (R2, A14) grep -o "\[A<n>[],]" docs/phase2/DESIGN2.md | wc -l for n = 1..14; read §0.3, §3.2, §3.7, §3.8, §7.3 against PLAN §1.19 R2-1 to R2-6 PASS: §0.3 lists A1–A14 with sections and sources; in-place tags A1 11, A2 4, A3 14, A4 6, A5 13, A6 3, A7 2, A8 3, A9 1, A10 1, A11 1, A12 3, A13 3, A14 11; no stale text left (grep for "56 px quiet", "6–7", "≥ 8 (single", "hidden real panel", "520 px box", "+15 if it was the focused": 0 hits) 2026-10-07 11:40
FD-4 window python glass.py shot main p2_p4_edge_window --route /library/home --back --settle 1.2 --pre "<edgewin.js: a 9 s test <style> in main: %{BasicUiRoot} { --lgs-edge: window; box-shadow: var(--lgs-mat-window-shade) } and its Phase 1 ::after rim off>"; python glass.py edge shots/p2_p4_edge_window.png 0 120 1800 PASS with P4's window edge: segments 38 → 75 (peak left of centre: the key arc + lobe) → −37, ratio 0.00 (≤ 0.35). Stock today (p2_p4_edge_window_stock.png, C1a's Phase 1 rim): ratio 0.87 FAIL (a uniform top line). First try showed the edge hidden under the header: fixed by --lgs-edge-z (window default 20000, above the header's 6000; contract §2) 2026-10-07 05:30
FD-4 CONFIRM alert python glass.py shot main p2_p4_edge_alert --route /library/home --back --settle 1.0 --pre "<inventory shell §0.2 CONFIRM (no-action lab dialog, closes itself after 4.5 s) + a 6 s test style: .GenericConfirmDialog { --lgs-edge: thick; background: var(--lgs-mat-thick-bg); backdrop-filter: var(--lgs-mat-thick-blur); box-shadow: var(--lgs-mat-thick-shade), var(--lgs-shadow-10mm) } !important>"; python glass.py edge shots/p2_p4_edge_alert.png 405 535 1385 PASS with P4's thick material: segments 44 → 97 → −2, ratio 0.00. With Phase 1's rim still winning (first try): ratio 0.98 FAIL, so C1c's alert rewrite must drop --lgs-glass-rim. Viewed next to shots/p2_window-nav_alert.png: same arc-and-lobe edge, no outline; the OK button still shows Phase 1's focus ring (C4a/C1c) 2026-10-07 05:32
FD-4 fixture slab python glass.py edge shots/p2_p4_states_bright.png 84 200 1700 PASS ratio 0.02 (.lgs-glass[data-lgs-mat=window]) 2026-10-07 05:21
FD-6 Reduce Motion python glass.py js "<fixture + fd6 probe, fixture removed at the end>" --mode pad --media reduce PASS: matchMedia(reduce) true; --lgs-press-grow 0, --lgs-press-max 1, --lgs-card-lift / --lgs-disc-lift 1; light transitions 150 ms linear (press out 90 ms); lgs-focus-in 150 ms (P5: opacity only); Phase 1 aliases --lgs-t-fast/-t/-t-slow 150 / 150 / 180 ms (was 1 ms): fades only 2026-10-07 05:27
FD-6 High Contrast the same with --media contrast; python glass.py shot main p2_p4_fd6_contrast --route /library/home --back --mode pad --media contrast --pre "<fixture>" PASS: slab rgba(11,13,16,.94), no backdrop-filter, no sheen; edge ::before = 2 px solid white .70 (the one allowed stroke) on the slab and on hooked controls; focus = white .30 + the 2 px stroke; no spot. First shot showed focus invisible on white and coloured fills (an inner white stroke on white): fixed with an outer stroke behind a 3 px dark gap (04-states §4), re-shot and viewed 2026-10-07 05:30
FD-5 pre-check on Steam's /zoo/buttons python glass.py shot main p2_p4_zoo_buttons_focus --route /zoo/buttons --back --mode pad --settle 0.8 --pre "<zoo.js: 9 s test style .DialogButton { --lgs-ill: raised; raised fill }, real gamepad focus with L.gpTake>" (and _disfocus on the disabled button) Hook works under Steam's own focus: the focused button has .gpfocus, --lgs-focus: 1, lit ::after; focus vs rest +97.0 L, disabled+focus +68.3 L (both inflated by Phase 1's focus ring, which still wins: REQ P4->C4a). The real FD-5 waits for C4a's primitives 2026-10-07 05:28
Laser mode keying (VP P-01, P-02) python glass.py js "<fixture; read --lgs-focus/--lgs-lift/--lgs-hover and ::after on 'focus', 'selfoc', 'hover', 'rest'; fixture removed>" --mode laser PASS: html lgs-input-laser; elements carrying a stale .gpfocus get --lgs-focus: 0, --lgs-lift: 0, ::after uniform 0 and no glow; --lgs-hover: 1 lights the .08 layer. In pad mode the same .gpfocus gives --lgs-focus: 1 (G-FOCUS rows above) 2026-10-07 05:34
FocusRing plate live (CTL §4.5, P-17, P-16) python glass.py shot main p2_p4_focusring_settings --route /settings/notifications --back --mode pad --settle 0.5 --pre "(window.__P4RING = {idx: 0}, <ring2.js: L.gpTake on the first radio segment, read the visible %{FocusRing}>)" and the same with idx: 1 (p2_p4_focusring_rest.png: the ring on the neighbouring segment, segment 0 at rest); band above and below segment 0 with tools/p2/edge_profile.py First run: the plate's fill and radius applied but Phase 1's outline: 2px solid … !important (10-primitives §10, same specificity, later file) still drew a ring, and CTL's 18/2/.22 glow gave only +8.3 L in the P-16 band on the white "Always" segment. Fixed: plate rules at html.lgs-on.lgs-on, glow 22/6/.44 from the white-glow token. Now PASS: outline transparent, animations 0, radius min(24px, 50%), fill .12 + .16 spot; P-16 band +34.3 L (8–16 CSS px), +53.2 (8–16 shot px); no ring (viewed). A game page and /zoo/* had no visible ring (p2_p4_focusring_pad.png) 2026-10-07 05:44
G-FOCUS at the final .32 (after REQ C1a) the same fixture shots p2_p4_states32_bright.png, _dim.png, _l85.png, _bright_t0.png (theme synced and reloaded with --lgs-focus-add: .32) PASS on all three: P-14 raised +61.2 / +68.2 / +51.3, recessed +80.1 / +86.0 / +71.7, row pill +74.2 / +82.6 / +62.0; P-15 focused vs selected nav +38.6 / +42.4 / +32.3, selected+focused vs selected +62.7 / +69.6 / +52.5; selected vs hovered +22.3 / +25.4 / +18.6; disabled+focus +19.2 / +21.5 / +16.0; P-16 band (8–16 CSS px) white +29.6 / +33.0 / +24.8, green +29.9 / +33.3 / +25.0; first frame 37.4 / 61.2 = 61 % 2026-10-07 05:37
FocusRing in laser mode python glass.py js "(window.__P4RING = {idx: 0}, <ring2.js>)" --mode laser PASS: the ring Steam still draws for its focus has computed opacity: 0 under html.lgs-input-laser (CTL §4.5, VP P-02) 2026-10-07 05:44
FD-2 SteamVR pages (after P8's binary FontFace load) python glass.py js "$(cat fd2.js)" --in vr:systemui and --in vr:controllerbindingui PASS: face loaded, check true, no load error; Latin 276.4 (Inter, = Steam windows); CJK 306.2 = Steam's stack (falls through, not tofu). The page's <style> is now 114,154 bytes: P8's core moves the 161,948-byte base64 out of the CSS into a binary FontFace (font section 1,197 bytes) 2026-10-07 06:11
FD-5 /zoo/buttons with C4a's live primitives (since 05:48) python glass.py shot main p2_p4_fd5_buttons_b<t> --route /zoo/buttons --back --mode pad --settle 0.8 --pre "(window.__P4ZOO = {sel: 'button.DialogButton', target: t}, <zoo2.js: L.gpTake, no test style>)" for t = 0 and 3; same-element pairs (pill, inset 6) PASS: P-14 button 0 +85.3 L, button 3 +85.8 L (focus at --lgs-focus-add .32, --lgs-ill: raised, no ring, outline none); the single-control row carries the focus (+105–110 L, CTL C-D14). A first try caught the page mid-repaint (black background) and was re-shot 2026-10-07 06:08
FD-5 /zoo/toggles the same with sel: '%{*GamepadDialogContent>Toggle}', t = 0 (on switch) and 2 (disabled) PASS: the on switch is a coloured fill, so its cue is the outer glow: band +97 L (8–16 CSS px, row lit too); disabled + focus +26.4 L (≥ 10). P-14 on the green fill itself +9 L, as designed (P-16 applies to white and coloured fills) 2026-10-07 06:10

R1 review fixes (session 3, 2026-10-07 08:26–)

Review: wp/P4-review-R1.md. Steam build 11094443. Every live step through python glass.py … (lab lock); the device was shared with ~20 agents (load average 5.3–5.5, another package's native session on part of the time), so absolute frame rates are lower and noisier than the review's; every perf number below is a paired, interleaved A/B inside one locked step, never stock-then-themed minutes apart.

Finding Fix (files) Test (command) Result Time
M1 universal hook subjects cost the grid ~20 % Every hook rule's subject is a generated flat host list: one :where(<host>)::before/::after entry per host, per family and per kind, written by python docs/phase2/fontkit.py --hooks (03-material.css §1/§3, 04-states.css §2; the in-transitions keep no list, they match only hovered/focused/pressed elements). Hosts = the rightmost compound of every theme selector that sets --lgs-ill/-edge/-scroll-band to a kind (pseudo-classes dropped, a superset), the whole selector when that compound has no class/id/attribute/token or only .Panel/.Focusable. :where() keeps the old (0,1,2) specificity 1. Diagnosis, recalc microbenchmark (forced style recalc of the 800-node grid subtree, CSSOM delete/insert of P4's rules, sheet digest equal after): python glass.py js "<perfab3.js>" before the fix: universal 122–158 ms, rules removed 73–77, a class-bucketed subject that matches nothing 71–76 (so the cost is the universal subject, not the queries) 08:45
2. Which list form: same probe, variants nested & under one :where(<list>) +29 % (Blink files a nested & rule in the universal bucket), nested plain list +32 %, flat entries +8 %, flat without the 9 %{*A>B} token entries ≈ 0 % (a multi-class :is() is unbucketed); 300 :where(.x)::after entries cost +1 % against 300 plain ones and :is(.a,.b) +25 % (bucketing of single-argument :where() confirmed) 09:35
3. G-PERF gate, scroll A/B (L.perf-style rAF scroll of the grid, 2.5 s per run, rounds alternate order): python glass.py js "(window.__P4AB = {route: '/library/tab/AllGames', mode: 'scroll', ms: 2500, rounds: 2}, $(python docs/phase2/fontkit.py --perf-js))" ×2 shipped hooks 51.2 / 47.4 fps, hooks removed 52.0 / 48.1, old universal subject 37.2 / 35.3: listed −1.5 %, universal −27 % → P4's share PASS (≤ 5 %); digest equal 09:43
4. Whole theme vs stock: python glass.py perf main --route /library/tab/AllGames --seconds 4 stock 83.4 fps / p95 22.1 / 4 long; themed 61.1 / 23.6 / 20 long (native session of another package running). The remaining gap is not P4's (step 3); noted for the coordinator / C2c (library) and P10 09:26
5. Hooks still paint: python glass.py focus main --route /zoo/buttons --mode pad --pairs <btn0 focus vs btn3 focus> P-14 button 0 +89.0 L (flat lists; +85.2 with the interim nested form; review +85.3) 09:53
5b. The FocusRing rule's :has() (new for M2), same A/B with only that rule removed with 50.5 fps, without 47.8 (noise): no measurable cost 09:58
5c. Size P4's hook rules in the live sheet: 23.9 KB (flat lists) + 11.0 KB (FocusRing host test), per window 09:45
6. Lists stay current: python docs/phase2/fontkit.py --hooks-check exit 0; it caught two staleness events during the session (areas added edge hosts: 28 → 35 → 38) and --hooks fixed them; REQ P4->P10 asks check-theme to warn 09:45
M2 FocusRing plate framed C4a's capsule segments 04-states.css §3: generated html.lgs-on.lgs-on:has(.gpfocus:where(<--lgs-ill hosts' whole selectors>)) %{FocusRing} { background: none; box-shadow: none } (0,4,1, also beats the High Contrast plate); radius var(--lgs-ring-r, min(var(--lgs-r-row), 50%)); contract §3.4 amended python glass.py shot main p2_p4r1_ring_<i> --route /settings/notifications --mode pad --settle 0.8 --pre "(window.__P4RI = i, <ringpre.js: scroll, L.gpTakeEl(segment i), read the ring and the segment>)" for i = 0 ("Always", selected) and 1 ring: background none, box-shadow none on both; the segment keeps only C4a's own glow (22/8/.55, one layer); label rgb(13,14,18) (was rgb(65,66,69) darkest); no grey frame, no 3 px dark band from the plate. Viewed next to p2_controls_states.png "On + focus": the white capsule with one soft glow. A dark crescent remains at each end inside the halo: C4a's fill is background-clip: content-box inset 20 px while its light is inset 2 px (REQ P4->C4a) 09:30
m1 full-width focus arc on wide rows row and nav kinds draw the arc as the E3 lobe (radial-gradient(50% 3px at 26% 0, …), alpha --lgs-focus-arc), not the uniform inset line (P4-D13); kit mirrors it python glass.py shot main p2_p4r1_zoo_tog1_pad --route /zoo/toggles --mode pad --pre "<togpre.js: L.gpTakeEl(toggle 1)>"; python glass.py edge shots/p2_p4r1_zoo_tog1_pad.png 282 460 1850 segments 15.5 → 33.9 → 29.6 → … → −13.7, ratio 0.00 PASS (review 0.89 FAIL on p2_p4rev_zoo_tog1_pad.png); viewed: a key-light glint at the left third instead of a lip along the row 09:28
m2 contract promised .lgs-lens contract §6 corrected: the class interface is not built; how to ask for it — — 09:12
m3 SteamVR pages: no hooks, ring tokens vr/00-vr-tokens.css: --lgs-vr-focus-ring = focus add as an inset layer + lit top arc + 32 px glow; --lgs-vr-hover-rim = lit top arc only (no closed ring); contract §5 documents that the hooks are not bundled into SteamVR pages; REQ P4->C5b, P4->C4b for their local restatements python glass.py js "<read --lgs-vr-focus-ring/-hover-rim>" --in vr:systemui both resolve on vr:systemui (html.lgs-on, scale 1.45, rgb(255 255 255 / .32) inset layer, arc .55); status: unresolved: [] on the SteamVR pages 09:58
m4 Phase 1 durations not tokens --lgs-t-fast/-t/-t-slow = --lgs-d-interactive / -hover-in / -fade (210/294/441 ms; Reduce Motion follows P5) python glass.py gates main --route /settings/notifications --only motion --mode pad --pre "<focus radio 1>"; python glass.py js "<read the aliases>" G-MOTION PASS, 0 findings (review: one non-token 140 ms); aliases 210ms / 294ms / 441ms 09:26
m5 unscoped in-transition rule :hover only when not pad, .gpfocus/.gpfocuswithin only when not laser (&:not(.lgs-input-pad) :hover::after, …) read in the live sheet scoped 10:00
m6 High Contrast window .84; dial order html.lgs-on.lgs-on { --lgs-dial: 1 } in the HC block (beats the bundler's remembered dial); new --lgs-mat-window-tint (.94 in HC); REQ P4->C1a to use it python glass.py js "<read dial, tints>" --media contrast --lgs-dial 1, --lgs-mat-window-tint rgb(11 13 16 / .94); C1a's --lgs-c1a-tint still .60 + 1 × .24 = .84 until REQ C1a 09:26
m7 P-03 laser hover never measured Measured on a P4 fixture (a window slab over a flat room of L 134 or 51, data-lgs-ill raised / row / nav controls at rest and with --lgs-hover: 1, laser mode, self-removing). First reading: button +20.5 / +22.8, row +24.5 / +27.4 (over 25 in the dim room), nav +9.8 (under 10 in the bright room) / +10.9. Tuned: new tokens --lgs-row-hover-add .06 and --lgs-nav-hover-spot .16 (kit twins; parity PASS) `python glass.py shot main p2_p4r1_hover_<bright dim> --mode laser --pre "(window.__P4HFX = {base: 'rgb(B B B)'}, <hoverfx.js>)"; python glass.py focus --png … --pair btn=pill:660,210,990,300:pill:240,210,570,300:10 --pair row=… --pair nav=…` after tuning: bright room: button +20.5, row +20.8, nav +12.8; dim room: button +22.8, row +23.1, nav +14.2: all inside +10..+25 PASS (p2_p4r1_hover_bright.png, _dim.png, viewed: the spot reads as light at the pointer, no rim). The review's +25.7 / +35.6 were over the transparent window with alpha dropped (black), outside the room band
m8 window rim above the scrim window edge takes --lgs-edge-dim: .35 while a scrim shows: &:is(:has(.ModalOverlayContent.inactive), :has(.ModalOverlayContent.active):not(:has(.ModalOverlayContent.active .BasicUIContextMenu))) :where(<window hosts>)::before (Steam keeps an empty, invisible .ModalOverlayBackground at all times, so a plain :has() of it was wrong: caught live, rim dimmed with no modal) python glass.py js "<modalprobe.js: rim before / during the lab CONFIRM / after>"; shots p2_p4r1_edge_window, p2_p4r1_edge_alert on /library/tab/AllGames --lgs-edge-dim none → .35 during → none after; rim (max of the top 4 rows, x 300–1600) − interior (rows 14–19) under the CONFIRM scrim: 47.6 L now against 70.3 L in the review's alert shot (p2_p4rev_edge_alert.png, same interior 25.8) and 67.1 L on the unscrimmed window (p2_p4rev_edge_window.png): the rim now dims with the window (×0.68) instead of standing out more. p2_p4r1_edge_alert.png viewed (my own window shot caught a transparent frame, alpha 0, and is not used) 09:50
m9 DESIGN2 contradicts A5 §7.6 (nav rows: spot-only hover, focus .32 + lobe), §7.8 (sheen: normal compositing), §10.1 (host lists, plate off) grep done 09:15
m10 kit recipes kit E3 bottom stop .20 → .16; focus rim = theme E3 ×1.5 (conic + lobe, same stops); "+ .28" → "+ .32"; row/tab focus arc as the lobe; P-03 hover twins python docs/phase2/fontkit.py --parity; python tools/mockshot.py docs/phase2/mockups/_example.html shots/p2_example.png (viewed) parity PASS (168 shared, 0 different) 10:00
m11 contract disabled selector §3.1 → %{*GamepadDialogContent>Disabled} — — 09:12
m12 shipped default without input-mode classes contract §3.1 "Runtime off" paragraph; REQ P4->V1 for defaults.json — recorded 09:20
m13 session-1 scratch on the Frame rm -rf /tmp/lgs/p4 (frame_ssh) ls /tmp/lgs/p4 gone 08:46
REQ P9->P4 DESIGN2 §11: menu slab materializes in place, CSS clip-path morph, no glassd morph / overshoot / neck; "morph k" dropped — answered in wp/P9.md 09:15
High Contrast after the restructure (FD-6) the HC variants now live in the flat lists python glass.py shot main p2_p4r1_fd6_contrast --mode pad --media contrast --pre "<hoverfx.js over L 51>" slab rgb(11 13 16 / .94) with the 2 px white .70 stroke, no spot (viewed); a focused /zoo/buttons button in High Contrast (python glass.py js "<hcfocus.js>" --mode pad --media contrast): ::after box-shadow is the inset 0 0 0 2px white stroke form, its edge ::before and the window's are the 2 px solid stroke. First shot showed no laser hover on nav rows in High Contrast (spot-only hover, spots are 0 there; also true before R1): fixed, nav takes the uniform add under prefers-contrast: more. Re-shot: hover button +16.9, row +14.3, nav +19.6 (was 0) 10:05
Device left probe globals __P4* deleted in SharedJSContext, fixture removed (timer + check), no modal open, route back to /library/home, theme on (enabled: true, unresolved: []); no flags set; native mode was another package's session (lgs-native on <html> during part of the session), never entered by P4; Frame scratch none python glass.py js "<cleanup + check>"; python glass.py status clean 10:10

Requests

  • REQ P4->C4a: selected radio segment (%{Group>Button}%{Group>Active}, theme/10-primitives.css §7): the white fill is background-clip: content-box inside padding: 2px 20px, so it is inset 20 px at the ends with ~10 px corners, while the light layer and its --lgs-ill: white glow follow the element (--lgs-ill-inset: 2px, radius 28). Result on Settings › Notifications with focus on "Always" (P4 R1 shot shots/p2_p4r1_ring_0.png, crop at x 430–520, y 742: track 29 L for 27 px between the white 242 and the glow): a dark crescent at each end inside the halo, and a white pill with square-ish ends instead of the mockup's capsule ("On + focus", p2_controls_states.png). P4's FocusRing plate no longer paints there (R1 M2). Please make the fill and the light the same shape: e.g. fill the padding box inset 2 px all round (border: 2px solid transparent; background-clip: padding-box; padding: 0 18px) so the white capsule meets its glow, or give --lgs-ill-inset the fill's inset.

  • REQ P4->C1a: High Contrast window tint (review R1 m6). theme/20-shell.css paints %{BasicUiRoot} with --lgs-c1a-tint: rgb(20 22 30 / calc(.60 + var(--lgs-dial) * .24)), which is .84 in High Contrast (live probe in the R1 review), while FD-6 asks for opaque glass (.94). P4 added --lgs-mat-window-tint (same formula; rgb(11 13 16 / .94) under prefers-contrast: more). Please set --lgs-c1a-tint: var(--lgs-mat-window-tint) (or restate .94 in your own High Contrast block). P4's High Contrast --lgs-dial: 1 now beats the bundler's remembered dial (html.lgs-on.lgs-on).

    • C1a answer (2026-10-07, R2 fix pass): done: --lgs-c1a-tint: var(--lgs-mat-window-tint) (High Contrast .94).
  • REQ P4->C5b: theme/vr/40-bindings.css restates --lgs-vr-hover-rim at the binding page's scale as the Phase 1 closed inner ring (inset 0 0 0 calc(1.5px * 1.875) var(--lgs-control-outline)). Since R1 (m3) P4's token is ring-free: the lit top arc only, inset 0 calc(1.5px * S) calc(1.5px * S) calc(-1px * S) rgb(255 255 255 / .45) with S your scale (1.875). Please restate it in that form (or drop your restatement and set --lgs-vr-scale). --lgs-vr-focus-ring (used in vr/20-nowplaying.css) is now the focus add as an inset layer + arc + glow, no ring; re-shoot Now Playing focus against your mockup.

  • REQ P4->C4b: theme/vr/50-keyboard.css restates --lgs-vr-hover-rim as the Phase 1 closed ring (inset 0 0 0 calc(1.5px * var(--lgs-vr-kb-scale)) var(--lgs-control-outline)). P4's token is ring-free since R1 (m3): please restate as inset 0 calc(1.5px * var(--lgs-vr-kb-scale)) calc(1.5px * var(--lgs-vr-kb-scale)) calc(-1px * var(--lgs-vr-kb-scale)) rgb(255 255 255 / .45).

  • REQ P4->P10: check-theme: also run python docs/phase2/fontkit.py --hooks-check (exit 1 = a theme rule sets --lgs-ill, --lgs-edge or --lgs-scroll-band on a selector the generated host lists in 03-material.css / 04-states.css do not cover yet) and print its message as a warning (not a failure). Why: since R1 M1 the hooks list their hosts instead of *::after (G-PERF), so a new hook selector paints nothing until python docs/phase2/fontkit.py --hooks is run; contract tokens.md §2 lets any package run it.

    • P10 (2026-10-07 session 4): done: check-theme runs fontkit.py --hooks-check --json (60 s cap) and prints a WARN hook host lists are stale (<lists>) … run python docs/phase2/fontkit.py --hooks line (also with --quiet; JSON warnings), never a failure; exit codes other than 0/1 become a note. Tested: the tree today → PASS, no warning; a scratch copy with an extra --lgs-edge: panel rule → WARN (edge ::before, edge:panel ::before), still PASS, exit 0. contracts/lab.md §3.
  • REQ P4->V1: defaults (review R1 m12). With wp.p3 off (today's shipped default) no window has lgs-input-*, so P4's states run in "runtime off" mode: a stale .gpfocus lights under the laser and a parked :hover lights in pad mode (VP P-02, P-09). Laser/pad keying needs P3's input mode on; please weigh this when you set device/defaults.json (contract tokens.md §3.1 "Runtime off").

  • REQ P4->P8: the UI font does not load in SteamVR pages. Their CSP is default-src 'self' 'unsafe-eval' (no font-src, measured on vr:systemui with fetch(location.href)), so the data: @font-face in theme/01-font.nowrap.css ends in NetworkError (document.fonts status error) on vr:systemui and vr:controllerbindingui. The same bytes load as a binary FontFace (no fetch, CSP does not apply): new FontFace('LGS Inter', bytes.buffer, {weight: '100 900'}) → loaded, width 276.4 = Steam windows. Please make device/lgs_vr_core.js do this in apply() for every @font-face in payload.css whose src is url(data:font/woff2;base64,…): parse font-family, font-weight, font-style, unicode-range (→ unicodeRange) and font-display (→ display), atob the base64 into a Uint8Array, document.fonts.add(ff), keep the list, and document.fonts.delete(ff) each one in disable() (G-REMOVE). Once per payload version, not on every re-check. This closes FD-2 on vr:systemui.

    • P8 (2026-10-07 06:20): done in device/lgs_vr_core.js: every @font-face with a data:font/woff2 src leaves the page CSS and becomes a binary FontFace (family, weight, style, stretch, unicode-range, display), built once per payload version, kept in document.fonts by apply(), deleted by disable(); __LGS_VR.status() lists fonts / fontErrors. Live on vr:systemui, vr:controllerbindingui, vr:keyboard: LGS Inter 100 900 loaded, document.fonts.check true, body family "LGS Inter", …, canvas width of a 24 px test string 296.8 = the Steam main window's 296.8; the page CSS shrank 272 KB → 114 KB (no data: font left).
  • REQ P4->C1a: theme/vr/00-vr-tokens.css now holds the SteamVR shared values of theme/vr/10-systemui.css §0 (same names, same values). Please delete §0 from vr/10-systemui.css (PLAN §6 two-step move). C1a answer (2026-10-07): accepted, not done yet: this session is M0 (documents and mockups only, by the coordinator's instruction). C1a deletes §0 at the start of its first M2 session, after a vr:systemui check that vr/00-vr-tokens.css loads before vr/10-systemui.css and every --lgs-vr-* still resolves. Done (C1a, 2026-10-07 session 3): §0 deleted from vr/10-systemui.css after a vr:systemui check: one copy of the block in the live bundle (yours, before 10-systemui), every --lgs-vr-* resolves (--lgs-vr-scale 1.45, -r-window, -ring-w, -focus-ring, -grabber*, -hit-inset, -hover-*, -solid-bg).

  • REQ P4->C4a: theme/04-states.css §3 restyles %{FocusRing} as CTL §4.5's light plate (input-mode keyed). When you rewrite theme/10-primitives.css, drop its section 10 (FocusRing ring + glow). (Update 05:44: P4's plate now wins by specificity, html.lgs-on.lgs-on, verified on Settings › Notifications, so §10 is dead code until you remove it.) Also measured on /zoo/buttons (2026-10-07, pad mode): a focused DialogButton still gets Phase 1's --lgs-focus-ring box-shadow (inset 2 px white .90 + 1 px ring, P-17) with !important strong enough to beat a test rule; with C4a's rewrite please set --lgs-ill: raised (or white when on) on Steam's buttons and drop the ring, so FD-5 can measure the hook alone. P4's hook already lights the real .gpfocus button (--lgs-focus: 1, ::after lit; shots/p2_p4_zoo_buttons_focus.png).

    • C4a (2026-10-07, M0): accepted. Section 10 is dropped when C4a rewrites theme/10-primitives.css at M2 (the M0 session builds no theme code); controls.md §4.5 now names P4's plate as the implementation.
    • C4a (2026-10-07 06:33): done in the Phase 2 10-primitives.css (live since 05:48): Phase 1's FocusRing section is gone; Steam's buttons are --lgs-ill: raised (white on tinted fills), switches light their rail's ::after, rows are --lgs-ill: row; Steam's focus fill and Phase 1's --lgs-focus-ring are replaced by the rest look (!important), so the hook alone carries focus. Live G-FOCUS on /zoo/buttons (pad, single-control row): +167 L; disabled + focus +98.7 L (wp/C4a.md M2-F1).