feat: event cards in chat/briefing, upcoming events on dashboard, briefing uses internal store

- ToolCallCard: event list items replaced with rich clickable cards (color dot,
  title, time, location); clicking opens EventSlideOver for edit/delete; single
  create/update events in header are also clickable; updated all event types to
  use start_dt/end_dt fields from internal store
- HomeView: new upcoming events widget shows today + next 7 days as a card grid
  above the hero project; clicking any card opens EventSlideOver inline
- briefing_pipeline: _gather_internal now queries the internal events store for
  today's events; CalDAV events are still appended (deduped) if configured

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 15:39:13 -04:00
parent df1194ee96
commit e0c3836fab
3 changed files with 348 additions and 25 deletions
@@ -162,17 +162,36 @@ async def _gather_internal(user_id: int) -> dict:
logger.warning("Failed to gather tasks for briefing", exc_info=True)
overdue, due_today, high_priority = [], [], []
# Calendar events today
calendar_events = []
# Calendar events today — internal store
calendar_events: list[str] = []
try:
from fabledassistant.services.events import list_events as list_internal_events
today_date = date.today()
day_start = datetime(today_date.year, today_date.month, today_date.day, 0, 0, 0)
day_end = datetime(today_date.year, today_date.month, today_date.day, 23, 59, 59)
internal_events = await list_internal_events(
user_id=user_id, date_from=day_start, date_to=day_end
)
for e in internal_events:
if e.all_day:
time_str = "all day"
elif e.start_dt:
time_str = e.start_dt.strftime("%-I:%M %p")
else:
time_str = "unknown time"
calendar_events.append(f"{e.title} at {time_str}")
except Exception:
logger.warning("Failed to gather internal calendar events for briefing", exc_info=True)
# Also pull CalDAV events (deduped)
try:
if await is_caldav_configured(user_id):
events = await list_events(user_id, start=today, end=today)
calendar_events = [
f"{e.get('summary', 'Event')} at {e.get('dtstart', 'unknown time')}"
for e in (events or [])
]
caldav_evs = await list_events(user_id, start=today, end=today)
for e in (caldav_evs or []):
summary = f"{e.get('summary', 'Event')} at {e.get('dtstart', 'unknown time')}"
if summary not in calendar_events:
calendar_events.append(summary)
except Exception:
logger.warning("Failed to gather calendar events for briefing", exc_info=True)
logger.warning("Failed to gather CalDAV calendar events for briefing", exc_info=True)
# Projects: active projects
projects_summary = []