Files
familysync/.planning/phases/17-ui-optimization-polish/17-VERIFICATION.md
T

120 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 `<img src="/logo.svg" alt="" aria-hidden="true">`; `<h1>FamilySync</h1>` 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)_