--- phase: 17 slug: ui-optimization-polish status: draft nyquist_compliant: false wave_0_complete: false created: 2026-06-18 --- # Phase 17 — Validation Strategy > Per-phase validation contract for feedback sampling during execution. > Source: `17-RESEARCH.md` §Validation Architecture. Requirement IDs are TBD for this > phase (UI/UX polish + branding) — decisions D-01…D-10 (CONTEXT.md) and workstreams > A–D (UI-SPEC.md) stand in for REQ-IDs until promoted in plan-phase. --- ## Test Infrastructure | Property | Value | |----------|-------| | **E2E Framework** | Playwright 1.60.0 | | **Unit Framework** | Vitest ^4.1.8 | | **E2E config file** | `apps/pwa/playwright.config.ts` (profiles: `iphone` 390×844 WebKit, `pixel` 412×915 Chromium, `desktop` 1280×720) | | **Unit config file** | `apps/pwa/vitest.config.ts` | | **Quick run command** | `pnpm --filter @familysync/pwa exec playwright test --project=pixel layout.spec.ts` | | **Full suite command** | `pnpm --filter @familysync/pwa test:e2e` | | **Build gate** | `pnpm --filter @familysync/pwa build` | | **Estimated runtime** | ~45–90 seconds (quick: ~15s; full 3-profile suite: ~60–90s) | | **Browser-verification skill** | `playwright-cli` (desktop/Chromium UI checks the spec suite cannot cover) | --- ## Sampling Rate - **After every task commit:** Run `pnpm --filter @familysync/pwa exec playwright test --project=pixel layout.spec.ts` (phone profile covers the primary fix geometry) - **After every plan wave:** Run `pnpm --filter @familysync/pwa test:e2e` (full 3-profile suite) + `pnpm --filter @familysync/pwa build` - **Before `/gsd-verify-work`:** Full Playwright suite green + playwright-cli visual sweeps passed + **human logo approval received** (Workstream B checkpoint) - **Max feedback latency:** ~90 seconds --- ## Per-Task Verification Map > Task IDs are assigned by the planner. This map is workstream/decision-keyed; the executor > binds each task ID to the matching row's automated command during execution. | Decision | Workstream | Behavior | Test Type | Automated Command / Method | Automated? | Status | |----------|-----------|----------|-----------|----------------------------|------------|--------| | D-01/D-02 | A | FAB bottom edge ≤ BottomTabBar top edge on iphone/pixel | Playwright geometry assertion | **New** assertion in `apps/pwa/e2e/layout.spec.ts` (skipped on desktop) | CI (iphone + pixel) | ⬜ pending | | D-01 | A | Color-legend chips visible; content scrollable above bar | Playwright + playwright-cli sweep | `layout.spec.ts` full suite + manual scroll | Partial | ⬜ pending | | D-01 | A | No horizontal overflow after fix (`scrollWidth ≤ clientWidth`) | Playwright Rule 2 | Existing `layout.spec.ts` Rule 2 | CI (all profiles) | ⬜ pending | | D-01 | A | Tap targets preserved (≥44px / ≥56px) | Playwright Rule 1 | Existing `layout.spec.ts` Rule 1 | CI (all profiles) | ⬜ pending | | D-03/D-04 | B | All 7 asset files present in `apps/pwa/public/` | Shell existence check | `ls apps/pwa/public/{logo.svg,favicon.svg,favicon.ico,icon-192.png,icon-512.png,icon-maskable-512.png,apple-touch-icon.png}` | CI gate / Wave 0 | ⬜ pending | | D-04 | B | `favicon.ico` is multi-size & non-trivial (>100 bytes) | Shell | `test $(wc -c < apps/pwa/public/favicon.ico) -gt 100` | CI gate | ⬜ pending | | D-04 | B | `icon-maskable-512.png` is exactly 512×512 (real safe-zone) | Node + sharp metadata | `node -e "require('sharp')('apps/pwa/public/icon-maskable-512.png').metadata().then(m=>console.log(m.width,m.height))"` | Scripted | ⬜ pending | | D-04 | B | PWA manifest + index.html icon links resolve (no 404) | Playwright | `page.goto('/')`, devtools manifest / network check | playwright-cli | ⬜ pending | | D-03 | B | Logo meets warm/rounded/at-home brief | Human review | playwright-cli screenshot + operator approval | **Human checkpoint** | ⬜ pending | | D-05 | B | BrandSlot swap: no LoginPage layout shift; `

