From 8742dd43a44663d46875e127e24d46e0b894af31 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 08:07:37 -0400 Subject: [PATCH] docs(14): create phase plan --- .planning/ROADMAP.md | 6 +- .../14-desktop-e2e-coverage/14-01-PLAN.md | 252 ++++++++++++++++++ 2 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/14-desktop-e2e-coverage/14-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 340d34c..57d48e2 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -252,7 +252,11 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem - The real work is the spec-compat pass, not CI plumbing — Phase 8 reused the Phase 7 harness unchanged, so the config addition is small but specs authored for touch/mobile need per-spec review. - Desktop WebKit is optional — the Apple member is already covered on mobile Safari via `iphone`; Desktop Chrome is likely sufficient for a shared/wall browser. -**Plans**: TBD +**Plans**: 1 plan + +Plans: +- [ ] 14-01-PLAN.md — Add the `desktop` Playwright project, desktop-skip the two mobile-only layout assertions (+ D-04 parity), update spec/README docs, and prove `pnpm test:e2e` is green on iphone + pixel + desktop with a blocking CI gate. + **UI hint**: no ## Progress diff --git a/.planning/phases/14-desktop-e2e-coverage/14-01-PLAN.md b/.planning/phases/14-desktop-e2e-coverage/14-01-PLAN.md new file mode 100644 index 0000000..0c58719 --- /dev/null +++ b/.planning/phases/14-desktop-e2e-coverage/14-01-PLAN.md @@ -0,0 +1,252 @@ +--- +phase: 14-desktop-e2e-coverage +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - apps/pwa/playwright.config.ts + - apps/pwa/e2e/layout.spec.ts + - apps/pwa/e2e/calendar.spec.ts + - apps/pwa/e2e/lists.spec.ts + - apps/pwa/e2e/README.md +autonomous: true +requirements: [SC-1, SC-2, SC-3] +must_haves: + truths: + - "A `desktop` Playwright project (Desktop Chrome, 1280×720, no hasTouch) exists in playwright.config.ts (SC-1, D-06)" + - "`pnpm --filter @familysync/pwa test:e2e` exits 0 on all three projects: iphone, pixel, desktop (SC-2)" + - "The two mobile-only assertions (BottomTabBar safe-area in-viewport, 56×56 FAB geometry) are skipped on desktop only, not deleted, and still run on iphone/pixel (SC-2, D-01, D-02)" + - "Cross-cutting tests (no-overflow, nav-landmark visible, tap targets ≥44px, populated/empty/error states) run unchanged on desktop (SC-2, D-04)" + - "The desktop gate is blocking — same severity as mobile, no continue-on-error carve-out (SC-3, D-03)" + artifacts: + - path: "apps/pwa/playwright.config.ts" + provides: "desktop project entry mirroring iphone/pixel" + contains: "name: 'desktop'" + - path: "apps/pwa/e2e/layout.spec.ts" + provides: "desktop-skip guards on the two mobile-only tests + optional D-04 parity assertion" + contains: "testInfo.project.name === 'desktop'" + - path: "apps/pwa/e2e/README.md" + provides: "harness docs listing the desktop profile" + contains: "desktop" + key_links: + - from: "apps/pwa/playwright.config.ts" + to: "devices['Desktop Chrome']" + via: "spread into the desktop project use block" + pattern: "devices\\['Desktop Chrome'\\]" + - from: "apps/pwa/e2e/layout.spec.ts" + to: "testInfo.project.name" + via: "test.skip first-statement guard keyed on the desktop project name" + pattern: "test\\.skip\\(\\s*testInfo\\.project\\.name === 'desktop'" +--- + + +Add a third `desktop` Playwright project (Desktop Chrome, 1280×720, no `hasTouch`) to +`apps/pwa/playwright.config.ts` and make the existing Phase 7 mobile-authored e2e specs +green on that no-touch desktop viewport — so the Phase 8 CI regression gate validates the +desktop layout/flows in addition to the `iphone`/`pixel` mobile profiles. + +The real work is the spec-compat pass, not CI plumbing. Phase 8's harness job already runs +`pnpm --filter @familysync/pwa test:e2e`, which executes every configured project; the new +`desktop` project is picked up automatically. `Desktop Chrome` uses the Chromium engine +already installed in CI (`playwright install --with-deps webkit chromium`), so no browser +install or CI plumbing change is required. + +Purpose: Earn a desktop-layout regression gate that is real (blocking, deterministic) without +duplicating the shared cross-cutting assertions (D-01) or weakening the mobile assertions (D-02). +Output: an extended `playwright.config.ts`, desktop-skip-guarded `layout.spec.ts`, header-comment +updates across the spec files, and an updated `e2e/README.md`. + + + +@$HOME/.claude/gsd-core/workflows/execute-plan.md +@$HOME/.claude/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +@.planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md +@.planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md + +@apps/pwa/playwright.config.ts +@apps/pwa/e2e/layout.spec.ts +@apps/pwa/e2e/calendar.spec.ts +@apps/pwa/e2e/lists.spec.ts +@apps/pwa/e2e/README.md +@apps/pwa/e2e/global-setup.ts + + + + + + Task 1: Add the `desktop` project to playwright.config.ts (SC-1, D-06) + apps/pwa/playwright.config.ts + + - apps/pwa/playwright.config.ts — the file being modified; mirror the existing `iphone` (lines 36-46) and `pixel` (lines 47-54) project entries exactly. + - .planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md §"apps/pwa/playwright.config.ts — add `desktop` project entry" (the exact new-entry shape and the four key invariants). + - .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-06 (single Desktop Chrome project, no Desktop WebKit, keep the default 1280×720 viewport). + + + Append a third project object to the `projects` array (after the `pixel` entry, before the closing `]`): + `name: 'desktop'` with `use: { ...devices['Desktop Chrome'], serviceWorkers: 'block' }`. Mirror the + iphone/pixel shape exactly. Do NOT add a `baseURL` override (inherited from the top-level `use` block, + line 29). Do NOT touch the shared `webServer` block (lines 57-64) or `globalSetup`. Keep `serviceWorkers: + 'block'` — mandatory on every project per D-02/Pitfall 15. Do NOT add Desktop WebKit (D-06). Keep the + `devices['Desktop Chrome']` default viewport (1280×720, no `hasTouch`); no custom width override. + Update the file header jsdoc (lines 4-12) so the device-matrix line names all three profiles + (iphone / pixel / desktop) and the example run-commands block reflects a desktop example. + + + cd apps/pwa && grep -q "name: 'desktop'" playwright.config.ts && grep -q "devices\['Desktop Chrome'\]" playwright.config.ts && grep -q "serviceWorkers: 'block'" playwright.config.ts && npx playwright test --list --project=desktop >/dev/null 2>&1 && echo OK + + + - `apps/pwa/playwright.config.ts` contains the literal `name: 'desktop'`. + - The desktop entry spreads `devices['Desktop Chrome']` and sets `serviceWorkers: 'block'`. + - No `baseURL` key appears inside the desktop project's `use` block (it is inherited). + - `npx playwright test --list --project=desktop` exits 0 (the project resolves and tests enumerate) — run from apps/pwa. + - The file header jsdoc lists three profiles, not two. + + The `desktop` project exists, resolves via `--project=desktop`, and mirrors the mobile entries with no baseURL/webServer duplication. + + + + Task 2: Desktop-skip the two mobile-only layout assertions + add D-04 parity (SC-2, D-01, D-02, D-04, D-05) + apps/pwa/e2e/layout.spec.ts + + - apps/pwa/e2e/layout.spec.ts — the file being modified; note the exact tests at line 62 ("BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)"), line 91 ("New Event FAB meets 56×56px touch-target minimum (Rule 1)"), and line 130 ("BottomTabBar is fully in-viewport on /lists (Rule 3)"). + - apps/pwa/e2e/calendar.spec.ts lines 56-62 — the established inline `test.skip(condition, reason)` first-statement pattern (the live skip reference in this suite). + - .planning/phases/14-desktop-e2e-coverage/14-PATTERNS.md §"apps/pwa/e2e/layout.spec.ts — spec-compat pass" (which tests skip, which pass unchanged, the D-04 parity block, and the testInfo signature) + §"Shared Patterns — Project-name conditional skip". + - .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-01 (one spec set, no desktop.spec.ts), D-02 (scope/skip, never weaken or delete mobile assertions), D-04 (parity-adapted: desktop toolbar button ≥44px), D-05 (do NOT add "mobile chrome absent on desktop" negative assertions — `New Event` is a shared accessible name). + + + Add a desktop-only skip guard to exactly the two mobile-only tests, plus the FAB test, using the + first-statement form `test.skip(testInfo.project.name === 'desktop', '')` with the + `testInfo` fixture added as the second body argument (`async ({ page }, testInfo) => {`): + (a) the line-62 "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)" test — reason: on desktop + the `Main navigation` landmark resolves to the DesktopNav sidebar, for which the safe-area-inset + bottom-edge assertion is semantically wrong; + (b) the line-130 "BottomTabBar is fully in-viewport on /lists (Rule 3)" test — same reason; + (c) the line-91 "New Event FAB meets 56×56px" test — reason: on desktop `New Event` resolves to the + toolbar button (CalendarShell.tsx:436-457), not the 56×56 FAB. + For D-04 parity, add a desktop toolbar-button ≥44px height assertion using + `page.getByRole('button', { name: 'New Event' })` and `boundingBox()`; place it so it runs ONLY on + desktop (e.g. a sibling test guarded by `test.skip(testInfo.project.name !== 'desktop', ...)`, or a + conditional desktop branch) — keep it from running on mobile where that name is the FAB. + Do NOT add any negative "mobile chrome absent" assertion (D-05). Do NOT modify the nav-landmark-visible, + 44×44 tab-target, PhoneNav-header, settings-button, Rule 2 overflow, or harness self-validation tests — + per PATTERNS they pass unchanged on desktop (the landmark resolves to the sidebar, sidebar links have + minHeight:44px, the DesktopNav title matches `getByText('FamilySync', { exact: true })`). Do NOT weaken + or delete any existing mobile assertion (D-02). Update the file header jsdoc (lines 10-12) to list all + three profiles. + + + cd apps/pwa && grep -c "testInfo.project.name === 'desktop'" e2e/layout.spec.ts | grep -qx 3 && grep -q "test.skip(\s*testInfo.project.name !== 'desktop'" e2e/layout.spec.ts && echo OK + + + - `e2e/layout.spec.ts` contains exactly three `test.skip(testInfo.project.name === 'desktop', ...)` guards (the two safe-area tests + the FAB test). + - A desktop parity assertion exists that checks `New Event` toolbar button height ≥ 44px and runs only on desktop (guarded by `testInfo.project.name !== 'desktop'` skip or equivalent desktop-only branch). + - No existing mobile assertion is removed or weakened: the file still contains the `>= 56` FAB-width/height checks and the `>= 44` tab-target checks (grep for `toBeGreaterThanOrEqual(56)` and `toBeGreaterThanOrEqual(44)` both still present). + - No `desktop.spec.ts` file is created (D-01). + - No negative "absent on desktop" assertion is added (D-05) — no new `toHaveCount(0)` / `not.toBeVisible()` keyed on the `New Event` name. + + layout.spec.ts skips exactly the three mobile-geometry tests on desktop, adds a desktop ≥44px parity assertion, preserves every mobile assertion, and stays a single spec set. + + + + Task 3: Update spec headers + README, then prove the full suite is green on all three projects (SC-2, SC-3, D-03) + apps/pwa/e2e/calendar.spec.ts, apps/pwa/e2e/lists.spec.ts, apps/pwa/e2e/README.md + + - apps/pwa/e2e/calendar.spec.ts header (lines 13-15) and apps/pwa/e2e/lists.spec.ts header (lines 16-19) — the "Runs on both device profiles" comment blocks to update; per PATTERNS no structural test changes are needed in these files. + - apps/pwa/e2e/README.md — preamble line 3 ("mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium)") and the run-commands block lines 37-47; mirror PATTERNS §"apps/pwa/e2e/README.md — docs update only". + - .gitea/workflows/ci.yml lines 241-296 — confirm the harness job runs `pnpm --filter @familysync/pwa test:e2e` (all projects) and installs the `chromium` engine that Desktop Chrome uses; assert no CI plumbing change is required beyond a cosmetic comment. + - .planning/phases/14-desktop-e2e-coverage/14-CONTEXT.md D-03 (desktop gate is blocking; fix flaky specs, never downgrade the gate) and 14-PATTERNS.md §calendar.spec.ts / §lists.spec.ts (all tests pass unchanged on desktop). + + + Update the header jsdoc comment block in `calendar.spec.ts` and `lists.spec.ts` to list all three + profiles (iphone / pixel / desktop) — comment-only, no test-body changes (PATTERNS confirms both files + pass unchanged on desktop). In `e2e/README.md`: update the preamble (line 3) to add "Desktop Chrome + (1280×720)" alongside the iPhone/Pixel emulation note, change the full-suite command comment (line 37) to + name all three profiles, and add a `--project=desktop` single-profile example. Confirm — by reading + `.gitea/workflows/ci.yml` — that the harness job already runs every project via `test:e2e` and installs + the `chromium` engine Desktop Chrome uses; the desktop gate is therefore blocking automatically (CI fails + the PR if any desktop test fails) with NO continue-on-error carve-out, satisfying D-03. Do NOT add + `continue-on-error`. If a desktop test flakes, fix the spec (proper waits/locators), never weaken the + assertion or downgrade the gate. Then run the FULL suite locally against all three projects and confirm + exit 0; the dev stack must be up with `DEV_AUTH_BYPASS=true` per e2e/README.md before running. + + + set -a; source .env 2>/dev/null; set +a; export DEV_AUTH_BYPASS=true DB_HOST=127.0.0.1 DB_PORT=3306; pnpm --filter @familysync/pwa test:e2e + + + - `pnpm --filter @familysync/pwa test:e2e` exits 0 with all three projects (iphone, pixel, desktop) reported — no failures, no unexpected skips beyond the three desktop-guarded geometry tests + the SW-availability skip. + - `apps/pwa/e2e/README.md` line 3 mentions Desktop Chrome, and the run-commands block contains a `--project=desktop` example. + - `calendar.spec.ts` and `lists.spec.ts` header blocks each list the `desktop` profile. + - `.gitea/workflows/ci.yml` is unchanged except (optionally) the cosmetic step-name comment on line 255 — no `continue-on-error` added, no new browser install needed (chromium already installed for Desktop Chrome). + + The full e2e suite is green on iphone + pixel + desktop, docs name the desktop profile, and the CI gate blocks on desktop with zero plumbing change. + + + + + +## Trust Boundaries + +This phase adds a test-only Playwright `desktop` project and adjusts existing e2e specs/docs. +It introduces NO new runtime code, NO new request handlers, NO new data flows, and NO new +attack surface in the shipped application. No new trust boundary is created. + +| Boundary | Description | +|----------|-------------| +| (none new) | All changes are confined to the e2e test harness (`apps/pwa/e2e/*`, `playwright.config.ts`) and docs; nothing in this phase is reachable in the production app. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-14-01 | Elevation of Privilege | desktop Playwright project (`serviceWorkers`, `DEV_AUTH_BYPASS`) | mitigate | The `desktop` project MUST set `serviceWorkers: 'block'` (mirrors mobile profiles) and rely ONLY on the test-only `DEV_AUTH_BYPASS` path that is a no-op when `NODE_ENV === 'production'` (devBypass.ts) and absent from `docker-compose.yml`. This phase adds no real-auth bypass and no production-reachable code. | +| T-14-02 | Tampering | npm/pip/cargo installs | accept | No new packages installed. `Desktop Chrome` uses the already-installed Chromium engine (`playwright install --with-deps webkit chromium`); no dependency or lockfile change. Package Legitimacy Gate N/A. | +| T-14-SC | Tampering | supply chain | accept | No package-manager install tasks in this plan; no `[ASSUMED]`/`[SUS]` packages to gate. | + +Honest scope statement: there is no genuine new application attack surface here. The only +test-infra concern worth recording is keeping `serviceWorkers: 'block'` on the desktop project +and never letting `DEV_AUTH_BYPASS` leak into a production-reachable path — both already enforced +by existing guards (T-14-01). + + + +- `npx playwright test --list --project=desktop` resolves the new project (run from apps/pwa). +- `pnpm --filter @familysync/pwa test:e2e` exits 0 across iphone + pixel + desktop. +- `grep -c "testInfo.project.name === 'desktop'" apps/pwa/e2e/layout.spec.ts` == 3. +- Mobile assertions intact: `grep "toBeGreaterThanOrEqual(56)" apps/pwa/e2e/layout.spec.ts` still matches the FAB checks. +- No `desktop.spec.ts` exists under `apps/pwa/e2e/`. +- `.gitea/workflows/ci.yml` has no `continue-on-error` on the harness job (desktop gate is blocking, D-03). +- `apps/pwa/e2e/README.md` documents the desktop profile. + + + +- SC-1: A `desktop` project (Desktop Chrome, 1280×720, no `hasTouch`) exists in `playwright.config.ts`. +- SC-2: The existing e2e specs pass (or are explicitly, justifiably skipped) on the desktop profile — the two mobile-only geometry assertions are desktop-skipped, everything else runs; `pnpm test:e2e` is green on all three projects. +- SC-3: The desktop gate is blocking in CI (no `continue-on-error`), runs automatically via the unchanged `test:e2e` invocation, and the specs are deterministic (D-03). +- D-01..D-06 all satisfied: one spec set (D-01), no weakened/deleted mobile assertions (D-02), blocking gate (D-03), parity ≥44px desktop assertion (D-04), no negative "absent on desktop" assertion (D-05), single Desktop Chrome project no WebKit (D-06). + + + +## Artifacts this phase produces + +New symbols / identifiers created by this phase (none are application runtime code — all test-harness): + +- **`desktop` Playwright project** — new entry in `apps/pwa/playwright.config.ts` `projects` array; `name: 'desktop'`, `use: { ...devices['Desktop Chrome'], serviceWorkers: 'block' }`. Referenceable via `--project=desktop`. +- **Desktop skip guards in `apps/pwa/e2e/layout.spec.ts`** — three `test.skip(testInfo.project.name === 'desktop', '')` first-statement guards on: "BottomTabBar is fully in-viewport (Rule 3 — safe-area-inset)" (line ~62), "BottomTabBar is fully in-viewport on /lists (Rule 3)" (line ~130), "New Event FAB meets 56×56px touch-target minimum (Rule 1)" (line ~91). +- **Desktop parity assertion (D-04)** — a new desktop-only check (test or branch) asserting the `New Event` toolbar button height ≥ 44px, guarded so it runs only on the `desktop` project. +- **Header-comment updates** — `layout.spec.ts`, `calendar.spec.ts`, `lists.spec.ts`, and `playwright.config.ts` jsdoc blocks updated to list all three profiles. +- **README update** — `apps/pwa/e2e/README.md` preamble + run-commands block document the `desktop` profile and a `--project=desktop` example. + +No new helper module (e.g. no `isDesktop` helper) is introduced — the inline `test.skip` form is used per PATTERNS.md, consistent with the existing skip pattern. No `desktop.spec.ts` file is created (D-01). + + + +Create `.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md` when done. +