@agent-native/core 0.91.0 → 0.91.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/blueprints/action/crud.md +1 -1
  2. package/blueprints/channel/discord.md +1 -1
  3. package/blueprints/provider/stripe.md +1 -1
  4. package/blueprints/sandbox/docker.md +1 -1
  5. package/corpus/README.md +1 -1
  6. package/corpus/core/CHANGELOG.md +26 -0
  7. package/corpus/core/blueprints/action/crud.md +1 -1
  8. package/corpus/core/blueprints/channel/discord.md +1 -1
  9. package/corpus/core/blueprints/provider/stripe.md +1 -1
  10. package/corpus/core/blueprints/sandbox/docker.md +1 -1
  11. package/corpus/core/package.json +6 -5
  12. package/corpus/core/src/agent/run-manager.ts +23 -2
  13. package/corpus/core/src/agent/run-store.ts +2 -2
  14. package/corpus/core/src/cli/add.ts +1 -1
  15. package/corpus/core/src/cli/create.ts +8 -3
  16. package/corpus/core/src/cli/design-connect.ts +53 -9
  17. package/corpus/core/src/cli/index.ts +5 -4
  18. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
  19. package/corpus/core/src/client/session-replay.ts +97 -3
  20. package/corpus/core/src/jobs/scheduler.ts +30 -1
  21. package/corpus/core/src/localization/default-messages.ts +21 -0
  22. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
  23. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
  24. package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
  25. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
  26. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
  27. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
  28. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
  29. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
  30. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
  31. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
  32. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
  33. package/corpus/core/src/templates/default/package.json +2 -2
  34. package/corpus/core/src/templates/headless/package.json +3 -3
  35. package/corpus/core/src/templates/workspace-core/package.json +6 -6
  36. package/corpus/core/src/templates/workspace-root/package.json +2 -2
  37. package/corpus/core/src/triggers/dispatcher.ts +30 -2
  38. package/corpus/core/src/vite/client.ts +96 -1
  39. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -2
  40. package/corpus/templates/analytics/app/global.css +26 -10
  41. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  42. package/corpus/templates/analytics/app/i18n-data.ts +25 -0
  43. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +548 -237
  44. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
  45. package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
  50. package/corpus/templates/analytics/package.json +2 -2
  51. package/corpus/templates/assets/package.json +2 -2
  52. package/corpus/templates/brain/package.json +2 -2
  53. package/corpus/templates/calendar/AGENTS.md +10 -0
  54. package/corpus/templates/calendar/actions/create-event.ts +5 -1
  55. package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
  56. package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
  57. package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
  58. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
  59. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
  60. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
  61. package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
  62. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  63. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  64. package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
  65. package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
  66. package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
  67. package/corpus/templates/calendar/package.json +2 -2
  68. package/corpus/templates/calendar/server/handlers/events.ts +5 -1
  69. package/corpus/templates/calendar/shared/api.ts +6 -0
  70. package/corpus/templates/chat/package.json +2 -2
  71. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
  72. package/corpus/templates/clips/actions/create-meeting.ts +8 -3
  73. package/corpus/templates/clips/actions/list-meetings.ts +31 -3
  74. package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
  75. package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
  76. package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
  79. package/corpus/templates/clips/chrome-extension/package.json +2 -2
  80. package/corpus/templates/clips/desktop/package.json +2 -2
  81. package/corpus/templates/clips/desktop/src/app.tsx +91 -34
  82. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -5
  83. package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
  84. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
  86. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +174 -50
  87. package/corpus/templates/clips/desktop/src/styles.css +128 -53
  88. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +364 -0
  89. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +1 -1
  90. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +5 -0
  91. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +91 -11
  92. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +69 -19
  93. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
  94. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
  95. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -24
  96. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
  97. package/corpus/templates/clips/package.json +2 -2
  98. package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
  99. package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
  100. package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
  101. package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
  102. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  103. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2584 -546
  104. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
  105. package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
  106. package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
  107. package/corpus/templates/content/app/i18n-data.ts +154 -0
  108. package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
  109. package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
  110. package/corpus/templates/content/package.json +2 -2
  111. package/corpus/templates/content/parity/matrix.md +1 -1
  112. package/corpus/templates/content/parity/matrix.ts +2 -0
  113. package/corpus/templates/content/shared/api.ts +24 -0
  114. package/corpus/templates/design/AGENTS.md +9 -0
  115. package/corpus/templates/design/actions/request-localhost-write-consent.ts +141 -0
  116. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +13 -19
  117. package/corpus/templates/design/package.json +2 -2
  118. package/corpus/templates/design/server/lib/verify-write-grant.ts +4 -2
  119. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
  120. package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
  121. package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
  122. package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
  123. package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
  124. package/corpus/templates/dispatch/package.json +2 -2
  125. package/corpus/templates/forms/package.json +2 -2
  126. package/corpus/templates/macros/package.json +2 -2
  127. package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
  128. package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
  129. package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
  130. package/corpus/templates/mail/package.json +2 -2
  131. package/corpus/templates/plan/package.json +2 -2
  132. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
  133. package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
  134. package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
  135. package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
  136. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  137. package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
  138. package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
  139. package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
  140. package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
  141. package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
  142. package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
  143. package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
  144. package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
  145. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
  146. package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
  147. package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
  148. package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
  149. package/corpus/templates/slides/package.json +2 -2
  150. package/dist/agent/run-manager.d.ts.map +1 -1
  151. package/dist/agent/run-manager.js +23 -2
  152. package/dist/agent/run-manager.js.map +1 -1
  153. package/dist/agent/run-store.js +2 -2
  154. package/dist/agent/run-store.js.map +1 -1
  155. package/dist/catalog.json +2 -2
  156. package/dist/cli/add.js +1 -1
  157. package/dist/cli/add.js.map +1 -1
  158. package/dist/cli/create.d.ts.map +1 -1
  159. package/dist/cli/create.js +8 -3
  160. package/dist/cli/create.js.map +1 -1
  161. package/dist/cli/design-connect.d.ts.map +1 -1
  162. package/dist/cli/design-connect.js +40 -10
  163. package/dist/cli/design-connect.js.map +1 -1
  164. package/dist/cli/index.js +5 -4
  165. package/dist/cli/index.js.map +1 -1
  166. package/dist/client/extensions/ExtensionSlot.js +6 -0
  167. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  168. package/dist/client/session-replay.d.ts +8 -0
  169. package/dist/client/session-replay.d.ts.map +1 -1
  170. package/dist/client/session-replay.js +80 -3
  171. package/dist/client/session-replay.js.map +1 -1
  172. package/dist/collab/routes.d.ts +1 -1
  173. package/dist/jobs/scheduler.js +26 -1
  174. package/dist/jobs/scheduler.js.map +1 -1
  175. package/dist/localization/default-messages.d.ts +19 -0
  176. package/dist/localization/default-messages.d.ts.map +1 -1
  177. package/dist/localization/default-messages.js +19 -0
  178. package/dist/localization/default-messages.js.map +1 -1
  179. package/dist/notifications/routes.d.ts +1 -1
  180. package/dist/observability/routes.d.ts +1 -1
  181. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  182. package/dist/resources/handlers.d.ts +1 -1
  183. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  184. package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
  185. package/dist/templates/default/app/i18n/de-DE.ts +21 -0
  186. package/dist/templates/default/app/i18n/en-US.ts +21 -0
  187. package/dist/templates/default/app/i18n/es-ES.ts +21 -0
  188. package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
  189. package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
  190. package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
  191. package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
  192. package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
  193. package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
  194. package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
  195. package/dist/templates/default/package.json +2 -2
  196. package/dist/templates/headless/package.json +3 -3
  197. package/dist/templates/workspace-core/package.json +6 -6
  198. package/dist/templates/workspace-root/package.json +2 -2
  199. package/dist/triggers/dispatcher.js +27 -2
  200. package/dist/triggers/dispatcher.js.map +1 -1
  201. package/dist/vite/client.d.ts.map +1 -1
  202. package/dist/vite/client.js +70 -1
  203. package/dist/vite/client.js.map +1 -1
  204. package/package.json +7 -6
  205. package/src/templates/default/app/i18n/ar-SA.ts +21 -0
  206. package/src/templates/default/app/i18n/de-DE.ts +21 -0
  207. package/src/templates/default/app/i18n/en-US.ts +21 -0
  208. package/src/templates/default/app/i18n/es-ES.ts +21 -0
  209. package/src/templates/default/app/i18n/fr-FR.ts +21 -0
  210. package/src/templates/default/app/i18n/hi-IN.ts +20 -0
  211. package/src/templates/default/app/i18n/ja-JP.ts +21 -0
  212. package/src/templates/default/app/i18n/ko-KR.ts +20 -0
  213. package/src/templates/default/app/i18n/pt-BR.ts +21 -0
  214. package/src/templates/default/app/i18n/zh-CN.ts +20 -0
  215. package/src/templates/default/app/i18n/zh-TW.ts +19 -0
  216. package/src/templates/default/package.json +2 -2
  217. package/src/templates/headless/package.json +3 -3
  218. package/src/templates/workspace-core/package.json +6 -6
  219. package/src/templates/workspace-root/package.json +2 -2
