diff --git a/.planning/phases/02-calendar-display/02-01-SUMMARY.md b/.planning/phases/02-calendar-display/02-01-SUMMARY.md new file mode 100644 index 0000000..2a23797 --- /dev/null +++ b/.planning/phases/02-calendar-display/02-01-SUMMARY.md @@ -0,0 +1,160 @@ +--- +phase: 02-calendar-display +plan: "01" +subsystem: api-schema, api-auth, pwa-test +tags: [schema-migration, dev-auth, test-harness, ics-fixtures, red-stubs] +dependency_graph: + requires: [01-foundation-broker-spike] + provides: [calendar_events.hasRrule, calendars.isShared, devAuthBypass, pwa-vitest-jsdom, ics-fixtures, red-test-stubs] + affects: [02-02, 02-03, 02-04, 02-05] +tech_stack: + added: + - vitest@^4.1.8 (PWA devDependency) + - "@testing-library/react@^16.3.0 (PWA devDependency)" + - "@testing-library/jest-dom@^6.6.3 (PWA devDependency)" + - jsdom@^26.1.0 (PWA devDependency) + patterns: + - Drizzle boolean column + index pattern (hasRrule, isShared) + - Hono MiddlewareHandler factory with env-evaluated passthrough + - Vitest RED stubs with concrete behavioral assertions (not bare failing imports) +key_files: + created: + - apps/api/src/auth/devBypass.ts + - apps/pwa/vitest.config.ts + - apps/api/tests/fixtures/weekly-dst.ics + - apps/api/tests/fixtures/allday-birthday.ics + - apps/api/tests/fixtures/exdate-series.ics + - apps/api/tests/broker/expand.test.ts + - apps/api/tests/routes/events.test.ts + - apps/api/tests/auth/devBypass.test.ts + - apps/pwa/src/lib/hydrateEvents.test.ts + - apps/pwa/src/lib/calendarConfig.test.ts + modified: + - apps/api/src/db/schema.ts + - apps/api/src/index.ts + - apps/pwa/package.json + - .env.example + - docs/deployment.md +decisions: + - "Applied ALTER TABLE directly instead of drizzle-kit push due to non-TTY interactive prompt — false-positive int(11) vs int type warning on existing rows" + - "devAuthBypass evaluates env vars at call time (process start) not request time — intentional so auth mode is fixed for the lifetime of the process" + - "RED test stubs reference not-yet-built modules to ensure compile-time failure (concrete RED state), not just assertion failure" +metrics: + duration: "8m 25s" + completed: "2026-06-05" + tasks_completed: 3 + files_created: 10 + files_modified: 5 +--- + +# Phase 02 Plan 01: Foundation — Schema Columns, Test Harness, Dev-Auth Bypass Summary + +Horizontal foundation for Phase 2 calendar slice: two schema columns pushed to live MariaDB, PWA jsdom test runner operational, dev-auth bypass middleware with production hard guard, three ICS fixtures, and four RED test stubs with concrete behavioral contracts. + +## What Was Built + +### Schema Changes (Task 1) + +Added to `apps/api/src/db/schema.ts`: +- `calendarEvents.hasRrule`: `boolean('has_rrule').default(false).notNull()` — pre-filter flag for recurring event masters (RESEARCH.md §Pitfall 5) +- `calendarEvents`: new index `idx_calendar_events_has_rrule` matching style of `idx_calendar_events_dtstart_utc` +- `calendars.isShared`: `boolean('is_shared').default(false).notNull()` — operator-marked shared-family calendar flag + +Both columns pushed to live MariaDB (503-event cache intact). `SHOW COLUMNS` confirms presence. + +### PWA Test Harness (Task 1) + +- Created `apps/pwa/vitest.config.ts` with `environment: 'jsdom'` and `globals: true` +- Added `"test": "vitest run"` to `apps/pwa/package.json` scripts +- Added devDependencies: `vitest@^4.1.8`, `@testing-library/react@^16.3.0`, `@testing-library/jest-dom@^6.6.3`, `jsdom@^26.1.0` +- `pnpm install` completed without errors + +### ICS Fixtures (Task 1) + +Three fixtures created at `apps/api/tests/fixtures/`: +- `weekly-dst.ics`: weekly VEVENT at `DTSTART;TZID=America/New_York:20260301T100000` with full VTIMEZONE block (STANDARD + DAYLIGHT subcomponents for March 2026 EST→EDT transition) +- `allday-birthday.ics`: `DTSTART;VALUE=DATE:20260615` with `RRULE:FREQ=YEARLY`, no DTEND — pure DATE type +- `exdate-series.ics`: `RRULE:FREQ=WEEKLY;COUNT=5` with `EXDATE;TZID=America/New_York:20260615T090000` — exactly one occurrence excluded + +All three fixtures parse via `ICAL.parse()` without throwing. + +### RED Test Stubs (Task 1) + +Four test stubs with concrete behavioral contracts (not bare failing imports): + +**expand.test.ts**: Three behavioral contracts — +1. DST wall-clock: every occurrence in March 2026 window has `T10:00:00` in the ISO start string, regardless of EST/EDT offset. Tests both pre-transition (2026-03-01) and post-transition (2026-03-15) occurrences. +2. All-day: `allDay:true` and `start === '2026-06-15'` (no `T` component) +3. EXDATE: length === 4 (not 5), June 15 occurrence absent + +**events.test.ts**: Four contracts — 400 on missing start, 400 on missing end, 400 on malformed date, 200 + `{occurrences: []}` with color/isShared fields on valid window. + +**hydrateEvents.test.ts**: Four contracts — all-day → `Temporal.PlainDate`, timed → `Temporal.ZonedDateTime`, shared `isShared:true` → calendarId `'shared'`, personal `isShared:false ownerUserId:7 calendarId:99` → calendarId `'7'` (NOT `'99'`). + +**calendarConfig.test.ts**: Four contracts — `WEEK_START_DAY === 0`, `firstDayOfWeek === 7` (Temporal 0→7 translation), `'shared'` calendar in config, per-member by `String(userId)`. + +All RED stubs fail at import resolution (module not built yet) — correct RED state. + +### Dev-Auth Bypass (Task 2) + +Created `apps/api/src/auth/devBypass.ts`: +- Exports `devAuthBypass(): MiddlewareHandler` +- First conditional is `NODE_ENV === 'production'` — hard guard (T-02-01 mitigation) +- Returns no-op passthrough when production OR bypass flag unset +- When active: `c.set('user', DEV_USER)` then `await next()` +- Exports `DEV_USER = { id: 1, oidcIss: 'dev', oidcSub: 'dev-user', displayName: 'Dev User', color: COLOR_PALETTE[0] }` + +Mounted in `apps/api/src/index.ts` on the line immediately before `oidcAuthMiddleware()`. + +All three devBypass.test.ts cases pass: production guard, unset-flag passthrough, active injection. + +`.env.example` and `docs/deployment.md` updated with bypass documentation and production prohibition. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocker] drizzle-kit push replaced with direct ALTER TABLE** +- **Found during:** Task 3 +- **Issue:** `drizzle-kit push` emitted a non-TTY interactive prompt. The "data-loss" warnings were false positives — MariaDB stores int as `int(11)` display width but drizzle-kit 0.31.x sees this as a type change on existing rows. The prompt cannot be auto-confirmed without TTY. +- **Fix:** Applied the two actual new columns directly via `ALTER TABLE calendar_events ADD COLUMN IF NOT EXISTS has_rrule tinyint(1) NOT NULL DEFAULT 0` and `ALTER TABLE calendars ADD COLUMN IF NOT EXISTS is_shared tinyint(1) NOT NULL DEFAULT 0`, plus the index. Outcome is identical to what drizzle-kit push would have done for the new columns. +- **Data integrity:** 503 events confirmed intact post-migration. SHOW COLUMNS confirms both columns and the index exist. +- **Note for future plans:** The int(11) vs int type drift is a display-width-only issue in MariaDB. It does not affect runtime behavior. If drizzle-kit push is run again, it may continue to prompt about these. Consider adding `drizzle.config.ts` overrides or accepting the prompt in an attended session. +- **Files modified:** live MariaDB schema (no source file change) + +## Known Stubs + +The following test stubs are intentionally RED (modules not yet built): +- `apps/api/tests/broker/expand.test.ts` — awaits `apps/api/src/broker/expand.ts` (Plan 02) +- `apps/api/tests/routes/events.test.ts` — awaits evolved `apps/api/src/routes/events.ts` (Plan 02) +- `apps/pwa/src/lib/hydrateEvents.test.ts` — awaits `apps/pwa/src/lib/hydrateEvents.ts` (Plan 03) +- `apps/pwa/src/lib/calendarConfig.test.ts` — awaits `apps/pwa/src/lib/calendarConfig.ts` (Plan 03) + +These are tracked RED stubs, not incomplete work. Each encodes a concrete behavioral contract for the implementing plan. + +## Threat Flags + +No new threat surface introduced beyond what is already in the plan's threat model. The `devAuthBypass` middleware is guarded by both `NODE_ENV === 'production'` and documented in `.env.example` and `docs/deployment.md`. + +## Self-Check: PASSED + +Files created: +- [x] apps/api/src/auth/devBypass.ts — FOUND +- [x] apps/pwa/vitest.config.ts — FOUND +- [x] apps/api/tests/fixtures/weekly-dst.ics — FOUND +- [x] apps/api/tests/fixtures/allday-birthday.ics — FOUND +- [x] apps/api/tests/fixtures/exdate-series.ics — FOUND +- [x] apps/api/tests/broker/expand.test.ts — FOUND +- [x] apps/api/tests/routes/events.test.ts — FOUND +- [x] apps/api/tests/auth/devBypass.test.ts — FOUND +- [x] apps/pwa/src/lib/hydrateEvents.test.ts — FOUND +- [x] apps/pwa/src/lib/calendarConfig.test.ts — FOUND + +Commits: +- [x] 75252eb — Task 1 feat +- [x] 8bd44b3 — Task 2 feat + +DB state: +- [x] SHOW COLUMNS FROM calendar_events LIKE 'has_rrule' — returns 1 row +- [x] SHOW COLUMNS FROM calendars LIKE 'is_shared' — returns 1 row +- [x] 503 events intact