The library round-trip reaches the other clients — C7 for Android and the console UI #278
Merged
enricobuehler
merged 3 commits from 2026-08-17 08:55:27 +00:00
worktree-library-parity-c7 into main
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b93815ac6e |
fix(console): "Start in collections" stopped working once the catalog was cached, and the sort bar's focus wash was taller than the row it highlights
ci / docs-site (pull_request) Successful in 1m19s
ci / rust-arm64 (pull_request) Successful in 1m25s
ci / bun-nix (pull_request) Successful in 1m33s
ci / web (pull_request) Successful in 3m37s
ci / rust (pull_request) Successful in 4m53s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m41s
android / android (pull_request) Successful in 8m54s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 2m52s
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. |
||
|
|
9a64a0faef |
revert(clients/android,console): item 1 — a host card's primary press stays "connect"
android / android (pull_request) Successful in 6m5s
ci / rust (pull_request) Successful in 6m13s
ci / web (pull_request) Successful in 1m12s
ci / docs-site (pull_request) Successful in 1m21s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m30s
ci / bun-nix (pull_request) Successful in 4m23s
ci / rust-arm64 (pull_request) Successful in 7m5s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m16s
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`. |
||
|
|
9e561f1404 |
feat(clients/android,console): the library round-trip reaches the other clients — C7
ci / docs-site (pull_request) Successful in 1m18s
ci / bun-nix (pull_request) Successful in 3m38s
ci / rust-arm64 (pull_request) Successful in 4m8s
ci / web (pull_request) Successful in 4m17s
android / android (pull_request) Successful in 4m58s
ci / rust (pull_request) Successful in 5m8s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Successful in 6m31s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Successful in 3m1s
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. |