Skip to content

Prefetch the loading hero art on card focus#74

Merged
aarikmudgal merged 1 commit into
owenselles:mainfrom
oliversluke:feature/prefetch-loading-art
Jul 11, 2026
Merged

Prefetch the loading hero art on card focus#74
aarikmudgal merged 1 commit into
owenselles:mainfrom
oliversluke:feature/prefetch-loading-art

Conversation

@oliversluke

Copy link
Copy Markdown
Contributor

Problem

Pressing Play showed a black screen behind the loading title and progress bar for a moment before the game art appeared (raised in #68). The loading screen's full-bleed background (StreamView) loads a ~1920px hero (heroImageUrl ?? heroBannerUrl) — a different, larger image than the 272px box art the grids cache — so it was always fetched fresh the instant Play was pressed, sitting on Color.black until it arrived. The title and loading bar render immediately; only the art behind them lagged.

This is pre-existing and unrelated to catalog caching (that caches metadata, not images).

Fix

HeroArtPrefetcher warms URLCache.shared with that exact hero image when a game card gains focus, so by the time the user presses Play it is already cached and the loading background renders instantly.

  • Only the focused game's art is fetched — not the whole catalog — so there is no thundering-herd of large-image downloads.
  • Requests are de-duplicated per session; failed fetches are removed from the set so they can retry.
  • Applied via a small .prefetchHeroArtOnFocus(_:) modifier on the Library, Store, and Home row cards (GameCardLabel/StoreCardLabel) and the Home hero Play button, reading @Environment(\.isFocused) inside the focusable card content.

No new dependencies; URLCache.shared is already configured in CloudNowApp (50 MB memory / 200 MB disk) and shared with AsyncImage's URLSession.shared.

Testing

Simulator build, SwiftFormat, and SwiftLint (strict) pass. On device the loading art now appears immediately for any game the user has focused before pressing Play. Best-effort: if the CDN response is uncacheable or focus-to-play is faster than the fetch, it falls back to the previous behavior (no regression).

🤖 Generated with Claude Code

@aarikmudgal aarikmudgal self-requested a review July 11, 2026 08:44
@aarikmudgal aarikmudgal added the enhancement New feature or request label Jul 11, 2026
@aarikmudgal

Copy link
Copy Markdown
Collaborator

@oliversluke With this PR testing, I noticed, that the main Home Screen loads very late, approximately 10sec late, when it loads all thumbnails are there but still with your previous PR that is merged the home was instantly loaded from cache.

Although Hero teaser does load very fast in this PR and the game starts quickly

@oliversluke

Copy link
Copy Markdown
Contributor Author

Let me look into this. Don’t merge this PR until this is fixed.

@oliversluke oliversluke marked this pull request as draft July 11, 2026 09:47
@aarikmudgal

Copy link
Copy Markdown
Collaborator

And @oliversluke the UI bug of stream zooming in with standard diagnostics is also back

@oliversluke

Copy link
Copy Markdown
Contributor Author

I will look into this shortly.

Pressing Play showed a black screen behind the loading title and bar
until the full-bleed hero downloaded. StreamView's loading background
loads a ~1920px hero (heroImageUrl ?? heroBannerUrl) — a different,
larger image than the 272px box art the grids cache — so it was always
fetched fresh on tap.

HeroArtPrefetcher warms URLCache with that exact image when a game card
gains focus, so by the time the user presses Play it is already cached
and the loading screen renders instantly. Only the focused game's art
is fetched (not the whole catalog), requests are de-duplicated, and
failures are allowed to retry. Applied to the Library/Store/Home cards
and the Home hero Play button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oliversluke oliversluke force-pushed the feature/prefetch-loading-art branch from b0c33ed to fa34be7 Compare July 11, 2026 10:35
@oliversluke

Copy link
Copy Markdown
Contributor Author

@aarikmudgal Thanks — that's exactly what was happening, and it's not a regression from this PR. This branch was cut from main before #68 (the instant-cache-from-disk startup work) merged this morning, so testing it in isolation meant testing without the catalog cache — hence the ~10s cold Home load. The prefetch code here never touched the caching path.

I've rebased this branch onto current main, so it now includes #68's cache and the hero-art prefetch. Home should load instantly from cache again, and the loading art still appears immediately on Play. Mind pulling the updated branch and giving it another run?

@aarikmudgal aarikmudgal marked this pull request as ready for review July 11, 2026 10:40
@oliversluke

Copy link
Copy Markdown
Contributor Author

@aarikmudgal Confirmed this one is not from this PR — the video/stream/HUD files are byte-identical between this branch and main (this PR only touches the game-card views and adds HeroArtPrefetcher). The zoom shipped with the statistics HUD in #62; you're just seeing it while testing here. So it shouldn't block this PR.

Root cause: the video is a UIViewControllerRepresentable, which (unlike a plain view) can expand to the union of its ZStack siblings' content. The Standard HUD with Diagnostics enabled is tall enough to grow the ZStack, and videoGravity = .resizeAspectFill then zooms the video to fill the taller bounds. I'll fix it separately by moving the HUD/menu/warning into an .overlay of the video, which can never affect the video's size.

@aarikmudgal

Copy link
Copy Markdown
Collaborator

But I definitely tested a fixed version of it before merging the previous PRs :( I remember I mentioned this same bug earlier and you applied the fix in the PR, I wonder if we reverted back some of the changes in other PR?

@oliversluke

Copy link
Copy Markdown
Contributor Author

@aarikmudgal Good memory — and you're half right. I checked the history: StreamView.swift hasn't been touched since #62 squash-merged, so nothing was reverted, and the fix you tested back then (the "padding inside the flexible frame" change) is still there.

The catch is that fix was incomplete. It stops the HUD's padding from growing the layout, but not a tall HUD's content height — and the real root cause is that the video is a UIViewControllerRepresentable that expands to the union of its ZStack siblings, which a sibling's own frame can't prevent. So it looked fixed when you tested a shorter HUD (Standard alone), and the zoom comes back once it's tall enough (Standard + Diagnostics).

Proper fix is in #76: move the HUD/menu/warning into an .overlay of the video, which can never grow it. Separate from this PR.

@aarikmudgal aarikmudgal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works

@aarikmudgal

aarikmudgal commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

@oliversluke Looks good, tested as well, small question, the hero banner is loaded in background as soon as the focus is move to the tile at least once, correct? there is no delay assigned to the focus time?

@aarikmudgal aarikmudgal merged commit 4abe7d3 into owenselles:main Jul 11, 2026
1 check passed
@oliversluke

Copy link
Copy Markdown
Contributor Author

But I definitely tested a fixed version of it before merging the previous PRs :( I remember I mentioned this same bug earlier and you applied the fix in the PR, I wonder if we reverted back some of the changes in other PR?

Nothing was reverted — I checked, both StreamView.swift and StatsHUDView.swift are untouched since #62 merged. Here's my theory for why it looks like a regression:

The HUD's height depends on the Diagnostics toggle (Settings → Diagnostics), which is separate from the Statistics level. With Diagnostics off, "Standard" is ~18 rows and fits on screen with room to spare, so it never overflowed and never zoomed — that's most likely the config you tested back then. Turning Diagnostics on appends the whole Debug section (~15–25 extra lines, several of them wrapping sentences like the decoded-video and color-diagnostic status), which roughly doubles the panel height and pushes it past the screen. That over-screen height is exactly the case the earlier padding-only fix never covered.

Could you confirm: when you saw the zoom, was Diagnostics enabled? And does "Standard" with Diagnostics off zoom too, or only with it on? That pins down the trigger.

Either way, #76 stops the zoom regardless (the HUD can no longer resize the video). If Diagnostics-on is the trigger, I'll follow up with a layout tweak so the extra rows don't run off the bottom of the screen.

@oliversluke oliversluke deleted the feature/prefetch-loading-art branch July 11, 2026 11:04
@oliversluke

Copy link
Copy Markdown
Contributor Author

the hero banner is loaded in background as soon as the focus is move to the tile at least once, correct? there is no delay assigned to the focus time?

Yes, correct on both counts. Each card fires the prefetch the moment its isFocused flips to true (onChange(of: isFocused)), and there's no delay or debounce — it kicks off immediately on focus.

Two details that keep it cheap: it's deduplicated via the requested set, so a given hero URL is fetched at most once per app session no matter how often you focus that tile (only a failed fetch is cleared so it can retry), and it's a low-priority background URLSession task just warming URLCache — no UI or main-thread work.

The one trade-off of having no delay is that fast-scrolling a row momentarily focuses each tile and fires a prefetch for every one it passes through. In practice that's bounded (each URL only once, and it's only cache-warming), but if you'd rather not prefetch tiles the focus just skims over, I can add a small focus-dwell debounce as a follow-up — only prefetch once a tile has stayed focused for ~200 ms. Happy to do that if you think it's worth it.

@aarikmudgal aarikmudgal self-assigned this Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants