fix: refresh the v2 gallery when a ROM is edited or matched - #4153
fix: refresh the v2 gallery when a ROM is edited or matched#4153sdornan wants to merge 1 commit into
Conversation
|
Resolved: the broadcast has been split back out — this PR is frontend-only again. The work lives on Splitting because that change has a different blast radius: a backend Socket.IO event contract, a new Earlier notes on the broadcast designThe two design points, for whenever it gets reviewed:
|
9005923 to
e430f73
Compare
436cebe to
4f07558
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes stale v2 gallery cards after ROM mutations by introducing a v2-side sync composable that updates both the legacy v1 ROM store and the v2 gallery window cache, and selectively triggers a gallery refetch when a write can change membership or ordering.
Changes:
- Add
useRomSync(syncRom,applyRomWrite,refreshAfterUserStateChange) to fan out ROM updates across v1 and v2 caches and refresh the gallery when required. - Route v2 write paths (edit, match, user toggles, asset refreshes, bulk actions) through
useRomSyncinstead of updating only the v1 store. - Add unit tests for
useRomSyncbehavior around filtering, ordering, and favourites/status membership.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/v2/composables/useRomSync/index.ts | New composable to sync ROM mutations to both stores and refresh v2 gallery windows when needed. |
| frontend/src/v2/composables/useRomSync/index.test.ts | Unit tests covering sync vs refetch behavior for filters, sorting, and user-state changes. |
| frontend/src/v2/composables/useGameActions/index.ts | Switch optimistic user-state writes to syncRom and reconcile gallery membership post-write. |
| frontend/src/v2/composables/useGameActions/index.test.ts | Update test mocking for the new useRomSync usage in useGameActions. |
| frontend/src/v2/components/GameDetails/ScreenshotsSubtab.vue | Use syncRom when refetching the ROM after screenshot operations. |
| frontend/src/v2/components/GameDetails/SaveDataTab.vue | Use syncRom when refetching the ROM after save/state operations. |
| frontend/src/v2/components/GameDetails/MediaTab.vue | Use syncRom when refetching the ROM after media operations. |
| frontend/src/v2/components/GameDetails/ManualSubtab.vue | Use syncRom when refetching the ROM after manual operations. |
| frontend/src/v2/components/GameDetails/MainSiblingToggle.vue | Use syncRom for optimistic main-sibling toggling to keep the v2 gallery in sync. |
| frontend/src/v2/components/GameDetails/FilesTab/FilesTab.vue | Use syncRom when refetching the ROM after file operations. |
| frontend/src/v2/components/Gallery/SelectionBar.vue | Use syncRom for bulk status updates and refresh gallery membership once per action. |
| frontend/src/v2/components/Dialogs/MatchRomDialog.vue | Use applyRomWrite after a successful match to update caches and refetch when needed. |
| frontend/src/v2/components/Dialogs/ManualUploadTargetDialog.vue | Use syncRom when refetching the ROM after manual upload targeting. |
| frontend/src/v2/components/Dialogs/EditRomDialog.vue | Use applyRomWrite after edit saves to keep the v2 gallery correct for membership and ordering. |
| frontend/src/v2/components/Dialogs/DeleteManualDialog.vue | Use syncRom when refetching the ROM after manual deletion. |
Suppressed comments (4)
frontend/src/v2/composables/useRomSync/index.ts:44
- Repo rule: avoid em-dashes in comments/text (see CLAUDE.md:64). Use a comma or separate sentence instead of an em-dash.
* ROM the gallery has never loaded — the gallery update is a no-op when
frontend/src/v2/composables/useRomSync/index.ts:65
- Repo rule: avoid em-dashes in comments/text (see CLAUDE.md:64). Use a comma or separate sentence instead of an em-dash.
* say what the new result set is — so any active filter forces a refetch
frontend/src/v2/composables/useRomSync/index.ts:92
- Repo rule: avoid em-dashes in comments/text (see CLAUDE.md:64). Use a comma or separate sentence instead of an em-dash.
* touches `rom_user` and collection membership — checking `isFiltered()`
frontend/src/v2/composables/useRomSync/index.test.ts:162
- Repo rule: avoid em-dashes in comments/text (see CLAUDE.md:64). Use a comma or separate sentence instead of an em-dash.
it("applyRomWrite leaves an unloaded ROM alone — no row on screen to reorder", () => {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryThis PR adds a shared ROM synchronization composable and routes v2 edit, match, asset, favourite, and user-status writes through both the legacy ROM store and the windowed gallery cache.
Confidence Score: 3/5The PR should not merge until failed reconciliation preserves or retries the gallery and clearing last_played correctly reorders the active gallery. The new synchronization improves same-client freshness, but a transient reconciliation failure can leave the gallery cleared indefinitely, and removing a game from Continue Playing leaves last-played-sorted windows in the wrong order. Files Needing Attention: frontend/src/v2/composables/useRomSync/index.ts; frontend/src/v2/composables/useGameActions/index.ts Important Files Changed
Prompt To Fix All With AI### Issue 1
frontend/src/v2/composables/useRomSync/index.ts:81-82
**Failed refresh leaves gallery empty**
When the metadata refresh encounters a transient network or server error, this path has already cleared every loaded window and the fetch only logs the failure, causing the gallery to remain empty or loading until another action triggers a refresh.
### Issue 2
frontend/src/v2/composables/useGameActions/index.ts:430
**Last-played ordering stays stale**
When the gallery is ordered by last played, clearing `rom.rom_user.last_played` through `syncRom` replaces the cached card without repositioning it, causing the ROM to remain at its former position until the gallery is refreshed.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix: refresh the v2 gallery when a ROM i..." | Re-trigger Greptile |
The v2 gallery renders from `galleryRoms.byPosition`, but every v2 write site only patched v1's `stores/roms`, whose `_allRoms` the gallery never reads. Edits and matches replace the ROM with a fresh object from the API response, so the card kept showing the pre-edit name and cover until its window happened to be refetched. Optimistic toggles appeared to work only because they mutate the cached object in place, and the gallery holds that same reference. Add `useRomSync` and route the v2 write sites through it: - `syncRom` fans a write out to both stores. `galleryRoms.update` already existed for exactly this and had no callers. - `applyRomWrite` (edit / match dialogs) also refetches when an in-place swap would leave the list lying: any active filter, since a match rewrites provider ids, name and the metadata behind half the drawer, or a change to the value the gallery is currently ordered by. - `refreshAfterUserStateChange` covers favourite / status writes, guarded narrowly on the filters those can actually move. The existing "drop it from the Favourites view" branches check v1's collection context, which v2 never sets, so they were dead under v2. - `refreshIfOrderedBy` covers writes that mutate the cached ROM in place, leaving `applyRomWrite` nothing to diff: clearing `last_played` has to reorder a gallery sorted by it. Optimistic toggles keep the cheap in-place update, since invalidating the windows costs skeletons and the scroll position. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4f07558 to
c7ca9c5
Compare
|
Thanks both. Went through the four findings; three were valid and are fixed in the amended commit, one I'd like to scope out with reasoning. Fixed: Fixed: em-dashes violate CLAUDE.md:64. Also correct. Six added lines across the two new files, plus the commit message. Cleaned; I left the pre-existing em-dashes in the files I only touched lightly alone rather than reformatting unrelated comments. Fixed: last-played ordering stays stale (@greptile-apps). Valid, and the interesting one. Scoping out: failed refresh leaves the gallery empty (@greptile-apps). Real, but not introduced here and not fixable in the right place from this diff. Re-verified after the changes: typecheck, 680 frontend tests, full backend suite (2798 passed, 2 skipped), and |
|
The three fixes all look correct, and the On scoping out the failed-refresh finding: the reasoning is sound. The pre-existing Worth filing a tracking issue now so it doesn't get lost. The description you've already written ("store tracks |
Note
This PR was written primarily by Claude Code (code, tests and this description). I reviewed the approach and directed the scope at each step.
Description
The v2 gallery ignored its own writes: editing or matching a ROM left the card showing the old name and cover.
The v2 gallery renders its cards from
galleryRoms.byPosition, but every v2 write site only patched v1'sstores/roms, whose_allRomsthe gallery never reads. Editing or matching a ROM replaces it with a fresh object from the API response, so the card kept showing the pre-edit name and cover until its window happened to be refetched (navigating away and back, a filter change, a scan).Optimistic toggles (favourite, playing status) appeared to work without this only by accident: they mutate the cached object in place, and the gallery holds that same reference.
galleryRoms.update()already existed for exactly this purpose and had no callers.This adds
useRomSyncand routes the 20 v2 write sites through it:syncRomfans a write out to both stores. Used by the optimistic toggles and the asset tabs, which keep the cheap in-place update — invalidating the windows costs skeletons and the scroll position, too much for a favourite flip or a screenshot upload.applyRomWrite(edit / match dialogs) additionally refetches when an in-place swap would leave the list lying:refreshAfterUserStateChangecovers favourite / status writes, guarded narrowly on just the filters those can move (Favourites collection, favourites filter, status filter). Notably the existing "drop it from the Favourites view" branches inuseFavoriteToggleandSelectionBarcheck v1'scurrentCollection, which v2 never sets — so un-hearting a game while viewing Favourites left the card in place. The bulk bar reconciles once per action rather than once per selected ROM.The one remaining
romsStore.updateinuseScanLifecycleis deliberate: it's the v1 gallery branch, and the v2 gallery is already refreshed byrefreshGallery()a few lines above.v1 is untouched.
Checklist
Testing
11 unit tests in
useRomSync/index.test.ts. Manually verified in a local instance (mock library, SNES + GBA), in both light and dark themes:Not verified, needs a reviewer with provider credentials: the real Match ROM search-and-pick flow. My test instance had no IGDB/ScreenScraper/MobyGames keys, so provider search returned nothing. I exercised the identical
applyRomWritepath through the edit dialog with the same membership change (gaining anigdb_id), and the match dialog's call is a single unconditional line aftersyncRom— but the search flow itself is untested.Verification run: frontend typecheck, 686 frontend tests,
npm run build, the full backend suite (2798 passed, 2 skipped), andtrunk fmt && trunk check— all clean. No locale, token, or/libprimitive changes, and no response-schema or route-signature change, so the i18n check,build:tokens, Storybook, andnpm run generatedon't apply.Out of scope: cross-client freshness. Everything here is same-client. There's no Socket.IO event for ROM mutations, so a second open browser stays stale until it refetches on its own. That follow-up is built and waiting on
claude/rom-mutation-broadcast— deliberately kept out of this PR because it adds a backend event contract, a new request header, and a change to the shared axios interceptor, which is a different blast radius from a frontend cache fix, and because it hasn't been verified with two live browsers yet. Happy to open it as a follow-up PR once this lands. This PR is most of its client-side groundwork: the listener just hands each refetched ROM toapplyRomWrite.