src/lib/rank.test.ts was the last co-located API test. The API tsconfig excludes tests/ from the build, so all test files belong there; a test in src/ gets compiled into dist/ and vitest then runs the stale compiled copy (the source of phantom '22 todo' and dist sourcemap warnings). Moving it to tests/lib/ matches convention and leaves zero test files in src/, so the production build no longer emits test artifacts. Updated rank.test.ts path refs in phase-04 plan docs.
9.2 KiB
9.2 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 |
|
|
|
|
|
|
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/:idnot/api/list-items/:idas 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-itemsin index.ts alongside the existinglistsRouterat/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):
/lists/284renders empty state: "Nothing here yet" + "Add your first item below." — PASS- Click input, type "milk", click Add → item appears in "Active items" list with checkbox + GripVertical handle — PASS
- Click checkbox "milk" → item moves to "Completed (1)" section (sinks per D-05) — PASS
- 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— PASSpnpm --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— FOUNDapps/api/tests/lib/rank.test.ts— FOUNDapps/api/src/routes/lists.ts(POST /:id/items route) — FOUNDapps/api/src/index.ts(listItemsRouter mounted at /api/list-items) — FOUNDapps/pwa/src/components/ItemRow.tsx— FOUNDapps/pwa/src/components/AddItemInput.tsx— FOUNDapps/pwa/src/routes/ListDetail.tsx(real implementation, not placeholder) — FOUNDapps/pwa/src/api/listsClient.ts(fetchListItems, addItem exported) — FOUND- Commit
b1dc9b8(RED) — FOUND - Commit
5e31514(GREEN) — FOUND - Commit
6da9c2a(Task 2) — FOUND