Skip to content

fix: fill the gallery in as thumbnails arrive - #135

Merged
rvanbaalen merged 3 commits into
mainfrom
fix/issue-132
Sep 13, 2026
Merged

rvanbaalen merged 3 commits into
mainfrom
fix/issue-132

Conversation

@rvanbaalen

@rvanbaalen rvanbaalen commented Sep 13, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #132

What changed

Thumbnails reach their cards one at a time instead of all at the end. renderThumbnails hands 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 renderThumbnails checks 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 GalleryCardView already had for a missing thumbnail. Only a run that stops moving raises the banner, which reads Previews stopped loading. Your wallpapers are all still here. with a Try again button 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.Logger under the gallery category, following WallpaperManager: 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.swift holds the renderer, the run outcomes, the idle wait, the per-card pending rule and ThumbnailRun.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.md gains 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.

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.
@rvanbaalen rvanbaalen changed the title fix: end the gallery's loading state on every path fix: fill the gallery in as thumbnails arrive Sep 13, 2026
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
rvanbaalen merged commit a59a3db into main Sep 13, 2026
@rvanbaalen
rvanbaalen deleted the fix/issue-132 branch September 13, 2026 20:40
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>
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.

[Bug]: the gallery can shimmer forever with no timeout, error or retry

1 participant