feat(decky): the settings tab covers the whole store, as a SteamOS-style sidebar #24

Merged
enricobuehler merged 3 commits from worktree-decky-stats-overlay-toggle into main 2026-08-03 16:44:59 +00:00
Owner

Starts from a field report on 0.23: "just noticed that as of version 0.23 of this plugin, there is no setting to toggle off the stat overlay."

Accurate, and it turned out to be the visible half of a general gap.

The stats overlay (8af6e2dd)

No commit in clients/decky had ever touched a stats key — the plugin never had the toggle, while the GTK dialog, the Windows page, the Apple app and the console's own settings screen all carry the four-tier picker.

The tier defaults to on: Settings::default() is show_stats: true with stats_verbosity: None, which Settings::stats_verbosity() resolves to Normal (crates/pf-client-core/src/trust.rs). A Deck configured only through this panel has a settings file that cannot express "off".

Why 0.23 is when it started biting: 9c5af8d7 fixed the GTK shell handing the session a spec built from Settings::default(). Before that the plugin's settings file was ignored by the stream entirely; as of 0.23 it is read — so what is in, and missing from, that file now decides what the stream does.

set_settings also stops replacing the file and merges onto it. The JSON is shared with the desktop client and the console and holds far more keys than this panel models; the panel reads it once on mount, so a wholesale write posts a snapshot that predates anything another editor stored while it sat open.

The rest of the store (6de78213)

Nine settings had a row, twenty didn't. Everything the store holds is now reachable, except the two things a plugin backend genuinely cannot answer (both named in backend.ts): which physical pad is player 1 — SDL's live device list lives in the client process and no CLI enumerates it — and the session's remembered window size, which is not a preference.

Thirty-odd rows is too many to scroll past on a thumbstick, so they are split across a SidebarNavigation: the left-rail-of-categories layout SteamOS's own Settings uses. Every page fits on screen without scrolling. The categories, their order and the row wording are the console settings screen's — that is the other settings editor reachable without leaving Gaming Mode, and two different orders for one store is how people stop trusting either.

Three rules:

  • Dependent rows are indented and disabled, never hidden — mic device and echo cancellation under the microphone, controller type under forwarding, smoothness buffer under Prioritize. A row that vanishes as you toggle the one above it is a moving target for a thumbstick.
  • No dead controls — the GPU picker appears only where the enumeration found more than one adapter: absent on a Deck, present on a Bazzite desktop with a dGPU.
  • Anything that behaves differently here says so — capture-system-shortcuts holds nothing back under gamescope, fullscreen-on-stream can't beat a launch that always passes --fullscreen, the client's library toggle isn't this plugin's browser.

The device pickers are real: list_devices reads --list-adapters and --list-audio off the session binary — the same two enumerations the GTK shell shells out for, since it links no Vulkan itself. Cached for the backend's life (that call inits Vulkan and PipeWire) with an explicit Refresh, and a client too old to ship the session binary leaves the pickers on Automatic and says so.

Two honesty fixes fell out: a stored value the option table doesn't list is carried as its own entry (a Dropdown can only display a value it has an option for, and this store has four other writers), and a disconnected audio endpoint keeps a (not connected) entry rather than silently re-pointing the next stream at the default.

Merge of main (f71bee91)

#20 landed while this was open and added four settings the console groups under a new Presentation header — Prioritize, Smoothness buffer, V-Sync, Follow variable refresh. A branch whose whole claim is "everything the store holds is reachable" can't merge past those, so they get a Presentation page in the console's position (after Video, before Audio).

The docs conflict resolves to main's side plus this branch's correction: main's rewritten 4:4:4 advertisement claim is the current one and stays, while "Android, Decky and the console home don't offer it" was wrong about two of the three before this branch and all three after it.

Docs

Nine claims about this plugin were falsified by the change. Several more about the console home were already wrong — it grew 4:4:4, echo cancellation, auto-wake and the library toggle on 2026-07-31 (screens/settings.rs) and the docs never caught up, and the four new presentation settings are on it too. Corrected together.

