A stats tier picked between streams reached nothing until the app was restarted #178

Merged
enricobuehler merged 1 commits from worktree-console-stats-tier-relatch into main 2026-08-12 15:38:05 +00:00
Owner

Field report from a Deck user: "no matter what I select the stats overlay is stuck showing as detailed" — cured by restarting the client app.

What was happening

The console (Gaming Mode, and therefore Decky) builds its window and its run loop once and streams every session through them. The loop took its stats tier from the settings snapshot read at process start, and there was no channel back into it — OverlayAction has no settings-changed variant.

So the console's own settings screen wrote the chosen tier to the file and redrew its row, the choice looked taken, and every stream kept the tier the process happened to start on. "Detailed" wasn't special: it was whatever was on disk when the app launched, so anyone who had been on Detailed stayed there. Only a restart re-read it.

  • clients/session/src/console.rssettings_at_start = trust::Settings::load()SessionOpts
  • crates/pf-presenter/src/run.rslet mut stats_verbosity = opts.stats_verbosity in run_inner, which browse mode enters once for the process's life

The desktop shells were never affected. GTK/WinUI spawn a session process per stream, so each stream resolves settings for itself. Only the persistent console latches — worth knowing before anyone tries to reproduce this in desktop mode.

The fix

The tier now rides SessionParams per launch, so browse mode adopts what this launch resolved and the start-of-process value only seeds the loop until the first stream.

Two choices worth calling out in review:

  • Adoption sits in the ActionOutcome::Start arm, not StreamState::new. The codec-fallback retry rebuilds stream state from a clone of those params mid-stream; doing it in the constructor would snap the overlay back every time a session fell down the codec ladder, undoing a cycle the user had just made with the chord.
  • Resolving per launch rather than per process means a profile bound to a host can now move the tier in console mode, which closes part of the documented P4 gap for free.

The --stats rule (a floor: lifts Off to Normal, demotes nothing) was written out three times and is now one tested helper, session_main::stats_tier.

Deliberately not in scope

Touch mode, mouse mode, invert scroll, shortcut inhibit, present priority, vsync, VRR, match-window, render scale and window size are still latched at console start and still have this same bug. Each needs the presenter's input and swapchain models rebuilt per launch — the P4 work the comment in console.rs already flags. Expect more "setting X does nothing until I restart" reports from the Deck until that lands; the comment there now says so explicitly.

params.cursor_forward stays deliberately pinned to the latched mouse mode and must remain that way — a profile flipping it mid-console produces a stream with no visible cursor at all.

Verification

pf-client-core doesn't build on macOS, so this was gated in the pf-lxcheck2 container on linux/amd64, re-run after rebasing onto 28b66330:

  • cargo clippy --all-targets -D warnings for pf-client-core, pf-presenter, punktfunk-client-session
  • plain cargo build as well (--all-targets compiles cfg(test) and hides "dead outside tests")
  • cargo test: 188 pass in pf-client-core, 2 new in punktfunk-client-session
  • cargo fmt --all --check clean
  • clippy proven non-vacuous — the run's log shows both crates actually re-Checking, not a cached Finished

⚠ The new tests cover the --stats rule and the resolve, not the run-loop adoption itself: run_inner needs a live SDL window, so that hop has no unit test. The field being non-Option is what forces a future construction site to think about it.

Not yet exercised on a Deck in Gaming Mode — worth one on-glass check that picking a tier between two streams takes effect on the second.

