style(13-03): apply Prettier formatting across repo

Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
@@ -3,7 +3,7 @@ phase: 02-calendar-display
plan: 02
type: execute
wave: 2
depends_on: ["02-01"]
depends_on: ['02-01']
files_modified:
- apps/api/src/broker/expand.ts
- apps/api/src/routes/events.ts
@@ -15,27 +15,27 @@ user_setup: []
must_haves:
truths:
- "GET /api/events?start=&end= returns a flat array of concrete occurrences (no recurring masters, no raw VCALENDAR blobs) windowed to the requested date range"
- "Each occurrence carries the owner member color (from users.color) or the shared-family rose, plus an isShared flag, an ownerUserId, and the DB calendarId"
- "Recurring events are expanded server-side with VTIMEZONE registered before expansion so DST occurrences keep correct wall-clock time"
- 'GET /api/events?start=&end= returns a flat array of concrete occurrences (no recurring masters, no raw VCALENDAR blobs) windowed to the requested date range'
- 'Each occurrence carries the owner member color (from users.color) or the shared-family rose, plus an isShared flag, an ownerUserId, and the DB calendarId'
- 'Recurring events are expanded server-side with VTIMEZONE registered before expansion so DST occurrences keep correct wall-clock time'
- "All-day occurrences are returned with allDay:true and a 'YYYY-MM-DD' start (no timezone shift) — single local timezone for v1 (D-10)"
- "EXDATE-excluded occurrences are omitted from the expansion"
- "Invalid or missing start/end query params are rejected (zod) before any SQL runs; window capped at 90 days"
- 'EXDATE-excluded occurrences are omitted from the expansion'
- 'Invalid or missing start/end query params are rejected (zod) before any SQL runs; window capped at 90 days'
artifacts:
- path: "apps/api/src/broker/expand.ts"
provides: "expandOccurrences() — ICAL.RecurExpansion + VTIMEZONE registration + allDay split → CalendarOccurrence[]"
exports: ["expandOccurrences", "CalendarOccurrence"]
- path: "apps/api/src/routes/events.ts"
provides: "windowed /api/events with calendarEvents→calendars→users join, hasRrule pre-filter, zod validation"
contains: "zValidator"
- path: 'apps/api/src/broker/expand.ts'
provides: 'expandOccurrences() — ICAL.RecurExpansion + VTIMEZONE registration + allDay split → CalendarOccurrence[]'
exports: ['expandOccurrences', 'CalendarOccurrence']
- path: 'apps/api/src/routes/events.ts'
provides: 'windowed /api/events with calendarEvents→calendars→users join, hasRrule pre-filter, zod validation'
contains: 'zValidator'
key_links:
- from: "apps/api/src/routes/events.ts"
to: "apps/api/src/broker/expand.ts"
via: "expandOccurrences() called per recurring/timed row"
pattern: "expandOccurrences"
- from: "apps/api/src/routes/events.ts"
to: "users.color"
via: "innerJoin calendars→users, select color + isShared + users.id"
- from: 'apps/api/src/routes/events.ts'
to: 'apps/api/src/broker/expand.ts'
via: 'expandOccurrences() called per recurring/timed row'
pattern: 'expandOccurrences'
- from: 'apps/api/src/routes/events.ts'
to: 'users.color'
via: 'innerJoin calendars→users, select color + isShared + users.id'
pattern: "users\\.color"
---
@@ -102,6 +102,7 @@ shared-family calendar (open question A3) — the operator marks it.
7. Use rrule ONLY as a fallback if ICAL.RecurExpansion cannot parse a given RRULE — do not import it on the primary path (D-09).
Turn the Plan 01 RED expand.test.ts stub green against the three fixtures.
</action>
<verify>
<automated>cd apps/api && pnpm test -- tests/broker/expand.test.ts</automated>
@@ -145,6 +146,7 @@ shared-family calendar (open question A3) — the operator marks it.
For each row, derive `color = row.isShared ? '#F25C7A' : row.userColor` and `isShared = row.isShared`, then call `expandOccurrences(row.rawVevent, windowStartDate, windowEndDate, { calendarId, calendarName: row.displayName, ownerUserId: row.userId, color, isShared })`. The `ownerUserId: row.userId` field is load-bearing — the client routes calendar color by it. Flatten all results into one array. Wrap the DB+expansion body in try/catch returning 503 on DB error (health.ts pattern). Return `c.json({ occurrences })`.
Turn the Plan 01 RED events.test.ts stub green (mock db.select chain following the health.test.ts vi.mock pattern; assert color field, isShared, ownerUserId, and 400 on bad params).
</action>
<verify>
<automated>cd apps/api && pnpm test -- tests/routes/events.test.ts</automated>
@@ -184,21 +186,23 @@ shared-family calendar (open question A3) — the operator marks it.
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| browser → /api/events | start/end query params are untrusted input crossing into SQL |
| cached VEVENT → expansion | rawVevent originates from Fastmail; parsed by ical.js |
| Boundary | Description |
| ------------------------- | ------------------------------------------------------------ |
| browser → /api/events | start/end query params are untrusted input crossing into SQL |
| cached VEVENT → expansion | rawVevent originates from Fastmail; parsed by ical.js |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-02b-01 | Tampering | start/end query params | mitigate | zod ISO-date regex validation before SQL; Drizzle parameterized queries (no string interpolation) |
| T-02b-02 | Denial of service | unwindowed/overwide fetch | mitigate | start+end required (zod); window hard-capped at 90 days; hasRrule index prevents full-table scan |
| T-02b-03 | Information disclosure | cross-account calendar leakage | mitigate | Route is behind oidcAuthMiddleware (Phase 1); each member's own credential fetched their own collections; no other-account ACL path exists (CAL-08-DECISION) |
| T-02b-04 | Tampering | malformed rawVevent | accept | expandOccurrences try/catch returns [] on parse failure; matches sync.ts resilience; no crash |
| Threat ID | Category | Component | Disposition | Mitigation Plan |
| --------- | ---------------------- | ------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| T-02b-01 | Tampering | start/end query params | mitigate | zod ISO-date regex validation before SQL; Drizzle parameterized queries (no string interpolation) |
| T-02b-02 | Denial of service | unwindowed/overwide fetch | mitigate | start+end required (zod); window hard-capped at 90 days; hasRrule index prevents full-table scan |
| T-02b-03 | Information disclosure | cross-account calendar leakage | mitigate | Route is behind oidcAuthMiddleware (Phase 1); each member's own credential fetched their own collections; no other-account ACL path exists (CAL-08-DECISION) |
| T-02b-04 | Tampering | malformed rawVevent | accept | expandOccurrences try/catch returns [] on parse failure; matches sync.ts resilience; no crash |
</threat_model>
<verification>
@@ -208,20 +212,22 @@ shared-family calendar (open question A3) — the operator marks it.
</verification>
<success_criteria>
- /api/events returns windowed, color-tagged, DST-correct, all-day-safe, EXDATE-aware occurrences
- Each occurrence carries ownerUserId + isShared for client-side color routing
- Bad/oversized windows rejected with 400
- Shared-family calendar marked and verified end-to-end
</success_criteria>
</success_criteria>
<artifacts_produced>
## Artifacts this phase produces (Plan 02)
- `expandOccurrences` (function) + `CalendarOccurrence` (interface) — apps/api/src/broker/expand.ts
- Evolved `eventsRouter` GET / handler with `{ occurrences }` response shape — apps/api/src/routes/events.ts
- `eventsQuerySchema` (zod) for start/end validation
- New JSON contract field set: id, uid, calendarId, calendarName, ownerUserId, color, isShared, title, start, end, allDay, location, description
</artifacts_produced>
</artifacts_produced>
<output>
Create `.planning/phases/02-calendar-display/02-02-SUMMARY.md` when done