mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
* feat(meetings): inline meeting prep under next meeting Resolve a meeting's attendees against the knowledge base and render each attendee's existing person.md inline beneath the next upcoming meeting in the Meetings view. Deterministic (email-exact match, then unambiguous name/alias), no LLM in the hot path. - core: resolveMeetingPrep() + meeting-prep:resolve IPC - renderer: notes-first rows with expandable person.md; unmatched attendees collapse into a 'no notes yet' row with a Create note action that hands off to the Copilot - prep re-resolves when a People note changes * feat(meetings): per-meeting prep toggle Add a Prep toggle to every eligible meeting row, not just the next one. The next meeting still auto-expands; other meetings resolve their attendee notes lazily when their toggle is opened. All-day and solo events get no toggle. * feat(meetings): org matching, cached index, and field-bleed fix #2 Matching: resolve the meeting's external companies from attendee email domains (and matched people's Organization field), excluding the user's own domain, and surface those org notes in a Companies section. Normalize display names (strip '(via ...)' suffixes) before name/alias matching. #3 Perf: meeting prep reads a cached knowledge index instead of rescanning the whole knowledge dir on every resolve; invalidated whenever a file under knowledge/ changes (wired to the workspace watcher). Fix: extractField in the knowledge index consumed newlines after the label, so an empty field (e.g. **Role:**) bled the next line's value. Restrict it to spaces/tabs so empty fields resolve to undefined. Also strip the folder prefix from link targets for display (Organizations/Rowboat Labs -> Rowboat Labs). * feat(meetings): proactive prep notes generated 6h ahead Generate a meeting prep note ~6h before each meeting and surface its brief in the prep card. - Generator (meeting_prep_brief.ts): assembles roster + 'last time' recap (extracts the prior instance's Action items) + agenda, ordered by meeting type (recurring -> recap first, one-off -> agenda first), then one model call for a 'what matters' brief. Writes knowledge/Meetings/prep/<slug>-<date>.md with eventId/recurringEventId frontmatter. Reuses the summarizeMeeting LLM path (configured model, useCase: meeting_prep). - Scheduler (meeting_prep_scheduler.ts): calendar-aware tick (5m), generates prep within the 6h lead window, state file dedupes, self-heals on changes. - Card: shows the brief (bulleted, compact) above a People list whose rows link out to each person's note instead of rendering it inline. Generated prep notes are kept out of the past-notes table. * chore(meetings): poll prep scheduler every 15m instead of 5m
5.9 KiB
5.9 KiB
Meeting Prep — "Next up" prep card
Goal
When a meeting is imminent, surface who will be there by pulling up each
attendee's existing person.md from the knowledge base and showing it — no
click, no LLM in the hot path. An ambient card that's just ready when you
look.
This is a deliberately light, deterministic surface. It does not replace the two existing meeting-prep paths:
- the chat skill (
assistant/skills/meeting-prep/) — LLM brief on demand - the pre-built automation (
pre_built/meeting-prep.md) — LLM brief files
It complements them with an instant, no-LLM "who's in this meeting" view.
Decisions (settled)
| Axis | Choice |
|---|---|
| Surface | Ambient card, no notification. Pinned at top of Meetings view. |
| Activation | Next eligible meeting is ≤ 30 min away. Outside that → no card. |
| Depth | Just show person.md — render the note, no LLM synthesis. |
| Who | Everyone incl. teammates. Skip self / all-day / declined / cancelled. |
| Sparse meetings | Notes-first: attendees with notes shown prominently; the rest collapse into "N others — no notes yet" → expand to create. |
| No-note action | Offer to create a note for that attendee. |
Resolution rules
For each attendee (excluding self):
- Email exact match (case-insensitive) against
KnowledgeIndex.people[].email— reliable. - Else name/alias match on
displayName→people[].name/people[].aliases, only when it's a single unambiguous hit. - Matched → read
person.md, return{ name, role, org, path, markdown }. - Unmatched → returned as a "no note" attendee (name + email).
Org notes (Organizations/{Org}.md) are deferred — person.md already
carries Org/Role inline, enough for v1.
Architecture
| Piece | Location | Notes |
|---|---|---|
| Attendee → note resolver | new IPC meeting-prep:resolve in apps/main/src/ipc.ts |
Input: attendee list ({email?, displayName?, self?}[]). Builds/uses buildKnowledgeIndex(), resolves per rules above, reads matched notes, returns structured payload. Deterministic + fast. |
| Shared types | packages/shared/src/ |
MeetingPrepAttendee, MeetingPrepResult request/response shapes + IPC channel typing. |
| "Next up" card UI | new component rendered above UpcomingEvents in meetings-view.tsx |
Picks next event ≤30 min out, calls IPC, renders notes-first + collapsed others. Reuses existing markdown rendering. |
| Create-note action | reuse the note-creation agent (only LLM touch, user-initiated) | v1: open a prefilled Copilot chat to create the note (visible/steerable) rather than firing silently. |
No new background loop — the renderer already polls calendar_sync/ every
minute, so the card stays current for free.
Build slices
- Resolver IPC + shared types —
meeting-prep:resolve, wired through preload. Unit-testable in isolation. ← first slice - "Next up" card UI — component + 30-min gating + notes-first / collapsed rendering, reading from the IPC.
- Create-note action — prefilled Copilot chat for an unmatched attendee.
Out of scope (fast-follow candidates)
- Notification variant (extend
notify_calendar_meetings.tstick). - Org-note surfacing.
- Configurable lead time (hardcode 30 min for v1).
- LLM-synthesized brief button on the card.
Phase 2 — Proactive, meeting-aware prep (6h ahead)
Shift from a static contact viewer to meeting-aware prep, generated ahead of time.
Decisions
- Trigger: a calendar-driven tick (clone of
notify_calendar_meetings.ts), NOT the cron/bg-task engine — fire time isstart − 6h, which varies per event, so a calendar scan + state file is the right fit (self-healing on reschedule/cancel). - Storage: write a real note →
knowledge/Meetings/prep/<slug>-<date>.md(openable/editable). - Notify: none — silent background generation; appears in the Meetings view.
- Adaptive by meeting type:
- Recurring (
recurringEventId+ prior notes found) → lead with "Last time" recap (prior instance's## Action items/ summary), then roster. - One-off / new → lead with agenda (event
description), then roster + company.
- Recurring (
- Roster: always list every attendee; ones with a note link to it, ones without get Create note.
- AI brief: the only LLM step. Reuses the
summarizeMeetingpath exactly —generateTextagainst the user's configured model (getMeetingNotesModel/getDefaultModelAndProvider), taggeduseCase: 'meeting_prep'. Deterministic parts (roster, recap, agenda) are assembled in code; the model only writes the "what matters" summary.
Build slices
- A. Core generator (
meeting_prep_brief.ts): assemble context (roster + recap + agenda, meeting-type aware) →generatePrepBrief()(one model call) → render +writePrepNote()toknowledge/Meetings/prep/. Frontmatter stampseventId/recurringEventId/meetingDate/generatedAt. - B. Scheduler (
meeting_prep_scheduler.ts): tick (~few min), scancalendar_sync/, for events starting within 6h, not ended, ≥1 non-self attendee, not already prepped (state file keyed by eventId) → generate + write.init()wired inmain.tsbesideinitCalendarNotifications(). - C. Card integration: the inline prep card prefers the generated prep note when it exists (instant), else falls back to the live deterministic resolve.
Open detail / enabler
- Notes don't store an event id today, so recap linking is a title/date heuristic.
Stamp
eventId/recurringEventIdinto captured notes' frontmatter going forward so future links are exact.
Verification
- Resolver: feed known attendee emails → correct note match; unknown → no-note.
- Card: appears only inside the 30-min window; notes-first ordering; collapse toggles; create action opens prefilled chat.
npm run deps && npm run lintclean.