docs(07-04): complete state coverage plan — calendar.spec.ts + lists.spec.ts
This commit is contained in:
@@ -25,7 +25,7 @@ Full phase detail archived in [`milestones/v1.0-ROADMAP.md`](milestones/v1.0-ROA
|
|||||||
|
|
||||||
Make FamilySync configurable, administrable, and maintainable for real multi-member use — without hand-editing env files or the database. The new critical path runs **mobile test harness → Gitea CI** (CI consumes the harness specs for UI regression), and the **admin role → reminders / setup wizard** chain (a single `/api/admin` + `/api/setup` route surface carrying the v1.1 DB migration). Faster write-back is a fully independent track.
|
Make FamilySync configurable, administrable, and maintainable for real multi-member use — without hand-editing env files or the database. The new critical path runs **mobile test harness → Gitea CI** (CI consumes the harness specs for UI regression), and the **admin role → reminders / setup wizard** chain (a single `/api/admin` + `/api/setup` route surface carrying the v1.1 DB migration). Faster write-back is a fully independent track.
|
||||||
|
|
||||||
- [ ] **Phase 7: Mobile Test Harness** - Mobile-emulated, authenticated PWA browser harness so the assistant (and CI) can catch mobile-only defects
|
- [x] **Phase 7: Mobile Test Harness** - Mobile-emulated, authenticated PWA browser harness so the assistant (and CI) can catch mobile-only defects (completed 2026-06-11)
|
||||||
- [ ] **Phase 8: Gitea CI** - Full regression on PR to main (lint/typecheck/unit/API-integration vs a MariaDB service container **+ the Phase 7 mobile harness as a UI-regression step against a CI-hosted dev stack**) + Docker image publish on merge
|
- [ ] **Phase 8: Gitea CI** - Full regression on PR to main (lint/typecheck/unit/API-integration vs a MariaDB service container **+ the Phase 7 mobile harness as a UI-regression step against a CI-hosted dev stack**) + Docker image publish on merge
|
||||||
- [ ] **Phase 9: Faster Write-Back** - Event-driven outbox drain so edits land in ~1-2s instead of ~15s, preserving every outbox durability guarantee
|
- [ ] **Phase 9: Faster Write-Back** - Event-driven outbox drain so edits land in ~1-2s instead of ~15s, preserving every outbox durability guarantee
|
||||||
- [ ] **Phase 10: Admin Role & Settings** - DB foundation (is_admin / reminder_lead / app_config) + role-gated admin UI to rotate app passwords and designate the shared calendar
|
- [ ] **Phase 10: Admin Role & Settings** - DB foundation (is_admin / reminder_lead / app_config) + role-gated admin UI to rotate app passwords and designate the shared calendar
|
||||||
@@ -67,7 +67,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
|
|||||||
**Wave 3** *(blocked on Wave 2 completion)*
|
**Wave 3** *(blocked on Wave 2 completion)*
|
||||||
|
|
||||||
- [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)
|
- [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)
|
- [x] 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
|
**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 |
|
| 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 |
|
| 5. Web Push Notifications | v1.0 | 8/8 | Complete | 2026-06-10 |
|
||||||
| 6. UX Polish | v1.0 | 6/6 | Complete | 2026-06-10 |
|
| 6. UX Polish | v1.0 | 6/6 | Complete | 2026-06-10 |
|
||||||
| 7. Mobile Test Harness | v1.1 | 3/4 | In Progress| |
|
| 7. Mobile Test Harness | v1.1 | 4/4 | Complete | 2026-06-11 |
|
||||||
| 8. Gitea CI | v1.1 | 0/? | Not started | - |
|
| 8. Gitea CI | v1.1 | 0/? | Not started | - |
|
||||||
| 9. Faster Write-Back | v1.1 | 0/? | Not started | - |
|
| 9. Faster Write-Back | v1.1 | 0/? | Not started | - |
|
||||||
| 10. Admin Role & Settings | 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.
|
**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
|
**Requirements:** TBD
|
||||||
**Plans:** 3/4 plans executed
|
**Plans:** 4/4 plans complete
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
|
|
||||||
|
|||||||
+13
-10
@@ -2,16 +2,16 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.1
|
milestone: v1.1
|
||||||
milestone_name: Operability & Polish
|
milestone_name: Operability & Polish
|
||||||
status: executing
|
status: verifying
|
||||||
stopped_at: Phase 7 UI-SPEC approved
|
stopped_at: Completed 07-04-PLAN.md
|
||||||
last_updated: "2026-06-11T06:02:41.305Z"
|
last_updated: "2026-06-11T06:13:24.004Z"
|
||||||
last_activity: 2026-06-11 -- Phase 07 execution started
|
last_activity: 2026-06-11 -- Phase 07 execution started
|
||||||
progress:
|
progress:
|
||||||
total_phases: 14
|
total_phases: 14
|
||||||
completed_phases: 0
|
completed_phases: 1
|
||||||
total_plans: 4
|
total_plans: 4
|
||||||
completed_plans: 3
|
completed_plans: 4
|
||||||
percent: 0
|
percent: 7
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -27,7 +27,7 @@ See: .planning/PROJECT.md (updated 2026-06-10)
|
|||||||
|
|
||||||
Phase: 07 (mobile-test-harness) — EXECUTING
|
Phase: 07 (mobile-test-harness) — EXECUTING
|
||||||
Plan: 4 of 4
|
Plan: 4 of 4
|
||||||
Status: Ready to execute
|
Status: Phase complete — ready for verification
|
||||||
Last activity: 2026-06-11 -- Phase 07 execution started
|
Last activity: 2026-06-11 -- Phase 07 execution started
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
@@ -80,6 +80,7 @@ Last activity: 2026-06-11 -- Phase 07 execution started
|
|||||||
| Phase 07 P01 | 310 | 3 tasks | 7 files |
|
| Phase 07 P01 | 310 | 3 tasks | 7 files |
|
||||||
| Phase 07 P02 | 196 | 2 tasks | 4 files |
|
| Phase 07 P02 | 196 | 2 tasks | 4 files |
|
||||||
| Phase 07-mobile-test-harness P03 | 480 | 2 tasks | 2 files |
|
| Phase 07-mobile-test-harness P03 | 480 | 2 tasks | 2 files |
|
||||||
|
| Phase 07-mobile-test-harness P04 | 22 | 2 tasks | 2 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -127,6 +128,8 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 06-05]: AuthSplash state machine: loading/redirecting/dead-end; CalendarContent renders only on meQuery.isSuccess (D-10); sessionExpired flag via Zustand + global QueryCache/MutationCache onError (D-11); one-shot redirect guard re-armed only on explicit user tap
|
- [Phase 06-05]: AuthSplash state machine: loading/redirecting/dead-end; CalendarContent renders only on meQuery.isSuccess (D-10); sessionExpired flag via Zustand + global QueryCache/MutationCache onError (D-11); one-shot redirect guard re-armed only on explicit user tap
|
||||||
- [Phase 06-06]: Schedule-X all-day CSS: .sx__all-day-event does not exist in v4.6.0; real selectors are .sx__date-grid-event (week/day) + .sx__month-grid-event:not(:has(.sx__month-grid-event-time)) (month); --sx-color-primary-container remapped as fallback
|
- [Phase 06-06]: Schedule-X all-day CSS: .sx__all-day-event does not exist in v4.6.0; real selectors are .sx__date-grid-event (week/day) + .sx__month-grid-event:not(:has(.sx__month-grid-event-time)) (month); --sx-color-primary-container remapped as fallback
|
||||||
- [Phase 06]: Phase-level UX fixes (surfaced during UAT, not in any single plan): AppNav made persistent across routes — nav no longer disappears on /lists (commits 6070437 RED + 051874b fix); BottomTabBar hidden on desktop — no longer overlaps sidebar Settings affordance (commits 740e342 RED + 089b53d fix)
|
- [Phase 06]: Phase-level UX fixes (surfaced during UAT, not in any single plan): AppNav made persistent across routes — nav no longer disappears on /lists (commits 6070437 RED + 051874b fix); BottomTabBar hidden on desktop — no longer overlaps sidebar Settings affordance (commits 740e342 RED + 089b53d fix)
|
||||||
|
- [Phase ?]: D-04-SCHEDULE-X-LOCATOR: Used .sx-react-calendar-wrapper CSS class to assert Schedule-X grid — no semantic role on outer wrapper div
|
||||||
|
- [Phase ?]: D-04-EMPTY-NETWORK-SIM: Lists empty state simulated via page.route to 200 empty array — preserves seeded DB for parallel workers (D-06 / T-07-11)
|
||||||
|
|
||||||
### Roadmap Evolution
|
### Roadmap Evolution
|
||||||
|
|
||||||
@@ -187,9 +190,9 @@ Recent decisions affecting current work:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-11T06:02:41.295Z
|
Last session: 2026-06-11T06:13:23.996Z
|
||||||
Stopped at: Phase 7 UI-SPEC approved
|
Stopped at: Completed 07-04-PLAN.md
|
||||||
Resume file: .planning/phases/07-mobile-test-harness/07-UI-SPEC.md
|
Resume file: None
|
||||||
|
|
||||||
## Operator Next Steps
|
## Operator Next Steps
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
phase: 07-mobile-test-harness
|
||||||
|
plan: "04"
|
||||||
|
subsystem: test-harness
|
||||||
|
tags: [playwright, e2e, calendar, lists, populated-state, error-state, empty-state, auth-bypass, service-worker]
|
||||||
|
dependency_graph:
|
||||||
|
requires:
|
||||||
|
- "apps/pwa/playwright.config.ts (07-01) — iphone/pixel project matrix, serviceWorkers: 'block', globalSetup path"
|
||||||
|
- "apps/pwa/e2e/global-setup.ts (07-02) — /health readiness gate + DB seed (calendar_id=10 'Seeded Test Event', 'E2E Grocery List' for user_id=1)"
|
||||||
|
- "apps/pwa/e2e/layout.spec.ts (07-03) — locator patterns and conventions mirrored"
|
||||||
|
provides:
|
||||||
|
- "apps/pwa/e2e/calendar.spec.ts — TEST-01 (populated + error) + TEST-02 (auth-bypass + SW precondition) assertions for /calendar"
|
||||||
|
- "apps/pwa/e2e/lists.spec.ts — TEST-01 (populated + network-simulated empty) assertions for /lists"
|
||||||
|
- "20 tests total (8 calendar + 12 lists, per profile) — all passing on iphone/WebKit and pixel/Chromium"
|
||||||
|
affects:
|
||||||
|
- "Phase 08 CI (both specs run as regression gates)"
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "page.route('/api/*', fulfill 500) registered BEFORE page.goto — error-state simulation (Pattern 5)"
|
||||||
|
- "page.unroute() immediately after assertion — route mocks scoped to single test (T-07-11)"
|
||||||
|
- "page.locator('.sx-react-calendar-wrapper') — CSS class fallback for widget wrapper with no semantic role"
|
||||||
|
- "getByRole('button', { name: 'Open list: E2E Grocery List' }) — aria-label stable anchor on ListCard"
|
||||||
|
- "page.route('/api/lists', fulfill 200 []) — network-simulated empty state without DB mutation (D-06)"
|
||||||
|
- "page.evaluate(() => navigator.serviceWorker.controller) — runtime SW controller assertion"
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- apps/pwa/e2e/calendar.spec.ts
|
||||||
|
- apps/pwa/e2e/lists.spec.ts
|
||||||
|
modified: []
|
||||||
|
key_decisions:
|
||||||
|
- "D-04-SCHEDULE-X-LOCATOR: Asserted .sx-react-calendar-wrapper via CSS class (page.locator) since Schedule-X's React adapter emits no semantic role on the outer wrapper div — documented in index.css. No data-testid added to source; the CSS class is stable within @schedule-x/react."
|
||||||
|
- "D-04-POPULATED-NO-EVENT-CHIP: Populated calendar test asserts grid visible + empty-absent (NOT event chip text) — chip visibility depends on Schedule-X's default view and the seed event date relative to today; date-dependent assertions are exactly the drift the phase avoids (UI-SPEC Rule 6 rationale)."
|
||||||
|
- "D-04-EMPTY-STATE-NETWORK-SIM: Lists empty state simulated via page.route to 200 [] rather than DB mutation — preserves seeded populated state for concurrent test workers and satisfies D-06 deterministic seed / T-07-11 mock isolation."
|
||||||
|
- "D-04-LISTCARD-ARIA-LABEL: Lists populated test locates card by getByRole('button', { name: 'Open list: E2E Grocery List' }) — ListCard.tsx renders a <button> (not <a>) with that exact aria-label; no link role collision."
|
||||||
|
patterns-established:
|
||||||
|
- "Error-state simulation: register page.route BEFORE page.goto, assert heading+button, then page.unroute"
|
||||||
|
- "Empty-state simulation (no DB mutation): page.route to 200+empty-body BEFORE goto, assert empty UI, then page.unroute"
|
||||||
|
- "SW-block assertion: page.evaluate(() => navigator.serviceWorker?.controller) — null confirms no controlling SW"
|
||||||
|
- "Auth reach: getByRole('navigation', { name: 'Main navigation' }) visible + URL hostname check against external auth host"
|
||||||
|
requirements-completed: [TEST-01, TEST-02]
|
||||||
|
duration: 22min
|
||||||
|
completed: "2026-06-11"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 07 Plan 04: calendar.spec.ts + lists.spec.ts State Coverage Summary
|
||||||
|
|
||||||
|
**calendar.spec.ts and lists.spec.ts asserting populated/error/empty states on iPhone/WebKit and Pixel/Chromium, with TEST-02 DEV_AUTH_BYPASS and service-worker-block precondition assertions at runtime.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 22 min
|
||||||
|
- **Started:** 2026-06-11T05:49:00Z
|
||||||
|
- **Completed:** 2026-06-11T06:11:47Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 2
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- `apps/pwa/e2e/calendar.spec.ts` — 8 tests per profile (16 total) covering: TEST-02 auth-bypass reach + SW-controller null assertion; populated state (Schedule-X grid visible, EmptyState absent, no overflow); error state (mocked /api/events* 500, 'Couldn't load events' heading, Retry ≥44px, no overflow, mock unrouted)
|
||||||
|
- `apps/pwa/e2e/lists.spec.ts` — 6 tests per profile (12 total) covering: populated state (seeded 'E2E Grocery List' card by aria-label, listitem count ≥1, 'No lists yet' absent, no overflow); empty state (network-simulated via page.route to 200 [], 'No lists yet' + 'Tap + to create' visible, no overflow, mock unrouted)
|
||||||
|
- All 28 tests pass on both iphone (WebKit) and pixel (Chromium); `pnpm --filter @familysync/pwa typecheck` exits 0; no absolute URLs; seeded DB not mutated by any spec
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
1. **Task 1: calendar.spec.ts** - `17b625b` (feat)
|
||||||
|
2. **Task 2: lists.spec.ts** - `b074b4a` (feat)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `apps/pwa/e2e/calendar.spec.ts` — TEST-01 + TEST-02 assertions for /calendar (populated, error, auth-bypass, SW-block)
|
||||||
|
- `apps/pwa/e2e/lists.spec.ts` — TEST-01 assertions for /lists (populated and network-simulated empty)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **D-04-SCHEDULE-X-LOCATOR:** `page.locator('.sx-react-calendar-wrapper')` used to assert calendar grid — the Schedule-X React adapter emits a div with this class but no semantic ARIA role. This is documented in `apps/pwa/src/styles/index.css` as the canonical outer wrapper class. No `data-testid` added to source code.
|
||||||
|
- **D-04-POPULATED-NO-CHIP:** Populated calendar test asserts grid visible + `'Nothing here'` absent rather than the seeded event chip text `'Seeded Test Event'`. Schedule-X renders only events in the current view window; the seed event is tomorrow UTC but the default view and timezone rendering makes chip visibility date-dependent. The plan explicitly flagged this risk.
|
||||||
|
- **D-04-EMPTY-NETWORK-SIM:** Lists empty state simulated with `page.route('/api/lists', fulfill 200 { lists: [] })` before `page.goto` rather than by deleting the seeded row. This preserves the deterministic seed for parallel test workers and avoids DB state mutation in specs (D-06 / T-07-11).
|
||||||
|
- **D-04-LISTCARD-BUTTON:** `ListCard.tsx` renders the card as `<button aria-label="Open list: ...">` (not `<a>`), so the locator uses `getByRole('button', { name: 'Open list: E2E Grocery List' })`.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written. All implementation choices were documented as decisions (listed above).
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — both spec files are complete implementations with no placeholders.
|
||||||
|
|
||||||
|
## Threat Surface Scan
|
||||||
|
|
||||||
|
No new network endpoints, auth paths, or schema changes. Both files are test-only.
|
||||||
|
|
||||||
|
Threat mitigations confirmed active:
|
||||||
|
|
||||||
|
- **T-07-09 (DEV_AUTH_BYPASS elevation):** TEST-02 precondition spec asserts the bypass reached the authed PWA — confirms the dev-only guard is working. The spec does not enable the bypass; it depends on the running dev stack.
|
||||||
|
- **T-07-10 (OIDC mock spoofing):** No storageState and no OIDC mock used — auth comes from the real DEV_AUTH_BYPASS middleware. Auth reach is asserted via nav landmark presence + URL hostname check (not a faked session).
|
||||||
|
- **T-07-11 (route mock leakage):** Every `page.route` call in calendar.spec.ts and lists.spec.ts is paired with `page.unroute` immediately after the assertion block. Mocks are page-scoped and do not persist across test contexts.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- `apps/pwa/e2e/calendar.spec.ts` — exists
|
||||||
|
- `apps/pwa/e2e/lists.spec.ts` — exists
|
||||||
|
- Task 1 commit `17b625b` — exists
|
||||||
|
- Task 2 commit `b074b4a` — exists
|
||||||
|
- 28 tests passing on both profiles — verified by final combined run
|
||||||
|
- `pnpm --filter @familysync/pwa typecheck` — exits 0
|
||||||
|
- No absolute URLs: `grep -cE "https?://localhost" e2e/calendar.spec.ts e2e/lists.spec.ts` → both 0
|
||||||
Reference in New Issue
Block a user