| 04-shared-lists-live-sync |
06 |
api-routes, api-sse, pwa-hooks, pwa-components |
| live-sync |
| sse |
| fan-out |
| D-04 |
| D-10 |
| D-11 |
| D-12 |
| tdd |
| list-04 |
| scoped-sse |
| bounded-backoff |
|
| requires |
provides |
affects |
| 04-02 (listEmitter.ts + listAccess.ts — fan-out primitives) |
| 04-03 (listsRouter CRUD with SSE seam comments) |
| 04-04 (listItemsRouter item CRUD with SSE seam comments) |
| 04-05 (drag-to-reorder; ListDetail established) |
|
| GET /api/sse/lists — scoped SSE stream (D-04, T-04-01, T-04-02) |
| publishListEvent triggers in lists.ts (item:added/updated/deleted, list:updated/deleted) |
| useListSSE — bounded-backoff EventSource wrapper (D-10/D-11) |
| LiveSyncIndicator — connected/reconnecting/disconnected status component |
| ListDetail wired with useListSSE + LiveSyncIndicator + refetchInterval polling (D-12) |
|
| apps/api/src/routes/lists.ts (publishListEvent fan-out wired at all 5 mutations) |
| apps/api/src/routes/sse.ts (GET /lists endpoint added) |
| apps/api/tests/routes/lists.test.ts (LIST-04 spy-based fan-out tests + D-04 scoped tests) |
| apps/pwa/src/hooks/useListSSE.ts (new) |
| apps/pwa/src/hooks/useListSSE.test.ts (stubs replaced with 8 real assertions) |
| apps/pwa/src/components/LiveSyncIndicator.tsx (new) |
| apps/pwa/src/routes/ListDetail.tsx (useListSSE + LiveSyncIndicator wired) |
|
|
| added |
patterns |
|
|
| In-memory EventEmitter fan-out via subscribeListEvents inside streamSSE (per RESEARCH Finding 1) |
| resolveUserId duplicated in sse.ts per per-router convention (matches events.ts + lists.ts) |
| BACKOFF_STEPS_MS=[250,500,1000,2000,4000,8000]; MAX_ATTEMPTS=6; close-before-retry (Pitfall 3) |
| refs (not state) for esRef/attemptsRef/timerRef to avoid re-render loops |
| LiveSyncIndicator |
| role=status (connected/reconnecting) + role=alert (disconnected) |
|
| refetchInterval:30000 polling fallback always active regardless of SSE state (D-12) |
|
|
| created |
modified |
| apps/pwa/src/hooks/useListSSE.ts |
| apps/pwa/src/components/LiveSyncIndicator.tsx |
|
| apps/api/src/routes/lists.ts (publishListEvent fan-out at 5 mutation handlers) |
| apps/api/src/routes/sse.ts (GET /lists scoped endpoint added; resolveUserId helper added) |
| apps/api/tests/routes/lists.test.ts (9 new LIST-04 tests |
| 5 fan-out spy + 4 D-04 scoped) |
|
| apps/pwa/src/hooks/useListSSE.test.ts (stubs → 8 real assertions; MockEventSource class) |
| apps/pwa/src/routes/ListDetail.tsx (useListSSE + setSyncState + LiveSyncIndicator) |
|
|
| SSE connection lives in ListDetail per plan spec; hoisting to Lists route level deferred to Phase 5 (acceptable for Phase 4 per RESEARCH note) |
| publishListEvent carries minimal payload (id, listId, minimal fields) — client uses only to trigger invalidateQueries/refetch (D-10) |
| resolveUserId duplicated in sse.ts (not extracted to shared module) — matches per-router convention established in events.ts + lists.ts |
| getAccessibleListIds called once at SSE connection time (D-03/D-10) — new shares visible after reconnect, acceptable per D-10 |
|
| duration |
completed |
task_count |
file_count |
| ~11 minutes |
2026-06-09 |
2 |
7 |
|