@agent-native/core 0.91.1 → 0.92.0

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 (383) 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 +2 -2
  6. package/corpus/core/CHANGELOG.md +31 -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/docs/content/faq.mdx +17 -0
  12. package/corpus/core/docs/content/notifications.mdx +12 -6
  13. package/corpus/core/package.json +6 -5
  14. package/corpus/core/src/agent/production-agent.ts +140 -46
  15. package/corpus/core/src/agent/run-manager.ts +23 -2
  16. package/corpus/core/src/agent/run-store.ts +2 -2
  17. package/corpus/core/src/agent/tool-search.ts +71 -4
  18. package/corpus/core/src/cli/add.ts +1 -1
  19. package/corpus/core/src/cli/create.ts +8 -3
  20. package/corpus/core/src/cli/index.ts +25 -4
  21. package/corpus/core/src/cli/upgrade.ts +798 -0
  22. package/corpus/core/src/client/AssistantChat.tsx +1 -1
  23. package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
  24. package/corpus/core/src/client/agent-chat-adapter.ts +31 -6
  25. package/corpus/core/src/client/chat/message-components.tsx +85 -8
  26. package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
  27. package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
  28. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +7 -0
  29. package/corpus/core/src/client/settings/SettingsPanel.tsx +533 -389
  30. package/corpus/core/src/client/settings/SettingsSection.tsx +154 -17
  31. package/corpus/core/src/client/sse-event-processor.ts +29 -2
  32. package/corpus/core/src/extensions/actions.ts +113 -5
  33. package/corpus/core/src/jobs/scheduler.ts +30 -1
  34. package/corpus/core/src/localization/default-messages.ts +21 -0
  35. package/corpus/core/src/notifications/channels.ts +88 -25
  36. package/corpus/core/src/notifications/registry.ts +17 -3
  37. package/corpus/core/src/notifications/types.ts +4 -1
  38. package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
  39. package/corpus/core/src/server/request-context.ts +7 -0
  40. package/corpus/core/src/styles/agent-conversation.css +1 -1
  41. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  42. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  43. package/corpus/core/src/templates/default/AGENTS.md +10 -3
  44. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +21 -0
  45. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +21 -0
  46. package/corpus/core/src/templates/default/app/i18n/en-US.ts +21 -0
  47. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +21 -0
  48. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
  49. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +20 -0
  50. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +21 -0
  51. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +20 -0
  52. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +21 -0
  53. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +20 -0
  54. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
  55. package/corpus/core/src/templates/default/package.json +4 -3
  56. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  57. package/corpus/core/src/templates/headless/AGENTS.md +9 -3
  58. package/corpus/core/src/templates/headless/package.json +4 -3
  59. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  60. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  61. package/corpus/core/src/templates/workspace-core/package.json +6 -6
  62. package/corpus/core/src/templates/workspace-root/AGENTS.md +10 -4
  63. package/corpus/core/src/templates/workspace-root/package.json +3 -2
  64. package/corpus/core/src/triggers/dispatcher.ts +30 -2
  65. package/corpus/core/src/vite/client.ts +96 -1
  66. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +20 -2
  67. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -2
  68. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  69. package/corpus/templates/analytics/AGENTS.md +7 -5
  70. package/corpus/templates/analytics/actions/save-monitor.ts +14 -0
  71. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +41 -6
  72. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
  73. package/corpus/templates/analytics/app/global.css +26 -10
  74. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  75. package/corpus/templates/analytics/app/i18n-data.ts +25 -0
  76. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +8 -2
  77. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +151 -32
  78. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
  79. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
  80. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +461 -286
  81. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +285 -294
  82. package/corpus/templates/analytics/changelog/2026-07-08-console-details-expand-inline-under-each-row-and-the-event-t.md +6 -0
  83. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-tables-keep-their-size-while-loading.md +6 -0
  84. package/corpus/templates/analytics/changelog/2026-07-08-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.md +6 -0
  85. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-keeps-a-usable-stage-when-dev-tools-is-open-a.md +6 -0
  86. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-resumes-after-scrubbing-and-expands-console-r.md +6 -0
  87. package/corpus/templates/analytics/changelog/2026-07-08-session-replay-uses-stock-rrweb-sizing-again-so-recorded.md +6 -0
  88. package/corpus/templates/analytics/changelog/2026-07-08-session-replays-wait-for-full-load-keep-a-normal-viewport-an.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.md +6 -0
  90. package/corpus/templates/analytics/changelog/2026-07-08-uptime-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
  91. package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
  92. package/corpus/templates/analytics/package.json +2 -2
  93. package/corpus/templates/analytics/server/db/schema-monitoring.ts +4 -0
  94. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +60 -12
  95. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  96. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +20 -2
  97. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  98. package/corpus/templates/assets/package.json +2 -2
  99. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +20 -2
  100. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  101. package/corpus/templates/brain/package.json +2 -2
  102. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +20 -2
  103. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  104. package/corpus/templates/calendar/AGENTS.md +10 -0
  105. package/corpus/templates/calendar/actions/create-event.ts +5 -1
  106. package/corpus/templates/calendar/actions/event-action-helpers.ts +41 -0
  107. package/corpus/templates/calendar/actions/get-attendee-timezones.ts +28 -0
  108. package/corpus/templates/calendar/actions/set-attendee-timezone.ts +68 -0
  109. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +150 -11
  110. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +36 -1
  111. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +32 -0
  112. package/corpus/templates/calendar/app/hooks/use-attendee-timezones.ts +24 -0
  113. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  114. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  115. package/corpus/templates/calendar/app/lib/attendee-local-time.ts +122 -0
  116. package/corpus/templates/calendar/changelog/2026-07-08-event-details-now-support-extension-widgets-and-you-can-see-.md +6 -0
  117. package/corpus/templates/calendar/changelog/2026-07-08-when-you-invite-guests-to-a-new-event-you-now-appear-in.md +6 -0
  118. package/corpus/templates/calendar/package.json +2 -2
  119. package/corpus/templates/calendar/server/handlers/events.ts +5 -1
  120. package/corpus/templates/calendar/shared/api.ts +6 -0
  121. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +20 -2
  122. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  123. package/corpus/templates/chat/package.json +2 -2
  124. package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +6 -2
  125. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +20 -2
  126. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  127. package/corpus/templates/clips/actions/create-meeting.ts +8 -3
  128. package/corpus/templates/clips/actions/list-meetings.ts +31 -3
  129. package/corpus/templates/clips/changelog/2026-07-08-after-finishing-a-desktop-recording-reopening-clips-restor.md +6 -0
  130. package/corpus/templates/clips/changelog/2026-07-08-clips-now-pops-up-to-take-notes-when-you-join-an-adhoc-zoom-.md +6 -0
  131. package/corpus/templates/clips/changelog/2026-07-08-desktop-clip-recordings-are-louder-after-capture-mic-only.md +6 -0
  132. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
  133. package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
  134. package/corpus/templates/clips/changelog/2026-07-08-meeting-notes-keep-their-recording-state-when-the-desktop.md +6 -0
  135. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.md +6 -0
  136. package/corpus/templates/clips/changelog/2026-07-08-meeting-reminders-now-appear-1-minute-before-start-stay-unti.md +6 -0
  137. package/corpus/templates/clips/changelog/2026-07-08-the-desktop-meetings-panel-has-room-for-its-drop-shadow-and-.md +6 -0
  138. package/corpus/templates/clips/chrome-extension/package.json +2 -2
  139. package/corpus/templates/clips/desktop/package.json +2 -2
  140. package/corpus/templates/clips/desktop/src/app.tsx +214 -37
  141. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +30 -6
  142. package/corpus/templates/clips/desktop/src/lib/meeting-notification-timing.ts +83 -0
  143. package/corpus/templates/clips/desktop/src/lib/native-recording-warm.ts +21 -0
  144. package/corpus/templates/clips/desktop/src/lib/recorder.ts +98 -20
  145. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +197 -50
  146. package/corpus/templates/clips/desktop/src/styles.css +150 -53
  147. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +362 -0
  148. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +41 -7
  149. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +9 -2
  150. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +89 -11
  151. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +71 -17
  152. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +36 -1
  153. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -10
  154. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
  155. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +1 -25
  156. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
  157. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
  158. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +36 -1
  159. package/corpus/templates/clips/package.json +2 -2
  160. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +20 -2
  161. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  162. package/corpus/templates/content/actions/_batch-utils.ts +8 -0
  163. package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
  164. package/corpus/templates/content/actions/_content-database-personal-view.ts +80 -0
  165. package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
  166. package/corpus/templates/content/actions/_database-utils.ts +6 -1
  167. package/corpus/templates/content/actions/_property-utils.ts +168 -0
  168. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
  169. package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
  170. package/corpus/templates/content/actions/delete-document.ts +9 -13
  171. package/corpus/templates/content/actions/get-content-database-personal-view.ts +27 -0
  172. package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
  173. package/corpus/templates/content/actions/update-content-database-personal-view.ts +31 -0
  174. package/corpus/templates/content/actions/update-content-database-view.ts +3 -0
  175. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  176. package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
  177. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2631 -551
  178. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +220 -18
  179. package/corpus/templates/content/app/hooks/use-content-database.ts +32 -0
  180. package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
  181. package/corpus/templates/content/app/i18n/zh-TW.ts +16 -0
  182. package/corpus/templates/content/app/i18n-data.ts +154 -0
  183. package/corpus/templates/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
  184. package/corpus/templates/content/changelog/2026-07-08-database-filters-now-support-notion-style-reset-save-for-eve.md +6 -0
  185. package/corpus/templates/content/changelog/2026-07-08-date-filters-can-now-target-a-between-range.md +6 -0
  186. package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
  187. package/corpus/templates/content/package.json +2 -2
  188. package/corpus/templates/content/parity/matrix.md +1 -1
  189. package/corpus/templates/content/parity/matrix.ts +2 -0
  190. package/corpus/templates/content/server/plugins/db.ts +19 -0
  191. package/corpus/templates/content/shared/api.ts +24 -0
  192. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +20 -2
  193. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  194. package/corpus/templates/design/package.json +2 -2
  195. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +20 -2
  196. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  197. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +34 -1
  198. package/corpus/templates/dispatch/app/i18n-data.ts +387 -10
  199. package/corpus/templates/dispatch/app/routes/automations.tsx +1 -0
  200. package/corpus/templates/dispatch/changelog/2026-07-08-added-an-automations-page-to-review-pause-and-create.md +6 -0
  201. package/corpus/templates/dispatch/changelog/2026-07-08-destinations-now-shows-outbound-delivery-queue-health.md +6 -0
  202. package/corpus/templates/dispatch/package.json +2 -2
  203. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +20 -2
  204. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  205. package/corpus/templates/forms/package.json +2 -2
  206. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  207. package/corpus/templates/macros/package.json +2 -2
  208. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +20 -2
  209. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  210. package/corpus/templates/mail/app/hooks/use-emails.ts +56 -44
  211. package/corpus/templates/mail/app/lib/gmail-mutation-queue.ts +325 -0
  212. package/corpus/templates/mail/changelog/2026-07-08-rapid-archive-and-mark-read-now-batch-gmail-updates-so-the-i.md +6 -0
  213. package/corpus/templates/mail/package.json +2 -2
  214. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +20 -2
  215. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  216. package/corpus/templates/plan/package.json +2 -2
  217. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +20 -2
  218. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  219. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +46 -29
  220. package/corpus/templates/slides/app/context/DeckContext.tsx +45 -18
  221. package/corpus/templates/slides/app/i18n/ar-SA.ts +4 -0
  222. package/corpus/templates/slides/app/i18n/de-DE.ts +4 -0
  223. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  224. package/corpus/templates/slides/app/i18n/es-ES.ts +4 -0
  225. package/corpus/templates/slides/app/i18n/fr-FR.ts +4 -0
  226. package/corpus/templates/slides/app/i18n/hi-IN.ts +4 -0
  227. package/corpus/templates/slides/app/i18n/ja-JP.ts +4 -0
  228. package/corpus/templates/slides/app/i18n/ko-KR.ts +4 -0
  229. package/corpus/templates/slides/app/i18n/pt-BR.ts +4 -0
  230. package/corpus/templates/slides/app/i18n/zh-CN.ts +4 -0
  231. package/corpus/templates/slides/app/i18n/zh-TW.ts +4 -0
  232. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +115 -0
  233. package/corpus/templates/slides/app/pages/DeckEditor.tsx +22 -13
  234. package/corpus/templates/slides/changelog/2026-07-08-dropping-an-image-onto-a-slide-and-sending-it-to-the-agent-w.md +6 -0
  235. package/corpus/templates/slides/changelog/2026-07-08-undo-works-after-the-agent-edits-a-slide-so-you-can-reverse.md +6 -0
  236. package/corpus/templates/slides/package.json +2 -2
  237. package/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  238. package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  239. package/dist/agent/production-agent.d.ts +5 -1
  240. package/dist/agent/production-agent.d.ts.map +1 -1
  241. package/dist/agent/production-agent.js +112 -42
  242. package/dist/agent/production-agent.js.map +1 -1
  243. package/dist/agent/run-manager.d.ts.map +1 -1
  244. package/dist/agent/run-manager.js +23 -2
  245. package/dist/agent/run-manager.js.map +1 -1
  246. package/dist/agent/run-store.js +2 -2
  247. package/dist/agent/run-store.js.map +1 -1
  248. package/dist/agent/tool-search.d.ts +2 -0
  249. package/dist/agent/tool-search.d.ts.map +1 -1
  250. package/dist/agent/tool-search.js +60 -4
  251. package/dist/agent/tool-search.js.map +1 -1
  252. package/dist/catalog.json +2 -2
  253. package/dist/cli/add.js +1 -1
  254. package/dist/cli/add.js.map +1 -1
  255. package/dist/cli/create.d.ts.map +1 -1
  256. package/dist/cli/create.js +8 -3
  257. package/dist/cli/create.js.map +1 -1
  258. package/dist/cli/index.js +24 -4
  259. package/dist/cli/index.js.map +1 -1
  260. package/dist/cli/upgrade.d.ts +100 -0
  261. package/dist/cli/upgrade.d.ts.map +1 -0
  262. package/dist/cli/upgrade.js +678 -0
  263. package/dist/cli/upgrade.js.map +1 -0
  264. package/dist/client/AssistantChat.js +1 -1
  265. package/dist/client/AssistantChat.js.map +1 -1
  266. package/dist/client/HighlightedCodeBlock.d.ts +2 -0
  267. package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
  268. package/dist/client/HighlightedCodeBlock.js +10 -1
  269. package/dist/client/HighlightedCodeBlock.js.map +1 -1
  270. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  271. package/dist/client/agent-chat-adapter.js +32 -4
  272. package/dist/client/agent-chat-adapter.js.map +1 -1
  273. package/dist/client/chat/message-components.d.ts.map +1 -1
  274. package/dist/client/chat/message-components.js +61 -8
  275. package/dist/client/chat/message-components.js.map +1 -1
  276. package/dist/client/chat/tool-call-display.d.ts +12 -0
  277. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  278. package/dist/client/chat/tool-call-display.js +136 -42
  279. package/dist/client/chat/tool-call-display.js.map +1 -1
  280. package/dist/client/code-agent-chat-adapter.js +2 -9
  281. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  282. package/dist/client/extensions/ExtensionSlot.js +6 -0
  283. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  284. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  285. package/dist/client/settings/SettingsPanel.js +52 -19
  286. package/dist/client/settings/SettingsPanel.js.map +1 -1
  287. package/dist/client/settings/SettingsSection.d.ts +19 -4
  288. package/dist/client/settings/SettingsSection.d.ts.map +1 -1
  289. package/dist/client/settings/SettingsSection.js +33 -4
  290. package/dist/client/settings/SettingsSection.js.map +1 -1
  291. package/dist/client/sse-event-processor.d.ts +9 -0
  292. package/dist/client/sse-event-processor.d.ts.map +1 -1
  293. package/dist/client/sse-event-processor.js +20 -2
  294. package/dist/client/sse-event-processor.js.map +1 -1
  295. package/dist/collab/routes.d.ts +1 -1
  296. package/dist/collab/struct-routes.d.ts +1 -1
  297. package/dist/extensions/actions.d.ts.map +1 -1
  298. package/dist/extensions/actions.js +85 -5
  299. package/dist/extensions/actions.js.map +1 -1
  300. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  301. package/dist/jobs/scheduler.js +26 -1
  302. package/dist/jobs/scheduler.js.map +1 -1
  303. package/dist/localization/default-messages.d.ts +19 -0
  304. package/dist/localization/default-messages.d.ts.map +1 -1
  305. package/dist/localization/default-messages.js +19 -0
  306. package/dist/localization/default-messages.js.map +1 -1
  307. package/dist/notifications/channels.d.ts +8 -3
  308. package/dist/notifications/channels.d.ts.map +1 -1
  309. package/dist/notifications/channels.js +61 -22
  310. package/dist/notifications/channels.js.map +1 -1
  311. package/dist/notifications/registry.d.ts.map +1 -1
  312. package/dist/notifications/registry.js +14 -3
  313. package/dist/notifications/registry.js.map +1 -1
  314. package/dist/notifications/routes.d.ts +1 -1
  315. package/dist/notifications/types.d.ts +4 -1
  316. package/dist/notifications/types.d.ts.map +1 -1
  317. package/dist/notifications/types.js.map +1 -1
  318. package/dist/observability/routes.d.ts +1 -1
  319. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  320. package/dist/resources/handlers.d.ts +3 -3
  321. package/dist/server/agent-chat-plugin.d.ts +5 -1
  322. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  323. package/dist/server/agent-chat-plugin.js +2 -2
  324. package/dist/server/agent-chat-plugin.js.map +1 -1
  325. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  326. package/dist/server/request-context.d.ts +7 -0
  327. package/dist/server/request-context.d.ts.map +1 -1
  328. package/dist/server/request-context.js.map +1 -1
  329. package/dist/styles/agent-conversation.css +1 -1
  330. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  331. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  332. package/dist/templates/default/AGENTS.md +10 -3
  333. package/dist/templates/default/app/i18n/ar-SA.ts +21 -0
  334. package/dist/templates/default/app/i18n/de-DE.ts +21 -0
  335. package/dist/templates/default/app/i18n/en-US.ts +21 -0
  336. package/dist/templates/default/app/i18n/es-ES.ts +21 -0
  337. package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
  338. package/dist/templates/default/app/i18n/hi-IN.ts +20 -0
  339. package/dist/templates/default/app/i18n/ja-JP.ts +21 -0
  340. package/dist/templates/default/app/i18n/ko-KR.ts +20 -0
  341. package/dist/templates/default/app/i18n/pt-BR.ts +21 -0
  342. package/dist/templates/default/app/i18n/zh-CN.ts +20 -0
  343. package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
  344. package/dist/templates/default/package.json +4 -3
  345. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  346. package/dist/templates/headless/AGENTS.md +9 -3
  347. package/dist/templates/headless/package.json +4 -3
  348. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  349. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  350. package/dist/templates/workspace-core/package.json +6 -6
  351. package/dist/templates/workspace-root/AGENTS.md +10 -4
  352. package/dist/templates/workspace-root/package.json +3 -2
  353. package/dist/triggers/dispatcher.js +27 -2
  354. package/dist/triggers/dispatcher.js.map +1 -1
  355. package/dist/vite/client.d.ts.map +1 -1
  356. package/dist/vite/client.js +70 -1
  357. package/dist/vite/client.js.map +1 -1
  358. package/docs/content/faq.mdx +17 -0
  359. package/docs/content/notifications.mdx +12 -6
  360. package/package.json +7 -6
  361. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +20 -2
  362. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  363. package/src/templates/default/AGENTS.md +10 -3
  364. package/src/templates/default/app/i18n/ar-SA.ts +21 -0
  365. package/src/templates/default/app/i18n/de-DE.ts +21 -0
  366. package/src/templates/default/app/i18n/en-US.ts +21 -0
  367. package/src/templates/default/app/i18n/es-ES.ts +21 -0
  368. package/src/templates/default/app/i18n/fr-FR.ts +21 -0
  369. package/src/templates/default/app/i18n/hi-IN.ts +20 -0
  370. package/src/templates/default/app/i18n/ja-JP.ts +21 -0
  371. package/src/templates/default/app/i18n/ko-KR.ts +20 -0
  372. package/src/templates/default/app/i18n/pt-BR.ts +21 -0
  373. package/src/templates/default/app/i18n/zh-CN.ts +20 -0
  374. package/src/templates/default/app/i18n/zh-TW.ts +19 -0
  375. package/src/templates/default/package.json +4 -3
  376. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  377. package/src/templates/headless/AGENTS.md +9 -3
  378. package/src/templates/headless/package.json +4 -3
  379. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +20 -2
  380. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
  381. package/src/templates/workspace-core/package.json +6 -6
  382. package/src/templates/workspace-root/AGENTS.md +10 -4
  383. package/src/templates/workspace-root/package.json +3 -2
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Meeting reminder visibility policy (Granola-aligned).
3
+ *
4
+ * Show the overlay from 1 minute before start through 5 minutes after start,
5
+ * unless the user dismisses it or takes an action.
6
+ */
7
+
8
+ export const MEETING_NOTIFY_LEAD_MS = 60_000;
9
+ export const MEETING_NOTIFY_HOLD_AFTER_START_MS = 5 * 60_000;
10
+
11
+ /** True when `now` is inside the show window for a meeting that starts at `startMs`. */
12
+ export function isMeetingNotificationWindowOpen(
13
+ startMs: number,
14
+ nowMs: number = Date.now(),
15
+ ): boolean {
16
+ if (!Number.isFinite(startMs)) return false;
17
+ const earliest = startMs - MEETING_NOTIFY_LEAD_MS;
18
+ const latest = startMs + MEETING_NOTIFY_HOLD_AFTER_START_MS;
19
+ return nowMs >= earliest && nowMs <= latest;
20
+ }
21
+
22
+ /**
23
+ * Milliseconds until the overlay should auto-hide, assuming it is currently
24
+ * visible. Returns 0 when the window has already closed.
25
+ */
26
+ export function meetingNotificationAutoHideMs(
27
+ startMs: number,
28
+ nowMs: number = Date.now(),
29
+ ): number {
30
+ if (!Number.isFinite(startMs)) return 0;
31
+ const latest = startMs + MEETING_NOTIFY_HOLD_AFTER_START_MS;
32
+ return Math.max(0, latest - nowMs);
33
+ }
34
+
35
+ export type MeetingJoinProvider = "zoom" | "meet" | "teams" | "webex" | "other";
36
+
37
+ export function detectMeetingJoinProvider(
38
+ joinUrl: string | null | undefined,
39
+ platform?: string | null,
40
+ ): MeetingJoinProvider {
41
+ const haystack = `${platform ?? ""} ${joinUrl ?? ""}`.toLowerCase();
42
+ if (haystack.includes("zoom")) return "zoom";
43
+ if (haystack.includes("meet.google") || /\bmeet\b/.test(haystack))
44
+ return "meet";
45
+ if (haystack.includes("teams.microsoft") || haystack.includes("teams"))
46
+ return "teams";
47
+ if (haystack.includes("webex")) return "webex";
48
+ return "other";
49
+ }
50
+
51
+ export function joinProviderLabel(provider: MeetingJoinProvider): string {
52
+ switch (provider) {
53
+ case "zoom":
54
+ return "Zoom";
55
+ case "meet":
56
+ return "Meet";
57
+ case "teams":
58
+ return "Teams";
59
+ case "webex":
60
+ return "Webex";
61
+ default:
62
+ return "meeting";
63
+ }
64
+ }
65
+
66
+ /** Format a local time range like "9:00 AM - 9:30 AM". */
67
+ export function formatMeetingTimeRange(
68
+ startIso: string | null | undefined,
69
+ endIso: string | null | undefined,
70
+ locale?: string,
71
+ ): string | null {
72
+ if (!startIso) return null;
73
+ const start = Date.parse(startIso);
74
+ if (!Number.isFinite(start)) return null;
75
+ const end = endIso ? Date.parse(endIso) : NaN;
76
+ const fmt = new Intl.DateTimeFormat(locale, {
77
+ hour: "numeric",
78
+ minute: "2-digit",
79
+ });
80
+ const startLabel = fmt.format(new Date(start));
81
+ if (!Number.isFinite(end)) return startLabel;
82
+ return `${startLabel} - ${fmt.format(new Date(end))}`;
83
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Hosted native start sequencing: overlap Whisper start, create-recording, and
3
+ * deferred SCK warm so Skip no longer waits serially on Whisper then warm.
4
+ * Begin/attach must still wait for this promise (transcription settled).
5
+ */
6
+ export function planNativeFullscreenWarmOverlap<
7
+ TRecording extends { id: string },
8
+ >(input: {
9
+ createRecording: () => Promise<TRecording>;
10
+ startTranscription: () => Promise<unknown>;
11
+ warmMic: (recordingId: string) => Promise<unknown>;
12
+ }): Promise<TRecording> {
13
+ const transcriptionPromise = input.startTranscription();
14
+ return (async () => {
15
+ const created = await input.createRecording();
16
+ // Deferred-output warm may overlap remaining Whisper startup; frames are
17
+ // not written until begin attaches the recording output.
18
+ await Promise.all([transcriptionPromise, input.warmMic(created.id)]);
19
+ return created;
20
+ })();
21
+ }
@@ -69,6 +69,7 @@ import {
69
69
  getAudioStreamWithFallback,
70
70
  getCameraStreamWithFallback,
71
71
  } from "./media-capture-constraints";
72
+ import { planNativeFullscreenWarmOverlap } from "./native-recording-warm";
72
73
  import { buildCaptureTitle, type CaptureTitleResult } from "./recording-title";
73
74
  import {
74
75
  startTranscriptionCapture,
@@ -77,6 +78,7 @@ import {
77
78
  } from "./transcription-capture";
78
79
 
79
80
  export type { LocalExportedFile } from "./local-export";
81
+ export { planNativeFullscreenWarmOverlap } from "./native-recording-warm";
80
82
 
81
83
  export type CaptureMode = "screen" | "screen-camera" | "camera";
82
84
  export type CaptureSource = "full-screen" | "window" | "region";
@@ -1677,6 +1679,11 @@ async function openNativeUploadUrl(
1677
1679
  }
1678
1680
  }
1679
1681
 
1682
+ /**
1683
+ * Hosted native start sequencing helper: overlap Whisper start, create-recording,
1684
+ * and deferred SCK warm so Skip no longer waits serially on Whisper then warm.
1685
+ * `begin` / attach still waits for transcription to settle first.
1686
+ */
1680
1687
  function abortCreatedRecordingOnCountdownCancel(
1681
1688
  err: unknown,
1682
1689
  recordingPromise: Promise<{ id: string }>,
@@ -1847,19 +1854,42 @@ async function startNativeFullscreenRecording(
1847
1854
  }).catch((err) => {
1848
1855
  console.warn("[clips-recorder] mic warm failed:", err);
1849
1856
  });
1857
+ const clickStartedAt = Date.now();
1850
1858
  if (localOnly) {
1859
+ // Local recordings have no create-recording round-trip; still overlap
1860
+ // Whisper startup with countdown + deferred SCK warm.
1851
1861
  const countdownPromise = runRecordingCountdown(true);
1852
1862
  id = localFolderName;
1853
- await Promise.all([countdownPromise, warmMic(id)]);
1863
+ const transcriptionPromise = startNativeTranscriptionBeforeRecording();
1864
+ const warmPromise = (async () => {
1865
+ const warmStartedAt = Date.now();
1866
+ await warmMic(id);
1867
+ console.log(
1868
+ `[clips-recorder] native warm durations: warmMs=${Date.now() - warmStartedAt}`,
1869
+ );
1870
+ })();
1871
+ try {
1872
+ await Promise.all([
1873
+ countdownPromise,
1874
+ transcriptionPromise,
1875
+ warmPromise,
1876
+ ]);
1877
+ } catch (err) {
1878
+ await transcriptionPromise.catch(() => {});
1879
+ throw err;
1880
+ }
1854
1881
  } else {
1855
1882
  const captureTitlePromise = captureTitleForRecording({
1856
1883
  mode: params.mode,
1857
1884
  source: params.source,
1858
1885
  });
1859
1886
  const countdownPromise = runRecordingCountdown(true);
1887
+ // Kick Whisper off immediately (no recording id needed) so Skip no longer
1888
+ // serializes create → Whisper → SCK warm on the critical path.
1889
+ const transcriptionPromise = startNativeTranscriptionBeforeRecording();
1860
1890
  const recordingPromise = (async () => {
1861
1891
  const captureTitle = await captureTitlePromise;
1862
- console.time("[clips-recorder] createServerRecording duration");
1892
+ const createStartedAt = Date.now();
1863
1893
  try {
1864
1894
  return await createServerRecording(
1865
1895
  params.serverUrl,
@@ -1873,26 +1903,47 @@ async function startNativeFullscreenRecording(
1873
1903
  },
1874
1904
  );
1875
1905
  } finally {
1876
- console.timeEnd("[clips-recorder] createServerRecording duration");
1906
+ console.log(
1907
+ `[clips-recorder] createServerRecording durationMs=${Date.now() - createStartedAt}`,
1908
+ );
1877
1909
  }
1878
1910
  })();
1879
- // The recording id usually lands well before the countdown ends — warm
1880
- // native capture as soon as it does so setup overlaps the 3-2-1.
1881
- const warmAndId = (async () => {
1882
- const createRes = await recordingPromise;
1883
- uploadMode = createRes.uploadMode;
1884
- id = createRes.id;
1885
- // Start the live transcription mic tap before SCK attaches its
1886
- // recording output. On macOS, opening AVAudioEngine after SCK has
1887
- // started writing can reconfigure the shared input device and leave
1888
- // SCK's microphone leg near-silent while Whisper still hears audio.
1889
- await startNativeTranscriptionBeforeRecording();
1890
- await warmMic(createRes.id);
1891
- return createRes.id;
1892
- })();
1911
+ // Once create returns an id, warm SCK with deferred_output in parallel
1912
+ // with any remaining Whisper startup. Begin/attach still waits on
1913
+ // transcription settling first (AVAudioEngine after SCK writing can mute
1914
+ // the SCK mic leg).
1915
+ const warmAndId = planNativeFullscreenWarmOverlap({
1916
+ createRecording: async () => {
1917
+ const createRes = await recordingPromise;
1918
+ uploadMode = createRes.uploadMode;
1919
+ id = createRes.id;
1920
+ return createRes;
1921
+ },
1922
+ startTranscription: async () => {
1923
+ const transcriptionStartedAt = Date.now();
1924
+ try {
1925
+ await transcriptionPromise;
1926
+ } finally {
1927
+ console.log(
1928
+ `[clips-recorder] transcription warm durationMs=${Date.now() - transcriptionStartedAt}`,
1929
+ );
1930
+ }
1931
+ },
1932
+ warmMic: async (recordingId) => {
1933
+ const warmStartedAt = Date.now();
1934
+ try {
1935
+ await warmMic(recordingId);
1936
+ } finally {
1937
+ console.log(
1938
+ `[clips-recorder] native warm durationMs=${Date.now() - warmStartedAt}`,
1939
+ );
1940
+ }
1941
+ },
1942
+ });
1893
1943
  try {
1894
- const [, warmedId] = await Promise.all([countdownPromise, warmAndId]);
1895
- id = warmedId;
1944
+ const [, createRes] = await Promise.all([countdownPromise, warmAndId]);
1945
+ id = createRes.id;
1946
+ uploadMode = createRes.uploadMode ?? uploadMode;
1896
1947
  } catch (err) {
1897
1948
  abortCreatedRecordingOnCountdownCancel(
1898
1949
  err,
@@ -1905,11 +1956,16 @@ async function startNativeFullscreenRecording(
1905
1956
 
1906
1957
  await audioCue.playBeforeCapture();
1907
1958
  // Phase 2: attach the recording output now that the mic is warm (or do a
1908
- // normal immediate start if warming was skipped/failed).
1959
+ // normal immediate start if warming was skipped/failed). Transcription has
1960
+ // already been awaited above so AVAudioEngine won't reconfigure mid-write.
1961
+ const beginStartedAt = Date.now();
1909
1962
  await invoke("native_fullscreen_recording_begin", {
1910
1963
  recordingId: id,
1911
1964
  ...captureAudioParams,
1912
1965
  });
1966
+ console.log(
1967
+ `[clips-recorder] native begin durationMs=${Date.now() - beginStartedAt} clickToLiveMs=${Date.now() - clickStartedAt}`,
1968
+ );
1913
1969
  await transcriptionCapture?.resetTimeline().catch((err) => {
1914
1970
  console.warn(
1915
1971
  "[clips-recorder] transcription timeline reset failed:",
@@ -2021,7 +2077,26 @@ async function startNativeFullscreenRecording(
2021
2077
  stopPromise = (async () => {
2022
2078
  stopped = true;
2023
2079
  console.log("[clips-recorder] native full-screen stop requested");
2080
+ // Tear chrome down immediately with finalizing so the live camera bubble /
2081
+ // toolbar don't linger while ScreencaptureKit finalize + upload run.
2082
+ // hide_recording_chrome leaves the bubble; close_bubble destroys it.
2083
+ // Order matters: show finalizing first, and never call hide_overlays here
2084
+ // because that also closes the finalizing window.
2024
2085
  if (!localOnly) showFinalizingFeedback();
2086
+ await invoke("hide_recording_chrome").catch((err) =>
2087
+ console.error(
2088
+ "[clips-recorder] immediate hide_recording_chrome after stop failed:",
2089
+ err,
2090
+ ),
2091
+ );
2092
+ if (wantsCamera) {
2093
+ await invoke("close_bubble").catch((err) =>
2094
+ console.error(
2095
+ "[clips-recorder] immediate close_bubble after stop failed:",
2096
+ err,
2097
+ ),
2098
+ );
2099
+ }
2025
2100
  clearSegmentRotator();
2026
2101
  if (tickHandle) {
2027
2102
  clearInterval(tickHandle);
@@ -3226,6 +3301,9 @@ async function startRecordingInner(
3226
3301
  label: params.micLabel,
3227
3302
  },
3228
3303
  wantsSystemAudio,
3304
+ // Match native path: VoiceProcessingIO AEC/ducking on a shared mic
3305
+ // can tank live call volume and attenuate the recorded mic leg.
3306
+ { voiceProcessing: false },
3229
3307
  )
3230
3308
  : null;
3231
3309
  // Stop/Cancel can fire during the await above — at that point stop()/cancel()
@@ -1,16 +1,33 @@
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";
9
+ import { LogicalSize } from "@tauri-apps/api/dpi";
3
10
  import { emit, listen } from "@tauri-apps/api/event";
4
11
  import { getCurrentWindow } from "@tauri-apps/api/window";
5
12
  import { open as openExternal } from "@tauri-apps/plugin-shell";
6
13
  import { useEffect, useRef, useState } from "react";
7
14
 
15
+ import {
16
+ detectMeetingJoinProvider,
17
+ joinProviderLabel,
18
+ meetingNotificationAutoHideMs,
19
+ type MeetingJoinProvider,
20
+ } from "../lib/meeting-notification-timing";
21
+
8
22
  interface NotificationData {
9
23
  type: "calendar" | "adhoc";
10
24
  title: string;
11
25
  subtitle: string;
12
26
  meetingId: string;
13
27
  joinUrl?: string | null;
28
+ platform?: string | null;
29
+ scheduledStart?: string | null;
30
+ scheduledEnd?: string | null;
14
31
  autoStart?: boolean;
15
32
  }
16
33
 
@@ -19,12 +36,14 @@ interface TranscriptionStatusPayload {
19
36
  error?: string;
20
37
  }
21
38
 
22
- const DEFAULT_AUTO_HIDE_MS = 30_000;
23
39
  const SNOOZE_MS = 5 * 60_000;
40
+ const FALLBACK_AUTO_HIDE_MS = 6 * 60_000;
41
+ const NOTIFICATION_WINDOW_WIDTH = 440;
42
+ const NOTIFICATION_COLLAPSED_HEIGHT = 120;
43
+ const NOTIFICATION_MENU_HEIGHT = 224;
24
44
 
25
45
  /**
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).
46
+ * Open a meeting join URL via the Tauri shell plugin.
28
47
  */
29
48
  async function openJoinUrl(url: string | null | undefined): Promise<void> {
30
49
  if (!url) return;
@@ -35,40 +54,90 @@ async function openJoinUrl(url: string | null | undefined): Promise<void> {
35
54
  }
36
55
  }
37
56
 
57
+ function resizeNotificationWindow(expanded: boolean) {
58
+ const height = expanded
59
+ ? NOTIFICATION_MENU_HEIGHT
60
+ : NOTIFICATION_COLLAPSED_HEIGHT;
61
+ getCurrentWindow()
62
+ .setSize(new LogicalSize(NOTIFICATION_WINDOW_WIDTH, height))
63
+ .catch((err) => {
64
+ console.warn("[clips-meeting-notif] resize failed", err);
65
+ });
66
+ }
67
+
68
+ function ProviderGlyph({ provider }: { provider: MeetingJoinProvider }) {
69
+ // Lightweight glyphs — keep the overlay free of extra assets. Zoom blue
70
+ // camera / Meet green / Teams purple, otherwise a generic video icon.
71
+ if (provider === "zoom") {
72
+ return (
73
+ <span
74
+ className="meeting-notification-provider meeting-notification-provider-zoom"
75
+ aria-hidden
76
+ >
77
+ <IconVideo size={14} stroke={2.2} />
78
+ </span>
79
+ );
80
+ }
81
+ if (provider === "meet") {
82
+ return (
83
+ <span
84
+ className="meeting-notification-provider meeting-notification-provider-meet"
85
+ aria-hidden
86
+ >
87
+ <IconVideo size={14} stroke={2.2} />
88
+ </span>
89
+ );
90
+ }
91
+ if (provider === "teams") {
92
+ return (
93
+ <span
94
+ className="meeting-notification-provider meeting-notification-provider-teams"
95
+ aria-hidden
96
+ >
97
+ <IconVideo size={14} stroke={2.2} />
98
+ </span>
99
+ );
100
+ }
101
+ return (
102
+ <span
103
+ className="meeting-notification-provider meeting-notification-provider-other"
104
+ aria-hidden
105
+ >
106
+ <IconNotes size={14} stroke={2.2} />
107
+ </span>
108
+ );
109
+ }
110
+
38
111
  /**
39
112
  * Granola-style meeting notification — small card in the top-right corner.
40
- * Variants:
41
113
  *
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.
114
+ * Primary split button: join the call and open Clips notes in one click.
115
+ * Chevron exposes secondary actions (join only / notes only / snooze).
46
116
  *
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.
117
+ * Data arrives via Tauri event `meetings:show-notification`. Visibility holds
118
+ * from 1 minute before start until 5 minutes after, unless dismissed.
51
119
  */
52
120
  export function MeetingNotification() {
53
121
  const [data, setData] = useState<NotificationData | null>(null);
54
122
  const [showClose, setShowClose] = useState(false);
123
+ const [menuOpen, setMenuOpen] = useState(false);
55
124
  const [error, setError] = useState<string | null>(null);
56
125
  const [pending, setPending] = useState(false);
57
126
  const autoHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
58
127
  const dataRef = useRef<NotificationData | null>(null);
59
128
 
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
129
  useEffect(() => {
64
130
  dataRef.current = data;
65
131
  }, [data]);
66
132
 
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.
133
+ useEffect(() => {
134
+ resizeNotificationWindow(Boolean(data && menuOpen));
135
+ }, [data, menuOpen]);
136
+
137
+ useEffect(() => {
138
+ return () => resizeNotificationWindow(false);
139
+ }, []);
140
+
72
141
  useEffect(() => {
73
142
  const win = getCurrentWindow();
74
143
  if (data) {
@@ -84,8 +153,15 @@ export function MeetingNotification() {
84
153
  ) {
85
154
  setData(payload);
86
155
  setError(null);
156
+ setMenuOpen(false);
87
157
  setPending(!!payload.autoStart && !options?.hydrated);
88
- scheduleAutoHide(DEFAULT_AUTO_HIDE_MS);
158
+ const startMs = payload.scheduledStart
159
+ ? Date.parse(payload.scheduledStart)
160
+ : NaN;
161
+ const hideMs = Number.isFinite(startMs)
162
+ ? meetingNotificationAutoHideMs(startMs)
163
+ : FALLBACK_AUTO_HIDE_MS;
164
+ scheduleAutoHide(hideMs);
89
165
  }
90
166
 
91
167
  useEffect(() => {
@@ -112,6 +188,15 @@ export function MeetingNotification() {
112
188
  }),
113
189
  );
114
190
 
191
+ // Cold overlay boot: hydrate any payload stored before this webview
192
+ // mounted (calendar or adhoc).
193
+ invoke<NotificationData | null>("take_pending_meeting_notification")
194
+ .then((pending) => {
195
+ if (stopped || !pending) return;
196
+ showNotification(pending, { hydrated: true });
197
+ })
198
+ .catch(() => {});
199
+
115
200
  trackListen(
116
201
  listen<TranscriptionStatusPayload>("meetings:hide-notification", (ev) => {
117
202
  if (ev.payload.meetingId !== dataRef.current?.meetingId) return;
@@ -154,14 +239,30 @@ export function MeetingNotification() {
154
239
 
155
240
  function scheduleAutoHide(ms: number) {
156
241
  clearAutoHide();
242
+ if (ms <= 0) {
243
+ hideNotification();
244
+ return;
245
+ }
157
246
  autoHideTimerRef.current = setTimeout(() => hideNotification(), ms);
158
247
  }
159
248
 
249
+ function resumeAutoHide() {
250
+ const current = dataRef.current;
251
+ const startMs = current?.scheduledStart
252
+ ? Date.parse(current.scheduledStart)
253
+ : NaN;
254
+ const hideMs = Number.isFinite(startMs)
255
+ ? meetingNotificationAutoHideMs(startMs)
256
+ : FALLBACK_AUTO_HIDE_MS;
257
+ scheduleAutoHide(hideMs);
258
+ }
259
+
160
260
  function hideNotification() {
161
261
  clearAutoHide();
162
262
  setData(null);
163
263
  setError(null);
164
264
  setPending(false);
265
+ setMenuOpen(false);
165
266
  dataRef.current = null;
166
267
  }
167
268
 
@@ -169,6 +270,7 @@ export function MeetingNotification() {
169
270
  if (!data || pending) return;
170
271
  setPending(true);
171
272
  setError(null);
273
+ setMenuOpen(false);
172
274
  emit("meetings:start-transcription", {
173
275
  meetingId: data.meetingId,
174
276
  joinUrl: data.joinUrl,
@@ -181,14 +283,23 @@ export function MeetingNotification() {
181
283
 
182
284
  async function joinMeeting() {
183
285
  if (!data?.joinUrl) return;
286
+ setMenuOpen(false);
184
287
  await openJoinUrl(data.joinUrl);
185
288
  }
186
289
 
290
+ /** Granola primary: join the call and start Clips notes together. */
291
+ async function joinAndOpenClips() {
292
+ if (!data || pending) return;
293
+ setMenuOpen(false);
294
+ if (data.joinUrl) {
295
+ await openJoinUrl(data.joinUrl);
296
+ }
297
+ await takeNotes();
298
+ }
299
+
187
300
  function snooze() {
188
301
  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.
302
+ setMenuOpen(false);
192
303
  invoke("meetings_snooze", {
193
304
  meetingId: data.meetingId,
194
305
  minutes: Math.round(SNOOZE_MS / 60_000),
@@ -202,6 +313,14 @@ export function MeetingNotification() {
202
313
 
203
314
  const isCalendar = data.type === "calendar";
204
315
  const hasJoin = Boolean(data.joinUrl);
316
+ const provider = detectMeetingJoinProvider(data.joinUrl, data.platform);
317
+ const providerName = joinProviderLabel(provider);
318
+ const primaryLabel = hasJoin
319
+ ? provider === "other"
320
+ ? "Join meeting"
321
+ : `Join ${providerName}`
322
+ : "Start notes";
323
+ const secondaryLabel = hasJoin ? "& open Clips" : null;
205
324
 
206
325
  return (
207
326
  <div
@@ -212,14 +331,11 @@ export function MeetingNotification() {
212
331
  }}
213
332
  onMouseLeave={() => {
214
333
  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);
334
+ setMenuOpen(false);
335
+ resumeAutoHide();
218
336
  }}
219
337
  >
220
- <div
221
- className={`meeting-notification${hasJoin ? " meeting-notification-with-join" : ""}`}
222
- >
338
+ <div className="meeting-notification">
223
339
  <div
224
340
  className={`meeting-notification-bar ${isCalendar ? "meeting-notification-bar-calendar" : "meeting-notification-bar-adhoc"}`}
225
341
  />
@@ -233,34 +349,65 @@ export function MeetingNotification() {
233
349
  </div>
234
350
  ) : null}
235
351
  </div>
236
- <div className="meeting-notification-actions">
352
+ <div className="meeting-notification-split">
237
353
  <button
238
- className="meeting-notification-btn meeting-notification-btn-primary"
239
- onClick={takeNotes}
354
+ className="meeting-notification-split-main"
355
+ onClick={hasJoin ? joinAndOpenClips : takeNotes}
240
356
  disabled={pending}
241
357
  data-no-drag
242
358
  >
243
- {pending ? "Starting…" : "Start notes"}
359
+ <ProviderGlyph provider={provider} />
360
+ <span className="meeting-notification-split-copy">
361
+ <span className="meeting-notification-split-primary">
362
+ {pending ? "Starting…" : primaryLabel}
363
+ </span>
364
+ {secondaryLabel && !pending ? (
365
+ <span className="meeting-notification-split-secondary">
366
+ {secondaryLabel}
367
+ </span>
368
+ ) : null}
369
+ </span>
244
370
  </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
371
  <button
255
- className="meeting-notification-btn meeting-notification-btn-secondary"
256
- onClick={snooze}
372
+ className="meeting-notification-split-chevron"
373
+ onClick={() => setMenuOpen((open) => !open)}
374
+ aria-label="More actions"
375
+ aria-expanded={menuOpen}
257
376
  data-no-drag
258
- aria-label="Snooze 5 minutes"
259
- title="Snooze 5 min"
260
377
  >
261
- <IconClock size={12} aria-hidden="true" />
262
- <span>5m</span>
378
+ <IconChevronDown size={14} aria-hidden="true" />
263
379
  </button>
380
+ {menuOpen ? (
381
+ <div className="meeting-notification-menu" role="menu">
382
+ {hasJoin ? (
383
+ <button
384
+ role="menuitem"
385
+ className="meeting-notification-menu-item"
386
+ onClick={joinMeeting}
387
+ data-no-drag
388
+ >
389
+ Join only
390
+ </button>
391
+ ) : null}
392
+ <button
393
+ role="menuitem"
394
+ className="meeting-notification-menu-item"
395
+ onClick={takeNotes}
396
+ disabled={pending}
397
+ data-no-drag
398
+ >
399
+ Start notes only
400
+ </button>
401
+ <button
402
+ role="menuitem"
403
+ className="meeting-notification-menu-item"
404
+ onClick={snooze}
405
+ data-no-drag
406
+ >
407
+ Snooze 5 min
408
+ </button>
409
+ </div>
410
+ ) : null}
264
411
  </div>
265
412
  {showClose ? (
266
413
  <button