Files
punktfunk/web/src
enricobuehler 22fdea66ff feat(host,clients/apple,console): the library round-trip — a quit game the host never noticed, and the loop back to the shelf
From a field report (Discord, 2026-08-16, Windows host 0.29.0 + iPad 0.30.0): quitting a game
mid-stream left the session up and the web console showing it as "running" forever, with no
setting that made any difference.

The console was not merely out of date — it was asserting something the host had no way to know.
A lease with nothing to recognise its game by set its OWN state to `Running`, on the reasoning
that the host had just launched it. That made three different situations indistinguishable: a
game being watched, a game that quit and was never noticed, and a game the host cannot see at
all. `session_on_game_exit` can never fire for a lease nothing is watching, which is why no
setting helped.

Windows reached that state far more easily than Linux, and for a reason worth naming: the host
never holds a `Child` there, and the pid `CreateProcessAsUserW` hands back was logged and
discarded. So a title whose provider published no detect hint had *nothing* identifying it — its
exit went unseen and `POST /game/end` had no pid to signal, while the same title on Linux was
fully tracked through its child.

Host:
* `GameState::Untracked` — a lease that nothing is watching says so, in `/status`, the console
  card and the tray label. Keyed on "is anything watching this" rather than on the lease kind, so
  a nested gamescope lease (whose exit the capture loop catches) still correctly reads `running`.
* `LeaseRequest::spawned` carries the pid Windows already knew, pinned to its start time by a new
  `Scanner::resolve` so a recycled pid cannot impersonate it. It takes the same lifetime rules the
  owned child gets, shim reclassification included, and feeds the Windows terminate ladder.
* `game_on_new_launch` (keep|end, default keep): close this client's previous game before starting
  a different one. Its own axis rather than a fourth `game_on_session_end` value — wanting a game
  to survive a disconnect says nothing about wanting it kept when you deliberately pick another.
  Four safety rules, made pure and unit-tested: never another client's game, never one the player
  started themselves, never the title being launched, and never a record whose liveness is merely
  Unknown.

The same pid fix closes a second defect: a launch that adopted nothing answers `Unknown`, falls
back to the 90-second in-flight window, and past it starts a SECOND copy. That is why "click the
game that is already running" resumed on Linux and relaunched on Windows.

Apple client — the loop the report was really about (browse, play, quit, browse):
* tapping a paired host opens its library; "Stream the Desktop" moves to the card menu
* the catalog is cached per host and rendered immediately, marked stale, so a sleeping host still
  shows its titles (only art was cached before; the catalog was fetched live every visit)
* opening the library wakes the host and retries across the boot window, so it is warm by the time
  a title is picked — waking was bound to CONNECTING, which is too late to help
* titles already up are badged Resume and sorted first, read from `/status` (already on the
  paired-cert lane — no new host API)
* the grid returns to where you were, remembered as the last title opened rather than a pixel
  offset, which survives a rotation, a resize and a host gaining titles

Verified: Linux container gate over punktfunk-host — fmt, clippy --all-targets -D warnings, a
plain build and 597 tests green, openapi regenerated and its drift test passing. Web console tsc
clean, 740 messages en+de. Apple swift build + 347 tests on macOS, and iOS + tvOS typechecked
(CI compiles neither).
2026-08-17 01:18:24 +02:00
..