@@ -1,16 +1,32 @@
1
- import { IconAlertCircle, IconClock, IconX } from "@tabler/icons-react";
1
+ import {
2
+ IconAlertCircle,
3
+ IconChevronDown,
4
+ IconNotes,
5
+ IconVideo,
6
+ IconX,
7
+ } from "@tabler/icons-react";
2
8
  import { invoke } from "@tauri-apps/api/core";
3
9
  import { emit, listen } from "@tauri-apps/api/event";
4
10
  import { getCurrentWindow } from "@tauri-apps/api/window";
5
11
  import { open as openExternal } from "@tauri-apps/plugin-shell";
6
12
  import { useEffect, useRef, useState } from "react";
7
13
 
14
+ import {
15
+ detectMeetingJoinProvider,
16
+ joinProviderLabel,
17
+ meetingNotificationAutoHideMs,
18
+ type MeetingJoinProvider,
19
+ } from "../lib/meeting-notification-timing";
20
+
8
21
  interface NotificationData {
9
22
  type: "calendar" | "adhoc";
10
23
  title: string;
11
24
  subtitle: string;
12
25
  meetingId: string;
13
26
  joinUrl?: string | null;
27
+ platform?: string | null;
28
+ scheduledStart?: string | null;
29
+ scheduledEnd?: string | null;
14
30
  autoStart?: boolean;
15
31
  }
