Files
familysync/.planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-03-SUMMARY.md
T

8.6 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed duration completed
18-auto-timezone-detection-and-ability-to-change-timezone 03 api
timezone
broker
reminder-scheduler
outbox-worker
tdd
drizzle
phase plan provides
18-auto-timezone-detection-and-ability-to-change-timezone 01 getHouseholdTimezone(db) — the accessor imported at all three rewired sites
reminderScheduler.ts all-day branch reads stored household_timezone via getHouseholdTimezone(db)
outboxWorker.ts UPDATE all-day branch reads stored household_timezone via getHouseholdTimezone(db)
outboxWorker.ts CREATE all-day branch reads stored household_timezone via getHouseholdTimezone(db)
18-04-PLAN (PWA settings UI — all three rewired sites now respect the timezone set via Plan 18-02 admin API)
added patterns
getHouseholdTimezone(db) imported and awaited at three all-day broker sites (replacing bare process.env.TZ ?? Intl)
Mock chain extension
makeAppConfigSelectMock for where().limit() chain (different from makeSelectMock which handles innerJoin chains)
mockTwoQueries updated to mock the new third db.select() call, keeping existing tests green via D-06 fallback
wireMockChain() extended in outboxWorker.test.ts to handle app_config table with where().limit() returning []
created modified
apps/api/src/broker/reminderScheduler.ts
apps/api/src/broker/outboxWorker.ts
apps/api/tests/broker/reminderScheduler.test.ts
apps/api/tests/broker/outboxWorker.test.ts
D-05 wired: three all-day sites replaced bare process.env.TZ ?? Intl with await getHouseholdTimezone(db)
D-06 preserved: getHouseholdTimezone falls back to process.env.TZ → Intl when no app_config row exists; existing tests pin process.env.TZ and pass unchanged
D-07 enforced: eventDateTime.ts and hydrateEvents.ts not modified (verified via git diff)
mockTwoQueries extended to emit a third mockReturnValueOnce for the new app_config SELECT (no-row → D-06 fallback)
mockReset() added in mockTwoQueries/mockThreeQueries to clear unconsumed once-queue entries across vi.resetModules() cycles (Vitest caches mock factory instances)
D-05
D-06
D-07
28min 2026-06-15

Phase 18 Plan 03: Broker Rewire — All-Day TZ Sites Summary

Three all-day "9 AM local" reminder call sites in reminderScheduler.ts and outboxWorker.ts now route through getHouseholdTimezone(db) instead of the bare process.env.TZ ?? Intl expression, making the stored household_timezone the source of truth for all-day reminder fire times (D-05/D-06/D-07)

Performance

  • Duration: 28 min
  • Started: 2026-06-15T02:04:00Z
  • Completed: 2026-06-15T02:32:01Z
  • Tasks: 2 (TDD RED + GREEN)
  • Files modified: 4

Accomplishments

  • Rewired reminderScheduler.ts line 247: const serverTz = await getHouseholdTimezone(db);
  • Rewired outboxWorker.ts UPDATE branch (~line 501): const tz = await getHouseholdTimezone(db);
  • Rewired outboxWorker.ts CREATE branch (~line 607): const tz = await getHouseholdTimezone(db);
  • Added 4 new RED tests (2 scheduler + 2 outbox) verifying stored 'America/Chicago' drives the 9 AM alert instant; all correctly FAIL before the rewire
  • Extended test mock infrastructure: makeAppConfigSelectMock, mockThreeQueries, updated mockTwoQueries to add third call for D-06 fallback path, updated wireMockChain() in outboxWorker.test.ts for app_config table
  • All 76 broker tests pass after GREEN commit; TypeScript typecheck (tsc --noEmit) clean
  • D-07 boundary confirmed: eventDateTime.ts and hydrateEvents.ts not in changed-files list

Task Commits

  1. Task 1: RED — failing stored-TZ all-day tests - 94daca3 (test)
  2. Task 2: GREEN — rewire all three all-day TZ call sites - c80845c (feat)