Verification

  • pnpm test (tsc --noEmit --skipLibCheck) — clean
  • pnpm run build — succeeds
  • scripts/test-backend.py — all pass, including 5 new checks for _parse_audio_endpoints, split out of the RPC so the malformed lines that must never reach a picker are pinned. Needs Python ≥3.10.

Not yet tested on glass. This compiles and SidebarNavigation is exported and typed as used, but the layout and gamepad focus behaviour — particularly how the outer Tabs L1/R1 handler interacts with the rail's own navigation — needs a look on a real Deck before release.

🤖 Generated with Claude Code

Starts from a field report on 0.23: *"just noticed that as of version 0.23 of this plugin, there is no setting to toggle off the stat overlay."* Accurate, and it turned out to be the visible half of a general gap. ## The stats overlay (`8af6e2dd`) No commit in `clients/decky` had ever touched a stats key — the plugin never had the toggle, while the GTK dialog, the Windows page, the Apple app and the console's own settings screen all carry the four-tier picker. The tier defaults to **on**: `Settings::default()` is `show_stats: true` with `stats_verbosity: None`, which `Settings::stats_verbosity()` resolves to `Normal` (`crates/pf-client-core/src/trust.rs`). A Deck configured only through this panel has a settings file that cannot express "off". **Why 0.23 is when it started biting:** `9c5af8d7` fixed the GTK shell handing the session a spec built from `Settings::default()`. Before that the plugin's settings file was ignored by the stream entirely; as of 0.23 it is read — so what is in, and missing from, that file now decides what the stream does. `set_settings` also stops replacing the file and **merges** onto it. The JSON is shared with the desktop client and the console and holds far more keys than this panel models; the panel reads it once on mount, so a wholesale write posts a snapshot that predates anything another editor stored while it sat open. ## The rest of the store (`6de78213`) Nine settings had a row, twenty didn't. Everything the store holds is now reachable, except the two things a plugin backend genuinely cannot answer (both named in `backend.ts`): which physical pad is player 1 — SDL's live device list lives in the client process and no CLI enumerates it — and the session's remembered window size, which is not a preference. Thirty-odd rows is too many to scroll past on a thumbstick, so they are split across a `SidebarNavigation`: the left-rail-of-categories layout SteamOS's own Settings uses. Every page fits on screen without scrolling. The categories, their order and the row wording are the console settings screen's — that is the other settings editor reachable without leaving Gaming Mode, and two different orders for one store is how people stop trusting either. Three rules: - **Dependent rows are indented and disabled, never hidden** — mic device and echo cancellation under the microphone, controller type under forwarding, smoothness buffer under Prioritize. A row that vanishes as you toggle the one above it is a moving target for a thumbstick. - **No dead controls** — the GPU picker appears only where the enumeration found more than one adapter: absent on a Deck, present on a Bazzite desktop with a dGPU. - **Anything that behaves differently here says so** — capture-system-shortcuts holds nothing back under gamescope, fullscreen-on-stream can't beat a launch that always passes `--fullscreen`, the client's library toggle isn't this plugin's browser. The device pickers are real: `list_devices` reads `--list-adapters` and `--list-audio` off the **session** binary — the same two enumerations the GTK shell shells out for, since it links no Vulkan itself. Cached for the backend's life (that call inits Vulkan and PipeWire) with an explicit Refresh, and a client too old to ship the session binary leaves the pickers on Automatic and says so. Two honesty fixes fell out: a stored value the option table doesn't list is carried as its own entry (a Dropdown can only display a value it has an option for, and this store has four other writers), and a disconnected audio endpoint keeps a `(not connected)` entry rather than silently re-pointing the next stream at the default. ## Merge of main (`f71bee91`) `#20` landed while this was open and added four settings the console groups under a new **Presentation** header — Prioritize, Smoothness buffer, V-Sync, Follow variable refresh. A branch whose whole claim is "everything the store holds is reachable" can't merge past those, so they get a Presentation page in the console's position (after Video, before Audio). The docs conflict resolves to main's side plus this branch's correction: main's rewritten 4:4:4 advertisement claim is the current one and stays, while "Android, Decky and the console home don't offer it" was wrong about two of the three before this branch and all three after it. ## Docs Nine claims about this plugin were falsified by the change. Several more about the **console home** were already wrong — it grew 4:4:4, echo cancellation, auto-wake and the library toggle on 2026-07-31 (`screens/settings.rs`) and the docs never caught up, and the four new presentation settings are on it too. Corrected together. ## Verification - `pnpm test` (`tsc --noEmit --skipLibCheck`) — clean - `pnpm run build` — succeeds - `scripts/test-backend.py` — all pass, including 5 new checks for `_parse_audio_endpoints`, split out of the RPC so the malformed lines that must never reach a picker are pinned. Needs Python ≥3.10. ⚠ **Not yet tested on glass.** This compiles and `SidebarNavigation` is exported and typed as used, but the layout and gamepad focus behaviour — particularly how the outer `Tabs` L1/R1 handler interacts with the rail's own navigation — needs a look on a real Deck before release. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
enricobuehler added 2 commits 2026-08-02 22:37:13 +00:00
Field report: "as of version 0.23 of this plugin, there is no setting to
toggle off the stat overlay." Correct, and it never had one — no commit in
`clients/decky` has ever touched a stats key. Every other client does:
the GTK dialog, the Windows page, the Apple app, and the console's own
settings screen all carry the four-tier picker.

