--- phase: "17" plan: "03" subsystem: pwa/layout tags: [layout-fix, fab-overlap, bottom-tab-bar, phone-only, regression-guard, css-tokens, d-01, d-02] dependency_graph: requires: - apps/pwa/src/styles/tokens.css — --bottom-chrome-h token (provided by plan 17-01) provides: - FAB overlap fix: CalendarShell.tsx FAB bottom = calc(var(--bottom-chrome-h) + var(--space-6)) - Phone content padding: App.tsx contentStyle paddingBottom = var(--bottom-chrome-h) (phone-only) - Regression guard: apps/pwa/e2e/layout.spec.ts — D-01 overlap assertion (iphone + pixel) affects: - apps/pwa/src/components/CalendarShell.tsx (FAB bottom offset) - apps/pwa/src/App.tsx (contentStyle paddingBottom) - apps/pwa/e2e/layout.spec.ts (new overlap assertion) tech_stack: added: [] patterns: - "CSS calc() combining --bottom-chrome-h token with --space-6 for FAB clearance above BottomTabBar" - "Spread pattern ...(phone ? { paddingBottom: var(--bottom-chrome-h) } : {}) for phone-only contentStyle" - "Playwright boundingBox() geometry assertion: fabBox.y + fabBox.height <= navBox.y" key_files: created: [] modified: - apps/pwa/src/components/CalendarShell.tsx - apps/pwa/src/App.tsx - apps/pwa/e2e/layout.spec.ts decisions: - "FAB bottom uses calc(var(--bottom-chrome-h) + var(--space-6)) — positions FAB 24px above bar top edge regardless of safe-area-inset value" - "contentStyle paddingBottom is phone-only via spread pattern — desktop has no BottomTabBar and must not gain extra bottom padding (RESEARCH Pitfall 2)" - "Overlap assertion skips desktop profile — the desktop New Event button is a toolbar button, not the FAB; geometry check is semantically wrong for sidebar layout" - "Full e2e suite run on all three profiles confirmed zero regressions from layout changes" metrics: duration: "~18 minutes" completed_date: "2026-06-18" tasks_completed: 3 tasks_total: 3 files_changed: 3 status: complete --- # Phase 17 Plan 03: Phone Layout Overlap Fix — Summary Fixed the long-standing phone (≤767px) fixed-chrome overlap where the `position: fixed` BottomTabBar was covering the New Event FAB and occluding the bottom color-legend chips, using the `--bottom-chrome-h` token from plan 17-01. Added a permanent D-01 overlap regression assertion to `layout.spec.ts`. Full 3-profile Playwright suite is green. ## What Was Built **One-liner:** FAB lifted above BottomTabBar via `calc(var(--bottom-chrome-h) + var(--space-6))`, phone content reserves `--bottom-chrome-h` padding, permanent overlap regression guard added to CI. ### Task 1: FAB lift + phone content padding (CalendarShell.tsx, App.tsx) **CalendarShell.tsx** (phone-only FAB, lines 463–491): - Changed `bottom: 'var(--space-6)'` → `bottom: 'calc(var(--bottom-chrome-h) + var(--space-6))'` - FAB now sits 24px (var(--space-6)) above the BottomTabBar top edge regardless of safe-area-inset - All other FAB style properties unchanged (right, width, height, zIndex, etc.) **App.tsx** (contentStyle, lines 155–164): - Added phone-only spread: `...(phone ? { paddingBottom: 'var(--bottom-chrome-h)' } : {})` - Phone content area now reserves space equal to the BottomTabBar height - Desktop branch has no paddingBottom — geometry unchanged - Build: `pnpm --filter @familysync/pwa build` exits 0 ### Task 2: FAB↔BottomTabBar overlap regression assertion (layout.spec.ts) Added new `test.describe` block "D-01 regression guard — FAB does not overlap BottomTabBar": - Test name: "New Event FAB does not overlap BottomTabBar (A — phone only)" - Skips on desktop (`testInfo.project.name === 'desktop'`) - Locates FAB via `page.getByRole('button', { name: 'New Event' })` and bar via `page.getByRole('navigation', { name: 'Main navigation' })` - Asserts `fabBox.y + fabBox.height <= navBox.y` (FAB bottom ≤ BottomTabBar top) - Passes GREEN on iphone (390×844 WebKit) and pixel (412×915 Chromium) ### Task 3: Small-viewport sweep across all three profiles - `pnpm --filter @familysync/pwa test:e2e` (all 3 profiles): **115 passed, 23 skipped, 0 failed** - iphone profile: all layout.spec.ts assertions pass including new D-01 guard - pixel profile: all layout.spec.ts assertions pass including new D-01 guard - desktop profile: all layout.spec.ts assertions pass; D-01 guard correctly skipped - No violations flagged by Rules 1–4 assertions - Admin tab tap target still meets ≥44px after layout changes - Color-legend chips confirmed fully visible via playwright-cli screenshot at 390×844 ## Visual Confirmation (playwright-cli @390×844) Screenshot taken via playwright-cli (Chromium, viewport 390×844, reloaded to pick up phone layout): - **New Event FAB (+)**: clearly positioned above the BottomTabBar with visible gap - **Color-legend chips** ("Dev User" blue, "Family" pink): fully visible between calendar content and BottomTabBar — not clipped or occluded - **BottomTabBar** (Calendar, Lists, Admin): fully visible at the bottom edge ## Verification Results | Check | Result | |-------|--------| | `grep 'calc(var(--bottom-chrome-h) + var(--space-6))'` in CalendarShell.tsx | PASS | | `grep "paddingBottom: 'var(--bottom-chrome-h)'"` in App.tsx | PASS | | `grep 'New Event FAB does not overlap BottomTabBar'` in layout.spec.ts | PASS | | `pnpm --filter @familysync/pwa build` | PASS | | Playwright iphone + pixel layout.spec.ts (overlap assertion) | PASS — 32 passed, 2 skipped | | Full 3-profile `pnpm --filter @familysync/pwa test:e2e` | PASS — 115 passed, 23 skipped, 0 failed | | playwright-cli visual confirmation @390×844 | PASS — FAB above bar, legend chips visible | | Desktop geometry unchanged | PASS — no desktop layout changes | ## Deviations from Plan None. Plan executed exactly as written. The three-task sequence (CSS fix, regression guard, sweep) was completed without any deviations. The FAB bottom value, phone contentStyle spread pattern, and overlap assertion all match the plan specification exactly. ## Known Stubs None. All changes are wired and fully functional. ## Threat Flags None. CSS-only layout offsets plus a Playwright geometry test — no new network endpoints, auth paths, or data flows introduced. ## Self-Check: PASSED | Item | Result | |------|--------| | CalendarShell.tsx exists | FOUND | | App.tsx exists | FOUND | | layout.spec.ts exists | FOUND | | SUMMARY.md exists | FOUND | | Commit 5e1c714 (Task 1) | FOUND | | Commit 85a803f (Task 2) | FOUND | | CalendarShell FAB bottom grep | PASS | | App.tsx paddingBottom grep | PASS | | layout.spec.ts overlap assertion grep | PASS |