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:
@@ -270,8 +270,11 @@ async function openEventSlideOver(id: number | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function closeEventSlideOver() {
|
||||
function closeEventSlideOver(changed = false) {
|
||||
eventSlideOverOpen.value = false;
|
||||
if (changed) {
|
||||
document.dispatchEvent(new Event("fable:calendar-changed"));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -522,9 +525,9 @@ function closeEventSlideOver() {
|
||||
:event="eventSlideOverEntry"
|
||||
initial-date=""
|
||||
@close="closeEventSlideOver"
|
||||
@created="closeEventSlideOver"
|
||||
@updated="closeEventSlideOver"
|
||||
@deleted="closeEventSlideOver"
|
||||
@created="() => closeEventSlideOver(true)"
|
||||
@updated="() => closeEventSlideOver(true)"
|
||||
@deleted="() => closeEventSlideOver(true)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -77,9 +77,11 @@ function _dateRange() {
|
||||
const today = new Date()
|
||||
const nextWeek = new Date(today)
|
||||
nextWeek.setDate(today.getDate() + 7)
|
||||
// Use full ISO strings so the server sees the correct UTC equivalent of
|
||||
// local midnight / end-of-day rather than a naive UTC-midnight guess.
|
||||
return {
|
||||
todayStr: today.toISOString().slice(0, 10) + 'T00:00:00',
|
||||
nextWeekStr: nextWeek.toISOString().slice(0, 10) + 'T23:59:59',
|
||||
todayStr: today.toISOString(),
|
||||
nextWeekStr: nextWeek.toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,11 +110,7 @@ function _backgroundRefresh() {
|
||||
|
||||
onMounted(async () => {
|
||||
// Phase 1: projects list + cross-project recent items + orphaned items + events — all parallel
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().slice(0, 10) + "T00:00:00";
|
||||
const nextWeek = new Date(today);
|
||||
nextWeek.setDate(today.getDate() + 7);
|
||||
const nextWeekStr = nextWeek.toISOString().slice(0, 10) + "T23:59:59";
|
||||
const { todayStr, nextWeekStr } = _dateRange();
|
||||
|
||||
const [projectsRes, recentRes, orphanTasksRes, orphanNotesRes, eventsRes] =
|
||||
await Promise.allSettled([
|
||||
|
||||
@@ -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 */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user