From 8c433d60cfb4cf4069666e59f463b344ab9c7b24 Mon Sep 17 00:00:00 2001 From: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:51:53 +0200 Subject: [PATCH] fix(client-runtime): silence globalDate diagnostics in recency groups #121's thread recency grouping uses calendar Date APIs intentionally; Effect's globalDate diagnostic was failing typecheck on the new helpers and tests. --- .../src/state/threadRecencyGroups.test.ts | 28 +++++++++++++++---- .../src/state/threadRecencyGroups.ts | 23 ++++++++++++--- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/client-runtime/src/state/threadRecencyGroups.test.ts b/packages/client-runtime/src/state/threadRecencyGroups.test.ts index edc79f91f45..5f208476432 100644 --- a/packages/client-runtime/src/state/threadRecencyGroups.test.ts +++ b/packages/client-runtime/src/state/threadRecencyGroups.test.ts @@ -8,9 +8,27 @@ import { THREAD_RECENCY_BUCKET_LABELS, } from "./threadRecencyGroups.ts"; +/** Local calendar fixture; Date APIs are intentional for bucket tests. */ +function localDate( + year: number, + monthIndex: number, + day: number, + hours = 0, + minutes = 0, + seconds = 0, +): Date { + // @effect-diagnostics-next-line globalDate:off + return new Date(year, monthIndex, day, hours, minutes, seconds); +} + +function addMs(date: Date, ms: number): Date { + // @effect-diagnostics-next-line globalDate:off + return new Date(date.getTime() + ms); +} + describe("getThreadRecencyBucketId", () => { // Fixed local morning so calendar math is stable across CI timezones. - const now = new Date(2026, 2, 15, 14, 30, 0); // 2026-03-15 local + const now = localDate(2026, 2, 15, 14, 30, 0); // 2026-03-15 local it("classifies today, yesterday, previous 7, previous 30, and older", () => { const startToday = startOfLocalDay(now).getTime(); @@ -31,7 +49,7 @@ describe("getThreadRecencyBucketId", () => { }); describe("groupThreadsByRecency", () => { - const now = new Date(2026, 2, 15, 12, 0, 0); + const now = localDate(2026, 2, 15, 12, 0, 0); const startToday = startOfLocalDay(now).getTime(); it("returns only non-empty buckets in order with labels", () => { @@ -61,10 +79,10 @@ describe("groupThreadsByRecency", () => { describe("groupSortedThreadsByRecency", () => { it("groups using activity timestamps from ThreadSortInput", () => { - const now = new Date(2026, 2, 15, 12, 0, 0); + const now = localDate(2026, 2, 15, 12, 0, 0); const startToday = startOfLocalDay(now); - const todayIso = new Date(startToday.getTime() + 3_600_000).toISOString(); - const olderIso = new Date(startToday.getTime() - 40 * 24 * 60 * 60 * 1000).toISOString(); + const todayIso = addMs(startToday, 3_600_000).toISOString(); + const olderIso = addMs(startToday, -40 * 24 * 60 * 60 * 1000).toISOString(); const groups = groupSortedThreadsByRecency( [ diff --git a/packages/client-runtime/src/state/threadRecencyGroups.ts b/packages/client-runtime/src/state/threadRecencyGroups.ts index f4d72fbbb88..d6a8037b77b 100644 --- a/packages/client-runtime/src/state/threadRecencyGroups.ts +++ b/packages/client-runtime/src/state/threadRecencyGroups.ts @@ -29,8 +29,23 @@ export const THREAD_RECENCY_BUCKET_LABELS: Record const MS_PER_DAY = 24 * 60 * 60 * 1000; +function makeDateFromEpochMs(ms: number): Date { + // @effect-diagnostics-next-line globalDate:off + return new Date(ms); +} + +function makeLocalDate(year: number, monthIndex: number, day: number): Date { + // @effect-diagnostics-next-line globalDate:off + return new Date(year, monthIndex, day); +} + +function makeNow(): Date { + // @effect-diagnostics-next-line globalDate:off + return new Date(); +} + export function startOfLocalDay(date: Date): Date { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + return makeLocalDate(date.getFullYear(), date.getMonth(), date.getDate()); } /** @@ -39,7 +54,7 @@ export function startOfLocalDay(date: Date): Date { */ export function getThreadRecencyBucketId( timestampMs: number, - now: Date = new Date(), + now: Date = makeNow(), ): ThreadRecencyBucketId { if (!Number.isFinite(timestampMs)) { return "older"; @@ -81,7 +96,7 @@ export interface ThreadRecencyGroup { export function groupThreadsByRecency( threads: readonly T[], getTimestampMs: (thread: T) => number, - now: Date = new Date(), + now: Date = makeNow(), ): ReadonlyArray> { const buckets = new Map(); for (const id of THREAD_RECENCY_BUCKET_ORDER) { @@ -112,7 +127,7 @@ export function groupThreadsByRecency( */ export function groupSortedThreadsByRecency( threads: readonly T[], - now: Date = new Date(), + now: Date = makeNow(), ): ReadonlyArray> { return groupThreadsByRecency( threads,