audit-g2: async race / state-leak fixes across eight stores #50
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Group 2 of the 2026-06-02 multi-system drift audit. Extracts
gallery.js's hand-rolledinflightIdpattern into a newuseInflightTokencomposable; adopts it across every store that previously had no guard against late-response overwrites or wrong-image URL interpolation.Two operator-impacting bugs the audit flagged:
modal tag mutations routed to the wrong image.
removeTagrolled back the chip rail unconditionally even when only the secondarydismissPOST had failed — UI lied until refresh. And all tag-mutation URLs interpolatedcurrentImageId.valueAFTER an await, so a fast prev/next could route DELETE/POST to the wrong image. Fixed with imageId-at-click-time capture inreloadTags/removeTag/addExistingTag/createAndAdd, plus a splittry/catchso a dismiss failure surfaces a warning but doesn't roll back the successful delete.suggestions accept could apply A's chosen tag to image B.
accept()dereferencedcurrentImageIdafter the awaitedPOST /api/tags, so the subsequent/suggestions/acceptURL went to whatever image the user had navigated to mid-flight — and that tag got pushed to B's allowlist. Fixed with imageId capture + inflight guard onload().Same shape across the remaining stores — rapid filter / nav changes used to interleave responses (last-writer-wins, not request-order-wins):
artist.js— rapid artist-to-artist navigation no longer briefly renders the previous artist's overview/imagesdownloads.js— filter changes cancel in-flight loadFirst/loadMore so a stale filter's response can't overwrite the new viewartistDirectory.js+tagDirectory.js— typing "alice" then "alice bob" no longer drops the second fetch (was:if (loading) returnearly-exited the second call because loading was still true from the first)posts.js— loadInitial / loadMore / loadAround / loadOlder / loadNewer all guarded; filter changes cancel in-flightgallery.js— refactored from its hand-rolledinflightIdto the shared composable so the pattern stays consistentTest Plan
Audit reference
Closes Group 2 of plan task
Scribe #551. Group 3 (lifecycle batch: recovery sweeps + retention for BackupRun / LibraryAuditRun / ImportBatch, time_limits on long-runners) next.🤖 Generated with Claude Code