docs(14): create phase plan
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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'"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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`.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="execute">
|
||||
<name>Task 1: Add the `desktop` project to playwright.config.ts (SC-1, D-06)</name>
|
||||
<files>apps/pwa/playwright.config.ts</files>
|
||||
<read_first>
|
||||
- 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).
|
||||
</read_first>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `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.
|
||||
</acceptance_criteria>
|
||||
<done>The `desktop` project exists, resolves via `--project=desktop`, and mirrors the mobile entries with no baseURL/webServer duplication.</done>
|
||||
</task>
|
||||
|
||||
<task type="execute">
|
||||
<name>Task 2: Desktop-skip the two mobile-only layout assertions + add D-04 parity (SC-2, D-01, D-02, D-04, D-05)</name>
|
||||
<files>apps/pwa/e2e/layout.spec.ts</files>
|
||||
<read_first>
|
||||
- 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).
|
||||
</read_first>
|
||||
<action>
|
||||
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', '<factual reason>')` 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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `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.
|
||||
</acceptance_criteria>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="execute">
|
||||
<name>Task 3: Update spec headers + README, then prove the full suite is green on all three projects (SC-2, SC-3, D-03)</name>
|
||||
<files>apps/pwa/e2e/calendar.spec.ts, apps/pwa/e2e/lists.spec.ts, apps/pwa/e2e/README.md</files>
|
||||
<read_first>
|
||||
- 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).
|
||||
</read_first>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `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).
|
||||
</acceptance_criteria>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## 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).
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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).
|
||||
</success_criteria>
|
||||
|
||||
<artifacts_produced>
|
||||
## 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', '<reason>')` 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).
|
||||
</artifacts_produced>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/14-desktop-e2e-coverage/14-01-SUMMARY.md` when done.
|
||||
</output>
|
||||
Reference in New Issue
Block a user