Files
familysync/.planning/milestones/v1.0-phases/03-event-write-back-pwa-install/03-03-SUMMARY.md
T
Lucas Berger 982438dc10 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.
2026-06-11 20:35:18 -04:00

7.9 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
03-event-write-back-pwa-install 03 api
hono
drizzle
zod
calendarOutbox
write-back
outbox-pattern
access-control
tdd
phase provides
03-event-write-back-pwa-install/03-01 calendarOutbox schema + calendarEvents.objectUrl + Wave-0 RED test scaffold
phase provides
03-event-write-back-pwa-install/03-02 broker primitives (vevent.ts, write.ts) — not used by routes but confirm broker boundary
POST /api/events/create — validates, checks D-03 ownership, enqueues pending outbox row, returns 202 with uid
PATCH /api/events/:uid/edit — looks up event, checks ownership, enqueues update or transaction-paired delete+create for calendar moves
DELETE /api/events/:uid — looks up event, checks ownership, enqueues delete row with server-side etag
GET /api/events/sync-status — member-scoped outbox status poll (D-09)
GET /api/events/writable-calendars — authoritative D-03 writable set (own personal + shared Family; never other member's personal)
zod schemas for event fields (title 255, location/description 2000 — T-03-08 bounds)
03-04 (outbox worker drains rows these endpoints enqueue)
03-05 (EventForm + client.ts consume these endpoints + writable-calendars)
added patterns
resolveUserId(c): dev-bypass c.get('user') first, fallback to getAuth(c) for OIDC — same pattern as me.ts
Enqueue-only write endpoints: no Fastmail call in routes; db.insert(calendarOutbox) is the only side effect
Edit-as-move: db.transaction with paired delete+create sharing a groupId (D-04)
sync-status: .orderBy(desc(createdAt)).limit(1) to get latest outbox row; userId-scoped (T-03-07)
writable-calendars: WHERE userId=currentUser.id OR isShared=1 — authoritative D-03 enforcement (T-03-11)
Test mock pattern for db.transaction: factory fn cb receives mock tx with insert; vi.mock hoisted factory captures mutable refs
devAuthBypass mock in tests: vi.mock('../auth/devBypass.js') injects dev user so write tests get authenticated context
created modified
apps/api/src/routes/events.ts
apps/api/tests/routes/events.test.ts
resolveUserId helper uses any type to avoid Hono context generic complexity — acceptable for internal helper
Two-query ownership check for edit/delete (get event, then check calendar isShared) to maintain simple from().where() chain that test mocks can intercept without innerJoin complexity
Writable-calendars response maps to { url, displayName, color, isShared } — the Plan 05 WritableCalendar shape
sync-status returns { uid, status: 'done' } when no outbox row found (nothing pending = settled)
Enqueue-only write route: validate → check ownership → db.insert(calendarOutbox) → return 202; no broker call
D-03 ownership enforcement at two layers: write endpoints AND writable-calendars listing
vi.mock devAuthBypass for write-endpoint tests avoids needing ENV manipulation or OIDC infrastructure
CAL-04
CAL-05
CAL-06
CAL-07
7min 2026-06-05

Phase 03 Plan 03: Write API Surface Summary

Hono write endpoints (create/edit/delete + sync-status + writable-calendars) enqueue to calendarOutbox with D-03 ownership enforcement; zod-validated, 202 optimistic-accept, no Fastmail call

Performance

  • Duration: ~7 min
  • Started: 2026-06-05T17:51:00Z
  • Completed: 2026-06-05T21:58:08Z
  • Tasks: 3 (Tasks 1-2-3 implemented in one feat commit; TDD RED gate committed separately)
  • Files modified: 2

Accomplishments

  • All five write/status/writable-calendars endpoints enqueue-only and member-scoped
  • D-03 ownership enforced on both the write path and the writable-calendars listing
  • D-04 edit-as-move pair implemented transactionally (db.transaction with shared groupId)
  • D-09 polling endpoint (sync-status) live with strict userId scoping (T-03-07)
  • Broker boundary preserved: no tsdav import in routes/events.ts

Task Commits

  1. RED gatee14c5da (test): extend events tests — write/sync-status/writable-calendars endpoints (9 new failing tests)
  2. GREEN + Tasks 1/2/30a82223 (feat): implement write API surface — all 69 events tests GREEN, tsc clean

Files Created/Modified

  • apps/api/src/routes/events.ts — extended with POST /create, PATCH /:uid/edit, DELETE /:uid, GET /sync-status, GET /writable-calendars; auth helper; zod schemas; db.transaction for edit-as-move
  • apps/api/tests/routes/events.test.ts — extended with 9 new write-endpoint tests; wired db.insert + db.transaction into vi.mock; added devAuthBypass mock for auth injection

Decisions Made

  • resolveUserId uses any type: Hono's generic context type is complex to thread through a standalone helper; any is acceptable for an internal module-private helper that does a simple property access.
  • Two-query ownership check for edit/delete: Rather than innerJoin (which would break the flat from().where() mock chain in tests), the implementation does a second query on calendars to check isShared when the event's userId doesn't match. Both queries share the same mock chain in tests, which works because both return the seeded mockDbRows.
  • writable-calendars response shape: { url, displayName, color, isShared } matches the WritableCalendar shape Plan 05's fetchWritableCalendars expects.
  • sync-status default to 'done': When no outbox row exists for a UID, the endpoint returns { uid, status: 'done' } — nothing pending means the event is settled.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] TypeScript error on resolveUserId helper

  • Found during: Task 1 (implementation) — tsc --noEmit reported TS2493/TS2339 on complex Hono context type inference
  • Issue: The helper function tried to infer the Hono context type from eventsRouter.get parameters, which failed due to tuple type length mismatch
  • Fix: Changed helper parameter to any with inline cast; added clarifying comment
  • Files modified: apps/api/src/routes/events.ts
  • Verification: tsc --noEmit passes clean
  • Committed in: 0a82223

Total deviations: 1 auto-fixed (Rule 1 - type error) Impact on plan: Minor typing accommodation; no behavior change.

Issues Encountered

  • Test mock architecture required careful design: the existing vi.mock for db/client.js only mocked db.select; extending it to include db.insert and db.transaction required restructuring the mock factory to use mutable vi.fn() references that can be reassigned in beforeEach. The devAuthBypass mock was added to give write-endpoint tests an authenticated user context without ENV manipulation.

Known Stubs

None — all endpoints are fully wired to the DB schema. The outbox rows they insert will be drained by the Plan 04 worker; until that plan runs, rows accumulate in pending state (correct behavior).

Threat Flags

No new network endpoints or auth paths beyond what is in the plan's threat model. All T-03-06 through T-03-11 mitigations are implemented.

Self-Check

  • apps/api/src/routes/events.ts exists and includes all 5 endpoints
  • apps/api/tests/routes/events.test.ts exists and tests are GREEN (69 passed)
  • Commits e14c5da (test RED) and 0a82223 (feat GREEN) exist
  • grep -c "tsdav\|createFastmailClient" apps/api/src/routes/events.ts = 1 (comment only, not import)
  • grep -c "db.transaction" apps/api/src/routes/events.ts = 1
  • tsc --noEmit passes clean

Self-Check: PASSED

Next Phase Readiness

  • Plan 04 (outbox worker): calendarOutbox rows are being enqueued; worker can now drain them
  • Plan 05 (EventForm + client.ts): POST /create, PATCH /:uid/edit, DELETE /:uid endpoints are live; GET /writable-calendars provides the picker data

Phase: 03-event-write-back-pwa-install Completed: 2026-06-05