14 KiB
C4b Keyboard: evidence log
Package card: docs/phase2/PLAN.md §2.4 "C4b Keyboard". Concept: docs/phase2/concepts/controls.md §12 (CTL, owned by C4a; §12 built by C4b). Steam build at the time of writing: 11094443. One session, expedited (user away, low on tokens).
Status
2026-10-07 20:45, R2 fix pass 1. M1 (Enter label contrast 3.6:1) fixed in theme/36-keyboard.css: --key-enter-background-color is now opaque rgb(0 110 220) (white label 4.9:1), --c4b-sheen-enter: none, and the lit Enter state drops its white .20 overlay (lip + glow only). check-theme PASS, synced. Live gates with wp.c4b, native off: AUD PASS in laser and pad (20:29, 0 findings; stock AUD also PASS). SIZE PASS (0, 66 exempt), TYPE PASS (0, 40 exempt) in both modes, MOTION PASS (pad). OUTLINE not re-measured: Page.captureScreenshot timed out (30 s) in every run that needed a shot, in both modes, so there are no new shots or mockup comparison this pass. The change only alters the Enter fill colour and removes a gradient, so it adds no edges, but the gate result is still open. Device left in CSS-only mode with no flags on.
READY: wp.c4b still stands on AUD/SIZE/TYPE/MOTION; it needs one OUTLINE + shot re-run once screenshots work again. wp.c4b.native still not ready (unchanged).
2026-10-07 11:50, session 1 (final; no review round follows). Milestones: M0, M2, M3 reached; M4 partly (fragment in, reported in a native session; ack, C15b, C17, C24 not measured).
READY: wp.c4b (the keyboard's CSS, echo row, masking, Enter label). Evidence below: gates AUD / SIZE / TYPE / OUTLINE / MOTION PASS in laser and pad with wp.c4b on (AUD 66 controls, 0 GONE / HIDDEN / UNCLICKABLE), D-pad traversal across the keys OK, C11 58/58 hit areas under their keys with the move, C11c, removal clean, no runtime errors or warnings from the module. Not ready: wp.c4b.native (the native thick platter, below): leave it off until V1's native gate measures K-G1/K-G5 (cover acked, coverDz −0.027 in glassd.json, C17 ramp), C15b (glassd median ≤ 2.5 ms with the keyboard up) and C24 (no key pixels in the room map). Until then the keyboard is T1 glass in native mode too (CTL §12.8).
What is built:
- T1
theme/36-keyboard.css(live): one thick-tint platter r 30 (--lgs-r-platter) with E3 edge (--lgs-edge: thick) and E4 shade, no ring; keys white .15 / modifiers .075 / Enter blue .86, radius 11, sheen + lit lip + 2 px contact shadow; corner keys concentric; hover and virtual focus white .32 with a white label (laser: the key under the pointer; a stale virtual focus goes dark when the pointer leaves,html.lgs-input-laser); pressed white .94 + dark label + glow, never scales; Shift / Caps white .94 (Caps bar); accent bubble, emoji, IME, buffered text buffer; native-mode drop of the CSS platter where the cover is acked; Reduce Motion. Restates every rule of35-hud.css§5 (REQ C4b->C3a to delete it).theme/vr/50-keyboard.css: SteamVR's keyboard on the same key tokens (REQ P4->C4b done). Hook host lists regenerated (fontkit.py --hooks). - T2/T3
device/rt/36-keyboard.js(modulekeyboard, flagwp.c4b): echo row (field name with a search or lock glyph, text, blue caret, selection, placeholder, long-text fade; dots for secret fields), top band with the key block moved 41 kb px, scoped away from IME / emoji / Steam's buffer (then no echo, no move); only when the keyboard is open for the main window (m_VRKeyboardState.m_sOpenForOverlayKey); context Enter label through a wrapper on the keyboard manager'sGetEnterKeyLabel(Steam's own label wins; "Done" for secrets, "Go" for URLs, "Send" for chat, "Search" for search; Steam strings#Button_Done,#Button_Go, else English foren*only);data-lgs-exempt="E-KEY"on the key grid; test hook__LGS_RT.kb.echoTest(text, {name, mask}). - Native
theme/layers/36-keyboard.json: thekeyboardsurface,thickcover r 30 on the platter element,coverMm−10 (K-G6), no pops (K-G4), flagwp.c4b.native(surface and fragment).
Open (for V1 / V2 or a later C4b session): the native measurements above; C12 live (a demo field on /zoo/input, not run); C11b with a real type=password field on /zoo/input (only echoTest with mask: true was shot); C15b; G-MOCK cmp (mockups not re-rendered, compared by eye); REQs below.
Device left (12:20): shell css-only (theme on), no session flags file, C4b's test-flag overlays popped or expired (TTL ≤ 300 s), every keyboard show paired with a Hide timer that has fired, so module keyboard is off. A last CDP read of the keyboard state could not get lab.lock (6 × 240 s, the lab was saturated by other agents), so the keyboard-closed check rests on those timers.
M0: PLAN §1 conformance note
- CTL revision 3 §12 already conforms to PLAN §1 (C4a's REQ C4a->C4b): keys keyed on the input mode, glyph badges in gamepad mode only, the keyboard as a
thickcover intheme/layers/36-keyboard.json, K-G6coverDz, strings from Steam's localization (English only foren*). Confirmed (answer to REQ C4a->C4b below). No mockup change, no re-render (expedited). - One conflict settled by PLAN §1.6 (material table: "keyboard platter:
thick") over the Phase 1 slab (panel): the native cover isthick; the CSS-only look stays the T1 tintrgb(28 30 38 / .84)(CTL §12.2 fallback, VP P-87), since the keyboard quad has nothing in-page to blur. - Radius: PLAN §1.3 / tokens give the platter
--lgs-r-platter30; CTL §12.2 names 30 for the cover and 24 for the T1 fallback. One radius (30) is used for both so the CSS slab and the native cover coincide (no sliver where the CSS glass is dropped).
Evidence
| Step | Command | Result | Shot / JSON | Time |
|---|---|---|---|---|
| T1 look, laser hover | shot keyboard p2_c4b_kb_t1_laser --theme on --mode laser --pre "<Keyboard.Show(), auto-hide>" --hover '[data-key="g"],400' |
thick platter r 30, keys white .15 / modifiers .075 / Enter blue "Search", hovered "g" lit white .32 with a white label, corner keys concentric; matches p2_controls_keyboard-t1.png / p2_controls_keyboard.png by eye (Steam's own label alignment on special keys kept) |
shots/p2_c4b_kb_t1_laser.png |
10:33 |
| Echo (C11 look) | same, --flags wp.c4b --mode pad, pre __LGS_RT.kb.echoTest('Liquid Glass') |
echo in the top band (8, 5, 838 × 33), field name + text + blue caret, keys moved 41 px, platter spans 854 × 280. First shot showed the echo under Steam's container (z-index 6000): fixed with z-index 6001 | shots/p2_c4b_kb_echo_pad.png |
10:38 |
| Masked echo (C11b look) | echoTest('hunter22', {name: 'Password', mask: true}), --mode laser --hover g |
lock glyph + "Password" + 8 dots + caret; no characters | shots/p2_c4b_kb_echo_mask_laser.png |
10:41 |
| Gates, laser, flag on (no E-KEY marker yet) | gates keyboard --mode laser --flags wp.c4b --pre kbshow.js --json |
AUD PASS 66 controls, 0 issues (moved 66: the 41 px key-block move, informational); MOTION, TYPE, OUTLINE PASS; SIZE fails only P-08/P-80 on the keys (Steam's 60 × 47 geometry, E-KEY; REQ C4b->P10) | shots/p2_c4b_gates_laser.png |
10:42 |
| Gates, laser, flag on (E-KEY marker on the key grid) | same | PASS all five: SIZE 58 exempt (E-KEY) 0 fails; TYPE 0; OUTLINE 0 fails, platter top edge ratio 0.185 (≤ .35), other sides no visible line; MOTION 0 at rest; AUD 0 issues (keys exempt) | c4b_g2_laser.json (scratch) |
10:55 |
| Gates, pad, flag on | same, --mode pad |
SIZE, TYPE, OUTLINE, AUD PASS; MOTION once FAIL (one background-color transition on the emoji key still running at 1 s; that step ran while another agent held native mode). Rerun --only motion,outline: PASS (0 running, 0 non-token) |
c4b_g2_pad.json, c4b_g3_pad.json |
10:58 / 11:03 |
| Gamepad traversal on the keyboard | js with --flags wp.c4b --mode pad: Keyboard.Show(), L.pad right, right, down, left, up |
virtual focus moves " " → ArrowLeft → (edge) → " " → "z"; the focused key computes white .32 + white label; elementFromPoint at its centre hits its own hit area |
(stdout) | 11:05 |
| C11 hit areas with the move | js --flags wp.c4b: echoTest on, elementFromPoint at each visible key centre |
58 / 58 keys hit their own %{KeyboardKeyHitArea}; translate 0px 41px; echo rect (8, 5, 838, 33) |
(stdout) | 11:06 |
| C11c move scoped | js --flags wp.c4b: append a temporary div.Row_IME, then %{Modal>EmojiKeyboard}, then %{VirtualKeyboardTextBuffer} to %{VRVirtualKeyboardContents}, read, remove |
base 0px 41px; with each: translate: none and the echo hidden; after: 0px 41px; 0 temporary nodes left |
(stdout) | 11:07 |
| Removal (G-REMOVE, flag off) | js after a --flags wp.c4b step |
module off; no .lgs-kbe, no lgs-kb-echo, no data-lgs-exempt / data-lgs-kbe in the keyboard window; the keyboard manager's GetEnterKeyLabel is Steam's own again (name GetEnterKeyLabel); with the flag on it was wrapped (enterWrapped: true) |
(stdout) | 11:04 |
| pad-bfs (main route of the search field) | pad-bfs --route /library/home --max 40 --budget 120 --flags wp.c4b --mode pad --json |
truncated at the budget (25 of 69 nodes); 4 irreversible edges on Home's shelf (C2a's %{LibraryItemBox} cards), nothing in C4b's area. The keyboard has no route of its own: its gamepad traversal is the D-pad row above. Not a C4b finding |
c4b_bfs.json (scratch) |
11:12 |
| Native check (P7 at M3) | native-session --pre <flag push (TTL 300 s) + Keyboard.Show() + echoTest> --step "sgcheck --settle 2" --step "hv p2_c4b_kb_native --look" |
sgcheck lists keyboard: 0 pops, depths [0.0] mm (K-G4: keys stay in the base mosaic, no crops); its FAILs are all main legacy layers (R2 dz 3.0 / 4.4 / 5.5 mm on hdr-back, tabs, tab-arrow, card: C1a / C2a / C2c ids, not C4b). hv (looked at, then deleted on both machines): the keyboard below the bar, echo "Search · Liquid Glass" with the glyph and caret legible, keys on a dark glass platter, no outline or dashed line on keys or platter (C16 look); auto rect only, so the G-HV verdict is withheld (exit 3). Back to CSS only: yes. (At 11:18 the surface was gated by wp.c4b; since 11:48 it is gated by wp.c4b.native, so repeat with --flags wp.c4b,wp.c4b.native.) A second session to read glassd.json failed to start at 11:45 (lab: lock busy for 240 s; nothing turned on) |
(look frame deleted) | 11:18 |
| T3 strings | js: rt.react.ui.loc |
#Button_Done "Done", #Button_Go "Go" exist; "Send", "Search", "Message" have no Steam token found, so English only for en* (ui.text). Steam itself already labels Enter "Search" on the search field (seen live) |
(stdout) | 10:25 |
REQs
- REQ C4a->C4b (controls.md rev 3 §12): confirmed, no corrections (C4b, 2026-10-07).
- REQ P4->C4b (hover-rim restatement in
theme/vr/50-keyboard.css): done 10:40.--lgs-vr-hover-rimrestated as P4's ring-free lit top arc at the page scale; the keys there no longer use--lgs-control-rim/--lgs-control-outline(same key tokens as Steam's keyboard: white .15 / .075, lip, contact shadow, radius 11 × 2.25); the slab is the T1 platter tint with--lgs-mat-thick-shade(no ring). - REQ C4b->C3a:
theme/36-keyboard.css(live since 10:35) restates every rule oftheme/35-hud.css§5 "VR keyboard" (the%{Modal>Keyboard}…DefaultThemeskin variables, slab, keys, corner keys, Focused / Touched / ToggleOn / ToggleOneShot, extended row and keys, emoji keys, buffered-mode text buffer). Please delete §5 from35-hud.css(PLAN §6 step 2) and drop the "Section 5 … is C4b's" sentence from its header. - REQ C4b->P6: the reporter's
SURFACE_FIELDS(device/lgs_layers.js) do not pass a fragment surface'sscaleFromto the daemon, which already forwards it to glassd (lgs_shell.py,"scaleFrom" in caps). P8's dm2 test gives the keyboardscaleFrom: "overlay". Please passscaleFromthrough so36-keyboard.jsoncan set it if K-G2 / C24 shows the keyboard's own overlay transform is the right one (today glassd rescales it to the main window's).- P6 (2026-10-07 13:25 EDT, maintenance session 4): done. A surface may set
"scaleFrom": "main" | "overlay"(first fragment or an owner, likecoverMm; another value is listed inerrorsand ignored); the report carries it as surfacescaleFrom, and P8 forwards it to glassd with itsscaleFromcap.36-keyboard.jsondefines the keyboard surface, so you can add"scaleFrom": "overlay"there when K-G2 / C24 says so. Test RP-MO on the Frame (13:24): an owner fragment's"overlay"reported, absent when not set, a bad value refused with an error; installed (device/lgs_layers.js). contracts/reporter.md §3.1, §6.
- P6 (2026-10-07 13:25 EDT, maintenance session 4): done. A surface may set
- REQ C4b->P10:
lab/exemptions.jsonE-KEY lists%{*KeyboardKey}(the visible key), but the SIZE sweep judges the focusable%{KeyboardKeyHitArea}(its parent), so withwp.c4boff every key fails P-08 (60 × 47 Steam geometry, CTL §12.1). Please add"%{KeyboardKeyHitArea}"to E-KEY. (Withwp.c4bon, C4b's module marks the key grid%{Modal>Keyboard}data-lgs-exempt="E-KEY", contracts/lab.md §6 rule 1; AUD then skips the keys, so AUD evidence for them is the 10:42 run without the marker.)- P10 (2026-10-07 session 5): done: E-KEY lists
%{*KeyboardKey}and%{KeyboardKeyHitArea}. Also, so the marker no longer blinds AUD: E-KEY is scoped to SIZE, TYPE and OUTLINE (_scope), because PLAN §1.16's criterion for it ("Steam's geometry, identical to stock") is a comparison with stock, which is what AUD makes. So AUD now judges the keys withwp.c4bon as well as off (GONE, HIDDEN, SHRUNK, UNCLICKABLE, CONTRAST). Live (gates keyboard --pre <show the keyboard> --mode laser, 13:31 withwp.c4boff, 13:32 with--flags wp.c4b): SIZE PASS both, 58 key hit areas listed underexemptas E-KEY (pass: null, judged by your tests), none failing P-08; AUD (keys judged) FAIL on one item in both runs:CONTRAST text span "Enter" 21 -> 3.6 (needs 4.5; rooms grey/bright/dark 3.7/3.6/3.7), a real finding for you. Fixture case 13 (tools/p2/test_gates_page.py: SIZE exempt, AUD still reports a shrunk key). contracts/lab.md §6.
- P10 (2026-10-07 session 5): done: E-KEY lists