Files
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
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.
2026-06-11 20:35:18 -04:00

11 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
04-shared-lists-live-sync 04 api-routes, pwa-components
item-crud
fractional-rank
optimistic-ui
D-05
D-06
D-07
D-08
D-09
D-13
tdd
list-02
requires provides affects
04-01 (list_items schema, BrowserRouter, react-router)
04-02 (listAccess.ts, listEmitter.ts primitives)
04-03 (listsRouter + ListsIndex + ListCard — prerequisite list data layer)
POST/GET /api/lists/:id/items with fractional rank (D-13)
PATCH /api/list-items/:id per-field LWW (D-08, exactly-one-field zod refine)
DELETE /api/list-items/:id delete-wins (D-09)
rank.ts
rankForAppend + rankBetween (fractional-indexing wrappers)
ListDetail with active/completed split (D-05), optimistic mutations (D-07/D-09)
ItemRow with checkbox, plain-text text, GripVertical slot, swipe/hover delete
AddItemInput sticky bottom input
listsClient item functions
fetchListItems, addItem, patchListItem, deleteItem
apps/api/src/routes/lists.ts (item routes added, listItemsRouter exported)
apps/api/src/index.ts (listItemsRouter mounted at /api/list-items)
apps/api/src/lib/rank.ts (new)
apps/api/tests/lib/rank.test.ts (new)
apps/api/tests/routes/lists.test.ts (item route tests added)
apps/pwa/src/api/listsClient.ts (item functions added)
apps/pwa/src/routes/ListDetail.tsx (placeholder replaced with real implementation)
apps/pwa/src/routes/ListDetail.test.tsx (todo stubs replaced with real tests)
apps/pwa/src/components/ItemRow.tsx (new)
apps/pwa/src/components/AddItemInput.tsx (new)
added patterns
fractional-indexing (already installed from Plan 04-01)
rankForAppend wraps generateKeyBetween(lastRank, null)
patchItemSchema .partial().refine(exactly one field) for D-08/T-04-07
listItemsRouter separate from listsRouter, mounted at /api/list-items
Optimistic mutations
onMutate/onError/onSettled against ['list', listId]
Delete-wins
no onError rollback in deleteMutation (D-09)
Uncheck recomputes rank to active-bottom in same DB write (Open Question 2)
created modified
apps/api/src/lib/rank.ts
apps/api/tests/lib/rank.test.ts
apps/pwa/src/components/ItemRow.tsx
apps/pwa/src/components/AddItemInput.tsx
apps/api/src/routes/lists.ts (item routes, listItemsRouter export)
apps/api/src/index.ts (listItemsRouter mount)
apps/api/tests/routes/lists.test.ts (25 new tests)
apps/pwa/src/api/listsClient.ts (item functions + ListItemsResponse type)
apps/pwa/src/routes/ListDetail.tsx (placeholder replaced)
apps/pwa/src/routes/ListDetail.test.tsx (7 real tests)
listItemsRouter exported separately from listsRouter; mounted at /api/list-items so PATCH/DELETE resolve at /api/list-items/:id per RESEARCH architecture diagram
Uncheck rank: recompute to active-bottom (generateKeyBetween(lastActiveRank, null)) in same write per Open Question 2 from 04-RESEARCH.md
Optimistic add uses negative id as temporary identifier (item.id < 0 → dim opacity 0.6)
Delete-wins: no onError rollback in deleteMutation; onSettled invalidates to reconcile
GripVertical drag handle present in ItemRow but non-functional (Plan 05 wires dnd-kit)
duration completed task_count file_count
~11 minutes 2026-06-09 2 10

Phase 4 Plan 4: Item CRUD + Checked-Sink Vertical Slice Summary

One-liner: Item CRUD vertical slice (LIST-02) — POST/GET/PATCH/DELETE item endpoints with fractional rank (D-13), per-field LWW (D-08), delete-wins (D-09), and ListDetail active/completed split with optimistic mutations (D-05/D-07).

TDD Gate Compliance

Gate Commit Status
RED — 16 failing item route tests + rank unit tests b1dc9b8 PASS — 16 route tests failed (404), rank.test.ts failed (no impl)
GREEN — rank.ts + item routes + listItemsRouter 5e31514 PASS — all 48 tests pass
REFACTOR (skipped) Implementation was clean on first pass

Tasks Completed

