feat(fase-11): realtime re-sort on sort_order UPDATE + drag-reorder e2e (11.4)

The drag handle + dndzone + reorderItems batch UPDATE were already wired
on the list detail view (Fase 5.10). One observable contract was missing:
when another client reorders an item, the local realtime UPDATE feed
shallow-merges the new payload onto the existing row but does NOT
re-position the row in the array — so the new order is invisible until
a reload. Fix: sort the `items` array by sort_order after every realtime
UPDATE / INSERT merge. Cheap (n is small per list) and matches the cold-
load order.

E2E (reorder-items.test.ts):
  * RO-01: create 3 items, write a new sort_order via the same PATCH the
    in-page reorderItems() helper does, reload, assert the reorder
    persisted.
  * RO-02: dual-context, Ana reorders, Borja sees the new order via
    realtime within the suite's 10s window — without this commit's
    sort fix the test failed because Borja's DOM order stayed at the
    old order.

Drag emulation in headless Chromium is unreliable for svelte-dnd-action
(native HTML5 DnD). Both tests drive the same write path the page uses
(reorderItems = batch PATCH on sort_order) via fetch from inside the
browser context, using the session token the SDK has already stored.
The realtime + reload paths are unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 03:26:12 +02:00
parent f28577e165
commit 1a6aae2874
2 changed files with 214 additions and 4 deletions

View File

@@ -338,10 +338,16 @@
// UPDATE payload. INSERTs of brand-new rows arrive without tags
// (they're attached separately); seed an empty array.
const next = applyItemEvent(items as unknown as ShoppingItem[], evt);
items = next.map((row) => {
const existing = items.find((i) => i.id === row.id);
return { ...row, tags: existing?.tags ?? [] };
});
// Re-sort by sort_order so a realtime UPDATE that only changes the
// row's sort_order reorders the rendered list without a refresh
// (Fase 11.4.3). Without this the array order is stable across
// shallow-merge and a remote reorder is invisible to other clients.
items = next
.map((row) => {
const existing = items.find((i) => i.id === row.id);
return { ...row, tags: existing?.tags ?? [] };
})
.sort((a, b) => a.sort_order - b.sort_order);
});
// Listen for shopping_item_tags changes so attach/detach in another tab