Phase 17: UI Optimization & Polish 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). Status: Verified ✓ (automated + playwright-cli UAT; device-only checks deferred — see Verification)
This phase fixes the long-standing phone-layout overlap where the fixed BottomTabBar covered the New Event FAB and the colour legend, ships a real FamilySync brand mark (warm family-house SVG) with a complete 7-asset favicon/PWA-icon set replacing the placeholder stubs, and restructures tokens.css into a themeable semantic-token layer (light-only groundwork for a future dark theme). It also folds in four UI fixes surfaced during prior UAT: a reachable Sign out control, admin success toasts, desktop-centered sheets, and an accessible two-tab admin navigation. Dark theme and a broader styling refresh were deferred to backlog (999.20 / 999.21).
Changes
Plan 17-01: CSS token groundwork
Restructure tokens.css to a combined :root, [data-theme="light"] selector and add the --bottom-chrome-h layout token. Key files:apps/pwa/src/styles/tokens.css
Plan 17-02: Branding assets
Author the approved FamilySync logo SVG and generate the full icon/favicon set via @vite-pwa/assets-generator. Key files (created):apps/pwa/public/{logo.svg,favicon.svg,favicon.ico,icon-192.png,icon-512.png,icon-maskable-512.png,apple-touch-icon.png}, apps/pwa/pwa-assets.config.ts · modified:apps/pwa/package.json
Plan 17-03: Phone layout overlap fix
Lift the FAB above the BottomTabBar (calc(var(--bottom-chrome-h) + var(--space-6))), reserve phone content padding, and add a CI overlap regression guard. Key files:apps/pwa/src/components/CalendarShell.tsx, apps/pwa/src/App.tsx, apps/pwa/e2e/layout.spec.ts
Plan 17-04: Brand wiring
Wire the logo into BrandSlot, hook up favicons + theme-color, fix the maskable icon reference, and apply the #e8915a brand accent. Key files:apps/pwa/src/components/BrandSlot.tsx, apps/pwa/src/styles/tokens.css, apps/pwa/index.html, apps/pwa/vite.config.ts
Plan 17-05: Logout control + sheet centering
Add a reachable, best-effort Sign out control and a phone/desktop centering branch to the settings/credential sheets. Key files:apps/pwa/src/components/SettingsSheet.tsx, apps/pwa/src/components/CredentialSheet.tsx
Plan 17-06: Admin polish
Add create-member / reset-password success toasts, an accessible two-tab ARIA navigation (roving tabindex, arrow/Home/End), and reset-sheet centering. Key files:apps/pwa/src/routes/AdminPage.tsx, apps/pwa/e2e/admin.spec.ts
Requirements Addressed
No REQ-IDs in ROADMAP; decisions D-01..D-10 serve as the requirement set (all delivered, mapped to plans):
D-01 / D-02 → 17-03: phone FAB no longer overlaps BottomTabBar; permanent CI overlap guard
D-03 / D-04 → 17-02: operator-approved FamilySync logo SVG; complete 7-asset icon set with a proper separate maskable
D-05 → 17-04: logo wired into BrandSlot, decorative, no LoginPage layout change
D-06 → 17-01: tokens.css restructured to a themeable layer (Schedule-X overrides intact) ⚠️ PARTIAL
D-07 / D-09 → 17-05: reachable Sign out (best-effort on API failure); desktop-centered sheets ⚠️ PARTIAL (D-09)
D-08 / D-10 → 17-06: admin success toasts (~3s auto-dismiss); two-tab ARIA strip with roving tabindex + keyboard nav
Score: 10/10 decisions delivered in code (8 fully VERIFIED, 2 PARTIAL with documented WARNING-class caveats); 0 FAILED, 0 BLOCKER.
Verification
Automated verification: 10/10 decisions delivered in code; two CI guards present (layout.spec.ts FAB overlap, admin.spec.ts tab ARIA/keyboard)
Known WARNING-class limitation (17-REVIEW WR-01): sheet/FAB phone-vs-desktop geometry is a one-shot render snapshot — it does not re-evaluate when the viewport crosses 767px after mount. Invisible on real phones (a phone is always a phone); only manifests on tablet rotation or desktop-window resize. useIsPhone() fix sketched in 17-REVIEW for a follow-up.
Key Decisions
--brand-logo-border-radius: 0 — the logo SVG draws its own rounded-square background (rx=104); no extra CSS clip needed
Brand accent --color-member-0 → #e8915a (warm amber), applied across tokens.css, index.html theme-color, and the Vite manifest
favicon.svg is an exact copy of logo.svg; modern browsers prefer the SVG favicon, favicon.ico is the legacy fallback
Phone detection via render-time window.matchMedia (matching the established App.tsx / BottomTabBar.tsx pattern) rather than a new hook
Maskable icon kept as its own distinct file (icon-maskable-512.png) with safe-zone padding, separate from the full-bleed icon-512.png
TDD Audit
No commit on this branch carries a gate_status: trailer, so every commit normalizes to missing. The phase did follow a test-first pattern for its CI guards — the RED commits below precede their GREEN implementation:
## Summary
**Phase 17: UI Optimization & Polish**
**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).
**Status:** Verified ✓ (automated + playwright-cli UAT; device-only checks deferred — see Verification)
This phase fixes the long-standing phone-layout overlap where the fixed `BottomTabBar` covered the New Event FAB and the colour legend, ships a real FamilySync brand mark (warm family-house SVG) with a complete 7-asset favicon/PWA-icon set replacing the placeholder stubs, and restructures `tokens.css` into a themeable semantic-token layer (light-only groundwork for a future dark theme). It also folds in four UI fixes surfaced during prior UAT: a reachable Sign out control, admin success toasts, desktop-centered sheets, and an accessible two-tab admin navigation. Dark theme and a broader styling refresh were deferred to backlog (999.20 / 999.21).
## Changes
### Plan 17-01: CSS token groundwork
Restructure `tokens.css` to a combined `:root, [data-theme="light"]` selector and add the `--bottom-chrome-h` layout token.
**Key files:** `apps/pwa/src/styles/tokens.css`
### Plan 17-02: Branding assets
Author the approved FamilySync logo SVG and generate the full icon/favicon set via `@vite-pwa/assets-generator`.
**Key files (created):** `apps/pwa/public/{logo.svg,favicon.svg,favicon.ico,icon-192.png,icon-512.png,icon-maskable-512.png,apple-touch-icon.png}`, `apps/pwa/pwa-assets.config.ts` · **modified:** `apps/pwa/package.json`
### Plan 17-03: Phone layout overlap fix
Lift the FAB above the `BottomTabBar` (`calc(var(--bottom-chrome-h) + var(--space-6))`), reserve phone content padding, and add a CI overlap regression guard.
**Key files:** `apps/pwa/src/components/CalendarShell.tsx`, `apps/pwa/src/App.tsx`, `apps/pwa/e2e/layout.spec.ts`
### Plan 17-04: Brand wiring
Wire the logo into `BrandSlot`, hook up favicons + `theme-color`, fix the maskable icon reference, and apply the `#e8915a` brand accent.
**Key files:** `apps/pwa/src/components/BrandSlot.tsx`, `apps/pwa/src/styles/tokens.css`, `apps/pwa/index.html`, `apps/pwa/vite.config.ts`
### Plan 17-05: Logout control + sheet centering
Add a reachable, best-effort Sign out control and a phone/desktop centering branch to the settings/credential sheets.
**Key files:** `apps/pwa/src/components/SettingsSheet.tsx`, `apps/pwa/src/components/CredentialSheet.tsx`
### Plan 17-06: Admin polish
Add create-member / reset-password success toasts, an accessible two-tab ARIA navigation (roving tabindex, arrow/Home/End), and reset-sheet centering.
**Key files:** `apps/pwa/src/routes/AdminPage.tsx`, `apps/pwa/e2e/admin.spec.ts`
## Requirements Addressed
No REQ-IDs in ROADMAP; decisions D-01..D-10 serve as the requirement set (all delivered, mapped to plans):
- **D-01 / D-02** → 17-03: phone FAB no longer overlaps `BottomTabBar`; permanent CI overlap guard
- **D-03 / D-04** → 17-02: operator-approved FamilySync logo SVG; complete 7-asset icon set with a proper separate maskable
- **D-05** → 17-04: logo wired into `BrandSlot`, decorative, no `LoginPage` layout change
- **D-06** → 17-01: `tokens.css` restructured to a themeable layer (Schedule-X overrides intact) ⚠️ PARTIAL
- **D-07 / D-09** → 17-05: reachable Sign out (best-effort on API failure); desktop-centered sheets ⚠️ PARTIAL (D-09)
- **D-08 / D-10** → 17-06: admin success toasts (~3s auto-dismiss); two-tab ARIA strip with roving tabindex + keyboard nav
**Score:** 10/10 decisions delivered in code (8 fully VERIFIED, 2 PARTIAL with documented WARNING-class caveats); 0 FAILED, 0 BLOCKER.
## Verification
- [x] Automated verification: 10/10 decisions delivered in code; two CI guards present (`layout.spec.ts` FAB overlap, `admin.spec.ts` tab ARIA/keyboard)
- [x] UAT (playwright-cli, Chromium): 9/10 passed, 0 issues — cold start, login branding, logout, sheet centering, modal focus trap, admin two-tab nav, admin success toast all verified live
- [x] Security: `17-SECURITY.md` `threats_open: 0` (status verified)
**Deferred — device-only human checkpoints** (cannot be driven headless per CLAUDE.md; phase shipped with these outstanding):
- [ ] Phone (≤767px notched device): FAB floats above `BottomTabBar`, legend chips not clipped behind it
- [ ] PWA home-screen install: maskable/adaptive icon safe-zone + `apple-touch-icon` rendering
- [ ] iOS standalone PWA behavior (install + home-screen icon + push)
**Known WARNING-class limitation (17-REVIEW WR-01):** sheet/FAB phone-vs-desktop geometry is a one-shot render snapshot — it does not re-evaluate when the viewport crosses 767px after mount. Invisible on real phones (a phone is always a phone); only manifests on tablet rotation or desktop-window resize. `useIsPhone()` fix sketched in 17-REVIEW for a follow-up.
## Key Decisions
- `--brand-logo-border-radius: 0` — the logo SVG draws its own rounded-square background (rx=104); no extra CSS clip needed
- Brand accent `--color-member-0 → #e8915a` (warm amber), applied across `tokens.css`, `index.html` theme-color, and the Vite manifest
- `favicon.svg` is an exact copy of `logo.svg`; modern browsers prefer the SVG favicon, `favicon.ico` is the legacy fallback
- Phone detection via render-time `window.matchMedia` (matching the established `App.tsx` / `BottomTabBar.tsx` pattern) rather than a new hook
- Maskable icon kept as its own distinct file (`icon-maskable-512.png`) with safe-zone padding, separate from the full-bleed `icon-512.png`
## TDD Audit
No commit on this branch carries a `gate_status:` trailer, so every commit normalizes to `missing`. The phase did follow a test-first pattern for its CI guards — the RED commits below precede their GREEN implementation:
| Test commit | Impl commit | gate_status |
|---|---|---|
| `85a803f` test(17-03): add FAB↔BottomTabBar overlap regression guard | `5e1c714` fix(17-03): lift FAB above BottomTabBar | missing |
| `944045c` test(17-06): add admin two-tab ARIA + keyboard assertions | `b5fcd1d` fix(17): resolve post-merge wave 2 integration issues | missing |
| `881f2d2` test(17): complete UAT — 9 passed, 0 issues | — | missing |
Aggregate (all 51 non-merge commits): 0 skill, 0 fallback, 0 exempt — 51 missing (no `gate_status:` trailers emitted by the executor this phase).
gate_status: skill=0, fallback=0, exempt=0, missing=51
- Add @vite-pwa/assets-generator@1.0.2 as devDependency in apps/pwa
- Add pwa:icons script (pwa-assets-generator generate) to apps/pwa/package.json
- Approve sharp build scripts in pnpm-workspace.yaml (required for @vite-pwa/assets-generator)
- Hand-author apps/pwa/public/logo.svg: warm/rounded/at-home family scene with house, two adults, child, heart — square 512x512 viewBox, self-contained, text-free
- Create apps/pwa/pwa-assets.config.ts with minimal2023Preset, images: ['public/logo.svg'], no overrideManifestIcons
- Overwrite logo.svg with approved higher-contrast family-house SVG
(warm peach gradient bg, amber roof, bold white walls, heart finial,
three clearly-spaced family figures in rose/gold/blue)
- Regenerate full icon/favicon set from new art:
favicon.svg, favicon.ico (967 B), icon-192.png (192x192),
icon-512.png (512x512), icon-maskable-512.png (512x512 safe-zone),
apple-touch-icon.png (180x180)
- Approved brand decisions recorded for plan 17-04:
accent #e8915a, --brand-logo-border-radius: 0
Records approved brand decisions for plan 17-04:
- logo art approved (family-house redesign)
- brand accent #e8915a (warm amber)
- --brand-logo-border-radius: 0
- New test: "New Event FAB does not overlap BottomTabBar (A — phone only)"
- Skips on desktop profile; runs iphone + pixel (the profiles that exposed D-01)
- Asserts fabBox.y + fabBox.height <= navBox.y (FAB bottom ≤ bar top)
- Confirms Task 1 fix resolves the seed defect — passes green on both profiles
- Add LogOut icon (lucide-react) and fetchLocalLogout (api/client) imports
- Add useNavigate (react-router) for post-logout redirect
- Implement handleSignOut: fire-and-best-effort try/catch, navigate to /login in both branches
- Add Sign out button row at bottom of sheet (44px tap target, var(--color-destructive), divider separator)
- Always navigates to /login even on API failure (server cookie cleared or expired)
- Add toast state + 3000ms auto-dismiss useEffect to AdminPage
- Set toast 'Member added.' in createMemberMutation.onSuccess
- Propagate 'Password reset.' up from ResetPasswordSheet via onSuccess callback
- Render role=status/aria-live=polite toast with CheckCircle icon
- Phone toast offset uses calc(var(--bottom-chrome-h) + var(--space-4)) to clear BottomTabBar
Reformats 4 phase-17 files (SettingsSheet.tsx, tokens.css, vite.config.ts,
pwa-assets.config.ts) plus 11 pre-existing non-conformant docs/READMEs that
the repo-wide format:check also flags.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 17 D-10 reworked /admin into a two-tab layout; the Timezone section
moved into the (initially hidden) Settings tab panel. timezone-verify.spec.ts
predates that rework and asserted the region visible on bare /admin, so all 7
tests timed out in the harness. Click the Settings tab in beforeEach and after
the persist-test reload. Product behavior is correct; the spec was stale.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Phase 17: UI Optimization & Polish
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).
Status: Verified ✓ (automated + playwright-cli UAT; device-only checks deferred — see Verification)
This phase fixes the long-standing phone-layout overlap where the fixed
BottomTabBarcovered the New Event FAB and the colour legend, ships a real FamilySync brand mark (warm family-house SVG) with a complete 7-asset favicon/PWA-icon set replacing the placeholder stubs, and restructurestokens.cssinto a themeable semantic-token layer (light-only groundwork for a future dark theme). It also folds in four UI fixes surfaced during prior UAT: a reachable Sign out control, admin success toasts, desktop-centered sheets, and an accessible two-tab admin navigation. Dark theme and a broader styling refresh were deferred to backlog (999.20 / 999.21).Changes
Plan 17-01: CSS token groundwork
Restructure
tokens.cssto a combined:root, [data-theme="light"]selector and add the--bottom-chrome-hlayout token.Key files:
apps/pwa/src/styles/tokens.cssPlan 17-02: Branding assets
Author the approved FamilySync logo SVG and generate the full icon/favicon set via
@vite-pwa/assets-generator.Key files (created):
apps/pwa/public/{logo.svg,favicon.svg,favicon.ico,icon-192.png,icon-512.png,icon-maskable-512.png,apple-touch-icon.png},apps/pwa/pwa-assets.config.ts· modified:apps/pwa/package.jsonPlan 17-03: Phone layout overlap fix
Lift the FAB above the
BottomTabBar(calc(var(--bottom-chrome-h) + var(--space-6))), reserve phone content padding, and add a CI overlap regression guard.Key files:
apps/pwa/src/components/CalendarShell.tsx,apps/pwa/src/App.tsx,apps/pwa/e2e/layout.spec.tsPlan 17-04: Brand wiring
Wire the logo into
BrandSlot, hook up favicons +theme-color, fix the maskable icon reference, and apply the#e8915abrand accent.Key files:
apps/pwa/src/components/BrandSlot.tsx,apps/pwa/src/styles/tokens.css,apps/pwa/index.html,apps/pwa/vite.config.tsPlan 17-05: Logout control + sheet centering
Add a reachable, best-effort Sign out control and a phone/desktop centering branch to the settings/credential sheets.
Key files:
apps/pwa/src/components/SettingsSheet.tsx,apps/pwa/src/components/CredentialSheet.tsxPlan 17-06: Admin polish
Add create-member / reset-password success toasts, an accessible two-tab ARIA navigation (roving tabindex, arrow/Home/End), and reset-sheet centering.
Key files:
apps/pwa/src/routes/AdminPage.tsx,apps/pwa/e2e/admin.spec.tsRequirements Addressed
No REQ-IDs in ROADMAP; decisions D-01..D-10 serve as the requirement set (all delivered, mapped to plans):
BottomTabBar; permanent CI overlap guardBrandSlot, decorative, noLoginPagelayout changetokens.cssrestructured to a themeable layer (Schedule-X overrides intact) ⚠️ PARTIALScore: 10/10 decisions delivered in code (8 fully VERIFIED, 2 PARTIAL with documented WARNING-class caveats); 0 FAILED, 0 BLOCKER.
Verification
layout.spec.tsFAB overlap,admin.spec.tstab ARIA/keyboard)17-SECURITY.mdthreats_open: 0(status verified)Deferred — device-only human checkpoints (cannot be driven headless per CLAUDE.md; phase shipped with these outstanding):
BottomTabBar, legend chips not clipped behind itapple-touch-iconrenderingKnown WARNING-class limitation (17-REVIEW WR-01): sheet/FAB phone-vs-desktop geometry is a one-shot render snapshot — it does not re-evaluate when the viewport crosses 767px after mount. Invisible on real phones (a phone is always a phone); only manifests on tablet rotation or desktop-window resize.
useIsPhone()fix sketched in 17-REVIEW for a follow-up.Key Decisions
--brand-logo-border-radius: 0— the logo SVG draws its own rounded-square background (rx=104); no extra CSS clip needed--color-member-0 → #e8915a(warm amber), applied acrosstokens.css,index.htmltheme-color, and the Vite manifestfavicon.svgis an exact copy oflogo.svg; modern browsers prefer the SVG favicon,favicon.icois the legacy fallbackwindow.matchMedia(matching the establishedApp.tsx/BottomTabBar.tsxpattern) rather than a new hookicon-maskable-512.png) with safe-zone padding, separate from the full-bleedicon-512.pngTDD Audit
No commit on this branch carries a
gate_status:trailer, so every commit normalizes tomissing. The phase did follow a test-first pattern for its CI guards — the RED commits below precede their GREEN implementation:85a803ftest(17-03): add FAB↔BottomTabBar overlap regression guard5e1c714fix(17-03): lift FAB above BottomTabBar944045ctest(17-06): add admin two-tab ARIA + keyboard assertionsb5fcd1dfix(17): resolve post-merge wave 2 integration issues881f2d2test(17): complete UAT — 9 passed, 0 issuesAggregate (all 51 non-merge commits): 0 skill, 0 fallback, 0 exempt — 51 missing (no
gate_status:trailers emitted by the executor this phase).gate_status: skill=0, fallback=0, exempt=0, missing=51
- Change :root { to :root, [data-theme="light"] { (D-06 combined selector) - Add --bottom-chrome-h: calc(56px + env(safe-area-inset-bottom, 0px)) to spacing scale - Keep all 12 --sx-color-* overrides inside the combined block (cascade unchanged) - Keep --brand-logo-* tokens inside the combined block (verbatim, no value changes) - Add dark-theme stub as comment-only (Phase 999.20 fills values; no live rule) - All existing values unchanged; PWA build passes; Schedule-X colors verified via playwright-cli- CalendarShell.tsx: FAB bottom changed from var(--space-6) to calc(var(--bottom-chrome-h) + var(--space-6)) — sits 24px above bar - App.tsx: contentStyle gains phone-only paddingBottom: var(--bottom-chrome-h) via spread pattern ...(phone ? {...} : {}) — desktop unchanged- Add window.matchMedia('(max-width: 767px)') phone check - Desktop branch: position fixed, top/left 50%, translate(-50%, -50%), maxWidth 480px, borderRadius 12px - Phone branch: unchanged bottom-sheet (bottom 0, left 0, right 0, borderRadius 12px 12px 0 0) - role=dialog, aria-modal, aria-label unchanged — only position CSS branched - SettingsSheet, ChangePasswordSheet, LinkOidcSheet centering in prior commit (Task 1)- SettingsSheet: void the navigate('/login') promise (react-router v7 returns Promise) — eslint no-floating-promises - InstructionSheet.test: wrap SettingsSheet render in MemoryRouter — 17-05 added useNavigate() which needs Router context Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>