---
phase: 17-ui-optimization-polish
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - apps/pwa/package.json
  - apps/pwa/pwa-assets.config.ts
  - apps/pwa/public/logo.svg
  - apps/pwa/public/favicon.svg
  - apps/pwa/public/favicon.ico
  - apps/pwa/public/icon-192.png
  - apps/pwa/public/icon-512.png
  - apps/pwa/public/icon-maskable-512.png
  - apps/pwa/public/apple-touch-icon.png
autonomous: false
requirements: [D-03, D-04]
user_setup: []
must_haves:
  truths:
    - "A real FamilySync logo (warm/rounded/at-home/caricature-family) exists as a committed SVG"
    - "All 7 branding assets exist in apps/pwa/public/ with correct formats and dimensions"
    - "A proper maskable 512 icon exists as its own file with the logo inside the 80% safe zone"
    - "The operator has approved the logo art AND selected the brand accent before wiring (next plan) is committed"
  artifacts:
    - path: "apps/pwa/public/logo.svg"
      provides: "Hand-authored source brand mark (BrandSlot img + icon derivation source)"
    - path: "apps/pwa/public/icon-maskable-512.png"
      provides: "512x512 maskable PWA icon with safe-zone padding"
    - path: "apps/pwa/pwa-assets.config.ts"
      provides: "@vite-pwa/assets-generator config (minimal-2023 preset)"
    - path: "apps/pwa/package.json"
      provides: "@vite-pwa/assets-generator devDependency + pwa:icons script"
  key_links:
    - from: "apps/pwa/pwa-assets.config.ts"
      to: "apps/pwa/public/logo.svg"
      via: "images: ['public/logo.svg'] — generator reads the source SVG"
      pattern: "logo.svg"
---

<objective>
Generate the complete FamilySync branding asset set (D-03, D-04): hand-author a warm/rounded/at-home/caricature-family `logo.svg`, install `@vite-pwa/assets-generator` as a devDependency in `apps/pwa`, and derive the full icon/favicon set (`favicon.svg`, `favicon.ico`, `icon-192.png`, `icon-512.png`, a PROPER `icon-maskable-512.png` with real safe-zone padding, `apple-touch-icon.png`) using the `minimal-2023` preset. Then obtain explicit operator approval of the logo art AND the brand-accent selection at a blocking human checkpoint BEFORE any wiring is committed (wiring happens in plan 17-04).

This plan deliberately produces and commits ONLY the assets + generator config. The BrandSlot/index.html/vite.config.ts wiring is a separate plan (17-04) so the checkpoint gates the wiring, exactly as the phase contract requires.

Purpose: replace the placeholder icon stubs (icon-192.png 699 B, icon-512.png, apple-touch-icon.png 617 B) and fix the missing-favicon + improper-maskable defects with a coherent, approved brand identity.
Output: 8 files (logo.svg + 6 derived assets + pwa-assets.config.ts), package.json devDep + script, and a recorded operator approval (logo art + accent choice).
</objective>

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

<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-RESEARCH.md
@.planning/phases/17-ui-optimization-polish/17-CONTEXT.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Install @vite-pwa/assets-generator and author logo.svg + pwa-assets.config.ts</name>
  <files>apps/pwa/package.json, apps/pwa/public/logo.svg, apps/pwa/pwa-assets.config.ts</files>
  <read_first>
    - 17-CONTEXT.md §"Specific Ideas" (the brand brief: FamilySync is the brand; caricature-family vibes; warm tones; rounded corners/shapes; "comfortable and at home")
    - 17-UI-SPEC.md §"Workstream B — Branding Assets" → "Brand brief" + "Logo asset contract" + "Maskable safe-zone rule"
    - 17-RESEARCH.md §"Architecture Patterns" → "Recommended Asset-Generation Script Shape" + the minimal pwa-assets.config.ts excerpt + the generate command
    - 17-RESEARCH.md §"Package Legitimacy Audit" (sharp SUS verdict is a documented false positive — 13-yr/65.6M-wk package; no human-verify checkpoint needed for the package itself)
    - apps/pwa/public/ (the 3 placeholder stubs being replaced)
  </read_first>
  <action>
    Install `@vite-pwa/assets-generator@1.0.2` as a devDependency in `apps/pwa` (pulls `sharp` + `sharp-ico` transitively — these are the only new packages; per the Package Legitimacy Audit they are Approved, no human-verify gate required for the package). Add a `"pwa:icons": "pwa-assets-generator generate"` script to `apps/pwa/package.json`.

    Hand-author `apps/pwa/public/logo.svg` as a square-viewBox SVG mark to the brand brief: warm tones, rounded corners/shapes, a friendly caricature-family feel that "makes you feel comfortable and at home" — NOT cold/corporate/geometric. Use a square viewBox so the generator's safe-zone arithmetic works. The mark is text-free art (the app name lives in the `<h1>`, not the logo). Keep the SVG self-contained (no external font/image refs).

    Create `apps/pwa/pwa-assets.config.ts` using `defineConfig` + `minimal2023Preset` from `@vite-pwa/assets-generator/config`, with `images: ['public/logo.svg']`. Do NOT set `overrideManifestIcons: true` — the manifest is maintained by hand in vite.config.ts (plan 17-04); auto-override would stomp the explicit entries (RESEARCH anti-pattern).
  </action>
  <verify>
    <automated>test -f apps/pwa/public/logo.svg && test -f apps/pwa/pwa-assets.config.ts && node -e "const p=require('./apps/pwa/package.json'); if(!p.devDependencies['@vite-pwa/assets-generator']) process.exit(1); if(!p.scripts['pwa:icons']) process.exit(1)" && head -c 5 apps/pwa/public/logo.svg | grep -q '<'</automated>
  </verify>
  <acceptance_criteria>
    - `@vite-pwa/assets-generator` present in `apps/pwa/package.json` devDependencies; `pwa:icons` script present.
    - `apps/pwa/public/logo.svg` exists, is valid SVG (parses; square viewBox), self-contained, text-free art.
    - `apps/pwa/pwa-assets.config.ts` exists, imports `defineConfig`/`minimal2023Preset` from `@vite-pwa/assets-generator/config`, sets `images: ['public/logo.svg']`, and does NOT set `overrideManifestIcons: true`.
  </acceptance_criteria>
  <done>The generator devDep + script are installed, logo.svg matches the brand brief as a valid square SVG, and pwa-assets.config.ts is configured for the minimal-2023 preset against the source logo.</done>
