Files
familysync/.planning/phases/06-ux-polish/06-03-SUMMARY.md
T

5.2 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
06-ux-polish 03 api/broker
tdd
expand
hasRrule
ical
recurrence
d-08
d-06
requires provides affects
apps/api/src/broker/expand.ts (CalendarOccurrence interface)
apps/api/tests/fixtures/*.ics (existing fixtures)
CalendarOccurrence.hasRrule: boolean (server source-of-truth)
weekly-count3.ics test fixture (bounded RRULE, COUNT=3)
expand.test.ts hasRrule + bounded-RRULE assertions
apps/api/src/broker/expand.ts (CalendarOccurrence consumers — routes/events.ts)
apps/pwa/src/api/client.ts (mirror field added in Plan 05)
added patterns
TDD RED→GREEN: test-only commit followed by implementation commit
Capture event.isRecurring() once before branch, pass to both push sites
epochMilliseconds (not epochSeconds) for Temporal duration arithmetic with temporal-polyfill
created modified
apps/api/tests/fixtures/weekly-count3.ics
apps/api/src/broker/expand.ts
apps/api/tests/broker/expand.test.ts
D-08: hasRrule derived from event.isRecurring() — no DB query change needed (already available on the parsed ICAL.Event)
Captured isRecurring once before the non-recurring/recurring branch (single capture pattern from PATTERNS.md)
epochMilliseconds used for Temporal duration math — temporal-polyfill returns number not BigInt for this property
weekly-count3.ics uses UTC DTSTART/DTEND (no VTIMEZONE needed) for simplicity in the bounded test fixture
duration completed tasks_completed files_modified
11m 2026-06-10 2 3

Phase 06 Plan 03: hasRrule Server-Side Exposure Summary

Added hasRrule: boolean to the CalendarOccurrence interface in expand.ts and populated it via event.isRecurring() — the server-side signal that gates the whole-series-edit confirmation prompt (D-08).

Tasks Completed

# Task Type Commit Outcome
1 RED — failing tests for hasRrule + bounded expansion TDD test 593302e 3 hasRrule failures + duration test confirmed red
2 GREEN — add hasRrule to interface and populate it TDD impl 44d336c 10/10 expand tests pass

What Was Built

apps/api/src/broker/expand.ts

Added hasRrule: boolean field to CalendarOccurrence interface with JSDoc. Captured const isRecurring = event.isRecurring() once before the non-recurring/recurring branch. Both occurrences.push({...}) sites now include hasRrule: isRecurring — false in the non-recurring branch, true in the recurring branch.

apps/api/tests/broker/expand.test.ts

Added two new describe blocks:

hasRrule field — D-08 (2 tests):

  • weekly-dst.ics (recurring): all occurrences have hasRrule === true
  • single-duration.ics (non-recurring): the single occurrence has hasRrule === false

Bounded RRULE (COUNT=3) — D-06 invariant (3 tests):

  • COUNT=3 within a 6-month window returns exactly 3 occurrences
  • Each bounded occurrence duration = 1 hour from DTSTART→DTEND (not recurrence span)
  • Bounded occurrences have hasRrule === true

apps/api/tests/fixtures/weekly-count3.ics

New fixture: FREQ=WEEKLY;COUNT=3, DTSTART:20260601T090000Z, DTEND:20260601T100000Z (1-hour UTC events). Used for the bounded expansion invariant test.

TDD Gate Compliance

  • RED commit (test(06-03): ...) at 593302e — tests written first, confirmed failing due to absent hasRrule field
  • GREEN commit (feat(06-03): ...) at 44d336c — implementation added, all 10 tests pass

Deviations from Plan

1. [Rule 1 - Bug] Duration test using epochMilliseconds instead of epochSeconds

  • Found during: Task 1 test writing
  • Issue: Temporal.ZonedDateTime.epochSeconds returns NaN in the temporal-polyfill package used in the test suite; epochMilliseconds returns a regular number
  • Fix: Duration assertion uses endZdt.epochMilliseconds - startZdt.epochMilliseconds and compares to 3_600_000 (1 hour in ms)
  • Files modified: apps/api/tests/broker/expand.test.ts
  • Commit: 593302e (incorporated into RED commit before final form)

No other deviations. Plan executed as written.

Verification

npx vitest run tests/broker/expand.test.ts
Test Files  1 passed (1)
     Tests  10 passed (10)
grep -n "hasRrule" apps/api/src/broker/expand.ts
68:  hasRrule: boolean
224:  // Capture once — used in both branches to populate hasRrule.
261:        hasRrule: isRecurring, // always false in the non-recurring branch
308:      hasRrule: isRecurring, // always true in the recurring branch

client.ts untouched — mirror field is Plan 05's responsibility.

Known Stubs

None. hasRrule is fully populated from event.isRecurring() — no placeholder values.

Threat Flags

No new threat surface. hasRrule is a boolean derived from data already returned to the authenticated caller. T-06-03 accepted in plan threat model.

Self-Check: PASSED

  • apps/api/src/broker/expand.ts exists and contains hasRrule
  • apps/api/tests/fixtures/weekly-count3.ics exists
  • apps/api/tests/broker/expand.test.ts contains hasRrule assertions
  • Commits 593302e and 44d336c exist in git log