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 <noreply@anthropic.com>
This commit is contained in:
@@ -83,13 +83,16 @@ async function fetchItems(reset = false) {
|
|||||||
loading.value = 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) {
|
if (!reset && items.value.length < total.value) {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
const rect = sentinelEl.value?.getBoundingClientRect();
|
if (sentinelEl.value && cardGridEl.value) {
|
||||||
if (rect && rect.top < window.innerHeight + 200) {
|
const containerBottom = cardGridEl.value.getBoundingClientRect().bottom;
|
||||||
page.value++;
|
const sentinelTop = sentinelEl.value.getBoundingClientRect().top;
|
||||||
await fetchItems();
|
if (sentinelTop < containerBottom + 200) {
|
||||||
|
page.value++;
|
||||||
|
await fetchItems();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,8 +393,13 @@ watchEffect(() => {
|
|||||||
<p v-else class="empty-hint">Start by creating a note, saving a person or place, or making a list.</p>
|
<p v-else class="empty-hint">Start by creating a note, saving a person or place, or making a list.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card grid -->
|
<!-- Card grid (sentinel lives inside so IntersectionObserver root works) -->
|
||||||
<div v-else ref="cardGridEl" class="card-grid">
|
<div v-else ref="cardGridEl" class="card-grid">
|
||||||
|
<!-- Infinite scroll sentinel — must be inside the scrolling root -->
|
||||||
|
<div ref="sentinelEl" class="scroll-sentinel">
|
||||||
|
<span v-if="loading && items.length > 0" class="sentinel-loading">Loading…</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -456,11 +464,6 @@ watchEffect(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Infinite scroll sentinel -->
|
|
||||||
<div ref="sentinelEl" class="scroll-sentinel">
|
|
||||||
<span v-if="loading && items.length > 0" class="sentinel-loading">Loading…</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Graph panel -->
|
<!-- Graph panel -->
|
||||||
@@ -888,11 +891,12 @@ watchEffect(() => {
|
|||||||
|
|
||||||
/* ── Infinite scroll sentinel ────────────────────────────── */
|
/* ── Infinite scroll sentinel ────────────────────────────── */
|
||||||
.scroll-sentinel {
|
.scroll-sentinel {
|
||||||
|
grid-column: 1 / -1; /* span all grid columns */
|
||||||
|
order: 9999; /* push to end of grid items */
|
||||||
height: 40px;
|
height: 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
.sentinel-loading {
|
.sentinel-loading {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|||||||
@@ -666,17 +666,17 @@ _CORE_TOOLS = [
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "list_events",
|
"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": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"date_from": {
|
"date_from": {
|
||||||
"type": "string",
|
"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": {
|
"date_to": {
|
||||||
"type": "string",
|
"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"],
|
"required": ["date_from", "date_to"],
|
||||||
@@ -1453,10 +1453,14 @@ async def execute_tool(
|
|||||||
|
|
||||||
elif tool_name == "list_events":
|
elif tool_name == "list_events":
|
||||||
try:
|
try:
|
||||||
date_from = datetime.fromisoformat(arguments["date_from"])
|
date_from = datetime.fromisoformat(arguments["date_from"].replace("Z", "+00:00"))
|
||||||
date_to = datetime.fromisoformat(arguments["date_to"])
|
date_to = datetime.fromisoformat(arguments["date_to"].replace("Z", "+00:00"))
|
||||||
except (ValueError, TypeError, KeyError) as exc:
|
except (ValueError, TypeError, KeyError) as exc:
|
||||||
return {"success": False, "error": f"Invalid date range: {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(
|
events = await events_list_events(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
date_from=date_from,
|
date_from=date_from,
|
||||||
|
|||||||
Reference in New Issue
Block a user