Field report from a Deck user: *"no matter what I select the stats overlay is stuck showing as detailed"* — cured by restarting the client app. ## What was happening The console (Gaming Mode, and therefore Decky) builds its window and its run loop **once** and streams every session through them. The loop took its stats tier from the settings snapshot read at process start, and there was no channel back into it — `OverlayAction` has no settings-changed variant. So the console's own settings screen wrote the chosen tier to the file and redrew its row, the choice looked taken, and every stream kept the tier the process happened to start on. "Detailed" wasn't special: it was whatever was on disk when the app launched, so anyone who had been on Detailed stayed there. Only a restart re-read it. - `clients/session/src/console.rs` — `settings_at_start = trust::Settings::load()` → `SessionOpts` - `crates/pf-presenter/src/run.rs` — `let mut stats_verbosity = opts.stats_verbosity` in `run_inner`, which browse mode enters once for the process's life **The desktop shells were never affected.** GTK/WinUI spawn a session *process* per stream, so each stream resolves settings for itself. Only the persistent console latches — worth knowing before anyone tries to reproduce this in desktop mode. ## The fix The tier now rides `SessionParams` per launch, so browse mode adopts what *this* launch resolved and the start-of-process value only seeds the loop until the first stream. Two choices worth calling out in review: - **Adoption sits in the `ActionOutcome::Start` arm, not `StreamState::new`.** The codec-fallback retry rebuilds stream state from a clone of those params mid-stream; doing it in the constructor would snap the overlay back every time a session fell down the codec ladder, undoing a cycle the user had just made with the chord. - **Resolving per launch rather than per process means a profile bound to a host can now move the tier in console mode**, which closes part of the documented P4 gap for free. The `--stats` rule (a floor: lifts Off to Normal, demotes nothing) was written out three times and is now one tested helper, `session_main::stats_tier`. ## Deliberately not in scope Touch mode, mouse mode, invert scroll, shortcut inhibit, present priority, vsync, VRR, match-window, render scale and window size are **still latched at console start and still have this same bug**. Each needs the presenter's input and swapchain models rebuilt per launch — the P4 work the comment in `console.rs` already flags. Expect more "setting X does nothing until I restart" reports from the Deck until that lands; the comment there now says so explicitly. `params.cursor_forward` stays deliberately pinned to the *latched* mouse mode and must remain that way — a profile flipping it mid-console produces a stream with no visible cursor at all. ## Verification `pf-client-core` doesn't build on macOS, so this was gated in the `pf-lxcheck2` container on `linux/amd64`, re-run after rebasing onto `28b66330`: - `cargo clippy --all-targets -D warnings` for `pf-client-core`, `pf-presenter`, `punktfunk-client-session` - plain `cargo build` as well (`--all-targets` compiles `cfg(test)` and hides "dead outside tests") - `cargo test`: 188 pass in `pf-client-core`, 2 new in `punktfunk-client-session` - `cargo fmt --all --check` clean - clippy proven non-vacuous — the run's log shows both crates actually re-`Checking`, not a cached `Finished` ⚠ The new tests cover the `--stats` rule and the resolve, **not** the run-loop adoption itself: `run_inner` needs a live SDL window, so that hop has no unit test. The field being non-`Option` is what forces a future construction site to think about it. Not yet exercised on a Deck in Gaming Mode — worth one on-glass check that picking a tier between two streams takes effect on the second.
enricobuehler added 1 commit 2026-08-12 15:32:00 +00:00
fix(client/console): a stats tier picked between streams reached nothing until a restart
ci / bun-nix (pull_request) Successful in 31s
apple / swift (pull_request) Successful in 1m43s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 59s
android / android (pull_request) Successful in 3m59s
ci / web (pull_request) Successful in 4m18s
ci / rust-arm64 (pull_request) Successful in 5m5s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 4m17s
ci / docs-site (pull_request) Successful in 4m32s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 3m15s
71c1970b93
Field report: "no matter what I select the stats overlay is stuck showing as
detailed" on the Deck, cured by restarting the client app.

The console (Gaming Mode, and therefore Decky) builds its window and its run loop
ONCE and streams every session through them, and the loop took its stats tier from
the settings snapshot read at process start. Its own settings screen writes the
chosen tier to the file and redraws its row, so the choice looked taken while every
stream kept the tier the process happened to start on — Detailed for anyone who had
been on Detailed. Only a restart re-read it. The desktop shells were never affected:
they spawn a session process per stream, which resolves settings for itself.

The tier now rides `SessionParams` per launch, so browse mode adopts what THIS launch
resolved and the start-of-process value only seeds the loop until the first stream.
Two things fall out of resolving per launch rather than per process: a profile bound
to a host can finally move the tier in console mode (part of the documented P4 gap),
and the adoption sits in the `Start` arm rather than `StreamState::new`, so the
codec-fallback retry can't snap the overlay back and undo an in-stream cycle.

The `--stats` rule (a floor that lifts Off to Normal and demotes nothing) was written
out three times and is now one tested helper. The rest of the console's latched
presentation tier — touch and mouse model, shortcut inhibit, match-window, render
scale — is unchanged and still needs the models rebuilt per launch.

Gate: clippy --all-targets -D warnings, plain build, and tests for pf-client-core,
pf-presenter and punktfunk-client-session, all green in pf-lxcheck2 (linux/amd64);
clippy proven non-vacuous by touching the four edited files. cargo fmt --all --check
clean.
enricobuehler merged commit 7528fd48a9 into main 2026-08-12 15:38:05 +00:00
enricobuehler deleted branch worktree-console-stats-tier-relatch 2026-08-12 15:38:09 +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#178