# Phase 17: UI Optimization & Polish - Context **Gathered:** 2026-06-17 **Status:** Ready for planning ## Phase Boundary A **visual-identity & polish pass** for the PWA, spanning four bounded workstreams (A–C from this discussion; D added from Phase 19 UAT, see Decisions §D): - **A — Phone-layout polish.** Fix the long-standing phone (≤767px) fixed-chrome overlap where the `position: fixed` BottomTabBar covers the New Event FAB (FAB lands on the Admin tab) and occludes the bottom of the calendar + the colour-legend chips, then sweep other small-viewport spacing / tap-target / overflow issues. CSS/layout only, no behaviour change. - **B — Branding assets.** Generate a real **FamilySync** logo and drop it into the already-built `BrandSlot` seam (`apps/pwa/src/components/BrandSlot.tsx`), and produce a **complete favicon / PWA-icon set** to replace the placeholder stubs in `apps/pwa/public/` (`icon-192.png` 699 B, `icon-512.png`, `apple-touch-icon.png` 617 B — all generated stubs; there is currently **no `favicon.ico`/`favicon.svg`** and index.html links only the apple-touch icon). - **C — Theme-token groundwork.** Restructure `apps/pwa/src/styles/tokens.css` from its single light `:root` into a **themeable semantic-token layer** (swappable by `data-theme` / `prefers-color-scheme`). Light stays the only *shipped* theme — this is enabling groundwork only. - **D — UAT-surfaced UI fixes** (from Phase 19 live UAT). Wire a **logout control** to the existing `fetchLocalLogout()` (no backend), add **success feedback** to admin create/reset-password flows, fix **dialog/popup bottom-center positioning**, and **rework the clunky admin navigation**. See Decisions §D (D-07…D-10). **Out of scope (explicitly deferred this discussion, 2026-06-17):** - **Shipped dark theme + light/dark/system toggle** → backlog **999.20** (Phase 17's token groundwork is the enabling seam). - **Broader "modern styling" visual refresh** (contemporary restyle of login/calendar/event-form/lists/admin) → backlog **999.21**, flagged for a **future milestone** — a redesign track, not a polish phase. This stays a focused polish + branding + groundwork pass, **not a redesign**. ## Implementation Decisions ### A — Phone-layout polish - **D-01:** Fix the seed defect (BottomTabBar overlapping the FAB + colour legend) AND run a bounded small-viewport sweep — the Phase 7 `layout.spec.ts` assertions (tap targets ≥44px, no horizontal overflow, critical elements in-viewport, accessible names) are the checklist; fix what they flag across phone routes. Bounded and checklist-driven, not a free-form audit. - **D-02:** **Fix technique and regression-guard mechanism are the researcher's call** (deferred from discussion). Inputs the researcher must weigh: the ROADMAP/todo fix sketch (lift FAB to `bottom: calc(56px + env(safe-area-inset-bottom,0px) + var(--space-6))` + matching content `padding-bottom`, OR shrink the `100dvh` column by the bar height) vs. a single shared `--bottom-chrome-h` token consumed by both the FAB offset and the content padding (single source of truth). Researcher also decides whether to add a permanent overlap assertion to `layout.spec.ts` (FAB/legend must not intersect the BottomTabBar rect on phone profiles) vs. playwright-cli manual verification only. **Default lean if evidence is neutral:** shared token + add the CI assertion (hardest to regress), but this is the researcher's decision to make on the merits. ### B — Branding assets - **D-03:** **Logo + full icon set are AI-generated in-phase** (option 2a). Claude generates the logo and the complete icon/favicon set from the brief below, wires them in (BrandSlot ``, `apps/pwa/public/` files, `index.html` ``s, and the `vite-plugin-pwa` manifest `icons[]` in `apps/pwa/vite.config.ts`), and the user approves the result before it's final. No external designer / user-supplied art. - **D-04:** Deliver a **complete** icon set, not just a logo: `favicon.ico` + `favicon.svg`, `icon-192.png`, `icon-512.png`, a **proper maskable** 512 (the current manifest reuses the non-maskable 512 as maskable — a real maskable needs safe-zone padding), and `apple-touch-icon.png` (180×180). Update `index.html` (add the missing ``s; `theme-color` currently `#4A90D9`) and the `vite.config.ts` manifest to reference them. - **D-05:** Drive the logo through the existing `BrandSlot` seam contract — swap the placeholder `
` for an `` and override the `--brand-logo-*` tokens — **without changing `LoginPage` layout** (the seam was built in Phase 19 precisely to isolate this). Keep the accessibility shape: `

