docs(02): create phase plan

This commit is contained in:
Lucas Berger
2026-06-04 15:09:55 -04:00
parent b842955612
commit fc4cc2ccf8
7 changed files with 134 additions and 76 deletions
@@ -29,7 +29,7 @@ must_haves:
- "Drizzle schema has an indexed hasRrule boolean on calendar_events and an isShared boolean on calendars, both pushed to the live MariaDB"
- "PWA test runner (vitest + jsdom + @testing-library/react) executes and a smoke test passes"
- "Dev-auth bypass middleware injects a fixed dev user only when DEV_AUTH_BYPASS=true AND NODE_ENV!=production"
- "Failing-but-present test stubs exist for expand, events route, hydrateEvents, and calendarConfig (Wave 0 RED state)"
- "Failing-but-present test stubs exist for expand, events route, hydrateEvents, and calendarConfig (Wave 0 RED state) with concrete behavioral assertions, not bare failing imports"
artifacts:
- path: "apps/api/src/db/schema.ts"
provides: "hasRrule + isShared columns + idx_calendar_events_has_rrule index"
@@ -65,7 +65,7 @@ Purpose: Every later plan (expansion engine, windowed route, hydration, calendar
these columns, the test harness, and the dev-auth bypass to exist first. This is the only
horizontal-foundation plan in the phase — kept minimal so the next plan delivers a real slice.
Output: Migrated schema (pushed), PWA vitest harness, dev-auth bypass middleware, ICS fixtures,
RED test stubs.
RED test stubs with concrete behavioral contracts.
</objective>
<execution_context>
@@ -93,15 +93,16 @@ RED test stubs.
- apps/pwa/package.json (current scripts + devDependencies block)
- apps/api/tests/broker/poller.test.ts (test file structure: describe/it/expect, vi.mock hoisting)
- apps/api/tests/health.test.ts (Hono app.request() route-test pattern)
- .planning/phases/02-calendar-display/02-RESEARCH.md §"Validation Architecture" (fixture corpus + Wave 0 gaps table)
- .planning/phases/02-calendar-display/02-RESEARCH.md §"Validation Architecture" (fixture corpus + Wave 0 gaps table) + §"Pattern 1" (CalendarOccurrence carries calendarId, ownerUserId, isShared) + §"Pattern 2" (hydrateEvents calendarId routing)
- .planning/phases/02-calendar-display/02-PATTERNS.md §"apps/pwa/vitest.config.ts" and §"apps/api/tests/broker/expand.test.ts"
</read_first>
<behavior>
- expand.test.ts: weekly America/New_York RRULE expanded across the March 2026 DST boundary returns occurrences whose wall-clock time stays 10:00 local on both sides of the transition (currently RED — expandOccurrences does not exist yet)
- expand.test.ts: all-day birthday fixture returns allDay:true with start as 'YYYY-MM-DD' and no time component
- expand.test.ts: exdate-series fixture omits the single EXDATE-excluded occurrence
- events.test.ts: GET /api/events?start=&end= returns occurrences each carrying a color field and isShared flag (RED — route not evolved yet)
- hydrateEvents.test.ts: all-day occurrence Temporal.PlainDate; timed occurrence → Temporal.ZonedDateTime
- expand.test.ts (CONCRETE — not a bare failing import): load weekly-dst.ics, call expandOccurrences for a window spanning the March 2026 America/New_York EST→EDT transition (e.g. 2026-03-01..2026-03-31), and assert that every returned occurrence's local wall-clock time is 10:00 America/New_York on BOTH sides of the DST boundary (i.e. the pre-transition and post-transition occurrences share the same 10:00 local hour — NOT shifted ±1h by a UTC fallback). The stub must encode this exact assertion so Plan 02's GREEN path has a real contract; do NOT settle for asserting only that ICAL.parse() succeeds.
- expand.test.ts: all-day birthday fixture returns allDay:true with start as 'YYYY-MM-DD' (e.g. '2026-06-15') and no time component
- expand.test.ts: exdate-series fixture omits the single EXDATE-excluded occurrence (returned array length is one fewer than an un-excluded expansion)
- events.test.ts: GET /api/events?start=&end= returns occurrences each carrying a color field and isShared flag; missing/malformed start or end → 400 (RED — route not evolved yet)
- hydrateEvents.test.ts: all-day occurrence (allDay:true, start '2026-06-15') → start is Temporal.PlainDate; timed occurrence → Temporal.ZonedDateTime
- hydrateEvents.test.ts (CONCRETE routing contract): a shared occurrence (isShared:true) → Schedule-X calendarId === 'shared'; a personal occurrence (isShared:false, ownerUserId:7, calendarId:99) → Schedule-X calendarId === '7' (String(ownerUserId)), explicitly NOT '99' (String(calendarId)). This assertion locks the Plan 03 routing fix.
- calendarConfig.test.ts: WEEK_START_DAY=0 translates to Schedule-X firstDayOfWeek 7
</behavior>
<action>
@@ -111,12 +112,13 @@ RED test stubs.
Create three ICS fixtures under `apps/api/tests/fixtures/`: `weekly-dst.ics` (VEVENT with `DTSTART;TZID=America/New_York:20260301T100000`, `RRULE:FREQ=WEEKLY`, and a full `VTIMEZONE` block for America/New_York with both STANDARD and DAYLIGHT subcomponents so DST rules are present), `allday-birthday.ics` (VEVENT with `DTSTART;VALUE=DATE:20260615`, yearly RRULE, no DTEND), `exdate-series.ics` (weekly VEVENT with one `EXDATE` line removing a single occurrence). These must be valid VCALENDAR strings parseable by ICAL.parse.
Create the four RED test stubs. Each test imports the not-yet-existing module (`../../src/broker/expand.js`, etc.) so the file fails to resolve — that is the intended RED state. Per the Nyquist rule, mark each `<automated>` for the modules they cover as satisfied here. Use the describe/it patterns from poller.test.ts and health.test.ts. Load fixtures with `readFileSync` relative to the test file. Do NOT implement expand.ts, the route changes, hydrateEvents.ts, or calendarConfig.ts in this task — only the stubs that later plans turn green.
Create the four RED test stubs with the CONCRETE behavioral assertions described in <behavior> above — each must encode its real contract (the DST wall-clock assertion in expand.test.ts; the 'shared'/String(ownerUserId) calendarId routing assertion in hydrateEvents.test.ts), not merely a failing import. Each test imports the not-yet-existing module (`../../src/broker/expand.js`, etc.) so the file fails to resolve / the assertion fails — that is the intended RED state. Per the Nyquist rule, mark each `<automated>` for the modules they cover as satisfied here. Use the describe/it patterns from poller.test.ts and health.test.ts. Load fixtures with `readFileSync` relative to the test file. Do NOT implement expand.ts, the route changes, hydrateEvents.ts, or calendarConfig.ts in this task — only the stubs that later plans turn green.
</action>
<verify>
<automated>cd apps/api && grep -q "has_rrule" src/db/schema.ts && grep -q "is_shared" src/db/schema.ts && grep -q "idx_calendar_events_has_rrule" src/db/schema.ts && echo SCHEMA_OK</automated>
<automated>cd /home/luc/Projects/familysync && grep -q jsdom apps/pwa/vitest.config.ts && grep -q '"test": "vitest run"' apps/pwa/package.json && echo PWA_HARNESS_OK</automated>
<automated>cd apps/api && node -e "const I=require('ical.js');for(const f of ['weekly-dst','allday-birthday','exdate-series']){I.parse(require('fs').readFileSync('tests/fixtures/'+f+'.ics','utf8'))};console.log('FIXTURES_PARSE_OK')"</automated>
<automated>cd /home/luc/Projects/familysync && grep -q "10:00" apps/api/tests/broker/expand.test.ts && grep -q "shared" apps/pwa/src/lib/hydrateEvents.test.ts && grep -q "ownerUserId" apps/pwa/src/lib/hydrateEvents.test.ts && echo STUB_CONTRACTS_PRESENT</automated>
</verify>
<acceptance_criteria>
- apps/api/src/db/schema.ts contains `hasRrule: boolean('has_rrule')` and `idx_calendar_events_has_rrule`
@@ -125,9 +127,11 @@ RED test stubs.
- apps/pwa/package.json scripts contains `"test": "vitest run"` and devDependencies include vitest, @testing-library/react, jsdom
- All three fixture .ics files parse via ICAL.parse without throwing
- weekly-dst.ics contains a VTIMEZONE block with both STANDARD and DAYLIGHT subcomponents
- expand.test.ts asserts 10:00 local wall-clock on both sides of the March 2026 DST boundary (concrete contract, not just ICAL.parse success)
- hydrateEvents.test.ts asserts shared→'shared' and personal→String(ownerUserId) (NOT String(calendarId)) for the Schedule-X calendarId
- expand.test.ts, events.test.ts, hydrateEvents.test.ts, calendarConfig.test.ts exist and reference their target modules (RED is expected — modules not yet built)
</acceptance_criteria>
<done>Schema columns + index added; PWA vitest/jsdom harness installed and runnable; three ICS fixtures parse; four RED test stubs exist referencing not-yet-built modules.</done>
<done>Schema columns + index added; PWA vitest/jsdom harness installed and runnable; three ICS fixtures parse; four RED test stubs exist with concrete behavioral assertions (DST wall-clock + calendarId routing) referencing not-yet-built modules.</done>
</task>
<task type="auto" tdd="true">
@@ -220,7 +224,7 @@ RED test stubs.
- Schema columns added, pushed, and verified against the live DB
- PWA test runner operational
- Dev-auth bypass green with production hard guard
- ICS fixtures parse; RED stubs in place for later waves
- ICS fixtures parse; RED stubs in place for later waves with concrete DST + calendarId-routing contracts
</success_criteria>
<artifacts_produced>