fix(decky): duplicate shortcut minted every boot + toast noise cut #184

Merged
enricobuehler merged 3 commits from worktree-decky-shortcut-dup into main 2026-08-12 20:26:00 +00:00
Owner

Two field complaints, both in the Decky plugin.

A new "Punktfunk" library shortcut on every plugin load (79dba7f9)

Reported from the field: each Steam start added another visible "Punktfunk" entry — plain to see in the desktop client's library.

Mechanism: db063792 (shipped in 0.26.0/0.27.0) made shortcutStillExists() actually answer for the first time, and its callers treat a null overview as "the user deleted the shortcut". But the plugin mounts while Steam is still starting up, before appStore has registered its overviews, so the remembered — perfectly live — appId looks up as null on every boot: mint a duplicate, remember the new id, orphan yesterday's.

The deleted verdict now has to be earned, and creation is a last resort:

  • shortcutStillExists() only believes "absent" once the store is demonstrably hydrated: wait out App.WaitForServicesInitialized (raced against the poll budget so a wedged signal can't hang the guard), poll until allApps is non-empty, then one grace recheck. Unverifiable within budget answers true: a false "alive" merely no-ops until the next ask, a false "dead" duplicates forever.
  • On a genuinely lost id, both ensure paths first adopt an existing same-named shortcut (excluding the other role's) instead of minting an N+1th — which also heals installs the old builds already littered.
  • Both ensures are single-flight: mount's fire-and-forget can now be mid-wait when a QAM press arrives, and two ensures racing past the liveness check would each AddShortcut.
  • "Recreate library shortcut" additionally sweeps surplus "Punktfunk" shortcuts (RemoveShortcut) and toasts the count — the cleanup path for piles already minted. Deliberately button-only, never mount.

Affected installs keep their pile until this ships and they press the button (or delete by hand).

Too many toasts (339a1d70)

Inventory of all 14 toast sites: almost all are rare, explicit-tap feedback. Two were routine-volume offenders:

  • startStream toasted "Starting stream — host" on every successful launch — the overwhelming majority of all toasts the plugin ever shows, repeating the button just pressed and landing on top of the starting stream. Gone; launch failures still toast.
  • useHosts.refresh() toasted "Couldn't list hosts" from its catch, and the panel remounts (and refreshes) on every QAM open — a broken backend nagged on each open. Now a third inline problem row next to the Refresh button that retries it, like the client-unavailable/outdated states.

Verification

tsc --noEmit and the rollup bundle pass (also re-run after merging main). The launch paths hit the fast path unchanged — a live overview answers the first query and nothing waits. Not yet exercised on a physical Deck; a boot-loop test there (deploy + two Steam restarts, count library entries) is the definitive check before tagging.

Two field complaints, both in the Decky plugin. ## A new "Punktfunk" library shortcut on every plugin load (`79dba7f9`) Reported from the field: each Steam start added another visible "Punktfunk" entry — plain to see in the desktop client's library. **Mechanism:** `db063792` (shipped in 0.26.0/0.27.0) made `shortcutStillExists()` actually answer for the first time, and its callers treat a null overview as "the user deleted the shortcut". But the plugin mounts while Steam is still starting up, **before `appStore` has registered its overviews**, so the remembered — perfectly live — appId looks up as null on every boot: mint a duplicate, remember the new id, orphan yesterday's. The deleted verdict now has to be earned, and creation is a last resort: - `shortcutStillExists()` only believes "absent" once the store is demonstrably hydrated: wait out `App.WaitForServicesInitialized` (raced against the poll budget so a wedged signal can't hang the guard), poll until `allApps` is non-empty, then one grace recheck. Unverifiable within budget answers **true**: a false "alive" merely no-ops until the next ask, a false "dead" duplicates forever. - On a genuinely lost id, both ensure paths first **adopt** an existing same-named shortcut (excluding the other role's) instead of minting an N+1th — which also heals installs the old builds already littered. - Both ensures are single-flight: mount's fire-and-forget can now be mid-wait when a QAM press arrives, and two ensures racing past the liveness check would each `AddShortcut`. - "Recreate library shortcut" additionally **sweeps surplus "Punktfunk" shortcuts** (`RemoveShortcut`) and toasts the count — the cleanup path for piles already minted. Deliberately button-only, never mount. Affected installs keep their pile until this ships **and** they press the button (or delete by hand). ## Too many toasts (`339a1d70`) Inventory of all 14 toast sites: almost all are rare, explicit-tap feedback. Two were routine-volume offenders: - `startStream` toasted "Starting stream — host" on **every successful launch** — the overwhelming majority of all toasts the plugin ever shows, repeating the button just pressed and landing on top of the starting stream. Gone; launch *failures* still toast. - `useHosts.refresh()` toasted "Couldn't list hosts" from its catch, and the panel remounts (and refreshes) on **every QAM open** — a broken backend nagged on each open. Now a third inline `problem` row next to the Refresh button that retries it, like the client-unavailable/outdated states. ## Verification `tsc --noEmit` and the rollup bundle pass (also re-run after merging main). The launch paths hit the fast path unchanged — a live overview answers the first query and nothing waits. Not yet exercised on a physical Deck; a boot-loop test there (deploy + two Steam restarts, count library entries) is the definitive check before tagging.
enricobuehler added 3 commits 2026-08-12 20:25:21 +00:00
Field report: each Steam start added another visible "Punktfunk" entry
(spotted in the desktop client, where the pile is plain to see).

Mechanism: db063792 made shortcutStillExists() actually answer for the
first time — and its callers treat a null overview as "the user deleted
the shortcut" and AddShortcut a replacement. But the plugin mounts while
Steam is still starting up, BEFORE appStore has registered its overviews,
so the remembered (perfectly live) appId looks up as null on every boot:
mint a duplicate, remember the new id, orphan yesterday's. One new entry
per load, forever.

The deleted verdict now has to be earned, and creation is a last resort:

  * shortcutStillExists() only believes "absent" once the store is
    demonstrably hydrated: wait out App.WaitForServicesInitialized (raced
    against the poll budget so a wedged signal can't hang the guard),
    poll until allApps is non-empty, then one grace recheck — overview
    registration can trail the bulk hydration. Unverifiable within budget
    answers true: a false "alive" merely no-ops until the next ask, a
    false "dead" duplicates forever.
  * On a genuinely lost id, both ensure paths first ADOPT an existing
    same-named shortcut (excluding the other role's) instead of minting
    an N+1th — which also heals installs the old builds already littered.
  * Both ensures are single-flight: mount's fire-and-forget can now be
    mid-wait when a QAM press arrives, and two ensures racing past the
    liveness check would each AddShortcut.
  * "Recreate library shortcut" additionally sweeps surplus "Punktfunk"
    shortcuts (RemoveShortcut) and toasts the count — cleanup for piles
    already minted. Deliberately button-only, never mount: automatic
    library deletion at boot is a bigger hazard than the mess.

Verified: tsc --noEmit and the rollup bundle both pass; the launch paths
(launchStream / launchGamepadUi) hit the fast path unchanged — a live
overview answers the first query and nothing waits.
Field complaint: the plugin toasts too much. Inventory of all 14 toast
sites says almost all are rare, explicit-tap feedback (pairing, update
buttons, recovery actions) — but two were routine-volume offenders:

  * startStream toasted "Starting stream — <host>" on EVERY successful
    launch, i.e. the overwhelming majority of all toasts the plugin ever
    shows. It repeats the button the user just pressed, and lands ON TOP
    of the starting stream after the QAM closes. Gone; launch FAILURES
    still toast (the QAM may already be closed, so inline state would go
    unseen).
  * useHosts.refresh() toasted "Couldn't list hosts" from its catch —
    and the panel remounts (and refreshes) on every QAM open, so a broken
    backend nagged on each open. It's now a third inline `problem` row
    ("Couldn't scan for hosts"), sitting next to the Refresh button that
    retries it, like the client-unavailable/client-outdated states
    already did.

The update-flow, pairing, trust and recovery toasts stay: each is a rare,
single, information-carrying response to an explicit tap (or, for the
request-access hint, the only warning that the connect is about to park).

Verified: tsc --noEmit and the rollup bundle pass.
Merge branch 'main' into worktree-decky-shortcut-dup
ci / rust (pull_request) Successful in 6m55s
ci / web (pull_request) Successful in 1m10s
ci / docs-site (pull_request) Successful in 1m21s
ci / bun-nix (pull_request) Successful in 34s
ci / rust-arm64 (pull_request) Successful in 1m30s
72189b29ec
enricobuehler merged commit 64e2af17c5 into main 2026-08-12 20:26:00 +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#184