docs(17): create phase plan (6 plans, 2 waves) — UI optimization & polish
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
---
|
||||
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 463–491 (the phone-only FAB block; current `bottom: 'var(--space-6)'` is the defect site)
|
||||
- apps/pwa/src/App.tsx lines 64, 84, 155–163 (`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 1–60 (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 1–4 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 1–4 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 1–4 (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>
|
||||
Reference in New Issue
Block a user