The game library was unreachable in Android's touch UI, and no library view could copy a title's link #266

Merged
enricobuehler merged 3 commits from worktree-library-access-and-copy-link into main 2026-08-16 08:54:01 +00:00
Owner

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) .contextMenuCopy 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 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.
enricobuehler added 3 commits 2026-08-16 08:45:16 +00:00
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.
fix(client-windows): the library tile's overflow sat inside its own launch backstop
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Failing after 3m17s
ci / bun-nix (pull_request) Successful in 2m11s
ci / rust (pull_request) Successful in 8m19s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Failing after 4m58s
ci / docs-site (pull_request) Successful in 1m59s
ci / web (pull_request) Successful in 4m41s
android / android (pull_request) Successful in 11m56s
apple / swift (pull_request) Failing after 12m45s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 3m22s
14bdfeaeeb
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.
enricobuehler merged commit 75eaf9ad91 into main 2026-08-16 08:54:01 +00:00
enricobuehler deleted branch worktree-library-access-and-copy-link 2026-08-16 08:54:08 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#266