+```
+
+### Section Divider Pattern
+**Source:** `apps/pwa/src/components/SettingsSheet.tsx` lines 372–376
+**Apply to:** Logout row separator in SettingsSheet
+
+```tsx
+
+```
+
+### Mutation + Toast Pattern
+**Source:** `apps/pwa/src/routes/AdminPage.tsx` lines 197–235 (`createMemberMutation`), `apps/pwa/src/components/SyncStateToast.tsx` lines 72–79 (auto-dismiss), lines 159–189 (toast render)
+**Apply to:** AdminPage create-member and reset-password success feedback
+
+Auto-dismiss pattern from SyncStateToast (lines 72–79):
+```ts
+useEffect(() => {
+ if (status !== 'done') return; // adapt: if (!toast) return;
+ const timer = setTimeout(() => {
+ setLastSyncedUid(null); // adapt: setToast(null)
+ }, 2000); // use 3000ms for admin toasts per UI-SPEC
+ return () => clearTimeout(timer);
+}, [status, setLastSyncedUid]);
+```
+
+### Accessible Button Row Pattern
+**Source:** `apps/pwa/src/components/SettingsSheet.tsx` lines 390–410 (Change password button row)
+**Apply to:** Logout button in SettingsSheet
+
+```tsx
+
+```
+
+---
+
+## No Analog Found
+
+| File | Role | Data Flow | Reason |
+|------|------|-----------|--------|
+| `apps/pwa/public/logo.svg` | asset | transform | New AI-generated SVG logo — no existing brand mark in codebase |
+| `apps/pwa/public/favicon.svg`, `favicon.ico`, `icon-maskable-512.png` | asset | transform | New generated assets — none exist in `public/` yet |
+| `apps/pwa/pwa-assets.config.ts` | config | transform | New `@vite-pwa/assets-generator` config — no prior asset-generation config in repo |
+
+---
+
+## Critical Pitfalls (for Planner)
+
+1. **Maskable icon must be a separate file.** `icon-maskable-512.png` is a distinct generated asset with safe-zone padding — the defect is reusing `icon-512.png` for the maskable purpose. Fix by generating `icon-maskable-512.png` via `@vite-pwa/assets-generator`.
+
+2. **`paddingBottom` in `contentStyle` is phone-only.** The `...(phone ? {...} : {})` spread pattern (from outerStyle at App.tsx lines 144–152) ensures desktop gets no extra bottom padding.
+
+3. **`--sx-color-*` overrides must stay inside the combined `tokens.css` rule block.** Do not split them to a separate selector — they must override the `@schedule-x/theme-default` values by staying in the same specificity context.
+
+4. **`fetchLocalLogout` error must not prevent navigation.** Wrap in try/catch and call `navigate('/login')` in both branches — fire-and-best-effort semantics per UI-SPEC §D-07.
+
+5. **`--brand-logo-border-radius` update required after logo approval.** The current `50%` value (circle) clips an SVG logo that draws its own shape. Update to `12px` (warm/rounded) or `0` (if SVG has own border-radius) at the checkpoint — before wiring.
+
+6. **Admin tab state is not URL-persisted.** Tab state is `useState` only — navigating away and back resets to Tab 1 ("Members & Accounts"). This is intentional per UI-SPEC §D-10.
+
+---
+
+## Metadata
+
+**Analog search scope:** `apps/pwa/src/components/`, `apps/pwa/src/routes/`, `apps/pwa/src/api/`, `apps/pwa/e2e/`, `apps/pwa/`
+**Files read:** 10 source files
+**Pattern extraction date:** 2026-06-18
diff --git a/.planning/phases/17-ui-optimization-polish/17-VERIFICATION.md b/.planning/phases/17-ui-optimization-polish/17-VERIFICATION.md
new file mode 100644
index 0000000..66560f2
--- /dev/null
+++ b/.planning/phases/17-ui-optimization-polish/17-VERIFICATION.md
@@ -0,0 +1,119 @@
+---
+phase: 17-ui-optimization-polish
+verified: 2026-06-18T00:00:00Z
+status: human_needed
+score: 10/10 decisions delivered in code (1 WARNING-class resize limitation, 4 device-only checks)
+behavior_unverified: 0
+overrides_applied: 0
+human_verification:
+ - test: "On a phone (≤767px, e.g. iPhone 14 / Pixel 7) load /calendar and /lists. Confirm the New Event FAB sits fully above the BottomTabBar and the color-legend chips are not clipped behind the bar."
+ expected: "FAB floats above the tab bar (not on the Admin tab); legend chips fully visible; CI overlap guard (layout.spec.ts) corroborates geometry on iphone/pixel profiles."
+ why_human: "Pixel-accurate fixed-chrome overlap + safe-area-inset rendering on a real notched device cannot be fully judged from CSS source; CI guard runs headless WebKit/Chromium only."
+ - test: "Resize a desktop browser window narrower than 768px (or rotate a tablet/foldable across the 767px breakpoint) with the Settings sheet, a credential sheet, or the admin reset sheet OPEN."
+ expected: "Sheet should switch between centered-modal (desktop) and bottom-sheet (phone) geometry. NOTE: 17-REVIEW WR-01 documents that the `phone` snapshot is computed once per render with no matchMedia listener, so the sheet keeps stale geometry until an unrelated re-render. Confirm severity for this household's actual devices."
+ why_human: "Resize-crossing-breakpoint re-render behavior is a runtime interaction; the static branches are correct at mount but do not re-evaluate. Operator decides if this WARNING blocks (real phones are always phones; impact is tablets/foldables/desktop-resize)."
+ - test: "Install the PWA to a device home screen and inspect the app icon (especially Android adaptive/maskable rendering and iOS apple-touch-icon)."
+ expected: "Maskable icon (icon-maskable-512.png) shows the family-house logo inside the safe zone with no clipping; favicon shows in browser tab; apple-touch-icon shows on iOS home screen."
+ why_human: "Maskable safe-zone correctness and home-screen icon rendering are device/installer-specific (iOS-Safari standalone is device-only per CLAUDE.md); cannot be driven headless."
+ - test: "Sign in (local auth, non-dev-bypass) and open Settings; tap 'Sign out'."
+ expected: "Session is cleared and the app routes to /login; signing back in works. (Endpoint + client wiring verified in code; live round-trip confirms cookie clearing end-to-end.)"
+ why_human: "Full logout round-trip (cookie cleared server-side + redirect) is a runtime/auth flow; code path is verified but live confirmation is prudent."
+---
+
+# Phase 17: UI Optimization & Polish Verification Report
+
+**Phase Goal:** A visual-identity & polish pass for the PWA spanning three workstreams — (A) phone-layout polish (no fixed-chrome overlap), (B) branding assets (real logo + complete icon set), (C) theme-token groundwork — plus (D) UAT-surfaced UI fixes (logout, admin toasts, sheet centering, admin nav rework).
+**Verified:** 2026-06-18
+**Status:** human_needed
+**Re-verification:** No — initial verification
+
+## Goal Achievement
+
+Decisions D-01..D-10 stand in for REQ-IDs (no Success Criteria array in ROADMAP; goal is prose + decisions). All 10 decisions have corresponding, substantive, wired code in the codebase. The status is `human_needed` (not `passed`) because device-only checks (phone overlap on real hardware, PWA icon install, logout round-trip) and one WARNING-class resize limitation (17-REVIEW WR-01) require operator confirmation.
+
+### Observable Truths (by Decision)
+
+| # | Decision / Truth | Status | Evidence |
+| ---- | ---------------- | ------ | -------- |
+| D-01 | Phone FAB no longer overlaps BottomTabBar; legend not occluded | ✓ VERIFIED | FAB `bottom: calc(var(--bottom-chrome-h) + var(--space-6))` (CalendarShell.tsx:470); phone `paddingBottom: var(--bottom-chrome-h)` (App.tsx:164); `--bottom-chrome-h: calc(56px + env(safe-area-inset-bottom,0px))` (tokens.css:69). Matches ROADMAP fix sketch exactly. |
+| D-02 | Permanent overlap regression guard in CI | ✓ VERIFIED | layout.spec.ts:209-231 "New Event FAB does not overlap BottomTabBar" — asserts `fabBox.bottom ≤ navBox.top`, phone-only (iphone/pixel), skipped on desktop. Shared-token approach chosen (the planner's default lean). |
+| D-03 | Real FamilySync logo (warm/rounded/at-home) committed as SVG; operator-approved | ✓ VERIFIED | logo.svg (warm peach gradient bg, rounded family-house mark, 512 viewBox). Accent #e8915a + `--brand-logo-border-radius: 0` documented as operator-approved (tokens.css:41,106). |
+| D-04 | Complete 7-asset icon set; proper separate maskable | ✓ VERIFIED | All 7 files present in public/ (logo.svg, favicon.svg, favicon.ico, icon-192/512, icon-maskable-512, apple-touch-icon) at real sizes (not stubs). Maskable is its own 8627B file, distinct from icon-512 (12056B). Manifest references it with `purpose: 'maskable'` (vite.config.ts:41). |
+| D-05 | Logo wired into BrandSlot, decorative, no LoginPage layout change | ✓ VERIFIED | BrandSlot.tsx swaps placeholder div for ``; `
FamilySync
` retained as page title; tokens drive size/radius. Seam contract honored. |
+| D-06 | tokens.css restructured to themeable layer; Schedule-X overrides intact; no new hardcoded colors | ⚠️ PARTIAL | Combined `:root, [data-theme="light"]` selector (tokens.css:16-17); `--sx-color-*` overrides remain inside the block after the theme-default import (tokens.css:127+). Groundwork-only (no dark palette/toggle) per scope. **Caveat:** sheet-centering work (17-05/06) added hardcoded px literals (`12px`, `480px`, boxShadow rgba) in component files — but these match pre-existing patterns and the color invariant (hex in tokens.css) holds. |
+| D-07 | Reachable Sign out clears session + routes to /login; best-effort on API failure | ✓ VERIFIED | SettingsSheet.tsx:522-554 always-visible "Sign out" control → `handleSignOut` (137-146): try `fetchLocalLogout()`, catch swallows error, then unconditional `navigate('/login')`. |
+| D-08 | Admin create-member + reset-password success toasts, ~3s auto-dismiss | ✓ VERIFIED | AdminPage.tsx: toast state + 3000ms `setTimeout` auto-dismiss (62-69); "Member added." (261), "Password reset." (1090); `role="status"` live region (1030). |
+| D-09 | Dialogs/sheets centered on desktop, bottom-sheet on phone | ⚠️ PARTIAL | SettingsSheet (204-233), CredentialSheet (178+), AdminPage reset sheet (1419-1443) all have phone (`bottom:0`) vs desktop (`translate(-50%,-50%)`, maxWidth 480px) branches. **Caveat:** branch is a one-shot render snapshot (17-REVIEW WR-01) — does not re-evaluate on resize across 767px. Correct for real phones; stale on tablet/foldable/desktop-resize. |
+| D-10 | Two-tab ARIA strip with roving tabindex + ArrowLeft/Right keyboard nav | ✓ VERIFIED | AdminPage.tsx: `role="tablist"` (321), `role="tab"`+`aria-selected`+roving `tabIndex` 0/-1 (331-358), `handleTabKeyDown` ArrowRight/Left with focus management (205-220), `role="tabpanel"` ×2. CI-tested in admin.spec.ts (ArrowRight/Left switch + default selection). |
+
+**Score:** 10/10 decisions delivered in code (8 fully VERIFIED, 2 PARTIAL with documented WARNING-class caveats). 0 behavior-unverified, 0 FAILED, 0 BLOCKER.
+
+### Required Artifacts
+
+| Artifact | Expected | Status | Details |
+| -------- | -------- | ------ | ------- |
+| `apps/pwa/src/styles/tokens.css` | Themeable layer + `--bottom-chrome-h` | ✓ VERIFIED | Combined selector + token defined; sx overrides intact |
+| `apps/pwa/public/logo.svg` + 6 icons | Brand mark + complete set | ✓ VERIFIED | All 7 real files; maskable separate |
+| `apps/pwa/pwa-assets.config.ts` + package.json | generator config + script | ✓ VERIFIED | Both exist; reads logo.svg |
+| `apps/pwa/e2e/layout.spec.ts` | FAB↔BottomTabBar overlap guard | ✓ VERIFIED | D-01 regression test present |
+| `apps/pwa/src/components/BrandSlot.tsx` | logo img swapped in | ✓ VERIFIED | Decorative img, h1 retained |
+| `apps/pwa/index.html` | favicon links + theme-color | ✓ VERIFIED | favicon.svg + .ico + apple-touch + #e8915a |
+| `apps/pwa/src/components/SettingsSheet.tsx` | Sign out + centering | ✓ VERIFIED | handleSignOut + desktop branch |
+| `apps/pwa/src/components/CredentialSheet.tsx` | centering branch | ✓ VERIFIED | phone/desktop branch present |
+| `apps/pwa/src/routes/AdminPage.tsx` | toasts + tablist + reset centering | ✓ VERIFIED | All present |
+| `apps/pwa/e2e/admin.spec.ts` | tab ARIA + keyboard + toast tests | ✓ VERIFIED | 9 tab assertions + toast structure test |
+
+### Key Link Verification
+
+| From | To | Via | Status | Details |
+| ---- | -- | --- | ------ | ------- |
+| tokens.css | @schedule-x/theme-default | sx overrides after import | ✓ WIRED | tool-verified |
+| pwa-assets.config.ts | public/logo.svg | images: ['public/logo.svg'] | ✓ WIRED | tool-verified |
+| CalendarShell.tsx | tokens.css | FAB reads `var(--bottom-chrome-h)` | ✓ WIRED | **Manually verified** (CalendarShell.tsx:470) — gsd-tools reported false-negative due to over-escaped regex `var\\(--bottom-chrome-h\\)`; pattern is present and correct. |
+| App.tsx | tokens.css | phone paddingBottom reads token | ✓ WIRED | **Manually verified** (App.tsx:164) — same false-negative; pattern present. |
+| vite.config.ts | icon-maskable-512.png | manifest `purpose: maskable` | ✓ WIRED | tool-verified |
+| BrandSlot.tsx | public/logo.svg | img src /logo.svg | ✓ WIRED | tool-verified |
+| SettingsSheet.tsx | api/client.ts | handleSignOut calls fetchLocalLogout | ✓ WIRED | tool-verified |
+| AdminPage.tsx | SyncStateToast.tsx | reuse toast visual pattern | ✓ WIRED (pattern) | gsd-tools reported "target not referenced" — the plan `via` says reuse the *visual pattern*, not import. Toast uses `role="status"` + 3s auto-dismiss as specified (AdminPage.tsx:1030,66-69). Intent satisfied. |
+
+### Behavioral Spot-Checks
+
+| Behavior | Command | Result | Status |
+| -------- | ------- | ------ | ------ |
+| Overlap guard test exists | grep "New Event FAB does not overlap BottomTabBar" layout.spec.ts | 1 match | ✓ PASS |
+| Admin tab tests exist | grep "getByRole('tab'" admin.spec.ts | 9 matches | ✓ PASS |
+| Tab keyboard switching exercised in CI | ArrowRight/ArrowLeft → aria-selected assertions | present (admin.spec.ts:134-151) | ✓ PASS (behavior-dependent truth has CI coverage) |
+| All 7 branding assets present + real sizes | ls public/ + file | logo.svg, favicon.svg/.ico, icon-192/512, maskable-512, apple-touch all >900B PNG/SVG | ✓ PASS |
+
+Production build / typecheck / eslint (0 warnings) / 266 unit tests already passing per phase context — relied upon, not re-run.
+
+### Requirements Coverage
+
+No REQ-IDs; D-01..D-10 serve as requirements. Coverage: D-01/D-02→17-03 ✓, D-03/D-04→17-02 ✓, D-04/D-05→17-04 ✓, D-06→17-01 ✓(⚠), D-07/D-09→17-05 ✓(⚠), D-08/D-09/D-10→17-06 ✓. All decisions mapped to a plan and delivered.
+
+### Anti-Patterns Found
+
+| File | Line | Pattern | Severity | Impact |
+| ---- | ---- | ------- | -------- | ------ |
+| SettingsSheet/CredentialSheet/AdminPage | sheet branches | Hardcoded px (`12px`, `480px`, boxShadow rgba) in component files | ℹ️ Info | Layout primitives, not colors; matches pre-existing patterns; color invariant (hex→tokens.css) intact |
+| SettingsSheet.tsx | various | `phone = matchMedia(...)` one-shot, no listener | ⚠️ Warning | 17-REVIEW WR-01 — stale sheet/FAB geometry on resize across 767px (tablet/foldable/desktop-resize). Not a blocker for real phones. |
+| AdminPage / dialogs | — | Hardcoded z-index literals (300/301/302/303) | ℹ️ Info | 17-REVIEW WR — duplicated magic numbers, no current layering bug |
+
+No TBD/FIXME/XXX debt markers found in phase-modified files. No BLOCKER-class issues (17-REVIEW: 0 critical).
+
+### Human Verification Required
+
+See frontmatter `human_verification` — 4 items: (1) phone overlap on real device, (2) **resize-across-breakpoint sheet geometry [WR-01 severity call]**, (3) PWA maskable/home-screen icon install, (4) logout round-trip. Items 1 and 3 are genuinely device-only (per CLAUDE.md iOS-Safari/install exception). Items 1, 2, 4 could alternatively be spot-checked via playwright-cli on Chromium if desired.
+
+### Gaps Summary
+
+No goal-blocking gaps. Every decision D-01..D-10 is delivered with substantive, wired code, and the two automated CI guards the phase promised (FAB overlap in layout.spec.ts, admin tab ARIA/keyboard in admin.spec.ts) exist and assert real geometry/state transitions. The phase achieves its goal in the codebase.
+
+Two WARNING-class caveats (both pre-documented in 17-REVIEW, both WARNING not BLOCKER) and four human/device confirmations keep the verdict at `human_needed` rather than `passed`:
+- **WR-01 (resize snapshot)** is the one substantive behavioral caveat — sheet/FAB geometry does not re-evaluate when the viewport crosses 767px after mount. For the two-person household's actual phones this is invisible (a phone is always a phone); it only manifests on tablet rotation or desktop-window narrowing. Operator should confirm this is acceptable for the milestone or fold the `useIsPhone()` fix (already sketched in 17-REVIEW) into a follow-up.
+- The hardcoded-px additions in sheet branches are a minor invariant softening (D-06 is primarily a *color* invariant, which holds), recorded as Info.
+
+---
+
+_Verified: 2026-06-18_
+_Verifier: Claude (gsd-verifier)_