From 24777413376ec97cb7a78e6a71805f353deaffc0 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Wed, 10 Jun 2026 21:49:28 -0400 Subject: [PATCH 01/66] docs(07): capture phase context --- .../07-mobile-test-harness/07-CONTEXT.md | 103 ++++++++++++++++++ .../07-DISCUSSION-LOG.md | 80 ++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 .planning/phases/07-mobile-test-harness/07-CONTEXT.md create mode 100644 .planning/phases/07-mobile-test-harness/07-DISCUSSION-LOG.md diff --git a/.planning/phases/07-mobile-test-harness/07-CONTEXT.md b/.planning/phases/07-mobile-test-harness/07-CONTEXT.md new file mode 100644 index 0000000..6eb5fe1 --- /dev/null +++ b/.planning/phases/07-mobile-test-harness/07-CONTEXT.md @@ -0,0 +1,103 @@ +# Phase 7: Mobile Test Harness - Context + +**Gathered:** 2026-06-10 +**Status:** Ready for planning + + +## Phase Boundary + +Deliver an automated, mobile-emulated, authenticated Playwright harness that drives the FamilySync PWA against the host-side dev stack, so mobile-only layout / tap-target / flow defects are caught automatically rather than only by the operator on real devices. The same specs are the artifact Phase 8 (Gitea CI) runs as its PR UI-regression step. + +**In scope:** mobile-emulated browser driving (`@playwright/test`, new dev dep in `apps/pwa`), authenticated via `DEV_AUTH_BYPASS`, structured to run headlessly in CI against a stack the runner brings up. + +**Out of scope (stays a human/device gate):** real production-service-worker behavior, iOS-Safari standalone-PWA behavior (Home-Screen install, standalone OIDC redirect, iOS push), live event-create against Fastmail (dev-bypass user 1 has no CalDAV credential / calendars). No backend changes. + + + + +## Implementation Decisions + +### Auth & Service Worker (locked by ROADMAP / PITFALLS — not re-discussed) +- **D-01:** Auth via `DEV_AUTH_BYPASS=true` on the host-side dev stack — **never** a checked-in `storage-state.json` with an expiring session cookie (Pitfall 14). No Authelia/OIDC mocking. Dev-bypass resolves to Dev User id 1. +- **D-02:** Playwright context uses `serviceWorkers: 'block'` so the PWA's `injectManifest` SW (`sw.js`, `registerType: 'autoUpdate'`) cannot intercept requests / return stale cached responses (Pitfall 15). Verify the trace shows no SW-sourced responses. + +### Device Emulation +- **D-03:** Run a **two-profile matrix: iPhone + Pixel** — covers both household ecosystems (Apple + Android/Fastmail). The iPhone profile satisfies the hard non-technical-Apple-member UX constraint; Pixel covers Chrome-viewport defects. +- **D-04:** Use **faithful browser engines** per profile: iPhone → **WebKit**, Pixel → **Chromium**. Adds a WebKit browser to the harness/CI image. (Note: this exceeds the existing global `playwright-cli` Chromium tooling — the harness brings its own `@playwright/test` browsers.) SW-block + dev-bypass apply to both profiles. + +### Test Data +- **D-05:** **Hybrid** — seed deterministic DB fixtures for populated views **and** keep explicit empty-state assertions. Dev-bypass user 1 natively has no calendars (calendar/list views render empty, live create 422s), so populated coverage requires seeding. +- **D-06:** Seeding is **deterministic and reset per run** (truncate/reset → insert, not insert-if-absent) to guarantee repeatable day-over-day results with no stale state (SC #3). Seed onto the shared calendar (id 10, per prior project memory) + list items so user 1's views render populated. +- **D-07:** Seeding runs in **global-setup** against the dev MariaDB (already port-bound on 3306 via `docker-compose.dev.yml`); teardown/reset keeps runs idempotent. + +### Stack Lifecycle / Connection +- **D-08:** Harness targets a **configurable `baseURL`** (env-driven: operator's vite dev server locally, CI service host in Phase 8) with a **readiness gate in global-setup** (wait on `/health` before any spec; mirrors the PITFALLS CI-readiness guidance to avoid flaky ECONNREFUSED). +- **D-09:** **Stack bring-up is the caller's responsibility** — operator's already-running dev stack locally, compose orchestration in Phase 8 CI. The harness never depends on a pre-running stack; it waits for one. Satisfies SC #4. +- **D-10:** Optionally use Playwright `webServer` for **vite only** with `reuseExistingServer: !process.env.CI` (reuse the operator's `pnpm dev` locally, start vite fresh in CI). The API + MariaDB + Redis always stay compose-managed — `webServer` cannot own a multi-container stack. + +### Claude's Discretion +- **Assertion approach (D-08-area) — deferred to research.** User wants a robust, low-maintenance, host↔CI-portable pattern and expects this is well-documented prior art. **Steer:** lead with structural / role-based locator assertions + explicit tap-target measurements (computed box ≥ 44px, no horizontal overflow, visibility/position) which are stable across environments. Add `toHaveScreenshot` visual snapshots **only** if research finds a well-established way to keep them non-flaky across host↔CI rendering (CI-generated baselines + tolerance config); otherwise omit screenshots. The Schedule-X calendar widget makes naive pixel snapshots especially drift-prone — weigh that heavily. +- **Stack lifecycle (D-08–D-10):** user said "you decide" — decisions above are Claude's recommendation; planner may refine the exact env-var name and webServer wiring. +- Spec file location/structure, trace/artifact capture on failure, and npm-script + Makefile wiring were not discussed — planner's discretion (follow existing conventions: `apps/pwa`, pnpm filters, Makefile-first per global instructions). + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Phase scope & requirements +- `.planning/ROADMAP.md` § "Phase 7: Mobile Test Harness" — goal, success criteria (4), phase-owned pitfalls, dependency notes. +- `.planning/REQUIREMENTS.md` — TEST-01 (mobile-emulated viewport), TEST-02 (DEV_AUTH_BYPASS auth, dev-build scope, consumed by Phase 8 CI). + +### Pitfalls this phase owns (MUST read — they lock D-01/D-02) +- `.planning/research/PITFALLS.md` § "Pitfall 14: Playwright Authed-Mobile Harness Reusing a Stale storage-state" (≈L354) — why `DEV_AUTH_BYPASS`, not storage-state. +- `.planning/research/PITFALLS.md` § "Pitfall 15: Production Service Worker Intercepting Playwright Requests" (≈L375) — `serviceWorkers: 'block'`, verify trace has no SW-sourced responses. +- `.planning/research/PITFALLS.md` quick-reference rows (≈L424–425, L491) and the CI-readiness-wait row (≈L409) — readiness gate before specs. + +### Codebase conventions +- `.planning/codebase/TESTING.md` — current Vitest setup, test locations, the "E2E not implemented; playwright-cli skill used for smoke tests" gap this phase fills. +- `apps/pwa/vite.config.ts` — vite dev-server proxy (`/api`, `/health`, `/callback` → :3000), `injectManifest` SW config (the SW that D-02 blocks). +- `docker-compose.dev.yml` — dev override exposing MariaDB :3306 / Redis :6379, API `dev` target. The stack the harness targets. +- Project `CLAUDE.md` § "Browser-based verification" — playwright-cli is global Chromium; `@playwright/test` is NOT yet a repo dep (this phase adds it to `apps/pwa`). + + + + +## Existing Code Insights + +### Reusable Assets +- `DEV_AUTH_BYPASS` already wired across the API (`apps/api/src/auth/devBypass.ts`, `apps/api/src/index.ts`, route handlers) and used in API tests — the harness rides the existing bypass, no new auth code. +- Shared calendar id 10 + dev MariaDB on :3306 (per prior project memory `dev-data-user1-no-calendars` / `dev-stack-bringup`) — the seed target. +- Existing `apps/pwa` Vitest config + test conventions to mirror for harness file layout/naming (note: Playwright specs are typically `*.spec.ts`, distinct from Vitest `*.test.ts` globs — keep them separate so runners don't collide). + +### Established Patterns +- Vite dev server proxies `/api`, `/health`, `/callback` to the API on :3000 — `baseURL` points at the vite origin; readiness gate hits proxied `/health`. +- Dev API `dev` target needs its own build (dist can be stale, per prior memory) — relevant when CI brings up the stack. + +### Integration Points +- Phase 8 (Gitea CI) consumes these specs as its PR UI-regression step against a CI-brought-up dev stack — keep the harness stack-agnostic via `baseURL` + readiness gate (D-08/D-09). + + + + +## Specific Ideas + +- User explicitly wants the assertion strategy to be **robust and low-maintenance**, grounded in established/documented prior art rather than a bespoke approach — flagged as the primary research question. +- Faithful WebKit-for-iPhone fidelity was a deliberate choice over Chromium-only emulation, accepting the heavier browser image. + + + + +## Deferred Ideas + +### Reviewed Todos (not folded) +- **"Gitea CI — full regression on PR to main + build/publish Docker image"** (`.planning/todos/2026-06-10-gitea-ci-regression-and-docker-publish.md`, match score 0.6) — belongs to **Phase 8 (Gitea CI)**, which *consumes* this harness. Not folded; Phase 7 only produces CI-runnable specs, it does not own the CI pipeline. + + + +--- + +*Phase: 7-Mobile Test Harness* +*Context gathered: 2026-06-10* diff --git a/.planning/phases/07-mobile-test-harness/07-DISCUSSION-LOG.md b/.planning/phases/07-mobile-test-harness/07-DISCUSSION-LOG.md new file mode 100644 index 0000000..c624509 --- /dev/null +++ b/.planning/phases/07-mobile-test-harness/07-DISCUSSION-LOG.md @@ -0,0 +1,80 @@ +# Phase 7: Mobile Test Harness - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-06-10 +**Phase:** 7-Mobile Test Harness +**Areas discussed:** Device profile(s), Test-data strategy, Assertion approach, Stack lifecycle / baseURL + +--- + +## Device profile(s) + +| Option | Description | Selected | +|--------|-------------|----------| +| iPhone only | Single iPhone profile; matches Apple-member UX constraint; fastest, misses Android-Chrome layout | | +| iPhone + Pixel | Two-profile matrix covering both ecosystems; ~2x runtime | ✓ | +| iPhone + small-Android | iPhone + narrow Android profile to stress tightest viewport | | + +**User's choice:** iPhone + Pixel + +### Follow-up: engine fidelity + +| Option | Description | Selected | +|--------|-------------|----------| +| Faithful engines | iPhone → WebKit, Pixel → Chromium; most faithful; adds WebKit to CI image | ✓ | +| Chromium-only | Both Chromium, iPhone viewport/UA/touch only; lighter, matches playwright-cli | | + +**User's choice:** Faithful engines +**Notes:** Accepts heavier browser image for true WebKit/Chromium rendering fidelity. + +--- + +## Test-data strategy + +| Option | Description | Selected | +|--------|-------------|----------| +| Seed DB fixtures | Insert deterministic rows before run; realistic end-to-end render path | | +| Mock API routes | Playwright route-fulfill canned JSON; hermetic, bypasses real API | | +| Chrome/empty-states only | No population; assert nav/drawers/tap-targets/empty copy; smallest scope | | +| Hybrid: seed + empty | Seed DB for populated views + keep empty-state assertions; broadest coverage | ✓ | + +**User's choice:** Hybrid: seed + empty +**Notes:** Captured constraint — seed must be deterministic and reset per run (SC #3, repeatable day-over-day); runs in global-setup against MariaDB :3306, targets shared calendar id 10 + lists. + +--- + +## Assertion approach + +| Option | Description | Selected | +|--------|-------------|----------| +| Structural + tap-targets | Role/locator + measured box checks (≥44px, no overflow); portable, stable; misses visual drift | | +| Both (structural + screenshots) | Add toHaveScreenshot; catches visual regressions but flaky cross-env | | +| Screenshots-primary | Lean on visual snapshots; highest flakiness/maintenance | | + +**User's choice:** Other (free text) — "Defer this decision to research and for you to decide as it needs to be robust and low maintenance. I have to imagine this has been done elsewhere before and should be well documented." +**Notes:** Marked as research question, not locked. Claude's steer: lead with structural + tap-target measurement; add screenshots only if research finds a non-flaky CI-baseline pattern. Schedule-X widget makes naive pixel snapshots drift-prone. + +--- + +## Stack lifecycle / baseURL + +| Option | Description | Selected | +|--------|-------------|----------| +| baseURL + readiness wait | Configurable baseURL, readiness gate; caller owns stack bring-up; matches existing dev stack | ✓ (Claude, per "you decide") | +| Playwright webServer | Auto-start vite; can't own multi-container API/DB/Redis stack | (partial — vite only) | +| You decide | Pick best fit for SC #4 + local ergonomics | ✓ | + +**User's choice:** You decide +**Notes:** Claude's recommendation — baseURL (env-driven) + global-setup readiness gate on /health; caller (operator locally / compose in CI) brings up the stack; optional webServer for vite only with `reuseExistingServer: !CI`; API+MariaDB+Redis stay compose-managed. Satisfies SC #4. + +## Claude's Discretion + +- **Assertion approach** — deferred to research (robust/low-maintenance, host↔CI portable). +- **Stack lifecycle** — "you decide"; recommendation captured above, planner may refine env-var name / webServer wiring. +- Spec file location/structure, failure trace/artifact capture, npm-script + Makefile wiring — not discussed; planner's discretion following existing conventions. + +## Deferred Ideas + +- "Gitea CI — full regression + Docker publish" todo (score 0.6) — belongs to Phase 8, which consumes this harness. Reviewed, not folded. -- 2.54.0 From c4b2f55ccf8ca16456baaec13a03b69b002db5a3 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Wed, 10 Jun 2026 21:49:33 -0400 Subject: [PATCH 02/66] docs(state): record phase 7 context session --- .planning/STATE.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.planning/STATE.md b/.planning/STATE.md index a0c7a5f..89f5012 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,10 +3,11 @@ gsd_state_version: 1.0 milestone: v1.1 milestone_name: Operability & Polish status: planning -last_updated: "2026-06-10T23:30:00.000Z" -last_activity: 2026-06-10 +stopped_at: Phase 7 context gathered +last_updated: "2026-06-11T01:49:32.921Z" +last_activity: 2026-06-10 — v1.1 roadmap reordered (6 phases, 17/17 requirements mapped) progress: - total_phases: 6 + total_phases: 14 completed_phases: 0 total_plans: 0 completed_plans: 0 @@ -183,9 +184,9 @@ Recent decisions affecting current work: ## Session Continuity -Last session: 2026-06-10T23:30:00.000Z -Stopped at: v1.1 roadmap reordered (Phases 7–12; 17/17 requirements mapped) -Resume file: None +Last session: 2026-06-11T01:49:32.904Z +Stopped at: Phase 7 context gathered +Resume file: .planning/phases/07-mobile-test-harness/07-CONTEXT.md ## Operator Next Steps -- 2.54.0 From 179766d44742ba1ada406f0dcab4741f238c0240 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Wed, 10 Jun 2026 22:11:18 -0400 Subject: [PATCH 03/66] docs(07): mobile UI quality-bar contract (UI-SPEC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 07-UI-SPEC.md for Phase 7 (Mobile Test Harness). Documents the assertable mobile-UI quality bar the harness enforces: touch-target thresholds (≥44px), no-horizontal-overflow rule, two-profile viewport matrix (iPhone 14/WebKit + Pixel 7/Chromium), role-based accessible name requirements, empty/error-state copy anchors, screenshot omission rationale, and CI portability rules. Locks D-01 through D-10 from CONTEXT.md as the assertion contract. --- .../07-mobile-test-harness/07-UI-SPEC.md | 388 ++++++++++++++++++ 1 file changed, 388 insertions(+) create mode 100644 .planning/phases/07-mobile-test-harness/07-UI-SPEC.md diff --git a/.planning/phases/07-mobile-test-harness/07-UI-SPEC.md b/.planning/phases/07-mobile-test-harness/07-UI-SPEC.md new file mode 100644 index 0000000..fad46c8 --- /dev/null +++ b/.planning/phases/07-mobile-test-harness/07-UI-SPEC.md @@ -0,0 +1,388 @@ +--- +phase: 7 +slug: mobile-test-harness +status: draft +shadcn_initialized: false +preset: none +created: 2026-06-11 +framing: quality-bar-contract +--- + +# Phase 7 — Mobile UI Quality-Bar Contract + +> This phase builds **no new UI**. The harness asserts against the existing +> FamilySync PWA. This document is a **quality-bar contract**, not a design +> system spec. Its job is to pin every measurable threshold the harness must +> enforce so the planner can turn each rule into a concrete Playwright +> assertion. Template sections that have no assertable content for a test +> harness are marked N/A with a one-line reason. + +--- + +## Design System + +N/A — test harness, no new UI. The existing design system is declared in +`apps/pwa/src/styles/tokens.css` and consumed by the assertions below. + +| Property | Value | +|----------|-------| +| Tool | none (no shadcn; inline CSS custom properties) | +| Preset | not applicable | +| Component library | none (lucide-react icons; Schedule-X calendar widget) | +| Icon library | lucide-react (via npm dep, no CDN) | +| Font | `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` | + +--- + +## Spacing Scale + +N/A — test harness, no new UI. Spacing tokens are declared in `tokens.css` +and are not re-specified here. Assertions reference computed pixel values +derived from those tokens where needed (e.g. BottomTabBar height = 56px + +safe-area-inset). + +--- + +## Typography + +N/A — test harness, no new UI. Typography tokens exist in `tokens.css`; the +harness does not assert on font metrics unless a visible-text / accessible-name +check requires it (captured in Assertion Contract below). + +--- + +## Color + +N/A — test harness, no new UI. The 60/30/10 color split is declared in +`tokens.css`. The harness does not assert computed colors — color drift is +out of scope and prone to rendering-pipeline variance. + +--- + +## Copywriting Contract + +Copywriting that the harness **must** be able to locate by text in assertions. +These are the exact strings emitted by the existing components; the harness +uses them as stable locator anchors. + +| Element | Exact String | Source Component | +|---------|-------------|------------------| +| Calendar empty-state heading | `Nothing here` | `EmptyState.tsx` | +| Calendar empty-state body | `No events in this period. Try a different date or switch views.` | `EmptyState.tsx` | +| Lists empty-state heading | `No lists yet` | `ListsEmptyState.tsx` | +| Lists empty-state body | `Tap + to create your first shared list` (contains) | `ListsEmptyState.tsx` | +| Calendar error heading | `Couldn't load events` | `CalendarShell.tsx` | +| Calendar error CTA | `Retry` (button text) | `CalendarShell.tsx` | +| New Event FAB | `aria-label="New Event"` | `CalendarShell.tsx` | +| Bottom nav — Calendar tab | `aria-label="Calendar"` | `BottomTabBar.tsx` | +| Bottom nav — Lists tab | `aria-label="Lists"` | `BottomTabBar.tsx` | +| Top nav (phone) | `FamilySync` (visible text) | `AppNav.tsx` — `PhoneNav` | +| Settings button | `aria-label` contains `open settings` | `AppNav.tsx` — `PhoneNav` | + +> Stable copywriting anchor rule: **always locate interactive elements by +> `aria-label` or `role` + accessible name first.** Text-content locators +> (`getByText`) are second resort — acceptable for static headings/bodies +> that have no ARIA role. + +--- + +## Registry Safety + +N/A — test harness, no new UI components. `@playwright/test` is a new dev +dependency in `apps/pwa`; it is the official Playwright package from the +Playwright team and requires no safety vetting under this gate. + +--- + +## Assertion Contract + +This section is the primary deliverable for Phase 7. It replaces the +design-system sections of the standard template with the measurable +quality-bar rules that the harness enforces. + +### Device / Viewport Matrix + +| Profile ID | Playwright Descriptor | Engine | Viewport | UA Type | +|---|---|---|---|---| +| `iphone` | `'iPhone 14'` | WebKit | 390×844 logical px | Mobile Safari | +| `pixel` | `'Pixel 7'` | Chromium | 412×915 logical px | Chrome Android | + +**Source:** D-03 (iPhone + Pixel matrix), D-04 (WebKit for iPhone, Chromium +for Pixel). These are the exact Playwright device descriptor strings to pass +to `devices['iPhone 14']` and `devices['Pixel 7']` in `playwright.config.ts`. + +Both profiles run with `serviceWorkers: 'block'` (D-02 / Pitfall 15) and +`DEV_AUTH_BYPASS=true` (D-01 / Pitfall 14). No `storageState` file. + +**CI note:** Both engines must be installed in the Phase 8 CI image. The +harness adds WebKit beyond the existing global `playwright-cli` (Chromium +only). Accept the larger CI image cost — this was a deliberate call (D-04, +07-CONTEXT.md §Specifics). + +--- + +### Rule 1 — Touch-Target Minimum + +**Threshold:** Every interactive element (button, link, `role="button"`) must +have a computed bounding box of **≥ 44 × 44 logical pixels**. + +**Basis:** +- Apple Human Interface Guidelines: minimum touch target 44×44 pt. +- WCAG 2.5.5 (Level AAA): minimum 44×44 CSS px. +- The existing codebase declares this as a hard constraint: `BottomTabBar` + uses `minHeight: '44px'`; `AppNav` `PhoneNav` settings button uses + `minWidth: '44px', minHeight: '44px'`; calendar FAB is `56×56px`; Retry + button uses `minHeight: '44px'`; nav links use `minHeight: '44px'`. + +**Measurement approach:** +```typescript +// Use boundingBox() on the element handle, not CSS-declared values. +const box = await element.boundingBox() +expect(box!.width).toBeGreaterThanOrEqual(44) +expect(box!.height).toBeGreaterThanOrEqual(44) +``` + +**What counts as an interactive target:** +- `