--- phase: 17-ui-optimization-polish plan: 01 type: execute wave: 1 depends_on: [] files_modified: - apps/pwa/src/styles/tokens.css autonomous: true requirements: [D-06] must_haves: truths: - "Light tokens still apply with no data-theme attribute on (combined :root, [data-theme=\"light\"] selector)" - "Schedule-X calendar grid keeps its custom member/shared colors after the restructure" - "A single --bottom-chrome-h token exists for Workstreams A and D to consume" - "No hard-coded hex/px appears in component/route files (existing invariant unbroken)" artifacts: - path: "apps/pwa/src/styles/tokens.css" provides: "Themeable token layer (:root, [data-theme=\"light\"]) + --bottom-chrome-h" contains: "--bottom-chrome-h" key_links: - from: "apps/pwa/src/styles/tokens.css" to: "@schedule-x/theme-default" via: "--sx-color-* overrides remain inside the combined rule block, after the theme-default import" pattern: "--sx-color-" --- Restructure `apps/pwa/src/styles/tokens.css` from a single light `:root {}` block into a themeable semantic-token layer using the combined selector `:root, [data-theme="light"]` (D-06), and add the shared `--bottom-chrome-h` layout-chrome token that Workstreams A and D will consume. This is GROUNDWORK ONLY — no dark palette values, no `prefers-color-scheme` wiring, no theme toggle. Light stays the sole shipped theme. This plan OWNS `tokens.css` for the phase. It runs first (Wave 1) so the phone-layout plan (A) and branding plan (B) add/update their token edits into the already-restructured block without write-ordering conflicts. Purpose: enable a future `data-theme="dark"` attribute (backlog 999.20) to override tokens with zero component-file changes, and establish one source of truth for the BottomTabBar height. Output: a restructured `tokens.css` with the combined selector, a dark-theme stub comment, the new `--bottom-chrome-h` token, all existing values unchanged. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.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 Task 1: Restructure tokens.css to a themeable layer and add --bottom-chrome-h apps/pwa/src/styles/tokens.css - apps/pwa/src/styles/tokens.css (the entire file — single `:root {}` block; note the `--sx-color-*` overrides near the bottom and the `--brand-logo-*` defaults) - 17-UI-SPEC.md §"Workstream C — Theme-Token Groundwork" (the exact combined-selector contract + dark-theme stub comment) - 17-UI-SPEC.md §"Spacing Scale" → "New layout-chrome token" (the --bottom-chrome-h definition) - 17-PATTERNS.md §"apps/pwa/src/styles/tokens.css" (before/after selector excerpt) - 17-RESEARCH.md §"Common Pitfalls" → Pitfall 3 (Schedule-X cascade break) Change the single top-level `:root {` opening selector of `apps/pwa/src/styles/tokens.css` to the combined selector `:root, [data-theme="light"] {`. Keep every existing declaration inside that one rule block VERBATIM — no value changes. Critically, the `--sx-color-*` Schedule-X overrides and the `--brand-logo-*` tokens MUST remain inside this same combined rule block (do NOT split them into a separate selector — splitting changes specificity and breaks the override of `@schedule-x/theme-default`, per RESEARCH Pitfall 3). Inside the same block, add the new layout-chrome token alongside the existing spacing scale: `--bottom-chrome-h: calc(56px + env(safe-area-inset-bottom, 0px));`. This is the source-of-truth for the BottomTabBar effective height; Workstreams A and D consume it. Below the closing brace of the combined block, add a dark-theme stub as a COMMENT ONLY (no live rule), documenting that Phase 999.20 fills the values and wires prefers-color-scheme. Do NOT add a live `[data-theme="dark"]` rule, do NOT add any dark color values, do NOT add a `data-theme` attribute anywhere in the app (anti-pattern per RESEARCH — the combined selector means light applies with no attribute present). Do not touch any component or route file in this plan. grep -q 'bottom-chrome-h' apps/pwa/src/styles/tokens.css && grep -qE '^\s*\[data-theme="light"\]' apps/pwa/src/styles/tokens.css && test $(grep -c -- '--sx-color' apps/pwa/src/styles/tokens.css) -ge 1 && pnpm --filter @familysync/pwa build - `grep -qE '\[data-theme="light"\]' apps/pwa/src/styles/tokens.css` succeeds (combined selector present). - `grep -q 'bottom-chrome-h' apps/pwa/src/styles/tokens.css` succeeds; the value is exactly `calc(56px + env(safe-area-inset-bottom, 0px))`. - The `--sx-color-*` override lines remain inside the combined `:root, [data-theme="light"]` block (count unchanged from before: still 12 `sx-color` occurrences). - No live `[data-theme="dark"]` rule exists: `grep -nE '\[data-theme="dark"\]\s*\{' apps/pwa/src/styles/tokens.css` returns nothing (a commented stub is acceptable; a live rule is not). - `pnpm --filter @familysync/pwa build` exits 0 (TypeScript + Vite build green). tokens.css uses the combined `:root, [data-theme="light"]` selector with all values unchanged, `--bottom-chrome-h` added, `--sx-color-*` overrides intact inside the block, dark stub is a comment only, and the PWA build passes. Task 2: Verify Schedule-X colors and the no-hard-coded-values invariant survive the restructure apps/pwa/src/styles/tokens.css (verification target — read-only) - apps/pwa/src/styles/tokens.css (post-restructure) - 17-VALIDATION.md §"Per-Task Verification Map" rows for D-06 (grep invariant + Schedule-X sweep + build) - .claude/skills/playwright-cli/ (browser-verification skill for the /calendar Schedule-X sweep) Confirm the no-hard-coded-values invariant still holds: component and route files reference only `var(--token)` — no literal hex or px values leaked in during the restructure (the restructure is selector-only, but verify nothing in components changed). Run the grep invariant from 17-VALIDATION.md across `apps/pwa/src/components/` and `apps/pwa/src/routes/`. Then verify the Schedule-X custom calendar colors are visually unchanged after the restructure using the playwright-cli skill: load `/calendar`, confirm the member/shared event chips render in the FamilySync custom colors (member-0 blue, shared rose) and have NOT reverted to the Schedule-X default theme colors. This is the regression check for RESEARCH Pitfall 3 (cascade break). If the grep flags any pre-existing literals that are NOT introduced by this restructure, do NOT fix them in this plan (out of scope — selector-only change); record them in the SUMMARY as a noted observation instead. ! grep -rnE '#[0-9a-fA-F]{3,6}|[0-9]+px' apps/pwa/src/components apps/pwa/src/routes --include='*.tsx' --include='*.ts' | grep -v 'var(--' | grep -vE '^\s*//|^\s*\*' | grep -q . ; echo "grep invariant check exit=$?" - The grep invariant command from 17-VALIDATION.md (D-06 row) shows no NEW hard-coded hex/px literals introduced by this plan in component/route files. - playwright-cli sweep of `/calendar`: Schedule-X event chips render in FamilySync custom colors (member blue + shared rose), not Schedule-X default blue/green — visual confirmation captured (screenshot or observation noted in SUMMARY). - `pnpm --filter @familysync/pwa exec playwright test --project=pixel layout.spec.ts` still green (no structural regression from the token change). The grep invariant passes with no new literals, Schedule-X colors are confirmed unchanged via playwright-cli on /calendar, and the layout.spec.ts pixel profile is green. ## Trust Boundaries | Boundary | Description | |----------|-------------| | (none new) | This plan is a CSS-only restructure of a static stylesheet. No new trust boundary is introduced. | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-17-01-01 | Tampering | tokens.css restructure | accept | CSS custom properties carry no executable content and no user input; a selector change cannot introduce injection. No new threat above LOW for Workstream C. | No new high/medium-severity threats. This is a static stylesheet selector restructure with zero runtime data flow and no new dependencies. - `grep -qE '\[data-theme="light"\]' apps/pwa/src/styles/tokens.css` — combined selector present - `grep -q 'bottom-chrome-h' apps/pwa/src/styles/tokens.css` — new token present - `pnpm --filter @familysync/pwa build` — exits 0 - playwright-cli `/calendar` sweep — Schedule-X custom colors intact - `pnpm --filter @familysync/pwa exec playwright test --project=pixel layout.spec.ts` — green tokens.css is restructured to the combined `:root, [data-theme="light"]` selector with all values unchanged, `--bottom-chrome-h` added as the single source of truth for bottom-chrome height, Schedule-X overrides confirmed working, the no-hard-coded-values invariant intact, and the build + pixel layout suite green. Create `.planning/phases/17-ui-optimization-polish/17-01-SUMMARY.md` when done.