feat(engine): theme-store preview thumbnails (Phase H part 2) #179

Merged
funman300 merged 2 commits from feat/theme-store-previews into master 2026-07-14 00:46:46 +00:00
Owner

Second Phase H bundle: the store modal now renders the preview PNGs the server has been advertising all along (preview_url is live on klondike.aleshym.co — the client just never fetched it; rows were text-only).

How it works

  • On catalog load, one preview fetch spawns per entry with a preview_url not already cached — same AsyncComputeTaskPool + Tokio pattern as the catalog fetch and the avatar image (§2.4: nothing blocks the main thread).
  • Finished PNGs decode via the image crate → Image asset → ImageNode thumbnail at the head of the row, sized by the theme's own card_aspect (72 px tall, width follows the ratio — never stretched).
  • Thumbnails pop in as they arrive (modal rebuilds on arrival, same despawn+respawn idiom as every other state change) and are cached per session, so reopening the store renders instantly.
  • Failures are decorative-grade: log + text-only row. The decode path is skipped under MinimalPlugins (no Assets<Image>), keeping the §4.3 headless-test contract.
  • solitaire_data: new ThemeStoreClient::fetch_preview with a 512 KB cap; previews carry no checksum (unlike archives) since they're never written to disk or imported.

Tests

2 new (cached entry renders exactly one thumbnail, uncached stays text-only; card-aspect sizing). Full workspace + clippy green.

Remaining Phase H: hint ghost-motion preview (optional per the doc) and the "consider" toast history.

🤖 Generated with Claude Code

Second Phase H bundle: the store modal now renders the preview PNGs the server has been advertising all along (`preview_url` is live on klondike.aleshym.co — the client just never fetched it; rows were text-only). ## How it works - On catalog load, one preview fetch spawns per entry with a `preview_url` not already cached — same `AsyncComputeTaskPool` + Tokio pattern as the catalog fetch and the avatar image (§2.4: nothing blocks the main thread). - Finished PNGs decode via the `image` crate → `Image` asset → `ImageNode` thumbnail at the head of the row, sized by the theme's own `card_aspect` (72 px tall, width follows the ratio — never stretched). - Thumbnails pop in as they arrive (modal rebuilds on arrival, same despawn+respawn idiom as every other state change) and are cached per session, so reopening the store renders instantly. - Failures are decorative-grade: log + text-only row. The decode path is skipped under `MinimalPlugins` (no `Assets<Image>`), keeping the §4.3 headless-test contract. - `solitaire_data`: new `ThemeStoreClient::fetch_preview` with a 512 KB cap; previews carry no checksum (unlike archives) since they're never written to disk or imported. ## Tests 2 new (cached entry renders exactly one thumbnail, uncached stays text-only; card-aspect sizing). Full workspace + clippy green. Remaining Phase H: hint ghost-motion preview (optional per the doc) and the "consider" toast history. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
funman300 added 1 commit 2026-07-14 00:27:08 +00:00
feat(engine): theme-store preview thumbnails (Phase H part 2)
Test / fmt (pull_request) Successful in 4s
Test / test (pull_request) Successful in 4m36s
ae8118d7df
The live catalog has advertised preview_url per theme since the store
shipped, but the client never fetched it — rows were text-only. The
modal now downloads each advertised preview PNG off the main thread
(same AsyncComputeTaskPool + Tokio pattern as the catalog fetch and
avatar image), decodes it into an Image asset, and renders a thumbnail
sized by the theme's own card_aspect at the head of the row. Previews
pop in as they arrive and are cached for the session, so reopening the
store is instant. Failures log and leave the row text-only; the decode
path is skipped entirely under MinimalPlugins (no Assets<Image>).

solitaire_data grows ThemeStoreClient::fetch_preview with a 512 KB cap
(previews carry no checksum — decorative only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

Pushed a second commit onto this PR — it now covers all of Phase H part 2:

ddee605 — hint ghost-motion preview. A hint now spawns a translucent copy of the hinted card that glides to the suggested destination twice (0.7s/pass, SmoothSnap easing, tail fade), alongside the existing static highlights. Auto-disabled under reduce-motion (static highlights remain the whole story). The ghost despawns on timer, on a fresh hint, or the moment the board changes — a preview of a stale board is worse than none. 6 new tests (pure timing/alpha math + spawn/suppress/replace/stale-despawn wiring); workspace + clippy green.

With this, every Phase H item from the redesign doc is implemented except the "consider" toast history, which I propose dropping.

Pushed a second commit onto this PR — it now covers **all of Phase H part 2**: **ddee605 — hint ghost-motion preview.** A hint now spawns a translucent copy of the hinted card that glides to the suggested destination twice (0.7s/pass, SmoothSnap easing, tail fade), alongside the existing static highlights. Auto-disabled under reduce-motion (static highlights remain the whole story). The ghost despawns on timer, on a fresh hint, or the moment the board changes — a preview of a stale board is worse than none. 6 new tests (pure timing/alpha math + spawn/suppress/replace/stale-despawn wiring); workspace + clippy green. With this, every Phase H item from the redesign doc is implemented except the "consider" toast history, which I propose dropping.
funman300 added 1 commit 2026-07-14 00:41:22 +00:00
feat(engine): hint ghost-motion preview (Phase H)
Test / fmt (pull_request) Successful in 4s
Test / test (pull_request) Successful in 4m20s
ddee605874
A hint now spawns a translucent copy of the hinted card that glides to
the suggested destination twice (0.7s per pass, SmoothSnap easing, tail
fade so the loop reads as a repeat), alongside the existing static
source/destination highlights. Auto-disabled under reduce-motion — the
static highlights remain the whole story. The ghost despawns on timer,
on a fresh hint, or the moment the board changes (a preview of a stale
board is worse than none).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
funman300 merged commit 7669a1bb56 into master 2026-07-14 00:46:46 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/Ferrous-Solitaire#179