Repository navigation
fix: fill the gallery in as thumbnails arrive - #135
Merged
Merged
Conversation
The shimmering placeholder grid had one way out: a detached thumbnail run delivering its results. A run that never delivered left the window shimmering with no message, no log line and nothing to click. A watchdog now bounds the wait at 30 seconds. Whatever the run does, the gallery leaves the skeleton, and a run that produced no images at all raises a banner offering another attempt. GalleryLoading holds the rules as pure, testable code.
The run delivered nothing until the whole library was downsampled, so the skeleton had one exit and a wait long enough to cover the slowest library. Thumbnails now reach their cards one at a time, and the wait is an idle one that restarts on every arrival: ten seconds of nothing at all, rather than a bet on total render time. A run that goes quiet has its stream closed, which cancels the renderer, so a retry no longer stacks a second full pass on the first. Images the app cannot read count as movement and keep their placeholder cards, and the banner that offers a retry can be dismissed.
A skipped job now names the preset it settles, so the gallery knows which cards have had their turn. A card still waiting shimmers in place, and only a card whose job reported without an image draws the missing image glyph and tells VoiceOver it has no preview. Also records the gallery's thumbnail run in CLAUDE.md.
rvanbaalen
pushed a commit
that referenced
this pull request
Sep 13, 2026
🤖 I have created a release *beep* *boop* --- ## [1.9.0](v1.8.0...v1.9.0) (2026-09-13) ### Features * crossfade day and night photos in the Light & Dark hero ([#121](#121)) ([f785c1f](f785c1f)) * cycle the Dynamic hero through a day of photographs ([#124](#124)) ([33d0367](33d0367)) * import wallpapers saved by earlier versions ([#113](#113)) ([0894dd8](0894dd8)) * new app icon ([#99](#99)) ([a4ba884](a4ba884)) * rebuild Settings as a native macOS settings window ([#71](#71)) ([c6f1e2a](c6f1e2a)) ### Bug Fixes * close the blind spots in the user-facing copy guard ([#127](#127)) ([a4225bb](a4225bb)) * fill the gallery in as thumbnails arrive ([#135](#135)) ([a59a3db](a59a3db)) * focus and select the save dialog name without AppKit responder tricks ([#92](#92)) ([fc4a33b](fc4a33b)) * harden the update checker against HTTP errors, odd tags and prereleases ([#96](#96)) ([0db734d](0db734d)) * keep the renders other desktops still point at ([#136](#136)) ([6ee6e4b](6ee6e4b)) * make drag and drop work in the wizard and editor ([#77](#77)) ([b757c00](b757c00)) * pick a schedule start time from short hour and minute menus ([#130](#130)) ([a1944c3](a1944c3)) * request read-only access to user-selected files ([#104](#104)) ([ddaeb91](ddaeb91)), closes [#98](#98) * restore hover on the editor header and canvas HUD buttons ([#133](#133)) ([b85273c](b85273c)) * restore the New Wallpaper heading ([#128](#128)) ([75d03e9](75d03e9)) * show one image spread across the hero monitors ([#119](#119)) ([1d80dfd](1d80dfd)) * stop the skeleton shimmer leaking into the search field ([#68](#68)) ([9424209](9424209)) * tighten the onboarding copy and its wrapping ([#125](#125)) ([56e7dd9](56e7dd9)) * tint Phosphor glyphs by template rendering so icons draw ([#117](#117)) ([0a6e92b](0a6e92b)) * write dynamic HEIC files straight to disk and report count mismatches ([#91](#91)) ([6c230c2](6c230c2)) * write dynamic wallpapers to a new file per apply so macOS reloads them ([#105](#105)) ([864c0db](864c0db)) ### Performance Improvements * build gallery thumbnails with ImageIO downsampling ([#79](#79)) ([1c48c7f](1c48c7f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #132
What changed
Thumbnails reach their cards one at a time instead of all at the end.
renderThumbnailshands each finished image straight to the gallery, so the skeleton gives way the moment the first card has something to show, and a large library fills in as it goes rather than staying grey until the last image is done. A card that has not had its turn yet keeps shimmering in place, so an unfinished card never claims to have no preview.What the gallery watches is no longer total render time but movement. An idle wait of ten seconds restarts on every arrival, so a library of any size is safe as long as something keeps landing, and a run that delivers nothing at all for ten seconds is given up on. A run given up on has its event stream closed, which cancels the renderer, and
renderThumbnailschecks for cancellation between jobs. Every colour scheme change, preset change and retry therefore stops the run it replaces instead of racing it.An image the app cannot read is reported as a skipped job naming its preset, rather than dropped in silence. Skips count as movement, so a library whose source folder has moved or been evicted finishes normally and keeps its cards, each drawing the framed glyph
GalleryCardViewalready had for a missing thumbnail. Only a run that stops moving raises the banner, which readsPreviews stopped loading. Your wallpapers are all still here.with aTry againbutton and a dismiss control beside it. The grid stays underneath either way, so the library is still searchable, applicable and editable, and an empty library keeps its own separate empty state.Trouble reaches
os.Loggerunder thegallerycategory, followingWallpaperManager: unreadable images are an expected condition and log at info, a stalled run logs at error. Nothing technical reaches the user.Whatever the render does, the gallery leaves the skeleton. A main actor that stops running work altogether stops the watchdog with it, and that remains out of reach from inside the app.
Where the rules live
SpreadPaper/Views/GalleryLoading.swiftholds the renderer, the run outcomes, the idle wait, the per-card pending rule andThumbnailRun.consume, which drives one run and decides when it has stopped. The view builds the jobs, settles each card under its run id, and reads the phase off the outcome.CLAUDE.mdgains the file.The idle watchdog is covered directly rather than through a table of hand-written outcomes: a stream that never delivers and one that stops part way both have to return a stalled outcome under a time limit, a stream fed slower than one idle wait has to come back delivered, and a watchdog that fires last against a run which already reported every job has to come back delivered too. Cancellation is covered against the renderer itself.