Group 2 of the 2026-06-02 multi-system drift audit. Extracts gallery.js's hand-rolled inflightId pattern into a new useInflightToken composable; 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. removeTag rolled back the chip rail unconditionally even when only the secondary dismiss POST had failed — UI lied until refresh. And all tag-mutation URLs interpolated currentImageId.value AFTER an await, so a fast prev/next could route DELETE/POST to the wrong image. Fixed with imageId-at-click-time capture in reloadTags / removeTag / addExistingTag / createAndAdd, plus a split try/catch so 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() dereferenced currentImageId after the awaited POST /api/tags, so the subsequent /suggestions/accept URL 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 on load().
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/images
downloads.js — filter changes cancel in-flight loadFirst/loadMore so a stale filter's response can't overwrite the new view
artistDirectory.js + tagDirectory.js — typing "alice" then "alice bob" no longer drops the second fetch (was: if (loading) return early-exited the second call because loading was still true from the first)
gallery.js — refactored from its hand-rolled inflightId to the shared composable so the pattern stays consistent
Test Plan
Open an image with suggestions, click Accept on a suggestion, immediately press Right Arrow — confirm the accepted tag landed on the first image, not the second
Open an image with several tags, click a chip's × to remove, immediately press Right Arrow — confirm the chip was removed from the first image
Browse to an artist with many images, immediately click another artist — confirm the second artist's content replaces, no flash of the first
In the artist/tag directory search input, type quickly: "al" → "alice" → "alice bob" — confirm the final query's results render, not an earlier fetch
In Downloads, change the status filter rapidly between "error" and "ok" — confirm the displayed events match the latest filter, not whichever request resolved last
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.
## Summary
Group 2 of the 2026-06-02 multi-system drift audit. Extracts `gallery.js`'s hand-rolled `inflightId` pattern into a new `useInflightToken` composable; 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. `removeTag` rolled back the chip rail unconditionally even when only the secondary `dismiss` POST had failed — UI lied until refresh. And all tag-mutation URLs interpolated `currentImageId.value` AFTER an await, so a fast prev/next could route DELETE/POST to the wrong image. Fixed with imageId-at-click-time capture in `reloadTags` / `removeTag` / `addExistingTag` / `createAndAdd`, plus a split `try/catch` so 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()` dereferenced `currentImageId` after the awaited `POST /api/tags`, so the subsequent `/suggestions/accept` URL 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 on `load()`.
**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/images
- `downloads.js` — filter changes cancel in-flight loadFirst/loadMore so a stale filter's response can't overwrite the new view
- `artistDirectory.js` + `tagDirectory.js` — typing "alice" then "alice bob" no longer drops the second fetch (was: `if (loading) return` early-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-flight
- `gallery.js` — refactored from its hand-rolled `inflightId` to the shared composable so the pattern stays consistent
## Test Plan
- [ ] Open an image with suggestions, click Accept on a suggestion, immediately press Right Arrow — confirm the accepted tag landed on the **first** image, not the second
- [ ] Open an image with several tags, click a chip's × to remove, immediately press Right Arrow — confirm the chip was removed from the **first** image
- [ ] Browse to an artist with many images, immediately click another artist — confirm the second artist's content replaces, no flash of the first
- [ ] In the artist/tag directory search input, type quickly: "al" → "alice" → "alice bob" — confirm the final query's results render, not an earlier fetch
- [ ] In Downloads, change the status filter rapidly between "error" and "ok" — confirm the displayed events match the **latest** filter, not whichever request resolved last
## 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](https://claude.com/claude-code)
Extracts gallery.js's hand-rolled inflightId pattern into a new
useInflightToken composable; adopts in every store that previously
had no guard against late-response overwrites or wrong-image URL
interpolation.
Two operator-impacting bugs the audit (workflow wf_bbe3fdb1-e62)
flagged:
- modal.removeTag rolled back the chip rail unconditionally even
when only the secondary dismiss POST had failed — UI lied until
refresh. And all tag-mutation URLs interpolated currentImageId
AFTER an await, so a fast prev/next could route DELETE/POST to
the wrong image. Both fixed: split try/catch (dismiss failure
surfaces a warning, doesn't roll back the delete); imageId
captured at call-time and used in URLs throughout.
- suggestions.accept dereferenced currentImageId after the awaited
POST /api/tags, so the subsequent /suggestions/accept could
apply A's chosen tag to image B AND push it to B's allowlist.
Fixed by capturing imageId at click-time + inflight guard on
load().
Same shape across artist / downloads / artistDirectory /
tagDirectory / posts stores: rapid filter/nav changes used to
interleave responses (last-writer-wins). Now the late response is
discarded and the most-recent request wins. Filter-change-during-
search no longer drops the second fetch because the loading flag
was still true from the first.
gallery.js's inflightId removed in favor of the shared composable
so the pattern stays consistent.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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