From d624d384127d4f259715bb7c69ca24dea64bda7f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 3 Apr 2026 19:28:33 -0400 Subject: [PATCH] fix: KnowledgeView infinite scroll root + calendar event refresh - KnowledgeView: IntersectionObserver was watching the viewport instead of the card-grid scroll container, causing infinite scroll to stop loading after only ~29 items. Pass card-grid element as `root`. - CalendarView: listen for 'fable:calendar-changed' custom event and call refetchEvents() so tool-created events appear without navigation. - ChatPanel: dispatch 'fable:calendar-changed' when create_event, update_event, or delete_event tool calls succeed. - tools.py: normalize naive datetimes to UTC before storing events so timezone comparisons in list_events queries are always consistent. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/ChatPanel.vue | 13 +++++++++++++ frontend/src/views/CalendarView.vue | 14 ++++++++++++-- frontend/src/views/KnowledgeView.vue | 9 +++++---- src/fabledassistant/services/tools.py | 6 +++++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ChatPanel.vue b/frontend/src/components/ChatPanel.vue index 6fc6d1b..3be41ef 100644 --- a/frontend/src/components/ChatPanel.vue +++ b/frontend/src/components/ChatPanel.vue @@ -76,6 +76,19 @@ function scrollToBottom() { watch(() => store.streamingContent, () => scrollToBottom()) watch(() => store.currentConversation?.messages.length, () => scrollToBottom()) +// Notify CalendarView when an event is created/updated/deleted via tool +const _calendarToolNames = new Set(['create_event', 'update_event', 'delete_event']) +const _seenCalendarToolIdx = new Set() +watch(() => store.streamingToolCalls, (calls) => { + calls.forEach((tc, i) => { + if (!_seenCalendarToolIdx.has(i) && _calendarToolNames.has(tc.function) && tc.status === 'success') { + _seenCalendarToolIdx.add(i) + document.dispatchEvent(new CustomEvent('fable:calendar-changed')) + } + }) +}, { deep: true }) +watch(() => store.streaming, (s) => { if (!s) _seenCalendarToolIdx.clear() }) + // ── RAG scope chip (full, non-briefing, non-workspace) ──────────────────────── const projects = ref<{ id: number; title: string }[]>([]) const scopeDropdownOpen = ref(false) diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue index ff72128..b003ba3 100644 --- a/frontend/src/views/CalendarView.vue +++ b/frontend/src/views/CalendarView.vue @@ -115,8 +115,18 @@ function onDocClick(e: MouseEvent) { } } -onMounted(() => document.addEventListener("mousedown", onDocClick)); -onUnmounted(() => document.removeEventListener("mousedown", onDocClick)); +function onCalendarChanged() { + calendarRef.value?.getApi().refetchEvents(); +} + +onMounted(() => { + document.addEventListener("mousedown", onDocClick); + document.addEventListener("fable:calendar-changed", onCalendarChanged); +}); +onUnmounted(() => { + document.removeEventListener("mousedown", onDocClick); + document.removeEventListener("fable:calendar-changed", onCalendarChanged); +}); // ── Calendar callbacks ───────────────────────────────────────────────────── async function loadEvents( diff --git a/frontend/src/views/KnowledgeView.vue b/frontend/src/views/KnowledgeView.vue index 9c59030..5786ac0 100644 --- a/frontend/src/views/KnowledgeView.vue +++ b/frontend/src/views/KnowledgeView.vue @@ -333,11 +333,12 @@ function formatDate(iso: string): string { // ─── Infinite scroll ────────────────────────────────────────────────────────── const sentinelEl = ref(null); +const cardGridEl = ref(null); let scrollObserver: IntersectionObserver | null = null; function setupScrollObserver() { if (scrollObserver) scrollObserver.disconnect(); - if (!sentinelEl.value) return; + if (!sentinelEl.value || !cardGridEl.value) return; scrollObserver = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && !loading.value && items.value.length < total.value) { @@ -345,7 +346,7 @@ function setupScrollObserver() { fetchItems(); } }, - { rootMargin: "200px" } + { root: cardGridEl.value, rootMargin: "200px" } ); scrollObserver.observe(sentinelEl.value); } @@ -368,7 +369,7 @@ onUnmounted(() => { }); watchEffect(() => { - if (sentinelEl.value) setupScrollObserver(); + if (sentinelEl.value && cardGridEl.value) setupScrollObserver(); }); @@ -471,7 +472,7 @@ watchEffect(() => { -
+