forked from unom/punktfunk
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).