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.
6.6 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | duration | completed | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-event-write-back-pwa-install | 02 | broker, caldav |
|
|
|
|
|
|
|
~4min | 2026-06-05 |
Phase 03 Plan 02: Broker Primitives — vevent.ts + write.ts Summary
VEVENT builder and tsdav write wrappers implemented GREEN against Wave 0 RED scaffolds — all 13 broker tests pass, tsc clean
Performance
- Duration: ~4 min
- Started: 2026-06-05T21:44Z
- Completed: 2026-06-05T21:48Z
- Tasks: 2
- Files created: 2
Accomplishments
- Implemented
buildVeventString(params: NewEventParams): { uid: string; icsString: string }inbroker/vevent.tsusing ical.js ICAL.Component/ICAL.Time APIs - D-13 DATE/DATETIME split: all-day events use
ICAL.Time({ isDate: true })→ VALUE=DATE (no TZID, no time); timed events useICAL.Time.fromJSDate(date, true)→ DTSTART:...Z (no TZID) - RRULE correctly serialized via
ICAL.Recur.fromString+ICAL.Property(notaddPropertyWithValuewhich produces char-split output) - Exported
NewEventParamsinterface andRRULE_PRESETSmap (daily/weekly/monthly/yearly preset strings) - Implemented
createCalendarEvent,updateCalendarEvent,deleteCalendarEventinbroker/write.tsas the sole CalDAV write boundary (D-12) - All etag null-coalescion to
''so tsdav safely omits the If-Match header rather than crashing
Task Commits
- Task 1: GREEN — buildVeventString —
b23b959(feat) - Task 2: GREEN — tsdav write wrappers + vevent.ts TS fix —
a1243c1(feat)
Files Created
apps/api/src/broker/vevent.ts— buildVeventString, NewEventParams, RRULE_PRESETS (117 lines)apps/api/src/broker/write.ts— createCalendarEvent, updateCalendarEvent, deleteCalendarEvent (99 lines)
Decisions Made
-
D-02-RRULE:
ICAL.Component.addPropertyWithValue('rrule', string)treats the raw string as a TEXT value and serializes character-by-character (e.g.,RRULE:0=F;1=R;2=E;3=Q...). The correct approach isICAL.Recur.fromString(rruleString)→prop = new ICAL.Property('rrule')→prop.setValue(recur)→vevent.addProperty(prop). This produces the correctRRULE:FREQ=WEEKLY;BYDAY=MOoutput. -
D-02-DATE-ZONE: ical.js
ICAL.TimeTypeScript types require 2 arguments(data: timeInit, zone: Timezone). For all-day DATE values,isDate: truein the data object suppresses any TZID/time output regardless of the zone passed.ICAL.Timezone.localTimezoneis the appropriate second arg — it satisfies the type and has no effect on DATE serialization.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] RRULE serialization via addPropertyWithValue produces character-split output
- Found during: Task 1 — first test run showed
RRULE:0=F;1=R;2=E;3=Q...instead ofRRULE:FREQ=WEEKLY;BYDAY=MO - Issue:
vevent.addPropertyWithValue('rrule', string)passes a JavaScript string where ical.js expects a RECUR value type. ical.js iterates the string object properties (0, 1, 2...) and serializes each character as a key-value pair. - Fix: Use
ICAL.Recur.fromString(params.rruleString)to parse the string into a RECUR value object, thennew ICAL.Property('rrule')+prop.setValue(recur)+vevent.addProperty(prop). - Files modified:
apps/api/src/broker/vevent.ts - Commit:
a1243c1(combined with Task 2)
2. [Rule 1 - Bug] ICAL.Time constructor TypeScript type error (2 args required)
- Found during: Task 2 —
tsc --noEmitreportedExpected 2 arguments, but got 1fornew ICAL.Time({ isDate: true })calls - Issue: ical.js TypeScript declarations define
constructor(data: timeInit, zone: Timezone)as requiring both arguments, though the JavaScript implementation accepts 1. - Fix: Pass
ICAL.Timezone.localTimezoneas the second arg. ForisDate: trueDATE values, the zone has no effect on serialization — it does not add TZID to the property. - Files modified:
apps/api/src/broker/vevent.ts - Commit:
a1243c1
Known Stubs
None — both files are fully implemented primitives. No hardcoded placeholder values.
Threat Surface Scan
No new network endpoints or auth paths introduced. broker/write.ts is a low-level CalDAV I/O primitive called only by the outbox worker (planned in 03-03). The T-03-03 (ical.js escaping) and T-03-04 (etag sourced server-side) mitigations from the threat model are implemented as designed.
Self-Check: PASSED
apps/api/src/broker/vevent.ts— exists (confirmed)apps/api/src/broker/write.ts— exists (confirmed)- Commit
b23b959— exists (git log confirmed) - Commit
a1243c1— exists (git log confirmed) pnpm --filter @familysync/api exec vitest run tests/broker/vevent.test.ts— 7/7 PASSpnpm --filter @familysync/api exec vitest run tests/broker/write.test.ts— 6/6 PASSpnpm --filter @familysync/api exec tsc --noEmit— clean (no errors)
Phase: 03-event-write-back-pwa-install Completed: 2026-06-05