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.