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