Skip to content

feat(AppShell): show cached cover art for local Play tab recents - #2005

Merged
alexwarren merged 1 commit into
mainfrom
feat/appshell-local-cover-art
Aug 8, 2026
Merged

feat(AppShell): show cached cover art for local Play tab recents#2005
alexwarren merged 1 commit into
mainfrom
feat/appshell-local-cover-art

Conversation

@alexwarren

Copy link
Copy Markdown
Contributor

Summary

  • Local "Recently Played" entries (Electron only) now show their actual game cover art instead of a generic icon, resolved via a new engine-backed query (WasmEditorBridge.ResolveLocalCover, mirroring PlayerCore.GameQuery) that handles both embedded-resource covers (.quest package, legacy .asl/.cas) and sibling-file covers (a plain unpacked .aslx).
  • The result is cached on the RecentGame record (coverDataUrl, persisted to recent-played.json) — resolved once at play time (fire-and-forget, off the critical path of opening the player window) or lazily as a one-time self-heal for a legacy/never-resolved entry — since resolving a cover means booting the full engine just to read one field, too expensive to redo on every app startup.
  • Also capitalizes the "Recently Played" heading to match the site's other section titles.

Bug fixes found while testing this live

  • wasm.ts's loadWasm() self-race: multiple local recents resolving their covers concurrently on mount could each see the WASM bridge not yet loaded and call dotnet.create() a second time ("Runtime module already loaded"), permanently breaking the editor for the rest of that session. Fixed by caching the in-flight load promise, not just its eventual result.
  • recent-games.ts file corruption: the read-modify-write cycle behind add/setCover/remove/clear had no synchronization across concurrent calls, so two cards self-healing their covers at once could have their fs.writeFile calls genuinely interleave at the OS level and corrupt recent-played.json outright (a short write's bytes followed by a stray tail fragment of a longer concurrent one) — silently wiping the entire Recently Played list on next read (JSON.parse throws, caught, falls back to []). Fixed with a per-kind write queue plus atomic temp-file+rename.

Test plan

  • dotnet build --configuration Release (full solution) and dotnet test --configuration Release — all 336 tests pass
  • npx svelte-check / npm run lint / tsc -p tsconfig.json (ElectronApp) — all clean
  • Standalone Node harness exercising recent-games.ts directly: carry-forward on replay, in-place cover patch without reordering/timestamp bump, explicit overwrite, no-op on a removed entry, and the exact concurrent-write race that previously corrupted the file (now produces valid JSON with both entries intact regardless of timing)
  • Live browser probes against the built WasmEditorBridge/loadWasm confirming concurrent ResolveLocalCover calls resolve correctly with no cross-contamination, and that loadWasm() no longer throws when called concurrently
  • Manually verified against real local .quest files (a published package with an embedded cover, and one with no cover set) in a running Electron build

🤖 Generated with Claude Code

Local Recently Played entries (Electron only) now show their actual
cover art instead of a generic icon, resolved via a new engine-backed
query (WasmEditorBridge.ResolveLocalCover, mirroring PlayerCore's
GameQuery) that handles both embedded-resource covers (.quest package,
legacy .asl/.cas) and sibling-file covers (a plain unpacked .aslx).

Resolving a cover means booting the full engine just to read one
field, which is too expensive to redo on every app startup. The result
is cached on the RecentGame record itself (coverDataUrl, persisted to
recent-played.json), resolved once at play time (fire-and-forget, off
the critical path of opening the player window) or lazily as a
one-time self-heal for a legacy/never-resolved entry.

Also fixes two bugs found while testing this feature live:
- wasm.ts's loadWasm() raced itself when multiple local recents
  resolved their covers concurrently on mount, each seeing the WASM
  bridge not yet loaded and calling dotnet.create() a second time
  ("Runtime module already loaded"), permanently breaking the editor
  for the rest of that session. Now caches the in-flight load promise,
  not just its result.
- recent-games.ts's read-modify-write cycle had no synchronization
  across concurrent calls, so two cards self-healing their covers at
  once could have their fs.writeFile calls genuinely interleave at the
  OS level and corrupt recent-played.json outright (a short write's
  bytes followed by a stray tail fragment of a longer concurrent one)
  — silently wiping the entire Recently Played list on next read.
  Fixed with a per-kind write queue plus atomic temp-file+rename.

Also capitalizes the "Recently Played" heading to match the site's
other section titles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@alexwarren
alexwarren merged commit 91e1497 into main Aug 8, 2026
7 checks passed
@alexwarren
alexwarren deleted the feat/appshell-local-cover-art branch August 8, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant