chore: merge executor worktree (worktree-agent-a1f3ad1ab5d67e70c)
This commit is contained in:
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
phase: 17-ui-optimization-polish
|
||||||
|
plan: "04"
|
||||||
|
subsystem: ui
|
||||||
|
tags: [pwa, branding, logo, favicon, tokens, manifest, brandslot]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires: ["17-01", "17-02"]
|
||||||
|
provides:
|
||||||
|
- "BrandSlot renders the approved decorative logo img (src /logo.svg, alt empty, aria-hidden)"
|
||||||
|
- "index.html: favicon.svg + favicon.ico + apple-touch-icon with sizes, theme-color #e8915a"
|
||||||
|
- "vite.config.ts manifest: maskable icon correctly references /icon-maskable-512.png (separate file)"
|
||||||
|
- "Approved brand accent #e8915a applied to --color-member-0, index.html theme-color, manifest theme_color"
|
||||||
|
- "--brand-logo-border-radius: 0 applied (SVG draws its own rx=104 shape)"
|
||||||
|
affects: []
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "BrandSlot seam: placeholder div replaced with <img src='/logo.svg' alt='' aria-hidden> — LoginPage.tsx untouched"
|
||||||
|
- "tokens.css --color-member-0 is the single source for brand accent; --sx-color-primary follows via var()"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- "apps/pwa/src/components/BrandSlot.tsx — placeholder FS div replaced with decorative logo img"
|
||||||
|
- "apps/pwa/src/styles/tokens.css — --brand-logo-border-radius: 0; --color-member-0: #e8915a"
|
||||||
|
- "apps/pwa/index.html — favicon.svg + favicon.ico links added; theme-color updated to #e8915a"
|
||||||
|
- "apps/pwa/vite.config.ts — maskable icon fixed to /icon-maskable-512.png; theme_color updated to #e8915a"
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Applied --brand-logo-border-radius: 0 verbatim from 17-02 checkpoint (SVG rx=104 draws its own shape)"
|
||||||
|
- "Applied brand accent #e8915a verbatim from 17-02 checkpoint across all three files (Variant B)"
|
||||||
|
- "Favicon order: favicon.svg first (modern browsers), favicon.ico second (legacy) — per UI-SPEC wiring contract"
|
||||||
|
- "--color-focus-ring left at #4a90d9 — semantic accessibility token; not in scope for brand accent update"
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 4min
|
||||||
|
completed: 2026-06-18
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 17 Plan 04: Brand Wiring Summary
|
||||||
|
|
||||||
|
**BrandSlot logo img wired from seam with no LoginPage change; favicon set + theme-color in index.html; manifest maskable icon fixed to /icon-maskable-512.png; brand accent #e8915a applied consistently across tokens.css, index.html, and vite.config.ts**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~4 min
|
||||||
|
- **Started:** 2026-06-18T16:47:28Z
|
||||||
|
- **Completed:** 2026-06-18T16:51:02Z
|
||||||
|
- **Tasks:** 2/2
|
||||||
|
- **Files modified:** 4
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
### Task 1: BrandSlot logo img + --brand-logo-border-radius
|
||||||
|
|
||||||
|
Replaced the Phase 19 placeholder `<div aria-hidden>FS</div>` with a decorative `<img>` element:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<img
|
||||||
|
src="/logo.svg"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `h1` "FamilySync" and tagline `p` unchanged
|
||||||
|
- `LoginPage.tsx` NOT in the diff (seam contract honored)
|
||||||
|
- `dangerouslySetInnerHTML` NOT used (T-05-24 invariant maintained)
|
||||||
|
- Updated `--brand-logo-border-radius: 50%` → `0` in tokens.css (approved 17-02: SVG draws its own rx=104 background)
|
||||||
|
|
||||||
|
### Task 2: index.html favicons + theme-color, vite.config.ts manifest fix, tokens.css accent
|
||||||
|
|
||||||
|
**index.html changes:**
|
||||||
|
- Added `<link rel="icon" href="/favicon.svg" type="image/svg+xml" />` (SVG first, modern browsers)
|
||||||
|
- Added `<link rel="icon" href="/favicon.ico" sizes="any" />` (legacy fallback)
|
||||||
|
- Updated `theme-color` from `#4A90D9` → `#e8915a`
|
||||||
|
- `apple-touch-icon` already had `sizes="180x180"` (was already present)
|
||||||
|
|
||||||
|
**vite.config.ts changes:**
|
||||||
|
- Fixed maskable icon: `/icon-512.png` with `purpose: 'maskable'` → `/icon-maskable-512.png` (the separate safe-zone file, 8627 B)
|
||||||
|
- Updated `theme_color: '#4A90D9'` → `'#e8915a'`
|
||||||
|
|
||||||
|
**tokens.css changes:**
|
||||||
|
- Updated `--color-member-0: #4a90d9` → `#e8915a` (warm amber, operator-approved Variant B)
|
||||||
|
- `--sx-color-primary: var(--color-member-0)` follows automatically (no additional edit needed)
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
| Task | Name | Commit | Files |
|
||||||
|
|------|------|--------|-------|
|
||||||
|
| 1 | BrandSlot logo img + border-radius token | ce95aa3 | BrandSlot.tsx, tokens.css |
|
||||||
|
| 2 | Favicons + theme-color + maskable fix + accent | df578fd | index.html, tokens.css, vite.config.ts |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
### Automated checks passed
|
||||||
|
|
||||||
|
- `grep -q 'src="/logo.svg"' BrandSlot.tsx` — OK
|
||||||
|
- `grep -q 'alt=""' BrandSlot.tsx` — OK
|
||||||
|
- `grep -q 'FamilySync' BrandSlot.tsx` (h1 intact) — OK
|
||||||
|
- `dangerouslySetInnerHTML` only in security comment, not in JSX — OK
|
||||||
|
- `--brand-logo-border-radius: 0` in tokens.css — OK
|
||||||
|
- `--color-member-0: #e8915a` in tokens.css — OK
|
||||||
|
- `favicon.svg` link in index.html — OK
|
||||||
|
- `favicon.ico` link in index.html — OK
|
||||||
|
- `theme-color: #e8915a` in index.html — OK
|
||||||
|
- `icon-maskable-512.png` in vite.config.ts manifest — OK
|
||||||
|
- No `icon-512.png` with `maskable` purpose in manifest — OK
|
||||||
|
- `theme_color: '#e8915a'` in vite.config.ts — OK
|
||||||
|
- `pnpm --filter @familysync/pwa build` — exits 0
|
||||||
|
|
||||||
|
### Build output verified
|
||||||
|
|
||||||
|
- `dist/index.html` contains `favicon.svg`, `favicon.ico`, `theme-color: #e8915a`
|
||||||
|
- `dist/manifest.webmanifest` contains `icon-maskable-512.png` with `"purpose":"maskable"`, `"theme_color":"#e8915a"`
|
||||||
|
- JS bundle contains `logo.svg` with `alt:""` (decorative img confirmed in minified output)
|
||||||
|
|
||||||
|
### playwright-cli observation
|
||||||
|
|
||||||
|
The Vite dev server at :5173 is the main-repo instance (not the worktree), so the live browser showed the pre-existing placeholder. Build artifact verification was used as the authoritative check — `dist/manifest.webmanifest` and `dist/index.html` confirm all wiring is correct. The production-equivalent build passes cleanly.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written. All approved branding decisions from 17-02 applied verbatim:
|
||||||
|
- `--color-member-0: #e8915a` (Variant B warm amber)
|
||||||
|
- `--brand-logo-border-radius: 0` (SVG self-rounds)
|
||||||
|
- Favicon order per UI-SPEC wiring contract (SVG first, ICO second)
|
||||||
|
|
||||||
|
## Threat Surface Scan
|
||||||
|
|
||||||
|
No new trust boundaries introduced. All changes are static asset references and token values:
|
||||||
|
- `BrandSlot.tsx` uses `<img>` with empty alt + aria-hidden — no executable content, no user input, no dangerouslySetInnerHTML
|
||||||
|
- `index.html` favicon links and theme-color meta — committed static references
|
||||||
|
- `vite.config.ts` manifest — committed static icon references at known paths
|
||||||
|
- `tokens.css` value updates — no new surface
|
||||||
|
|
||||||
|
No new threat flags above the LOW level accepted in the plan's threat model.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- `apps/pwa/src/components/BrandSlot.tsx` — modified, committed at ce95aa3
|
||||||
|
- `apps/pwa/src/styles/tokens.css` — modified, committed at ce95aa3 (border-radius) + df578fd (color)
|
||||||
|
- `apps/pwa/index.html` — modified, committed at df578fd
|
||||||
|
- `apps/pwa/vite.config.ts` — modified, committed at df578fd
|
||||||
|
- `dist/manifest.webmanifest` — correct maskable + theme_color confirmed
|
||||||
|
- Build: exits 0
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 17-ui-optimization-polish*
|
||||||
|
*Completed: 2026-06-18*
|
||||||
+3
-1
@@ -3,8 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#4A90D9" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
|
||||||
|
<meta name="theme-color" content="#e8915a" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<meta name="apple-mobile-web-app-title" content="FamilySync" />
|
<meta name="apple-mobile-web-app-title" content="FamilySync" />
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* BrandSlot — Phase 17 seam component for the login page brand area.
|
* BrandSlot — Phase 17 seam component for the login page brand area.
|
||||||
*
|
*
|
||||||
* Phase 19 ships a minimal shippable placeholder: a 48px circle with "FS"
|
* Phase 19 shipped a minimal shippable placeholder: a 48px circle with "FS"
|
||||||
* initials, the app name "FamilySync", and the tagline "Family calendar & lists".
|
* initials, the app name "FamilySync", and the tagline "Family calendar & lists".
|
||||||
*
|
*
|
||||||
* Phase 17 replaces the internals of this component (swap the placeholder div for
|
* Phase 17 replaces the placeholder div with a decorative logo <img> — the approved
|
||||||
* an <img> with a real logo) without touching LoginPage's layout. This isolates
|
* FamilySync family-house SVG. LoginPage layout is untouched (seam contract honored;
|
||||||
* the branding seam — see 19-UI-SPEC.md §Brand Slot section.
|
* see 19-UI-SPEC.md §Brand Slot section).
|
||||||
*
|
*
|
||||||
* CSS custom properties used (all set in tokens.css with placeholder defaults;
|
* CSS custom properties used (all set in tokens.css):
|
||||||
* Phase 17 overrides these values):
|
* --brand-logo-size — image size (default: 48px)
|
||||||
* --brand-logo-bg — logo circle background (default: var(--color-member-0))
|
* --brand-logo-border-radius — image border-radius (0 — SVG draws its own shape)
|
||||||
* --brand-logo-text — initials color (default: #ffffff)
|
|
||||||
* --brand-logo-size — circle diameter (default: 48px)
|
|
||||||
* --brand-logo-border-radius — circle shape (default: 50%)
|
|
||||||
*
|
*
|
||||||
* Accessibility:
|
* Accessibility:
|
||||||
* <h1> contains the app name — screen readers read "FamilySync" as the page title.
|
* <h1> contains the app name — screen readers read "FamilySync" as the page title.
|
||||||
* The logo circle is aria-hidden (the text is the accessible label).
|
* The logo image is decorative (alt="", aria-hidden="true").
|
||||||
* No <img> today → no broken image ref → no layout shift when Phase 17 replaces it.
|
|
||||||
*
|
*
|
||||||
* Security: all copy is plain-text JSX children — no dangerouslySetInnerHTML (T-05-24).
|
* Security: all copy is plain-text JSX children — no dangerouslySetInnerHTML (T-05-24).
|
||||||
*/
|
*/
|
||||||
@@ -26,28 +22,22 @@
|
|||||||
export function BrandSlot() {
|
export function BrandSlot() {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
{/* Phase 17 replaces this div with <img src="..." alt="" /> */}
|
{/* Decorative brand logo — Phase 17 approved brand mark (logo.svg) */}
|
||||||
<div
|
<img
|
||||||
|
src="/logo.svg"
|
||||||
|
alt=""
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{
|
style={{
|
||||||
width: 'var(--brand-logo-size, 48px)',
|
width: 'var(--brand-logo-size, 48px)',
|
||||||
height: 'var(--brand-logo-size, 48px)',
|
height: 'var(--brand-logo-size, 48px)',
|
||||||
borderRadius: 'var(--brand-logo-border-radius, 50%)',
|
borderRadius: 'var(--brand-logo-border-radius)',
|
||||||
background: 'var(--brand-logo-bg, var(--color-member-0, #4a90d9))',
|
|
||||||
color: 'var(--brand-logo-text, #ffffff)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto var(--space-2, 8px)',
|
margin: '0 auto var(--space-2, 8px)',
|
||||||
fontSize: 'var(--text-display-size, 24px)',
|
display: 'block',
|
||||||
fontWeight: 600,
|
|
||||||
fontFamily: 'var(--font-family-base)',
|
|
||||||
flexShrink: 0,
|
|
||||||
aspectRatio: '1 / 1',
|
aspectRatio: '1 / 1',
|
||||||
|
objectFit: 'contain',
|
||||||
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
FS
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* App name — <h1> so screen readers identify the page (UI-SPEC §Accessibility) */}
|
{/* App name — <h1> so screen readers identify the page (UI-SPEC §Accessibility) */}
|
||||||
<h1
|
<h1
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
* 10% accent band — ONLY for event chip fills and color legend swatches.
|
* 10% accent band — ONLY for event chip fills and color legend swatches.
|
||||||
* ───────────────────────────────────────────────────────────────────────── */
|
* ───────────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
--color-member-0: #4a90d9;
|
--color-member-0: #e8915a; /* brand accent — warm amber (approved 17-02 checkpoint) */
|
||||||
--color-member-1: #50c878;
|
--color-member-1: #50c878;
|
||||||
--color-member-2: #f5a623;
|
--color-member-2: #f5a623;
|
||||||
--color-member-3: #9b59b6;
|
--color-member-3: #9b59b6;
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
--brand-logo-bg: var(--color-member-0); /* placeholder circle background */
|
--brand-logo-bg: var(--color-member-0); /* placeholder circle background */
|
||||||
--brand-logo-text: #ffffff; /* placeholder initials color */
|
--brand-logo-text: #ffffff; /* placeholder initials color */
|
||||||
--brand-logo-size: 48px; /* reserved slot height; keep 1:1 aspect */
|
--brand-logo-size: 48px; /* reserved slot height; keep 1:1 aspect */
|
||||||
--brand-logo-border-radius: 50%; /* circle for initials; Phase 17 may change */
|
--brand-logo-border-radius: 0; /* SVG draws its own rounded-square background (rx=104); no CSS clip needed */
|
||||||
--brand-app-name: 'FamilySync'; /* drives doc only — not used as CSS content */
|
--brand-app-name: 'FamilySync'; /* drives doc only — not used as CSS content */
|
||||||
|
|
||||||
/* ─────────────────────────────────────────────────────────────────────────
|
/* ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default defineConfig({
|
|||||||
name: 'FamilySync',
|
name: 'FamilySync',
|
||||||
short_name: 'FamilySync',
|
short_name: 'FamilySync',
|
||||||
description: 'Family calendar and lists',
|
description: 'Family calendar and lists',
|
||||||
theme_color: '#4A90D9',
|
theme_color: '#e8915a',
|
||||||
background_color: '#ffffff',
|
background_color: '#ffffff',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
scope: '/',
|
scope: '/',
|
||||||
@@ -38,7 +38,7 @@ export default defineConfig({
|
|||||||
icons: [
|
icons: [
|
||||||
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
|
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
|
||||||
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
|
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
|
||||||
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
{ src: '/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user