diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 3e8634a0..6001f48b 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -54,6 +54,7 @@ import { Button } from "@/components/ui/button" import { Toaster } from "@/components/ui/sonner" import { stripKnowledgePrefix, toKnowledgePath, wikiLabel } from '@/lib/wiki-links' import { splitFrontmatter, joinFrontmatter } from '@/lib/frontmatter' +import { extractConferenceLink } from '@/lib/calendar-event' import { OnboardingModal } from '@/components/onboarding' import { CommandPalette, type CommandPaletteContext, type CommandPaletteMention } from '@/components/search-dialog' import { TrackModal } from '@/components/track-modal' @@ -3120,10 +3121,7 @@ function App() { conferenceData?: { entryPoints?: Array<{ entryPointType?: string; uri?: string }> } } if (!e || typeof e !== 'object') return - // Order matches extractConferenceLink in calendar-block.tsx: - // entryPoints first (covers Zoom/integrated providers), then hangoutLink (Google Meet shortcut). - const conferenceLink = e.conferenceData?.entryPoints?.find(p => p.entryPointType === 'video')?.uri - || e.hangoutLink + const conferenceLink = extractConferenceLink(e as Record) if (openMeeting && conferenceLink) { window.open(conferenceLink, '_blank') } else if (openMeeting) { diff --git a/apps/x/apps/renderer/src/extensions/calendar-block.tsx b/apps/x/apps/renderer/src/extensions/calendar-block.tsx index 9f0eec02..ecc5403d 100644 --- a/apps/x/apps/renderer/src/extensions/calendar-block.tsx +++ b/apps/x/apps/renderer/src/extensions/calendar-block.tsx @@ -3,6 +3,7 @@ import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react' import { X, Calendar, Video, ChevronDown, Mic } from 'lucide-react' import { blocks } from '@x/shared' import { useState, useEffect, useRef } from 'react' +import { extractConferenceLink } from '../lib/calendar-event' function formatTime(dateStr: string): string { const d = new Date(dateStr) @@ -40,25 +41,6 @@ function getTimeRange(event: blocks.CalendarEvent): string { return `${startTime} \u2013 ${endTime}` } -/** - * Extract a video conference link from raw Google Calendar event JSON. - * Checks conferenceData.entryPoints (video type), hangoutLink, then falls back - * to conferenceLink if already set. - */ -function extractConferenceLink(raw: Record): string | undefined { - // Check conferenceData.entryPoints for video entry - const confData = raw.conferenceData as { entryPoints?: { entryPointType?: string; uri?: string }[] } | undefined - if (confData?.entryPoints) { - const video = confData.entryPoints.find(ep => ep.entryPointType === 'video') - if (video?.uri) return video.uri - } - // Check hangoutLink (Google Meet shortcut) - if (typeof raw.hangoutLink === 'string') return raw.hangoutLink - // Fall back to conferenceLink if present - if (typeof raw.conferenceLink === 'string') return raw.conferenceLink - return undefined -} - interface ResolvedEvent { event: blocks.CalendarEvent loaded: blocks.CalendarEvent | null diff --git a/apps/x/apps/renderer/src/lib/calendar-event.ts b/apps/x/apps/renderer/src/lib/calendar-event.ts new file mode 100644 index 00000000..b7ace75a --- /dev/null +++ b/apps/x/apps/renderer/src/lib/calendar-event.ts @@ -0,0 +1,15 @@ +/** + * Extract a video conference link from raw Google Calendar event JSON. + * Checks conferenceData.entryPoints (video type), hangoutLink, then falls back + * to a top-level conferenceLink if present. + */ +export function extractConferenceLink(raw: Record): string | undefined { + const confData = raw.conferenceData as { entryPoints?: { entryPointType?: string; uri?: string }[] } | undefined + if (confData?.entryPoints) { + const video = confData.entryPoints.find(ep => ep.entryPointType === 'video') + if (video?.uri) return video.uri + } + if (typeof raw.hangoutLink === 'string') return raw.hangoutLink + if (typeof raw.conferenceLink === 'string') return raw.conferenceLink + return undefined +} diff --git a/apps/x/packages/core/src/knowledge/notify_calendar_meetings.ts b/apps/x/packages/core/src/knowledge/notify_calendar_meetings.ts index 5e3aa68b..cca9d230 100644 --- a/apps/x/packages/core/src/knowledge/notify_calendar_meetings.ts +++ b/apps/x/packages/core/src/knowledge/notify_calendar_meetings.ts @@ -48,7 +48,12 @@ async function loadState(): Promise { } async function saveState(state: NotificationState): Promise { - await fs.writeFile(STATE_FILE, JSON.stringify(state, null, 2), "utf-8"); + // Write to a sibling tmp file then rename so a mid-write crash can't leave + // the JSON corrupt — a corrupt state file would make every event in the + // 90s notify window re-fire on next start. + const tmp = `${STATE_FILE}.tmp`; + await fs.writeFile(tmp, JSON.stringify(state, null, 2), "utf-8"); + await fs.rename(tmp, STATE_FILE); } function gcState(state: NotificationState): NotificationState {