The shelf was reachable only with Y from a console tile — and a finger has no Y. The touch home simply could not open it on any device.
LibraryScreen now has two presentations of the same shelf, the way the Apple client's LibraryView does: the existing console coverflow, and the poster grid the Apple, GTK and Windows shells already draw. Everything data-shaped — the fetch, the loading/error/empty states, the launch — is shared; only the arrangement differs, because only the input device does.
A host card's overflow grew "Browse library…", behind the same Game library toggle as everywhere else. Pinned cards offer it too: browsing is that card's connect with a title picked first, so its shelf launches with the card's profile (design §5.2a).
The touch shell also consumes the "come back to this library" intent after a launched game exits — previously only the console shell did.
The grid is deliberately not the coverflow with its legend hidden: a coverflow is a one-at-a-time strip built for a D-pad, and on a phone it turns a 400-title library into 400 swipes.
2. No library view offered "Copy link"
design/client-deep-links.md §5 names the library game context menu as an attach point beside the host card and the pinned card, but no client had it. Every library view now hands out that title's own URL — the same self-emitted form a host card gives (stable id + host= + fp=), plus the game's launch= id, so pasting it into Playnite or a Stream Deck macro boots straight into the title. A shelf opened from a pinned card carries that card's profile into the link.
The gesture follows each surface:
Surface
Affordance
Apple grid (iOS/iPadOS/macOS)
.contextMenu → Copy Link
Apple coverflow (iOS/macOS)
X, with the legend flipping to "Copied"
Android touch grid
long-press → Copy link
Android coverflow
X + legend entry, toast
GTK library page
per-poster overflow and right-click
Windows library page
per-tile overflow button
pf-console-ui coverflow (Linux/Windows consoles)
X + legend entry
Windows uses a button rather than a right-click flyout because the reactor hangs menu_flyout off buttons only — a menu a mouse user cannot see is one they never find.
Two deliberate absences. tvOS gets no Copy Link: that platform has no clipboard, and its host cards already hide the action for the same reason (LinkClipboard.isAvailable). Windows copies silently, matching its own host tiles — that shell has no toast, and the two must not disagree about what copying a link looks like.
Verification
Real toolchains, not just a local check:
Linux / console UI (pf-gtkflow container): cargo fmt --all --check, clippy --all-targets -D warnings on pf-console-uiandpunktfunk-client-linux, a plain cargo build (catches dead-outside-tests), and 85 tests — all green, and non-vacuous (both crates really compiled). The GTK library page was also rendered headless to check the new overflow reads on artwork.
Windows (CI runner .133): clippy --all-targets -D warnings + cargo build, clean, non-vacuous, run twice.
Apple: swift build green on macOS, iOS and tvOS; swift test 334 passed / 0 failed.
Android: compileDebugKotlin + compileDebugUnitTestKotlin green; both library screenshot scenes rendered and inspected.
Notes for the reviewer
⚠️The GTK library page has a pending crash fix elsewhere.worktree-gtk-library-card-activation-overflow (9e71cda2) fixes a stack overflow on every game click there, and is not on main yet. These changes touch different parts of ui_library.rs and should merge cleanly, but GTK's copy-link is only usable in practice once that lands.
⚠️The two GTK display tests cannot share a process. GTK may only be initialised from one thread ever, so a bare cargo test -- --ignored on punktfunk-client-linux is a false red (--test-threads=1 does not help — same process). Both pass when run one per process with --exact.
TouchGrid is internal rather than private for the same reason Coverflow already is: the screenshot harness composes the real grid with a mock shelf, because the screen around it takes its state off the network.
Two gaps, both about the game library.
## 1. Android's touch UI had no route to the library
The shelf was reachable only with **Y from a console tile** — and a finger has no Y. The touch home simply could not open it on any device.
`LibraryScreen` now has **two presentations of the same shelf**, the way the Apple client's `LibraryView` does: the existing console coverflow, and the poster **grid** the Apple, GTK and Windows shells already draw. Everything data-shaped — the fetch, the loading/error/empty states, the launch — is shared; only the arrangement differs, because only the input device does.
* A host card's overflow grew **"Browse library…"**, behind the same *Game library* toggle as everywhere else. Pinned cards offer it too: browsing is that card's connect with a title picked first, so its shelf launches with the card's profile (design §5.2a).
* The touch shell also consumes the **"come back to this library"** intent after a launched game exits — previously only the console shell did.
* The grid is deliberately not the coverflow with its legend hidden: a coverflow is a one-at-a-time strip built for a D-pad, and on a phone it turns a 400-title library into 400 swipes.
## 2. No library view offered "Copy link"
`design/client-deep-links.md` §5 names **the library game context menu** as an attach point beside the host card and the pinned card, but no client had it. Every library view now hands out that title's own URL — the same self-emitted form a host card gives (stable id + `host=` + `fp=`), plus the game's `launch=` id, so pasting it into Playnite or a Stream Deck macro boots straight into the title. A shelf opened from a pinned card carries that card's profile into the link.
The gesture follows each surface:
| Surface | Affordance |
|---|---|
| Apple grid (iOS/iPadOS/macOS) | `.contextMenu` → **Copy Link** |
| Apple coverflow (iOS/macOS) | **X**, with the legend flipping to "Copied" |
| Android touch grid | long-press → **Copy link** |
| Android coverflow | **X** + legend entry, toast |
| GTK library page | per-poster overflow **and** right-click |
| Windows library page | per-tile overflow button |
| `pf-console-ui` coverflow (Linux/Windows consoles) | **X** + legend entry |
Windows uses a button rather than a right-click flyout because the reactor hangs `menu_flyout` off buttons only — a menu a mouse user cannot see is one they never find.
**Two deliberate absences.** tvOS gets no Copy Link: that platform has no clipboard, and its host cards already hide the action for the same reason (`LinkClipboard.isAvailable`). Windows copies silently, matching its own host tiles — that shell has no toast, and the two must not disagree about what copying a link looks like.
## Verification
Real toolchains, not just a local check:
* **Linux / console UI** (`pf-gtkflow` container): `cargo fmt --all --check`, `clippy --all-targets -D warnings` on `pf-console-ui` **and** `punktfunk-client-linux`, a plain `cargo build` (catches dead-outside-tests), and 85 tests — all green, and non-vacuous (both crates really compiled). The GTK library page was also rendered headless to check the new overflow reads on artwork.
* **Windows** (CI runner .133): `clippy --all-targets -D warnings` + `cargo build`, clean, non-vacuous, run twice.
* **Apple**: `swift build` green on **macOS, iOS and tvOS**; `swift test` 334 passed / 0 failed.
* **Android**: `compileDebugKotlin` + `compileDebugUnitTestKotlin` green; both library screenshot scenes rendered and inspected.
### Notes for the reviewer
* ⚠️ **The GTK library page has a pending crash fix elsewhere.** `worktree-gtk-library-card-activation-overflow` (`9e71cda2`) fixes a stack overflow on *every* game click there, and is not on `main` yet. These changes touch different parts of `ui_library.rs` and should merge cleanly, but GTK's copy-link is only usable in practice once that lands.
* ⚠️ **The two GTK display tests cannot share a process.** GTK may only be initialised from one thread ever, so a bare `cargo test -- --ignored` on `punktfunk-client-linux` is a false red (`--test-threads=1` does not help — same process). Both pass when run one per process with `--exact`.
* `TouchGrid` is `internal` rather than `private` for the same reason `Coverflow` already is: the screenshot harness composes the real grid with a mock shelf, because the screen around it takes its state off the network.
Two gaps, both about the library:
* On Android, the shelf was reachable only with Y from a console tile — a
finger has no Y, so the touch home had no route to it at all. LibraryScreen
grows a second presentation (the poster GRID the Apple, GTK and Windows
shells already draw) and a card's overflow grows "Browse library…", behind
the same experimental toggle as everywhere else.
* No library view offered "Copy link", although design/client-deep-links.md §5
names the library game context menu as an attach point beside the host card
and the pinned card. Every shell now hands out a title's own URL — the same
self-emitted form a host card gives, plus that game's launch= id, so pasting
it into Playnite or a Stream Deck macro boots straight into the title.
The gesture follows each surface: a context menu on the touch/mouse grids
(Apple, Android, GTK; Windows uses a tile overflow button, since the reactor
hangs menu_flyout off buttons only), and the X face button plus a legend entry
on the three gamepad coverflows, where there is no right-click.
Two follow-ups on the library work:
* LibraryScreen handed ConsoleLibrary/TouchLibrary `::launch` and `::copyLink`.
Two callable references to the same local function compare EQUAL however
different the frame they captured, so a skipped recomposition would have left
the child calling a closure over stale settings — the trap SettingsScreen
documents at scopeProfile(), having already been bitten by it. Lambdas
instead, which is what every other call site here passes.
* The touch grid was a brand-new screen with no visual coverage. TouchGrid goes
internal (the reason Coverflow already is) and the harness grows a portrait
scene over the same mock shelf the coverflow scene uses, so the column count,
the group headings and the launcher brand-mark tile are all captured.
The poster's new "…" button was a child of the border carrying on_tapped, so
opening the menu was also a click on the tile. host_tile on the hosts page
already splits these — the tapped region is the inner summary and the menu
button is its SIBLING in the grid — and the library tile now does the same.
Relying on Button marking Tapped handled is a guess about event routing where
the shell already has an answer that does not need one.
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.
Two gaps, both about the game library.
1. Android's touch UI had no route to the library
The shelf was reachable only with Y from a console tile — and a finger has no Y. The touch home simply could not open it on any device.
LibraryScreennow has two presentations of the same shelf, the way the Apple client'sLibraryViewdoes: the existing console coverflow, and the poster grid the Apple, GTK and Windows shells already draw. Everything data-shaped — the fetch, the loading/error/empty states, the launch — is shared; only the arrangement differs, because only the input device does.2. No library view offered "Copy link"
design/client-deep-links.md§5 names the library game context menu as an attach point beside the host card and the pinned card, but no client had it. Every library view now hands out that title's own URL — the same self-emitted form a host card gives (stable id +host=+fp=), plus the game'slaunch=id, so pasting it into Playnite or a Stream Deck macro boots straight into the title. A shelf opened from a pinned card carries that card's profile into the link.The gesture follows each surface:
.contextMenu→ Copy Linkpf-console-uicoverflow (Linux/Windows consoles)Windows uses a button rather than a right-click flyout because the reactor hangs
menu_flyoutoff buttons only — a menu a mouse user cannot see is one they never find.Two deliberate absences. tvOS gets no Copy Link: that platform has no clipboard, and its host cards already hide the action for the same reason (
LinkClipboard.isAvailable). Windows copies silently, matching its own host tiles — that shell has no toast, and the two must not disagree about what copying a link looks like.Verification
Real toolchains, not just a local check:
pf-gtkflowcontainer):cargo fmt --all --check,clippy --all-targets -D warningsonpf-console-uiandpunktfunk-client-linux, a plaincargo build(catches dead-outside-tests), and 85 tests — all green, and non-vacuous (both crates really compiled). The GTK library page was also rendered headless to check the new overflow reads on artwork.clippy --all-targets -D warnings+cargo build, clean, non-vacuous, run twice.swift buildgreen on macOS, iOS and tvOS;swift test334 passed / 0 failed.compileDebugKotlin+compileDebugUnitTestKotlingreen; both library screenshot scenes rendered and inspected.Notes for the reviewer
worktree-gtk-library-card-activation-overflow(9e71cda2) fixes a stack overflow on every game click there, and is not onmainyet. These changes touch different parts ofui_library.rsand should merge cleanly, but GTK's copy-link is only usable in practice once that lands.cargo test -- --ignoredonpunktfunk-client-linuxis a false red (--test-threads=1does not help — same process). Both pass when run one per process with--exact.TouchGridisinternalrather thanprivatefor the same reasonCoverflowalready is: the screenshot harness composes the real grid with a mock shelf, because the screen around it takes its state off the network.