` retains name; img `alt=""` | Playwright + playwright-cli | login route render + a11y name check | playwright-cli | ⬜ pending | | D-06 | C | No hard-coded hex/px literals in component files | `grep` | `grep -rnE '#[0-9a-fA-F]{3,6}\|[0-9]+px' apps/pwa/src/components/ apps/pwa/src/routes/` (expect token refs only) | CI gate | ⬜ pending | | D-06 | C | Schedule-X `--sx-color-*` overrides still apply post-restructure | playwright-cli | visual sweep of `/calendar` | playwright-cli | ⬜ pending | | D-06 | C | TypeScript + Vite build exits 0 | Build | `pnpm --filter @familysync/pwa build` | CI | ⬜ pending | | D-07 | D | "Sign out" control renders in SettingsSheet | Playwright + playwright-cli | open settings sheet, confirm button + aria-label | playwright-cli | ⬜ pending | | D-07 | D | Logout calls `fetchLocalLogout()` then navigates to `/login` | Playwright interaction | click Sign out, confirm redirect | playwright-cli | ⬜ pending | | D-08 | D | "Member added." toast on create success | Playwright interaction | admin: create member, confirm toast (role=status) | playwright-cli (admin) | ⬜ pending | | D-08 | D | "Password reset." toast on reset success | Playwright interaction | admin: reset password, confirm toast | playwright-cli (admin) | ⬜ pending | | D-08 | D | Toast auto-dismisses after ~3s | Playwright interaction | wait 3.5s, confirm gone | playwright-cli | ⬜ pending | | D-09 | D | Sheet centered on desktop (`top/left 50%`, transform) | Playwright geometry | playwright-cli @1280×720 SettingsSheet geometry | playwright-cli (desktop) | ⬜ pending | | D-09 | D | Sheet renders as bottom-sheet on phone | Playwright geometry | playwright-cli @390×844 geometry | playwright-cli (phone) | ⬜ pending | | D-10 | D | Admin two-tab strip renders with correct labels | Playwright | `/admin`: confirm "Members & Accounts" + "Settings" | playwright-cli (admin) | ⬜ pending | | D-10 | D | ArrowLeft/ArrowRight switches tabs (roving tabindex) | Playwright keyboard | focus tab, ArrowRight, confirm active | playwright-cli | ⬜ pending | | D-10 | D | Admin tab ARIA roles present (`tablist`/`tab`/`aria-selected`/`tabpanel`) | Playwright | **New** assertion in `admin.spec.ts` or `layout.spec.ts` via `getByRole` | CI | ⬜ pending | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* --- ## Wave 0 Requirements - [ ] **Overlap regression assertion** — add the FAB↔BottomTabBar geometry test to `apps/pwa/e2e/layout.spec.ts` (Workstream A; runs on iphone + pixel, skipped on desktop). Contract in UI-SPEC §"Regression guard". - [ ] **Admin tab ARIA assertion** — add `role="tablist"`/`role="tab"`/`aria-selected`/`role="tabpanel"` checks to `apps/pwa/e2e/admin.spec.ts` (or append to `layout.spec.ts`) (Workstream D-10). - [ ] **Asset existence check** — Wave 0 npm script or inline CI step asserting all 7 `apps/pwa/public/` assets exist with valid format (ICO size, maskable dimensions). - [ ] **`@vite-pwa/assets-generator` devDependency** — install in `apps/pwa` (pulls `sharp` + `sharp-ico`; the only new package) before Workstream B asset derivation runs. *Existing infrastructure (Playwright 3-profile config + `layout.spec.ts` Rules 1–4 + Vitest) covers the bulk of the structural assertions.* --- ## Manual-Only Verifications | Behavior | Decision | Why Manual | Test Instructions | |----------|----------|------------|-------------------| | Logo / icon set meets warm/rounded/at-home brief | D-03 | Aesthetic judgment; AI-generated art needs operator sign-off | playwright-cli screenshot of BrandSlot on `/login` + favicon in tab; operator approves at checkpoint before wiring is committed | | Brand accent selection (cool-blue vs warm rose/amber) | UI-SPEC Q1 | Subjective brand decision | Present both variants (one-line token swap); operator selects at logo checkpoint | | Maskable adaptive-icon render on a real device | D-04 | iOS/Android home-screen mask is device-only (cannot be driven by playwright-cli) | Install PWA on a device, confirm logo within safe-zone (optional spot-check; format validated automatically) | | Schedule-X calendar colors visually unchanged post-restructure | D-06 | Visual regression of third-party calendar theme | playwright-cli sweep of `/calendar` before/after the tokens.css restructure | --- ## Validation Sign-Off - [ ] All tasks have an automated verify command or a Wave 0 dependency - [ ] Sampling continuity: no 3 consecutive tasks without automated verify - [ ] Wave 0 covers all MISSING references (overlap assertion, admin ARIA assertion, asset-existence check, assets-generator install) - [ ] No watch-mode flags (`playwright test` / `vitest run` are one-shot) - [ ] Feedback latency < 90s - [ ] `nyquist_compliant: true` set in frontmatter **Approval:** pending