Files
familysync/.planning/milestones/v1.0-phases/02-calendar-display/02-01-SUMMARY.md
T

8.6 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
02-calendar-display 01 api-schema, api-auth, pwa-test
schema-migration
dev-auth
test-harness
ics-fixtures
red-stubs
requires provides affects
01-foundation-broker-spike
calendar_events.hasRrule
calendars.isShared
devAuthBypass
pwa-vitest-jsdom
ics-fixtures
red-test-stubs
02-02
02-03
02-04
02-05
added patterns
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)
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)
created modified
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
apps/api/src/db/schema.ts
apps/api/src/index.ts
apps/pwa/package.json
.env.example
docs/deployment.md
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
duration completed tasks_completed files_created files_modified
8m 25s 2026-06-05 3 10 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:

  • apps/api/src/auth/devBypass.ts — FOUND
  • apps/pwa/vitest.config.ts — FOUND
  • apps/api/tests/fixtures/weekly-dst.ics — FOUND
  • apps/api/tests/fixtures/allday-birthday.ics — FOUND
  • apps/api/tests/fixtures/exdate-series.ics — FOUND
  • apps/api/tests/broker/expand.test.ts — FOUND
  • apps/api/tests/routes/events.test.ts — FOUND
  • apps/api/tests/auth/devBypass.test.ts — FOUND
  • apps/pwa/src/lib/hydrateEvents.test.ts — FOUND
  • apps/pwa/src/lib/calendarConfig.test.ts — FOUND

Commits:

DB state:

  • SHOW COLUMNS FROM calendar_events LIKE 'has_rrule' — returns 1 row
  • SHOW COLUMNS FROM calendars LIKE 'is_shared' — returns 1 row
  • 503 events intact