fix(events): audit pass — 7 correctness fixes across the events system

Backend:
- tools.py: apply UTC normalization to update_event datetime fields
  (matched create_event which already did this)
- events.py service: allow end_dt/recurrence/project_id to be cleared
  via update_event by permitting None for nullable fields
- events.py service: find_events_by_query now returns upcoming events
  first, falling back to past — prevents AI tools from mutating stale
  past events when a future match exists
- events.py service: list_events now uses overlap logic (start <= to
  AND end >= from) so multi-day events spanning the query boundary
  are included; previously only start_dt was checked

Frontend:
- ToolCallCard: fire fable:calendar-changed on created/updated/deleted
  so CalendarView refetches without requiring a manual page refresh
- KnowledgeView: replace raw apiGet('/api/events') with listEvents()
  client function; also fix today bar which was reading .events off a
  flat array (always empty) — now correctly receives EventEntry[]
- HomeView: use full ISO strings for event date range instead of naive
  UTC-midnight strings; deduplicate inline date math via _dateRange()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:02:10 -04:00
parent 7677ab4028
commit 358534efbf
6 changed files with 63 additions and 31 deletions
+4 -6
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
import { useRouter } from "vue-router";
import { apiGet, apiPatch } from "@/api/client";
import { apiGet, apiPatch, listEvents } from "@/api/client";
import { fmtCompact } from "@/utils/dateFormat";
import { useChatStore } from "@/stores/chat";
import GraphView from "@/views/GraphView.vue";
@@ -167,15 +167,13 @@ async function fetchTodayBar() {
try {
const now = new Date();
const end = new Date(now.getTime() + 7 * 86_400_000);
const [evData, taskData] = await Promise.all([
apiGet<{ events: UpcomingEvent[] }>(
`/api/events?from=${now.toISOString().slice(0, 10)}&to=${end.toISOString().slice(0, 10)}`
).catch(() => ({ events: [] as UpcomingEvent[] })),
const [events, taskData] = await Promise.all([
listEvents(now.toISOString(), end.toISOString()).catch(() => [] as UpcomingEvent[]),
apiGet<{ total: number }>(
`/api/tasks?status=todo&status=in_progress&overdue=true&limit=1`
).catch(() => ({ total: 0 })),
]);
upcomingEvents.value = (evData.events ?? []).slice(0, 3);
upcomingEvents.value = events.slice(0, 3);
overdueCount.value = taskData.total ?? 0;
} catch { /* silent */ }
}