---
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 <html> (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-"
---

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

<tasks>

<task type="auto">
  <name>Task 1: Restructure tokens.css to a themeable layer and add --bottom-chrome-h</name>
  <files>apps/pwa/src/styles/tokens.css</files>
  <read_first>
    - 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)
  </read_first>
  <action>
    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.
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <acceptance_criteria>
    - `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).
  </acceptance_criteria>
  <done>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.</done>
</task>

<task type="auto">
  <name>Task 2: Verify Schedule-X colors and the no-hard-coded-values invariant survive the restructure</name>
  <files>apps/pwa/src/styles/tokens.css (verification target — read-only)</files>
  <read_first>
    - 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)
  </read_first>
  <action>
    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.
  </action>
  <verify>
    <automated>! 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=$?"</automated>
  </verify>
  <acceptance_criteria>
    - 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).
  </acceptance_criteria>
  <done>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.</done>
</task>

</tasks>

<threat_model>
## 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.
</threat_model>

<verification>
- `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
</verification>

<success_criteria>
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.
</success_criteria>

<output>
Create `.planning/phases/17-ui-optimization-polish/17-01-SUMMARY.md` when done.
</output>
