docs(07-03): complete layout.spec.ts plan

This commit is contained in:
Lucas Berger
2026-06-11 02:02:54 -04:00
parent 52e14a88db
commit f8227855dc
3 changed files with 132 additions and 7 deletions
+3 -3
View File
@@ -66,7 +66,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 07-03-PLAN.md — layout.spec.ts: tap targets >=44px, no overflow, in-viewport, accessible names (UI-SPEC Rules 1-4) + harness self-validation injected-defect proofs (Wave 3)
- [x] 07-03-PLAN.md — layout.spec.ts: tap targets >=44px, no overflow, in-viewport, accessible names (UI-SPEC Rules 1-4) + harness self-validation injected-defect proofs (Wave 3)
- [ ] 07-04-PLAN.md — calendar.spec.ts + lists.spec.ts: populated/empty/error states (Rules 4/5) + DEV_AUTH_BYPASS auth-reached + no-SW-controller precondition (Wave 3)
**UI hint**: yes
@@ -200,7 +200,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
| 4. Shared Lists + Live Sync | v1.0 | 7/7 | Complete | 2026-06-09 |
| 5. Web Push Notifications | v1.0 | 8/8 | Complete | 2026-06-10 |
| 6. UX Polish | v1.0 | 6/6 | Complete | 2026-06-10 |
| 7. Mobile Test Harness | v1.1 | 2/4 | In Progress| |
| 7. Mobile Test Harness | v1.1 | 3/4 | In Progress| |
| 8. Gitea CI | v1.1 | 0/? | Not started | - |
| 9. Faster Write-Back | v1.1 | 0/? | Not started | - |
| 10. Admin Role & Settings | v1.1 | 0/? | Not started | - |
@@ -213,7 +213,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
**Requirements:** TBD
**Plans:** 2/4 plans executed
**Plans:** 3/4 plans executed
Plans:
+5 -4
View File
@@ -4,13 +4,13 @@ milestone: v1.1
milestone_name: Operability & Polish
status: executing
stopped_at: Phase 7 UI-SPEC approved
last_updated: "2026-06-11T05:52:57.489Z"
last_updated: "2026-06-11T06:02:41.305Z"
last_activity: 2026-06-11 -- Phase 07 execution started
progress:
total_phases: 14
completed_phases: 0
total_plans: 4
completed_plans: 2
completed_plans: 3
percent: 0
---
@@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-06-10)
## Current Position
Phase: 07 (mobile-test-harness) — EXECUTING
Plan: 3 of 4
Plan: 4 of 4
Status: Ready to execute
Last activity: 2026-06-11 -- Phase 07 execution started
@@ -79,6 +79,7 @@ Last activity: 2026-06-11 -- Phase 07 execution started
| Phase 06-ux-polish P06 | 45 | 4 tasks | 5 files |
| Phase 07 P01 | 310 | 3 tasks | 7 files |
| Phase 07 P02 | 196 | 2 tasks | 4 files |
| Phase 07-mobile-test-harness P03 | 480 | 2 tasks | 2 files |
## Accumulated Context
@@ -186,7 +187,7 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-06-11T05:52:57.479Z
Last session: 2026-06-11T06:02:41.295Z
Stopped at: Phase 7 UI-SPEC approved
Resume file: .planning/phases/07-mobile-test-harness/07-UI-SPEC.md
@@ -0,0 +1,124 @@
---
phase: 07-mobile-test-harness
plan: "03"
subsystem: test-harness
tags: [playwright, e2e, layout, tap-targets, overflow, accessibility, self-validation]
dependency_graph:
requires:
- "apps/pwa/playwright.config.ts (07-01) — iphone/pixel project matrix, serviceWorkers: 'block'"
- "apps/pwa/e2e/global-setup.ts (07-02) — /health readiness gate + DB seed (calendar_id=10, E2E Grocery List)"
provides:
- "apps/pwa/e2e/layout.spec.ts — UI-SPEC Rules 1-4 assertions (tap targets, overflow, in-viewport, accessible names)"
- "Harness self-validation: addStyleTag injected-defect proofs for Rule 1 + Rule 2"
- "30 tests (15 per profile) — all passing on iphone (WebKit) and pixel (Chromium)"
affects:
- "Phase 07 plan 04 (calendar.spec.ts / lists.spec.ts share the same harness foundation)"
- "Phase 08 CI (layout.spec.ts is a PR regression step)"
tech_stack:
added: []
patterns:
- "boundingBox() — rendered geometry measurement, not CSS-declared values"
- "page.evaluate(() => scrollWidth/clientWidth) — DOM overflow measurement"
- "page.addStyleTag + handle.evaluate(el => el.remove()) — injected-defect proof pattern"
- "getByRole('navigation', { name }) scoping — avoids strict-mode collision between BottomTabBar and DesktopNav"
- "getByText('FamilySync', { exact: true }) — avoids matching 'Install FamilySync' install-prompt"
key_files:
created:
- apps/pwa/e2e/layout.spec.ts
modified:
- apps/pwa/e2e/global-setup.ts
decisions:
- "D-03-SCOPE-NAV: On mobile profiles (390px/412px), AppNav renders PhoneNav as <header> (not a nav landmark) — only BottomTabBar exposes <nav aria-label='Main navigation'>. No strict-mode collision in practice, but tap-target locators are scoped inside the nav landmark for robustness."
- "D-03-PHONENAV-TEXT: getByText('FamilySync', { exact: true }) required — the InstallPrompt renders 'Install FamilySync', which getByText('FamilySync') without exact:true matches as a substring, causing a strict-mode violation on WebKit."
- "D-03-SELF-VALIDATION: Self-validation proofs use addStyleTag + handle.evaluate(el => el.remove()) to inject and remove the defect style within the same test. No page.reload() needed — handle removal is synchronous and immediately clears the injected CSS."
metrics:
duration_seconds: 480
completed_date: "2026-06-11"
tasks_completed: 2
files_changed: 2
---
# Phase 07 Plan 03: layout.spec.ts Layout Assertions Summary
**One-liner:** layout.spec.ts enforcing UI-SPEC Rules 1-4 (tap targets ≥44/56px, no horizontal overflow, in-viewport, accessible names) on iPhone/WebKit + Pixel/Chromium with addStyleTag injected-defect proofs — 30 tests, 0 failures.
## What Was Built
`apps/pwa/e2e/layout.spec.ts` with four describe blocks covering:
**Rule 1/3/4 — BottomTabBar on /calendar (8 tests per profile)**
- Navigation landmark visible (Rule 4 — accessible name proof)
- Calendar tab boundingBox ≥ 44×44px (Rule 1)
- Lists tab boundingBox ≥ 44×44px (Rule 1)
- BottomTabBar bottom edge ≤ viewport height (Rule 3 — in-viewport, safe-area-inset)
- PhoneNav header "FamilySync" visible (Rule 3)
- Settings button boundingBox ≥ 44×44px (`getByRole('button', { name: /open settings/i })`)
- New Event FAB boundingBox ≥ 56×56px (Rule 1 — larger threshold)
**Rule 1/3/4 — BottomTabBar on /lists (4 tests per profile)**
- Navigation landmark visible on /lists
- Calendar tab ≥ 44×44px on /lists
- Lists tab ≥ 44×44px on /lists
- BottomTabBar in-viewport on /lists
**Rule 2 — No horizontal overflow (2 tests per profile)**
- `scrollWidth ≤ clientWidth` on /calendar
- `scrollWidth ≤ clientWidth` on /lists
**Harness self-validation — injected defects (2 tests per profile)**
- Rule 1 proof: injects `nav[aria-label="Main navigation"] a { height: 20px !important }`, asserts height < 44, removes, asserts height ≥ 44 — proves boundingBox tracks rendered geometry
- Rule 2 proof: injects `body { width: 2000px !important }`, asserts scrollWidth > clientWidth, removes, asserts scrollWidth ≤ clientWidth — proves overflow detection is live
**Total: 30 tests (15 iphone, 15 pixel), 0 failures.**
## Verification Evidence
- `playwright test e2e/layout.spec.ts` (both profiles): `30 passed`
- `grep -E "https?://" apps/pwa/e2e/layout.spec.ts` → empty (no absolute URLs)
- `grep -c addStyleTag apps/pwa/e2e/layout.spec.ts` → 2 (both self-validation proofs present)
- All locators use `getByRole(..., { name })` or scoped-within-nav — no CSS selector fallback
- No strict-mode "resolved to N elements" errors in either profile run
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] WebKit strict-mode violation: `getByText('FamilySync')` matched 2 elements**
- **Found during:** Task 1 first iphone run
- **Issue:** `getByText('FamilySync')` without `exact:true` also matched the `<div>Install FamilySync</div>` text in the InstallPrompt component, causing a strict-mode violation on WebKit (where the install prompt was visible).
- **Fix:** Changed to `getByText('FamilySync', { exact: true })` — matches only the `<span>FamilySync</span>` in PhoneNav.
- **Files modified:** `apps/pwa/e2e/layout.spec.ts`
- **Commit:** 52e14a8
**2. [Rule 3 - Blocking] global-setup.ts: MariaDB TIMESTAMP rejected ISO 8601 format**
- **Found during:** Task 1 execution — global-setup failed before any spec could run
- **Issue:** `futureStart.toISOString().replace(/\.\d+Z$/, 'Z')` produces `'2026-06-12T05:58:35Z'` (with `T` separator), which MariaDB TIMESTAMP rejects with `Incorrect datetime value`. MariaDB requires `'YYYY-MM-DD HH:MM:SS'` format.
- **Fix:** Added `.replace('T', ' ')` and removed the trailing `Z` — produces `'2026-06-12 05:58:35'` which MariaDB TIMESTAMP accepts.
- **Files modified:** `apps/pwa/e2e/global-setup.ts`
- **Commit:** d3c6726 (fix(07-02))
**3. [Observation] DesktopNav nav landmark absent on mobile profiles — no strict-mode risk**
- **Found during:** Component analysis before writing locators
- **Issue:** The plan warned about strict-mode collision between BottomTabBar nav and DesktopNav nav, both named "Main navigation". In practice, on mobile profiles (390px/412px), `AppNav` renders `PhoneNav` (a `<header>`, not a nav), so DesktopNav's nav is absent. No collision occurs.
- **Fix:** Still scoped tap-target locators inside `getByRole('navigation', { name: 'Main navigation' })` for defensive robustness against any future layout change.
- **Files modified:** None (design decision, no code change)
## Known Stubs
None.
## Threat Surface Scan
No new network endpoints, auth paths, or schema changes. `layout.spec.ts` is a test-only file. Threat mitigations from plan:
- **T-07-07 (injected style leakage):** Each self-validation test removes the injected style via `handle.evaluate(el => el.remove())` within the same test before completing. Styles are page-scoped and do not persist across navigations or test contexts.
- **T-07-08 (hardcoded host):** `grep -E "https?://" apps/pwa/e2e/layout.spec.ts` returns empty — all navigation uses relative paths (`/calendar`, `/lists`) that resolve against `playwright.config.ts` `baseURL`.
- **T-07-12 (SW intercept):** `serviceWorkers: 'block'` is set per-context in `playwright.config.ts` (Plan 01). Geometry assertions (boundingBox, scrollWidth) cannot be satisfied by a cached SW response, so the assertions remain authoritative even if the block were bypassed.
## Self-Check: PASSED
- `apps/pwa/e2e/layout.spec.ts` — exists (`git show --stat 52e14a8`)
- `apps/pwa/e2e/global-setup.ts` — modified (fix commit d3c6726)
- Fix commit `d3c6726` — exists
- Task commit `52e14a8` — exists
- 30 tests passing on both profiles — verified by final run output