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

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
17-ui-optimization-polish 04 execute 2
17-01
17-02
apps/pwa/src/components/BrandSlot.tsx
apps/pwa/index.html
apps/pwa/vite.config.ts
apps/pwa/src/styles/tokens.css
true
D-04
D-05
truths artifacts key_links
The real logo renders in BrandSlot on /login with no LoginPage layout shift; <h1> still carries the app name
index.html links favicon.svg + favicon.ico + apple-touch-icon, and theme-color matches the approved accent
The PWA manifest references the proper separate icon-maskable-512.png (not a reused icon-512.png)
The approved brand accent is applied consistently across tokens.css, index.html, and the manifest
path provides contains
apps/pwa/src/components/BrandSlot.tsx Logo img swapped in for the placeholder div, decorative (alt empty, aria-hidden) logo.svg
path provides contains
apps/pwa/index.html favicon link set + theme-color meta favicon.svg
from to via pattern
apps/pwa/vite.config.ts apps/pwa/public/icon-maskable-512.png manifest icons[] references the separate maskable file with purpose maskable icon-maskable-512.png
from to via pattern
apps/pwa/src/components/BrandSlot.tsx apps/pwa/public/logo.svg img src /logo.svg logo.svg
Wire the approved branding assets (from plan 17-02) into the app (D-05, D-04): swap the BrandSlot placeholder div for a decorative logo img through the existing seam (no LoginPage layout change), add the missing favicon links to index.html, fix the vite.config.ts manifest to reference the proper separate `icon-maskable-512.png`, and apply the operator-selected brand accent plus `--brand-logo-border-radius` (from the 17-02 checkpoint) consistently across tokens.css, index.html, and the manifest.

This plan runs AFTER 17-02 (assets exist + accent/border-radius approved at the checkpoint) and 17-01 (tokens.css restructured, --brand-logo-* live in the combined block). It does not touch logo art — only wiring + the approved token values.

Purpose: replace placeholder stubs/missing favicons with the real, approved identity and fix the improper-maskable defect. Output: BrandSlot img, index.html favicon links + theme-color, manifest icons[] with separate maskable, and the accent/border-radius token values.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/17-ui-optimization-polish/17-UI-SPEC.md @.planning/phases/17-ui-optimization-polish/17-PATTERNS.md @.planning/phases/17-ui-optimization-polish/17-RESEARCH.md @.planning/phases/17-ui-optimization-polish/17-01-SUMMARY.md @.planning/phases/17-ui-optimization-polish/17-02-SUMMARY.md Task 1: Swap BrandSlot placeholder div for the logo img and set --brand-logo-border-radius apps/pwa/src/components/BrandSlot.tsx, apps/pwa/src/styles/tokens.css - apps/pwa/src/components/BrandSlot.tsx lines 29-50 (the placeholder div to replace; note the h1 FamilySync and tagline p must stay) - 17-UI-SPEC.md section "Workstream B" -> "BrandSlot swap contract (D-05)" and section "Surface Architecture" -> "Surface B-1" (the exact img style object) - 17-PATTERNS.md section "apps/pwa/src/components/BrandSlot.tsx" (before/after excerpt) - 17-RESEARCH.md section "Common Pitfalls" -> Pitfall 5 (--brand-logo-border-radius 50% clips an SVG into a circle) - 17-02-SUMMARY.md (the operator-approved --brand-logo-border-radius value from the checkpoint) - .planning/phases/19-local-auth-no-oidc-mode/19-UI-SPEC.md section "Brand Slot" (the Phase 19 seam contract — LoginPage layout MUST NOT change) In apps/pwa/src/components/BrandSlot.tsx, replace the placeholder div (currently rendering the "FS" initials with aria-hidden) with a decorative logo image element: src "/logo.svg", empty alt, aria-hidden true. Apply the existing brand-logo token surface to the image per UI-SPEC Surface B-1: width var(--brand-logo-size, 48px), height var(--brand-logo-size, 48px), borderRadius var(--brand-logo-border-radius), margin "0 auto var(--space-2, 8px)", display block, aspectRatio "1 / 1", objectFit contain, flexShrink 0. The --brand-logo-bg background is NOT applied (no background div now). Keep the h1 "FamilySync" and the tagline p exactly as-is — the logo is decorative; the h1 remains the accessible page name. Do NOT modify LoginPage.tsx (the Phase 19 seam contract forbids it). Do NOT use dangerouslySetInnerHTML.
In apps/pwa/src/styles/tokens.css, update --brand-logo-border-radius from 50% to the value the operator approved at the 17-02 checkpoint (recorded in 17-02-SUMMARY — likely 0 if the SVG draws its own rounded shape, or 12px for a square mark; per RESEARCH Pitfall 5, leaving it at 50% would clip the logo into a circle). This is the only tokens.css edit in this task; it goes inside the combined :root, [data-theme="light"] block.
grep -q 'src="/logo.svg"' apps/pwa/src/components/BrandSlot.tsx && grep -q 'alt=""' apps/pwa/src/components/BrandSlot.tsx && grep -q 'FamilySync' apps/pwa/src/components/BrandSlot.tsx && ! grep -q 'dangerouslySetInnerHTML' apps/pwa/src/components/BrandSlot.tsx && pnpm --filter @familysync/pwa build - BrandSlot renders a decorative logo image (src "/logo.svg", empty alt, aria-hidden); the h1 "FamilySync" and tagline p are unchanged. - --brand-logo-border-radius in tokens.css equals the operator-approved value from 17-02-SUMMARY (not the old 50%, unless the operator explicitly chose circular). - LoginPage.tsx is NOT in this plan's diff (seam contract honored). - playwright-cli on /login: the logo renders, no layout shift vs the placeholder (aspect-ratio 1/1 + explicit width), h1 "FamilySync" still present as text. Observation noted in SUMMARY. - pnpm --filter @familysync/pwa build exits 0. BrandSlot shows the real decorative logo via the seam with no LoginPage change, the border-radius token matches the approved shape, and the login view has no layout shift. Task 2: Wire index.html favicons + theme-color and fix the vite.config.ts manifest maskable + accent apps/pwa/index.html, apps/pwa/vite.config.ts, apps/pwa/src/styles/tokens.css - apps/pwa/index.html (current: one apple-touch-icon link, theme-color #4A90D9, no rel=icon links) - apps/pwa/vite.config.ts lines ~33 (theme_color) and ~38-42 (the icons array; the last entry reuses icon-512.png for maskable — the defect) - 17-UI-SPEC.md section "Workstream B" -> "index.html wiring contract" and "vite.config.ts manifest wiring contract" (exact link set + icons[] + theme_color rules) - 17-PATTERNS.md sections "apps/pwa/index.html" and "apps/pwa/vite.config.ts" (before/after excerpts) - 17-02-SUMMARY.md (the operator-selected accent hex from the checkpoint — applies to index.html theme-color + manifest theme_color; tokens.css --color-member-0 only if Variant B) - 17-UI-SPEC.md section "Color" -> "Brand accent checkpoint" (which files flip for the accent) In apps/pwa/index.html, add the favicon links in order (SVG first for modern browsers, ICO second for legacy): a rel=icon link to /favicon.svg with type image/svg+xml, then a rel=icon link to /favicon.ico with sizes "any". Update the existing apple-touch-icon link to include sizes "180x180". Add the apple-mobile-web-app meta tags per UI-SPEC (capable yes, status-bar-style default, title FamilySync). Set the theme-color meta content to the operator-approved accent hex from 17-02-SUMMARY (#4A90D9 if Variant A was kept).
In apps/pwa/vite.config.ts, fix the manifest icons array so the maskable entry references the SEPARATE /icon-maskable-512.png file (src /icon-maskable-512.png, sizes 512x512, type image/png, purpose maskable) — remove the defective reuse of /icon-512.png for the maskable purpose. Keep the /icon-192.png and /icon-512.png (purpose any) entries. Update the manifest theme_color to the same approved accent hex so it matches index.html.

