Closes the gamepad-UI half of the client settings profiles design (client-settings-profiles.md §5.2a, §5.4) across all three gamepad UI implementations. Profiles stay desktop-authored; the gamepad surfaces render pinned cards, connect with them, and — new — manage pins.
Pinned cards: the service thread expands each saved host into its primary tile followed by one card per KnownHost::pinned_profiles entry (resolved against the catalog, dedup + dangling-drop via resolved_pins). Card subtitle = profile name, tinted with the profile accent. Primary tiles show the bound default profile chip after the address.
Connect-with-profile: ConnectIntent/OverlayAction::Launch carry an optional one-off profile id fed to trust::effective_settings — the same resolver --connect --profile uses; dangling ids fall back to defaults without blocking the connect. Wake-then-connect from a pinned card keeps the profile.
Pin management: Settings gains a trailing Profiles section (one row per profile, live "Pinned to N hosts" counts, desktop-app explainer; inert "No profiles yet" placeholder when the catalog is empty). Activating opens a pin-to-hosts screen; toggles ride ConsoleCmd::SetPin to the binary, which persists pinned_profiles (append = card order; never touches the default binding; idempotent).
ConsoleEntry::Library boxes its HostRow (clippy large_enum_variant once the row learned its profile chips).
This is the Decky contract: pins created here land in KnownHost.pinned_profiles, which the CLI already resolves into its hosts JSON, and punktfunk-session --connect --profile already exists — the redesigned Decky panel renders pinned cards with no plugin-side work.
Apple (80b4eccf)
Pinned cards + connect were already in from WP5; this adds the missing management: GamepadSettingsView gains the same Profiles section with an in-place pin-to-hosts picker driving HostStore.setPinned. On tvOS this is the only possible pin surface — and since tvOS ships no profile editor and the catalog is a per-device App Group, the tvOS strings honestly say creation isn't available on Apple TV instead of pointing at a UI that doesn't exist there.
Android (857d7d7b, ff560236)
Pinned cards + unpin were already in from WP6; pin-add was touch-only. GamepadSettingsScreen gains the Profiles section plus a console-styled, remote-navigable GamepadPinHostsDialog writing KnownHost.pinnedProfileIds. Strings branch on isTvDevice: a TV names the actual on-device route (turn off Controller-optimized UI → standard interface, which unlike tvOS does contain the editor); phones keep "the touch interface".
Verification
Rust: cargo clippy --all-targets --locked -D warnings on pf-console-ui / punktfunk-client-session / pf-presenter + cargo test -p pf-console-ui (47 passed, incl. new tests for pinned-card connect intent, the settings section, and SetPin emission) — Linux amd64 container; cargo fmt --all clean.
Apple: swift build clean (macOS pass over the shared gamepad views; required rebuilding the stale local xcframework — gitignored, not in this diff); SharedFoundationTests 22/22 + 4 related suites green.
Owed / not covered: on-glass passes on all three surfaces (Deck console visuals, tvOS focus engine, Android TV remote); tvOS-conditional #if lines are typecheck-unverified (macOS build doesn't compile them). tvOS will realistically show the empty-catalog placeholder until a profile sync/import path exists — worth a design-doc note.
Closes the gamepad-UI half of the client settings profiles design (`client-settings-profiles.md` §5.2a, §5.4) across all three gamepad UI implementations. Profiles stay desktop-authored; the gamepad surfaces render pinned cards, connect with them, and — new — manage pins.
## Console (Linux/Windows, `pf-console-ui` + session binary) — `63a4f583`
Previously had no profiles integration at all.
- **Pinned cards**: the service thread expands each saved host into its primary tile followed by one card per `KnownHost::pinned_profiles` entry (resolved against the catalog, dedup + dangling-drop via `resolved_pins`). Card subtitle = profile name, tinted with the profile accent. Primary tiles show the bound default profile chip after the address.
- **Connect-with-profile**: `ConnectIntent`/`OverlayAction::Launch` carry an optional one-off profile id fed to `trust::effective_settings` — the same resolver `--connect --profile` uses; dangling ids fall back to defaults without blocking the connect. Wake-then-connect from a pinned card keeps the profile.
- **Pin management**: Settings gains a trailing **Profiles** section (one row per profile, live "Pinned to N hosts" counts, desktop-app explainer; inert "No profiles yet" placeholder when the catalog is empty). Activating opens a pin-to-hosts screen; toggles ride `ConsoleCmd::SetPin` to the binary, which persists `pinned_profiles` (append = card order; never touches the default binding; idempotent).
- `ConsoleEntry::Library` boxes its `HostRow` (clippy `large_enum_variant` once the row learned its profile chips).
**This is the Decky contract**: pins created here land in `KnownHost.pinned_profiles`, which the CLI already resolves into its hosts JSON, and `punktfunk-session --connect --profile` already exists — the redesigned Decky panel renders pinned cards with no plugin-side work.
## Apple (`80b4eccf`)
Pinned cards + connect were already in from WP5; this adds the missing management: `GamepadSettingsView` gains the same Profiles section with an in-place pin-to-hosts picker driving `HostStore.setPinned`. On tvOS this is the only possible pin surface — and since tvOS ships no profile editor and the catalog is a per-device App Group, the tvOS strings honestly say creation isn't available on Apple TV instead of pointing at a UI that doesn't exist there.
## Android (`857d7d7b`, `ff560236`)
Pinned cards + unpin were already in from WP6; pin-add was touch-only. `GamepadSettingsScreen` gains the Profiles section plus a console-styled, remote-navigable `GamepadPinHostsDialog` writing `KnownHost.pinnedProfileIds`. Strings branch on `isTvDevice`: a TV names the actual on-device route (turn off Controller-optimized UI → standard interface, which unlike tvOS does contain the editor); phones keep "the touch interface".
## Verification
- Rust: `cargo clippy --all-targets --locked -D warnings` on `pf-console-ui` / `punktfunk-client-session` / `pf-presenter` + `cargo test -p pf-console-ui` (47 passed, incl. new tests for pinned-card connect intent, the settings section, and SetPin emission) — Linux amd64 container; `cargo fmt --all` clean.
- Apple: `swift build` clean (macOS pass over the shared gamepad views; required rebuilding the stale local xcframework — gitignored, not in this diff); `SharedFoundationTests` 22/22 + 4 related suites green.
- Android: `:app:compileDebugKotlin` green; `KnownHostStoreTest` + `ProfilesTest` green.
**Owed / not covered**: on-glass passes on all three surfaces (Deck console visuals, tvOS focus engine, Android TV remote); tvOS-conditional `#if` lines are typecheck-unverified (macOS build doesn't compile them). tvOS will realistically show the empty-catalog placeholder until a profile sync/import path exists — worth a design-doc note.
The Skia console now renders a pinned profile card after its host's primary
tile (KnownHost::pinned_profiles resolved by the service thread), connects
with that profile as a one-off via the existing effective_settings resolver,
and shows the bound default profile on the primary tile. The settings screen
gains a trailing Profiles section — one row per catalog profile with a live
pin count — whose activation opens a pin-to-hosts screen; toggles ride the
new ConsoleCmd::SetPin to the binary, which persists pinned_profiles (the
same field the CLI resolves for Decky's host list). Profiles themselves stay
desktop-authored (design client-settings-profiles.md §5.2a, §5.4).
GamepadSettingsView gains a trailing Profiles section (one row per catalog
profile, live pinned-to-N-hosts counts) and an in-place pin-to-hosts picker
driving HostStore.setPinned — the first pin management reachable from the
controller-first UI, and on tvOS the only possible one. tvOS wording drops
the 'create them in the standard interface' promise (no profile editor
exists there); other platforms keep it. Pinned-card rendering and the
connect path were already in from WP5 and stay untouched.
GamepadSettingsScreen gains the trailing Profiles section (per-profile rows
with live pin counts, touch-interface explainer) and a console-styled
GamepadPinHostsDialog — controller- and TV-remote-navigable pin management
writing KnownHost.pinnedProfileIds through the existing store path. Pin-add
was previously touch-only; pinned-card rendering and unpin stay as they
were.
'Created and edited in the touch interface' is dead advice on a TV box — no
touch to reach it with. Unlike tvOS the editor DOES exist on-device (same
APK), behind this screen's own Controller-optimized UI toggle, so on TV the
Profiles strings now name that route instead.
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.
Closes the gamepad-UI half of the client settings profiles design (
client-settings-profiles.md§5.2a, §5.4) across all three gamepad UI implementations. Profiles stay desktop-authored; the gamepad surfaces render pinned cards, connect with them, and — new — manage pins.Console (Linux/Windows,
pf-console-ui+ session binary) —63a4f583Previously had no profiles integration at all.
KnownHost::pinned_profilesentry (resolved against the catalog, dedup + dangling-drop viaresolved_pins). Card subtitle = profile name, tinted with the profile accent. Primary tiles show the bound default profile chip after the address.ConnectIntent/OverlayAction::Launchcarry an optional one-off profile id fed totrust::effective_settings— the same resolver--connect --profileuses; dangling ids fall back to defaults without blocking the connect. Wake-then-connect from a pinned card keeps the profile.ConsoleCmd::SetPinto the binary, which persistspinned_profiles(append = card order; never touches the default binding; idempotent).ConsoleEntry::Libraryboxes itsHostRow(clippylarge_enum_variantonce the row learned its profile chips).This is the Decky contract: pins created here land in
KnownHost.pinned_profiles, which the CLI already resolves into its hosts JSON, andpunktfunk-session --connect --profilealready exists — the redesigned Decky panel renders pinned cards with no plugin-side work.Apple (
80b4eccf)Pinned cards + connect were already in from WP5; this adds the missing management:
GamepadSettingsViewgains the same Profiles section with an in-place pin-to-hosts picker drivingHostStore.setPinned. On tvOS this is the only possible pin surface — and since tvOS ships no profile editor and the catalog is a per-device App Group, the tvOS strings honestly say creation isn't available on Apple TV instead of pointing at a UI that doesn't exist there.Android (
857d7d7b,ff560236)Pinned cards + unpin were already in from WP6; pin-add was touch-only.
GamepadSettingsScreengains the Profiles section plus a console-styled, remote-navigableGamepadPinHostsDialogwritingKnownHost.pinnedProfileIds. Strings branch onisTvDevice: a TV names the actual on-device route (turn off Controller-optimized UI → standard interface, which unlike tvOS does contain the editor); phones keep "the touch interface".Verification
cargo clippy --all-targets --locked -D warningsonpf-console-ui/punktfunk-client-session/pf-presenter+cargo test -p pf-console-ui(47 passed, incl. new tests for pinned-card connect intent, the settings section, and SetPin emission) — Linux amd64 container;cargo fmt --allclean.swift buildclean (macOS pass over the shared gamepad views; required rebuilding the stale local xcframework — gitignored, not in this diff);SharedFoundationTests22/22 + 4 related suites green.:app:compileDebugKotlingreen;KnownHostStoreTest+ProfilesTestgreen.Owed / not covered: on-glass passes on all three surfaces (Deck console visuals, tvOS focus engine, Android TV remote); tvOS-conditional
#iflines are typecheck-unverified (macOS build doesn't compile them). tvOS will realistically show the empty-catalog placeholder until a profile sync/import path exists — worth a design-doc note.