16
32
 
@@ -19,12 +35,11 @@ interface TranscriptionStatusPayload {
19
35
  error?: string;
20
36
  }
21
37
 
22
- const DEFAULT_AUTO_HIDE_MS = 30_000;
23
38
  const SNOOZE_MS = 5 * 60_000;
39
+ const FALLBACK_AUTO_HIDE_MS = 6 * 60_000;
24
40
 
25
41
  /**
26
- * Open a meeting join URL via the Tauri shell plugin. Used by the
27
- * notification's dedicated Join CTA (notes start is a separate action).
42
+ * Open a meeting join URL via the Tauri shell plugin.
28
43
  */
29
44
  async function openJoinUrl(url: string | null | undefined): Promise<void> {
30
45
  if (!url) return;
@@ -35,40 +50,71 @@ async function openJoinUrl(url: string | null | undefined): Promise<void> {
35
50
  }
36
51
  }
37
52
 
53
+ function ProviderGlyph({ provider }: { provider: MeetingJoinProvider }) {
54
+ // Lightweight glyphs — keep the overlay free of extra assets. Zoom blue
55
+ // camera / Meet green / Teams purple, otherwise a generic video icon.
56
+ if (provider === "zoom") {
57
+ return (
58
+ <span
59
+ className="meeting-notification-provider meeting-notification-provider-zoom"
60
+ aria-hidden
61
+ >
62
+ <IconVideo size={14} stroke={2.2} />
63
+ </span>
64
+ );
65
+ }
66
+ if (provider === "meet") {
67
+ return (
68
+ <span
69
+ className="meeting-notification-provider meeting-notification-provider-meet"
70
+ aria-hidden
71
+ >
72
+ <IconVideo size={14} stroke={2.2} />
73
+ </span>
74
+ );
75
+ }
76
+ if (provider === "teams") {
77
+ return (
78
+ <span
79
+ className="meeting-notification-provider meeting-notification-provider-teams"
80
+ aria-hidden
81
+ >
82
+ <IconVideo size={14} stroke={2.2} />
83
+ </span>
84
+ );
85
+ }
86
+ return (
87
+ <span
88
+ className="meeting-notification-provider meeting-notification-provider-other"
89
+ aria-hidden
90
+ >
91
+ <IconNotes size={14} stroke={2.2} />
92
+ </span>
93
+ );
94
+ }
95
+
38
96
  /**
39
97
  * Granola-style meeting notification — small card in the top-right corner.
40
- * Variants:
41
98
  *
42
- * - Calendar event: solid left bar (green), meeting title, time,
43
- * "Start notes" + optional "Join" + "Snooze 5 min" buttons.
44
- * - Ad-hoc call: dashed left bar (slate), "Call detected", app name,
45
- * same controls.
99
+ * Primary split button: join the call and open Clips notes in one click.
100
+ * Chevron exposes secondary actions (join only / notes only / snooze).
46
101
  *
47
- * Data arrives via Tauri event `meetings:show-notification`. Auto-hides
48
- * after 30s by default. Hover pauses the auto-hide timer. Errors from the
49
- * persistent popover transcription session surface inline beneath the title
50
- * so the user isn't left wondering why nothing happened.
102
+ * Data arrives via Tauri event `meetings:show-notification`. Visibility holds
103
+ * from 1 minute before start until 5 minutes after, unless dismissed.
51
104
  */
