From 7aa8416c753dc0b1104f8fa1e7688d22dbcfb763 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 22 Jul 2026 17:03:34 -0500 Subject: [PATCH] =?UTF-8?q?feat(3c):=20surface=20the=20schedule's=20timezo?= =?UTF-8?q?ne=20on=20the=20task=20read=20path=20(chat#1881=203c=C2=B7api-r?= =?UTF-8?q?ead)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit enrichTasks now reads the timezone back from the Trigger.dev schedule (retrieveScheduleTimezone) alongside the runs, and exposes it as `timezone` on each task in GET /api/tasks. The schedule is the source of truth (no stored column), so this lets the chat edit UI prefill the current timezone. Null when the task has no schedule or Trigger.dev is unavailable. enrichTasks tests updated. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/tasks/__tests__/enrichTasks.test.ts | 22 ++++++++++++++++++---- lib/tasks/enrichTasks.ts | 20 ++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/lib/tasks/__tests__/enrichTasks.test.ts b/lib/tasks/__tests__/enrichTasks.test.ts index 785204022..5ba06b330 100644 --- a/lib/tasks/__tests__/enrichTasks.test.ts +++ b/lib/tasks/__tests__/enrichTasks.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { enrichTasks } from "../enrichTasks"; import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns"; import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; +import { retrieveScheduleTimezone } from "@/lib/trigger/retrieveScheduleTimezone"; import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; vi.mock("@/lib/trigger/fetchTriggerRuns", () => ({ @@ -12,6 +13,10 @@ vi.mock("@/lib/trigger/retrieveTaskRun", () => ({ retrieveTaskRun: vi.fn(), })); +vi.mock("@/lib/trigger/retrieveScheduleTimezone", () => ({ + retrieveScheduleTimezone: vi.fn(), +})); + vi.mock("@/lib/supabase/account_emails/selectAccountEmails", () => ({ default: vi.fn(), })); @@ -44,10 +49,12 @@ const mockRun = { describe("enrichTasks", () => { beforeEach(() => { vi.clearAllMocks(); + vi.mocked(retrieveScheduleTimezone).mockResolvedValue(undefined); }); - it("returns recent_runs, upcoming, and owner_email", async () => { + it("returns recent_runs, upcoming, owner_email, and the schedule's timezone", async () => { vi.mocked(fetchTriggerRuns).mockResolvedValue([mockRun] as never); + vi.mocked(retrieveScheduleTimezone).mockResolvedValue("America/New_York"); vi.mocked(retrieveTaskRun).mockResolvedValue({ ...mockRun, payload: { @@ -71,13 +78,15 @@ describe("enrichTasks", () => { recent_runs: [mockRun], upcoming: ["2026-03-27T09:00:00Z", "2026-04-03T09:00:00Z"], owner_email: "owner@example.com", + timezone: "America/New_York", }, ]); expect(fetchTriggerRuns).toHaveBeenCalledWith({ "filter[schedule]": "sched_abc" }, 5); + expect(retrieveScheduleTimezone).toHaveBeenCalledWith("sched_abc"); expect(selectAccountEmails).toHaveBeenCalledWith({ accountIds: ["account-456"] }); }); - it("returns empty trigger fields and null owner_email when no schedule or email exists", async () => { + it("returns empty trigger fields, null owner_email, and null timezone when no schedule exists", async () => { vi.mocked(selectAccountEmails).mockResolvedValue([]); const result = await enrichTasks([{ ...mockTask, trigger_schedule_id: null }]); @@ -89,12 +98,14 @@ describe("enrichTasks", () => { recent_runs: [], upcoming: [], owner_email: null, + timezone: null, }, ]); expect(fetchTriggerRuns).not.toHaveBeenCalled(); + expect(retrieveScheduleTimezone).not.toHaveBeenCalled(); }); - it("returns empty trigger enrichment when Trigger.dev fails", async () => { + it("returns empty enrichment (timezone null) when Trigger.dev fails", async () => { vi.mocked(fetchTriggerRuns).mockRejectedValue(new Error("API error")); vi.mocked(selectAccountEmails).mockResolvedValue([]); @@ -106,12 +117,14 @@ describe("enrichTasks", () => { recent_runs: [], upcoming: [], owner_email: null, + timezone: null, }, ]); }); - it("returns empty upcoming when no runs exist", async () => { + it("returns empty upcoming but still the timezone when no runs exist", async () => { vi.mocked(fetchTriggerRuns).mockResolvedValue([] as never); + vi.mocked(retrieveScheduleTimezone).mockResolvedValue("UTC"); vi.mocked(selectAccountEmails).mockResolvedValue([]); const result = await enrichTasks([mockTask]); @@ -122,6 +135,7 @@ describe("enrichTasks", () => { recent_runs: [], upcoming: [], owner_email: null, + timezone: "UTC", }, ]); expect(retrieveTaskRun).not.toHaveBeenCalled(); diff --git a/lib/tasks/enrichTasks.ts b/lib/tasks/enrichTasks.ts index e74df5506..b8eece49f 100644 --- a/lib/tasks/enrichTasks.ts +++ b/lib/tasks/enrichTasks.ts @@ -1,6 +1,7 @@ import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; import { fetchTriggerRuns, type TriggerRun } from "@/lib/trigger/fetchTriggerRuns"; import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; +import { retrieveScheduleTimezone } from "@/lib/trigger/retrieveScheduleTimezone"; import type { Tables } from "@/types/database.types"; type ScheduledAction = Tables<"scheduled_actions">; @@ -9,11 +10,14 @@ export type EnrichedTask = ScheduledAction & { recent_runs: TriggerRun[]; upcoming: string[]; owner_email: string | null; + /** IANA timezone read from the Trigger.dev schedule (source of truth); null when unavailable. */ + timezone: string | null; }; interface TriggerInfo { recent_runs: TriggerRun[]; upcoming: string[]; + timezone: string | null; } type TriggerInfoEntry = readonly [string, TriggerInfo]; @@ -30,11 +34,16 @@ export async function enrichTasks(tasks: ScheduledAction[]): Promise