@agent-native/core 0.120.1 → 0.120.3

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 (396) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/docs/content/extensions.mdx +23 -4
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +4 -0
  6. package/corpus/core/src/agent/run-manager.ts +36 -10
  7. package/corpus/core/src/agent/types.ts +2 -0
  8. package/corpus/core/src/cli/context-xray-local.ts +1 -1
  9. package/corpus/core/src/cli/index.ts +63 -18
  10. package/corpus/core/src/cli/skills.ts +5 -0
  11. package/corpus/core/src/cli/telemetry.ts +118 -1
  12. package/corpus/core/src/client/AgentPanel.tsx +10 -0
  13. package/corpus/core/src/client/CommandMenu.tsx +13 -1
  14. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
  15. package/corpus/core/src/client/analytics.ts +13 -0
  16. package/corpus/core/src/client/error-capture.ts +6 -1
  17. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +28 -8
  18. package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  19. package/corpus/core/src/client/navigation/index.ts +2 -0
  20. package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
  21. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  22. package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
  23. package/corpus/core/src/deploy/build.ts +3 -0
  24. package/corpus/core/src/extensions/theme.ts +1 -1
  25. package/corpus/core/src/localization/default-messages.ts +2 -0
  26. package/corpus/core/src/server/analytics.ts +154 -25
  27. package/corpus/core/src/server/core-routes-plugin.ts +10 -1
  28. package/corpus/core/src/server/framework-request-handler.ts +17 -26
  29. package/corpus/core/src/styles/agent-native.css +13 -0
  30. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  31. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  32. package/corpus/core/src/templates/chat/app/global.css +1 -1
  33. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  34. package/corpus/core/src/templates/chat/netlify.toml +1 -0
  35. package/corpus/core/src/templates/default/app/global.css +1 -1
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  37. package/corpus/core/src/tracking/error-capture.ts +151 -0
  38. package/corpus/core/src/tracking/index.ts +5 -0
  39. package/corpus/core/src/vite/client.ts +6 -0
  40. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
  41. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
  42. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
  43. package/corpus/templates/analytics/AGENTS.md +20 -8
  44. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
  45. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
  46. package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
  47. package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
  48. package/corpus/templates/analytics/actions/view-screen.ts +1 -1
  49. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
  50. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
  51. package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
  52. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
  53. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
  54. package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
  55. package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
  56. package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
  57. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
  58. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
  59. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
  60. package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
  61. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
  62. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
  63. package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
  64. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  65. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +255 -626
  66. package/corpus/templates/analytics/app/global.css +31 -1
  67. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
  68. package/corpus/templates/analytics/app/i18n-data.ts +80 -0
  69. package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
  70. package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
  71. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
  72. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
  73. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
  74. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
  75. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
  76. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
  77. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
  78. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
  79. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  80. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +4 -54
  81. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +178 -69
  82. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  83. package/corpus/templates/analytics/app/root.tsx +28 -0
  84. package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
  85. package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
  86. package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
  87. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
  88. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
  90. package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
  91. package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  92. package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  95. package/corpus/templates/analytics/netlify.toml +4 -0
  96. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
  97. package/corpus/templates/analytics/server/db/schema.ts +9 -0
  98. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
  99. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
  100. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
  101. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
  102. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
  103. package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
  104. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
  105. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
  106. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
  107. package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
  108. package/corpus/templates/analytics/server/plugins/db.ts +19 -0
  109. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
  110. package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
  111. package/corpus/templates/assets/actions/generate-image.ts +28 -1
  112. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
  113. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
  114. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  115. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +164 -82
  116. package/corpus/templates/assets/app/global.css +1 -1
  117. package/corpus/templates/assets/app/i18n-data.ts +10 -0
  118. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
  119. package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
  120. package/corpus/templates/assets/app/routes/library.tsx +21 -198
  121. package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
  122. package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
  123. package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  124. package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  125. package/corpus/templates/assets/netlify.toml +1 -0
  126. package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
  127. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +114 -51
  128. package/corpus/templates/brain/app/global.css +1 -1
  129. package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
  130. package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  131. package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  132. package/corpus/templates/brain/netlify.toml +1 -0
  133. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +110 -57
  134. package/corpus/templates/calendar/app/global.css +1 -1
  135. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
  136. package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  137. package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  138. package/corpus/templates/calendar/netlify.toml +1 -0
  139. package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
  140. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  141. package/corpus/templates/chat/app/global.css +1 -1
  142. package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
  143. package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  144. package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  145. package/corpus/templates/chat/netlify.toml +1 -0
  146. package/corpus/templates/clips/app/components/library/library-layout.tsx +117 -37
  147. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
  148. package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
  149. package/corpus/templates/clips/app/global.css +1 -1
  150. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
  151. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
  152. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
  153. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
  154. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
  155. package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  156. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
  157. package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  158. package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  159. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
  160. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
  161. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
  162. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
  163. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
  164. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
  165. package/corpus/templates/clips/desktop/src/styles.css +35 -3
  166. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
  167. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
  168. package/corpus/templates/clips/netlify.toml +1 -0
  169. package/corpus/templates/clips/server/plugins/db.ts +113 -68
  170. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +69 -49
  171. package/corpus/templates/content/app/global.css +1 -1
  172. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
  173. package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  174. package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  175. package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  176. package/corpus/templates/content/netlify.toml +1 -0
  177. package/corpus/templates/design/app/components/layout/Sidebar.tsx +115 -42
  178. package/corpus/templates/design/app/entry.client.tsx +14 -1
  179. package/corpus/templates/design/app/global.css +1 -1
  180. package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
  181. package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  184. package/corpus/templates/design/netlify.toml +1 -0
  185. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
  186. package/corpus/templates/dispatch/app/global.css +1 -1
  187. package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  188. package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  189. package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  190. package/corpus/templates/dispatch/netlify.toml +1 -0
  191. package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
  192. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +113 -82
  193. package/corpus/templates/forms/app/global.css +1 -1
  194. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
  195. package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  196. package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  197. package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
  198. package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  199. package/corpus/templates/forms/netlify.toml +1 -0
  200. package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
  201. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  202. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  203. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  204. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +92 -34
  205. package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
  206. package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
  207. package/corpus/templates/macros/app/global.css +1 -1
  208. package/corpus/templates/macros/app/routes/_index.tsx +1 -1
  209. package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
  210. package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
  211. package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  212. package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  213. package/corpus/templates/macros/netlify.toml +1 -0
  214. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -46
  215. package/corpus/templates/mail/app/global.css +1 -1
  216. package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
  217. package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  218. package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  219. package/corpus/templates/mail/netlify.toml +1 -0
  220. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
  221. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +128 -65
  222. package/corpus/templates/plan/app/global.css +1 -1
  223. package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
  224. package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  225. package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  226. package/corpus/templates/plan/netlify.toml +1 -0
  227. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +116 -36
  228. package/corpus/templates/slides/app/global.css +1 -1
  229. package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
  230. package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  231. package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  232. package/corpus/templates/slides/netlify.toml +1 -0
  233. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
  234. package/corpus/templates/tasks/app/global.css +1 -1
  235. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
  236. package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
  237. package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  238. package/corpus/templates/tasks/netlify.toml +1 -0
  239. package/corpus/toolkit/CHANGELOG.md +8 -0
  240. package/corpus/toolkit/README.md +8 -1
  241. package/corpus/toolkit/agent-native.eject.json +1 -1
  242. package/corpus/toolkit/package.json +1 -1
  243. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  244. package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
  245. package/corpus/toolkit/src/chat-history.css +52 -1
  246. package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
  247. package/corpus/toolkit/src/ui/button.tsx +1 -2
  248. package/corpus/toolkit/src/ui/card.tsx +1 -1
  249. package/corpus/toolkit/src/ui/table.tsx +4 -11
  250. package/dist/agent/production-agent.d.ts.map +1 -1
  251. package/dist/agent/production-agent.js +4 -0
  252. package/dist/agent/production-agent.js.map +1 -1
  253. package/dist/agent/run-manager.d.ts +9 -0
  254. package/dist/agent/run-manager.d.ts.map +1 -1
  255. package/dist/agent/run-manager.js +25 -10
  256. package/dist/agent/run-manager.js.map +1 -1
  257. package/dist/agent/types.d.ts +2 -2
  258. package/dist/agent/types.d.ts.map +1 -1
  259. package/dist/agent/types.js.map +1 -1
  260. package/dist/cli/context-xray-local.js +1 -1
  261. package/dist/cli/context-xray-local.js.map +1 -1
  262. package/dist/cli/index.js +56 -17
  263. package/dist/cli/index.js.map +1 -1
  264. package/dist/cli/skills.d.ts.map +1 -1
  265. package/dist/cli/skills.js +5 -0
  266. package/dist/cli/skills.js.map +1 -1
  267. package/dist/cli/telemetry.d.ts +7 -0
  268. package/dist/cli/telemetry.d.ts.map +1 -1
  269. package/dist/cli/telemetry.js +91 -1
  270. package/dist/cli/telemetry.js.map +1 -1
  271. package/dist/client/AgentPanel.d.ts +7 -1
  272. package/dist/client/AgentPanel.d.ts.map +1 -1
  273. package/dist/client/AgentPanel.js +2 -2
  274. package/dist/client/AgentPanel.js.map +1 -1
  275. package/dist/client/CommandMenu.d.ts +2 -0
  276. package/dist/client/CommandMenu.d.ts.map +1 -1
  277. package/dist/client/CommandMenu.js +12 -1
  278. package/dist/client/CommandMenu.js.map +1 -1
  279. package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
  280. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  281. package/dist/client/agent-page/AgentTabsPage.js +8 -4
  282. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  283. package/dist/client/analytics.d.ts.map +1 -1
  284. package/dist/client/analytics.js +7 -0
  285. package/dist/client/analytics.js.map +1 -1
  286. package/dist/client/error-capture.d.ts.map +1 -1
  287. package/dist/client/error-capture.js +8 -1
  288. package/dist/client/error-capture.js.map +1 -1
  289. package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
  290. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  291. package/dist/client/extensions/ExtensionsListPage.js +6 -4
  292. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  293. package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
  294. package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
  295. package/dist/client/navigation/index.d.ts +1 -1
  296. package/dist/client/navigation/index.d.ts.map +1 -1
  297. package/dist/client/navigation/index.js +1 -1
  298. package/dist/client/navigation/index.js.map +1 -1
  299. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  300. package/dist/client/org/OrgSwitcher.js +9 -13
  301. package/dist/client/org/OrgSwitcher.js.map +1 -1
  302. package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
  303. package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
  304. package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
  305. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
  306. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  307. package/dist/client/settings/SettingsPanel.js +10 -1
  308. package/dist/client/settings/SettingsPanel.js.map +1 -1
  309. package/dist/deploy/build.js +1 -0
  310. package/dist/deploy/build.js.map +1 -1
  311. package/dist/extensions/theme.js +1 -1
  312. package/dist/extensions/theme.js.map +1 -1
  313. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  314. package/dist/localization/default-messages.d.ts +2 -0
  315. package/dist/localization/default-messages.d.ts.map +1 -1
  316. package/dist/localization/default-messages.js +2 -0
  317. package/dist/localization/default-messages.js.map +1 -1
  318. package/dist/observability/routes.d.ts +3 -3
  319. package/dist/progress/routes.d.ts +1 -1
  320. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  321. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  322. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  323. package/dist/resources/handlers.d.ts +1 -1
  324. package/dist/server/analytics.d.ts +9 -4
  325. package/dist/server/analytics.d.ts.map +1 -1
  326. package/dist/server/analytics.js +111 -22
  327. package/dist/server/analytics.js.map +1 -1
  328. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  329. package/dist/server/core-routes-plugin.js +8 -1
  330. package/dist/server/core-routes-plugin.js.map +1 -1
  331. package/dist/server/framework-request-handler.d.ts.map +1 -1
  332. package/dist/server/framework-request-handler.js +17 -27
  333. package/dist/server/framework-request-handler.js.map +1 -1
  334. package/dist/server/realtime-token.d.ts +1 -1
  335. package/dist/styles/agent-native.css +13 -0
  336. package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
  337. package/dist/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  338. package/dist/templates/chat/app/global.css +1 -1
  339. package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
  340. package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  341. package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  342. package/dist/templates/chat/netlify.toml +1 -0
  343. package/dist/templates/default/app/global.css +1 -1
  344. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  345. package/dist/tracking/error-capture.d.ts +14 -0
  346. package/dist/tracking/error-capture.d.ts.map +1 -0
  347. package/dist/tracking/error-capture.js +113 -0
  348. package/dist/tracking/error-capture.js.map +1 -0
  349. package/dist/tracking/index.d.ts +1 -0
  350. package/dist/tracking/index.d.ts.map +1 -1
  351. package/dist/tracking/index.js +1 -0
  352. package/dist/tracking/index.js.map +1 -1
  353. package/dist/vite/client.d.ts.map +1 -1
  354. package/dist/vite/client.js +2 -0
  355. package/dist/vite/client.js.map +1 -1
  356. package/docs/content/extensions.mdx +23 -4
  357. package/package.json +2 -2
  358. package/src/agent/production-agent.ts +4 -0
  359. package/src/agent/run-manager.ts +36 -10
  360. package/src/agent/types.ts +2 -0
  361. package/src/cli/context-xray-local.ts +1 -1
  362. package/src/cli/index.ts +63 -18
  363. package/src/cli/skills.ts +5 -0
  364. package/src/cli/telemetry.ts +118 -1
  365. package/src/client/AgentPanel.tsx +10 -0
  366. package/src/client/CommandMenu.tsx +13 -1
  367. package/src/client/agent-page/AgentTabsPage.tsx +39 -4
  368. package/src/client/analytics.ts +13 -0
  369. package/src/client/error-capture.ts +6 -1
  370. package/src/client/extensions/ExtensionsListPage.tsx +28 -8
  371. package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  372. package/src/client/navigation/index.ts +2 -0
  373. package/src/client/org/OrgSwitcher.tsx +14 -32
  374. package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  375. package/src/client/settings/SettingsPanel.tsx +10 -0
  376. package/src/deploy/build.ts +3 -0
  377. package/src/extensions/theme.ts +1 -1
  378. package/src/localization/default-messages.ts +2 -0
  379. package/src/server/analytics.ts +154 -25
  380. package/src/server/core-routes-plugin.ts +10 -1
  381. package/src/server/framework-request-handler.ts +17 -26
  382. package/src/styles/agent-native.css +13 -0
  383. package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  384. package/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  385. package/src/templates/chat/app/global.css +1 -1
  386. package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  387. package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  388. package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  389. package/src/templates/chat/netlify.toml +1 -0
  390. package/src/templates/default/app/global.css +1 -1
  391. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  392. package/src/tracking/error-capture.ts +151 -0
  393. package/src/tracking/index.ts +5 -0
  394. package/src/vite/client.ts +6 -0
  395. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
  396. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
