Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
7.6 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-mobile-test-harness | 03 | test-harness |
|
|
|
|
|
|
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 ≤ clientWidthon /calendarscrollWidth ≤ clientWidthon /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 passedgrep -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')withoutexact:truealso 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'(withTseparator), which MariaDB TIMESTAMP rejects withIncorrect datetime value. MariaDB requires'YYYY-MM-DD HH:MM:SS'format. - Fix: Added
.replace('T', ' ')and removed the trailingZ— 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),
AppNavrendersPhoneNav(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.tsreturns empty — all navigation uses relative paths (/calendar,/lists) that resolve againstplaywright.config.tsbaseURL. - T-07-12 (SW intercept):
serviceWorkers: 'block'is set per-context inplaywright.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 commitd3c6726)- Fix commit
d3c6726— exists - Task commit
52e14a8— exists - 30 tests passing on both profiles — verified by final run output