Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
20 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-shared-lists-live-sync | 01 | execute | 1 |
|
false |
|
|
This is the MVP first slice: after this plan a real user can tap "Lists" and see the Lists surface render (empty state). No list data yet — later slices fill it in. Wave 0 test stubs are created here so every downstream task has an <automated> target per 04-VALIDATION.md.
Purpose: De-risk the transport/routing/schema/test plumbing before any list feature is built, and satisfy the [BLOCKING] generate+migrate schema constraint once for all later DB-dependent work. Output: New deps installed; three tables migrated; API vitest harness + 4 RED stub test files; router + BottomTabBar + ListsIndex empty state; listsStore (UI-only).
Phase Goal
As a household member, I want to create and manage shared named lists with real-time co-edit sync, so that my partner and I see each other's list edits appear within seconds without refreshing. (This plan delivers the navigable shell; later plans fill in CRUD, reorder, and live sync.)
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-shared-lists-live-sync/04-CONTEXT.md @.planning/phases/04-shared-lists-live-sync/04-RESEARCH.md @.planning/phases/04-shared-lists-live-sync/04-PATTERNS.md @.planning/phases/04-shared-lists-live-sync/04-UI-SPEC.md @.planning/phases/04-shared-lists-live-sync/04-VALIDATION.md Task 1: Package legitimacy gate for the SUS-flagged react-router apps/pwa/package.json - .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md §"Package Legitimacy Audit" Nothing yet — this gate precedes the install in Task 2. Per the Package Legitimacy Audit, three packages (@dnd-kit/core, @dnd-kit/sortable, fractional-indexing) are verdict OK and auto-approved. `react-router` is flagged SUS only because version 7.17.0 was published 2026-06-04 (version-recency false positive); the package is the canonical React Router (remix-run, ~12 yrs, 47.5M/wk). Surface this to the operator for a one-time confirm before installing, since legitimacy checkpoints are never auto-approvable. 1. Open https://www.npmjs.com/package/react-router and confirm publisher is `remix-run`/`react-router` org with multi-year history and ~47M weekly downloads. 2. Confirm version 7.x is the current major. - Operator types "approved" (or names a pinned version) before Task 2 runs. Type "approved" to proceed with the install, or specify an alternate version. Task 2: Install new dependencies + scaffold API test harness with Wave-0 RED stubs apps/pwa/package.json, apps/api/package.json, apps/api/test/setup.ts, apps/api/vitest.config.ts, apps/api/tests/routes/lists.test.ts, apps/api/tests/lib/listEmitter.test.ts, apps/pwa/src/hooks/useListSSE.test.ts, apps/pwa/src/routes/ListDetail.test.tsx - apps/api/vitest.config.ts - apps/api/src/db/client.ts - apps/pwa/src/api/client.test.ts (existing PWA test convention) - .planning/phases/04-shared-lists-live-sync/04-VALIDATION.md §"Wave 0 Requirements" and §"Per-Task Verification Map" - .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md §"Standard Stack" → "New Dependencies" Install PWA deps: react-router@7, @dnd-kit/core, @dnd-kit/sortable, fractional-indexing via `pnpm --filter @familysync/pwa add`. Install API dep fractional-indexing via `pnpm --filter @familysync/api add` (needed server-side for rank generation). Do NOT install ioredis — fan-out is in-memory EventEmitter per RESEARCH discretion (justified in Plan 02).Scaffold the API test harness: the API currently has zero test files. Create `apps/api/test/setup.ts` and reference it from `apps/api/vitest.config.ts` (`test.setupFiles`). The setup file must establish how DB-backed route tests connect — point at the local MariaDB via the existing `apps/api/src/db/client.ts` pool (DB_HOST/DB_NAME from env), and provide a per-test cleanup (truncate lists/list_items/list_shares between tests). Pure-logic tests (listEmitter, fractional rank) do NOT need the DB.
Create the four Wave-0 RED stub test files listed in 04-VALIDATION.md, each with `describe`/`it.todo` or `it(... )` blocks that compile and FAIL (red) rather than error on import — they import the not-yet-existing modules behind a guard or use `it.todo` placeholders that downstream plans convert to real assertions:
- apps/api/tests/routes/lists.test.ts — LIST-01/02/03/04 API behavior stubs
- apps/api/tests/lib/listEmitter.test.ts — scoped fan-out correctness (D-04) stubs
- apps/pwa/src/hooks/useListSSE.test.ts — D-11 bounded backoff (mock EventSource) stubs
- apps/pwa/src/routes/ListDetail.test.tsx — D-07 optimistic update + rollback stubs
Then generate and apply the migration. This is [BLOCKING]: run `pnpm --filter @familysync/api db:generate` to produce `apps/api/src/db/migrations/0002_lists_schema.sql` (+ journal/snapshot). REVIEW the generated SQL — it MUST be additive (CREATE TABLE only, NO DROP/TRUNCATE of existing tables). Then run `pnpm --filter @familysync/api db:migrate` to apply. NEVER run `db:push` / `drizzle-kit push` — it emits a false destructive diff on populated MariaDB (hard project constraint). Build/type checks pass without the live migration, so this task is mandatory and must complete before any DB-dependent verification in later plans.
Create BottomTabBar.tsx: fixed-bottom 56px + env(safe-area-inset-bottom), background var(--color-surface-dim), border-top var(--color-border), two equal NavLink tabs (CalendarDays→/calendar, List→/lists) with isActive callback applying accent var(--color-member-0) to icon+label and a 2px active indicator; inactive var(--color-text-muted); 13px label; ≥44px touch target; z-index 200. On desktop (≥768px) the existing AppNav sidebar remains; per UI-SPEC add a "Lists" NavLink there too (sidebar) — do this without breaking the existing AppNav signature.
Create ListsIndex.tsx: full-height scrollable column, "Lists" heading, useQuery(['lists'], fetchLists) where fetchLists is imported from a minimal listsClient (create only the fetchLists function + List type here if listsClient does not yet exist; Plan 03 expands it). Render ListsEmptyState ("No lists yet" / "Tap + to create your first shared list…") when there are zero lists; render a placeholder card stack otherwise. Wire isLoading/isError/success branches mirroring CalendarShell. Include the "+ New List" FAB affordance (non-functional placeholder is acceptable here; Plan 03 wires CreateListSheet).
Create listsStore.ts (Zustand, UI-only): activeTab and createListSheetOpen state with setters, following calendarStore conventions (no persist, no immer).
Confirm vite.config.ts navigateFallback ('/index.html') + denylist already cover SPA deep-links to /lists/* (it does per Phase 3 config) — if a denylist entry would block /lists, fix it; otherwise leave unchanged and note in SUMMARY.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| browser → /api/* | All list/SSE requests cross here; untrusted client input |
| API → MariaDB | Drizzle parameterized queries only |
| drizzle-kit → MariaDB (migration) | DDL applied to a populated production-shaped DB |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-04-01 | Tampering | drizzle-kit push truncating populated tables | mitigate | generate+migrate ONLY; verify 0002 SQL has no DROP/TRUNCATE of existing tables before applying (Task 3 gate) |
| T-04-02 | Information Disclosure | scoped-fan-out leak (D-04) — foundational | mitigate | Schema models access via list_shares join table + owner_id (this plan); enforcement lands in Plans 02/03/06; negative test seeded in lists.test.ts here |
| T-04-SC | Tampering | npm installs (react-router SUS, dnd-kit, fractional-indexing) | mitigate | Legitimacy audit in RESEARCH; blocking human checkpoint (Task 1) for the SUS react-router before install |
</threat_model>
- `pnpm --filter @familysync/api typecheck` and `pnpm --filter @familysync/pwa exec tsc --noEmit` both pass. - `pnpm --filter @familysync/api test` runs (Wave-0 stubs red/todo, not erroring). - 0002_lists_schema.sql is additive; migration applied; three tables queryable. - `playwright-cli` confirms /lists renders the empty state with the bottom tab bar.<success_criteria>
- New deps installed (no ioredis); API test harness operational.
- Three list tables migrated additively (no push).
- Router + BottomTabBar live; Lists tab navigates to an empty Lists surface.
- Four Wave-0 RED stub test files exist and execute. </success_criteria>
<artifacts_produced> Symbols/files this plan creates (exclude from drift verification — they are new):
- Tables:
lists,list_shares,list_items(apps/api/src/db/schema.ts) - Migration:
apps/api/src/db/migrations/0002_lists_schema.sql(+ journal/snapshot) - API test harness:
apps/api/test/setup.ts; setupFiles wiring inapps/api/vitest.config.ts - RED stub tests:
apps/api/tests/routes/lists.test.ts,apps/api/tests/lib/listEmitter.test.ts,apps/pwa/src/hooks/useListSSE.test.ts,apps/pwa/src/routes/ListDetail.test.tsx - Components:
BottomTabBar(apps/pwa/src/components/BottomTabBar.tsx),ListsIndex(apps/pwa/src/routes/ListsIndex.tsx), temporary ListDetail placeholder route element - Store:
useListsStore(apps/pwa/src/store/listsStore.ts) with activeTab/createListSheetOpen - App.tsx now exports a BrowserRouter-wrapped App + AppShell
- (Possibly) initial
apps/pwa/src/api/listsClient.tswithfetchLists+Listtype </artifacts_produced>