@@ -0,0 +1,86 @@
1
+ import { listen } from "@tauri-apps/api/event";
2
+ import { useEffect, useRef, useState } from "react";
3
+
4
+ const BAR_SHAPES = [0.52, 1, 0.68];
5
+ const LEVEL_DECAY = 0.78;
6
+ const EVENT_ATTACK_DECAY = 0.52;
7
+
8
+ interface LiveAudioBarsProps {
9
+ className?: string;
10
+ compact?: boolean;
11
+ }
12
+
13
+ /** Small Granola-style meter shared by the compact and expanded overlays. */
14
+ export function LiveAudioBars({
15
+ className,
16
+ compact = false,
17
+ }: LiveAudioBarsProps) {
18
+ const [level, setLevel] = useState(0);
19
+ const levelRef = useRef(0);
20
+
21
+ useEffect(() => {
22
+ let stopped = false;
23
+ let unlisten: (() => void) | null = null;
24
+
25
+ const decayTimer = window.setInterval(() => {
26
+ const next = levelRef.current * LEVEL_DECAY;
27
+ levelRef.current = next < 0.01 ? 0 : next;
28
+ setLevel(levelRef.current);
29
+ }, 60);
30
+
31
+ listen<{ level?: number }>("voice:audio-level", (event) => {
32
+ const incoming = Number(event.payload?.level);
33
+ if (!Number.isFinite(incoming)) return;
34
+ const next = Math.max(
35
+ levelRef.current * EVENT_ATTACK_DECAY,
36
+ Math.max(0, Math.min(1, incoming)),
37
+ );
38
+ levelRef.current = next;
39
+ setLevel(next);
40
+ })
41
+ .then((cleanup) => {
42
+ if (stopped) {
43
+ cleanup();
44
+ } else {
45
+ unlisten = cleanup;
46
+ }
47
+ })
48
+ .catch(() => {});
49
+
50
+ return () => {
51
+ stopped = true;
52
+ window.clearInterval(decayTimer);
53
+ unlisten?.();
54
+ };
55
+ }, []);
56
+
57
+ // Peak levels from speech taps are often quiet even when speech is clear.
58
+ // A gentle curve keeps the meter responsive without turning background noise
59
+ // into a full-height signal.
60
+ const visualLevel = level > 0 ? Math.min(1, Math.pow(level, 0.52) * 1.08) : 0;
61
+ const rootClassName = [
62
+ "live-audio-bars",
63
+ compact ? "live-audio-bars-compact" : null,
64
+ className,
65
+ ]
66
+ .filter(Boolean)
67
+ .join(" ");
68
+
69
+ return (
70
+ <span className={rootClassName} aria-hidden="true">
71
+ {BAR_SHAPES.map((shape, index) => {
72
+ const idleHeight = index === 1 ? 0.2 : 0.14;
73
+ const height = Math.round(
74
+ (idleHeight + visualLevel * shape * 0.8) * 100,
75
+ );
76
+ return (
77
+ <span
78
+ className="live-audio-bar"
79
+ key={index}
80
+ style={{ height: `${Math.max(12, height)}%` }}
81
+ />
82
+ );
83
+ })}
84
+ </span>
85
+ );
86
+ }
@@ -39,6 +39,7 @@ interface TranscriptionStatusPayload {
39
39
 
40
40
  const SNOOZE_MS = 5 * 60_000;
41
41
  const FALLBACK_AUTO_HIDE_MS = 6 * 60_000;
42
+ const DISMISSAL_TOMBSTONE_MS = 30 * 60_000;
42
43
  // Card is up to 440px wide; the extra width leaves room for the drop shadow
43
44
  // (~32px each side) so it isn't clipped by the transparent window edges.
44
45
  const NOTIFICATION_WINDOW_WIDTH = 504;
@@ -128,6 +129,7 @@ export function MeetingNotification() {
128
129
  const [pending, setPending] = useState(false);
129
130
  const autoHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
130
131
  const dataRef = useRef<NotificationData | null>(null);
132
+ const dismissedKeysRef = useRef(new Map<string, number>());
131
133
  // Real DOM hover only fires while this overlay window is key, which macOS
132
134
  // won't grant it without a click (`show_without_activation` never
133
135
  // activates). `polledHovered` mirrors the Rust-side global cursor poll
@@ -139,6 +141,21 @@ export function MeetingNotification() {
139
141
  const hovered = domHovered || polledHovered;
140
142
  const prevHoveredRef = useRef(false);
141
143
 
144
+ function notificationKey(payload: NotificationData): string {
145
+ return [payload.type, payload.meetingId, payload.scheduledStart ?? ""].join(
146
+ "|",
147
+ );
148
+ }
149
+
150
+ function isDismissed(payload: NotificationData): boolean {
151
+ const now = Date.now();
152
+ const dismissed = dismissedKeysRef.current;
153
+ for (const [key, expiresAt] of dismissed) {
154
+ if (expiresAt <= now) dismissed.delete(key);
155
+ }
156
+ return dismissed.has(notificationKey(payload));
157
+ }
158
+
142
159
  useEffect(() => {
143
160
  dataRef.current = data;
144
161
  }, [data]);
@@ -188,6 +205,7 @@ export function MeetingNotification() {
188
205
  payload: NotificationData,
189
206
  options?: { hydrated?: boolean },
190
207
  ) {
208
+ if (isDismissed(payload)) return;
191
209
  setData(payload);
192
210
  setError(null);
193
211
  setMenuOpen(false);
@@ -311,6 +329,12 @@ export function MeetingNotification() {
311
329
 
312
330
  function dismissNotification() {
313
331
  const current = dataRef.current;
332
+ if (current) {
333
+ dismissedKeysRef.current.set(
334
+ notificationKey(current),
335
+ Date.now() + DISMISSAL_TOMBSTONE_MS,
336
+ );
337
+ }
314
338
  hideNotification();
315
339
  if (current) {
316
340
  void dismissMeetingNotification(current);
@@ -17,6 +17,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
17
17
 
18
18
  import { isDirectPillClick, type ScreenPoint } from "../lib/pill-interaction";
19
19
  import { speakerFor } from "../lib/transcription-engine";
20
+ import { LiveAudioBars } from "./live-audio-bars";
20
21
  import { LiveTranscript, type FinalLine } from "./live-transcript";
21
22
  import { PillLogo } from "./pill-logo";
22
23
 
@@ -65,9 +66,6 @@ export function RecordingPill() {
65
66
  const tickRef = useRef<ReturnType<typeof setInterval> | null>(null);
66
67
  // Mic and system audio share one calm activity meter, matching Granola's
67
68
  // single indicator for the combined meeting capture.
68
- const levelRef = useRef(0);
69
- const canvasRef = useRef<HTMLCanvasElement | null>(null);
70
- const rafRef = useRef<number | null>(null);
71
69
  const stopFallbackRef = useRef<ReturnType<typeof setTimeout> | null>(null);
72
70
  const dragStartScreenPointRef = useRef<ScreenPoint | null>(null);
73
71
 
@@ -165,15 +163,6 @@ export function RecordingPill() {
165
163
  if (ev.payload?.detached) setExpanded(false);
166
164
  }),
167
165
  );
168
- trackListen(
169
- listen<{ level: number; source?: "mic" | "system" }>(
170
- "voice:audio-level",
171
- (ev) => {
172
- const lvl = Math.max(0, Math.min(1, ev.payload.level));
173
- levelRef.current = lvl;
174
- },
175
- ),
176
- );
177
166
  // Signal that all listeners are registered. app.tsx listens for this and
178
167
  // re-emits the pill context and transcript preload for a fresh window.
179
168
  emit("clips:pill-ready", {}).catch(() => {});
@@ -207,62 +196,6 @@ export function RecordingPill() {
207
196
  };
208
197
  }, [ctx.mode, paused]);
209
198
 
210
- // One combined "dancing bars" meter — a few discrete vertical bars instead
211
- // of separate mic and system waveforms.
212
- useEffect(() => {
213
- const canvas = canvasRef.current;
214
- if (!canvas) return;
215
- const N_BARS = 3;
216
- const dpr = window.devicePixelRatio || 1;
217
- const W = canvas.clientWidth;
218
- const H = canvas.clientHeight;
219
- canvas.width = W * dpr;
220
- canvas.height = H * dpr;
221
- const ctx2d = canvas.getContext("2d");
222
- if (!ctx2d) return;
223
- ctx2d.scale(dpr, dpr);
224
- const slot = W / N_BARS;
225
- const gap = Math.max(2, slot * 0.3);
226
- const barWidth = Math.max(3, slot - gap);
227
- const centerY = H / 2;
228
- const startMs = Date.now();
229
- let lastDrawMs = 0;
230
- const FRAME_INTERVAL_MS = 1000 / 20;
231
- const tick = () => {
232
- rafRef.current = requestAnimationFrame(tick);
233
- const nowMs = Date.now();
234
- if (nowMs - lastDrawMs < FRAME_INTERVAL_MS) return;
235
- lastDrawMs = nowMs;
236
- const t = (nowMs - startMs) % 1_000_000;
237
- const target = Math.min(1, levelRef.current);
238
- ctx2d.clearRect(0, 0, W, H);
239
- ctx2d.fillStyle = "rgba(132, 204, 22, 0.98)";
240
- ctx2d.shadowColor = "rgba(132, 204, 22, 0.48)";
241
- ctx2d.shadowBlur = 4;
242
- for (let i = 0; i < N_BARS; i += 1) {
243
- const phase = t * 0.005 + i * (Math.PI * 0.65);
244
- const barTarget = 0.2 + Math.sin(phase) * 0.42 * target + target * 0.38;
245
- const h = Math.max(4, Math.min(1, barTarget) * H * 0.92);
246
- const x = i * (barWidth + gap) + gap / 2;
247
- const y = centerY - h / 2;
248
- const radius = Math.min(barWidth / 2, 3);
249
- ctx2d.beginPath();
250
- if (typeof ctx2d.roundRect === "function") {
251
- ctx2d.roundRect(x, y, barWidth, h, radius);
252
- } else {
253
- ctx2d.rect(x, y, barWidth, h);
254
- }
255
- ctx2d.fill();
256
- }
257
- ctx2d.shadowBlur = 0;
258
- };
259
- tick();
260
- return () => {
261
- if (rafRef.current) cancelAnimationFrame(rafRef.current);
262
- rafRef.current = null;
263
- };
264
- }, [expanded]);
265
-
266
199
  // Let the compact chip land first, then reveal the live transcript once per
267
200
  // meeting. The delay keeps the indicator from feeling like a sudden panel.
268
201
  useEffect(() => {
@@ -380,7 +313,10 @@ export function RecordingPill() {
380
313
  onClick={!expanded && !detached ? handlePillMediaClick : undefined}
381
314
  >
382
315
  <PillLogo className="pill-logo" />
383
- <canvas ref={canvasRef} className="pill-wave-canvas" aria-hidden />
316
+ <LiveAudioBars
317
+ compact={!expanded && !detached}
318
+ className="pill-wave-meter"
319
+ />
384
320
  </div>
385
321
  <div className="pill-controls">
386
322
  <span className="pill-timer">
@@ -805,13 +805,45 @@ body[data-clips-route="recording-pill"] #root {
805
805
  fill: none;
806
806
  /* Matches the recording pill's mic waveform accent. */
807
807
  stroke: rgba(74, 222, 128, 0.95);
808
- stroke-width: 1.75;
808
+ stroke-width: 1.25;
809
809
  stroke-linecap: round;
810
810
  stroke-linejoin: round;
811
811
  /* Keep the line crisp regardless of the non-uniform x-stretch. */
812
812
  vector-effect: non-scaling-stroke;
813
813
  }
814
814
 
815
+ .live-audio-bars {
816
+ display: inline-flex;
817
+ width: 48px;
818
+ height: 26px;
819
+ align-items: center;
820
+ justify-content: center;
821
+ gap: 4px;
822
+ flex-shrink: 0;
823
+ }
824
+
825
+ .live-audio-bars-compact {
826
+ width: 22px;
827
+ height: 22px;
828
+ gap: 3px;
829
+ }
830
+
831
+ .live-audio-bar {
832
+ display: block;
833
+ width: 2px;
834
+ min-height: 3px;
835
+ border-radius: 999px;
836
+ background: rgba(132, 204, 22, 0.98);
837
+ box-shadow: 0 0 5px rgba(132, 204, 22, 0.35);
838
+ transition: height 70ms ease-out;
839
+ }
840
+
841
+ @media (prefers-reduced-motion: reduce) {
842
+ .live-audio-bar {
843
+ transition: none;
844
+ }
845
+ }
846
+
815
847
  /* ------------------------------------------------------------------------- */
816
848
  /* Primary CTA */
817
849
  /* ------------------------------------------------------------------------- */
@@ -5534,7 +5566,7 @@ body[data-clips-route="recording-pill"] #root {
5534
5566
  display: none;
5535
5567
  }
5536
5568
 
5537
- .pill-vertical .pill-wave-canvas {
5569
+ .pill-vertical .pill-wave-meter {
5538
5570
  height: 22px;
5539
5571
  width: 22px;
5540
5572
  }
@@ -5569,7 +5601,7 @@ body[data-clips-route="recording-pill"] #root {
5569
5601
  font-variant-numeric: tabular-nums;
5570
5602
  }
5571
5603
 
5572
- .pill-wave-canvas {
5604
+ .pill-wave-meter {
5573
5605
  height: 26px;
5574
5606
  width: 48px;
5575
5607
  flex-shrink: 0;
@@ -2,8 +2,9 @@
2
2
  //!
3
3
  //! Polls the frontmost macOS app every few seconds. When Zoom or Teams stays
4
4
  //! frontmost for a short dwell window, creates a meeting row via
5
- //! `create-meeting` and shows the same meeting-notification overlay used for
6
- //! calendar reminders with `type: "adhoc"`.
5
+ //! `create-meeting`, and silently starts transcription when the user has
6
+ //! explicitly selected Auto mode. Ask/manual mode stays quiet because a
7
+ //! frontmost Zoom window is not reliable evidence of an active call.
7
8
  //!
8
9
  //! Reuses `MeetingsWatcherState` session (server URL + cookie + auth token)
9
10
  //! so the popover only needs to push credentials once.
@@ -157,7 +158,7 @@ async fn tick_macos(
157
158
  let front = crate::util::frontmost_bundle_id();
158
159
  let matched = front.as_deref().and_then(match_vc_bundle);
159
160
 
160
- let Some((platform, title)) = matched else {
161
+ let Some((platform, _title)) = matched else {
161
162
  // VC app left front — clear session dedupe so a later re-focus can
162
163
  // fire again (cooldown still applies).
163
164
  clear_session_if_left(app, None);
@@ -173,9 +174,7 @@ async fn tick_macos(
173
174
  return Ok(());
174
175
  }
175
176
 
176
- if config.meeting_transcription_mode == MeetingTranscriptionMode::Manual
177
- && !config.show_meeting_widget_enabled
178
- {
177
+ if config.meeting_transcription_mode != MeetingTranscriptionMode::Auto {
179
178
  reset_dwell(app);
180
179
  return Ok(());
181
180
  }
@@ -250,44 +249,14 @@ async fn tick_macos(
250
249
  }
251
250
  };
252
251
 
253
- let auto_start = config.meeting_transcription_mode == MeetingTranscriptionMode::Auto;
254
- let show_widget = config.show_meeting_widget_enabled
255
- || config.meeting_transcription_mode == MeetingTranscriptionMode::Ask
256
- || auto_start;
257
-
258
- if show_widget {
259
- let app_clone = app.clone();
260
- let id_clone = meeting_id.clone();
261
- let title_clone = title.to_string();
262
- let platform_clone = platform.to_string();
263
- let scheduled_start = chrono::Utc::now().to_rfc3339();
264
- tauri::async_runtime::spawn(async move {
265
- let _ = crate::notifications::notify_meeting_starting(
266
- app_clone,
267
- id_clone,
268
- title_clone,
269
- 0,
270
- None,
271
- Some(scheduled_start),
272
- None,
273
- Some(platform_clone),
274
- Some(auto_start),
275
- Some("adhoc".to_string()),
276
- )
277
- .await;
278
- });
279
- }
280
-
281
- if auto_start {
282
- let _ = app.emit(
283
- "meetings:start-transcription",
284
- serde_json::json!({
285
- "meetingId": meeting_id,
286
- "joinUrl": null,
287
- "reason": "adhoc-auto",
288
- }),
289
- );
290
- }
252
+ let _ = app.emit(
253
+ "meetings:start-transcription",
254
+ serde_json::json!({
255
+ "meetingId": meeting_id,
256
+ "joinUrl": null,
257
+ "reason": "adhoc-auto",
258
+ }),
259
+ );
291
260
 
292
261
  Ok(())
293
262
  }
@@ -6,6 +6,7 @@
6
6
 
7
7
  use serde::{Deserialize, Serialize};
8
8
  use serde_json::Value;
9
+ use std::collections::HashMap;
9
10
  use std::sync::atomic::{AtomicBool, Ordering};
10
11
  use std::sync::Mutex;
11
12
  use std::time::Duration;
@@ -28,9 +29,16 @@ const NOTIFICATION_W_LOGICAL: u32 = 504;
28
29
  const NOTIFICATION_H_LOGICAL: u32 = 120;
29
30
  const NOTIFICATION_TOP_MARGIN_LOGICAL: u32 = 44;
30
31
  const NOTIFICATION_RIGHT_MARGIN_LOGICAL: u32 = 0;
32
+ const DISMISSAL_TOMBSTONE_SECS: i64 = 30 * 60;
31
33
 
32
34
  #[derive(Default)]
33
- pub struct MeetingNotificationState(pub Mutex<Option<Value>>);
35
+ pub struct MeetingNotificationState(Mutex<MeetingNotificationStateInner>);
36
+
37
+ #[derive(Default)]
38
+ struct MeetingNotificationStateInner {
39
+ pending: Option<Value>,
40
+ dismissed_until: HashMap<String, i64>,
41
+ }
34
42
 
35
43
  #[allow(dead_code)]
36
44
  #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -176,22 +184,54 @@ pub fn show_meeting_notification_window(app: &AppHandle) -> Result<(), String> {
176
184
  Ok(())
177
185
  }
178
186
 
179
- fn store_pending_meeting_notification(app: &AppHandle, payload: &Value) {
180
- if let Some(state) = app.try_state::<MeetingNotificationState>() {
181
- if let Ok(mut pending) = state.0.lock() {
182
- *pending = Some(payload.clone());
183
- }
184
- }
185
- }
186
-
187
187
  #[tauri::command]
188
188
  pub fn take_pending_meeting_notification(app: AppHandle) -> Result<Option<Value>, String> {
189
189
  let state = app.state::<MeetingNotificationState>();
190
- let mut pending = state
190
+ let mut state = state
191
191
  .0
192
192
  .lock()
193
193
  .map_err(|_| "meeting notification state lock poisoned".to_string())?;
194
- Ok(pending.take())
194
+ let now = chrono::Utc::now().timestamp();
195
+ state.dismissed_until.retain(|_, until| *until > now);
196
+ if state.pending.as_ref().is_some_and(|payload| {
197
+ state
198
+ .dismissed_until
199
+ .contains_key(&notification_key(payload))
200
+ }) {
201
+ state.pending = None;
202
+ }
203
+ Ok(state.pending.take())
204
+ }
205
+
206
+ fn notification_key(payload: &Value) -> String {
207
+ format!(
208
+ "{}|{}|{}",
209
+ payload
210
+ .get("type")
211
+ .and_then(Value::as_str)
212
+ .unwrap_or("calendar"),
213
+ payload
214
+ .get("meetingId")
215
+ .and_then(Value::as_str)
216
+ .unwrap_or_default(),
217
+ payload
218
+ .get("scheduledStart")
219
+ .and_then(Value::as_str)
220
+ .unwrap_or_default(),
221
+ )
222
+ }
223
+
224
+ fn notification_key_from_parts(
225
+ notification_type: &str,
226
+ meeting_id: &str,
227
+ scheduled_start: Option<&str>,
228
+ ) -> String {
229
+ format!(
230
+ "{}|{}|{}",
231
+ notification_type,
232
+ meeting_id,
233
+ scheduled_start.unwrap_or_default()
234
+ )
195
235
  }
196
236
 
197
237
  fn clear_pending_notification(pending: &mut Option<Value>, meeting_id: &str) -> bool {
@@ -211,19 +251,38 @@ pub fn dismiss_meeting_notification(
211
251
  app: AppHandle,
212
252
  meeting_id: String,
213
253
  notification_type: String,
254
+ join_url: Option<String>,
214
255
  platform: Option<String>,
215
256
  scheduled_start: Option<String>,
216
257
  scheduled_end: Option<String>,
217
258
  ) -> Result<(), String> {
218
259
  let state = app.state::<MeetingNotificationState>();
219
- let mut pending = state
260
+ let mut state = state
220
261
  .0
221
262
  .lock()
222
263
  .map_err(|_| "meeting notification state lock poisoned".to_string())?;
223
- clear_pending_notification(&mut pending, &meeting_id);
224
- drop(pending);
264
+ let now = chrono::Utc::now().timestamp();
265
+ state.dismissed_until.retain(|_, until| *until > now);
266
+ state.dismissed_until.insert(
267
+ notification_key_from_parts(&notification_type, &meeting_id, scheduled_start.as_deref()),
268
+ now + DISMISSAL_TOMBSTONE_SECS,
269
+ );
270
+ clear_pending_notification(&mut state.pending, &meeting_id);
271
+ drop(state);
225
272
 
226
- if let Some(platform) = platform.as_deref() {
273
+ let suppression_platform = platform.as_deref().or_else(|| {
274
+ join_url.as_deref().and_then(|url| {
275
+ let url = url.to_ascii_lowercase();
276
+ if url.contains("zoom") {
277
+ Some("zoom")
278
+ } else if url.contains("teams.microsoft") || url.contains("teams") {
279
+ Some("teams")
280
+ } else {
281
+ None
282
+ }
283
+ })
284
+ });
285
+ if let Some(platform) = suppression_platform {
227
286
  crate::adhoc_meetings_watcher::refresh_dismissal_suppression(&app, platform)?;
228
287
  }
229
288
 
@@ -312,14 +371,41 @@ pub async fn notify_meeting_starting(
312
371
  "type": kind,
313
372
  "title": title,
314
373
  "subtitle": body,
315
- "meetingId": meeting_id,
374
+ "meetingId": meeting_id.clone(),
316
375
  "joinUrl": join_url,
317
376
  "platform": platform,
318
377
  "scheduledStart": scheduled_start,
319
378
  "scheduledEnd": scheduled_end,
320
379
  "autoStart": auto_start.unwrap_or(false),
321
380
  });
322
- store_pending_meeting_notification(&app, &payload);
381
+ let state = app.state::<MeetingNotificationState>();
382
+ let should_show = {
383
+ let mut state = state
384
+ .0
385
+ .lock()
386
+ .map_err(|_| "meeting notification state lock poisoned".to_string())?;
387
+ let now = chrono::Utc::now().timestamp();
388
+ state.dismissed_until.retain(|_, until| *until > now);
389
+ if state
390
+ .dismissed_until
391
+ .get(&notification_key(&payload))
392
+ .copied()
393
+ .unwrap_or_default()
394
+ > now
395
+ {
396
+ false
397
+ } else {
398
+ state.pending = Some(payload.clone());
399
+ true
400
+ }
401
+ };
402
+ if !should_show {
403
+ dlog!(
404
+ "[clips-tray] skipped dismissed meeting notification id={}",
405
+ meeting_id
406
+ );
407
+ return Ok(());
408
+ }
323
409
  let _ = app.emit("meetings:show-notification", payload.clone());
324
410
 
325
411
  // Ensure the overlay window exists / is visible for cold starts.
@@ -346,4 +432,20 @@ mod tests {
346
432
  assert!(clear_pending_notification(&mut pending, "meeting-1"));
347
433
  assert!(pending.is_none());
348
434
  }
435
+
436
+ #[test]
437
+ fn dismissal_key_changes_when_a_meeting_is_rescheduled() {
438
+ let first = serde_json::json!({
439
+ "type": "calendar",
440
+ "meetingId": "meeting-1",
441
+ "scheduledStart": "2026-07-23T17:00:00Z"
442
+ });
443
+ let moved = serde_json::json!({
444
+ "type": "calendar",
445
+ "meetingId": "meeting-1",
446
+ "scheduledStart": "2026-07-23T18:00:00Z"
447
+ });
448
+
449
+ assert_ne!(notification_key(&first), notification_key(&moved));
450
+ }
349
451
  }
@@ -6,6 +6,7 @@ functions = "templates/clips/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
11
12
  AUTO_CREATE_DEFAULT_ORG = "1"