Files
familysync/.planning/phases/17-ui-optimization-polish/17-03-PLAN.md
T

174 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 17-ui-optimization-polish
plan: 03
type: execute
wave: 2
depends_on: ["17-01"]
files_modified:
- apps/pwa/src/components/CalendarShell.tsx
- apps/pwa/src/App.tsx
- apps/pwa/e2e/layout.spec.ts
autonomous: true
requirements: [D-01, D-02]
must_haves:
truths:
- "The New Event FAB never intersects the BottomTabBar rect on iphone/pixel profiles"
- "Phone content scrolls fully above the BottomTabBar — the color-legend chips are not occluded"
- "Desktop layout geometry is unchanged (no extra bottom padding, no FAB offset change)"
- "A permanent overlap regression assertion guards the FAB↔BottomTabBar geometry in CI"
artifacts:
- path: "apps/pwa/e2e/layout.spec.ts"
provides: "Permanent FAB↔BottomTabBar overlap assertion (iphone + pixel, skipped on desktop)"
contains: "New Event FAB does not overlap BottomTabBar"
key_links:
- from: "apps/pwa/src/components/CalendarShell.tsx"
to: "apps/pwa/src/styles/tokens.css"
via: "FAB bottom offset reads var(--bottom-chrome-h)"
pattern: "var\\(--bottom-chrome-h\\)"
- from: "apps/pwa/src/App.tsx"
to: "apps/pwa/src/styles/tokens.css"
via: "phone contentStyle paddingBottom reads var(--bottom-chrome-h)"
pattern: "var\\(--bottom-chrome-h\\)"
---
<objective>
Fix the long-standing phone (≤767px) fixed-chrome overlap where the `position: fixed` BottomTabBar covers the New Event FAB and occludes the bottom of the calendar + color-legend chips (D-01, the phase seed defect), using the shared `--bottom-chrome-h` token added by plan 17-01 (D-02 fix technique). Add a permanent overlap regression assertion to `layout.spec.ts` (D-02 regression guard) and run the bounded small-viewport sweep across all three Playwright profiles. CSS-only — no behaviour change, no desktop geometry change.
Purpose: the FAB currently lands on the Admin tab and the "Dev User" legend is clipped at 390×844; this fix lifts the FAB above the bar and reserves content padding so nothing is occluded at rest.
Output: corrected FAB offset (CalendarShell.tsx), phone-only content padding (App.tsx), and a new CI overlap assertion (layout.spec.ts).
</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
@.planning/phases/17-ui-optimization-polish/17-01-SUMMARY.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Lift the FAB and reserve phone content padding via --bottom-chrome-h</name>
<files>apps/pwa/src/components/CalendarShell.tsx, apps/pwa/src/App.tsx</files>
<read_first>
- apps/pwa/src/components/CalendarShell.tsx lines 463491 (the phone-only FAB block; current `bottom: 'var(--space-6)'` is the defect site)
- apps/pwa/src/App.tsx lines 64, 84, 155163 (`isPhone()`, the `phone` boolean, and `contentStyle` which lacks paddingBottom)
- apps/pwa/src/components/BottomTabBar.tsx line ~73 (bar height `calc(56px + env(safe-area-inset-bottom, 0px))` — resolves identically to `--bottom-chrome-h`)
- 17-UI-SPEC.md §"Workstream A — Phone-Layout Overlap Fix" → "Fix contract" + "Visual invariants" (exact before/after values)
- 17-PATTERNS.md §"apps/pwa/src/components/CalendarShell.tsx" and §"apps/pwa/src/App.tsx" (line-anchored before/after excerpts + the `...(phone ? {...} : {})` spread pattern)
- 17-RESEARCH.md §"Common Pitfalls" → Pitfall 2 (paddingBottom must be phone-only)
- 17-01-SUMMARY.md (confirms `--bottom-chrome-h` is available in tokens.css)
</read_first>
<action>
In `apps/pwa/src/components/CalendarShell.tsx`, in the phone-only FAB style block, change ONLY the `bottom` property from `'var(--space-6)'` to `'calc(var(--bottom-chrome-h) + var(--space-6))'`. This sits the FAB `var(--space-6)` (24px) above the BottomTabBar top edge regardless of the safe-area-inset value. Leave `right`, `width`, `height`, `zIndex`, and all other FAB style properties unchanged.
In `apps/pwa/src/App.tsx`, add `paddingBottom` to `contentStyle` PHONE-ONLY using the existing `phone` boolean and the established spread pattern: append `...(phone ? { paddingBottom: 'var(--bottom-chrome-h)' } : {})` to the `contentStyle` object literal. Do NOT add this unconditionally — desktop has no BottomTabBar and must not gain extra bottom padding (RESEARCH Pitfall 2). Do not change any other contentStyle property.
Optionally (not required for the visual invariant) reference `var(--bottom-chrome-h)` from BottomTabBar.tsx's height; if you do, the value must remain identical (`calc(56px + env(safe-area-inset-bottom, 0px))`). Skipping this is fine — keep the diff minimal.
</action>
<verify>
<automated>grep -q 'calc(var(--bottom-chrome-h) + var(--space-6))' apps/pwa/src/components/CalendarShell.tsx && grep -q "paddingBottom: 'var(--bottom-chrome-h)'" apps/pwa/src/App.tsx && pnpm --filter @familysync/pwa build</automated>
</verify>
<acceptance_criteria>
- CalendarShell.tsx FAB `bottom` is exactly `calc(var(--bottom-chrome-h) + var(--space-6))`.
- App.tsx `contentStyle` adds `paddingBottom: 'var(--bottom-chrome-h)'` ONLY inside the `phone` branch (the `...(phone ? {...} : {})` spread); desktop branch has no paddingBottom.
- `pnpm --filter @familysync/pwa build` exits 0.
- No other FAB/contentStyle properties changed (diff is minimal, CSS-only).
</acceptance_criteria>
<done>The FAB clears the BottomTabBar by var(--space-6), phone content reserves --bottom-chrome-h padding, desktop geometry is untouched, and the build passes.</done>
</task>
<task type="auto">
<name>Task 2: Add the FAB↔BottomTabBar overlap regression assertion to layout.spec.ts</name>
<files>apps/pwa/e2e/layout.spec.ts</files>
<read_first>
- apps/pwa/e2e/layout.spec.ts lines 160 (file header, imports, existing `test.describe` + `boundingBox()` assertion pattern, the `navigation`/`Main navigation` role locator)
- apps/pwa/playwright.config.ts (the three profiles: iphone 390×844 WebKit, pixel 412×915 Chromium, desktop 1280×720 — the assertion skips desktop via `testInfo.project.name`)
- 17-UI-SPEC.md §"Workstream A" → "Regression guard (D-02 decision)" (the exact assertion contract)
- 17-PATTERNS.md §"apps/pwa/e2e/layout.spec.ts" (the new overlap assertion excerpt)
- 17-VALIDATION.md §"Wave 0 Requirements" (this assertion is the Wave 0 overlap-regression item)
</read_first>
<action>
Add a new Playwright test to `apps/pwa/e2e/layout.spec.ts` named "New Event FAB does not overlap BottomTabBar (A — phone only)". It must: skip on the desktop profile via `test.skip(testInfo.project.name === 'desktop', 'Phone-only assertion')`; `page.goto('/calendar')`; locate the FAB via `page.getByRole('button', { name: 'New Event' })` and the bar via `page.getByRole('navigation', { name: 'Main navigation' })`; read both `boundingBox()`; assert neither is null; and assert `fabBox.y + fabBox.height <= navBox.y` (the FAB bottom edge is at or above the BottomTabBar top edge). Match the existing file's test style and import (`import { test, expect } from '@playwright/test'`).
This is the permanent regression guard for the seed defect. It runs on iphone + pixel (the profiles that exposed the long-standing, desktop-invisible defect).
</action>
<verify>
<automated>grep -q 'New Event FAB does not overlap BottomTabBar' apps/pwa/e2e/layout.spec.ts && pnpm --filter @familysync/pwa exec playwright test --project=iphone --project=pixel layout.spec.ts</automated>
</verify>
<acceptance_criteria>
- The new test "New Event FAB does not overlap BottomTabBar (A — phone only)" exists in layout.spec.ts.
- It skips on desktop (`testInfo.project.name === 'desktop'`).
- It asserts `fabBox.y + fabBox.height <= navBox.y`.
- `pnpm --filter @familysync/pwa exec playwright test --project=iphone --project=pixel layout.spec.ts` is GREEN (the assertion passes against the Task 1 fix — proving the overlap is resolved on both phone profiles).
</acceptance_criteria>
<done>The overlap assertion is committed to layout.spec.ts, skips desktop, and passes green on iphone + pixel — confirming the fix and guarding against regression.</done>
</task>
<task type="auto">
<name>Task 3: Run the bounded small-viewport sweep across all three profiles</name>
<files>apps/pwa/e2e/layout.spec.ts (sweep run — fix only violations flagged, CSS-only)</files>
<read_first>
- 17-UI-SPEC.md §"Workstream A" → "Small-viewport sweep (D-01)" (the checklist: Admin tab tap target, ColorLegend chips visible, no overflow on phone profiles)
- 17-VALIDATION.md §"Per-Task Verification Map" D-01 rows (color-legend visible, no horizontal overflow Rule 2, tap targets Rule 1)
- apps/pwa/e2e/layout.spec.ts (Rules 14 assertions: ≥44px/≥56px tap targets, no overflow, in-viewport, accessible names)
- .claude/skills/playwright-cli/ (for the manual color-legend visibility/scroll observation the spec suite cannot fully cover)
</read_first>
<action>
Run the full `layout.spec.ts` suite across all three profiles (`test:e2e`). The D-01 sweep is checklist-driven by the existing Rules 14 assertions — fix ANY violation they flag, staying within the no-behaviour-change boundary (CSS/layout only). Expected areas to confirm per UI-SPEC: the Admin tab still meets ≥44px after the layout fix, the ColorLegend "Dev User"/member chips are fully visible (not occluded) now that content padding is added, and no horizontal overflow on phone profiles.
Use the playwright-cli skill at 390×844 to visually confirm the color-legend chips are reachable by scrolling and not clipped behind the bar (the occlusion symptom from the seed defect). Do NOT undertake a free-form audit or touch desktop geometry — fix only what the assertions flag.
</action>
<verify>
<automated>pnpm --filter @familysync/pwa test:e2e</automated>
</verify>
<acceptance_criteria>
- `pnpm --filter @familysync/pwa test:e2e` (full 3-profile suite) is GREEN — Rules 14 (tap targets, no overflow, in-viewport, accessible names) all pass on iphone/pixel/desktop after the fix.
- playwright-cli @390×844: the ColorLegend chips are fully visible (scroll-reachable, not clipped behind the BottomTabBar) — observation noted in SUMMARY.
- No desktop geometry regression (desktop profile assertions unchanged/green).
</acceptance_criteria>
<done>The full layout.spec.ts suite is green on all three profiles, the color-legend occlusion is confirmed resolved via playwright-cli, and no desktop geometry changed.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| (none new) | CSS-only layout offsets + a Playwright test. No runtime data flow, no new endpoints, no user input. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-17-03-01 | Tampering | FAB/content CSS offsets | accept | Pure layout geometry via existing CSS custom property; no executable content, no input. No new threat above LOW for Workstream A. |
No new high/medium-severity threats. CSS-only positioning change plus a geometry test.
</threat_model>
<verification>
- `grep -q 'calc(var(--bottom-chrome-h) + var(--space-6))' apps/pwa/src/components/CalendarShell.tsx` — FAB lifted
- `grep -q "paddingBottom: 'var(--bottom-chrome-h)'" apps/pwa/src/App.tsx` — phone padding added
- `grep -q 'New Event FAB does not overlap BottomTabBar' apps/pwa/e2e/layout.spec.ts` — regression guard added
- `pnpm --filter @familysync/pwa exec playwright test --project=iphone --project=pixel layout.spec.ts` — overlap assertion green
- `pnpm --filter @familysync/pwa test:e2e` — full 3-profile sweep green
</verification>
<success_criteria>
The FAB no longer overlaps the BottomTabBar on phone, the color-legend is no longer occluded, desktop geometry is unchanged, a permanent overlap assertion guards the regression in CI, and the full Playwright suite is green across all three profiles.
</success_criteria>
<output>
Create `.planning/phases/17-ui-optimization-polish/17-03-SUMMARY.md` when done.
</output>