</task>

<task type="auto">
  <name>Task 2: Generate the full icon/favicon set and validate formats</name>
  <files>apps/pwa/public/favicon.svg, apps/pwa/public/favicon.ico, apps/pwa/public/icon-192.png, apps/pwa/public/icon-512.png, apps/pwa/public/icon-maskable-512.png, apps/pwa/public/apple-touch-icon.png</files>
  <read_first>
    - 17-UI-SPEC.md §"Workstream B" → "Logo asset contract" table (all 7 filenames + dimensions)
    - 17-RESEARCH.md §"Architecture Patterns" → the minimal-2023 preset output list + "Maskable safe-zone math" + "ICO generation"
    - 17-VALIDATION.md §"Per-Task Verification Map" rows D-03/D-04 (asset existence ls, ICO >100 bytes, maskable 512x512 via sharp metadata)
    - apps/pwa/pwa-assets.config.ts (authored in Task 1)
  </read_first>
  <action>
    Run the generator from `apps/pwa/` (`pnpm pwa:icons` or `npx @vite-pwa/assets-generator generate`) to produce the derived assets into `apps/pwa/public/`: `favicon.svg`, `favicon.ico`, `icon-192.png`, `icon-512.png`, `icon-maskable-512.png` (separate maskable file with safe-zone padding — this fixes the defect where the manifest reused icon-512.png for the maskable purpose), and `apple-touch-icon.png` (180x180). The minimal-2023 preset handles the safe-zone math, maskable background fill, and ICO encoding via sharp-ico.

    Validate the outputs: confirm all 7 files (logo.svg + 6 generated) exist in `apps/pwa/public/`; confirm `favicon.ico` is non-trivial (>100 bytes); confirm `icon-maskable-512.png` is exactly 512x512 via sharp metadata (proving real safe-zone generation, not a stub). The 48px single-size ICO from the preset is acceptable (RESEARCH documents this as adequate vs 16+32 multi-size for this household app; favicon.svg covers modern browsers).

    Do NOT wire anything yet (no BrandSlot/index.html/vite.config.ts edits — that is plan 17-04, gated behind the Task 3 checkpoint).
  </action>
  <verify>
    <automated>ls apps/pwa/public/logo.svg apps/pwa/public/favicon.svg apps/pwa/public/favicon.ico apps/pwa/public/icon-192.png apps/pwa/public/icon-512.png apps/pwa/public/icon-maskable-512.png apps/pwa/public/apple-touch-icon.png && test $(wc -c < apps/pwa/public/favicon.ico) -gt 100 && node -e "require('sharp')('apps/pwa/public/icon-maskable-512.png').metadata().then(m=>{if(m.width!==512||m.height!==512){console.error('bad maskable dims',m.width,m.height);process.exit(1)}console.log('maskable',m.width+'x'+m.height)})"</automated>
  </verify>
  <acceptance_criteria>
    - `ls` of all 7 asset paths succeeds (logo.svg + favicon.svg + favicon.ico + icon-192.png + icon-512.png + icon-maskable-512.png + apple-touch-icon.png).
    - `test $(wc -c < apps/pwa/public/favicon.ico) -gt 100` passes (ICO is real, not zero-byte).
    - `icon-maskable-512.png` reports exactly 512x512 via sharp metadata.
    - `icon-maskable-512.png` is a DISTINCT file from `icon-512.png` (different bytes — the maskable has safe-zone padding).
  </acceptance_criteria>
  <done>All 7 branding assets exist with correct formats; favicon.ico is non-trivial; the maskable icon is a separate, properly-padded 512x512 file.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking-human">
  <name>Task 3: Checkpoint — operator approves logo art + selects brand accent + logo border-radius</name>
  <files>apps/pwa/public/logo.svg, apps/pwa/public/favicon.svg, apps/pwa/public/favicon.ico, apps/pwa/public/icon-192.png, apps/pwa/public/icon-512.png, apps/pwa/public/icon-maskable-512.png, apps/pwa/public/apple-touch-icon.png</files>
  <action>Present the generated branding assets and the brand-accent decision to the operator for blocking approval before any wiring is committed (see how-to-verify). Capture the approved logo, the selected accent hex, and the chosen --brand-logo-border-radius for plan 17-04.</action>
  <what-built>
    A complete FamilySync branding asset set generated from a hand-authored warm/rounded/at-home logo: `logo.svg` (source), `favicon.svg`, `favicon.ico`, `icon-192.png`, `icon-512.png`, `icon-maskable-512.png` (proper safe-zone maskable), and `apple-touch-icon.png` — all in `apps/pwa/public/`. Nothing is wired into the app yet; this checkpoint gates the wiring (plan 17-04).
  </what-built>
  <how-to-verify>
    1. Use the playwright-cli skill to render the logo in context: open the logo.svg directly and/or stage it in the BrandSlot region of `/login` for a preview screenshot, and show the favicon at small sizes.
    2. Present the generated logo to the operator against the acceptance lens: does it feel warm / rounded / at-home / caricature-family (not cold/corporate/geometric)? Confirm the maskable icon keeps the mark inside the safe zone (no clipping of meaningful art).
    3. Present the BRAND ACCENT decision (UI-SPEC §Color "Brand accent checkpoint", Q1) — two comparable options:
       - Variant A — keep cool-blue: `--color-member-0: #4a90d9`, `theme-color #4A90D9` (default if no choice).
       - Variant B — warm: `--color-member-0: #f25c7a` (rose, already `--color-shared-family`) OR amber `#e8915a`; contrast ≥3:1 on #ffffff.
       Ask the operator to pick A or one of the B candidates. Record the chosen accent hex for plan 17-04 to apply to tokens.css `--color-member-0`, index.html `theme-color`, and vite.config.ts `theme_color`.
    4. Also confirm the intended `--brand-logo-border-radius` for the logo shape (0 if the SVG draws its own rounded shape, or 12px if it's a square mark needing rounding) — record for plan 17-04.
  </how-to-verify>
  <resume-signal>Operator types "approved" with: (a) logo accepted (or revise instructions), (b) the selected brand-accent hex, (c) the --brand-logo-border-radius value. These three feed plan 17-04.</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Build tooling → repo | `@vite-pwa/assets-generator` (+ sharp, sharp-ico) runs at design time and writes static assets into `public/`. New devDependency = supply-chain surface. |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-17-02-SC | Tampering | npm devDependency install (@vite-pwa/assets-generator, sharp, sharp-ico) | accept | Per RESEARCH Package Legitimacy Audit all three are Approved: assets-generator (official vite-pwa project, 231K/wk), sharp (13-yr, 65.6M/wk — the `too-new` SUS flag is a documented false positive from the latest version's publish date), sharp-ico (431K/wk). No `[SLOP]`/unverified packages; no human-verify gate required for the package legitimacy. Generated assets are static images served as files — no executable content. |
| T-17-02-02 | Information disclosure | generated assets | accept | Assets are public-by-design brand images; no secrets or PII. |

No new high-severity threats. devDependencies only; zero new runtime dependencies; generated output is static images.
</threat_model>

<verification>
- `ls` all 7 asset paths — present
- `test $(wc -c < apps/pwa/public/favicon.ico) -gt 100` — ICO non-trivial
- sharp metadata on `icon-maskable-512.png` — exactly 512x512
- `@vite-pwa/assets-generator` in apps/pwa devDependencies; `pwa:icons` script present
- Blocking human checkpoint: logo art approved + brand-accent hex selected + --brand-logo-border-radius value recorded
</verification>

<success_criteria>
The full branding asset set is generated and committed (logo.svg + 6 derived assets), the assets-generator devDep + script are in place, format/dimension checks pass, and the operator has approved the logo and selected the brand accent + logo border-radius — unblocking the wiring plan (17-04).
</success_criteria>

<output>
Create `.planning/phases/17-ui-optimization-polish/17-02-SUMMARY.md` when done. Record the approved brand-accent hex and --brand-logo-border-radius value in the SUMMARY (plan 17-04 reads them).
</output>
