---
status: complete
phase: 04-shared-lists-live-sync
source:
  - 04-01-SUMMARY.md
  - 04-02-SUMMARY.md
  - 04-03-SUMMARY.md
  - 04-04-SUMMARY.md
  - 04-05-SUMMARY.md
  - 04-06-SUMMARY.md
  - 04-07-SUMMARY.md
mode: playwright-cli (automated, operator-elected)
started: 2026-06-09T18:39:39Z
updated: 2026-06-09T18:48:06Z
---

## Current Test

[testing complete]

## Tests

### 1. Cold Start Smoke Test
expected: API /health returns 200, PWA loads, navigating to /lists renders the Lists surface with live data (not an error/blank).
result: pass
evidence: API /health → {"ok":true,"db":"up"}; PWA served on :5173; /lists rendered empty-state ("No lists yet") with GET /api/lists → 200.

### 2. Navigate to Lists (bottom tab bar)
expected: Bottom tab bar / desktop nav exposes a "Lists" link; clicking routes to /lists and shows the index.
result: pass
evidence: Desktop sidebar nav shows Calendar + Lists links; Calendar link routed to /calendar (full calendar rendered), Lists link routed to /lists. (Bottom tab bar is the mobile-width variant of the same nav.)

### 3. Create a named list (defaults to Shared)
expected: CreateListSheet opens; entering a name + confirming creates the list (default Shared), sheet closes, new card appears.
result: pass
evidence: "New list" sheet opened with Visibility toggle defaulting to Shared [pressed], Create disabled until named. Created "Groceries" → POST /api/lists → 201; card "Groceries · 0 items · Shared" appeared.

### 4. Delete a list with confirmation
expected: Delete (X) control opens ListDeleteDialog with clear heading/body; confirm removes card, cancel leaves it.
result: pass
evidence: Created throwaway "ToDelete"; hover revealed "Delete list: ToDelete"; dialog "Delete list?" with body "'ToDelete' and all its items will be permanently removed." Confirm → DELETE /api/lists/1279 → 200; ToDelete removed, Groceries remained.

### 5. Open a list and add items
expected: ListDetail shows add-item input; typing + Add inserts into "Active items" with checkbox + drag handle; optimistic.
result: pass
evidence: Opened /lists/1278 — "Live sync connected" indicator present. Added milk, eggs, bread → all in "Active items" with checkboxes and "Drag to reorder" handles.

### 6. Check an item — sinks to Completed
expected: Checking moves item into a "Completed (N)" section (D-05); unchecking returns to bottom of Active.
result: pass
evidence: Checked "milk" → moved into "Completed (1)" collapsible section (checkbox checked); Active showed eggs, bread.

### 7. Delete an item instantly (no confirm)
expected: Delete control removes item immediately, no confirmation (D-06/D-09 delete-wins).
result: pass
evidence: Deleted "eggs" → removed instantly, no dialog rendered (snapshot confirmed no dialog/confirm element).

### 8. Drag to reorder active items
expected: Dragging an item by its handle to a new position persists (survives reload); single-row rank write.
result: pass
evidence: Dragged "cheese" from bottom to top → order cheese/bread/apples; PATCH /api/list-items → 200; order persisted after full page reload.

### 9. Drag an item to the very top (collation regression)
expected: Dragging to position 0 persists; dragged item stays first after reload (utf8mb4_bin collation, Plan 04-07).
result: pass
evidence: Manual pointer drag of "cheese" above the a0-ranked top generated rank "Zz" (uppercase-prefixed). DB-ordered API AND UI both returned cheese FIRST (cheese=Zz, apples=a0, bread=a0V); persisted after reload. Without the collation fix, MariaDB's case-insensitive default would sort 'Zz' after 'a0' and bounce it to the bottom — confirmed fixed end-to-end.

### 10. Live sync between two members (within seconds)
expected: With two sessions on the same shared list, an edit in A appears in B within seconds, no manual refresh.
result: pass
evidence: Opened session B (separate browser context) on /lists/1278 — "Live sync connected". Added "butter" in session A → appeared in session B within ~3s with no reload.

### 11. Live sync survives a brief reconnect
expected: On SSE drop, LiveSyncIndicator reflects reconnecting/disconnected then returns to connected (bounded backoff); edits reconcile.
result: pass
evidence: (a) Took B offline + added "yogurt" in A → on reconnect, yogurt reconciled into B. (b) Blocked **/api/sse/lists (503) + reloaded B → indicator showed "Reconnecting…"; unblocked + reloaded → returned to "Live sync connected". Bounded-backoff hook also unit-tested (04-06, 8 passing hook tests).

### 12. Private-list isolation (no cross-leak)
expected: A member's private list and its events are never visible to a member without access (D-04).
result: skipped
reason: Not drivable via the live UI — the dev-auth bypass injects a single static DEV_USER with no user-switching, so two distinct authenticated members cannot be simulated through the PWA. D-04 isolation is comprehensively proven at the route layer by passing automated tests: T-04-02 (GET excludes another member's private list), the 4 D-04 scoped-SSE tests in 04-06 (no fan-out leak to non-members), and the 04-07 sharee-403 tests. Re-confirm during the live multi-user Pangolin/Authelia smoke (deployment gate).

## Summary

total: 12
passed: 11
issues: 0
pending: 0
skipped: 1
blocked: 0

## Gaps

[none — all functional tests passed; test 12 deferred to deployment-time multi-user smoke, already covered by automated route-layer D-04 tests]

## Notes

- Known non-blocking stub observed: ListDetail header renders "List" rather than the list name (carried from Plans 04-04/04-05; fetchListItems returns items only). Does not affect any LIST-01..04 behavior. Tracked in plan summaries.
- Console: only a favicon.ico 404 (harmless); no application errors during any flow.
