Polish pass over all three gamepad UIs (pf-console-ui for Linux/Windows, Apple, Android).
Section tabs
The console settings were one 30-row scroll, which on a Deck meant thumbing past Video and Audio to reach the pad settings. They are now Stream · Video · Audio · Controller · Interface · Profiles, plus Input on the desktop console, which alone carries the touch/mouse rows. L1/R1 walks them, each section remembers where its cursor was, and the names are the same word on every client so a setting is where you looked for it last. The in-list group headers that just repeated the section name are gone.
Shoulders are not the only route, because a D-pad remote hasn't got any:
Android — Up from the first row moves onto the strip (left/right walks sections there, A drops back in). Pills are tappable too.
tvOS — the pills are focusable Buttons, so the focus engine handles it. A Siri Remote has no extended gamepad profile and never reaches GamepadMenuInput's poll at all.
Desktop console — needs neither; PageUp/PageDown already map to JumpBack/JumpForward.
Six background palettes
New "Background" row in Interface: Violet (the brand default), Tide, Forest, Ember, Rose, Graphite.
A palette is a hue rotation + a saturation scale over the one colour field each client already draws — not a second hand-tuned field. So every palette inherits the field's structure (dark corners, bright drifting pools) and Violet is the identity transform: existing installs see exactly what they see today. The backdrop recolours live as you step the row, which is why the picker is on a screen rather than in a dialog.
Stored under one shared ui_palette key (trust::Settings, DefaultsKey.uiPalette, Android Settings). Presentation only, so it's a device preference and deliberately never part of a stream profile. An unknown value reads as Violet on every client — a newer client may ship a palette an older one doesn't know.
⚠️The tint maths is ported three times (Rust / Swift / Kotlin) and each language pins the same four assertions, so the three can't drift. The sin term must be cross(k, c) = (b-g, r-b, g-r)/√3; the opposite sign rotates hue backwards (Ember came out green the first time).
A backdrop that moves everywhere
Settings, add-host and pair used to sit on a still gradient on all three clients. They now wear the same living field at a calm mix — pools dimmed onto the palette's own corner colour, vignette halved so rows that run to the edges don't get crushed. On the desktop console that collapsed the old aurora-over-static crossfade into one shader pass with a chased uniform (theme::draw_form_background is gone).
Motion speed is identical in both modes on purpose: changing it would make the field jump mid-transition. Nothing in the gamepad UI is backed by a static image now, and Reduce Motion (Apple) / "remove animations" (Android, new) still freeze it.
Coverage
The settings screen had no raster coverage at all — the eyeball dump is #[ignore]d — so every_settings_tab_rasters now draws every tab on the CPU, and the Android screenshot set gains a console-settings scene. Both earned their keep immediately: the real renders showed the extra hint pushing "Done" off a 360 dp phone (the legend scrolls now, and the Section cell only appears where shoulders exist) and the form backdrop crushing its own edges.
swift build + 199 tests + a real arm64-apple-ios17.0 typecheck
Android
:app:compileDebugKotlin + 61 tests
fmt
cargo fmt --all clean
Plus eyeball passes on actual renders (console dump_console_screens PNGs: home, settings, the Interface tab, and Ember; Android Roborazzi phone-console-settings).
Not done
On-glass pass owed — Deck, a TV box, tvOS focus.
tvOS #if branches are typecheck-unverified from a Mac (needs a tvOS Rust slice: nightly + -Zbuild-std, tier 3). They mirror the .focused($x, equals:) pattern GamepadMenuList already ships on tvOS.
The focus/accent tint stays brand violet under every palette — the ask was background palettes. On Ember that reads violet-on-copper; an easy follow-up if it looks wrong in the hand.
Polish pass over all three gamepad UIs (`pf-console-ui` for Linux/Windows, Apple, Android).
## Section tabs
The console settings were one 30-row scroll, which on a Deck meant thumbing past Video and Audio to reach the pad settings. They are now **Stream · Video · Audio · Controller · Interface · Profiles**, plus **Input** on the desktop console, which alone carries the touch/mouse rows. L1/R1 walks them, each section remembers where its cursor was, and the names are the same word on every client so a setting is where you looked for it last. The in-list group headers that just repeated the section name are gone.
Shoulders are not the only route, because **a D-pad remote hasn't got any**:
- **Android** — Up from the first row moves onto the strip (left/right walks sections there, A drops back in). Pills are tappable too.
- **tvOS** — the pills are focusable Buttons, so the focus engine handles it. A Siri Remote has no extended gamepad profile and never reaches `GamepadMenuInput`'s poll at all.
- **Desktop console** — needs neither; PageUp/PageDown already map to `JumpBack`/`JumpForward`.
## Six background palettes
New "Background" row in Interface: **Violet** (the brand default), **Tide**, **Forest**, **Ember**, **Rose**, **Graphite**.
A palette is a hue rotation + a saturation scale over the *one* colour field each client already draws — not a second hand-tuned field. So every palette inherits the field's structure (dark corners, bright drifting pools) and **Violet is the identity transform**: existing installs see exactly what they see today. The backdrop recolours live as you step the row, which is why the picker is on a screen rather than in a dialog.
Stored under one shared **`ui_palette`** key (`trust::Settings`, `DefaultsKey.uiPalette`, Android `Settings`). Presentation only, so it's a device preference and deliberately never part of a stream profile. An unknown value reads as Violet on every client — a newer client may ship a palette an older one doesn't know.
⚠️ **The `tint` maths is ported three times** (Rust / Swift / Kotlin) and each language pins the same four assertions, so the three can't drift. The `sin` term must be `cross(k, c)` = `(b-g, r-b, g-r)/√3`; the opposite sign rotates hue backwards (Ember came out green the first time).
## A backdrop that moves everywhere
Settings, add-host and pair used to sit on a **still** gradient on all three clients. They now wear the same living field at a `calm` mix — pools dimmed onto the palette's own corner colour, vignette halved so rows that run to the edges don't get crushed. On the desktop console that collapsed the old aurora-over-static crossfade into **one shader pass** with a chased uniform (`theme::draw_form_background` is gone).
Motion speed is identical in both modes on purpose: changing it would make the field jump mid-transition. Nothing in the gamepad UI is backed by a static image now, and Reduce Motion (Apple) / "remove animations" (Android, new) still freeze it.
## Coverage
The settings screen had **no raster coverage at all** — the eyeball dump is `#[ignore]`d — so `every_settings_tab_rasters` now draws every tab on the CPU, and the Android screenshot set gains a `console-settings` scene. Both earned their keep immediately: the real renders showed the extra hint pushing "Done" off a 360 dp phone (the legend scrolls now, and the Section cell only appears where shoulders exist) and the form backdrop crushing its own edges.
## Verification
| Surface | Result |
| --- | --- |
| console | `cargo build` + `clippy --all-targets -D warnings` + **170 tests** (`pf-lxcheck2`, amd64) |
| Apple | `swift build` + **199 tests** + a real `arm64-apple-ios17.0` typecheck |
| Android | `:app:compileDebugKotlin` + **61 tests** |
| fmt | `cargo fmt --all` clean |
Plus eyeball passes on actual renders (console `dump_console_screens` PNGs: home, settings, the Interface tab, and Ember; Android Roborazzi `phone-console-settings`).
## Not done
- **On-glass pass owed** — Deck, a TV box, tvOS focus.
- **tvOS `#if` branches are typecheck-unverified** from a Mac (needs a tvOS Rust slice: nightly + `-Zbuild-std`, tier 3). They mirror the `.focused($x, equals:)` pattern `GamepadMenuList` already ships on tvOS.
- The focus/accent tint **stays brand violet under every palette** — the ask was background palettes. On Ember that reads violet-on-copper; an easy follow-up if it looks wrong in the hand.
The console settings were one 30-row scroll, which on a Deck meant thumbing past
Video and Audio to reach the pad settings. They are now split across sections —
Stream · Video · Audio · Controller · Interface · Profiles, plus Input on the
desktop console, which alone carries the touch/mouse rows. L1/R1 walks them,
each section remembers where its cursor was, and the names are the same word on
every client so a setting is where you looked for it last.
Shoulders are not the only route, because a D-pad remote hasn't got any: on
Android, Up from the first row moves onto the strip (left/right walks sections
there, A drops back in), and on tvOS the pills are focusable, so the focus
engine handles it — a Siri Remote has no extended gamepad profile and never
reaches the input poll at all. The desktop console needs neither; PageUp and
PageDown already map to the same events.
New "Background" row, six palettes: Violet (the brand default), Tide, Forest,
Ember, Rose, Graphite. A palette is a hue rotation plus a saturation scale over
the ONE colour field each client already draws, so every palette inherits its
structure and Violet is the identity transform — existing installs see exactly
what they see today. The maths is ported three times (Rust/Swift/Kotlin) under
one shared `ui_palette` key, with the same assertions pinned in each language.
It is presentation only, so it is a device preference and never part of a
profile.
The form screens no longer have a backdrop of their own. Settings, add-host and
pair used to sit on a still gradient; they now wear the same living field at a
calm mix — pools dimmed onto the palette's own corner colour, vignette halved so
rows that run to the edges don't get crushed. On the desktop console that
collapsed the old aurora-over-static crossfade into one shader pass with a
chased uniform. Motion speed is identical in both modes on purpose: changing it
would make the field jump mid-transition. Nothing in the gamepad UI is backed by
a static image now, and Reduce Motion (Apple) / "remove animations" (Android)
still freeze it.
Also: the settings screen had no raster coverage at all — the eyeball dump is
`#[ignore]`d — so a new test draws every tab, and the Android screenshot set
gains a console-settings scene. Both earned their keep immediately: the renders
showed the extra hint pushing "Done" off a 360 dp phone (the legend scrolls now,
and the Section cell only appears where shoulders exist) and the form backdrop
crushing its own edges.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Polish pass over all three gamepad UIs (
pf-console-uifor Linux/Windows, Apple, Android).Section tabs
The console settings were one 30-row scroll, which on a Deck meant thumbing past Video and Audio to reach the pad settings. They are now Stream · Video · Audio · Controller · Interface · Profiles, plus Input on the desktop console, which alone carries the touch/mouse rows. L1/R1 walks them, each section remembers where its cursor was, and the names are the same word on every client so a setting is where you looked for it last. The in-list group headers that just repeated the section name are gone.
Shoulders are not the only route, because a D-pad remote hasn't got any:
GamepadMenuInput's poll at all.JumpBack/JumpForward.Six background palettes
New "Background" row in Interface: Violet (the brand default), Tide, Forest, Ember, Rose, Graphite.
A palette is a hue rotation + a saturation scale over the one colour field each client already draws — not a second hand-tuned field. So every palette inherits the field's structure (dark corners, bright drifting pools) and Violet is the identity transform: existing installs see exactly what they see today. The backdrop recolours live as you step the row, which is why the picker is on a screen rather than in a dialog.
Stored under one shared
ui_palettekey (trust::Settings,DefaultsKey.uiPalette, AndroidSettings). Presentation only, so it's a device preference and deliberately never part of a stream profile. An unknown value reads as Violet on every client — a newer client may ship a palette an older one doesn't know.⚠️ The
tintmaths is ported three times (Rust / Swift / Kotlin) and each language pins the same four assertions, so the three can't drift. Thesinterm must becross(k, c)=(b-g, r-b, g-r)/√3; the opposite sign rotates hue backwards (Ember came out green the first time).A backdrop that moves everywhere
Settings, add-host and pair used to sit on a still gradient on all three clients. They now wear the same living field at a
calmmix — pools dimmed onto the palette's own corner colour, vignette halved so rows that run to the edges don't get crushed. On the desktop console that collapsed the old aurora-over-static crossfade into one shader pass with a chased uniform (theme::draw_form_backgroundis gone).Motion speed is identical in both modes on purpose: changing it would make the field jump mid-transition. Nothing in the gamepad UI is backed by a static image now, and Reduce Motion (Apple) / "remove animations" (Android, new) still freeze it.
Coverage
The settings screen had no raster coverage at all — the eyeball dump is
#[ignore]d — soevery_settings_tab_rastersnow draws every tab on the CPU, and the Android screenshot set gains aconsole-settingsscene. Both earned their keep immediately: the real renders showed the extra hint pushing "Done" off a 360 dp phone (the legend scrolls now, and the Section cell only appears where shoulders exist) and the form backdrop crushing its own edges.Verification
cargo build+clippy --all-targets -D warnings+ 170 tests (pf-lxcheck2, amd64)swift build+ 199 tests + a realarm64-apple-ios17.0typecheck:app:compileDebugKotlin+ 61 testscargo fmt --allcleanPlus eyeball passes on actual renders (console
dump_console_screensPNGs: home, settings, the Interface tab, and Ember; Android Roborazziphone-console-settings).Not done
#ifbranches are typecheck-unverified from a Mac (needs a tvOS Rust slice: nightly +-Zbuild-std, tier 3). They mirror the.focused($x, equals:)patternGamepadMenuListalready ships on tvOS.