docs(18): code review clean + fix report (--fix --auto --all)
Re-review after fixes: status clean (0 Critical/Warning). All 5 findings from the prior pass resolved across 4 atomic fix commits: - WR-01: treat empty/blank TZ as unset in the D-06 fallback chain - WR-02: derive seed `seeded` flag from INSERT IGNORE affectedRows (accurate under concurrent race; D-03 no-overwrite preserved) - IN-01/02: reuse fetched row on GET unset path; centralize D-06 fallback - IN-03: memoize household timezone per outbox drain cycle Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1fb431e8da
commit
60621468be
+92
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
phase: 18-auto-timezone-detection-and-ability-to-change-timezone
|
||||||
|
fixed_at: 2026-06-15T07:43:30Z
|
||||||
|
review_path: .planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-REVIEW.md
|
||||||
|
iteration: 1
|
||||||
|
findings_in_scope: 5
|
||||||
|
fixed: 5
|
||||||
|
skipped: 0
|
||||||
|
status: all_fixed
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18: Code Review Fix Report
|
||||||
|
|
||||||
|
**Fixed at:** 2026-06-15T07:43:30Z
|
||||||
|
**Source review:** .planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-REVIEW.md
|
||||||
|
**Iteration:** 1
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
- Findings in scope: 5 (2 Warning + 3 Info; fix_scope = all)
|
||||||
|
- Fixed: 5
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
All in-scope findings were fixed. The full API test suite (375 tests across 28
|
||||||
|
files) and `tsc --noEmit` pass cleanly. No PWA files were touched, so PWA tests
|
||||||
|
were not run.
|
||||||
|
|
||||||
|
## Fixed Issues
|
||||||
|
|
||||||
|
### WR-01: Empty-but-set `process.env.TZ` defeats the D-06 fallback and yields an invalid zone
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/lib/householdTimezone.ts`, `apps/api/tests/lib/householdTimezone.test.ts`
|
||||||
|
**Commit:** d168da7
|
||||||
|
**Applied fix:** Extracted the D-06 fallback into a new `resolveHouseholdTimezone(storedValue)`
|
||||||
|
helper that `.trim()`s candidate values and treats empty/whitespace-only values
|
||||||
|
(both the stored value and `process.env.TZ`) as absent so they fall through to the
|
||||||
|
`Intl` resolved zone, instead of relying on `??` which only short-circuits on
|
||||||
|
null/undefined. `getHouseholdTimezone` now delegates to it. Added RED→GREEN unit
|
||||||
|
tests for `TZ=''` and `TZ=' '` proving fall-through to the Intl zone. Updated the
|
||||||
|
doc comment to reflect the now-enforced "non-empty" guarantee.
|
||||||
|
|
||||||
|
### WR-02: Seed `seeded` flag can misreport under a real concurrent race
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/routes/admin.ts`, `apps/api/tests/routes/admin.test.ts`
|
||||||
|
**Commit:** 93217b5
|
||||||
|
**Applied fix:** Replaced the pre-flight `SELECT` + conditional
|
||||||
|
`onDuplicateKeyUpdate` with a single `INSERT IGNORE` and derive `seeded` from the
|
||||||
|
result's `affectedRows`. **Note (deviation from the review's literal suggestion):**
|
||||||
|
the review proposed `seeded: insertResult.affectedRows === 1` against the existing
|
||||||
|
`onDuplicateKeyUpdate(value=value)`. I empirically probed this MariaDB and found
|
||||||
|
`onDuplicateKeyUpdate(value=value)` returns `affectedRows: 1` for BOTH a fresh insert
|
||||||
|
and a no-op duplicate, so it cannot distinguish them. `INSERT IGNORE` reliably returns
|
||||||
|
`affectedRows: 1` on insert and `0` when the row already exists (ignored, value
|
||||||
|
preserved — D-03), which is what makes the derived flag accurate under a concurrent
|
||||||
|
race. The `timezone` is interpolated via drizzle's parameterized `sql` template (bound
|
||||||
|
param, not string concatenation) and is already IANA-validated by `timezoneSchema`.
|
||||||
|
Corrected the overstated in-code comment. Added a test asserting `seeded:false` for a
|
||||||
|
row pre-inserted directly (bypassing the endpoint), which only an INSERT-derived flag
|
||||||
|
can satisfy.
|
||||||
|
|
||||||
|
### IN-01: `getHouseholdTimezone` re-runs the same `app_config` SELECT the GET handler just issued
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/routes/admin.ts`
|
||||||
|
**Commit:** 692fe2a
|
||||||
|
**Applied fix:** The `GET /config/timezone` handler now reuses the row it already
|
||||||
|
SELECTed by calling `resolveHouseholdTimezone(row?.value ?? null)` instead of
|
||||||
|
`getHouseholdTimezone(db)`, removing the redundant second `app_config` round-trip on
|
||||||
|
the unset path. Behavior unchanged.
|
||||||
|
|
||||||
|
### IN-02: D-06 fallback policy is duplicated between the accessor and the GET handler
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/lib/householdTimezone.ts`, `apps/api/src/routes/admin.ts`
|
||||||
|
**Commit:** d168da7 (accessor), 692fe2a (handler)
|
||||||
|
**Applied fix:** Centralized the fallback policy in the new `resolveHouseholdTimezone`
|
||||||
|
helper (single source of truth, D-05 intent). The GET handler derives
|
||||||
|
`isExplicitlySet` from row presence and routes the value through the same helper, so
|
||||||
|
the WR-01 empty-`TZ` guard lives in exactly one place and the two sites cannot drift.
|
||||||
|
|
||||||
|
### IN-03: `outboxWorker` may read the stored timezone twice within one drain cycle
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/broker/outboxWorker.ts`
|
||||||
|
**Commit:** 1fb431e
|
||||||
|
**Applied fix:** Added a lazy per-drain-cycle `TimezoneResolver` (mirroring the
|
||||||
|
existing `clientCache` thread-through, IN-01) created in `runOutboxDrain` and passed
|
||||||
|
into `dispatchRow`. The UPDATE and CREATE all-day branches now share a single
|
||||||
|
`app_config` read. The read stays lazy — cycles with no all-day work never touch the
|
||||||
|
DB. Behavior unchanged; all 39 outboxWorker tests pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Fixed: 2026-06-15T07:43:30Z_
|
||||||
|
_Fixer: Claude (gsd-code-fixer)_
|
||||||
|
_Iteration: 1_
|
||||||
+65
-86
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
phase: 18-auto-timezone-detection-and-ability-to-change-timezone
|
phase: 18-auto-timezone-detection-and-ability-to-change-timezone
|
||||||
reviewed: 2026-06-15T02:49:55Z
|
reviewed: 2026-06-15T04:30:00Z
|
||||||
depth: standard
|
depth: standard
|
||||||
files_reviewed: 6
|
files_reviewed: 8
|
||||||
files_reviewed_list:
|
files_reviewed_list:
|
||||||
- apps/api/src/lib/householdTimezone.ts
|
- apps/api/src/lib/householdTimezone.ts
|
||||||
- apps/api/src/routes/admin.ts
|
- apps/api/src/routes/admin.ts
|
||||||
@@ -10,112 +10,91 @@ files_reviewed_list:
|
|||||||
- apps/api/src/broker/outboxWorker.ts
|
- apps/api/src/broker/outboxWorker.ts
|
||||||
- apps/pwa/src/api/client.ts
|
- apps/pwa/src/api/client.ts
|
||||||
- apps/pwa/src/routes/AdminPage.tsx
|
- apps/pwa/src/routes/AdminPage.tsx
|
||||||
|
- apps/api/tests/lib/householdTimezone.test.ts
|
||||||
|
- apps/api/tests/routes/admin.test.ts
|
||||||
findings:
|
findings:
|
||||||
critical: 0
|
critical: 0
|
||||||
warning: 3
|
warning: 0
|
||||||
info: 4
|
info: 0
|
||||||
total: 7
|
total: 0
|
||||||
status: wr-01-resolved wr-02-resolved wr-03-accepted
|
status: clean
|
||||||
---
|
---
|
||||||
|
|
||||||
# Phase 18: Code Review Report
|
# Phase 18: Code Review Report
|
||||||
|
|
||||||
**Reviewed:** 2026-06-15T02:49:55Z
|
**Reviewed:** 2026-06-15T04:30:00Z
|
||||||
**Depth:** standard
|
**Depth:** standard
|
||||||
**Files Reviewed:** 6 (production) + 5 test files (coverage review)
|
**Files Reviewed:** 8
|
||||||
**Status:** issues-found
|
**Status:** clean
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Phase 18 adds a stored household timezone with auto-detection, an admin picker, and rewires the all-day "9 AM local" reminder math through a single shared accessor. The core security and correctness contract holds up well under adversarial review:
|
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.
|
||||||
|
|
||||||
- **Access control (D-04) is sound.** All three new endpoints (`GET`/`PUT /config/timezone`, `POST /config/timezone/seed`) are registered on `adminRouter` *after* `adminRouter.use('*', requireAdmin)` (admin.ts:42), so they inherit the guard. Tests assert 403 for non-admins on GET and PUT (admin.test.ts:603-625).
|
## Narrative Findings (AI reviewer)
|
||||||
- **Injection safety is solid.** User-supplied timezone strings are validated by `isValidIanaTimezone` (Zod `.refine`) *and* Drizzle parameterizes the INSERT/upsert — no string interpolation reaches SQL. No path traversal or command surface is touched.
|
|
||||||
- **D-03 no-overwrite seed semantics are correct** (SELECT-then-conditional-INSERT, admin.test.ts:730-754).
|
|
||||||
- **D-05/D-06 accessor + fallback chain is correct** and well-tested (householdTimezone.test.ts), including the null-value fall-through.
|
|
||||||
- **D-07 boundary respected** — the browser-local display/serialization path is untouched.
|
|
||||||
|
|
||||||
No Critical findings. Three Warnings: a genuine logic defect that prevents an admin from ever saving the displayed system-default value (contradicting the on-screen instruction), an unhandled race/duplicate-key path on the seed endpoint, and a redundant DB round-trip on the GET handler. Four Info items.
|
No Critical, Warning, or actionable Info findings remain. Verification notes below.
|
||||||
|
|
||||||
## Warnings
|
### Verification of applied fixes
|
||||||
|
|
||||||
### WR-01: Admin cannot save the displayed system-default timezone to make it explicit — RESOLVED
|
- **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`).
|
||||||
|
|
||||||
**File:** `apps/pwa/src/routes/AdminPage.tsx:111-118` (with 362-372)
|
- **WR-02 — `seeded` derived from affectedRows (`apps/api/src/routes/admin.ts:251-269`).**
|
||||||
**Status:** Fixed in commit `173e06e` — `fix(18): enable first-run timezone save when not explicitly set (WR-01)`
|
Correct. The endpoint runs a single `INSERT IGNORE` and derives `seeded` from
|
||||||
**Issue:** When no row is stored, `GET /config/timezone` returns `isExplicitlySet: false` and `timezone` = the D-06 fallback (e.g. `UTC` inside Docker, or the detected zone). The UI pre-fills the input with that fallback (`effectiveTimezoneInput = timezoneInput ?? storedTimezone`) and simultaneously shows the notice **"Using system default — save a timezone to make it explicit."** But `timezoneSaveDisabled` includes `effectiveTimezoneInput === storedTimezone` (line 118). Since the input already equals `storedTimezone` (the fallback), **Save is disabled** — the admin literally cannot perform the action the notice instructs. They must first change the value to something else, then back, or pick a different zone, to enable Save. This defeats the explicit-set affordance for the common first-run case where the detected/fallback zone is already correct.
|
`affectedRows === 1`. On MariaDB an ignored duplicate yields `affectedRows === 0`, so the flag
|
||||||
**Fix applied:**
|
is accurate even under a genuine concurrent race — only the racer whose row actually wrote gets
|
||||||
```tsx
|
`seeded:true`. D-03 no-overwrite is preserved (duplicate is silently ignored, value untouched).
|
||||||
const isExplicit = timezoneQuery.data?.isExplicitlySet ?? false;
|
The IANA value is validated by `timezoneSchema.refine(isValidIanaTimezone)` before the handler
|
||||||
const timezoneSaveDisabled =
|
runs and is bound as a parameterized value via drizzle's `sql` template (not string-
|
||||||
timezoneMutation.isPending ||
|
concatenated); column identifiers use `sql.identifier` — no injection. `seeded:false` accuracy
|
||||||
effectiveTimezoneInput === '' ||
|
is pinned by the pre-inserted-row test (`admin.test.ts:821-846`) and the idempotent-race test
|
||||||
(isExplicit && effectiveTimezoneInput === storedTimezone);
|
(`:783-815`).
|
||||||
```
|
|
||||||
Unit tests added in `apps/pwa/src/routes/AdminPage.timezone.test.ts` (7 tests covering first-run enabled and already-explicit disabled cases).
|
|
||||||
|
|
||||||
### WR-02: Seed endpoint has an unhandled duplicate-key path (race + no try/catch) — RESOLVED
|
- **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`).
|
||||||
|
|
||||||
**File:** `apps/api/src/routes/admin.ts:248-265`
|
- **IN-03 — per-drain-cycle timezone memoization
|
||||||
**Status:** Fixed in commit `bda31a3` — `fix(18): make timezone seed idempotent under concurrent race (WR-02)`
|
(`apps/api/src/broker/outboxWorker.ts:385-395, 774`).** Correct. `makeTimezoneResolver` lazily
|
||||||
**Issue:** `POST /config/timezone/seed` does a non-transactional SELECT, then a bare `INSERT` (no `onDuplicateKeyUpdate`) when `!alreadySet`. Two concurrent seeds, or a seed racing a `PUT`, can both observe "unset" and both attempt the INSERT; the second hits the `app_config.key` primary-key constraint and throws. Because there is no `try/catch`, the rejection propagates as an unhandled 500 rather than the documented `200 { ok, seeded }`. The first-run wizard calling this on initial load makes the seed-vs-PUT overlap plausible. (For a two-person household the probability is low, hence Warning not Blocker — but the failure mode is a 500 with a stack, not graceful no-op.)
|
caches the `getHouseholdTimezone(db)` promise so multiple all-day rows in one drain cycle share a
|
||||||
**Fix applied:** Added `onDuplicateKeyUpdate({ set: { value: sql\`value\` } })` to the INSERT so the PK constraint can never be violated. The `seeded` flag still reflects the pre-flight SELECT. Three new tests added in `apps/api/tests/routes/admin.test.ts`: 403 access control on POST seed, `seeded:true` on first seed, and sequential idempotent second seed returns `seeded:false` without throwing.
|
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.
|
||||||
|
|
||||||
### WR-03: GET /config/timezone issues a redundant second DB query on the unset path — ACCEPTED (not fixing)
|
### Other checks (no regressions)
|
||||||
|
|
||||||
**File:** `apps/api/src/routes/admin.ts:200-213`
|
- **Access control.** `adminRouter.use('*', requireAdmin)` remains the first router statement; all
|
||||||
**Issue:** The handler selects `household_timezone` (lines 201-205), then when `row?.value == null` calls `await getHouseholdTimezone(db)` (line 210) — which runs the *same* `SELECT` again before applying the fallback chain. Two round-trips for the common first-run case. Not a correctness bug, but it duplicates the query the accessor already performs and couples the handler to the accessor's internals.
|
three timezone routes (GET/PUT/POST-seed) sit behind it. 403 coverage exists for GET, PUT, and
|
||||||
**Fix:** Compute the fallback inline from the already-fetched `row`, or have `getHouseholdTimezone` accept an optional pre-fetched value. Minimal inline version:
|
seed (`admin.test.ts:603-625, 711-720`).
|
||||||
```ts
|
- **IANA validation.** Both PUT and seed share `timezoneSchema` with the try/catch-based
|
||||||
const isExplicitlySet = row?.value != null;
|
`isValidIanaTimezone` (not `Intl.supportedValuesOf`, so `UTC` is accepted — Pitfall 2). Invalid
|
||||||
const timezone = isExplicitlySet
|
input returns 400 and writes nothing (`admin.test.ts:684-701`).
|
||||||
? (row.value as string)
|
- **Broker async correctness.** `getHouseholdTimezone` is awaited before the all-day loop in
|
||||||
: (process.env.TZ ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
|
`reminderScheduler.ts:250`; the outbox resolver is awaited inside each all-day branch. No
|
||||||
```
|
un-awaited promises or new timer/handle leaks.
|
||||||
(Keeps a single SELECT; mirrors the accessor's D-06 chain — extract a shared `resolveFallbackTz()` if you want to avoid drift.)
|
- **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).
|
||||||
|
|
||||||
## Info
|
All reviewed files meet quality standards. No actionable issues remain.
|
||||||
|
|
||||||
### IN-01: `isValidIanaTimezone` accepts non-canonical zones Intl tolerates
|
|
||||||
|
|
||||||
**File:** `apps/api/src/lib/householdTimezone.ts:48-55`
|
|
||||||
**Issue:** `Intl.DateTimeFormat(undefined, { timeZone: tz })` accepts inputs beyond the canonical IANA set the picker offers (e.g. legacy aliases like `Etc/GMT+5`, or case-insensitive `utc`). This is *correct and intentional* for the security gate (it rejects garbage, which is all that matters before the parameterized write), and the comment correctly explains why `Intl.supportedValuesOf` is avoided. Noting only that the stored value may not match a `datalist` option exactly, which is harmless. No fix required; documenting the accepted-set breadth would help future readers.
|
|
||||||
**Fix:** Optional — add a one-line note that any zone Intl accepts is storable, not only `supportedValuesOf` entries.
|
|
||||||
|
|
||||||
### IN-02: Detected-zone affordance hidden once input matches detected zone
|
|
||||||
|
|
||||||
**File:** `apps/pwa/src/routes/AdminPage.tsx:405`
|
|
||||||
**Issue:** `detectedTz && detectedTz !== effectiveTimezoneInput` hides the "Use detected" button as soon as the input equals the detected zone. Combined with WR-01, a first-run user whose fallback already equals their browser zone sees neither an enabled Save nor the detected affordance — there is no single tap to make the correct value explicit. Resolving WR-01 removes the dead-end; this is just the contributing display condition.
|
|
||||||
**Fix:** No change needed once WR-01 is fixed.
|
|
||||||
|
|
||||||
### IN-03: Broker reads the timezone once per tick / per outbox item (acceptable, worth a note)
|
|
||||||
|
|
||||||
**File:** `apps/api/src/broker/reminderScheduler.ts:250`, `apps/api/src/broker/outboxWorker.ts:504,612`
|
|
||||||
**Issue:** The scheduler reads `getHouseholdTimezone(db)` once per tick (good — hoisted above the `allDayRows` loop). The outbox worker reads it once per processed all-day create/update item. Both are correct (no caching bug, picks up admin changes promptly) and async ordering is sound — `await` completes before `computeAlertInstantUtc` consumes `tz`. Per-item reads in the worker are a minor extra query but well within scope and not a performance concern at household scale (performance is out of v1 review scope regardless).
|
|
||||||
**Fix:** None required. If desired later, hoist the worker read to once per drain batch.
|
|
||||||
|
|
||||||
### IN-04: `<section aria-label="Timezone">` relies on implicit region role for e2e selectors
|
|
||||||
|
|
||||||
**File:** `apps/pwa/src/routes/AdminPage.tsx:332`; `apps/pwa/e2e/timezone-verify.spec.ts:17`
|
|
||||||
**Issue:** The e2e spec selects `getByRole('region', { name: 'Timezone' })`. A `<section>` only exposes the `region` role when it has an accessible name — which `aria-label` provides here, so the selectors are valid. This is correct; flagged only because the coupling is implicit (removing the `aria-label` would silently break both the a11y affordance and the e2e suite).
|
|
||||||
**Fix:** None required; keep the `aria-label`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Test Coverage Assessment
|
_Reviewed: 2026-06-15T04:30:00Z_
|
||||||
|
|
||||||
Coverage is strong and behavior-pinned:
|
|
||||||
- **householdTimezone.test.ts** covers all four `getHouseholdTimezone` branches (stored / TZ env / Intl / null-value fall-through) and `isValidIanaTimezone` valid+invalid incl. the `UTC` Pitfall-2 case.
|
|
||||||
- **admin.test.ts** covers access control (403) for GET/PUT, the PUT→GET round-trip, UTC acceptance, invalid-zone 400 + no-write assertion, seed-when-unset, and seed-no-overwrite (D-03).
|
|
||||||
|
|
||||||
Gaps (not blocking, recommend adding):
|
|
||||||
- No test for `POST /config/timezone/seed` returning the `seeded: boolean` flag value explicitly (only the stored value is asserted).
|
|
||||||
- No test asserting `POST seed` 403 for non-admins (GET and PUT are covered; seed is the same guard but untested).
|
|
||||||
- No test for the WR-01 UI dead-end (admin saving the displayed default to make it explicit) — add once WR-01 is fixed.
|
|
||||||
- Broker rewire tests (reminderScheduler/outboxWorker) were not re-read in full here; confirm they assert the stored value is actually threaded into `computeAlertInstantUtc` (not just that the accessor is called).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Reviewed: 2026-06-15T02:49:55Z_
|
|
||||||
_Reviewer: Claude (gsd-code-reviewer)_
|
_Reviewer: Claude (gsd-code-reviewer)_
|
||||||
_Depth: standard_
|
_Depth: standard_
|
||||||
|
|||||||
Reference in New Issue
Block a user