Skip to content

installed-apps-sync: Steam client_icon never preferred, and empty service slice bypasses the store fallback #166

Description

@divya0795

Two independent defects in web-panel/bridge/scripts/default/installed-apps-sync/installed-data.js, both affecting the My Games snapshot. Filed together because they live in the same file. Observed on master @ 643c8f8 (1.0.9.4).


1. The Steam client_icon CDN is never preferred

AGENTS.md:20 states the rule:

installed-apps-sync.js must prefer Wand's own client icon CDN shape https://api-cdn.wemod.com/steam_community/<steamAppId>/client_icon/96.webp whenever the matched title/game/version metadata contains a Steam AppID, regardless of install platform. […] If metadata still does not expose the icon, fall back to the rendered Wand sidebar DOM.

The code does the opposite. pickImageUrl returns the first non-null candidate in argument order (artwork.js:21-30), and in pickImageUrlForTitle the client icon sits at argument 7:

return pickImageUrl(
  title?.imageUrl,        // 1  ← wins whenever present
  title?.iconUrl,         // 2
  title?.coverUrl,        // 3
  title?.thumbnailUrl,    // 4
  title?.logoUrl,         // 5
  title?.headerImageUrl,  // 6
  getSteamClientIconUrl(  // 7  ← only reached if 1-6 are all empty
    findSteamAppId(...steamRoots),
    getInstalledAppSteamAppId(preferredApp.platform, preferredApp.sku)
  ),
  sidebarClientIconUrl,   // 8  ← ahead of game.* metadata
  ...
  game.imageUrl,          // 11
  ...
)

Trigger: any Steam-installed game whose catalog title carries a flat image field. The snapshot's imageUrl becomes title.imageUrl and the client icon is never selected, even though the AppID is available.

Secondary: sidebarClientIconUrl is at argument 8, ahead of game.imageUrl and friends. AGENTS.md places the scraped sidebar DOM last, but today it overrides the game's own metadata.

Suggested fix: move the getSteamClientIconUrl(...) argument to the front — it returns null when no AppID resolves, so non-Steam titles fall through unchanged — and move sidebarClientIconUrl to last.


2. An empty-but-present service slice bypasses the store fallback

// :25
if (isRecord(state.installedAppsService?.installedApps)) {
  return { rawInstalledApps: state.installedAppsService.installedApps, ..., source: "service" }
}

// :48 — never reached when the service slice is {}
if (isRecord(storeState?.installedApps)) { ... source: "store" }

isRecord({}) is true. When the service instance exists but installedApps is still {} — its state before refreshApps populates it — the service branch is taken with rawInstalledApps = {}, and the store branch is skipped. Unlike catalog and installedGameVersions, rawInstalledApps has no fallback inside that branch.

Trigger: store hydrated from cache with a populated installedApps slice while the freshly-resolved service is still empty → My Games builds from an empty map and renders empty.

Impact: transient — the refreshApps hook / 15s poll heals it — so the symptom is an empty My Games on open rather than a permanent failure. Note the existing warn text at buildSnapshot ("Service resolved but installedApps is empty/undefined. Store fallback also unavailable.") already anticipates this situation.

Suggested fix: require a non-empty record for the service branch: isRecord(x) && Object.keys(x).length > 0.


Verification

Both reproduce as unit tests driven through the public buildSnapshot / resolveInstalledData contracts (no internals exported), and both fail against unfixed master:

× prefers the Steam client icon over the title's own artwork fields
  Expected: "https://api-cdn.wemod.com/steam_community/1245620/client_icon/96.webp"
  Received: "https://cdn.example/subnautica-cover.webp"

× reads installed apps from the store while the service slice is still empty
  Expected: "store"
  Received: "service"

A fix is attached; it takes the suite from 34 to 37 tests, with pnpm lint --max-warnings=0 and pnpm typecheck clean.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions