151 KiB
Glass Shell Phase 2 build plan (PLAN)
This is the build plan for Phase 2: the radical visionOS redesign of the Steam Frame's VR interface that the user asked for. It turns the design system (DESIGN2.md), seven reviewed concepts, four capability studies and the native layer into work that a team of 10 to 20 agents can run in parallel, with every result checked by agents alone.
Who it is for. The coordinator, who assigns packages and commits, and the agents who build them. Agents never commit.
The user is not available. Every gate in this plan can be run by an agent. The few questions that only a person wearing the headset can answer are listed in §5.3. None of them blocks the default build: the default uses only mechanisms that agents can prove.
How this file binds.
- Where two concepts disagree, §1 decides.
- Where a concept disagrees with §1, §1 wins. The package that owns the concept updates the concept text and its mockups first (milestone M0, §2.1).
DESIGN2.mdis amended to match §1 by package P4 (the list is in §1.18).- §1.19 records the amendments the coordinator decided during the build (round R2). They are applied in place and marked
[R2-n]. Where an unmarked line still disagrees with §1.19, §1.19 wins.
Sources, by short name
| Short | File (under docs/phase2/ unless a path is given) |
|---|---|
| D2 | DESIGN2.md (the design system) |
| WN | concepts/window-nav.md (window, navigation, search, system presentations) |
| HA | concepts/home-apps.md (Home, launcher, library) |
| XC | concepts/xc-home-search.md (home-apps and window-nav agreement) |
| CC | concepts/control-center.md (bar, Control Center, HUD, toasts) |
| CTL | concepts/controls.md (controls, keyboard, hover and focus) |
| GP | concepts/game-pages.md (game pages, Now Playing, bindings) |
| SET | concepts/settings.md (Steam and SteamVR settings) |
| SM | concepts/social-media.md (People, Photos, Downloads, Store) |
| SR | capabilities/steam-react.md (T3, proven) |
| SP | capabilities/spatial.md (T4 scene graph and T5 glassd) |
| IM | capabilities/input-mode.md (laser vs gamepad, dwell, sounds, haptics, scroll guard) |
| E2E | capabilities/native-e2e.md (first live native run: ghosts, rims, flat glass) |
| GM | glassd-material.md (glassd material v2) |
| VP | research/visionos-principles.md (principles and the P-01 to P-89 conformance checklist) |
| MO | research/liquid-glass-motion.md (motion) |
| NAT, LAB | docs/NATIVE.md, docs/LAB.md |
Evidence tags as in D2: [PROVEN], [PROVEN-P1], [PLAUSIBLE], [UNPROVEN], [inferred], [ours].
0. The plan on one screen
What ships. One visionOS app in the room:
- Home is windowless: games, shortcuts and programs float as glass discs on the visionOS honeycomb, and the focused one opens into a card with Play.
- The "+" popup is a grid launcher. The Liquid Glass switch lives only there.
- The library is a poster window with one bottom toolbar.
- Search is a sheet over the page you were on.
- Game pages are full-bleed art with one action cluster.
- Settings is one visionOS Settings app for Steam and SteamVR.
- Control Center turns the window into three glass tiles.
- People, Photos, Downloads and Store follow their visionOS counterparts.
- Everything uses one control vocabulary at visionOS sizes, one illumination model, one motion system (MO's springs) and one material system.
- In native mode the glass is real (glassd frosts and bends the room) and parts of the page pop out by 10 to 25 mm.
How it is built. 27 work packages, each owning a disjoint set of files (§2.6):
- 10 platform packages (P1 to P10): runtime, React framework, interaction runtime, CSS foundation, motion library, reporter, scene graph, daemon, glassd, lab tools.
- 15 context packages (C1a to C7), one per concept area.
- 2 verification and integration packages (V1, V2).
Order.
- The platform packages publish their contracts first (milestone M1).
- Context packages start their CSS (T1) as soon as P4's tokens land, and their T2/T3 code once P1 to P3 are in.
- Native integration (plates, pops, depth) follows P6 to P9.
- V1 runs the native gate, and V2 runs the full verification sweep.
Defaults that ship without the user (§1.17):
- Every pop is non-interactive.
- Native glass becomes the default only if the native gate (§4.3) passes. Otherwise the honest CSS-only look ships, and the final report says so.
- Nothing persists:
lgs off, a Steam restart or a reboot gives the stock UI.
The ten decisions that matter most (all in §1):
| # | Decision |
|---|---|
| 1 | Hover never moves Steam's focus (IM D-7, proven). Reveals are driven by one attention state machine: gamepad focus, or laser dwell. HA's "laser hover moves focus" premise is withdrawn |
| 2 | One depth plan with two profiles. The default profile (non-interactive pops at 0 / +10 / +15 / +25 mm, admitted by a click-safe rule) ships. The wearer profile (+30 / +50 mm interactive crops) stays off until a wearer check |
| 3 | Four glass mechanisms: cover, plate (new: opaque per-shape glass, up to 32 per surface), slab, hole treatment (new). Plates make Home, folders, /invites and Control Center real glass without ghosts |
| 4 | Gamepad focus add white .28 (tuning range .28 to .32), checked by VP P-14 and P-15. Navigation selection is a .18 pill with a top arc and a Semibold label; hover on navigation rows is the light spot only |
| 5 | Steam's legend nodes are never hidden. The ornament is a capsule when it holds actions and a quiet legend when it holds only A and B. WN's laser-mode hiding (Q13) and SET's E-ORN exception are withdrawn |
| 6 | window-nav owns search, as a sheet over a snapshot of the page. home-apps adds programs, the Apps cell and X = Play through a provider API |
| 7 | Dimming for alerts and sheets is Steam's overlay restyled to black .35. The t1 tint is not used for in-window modals: in native mode it dims everything reparented to Steam's panel, the modal's own crop included (SG-5, measured) [R2-4] |
| 8 | One More circle helper for every area: 60 px with an 80 px hit, inside its host, calling the host's own menu handler |
| 9 | Menus: WN's layout-by-count rule for actions, SET's list page for long value lists on settings routes, a red rule based on the number of destructive rows, Steam's order and default focus untouched |
| 10 | Control Center is drawn in the main window (CC-M). The popup variant (CC-A) ships only if its gamepad spike passes |
1. Cross-concept decisions
1.1 Who owns each shared element
| Element | Owner (concept → package) | Contributions from others |
|---|---|---|
| Window glass, route glass modes (§1.2), toolbar row (Back, Large Title, search field), tab-bar ornament, bottom ornament contract, window-bar row, More circle, frozen target, pointer proxy | WN → C1a | HA (windowless routes, the library's ornament members and its toolbar trailing group [R2-2]), SM (quiet legend, header positions on /chat and /invites), SET (ornament on settings routes) |
| Search route and sheet | WN → C1b | HA through the provider API (§1.9) |
| Context menus, popovers, dropdown slabs, alerts, sheets, Power menu layout, route transitions | WN → C1c | CC (Power glyphs, group labels, red confirm), CTL (value menus), SET (list page), GP (placement rule for sources in a row) |
| Toasts, bar tooltips, volume HUD, floating footer | CC → C3a | WN §5.5 card size (320 × 76) |
| Illumination model (hover, focus, press), FocusRing light plate, disabled focus | CTL → P3 (JS) and P4 (CSS) | Every area |
| Buttons, switches, check circles, sliders, pop-ups, segmented controls, fields, grouped rows | CTL → C4a | — |
| Keyboard | CTL → C4b | SM (echo context strings) |
| Achievements page | GP → C5a | SM §3.11 (fallback layout) |
| Steam's Quick Access panels inside Control Center | CC → C3b | SET P-S2 (the same rendering method) |
1.2 Route glass modes
The route decides the mode, never focus, so the glass never changes size under the user. C1a's T2 sets data-lgs-glass on %{BasicUiRoot}. The reporter (P6) reads it.
| Mode | Routes | Glass | Native (T5) | CSS-only (T1) |
|---|---|---|---|---|
window |
Library tabs and collections, search, Downloads, Photos grid, /chat, game details states, Properties, Steam Settings, achievements |
1280 × 656, radius 54, a 64 px ornament margin below (WN §3.1) | window cover with that shape |
Smoky tint rgb(20 22 30 / .74) (dial .60 to .84) + edge cues (WN §8.4) |
window-full |
What's New (/library/lgs/steamhome), /account, the Steam Input page, routes without a footer |
1280 × 720 | window cover, full overlay |
As above |
windowless |
/library/home, /library/lgs/folder/*, /invites, Control Center (CC-M) while it is open |
None. Every glass element is a plate | Plates (§1.6) | CSS plates: black .58 with a white .16 → .04 gradient + edge cues; CC-M tiles at .94 (CC §4.2) |
hero |
/library/app/:appid title view |
The art fills the window | window cover, hidden under the opaque art |
The same |
SM's quiet-legend route /account is window-full. Achievements stay window, as the table says; their quiet legend sits in the margin on the dim band of §1.10 [R2-10]. Settings routes are window (656) on every page, so the ornament can come and go inside the margin (SET §3.2).
1.3 One control vocabulary
Sizes are main-window CSS px. Other surfaces multiply by their m (D2 §2.5, CTL §13). 1 pt = 4/3 px.
| Control | Visible | Hit region | Spacing and notes | Source |
|---|---|---|---|---|
| Icon button | 60 circle, symbol 26–28 | 80 | ≥ 24 px clear between 60 px targets | D2 §4, CTL R2 |
| Text button | 60 capsule, 24 px side padding, label 24 Semibold | 80 tall | — | D2 §4 |
| Primary | 70 capsule; Play 280 × 80 on game pages | ≥ 80 | One per screen, tinted | D2 §4, GP §3.2 |
| Mini circle (clear, disclosure) | 38–44 | 80 clear region | Alone only | CTL §5 |
| Back / close | 60 circle at (24, 24) | 80 box at (14, 14) | Top left on every route; grows into a titled capsule after 0.6 s | WN §3.2 |
| Search field | 64 capsule: 520 wide on section roots, 640 nested; or a 60 circle (§1.9) | 80 tall | No microphone | WN §3.2, XC §2 |
| Segmented control | 64 track, segments 56–60 tall and ≥ 140 wide, contiguous | 80 tall | White selected pill travels on snappy |
D2 §7.10, CTL §8.3 |
| Switch | 66 × 40 (Steam's toggle at scale: 1.75) |
88 × 80 | Knob lifts into clear glass on press | CTL §6.1, P-C3 |
| Check circle | 40 | 80 cell | Blue when on | CTL §6.2 |
| Slider | 64 capsule, knob 64, value fill from Steam's origin | 80 | Inert glyph inside the fill; mute is its own zone | CTL §7 |
| Row in a platter | 80 contiguous, hover pill inset 6 | 80 | 2 px white .08 separator inset 26, hidden next to a lit row | CTL §10, R11 |
| Free-standing row | 72 tall | 80 pitch | — | D2 §4 |
| Menu rows | §1.12 | §1.12 | Exemption E-MENU (§1.16) | WN §5.1 |
| Tab-bar item | Live pitch 52–66 frame-menu px, circle = pitch − 8 | Full row | WN's live sizing rule | WN §3.3.2 |
| Bar disc | 56 bar px at 64 bar px pitch | 64 × 72 slot | 3.0° at the measured distance | CC §3.1, D-CC8 |
| Bottom ornament | 84 capsule, y 628–712, ≤ 960 wide, members 60 | 84 tall | Straddles the glass by 28 | WN §3.4 |
| More circle | 60, black .38 + 10 px blur | 80 | Inside its host (§1.11) | HA §7.2 |
| Home disc | 120 circle on the 4-5-4 honeycomb (224 / 188 pitch) | 200 × 180 cell | One-line label | HA §3.1 |
| Glyph badge | 30 circle, 16 px Bold letter | — | Gamepad mode only (§1.4) | VP P-26 |
1.4 States, input modes and attention
Two input signals (IM §2), both set by P3 on every Steam popup window:
| Signal | Source | Drives |
|---|---|---|
html.lgs-input-pad / html.lgs-input-laser |
FocusNavController.NavigationSource |
Every state look (hover, focus, press) |
html[data-lgs-vr-mode="gamepad" | "laser"] |
vrGamepadInput.IsInGamepadNav |
Which of Steam's mode-dependent nodes render (the laser-mode Sort/Filter pill), and glyph badges |
- One accessor,
__LGS_RT.input, with a test stub that changes only our classes and never Steam's getter (HA AT-14d, WN AT-24). - WN's
html.lgs-gp/html.lgs-laserand HA'slgsInputMode()are replaced by this accessor.
Laser rules.
- Laser looks key on
:hover, never on.gpfocusor.Focusablealone (VP P-01, P-02; IM §3). Steam removes.gpfocusand.Focusableunder the laser, and can leave a stale.gpfocusbehind. - Brightness changes at once. Lift, scale and depth start only on
.lgs-dwell, which is set after 80 ms of dwell (IM §4, VP P-06).
Attention. Hover never moves Steam's focus [PROVEN, IM D-7]. Every delayed reveal is therefore fed by one state machine (P3, 05-attention.js):
- in gamepad mode, from
vgp_onfocus/vgp_onbluror aFocusable'sonGamepadFocus/onGamepadBlur; - in laser mode, from
pointerenterplus dwell.
It drives Home's card ramp and name plates (HA §3.4: its documented fallback becomes the primary path), tooltips, the More circle and the frozen target. The frozen target (WN §3.4.3) stays, because legend actions act on .gpfocus, which under the laser is either absent or stale (IM §3.3).
State recipe (tokens in theme/00-tokens.nowrap.css, CSS in theme/04-states.css, both owned by P4):
| State | Look |
|---|---|
| Rest, raised | White .10 + top sheen + inset 0 1.5px 1.5px -1px white .22 |
| Rest, recessed | Black .30 + inset 0 2px 5px black .30 |
| Hover (laser) | + white .08, and a light spot of .12 at the pointer, drawn under the label (VP P-10) |
| Hover on navigation rows (sidebar, list, tab bar) | The light spot only, no fill added (SM-D13, SET T-SEL) |
| Gamepad focus | + white .28 uniform, a spot of .16 in the upper third, the control's own specular arc ×1.5. The first frame shows 60 % (CTL P-C11). P4 sets the final value once, between .28 and .32, from the G-FOCUS measurement. Final: --lgs-focus-add = .32 (P4-D8: .28 and .30 failed P-14/P-15 on the tab bar over glass at L ~95) [R2-7] |
| Focus inside rows | CTL C-D14: a single-control row carries the focus and its control is lifted; a multi-control row warms (.06) and its control carries the focus |
| Focus on a white or coloured fill | An outer glow, P4's token --lgs-white-glow (0 0 22px 8px white .55 on build 11094443, contracts/tokens.md §1.6; scaled by m on other surfaces) (VP P-16). It is blurred, not a ring. The earlier 0 0 18px 2px white .30 measured only +4 to +12 L in the P-16 band [R2-7] |
| Navigation selected | White .18 pill + specular top arc + Semibold label |
| Selected, on, or the source of an open menu | White .94, label #0d0e12 |
| Disabled | Content at 40 %, fill at 40 % of its rest alpha, no hover |
| Disabled + focus | Fill .10, arc at 60 %, no spot (CTL C-D15) |
| Press | Glow from the hit point on interactive (210 ms); glass controls swell by min(1.06, 1 + 6 / maxSide); rows, cards, keys, switches and sliders only brighten |
- Withdrawn: CC's D-CC6 scale on focus. Rows and toolbar buttons never scale (VP P-05). The bloom stays as the outer glow above.
- Only content cards (×1.05, +15 mm) and Home discs (×1.10, +15 mm) lift on focus (VP P-19).
- Criteria (gate G-FOCUS, §4.1):
- focus ≥ +40 L over rest (P-14);
- focus ≥ selected + 15 L (P-15);
- selected ≥ hovered + 12 L (SET T-SEL, SM);
- glow band ≥ +20 L on white and coloured fills (P-16);
- disabled + focus ≥ +10 L;
- first frame ≥ 60 % of the final contrast.
- Glyph badges show only in gamepad mode (VP P-26), drawn from Steam's own glyph components where they exist (P-27). WN's always-on badges are withdrawn.
1.5 One motion system
- One source.
research/springs.pygenerates three outputs, all owned by P5:- the CSS easing strings and durations (
theme/02-motion.nowrap.css); device/shared/motion.js: the closed-form springs and the token table, used by the T3 views and by the scene graph's depth pushes;native/shared/motion_tokens.h, used by glassd's phase ramps. A change to a token is a change tosprings.py, never to one of its outputs.
- the CSS easing strings and durations (
- Tokens are D2 §11.2, unchanged. Rules are D2 §11.4 (C1 to C8, M1 to M5) and §11.7 (CEF R1 to R11).
- At rest: one second after any interaction, no animation is in
playState: running, and no animation whose name starts withlgs-remains. Steam's finishedforwardsfills (ItemFocusAnim-*) are allowed (CTL C13). - Route, card and menu entrance overrides of Steam's animations are adopted (sign-off S3). They stay inside Steam's React timeouts: enter ≤ 800 ms including delay, exit ≤ 200 ms (D2 §11.5). Toasts keep Steam's
toastExit*entry. - Reduce Motion: fades of 150–200 ms only; the end depth is pushed once; glassd gets
reduceMotion: true.
| Interaction | Channel | Package |
|---|---|---|
| Hover, focus, press | CSS (04-states.css) + T2 classes (light spot, pressed) |
P4, P3 |
| Materialize and dematerialize (toasts, tooltips, More circle, ornaments, bar popups) | CSS keyframes lgs-mat-* |
P5 (keyframes), each user |
| Menu morph | CSS clip-path from --sx --sy --sw --sh (slab ≤ 600 × 600). In native mode glassd has no morph: the slab materializes in place on its phase while the CSS clip-path draws the open morph, and glassd dematerializes it on close (C1c D15, contracts/glassd.md §6) [R2-12] |
C1c, P9 |
| Sheets and alerts | sheet-in / sheet-out, materialize + swell; scrim on fade |
C1c |
| Route and tab transitions | theme/23-transitions.css (±16 px + fade on page) |
C1c |
| Depth changes | The scene-graph depth channel: the depth spring, ≤ 60 pushes/s only while a value moves, then one final push (SP §4.2) |
P7 |
| Glass materialize and dissolve | glassd phase / appear (GM §5) |
P8 writes, P9 renders |
| Home ramp, section and page changes | T3 components with motion.js and CSS |
C2a |
| Control Center open and close, Now ↔ list, More Controls | T3 components | C3b |
1.6 One material system
Five materials (D2 §6.1) with the GM v2 presets. Thickness θ is computed from the shorter side in both glassd and the CSS fallback (GM §1.7; P4 updates D2 and the kit).
Four glass mechanisms in native mode:
| Mechanism | What it is | Used for | Contract |
|---|---|---|---|
| Cover | The glass of a whole surface: a union of rounded shapes | Window (656 or 720), bar segments, popup cards, frame menu, footer, toasts, volume HUD, keyboard | surfaces[].shapes (≤ 8 today) |
| Plate (new) | An opaque glass shape with its own material, phase and tint, drawn at the cover's depth. Steam's real panel is hidden under it, and the base mosaic shows Steam's content in front of it | Home and folder discs, the top-row controls, the open card and name plate; the /invites card; Control Center tiles and close circle; menus, alerts and sheets kept flat by the depth rules |
surfaces[].plates, ≤ 32 per surface, each {x, y, w, h, r, material, phase, tint, fill, occluder} (P9 G1) |
| Slab | Liquid Glass under a popped crop, at dz − 0.8 mm | Every pop | As today, plus a per-slab tint (G3) |
| Hole treatment (new) | Drawn into the cover under a popped element: that element's contact shadow and its container's tone, so the hole a pop leaves reads as its shadow, not as a bright sliver of glass or room | Every pop over glass or art | slabs[].hole = {shadow, fill} (G2). Merges GP's GQ8b and SET's hole tone |
Edges.
- CSS E3 is a conic arc with gaps on both sides plus a radial lobe at 26 % of the width (WN D-15). There is no linear top layer.
- glassd draws the GM v2 crescent.
- Nowhere is there a border, an outline or a 1 px ring (VP P-42). Gate: the WN AT-23 edge ratio ≤ 0.35, in CDP shots and in
hvgrabframes.
Tone. Glass luminance L 55–110, and 70–90 under text (D2 §6.3).
Native-mode CSS (theme/05-native.css, P6) drops the CSS glass only where the daemon has acknowledged a cover, plate or pop. Anything not acknowledged keeps its CSS glass.
CSS-only look. WN §8.4's degraded spec is what ships while native mode is off. It is tinted glass with light from above, not frost of the room, and reports say so.
No glass on glass (VP P-45). Content (art, posters, web pages) is never glass.
| Element | Material | Native mechanism | CSS-only |
|---|---|---|---|
| Steam window | window |
Cover | Smoky tint + edges |
| Tab-bar capsules, bar segments, bottom ornament, More circle, Home discs | liquid |
Cover (popups), slab behind (ornament, inset method), plate (Home) | Panel tint or in-page blur 12 + saturate 1.7 + liquid edges |
Bar popups, Control Center tiles, toasts, HUD, /invites card |
panel |
Cover or plate | Panel tint rgb(28 30 40 / .78) + edges |
| Menus, alerts, sheets, search sheet, keyboard platter | thick |
Slab under the pop, or a plate when kept flat | In-page blur 30 + tint .30–.40 + edges |
| Controls over art and media | clear + a 35 % dimming layer |
In-page glass only (nothing pops over media) | The same |
1.7 One depth plan
Two profiles.
- Default ships. Every pop is a non-interactive crop: the laser passes through it to Steam's panel at the same x/y.
- Wearer is behind the flag
interactivePops(off). It turns on only after the SP §12 check by a person wearing the headset. It restores D2 §3.8's larger interactive depths.
Admission rules. A pop in the default profile must satisfy all of these. The reporter (P6) enforces them and the gate G-DEPTH checks them.
- Covered (ghost rule, E2E §1). The pop lies inside a cover or a plate. Never over the bottom ornament, the store's navigation ornament, the tab bar, the window-bar row, or the
/invitesheader nodes. - Click-safe. dz_mm ≤ 0.25 × s × 0.769, where s is the shorter side (CSS px) of the smallest visible focusable that intersects the crop. In scene units, dz ≤ 0.000521 × s (SM §4.1). +10 mm needs s ≥ 52; +15 mm needs s ≥ 78. A pop that fails drops to the next lower value in the allowed set, never to a value in between.
- Not over media or opaque art, unless the hole treatment (G2) is live and an off-axis
hvgrabpasses (GP AT-HV-OFFAXIS). - Never a destructive confirmation: an alert or sheet that contains a destructive button stays flat (SET §5 rule 3).
- Containers only: ≥ 60 × 60 px, never text or a glyph alone (VP P-47).
- Still: never while its scroller moves. While a modal (menu, alert, sheet) is open, only the modal itself pops; every other in-page pop is dropped.
- Few depths: at most 4 distinct dz values at rest per route (VP P-46). The set is {0, +10, +15, +25} mm.
| Element | Default | Wearer | Mechanism |
|---|---|---|---|
| Window, content, rows, in-window controls, fields, platters, bottom ornament, store ornament | 0 | 0 | Cover; ornaments get an inset slab behind them |
| Tab-bar ornament | +25 | +25 | The frame-menu popup's own transform (WN §3.3.6) [PLAUSIBLE]; fallback Steam's +10 |
| Bar popups (+, Quick Access, Playspace, Streaming, tab menus) | +25 | +25 | Popup request z through the wrapper (SP §3.3) [PLAUSIBLE]; fallback Steam's 3.7 mm. HA's +30 for the "+" popup is unified to +25 |
| Home / folder plates, labels, page dots | 0 | 0 | Plates + mosaic bands (HA §10.2) |
| Focused Home cell, open card, name plate | +15 | +15, interactive | Crop over its plate in the occluder variant |
| Focused or hovered content card ≥ 150 px (poster, media tile, event card, DLC tile, search result) | +15 | +15, interactive | Crop over the cover |
| Primary capsule ≥ 70 px (Apply, Install), and the 60 px Pause circle on the Now Downloading card | +10 | +15, interactive | Crop + tinted slab (G3) |
| Game-page cluster, Play, tab row at rest (over art) | 0; then +10 once G2 lands and GP AT-HV-OFFAXIS passes | Play +15, others +10 | Crop + hole treatment |
| Game-page tabs, pinned | +10 | +20 | Crop + slab |
| Menus, popovers, dropdown slabs | +10, appearing with the materialize (0 → +10 on depth) |
+30, growing from the source's depth | Crop + thick slab |
| Alerts | +10; 0 if it contains a destructive button | +30 (still 0 if destructive) | Crop + thick slab, or a flat thick plate |
| Sheets, the search sheet | +10 | +30 → +50 | Crop + thick slab |
| Source card while its menu is open | 0 with a CSS glow, in both input modes (rule 6: only the modal pops) [R2-3] | 0 with a CSS glow (rule 6 holds in both profiles) | WN's +5 mm is withdrawn (it would be a fifth depth). The control that opened the menu (More circle, Manage) is white .94 (§1.4) |
| Settings hero icon, account avatar | +10 | +10 | Decorative crop |
| Control Center tiles (CC-M) | 0 | 0 | Plates |
| CC-A tiles (gated) | +25 in front of the bar | +25 | Its own popup request |
| Keyboard keys over the platter | 0 (platter at −10 mm after K-G6) | — | Cover + base mosaic |
| Tooltips | CSS shadow only | Owner + 5 mm | — |
Units. units = mm / (369 × r), with S and r read live (SP §1.2). Animated deltas ≤ 20 mm; total ≤ 80 mm.
1.8 Dimming
- Menus: no scrim (WN §5.1.3).
- Alerts and sheets: Steam's
.ModalOverlayBackgroundrestyled to black .35 in CSS, in both modes. Under a pop, the hole treatment'sfillfollows the scrim, so no bright sliver shows. - The
t1tint is not used for in-window modals [R2-4].- Measured (SG-5,
contracts/sg.md§5): in native mode at1tint (the report'swindow.dim) dims everything reparented to Steam's panel: glassd's cover, the base mosaic, pops and slabs. The frame menu, the bar and other popups keep their brightness. The earlier inference ("it would dim only the hidden real panel") was wrong. So in native mode at1tint would dim the modal's own crop as well. - In CSS-only mode a
t1tint would also dim the modal (SET §14 #5). - A surface
dim(P7's cover and base wrappers) dims the cover and the base only; popped crops keep their brightness. Use it where a window must dim behind a bright popped element.
- Measured (SG-5,
t1is used only for CC-A (the window dims to 0.6 while the popup shows:window.dim 0.6alone, never together with a surfacedim, which would compound to 0.36) and for the sheet-recede variant (sign-off S7, off).- Control Center (CC-M) hides the window's content with CSS opacity on Steam's page layers (CC §4.2). The window cover's phase goes to 0 while the tile plates materialize.
1.9 Toolbar, search field and search
- Toolbar row. 108 px when CQ1 holds (WN AT-2). Otherwise WN §3.2's three-rule fallback applies. C1a sets
html.lgs-hdr-108orhtml.lgs-hdr-40, and area CSS adapts to that class only. - One field, three variants of the same
%{SearchAndTitleContainer}:
| Variant | Where |
|---|---|
| 520 × 64 capsule | Section roots (Library, Photos, Downloads) |
| 640 × 64 capsule | Nested routes and the search route |
| 60 px circle in an 80 × 80 box | Home, folders, hero routes, Steam Settings, the /chat sidebar row (box at the sidebar's trailing end: x = sidebar width − 94, y 14; (418, 14) with SM-D14's 512 px sidebar [R2-14]), the photo viewer and clip player, /account, the /invites card corner, achievements |
The circle is gated by WN AT-4 (no SHRUNK); its fallback is the capsule. There is no microphone anywhere (WN D-8, CTL C-D7, XC §4).
- Search is WN §4: a sheet over a snapshot of the page you were on, with Steam's routes underneath. HA's split view is withdrawn (XC §4).
- HA's contributions go in through a provider API owned by C1b,
__LGS_RT.search.addProvider({id, rank, render, primary}):- S-A: programs in the ranking; a program can be the Top Hit;
- S-B: the Apps cell beside the Store row;
- S-C: X = Steam's primary action on library items. A still opens the game page. The Liquid Glass entry is excluded from every provider (HA-10).
1.10 Bottom ornament and legends
This resolves WN Q13, XC §3, SET's E-ORN exception and SM's quiet legend.
- Steam's legend nodes are never hidden (no
display: none). There are no audit exceptions for legends. - Glass height is set by the route (§1.2). The ornament's material is set by its content:
- When the legend holds any action other than A and B, it is a capsule: 84 px, y 628–712, ≤ 960 wide, members 60 px.
- When it holds only A and B, it is a quiet legend: no capsule material, items 60 px tall, labels Medium white .70. On
window-fullroutes it sits inside the glass; onwindowroutes it sits in the margin. - On
windowroutes the quiet members sit on one dim band [R2-1]: black .62, 60 px tall, radius 30, edges blurred 7 px, spanning the quiet members plus 10 px on each side; no rim, sheen orbackdrop-filter, and no plate or slab in native mode (it is a shadow on the room, not glass). Labels there are 22 px Medium white .82 with the on-room shadow (P-41). FlagquietBacking(S27).
- A and B are always present as quiet trailing members, in both input modes, because Steam renders them in laser mode too (HA §7.1,
shots/p2_lib_collections_on.png). - Laser mode: Steam's
%{SortAndFilterContainer}(rendered only on library routes) is the toolbar row's trailing group, not an ornament member [R2-2]. It is a page node, and C1a's window clip at the glass edge (y 656) cuts every page node in the ornament margin, so in slot 1 it was half hidden and took no clicks. In gamepad mode the same functions are Steam's X and Y legends in the ornament. - Library routes: the ornament hugs its members, as on every other route (WN §3.4.1) [R2-2]. HA's five fixed slots (300 · 130 · 156 · 134 · 120 = 840 px of slots; with four 4 px gaps and 12 + 12 px padding an 880 px capsule at x 200–1080) stay built behind C2c's flag
libFixedSlots(off, S28): Steam drops X, Y and ≡ whenever focus is on the header rows, and a fixed capsule then shows up to 434 px of empty slots. - Depth 0, with an inset slab behind (WN D-7).
- Too many members: members switch to a compact style (20 px labels, 16 px padding) rather than exceed 960 px. They are never dropped. SM's ≤ 1232 px limit is withdrawn.
1.11 More circle, frozen target, pointer proxy
- More circle. One helper for every area (C1a,
device/rt/20-more.js):- Look: 60 px visible with an 80 px hit, white while its menu is open.
- Placement: inside the host. Cards: top right, inset 10 (HA §7.2). Rows: trailing end, inset 24.
- One node per document, moved to the current target (attention, §1.4), so there is nothing to re-attach when virtualized rows recycle.
- Isolation: a capture-phase listener stops
pointerdown,mousedown,mouseupandclick, so the host never opens. - Dispatch: the host's own
onMenuButtonfrom itsFocusablefiber props (SM-D15). Fallback: Steam'svgp_onmenu(button 14) on the host (HA §7.2). - Registration: areas call
more.register(selector, {placement}). The helper is never shown on Home's launcher discs.
- Frozen target. WN §3.4.3, unchanged (C1a).
- Pointer proxy. WN §3.6, in native mode only (flag
pointerProxy = "native").- This deviates from VP P-11 ("draw no cursor"). The reason: in native mode SteamVR's dot may be drawn behind the cover (E2E §5), and no agent can see it.
- Retire the proxy when a wearer confirms that the dot is visible with the cover flags (§5.3).
1.12 Menus, alerts and sheets
- Context menus (actions): WN §5.1.1's layout by item count. The slab stays inside the menu box, y 108–616 (12 px clear of the ornament's top). Steam's Cancel stays, as a 60 px quiet capsule (HA-11; 56 → 60 for VP P-80) [R2-5]: 60 visible on its 60 px element (no clear border), ≥ 192 wide (G-AUD keeps ≥ 85 % of Steam's 280 × 48), white .08 fill, 22 px Medium white .70, centred under the rows (across both columns in the grid), last in Steam's order, ≥ 4 px clear of the last row's visible fill.
| Actionable items | Layout [R2-5] |
|---|---|
| ≤ 5 | One column of 72 px rows, 6 px apart, 40 px header row |
| 6 | Compact: 60 px visible on a contiguous 64 px pitch, inline label |
| 7–10 | Two columns of 72 px rows, ≤ 592 px wide (columns ≥ 280). Seven compact rows with a title and Cancel need 564 px, more than the 508 px box (C1c D28) |
| 11–14 | Two columns, ≤ 592 px wide; the rows exceed the box, so the slab's row area scrolls inside it: scroll-edge fade, no scrollbar at rest (P-72), the focused row kept fully inside the slab |
- Value menus (dropdowns): layout follows the same count table; the "up to 8" rule below is placement (C1c D11) [R2-5].
- up to 8 options: a slab anchored to its capsule, right-aligned, below, above or over it (SET §4.5);
- more than 8 on settings routes: a list page over the detail pane (SET §4.5);
- elsewhere, 9–14 options: the two-column grid;
- 15 or more: one scrolling column of two-line rows (CTL C-D19).
- Anchoring and morph (T2 +
translate) is gated on WN AT-11, GP AT-MENU and SET CQ10: a click outside must still dismiss, and the D-pad must be unchanged. Otherwise the menu stays centred and still morphs from its source. - Placement for sources inside a page row: GP §3.4's order (above, right, left, then Steam's centred placement).
- Destructive items:
- ≤ 2 destructive rows: red label at rest;
-
2 (Power): red glyph and white label at rest;
- always a red whole fill on focus (WN D-16 = CC D-CC1);
- never reordered (SM-D8); Steam's default focus untouched (S6).
- Power menu: WN §5.2's layout (592 × 464, "This Device" and "Steam" side by side), CC §5's glyphs and group labels, and CC's second entry point (the Power circle in Control Center). Its confirmation is a visionOS alert with a red confirm capsule; text on coloured fills is dark (CC §7).
- Alerts: 640 px wide, buttons in Steam's order (
DialogTwoColLayoutis a nav row). Sheets: ≤ 960 px wide, with a close circle. Scrim .35 for both. - Placement on the glass (VP P-35, ± 24 px) [R2-6]: alerts are centred on the route's glass: centre (640, 328) on
windowroutes, (640, 360) onwindow-fullandwindowless. Sheets are centred at x 640, top at y 108 (under the toolbar row), at most 488 tall onwindowroutes and 552 onwindow-full, so their centre stays within 24 px of the glass centre; content scrolls inside. Not the 108–628 modal box (centre 368, 40 px off). - Dismissal and default focus keep Steam's behaviour [R2-6]: an outside click cancels alerts and sheets exactly as stock (Steam's
ModalClickToDismiss; it only ever cancels, never confirms), and Steam's default focus is untouched (S6). Both are recorded deviations (§4.4: P-65, P-67). - Depth: §1.7.
1.13 Tooltips, sounds and haptics
- Tooltips (P3,
device/rt/07-tooltip.js):- a thick-glass capsule 48 px tall, 20 px Semibold, below its owner by default;
data-lgs-tip="above"for GP's action cluster; - 0.8 s in and 0.2 s out for both laser and gamepad (VP P-12). Both are delays: the 250 ms materialize starts after 0.8 s of attention, and the 350 ms dematerialize starts 0.2 s after attention ends; attention that returns within the 0.2 s keeps the tooltip (D2 §11, MO §4.17, Apple's sample). IN-7 tests exactly this [R2-8];
- the one exception: GP's icon-only cluster controls show at once under gamepad focus (
data-lgs-tip-pad="now"); - bar tooltips get the 0.8 s delay through a
ShowTooltipwrapper (CTL P-C5).
- a thick-glass capsule 48 px tall, 20 px Semibold, below its owner by default;
- Sounds: our controls call Steam's sound bus with the same
ENavSoundSteam uses for the same event (IM §6.4). No sound on hover. Steam's UI-sounds setting is honoured. - Haptics: disarmed (S17). No wearer can judge them.
1.14 Home and launcher
HA's decisions HA-1 to HA-14 are adopted, with these corrections from §1:
- The card ramp and name plates are driven by attention (§1.4).
- The focused cell pops +15 mm as a non-interactive crop (§1.7).
- Plates use glassd's new plate primitive (G1); the CSS-only floor is HA §10.5.
- The "+" popup's depth is +25 mm, not +30.
Adopted without change, with D2 §3.5 amended by P4:
- HA-3: LB/RB switch sections on Home; D-pad Left/Right past a row end turns the page.
- HA-4: A opens the game page; X is Play.
- HA-5: one-line labels.
- HA-10: the Liquid Glass switch lives only in the "+" popup and never takes default focus.
1.15 Strings
- Text that T2 or T3 draws comes from Steam's or SteamVR's localization, found by source text (the GP
game-pages-locgrep.shmethod). - Where no string exists, an English string is drawn only when the UI language starts with
en; otherwise the element is icon-only or omitted (SET T-I18N). - Every new string is listed in its package's evidence log, and V2 compiles the list.
- Personal names in live shots (
/chat,/account,/invites) are never quoted in documents.
1.16 Exemptions to the size and audit gates
| Id | Element | Its own criterion | Source |
|---|---|---|---|
| E-BACK | Steam's "Back" text, shown as a chevron circle | aria-label equals the stock text; elementFromPoint at the centre hits it; it grows into a titled capsule after 0.6 s |
WN Q8, CTL C3a, GP Q-B |
| E-MENU | Menu rows | ≥ 60 visible on a contiguous pitch of ≥ 64 (compact) or 78 (regular); rows ≥ 320 wide in one-column layouts, ≥ 280 wide in the two-column grid (slab ≤ 592) [R2-5]. Resolves GP GQ20 | WN §5.1 |
| E-MENU (Cancel) | Steam's appended Cancel item in a menu | Its own line [R2-5]: visible 60 tall (= its element), ≥ 192 wide, ≥ 4 px clear of the last row's visible fill, inside the slab, last in DOM order, elementFromPoint at its centre hits it; judged by this line instead of P-08 and E-MENU's row clauses |
§1.12 |
| E-SWITCH | Switch | Hit ≥ 86 × 80 around its centre | CTL §18.1 |
| E-CHECK | Check circle | Hit ≥ 80 × 80, column pitch 80 | CTL §18.1 |
| E-MINI | Mini circle (clear, disclosure) | Hit ≥ 80 × 80 and no other target within 80 px of its centre (the field it clears excepted) | CTL §18.1 |
| E-SEG | Segment | Contiguous across the track; each ≥ 60 × 140 (120 compact) | CTL §18.1 |
| E-KEY | Keyboard keys | Steam's geometry, identical to stock | CTL C10 |
| E-TAB | Tab-bar items | Pitch ≥ 52 frame-menu px at the live window size | WN D-2 |
| E-GRID | Launcher grid cells (the "+" popup, HA §4) [R2-15] | AUD's SHRUNK only: SIZE, TYPE and OUTLINE judge the cells as usual, and GONE, HIDDEN, UNCLICKABLE and CONTRAST are never waived. Per cell, live: ≥ 80 × m wide and ≥ 96 × m tall (67 × 80 popup px at m .83), abutting its row and column neighbours (gap ≤ 2), its own whole hit (≥ 95 % own, 0 % another target, sampled over the cell) | HA §4, §1.14 |
| E-GRID (labels) | A text run inside an E-GRID cell [R2-15] | AUD's SHRUNK only, its own line: (1) the run lies inside its cell (± 1); (2) it shows ≤ 2 line boxes; (3) no glyph is cut: wherever the run overflows its own box or a clipping ancestor up to the cell by > 1 px, that box draws the ellipsis (text-overflow: ellipsis across, -webkit-line-clamp ≤ 2 down), and lines break only between words or after "/" (word-break and overflow-wrap normal, hyphens not auto); (4) the full name stays in the DOM: the run's text equals the stock run's text or, where T3 draws its own shortened string, the cell's aria-label equals it. The attended cell's run, shown whole as the name plate, meets (1) inside the popup's width (x 0–300) instead of its cell |
HA §4 (Labels) |
| E-BAR | Bar slots | ≥ 64 × 72 bar px | CC D-CC8 |
| E-ROW58 | Storage rows, if the rowHeight patch (LA LQ2) fails |
58 px, with the actions also in the ornament | SET Q9 |
| E-DRILL | Settings sections hidden in one drill-down view | Visible in exactly one other view; T-CNT sums equal stock | SET T-AUD |
| E-WEB | Web content (/steamweb, /externalweb) |
Exempt from size, type and outline checks; not from chrome checks | VP §6 scope |
1.17 Sign-off register: decided without the user
The user cannot be asked. Each item below has a safe default and, where useful, a runtime flag. Defaults live in device/defaults.json (owned by V1). Flags can be overridden for a session in /tmp/lgs/flags.json, which lives in RAM.
| # | Question | Source | Decision | Default | Flag |
|---|---|---|---|---|---|
| S1 | Native layer on by default | WN Q14 | On when the native gate (§4.3) passes | auto |
native |
| S2 | Interactive pops (+30 / +50 mm) | WN Q12, SET §5, SM §4.3, CTL C-D16 | Built; needs one wearer session (SP §12) | Off | interactivePops |
| S3 | Override Steam's entrance animations | WN Q2 | Adopted: the brief asks for Liquid Glass motion. Steam's timeouts are respected | On | — |
| S4 | Back text shown as a chevron (audit exception) | WN Q8, GP Q-B | Adopted (E-BACK) | On | — |
| S5 | Hide A/B legends in laser mode | WN Q13 | Rejected (§1.10) | — | — |
| S6 | Default focus in Power and alerts | WN Q3, CC R7, VP P-67 | Steam's, unchanged | — | — |
| S7 | Sheet push-back (window recedes) | WN Q4, D2 D12 | Off: the sheet comes forward | Off | sheetRecede |
| S8 | Frame-height override (window × 1.067) | WN Q5, SP §7 | Off: it changes the user's window size | Off | frameHeight |
| S9 | Tab bar always visible | WN Q7 | On if WN AT-7 shows the flag cleared and the bar placed | On (gated) | tabBarAlways |
| S10 | Move SteamVR's chrome into one window-bar row | WN Q10 | On if WN AT-17 passes | On (gated) | winbarMove |
| S11 | Ornament pop with an occluder | WN Q11, SM Q13 | Off | Off | — |
| S12 | Home: LB/RB sections, A = page, X = Play, one-line labels | HA-3, HA-4, HA-5 | Adopted; D2 §3.5 amended | On | — |
| S13 | Controller Bindings capsule and Steam Input link | GP Q-A | Built and tested, kept off: it adds navigation into SteamVR pages | Off | vrBindings |
| S14 | More circle on every host, including GP's event "⋯" | GP Q-A (part), WN §3.4.4 | On: a laser path to an existing menu (D2 §10.4 parity) | On | — |
| S15 | CC-A popup variant | CC §4.11 | Ships only if CS10 records PASS | Off | ccPopup |
| S16 | Pointer proxy | WN §3.6, VP P-11 | Native mode only | native |
pointerProxy |
| S17 | Haptics | IM §5 | Disarmed | Off | haptics |
| S18 | Settings sidebar selection at .26 | SET Q12 | Replaced by the selection recipe in §1.4, with its tuning range | — | — |
| S19 | Red fill on destructive confirmations | SET Q4 | Adopted (D2) | On | — |
| S20 | No reordering of destructive items | SM-D8 | Adopted | — | — |
| S21 | Volume HUD moved to 12° below the eye line | CC §6 | Off: head-locked comfort needs a wearer. The new look ships at Steam's placement | Off | hudPlacement |
| S22 | Store navigation ornament (96 px top strip) | SM-D7 | On after SM's S-P passes | On (gated) | storeOrnament |
| S23 | Settings drill-down | SET §4.3 | On after P-S4 and T-CNT pass | On (gated) | settingsDrill |
| S24 | Power menu as a two-column grid | WN §5.2 | Adopted | On | — |
| S25 | Laser dwell before lift (80 ms) | IM §4, VP P-06 | Adopted | On | — |
| S26 | T3 actions run (Home and "+" launches, the game page's Play, Search's Open, launcher rows) [R2-9] | D-P2-1, contracts/react.md §8 |
Built dry-run for the build: every rt.react.actions call is logged, never run. V1 turns it on at release, after RX-7 (with its gamepad phase), HA AT-4 and AT-13 pass on the release build |
Off during the build, On at release | actionsLive |
| S27 | Quiet legend on window routes on a dim band [R2-1] |
WN §3.4.1, O3; VP P-39 | Adopted: P-39 is a must and the bare look measures 1.1–1.5 : 1 over a bright room | "window" |
quietBacking ("window" | "off") |
| S28 | Library ornament in five fixed slots (880 px) [R2-2] | HA §7.1, C2c D-C2c-12 | Built, kept off: the ornament hugs Steam's legends; the laser Sort & Filter pill is the toolbar's trailing group | Off | libFixedSlots |
| S29 | Tooltips fully in at 0.8 s, gone ≤ 0.25 s after leaving [R2-8] | P3-D4 | Built, kept off: PLAN §1.13's 0.8 s and 0.2 s are delays before the materialize and dematerialize (Apple's sample, MO §4.17) | Off | tipQuick |
| S30 | An outside click does not close alerts and sheets (VP P-65) [R2-6] | C1c D9 | Not built: Steam's outside-click cancel stays (a laser path to cancel in every tier, also where a sheet has no close circle). Reserved | Off | modalOutsideGuard (reserved) |
"On (gated)" means V1 enables the flag in defaults.json only after the named test passes. "On at release" means V1 sets it when the release checklist (§4.6) runs, never earlier.
1.18 Amendments to DESIGN2 (applied by P4)
| # | DESIGN2 section | Change | From |
|---|---|---|---|
| A1 | §3.2, §3.3, §7.4, §7.5 | Glass 656 and ornament y 628–712; title at x 100; search 520 / 640 / circle; no microphone | WN D-1 to D-4, D-8 |
| A2 | §3.5 | Windowless routes and plates; LB/RB = sections; one-line labels; folder pages | HA-1, HA-3, HA-5 |
| A3 | §3.7, §3.8 | The two depth profiles, the admission rules and the values table of §1.7; the dimming recipe of §1.8 | §1.7, §1.8 |
| A4 | §6.1, §6.2, §6.6 | θ from the shorter side; E3 lobe instead of the linear top layer; plates and hole treatment | GM §1.7, WN D-15 |
| A5 | §8.2, §10 | Focus add .28 (tuning .28–.32); navigation-row hover = spot only; selection with arc and Semibold; input-mode keying; laser dwell; glyph badges in gamepad mode only | §1.4, VP D-1, D-2, D-5, D-7 |
| A6 | §3.7 | Menu layouts by count; E-MENU; the destructive rule by count | WN D-5, D-16 |
| A7 | §12 | The friends list is not virtualized; drill-down section hiding is allowed under E-DRILL | SM-D9, SET §4.3 |
| A8 | §2.5 | Bar pitch 64 bar px; tab-bar live pitch with a floor of 52 | CC D-CC8, WN D-2 |
| A9 | new §10.5 | Feedback: Steam's sounds through its bus; haptics off | VP D-4, IM §6 |
| A10 | §11.5 | Route transition overrides allowed within Steam's timeouts | S3 |
| A11 | §17 | The verification checklist points to PLAN §4 | — |
| A12 | §5.2 | theme/01-font.nowrap.css now exists (generated) |
— |
| A13 | §14 | Tier status updated from the capability studies (T3 proven; T4 click unproven; plates and holes planned) | SR, SP, E2E |
| A14 | §3.2, §3.7, §3.8 | Round R2 (§1.19): the quiet legend's dim band on window routes (R2-1); the library ornament hugs its members (R2-2); menu layout by count with 7–14 in the grid, E-MENU widths and Cancel at 60 (R2-5); alerts and sheets placed on the glass (R2-6); the source card at 0 with a glow (R2-3); the measured t1 dimming (R2-4) |
§1.19 |
1.19 Amendments from the build (R2)
The coordinator decided these on 2026-10-07 (Steam build 11094443); R2-15 and R2-16 came in a second pass the same afternoon. They come from the requests packages filed while building (grep -n "REQ [A-Za-z0-9-]*->[Cc]oordinator" docs/phase2/wp/*.md) and from open review notes. Each one is applied in place in §0 to §5 and marked [R2-n] there. The decision log, with the evidence read for each, is docs/phase2/wp/coordinator.md. The requests to the owners of files that must change are filed there too.
How they were decided.
- §1 decides conflicts.
- A Steam function, with its laser path and its gamepad path, is never traded for a look.
- Between two looks, the one closer to visionOS (VP) wins when it can be measured.
- Every "must" item of VP §6 passes, or it is a recorded deviation in §4.4.
- Every rule below has a check that an agent can run.
| Id | Topic | Asked by | Changed |
|---|---|---|---|
| R2-1 | Quiet legend on window routes: the dim band |
C1a (WN O3) | §1.10, §1.17 S27 |
| R2-2 | The library ornament hugs its members; Steam's laser Sort & Filter goes in the toolbar | C2c REQ-8, C1a | §1.1, §1.10, §1.17 S28, §2.4 C2c |
| R2-3 | Source card while its menu is open: 0 mm with a glow | C2c REQ-7, C1a (WN O4) | §1.7 |
| R2-4 | The t1 tint, measured (SG-5) |
P7 | §0, §1.8, §2.3 SG-5, §5.1 R9 |
| R2-5 | Menus: layout by count, E-MENU widths, Cancel at 60 | C1c (REQ 6a, gates run 3, D28) | §1.12, §1.16 |
| R2-6 | Alerts and sheets on the glass; P-65 and P-67 deviations | C1c (REQ 6b, 6c) | §1.12, §1.17 S30, §4.4 |
| R2-7 | Focus tokens named: --lgs-white-glow, focus add .32 |
C4a (and C1a's REQ to P4) | §1.4 |
| R2-8 | Tooltip timing test (IN-7, CTL C9) | P3 | §1.13, §2.3 P3, §1.17 S29 |
| R2-9 | Release switch for T3 actions (actionsLive) |
P2 | §1.17 S26, §4.5, §4.6 |
| R2-10 | Achievements are window |
C1a (WN O1) | §1.2 |
| R2-11 | VP P-23 scored at 612 | C1a (WN O2) | §4.4 |
| R2-12 | Menu morph without a glassd morph | P9 review R1 m2 | §1.5 |
| R2-13 | Performance verdicts on a shared device | P1 (RT-7, review R1 m1) | §2.3 P1, §4.1 G-PERF |
| R2-14 | The /chat search circle follows the sidebar |
C7 (SM-D2 rev. 3) | §1.9 |
| R2-15 | Launcher grid cells and their labels (E-GRID) | P10 (REQ P10->Coordinator), C2b review R2 (M1, M4) | §1.16, §2.4 C2b, §5.1 R23 |
| R2-16 | theme/layers/21-search.json belongs to C1b |
C1b review R2 (m3) | §2.4 C1b, §2.6 |
R2-1 Quiet legend on window routes: the dim band
- Was: §1.10 gave every quiet legend "no capsule material, labels Medium white .70".
- Evidence:
- On
windowroutes the labels sit in the ornament margin, over the room. - Over a bright room they measure 1.1–1.5 : 1 (C1b #3, C1c REQ 4, C1a: background L 209–236 behind "Open" and "Select"). VP P-39 is a must, at 4.5 : 1.
- With C1a's band they measure 4.9–6.7 : 1 over the sill and 5.1–6.0 : 1 over the curtain (WN §13.1).
- On
- Now:
- §1.10's band: black .62, 60 px, radius 30, 7 px blurred edges, the members + 10 px; no rim, sheen,
backdrop-filter, plate or slab. - Labels 22 px Medium white .82 with the on-room shadow.
window-fullroutes keep the bare look inside the glass.- Flag
quietBacking, default"window"(S27).
- §1.10's band: black .62, 60 px, radius 30, 7 px blurred edges, the members + 10 px; no rim, sheen,
- Why:
- A legend that cannot be read is a lost path.
- The band is a shadow on the room, with no edge and no frost. §1.10's "no capsule material" and the no-outline rule both still hold, and nothing reads as an extra slab.
- Check:
- WN AT-29 (C1a):
glass.py audit main --route /settings/systemin both modes gives CONTRAST = 0, and the band's computed style matches the values above. - The mockup measurement of WN §13.1: quiet labels ≥ 4.5 : 1 over the curtain (L ≥ 230).
- G-OUTLINE: the band's edge probe reads
edge: "none". - Native:
sgcheckreports no plate and no slab for#Footerwhiledata-lgs-orn="quiet".
- WN AT-29 (C1a):
R2-2 Library ornament and Steam's laser Sort & Filter
-
Was:
- §1.10: in laser mode,
%{SortAndFilterContainer}moves into ornament slot 1. - Library routes use five fixed slots, "= 880 px". The slots sum to 840; 880 is the capsule.
- PLAN-2c-1: ornament width 880 ± 1 in both modes.
- §1.10: in laser mode,
-
Evidence (C2c REQ-8, session 2):
- C1a's window clip at y 656 cuts every page node in the margin. Steam's pill is a page node, so in slot 1 it was half hidden, and
elementFromPointat it returned#MainNavMenu-Rest. - Steam drops X, Y and ≡ whenever focus is on the header rows, and in laser mode. The fixed capsule then showed up to 434 px of empty slots.
- D-C2c-12 ships the pill as the toolbar's trailing group. Gates PASS on AllGames and on a collection page, in both modes (C2c S2-G1, S2-G2, S2-G6).
- C1a's window clip at y 656 cuts every page node in the margin. Steam's pill is a page node, so in slot 1 it was half hidden, and
-
Now:
- The ornament on library routes hugs its members, as on every other route (WN §3.4.1): centred, ≤ 960, and compact members before anything is dropped. The fixed slots stay built behind
libFixedSlots(off, S28). - In laser mode, Steam's Sort & Filter pill is the toolbar row's trailing group. This is where visionOS puts a window's sort and filter (DESIGN2 §3.2, "Trailing actions"). It is one 60 px capsule holding Steam's two buttons, with 80 px hits:
- visible y 24–84;
- its right edge at x 1256 (24 px inside the glass);
- at least 20 px clear of the search field's visible capsule (DESIGN2 §3.2's spacing for trailing actions);
- when Steam's account-alert circle (
#header_profile) shows, the circle stays outermost at the 24 px inset, and the group sits 20 px to its left; - when Steam's labels do not fit, the sort name ellipsizes inside the group (Steam's text stays in the DOM); the gap and the inset never shrink.
- The pill's node, its handlers and its laser-only rendering are untouched.
- In gamepad mode the same functions stay Steam's X and Y legends in the ornament. The Y legend carries the current sort ("Sort By · Alphabetical").
- The ornament on library routes hugs its members, as on every other route (WN §3.4.1): centred, ≤ 960, and compact members before anything is dropped. The fixed slots stay built behind
-
Why:
- Both paths keep every function: the laser has the toolbar group, the gamepad has X and Y.
- A capsule that shows empty slots, or a control that the window clips, is worse than a capsule whose width follows Steam's legends. Every other route already behaves that way.
-
Check (replaces PLAN-2c-1). Run on
/library/tab/AllGamesand/library/collection/<id>with--flags wp.p3,wp.c1a,wp.c2c, in--mode laserand--mode pad, with focus in the grid and on the tab row (glass.py jsreading rects):- The capsule spans its members: left = the first member's left − 12, right = the last member's right + 12 (± 1). It is centred at x 640 ± 1 and ≤ 960 wide. No gap between adjacent members is wider than 14 px.
- Laser mode: the pill's visible capsule is at y 24–84 (± 1), with its right edge at 1256 ± 1 and its left edge ≥ the search capsule's right edge + 20.
elementFromPointat the centre of Sort and of Filter returns that button. - No legend node and no pill node has
display: noneorvisibility: hidden. G-AUD has no GONE, HIDDEN or UNCLICKABLE.
With
libFixedSlotson, the old check (880 ± 1 at x 200–1080, in both modes) still applies.
R2-3 Source card while its menu is open
- Was: §1.7's table said "It keeps +15 if it was the focused card; otherwise 0 with a CSS glow" (wearer +15). Admission rule 6 says "while a modal is open, only the modal itself pops".
- Evidence:
- P6's reporter applies rule 6 in both profiles (
contracts/reporter.mdadmission step 6,modal: true). - A +15 source beside its +10 menu would stand in front of its own menu.
- C2c (D-C2c-1), C1c (D6b) and C2a (D-C2a-3) already build rule 6. C2c's native session S2-N2 had the sheet open and the poster not popped.
- P6's reporter applies rule 6 in both profiles (
- Now: the source card drops to 0 with a CSS glow, in both input modes and both profiles. The control that opened the menu (More circle, Manage) is white .94 (§1.4).
- Why:
- The menu must be in front of its source. In visionOS a presented menu is the frontmost layer.
- One rule, enforced in one place.
- Check:
- In a native session, run
sgcheckwith the tile menu open, twice: once from a gamepad-focused poster (--mode pad, ≡) and once from the More circle (--mode laser --hover). Pass: exactly one pop onmain(the menu, 10.0 mm), the source at 0, and ≤ 4 distinct dz (P-46). - In CSS-only mode, the source's computed
box-shadowcarries the glow.
- In a native session, run
R2-4 Dimming: the t1 tint, measured
- Was: §1.8 said "a
t1tint would dim only the hidden real panel [inferred]. SG-5 confirms this." - Evidence: SG-5 (P7,
native/spike/sg_native.py sg5,contracts/sg.md§5,p7_results/sg5n.jsonl, 2026-10-07 08:38):- With
window.dim0.35, the window glass went from L 73.8 to 45.7, a text row from 108.6 to 65.7, and the popped posters from 95.0 to 57.4. The frame menu, the bar and the room were unchanged. - A surface
dimof 0.35 dimmed the cover and the base only: a popped card stayed at 38.8 → 38.8.
- With
- Now:
- §1.8 as amended:
t1dims the whole visible window, pops included. - In-window modals use the CSS scrim only, in both modes.
- CC-A uses
window.dim 0.6alone. - A window that must dim behind a bright popped element uses the surface
dim. - SG-5's expectation (§2.3) and R9 (§5.1) follow.
- §1.8 as amended:
- Why:
- Nothing that ships relies on either path (CC-A is gated, S7 is off).
- But the stated reason was wrong, and CC-A would have compounded two dims (0.36).
- Check:
- SG-5, as recorded.
- For CC-A (C3b, once CS10 passes): the report carries
window.dim 0.6and no surfacedimonmain(__LGS_SG.dump()).
R2-5 Menus: layout by count, E-MENU widths, Cancel
- Was:
- §1.12: "6–7: compact"; "≥ 8: two columns, ≤ 592 wide"; Cancel "a 56 px quiet capsule".
- §1.16 E-MENU: "rows ≥ 320 wide".
- Evidence (C1c gates run 3, 07:04–07:11, and D28):
- Two columns of ≥ 320 cannot fit a ≤ 592 slab (2 × 320 + 8 + 16 = 664). Steam's own row min-width is 280.
- Measured live, 7 compact rows with a title and Cancel are 564 px tall. That is beyond the 508 px menu box (y 108–616, C2c REQ-6).
- Cancel at 56 fails VP P-80, a must (≥ 60 visible). It also has no line of its own in E-MENU.
- Now:
- §1.12's count table: ≤ 5 one column; 6 compact; 7–10 the grid; 11–14 the grid, with the row area scrolling inside the slab. Value menus follow the same table.
- E-MENU widths: ≥ 280 in the grid, ≥ 320 elsewhere.
- Cancel: 60 visible on its 60 px element, ≥ 192 wide, ≥ 4 px clear of the last row, with its own E-MENU line.
- Why: each change keeps the function and moves closer to VP.
- The grid gives 7-action menus 72 px rows (P-64) instead of compact ones.
- Cancel at 60 meets P-80 at no cost in height, because its element is already 60.
- Check:
glass.py gates main --pre <menu recipe>in both modes, for MENU with 5, 6, 7, 8, 10 and 14 items, the tile menu, POWER and the Sort menu. Pass: G-SIZE has no P-08 or P-80 failure on menu items, and every E-MENU line passes (P10's criterion per row, and the Cancel line).- WN AT-11b: the class matches the count table;
scrollHeight == clientHeightup to 10 items; the slab is inside y 108–616 and ≤ 600 wide.
R2-6 Alerts and sheets: placement, outside click, default focus
- Was:
- WN placed alerts and sheets "centred in the modal box": centre 368, 40 px off the glass centre. VP P-35 asks for ± 24.
- §4.4 listed only P-11 and P-37 as deviations.
- Evidence:
- C1c D7: alerts centred on the glass; sheets from y 108, ≤ 488 / 552 tall. Live on ZOO('Scroll Panel Test'): 958 × 487 at (161, 109).
- C1c D9 (outside click) and D10 (default focus).
- Now:
- §1.12's placement rule.
- Outside click: Steam's
ModalClickToDismisskeeps cancelling alerts and sheets. With the runtime off (and so without C1c's close circle), it is the only laser dismiss on a sheet that has no button of its own, and it can only cancel. - Default focus: Steam's (S6).
- Both are recorded deviations in §4.4. The guard flag
modalOutsideGuardis reserved, not built (S30).
- Why:
- P-65's goal is no accidental loss. Steam's design already meets it: an outside click never confirms. Removing a laser path would break "every function keeps a laser path" in the CSS-only tier.
- P-67 would change what A does on Steam's own confirmations. S6 already decided against that without a wearer.
- Check:
- DOM after open (
glass.py jswith the CONFIRM and ZOO('Scroll Panel Test') recipes, both modes): the alert's centre is within 24 px of (640, 328) onwindowroutes; the sheet's top is 108 ± 1, its height ≤ 488 (552 onwindow-full), and its centre x 640 ± 1. - C1c-3: an outside click cancels exactly as stock and never confirms (a spy on the dialog's
onOKrecords 0 calls). - WN AT-12: the item focused on open is the same with
wp.c1con and off.
- DOM after open (
R2-7 Focus tokens named
- Was: §1.4 gave the white-fill glow as
0 0 18px 2pxwhite .30, and left the focus add between .28 and .32. - Evidence:
- C1c E8, C3a and C4a's
controls-measure.py: the old glow gave +4 to +12 L in the P-16 band, which needs ≥ +20. - P4's
--lgs-white-glow(0 0 22px 8pxwhite .55) gives +23 L in the bright room and +27 L in the dim one. - P4-D8 set
--lgs-focus-addto .32 after C1a's G-FOCUS on the tab bar: .28 gave +35.2 / +7.6 (FAIL), .32 gave +42.5 / +15.1 (PASS).
- C1c E8, C3a and C4a's
- Now:
- §1.4 names both tokens. Their values live in
theme/00-tokens.nowrap.css(P4). - PLAN carries the names, so a later tuning by P4 does not reopen PLAN.
- DESIGN2 A5 already names the glow token.
- §1.4 names both tokens. Their values live in
- Check:
- G-FOCUS as before: the P-16 band ≥ +20 L on white and coloured fills; focus ≥ +40 L over rest; focus ≥ selected + 15 L.
python glass.py status: 0 unresolved tokens.
R2-8 Tooltip timing test (IN-7 and CTL C9)
- Was: P3's IN-7 and C4a's C9 said "none at 500 ms; the label at 900 ms; gone ≤ 250 ms after leaving". But §1.13, DESIGN2 §11 and MO §4.17 (Apple's sample) make 0.8 s and 0.2 s delays, before a 250 ms materialize and a 350 ms dematerialize.
- Evidence (P3, measured on computed opacity,
wp/P3.mdIN-7):- The glass shows from about 820 ms, and the label reaches 0.9 by about 1050 ms.
- The leave starts about 210–230 ms after attention ends. The label is gone by about 400–420 ms, and the node is hidden by about 560–610 ms.
tipQuickmeets the card's literal numbers. But when the laser leaves and comes back within about 180 ms, the capsule dips to about 0.6.
- Now:
- §1.13's timing stands: the numbers are delays.
- IN-7's pass criterion is amended (§2.3 P3), and C9 follows it.
tipQuickstays off (S29).
- Why:
- §1 decides, and it matches Apple's sample.
- The 0.2 s grace absorbs laser jitter, which a laser (unlike eyes) produces all the time.
- Check: IN-7 as amended, in both input modes, with P3's runner (opacity sampled on the tooltip node's capsule and label).
R2-9 Release switch for T3 actions
- Was: nothing in PLAN named P2's
actionsLive. HA AT-4 and AT-13 and the §4.5 ledger's static check all pass while every T3 launch is a logged dry run. - Now: S26 (§1.17), a rule in §4.5 and an item in §4.6.
- Why: a release that forgets the switch silently loses Play, launches and Open from every T3 view. No gate would catch that lost function.
- Check: V2's shipped-state check, with the shipped
defaults.jsonand no--flags:python glass.py js "__LGS_RT.react.actions.mode()"returnsreasonsexactly['runtime action logger on'](the lab step's own logger).mode(ev)for Steam's programmatic click (new PointerEvent('click'),pointerType '') adds no other reason.
R2-10 Achievements are window
- Was: §1.2's table put achievements in
window. Its note called "SM's quiet-legend routes"window-full, and SM listed achievements among them. - Now:
- The table holds.
- SM revision 3, WN §3.1.1 and C5a already build
window(656). - The quiet legend sits in the margin, on the dim band (R2-1).
- Check: on
/library/app/<appid>/achievements/my/individual, withwp.c1a,wp.c5a:%{BasicUiRoot}[data-lgs-glass="window"].
R2-11 VP P-23 after A1
- Was: VP P-23's bottom bound is 620 (the ornament's top at 636 − 16). A1 moved the ornament to 628.
- Now:
- P-23 is scored at top ≥ 124 and bottom ≤ 612 on routes with a bottom ornament (C1a's
--lgs-guard-bottom). - Without an ornament: bottom ≤ the glass bottom − 16 (704 on
window-full). - VP stays frozen; §4.4 carries the bound.
- P-23 is scored at top ≥ 124 and bottom ≤ 612 on routes with a bottom ornament (C1a's
- Check:
glass.py conformance --pad --only P-23with the 612 bound. P10'stools/p2/conformance.pystill says 620: the REQ is in the coordinator's log.- WN AT-2's guard.
R2-12 Menu morph without a glassd morph
- Was: §1.5 said "glassd plays
morph-close". - Evidence:
- glassd v3 has no morph (
contracts/glassd.md§6; C1c D15). - P9's review R1 (m2) asked for a sign-off.
- P4 amended DESIGN2 §11 (REQ P9->P4).
- glassd v3 has no morph (
- Now: in T5 the slab materializes in place on its
phasewhile the CSS clip-path draws the open morph, and glassd dematerializes it on close. - Why:
- The visible open morph is the CSS clip-path in both tiers, so nothing the eye sees is lost.
- A glassd rect morph would add a second animation path that must be kept in step with the CSS one.
- Check: PLAN-1c-2's filmstrips (G-MOTION) of the menu opening and closing; GL-4 and MO-5 for the phase ramps.
R2-13 Performance verdicts on a shared device
-
Was: RT-7 and G-PERF said "no new frames over 34 ms".
-
Evidence (P1: runtime.md §9, review R1 m1):
- On the shared device, theme-only runs alone differ by up to 7 long frames per 3 s.
- Of six RT-7 runs, four met the strict reading. The other two were inside their A/A spread.
-
Now: RT-7 and G-PERF use one statistic:
- ABBA × 2, with a second round pooled if the first fails;
- median fps ratio ≥ 0.95 against the reference (theme only for RT-7, stock for G-PERF);
- median extra long frames (> 34 ms) ≤ the reference's A/A spread (minimum 1).
A CSS-only verdict pools only runs whose step line reports
native=off. -
Why:
- The strict reading fails on other agents' noise, not on our cost.
- The A/A spread is that noise, measured in the same session.
-
Check:
- The numbers above, from the run's JSON.
- P10 is asked for an ABBA mode of
perf, so that every owner computes the statistic the same way (the REQ is in the coordinator's log).
R2-14 The /chat search circle
- Was: §1.9 put the
/chatcircle's box at (362, 14), the trailing end of SM revision 2's 456 px sidebar. - Now:
- The box follows the sidebar's trailing end: x = sidebar width − 94, y 14. With SM-D14's 512 px sidebar that is x 418.
- This applies only while the circle variant is on (
searchCircle, off today because of WN AT-4's SHRUNK).
- Check: SM's People test with
searchCircleon: the search element's rect is the 80 × 80 box at (sidebar width − 94, 14).
R2-15 Launcher grid cells and their labels (E-GRID)
- Was: §1.14 adopts HA §4's grid for the "+" popup (4 columns of 72 × 96 popup px) and the C2b card builds it, but §1.16 had no line for it. G-AUD therefore counted every cell as SHRUNK: Steam's 260 × 40 rows (10 400 px²) become 72 × 96 cells (6 912 px², 66 %), under AUD's 85 % area rule. PLAN-2b-1 could not pass. The labels inside the cells are SHRUNK too: a name on one line of 260 px becomes at most two lines of 70 px.
- Evidence:
- P10 (REQ P10->Coordinator):
lab/exemptions.jsonhas carried E-GRID since session 4, scoped to AUD's SHRUNK.gates barpopup --pre <OPEN> --only aud,size --mode laser(11:15, T1, native off): AUD PASS with 35 E-GRID waivers, each cell "72 x 96, gaps 0 / 0, hit 100% own, 0% other"; SIZE PASS on all 24 cells. - C2b review R2, M1: 11 of the 35 waived records are label runs, which the cell criterion does not judge. M4: 9 of 23 labels are cut mid-glyph with no ellipsis (
scrollWidth > clientWidth: Chromium 73 > 70, LXTerminal 78, RenderDoc 78 and six more). C2b's log lists the word-aware "…" as "not built". - HA §4 (Labels): lines break only between words or after "/"; a word too long for its line is cut with "…"; the full name shows on a plate on focus or after 0.4 s of attention, clamped inside the 300 px host, and the neighbours' labels under it fade to .22. HA §0.3: 11 of 23 names cannot fit two lines at the 15 px floor. HA §11: the plate materializes in 250 ms after 0.4 s, and dematerializes in 350 ms.
- P10 (REQ P10->Coordinator):
- Now:
- §1.16 gains E-GRID for the cells: it waives AUD's SHRUNK only, and only while the cell meets its live criterion (size, abutting neighbours, the whole cell its own hit).
- §1.16 gains E-GRID (labels), a line of its own for the text runs inside the cells: a label's SHRUNK is waived only while it stays inside its cell, shows at most two lines, cuts no glyph (the box that clips it draws the ellipsis; breaks between words or after "/") and keeps the full name in the DOM.
- C2b's "not built" word-aware "…" is not accepted as a deviation: a cut glyph fails clause (3).
- Knowing which program a cell launches is part of the launch function, so the full name on attention is a card test: PLAN-2b-4 (§2.4 C2b). It runs in T3 and in R23's T1 fallback. Without T3 the attended cell's own label shows the whole name in the plate's look, in CSS: on
.gpfocusin gamepad mode, and on:hoverafter the 0.4 s dwell in laser mode, keyed on the input mode (P-01, P-02).
- Why:
- SHRUNK is AUD's area proxy for P-08: VP's check for P-08 is "AUD SHRUNK = 0". Here the proxy misreads the design. Each cell is 87 × 116 main px and the whole cell is the hit, above P-08's 80 × 80 on both sides; Steam's rows were 48 main px tall. The cells are the better targets, and all 23 programs fit without scrolling (the stock list hides 40 %, HA §1.3). Both paths stay: click a cell, or the D-pad and A.
- A label's SHRUNK is a different loss, of text. visionOS shortens a name with an ellipsis and shows the whole name on attention; it never cuts a glyph. The label line waives exactly that, and nothing more.
- Both waivers are live. A cell that shrinks, opens a gap or loses part of its hit fails again, and so does a label that clips.
- Check:
- PLAN-2b-1:
glass.py gates barpopup --pre <OPEN>in T1 and in T3 (--flags wp.c2b), in both modes, fromnative=offsteps. Pass: AUD has 0 issues; every E-GRID entry underexemptsayswaived: SHRUNK, with awhythat names the cell's numbers or the label's clauses; no line says the E-GRID criterion is not met. - PLAN-2b-4, as in the C2b card.
- P10's offline fixture (
tools/p2/test_gates_page.py) adds four label cases: an ellipsized label (waived), a label cut without an ellipsis (SHRUNK kept), a three-line label (kept), and a label that T3 shortened while the cell has noaria-label(kept).
- PLAN-2b-1:
R2-16 theme/layers/21-search.json belongs to C1b
- Was: §2.6 had no row for this file, C1b's card listed no fragment, and its tiers were T1–T3. A file that is not listed is the coordinator's, and a package creates new files only inside its listed patterns (§2.6).
- Evidence:
- C1b wrote the search sheet's native rule in this file (C1b log, session 2; REQ C1b->C1c #8 withdrawn). The round's task asked every area for its own fragment, and §2.1's naming gives it NN = 21. Its rules are behind
wp.c1b. - C1b review R2 (m3) asked the coordinator to confirm.
- The coordinator's ownership sweep (every file under
theme/,device/,lab/,tools/,native/anddocs/phase2/against §2.6,_wipfolders excepted) found no other unlisted file outside the frozen inputs.
- C1b wrote the search sheet's native rule in this file (C1b log, session 2; REQ C1b->C1c #8 withdrawn). The round's task asked every area for its own fragment, and §2.1's naming gives it NN = 21. Its rules are behind
- Now:
- The file is C1b's: §2.6 has the row, and the C1b card lists it with a T4 line.
- Like every area fragment, it follows §1.7 and §1.8: the sheet rule is
modal: true(rule 6), and under a pop the hole'sfillfollows the scrim. C1b review R2's m1 and m2 are C1b's to fix in its own file.
- Check:
grep -n "21-search.json" docs/phase2/PLAN.mdfinds the §2.6 row and the C1b card.- G-DEPTH in a native session with the search sheet open: the sheet is the only pop on
main(rule 6), at 10 mm.
2. Work packages
2.1 Rules for every package
Milestones.
| Milestone | Meaning |
|---|---|
| M0 Conformance | Context packages: the owned concept text and mockups are updated to §1, mockups re-rendered with tools/mockshot.py. Platform packages: none |
| M1 Contract | The package's contract file in docs/phase2/contracts/ is written. Code exists as stubs that load without errors and do nothing (behind flags) |
| M2 T1 | CSS complete for the package's routes; gates G-AUD, G-SIZE, G-TYPE and G-OUTLINE pass in CSS-only mode |
| M3 T2/T3 | Runtime code complete behind the package flag; the package's own tests pass in both input modes |
| M4 Native | The package's layer, plate and popup fragments are in; G-DEPTH and G-HV pass in a native session |
| M5 Accepted | V2 has run the global gates on the package's routes and recorded PASS |
Definition of done.
- Every acceptance test in the package card passes.
- Evidence is logged in
docs/phase2/wp/<ID>.md: command, result, shot names, date, Steam build. - No file outside the package's set was changed (§2.6).
lgs offleaves nothing behind (G-REMOVE).
Flags. Each context package's runtime code is gated by a flag wp.<id> (for example wp.c2a), off by default until V2 accepts the package. Tests turn it on only inside their locked step, with the lab option --flags wp.c2a (P10), so other agents keep seeing the integrated state.
Sync safety. python glass.py sync uploads everyone's files (LAB.md), so a broken file breaks everyone.
- Work in progress goes in
theme/_wip/ordevice/rt/_wip/, which the bundler and the runtime loader skip (P1). - Run
python glass.py check-theme(P1) before saving intotheme/: offline brace and nesting check,@font-face/@keyframesplacement. - Runtime modules load fail-closed (P1).
Naming.
| What | Name |
|---|---|
| Live shots | shots/p2_<id>_<what>.png (for example p2_c2a_home_live.png) |
| Comparisons | shots/p2_cmp_<id>_<what>.png |
| Motion filmstrips | shots/p2_motion_<id>_<interaction>_<f>.png |
| Runtime modules | device/rt/<NN>-<name>.js, where NN is the owner's theme number (§2.6) |
| Fragments | theme/layers/, theme/popups/, theme/sg/, <NN>-<name>.json with the same NN |
| SteamVR page scripts | device/vr/<page>.<name>.js, injected by the daemon into page <page> (P8) |
| Daemon plugins | device/shell_ext/<name>.py (P8 loads them) |
Never-list (LAB.md): never launch games or programs, change setting values, confirm dialogs, message, purchase, sign out, take power actions, or keep frames of the room. Launch-type calls in tests go through the action logger (P2), which logs them and never runs them.
2.2 Package list
Sizes: S ≈ 1 agent-session, M ≈ 2–3, L ≈ 4 or more.
| ID | Package | Tiers | Depends on (milestone) | Size | Wave |
|---|---|---|---|---|---|
| P1 | Runtime core and lifecycle | T2/T3 host | — | M | A |
| P2 | Steam React framework | T3 | P1 M1 | M | A |
| P3 | Interaction runtime (input mode, attention, states, tooltips, sounds) | T2 | P1 M1 | M | A |
| P4 | CSS foundation (tokens, font, material, states CSS, kit, DESIGN2) | T1 | — | M | A |
| P5 | Motion library | T1, T4, T5 | — | S | A |
| P6 | Compositor, Steam side (reporter, fragments, popup wrapper, native CSS) | T4, T5 | P1 M1, P9 M1 | L | A |
| P7 | Scene graph (depth channel, transform overrides, dim wrappers) | T4 | P5 M1 | M | A |
| P8 | Daemon (lgs-shell) | T4, T5 | P6 M1, P7 M1, P9 M1 | M | A |
| P9 | glassd (plates, holes, tints, keyboard surface) | T5 | P5 M1 | L | A |
| P10 | Lab and verification tools | — | P1 M1 | M | A |
| C1a | Shell chrome (window, toolbar, tab bar, ornament, window-bar row, More, pointer proxy) | T1–T5 | P1–P4 M1; P6, P7 M2 for M4 | L | A′ |
| C1b | Search | T1–T3 | C1a M2, P2 M3 | M | B |
| C1c | System presentations (menus, alerts, sheets, Power, transitions) | T1, T2, T4 | P4, P5 M1; P6 M2 for M4 | M | A′ |
| C2a | Home, folders, What's New | T1–T5 | P2, P3 M3; C1a M2; P6, P9 M2 (plates) | L | A′ |
| C2b | Launcher ("+" popup) | T1, T3, T4 | P2 M3; P6 M2 (popup wrapper) | M | B |
| C2c | Library catalogue | T1–T4 | C1a M2 (ornament), C1c M2 (menus) | M | B |
| C3a | Bar, HUD, toasts, tooltips look | T1, T2, T4, T5 | P4 M1; P6 M2 | M | B |
| C3b | Control Center (CC-M; CC-A spike) | T1–T5 | P2 M3; P9 G1 (plates); P8 M2 | L | B |
| C4a | Controls and primitives | T1, T2 | P3, P4 M1 | M | A′ |
| C4b | Keyboard | T1–T5 | P3 M3; P9 keyboard surface | M | B |
| C5a | Game pages, achievements, Properties | T1–T4 | C1a, C1c, C4a M2; P9 G2/G3 for over-art depth | L | B |
| C5b | Now Playing, binding UI, VR bindings link | T1, T2, T3 | P8 M2 (vr scripts, actions) | M | B |
| C6a | Steam Settings | T1–T4 | C4a M2; P2 M3 | L | B |
| C6b | SteamVR Settings | T1, T2 | P8 M2 (vr scripts); C4a M2 | M | B |
| C7 | People, Photos, Downloads, Store | T1–T4 | C1a, C1c, C4a M2; P2 M3 (lab route) | L | B |
| V1 | Native gate and integration | — | P6–P9 M2; C1a M4 | M | C |
| V2 | Verification suite and conformance | — | P10 M2; each package M3/M4 | L | C (harness from A) |
2.3 Platform packages
P1 Runtime core and lifecycle
- Tier: host for T2 and T3. Size: M.
- Owns:
device/lgs.py,device/lgs_core.js,device/lgs_index.js,device/lgs,device/glass-shell.desktop,device/icon.png,device/rt/00-rt.js(new),docs/phase2/contracts/runtime.md(new). - Inputs: SR §3.9, §7; IM §8 (popup created/destroyed callbacks); LAB.md; NAT "Runtime".
- Builds:
- Loader.
lgs onevaluates, after the CSS core, one bundle in SharedJSContext:device/shared/*.js, thendevice/rt/*.jsin name order (_wip/skipped).- Each module calls
__LGS_RT.define({name, deps, flag, install(rt), remove()}). - A module that throws is marked failed; the theme and the other modules keep working.
- Each module calls
00-rt.js:- registry with dependency order,
status()and a log ring; - flags:
device/defaults.json(V1; built-in defaults when absent), overridden by/tmp/lgs/flags.json, and the CLIlgs flags [name=value]; - per-window hooks over
g_PopupManagercallbacks (rt.windows.each,rt.windows.onAdd); - a bridge for daemon messages (
rt.bridge.set('geom' | 'page' | 'acks', …)), used by P8; - test hooks (
rt.test): input-mode stub, action-logger switch,flags.with()for a locked step; - a liveness watch: if
html.lgs-onleaves main for 2 s (another agent's--theme off), every module is removed.
- registry with dependency order,
lgs offremoves modules in reverse order and returns a cleanup report.lgs statuslists module states.- Bundler: skips
theme/_wip/;lgs check(exposed asglass.py check-themeby P10) checks braces and.nowrapplacement offline. - Native argument:
lgs onpassesnative = auto | on | offtolgs_shell.start(P8), withautoread fromdefaults.json.
- Loader.
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| RT-1 | lgs on / lgs off three times with three stub modules |
Identical status() each time; 0 duplicate listeners (subscriber counts as in IM §8) |
| RT-2 | A stub module that throws in install |
The other modules and the CSS are on; status() names the failure |
| RT-3 | Open the "+" popup (LAB OPEN recipe) | rt.windows.onAdd fired for barpopup; a stub class is on its <html> |
| RT-4 | lgs off, then a sweep of every popup |
No __LGS_RT, no lgs-* classes and no data-lgs-* attributes on any window; no patched fibers (patchedLeft: 0) |
| RT-5 | Lab --theme off step |
Runtime modules removed within 2.5 s |
| RT-6 | Persistence scan after on/off | Writes only under /tmp/lgs and /dev/shm/lgs; nothing new under ~/.local/share/glass-shell except synced sources |
| RT-7 | python glass.py perf main --route /library/tab/AllGames with the runtime idle |
Within 5 % of the theme-only baseline; no new frames over 34 ms, read as R2-13's statistic: ABBA × 2, median extra long frames ≤ the theme-only A/A spread (min 1) [R2-13] |
- Fallback: if the loader fails,
lgs onstill injects the CSS theme (Phase 1 behaviour) and reports the runtime as off.
P2 Steam React framework
- Tier: T3. Size: M.
- Owns:
device/rt/02-react.js,device/rt/03-react-lab.js,device/proto/react_proto.js(retired at M5),docs/phase2/contracts/react.md. - Inputs: SR (all); HA §13 (props-shape patches); CC CC3, CC11; SET P-S1; GP AT-T3-ROUTE; SM §8.3.
- Builds (from
react_proto.js):- finders with candidate counts, fail-closed for the required ones (SR §3.2);
- the route-switch patch:
routes.add(path, Component)under/library/lgs/…,routes.override(path, fn(steamChildren)), self-healing on history changes (SR §3.4–3.5); patch.byProps(predicate, wrap)for memo and observer components found by props shape. Targets: the "+" popup{allowLaunchProgram}(HA), the status pill's button (CC3),%{AppButtons}' observer (GP),PagedSettings(SET P-S1);- UI helpers:
ui.Page(GamepadPage with a rootonCancel),ui.ErrorBoundary(a focused Back on error),ui.menu(showContextMenu),ui.modal(showModal); - re-exports of Steam's components (
Focusable,DialogButton,ToggleField,SliderField,DropdownField,ScrollPanel,Menu,MenuItem,ConfirmModal); - data helpers (collections, art URLs,
useNonSteamAppskeyed bystrCmdline); actions:launchNonSteam(cmdline),primary(appid),desktopWindow(id),navigate(path). In test mode they log{fn, arg}and never run (HA §14);- lab route
/library/lgs/lab(03-react-lab.js): mounts Steam's components with fake props for fixtures (SM §8.3) and unmounts them; remove()returnspatchedLeft.
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| RX-1 | The SR §6 selftest, ported | 19 / 19 PASS, run twice; patchedLeft: 0 |
| RX-2 | patch.byProps on the "+" popup memo with a no-op wrapper |
audit barpopup (OPEN pre) unchanged against unpatched; removal leaves patchedLeft: 0 |
| RX-3 | ui.modal over /library/tab/AllGames (CC11 method) |
Route DOM node count unchanged; .gpfocus inside the modal after FocusApplicationRoot(); B closes it; focus back on the same poster |
| RX-4 | Lab route mounts ConfirmModal with no-op props |
Rendered, then unmounted; no route left behind |
| RX-5 | A required finder broken through a test hook | install() throws before patching anything |
| RX-6 | Install time | ≤ 250 ms, once per lgs on (SR §5) |
| RX-7 | Each action in test mode | Logged, not executed; an identity check against Steam's own handler where one exists (HA AT-4) |
- Fallback: finders fail closed; every T3 feature falls back to its T1 form (the concepts' tier tables).
P3 Interaction runtime
- Tier: T2. Size: M.
- Owns:
device/rt/04-input.js,device/rt/05-attention.js,device/rt/06-states.js,device/rt/07-tooltip.js,device/proto/input_mode.js(retired at M5),docs/phase2/contracts/interaction.md. - Inputs: IM (all); CTL §4, §11, P-C1, P-C5, P-C11; VP §1, P-01 to P-12; §1.4, §1.13 here.
- Builds:
- input mode: the two signals of §1.4 on every popup, the stub, and
onChange; - attention:
rt.attend(el, {dwellMs, padImmediate})emits enter, dwell and leave events and sets.lgs-dwell(80 ms) and.lgs-attend-<ms>; - states:
- the light spot:
--hx/--hywritten onpointermove, throttled to frames; lgs-pressedfromvgp_onbuttondown/vgp_onbuttonup(button 1), with a 400 ms safety clear;- the
.lgs-ring-checktag on FocusRing over check circles; - disabled + focus tags;
- the
lgs-focus-inentry class;
- the light spot:
- tooltip layer: in-window, plus the bar
ShowTooltipdelay wrapper; - sounds:
rt.sound(event)maps to Steam'sENavSound(IM §6.4); - haptics:
rt.haptic()disarmed (dry-run).
- input mode: the two signals of §1.4 on every popup, the stub, and
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| IN-1 | IM §8 live table (one locked step, sounds and haptics intercepted) | All 10 popups switch classes on a CDP mouse move and back on L.pad |
| IN-2 | The input stub | Only our classes change; vrGamepadInput and NavigationSource untouched |
| IN-3 | Dwell (IM §4) | .lgs-dwell at 80 ± 10 ms; none during a 45 ms-per-card sweep |
| IN-4 | Gamepad press (CTL C7) | lgs-pressed set on button 1, cleared ≤ 100 ms after the up event; other buttons ignored; never preventDefault |
| IN-5 | Light spot (CTL C8) | Writes follow within one frame; 0 writes while idle |
| IN-6 | First focus frame (CTL C6, opacity half) | 0.60 ± 0.02 at t0, 1.0 at 700 ms |
| IN-7 | Tooltips (CTL C9), measured on computed opacity [R2-8] | Nothing visible before 760 ms; the materialize starts 800 ± 40 ms after attention begins; label opacity ≥ 0.9 by 1100 ms; the leave starts 200 ± 40 ms after attention ends; label opacity 0 by 460 ms and the node hidden by 640 ms after attention ends; attention back within 200 ms keeps the label ≥ 0.9 (no dip); data-lgs-tip-pad="now": label ≥ 0.9 within 300 ms of gamepad focus; m_mapTooltips empty after cleanup |
| IN-8 | Sounds | rt.sound('activate') requests DefaultOk on Steam's bus; nothing on hover (PlayAudioURL intercepted) |
| IN-9 | Removal | IM §8 removal row: subscriber counts back to baseline |
P4 CSS foundation
- Tier: T1. Size: M.
- Owns:
- theme:
theme/00-tokens.nowrap.css,theme/01-font.nowrap.css(generated),theme/03-material.css(new),theme/04-states.css(new),theme/vr/00-vr-tokens.css(new),theme/fonts/**; - device:
device/lgs_lens.js; - tools and mockup kit:
docs/phase2/fontkit.py,docs/phase2/mockups/kit.css,kit.js,_example.html,mockups/assets/LICENSE.md,assets/room-lounge.jpg,assets/room-studio.jpg; - docs:
docs/phase2/DESIGN2.md,docs/phase2/contracts/tokens.md.
- theme:
- Inputs: D2 §4–§10, §13, §16; CTL §4; WN §8.2; GM §1.7; VP P-01 to P-17, P-38 to P-45; §1.3, §1.4, §1.6, §1.18 here.
- Builds:
- tokens (D2 §16 with §1 changes):
- focus add .28;
- selection recipe;
- edge lobe;
- the materials' T1 values;
- Phase 1 token names kept as aliases until the last area migrates, then removed (§6);
- motion tokens are not here: P5 owns them;
- font:
theme/01-font.nowrap.cssgenerated withfontkit.py, and the--lgs-fontsweep rules (D2 §5.2); 03-material.css:.lgs-edge(conic arcs and lobe), the four material recipes for T1, scroll-edge utilities, the clear-over-media dimming layer;04-states.css: the illumination layer keyed by input mode (§1.4); the FocusRing light plate (CTL §4.5); disabled focus; the selection vocabulary; Reduce Motion and High Contrast variants;vr/00-vr-tokens.css: the shared SteamVR values moved out ofvr/10-systemui.css§0;- kit: token parity with the theme;
- DESIGN2: amendments A1–A13.
- tokens (D2 §16 with §1 changes):
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| FD-1 | python glass.py status |
0 unresolved tokens |
| FD-2 | Font on main, bar, barpopup, keyboard, notifications, vr:systemui |
document.fonts.check('500 24px "LGS Inter"') true; a CJK string renders; no CSP error; bundle +163 KB measured; perf main within 5 % |
| FD-3 | Token parity script, kit against theme | Every shared token equal |
| FD-4 | Edge profile on a CONFIRM alert and the window (tools/p2/edge_profile.py, P10) |
Ratio ≤ 0.35 on every slab top edge (WN AT-23) |
| FD-5 | /zoo/buttons, /zoo/toggles with L.pad (with C4a) |
G-FOCUS criteria (§1.4); the final focus token is recorded |
| FD-6 | CDP Emulation.setEmulatedMedia reduced motion and contrast (lab lock) |
Fades only; opaque glass with the 2 px edge |
| FD-7 | DESIGN2 review | Each amendment A1–A13 present, with its source |
P5 Motion library
- Tier: T1, T4, T5. Size: S.
- Owns:
docs/phase2/research/springs.py,theme/02-motion.nowrap.css(new),device/shared/motion.js(new),native/shared/motion_tokens.h(new),docs/phase2/contracts/motion.md. - Inputs: D2 §11; MO §3, §6, §8, §9; §1.5 here.
- Builds:
- generator mode in
springs.py(--emit css|js|h); 02-motion.nowrap.css:@propertydeclarations;- easing and duration tokens;
- keyframes
lgs-mat-glass-in/-out,lgs-mat-content-in/-out,lgs-focus-in,lgs-page-in/-out,lgs-arrive,lgs-sheet-in/-out,lgs-toast-in,lgs-morph(clip-path from--sx --sy --sw --sh --sr); - all with
backwardsfill (R11);
motion.js: closed-form springs (MO §8),sample(token, t),settle(token), and a retarget that keeps velocity;motion_tokens.h: the same table for glassd.
- generator mode in
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| MO-1 | Generator output against D2 §11.3 | Byte-identical linear() strings |
| MO-2 | motion.js against springs.py |
Max error ≤ 0.002 at 1 ms steps for every token |
| MO-3 | Keyframes on a test element on /zoo |
getAnimations() empty of lgs-* 1 s after; no resting scale, filter or clip-path left on the element |
| MO-4 | Reduce Motion | Every keyframe falls back to opacity only, ≤ 200 ms |
| MO-5 | glassd compiled with the header (P9 build) | Phase ramps equal the table (test_phase.sh, GM §5.5) |
P6 Compositor, Steam side
- Tier: T4, T5. Size: L.
- Owns:
device/lgs_layers.js,theme/layers.json(retired),theme/layers/00-base.json,theme/layers/99-legacy.json(temporary),theme/05-native.css,device/rt/08-popups.js,theme/popups/00-base.json,docs/phase2/contracts/reporter.md. - Inputs: NAT (interfaces); E2E §1; SP §2.4, §2.6, §3; §1.2, §1.6, §1.7 here; HA §10; SET §5; SM §4; GP §3.5, §6; CTL §14.
- Builds:
- Fragments. The loader merges
theme/layers/*.jsonin name order.00-base.jsonholds the surfaces (main, bar, barpopup, frame.menu, floatingfooter, notifications, volumelevel; the keyboard comes from C4b's fragment).99-legacy.jsonholds Phase 1's rules. An area fragment lists"supersedes": [ids]to drop legacy rules it replaces.- A fragment may replace a surface's
coveronly if it declares"owns": [surface].
- Glass modes and plates.
data-lgs-glasssets the main surface's mode (§1.2).data-lgs-plate="<material>"elements are reported as plates (withdata-lgs-plate-phase,-tint,-fill,-occluder).data-lgs-mosaicbands restrict the base mosaic (HA §10.2).
- Layer rule fields:
profile(default | wearer),interactive;- the click-safe cap of §1.7, computed from the smallest intersecting focusable, snapping down to the allowed set;
hole,tint;excludeselectors (destructive, media);- laser lifts on
.lgs-dwell:hoverand gamepad lifts on.gpfocus; - a warning in
errorswhen a route shows more than 4 distinct dz.
- Popup wrapper (
08-popups.js): wrapsvrPooledPopupStore.SendPendingInstanceParamsToSteamVRand the type-3CreatePooledPopup, applying per-hostz,scaleand flags fromtheme/popups/*.json(SP §3.3, §6.3). Restored on removal, with a TTL in the page. 05-native.css: per-element acks for covers, plates and pops; scroll-edge bands from a neutral scrim; plate surfaces transparent in CSS once acked.
- Fragments. The loader merges
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| RP-1 | Only 00-base + 99-legacy loaded |
Report identical to Phase 1's layers.json report on Home, AllGames and a game page (snapshot() diff) |
| RP-2 | Lab route with 20 data-lgs-plate nodes of four materials |
Reported as 20 plates with the right materials and rects (texture px) |
| RP-3 | Click-safe cap: a test rule at dz 0.081 over 72 px rows | Reported at 0.0271 (10 mm) with capped: true; over 40 px targets it is dropped |
| RP-4 | interactivePops false, then true |
Every interactive true only in the wearer profile |
| RP-5 | CONFIRM with a destructive button; a media route | No pop reported |
| RP-6 | Laser lift (CDP hover with dwell) and gamepad lift (L.pad) on posters |
card reported in both modes; none during a 45 ms sweep |
| RP-7 | Popup wrapper on a test host (SP E2 method) | Requested z and scale read back from systemui's scene graph; the frame-menu flag cleared; all restored after removal |
| RP-8 | 05-native.css regression with fakeglassd (lgs_shell.py start --native --glassd fakeglassd --stay) |
Glass dropped only on acked elements; plates acked |
P7 Scene graph
- Tier: T4. Size: M.
- Owns:
device/lgs_sg.js,theme/sg/00-base.json,native/spike/**exceptfakeglassd.cppandhvgrab.cpp,docs/phase2/contracts/sg.md. - Inputs: NAT "Scene-graph facts"; SP §4, §5, §6.2, §8, §9; WN §3.3.6, §3.5; §1.5, §1.7, §1.8 here.
- Builds:
- Depth channel: dz changes follow the
depthspring (device/shared/motion.js, prepended by P8), pushed at ≤ 60/s only while moving, then one final push. Steady-state pushes stay ≤ 15/s. - Transform overrides from
theme/sg/*.json:- targets: the frame's left side panel (tab bar +15 mm),
bottom-controls-transform's child (frame controls),DashboardGrabHandleTransform(window bar), tooltip panels, the resize corner; - always composed with React's values, re-applied on each push, restored in
finallywith a TTL (SP §5, §9.5).
- targets: the frame's left side panel (tab bar +15 mm),
- Dim wrappers:
tintnodes on a surface's cover and base pieces (dimin the spec), for CC-A and the recede variant. - Profiles:
interactiveandsteam-input-appidset only in the wearer profile. - Ordering of plates, occluder plates, base, slabs and pops.
- Retire hygiene for every removed sgid (SP §9.1).
- Depth channel: dz changes follow the
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| SG-1 | sg_timeline.py with a 0 → 10 mm step |
Pushed dz values follow the closed-form spring within 0.5 mm; ≤ 60 pushes/s during, 0 after settle |
| SG-2 | 300 add/remove cycles | Rendering continues (E9–E14 regression); no leaked sgids (dump() against DOM) |
| SG-3 | Transform override round trip on the frame-control node | DumpLaserOverlays shows the target moved and the same size; restored after clear(); restored by the TTL when the daemon is killed |
| SG-4 | dim on a surface |
Only cover and base pieces wrapped (dump()) |
| SG-5 | The t1 tint in native mode (native session; hvgrab look, then delete) |
Records whether a t1 tint is visible over the cover. The result is written to contracts/sg.md. Recorded 2026-10-07: visible: it dims everything reparented to Steam's panel, pops included (§1.8) [R2-4] |
| SG-6 | Watchdog and freeze regression (NAT: SIGSTOP 15 s) | Nodes off at 12 s, back 1 s after SIGCONT |
P8 Daemon
- Tier: T4, T5 orchestration. Size: M.
- Owns:
device/lgs_shell.py,device/lgs_vr.py,device/lgs_vr_core.js,device/shell_ext/__init__.py,docs/phase2/contracts/daemon.md. - Inputs: NAT (Daemon, Runtime); GM §5.4; GP §4.12 (relay); CC CC15; WN §3.3.2 (geometry); SET §4.12.
- Builds:
- glassd.json v3 passthrough: plates, holes, tints, phase and appear (materialize in and out),
reduceMotion(read from Steam's media query), extra mask rects, the keyboard surface; - acks for plates;
native=auto:start(native='auto')honoursdefaults.json;- geometry publishing: S, r, Hm, the dashboard distance, and the frame-menu panel's
fHeightand clip height fromDumpLaserOverlays, sent to Steam through__LGS_RT.bridgeonce a second while they change.activePage(CC15) is published the same way; - profile flag file:
/tmp/lgs/flags.jsonread forinteractivePops; - actions: a generic
lgsActionbinding that dispatches todevice/shell_ext/*.pyplugins, with type validation, source-window check and rate limit (GP §11 relay rules); - SteamVR page scripts:
device/vr/<page>.<name>.jsinjected into matching pages and removed on stop; - depth channel support: prepends
device/shared/motion.jstolgs_sg.js.
- glassd.json v3 passthrough: plates, holes, tints, phase and appear (materialize in and out),
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| DM-1 | NAT regressions: teardown order, SIGSTOP freeze, restart backoff | As NAT |
| DM-2 | Spec with plates, holes and tints | glassd.json carries them; fakeglassd --dump shows them |
| DM-3 | Geometry | Published values equal the SP §1.2 snippet run in the same step |
| DM-4 | Actions: an echo plugin; an unknown type; a wrong source | Echo returns; the others are rejected and logged |
| DM-5 | A test device/vr/systemui.echo.js |
Injected into vr:systemui, removed on stop |
| DM-6 | lgs on with native: auto and no gate pass in defaults.json |
status.mode css-only, with the reason |
P9 glassd
- Tier: T5. Size: L.
- Owns:
native/glassd/**,native/spike/fakeglassd.cpp,docs/phase2/glassd-material.md,docs/phase2/contracts/glassd.md. - Inputs: GM (all); NAT (glassd); SP §11; HA §10; CC CC9, CC14; GP GQ8a, GQ8b; SET §5, §10 (hole tone); CTL §12.8 (K-G1 to K-G6); SM-D6.
- Builds:
| Id | Feature | Notes |
|---|---|---|
| G1 | Plates | ≤ 32 per surface (today kMaxShapes = 8). Per-plate material (window, panel, liquid, thick, clear, dim), phase, tint, fill, and the occluder variant (brightness .55, no rim; HA §10.2). Drawn at the cover's depth in the two-pass cover (GM §4) |
| G2 | Hole treatment per slab | The slab's own contact shadow (black .35, 6 px y, 18 px blur, its rounded rect) clipped to the crop's rect, plus an optional fill tone |
| G3 | Per-slab tint | Green and blue primary capsules |
| G4 | Extra mask rects and the keyboard surface | K-G1, K-G2; ornaments outside the main window's mask zone (SP §11.2) |
| G5 | Wide-slab lower lip | Re-check the dashed line on wide slabs (GM §6.2) live |
| G6 | Budget | ≤ 2.5 ms median GPU with the Phase 2 scenes |
| G7 | Optional | Cover offset behind its surface (K-G6); a room-dim plate behind the window (SM-D6, flag) |
fakeglassd follows the same contract.
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| GL-1 | tools/test_shapes.py, extended |
32 plates, mixed materials, occluder; unions without holes; shapes: [] draws nothing |
| GL-2 | tools/test_material.sh with holes and tints over --test-backdrop room, --dump-view from 0.35 m off-axis |
Hole reads as shadow; tinted slabs green and blue. Dumps contain no camera data and may be kept as shots/p2_glassd_* |
| GL-3 | glassd --bench with four scenes: Home (19 plates + 1 slab), library window (cover + 3 slabs), CC-M (4 plates), keyboard (+1 cover) |
Median ≤ 2.5 ms at idle clocks, also reported at 903 MHz |
| GL-4 | tools/test_phase.sh with plates |
Ramps equal motion_tokens.h (MO-5) |
| GL-5 | A live native session with a wide slab (hvgrab look, then delete) |
No dashed lower lip; edge-profile ratio ≤ 0.35 |
| GL-6 | fakeglassd contract | Accepts every v3 field; glassd-out.json lists plates and dropped counts |
P10 Lab and verification tools
- Tier: tooling. Size: M.
- Owns:
glass.py,lab/**,tools/mockshot.py,tools/p2/**(new),native/spike/hvgrab.cpp,docs/phase2/contracts/lab.md. - Inputs: LAB.md; VP §6 (verification codes); WN
window-nav-measure.py; SETsettings-pad.js,settings-sel.py; HA AT-10b; D2 §11.9, §17. - Builds (every live command holds
lab.lock, pluslab-vr.lockwhen it touches systemui):
| Command | What it does |
|---|---|
--flags a,b |
On every lab command: flags on for the locked step only |
--mode laser|pad |
Runtime input stub for the step (never Steam's getter) |
--media reduce|contrast |
CDP media emulation for the step |
glass.py check-theme |
Offline bundle check |
glass.py gates SURF --route R [--pre JS] |
G-AUD, G-SIZE, G-TYPE, G-OUTLINE, G-MOTION in one lock, JSON out |
glass.py pad-bfs --route R |
Reachability BFS over the four directions, reversibility, B behaviour, vr-null-tree guard (FocusApplicationRoot() first) |
glass.py focus SURF --route R --pairs … |
Luma of rest, focused, selected and hovered pairs (VP P-14 to P-16; SHOT definition of VP §6) |
glass.py motion SURF --pre … |
getAnimations() token audit; filmstrips at f = 0, .15, .35, .5, .75, 1 |
glass.py sgcheck |
The ghost and click-safe rules over __LGS_SG.dump() + DOM rects (HA AT-10b, SM G9) |
glass.py hv NAME [--offaxis] |
hvgrab on the Frame, fetch, print metrics (glass L inside and outside, edge profile, a doubling score by template match), then delete both copies |
glass.py cmp MOCK.html LIVE.png |
Side-by-side shots/p2_cmp_*.png plus rect deltas of named elements (mockup data-id rects dumped by mockshot.py; live selectors from docs/phase2/wp/<id>-cmp.json) |
glass.py ledger |
Builds the function ledger from the concepts' retention tables (§4.5) |
glass.py conformance |
Runs the automatable VP P-items and prints PASS / FAIL / MANUAL per item |
glass.py native-session --pre … |
Holds /tmp/lgs/native.lock, starts lgs_shell.py start --native --stay, runs the step, returns to --css |
- Acceptance tests:
| Id | Test | Pass |
|---|---|---|
| TL-1 | Each command on the stock UI | Runs; baselines saved in docs/phase2/wp/P10.md |
| TL-2 | focus on shots/p2_window-nav_tabbar.png |
ΔL 40 ± 2; edge reproduces WN §8.2's table ± 3 |
| TL-3 | pad-bfs on /settings/system |
Same reachable set as settings-pad.js |
| TL-4 | sgcheck on a synthetic spec with an uncovered pop |
Flagged |
| TL-5 | hv |
No PNG left locally or on the Frame afterwards |
| TL-6 | Lock audit | Every new live command holds the right locks (code review + a concurrent-run test) |
2.4 Context packages
Each card lists its routes, its sources in the concepts, what it builds, and its acceptance tests. Every context package also runs the global gates (§4.1) on its routes, in both input modes, CSS-only and native. "Their ATs" means the concept's own tests, updated to §1 at M0.
C1a Shell chrome
- Tiers: T1–T5. Size: L.
- Owns:
- theme:
theme/20-shell.css,theme/vr/10-systemui.css,theme/vr/pre/sys_hover.js,theme/vr/pre/panel_sizes.js,theme/vr/pre/audit_inpage.js; - runtime:
device/rt/20-shell.js,device/rt/20-more.js,device/rt/20-pointer.js; - fragments:
theme/layers/20-shell.json,theme/popups/20-shell.json,theme/sg/20-winbar.json; - concept:
docs/phase2/concepts/window-nav.md,window-nav-measure.py; - mockups:
window-nav-shared.css,window-nav-shared.js,window-nav-anatomy.html,window-nav-anatomy-t1.html,window-nav-anatomy-depth.html,window-nav-tabbar.html,window-nav-tabbar-r863.html,window-nav-ornament.html,window-nav-chrome.html.
- theme:
- Routes and surfaces: every
mainroute (frame),frame.menu.*,floatingfooter,vr:systemui(window-bar row). - Inputs: WN §3.1–§3.7, §6, §7 (chrome rows), §8, §9.1–§9.3, §9.8; XC §1; SM §3.0; SET §3.2, §4.11; §1.2, §1.9–§1.11 here.
- Builds:
- T1: window glass and modes; toolbar row; tab bar (two groups, states); ornament; window-bar row look; floating hint.
- T2:
data-lgs-glass;- the CQ1 header (
--basicui-header-heightand theHeaderStorewrite) with the fallback class; - the Large Title route map and Back reveal;
- ornament backing, member tags
data-lgs-btnand the quiet / capsule state; - the frozen target, the More helper (§1.11) and the pointer proxy;
- the live tab-bar pitch from P8's geometry.
- T4: the tab bar +15 mm through the side-panel transform and the always-visible flag (
popups/20-shell.json); the window-bar row moves (sg/20-winbar.json). - T5: the window cover rule in
layers/20-shell.json, superseding the legacyhdr-*,footerandsort-filterpops (none of them pop now).
- Acceptance tests: WN AT-1 to AT-8, AT-8b, AT-17, AT-20, AT-22, AT-23, AT-24, AT-25, AT-26, AT-27, AT-0b, updated for §1:
- AT-24: A and B stay visible as quiet members in laser mode;
- AT-26: the More circle is 60 / 80 and inside its host;
- PLAN-1a-1: the ornament is a capsule when a non-A/B member exists and quiet otherwise, on
/library/tab/AllGames,/settings/systemand/account; no legend node hasdisplay: none; - PLAN-1a-2: G-FOCUS on the tab bar (focus ≥ current-route circle + 15 L);
- PLAN-1a-3:
glass.py cmpagainstwindow-nav-anatomy.html,-tabbar.html,-ornament.htmland-chrome.html.
C1b Search
- Tiers: T1–T4 [R2-16]. Size: M.
- Owns:
theme/21-search.css,device/rt/21-search.js(LgsSearch + the provider API),theme/layers/21-search.json[R2-16],docs/phase2/mockups/window-nav-search.html,window-nav-results.html. - Routes:
/search,/search/tab/*. - Inputs: WN §4, §9.1; XC §2; HA §6; §1.9 here.
- Builds: the trigger on
focusinof the field; the snapshot; the sheet (zero state, results, categories hosting Steam's page); recent searches in memory only; the provider API.- T4 [R2-16]:
layers/21-search.json, the search sheet at +10 mm over athickslab (§1.7's row "Sheets, the search sheet"), amodal: truerule (rule 6), with the hole'sfillfollowing the scrim (§1.8).
- T4 [R2-16]:
- Acceptance tests: WN AT-9a to AT-9e, AT-10 (with C4b's echo), AT-16 (snapshot cost); HA AT-13 against C2a's provider; PLAN-1b-1:
cmpagainstwindow-nav-search.htmlandwindow-nav-results.html; G-PAD from the sheet (Down from the field enters the sheet, B returns to the same poster).
C1c System presentations
- Tiers: T1, T2, T4. Size: M.
- Owns:
theme/22-presentations.css,theme/23-transitions.css,device/rt/22-menus.js,theme/layers/22-presentations.json,docs/phase2/mockups/window-nav-menu.html,window-nav-sort.html,window-nav-power.html,window-nav-alert.html,window-nav-sheet.html,window-nav-motion.html. - Surfaces:
mainmodals and context menus (inventory shell §0.2 recipes CONFIRM, ALERT, MENU, SUBMENU, POWER, ZOO). - Inputs: WN §5.1–§5.4, §7, §9.4–§9.7; CC §5 (glyphs, groups, red confirm, dark text on fills); CTL §8.2; SET §4.5, §4.6; GP §3.4; §1.12, §1.5 here.
- Builds:
- T2: count classes; menu type (action or value); source tracking and the white source; anchoring vars and
translate; destructive tags; Power group labels and glyphs; the sheet close circle; the value-menu grid and scrolling column (the list page is C6a's). - T1: slab, rows, tones, alert and sheet cards, scrim .35; route transitions.
- T4:
layers/22-presentations.json(menus, alerts, sheets at +10 with the admission rules; destructive alerts flat; superseding the legacymenu,sheetandsheet-panelrules).
- T2: count classes; menu type (action or value); source tracking and the white source; anchoring vars and
- Acceptance tests: WN AT-11, AT-11b, AT-12, AT-13, AT-14 (depth at 10 mm,
interactive: false), AT-15, AT-19, AT-21; CC A13 (Power look only); PLAN-1c-1: CONFIRM with a destructive button has no pop (sgcheck); PLAN-1c-2: G-MOTION filmstrips of a menu morph, an alert and a sheet; PLAN-1c-3:cmpagainstwindow-nav-menu.html,-sort.html,-power.html,-alert.htmland-sheet.html.
C2a Home, folders, What's New
-
Tiers: T1–T5. Size: L.
-
Owns:
theme/41-home.css,device/rt/41-home.js,device/rt/41-search-apps.js,theme/layers/41-home.json;docs/phase2/concepts/home-apps.md,docs/phase2/concepts/xc-home-search.md;- mockups:
home-apps-home.html,home-apps-home-t1.html,home-apps-apps.html,home-apps-collections.html,home-apps-folder.html,home-apps-depth.html,home-apps-search.html,home-apps.css,home-apps.js,home-apps-icons.js.
-
Routes:
/library/home(override),/library/lgs/folder/<id>,/library/lgs/steamhome. -
Inputs: HA §3, §5, §6, §10, §11, §12.1, §12.5, §12.9, §13, §14; XC; §1.4, §1.7, §1.14 here.
-
Builds:
- T3:
- the Home override (sections Recent, Collections, Apps, Windows; the honeycomb with explicit neighbours and one-step memory);
- folder and What's New routes;
- the attention-driven card ramp and name plates;
- launch wiring through P2
actions; - the search provider (S-A, S-B, S-C);
- Steam's sounds for section and page changes.
- T1: discs, labels, top row, page dots, peeks; What's New restyle (Home feed rules moved here from
40-library.css). - T5: plates for discs, top-row controls, card and name plate; mosaic bands; the focused pop at +15 mm over an occluder plate.
- T3:
-
Acceptance tests: HA AT-1 to AT-11, AT-10b, AT-10c, AT-16 to AT-19, updated for §1:
- AT-8(d): the ramp is fed by attention; there is no CSS
:hoverreveal; - AT-10: the focused cell at 0.041 u with
interactive: falsein the default profile.
PLAN-2a-1: with native off,
p2_c2a_home_t1compared withhome-apps-home-t1.html; with native on, anhvlook shows one copy of each icon and plate glass beside the lifted disc. - AT-8(d): the ramp is fed by attention; there is no CSS
C2b Launcher ("+" popup)
- Tiers: T1, T3, T4. Size: M.
- Owns:
theme/32-launcher.css("+" popup rules moved out of30-bar.css),device/rt/32-launcher.js,theme/popups/32-launcher.json,docs/phase2/mockups/home-apps-plus.html,home-apps-plus-t1.html. - Surface:
barpopup(the "+" popup). - Inputs: HA §4, §5, §12.7; CC §3.4 row "+"; §1.14 here.
- Builds:
- T1: the 4-column grid with no
flow-children, raisedmax-height, labels, the green pip on Liquid Glass. Labels end in "…" where a name does not fit, and never cut a glyph; without T3, the attended cell's own label shows the whole name in the plate's look [R2-15]. - T3: the memo patch (A–Z, the full-name plate, the toggle row with
preferredFocuson the first program, the LQ10 order: navigate main to/library/homebefore launching the switch). - T4: popup
z+25 mm.
- T1: the 4-column grid with no
- Acceptance tests: HA AT-5 (a–e), AT-12; PLAN-2b-1:
audit barpopup(OPEN pre) clean in T1 and T3, with §1.16's E-GRID and E-GRID (labels) as the only waivers [R2-15]; PLAN-2b-2:cmpagainsthome-apps-plus.htmland-plus-t1.html; PLAN-2b-3: the toggle row is logged, never executed, and logsNavigate('/library/home', replace)before the launch when main is on a/library/lgs/*route; PLAN-2b-4 [R2-15]: the full name on attention, in T3 (--flags wp.c2b) and without T3 (R23's T1 fallback), in both input modes. For every cell whose label is shortened (it overflows its box, or T3 drew "…"): gamepad focus (L.gpTake) and a laser hover held 0.8 s (--hover) each show the stock name whole (no overflow in its box), inside x 0–300 of the popup, by 800 ms after attention begins; the other cells' labels it overlaps are at opacity ≤ .22 (HA §4); 500 ms after attention ends the label is back at rest and no plate is visible.
C2c Library catalogue
- Tiers: T1–T4. Size: M.
- Owns:
theme/40-library.css,device/rt/40-library.js,theme/layers/40-library.json,docs/phase2/mockups/home-apps-library.html,home-apps-library-pad.html,home-apps-filter.html,docs/phase2/mockups/fetch-library-refs.py. - Routes:
/library/tab/*,/library/collection/*, the Sort menu, the tile menu, Library Filters. - Inputs: HA §7, §8, §9, §12.2–§12.6; WN §3.4.6; §1.10, §1.12 here.
- Builds:
- T1: tabs as a segmented control, the VR sub-filter in place, posters (Steam's geometry), focus lift, the "N apps hidden" notice, section headers.
- T2: state labels on Steam's legends (current sort, filter count); the five fixed ornament slots only behind
libFixedSlots(S28) [R2-2]; More hosts registered with C1a's helper. - T3: the letter scrubber; larger posters only if LQ2 passes; the filter sheet content (chips, segments, collapsed sections) with the T1 fallback.
- T4: posters at +15 mm (
layers/40-library.json, superseding the legacycard,tabsandtab-arrowrules for library routes).
- Acceptance tests: HA AT-6, AT-7, AT-14 (a–d), AT-15; PLAN-2c-1 [R2-2]: the ornament hugs its members and Steam's laser Sort & Filter pill is the toolbar row's trailing group (the three checks of §1.19 R2-2); with
libFixedSlotson, the ornament is 880 ± 1 in both modes; PLAN-2c-2:cmpagainsthome-apps-library.html,-library-pad.htmland-filter.html; G-PAD on AllGames, including Up to the tabs and back.
C3a Bar, HUD, toasts, tooltips look
- Tiers: T1, T2, T4, T5. Size: M.
- Owns:
theme/30-bar.css,theme/35-hud.css,theme/vr/60-overlays.css,theme/vr/pre/sys_zoo.js,device/rt/30-bar.js,device/rt/35-hud.js,theme/popups/30-bar.json,theme/layers/30-bar.json,docs/phase2/mockups/control-center-bar.html,control-center-hud.html,window-nav-toast.html. - Surfaces:
bar, bar popups other than "+" and Quick Access,tooltip,volumelevel,notifications,floatingfooter(look only; its content is C1a's), SteamVR overlays. - Inputs: CC §3, §6, §7, §8, §11.1, §11.4, §11.5; WN §5.5, §5.6; §1.3, §1.13 here.
- Builds:
- T1: the three bar pieces (Home circle, apps capsule, system capsule on
%{Tray}), states, the status pill, battery capsule rules, bar popups, toasts (320 × 76), volume HUD look, SteamVR toasts. - T2: the unread count, the HUD number.
- T4: bar popups +25 mm; HUD placement behind
hudPlacement(off). - T5: the bar cover as three shapes (
layers/30-bar.jsonownsbar).
- T1: the three bar pieces (Home circle, apps capsule, system capsule on
- Acceptance tests: CC A3 to A6, A15, A16, A25 (bar), A26; WN AT-18; PLAN-3a-1:
cmpagainstcontrol-center-bar.htmland-hud.html; PLAN-3a-2: the battery number is never shown when Steam's setting is off.
C3b Control Center
- Tiers: T1–T5. Size: L.
- Owns:
theme/31-cc.css,device/rt/31-cc.js,theme/layers/31-cc.json;docs/phase2/capabilities/cc-focus.md(new; the CS10 result);docs/phase2/concepts/control-center.md,control-center-mockaudit.py;- mockups: every
control-center-*file except-bar.htmland-hud.html, pluscontrol-center.css,control-center-build.js,control-center-icons.js.
- Surfaces:
main(CC-M modal), Quick Access (CC-C restyle; C3b owns these selectors in31-cc.css), the gated CC-A host. - Inputs: CC §4, §5 (the Power circle), §9, §10, §11.2, §11.3, §11.6, §12, §13; §1.2, §1.6, §1.8 here.
- Builds:
- T3: the CC-M view in
ui.modal, with the route container as fallback (tiles, toggles, sliders, rows, Now / list, More Controls with Steam's Quick Access panels, explicit neighbours, LB/RB); the pill patch (CC3); bindings to Steam's handlers (CC4, CC6) behind spies in tests. - T1: CC-C restyle; the tiles' CSS-only look (.94).
- T5: window cover phase 0 and 4 plates while open (CC14, through G1).
- Spike: the CS10 spike for CC-A, writing
cc-focus.md.
- T3: the CC-M view in
- Acceptance tests: CC A1, A2, A7 to A14, A17 to A23, A25, A27 to A33 (A28 to A30 only if CS10 passes); PLAN-3b-1: no window content visible in an
hvlook with CC-M open (A21); PLAN-3b-2:cmpagainstcontrol-center-overview.html,-gamepad.html,-more.html,-notifications.html.
C4a Controls and primitives
- Tiers: T1, T2. Size: M.
- Owns:
theme/10-primitives.css,device/rt/10-controls.js;docs/phase2/concepts/controls.md,controls-probe.js;- mockups: every
controls-*file exceptcontrols-keyboard*, pluscontrols.css.
- Routes:
/zoo/*; the primitives on every route. - Inputs: CTL §4–§11, §13, §15–§19; §1.3, §1.4, §1.16 here.
- Builds:
- T1: buttons, switches (with the extender), check circles, sliders (64 px knob, notch and default-tick fixes, inert glyph, mute zone), pop-up capsules, segmented controls, fields, grouped rows and platters, disabled rules.
- T2: row tagging
data-lgs-ctl, slider value text, mute-zone tags. - Migration: the context-menu and modal sections of
10-primitives.cssare removed once C1c's22-presentations.csscarries them (§6).
- Acceptance tests: CTL C1 to C9, C13 to C15, C18 to C23; PLAN-4a-1: G-FOCUS on
/zoo/buttons,/zoo/toggles,/zoo/sliders,/settings/system; PLAN-4a-2:cmpagainstcontrols-states.html,-system.html,-quickaccess.html.
C4b Keyboard
- Tiers: T1–T5. Size: M.
- Owns:
theme/36-keyboard.css(keyboard rules moved out of35-hud.css),theme/vr/50-keyboard.css,theme/vr/pre/kb_states.js,device/rt/36-keyboard.js,theme/layers/36-keyboard.json,docs/phase2/mockups/controls-keyboard.html,controls-keyboard-pad.html,controls-keyboard-dim.html,controls-keyboard-t1.html,controls-keyboard.js. - Surfaces:
keyboard,vr:keyboard. - Inputs: CTL §12 (K-G1 to K-G6), C10 to C12, C16, C17, C24; SM §3.3; §1.6 here.
- Builds: keys and states (T1); the echo row with masking and the scoped key-block move (T2); the context Enter label (T3); the keyboard surface with a
thickcover and its mask (T5, with P9 G4); materialize (K-G5). - Acceptance tests: CTL C10, C11, C11b, C11c, C12, C15b, C16, C17, C24; SM keyboard tests; PLAN-4b-1:
cmpagainstcontrols-keyboard.htmland-keyboard-t1.html.
C5a Game pages, achievements, Properties
- Tiers: T1–T4. Size: L.
- Owns:
theme/50-appdetails.css,device/rt/50-gamepage.js,theme/layers/50-gamepage.json;docs/phase2/concepts/game-pages.md,game-pages-locgrep.sh,game-pages-probe.js;- mockups: every
game-pages-*file exceptgame-pages-nowplaying*andgame-pages-bindings*, plusgame-pages.css,game-pages.js,make-bright-hero.py,legibility-check.py,mockups/assets/hero-bright.jpg,assets/logo-bright.png.
- Routes:
/library/app/:appid(title, details, running), achievements, Properties,/app/:appid/controllerconfigurator(look; the link is C5b's). - Inputs: GP §3, §4.1–§4.12, §5, §6, §9.1–§9.6, §9.9, §10; SM §3.11; §1.7 (over-art rule) here.
- Builds:
- T1: the hero layout, sticky art, the cluster, the segmented tabs with paired arrows, tab content, the achievements split view, Properties as Settings.
- T2: the Steam Input label, tooltips (above), the adaptive dimming sampler, the inline title, arrival detection, More hosts for event cards.
- T4: over-art pops only after G2 and AT-HV-OFFAXIS; cards at +15 mm.
- Acceptance tests: GP AT-MOCK, AT-AUDIT, AT-GEO, AT-SIZE, AT-TYPE, AT-TIP, AT-LEGIBLE, AT-NAV, AT-PIN, AT-FADE, AT-NAV-AP, AT-MENU, AT-OPT, AT-EDGE, AT-REST, AT-MOT, AT-DEPTH, AT-HV-OFFAXIS, AT-PERF, AT-RM, AT-RUN; PLAN-5a-1: over-art pops stay at 0 until G2 is live (
sgcheck).
C5b Now Playing, binding UI, VR bindings link
- Tiers: T1, T2, T3. Size: M.
- Owns:
theme/vr/20-nowplaying.css,theme/vr/40-bindings.css,theme/vr/pre/np_states.js,theme/vr/pre/bind_hover.js,device/vr/systemui.nowplaying.js,device/shell_ext/vrbind.py,device/rt/52-vrbind.js(the capsule and the Steam Input link, behindvrBindings),docs/phase2/mockups/game-pages-nowplaying.html,game-pages-nowplaying-variants.html,game-pages-nowplaying-bindings.html,game-pages-bindings.html,game-pages-bindings-view.html. - Surfaces:
vr:systemui(Now Playing),vr:controllerbindingui. - Inputs: GP §4.12 (GQ11a–d), §4.13, §4.14, §9.7, §9.8, §10.
- Builds: the Now Playing restyle with T2 keys
data-lgs-npfrom SteamVR's own strings; the binding UI restyle at ×1.87; the vrbind relay plugin and capsule (off). - Acceptance tests: GP AT-NP, AT-NP-VARIANTS, AT-BIND, AT-T3, AT-T3-ROUTE, AT-BIND-RETURN (dry-run logger first), AT-HV; PLAN-5b-1: with
vrBindingsoff, no capsule or link is rendered andaudit mainon a game page equals C5a's result.
C6a Steam Settings
- Tiers: T1–T4. Size: L.
- Owns:
theme/60-settings.css,device/rt/60-settings.js,theme/layers/60-settings.json;docs/phase2/concepts/settings.md,settings-accept.js,settings-pad.js,settings-sel.py;- mockups: every
settings-*file exceptsettings-steamvr*, plussettings.css,settings-shared.js,settings-icons.js.
- Routes: the 24
/settings/*pages,/settings/lgsvr(new), Controller › Advanced. - Inputs: SET §3, §4.1–§4.9, §4.11–§4.13, §5 to §9.4, §9.6, §10 to §12; CTL §13; §1.12 (list page), §1.16 (E-DRILL) here.
- Builds:
- T1: the 400 px sidebar, 760 px platters, compact hero, magnifier circle, contextual ornament.
- T2:
data-lgs-pageicons, the System badge, the inline title, destructive tags, section tags. - T3: the P-S1 wrapper (VR Settings page, drill-down slots); the list page for long value menus; Steam's Quick Access VR components (P-S2).
- T4: the hero icon pop at +10 mm; menus per §1.7. SET's T-DEPTH is updated to the unified plan: menus +10, destructive alerts 0.
- Acceptance tests: SET T-ACC, T-ROWS, T-SEL, T-PAD, T-DRILL, T-AUD, T-CNT, T-HIT, T-MENU, T-DLG, T-SHOT, T-MOT, T-DEPTH, T-STORE-SEL, T-PERF, T-FONT, T-A11Y, T-I18N, T-T3; P-S1, P-S2, P-S4.
C6b SteamVR Settings
- Tiers: T1, T2. Size: M.
- Owns:
theme/vr/30-settings.css,theme/vr/pre/settings_mock.js,theme/vr/pre/settings_open.js,device/vr/systemui.settings.js,docs/phase2/mockups/settings-steamvr.html,settings-steamvr-playarea.html. - Surface:
vr:systemui(frame pagesystem.settings). - Inputs: SET §4.10, §4.12, §9.5, T-VR group, P-S3, P-S5, P-S6.
- Builds: the footprint inset to 1858 × 952; the sidebar; the Back circle (
SwitchToPage); Off/On pairs as switches; the Advanced ornament; the modal fit rule. - Acceptance tests: SET T-VR, T-VR-FOOT, T-VR-SW, T-VR-MODAL, T-VR-EXIT; P-S3, P-S5, P-S6; CTL C18.
C7 People, Photos, Downloads, Store
- Tiers: T1–T4. Size: L.
- Owns:
theme/70-social.css,device/rt/70-social.js,theme/layers/70-social.json,docs/phase2/concepts/social-media.md, everysocial-media-*mockup,social-media.css,social-media.js. - Routes:
/chat,/account,/invites,/media/*,/library/downloads,/steamweb,/externalweb. - Inputs: SM §3.0–§3.10, §3.12, §4, §5, §6, §7, §8, §9; §1.10, §1.11 here.
- Builds:
- T1: People split view, account column, the
/invitescard (windowless with a plate), Photos, viewer, clip player, Downloads, the store ornament. - T2: segment labels, the current-conversation pill, the compose placeholder, More hosts registered with C1a's helper, the store bounds refresh.
- T3: the media segmented control; lab fixtures through P2's lab route.
- T4: pops per SM §4.2, at +10 instead of +12 (§1.7).
- Migration: the Power menu rules leave
70-social.css(C1c owns them).
- T1: People split view, account column, the
- Acceptance tests: SM G1 to G9 (G9 with the §1.7 rules), the per-screen tests of §8.2, the fixtures F-INV, F-CHAT, F-DL, F-FAV, S-P.
2.5 Verification and integration (V)
The verification and integration package has two parts that run side by side.
V1 Native gate and integration
- Owns:
device/defaults.json(new),README.md,docs/LAB.md,docs/NATIVE.md,docs/DESIGN.md,docs/coverage/**,docs/inventory/**(errata only),docs/phase2/wp/V1.md. - Builds:
- the native gate (§4.3) and the flip of
nativeto on, or a recorded fail with the reason; - the
defaults.jsonflags of §1.17 as their gates pass; - documentation (§6).
- the native gate (§4.3) and the flip of
- Acceptance: §4.3 and the release checklist (§4.6).
V2 Verification suite and conformance
- Owns:
docs/phase2/verify/**(reports,functions.csv,strings.md,REPORT.md). - Builds:
- the global gate runs over the route matrix (§4.2) in four states (CSS-only and native, laser and gamepad);
- the VP conformance run (P-01 to P-89);
- the function ledger;
- mockup comparisons;
- the final report.
- Acceptance:
REPORT.mdwith PASS / FAIL per gate per route, per P-item and per function, and the list of wearer-only items (§5.3).
2.6 File ownership index
Every file has exactly one owner. A file that is not listed is owned by the coordinator. These inputs are frozen and change only by coordinator decision:
docs/phase2/audit/**,docs/phase2/research/**(exceptsprings.py, which is P5's),docs/phase2/capabilities/**(exceptcc-focus.md, which is C3b's),docs/refs/**;- every
shots/p2_*file from Phase 2 so far.
A package creates new files only inside its listed patterns.
| Path | Owner |
|---|---|
README.md |
V1 |
glass.py |
P10 |
lab/** |
P10 |
tools/mockshot.py |
P10 |
tools/p2/** |
P10 |
device/lgs.py |
P1 |
device/lgs_core.js |
P1 |
device/lgs_index.js |
P1 |
device/lgs |
P1 |
device/glass-shell.desktop |
P1 |
device/icon.png |
P1 |
device/defaults.json |
V1 |
device/rt/00-rt.js |
P1 |
device/rt/02-react.js |
P2 |
device/rt/03-react-lab.js |
P2 |
device/rt/04-input.js |
P3 |
device/rt/05-attention.js |
P3 |
device/rt/06-states.js |
P3 |
device/rt/07-tooltip.js |
P3 |
device/rt/08-popups.js |
P6 |
device/rt/10-controls.js |
C4a |
device/rt/20-shell.js |
C1a |
device/rt/20-more.js |
C1a |
device/rt/20-pointer.js |
C1a |
device/rt/21-search.js |
C1b |
device/rt/22-menus.js |
C1c |
device/rt/30-bar.js |
C3a |
device/rt/31-cc.js |
C3b |
device/rt/32-launcher.js |
C2b |
device/rt/35-hud.js |
C3a |
device/rt/36-keyboard.js |
C4b |
device/rt/40-library.js |
C2c |
device/rt/41-home.js |
C2a |
device/rt/41-search-apps.js |
C2a |
device/rt/50-gamepage.js |
C5a |
device/rt/52-vrbind.js |
C5b |
device/rt/60-settings.js |
C6a |
device/rt/70-social.js |
C7 |
device/shared/motion.js |
P5 |
device/lgs_layers.js |
P6 |
device/lgs_sg.js |
P7 |
device/lgs_shell.py |
P8 |
device/lgs_vr.py |
P8 |
device/lgs_vr_core.js |
P8 |
device/lgs_lens.js |
P4 |
device/shell_ext/__init__.py |
P8 |
device/shell_ext/vrbind.py |
C5b |
device/vr/systemui.nowplaying.js |
C5b |
device/vr/systemui.settings.js |
C6b |
device/proto/react_proto.js |
P2 |
device/proto/input_mode.js |
P3 |
theme/00-tokens.nowrap.css |
P4 |
theme/01-font.nowrap.css |
P4 |
theme/02-motion.nowrap.css |
P5 |
theme/03-material.css |
P4 |
theme/04-states.css |
P4 |
theme/05-native.css |
P6 |
theme/10-primitives.css |
C4a |
theme/20-shell.css |
C1a |
theme/21-search.css |
C1b |
theme/22-presentations.css |
C1c |
theme/23-transitions.css |
C1c |
theme/30-bar.css |
C3a |
theme/31-cc.css |
C3b |
theme/32-launcher.css |
C2b |
theme/35-hud.css |
C3a |
theme/36-keyboard.css |
C4b |
theme/40-library.css |
C2c |
theme/41-home.css |
C2a |
theme/50-appdetails.css |
C5a |
theme/60-settings.css |
C6a |
theme/70-social.css |
C7 |
theme/fonts/** |
P4 |
theme/layers.json |
P6 (retired) |
theme/layers/00-base.json |
P6 |
theme/layers/99-legacy.json |
P6 (temporary) |
theme/layers/20-shell.json |
C1a |
theme/layers/21-search.json |
C1b [R2-16] |
theme/layers/22-presentations.json |
C1c |
theme/layers/30-bar.json |
C3a |
theme/layers/31-cc.json |
C3b |
theme/layers/36-keyboard.json |
C4b |
theme/layers/40-library.json |
C2c |
theme/layers/41-home.json |
C2a |
theme/layers/50-gamepage.json |
C5a |
theme/layers/60-settings.json |
C6a |
theme/layers/70-social.json |
C7 |
theme/popups/00-base.json |
P6 |
theme/popups/20-shell.json |
C1a |
theme/popups/30-bar.json |
C3a |
theme/popups/32-launcher.json |
C2b |
theme/sg/00-base.json |
P7 |
theme/sg/20-winbar.json |
C1a |
theme/vr/00-vr-tokens.css |
P4 |
theme/vr/10-systemui.css |
C1a |
theme/vr/20-nowplaying.css |
C5b |
theme/vr/30-settings.css |
C6b |
theme/vr/40-bindings.css |
C5b |
theme/vr/50-keyboard.css |
C4b |
theme/vr/60-overlays.css |
C3a |
theme/vr/pre/audit_inpage.js |
C1a |
theme/vr/pre/panel_sizes.js |
C1a |
theme/vr/pre/sys_hover.js |
C1a |
theme/vr/pre/sys_zoo.js |
C3a |
theme/vr/pre/kb_states.js |
C4b |
theme/vr/pre/np_states.js |
C5b |
theme/vr/pre/bind_hover.js |
C5b |
theme/vr/pre/settings_mock.js |
C6b |
theme/vr/pre/settings_open.js |
C6b |
native/glassd/** |
P9 |
native/shared/motion_tokens.h |
P5 |
native/spike/fakeglassd.cpp |
P9 |
native/spike/hvgrab.cpp |
P10 |
native/spike/** (all other files) |
P7 |
docs/DESIGN.md |
V1 |
docs/LAB.md |
V1 |
docs/NATIVE.md |
V1 |
docs/coverage/** |
V1 |
docs/inventory/** |
V1 (errata only) |
docs/phase2/PLAN.md |
Coordinator |
docs/phase2/wp/coordinator.md (decision log and the coordinator's requests) |
Coordinator |
docs/phase2/DESIGN2.md |
P4 |
docs/phase2/fontkit.py |
P4 |
docs/phase2/glassd-material.md |
P9 |
docs/phase2/research/springs.py |
P5 |
docs/phase2/capabilities/cc-focus.md |
C3b |
docs/phase2/contracts/runtime.md |
P1 |
docs/phase2/contracts/react.md |
P2 |
docs/phase2/contracts/interaction.md |
P3 |
docs/phase2/contracts/tokens.md |
P4 |
docs/phase2/contracts/motion.md |
P5 |
docs/phase2/contracts/reporter.md |
P6 |
docs/phase2/contracts/sg.md |
P7 |
docs/phase2/contracts/daemon.md |
P8 |
docs/phase2/contracts/glassd.md |
P9 |
docs/phase2/contracts/lab.md |
P10 |
docs/phase2/wp/<ID>.md, docs/phase2/wp/<ID>-cmp.json |
That package |
docs/phase2/verify/** |
V2 |
docs/phase2/concepts/window-nav.md, window-nav-measure.py |
C1a |
docs/phase2/concepts/home-apps.md, xc-home-search.md |
C2a |
docs/phase2/concepts/control-center.md, control-center-mockaudit.py |
C3b |
docs/phase2/concepts/controls.md, controls-probe.js |
C4a |
docs/phase2/concepts/game-pages.md, game-pages-locgrep.sh, game-pages-probe.js |
C5a |
docs/phase2/concepts/settings.md, settings-accept.js, settings-pad.js, settings-sel.py |
C6a |
docs/phase2/concepts/social-media.md |
C7 |
docs/phase2/mockups/kit.css, kit.js, _example.html |
P4 |
docs/phase2/mockups/assets/LICENSE.md, room-lounge.jpg, room-studio.jpg |
P4 |
docs/phase2/mockups/assets/hero-bright.jpg, logo-bright.png |
C5a |
docs/phase2/mockups/window-nav-shared.css, window-nav-shared.js |
C1a |
docs/phase2/mockups/window-nav-anatomy.html, -anatomy-t1.html, -anatomy-depth.html, -tabbar.html, -tabbar-r863.html, -ornament.html, -chrome.html |
C1a |
docs/phase2/mockups/window-nav-search.html, -results.html |
C1b |
docs/phase2/mockups/window-nav-menu.html, -sort.html, -power.html, -alert.html, -sheet.html, -motion.html |
C1c |
docs/phase2/mockups/window-nav-toast.html |
C3a |
docs/phase2/mockups/home-apps-home.html, -home-t1.html, -apps.html, -collections.html, -folder.html, -depth.html, -search.html, home-apps.css, home-apps.js, home-apps-icons.js |
C2a |
docs/phase2/mockups/home-apps-plus.html, -plus-t1.html |
C2b |
docs/phase2/mockups/home-apps-library.html, -library-pad.html, -filter.html, fetch-library-refs.py |
C2c |
docs/phase2/mockups/control-center-bar.html, -hud.html |
C3a |
docs/phase2/mockups/control-center-* (all others), control-center.css, control-center-build.js, control-center-icons.js |
C3b |
docs/phase2/mockups/controls-keyboard*.html, controls-keyboard.js |
C4b |
docs/phase2/mockups/controls-* (all others), controls.css |
C4a |
docs/phase2/mockups/game-pages-nowplaying*.html, game-pages-bindings*.html |
C5b |
docs/phase2/mockups/game-pages-* (all others), game-pages.css, game-pages.js, make-bright-hero.py, legibility-check.py |
C5a |
docs/phase2/mockups/settings-steamvr*.html |
C6b |
docs/phase2/mockups/settings-* (all others), settings.css, settings-shared.js, settings-icons.js |
C6a |
docs/phase2/mockups/social-media-*, social-media.css, social-media.js |
C7 |
Cross-file requests. When package X needs a change in a file owned by Y (for example C2b needs the "+" popup rules removed from 30-bar.css), X writes the request in docs/phase2/wp/X.md under "Requests". Y makes the change and answers there. The coordinator checks open requests at every sync point.
3. Schedule and dependencies
3.1 Waves
| Wave | Starts when | Packages (agents) | Ends with |
|---|---|---|---|
| A: platform | Now | P1, P2, P3, P4, P5, P6, P7, P8, P9, P10 (10) | M1 contracts in the first session; then M2 |
| A′: shared context | P4 M1 (tokens and states CSS) | C1a, C1c, C4a, C2a (4; up to 14 agents in all) | M0 at once, then M2 |
| B: contexts | P1, P2, P3 M3 for T2/T3 work; T1 work can start earlier | C1b, C2b, C2c, C3a, C3b, C4b, C5a, C5b, C6a, C6b, C7 (11). Agents freed by wave A join here; at most 20 run at once | M3, then M4 once P6, P8 and P9 reach M2 |
| C: verification | V2's harness with P10 in wave A; V1 when P6–P9 reach M2 and C1a reaches M4 | V1, V2 (2) | §4.6 checklist |
Suggested staffing for 16 agents:
- Wave A takes ten agents.
- Four agents start A′ once P4's tokens land (a few hours).
- As P5, P1, P3 and P10 finish (the smaller packages), their agents take C4b, C3a, C6b and C5b.
- The wave A agents with deep context continue into the matching context packages: P2 → C3b, P9 → C2a's native work if needed, P6 → C1c.
3.2 Critical path
P4 tokens ──► C1a window, toolbar (CQ1), ornament ──► C2c, C5a, C6a, C7 layouts
P1 ─► P2 React ───────────────► C2a Home, C3b CC-M, C6a drill-down, C1b search
P1 ─► P3 attention ──────────► C2a ramp, More circle, tooltips
P5 ─► P7 depth channel ┐
P9 G1 plates ──────────┼─► P8 v3 spec ─► P6 plates ─► C2a, C3b, C7 native ─► V1 native gate ─► V2 sweep
P9 G2 holes ───────────┘ └─► C5a over-art depth
The two longest chains:
- Plates: P9 G1 → P8 → P6 → C2a native → V1. Mitigation: C2a ships CSS plates first (HA §10.5), so Home is never blocked on glassd.
- CQ1 (C1a). Mitigation: every area uses only the
lgs-hdr-108/lgs-hdr-40classes, so both outcomes work.
3.3 Sync points
The coordinator runs a sync point after each wave's milestone:
- Pull every package's evidence log.
- Resolve cross-file requests.
- Run
python glass.py check-themeandglass.py gates main --route /library/homeas a smoke test. - Commit.
Packages never wait on a commit to continue. They read each other's contracts from the working tree.
4. Verification and integration
4.1 Global gates
Every context package runs these gates on its routes (§4.2). V2 reruns them for M5. Unless stated, each gate runs in both input modes (--mode laser|pad) and in both tiers (CSS-only, then native through native-session).
| Gate | What | Command | Pass |
|---|---|---|---|
| G-AUD | Functions kept | glass.py audit SURF --route R [--pre P] |
GONE / HIDDEN / SHRUNK / UNCLICKABLE / CONTRAST = 0, apart from the exemptions of §1.16 |
| G-PAD | Gamepad traversal | glass.py pad-bfs --route R |
Every focusable reached; Down-Up and Right-Left return (except at edges); B closes the topmost layer first; after a menu, sheet or alert closes, focus is on its source; on route entry focus is on the primary item (P-20 to P-24) |
| G-SIZE | Target sizes | glass.py gates (DOM sweep with elementFromPoint sampling, SM G2b method) |
VP P-08, P-80, P-83, or the element's exemption |
| G-TYPE | Type | gates |
No text under 18 px; no weight under 500; no uppercase, tracking or italics in chrome (P-38, P-84) |
| G-OUTLINE | No outlines | gates + edge_profile.py |
P-42, P-43; edge ratio ≤ 0.35 on glass top edges |
| G-FOCUS | Focus and selection | glass.py focus |
§1.4 criteria (P-14 to P-16, P-18, T-SEL) |
| G-MOTION | Motion | glass.py motion |
Every duration and easing a token (P-58); nothing at rest (§1.5); filmstrips for the package's listed interactions show glass before content on entry, content before glass on exit, no text scaling, no closed outline in any frame; Reduce Motion fades only (P-56) |
| G-PERF | Performance | glass.py perf SURF --route R |
Themed fps within 5 % of stock; no new frames over 34 ms, both read as R2-13's statistic (ABBA × 2: median fps ratio ≥ 0.95; median extra long frames ≤ stock's A/A spread, min 1; CSS-only verdicts pool only native=off runs) [R2-13]. Native: glassd median ≤ 2.5 ms (glassd-out.json) |
| G-DEPTH | Depth (native) | glass.py sgcheck |
§1.7 admission rules; ≤ 4 distinct dz at rest; every crop interactive: false in the default profile; every lift has its shadow (P-48) |
| G-HV | Headset view (native) | glass.py hv NAME and --offaxis (look, then delete) |
No doubled element; glass L 55–110; no closed outline; labels legible |
| G-MOCK | Looks like the design | glass.py cmp against the package's mockups |
Named rects within ±8 px, or the difference explained in the evidence log; the agent views both images and records a verdict |
| G-REMOVE | Nothing persists | lgs off, then outline + status |
Stock DOM; no data-lgs-*; no route patches; no scene-graph nodes; flags file cleared |
| G-A11Y | Accessibility | --media reduce, --media contrast |
Fades only; near-opaque glass with the 2 px edge; contrast clean |
| G-FONT | Font | document.fonts.check on each surface |
True |
4.2 Route matrix
V2 runs every row in the four states. The owner fixes failures.
| Route or surface | Owner | Glass mode | States to capture | Mockups |
|---|---|---|---|---|
/library/home (Recent, Collections, Apps) |
C2a | windowless | Rest, focus on a cell, card open, section change | home-apps-home, -apps, -collections, -home-t1, -depth |
/library/lgs/folder/<id> |
C2a | windowless | Rest, focus | home-apps-folder |
/library/lgs/steamhome |
C2a | window-full | Rest, focus on a shelf card | (Steam's page, restyled) |
/library/tab/AllGames, /library/collection/*, Sort, tile menu, Filters |
C2c (+C1c menus) | window | Laser and gamepad ornament; menus open; sheet open | home-apps-library, -library-pad, -filter |
/search, /search/tab/* |
C1b | window | Zero state, results for "half", a category | window-nav-search, -results, home-apps-search |
| CONFIRM, ALERT, MENU, SUBMENU, POWER, ZOO dialogs | C1c | — | Each open | window-nav-menu, -sort, -power, -alert, -sheet |
/library/app/:appid (title, details, running), achievements, Properties, controller page |
C5a | hero / window | GP's state list | game-pages-* |
/settings/* (25 routes and sub-views) |
C6a | window | Root and sub-views, dropdown, list page, dialog | settings-* |
/chat, /account, /invites, /media/*, /library/downloads, /steamweb |
C7 | window / window-full / windowless | SM §8.2 list | social-media-* |
/zoo/* |
C4a | window | State matrix | controls-* |
| CC-M open | C3b | windowless | Rest, gamepad, More Controls, notification list | control-center-overview, -gamepad, -more, -notifications |
bar, bar popups, Quick Access (CC-C) |
C3a, C3b | — | Rest, hover, focus, open | control-center-bar, -fallback |
| "+" popup | C2b | — | T1, T3 | home-apps-plus, -plus-t1 |
frame.menu, floatingfooter |
C1a | — | Collapsed, expanded with gamepad focus | window-nav-tabbar |
tooltip, volumelevel, notifications |
C3a | — | Mock recipes (inventory hud.md) |
control-center-hud, window-nav-toast |
keyboard, vr:keyboard |
C4b | — | Shown, echo, masked | controls-keyboard* |
vr:systemui window-bar row |
C1a | — | Hover, focus, tooltip | window-nav-chrome |
vr:systemui Now Playing |
C5b | — | Variants (needs the showcase running, LAB.md) | game-pages-nowplaying* |
vr:systemui SteamVR Settings |
C6b | — | Each section | settings-steamvr* |
vr:controllerbindingui |
C5b | — | List, view | game-pages-bindings* |
4.3 The native gate (V1)
The brief asks for full glass, which only glassd can draw over the room. NATIVE.md keeps native mode opt-in until a wearer checks input, the cursor dot and resizing. Since the user is unavailable, V1 replaces each wearer check with an agent check (WN §8.3) and adds the Phase 2 checks. All steps run in one native-session.
| Id | Check | Pass |
|---|---|---|
| AT-0a | Pass-through: DumpLaserOverlays with lgs on --native |
Every glassd cover, plate, slab and base piece is under skippingDueToNonInteractivity; the main, frame-menu and bar targets have the same size as in CSS-only mode |
| AT-0b | Pointer proxy (WN AT-0b) | The ring is found in CDP shots and in an hv frame over the cover |
| AT-0c | Alignment, flags, cost (WN AT-0c) | At r = 0.863: cover and crop edges within 3 view px of Steam's content; no mosaic hairlines; the cover flag sets change nothing visible; glassd median ≤ 2.5 ms; compositor frame time within 5 % of CSS-only |
| AT-0d | Ghost and click-safe rules on every route of §4.2 | sgcheck clean |
| AT-0e | Materialize | Covers and plates ramp with GM §5 curves; no doubled content while the window cover's m < 0.3 (covers animate only while the dashboard opens) |
| AT-0f | Return to CSS | lgs on --css removes every node within 1 s and lgs-native within 3 s |
Decision.
- If AT-0a to AT-0f pass, V1 sets
native: onindefaults.json.lgs onand the "+" toggle then start native mode. The unit stays transient: nothing surviveslgs off, a Steam restart or a reboot. - If any check fails, V1 records it, CSS-only ships with the degraded spec (WN §8.4), and
REPORT.mdstates plainly that real glass needs native mode.
4.4 Conformance (VP §6)
- V2 runs
glass.py conformancefor every automatable P-item and records manual verdicts for the REV and MOCK items indocs/phase2/verify/conformance.md. - Every "must" item passes, or carries a recorded deviation from §1. The known deviations:
- P-11, the pointer proxy (S16);
- P-37, a second capsule on the tab bar's edge (WN's two groups are one Steam popup; moving Settings or Power out would hide Steam's nodes);
- P-65, an outside click still cancels alerts and sheets: Steam's
ModalClickToDismiss, kept as stock. It only ever cancels, and it is the only laser dismiss of a sheet without its own button when the runtime is off (S30) [R2-6]; - P-67, alerts and Power keep Steam's default focus (S6) [R2-6].
- Scoring rule for P-23 [R2-11]: after A1 moved the ornament to 628, the bottom bound is 612 (628 − 16) on routes with a bottom ornament, and the glass bottom − 16 without one; the top bound stays 124. VP's "620" is read as 612.
4.5 Function ledger
glass.py ledgermerges every concept's retention table intodocs/phase2/verify/functions.csv, with the columns: audit id, function, concept, owner package, laser path, gamepad path, test id, status.- Pass: every function in the audits' §A lists (SN, LA, GP, SY, SM) appears at least once, with both paths, and either a passing test or, for actions tests never perform, a static check that the handler is Steam's own (the P2 action logger or a spy).
- The concepts map 59 (HA), 83 (CC) and 93 (SM) functions, plus the WN, CTL, GP and SET tables. Duplicates across concepts are merged by audit id.
- T3 actions [R2-9]: a function whose path runs through
rt.react.actions(Home and "+" launches, the game page's Play, Search's Open, launcher rows) counts as kept only when V2's shipped-state check readslive(§4.6 item 9). Until then its row ispartial, whatever its test says.
4.6 Release checklist (V1 with V2)
- Every package at M5, with its evidence log.
REPORT.md: all global gates PASS on the route matrix in the shipped state.- The native gate decided and recorded.
defaults.jsonholds only flags whose gates passed.- G-REMOVE on every surface. A Steam restart, only if another agent restarted Steam, shows the stock UI (CC A24); never restart Steam for this.
- The
README.md,LAB.mdandNATIVE.mdupdates (§6) done. - Phase 1 aliases and
99-legacy.jsonremoved (§6). - The wearer-only list (§5.3) included in
REPORT.mdfor the user. - T3 actions live [R2-9]:
defaults.jsonhas"actionsLive": true(S26), set only after RX-7 (with its gamepad phase), HA AT-4 and AT-13 pass on the release build. V2's shipped-state run (shippeddefaults.json, no--flags) showsrt.react.actions.mode()withreasonsexactly['runtime action logger on'](the lab step's own logger), andmode(ev)for Steam's programmatic click (PointerEvent('click'),pointerType '') adds no other reason. Any other reason fails the release.
5. Risks and fallbacks
5.1 Technical risks
| # | Risk | How it is detected | Fallback | Owner |
|---|---|---|---|---|
| R1 | A Steam update breaks a finder or a fiber patch | P2's fail-closed install(); RX-5; lgs status |
That feature drops to its T1 form; the CSS theme stays | P2, each context |
| R2 | React-private internals change (fiber type, alternate) |
RX-1 on every Steam build | T1 only for T3 features | P2 |
| R3 | The 108 px header (CQ1) fails | WN AT-2 | The three-rule fallback; areas key on lgs-hdr-40 |
C1a |
| R4 | Clicks on interactive crops cannot be verified | — (needs a wearer) | The default profile is non-interactive | P6, P7 |
| R5 | The laser on transparent texels (Home, CC-M gaps, /invites) |
Needs a wearer | Harmless: a hit on empty page area does nothing; every view has explicit close paths | C2a, C3b, C7 |
| R6 | SteamVR's laser dot hidden behind covers | Needs a wearer | The pointer proxy in native mode | C1a |
| R7 | The native gate fails | AT-0a–f | CSS-only with the degraded spec, stated in the report | V1 |
| R8 | glassd cost with plates exceeds 2.5 ms | GL-3, G-PERF | Plates drawn in the quarter-resolution interior pass; drop slabs on the main window while the keyboard is open; CSS plates on Home | P9 |
| R9 | t1 tint behaves differently from §1.8's inference |
SG-5 | Happened (SG-5, 2026-10-07: t1 dims pops too). §1.8 never relied on it and is amended [R2-4]: in-window modals use the CSS scrim; CC-A uses window.dim 0.6 alone; a dim behind a bright pop uses the surface dim (cover and base wrappers) |
P7 |
| R10 | Shared device: other agents change routes, theme or native mode between steps | Status checks in every step | Atomic locked steps; native.lock; flags only inside a step |
All |
| R11 | data: font blocked by CSP somewhere, or bundle memory too high |
FD-2 | Steam's font stack on that surface; drop opsz (−42 KB) |
P4 |
| R12 | Honeycomb neighbours misbehave | HA AT-3 | 5 × 3 square lattice with flow-children: grid |
C2a |
| R13 | Moved menus stop dismissing on an outside click | WN AT-11, GP AT-MENU, SET CQ10 | Centred menus | C1c |
| R14 | A stale .gpfocus under the laser paints a focus look |
P-02 CSS audit (V2) | Laser looks key on :hover only (§1.4) |
P4 |
| R15 | Hero art decode cost on Home | HA AT-11 | Heroes only for the visible page; portraits for peeks | C2a |
| R16 | Decisions taken without the user turn out wrong | §1.17 register | Every one has a flag; defaults are the conservative option | V1 |
| R17 | A broken file synced by one package breaks everyone | check-theme; the runtime's fail-closed loader |
_wip/ folders; per-module isolation |
P1 |
| R18 | Inter at 1.5× sizes truncates labels | G-TYPE + visual pass | Ellipsis rules; Callout 22 px for dense text; compact ornament members | Each context |
| R19 | Removal leaves state behind (TTL failures, patched fibers) | G-REMOVE, RT-4 | TTLs in the page; the 12 s scene-graph watchdog; lgs off cleanup report |
P1, P7 |
| R20 | The room behind the window is never captured, so the glass is flat | hv look |
GM v2 row fill; shots right after the dashboard hides (GM §3.3) | P9 |
| R21 | The vrbind relay disturbs SteamVR pages | AT-BIND-RETURN dry run | Flag off by default (S13) | C5b |
| R22 | Plates exceed 24 layers per surface (Home with peeks and card) | RP-2, GL-1 | Peeks stay CSS only; the mosaic in 4 bands (HA §10.2) | C2a, P6 |
| R23 | The "+" popup patch fails | HA AT-12 | The T1 grid in Steam's scan order; the green pip on Liquid Glass; the attended cell shows its whole name in place (CSS, PLAN-2b-4) [R2-15] | C2b |
| R24 | CC-M's modal container unmounts the page | CC A7, CC11 | The route container /library/lgs/cc |
C3b |
5.2 Fallback ladder for the whole product
- Full: native glass, T1–T5, default depth profile.
- No glassd (gate failed or glassd down): CSS-only T1–T3. Same layouts, same controls, tinted glass, no depth.
- No runtime (loader or finders failed): Phase 2 CSS only. Layouts that need T3 fall back as each concept specifies (Steam's Home, Steam's search page, Steam's Quick Access).
- Theme off: stock Steam.
lgs off, a Steam restart or a reboot.
5.3 Questions only a wearer can close
None of these blocks the default build. V2 lists them in REPORT.md for the user.
- A real click on an in-place interactive crop at a steep angle (SP §12). This unlocks the wearer profile (S2).
- SteamVR's laser dot over covers and plates. This could retire the pointer proxy (S16).
- Laser hits on transparent texels (R5).
- Comfort of the depths and of the head-locked HUD placement (S21).
- Haptics (S17).
- Gamepad focus entering an always-visible frame menu from the window (SP §12, WN AT-8 covers it with
L.pad, but not the SteamVR overlay focus hand-off). - Resized windows keep crops aligned at 0.5× and 1.5× (NAT).
6. Phase 1 migration and retirement
Rules.
- Phase 1 area files are rewritten in place by their new owners, so the bundle order stays.
- Rules move between files only by the two-step request (§2.6): the receiving package adds the rules, then the giving package deletes them.
- Until both steps are done, the old rules win by name order, so nothing breaks in between.
| Phase 1 file | Fate | By | Notes |
|---|---|---|---|
theme/00-tokens.nowrap.css |
Rewritten to D2 §16 + §1 | P4 | Phase 1 names (--lgs-spring, --lgs-window-rim, …) kept as aliases until every area reaches M2, then removed. Motion tokens move to 02-motion.nowrap.css (P5) |
theme/05-native.css |
Rewritten (plates, acks, modes) | P6 | — |
theme/10-primitives.css |
Rewritten (CTL) | C4a | Context-menu and modal sections → 22-presentations.css (C1c) |
theme/20-shell.css |
Rewritten (WN chrome) | C1a | Page transitions → 23-transitions.css (C1c); search-route rules → 21-search.css (C1b); frame-menu rules come in from 30-bar.css |
theme/30-bar.css |
Rewritten (CC bar) | C3a | Frame-menu rules → 20-shell.css (C1a); "+" popup rules → 32-launcher.css (C2b); Quick Access rules → 31-cc.css (C3b) |
theme/35-hud.css |
Rewritten (toasts, volume, tooltip, footer look) | C3a | Keyboard sections → 36-keyboard.css (C4b) |
theme/40-library.css |
Rewritten (catalogue) | C2c | Home shelf and feed rules → 41-home.css (C2a); search results → 21-search.css (C1b) |
theme/50-appdetails.css |
Rewritten (GP) | C5a | — |
theme/60-settings.css |
Rewritten (SET) | C6a | — |
theme/70-social.css |
Rewritten (SM) | C7 | Power menu rules → 22-presentations.css (C1c) |
theme/vr/10-systemui.css |
Rewritten (window-bar row) | C1a | §0 shared values → vr/00-vr-tokens.css (P4) |
theme/vr/20-nowplaying.css, 40-bindings.css |
Rewritten | C5b | — |
theme/vr/30-settings.css |
Rewritten | C6b | — |
theme/vr/50-keyboard.css |
Rewritten | C4b | — |
theme/vr/60-overlays.css |
Rewritten | C3a | — |
theme/layers.json |
Split into theme/layers/00-base.json + 99-legacy.json, then retired |
P6 | Areas supersede legacy rules by id; 99-legacy.json is deleted when empty (release checklist step 7) |
device/lgs_layers.js, lgs_sg.js, lgs_shell.py, lgs_vr.py |
Extended (§2.3) | P6, P7, P8 | Behaviour and interfaces stay backward compatible until M2 |
device/proto/react_proto.js, input_mode.js |
Promoted into device/rt/, then deleted at M5 |
P2, P3 | — |
device/lgs_lens.js |
Kept for E2 lensing on the 1–3 most important controls per page | P4 | Lens targets declared by areas through a class, not a shared file |
docs/DESIGN.md |
Banner: superseded by DESIGN2 and this plan. §8 guardrails stay, amended by the layout rules of D2 §12. §10 (ownership) replaced by §2.6 here | V1 | — |
docs/LAB.md |
Updated: Phase 2 layout rules (D2 §12) replace "never change position, width, flex"; display: none on Steam nodes stays forbidden except E-DRILL; T2/T3 rules (SR §7); the new commands (P10); flags, input stub, media emulation, native.lock; hvgrab hygiene |
V1 | — |
docs/NATIVE.md |
Updated: points to docs/phase2/contracts/* and GM; layering with plates and holes; default mode per §4.3 |
V1 | — |
docs/coverage/*.md |
Marked historical (Phase 1 evidence); replaced by docs/phase2/verify/ |
V1 | — |
docs/inventory/*.md |
Kept as the DOM reference; errata added by V1 (for example: the friends list is not virtualized, SM-D9) | V1 | — |
native/spike/* |
Kept: hvgrab, fakeglassd, sg_timeline.py are test tools |
P7, P9, P10 | — |
shots/ (Phase 1) |
Kept as the before-shots; not regenerated | — | — |
7. Team rules on the shared device
- Locks.
- Use only the locked lab commands (LAB.md).
- Native work runs only inside
glass.py native-session, which holds/tmp/lgs/native.lock. - Never leave native mode or a flag on after a step.
- Start of every step. Gamepad sequences call
FocusApplicationRoot()first (SR §4,vr-null-tree). After a synthetic hover, send the pointer to (1400, 900) to clear:hoverand bar tooltips (IM §9). - Room frames.
hvgrabframes and live glassd dumps show the room.glass.py hvdeletes them; never copy one elsewhere. Procedural-room dumps (--test-backdrop) may be kept. - Never-list (§2.1), including: no real A, B, X or Y on Steam's own controls that act; spies and the action logger instead.
- Privacy. Live shots of
/chat,/accountand/invitescontain personal names. Look at them; never quote the names in documents. - Commits. Agents never commit. The coordinator commits at sync points.
- Evidence. Every claim of PASS in an evidence log names the command, the shot or JSON it produced, the date and the Steam build (
11094443at the time of writing). A Steam update invalidates T3 results until RX-1 passes again.