` carries the app name, logo image is decorative (`alt=""` / `aria-hidden`), no layout shift. ### C — Theme-token groundwork - **D-06:** Restructure `tokens.css` into a themeable layer (semantic tokens resolvable per theme via `data-theme`/`prefers-color-scheme`) — **groundwork only** (option 3a). **Do NOT** author dark palette values, wire `prefers-color-scheme` to actually flip, or add a toggle this phase. Light remains the sole shipped theme. Keep the existing invariant: no hard-coded hex/px in component files — all values stay in `tokens.css`. The restructure must leave the Schedule-X `--sx-color-*` overrides (bottom of tokens.css) working unchanged. ### D — UAT-surfaced UI findings (from Phase 19 live UAT, 2026-06-17) Surfaced by the operator during the Phase 19 local-auth UAT and **routed here by operator decision** — these are UI concerns, not Phase 19 auth blockers. Phase 19 ships functionally complete; Phase 17 owns the UI. (Recorded in `.planning/phases/19-local-auth-no-oidc-mode/19-UAT.md` as F-01…F-04.) - **D-07 (F-02) — Wire a logout control into the UI.** Logout is fully plumbed but unreachable: the endpoint `POST/GET /api/auth/local/logout` works (200, clears cookie — BL-02 verified live) and `fetchLocalLogout()` exists at `apps/pwa/src/api/client.ts:127`, but **no component calls it** (zero logout buttons in `apps/pwa/src`). Add a logout control (likely in `SettingsSheet.tsx` and/or `AppNav`) that calls the existing client function + redirects to `/login`. **No backend work** — UI wiring only. (Slightly beyond pure "polish" — it's a small new control; planner should size it.) - **D-08 (F-01) — Add success feedback to admin local-account actions.** Admin create-member and reset-password both succeed (verified at DB/login level in UAT) but show **no success toast/confirmation**, leaving the operator unsure it worked. Add success feedback to those admin flows (`AdminPage.tsx`). - **D-09 (F-03) — Fix dialog/popup positioning.** Popups/sheets render **bottom-center instead of properly centered**. Fits the fixed-chrome/sheet-positioning sweep already in workstream A; likely the same dialog/sheet CSS (`SettingsSheet.tsx` and shared dialog styles). Verify across phone + desktop via playwright-cli. - **D-10 (F-04) — Rework the clunky admin navigation.** Admin UI navigation reads as clunky and needs a rework. Larger UX item than the others — planner should decide whether it fits this phase's "polish" budget or warrants its own slice. (`AdminPage.tsx`, admin nav/tab surface.) ### Claude's Discretion - **D-02** (fix technique + regression guard) is explicitly delegated to the researcher/planner. - Exact small-viewport issues surfaced by the `layout.spec` sweep (D-01) — fix as found, within the no-behaviour-change boundary. - Logo visual execution within the brand brief (see Specific Ideas) — subject to user approval at the checkpoint. ### Folded Todos - **`2026-06-13-pwa-phone-bottombar-overlap.md`** (`area: pwa-ui`, `resolves_phase: 17`) — the phase's seed defect. Phone-layout fixed BottomTabBar overlaps the New Event FAB + colour legend at ≤767px. Reproduced 2026-06-13 via playwright-cli at 390×844 (FAB over Admin tab; "Dev User" legend clipped) vs 1280×800 (no overlap). Long-standing (BottomTabBar dates to Phase 04), not a Phase 10 regression. This is workstream A's anchor — its `files:` list (`App.tsx`, `BottomTabBar.tsx`, `CalendarShell.tsx`) is the fix surface. ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Phase scope & seed defect - `.planning/ROADMAP.md` §"Phase 17: UI Optimization & Polish" — goal, three-workstream scope, scope boundary, seed-defect detail + CSS fix sketch. - `.planning/todos/pending/2026-06-13-pwa-phone-bottombar-overlap.md` — the folded seed-defect todo (repro, fix sketch, affected files). - `.planning/ROADMAP.md` §"Phase 999.20" / §"Phase 999.21" — the deferred dark-mode and styling-refresh backlog items (what is explicitly NOT in this phase). ### A — Phone layout (fix surface) - `apps/pwa/src/App.tsx` — `window.matchMedia('(max-width: 767px)')` phone breakpoint + `contentStyle` (currently reserves no `padding-bottom` for the fixed bar). - `apps/pwa/src/components/BottomTabBar.tsx` — `position: fixed; height: calc(56px + env(safe-area-inset-bottom)); z-index: 200`. - `apps/pwa/src/components/CalendarShell.tsx` — the "New Event" FAB (`position: fixed; bottom: var(--space-6); right: var(--space-6)`). - `apps/pwa/e2e/layout.spec.ts` — Phase 7 quality-bar assertions (UI-SPEC Rules 1–4: ≥44px tap targets, no overflow, in-viewport, accessible names); the sweep checklist AND the home for any new overlap regression assertion. - `apps/pwa/playwright.config.ts` — `iphone` (iPhone 14/WebKit 390×844), `pixel` (Pixel 7/Chromium 412×915), `desktop` (Desktop Chrome 1280×720) profiles to verify across. ### B — Branding - `apps/pwa/src/components/BrandSlot.tsx` — the Phase-17 branding seam; header documents the `--brand-logo-*` token contract and the swap-div-for-`` plan. - `.planning/phases/19-local-auth-no-oidc-mode/19-UI-SPEC.md` §Brand Slot — the seam's design contract (referenced by BrandSlot.tsx). - `apps/pwa/public/` — `apple-touch-icon.png`, `icon-192.png`, `icon-512.png` (placeholder stubs to replace). - `apps/pwa/index.html` — current `` + `theme-color` meta (no `` yet). - `apps/pwa/vite.config.ts` §`VitePWA({ manifest: { icons: [...] } })` (lines ~29–41) — PWA manifest icon list to update (incl. the improper maskable reuse). ### C — Theming - `apps/pwa/src/styles/tokens.css` — the single light `:root` token system to restructure into a themeable layer (note the Schedule-X `--sx-color-*` overrides at the bottom and the `--brand-logo-*` defaults). ## Existing Code Insights ### Reusable Assets - **`BrandSlot` seam** (`apps/pwa/src/components/BrandSlot.tsx`): purpose-built in Phase 19 to absorb the real logo with zero `LoginPage` layout change — swap the placeholder div for an `` and set `--brand-logo-*`. The branding workstream is wiring, not new architecture. - **`layout.spec.ts` + 3 Playwright profiles** (Phase 7/14): a ready, CI-wired structural quality bar (tap targets / overflow / in-viewport / a11y names) — doubles as the sweep checklist (D-01) and the natural home for a regression guard (D-02). - **CSS custom-property token system** (`tokens.css`): everything is already a variable with "no hard-coded hex/px in components" enforced — the themeable-layer restructure (D-06) builds on an already-favourable structure rather than fighting inline values. - **`vite-plugin-pwa` manifest** (`vite.config.ts`): the icon set is declared in one place; replacing stubs = update files in `public/` + the manifest `icons[]` + `index.html` links. ### Established Patterns - **Phone/desktop split at 767/768px** via `matchMedia` in `App.tsx`; phone uses top AppNav (PhoneNav `
`) + fixed BottomTabBar (sole `