Task Name Commit Files
RED Failing tests for item routes + rank helpers b1dc9b8 tests/routes/lists.test.ts, tests/lib/rank.test.ts
GREEN rank.ts + item endpoints + listItemsRouter + index.ts mount 5e31514 rank.ts, lists.ts, index.ts
2 ListDetail + ItemRow + AddItemInput + listsClient item fns 6da9c2a 5 files (2 new, 3 modified)

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Routing] listItemsRouter exported separately from listsRouter

  • Found during: GREEN phase — PATCH/DELETE routes at listsRouter.patch('/list-items/:itemId') resolved to /api/lists/list-items/:id not /api/list-items/:id as the tests expected and RESEARCH.md architecture diagram specified.
  • Issue: The plan's note "Mount accordingly so both resolve under /api" required a second router export. Routes for single-item mutations must be at /api/list-items/:id, not nested under /api/lists.
  • Fix: Added export const listItemsRouter = new Hono() in lists.ts for PATCH/DELETE routes; mounted it at /api/list-items in index.ts alongside the existing listsRouter at /api/lists. The two routers share the same helper functions (resolveUserId, checkListAccess, rankForAppend).
  • Files modified: apps/api/src/routes/lists.ts, apps/api/src/index.ts
  • Commit: 5e31514

Playwright Browser Check

Ran against http://localhost:5173/lists/284 (list id 284, Test Groceries) with API on port 3000 (DEV_AUTH_BYPASS=true):

  1. /lists/284 renders empty state: "Nothing here yet" + "Add your first item below." — PASS
  2. Click input, type "milk", click Add → item appears in "Active items" list with checkbox + GripVertical handle — PASS
  3. Click checkbox "milk" → item moves to "Completed (1)" section (sinks per D-05) — PASS
  4. Hover over completed item → "Delete milk" button appears → click → item vanishes instantly, returns to "Nothing here yet" (no confirmation per D-06) — PASS

Verification Results

API Tests

  • tests/routes/lists.test.ts + tests/lib/rank.test.ts: 48 passed (0 failed)
  • rank.ts pure unit tests: 8 passed (rankForAppend/rankBetween ordering/stability)
  • Item POST assigns rank "a0" for first item; subsequent items rank > prior — PASS
  • Per-field PATCH zod refine (exactly one field) — two-field body → 400 — PASS
  • Uncheck rank recompute to active-bottom in same write — PASS
  • Access gating T-04-05: 403 for non-member on GET/POST/PATCH/DELETE — PASS
  • Delete-wins D-09: PATCH after DELETE returns 404 (no resurrection) — PASS

PWA Tests

  • src/routes/ListDetail.test.tsx: 7 passed (0 failed)
  • Optimistic check/uncheck/add/delete mutations
  • Rollback on error restores previous state
  • D-05 active/completed split verified
  • D-09 delete-wins no-rollback verified

TypeScript

  • pnpm --filter @familysync/api typecheck — PASS
  • pnpm --filter @familysync/pwa exec tsc --noEmit — PASS

Known Stubs

File Stub Reason
apps/pwa/src/routes/ListDetail.tsx List header shows "List" (not the list name) fetchListItems returns items only; list name not in the items response. Plan 05/06 can enrich from the ['lists'] cache. Non-blocking — user can still use the list.
apps/api/src/routes/lists.ts Plan 06 SSE seam comments (publishListEvent calls commented out) Plan 06 adds fan-out once the SSE /api/sse/lists endpoint exists
apps/pwa/src/components/ItemRow.tsx GripVertical handle present but non-functional Plan 05 wires dnd-kit; handle slot is structural as specified

The "List" heading stub does not prevent the plan's goal (add, check, delete items). Items are functionally correct. The heading will be enriched in Plan 05/06.

Threat Surface Scan

All threats from the plan's threat model are mitigated:

Threat ID Status Notes
T-04-05 (EoP — mutating items in inaccessible list) Mitigated checkListAccess() on every item handler; 403 tested for GET/POST/PATCH/DELETE
T-04-07 (Tampering — overposting on item PATCH) Mitigated patchItemSchema .partial().refine(exactly one field); 400 on two-field body tested
T-04-06 (Tampering — XSS via item text) Mitigated Item text rendered as plain-text JSX child in ItemRow; no dangerouslySetInnerHTML
T-04-09 (Tampering — resurrecting deleted item) Mitigated DELETE final; PATCH on deleted id → 404 (no upsert); delete-wins test asserts no resurrection

No new threat surface beyond the plan's trust boundaries.

Self-Check: PASSED

  • apps/api/src/lib/rank.ts — FOUND
  • apps/api/tests/lib/rank.test.ts — FOUND
  • apps/api/src/routes/lists.ts (POST /:id/items route) — FOUND
  • apps/api/src/index.ts (listItemsRouter mounted at /api/list-items) — FOUND
  • apps/pwa/src/components/ItemRow.tsx — FOUND
  • apps/pwa/src/components/AddItemInput.tsx — FOUND
  • apps/pwa/src/routes/ListDetail.tsx (real implementation, not placeholder) — FOUND
  • apps/pwa/src/api/listsClient.ts (fetchListItems, addItem exported) — FOUND
  • Commit b1dc9b8 (RED) — FOUND
  • Commit 5e31514 (GREEN) — FOUND
  • Commit 6da9c2a (Task 2) — FOUND