From cb86c6192bab43b3807cb0835b8e156d927fa8ef Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:05:33 +0000 Subject: [PATCH 1/2] fix: robust session matching for recurring events with stale has_recurrence_rules When a session's event_json has stale or missing has_recurrence_rules, the key format mismatch between eventMatchingKey (trackingId:day) and sessionEventMatchingKey (trackingId) prevents both syncSessionEmbeddedEvents and syncParticipants from finding the session. This causes participants to never sync for recurring events where the session was created before has_recurrence_rules was correctly set. Fix: - Add fallback matching in findSessionByKey that compares tracking_id and day independently when the primary key match fails - Add fallback matching in syncSessionEmbeddedEvents using a secondary index by tracking_id to find incoming events when key format differs Co-Authored-By: john@hyprnote.com --- .../apple-calendar/process/events/execute.ts | 38 ++++++++++++++++++- apps/desktop/src/utils/session-event.ts | 25 ++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/services/apple-calendar/process/events/execute.ts b/apps/desktop/src/services/apple-calendar/process/events/execute.ts index c7d62b8df3..fcbfff6278 100644 --- a/apps/desktop/src/services/apple-calendar/process/events/execute.ts +++ b/apps/desktop/src/services/apple-calendar/process/events/execute.ts @@ -1,4 +1,5 @@ import type { EventStorage, SessionEvent } from "@hypr/store"; +import { format, safeParseDate, TZDate } from "@hypr/utils"; import { id } from "../../../../utils"; import { @@ -97,8 +98,13 @@ export function syncSessionEmbeddedEvents( timezone?: string, ): void { const incomingByKey = new Map(); + const incomingByTrackingId = new Map(); for (const event of incoming) { incomingByKey.set(eventMatchingKey(event, timezone), event); + const tid = event.tracking_id_event; + const arr = incomingByTrackingId.get(tid) ?? []; + arr.push(event); + incomingByTrackingId.set(tid, arr); } ctx.store.transaction(() => { @@ -107,7 +113,14 @@ export function syncSessionEmbeddedEvents( if (!sessionEvent) return; const key = sessionEventMatchingKey(sessionEvent, timezone); - const incomingEvent = incomingByKey.get(key); + let incomingEvent = incomingByKey.get(key); + if (!incomingEvent) { + incomingEvent = findIncomingByTrackingIdAndDay( + incomingByTrackingId, + sessionEvent, + timezone, + ); + } if (!incomingEvent) return; const calendarId = @@ -134,3 +147,26 @@ export function syncSessionEmbeddedEvents( }); }); } + +function dayFromDateLocal( + dateStr: string | null | undefined, + timezone?: string, +): string { + const parsed = safeParseDate(dateStr); + if (!parsed) return "1970-01-01"; + const date = timezone ? new TZDate(parsed, timezone) : parsed; + return format(date, "yyyy-MM-dd"); +} + +function findIncomingByTrackingIdAndDay( + incomingByTrackingId: Map, + sessionEvent: SessionEvent, + timezone?: string, +): IncomingEvent | undefined { + const candidates = incomingByTrackingId.get(sessionEvent.tracking_id); + if (!candidates) return undefined; + const sessionDay = dayFromDateLocal(sessionEvent.started_at, timezone); + return candidates.find( + (e) => dayFromDateLocal(e.started_at, timezone) === sessionDay, + ); +} diff --git a/apps/desktop/src/utils/session-event.ts b/apps/desktop/src/utils/session-event.ts index 1a3f3eb6df..3edbcd086c 100644 --- a/apps/desktop/src/utils/session-event.ts +++ b/apps/desktop/src/utils/session-event.ts @@ -73,11 +73,36 @@ export function findSessionByKey( if (!sessionEvent) return; if (sessionEventMatchingKey(sessionEvent, timezone) === key) { found = rowId; + return; + } + if (matchesWithFallback(sessionEvent, key, timezone)) { + found = rowId; } }); return found; } +function matchesWithFallback( + sessionEvent: SessionEvent, + targetKey: string, + timezone?: string, +): boolean { + const colonIdx = targetKey.indexOf(":"); + if (colonIdx === -1) { + const sessionDay = dayFromDate(sessionEvent.started_at, timezone); + const candidateKey = `${sessionEvent.tracking_id}:${sessionDay}`; + return candidateKey === targetKey; + } + + const targetTrackingId = targetKey.substring(0, colonIdx); + const targetDay = targetKey.substring(colonIdx + 1); + if (sessionEvent.tracking_id !== targetTrackingId) { + return false; + } + const sessionDay = dayFromDate(sessionEvent.started_at, timezone); + return sessionDay === targetDay; +} + export function findSessionByTrackingId( store: Store, trackingId: string, From 428dd15bb1ce20e8c415b4c5eeece7a8554ce403 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:17:44 +0000 Subject: [PATCH 2/2] fix: remove dead code branch in matchesWithFallback Co-Authored-By: john@hyprnote.com --- apps/desktop/src/utils/session-event.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/desktop/src/utils/session-event.ts b/apps/desktop/src/utils/session-event.ts index 3edbcd086c..55ef6949a9 100644 --- a/apps/desktop/src/utils/session-event.ts +++ b/apps/desktop/src/utils/session-event.ts @@ -89,9 +89,7 @@ function matchesWithFallback( ): boolean { const colonIdx = targetKey.indexOf(":"); if (colonIdx === -1) { - const sessionDay = dayFromDate(sessionEvent.started_at, timezone); - const candidateKey = `${sessionEvent.tracking_id}:${sessionDay}`; - return candidateKey === targetKey; + return false; } const targetTrackingId = targetKey.substring(0, colonIdx);