feat(02-01): schema columns, PWA vitest harness, ICS fixtures, RED test stubs

- Add calendarEvents.hasRrule boolean + idx_calendar_events_has_rrule index (Phase 2 pre-filter)
- Add calendars.isShared boolean for shared-family calendar identification
- Create apps/pwa/vitest.config.ts with jsdom environment
- Add vitest, @testing-library/react, jsdom, @testing-library/jest-dom to PWA devDependencies
- Add "test": "vitest run" script to apps/pwa/package.json
- Create three ICS fixtures: weekly-dst.ics (DST spanning), allday-birthday.ics, exdate-series.ics
- Create RED test stub expand.test.ts with concrete DST wall-clock assertions (10:00 local both sides of March 2026 boundary)
- Create RED test stub events.test.ts with 400 validation and color/isShared field contracts
- Create RED test stub hydrateEvents.test.ts with Temporal type and calendarId routing contracts (shared→"shared", personal→String(ownerUserId))
- Create RED test stub calendarConfig.test.ts with firstDayOfWeek 0→7 translation contract
This commit is contained in:
Lucas Berger
2026-06-05 09:29:45 -04:00
parent 62ebb1f9d4
commit 75252eb08c
11 changed files with 971 additions and 4 deletions
+7
View File
@@ -55,6 +55,7 @@ export const memberCredentials = mysqlTable(
/**
* Calendar collections discovered via CalDAV PROPFIND.
* One row per calendar per member. ctag/syncToken track change state for polling (D-13).
* isShared: true when this calendar is the shared-family calendar (marked by operator).
*/
export const calendars = mysqlTable(
'calendars',
@@ -69,6 +70,7 @@ export const calendars = mysqlTable(
ctag: varchar('ctag', { length: 512 }),
syncToken: varchar('sync_token', { length: 1024 }),
lastSyncedAt: timestamp('last_synced_at'),
isShared: boolean('is_shared').default(false).notNull(),
},
(t) => [index('idx_calendars_user_id').on(t.userId)],
)
@@ -94,11 +96,16 @@ export const calendarEvents = mysqlTable(
dtstartUtc: timestamp('dtstart_utc'), // NULL for all-day events
dtstartDate: date('dtstart_date'), // set for all-day events; NULL for timed
allDay: boolean('all_day').default(false).notNull(),
// hasRrule: pre-computed flag for SQL pre-filtering of recurring event masters.
// Events with hasRrule=true have dtstartUtc potentially years before any window,
// so the windowed query must include them regardless of dtstartUtc range (see RESEARCH.md §Pitfall 5).
hasRrule: boolean('has_rrule').default(false).notNull(),
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
},
(t) => [
index('idx_calendar_events_dtstart_utc').on(t.dtstartUtc),
index('idx_calendar_events_dtstart_date').on(t.dtstartDate),
index('idx_calendar_events_has_rrule').on(t.hasRrule),
// uid is unique per calendar (idempotency key for broker upsert)
unique('uniq_calendar_uid').on(t.calendarId, t.uid),
],