Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.0 KiB
phase, plan, subsystem, tags, status, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | status | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20-admin-member-editor-form-declutter | 02 | pwa-api-client |
|
complete |
|
|
|
|
|
Phase 20 Plan 02: PWA API Client — updateMemberProfile Fetcher + AdminMember.isAdmin Summary
One-liner: Thin API client glue: updateMemberProfile PATCH fetcher with last-admin 409/422 sentinel + isAdmin: boolean on AdminMember, enabling the Plan 20-03 editor.
What Was Built
Added two changes to apps/pwa/src/api/client.ts:
-
AdminMember.isAdmin: boolean— new required field on theAdminMemberinterface (aftercolor, beforehasCredential). Feeds the editor toggle's initial state once Plan 20-01 lands (theGET /api/admin/membersroute already returns it after that plan's changes). No consumer code changes needed; Plan 20-03 reads it directly. -
updateMemberProfile(memberId, body)— exportedasync functionthat issuesPATCH /api/admin/members/${memberId}withcredentials: 'include',redirect: 'manual',Content-Type: application/json, and JSON-stringified body{ displayName?, isAdmin? }. Error mapping:opaqueredirector401→SessionExpiredError(existing re-auth flow convention)409or422→new Error('last-admin')(D-03 sentinel; editor branches on this message)- other non-ok → generic
Error 200→ resolvesvoid
TDD Gate Compliance
| Gate | Commit | Notes |
|---|---|---|
| RED | 18da7e9 |
8 updateMemberProfile behavior tests + 1 AdminMember.isAdmin shape test — all fail with updateMemberProfile is not a function; 42 existing tests pass |
| GREEN | 5bcd818 |
All 50 tests pass after implementation; eslint + prettier + tsc --noEmit exit 0 |
| REFACTOR | N/A | No refactor needed — the implementation was minimal and clean on the first pass |
Task Summary
| Task | Name | Commit | Files |
|---|---|---|---|
| RED | Add failing tests for updateMemberProfile + AdminMember.isAdmin | 18da7e9 |
client.test.ts |
| GREEN | Add updateMemberProfile fetcher + AdminMember.isAdmin | 5bcd818 |
client.ts, client.test.ts |
Verification
grep -n "updateMemberProfile" apps/pwa/src/api/client.ts→ line 249 (export), line 263 (error throw)grep -n "PATCH" apps/pwa/src/api/client.ts→ line 254 (method: 'PATCH')grep -nE "isAdmin: boolean" apps/pwa/src/api/client.ts→ line 597 (AdminMember)grep -n "last-admin" apps/pwa/src/api/client.ts→ line 262 (the sentinel throw)pnpm --filter @familysync/pwa exec tsc --noEmit→ exit 0pnpm --filter @familysync/pwa exec eslint src/api/client.ts src/api/client.test.ts→ exit 0pnpm exec prettier --check apps/pwa/src/api/client.ts apps/pwa/src/api/client.test.ts→ exit 0- 50/50 tests pass
Deviations from Plan
None — plan executed exactly as written.
- The PATTERNS.md excerpt was followed verbatim for the function shape.
- The eslint
require-awaitissue in the test file was caught and fixed during Task 2 CI gates (test function did not needasync— removed it). Not a plan deviation; it was a CI gate finding during Task 2 as specified.
Known Stubs
None. This plan delivers typed wire code only; no UI rendering or data display.
Threat Flags
No new security-relevant surface introduced. updateMemberProfile reuses the existing session-expiry path (T-20-05 mitigated) and does not introduce new trust boundaries (T-20-06 accepted per plan threat model).
Self-Check: PASSED
| Check | Result |
|---|---|
apps/pwa/src/api/client.ts exists |
FOUND |
apps/pwa/src/api/client.test.ts exists |
FOUND |
20-02-SUMMARY.md exists |
FOUND |
RED commit 18da7e9 |
FOUND |
GREEN commit 5bcd818 |
FOUND |