The tier defaults to on. `Settings::default` is `show_stats: true` and
`stats_verbosity: None`, which `Settings::stats_verbosity` resolves to
Normal — so a Deck that has only ever been configured through this panel
streams with the overlay up and no way here to put it down. What escapes
exist are not discoverable: Ctrl+Alt+Shift+S wants a keyboard, and the
three-finger touchscreen tap is documented in `docs/stats`, not on the
glass. The console's picker is reachable (X on console home), but that is
a different shortcut than the one-tap stream this panel launches, and a
user editing stream settings here has no reason to look there.

So the row lands here, last in the section, matching the console's
wording. It writes `stats_verbosity` AND the legacy `show_stats` in the
same pairing `Settings::set_stats_verbosity` keeps, so a client too old
for the tiers still honours an Off chosen here; it reads them back the
way `Settings::stats_verbosity` does, so a pre-tier file — including
every file this plugin wrote before today — shows the Normal the stream
actually runs at.

`set_settings` stops replacing the file and merges onto it instead. This
JSON is shared with the desktop client and the console, and holds many
more keys than this panel models (decoder, GPU, profiles, touch/mouse
model). The panel reads it once when it mounts, so a wholesale write
posts a snapshot that predates anything another editor stored while it
sat open — silently reverting it. That was invisible until 0.23.0:
`9c5af8d7` fixed the GTK shell handing the session a spec built from
`Settings::default()`, and only since then does this file reach a stream
at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(decky): the settings tab covers the whole store, as a SteamOS-style sidebar
ci / web (pull_request) Successful in 1m4s
ci / docs-site (pull_request) Successful in 1m25s
ci / rust-arm64 (pull_request) Successful in 1m31s
ci / rust (pull_request) Canceled after 3m0s
6de78213ee
The stats overlay was the visible half of a general problem: nine of the
client's settings had a row here and twenty didn't, so a Deck that never
sees a desktop could not reach its own decoder, chroma, HDR, audio
layout, echo cancellation, touch or mouse model, scroll direction,
auto-wake, or either audio endpoint. Everything the store holds is here
now — except the two things a plugin backend genuinely cannot answer,
named in `backend.ts` so the next reader doesn't go looking: which
physical pad is player 1 (SDL's live device list lives in the client
process, and no CLI enumerates it) and the session's remembered window
size, which is not a preference.