52
105
  export function MeetingNotification() {
53
106
  const [data, setData] = useState<NotificationData | null>(null);
54
107
  const [showClose, setShowClose] = useState(false);
108
+ const [menuOpen, setMenuOpen] = useState(false);
55
109
  const [error, setError] = useState<string | null>(null);
56
110
  const [pending, setPending] = useState(false);
57
111
  const autoHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
58
112
  const dataRef = useRef<NotificationData | null>(null);
59
113
 
60
- // Keep a ref to the latest data so the transcription-status listeners can
61
- // match incoming events against the meeting currently on screen without
62
- // re-subscribing on every render.
63
114
  useEffect(() => {
64
115
  dataRef.current = data;
65
116
  }, [data]);
66
117
 
67
- // The notification window is a single persistent overlay (created once at
68
- // startup so it can receive `meetings:show-notification` events). Drive its
69
- // OS-level visibility from React state: a shown-but-empty transparent window
70
- // would otherwise sit at the top of the screen swallowing clicks. Visible
71
- // only while there's a notification on screen.
72
118
  useEffect(() => {
73
119
  const win = getCurrentWindow();
74
120
  if (data) {
@@ -84,8 +130,15 @@ export function MeetingNotification() {
84
130
  ) {
85
131
  setData(payload);
86
132
  setError(null);
133
+ setMenuOpen(false);
87
134
  setPending(!!payload.autoStart && !options?.hydrated);
88
- scheduleAutoHide(DEFAULT_AUTO_HIDE_MS);
135
+ const startMs = payload.scheduledStart
136
+ ? Date.parse(payload.scheduledStart)
137
+ : NaN;
138
+ const hideMs = Number.isFinite(startMs)
139
+ ? meetingNotificationAutoHideMs(startMs)
140
+ : FALLBACK_AUTO_HIDE_MS;
141
+ scheduleAutoHide(hideMs);
89
142
  }
90
143
 
91
144
  useEffect(() => {
@@ -112,6 +165,15 @@ export function MeetingNotification() {
112
165
  }),
113
166
  );
114
167
 
168
+ // Cold overlay boot: hydrate any payload stored before this webview
169
+ // mounted (calendar or adhoc).
170
+ invoke<NotificationData | null>("take_pending_meeting_notification")
171
+ .then((pending) => {
172
+ if (stopped || !pending) return;
173
+ showNotification(pending, { hydrated: true });
174
+ })
175
+ .catch(() => {});
176
+
115
177
  trackListen(
116
178
  listen<TranscriptionStatusPayload>("meetings:hide-notification", (ev) => {
117
179
  if (ev.payload.meetingId !== dataRef.current?.meetingId) return;
@@ -154,14 +216,30 @@ export function MeetingNotification() {
154
216
 
155
217
  function scheduleAutoHide(ms: number) {
156
218
  clearAutoHide();
219
+ if (ms <= 0) {
220
+ hideNotification();
221
+ return;
222
+ }
157
223
  autoHideTimerRef.current = setTimeout(() => hideNotification(), ms);
158
224
  }
159
225
 
226
+ function resumeAutoHide() {
227
+ const current = dataRef.current;
228
+ const startMs = current?.scheduledStart
229
+ ? Date.parse(current.scheduledStart)
230
+ : NaN;
231
+ const hideMs = Number.isFinite(startMs)
232
+ ? meetingNotificationAutoHideMs(startMs)
233
+ : FALLBACK_AUTO_HIDE_MS;
234
+ scheduleAutoHide(hideMs);
235
+ }
236
+
160
237
  function hideNotification() {
161
238
  clearAutoHide();
162
239
  setData(null);
163
240
  setError(null);
164
241
  setPending(false);
242
+ setMenuOpen(false);
165
243
  dataRef.current = null;
166
244
  }
167
245
 
@@ -169,6 +247,7 @@ export function MeetingNotification() {
169
247
  if (!data || pending) return;
170
248
  setPending(true);
171
249
  setError(null);
250
+ setMenuOpen(false);
172
251
  emit("meetings:start-transcription", {
173
252
  meetingId: data.meetingId,
174
253
  joinUrl: data.joinUrl,
@@ -181,14 +260,23 @@ export function MeetingNotification() {
181
260
 
182
261
  async function joinMeeting() {
183
262
  if (!data?.joinUrl) return;
263
+ setMenuOpen(false);
184
264
  await openJoinUrl(data.joinUrl);
185
265
  }
186
266
 
267
+ /** Granola primary: join the call and start Clips notes together. */
268
+ async function joinAndOpenClips() {
269
+ if (!data || pending) return;
270
+ setMenuOpen(false);
271
+ if (data.joinUrl) {
272
+ await openJoinUrl(data.joinUrl);
273
+ }
274
+ await takeNotes();
275
+ }
276
+
187
277
  function snooze() {
188
278
  if (!data) return;
189
- // Hand the snooze to the Rust watcher so the reminder re-fires after the
190
- // delay even though this overlay window closes right away. A setTimeout
191
- // here would be torn down with the window and never fire.
279
+ setMenuOpen(false);
192
280
  invoke("meetings_snooze", {
193
281
  meetingId: data.meetingId,
194
282
  minutes: Math.round(SNOOZE_MS / 60_000),
@@ -202,6 +290,14 @@ export function MeetingNotification() {
202
290
 
203
291
  const isCalendar = data.type === "calendar";
204
292
  const hasJoin = Boolean(data.joinUrl);
293
+ const provider = detectMeetingJoinProvider(data.joinUrl, data.platform);
294
+ const providerName = joinProviderLabel(provider);
295
+ const primaryLabel = hasJoin
296
+ ? provider === "other"
297
+ ? "Join meeting"
298
+ : `Join ${providerName}`
299
+ : "Start notes";
300
+ const secondaryLabel = hasJoin ? "& open Clips" : null;
205
301
 
206
302
  return (
207
303
  <div
@@ -212,14 +308,11 @@ export function MeetingNotification() {
212
308
  }}
213
309
  onMouseLeave={() => {
214
310
  setShowClose(false);
215
- // Resume the auto-hide timer with the remaining-ish budget.
216
- // Cheap approximation: just restart the full timer on leave.
217
- scheduleAutoHide(DEFAULT_AUTO_HIDE_MS);
311
+ setMenuOpen(false);
312
+ resumeAutoHide();
218
313
  }}
219
314
  >
220
- <div
221
- className={`meeting-notification${hasJoin ? " meeting-notification-with-join" : ""}`}
222
- >
315
+ <div className="meeting-notification">
223
316
  <div
224
317
  className={`meeting-notification-bar ${isCalendar ? "meeting-notification-bar-calendar" : "meeting-notification-bar-adhoc"}`}
225
318
  />
@@ -233,34 +326,65 @@ export function MeetingNotification() {
233
326
  </div>
234
327
  ) : null}
235
328
  </div>
236
- <div className="meeting-notification-actions">
329
+ <div className="meeting-notification-split">
237
330
  <button
238
- className="meeting-notification-btn meeting-notification-btn-primary"
239
- onClick={takeNotes}
331
+ className="meeting-notification-split-main"
332
+ onClick={hasJoin ? joinAndOpenClips : takeNotes}
240
333
  disabled={pending}
241
334
  data-no-drag
242
335
  >
243
- {pending ? "Starting…" : "Start notes"}
336
+ <ProviderGlyph provider={provider} />
337
+ <span className="meeting-notification-split-copy">
338
+ <span className="meeting-notification-split-primary">
339
+ {pending ? "Starting…" : primaryLabel}
340
+ </span>
341
+ {secondaryLabel && !pending ? (
342
+ <span className="meeting-notification-split-secondary">
343
+ {secondaryLabel}
344
+ </span>
345
+ ) : null}
346
+ </span>
244
347
  </button>
245
- {hasJoin ? (
246
- <button
247
- className="meeting-notification-btn meeting-notification-btn-secondary"
248
- onClick={joinMeeting}
249
- data-no-drag
250
- >
251
- Join
252
- </button>
253
- ) : null}
254
348
  <button
255
- className="meeting-notification-btn meeting-notification-btn-secondary"
256
- onClick={snooze}
349
+ className="meeting-notification-split-chevron"
350
+ onClick={() => setMenuOpen((open) => !open)}
351
+ aria-label="More actions"
352
+ aria-expanded={menuOpen}
257
353
  data-no-drag
258
- aria-label="Snooze 5 minutes"
259
- title="Snooze 5 min"
260
354
  >
261
- <IconClock size={12} aria-hidden="true" />
262
- <span>5m</span>
355
+ <IconChevronDown size={14} aria-hidden="true" />
263
356
  </button>
357
+ {menuOpen ? (
358
+ <div className="meeting-notification-menu" role="menu">
359
+ {hasJoin ? (
360
+ <button
361
+ role="menuitem"
362
+ className="meeting-notification-menu-item"
363
+ onClick={joinMeeting}
364
+ data-no-drag
365
+ >
366
+ Join only
367
+ </button>
368
+ ) : null}
369
+ <button
370
+ role="menuitem"
371
+ className="meeting-notification-menu-item"
372
+ onClick={takeNotes}
373
+ disabled={pending}
374
+ data-no-drag
375
+ >
376
+ Start notes only
377
+ </button>
378
+ <button
379
+ role="menuitem"
380
+ className="meeting-notification-menu-item"
381
+ onClick={snooze}
382
+ data-no-drag
383
+ >
384
+ Snooze 5 min
385
+ </button>
386
+ </div>
387
+ ) : null}
264
388
  </div>
265
389
  {showClose ? (
266
390
  <button
@@ -25,7 +25,9 @@
25
25
  --radius: 12px;
26
26
  --radius-sm: 8px;
27
27
  --radius-pill: 999px;
28
- --popover-shadow-gutter: 12px;
28
+ /* Must cover `--shadow-md` blur (24px in dark mode) or the transparent
29
+ Tauri window clips the drop shadow on every edge. */
30
+ --popover-shadow-gutter: 24px;
29
31
  --overlay-shadow-gutter: 18px;
30
32
 
31
33
  --shadow-sm: 0 1px 2px rgba(23, 23, 23, 0.06);
@@ -1419,6 +1421,13 @@ body[data-clips-route="recording-pill"] #root {
1419
1421
  overscroll-behavior: contain;
1420
1422
  }
1421
1423
 
1424
+ /* Meetings / dictation drill-ins: a touch more room under the last row so
1425
+ content isn't flush with the panel edge. Must follow `.setup` so it wins
1426
+ over the shorthand padding above. */
1427
+ .setup.popover-view {
1428
+ padding-bottom: 20px;
1429
+ }
1430
+
1422
1431
  .setup h2 {
1423
1432
  margin: 0;
1424
1433
  font-size: 14px;
@@ -3599,7 +3608,7 @@ body[data-clips-route="recording-pill"] #root {
3599
3608
  position: relative;
3600
3609
  display: flex;
3601
3610
  align-items: center;
3602
- gap: 10px;
3611
+ gap: 12px;
3603
3612
  padding: 12px 14px;
3604
3613
  border-radius: 14px;
3605
3614
  background: rgba(20, 22, 28, 0.94);
@@ -3611,37 +3620,26 @@ body[data-clips-route="recording-pill"] #root {
3611
3620
  0 2px 6px rgba(0, 0, 0, 0.3);
3612
3621
  color: white;
3613
3622
  pointer-events: auto;
3614
- min-width: 280px;
3615
- max-width: 400px;
3616
- }
3617
-
3618
- .meeting-notification-with-join {
3619
- flex-wrap: wrap;
3620
- align-items: flex-start;
3621
- row-gap: 10px;
3622
- }
3623
-
3624
- .meeting-notification-with-join .meeting-notification-actions {
3625
- flex: 1 1 100%;
3626
- justify-content: flex-end;
3627
- padding-left: 13px; /* align under content past the bar */
3623
+ min-width: 320px;
3624
+ max-width: 440px;
3628
3625
  }
3629
3626
 
3630
3627
  .meeting-notification-bar {
3631
3628
  width: 3px;
3632
- height: 40px;
3633
- border-radius: 2px;
3629
+ height: 36px;
3630
+ border-radius: 999px;
3634
3631
  flex-shrink: 0;
3635
3632
  }
3636
3633
 
3637
3634
  .meeting-notification-bar-calendar {
3638
- background: #16a34a;
3635
+ background: #7dd3fc;
3639
3636
  }
3640
3637
 
3641
3638
  .meeting-notification-bar-adhoc {
3642
3639
  background: transparent;
3643
3640
  border-left: 3px dashed #a3a3a3;
3644
3641
  width: 3px;
3642
+ border-radius: 0;
3645
3643
  }
3646
3644
 
3647
3645
  .meeting-notification-content {
@@ -3650,6 +3648,7 @@ body[data-clips-route="recording-pill"] #root {
3650
3648
  display: flex;
3651
3649
  flex-direction: column;
3652
3650
  gap: 2px;
3651
+ padding-right: 4px;
3653
3652
  }
3654
3653
 
3655
3654
  .meeting-notification-title {
@@ -3666,65 +3665,141 @@ body[data-clips-route="recording-pill"] #root {
3666
3665
  color: rgba(255, 255, 255, 0.55);
3667
3666
  }
3668
3667
 
3669
- .meeting-notification-btn {
3670
- border: none;
3671
- border-radius: 8px;
3672
- padding: 6px 12px;
3673
- background: rgba(255, 255, 255, 0.1);
3674
- color: white;
3675
- font-size: 12px;
3676
- font-weight: 600;
3677
- cursor: pointer;
3678
- white-space: nowrap;
3668
+ .meeting-notification-split {
3669
+ position: relative;
3670
+ display: inline-flex;
3671
+ align-items: stretch;
3679
3672
  flex-shrink: 0;
3673
+ border: 1px solid rgba(255, 255, 255, 0.14);
3674
+ border-radius: 10px;
3675
+ overflow: visible;
3676
+ background: rgba(255, 255, 255, 0.04);
3680
3677
  }
3681
3678
 
3682
- .meeting-notification-btn:hover {
3683
- background: rgba(255, 255, 255, 0.16);
3679
+ .meeting-notification-split-main {
3680
+ display: inline-flex;
3681
+ align-items: center;
3682
+ gap: 8px;
3683
+ border: none;
3684
+ background: transparent;
3685
+ color: white;
3686
+ padding: 6px 10px;
3687
+ cursor: pointer;
3688
+ text-align: left;
3689
+ min-height: 40px;
3684
3690
  }
3685
3691
 
3686
- .meeting-notification-btn:active {
3687
- transform: scale(0.96);
3692
+ .meeting-notification-split-main:hover {
3693
+ background: rgba(255, 255, 255, 0.06);
3688
3694
  }
3689
3695
 
3690
- .meeting-notification-btn:disabled {
3691
- opacity: 0.6;
3696
+ .meeting-notification-split-main:disabled {
3697
+ opacity: 0.65;
3692
3698
  cursor: progress;
3693
3699
  }
3694
3700
 
3695
- .meeting-notification-btn-primary {
3696
- background: #ffffff;
3697
- color: #111827;
3701
+ .meeting-notification-split-copy {
3702
+ display: flex;
3703
+ flex-direction: column;
3704
+ gap: 0;
3705
+ line-height: 1.15;
3698
3706
  }
3699
3707
 
3700
- .meeting-notification-btn-primary:hover {
3701
- background: #f3f4f6;
3708
+ .meeting-notification-split-primary {
3709
+ font-size: 12px;
3710
+ font-weight: 600;
3711
+ color: white;
3712
+ white-space: nowrap;
3702
3713
  }
3703
3714
 
3704
- .meeting-notification-btn-primary:disabled {
3705
- background: rgba(255, 255, 255, 0.7);
3706
- color: #374151;
3715
+ .meeting-notification-split-secondary {
3716
+ font-size: 10px;
3717
+ font-weight: 500;
3718
+ color: rgba(255, 255, 255, 0.55);
3719
+ white-space: nowrap;
3707
3720
  }
3708
3721
 
3709
- .meeting-notification-actions {
3722
+ .meeting-notification-split-chevron {
3710
3723
  display: inline-flex;
3711
3724
  align-items: center;
3712
- gap: 6px;
3713
- flex-shrink: 0;
3725
+ justify-content: center;
3726
+ width: 28px;
3727
+ border: none;
3728
+ border-left: 1px solid rgba(255, 255, 255, 0.12);
3729
+ background: transparent;
3730
+ color: rgba(255, 255, 255, 0.75);
3731
+ cursor: pointer;
3732
+ border-radius: 0 10px 10px 0;
3714
3733
  }
3715
3734
 
3716
- .meeting-notification-btn-secondary {
3735
+ .meeting-notification-split-chevron:hover {
3736
+ background: rgba(255, 255, 255, 0.06);
3737
+ color: white;
3738
+ }
3739
+
3740
+ .meeting-notification-provider {
3741
+ width: 22px;
3742
+ height: 22px;
3743
+ border-radius: 6px;
3717
3744
  display: inline-flex;
3718
3745
  align-items: center;
3719
- gap: 4px;
3720
- padding: 6px 8px;
3721
- background: rgba(255, 255, 255, 0.06);
3722
- color: rgba(255, 255, 255, 0.85);
3746
+ justify-content: center;
3747
+ flex-shrink: 0;
3748
+ color: white;
3723
3749
  }
3724
3750
 
3725
- .meeting-notification-btn-secondary:hover {
3751
+ .meeting-notification-provider-zoom {
3752
+ background: #2d8cff;
3753
+ }
3754
+
3755
+ .meeting-notification-provider-meet {
3756
+ background: #34a853;
3757
+ }
3758
+
3759
+ .meeting-notification-provider-teams {
3760
+ background: #6264a7;
3761
+ }
3762
+
3763
+ .meeting-notification-provider-other {
3726
3764
  background: rgba(255, 255, 255, 0.12);
3727
- color: white;
3765
+ }
3766
+
3767
+ .meeting-notification-menu {
3768
+ position: absolute;
3769
+ top: calc(100% + 6px);
3770
+ right: 0;
3771
+ min-width: 160px;
3772
+ padding: 4px;
3773
+ border-radius: 10px;
3774
+ background: rgba(24, 26, 32, 0.98);
3775
+ border: 1px solid rgba(255, 255, 255, 0.1);
3776
+ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);
3777
+ z-index: 5;
3778
+ display: flex;
3779
+ flex-direction: column;
3780
+ gap: 2px;
3781
+ }
3782
+
3783
+ .meeting-notification-menu-item {
3784
+ border: none;
3785
+ background: transparent;
3786
+ color: rgba(255, 255, 255, 0.9);
3787
+ text-align: left;
3788
+ font-size: 12px;
3789
+ font-weight: 500;
3790
+ padding: 8px 10px;
3791
+ border-radius: 7px;
3792
+ cursor: pointer;
3793
+ white-space: nowrap;
3794
+ }
3795
+
3796
+ .meeting-notification-menu-item:hover {
3797
+ background: rgba(255, 255, 255, 0.08);
3798
+ }
3799
+
3800
+ .meeting-notification-menu-item:disabled {
3801
+ opacity: 0.5;
3802
+ cursor: progress;
3728
3803
  }
3729
3804
 
3730
3805
  .meeting-notification-error {