PR #276 shipped six library behaviours on the Apple client only, and deferred parity (C7) to a follow-up. This is that follow-up for Android and the Linux/console-UI shell. webOS rides in its own repo — see the note at the bottom.
No host work: GET /api/v1/status already carries games[] and is already on the paired-cert allowlist (mgmt::auth::cert_may_access), the same mTLS identity the catalog fetch uses.
⚠️ Item 1 was implemented and then reverted at review — see the bottom section. No input mapping changes anywhere in this PR. A host card's primary press still connects; the library stays where it already was (Y on the carousels, "Browse library…" in Android's touch overflow menu).
The five behaviours this PR actually ships
Judged per item and per client rather than ported — two of them meant something different away from SwiftUI.
2. The catalog is cached per host and rendered immediately, marked stale, so a sleeping box still shows its titles. pf_client_core::library_cache (keyed on the pinned fingerprint, so a new DHCP lease is still the same host) and kit/library/LibraryCache.kt (keyed on the host record id). A cached shelf outranks a failure: if the host never answers, the titles on screen are still the right ones to choose from, and replacing them with a red error because a box is asleep is exactly what the cache exists to prevent. The wording changes instead — "waking the host…" → "the host didn't answer" — which is why staleness is a three-state enum and not a flag. A progress line that claims something is happening while nothing is must not be possible to write here.
3. The host is woken on library ENTRY, and the fetch retries across the boot window (12 × 5 s, packet re-sent every other attempt — the same budget the existing wake loops allow). Waking was bound to CONNECTING, which is too late to help: by then a title has been picked and the player is sitting through a cold boot. Only "can't reach it" is retried; a rejected certificate does not become acceptable by waiting.
4. Titles already up are badged Resume, read from /status. untracked and grace both count as up; only a confirmed exited does not.
5. Running-first sorts WITHIN each group, never across. On console-UI that is not cosmetic: GridShape is told a launcher count and lays those entries out as a prefix, so a running game jumping ahead of a launcher would put the cursor arithmetic and the renderer on two different fields.
6. The grid returns to where you were — remembered as the last title opened, not an offset, so it survives a rotation, a resize and a host gaining titles. Android only. The console-UI shell does not need it and does not get it: its screen stack survives a stream (session_ended never pops), so the shelf is still exactly where it was.
What that shell needed instead was the same principle applied to a different problem — the list moving on its own. A /status read re-orders the shelf under a live cursor, so sync now anchors on the focused title's id across a re-order, and reads "a different library" as a different set of ids rather than a different sequence. Without that, running-first would have cleared every decoded poster and thrown the cursor back to the start while somebody was standing on a tile.
Also
ConsoleCmd::RefreshRunning, raised when a stream ends onto a shelf — the one moment the running set is most likely to have changed, and the moment the player is standing in front of the badge that claims to know. Its own command rather than a re-fetch, which would put a spinner over the shelf they just came back to.
Forgetting a host drops its cached catalog (and, on Android, its remembered position): a removed host leaves no list of what somebody plays behind on disk.
Item 1 — implemented, then reverted (9a64a0fa)
#276's item 1 read "tapping a paired host opens its LIBRARY (streaming the desktop becomes the secondary action)". 9e561f14 applied that to three surfaces: Android's touch host card (tap → library, "Stream the desktop" into the overflow, mirroring Apple's HostCards.swift), Android's gamepad carousel, and the console-UI carousel (both: A → library, Y → desktop).
Backed out at review. The two gamepad halves were an extrapolation — those shells have no "tap" — and bought much less than the touch one: Y already opened the library there, directly, with a legend hint. Nothing was buried, so the flip spent entrenched A-connects muscle memory on an affordance that already existed one press away. The touch card was the honest case, but it goes back too rather than leaving Android's own two shells disagreeing about what a host card's primary press does.
screens/home.rs, GamepadHome.kt and ConnectGrid.kt are now byte-identical to main. shell/tests.rs differs from base by exactly the seven running: false lines item 4/5 needs — the eight tests that encode the A/Y mapping are back to asserting what ships.
Verified
console-UI — Linux container gate over pf-console-ui + pf-client-core + punktfunk-client-session: fmt clean, clippy --all-targets -D warnings clean, a plain build (not just --all-targets), 175 + 219 tests green (175 = the pre-existing count, restored by the revert). Non-vacuity confirmed.
Android — :kit: and :app:compileDebugKotlinandcompileDebugUnitTestKotlin green (the screenshot harness composes the real grid and coverflow, so their new parameters are defaulted and it still builds).
Both lanes re-run over the reverted tree, not just the original one.
New unit tests: the /status decode (untracked/grace/unknown states, a command with no app_id), the cache's catalog round trip, the fingerprint path check refusing anything that isn't 64 lowercase hex, running-first preserving the launcher prefix, and set_running being a no-op — no generation bump, so no re-sync — when nothing changed.
webOS — separate repo, NOT in this PR
The webOS half is written and gated green (cargo clippy --target armv7-unknown-linux-gnueabi --all-targets -- -D warnings, in the project's own Docker toolchain), but it lives in punktfunk-webos and this session could not commit there. Item 1 never applied to it — its home is a host sidebar beside a library grid, so selecting a host has always opened its library, and that path was never touched. Item 3 was already satisfied too (start_wake already probes and retries). Items 2, 4, 5 and 6 are new. One judgement worth flagging: webOS has user-pinned games, so running-first leads the unpinned remainder only — an explicit pin is a placement the player chose and should not be overruled by a transient state.
PR #276 shipped six library behaviours on the **Apple client only**, and deferred parity (C7) to a follow-up. This is that follow-up for **Android** and the **Linux/console-UI shell**. webOS rides in its own repo — see the note at the bottom.
No host work: `GET /api/v1/status` already carries `games[]` and is already on the paired-cert allowlist (`mgmt::auth::cert_may_access`), the same mTLS identity the catalog fetch uses.
> **⚠️ Item 1 was implemented and then reverted at review — see the bottom section.** No input mapping changes anywhere in this PR. A host card's primary press still connects; the library stays where it already was (Y on the carousels, "Browse library…" in Android's touch overflow menu).
## The five behaviours this PR actually ships
Judged per item and per client rather than ported — two of them meant something different away from SwiftUI.
**2. The catalog is cached per host** and rendered immediately, marked stale, so a sleeping box still shows its titles. `pf_client_core::library_cache` (keyed on the pinned fingerprint, so a new DHCP lease is still the same host) and `kit/library/LibraryCache.kt` (keyed on the host record id). A cached shelf **outranks a failure**: if the host never answers, the titles on screen are still the right ones to choose from, and replacing them with a red error because a box is asleep is exactly what the cache exists to prevent. The wording changes instead — "waking the host…" → "the host didn't answer" — which is why staleness is a three-state enum and not a flag. A progress line that claims something is happening while nothing is must not be *possible* to write here.
**3. The host is woken on library ENTRY**, and the fetch retries across the boot window (12 × 5 s, packet re-sent every other attempt — the same budget the existing wake loops allow). Waking was bound to CONNECTING, which is too late to help: by then a title has been picked and the player is sitting through a cold boot. Only "can't reach it" is retried; a rejected certificate does not become acceptable by waiting.
**4. Titles already up are badged Resume**, read from `/status`. `untracked` and `grace` both count as up; only a confirmed `exited` does not.
**5. Running-first sorts WITHIN each group, never across.** On console-UI that is not cosmetic: `GridShape` is told a launcher *count* and lays those entries out as a prefix, so a running game jumping ahead of a launcher would put the cursor arithmetic and the renderer on two different fields.
**6. The grid returns to where you were** — remembered as the last title **opened**, not an offset, so it survives a rotation, a resize and a host gaining titles. **Android only.** The console-UI shell does not need it and does not get it: its screen stack survives a stream (`session_ended` never pops), so the shelf is still exactly where it was.
What that shell needed instead was the same *principle* applied to a different problem — **the list moving on its own**. A `/status` read re-orders the shelf under a live cursor, so `sync` now anchors on the focused title's id across a re-order, and reads "a different library" as a different **set** of ids rather than a different sequence. Without that, running-first would have cleared every decoded poster and thrown the cursor back to the start while somebody was standing on a tile.
## Also
- `ConsoleCmd::RefreshRunning`, raised when a stream ends onto a shelf — the one moment the running set is most likely to have changed, and the moment the player is standing in front of the badge that claims to know. Its own command rather than a re-fetch, which would put a spinner over the shelf they just came back to.
- Forgetting a host drops its cached catalog (and, on Android, its remembered position): a removed host leaves no list of what somebody plays behind on disk.
## Item 1 — implemented, then reverted (`9a64a0fa`)
#276's item 1 read *"tapping a paired host opens its LIBRARY (streaming the desktop becomes the secondary action)"*. `9e561f14` applied that to three surfaces: Android's touch host card (tap → library, "Stream the desktop" into the overflow, mirroring Apple's `HostCards.swift`), Android's gamepad carousel, and the console-UI carousel (both: A → library, Y → desktop).
Backed out at review. The two gamepad halves were an extrapolation — those shells have no "tap" — and bought much less than the touch one: **Y already opened the library there, directly, with a legend hint.** Nothing was buried, so the flip spent entrenched A-connects muscle memory on an affordance that already existed one press away. The touch card was the honest case, but it goes back too rather than leaving Android's own two shells disagreeing about what a host card's primary press does.
`screens/home.rs`, `GamepadHome.kt` and `ConnectGrid.kt` are now **byte-identical to `main`**. `shell/tests.rs` differs from base by exactly the seven `running: false` lines item 4/5 needs — the eight tests that encode the A/Y mapping are back to asserting what ships.
## Verified
- **console-UI** — Linux container gate over `pf-console-ui` + `pf-client-core` + `punktfunk-client-session`: fmt clean, clippy `--all-targets -D warnings` clean, a **plain** build (not just `--all-targets`), **175 + 219 tests green** (175 = the pre-existing count, restored by the revert). Non-vacuity confirmed.
- **Android** — `:kit:` and `:app:` `compileDebugKotlin` **and** `compileDebugUnitTestKotlin` green (the screenshot harness composes the real grid and coverflow, so their new parameters are defaulted and it still builds).
- Both lanes re-run over the reverted tree, not just the original one.
- New unit tests: the `/status` decode (`untracked`/`grace`/unknown states, a command with no `app_id`), the cache's catalog round trip, the fingerprint path check refusing anything that isn't 64 lowercase hex, running-first preserving the launcher prefix, and `set_running` being a no-op — no generation bump, so no re-sync — when nothing changed.
## webOS — separate repo, NOT in this PR
The webOS half is written and **gated green** (`cargo clippy --target armv7-unknown-linux-gnueabi --all-targets -- -D warnings`, in the project's own Docker toolchain), but it lives in `punktfunk-webos` and this session could not commit there. Item 1 never applied to it — its home *is* a host sidebar beside a library grid, so selecting a host has always opened its library, and that path was never touched. Item 3 was already satisfied too (`start_wake` already probes and retries). Items 2, 4, 5 and 6 are new. One judgement worth flagging: webOS has **user-pinned games**, so running-first leads the *unpinned remainder* only — an explicit pin is a placement the player chose and should not be overruled by a transient state.
Files touched there: `src/{main,library,library_cache,store}.rs`, `src/app/{mod,home,addhost,edithost,pairing}.rs`, `src/ui/{cards,tiles}.rs`.
PR #276 shipped six library behaviours on the Apple client only, and deferred parity to C7.
This is C7 for Android and the Linux/console-UI shell (webOS rides in its own repo).
Judged per client and per item rather than ported mechanically — two of the six turned out to
mean something different away from SwiftUI, and one of them is already solved on one shell:
* **tapping a paired host opens its LIBRARY.** Android's touch card and its console carousel
both did it, and so does the console-UI shell: A is the library, Y streams the desktop, and
the legend says so. A host with no library to open — unpaired, or unsaved — keeps connecting
as its primary press, which is what stops the swap from leaving those tiles doing nothing.
The wake-then-connect flow moves to Y with it: there is nothing to look at while a machine
boots into a *desktop*, which is precisely what makes the library path different.
* **the catalog is cached per host** and rendered immediately, marked stale, so a sleeping box
still shows its titles. `pf_client_core::library_cache` (keyed on the pinned fingerprint, so a
new DHCP lease is still the same host) and `kit/library/LibraryCache.kt` (keyed on the host
record id). A cached shelf also OUTRANKS a failure: if the host never answers, the titles on
screen are still the right ones to choose from, and replacing them with a red error because a
box is asleep is exactly what the cache exists to prevent — the wording changes instead
("waking the host…" → "the host didn't answer"), which is why staleness is a three-state enum
and not a flag. A progress line that says something is happening while nothing is must not be
possible to write here.
* **the host is woken on library ENTRY** and the fetch retries across the boot window (12 × 5 s,
the packet re-sent every other attempt) — the same budget the existing wake loops allow.
Waking was bound to CONNECTING, which is too late to help: by then a title has been picked and
the player is sitting through a cold boot. Only "can't reach it" is retried; a rejected
certificate does not become acceptable by waiting.
* **titles already up are badged Resume and sorted first**, read from `/api/v1/status` — already
on the paired-cert lane beside `/library`, so no host work. `untracked` and `grace` both count
as up; only a confirmed `exited` does not.
* **running-first sorts WITHIN each group**, never across. On console-UI that is not cosmetic:
`GridShape` is told a launcher COUNT and lays those entries out as a prefix, so a running game
jumping ahead of a launcher would put the cursor arithmetic and the renderer on two different
fields.
* **the grid returns to where you were** — remembered as the last title OPENED, not an offset, so
it survives a rotation, a resize and a host gaining titles. Android only. The console-UI shell
does not need it and does not get it: its screen stack survives a stream (`session_ended` never
pops), so the shelf is still exactly where it was. What that shell needed instead was the same
principle applied to a different problem — the list moving on its OWN. A `/status` read
re-orders the shelf under a live cursor, so `sync` now anchors on the focused title's id across
a re-order, and reads "a different library" as a different SET of ids rather than a different
sequence. Without that, running-first would have cleared every decoded poster and thrown the
cursor back to the start while somebody was standing on a tile.
Also on console-UI: `ConsoleCmd::RefreshRunning`, raised when a stream ends onto a shelf — the one
moment the running set is most likely to have changed, and the moment the player is standing in
front of the badge that claims to know. Its own command rather than a re-fetch, which would put a
spinner over the shelf they just came back to. And forgetting a host now drops its cached catalog
(and, on Android, its remembered position): a removed host leaves no list of what somebody plays
behind on disk.
Verified:
* console-UI — the Linux container gate over `pf-console-ui` + `pf-client-core` +
`punktfunk-client-session`: fmt clean, clippy `--all-targets -D warnings` clean, a PLAIN build
(not just `--all-targets`), and 176 + 219 tests green. Eight shell/home tests encoded the old
A-connects/Y-library mapping and were rewritten to the new one rather than deleted; the Confirm
arm's coverage is now exhaustive, including the paired-but-unsaved tile that must still connect.
* Android — `:kit:` and `:app:` `compileDebugKotlin` and `compileDebugUnitTestKotlin` all green
(the screenshot harness composes the real grid and coverflow, so their new parameters are
defaulted and it still builds).
* New unit tests cover the `/status` decode (`untracked`/`grace`/unknown states, a command with no
`app_id`), the cache's catalog round trip, the fingerprint path check refusing anything that
isn't 64 lowercase hex, the running-first order preserving the launcher prefix, and `set_running`
being a no-op — no generation bump, so no re-sync — when nothing actually changed.
Backs out the first of C7's six behaviours on both shells it had reached, at review. Items 2–6
(catalog cache, wake on library entry, the Resume badge, running-first ordering, and the position
restore) are untouched and stay.
What item 1 did, and why it is going away:
`9e561f14` read PR #276's "tapping a paired host opens its LIBRARY (streaming the desktop becomes
the secondary action)" as a device-neutral instruction and applied it to three surfaces: Android's
touch host card (tap → library, "Stream the desktop" into the overflow menu, mirroring Apple's
`HostCards.swift`), Android's gamepad carousel, and the console-UI carousel (both: A → library,
Y → stream the desktop).
The two gamepad halves were an extrapolation — those shells have no "tap", so primary/secondary had
to be mapped onto face buttons — and they bought much less than the touch one: **Y already opened
the library there, directly, with a legend hint.** Nothing was buried. So the flip spent entrenched
A-connects muscle memory on an affordance that already existed one press away. The touch card was
the honest case (its library really was buried in an overflow menu), but it goes back too rather
than leaving Android's own two shells disagreeing about what a host card's primary action is.
So: A connects and Y opens the library, exactly as before, on both carousels; and Android's touch
card connects on tap with "Browse library…" back in its overflow menu.
`screens/home.rs`, `GamepadHome.kt` and `ConnectGrid.kt` carried nothing but item 1 and are restored
verbatim from `d0c0e8cc`. `shell/tests.rs` also carries items 4/5' `running: false` model field, so
it was restored the same way and that one field re-applied — it now differs from the base by exactly
that field and nothing else, which is the point: the eight tests that encode the A/B mapping are
back to asserting the mapping that ships.
webOS needed no revert. Item 1 was already satisfied by its design — its home IS a host sidebar
beside a library grid, so selecting a host has always opened its library — and that path was never
touched.
Verified: the same two lanes as the parent commit, re-run over the reverted tree — the Linux
container gate (fmt, clippy `--all-targets -D warnings`, a plain build, and the pf-console-ui +
pf-client-core suites), and Gradle `:kit:`/`:app:` `compileDebugKotlin` plus
`compileDebugUnitTestKotlin`.
Two console-UI defects found on glass.
**1. The collections hand-over silently stopped happening — a C7 regression.**
`LibraryScreen::collections_upgrade` decided "the library in the shared model is the one MY fetch
produced" by OBSERVING a non-`Ready` phase from the render loop, and latching a `fetch_seen` flag
when it saw one. That only ever held because a fetch's first act was to block on the network: the
model sat at `Loading` for hundreds of milliseconds, so any frame caught it.
The catalog cache (`9e561f14`, C7 item 2) put a disk read in front of that. On a warm cache the
fetch thread publishes `Ready` about a millisecond after `Loading` — both inside a single 60 Hz
frame — so a shelf pushed over a host visited before went from the PREVIOUS host's `Ready` straight
to its own without any frame observing the `Loading` between them. `fetch_seen` stayed false, the
hand-over was refused for the life of the screen, and "Start in collections" did nothing. Worse the
more you used it: the failure needed a populated cache, so it appeared exactly on the hosts you open
most and never on a fresh install.
Fixed by not inferring it. `LibraryShared` gains a `fetch_epoch`, bumped once per fetch by a new
`begin_fetch()` (which the console binary now calls in place of `set_phase(Loading)`), and a shelf
records the epoch at the moment it is PUSHED — before its `FetchLibrary` is even queued. "Is this
list mine" becomes `fetch_epoch() != entry_epoch`: a counter cannot be missed the way a passing
state can, and it is exact rather than probabilistic. `fetch_seen` is gone.
Two regression tests, and the first is written so a phase-observing implementation cannot pass it —
there is deliberately no `collections_upgrade` call between `begin_fetch` and the cached list, which
is the whole point:
* `a_warm_cache_still_hands_over_though_no_frame_ever_saw_loading`
* `a_cached_list_from_before_the_push_is_still_refused` — the other half, so the fix cannot degrade
into "always upgrade"
The existing tests that drove the old flag through a phase transition now model a real push
(`begin_fetch`), including the failed-fetch-then-retry case, where a retry is a second epoch.
**2. The sort/view bar's focus wash covered the band, not the row.**
`TabStrip` seats its pills 2 dp into whatever band it is given and draws them 30 tall, so the
library bar's content occupies 34 of its 46 dp band — the remaining 12 is the air before the field.
The focus wash was drawn over the whole `bar` rect, putting 2 dp above the pills and 14 below: a
backdrop its own content visibly sat high inside. It now covers the pill row's extent with
symmetric padding, so it is centred on the thing it highlights at any scale. `TAB_PILL_TOP` /
`TAB_PILL_H` are published from `widgets` and used by both the strip and the wash, so the two
cannot drift.
Deliberately NOT fixed by moving the pills: settings and collections hand `TabStrip` the same band
and draw no backdrop behind it, where "tabs, then air, then content" is the right rhythm. Centring
the row there would have crowded their lists for a defect neither has.
Also adds the screenshot scene that was missing — `07c-library-bar` at both palette poles. The bar's
wash only draws when the bar has focus, and no dump ever put it in that state, which is how a
mis-sized backdrop shipped. Shot on violet AND mint because the wash is `accent(0.14)` and a
translucent accent reads differently over a dark field than a pale one.
Verified: the Linux container gate over `pf-console-ui` + `pf-client-core` +
`punktfunk-client-session` — fmt, clippy `--all-targets -D warnings`, a plain build, and 177 + 219
tests green. Both new shots rendered and eyeballed: the wash hugs the pill row on both palettes.
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.
PR #276 shipped six library behaviours on the Apple client only, and deferred parity (C7) to a follow-up. This is that follow-up for Android and the Linux/console-UI shell. webOS rides in its own repo — see the note at the bottom.
No host work:
GET /api/v1/statusalready carriesgames[]and is already on the paired-cert allowlist (mgmt::auth::cert_may_access), the same mTLS identity the catalog fetch uses.The five behaviours this PR actually ships
Judged per item and per client rather than ported — two of them meant something different away from SwiftUI.
2. The catalog is cached per host and rendered immediately, marked stale, so a sleeping box still shows its titles.
pf_client_core::library_cache(keyed on the pinned fingerprint, so a new DHCP lease is still the same host) andkit/library/LibraryCache.kt(keyed on the host record id). A cached shelf outranks a failure: if the host never answers, the titles on screen are still the right ones to choose from, and replacing them with a red error because a box is asleep is exactly what the cache exists to prevent. The wording changes instead — "waking the host…" → "the host didn't answer" — which is why staleness is a three-state enum and not a flag. A progress line that claims something is happening while nothing is must not be possible to write here.3. The host is woken on library ENTRY, and the fetch retries across the boot window (12 × 5 s, packet re-sent every other attempt — the same budget the existing wake loops allow). Waking was bound to CONNECTING, which is too late to help: by then a title has been picked and the player is sitting through a cold boot. Only "can't reach it" is retried; a rejected certificate does not become acceptable by waiting.
4. Titles already up are badged Resume, read from
/status.untrackedandgraceboth count as up; only a confirmedexiteddoes not.5. Running-first sorts WITHIN each group, never across. On console-UI that is not cosmetic:
GridShapeis told a launcher count and lays those entries out as a prefix, so a running game jumping ahead of a launcher would put the cursor arithmetic and the renderer on two different fields.6. The grid returns to where you were — remembered as the last title opened, not an offset, so it survives a rotation, a resize and a host gaining titles. Android only. The console-UI shell does not need it and does not get it: its screen stack survives a stream (
session_endednever pops), so the shelf is still exactly where it was.What that shell needed instead was the same principle applied to a different problem — the list moving on its own. A
/statusread re-orders the shelf under a live cursor, sosyncnow anchors on the focused title's id across a re-order, and reads "a different library" as a different set of ids rather than a different sequence. Without that, running-first would have cleared every decoded poster and thrown the cursor back to the start while somebody was standing on a tile.Also
ConsoleCmd::RefreshRunning, raised when a stream ends onto a shelf — the one moment the running set is most likely to have changed, and the moment the player is standing in front of the badge that claims to know. Its own command rather than a re-fetch, which would put a spinner over the shelf they just came back to.Item 1 — implemented, then reverted (
9a64a0fa)#276's item 1 read "tapping a paired host opens its LIBRARY (streaming the desktop becomes the secondary action)".
9e561f14applied that to three surfaces: Android's touch host card (tap → library, "Stream the desktop" into the overflow, mirroring Apple'sHostCards.swift), Android's gamepad carousel, and the console-UI carousel (both: A → library, Y → desktop).Backed out at review. The two gamepad halves were an extrapolation — those shells have no "tap" — and bought much less than the touch one: Y already opened the library there, directly, with a legend hint. Nothing was buried, so the flip spent entrenched A-connects muscle memory on an affordance that already existed one press away. The touch card was the honest case, but it goes back too rather than leaving Android's own two shells disagreeing about what a host card's primary press does.
screens/home.rs,GamepadHome.ktandConnectGrid.ktare now byte-identical tomain.shell/tests.rsdiffers from base by exactly the sevenrunning: falselines item 4/5 needs — the eight tests that encode the A/Y mapping are back to asserting what ships.Verified
pf-console-ui+pf-client-core+punktfunk-client-session: fmt clean, clippy--all-targets -D warningsclean, a plain build (not just--all-targets), 175 + 219 tests green (175 = the pre-existing count, restored by the revert). Non-vacuity confirmed.:kit:and:app:compileDebugKotlinandcompileDebugUnitTestKotlingreen (the screenshot harness composes the real grid and coverflow, so their new parameters are defaulted and it still builds)./statusdecode (untracked/grace/unknown states, a command with noapp_id), the cache's catalog round trip, the fingerprint path check refusing anything that isn't 64 lowercase hex, running-first preserving the launcher prefix, andset_runningbeing a no-op — no generation bump, so no re-sync — when nothing changed.webOS — separate repo, NOT in this PR
The webOS half is written and gated green (
cargo clippy --target armv7-unknown-linux-gnueabi --all-targets -- -D warnings, in the project's own Docker toolchain), but it lives inpunktfunk-webosand this session could not commit there. Item 1 never applied to it — its home is a host sidebar beside a library grid, so selecting a host has always opened its library, and that path was never touched. Item 3 was already satisfied too (start_wakealready probes and retries). Items 2, 4, 5 and 6 are new. One judgement worth flagging: webOS has user-pinned games, so running-first leads the unpinned remainder only — an explicit pin is a placement the player chose and should not be overruled by a transient state.Files touched there:
src/{main,library,library_cache,store}.rs,src/app/{mod,home,addhost,edithost,pairing}.rs,src/ui/{cards,tiles}.rs.