From 3887cab66e762c0b3033a90e81bbaf20cb50b3e3 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 3 Apr 2026 21:10:43 -0400 Subject: [PATCH] fix: knowledge infinite scroll + list_events timezone handling KnowledgeView: sentinel was OUTSIDE the card-grid div, making IntersectionObserver (root: cardGridEl) never fire since the target must be a descendant of the root. Moved sentinel inside card-grid with grid-column:1/-1 + order:9999 so it spans all columns and sits at the bottom. Fixed backup check to compare against container bounds not viewport. tools.py list_events: apply same UTC normalization as create_event (treat naive datetimes as UTC, handle Z suffix). Update tool description to explicitly request full-day UTC ranges so the LLM doesn't send local time without offsets, which caused the recall query to miss UTC-stored events. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/views/KnowledgeView.vue | 28 +++++++++++++++------------ src/fabledassistant/services/tools.py | 14 +++++++++----- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/frontend/src/views/KnowledgeView.vue b/frontend/src/views/KnowledgeView.vue index c4c6cb6..a2e865b 100644 --- a/frontend/src/views/KnowledgeView.vue +++ b/frontend/src/views/KnowledgeView.vue @@ -83,13 +83,16 @@ async function fetchItems(reset = false) { loading.value = false; } - // If the sentinel is still in the viewport after appending, keep loading + // If the sentinel is still visible in the card-grid after appending, keep loading if (!reset && items.value.length < total.value) { await nextTick(); - const rect = sentinelEl.value?.getBoundingClientRect(); - if (rect && rect.top < window.innerHeight + 200) { - page.value++; - await fetchItems(); + if (sentinelEl.value && cardGridEl.value) { + const containerBottom = cardGridEl.value.getBoundingClientRect().bottom; + const sentinelTop = sentinelEl.value.getBoundingClientRect().top; + if (sentinelTop < containerBottom + 200) { + page.value++; + await fetchItems(); + } } } } @@ -390,8 +393,13 @@ watchEffect(() => {

Start by creating a note, saving a person or place, or making a list.

- +
+ +
+ Loading… +
+
{
- - -
- Loading… -
@@ -888,11 +891,12 @@ watchEffect(() => { /* ── Infinite scroll sentinel ────────────────────────────── */ .scroll-sentinel { + grid-column: 1 / -1; /* span all grid columns */ + order: 9999; /* push to end of grid items */ height: 40px; display: flex; align-items: center; justify-content: center; - flex-shrink: 0; } .sentinel-loading { font-size: 0.8rem; diff --git a/src/fabledassistant/services/tools.py b/src/fabledassistant/services/tools.py index bf7f718..a351676 100644 --- a/src/fabledassistant/services/tools.py +++ b/src/fabledassistant/services/tools.py @@ -666,17 +666,17 @@ _CORE_TOOLS = [ "type": "function", "function": { "name": "list_events", - "description": "List calendar events in a date range. Use this when the user asks what events or meetings they have.", + "description": "List calendar events in a date range. Use this when the user asks what events or meetings they have. Always use full-day UTC ranges: date_from at T00:00:00Z and date_to at T23:59:59Z for the days of interest so events stored in UTC are not missed.", "parameters": { "type": "object", "properties": { "date_from": { "type": "string", - "description": "Start of range in ISO 8601 format (e.g. 2025-01-15T00:00:00)", + "description": "Start of range in ISO 8601 UTC format (e.g. 2025-01-15T00:00:00Z). Use T00:00:00Z for the start of the day.", }, "date_to": { "type": "string", - "description": "End of range in ISO 8601 format (e.g. 2025-01-22T23:59:59)", + "description": "End of range in ISO 8601 UTC format (e.g. 2025-01-22T23:59:59Z). Use T23:59:59Z for the end of the day.", }, }, "required": ["date_from", "date_to"], @@ -1453,10 +1453,14 @@ async def execute_tool( elif tool_name == "list_events": try: - date_from = datetime.fromisoformat(arguments["date_from"]) - date_to = datetime.fromisoformat(arguments["date_to"]) + date_from = datetime.fromisoformat(arguments["date_from"].replace("Z", "+00:00")) + date_to = datetime.fromisoformat(arguments["date_to"].replace("Z", "+00:00")) except (ValueError, TypeError, KeyError) as exc: return {"success": False, "error": f"Invalid date range: {exc}"} + if date_from.tzinfo is None: + date_from = date_from.replace(tzinfo=timezone.utc) + if date_to.tzinfo is None: + date_to = date_to.replace(tzinfo=timezone.utc) events = await events_list_events( user_id=user_id, date_from=date_from,