Files
2026-06-18 22:21:38 -04:00

5.1 KiB

phase, reviewed, depth, files_reviewed, files_reviewed_list, findings, status
phase reviewed depth files_reviewed files_reviewed_list findings status
18-auto-timezone-detection-and-ability-to-change-timezone 2026-06-15T04:30:00Z standard 8
apps/api/src/lib/householdTimezone.ts
apps/api/src/routes/admin.ts
apps/api/src/broker/reminderScheduler.ts
apps/api/src/broker/outboxWorker.ts
apps/pwa/src/api/client.ts
apps/pwa/src/routes/AdminPage.tsx
apps/api/tests/lib/householdTimezone.test.ts
apps/api/tests/routes/admin.test.ts
critical warning info total
0 0 0 0
clean

Phase 18: Code Review Report

Reviewed: 2026-06-15T04:30:00Z Depth: standard Files Reviewed: 8 Status: clean

Summary

Re-review (iteration 2 of the --auto fix loop) of Phase 18 timezone changes after fixes for WR-01 (empty/whitespace process.env.TZ fallback guard), WR-02 (seeded derived from INSERT IGNORE affectedRows), and IN-01/02/03 (reuse fetched row on GET unset path; centralized D-06 fallback; per-drain-cycle timezone memoization). All previously-raised findings are resolved. No new Critical or Warning defects were introduced.

Narrative Findings (AI reviewer)

No Critical, Warning, or actionable Info findings remain. Verification notes below.

Verification of applied fixes

  • WR-01 — empty/whitespace TZ fallthrough (apps/api/src/lib/householdTimezone.ts:49-61). Correct. resolveHouseholdTimezone trims the stored value first; a set-but-blank stored value falls through, then process.env.TZ?.trim() rejects ''/' ' and falls through to the Intl zone. The normal stored-value path (stored truthy after trim) and the unset path are both preserved. D-05 (single accessor — both reminderScheduler.ts:250 and outboxWorker.ts:391 route through getHouseholdTimezone) and D-06 (stored → env.TZ → Intl chain) still hold. New unit tests pin both empty and whitespace cases (householdTimezone.test.ts:101-117).

  • WR-02 — seeded derived from affectedRows (apps/api/src/routes/admin.ts:251-269). Correct. The endpoint runs a single INSERT IGNORE and derives seeded from affectedRows === 1. On MariaDB an ignored duplicate yields affectedRows === 0, so the flag is accurate even under a genuine concurrent race — only the racer whose row actually wrote gets seeded:true. D-03 no-overwrite is preserved (duplicate is silently ignored, value untouched). The IANA value is validated by timezoneSchema.refine(isValidIanaTimezone) before the handler runs and is bound as a parameterized value via drizzle's sql template (not string- concatenated); column identifiers use sql.identifier — no injection. seeded:false accuracy is pinned by the pre-inserted-row test (admin.test.ts:821-846) and the idempotent-race test (:783-815).

  • IN-01/02 — GET unset path reuses the fetched row (apps/api/src/routes/admin.ts:200-213). Correct. The GET handler SELECTs once, computes isExplicitlySet from row?.value != null, and passes the same row?.value ?? null to the centralized resolveHouseholdTimezone. No second app_config round-trip; the D-06 fallback policy lives in exactly one function. Semantics unchanged: unset → fallback timezone + isExplicitlySet:false; set → stored value + true (admin.test.ts:631-663).

  • IN-03 — per-drain-cycle timezone memoization (apps/api/src/broker/outboxWorker.ts:385-395, 774). Correct. makeTimezoneResolver lazily caches the getHouseholdTimezone(db) promise so multiple all-day rows in one drain cycle share a single app_config read; cycles with no all-day work never touch the DB. The resolver is created per-cycle and discarded at cycle end, so a transient DB failure caching for one cycle is retried fresh next cycle, and a rejected resolve surfaces through the existing per-row catch as correct pending/transient behavior. No double-read regression. Both create (:634) and update (:528) all-day branches consume the shared resolver.

Other checks (no regressions)

  • Access control. adminRouter.use('*', requireAdmin) remains the first router statement; all three timezone routes (GET/PUT/POST-seed) sit behind it. 403 coverage exists for GET, PUT, and seed (admin.test.ts:603-625, 711-720).
  • IANA validation. Both PUT and seed share timezoneSchema with the try/catch-based isValidIanaTimezone (not Intl.supportedValuesOf, so UTC is accepted — Pitfall 2). Invalid input returns 400 and writes nothing (admin.test.ts:684-701).
  • Broker async correctness. getHouseholdTimezone is awaited before the all-day loop in reminderScheduler.ts:250; the outbox resolver is awaited inside each all-day branch. No un-awaited promises or new timer/handle leaks.
  • D-07 boundary guard. No changes to eventDateTime.ts / hydrateEvents.ts; the browser-local write/display path is untouched. AdminPage uses Intl…resolvedOptions().timeZone only for the "Use detected" affordance and never auto-writes (D-03 respected).

All reviewed files meet quality standards. No actionable issues remain.


Reviewed: 2026-06-15T04:30:00Z Reviewer: Claude (gsd-code-reviewer) Depth: standard