If the operator selected the warm accent (Variant B) at the 17-02 checkpoint, ALSO update --color-member-0 in tokens.css to the chosen hex (this is the only additional tokens.css edit, inside the combined block; --sx-color-primary already maps to --color-member-0 and follows automatically). If Variant A was kept, no tokens.css color edit is needed.
grep -q 'favicon.svg' apps/pwa/index.html && grep -q 'favicon.ico' apps/pwa/index.html && grep -q 'icon-maskable-512.png' apps/pwa/vite.config.ts && ! grep -E "/icon-512.png'.*maskable|maskable.*/icon-512.png'" apps/pwa/vite.config.ts && pnpm --filter @familysync/pwa build - index.html contains rel=icon links to /favicon.svg (svg first) and /favicon.ico (sizes any), plus the apple-touch-icon with sizes 180x180. - index.html theme-color content equals the approved accent hex; vite.config.ts manifest theme_color matches it. - vite.config.ts manifest maskable entry references /icon-maskable-512.png (the separate file), and no manifest entry uses /icon-512.png with purpose maskable. - If Variant B accent was chosen: tokens.css --color-member-0 updated to the chosen hex; otherwise tokens.css color unchanged. - pnpm --filter @familysync/pwa build exits 0; playwright-cli on / confirms manifest icon links resolve (no 404). Observation noted in SUMMARY. index.html links the full favicon set with the approved theme-color, the manifest references the proper separate maskable icon and matching theme_color, the accent is applied consistently, and the build passes.

<threat_model>

Trust Boundaries

Boundary Description
(none new) Wiring static asset references + an img element + token values. No runtime data flow, no user input, no new endpoints.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-17-04-01 Tampering BrandSlot img / index.html links accept The logo img is decorative with empty alt; no dangerouslySetInnerHTML (T-05-24 invariant maintained); favicon links and manifest entries point at committed static files. No new threat above LOW for Workstream B wiring.

No new high/medium-severity threats. Static asset wiring + token value edits; no executable content, no input surface. </threat_model>

- grep favicon.svg / favicon.ico in apps/pwa/index.html — links present - grep icon-maskable-512.png in apps/pwa/vite.config.ts — maskable references the separate file - no manifest entry uses icon-512.png for maskable purpose - grep src="/logo.svg" + alt="" in BrandSlot.tsx; no dangerouslySetInnerHTML - pnpm --filter @familysync/pwa build — exits 0 - playwright-cli /login (logo, no layout shift, h1 intact) + / (manifest icons resolve)

<success_criteria> The real logo is wired through the BrandSlot seam with no LoginPage change, the full favicon set + theme-color are in index.html, the manifest references the proper separate maskable icon, the approved accent + logo border-radius are applied consistently, and the build is green. </success_criteria>

Create `.planning/phases/17-ui-optimization-polish/17-04-SUMMARY.md` when done.