46 KiB
UX audit: window chrome and system navigation (audit:shell-nav)
Scope: everything that frames and navigates the main Steam window on the Steam Frame:
- the header (Back, the search field) and the search results route;
- the left navigation column (the VR main menu in
frame.menu); - page transitions;
- the in-window footer button legend and the floating footer under the window;
- modals, alerts and sheets, and context menus (including the Power menu);
- toasts;
- SteamVR's window chrome under the window: grab bar, resize corner, frame controls.
The dashboard bar is covered only where it overlaps navigation: the Steam tab menu, Quick Access and the avatar.
Audited live on 2026-10-06/07 (Steam build 11041156, SteamVR web UI 11065908) with the theme off and on. Inputs: docs/inventory/shell.md, bar.md, hud.md, steamvr.md, docs/coverage/shell.md, the 12 visionOS references in docs/refs/visionos/, and the bible rulebook (../.claude/skills/run-liquid-glass-frame/references/design-language.md).
The capability results in docs/phase2/capabilities/ were empty when this was written. §C therefore names the open capability question each proposal depends on.
0. Evidence and units
0.1 Shots taken for this audit (all in shots/, 1.5x, transparent = room)
| file | what it shows |
|---|---|
p2_audit_home_off.png / _on |
Home: header (Back + search), no footer |
p2_audit_alltab_off.png / _on |
/library/tab/AllGames: tab row, filter segment, footer legend with 5 actions |
p2_audit_search_off.png / _on |
/search/tab/All with query "half": filled search field, result tabs, results, "View more in the Store" |
p2_audit_confirm_off.png / _on |
synthetic confirm dialog (no-op handlers) over AllGames |
p2_audit_menu_off.png / _on |
synthetic context menu with every item state |
p2_audit_power_off.png / _on |
the real Power menu (looked at, never selected) |
p2_audit_mainmenu_off.png / _on |
VR main menu (frame.menu.70880005), expanded by a synthetic hover |
p2_audit_vrsys_off.png / _on |
vr:systemui atlas: grab bar, resize corner, frame controls, controller status |
p2_audit_bar_on.png |
the dashboard bar, themed |
Phase 1 shots reused: shell_* (header title mode, modals, scroll panel, submenu, search focus, tab focus), hud_keyboard_before.png, hud_footer_real_before.png, vr_np_*, vr_settings_before.png, bar_framemenu_*, bar_tabmenu_*.
Snippets used: the --pre bodies in docs/inventory/shell.md §0.2 (HOME, SEARCH, CONFIRM, MENU, POWER) and bar.md §0.3 (FRAME MENU expand, with the popup id updated to 70880005).
0.2 Physical scale of each surface (measured, not assumed)
The scales come from SteamVR's scene graph in vr:systemui. They were read with buildNode() on each vsg-node[vsg-type=panel] and by walking its vsg-transform parents.
| surface | metres per CSS px | how it was derived | degrees per px at 1.43 m |
|---|---|---|---|
| main window | 0.77 mm | docs/NATIVE.md (window corners from GetTransformForOverlayCoordinates: 0.98 m for 1280 px) |
0.031° |
Steam popups (bar, frame.menu, floatingfooter, barpopup, tooltips, keyboard, toasts) |
0.847 mm | meters-per-pixel 1.5308 mm per texture px × devicePixelRatio 1.5 × frame scale 0.369 |
0.034° (the bar is 0.13 m nearer: 0.037°) |
| SteamVR frame controls, their tooltips, More Options | 0.581 mm | meters-per-pixel 1.5744 mm × frame scale 0.369 (ignore-parent-scale) |
0.023° |
| grab bar | 0.300 mm | width 0.66675 m × 0.369 over 820 px |
0.012° |
| resize corner | 0.658 mm | width 0.1 m over 152 px (no frame scale) |
0.026° |
The visionOS minimum target is 60 pt, about 2.5°. At 1.43 m that is 62 mm, which equals:
- 81 px in the main window
- 74 px in a Steam popup (67 px on the bar)
- 107 px in the SteamVR frame controls
The bible equates 60 pt with 2.5°, so 1 pt ≈ 0.042°. visionOS body text (17 pt) is then ≈ 0.71° per em, which is ≈ 23 px in the main window.
0.3 Where things sit (scene-graph offsets)
- Main window: 0.98 × 0.55 m at about 1.43 m, so it subtends 37.8° × 21.8°.
- VR main menu (
frame.menu): anchored to the window's centre-left edge (main_CenterLeft), 1.4 cm outside it and 1 cm nearer. - Frame controls: 1.5 cm below the window's bottom centre (
main_BottomCenter, translation −0.04 × 0.369). - Bar and floating footer: under translation (0, −1.2, +0.35) × 0.369, which is 0.44 m below and 0.13 m in front of the dashboard origin.
- If that origin is the window centre, the bar's top edge is about 0.17 m below the window's bottom edge.
- The vertical span from the header to the bar is then about 0.75 m, roughly 29° of gaze travel.
- The origin is an inference; confirm it with one
native/spike/hvgrabcapture.
- Laser-only panels: the grab bar, resize corner, frame controls, their tooltips and the VR main menu are visible in the headset only while a laser points at the dashboard (
only_visible_with_laser).
0.4 What could not be exercised
-
Gamepad traversal (
L.pad). At audit time Steam's navigation source was the laser (ActiveNavigationSourceType= 3). There was no.gpfocusin any window, andFocusNavController.DispatchVirtualButtonClickmoved nothing. The gamepad paths below therefore come from Steam's source:- module 37730:
onMoveLeft→OpenMainMenuin VR, andonCancelButton; - module 7284:
OpenMainMenu→FocusLeftFrameMenu; - the inventories.
A lab helper that hands focus to the main window's nav tree is needed before any Phase 2 traversal test can run (see §C.0).
- module 37730:
-
Laser
:hover. It cannot be synthesised, so hover looks are taken from CSS. -
Real toasts, the More Options popout and the header's browser mode. None has a safe trigger; they are mapped from source.
-
One transient false alarm. A first themed search shot showed no window glass and a white "View more" tile with white text. A re-shot and
glass.py audit main(72 controls, 35 text runs, 0 issues) showed it was caught during another agent's theme reload.- Lesson for Phase 2 verification: re-shoot before judging a regression.
A. Function list
"Laser" means the controller laser, which sets :hover and Steam's laser focus. "Gamepad" means Steam's FocusNavController (.gpfocus), or SteamVR's own focus where noted. Every row must stay reachable both ways after the redesign. A dash (—) means there is no path today.
A.1 Header (#header, 1280×40 px, z 6000, above the modal scrim)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| H1 | Go back (history) | click Back (%{BackContainer}, 108×40) |
B (each page's cancel handler; at the window root B opens the main menu, see N3) | Back is not focusable, so it is laser-only by design |
| H2 | Start a search | click the field (%{SearchBox}) |
D-pad Up from the top row of a page reaches the header (Main's onMoveUp); focusing the field calls onKeyboardShow |
Focusing it navigates to /search/tab/All and raises the VR keyboard |
| H3 | Type / edit the query | Steam VR keyboard (keyboard surface, 854×280 popup px, placed by SteamVR under the main window): click keys |
D-pad over keys + A (keyboard nav tree) | The keyboard is in minimal mode for Steam fields: no text echo on the keyboard itself, so the typed text is visible only in the header |
| H4 | Clear the query | the field's clear (x) button (26 px circle) | Backspace on the keyboard | |
| H5 | Read the page title (title mode) | — (text only) | — | e.g. /controller/calibration/0; Back stays |
| H6 | Browser mode: URL bar | click | focus + A | on Steam web views; replaces search, removes Back. Not seen live |
| H7 | Account/support alert | click #header_profile |
focus + A | only with active support alerts |
A.2 Search results route (/search/tab/{All,Library,Friends,Store,Tools,Hidden})
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| S1 | Switch result category | click a tab (34 px tall capsules with counts) or the ‹ › arrows (32×32) | LB / RB (Steam's tabbed-page convention), or D-pad to the tab row + A | Empty categories can be hidden ("Hidden" appears only with matches) |
| S2 | Open a result | click a tile (game → app page, friend → profile, store → store page) | D-pad in the virtualized grid + A | tiles are about 191×89 px art (≈5.9° × 2.7°) plus a label row |
| S3 | "View more in the Store" | click the last tile | focus + A | opens store search |
| S4 | Leave search | Back, or any nav item | B | Clearing the text does not leave the route |
A.3 Left navigation: VR main menu (frame.menu.<id>, popup quad at the window's left edge)
Items, in order:
- Home
- Library
- Store
- Friends & Chat
- Media
- Downloads
- Console (Steam's own developer-console entry, module 80096, shown when its developer flag is set; it is present on this device)
- Steam Settings
- (Help, only during setup)
- VR Settings (SteamVR action 432800008: switches the Steam frame to SteamVR's own settings page)
- a gap, then Power (opens the Power menu, A.6)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| N1 | See the menu | appears only while the laser is active. Collapsed to 54 px icons; expands 500 ms after hover, collapses 800 ms after leaving | shown when it takes focus | The current section has a blue dot, so it is visible only when the menu is |
| N2 | Go to a section | click an item | D-pad Left at the left edge of any page's content (onMoveLeft → OpenMainMenu → FocusLeftFrameMenu), then Up/Down + A |
Right or B returns focus to the window |
| N3 | Open the menu from anywhere | — | B at the window root (onCancelButton → OpenMainMenu) |
The behaviour of B depends on history depth |
| N4 | Same items, second path | hover the bar's Steam tab: its tab menu (%{Variant_TabMenu}) lists the same items |
focus the bar (View button, "Cycle View"), then the Steam tab. Whether gamepad focus opens the tab menu was not verified | a duplicate of N2 in another quad |
A.4 Footer legend (#Footer, bottom of the window, 42 px tall, z 7000)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| F1 | Perform the focused element's actions (example, AllGames: X Filter, Y Sort By, ☰ Options, A Select, B Back) | click a legend item (%{ActionButtonLegend}, 83–106 × 35 px). It dispatches the action |
the physical button | For the laser this is the only path to Filter, Sort By and the Options (context) menu of a tile. Hidden on Home. The legend changes with focus |
A.5 Floating footer (floatingfooter quad under the window)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| FF1 | Learn the global buttons: "Cycle View" (View button: moves dashboard focus window ↔ bar) and "Laser Mouse" (LG/RG grips) | — (interactive:false) |
the shown buttons | shown when the dashboard has no focus. Dimmed to 60% by Steam's filter: brightness(.6). 12 px uppercase |
A.6 Modals, alerts and sheets (ModalManager, in-window, scrim z 1500)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| M1 | Confirm / cancel a dialog | click OK / Cancel (about 278×40 px each). Click outside the card = cancel (ModalClickToDismiss) |
D-pad Left/Right between buttons, A, B = cancel | Opening a modal closes every bar popup |
| M2 | Alert (single Close) | click | A / B | |
| M3 | Text prompt | click the input → keyboard | focus + A → keyboard | |
| M4 | Scroll a long dialog | wheel/drag in ModalPosition or %{ScrollPanel} |
D-pad moves focus and scrolls (FocusRing visible) | |
| M5 | Header and footer during a modal | Back, the search field and footer legends stay above the scrim, undimmed (header z 6000, footer z 7000) | B goes to the dialog | Whether Back and search accept clicks while a dialog is up was not tested (no side-effect-free way) |
A.7 Context menus (gamepad presentation: a centred sheet, not anchored to the invoker)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| C1 | Choose an item | click a row (48 px tall, min 280 px wide) | D-pad + A | Steam always appends a separator and Cancel |
| C2 | Open a submenu | click the row with › | A or Right | the submenu opens beside it |
| C3 | Toggle a checked item | click | A | |
| C4 | Dismiss | click outside, or Cancel | B | |
| C5 | Scroll a long menu | wheel | D-pad | scroll-padding 72 px |
Power menu (from N1 Power), in order:
- Sleep
- Shutdown
- Restart Device
- Restart Steam VR (Steam's preferred focus)
- separator
- Change Account
- Sign Out
- Restart Steam
- separator
- Cancel
Every item except Cancel opens a confirmation dialog. In the stock capture the first focus sat on the destructive Restart Steam VR (p2_audit_power_off.png). In the themed run the first focus was Sleep, so the initial focus is timing-dependent.
A.8 Page transitions (no user function, but they gate input)
Route change:
- the old page fades and scales out over 200 ms with
pointer-events:none; - the new page waits 200 ms, then scales 0.95→1 and fades in over 600 ms.
About 800 ms per navigation in total. The app-details overlay cross-fades with a blurred scrim.
A.9 Toasts (notifications quad, 340×80 popup px, placed by SteamVR relative to the dashboard)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| T1 | Read a notification (title 11 px, body 12 px, 40×40 logo) | passive; the card has a laser hover fill | — | Disappears after --toast-duration. No VR motion (enter/exit are opacity 1→1). Click behaviour not verified (no safe trigger). The notification list itself lives in Quick Access › Notifications (bar) |
A.10 SteamVR window chrome (vr:systemui panels; laser-only visible)
| # | function | laser | gamepad | notes |
|---|---|---|---|---|
| W1 | Move the window | drag the grab bar under the window (visual 636×12 px = 191×3.6 mm; hit area 246×40 mm) | — | not focusable (bCanTakeKeyboardFocus:false) |
| W2 | Resize the window | drag the bottom-right resize corner (100×100 mm hit) | — | |
| W3 | Show/hide the keyboard | frame control 1 (keyboard icon) | D-pad Down from the window into the frame controls (%{HasGamepadFocus}), Left/Right, A |
toggle; the icon swaps when active |
| W4 | Float in World / View in Theater | frame controls 2–3 (icons only; names in tooltips) | same | move the window |
| W5 | More Options › Toggle Curvature, Dock on Left Controller, Dock on Right Controller | frame control 4 opens a popout | same; the popout takes SteamVR input focus | |
| W6 | Close (game frames only) | frame control after a wide gap | same | exits the app |
| W7 | Tooltips for W3–W6 | hover | focus | capsules 0.15 m above the button |
| W8 | "Enter Gamepad Mode" / "Use Laser Mouse to Interact" pill | click (enter variant only) | — | appears under laser-only pages (SteamVR Settings, bindings) |
| W9 | Now Playing frame (while a scene app runs): Resume, VR Controller Bindings, VR Video Settings, Exit Game | click | SteamVR's own focus: D-pad Up/Down, A, B = Resume | a different app's look inside the same dashboard |
| W10 | SteamVR Settings page (via VR Settings, N2) | click; laser only | none: a "Use Laser Mouse to Interact" banner | the Steam frame shows SteamVR's page in place of Steam's window |
A.11 Bar entries that belong to navigation (detail in bar.md)
- Steam tab: selects the Steam frame; its hover menu is N4.
- App tabs: switch frames.
- + Launch Program: lists programs, including Liquid Glass.
- Small buttons: Playspace Menu, Toggle Room View, Streaming Status.
- Quick Access: clock and status; opens the QAM with Notifications, Quick Settings, Performance, Battery and Help.
- Avatar: goes to
/account. - Gamepad: the View button cycles focus to the bar; then Left/Right + A. Tooltips show on focus.
B. UX critique for VR
B.1 Target sizes (angles at 1.43 m, or 1.30 m for the bar)
| control | size (px) | physical | angle (limiting side) | vs 2.5° |
|---|---|---|---|---|
| Header height / Back | 40 (Back 108×40) | 31 mm | 1.23° | 49% |
| Search field | 1172×40 | 902×31 mm | 1.23° tall | 49% |
| Search clear (x) | 26 | 20 mm | 0.80° | 32% |
| Search / library tabs | 34 tall | 26 mm | 1.05° | 42% |
| Tab-row arrows ‹ › | 32×32 | 25 mm | 0.99° | 40% |
| Footer legend item | 83–106 × 35 | 27 mm | 1.08° | 43% |
| Dialog button | ~278×40 | 31 mm | 1.23° | 49% |
| Context-menu / Power row | 48 tall | 37 mm | 1.48° | 59% |
| VR main menu row (popup) | 54×54 | 46 mm | 1.83° | 73% |
| Bar tab (popup, nearer) | 80×80 | 68 mm | 2.99° | 120% (OK) |
| Bar small button / + hit | 48 / 54×48 | 41 mm | 1.79° | 72% |
| Toast card | 300×40 | 34 mm | 1.36° | — (passive) |
| VR keyboard key row | ~47 | 40 mm | 1.6° | 64% |
| Frame controls (SteamVR) | 67×51 | 39×30 mm | 1.19° | 48% |
| Grab bar: visual / hit | 12 / 132 tall | 3.6 / 40 mm | 0.14° / 1.59° | 64% (hit) |
| Resize corner | 152 | 100 mm | 4.0° | OK |
Findings
- Every in-window chrome control (header, tabs, footer legend, dialog buttons) is 1.0–1.25°: half the visionOS minimum. A typical controller laser at 1.4 m jitters by a few tenths of a degree, so these targets need deliberate aiming.
- The worst offenders are the ones used most: Back and search (1.23°), footer legends (1.08°, the laser's only route to Filter, Sort and Options) and tabs (1.05°).
- The window chrome under the window (frame controls 1.19°, grab bar visual 0.14°) is small and shown only while the laser is active.
- Its hit sizes are SteamVR panel sizes, and changing a panel root's box resizes the quad. A CSS-only redesign cannot enlarge them.
- Spacing: footer legends touch (0 px gaps), as do the tabs (2 px). visionOS wants centres ≥ 60 pt apart or a 16 pt margin.
B.2 Legibility
| text | size | angle (em) | vs visionOS body ≈ 0.71° |
|---|---|---|---|
| Search placeholder (italic, grey) | 16 px | 0.49° | 69%, and italic thin strokes |
| Back label, dialog body, menu rows | 16 px | 0.49° | 69% |
| Tab titles, result labels ("IN LIBRARY") | ~14 px bold uppercase, tracked | 0.43° | 61% |
| Footer legend labels | 12 px bold uppercase | 0.37° | 52% |
| Toast title / body | 11 / 12 px | 0.37–0.41° | 52–58% |
| Floating footer | 12 px uppercase at 60% brightness | 0.41° (popup) | 58%, and dimmed |
| Dialog title | 22 px bold | 0.68° | ~96% |
Findings
- Chrome text runs at 50–70% of visionOS body size.
- Uppercase tracked labels (tabs, footer, result labels) remove word shapes, which costs most at small angles.
- The placeholder is italic grey on glass, the weakest combination.
- Phase 1 fixed colour contrast, but not size.
B.3 Discoverability
- The main navigation is invisible most of the time.
- It is a separate quad that appears only with an active laser, collapsed to icons.
- A gamepad user has no visual cue that pressing Left at the edge of a page, or B at the root, opens it.
- The "you are here" indicator (blue dot) lives only in that hidden menu. The window shows no section title (compare visionOS Settings, ref 8: section title centred in the toolbar).
- The footer legend does not look clickable. It reads as console glyph hints (Steam Deck heritage). Laser users may never learn that it is the only way to sort, filter or open a game's Options.
- Back is laser-only and B is gamepad-only. That is consistent, but on Home Back is still drawn and its effect (history) is not predictable from the UI.
- The frame controls are icon-only. Pop-out = Float in World; the curved-screen icon = View in Theater. They idle at 60% opacity and are visible only with the laser. Curvature and docking are hidden under More Options.
- The floating footer explains the two most important global buttons (View = Cycle View, grips = Laser Mouse) in the dimmest text of the whole UI, below the window.
- "VR Settings" and "Steam Settings" sit side by side but open two different apps, one of which a gamepad cannot operate (W10).
B.4 "Window into another app" seams
Ranked by how often the user meets them:
-
Search (the user's example):
- A 40 px, full-width text strip with an italic placeholder: a browser address bar, not an OS search.
- On the search route it becomes a light-grey
#b8bcbfweb-form field with dark text (stock). - The keyboard appears as a separate black slab below the window, about 25° of gaze from the text being typed. Minimal mode shows no echo.
- Results replace the whole page as a new route with its own tab row. Search is a destination, not an overlay.
-
Header idiom. "← Back | 🔍 Search…" with a 1 px divider is a desktop-browser toolbar. visionOS uses a circular back chevron, a centred title and capsule fields (refs 5, 8, 4).
-
Context menus and the Power menu.
- They are centred full-window modal lists with a floating text title, disconnected from whatever invoked them.
- visionOS menus grow out of their button.
- Destructive items look like normal rows until focused.
-
Footer legend. A gamepad-console legend strip in the bottom-right corner of a spatial window. visionOS has no legends; actions are buttons in a toolbar ornament (ref 11, the Music playback ornament).
-
Two apps in one frame. VR Settings swaps the window for SteamVR's settings (different type scale, purple "advanced" fills, laser-only banner). Now Playing and Controller Bindings are also SteamVR pages with their own idioms (AllCaps title bar, 72 px titles).
-
Three focus languages:
- Steam's
.gpfocuswhite fills; - SteamVR's
%{GamepadFocused}grey fills and the blinking%{FocusRing}outline (Now Playing); - the bar's ring hint.
Moving focus between the window, menu, bar and frame controls changes the look of "focused" each time.
- Steam's
-
Window chrome under the window.
- SteamVR's grey rounded-square pills and a 12 px dark grab line form a different shape vocabulary from Steam's.
- In dark mode the grab and resize panels are multiplied to 30% brightness.
- In stock the grab bar is a dark line on a dark background.
-
Toasts. Steam Deck's 300×40 toast with a 1 px grey outline, square corners and 11 px text. visionOS notifications are rounded glass cards with an app-icon circle.
-
Page transitions. About 800 ms with a 200 ms dead time and a scale "breath" of the whole 38° window. It reads as a web page loading, not an OS view change.
B.5 Navigation model problems
-
Four navigation surfaces in four quads:
- the hidden left menu;
- the bar's Steam tab menu (the same items);
- B / Back history in the window;
- the bar itself (tabs, Quick Access, avatar → account).
The user must learn which quad holds which function. Nothing in the window says where you are.
-
Search is a route, not an overlay. Entering search loses the current context, and leaving needs Back. There are no recent searches or suggestions before typing; an empty query lists the whole library.
-
Focus hand-offs between quads are invisible.
- Left at the page edge jumps into another quad (the menu).
- Down from the window bottom jumps into SteamVR's frame controls.
- View cycles to the bar.
Each hop changes the focus style (B.4 item 6) and the menu/frame controls may not be visible until focus arrives.
-
B is overloaded: page back, close modal, close menu, open main menu at the root.
-
Modal scope. The header and footer stay above the scrim, undimmed, so the Back and search targets look live during a confirm dialog.
-
Power is one click from navigation, and its menu can open with focus on a destructive item (Restart Steam VR). Account actions (Change Account, Sign Out) are mixed in with device power.
-
Ten to eleven nav items (with Console and Help) against visionOS's ≤ 6 tabs. Power is in the nav list rather than a system place (QAM or Control Center, ref 3).
-
A gamepad dead end: VR Settings (W10) cannot be operated without the laser.
B.6 Ergonomics (reach, neck, distance)
-
The most-used controls sit at the extremes:
- Back: top-left corner (≈ −18°, +10° from window centre);
- search: along the top edge;
- footer legends: bottom-right (≈ +4° to +19°, −10°);
- the main menu: outside the left edge (≈ −19° to −27° expanded);
- the bar: ≈ 0.44 m below the dashboard origin.
A typical "browse → sort → open → back" loop sweeps the full 38° × 22° window plus the bar. Comfortable eye-only rotation is commonly put at about ±15°; beyond that the head turns.
-
Search ↔ keyboard: the field is at the window's top edge and the keyboard is under the window. Every glance from keys to text crosses the whole window height (≈ 22–28°).
-
Laser arm posture: small targets (B.1) mean holding the arm steady. The bottom-right footer and the bar below need the wrist dropped further.
-
Distance: 1.43 m is fine for reading (≥ 1 m), but at that distance Steam's px are smaller than visionOS points. All chrome is about 60% of the visionOS angle, so the fix is size, not distance.
-
Vertical stack: window, frame controls (1.5 cm below), grab bar, floating footer, then the bar (+ its tooltips below it, + its popups growing upward). About 29° from header to bar, with the bar 13 cm nearer. That depth step makes the bar a separate plane to refocus on.
B.7 Motion versus the Liquid Glass spec
| element | today | Liquid Glass / bible |
|---|---|---|
| Route change | 200 ms delay + 600 ms cubic-bezier(0,0,.1,1) fade + scale .95→1; exit 200 ms |
Materialize, not fade (P7). Springs: materialize 120–160 stiffness / damping 20, about 220–300 ms. No dead time. Avoid scaling a 38° field (peripheral motion) |
| Dialog / context menu | 500 ms opacity + scale(1) cubic-bezier(.16,.86,.43,.99) |
220 ms spring materialize; sheet: parent pushes back and dims |
| Main menu expand | width 300 ms after a 500 ms hover delay | visionOS tab bar expands on look; the delay is good but should be shorter (~250–350 ms) |
| FocusRing | pulses 20 × 1.2 s (≈0.8 Hz, about 24 s) | "No pulsing at rest"; avoid oscillation, especially near 0.2 Hz |
| Frame controls | opacity .6 ↔ 1 over 0.4 s | fine (state change) |
| Toasts (VR) | no motion (opacity 1→1) | materialize in, dematerialize out |
B.8 Materials versus the brief (Phase 1 state, from the _on shots)
- Window glass is tint + rim. It cannot frost or lens the room (CSS cannot see passthrough). The brief requires real Liquid Glass, which means T5
glassd. - Visible outline strokes. The search capsule, tab capsule, footer capsule and menus show a thin light outline stroke. The brief says menus are glass slabs whose edge comes from the shader (specular rim and lensing), not from a stroke.
- The VR main menu themed is a flat grey gradient slab with a hairline (
p2_audit_mainmenu_on.png): neither visionOS tab-bar glass nor a capsule. - No stereo. All chrome lies on the window plane. The brief asks for subtle pop-out (header and footer capsules, menus, sheets).
C. Redesign opportunities, ranked by impact
Impact = how often the user meets it × how far it is from visionOS × how much it fixes size or navigation. Tiers: T1 CSS incl. layout, T2 DOM augmentation, T3 Steam React views, T4 spatial compositor, T5 glassd.
Rules that apply to every item:
- Steam's nodes keep their identity, focusability and handlers.
glass.py auditmust report 0 GONE / HIDDEN / SHRUNK / UNCLICKABLE ("moved >24px" is expected in Phase 2).- D-pad direction must still match the visual layout. Steam's nav tree declares a
flow-childrendirection (row or column) for each group, so a CSS reflow that turns a row into a column makes Left/Right move focus up and down the screen.
C.0 Prerequisites found by this audit (do first)
- Header height is a constant, footer height is measured.
- The footer is measured:
#Footerhas a resize observer that writesm_flCurrentFooterHeight(module 97502). So a taller footer automatically moves modals and page bottoms through--gamepadui-current-footer-height. - The header is not:
m_flCurrentHeaderHeightis set from the CSS-module constantHeaderHeightVisible: "40px"(module 37730, constant in 329). A taller header therefore needs both:--basicui-header-height: Npx !importanton%{BasicUiRoot}(beats Steam's inline var);- setting
HeaderStore.m_flCurrentHeaderHeight = N(T3, in memory, restored on theme off), because JS consumers read it through a hook in module 1282.
- Capability question CQ1: do both hold, and does scroll-into-view clear the taller header?
- The footer is measured:
- A lab helper to activate gamepad focus in the main window, so
L.padtraversal can be verified unattended. TodayL.padsilently does nothing when the navigation source is the laser. - Transient theme reloads by other agents make single shots unreliable. Verification should shoot twice, or check
glass.py statusand a known element, before judging.
C.1 Search becomes a system search (highest impact; the user's named example)
- Problem: B.4 item 1, B.1 (1.23°), B.6 (field ↔ keyboard).
- visionOS pattern: a capsule search field with a leading magnifier and a mic slot (ref 4 Messages, ref 8 Settings sidebar search, ref 11 Music). Results appear in place.
- Proposal:
- The header search becomes a centred Liquid Glass capsule 64–72 px tall (≈2.0–2.2°; 80 px = 2.5° if the header can be 88 px), about 640–760 px wide, with a 22–24 px upright (not italic) placeholder at
--lgs-text-2. - In its idle state it is a field, not a strip. On the search route it stays the same glass capsule (no grey web field).
- The query echo moves near the keyboard. Use the keyboard's own buffered text row if Steam can be told to use it for Steam fields (CQ5), or a T4 display-only crop of the field shown just above the keyboard. Eyes stay within about 5° while typing.
- The search sheet (T3): focusing the field opens a glass sheet over the current page (not a route change) with recent searches and top library matches as large rows (≥ 64 px). Typing hands off to Steam's real
/search/tab/Allroute, so every existing result path (S1–S4) stays untouched. - Results: keep Steam's virtualized grid. Enlarge the result tabs to ≥ 56–64 px capsules in one glass segmented control. Labels in title case, not tracked uppercase.
- The header search becomes a centred Liquid Glass capsule 64–72 px tall (≈2.0–2.2°; 80 px = 2.5° if the header can be 88 px), about 640–760 px wide, with a 22–24 px upright (not italic) placeholder at
- Tiers:
- T1: header layout and field size, after CQ1;
- T2: magnifier glyph and placeholder text unchanged;
- T3: the sheet and the
m_flCurrentHeaderHeightsync; - T4: pop the capsule +1.5 cm in stereo at the same x/y;
- T5: real glass slab.
- Retention:
- the
%{SearchBox}input stays Steam's element, so keyboard, IME,onKeyboardShowand route behaviour are unchanged; - the gamepad still reaches it with D-pad Up;
- the clear (x) grows to ≥ 44 px inside the capsule;
- the sheet must be a first-class nav tree (Down from the field enters it, B closes it).
- the
- Verify:
shot main p2_search_*for idle, filled and simulated focus (gpfocuswithinclass as indocs/coverage/shell.mdFIELDFOCUS);audit mainon Home and the SEARCH pre;L.padUp/Down after C.0.2;- an hvgrab capture to confirm the depth and the keyboard-echo position.
- Open: CQ1, CQ5 (keyboard buffered mode or T4 echo), CQ8 (a T3 overlay as a gamepad nav tree).
C.2 The VR main menu becomes a visionOS tab-bar ornament, always visible
- Problem: B.3 item 1, B.5 items 1 and 7, B.1 (1.83°).
- Key insight: Steam's VR main menu is already structurally a visionOS tab bar. It is a separate quad hugging the window's left edge, collapsed to icons, and it expands on hover, exactly like TV (ref 5) and Music (ref 11). It fails only on visibility (laser-only), shape (a square slab of rows), size and selected state.
- Proposal:
- Shape and size:
- a vertical glass capsule with circular items 64–72 popup px (2.2–2.4°) via the component variables
--menu-item-height,--menu-icon-sizeand--menu-item-padding(Steam's own custom properties on%{DashboardMenu}), with 24–28 px icons; - selected = white circle with a dark glyph (DESIGN §3: white is reserved for selected);
- labels appear on expand as a glass pill to the right of each icon, as in Music (ref 11);
- shorter expand delay (CQ: is the 500 ms a JS constant we may change? if not, keep it).
- a vertical glass capsule with circular items 64–72 popup px (2.2–2.4°) via the component variables
- Always visible. The frame menu popup is
only_visible_with_laser.- Option A (T3): change that popup flag in memory (CQ3).
- Option B (T4): show a non-interactive crop of the menu's texture at its exact position whenever the real quad is hidden. When the laser is active the real quad covers it and takes input. Gamepad focus already moves into the real quad (N2); verify that it becomes visible then.
- Grouping:
- Primary: Home, Library, Store, Friends, Media.
- Utilities group (separate capsule): Downloads, Console, Steam Settings, VR Settings.
- Power last, in its own small circle.
- This uses Steam's existing
%{SectionGap}split plus CSS spacing; no node moves.
- Where am I: a T2 decorative label in the header showing the current section (from the active item), e.g. "Library", 28–34 px bold, like ref 8's toolbar title.
- Shape and size:
- Tiers: T1 (variables, shape), T2 (title label), T3 or T4 (visibility), T5 (capsule glass), T4 (ornament at +2–3.5 cm, as in the bible's tab bar at z +3.5 cm).
- Retention:
- the same
DashboardMenuitems, routes and actions; - D-pad Left at the page edge and B at the root still open it;
- the bar's Steam tab menu (N4) remains as a second path;
- Power still opens the same menu.
- Do not set
widthon the menu (it animates); the size changes go through the item variables, and the collapsed width follows the item size.
- the same
- Verify:
shot frame.menu.<id> p2_nav_*collapsed, expanded and active-route (bar.md§3 commands);audit frame.menu.<id>;- pad Left from the first tile, then Up/Down, then Right (after C.0.2);
- an hvgrab capture with the laser idle to prove visibility.
- Open: CQ3 (visibility), plus whether
--menu-item-heightchanges keep the item'sscale(1.1)active transform inside the 300 px popup.
C.3 The header becomes a visionOS toolbar: circular Back, title, search
- Problem: B.1 (Back 1.23°), B.4 item 2, B.3 item 3.
- visionOS pattern: a circular back chevron at the top-left inside the window (ref 5 TV, ref 8 Settings), a centred title, and trailing circular action buttons (ref 2 Image Playground).
- Proposal:
- Back becomes a 64–72 px glass circle with a chevron glyph. Steam's "Back" text node stays in the DOM and visible, because hiding Steam text trips the audit's HIDDEN check. It can sit beside the chevron as a short label in the same capsule, or appear in a hover pill if a capability confirms that this passes the audit. T2 adds an
aria-label. - Drop the 1 px divider look.
- Header height becomes 80–88 px with a scroll-edge glass band instead of a black bar (C.0.1).
- Back becomes a 64–72 px glass circle with a chevron glyph. Steam's "Back" text node stays in the DOM and visible, because hiding Steam text trips the audit's HIDDEN check. It can sit beside the chevron as a short label in the same capsule, or appear in a hover pill if a capability confirms that this passes the audit. T2 adds an
- Tiers: T1, T2, T4 (Back and the search capsule popped +1.5 cm), T5.
- Retention:
- Back stays
%{BackContainer}(laser click; the gamepad keeps B); - title mode (H5) and browser mode (H6) get the same toolbar (the title centred, the URL bar as the capsule).
- Back stays
- Verify:
- shots on Home, AllGames, title mode (
/controller/calibration/0) and a game page; - the audit (Back must not shrink);
L.click('main','%{BackContainer}')on a safe route returns to the previous safe route.
- shots on Home, AllGames, title mode (
C.4 The footer legend becomes a bottom action ornament with real buttons
-
Problem: B.1 (1.08°), B.2 (12 px uppercase), B.3 item 2, B.4 item 4, B.6.
-
visionOS pattern: the bottom toolbar or playback ornament (ref 11 Music: a capsule overlapping the window's bottom edge; ref 7 Safari toolbar).
-
Proposal:
- The legend becomes one centred glass capsule of buttons 64–72 px tall (2.0–2.2°), centres ≥ 80 px apart.
- Each button: a mixed-case label at 18–20 px with the controller glyph kept as a small leading badge, so it still teaches the gamepad mapping.
- Primary-ish actions (Options, Sort, Filter) read as buttons. Select and Back may become compact circles.
- Pop +1.5 cm (T4, same x/y); slab glass (T5).
- The footer height grows, and Steam's measured
--gamepadui-current-footer-heightmoves modal and page bottoms automatically (C.0.1).
-
Overlapping the window's bottom edge as visionOS does is not possible in T1 (the window quad clips). It needs either:
- a T4 crop moved down with proven input routing (CQ4), or
- making the
floatingfooterquad interactive (it isinteractive:falsetoday; CQ: can a T3 change make it interactive and host the same actions?).
Until then, keep it inside the bottom edge.
-
Retention:
- legend items stay
%{ActionButtonLegend}with their click dispatch (laser) and are unchanged for gamepad buttons; - the content still varies with focus.
- legend items stay
-
Verify:
- FOOTER shot (AllGames), a single-action shot (title mode) and over a modal (CONFIRM);
- the audit;
- a laser click on the "Back" legend on a safe route (other legends act on the focused game; look, don't click).
C.5 Modals and alerts become visionOS alerts and sheets with depth
- Problem: B.1 (buttons 1.23°), B.5 item 5, B.7, B.8.
- visionOS pattern: alerts are compact glass cards with bold left-aligned titles and capsule buttons. A sheet comes forward while the parent window pushes back and dims (bible §5: sheet +10 cm, window −7 cm, alpha × 0.45).
- Proposal:
- The dialog card becomes thick glass at
--lgs-r-sheetwith 22–26 px titles and 18–20 px body text. - Buttons become ≥ 64 px capsules. Keep them side by side (
DialogTwoColLayoutis a row in the nav tree; stacking them would invert the D-pad directions). Primary tinted. - While a modal is open, visually recess the header and footer (lower contrast, no glass highlight), so they stop looking like live targets. Any dimming layer must be
pointer-events:noneand must not cover the footer legends, which stay operable. - Depth (T4): the dialog crop at +3 cm, the window base at its rest depth and dimmed by glassd. The x/y are unchanged, so input still lands on the real dialog.
- Motion: a 220–300 ms materialize (
scale/opacityindependent properties, not Steam'stransform).
- The dialog card becomes thick glass at
- Retention: the same buttons and the same
ModalClickToDismiss. The scroll container keepsoverflow. - Verify: CONFIRM, ALERT and ZOO text prompt / scroll panel shots; the audit; hvgrab for depth.
- Open: CQ7 (pushing the window plane back while the dialog stays put).
C.6 Context menus and the Power menu become glass menus with safer defaults
- Problem: B.1 (rows 1.48°), B.4 item 3, B.5 item 6.
- visionOS pattern: a menu bubble that grows out of its button (bible §10 "menu bubble"), rows about 44–52 pt, destructive items in red.
- Proposal:
- Rows 64 px, 18–20 px text,
--lgs-r-rowradius. - The menu title becomes a header inside the slab (not floating above).
- Destructive rows are tinted red at rest (label colour, plus the whole-row red fill on focus).
- Power splits visually into "Device" (Sleep, Shutdown, Restart Device, Restart SteamVR) and "Account" (Change Account, Sign Out, Restart Steam) cards, using the existing separators.
- Depth +3 cm (T4).
- Anchoring to the invoker is attractive, but Steam's VR presentation is always the centred sheet. Treat anchoring as T3-only research (CQ10) and do not pursue it in the first pass.
- Default focus: making the Power menu open on Cancel or Sleep instead of Restart Steam VR is a behaviour change. List it as an option needing the user's approval, not a default.
- Rows 64 px, 18–20 px text,
- Verify: MENU, SUBMENU and POWER shots (look only); the audit; pad Up/Down inside the menu.
C.7 SteamVR window chrome becomes a visionOS window bar
- Problem: B.1 (frame controls 1.19°, grab visual 0.14°), B.3 item 4, B.4 item 7.
- visionOS pattern: a single grab pill under the window plus a close dot (ref 2, ref 8, ref 12); the resize corner appears on look.
- Proposal:
- Grab bar: a thicker glass pill visual (8–10 mm) drawn inside the existing 40 mm hit area. Bright on hover. Counter the dark-mode 0.3 tint by painting brighter.
- Frame controls: individual glass circles with white icons. Tooltip capsules show the names.
- More Options: a glass menu.
- Hard limit: these hit areas are SteamVR panel sizes (1.19°). Enlarging them requires changing panel roots, which resizes the VR quad. Only T4 own-ornament panels with proven interactive routing (CQ4) could provide larger targets.
- Until then, improve visibility only:
- higher idle opacity than Steam's 0.6. That is Steam's opacity rule, so it must stay, but brighter fills compensate.
- glassd slabs behind each circle (T5).
- Verify:
shot vr:systemui p2_chrome_*withsteamvr-prehover and focus scripts; the panel-size check insteamvr.md§7 (sizes identical on and off);audit vr:systemui.
C.8 Page transitions follow the Liquid Glass materialize spec
- Problem: B.7 (≈800 ms, a 200 ms dead time, a full-field scale).
- Proposal:
- Shorten to a ~250 ms ease-out (spring approximation), with no delay.
- Smaller travel: scale .98, or none, with opacity only.
- Keep reduced motion at 1 ms.
- The theme may change only
transition-duration,transition-delayandtransition-timing-functionon%{TopLevelTransition}; never itstransform/opacityend states.
- Open: CQ6 (does Steam's transition group rely on
transitionendor on fixed timeouts? If it uses timeouts of 200/600 ms, shortening CSS alone may cause a pop). Verify by capturing frames duringL.nav.
C.9 Toasts become visionOS notification cards
- Problem: B.2 (11–12 px), B.4 item 8.
- Proposal:
- Use the whole 340×80 quad (layout inside the toast window only; no nav tree involved): a rounded glass card with a circular 48 px app icon, a 16–18 px title and a 14–15 px body, up to 2 lines.
- Materialize in and out (replacing Steam's VR opacity 1→1 no-op).
- Placement is SteamVR's (
ShowCustomNotification); leave it.
- Verify: the static mock recipe in
hud.md§4.3 hosted in a larger window (a 40 px tooltip host cannot show a 2-line card; CQ9: a lab host for an 80 px mock).
C.10 One focus and hover language across all quads
- Problem: B.4 item 6.
- Proposal:
- Map
.gpfocus(Steam),%{GamepadFocused}and.gpfocus(SteamVR) and%{FocusRing}to one treatment: glass "illuminate from within" fill + ring (DESIGN §3). - Replace the 20× pulsing ring with a static ring.
- Laser hover = a soft fill (glassd could add the bible's Gaussian hover light at the ray hit only if SteamVR exposes the laser hit point; research item).
- Map
- Tiers: T1 everywhere; T5 optional.
C.11 Floating footer hints become legible
- Proposal:
- Restyle
%{FloatingVRFooter}: replacebrightness(.6)with full-brightness glyphs on a small glass capsule (inside the clip rect). - Mixed-case 16–18 px labels.
- The quad is non-interactive, so this is a hint only.
- Restyle
- Verify:
hud.md§3.2 recipes.
C.12 VR Settings stops feeling like another app (cross-area; owner: settings / steamvr)
- Proposal:
- Theme SteamVR's settings page to the visionOS Settings pattern (ref 8: sidebar of rows with circular coloured icons, a white selected pill, grouped rounded rows).
- Flag the gamepad dead end (W10) to the settings owner. Making SteamVR settings gamepad-navigable is outside CSS (T3-level work in SteamVR's own React, not Steam's).
Summary ranking
| rank | opportunity | main fix | tiers | blocking question |
|---|---|---|---|---|
| 1 | C.1 system search | size, seam, typing ergonomics | T1 T2 T3 T4 T5 | CQ1, CQ5, CQ8 |
| 2 | C.2 tab-bar ornament | discoverability, where-am-I, size | T1 T2 T3/T4 T5 | CQ3 |
| 3 | C.3 toolbar header | Back size, OS idiom, title | T1 T2 T4 T5 | CQ1 |
| 4 | C.4 action ornament | laser path to Sort/Filter/Options, size | T1 T4 T5 | CQ4 (overlap) |
| 5 | C.5 alerts and sheets | button size, depth, modal scope | T1 T4 T5 | CQ7 |
| 6 | C.6 menus and Power | row size, destructive safety | T1 T4 T5 | (approval for default focus) |
| 7 | C.7 window bar | visibility of move/resize/controls | T1 T5 (T4) | CQ4 |
| 8 | C.8 transitions | LG motion, perceived speed | T1 | CQ6 |
| 9 | C.9 toasts | legibility | T1 | CQ9 |
| 10 | C.10 focus language | consistency | T1 (T5) | — |
| 11 | C.11 floating footer | hint legibility | T1 | — |
| 12 | C.12 VR Settings | app seam (cross-area) | T1 (vr) | owner |
Capability questions for docs/phase2/capabilities/
| id | question |
|---|---|
| CQ1 | Taller header: does --basicui-header-height with !important, plus a T3 write of m_flCurrentHeaderHeight, keep modals, search padding and scroll-into-view correct? |
| CQ3 | Can the frame.menu popup be shown without the laser (T3 popup flag), or a T4 crop be shown in its place when the real quad is hidden? Is it visible while it holds gamepad focus? |
| CQ4 | Can a T4 crop be moved off its original x/y with input routed to the real element (needed for ornaments overlapping the window edge and for larger frame controls)? |
| CQ5 | Can Steam's VR keyboard show its text buffer for Steam UI fields without behaviour change, or can a T4 display-only crop echo the field above the keyboard? |
| CQ6 | Do Steam's route transitions finish on transitionend or on fixed timers? |
| CQ7 | Can T4 recess the window plane and dim it while a dialog crop stays at its x/y? |
| CQ8 | Can a T3 overlay (the search sheet) join the main nav tree as a first-class gamepad target with B to close? |
| CQ9 | A lab host for 340×80 toast mocks |
| CQ10 | Can a context menu be shown anchored in VR (non-centred presentation) without changing its handlers? |