From e76b0826289625818dbe93d77fe4a459f6031a66 Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Fri, 29 May 2026 22:28:33 +0200 Subject: [PATCH 1/2] Set @id on calendar events from EKEvent.eventIdentifier Without an identifier, Event values produced from EKEvent encoded as JSON-LD blank nodes. Two events with the same name/dates/calendar serialised to byte-identical objects, and downstream JSON-LD-aware consumers collapsed them into a single node. Symptom: events_fetch appeared to silently filter out duplicate-named events. Populate Event.identifier from EKEvent.eventIdentifier in both events_fetch and events_create so each event carries a stable @id, matching what Ontology already does for Person (CNContact.identifier) and ItemList (EKCalendar.calendarIdentifier). --- App/Services/Calendar.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/App/Services/Calendar.swift b/App/Services/Calendar.swift index 1a58890d..161c084a 100644 --- a/App/Services/Calendar.swift +++ b/App/Services/Calendar.swift @@ -216,7 +216,11 @@ final class CalendarService: Service { events = events.filter { ($0.hasRecurrenceRules) == isRecurring } } - return events.map { Event($0) } + return events.map { event -> Event in + var e = Event(event) + e.identifier = event.eventIdentifier + return e + } } Tool( name: "events_create", @@ -533,7 +537,9 @@ final class CalendarService: Service { // Save the event try self.eventStore.save(event, span: .thisEvent) - return Event(event) + var result = Event(event) + result.identifier = event.eventIdentifier + return result } } } From b936821174326989e1e9d25711a45082e259eaf6 Mon Sep 17 00:00:00 2001 From: Ole Lilienthal Date: Fri, 29 May 2026 23:46:47 +0200 Subject: [PATCH 2/2] Fix date-only inputs being misread as having a timezone suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISO8601DateFormatter.lenientDate(fromISO8601String:) ran a regex to decide whether the input already carried a timezone offset and thus should not be re-parsed in local time. The regex #"([Zz]|[+-]\\d{2}(:?\\d{2})?)$"# matches the trailing -DD of any bare yyyy-MM-dd date — "2026-06-15" looks like it ends with a -15 offset — so date-only inputs returned nil instead of falling through to the "yyyy-MM-dd" DateFormatter fallback. In events_fetch this manifested as a silent regression: when the caller passed e.g. start="2026-06-02", end="2026-06-15", parsing failed for both, hasStart/hasEnd stayed false, and the tool fell back to its 'now → now + 1 week' default range. The user saw events from the current week and assumed events past that point had been filtered out (in this case two same-name events where only the earlier one was in the default range). Gate the timezone-suffix check on dateString.count > 10 so it can't fire on a bare yyyy-MM-dd. The day component can no longer collide with the offset branch of the regex; date-only inputs now reach the "yyyy-MM-dd" fallback as intended. --- App/Extensions/Foundation+Extensions.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/App/Extensions/Foundation+Extensions.swift b/App/Extensions/Foundation+Extensions.swift index 53f3121b..2989b823 100644 --- a/App/Extensions/Foundation+Extensions.swift +++ b/App/Extensions/Foundation+Extensions.swift @@ -36,9 +36,15 @@ extension ISO8601DateFormatter { } } - // If the string already includes a timezone, don't guess with local-time parsing. + // If the string already includes a timezone suffix on a *time* component, + // don't guess with local-time parsing. We must NOT run this check on a + // bare `yyyy-MM-dd` because the trailing `-DD` would match the + // `[+-]\d{2}` branch of this regex and falsely classify the day as a + // timezone offset, causing the parser to bail before trying the + // `yyyy-MM-dd` fallback below. let hasTimeZoneInfo = - dateString.range( + dateString.count > 10 + && dateString.range( of: #"([Zz]|[+-]\d{2}(:?\d{2})?)$"#, options: .regularExpression ) != nil