diff --git a/.planning/phases/04-shared-lists-live-sync/04-UAT.md b/.planning/phases/04-shared-lists-live-sync/04-UAT.md new file mode 100644 index 0000000..a7aa18e --- /dev/null +++ b/.planning/phases/04-shared-lists-live-sync/04-UAT.md @@ -0,0 +1,99 @@ +--- +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.