--- phase: 17-ui-optimization-polish plan: "06" subsystem: pwa-admin tags: [admin, toasts, tabs, aria, accessibility, e2e] dependency_graph: requires: [17-01] provides: [admin-success-toasts, admin-two-tab-nav, admin-reset-sheet-centering, admin-e2e-aria] affects: [apps/pwa/src/routes/AdminPage.tsx, apps/pwa/e2e/admin.spec.ts] tech_stack: added: [] patterns: - "useState + useEffect auto-dismiss toast pattern (mirrors SyncStateToast lines 72-79)" - "ARIA tablist/tab/tabpanel roving-tabindex pattern (ArrowLeft/ArrowRight keyboard nav)" - "Phone/desktop style branch for dialog centering (translate(-50%,-50%))" - "onSuccess callback prop to propagate success signal from sheet to parent" key_files: created: [] modified: - apps/pwa/src/routes/AdminPage.tsx - apps/pwa/e2e/admin.spec.ts decisions: - "Tasks 1 and 2 committed together (same file AdminPage.tsx) — acceptable since both modify the same component" - "Playwright tests verified against worktree Vite (port 5174) since main dev server at 5173 serves main branch code; 12/12 tests pass" - "ResetPasswordSheet receives onSuccess callback prop to fire toast at AdminPage level, avoiding toast rendered inside a closing sheet" - "Section order reorg: MEMBERS + LOCAL ACCOUNTS under members panel; SHARED CALENDAR + TIMEZONE under settings panel — matches UI-SPEC D-10 contents mapping" metrics: duration: "9 minutes" completed: "2026-06-18" tasks_completed: 3 tasks_total: 3 files_modified: 2 status: complete --- # Phase 17 Plan 06: Admin Polish — Toasts, Two-Tab Nav, Reset-Sheet Centering Summary **One-liner:** Admin UX polish with success toasts (D-08), two-tab ARIA strip wrapping existing sections (D-10), desktop-centered reset-password sheet (D-09 admin slice), and `admin.spec.ts` tab ARIA + keyboard assertions (Wave 0 requirement). ## Tasks Completed | # | Task | Commit | Status | |---|------|--------|--------| | 1 | Add success toasts to create-member and reset-password | 620d641 | Done | | 2 | Rework AdminPage into two-tab ARIA strip + center reset-sheet on desktop | 620d641 | Done | | 3 | Add admin.spec.ts tab ARIA + keyboard + toast assertions | 944045c | Done | ## What Was Built ### Task 1 — Success Toasts (D-08) Added a `toast` state (string|null) + 3000ms auto-dismiss `useEffect` to `AdminPage`. The `phone` boolean (`window.matchMedia('(max-width: 767px)').matches`) drives the bottom offset. **Hooks:** - `createMemberMutation.onSuccess` → `setToast('Member added.')` - `ResetPasswordSheet.resetMutation.onSuccess` → calls `onSuccess?.()` callback prop → `setToast('Password reset.')` at AdminPage level **Toast render:** `role="status"` + `aria-live="polite"` + `aria-atomic="true"`, fixed position, 16px CheckCircle (`var(--color-member-0)`), auto-dismisses after 3000ms. Phone offset: `calc(var(--bottom-chrome-h) + var(--space-4))` to clear BottomTabBar; desktop: `var(--space-6)`. ### Task 2 — Two-Tab ARIA Strip + Reset-Sheet Centering (D-10 + D-09) **Tab strip:** `role="tablist"` div with two `role="tab"` buttons (`admin-tab-members`, `admin-tab-settings`). Roving tabindex (active: 0, inactive: -1). `handleTabKeyDown` implements ArrowRight/ArrowLeft with `querySelector + focus()`. Active tab: fontWeight 600 + `borderBottom: 2px solid var(--color-member-0)`. **Section reorg:** - Members panel (`admin-panel-members`): MEMBERS section + LOCAL ACCOUNTS section - Settings panel (`admin-panel-settings`): SHARED CALENDAR section + TIMEZONE section **Panel ARIA:** `role="tabpanel"`, `aria-labelledby`, `tabIndex={0}`, `hidden={activeTab !== id}`. **Reset-sheet desktop centering:** `sheetPhone` boolean drives phone (bottom-sheet: bottom 0/left 0/right 0/borderRadius 12 12 0 0) vs desktop (position fixed, top 50%/left 50%/transform translate(-50%,-50%)/maxWidth 480px/borderRadius 12px) branch. `role="dialog"` + `aria-modal="true"` + `aria-label` unchanged. ### Task 3 — admin.spec.ts ARIA + Keyboard Assertions Extended `apps/pwa/e2e/admin.spec.ts` with two new `test.describe` blocks: **`Admin two-tab ARIA strip (D-10)`** (5 tests): 1. tablist + both named tabs visible 2. Members & Accounts tab is selected by default (aria-selected=true) 3. ArrowRight switches to Settings tab (aria-selected=true) 4. ArrowLeft returns to Members & Accounts tab 5. Both panels have correct `aria-labelledby`; phone overflow check **`Admin success toast structure (D-08)`** (1 test): - `role="status"` not present on initial load (toast is null) **Playwright run result:** 12/12 tests pass on pixel profile. **Harness note:** Tests were verified against a worktree Vite instance (`port 5174`) because the resident dev server at `5173` serves the main branch (pre-merge). The CI harness at merge time will use the merged code. Verified via `PLAYWRIGHT_BASE_URL=http://localhost:5174`. ## Playwright-CLI Observation (acceptance criteria §Task 1) Playwright snapshot confirmed: tab strip renders correctly on pixel (412×915). Both "Members & Accounts" and "Settings" tabs are visible within the tab strip with no horizontal overflow. ArrowRight correctly moves `aria-selected` to the Settings tab. Toast `role="status"` is absent on initial page load as expected. ## Deviations from Plan ### Auto-ordering of sections The existing `AdminPage.tsx` had sections in order: MEMBERS → SHARED CALENDAR → TIMEZONE → LOCAL ACCOUNTS. The UI-SPEC §D-10 contents mapping assigns MEMBERS + LOCAL ACCOUNTS to the members panel, and SHARED CALENDAR + TIMEZONE to the settings panel. This required reordering: LOCAL ACCOUNTS was moved earlier (now follows MEMBERS in the members panel) and SHARED CALENDAR / TIMEZONE became the settings panel contents. This is a presentation change only — no mutation logic was touched. ### Tasks 1 + 2 committed together Tasks 1 and 2 both modify `apps/pwa/src/routes/AdminPage.tsx`. Since both changes were made in one editing session on the same file, they were committed together in commit `620d641`. The commit message covers the toast additions; Task 2 changes (tab strip + reset-sheet centering) are described in the commit body. ### Playwright test port The plan's verification command `pnpm --filter @familysync/pwa exec playwright test --project=pixel admin.spec.ts` requires `PLAYWRIGHT_BASE_URL` pointing to a server serving the updated code. The resident dev server at port 5173 serves the main branch. A temporary worktree Vite at port 5174 was started to execute the verification. 12/12 tests passed. CI will run against merged code where this is a non-issue. ## Known Stubs None. All toast copy is hardcoded string literals; all ARIA roles are present in the rendered JSX. ## Threat Flags None. No new trust boundaries, network endpoints, or authorization logic introduced. Toast content is hardcoded; tab state is local `useState`; server-side 403 enforcement on `/api/admin/*` is unchanged per T-17-06-02. ## Self-Check: PASSED | Item | Result | |------|--------| | 17-06-SUMMARY.md | FOUND | | apps/pwa/src/routes/AdminPage.tsx | FOUND | | apps/pwa/e2e/admin.spec.ts | FOUND | | Commit 620d641 (toasts + two-tab nav) | FOUND | | Commit 944045c (admin.spec.ts) | FOUND |