fix(playlists): a rejected reorder goes back to the order the server accepted
Deep-review round 5, over round 4's own fixes. - Round 4 made a failed reorder roll the rows back to the CACHED detail, because a resync cannot do it (the server's list is unchanged, so the refetch is deep-equal, react-query keeps the same `data` object and the effect keyed on it never runs). But nothing wrote an ACCEPTED order into that cache — only the refetch that follows a successful reorder did. So a second reorder rejected while that refetch was still in flight rolled the rows back past the first one, which the server had accepted; the membership guard cannot correct an order (its key is the sorted id set), so the wrong order stayed on screen for good and the next drag persisted it. `onApply` now writes the accepted order into the cache before invalidating. - `PlexPlaylistView.rename` had a `catch` no test could reach, and `commit` (reorder) was not gated by `busy` the way its siblings are — a reorder in flight while a removal completes is what makes a rollback ambiguous in the first place. The rename failure path is now covered; `commit` defers. - `putBack` read `q.data` from the render that bound the failing handler instead of the cache at rollback time, unlike the sibling `restoreFromCache`. Aligned. Stated plainly: I could NOT drive that staleness in jsdom — a mutation that restores the old shape leaves every candidate test green — so this one ships as an alignment with the proven sibling, not as a fix with a red test behind it. The scenario and the harness limit are on ROADMAP.md. Two regression tests, both mutation-checked red (the cache write, the rename catch). The drag-reorder failure path still has no test: dnd-kit's pointer and keyboard sensors do not activate under jsdom even with geometry and scrollIntoView stubbed, so a test there would have pinned the harness, not the code. Logged, not faked. vitest 1009.
This commit is contained in:
@@ -408,12 +408,19 @@ export default function PlexPlaylistView({
|
||||
// nothing at all (deep-review round 4; the YouTube side was fixed in round 3). A failure is said,
|
||||
// and the rows are put back from what the server last sent — explicitly, because a resync that
|
||||
// returns the same list keeps the same `q.data` and the re-sync effect above never runs.
|
||||
// Read the cache AT ROLLBACK TIME, not the `q.data` of the render that bound the failing handler:
|
||||
// a removal completing while a reorder was in flight refreshed the list, and the reorder's stale
|
||||
// closure then put the removed row back on screen — where the deep-equal refetch could not clear
|
||||
// it, and the next drag persisted an order containing it (deep-review round 5). `Playlists.tsx`'s
|
||||
// sibling `restoreFromCache` reads it live for the same reason.
|
||||
const putBack = () => {
|
||||
if (q.data) setItems(q.data.items);
|
||||
const cur = qc.getQueryData<{ items: PlexCard[] }>(qk.plexPlaylist(playlistId));
|
||||
if (cur) setItems(cur.items);
|
||||
invalidate();
|
||||
};
|
||||
|
||||
function commit(next: PlexCard[]) {
|
||||
if (busy) return; // a reorder racing a removal is what makes a rollback ambiguous
|
||||
setItems(next);
|
||||
api
|
||||
.plexReorderPlaylist(
|
||||
|
||||
Reference in New Issue
Block a user