Files Created/Modified

  • apps/api/src/broker/reminderScheduler.ts — import added + line 247 rewired to await getHouseholdTimezone(db)
  • apps/api/src/broker/outboxWorker.ts — import added + lines ~501 and ~607 rewired to await getHouseholdTimezone(db)
  • apps/api/tests/broker/reminderScheduler.test.tsmakeAppConfigSelectMock, mockThreeQueries helpers added; mockTwoQueries extended to mock the new third db.select(); Plan 18-03 describe block with 2 stored-TZ tests
  • apps/api/tests/broker/outboxWorker.test.tswireMockChain() extended for app_config table; wireMockChainWithTz() helper for D-05 stored-TZ tests; Plan 18-03 describe block with 2 stored-TZ tests (create + update branch)

Decisions Made

  • mockTwoQueries was extended (not renamed) to avoid updating 20+ call sites. The third mock call returns an empty app_config row (no stored TZ → D-06 fallback), which is transparent to all existing tests that pin process.env.TZ.
  • mockReset() added inside mockTwoQueries and mockThreeQueries to flush any unconsumed mockReturnValueOnce entries. Vitest caches mock factory instances across vi.resetModules() cycles, so the pending third entry from one test bleeds into the next test's queue without an explicit reset.
  • The outbox UPDATE branch test uses mockWhereCalEvents.mockResolvedValue([{etag: 'W/"abc"'}]) after wireMockChainWithTz to drive the freshEtagRows path into the all-day VALARM branch (no rawVevent → falls through to the explicit-reminder/allDay condition).

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] vi.fn() queue contamination across tests with vi.resetModules()

  • Found during: Task 1 (RED) — second scheduler test crashed with "innerJoin is not a function"
  • Issue: vi.clearAllMocks() does not flush mockReturnValueOnce queues. Vitest's mock factory for vi.mock() returns the SAME vi.fn() instance across module resets, so an unconsumed third entry from the first test bled into the first call slot of the second test — causing makeAppConfigSelectMock to be returned where makeSelectMock was expected.
  • Fix: Added db.select.mockReset() at the start of both mockTwoQueries and mockThreeQueries to explicitly clear the queue before configuring new once-values.
  • Files modified: apps/api/tests/broker/reminderScheduler.test.ts

2. [Rule 2 - Missing critical functionality] wireMockChain() in outboxWorker.test.ts didn't handle app_config

  • Found during: Task 2 (GREEN) — the existing CAL-13 all-day test would have broken after the rewire because app_config fell through to { where: mockWherePending }, and mockWherePending(...) returns a Promise; calling .limit(1) on a Promise throws "limit is not a function"
  • Fix: Extended wireMockChain() to handle app_config table with a { where: fn → {limit: fn} } chain returning empty rows (D-06 fallback), matching the getHouseholdTimezone SELECT chain
  • Files modified: apps/api/tests/broker/outboxWorker.test.ts

3. [Rule 2 - Missing critical functionality] mockTwoQueries needed a third mock for the new db.select() call

  • Found during: Task 2 (GREEN) — all 20 existing scheduler tests that call mockTwoQueries started failing because runReminderCheck now issues a third db.select() for getHouseholdTimezone; the undefined return caused crashes
  • Fix: Updated mockTwoQueries to add a third mockReturnValueOnce(makeAppConfigSelectMock(null)) returning no row, keeping the D-06 fallback path for all existing tests
  • Files modified: apps/api/tests/broker/reminderScheduler.test.ts

Known Stubs

None — all three rewired sites now read from the actual DB through getHouseholdTimezone(db). No placeholder values.

Threat Flags

None — no new network endpoints, auth paths, file access patterns, or schema changes. The three rewired sites are read-only DB lookups within the trusted server process (T-18-08 disposition: mitigate → validated values only reach computeAlertInstantUtc via the Plan 01 accessor that enforces the D-06 fallback chain).

TDD Gate Compliance

  • RED gate: 94daca3test(18-03): add failing stored-TZ all-day tests for scheduler + outbox
  • GREEN gate: c80845cfeat(18-03): route all-day reminder TZ through stored household_timezone
  • REFACTOR gate: N/A (implementation was clean on first pass; deviations were auto-fixed inline)

Self-Check: PASSED

  • apps/api/src/broker/reminderScheduler.ts — FOUND
  • apps/api/src/broker/outboxWorker.ts — FOUND
  • apps/api/tests/broker/reminderScheduler.test.ts — FOUND
  • apps/api/tests/broker/outboxWorker.test.ts — FOUND
  • Commit 94daca3 — FOUND
  • Commit c80845c — FOUND

Phase: 18-auto-timezone-detection-and-ability-to-change-timezone Completed: 2026-06-15