Thirty rows is too many to scroll past on a thumbstick, so they are
split across a `SidebarNavigation` — the left-rail-of-categories layout
SteamOS's own Settings uses, and the one Deck users already know. Every
page fits on screen without scrolling, which is the point: the rail is
the index, so nothing is more than one hop away. The categories, their
order and the wording of the rows are the console settings screen's — it
is the other settings editor reachable without leaving Gaming Mode, and
two different orders for one store is how people stop trusting either.
It shows them as one steppable list because it has no pointer and no
room for a rail; here they become the rail's pages. The six pages take
one shared settings object rather than each holding state, so a change
on one is visible on the others the moment you switch.

Three more rules:

- A dependent setting is INDENTED under what it depends on and DISABLED,
  never hidden: mic device and echo cancellation under the microphone,
  controller type under forwarding. The console dims those rows for the
  same reason, and a row that vanishes as you toggle the one above it is
  a moving target for a thumbstick. The device row at the foot of Audio
  is rendered even while it reads, for that reason.
- A picker with nothing to pick doesn't appear: the GPU row shows up
  only where the enumeration found more than one adapter, so it is
  absent on a Deck and present on a Bazzite desktop with a dGPU.
- A setting that behaves differently HERE says so in its own
  description rather than being dropped. Capture system shortcuts holds
  nothing back under gamescope; fullscreen-on-stream can't lose to a
  launch that always passes `--fullscreen`; the client's library toggle
  isn't this plugin's browser. Each says which.

The device pickers are real, not stubs: `list_devices` reads
`--list-adapters` and `--list-audio` off the SESSION binary, the same
two enumerations the GTK shell shells out for because it links no Vulkan
itself. It is cached for the life of the backend (that call inits Vulkan
and PipeWire) with an explicit Refresh for the headset you just plugged
in, and a failure — a client too old to ship the session binary — leaves
the pickers on Automatic and says so instead of claiming you have no
devices. `_parse_audio_endpoints` is split out and unit-tested with the
malformed lines that must never reach a picker.

Two smaller honesty fixes fall out of building it. A Dropdown can only
display a value that is one of its options, and this store has four
other writers — so a stored value the table doesn't list is carried as
its own entry rather than rendering blank or, worse, showing a different
value than the stream will use. And a stored audio endpoint that isn't
currently connected keeps a "(not connected)" entry, the way the Linux
picker keeps "(not detected)", instead of silently re-pointing the next
stream at the default.

The Settings tab's wrapper deliberately stops being a scroll area: a
SidebarNavigation given an indefinite height to fill collapses its rail,
so the pane hands it the full height and keeps its hands off the
overflow, and the footer inset moves inside the pages.

The docs claimed nine things about this plugin that are no longer true,
and three about the console home that stopped being true when its own
row set grew on 2026-07-31 (4:4:4, echo cancellation, auto-wake and the
library toggle are all there in `screens/settings.rs`). Both corrected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
enricobuehler added 1 commit 2026-08-02 22:40:14 +00:00
Merge origin/main; the presenter rebuild's four settings join the sidebar
ci / web (pull_request) Successful in 1m14s
ci / rust-arm64 (pull_request) Successful in 1m41s
ci / docs-site (pull_request) Successful in 1m36s
ci / rust (pull_request) Successful in 6m1s
f71bee917b
`#20` landed while this branch was open and added four settings the
console screen groups under a new "Presentation" header: Prioritize,
Smoothness buffer, V-Sync and Follow variable refresh. A branch whose
whole claim is "everything the store holds is reachable" cannot merge
past those, so they get a Presentation page of their own, in the console
screen's position (after Video, before Audio) and with its wording.
Smoothness buffer is indented under Prioritize and disabled until the
intent is Smoothness — the same relationship the console's `enabled`
gate draws.

The docs conflict resolves to main's side plus this branch's correction:
the 4:4:4 advertisement claim main rewrote is the current one and stays,
while "Android, Decky and the console home don't offer it" was wrong
about two of the three before this branch and about all three after it.
The four new settings' paragraphs pick up the console home and Decky the
same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
enricobuehler merged commit 926e2ccbdd into main 2026-08-03 16:44:59 +00:00
enricobuehler deleted branch worktree-decky-stats-overlay-toggle 2026-08-03 16:45:04 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#24