Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 KiB
phase, verified, status, score, behavior_unverified, overrides_applied, human_verification
| phase | verified | status | score | behavior_unverified | overrides_applied | human_verification | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-ui-optimization-polish | 2026-06-18T00:00:00Z | human_needed | 10/10 decisions delivered in code (1 WARNING-class resize limitation, 4 device-only checks) | 0 | 0 |
|
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)