diff --git a/packages/aris-source-caldav/src/caldav-source.test.ts b/packages/aris-source-caldav/src/caldav-source.test.ts index 57d19b7..130417e 100644 --- a/packages/aris-source-caldav/src/caldav-source.test.ts +++ b/packages/aris-source-caldav/src/caldav-source.test.ts @@ -479,4 +479,29 @@ describe("computeSignals", () => { }) expect(computeSignals(event2h1m, now).urgency).toBe(0.5) }) + + test("cancelled events get urgency 0.1 regardless of timing", () => { + const event = makeEvent({ + status: "cancelled", + startDate: new Date("2026-01-15T12:20:00Z"), // would be 0.9 if not cancelled + }) + const signals = computeSignals(event, now) + expect(signals.urgency).toBe(0.1) + expect(signals.timeRelevance).toBe(TimeRelevance.Ambient) + }) + + test("uses timezone for 'later today' boundary", () => { + // now = 2026-01-15T12:00:00Z = 2026-01-15T21:00:00 JST (UTC+9) + // event at 2026-01-15T15:30:00Z = 2026-01-16T00:30:00 JST — next day in JST + const event = makeEvent({ + startDate: new Date("2026-01-15T15:30:00Z"), + }) + + // Without timezone: UTC day ends at 2026-01-16T00:00:00Z, event is before that → "later today" + expect(computeSignals(event, now).urgency).toBe(0.5) + + // With Asia/Tokyo: local day ends at 2026-01-15T15:00:00Z (midnight Jan 16 JST), + // event is after that → "future days" + expect(computeSignals(event, now, "Asia/Tokyo").urgency).toBe(0.2) + }) }) diff --git a/packages/aris-source-caldav/src/caldav-source.ts b/packages/aris-source-caldav/src/caldav-source.ts index 23e9dfc..442db15 100644 --- a/packages/aris-source-caldav/src/caldav-source.ts +++ b/packages/aris-source-caldav/src/caldav-source.ts @@ -4,6 +4,7 @@ import { TimeRelevance, UnknownActionError } from "@aris/core" import { DAVClient } from "tsdav" import type { CalDavDAVClient, CalDavEventData, CalDavFeedItem } from "./types.ts" +import { CalDavEventStatus } from "./types.ts" import { CalDavCalendarKey, type CalendarContext } from "./calendar-context.ts" import { parseICalEvents } from "./ical-parser.ts" @@ -106,9 +107,10 @@ export class CalDavSource implements FeedSource { } const now = context.time - const inProgress = events.filter((e) => !e.isAllDay && e.startDate <= now && e.endDate > now) + const active = events.filter((e) => e.status !== CalDavEventStatus.Cancelled) + const inProgress = active.filter((e) => !e.isAllDay && e.startDate <= now && e.endDate > now) - const upcoming = events + const upcoming = active .filter((e) => !e.isAllDay && e.startDate > now) .sort((a, b) => a.startDate.getTime() - b.startDate.getTime()) @@ -125,7 +127,7 @@ export class CalDavSource implements FeedSource { async fetchItems(context: Context): Promise { const now = context.time const events = await this.fetchEvents(context) - return events.map((event) => createFeedItem(event, now)) + return events.map((event) => createFeedItem(event, now, this.timeZone)) } private fetchEvents(context: Context): Promise { @@ -290,7 +292,15 @@ function startOfDay(date: Date, timeZone?: string): Date { return new Date(Date.UTC(year, month - 1, day) - offsetMs) } -export function computeSignals(event: CalDavEventData, now: Date): FeedItemSignals { +export function computeSignals( + event: CalDavEventData, + now: Date, + timeZone?: string, +): FeedItemSignals { + if (event.status === CalDavEventStatus.Cancelled) { + return { urgency: 0.1, timeRelevance: TimeRelevance.Ambient } + } + if (event.isAllDay) { return { urgency: 0.3, timeRelevance: TimeRelevance.Ambient } } @@ -315,11 +325,9 @@ export function computeSignals(event: CalDavEventData, now: Date): FeedItemSigna return { urgency: 0.7, timeRelevance: TimeRelevance.Upcoming } } - // Later today - const startOfDay = new Date(now) - startOfDay.setUTCHours(0, 0, 0, 0) - const endOfDay = new Date(startOfDay) - endOfDay.setUTCDate(endOfDay.getUTCDate() + 1) + // Later today (using local day boundary when timeZone is set) + const todayStart = startOfDay(now, timeZone) + const endOfDay = new Date(todayStart.getTime() + 24 * 60 * 60 * 1000) if (event.startDate.getTime() < endOfDay.getTime()) { return { urgency: 0.5, timeRelevance: TimeRelevance.Upcoming } @@ -329,12 +337,12 @@ export function computeSignals(event: CalDavEventData, now: Date): FeedItemSigna return { urgency: 0.2, timeRelevance: TimeRelevance.Ambient } } -function createFeedItem(event: CalDavEventData, now: Date): CalDavFeedItem { +function createFeedItem(event: CalDavEventData, now: Date, timeZone?: string): CalDavFeedItem { return { id: `caldav-event-${event.uid}${event.recurrenceId ? `-${event.recurrenceId}` : ""}`, type: "caldav-event", timestamp: now, data: event, - signals: computeSignals(event, now), + signals: computeSignals(event, now, timeZone), } }