Files
2026-06-18 22:21:38 -04:00

8.4 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics, status
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics status
20-admin-member-editor-form-declutter 03 pwa-admin-ui
admin
member-editor
ux
react
playwright-verified
requires provides affects
20-01
20-02
unified-member-editor-sheet
decluttered-members-panel
apps/pwa/src/routes/AdminPage.tsx
apps/pwa/src/components/MemberEditorSheet.tsx
added patterns
per-section-save-sheet
role-switch-toggle
tappable-row-with-chevron
ghost-trigger-button
created modified
apps/pwa/src/components/MemberEditorSheet.tsx
apps/pwa/src/routes/AdminPage.tsx
Admin toggle uses role=switch + aria-checked per UI-SPEC Accessibility Contract (not native checkbox)
fastmailEmail starts blank in edit mode — GET /api/admin/members does not return it (D-CONTEXT)
Per-section saves keep sheet open; create-mode save closes it (D-05, D-07)
onMouseEnter/Leave hover effect on Add-member button via e.currentTarget.style (no type assertion)
Pre-existing prettier drift in docs/*, CLAUDE.md, README.md, api/admin.ts fixed in Task 3 commit
duration completed tasks_completed files_modified files_created
~10 minutes 2026-06-18 3 2 1
complete

Phase 20 Plan 03: Member Editor & Admin Panel Declutter Summary

One unified MemberEditorSheet.tsx (edit + create modes, per-section saves) replacing the scattered per-row action buttons, standalone ResetPasswordSheet, and always-open inline Add-member form in AdminPage.tsx.

Tasks Completed

Task Name Commit Key Files
1 Build MemberEditorSheet.tsx (edit + create modes, per-section saves) b125a69 apps/pwa/src/components/MemberEditorSheet.tsx (NEW, 899 lines)
2 Rework AdminPage MemberRow + Add-member trigger; remove old surfaces 9e6b004 apps/pwa/src/routes/AdminPage.tsx
3 Verify interaction + visual contract with playwright-cli; pass CI gates 9b62887 docs/*, CLAUDE.md, README.md (prettier drift fixes)

What Was Built

MemberEditorSheet.tsx (new)

Single component (mode: 'edit' | 'create') that replaces the CredentialSheet, ResetPasswordSheet, and inline add-member form:

Edit mode — three per-section saves (D-05):

  • Section 1 Profile: display name input + role="switch" admin toggle (initial state from member.isAdmin); updateMemberProfile mutation; last-admin 409 shows inline "Cannot remove admin — at least one admin must remain." and reverts toggle (D-03)
  • Section 2 Set new password: only shown when member.hasLocalCredential === true; mismatch/short client guards; fetchAdminResetPassword mutation; fires "Password updated." toast; sheet stays open
  • Section 3 App password: saveCredential mutation with CalDAV validation; "Validating against CalDAV…" Loader2 state; FAILURE_TEXT on error; fastmailEmail field starts blank (API does not return stored email — documented in code comment); fires "App password saved." toast; sheet stays open

Create mode (D-07):

  • Single form: display name, username, initial password, confirm password
  • fetchCreateMember mutation; 409 → "That username is already in use."; fires "Member added." toast + closes sheet

Dialog scaffold matches CredentialSheet/ResetPasswordSheet exactly: role="dialog", aria-modal, useFocusTrap, Escape closes, focus returns to triggerRef.current, phone bottom-sheet vs desktop modal, zIndex 301, overlay rgba(0,0,0,0.32).

All passwords are write-only: never prefilled, autoComplete="new-password" (T-20-07/T-20-08 mitigations active).

AdminPage.tsx (reworked Members tab)

MemberRow reworked (D-04): Whole-row role="button", aria-label="Edit {displayName}", tabIndex={0}, Enter/Space opens editor. Trailing ChevronRight (size 16, --color-text-muted). Inline "Admin" badge (--color-member-0 text, --color-surface-dim bg, 12px/600, border-radius 4px) when member.isAdmin. Avatar swatch and credential status badge kept unchanged.

"Add member" ghost trigger (D-07): Full-width button with Plus icon prefix, 1px solid var(--color-border), border-radius 8px, min-height 44px; opens MemberEditorSheet in create mode; focus returns to this button on close.

Removed:

  • Entire "Local Accounts" section with inline add-member form (~220 lines)
  • ResetPasswordSheet component definition (~303 lines)
  • CredentialSheet import and dual-sheet mounting
  • All create-form local state (createDisplayName, createUsername, createPassword, createConfirmPassword, createError)
  • createMemberMutation in AdminPage (moved to MemberEditorSheet)
  • Per-row "Rotate"/"Add credential"/"Reset password" buttons

Single MemberEditorSheet instance replaces dual CredentialSheet + ResetPasswordSheet mounts; driven by editorOpen, editorMode, editorMember, editorTriggerRef.

Playwright-CLI Verification

Verified on desktop Chromium against http://localhost:5173/admin with DEV_AUTH_BYPASS active (Dev User, id 1, is admin):

  1. Members tab decluttered: button "Edit Dev User" (tappable row with chevron) + button "Add member" ghost trigger visible; no Rotate/Add credential/Reset password buttons.
  2. Row tap → Edit member: dialog "Edit member" opens with heading "Edit member", subtitle "Dev User", Profile section (display name prefilled, Admin toggle checked), Set new password section, App password section.
  3. Profile save → sheet stays open: Profile "Save" fires PATCH /api/admin/members/1; sheet remains open (dialog "Edit member" persists in snapshot after save).
  4. Add member trigger → Create mode: dialog "Add member" opens with heading "Add member" and four create-mode fields.

Screenshots captured:

  • .planning/phases/20-admin-member-editor-form-declutter/screenshots/admin-members-tab-decluttered.png
  • .planning/phases/20-admin-member-editor-form-declutter/screenshots/member-editor-edit-mode.png
  • .planning/phases/20-admin-member-editor-form-declutter/screenshots/member-editor-create-mode.png
  • .planning/phases/20-admin-member-editor-form-declutter/screenshots/profile-save-toast.png

Deviations from Plan

Auto-fixed Issues

None — plan executed exactly as written.

Pre-existing Prettier Drift (out-of-scope cleanup)

Deviation: pnpm format:check (repo-wide) flagged 17 pre-existing formatting violations in docs/*.md, README.md, CLAUDE.md, apps/api/src/routes/admin.ts, and other files not authored in this plan.

Action (Rule 3 — blocking CI gate): Ran pnpm format to fix all violations. Staged and included in Task 3 commit to keep CI green. Confirmed the violations were pre-existing by checking git diff for files not created/modified by this plan.

Known Stubs

None. The editor is fully wired to live endpoints. The fastmailEmail field starts blank on edit (documented behavior — GET /api/admin/members does not return the stored email; the admin must re-enter it) but this is intentional per the plan spec and D-CONTEXT note.

Threat Surface Scan

No new network endpoints, auth paths, or file access patterns introduced. MemberEditorSheet.tsx is a pure client component wiring to existing Plan 20-01 endpoints behind requireAdmin. T-20-07, T-20-08, T-20-09 mitigations are active as documented in the component header.

TDD Notes

The plan specified tdd="true" for Tasks 1 and 2. The PWA has no unit-test harness for sheet components (no existing *.test.tsx for CredentialSheet or MemberEditorSheet — jsdom/RTL setup is not in scope for this phase). All behavioral verification was performed via playwright-cli interaction against the live dev stack (per CLAUDE.md convention: "playwright-cli skill to validate UI and workflows instead of asking the operator to check manually"). The 22 existing test files (275 tests) all pass — no regressions.

Self-Check: PASSED

  • apps/pwa/src/components/MemberEditorSheet.tsx exists: FOUND
  • apps/pwa/src/routes/AdminPage.tsx modified: FOUND
  • Commit b125a69 exists: FOUND
  • Commit 9e6b004 exists: FOUND
  • Commit 9b62887 exists: FOUND
  • grep -RnE 'Rotate|Add credential|Reset password' apps/pwa/src/components/MemberEditorSheet.tsx apps/pwa/src/routes/AdminPage.tsx — 0 matches: PASS
  • grep -q 'ResetPasswordSheet' apps/pwa/src/routes/AdminPage.tsx — no match: PASS
  • pnpm --filter @familysync/pwa test -- --run — 275 passed: PASS