web: waveform thumbnails show instantly and stop flashing - #30
Merged
Conversation
Switching between collection and playlist views (or resizing) settles the waveform column at a width that can differ from the render-time estimate, and updateThumbnailSrcs then rewrites every thumbnail src at the new size. Setting img.src directly blanks the element while an uncached resource loads, exposing the dark cell background - most visible on recently-added tracks, whose PNG is not disk-cached at the new size yet and needs a live server render. Thumbnails showed, went black, then showed again. updateThumbnailSrcs now preloads the new URL through a detached Image and swaps only once the bytes are in, so the old frame stays up throughout; same-URL calls no-op. The placeholder self-heal retry in thumbLoaded keeps its direct src set on purpose: its retry chain rides the onload cycle and the cell is showing the 1x1 placeholder anyway. Also found on the way: the server wrote the rendered PNG to its disk cache with a plain WriteFile while concurrent requests read the same path, so a reader could get a truncated PNG - and the 7-day Cache-Control would pin the corrupt image in the browser. The cache write is now write-to-temp + rename. Verified in a headless browser against the extracted swapImgSrc with a deliberately slow image server: a direct src set observably blanks the img during load, the preload swap never does, and same-URL calls no-op. The live view-switch repro was not run (the release-checklist instance holds the ports); worth one eyeball after merging.
The placeholder self-heal loads a new track's first real waveform via a cache-busted URL, and the natural URL was last fetched while it still returned the no-store placeholder - so the browser has nothing cached under the URL every future table render will use. Switching to a playlist and back rebuilt the row cache-cold, and the freshly-analyzed track sat blank again waiting on a full round-trip while older tracks (cached from prior sessions) appeared instantly. After a successful heal, thumbLoaded now swaps the img back to the natural URL through the preload path: one cheap request (the server just disk-cached that exact PNG) that primes the browser cache and keeps the old frame up during the swap. Re-renders after that are instant. No retry-loop risk: the swapped load re-enters thumbLoaded with a real width and an unbusted URL and takes no action. Verified in a browser harness against the extracted functions: a healed img swaps to the natural URL with no blank frame observed and exactly one follow-up fetch.
The placeholder self-heal retried for a fixed ~6 minute budget and then gave up silently. With analysis behind a long queue - a bulk add, or the one-time cacheVersion re-analysis pass - the budget exhausted before the track's turn came, and the thumbnail then NEVER appeared, even after analysis completed, until a page refresh built a fresh img. A failed fetch (server restart mid-request) killed the chain the same way: no onload, nothing rescheduled. The retry now backs off instead of giving up: quick retries for the first minute (a normal analysis wins that race), then ~30-45s for up to roughly two hours, which covers any realistic queue while still ending eventually for permanently-failed analysis. The retried placeholder responses are no-store and cost the server almost nothing. An onerror handler reschedules through the same path, so a restart cannot strand pending thumbnails. The heal-swap bust check also tightened to a [?&]_= match. Verified in the accelerated browser harness against the extracted functions: a track whose analysis completes past the old budget now heals on completion (the old code was reproduced giving up at 60 tries and staying blank forever), and an img pointed at a dead server keeps its retry chain alive through repeated fetch errors.
Root cause of new tracks' waveforms not appearing, found by driving the real app: the browser's per-document image cache satisfies a re-created <img> with the bytes it already has for that URL - the placeholder - no-store notwithstanding. During a batch analysis the rev changes every poll tick and the table fully rebuilds each time, so every rebuilt row resurrected the placeholder, and neither the retry chain nor an explicit post-analysis kick could win the race against the next rebuild (measured: 27s and 21s of lag after analysis completed; the loser saw the waveform only when the batch finished and the churn stopped). waveThumbSrc now appends &a=1 once the track has analysis (memoized per allTracks snapshot), so the URL itself changes the moment the poll learns the BPM landed: the rebuilt row can no longer collide with the cached placeholder, fetches fresh, and every subsequent rebuild reuses the real bytes cached under the analyzed URL. Measured end to end against the real app: waveform visible in the same poll tick as the BPM (0s heal latency, from 27s), all rows intact across view switches. The self-heal retry chain stays as the fallback for lazy-analysis mode and fetch errors.
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.
What & why
Four commits chasing one user-visible bug to its root: newly-added tracks' waveform thumbnails flashed black on view switches, and under a busy analysis queue never appeared at all until a page refresh. Found and fixed during the v0.3.0 release-test pass.
The root cause (last commit, and the one that actually matters): the browser's per-document image cache satisfies a re-created img with the bytes it already holds for that URL - the pre-analysis placeholder - no-store notwithstanding. During a batch analysis the library rev changes every poll tick and the table fully rebuilds each time, so every rebuilt row resurrected the placeholder; nothing racing the rebuilds could win. Measured against the real app: the waveform appeared 27 seconds after analysis completed (whenever the retry cadence happened to land after the churn stopped), and an explicit post-analysis kick only got that to 21 seconds because the next rebuild destroyed it. waveThumbSrc now appends a marker (&a=1) once the track has analysis, memoized per tracks snapshot, so the URL itself changes the moment the poll sees the BPM land - a rebuilt row can no longer collide with the cached placeholder. Measured after the fix: waveform visible in the same poll tick as the BPM, zero heal latency, all rows intact across view switches.
The three earlier commits fix real adjacent bugs the investigation surfaced, each verified in a browser harness against the extracted shipped functions:
Verified end to end by driving the real binary with a synthesized 12-track batch in a headless browser, measuring the time from BPM landing on the row to the waveform rendering: 27s before, 0s after, with view switches and a mid-batch tab-away/tab-back leaving every row intact.
Hardware testing
Checklist
go build ./...,go vet ./..., andgo test ./...passgofmt -l .is cleanGPL-3.0-or-later) (n/a, no new files)