@agent-native/core 0.69.0 → 0.70.1

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 (336) hide show
  1. package/README.md +15 -15
  2. package/corpus/README.md +2 -2
  3. package/corpus/core/CHANGELOG.md +109 -0
  4. package/corpus/core/docs/design/durable-agent-runs.md +217 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/action.ts +89 -0
  7. package/corpus/core/src/agent/model-config.ts +0 -2
  8. package/corpus/core/src/agent/run-loop-with-resume.ts +48 -0
  9. package/corpus/core/src/changelog/parse.ts +245 -0
  10. package/corpus/core/src/cli/changelog.ts +199 -0
  11. package/corpus/core/src/cli/index.ts +19 -0
  12. package/corpus/core/src/cli/recap.ts +24 -1
  13. package/corpus/core/src/cli/skills.ts +74 -47
  14. package/corpus/core/src/client/AgentPanel.tsx +5 -3
  15. package/corpus/core/src/client/AssistantChat.tsx +8 -0
  16. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  17. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  18. package/corpus/core/src/client/analytics.ts +181 -0
  19. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  20. package/corpus/core/src/client/client-surface.ts +41 -0
  21. package/corpus/core/src/client/composer/TiptapComposer.tsx +123 -12
  22. package/corpus/core/src/client/feedback-context.ts +4 -0
  23. package/corpus/core/src/client/index.ts +12 -0
  24. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  25. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  26. package/corpus/core/src/client/sse-event-processor.ts +7 -1
  27. package/corpus/core/src/mcp/build-server.ts +57 -1
  28. package/corpus/core/src/mcp/embed-app.ts +197 -7
  29. package/corpus/core/src/mcp/oauth-route.ts +109 -13
  30. package/corpus/core/src/server/attribution.ts +211 -0
  31. package/corpus/core/src/server/auth.ts +6 -0
  32. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  33. package/corpus/core/src/server/builder-browser.ts +27 -1
  34. package/corpus/core/src/server/core-routes-plugin.ts +136 -0
  35. package/corpus/core/src/server/google-oauth.ts +19 -6
  36. package/corpus/core/src/server/onboarding-html.ts +35 -0
  37. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  38. package/corpus/core/src/styles/agent-conversation.css +21 -0
  39. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  40. package/corpus/core/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  41. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  42. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +22 -0
  43. package/corpus/templates/analytics/AGENTS.md +8 -1
  44. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  45. package/corpus/templates/analytics/actions/install-dashboard-template.ts +99 -4
  46. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  47. package/corpus/templates/analytics/actions/update-dashboard.ts +39 -8
  48. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -3
  49. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  50. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -3
  51. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  52. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -215
  53. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -6
  54. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  55. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  56. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  57. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  58. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  59. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -2
  60. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  61. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  62. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  63. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  64. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  65. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  66. package/corpus/templates/analytics/changelog/2026-06-23-long-dashboard-and-analysis-names-in-the-sidebar-now-truncat.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-23-open-any-dashboard-chart-full-screen-in-a-modal-from-the-pan.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  69. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  70. package/corpus/templates/analytics/server/db/index.ts +1 -1
  71. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  72. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  74. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  75. package/corpus/templates/assets/CHANGELOG.md +10 -0
  76. package/corpus/templates/assets/actions/generate-image.ts +24 -0
  77. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
  78. package/corpus/templates/assets/app/global.css +17 -1
  79. package/corpus/templates/assets/app/root.tsx +7 -1
  80. package/corpus/templates/assets/app/routes/_index.tsx +94 -14
  81. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  82. package/corpus/templates/assets/changelog/2026-06-23-chat-dates-in-the-sidebar-no-longer-wrap-onto-two-lines.md +6 -0
  83. package/corpus/templates/assets/changelog/2026-06-23-clicking-image-video-or-refine-on-the-start-screen-now-drops.md +6 -0
  84. package/corpus/templates/assets/changelog/2026-06-23-pick-your-image-generation-model-right-from-the-chat-model-m.md +6 -0
  85. package/corpus/templates/brain/CHANGELOG.md +10 -0
  86. package/corpus/templates/brain/app/root.tsx +7 -1
  87. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  88. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  89. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  90. package/corpus/templates/calendar/app/root.tsx +7 -1
  91. package/corpus/templates/chat/CHANGELOG.md +10 -0
  92. package/corpus/templates/chat/app/root.tsx +7 -1
  93. package/corpus/templates/clips/CHANGELOG.md +10 -0
  94. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -20
  95. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  96. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  97. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  98. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +72 -48
  99. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
  100. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
  101. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  102. package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +35 -17
  103. package/corpus/templates/clips/app/root.tsx +7 -1
  104. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  105. package/corpus/templates/clips/app/routes/record.tsx +78 -5
  106. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  107. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-06-23-removed-select-button-from-library-hover-a-clip-to-select-it.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-06-23-shared-video-links-now-play-for-anyone-without-signing-in.md +6 -0
  113. package/corpus/templates/clips/changelog/2026-06-23-skip-or-cancel-the-recording-countdown-with-buttons-beside-t.md +6 -0
  114. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-camera-bubble-now-looks-sharp-the-instant-it-ope.md +6 -0
  115. package/corpus/templates/clips/changelog/2026-06-23-the-desktop-recording-widget-now-grows-downward-only-when-yo.md +6 -0
  116. package/corpus/templates/clips/changelog/2026-06-23-the-recording-start-sound-is-now-a-softer-more-modern-chime.md +6 -0
  117. package/corpus/templates/clips/chrome-extension/public/manifest.json +17 -8
  118. package/corpus/templates/clips/chrome-extension/src/background.ts +769 -180
  119. package/corpus/templates/clips/chrome-extension/src/content-script.ts +177 -0
  120. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +466 -329
  121. package/corpus/templates/clips/chrome-extension/src/overlay.css +377 -0
  122. package/corpus/templates/clips/chrome-extension/src/overlay.html +12 -0
  123. package/corpus/templates/clips/chrome-extension/src/overlay.ts +330 -0
  124. package/corpus/templates/clips/chrome-extension/src/permission.html +227 -0
  125. package/corpus/templates/clips/chrome-extension/src/permission.ts +105 -0
  126. package/corpus/templates/clips/chrome-extension/src/popup.html +3 -0
  127. package/corpus/templates/clips/chrome-extension/src/popup.ts +50 -2
  128. package/corpus/templates/clips/chrome-extension/src/styles.css +50 -0
  129. package/corpus/templates/clips/chrome-extension/vite.config.ts +3 -0
  130. package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +41 -24
  131. package/corpus/templates/clips/desktop/src/lib/bubble-webrtc.ts +119 -2
  132. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  133. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  134. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +21 -2
  135. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  136. package/corpus/templates/clips/desktop/src/styles.css +119 -2
  137. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  138. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +140 -1
  139. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  140. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  141. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +85 -6
  142. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  143. package/corpus/templates/content/AGENTS.md +20 -3
  144. package/corpus/templates/content/CHANGELOG.md +10 -0
  145. package/corpus/templates/content/actions/_database-utils.ts +15 -0
  146. package/corpus/templates/content/actions/_property-utils.ts +371 -4
  147. package/corpus/templates/content/actions/configure-document-property.ts +35 -1
  148. package/corpus/templates/content/actions/create-content-database.ts +5 -1
  149. package/corpus/templates/content/actions/delete-document-property.ts +52 -2
  150. package/corpus/templates/content/actions/duplicate-document-property.ts +34 -17
  151. package/corpus/templates/content/actions/reorder-document-property.ts +79 -0
  152. package/corpus/templates/content/actions/set-document-property.ts +39 -0
  153. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +806 -0
  154. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +226 -68
  155. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +59 -30
  156. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +25 -1
  157. package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +180 -0
  158. package/corpus/templates/content/app/components/editor/blockFieldSaveRegistry.ts +179 -0
  159. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +244 -0
  160. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +132 -0
  161. package/corpus/templates/content/app/global.css +9 -0
  162. package/corpus/templates/content/app/hooks/use-document-properties.ts +21 -0
  163. package/corpus/templates/content/app/root.tsx +7 -1
  164. package/corpus/templates/content/server/db/schema.ts +30 -0
  165. package/corpus/templates/content/server/plugins/db.ts +87 -0
  166. package/corpus/templates/content/shared/api.ts +7 -0
  167. package/corpus/templates/content/shared/properties.ts +109 -0
  168. package/corpus/templates/design/CHANGELOG.md +10 -0
  169. package/corpus/templates/design/actions/generate-design.ts +4 -0
  170. package/corpus/templates/design/app/root.tsx +7 -1
  171. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  172. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  173. package/corpus/templates/dispatch/app/root.tsx +7 -1
  174. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +10 -0
  175. package/corpus/templates/forms/CHANGELOG.md +10 -0
  176. package/corpus/templates/forms/actions/export-responses.ts +6 -0
  177. package/corpus/templates/forms/actions/list-responses.ts +2 -0
  178. package/corpus/templates/forms/actions/response-insights.ts +2 -0
  179. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +138 -1
  180. package/corpus/templates/forms/app/root.tsx +7 -1
  181. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-the-page-each-submission-came-from-.md +6 -0
  182. package/corpus/templates/forms/changelog/2026-06-23-response-tables-now-show-whether-feedback-came-from-the-web-.md +6 -0
  183. package/corpus/templates/forms/server/db/schema.ts +7 -0
  184. package/corpus/templates/forms/server/handlers/submissions.ts +19 -2
  185. package/corpus/templates/forms/server/lib/integrations.ts +76 -2
  186. package/corpus/templates/forms/server/plugins/db.ts +20 -0
  187. package/corpus/templates/forms/shared/types.ts +12 -0
  188. package/corpus/templates/macros/CHANGELOG.md +10 -0
  189. package/corpus/templates/macros/app/root.tsx +7 -1
  190. package/corpus/templates/mail/CHANGELOG.md +10 -0
  191. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  192. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  193. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  194. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  195. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  196. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  197. package/corpus/templates/plan/AGENTS.md +4 -0
  198. package/corpus/templates/plan/CHANGELOG.md +10 -0
  199. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  200. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  201. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +3 -1
  202. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  203. package/corpus/templates/plan/app/root.tsx +7 -1
  204. package/corpus/templates/plan/changelog/2026-06-23-large-diagrams-in-a-plan-now-scroll-within-their-block-inste.md +6 -0
  205. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  206. package/corpus/templates/slides/CHANGELOG.md +10 -0
  207. package/corpus/templates/slides/app/root.tsx +7 -1
  208. package/corpus/templates/videos/CHANGELOG.md +4 -151
  209. package/corpus/templates/videos/app/root.tsx +7 -1
  210. package/dist/action.js +87 -0
  211. package/dist/action.js.map +1 -1
  212. package/dist/agent/engine/builder-engine.d.ts +1 -1
  213. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  214. package/dist/agent/model-config.d.ts +2 -2
  215. package/dist/agent/model-config.d.ts.map +1 -1
  216. package/dist/agent/model-config.js +0 -2
  217. package/dist/agent/model-config.js.map +1 -1
  218. package/dist/agent/run-loop-with-resume.d.ts +13 -0
  219. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  220. package/dist/agent/run-loop-with-resume.js +44 -0
  221. package/dist/agent/run-loop-with-resume.js.map +1 -1
  222. package/dist/changelog/parse.d.ts +70 -0
  223. package/dist/changelog/parse.d.ts.map +1 -0
  224. package/dist/changelog/parse.js +200 -0
  225. package/dist/changelog/parse.js.map +1 -0
  226. package/dist/cli/changelog.d.ts +4 -0
  227. package/dist/cli/changelog.d.ts.map +1 -0
  228. package/dist/cli/changelog.js +166 -0
  229. package/dist/cli/changelog.js.map +1 -0
  230. package/dist/cli/index.js +18 -0
  231. package/dist/cli/index.js.map +1 -1
  232. package/dist/cli/recap.d.ts.map +1 -1
  233. package/dist/cli/recap.js +22 -1
  234. package/dist/cli/recap.js.map +1 -1
  235. package/dist/cli/skills.d.ts +3 -3
  236. package/dist/cli/skills.d.ts.map +1 -1
  237. package/dist/cli/skills.js +74 -47
  238. package/dist/cli/skills.js.map +1 -1
  239. package/dist/client/AgentPanel.d.ts.map +1 -1
  240. package/dist/client/AgentPanel.js +5 -3
  241. package/dist/client/AgentPanel.js.map +1 -1
  242. package/dist/client/AssistantChat.d.ts +6 -1
  243. package/dist/client/AssistantChat.d.ts.map +1 -1
  244. package/dist/client/AssistantChat.js +2 -2
  245. package/dist/client/AssistantChat.js.map +1 -1
  246. package/dist/client/CommandMenu.d.ts +17 -1
  247. package/dist/client/CommandMenu.d.ts.map +1 -1
  248. package/dist/client/CommandMenu.js +49 -13
  249. package/dist/client/CommandMenu.js.map +1 -1
  250. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  251. package/dist/client/agent-chat-adapter.js +43 -6
  252. package/dist/client/agent-chat-adapter.js.map +1 -1
  253. package/dist/client/analytics.d.ts +18 -0
  254. package/dist/client/analytics.d.ts.map +1 -1
  255. package/dist/client/analytics.js +177 -0
  256. package/dist/client/analytics.js.map +1 -1
  257. package/dist/client/changelog/Changelog.d.ts +47 -0
  258. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  259. package/dist/client/changelog/Changelog.js +138 -0
  260. package/dist/client/changelog/Changelog.js.map +1 -0
  261. package/dist/client/client-surface.d.ts +17 -0
  262. package/dist/client/client-surface.d.ts.map +1 -0
  263. package/dist/client/client-surface.js +24 -0
  264. package/dist/client/client-surface.js.map +1 -0
  265. package/dist/client/composer/TiptapComposer.d.ts +26 -1
  266. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  267. package/dist/client/composer/TiptapComposer.js +27 -5
  268. package/dist/client/composer/TiptapComposer.js.map +1 -1
  269. package/dist/client/feedback-context.d.ts +3 -0
  270. package/dist/client/feedback-context.d.ts.map +1 -1
  271. package/dist/client/feedback-context.js +2 -0
  272. package/dist/client/feedback-context.js.map +1 -1
  273. package/dist/client/index.d.ts +3 -1
  274. package/dist/client/index.d.ts.map +1 -1
  275. package/dist/client/index.js +3 -1
  276. package/dist/client/index.js.map +1 -1
  277. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  278. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  279. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  280. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  281. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  282. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  283. package/dist/client/settings/useBuilderStatus.js +58 -10
  284. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  285. package/dist/client/sse-event-processor.d.ts.map +1 -1
  286. package/dist/client/sse-event-processor.js +7 -1
  287. package/dist/client/sse-event-processor.js.map +1 -1
  288. package/dist/mcp/build-server.d.ts +21 -0
  289. package/dist/mcp/build-server.d.ts.map +1 -1
  290. package/dist/mcp/build-server.js +43 -1
  291. package/dist/mcp/build-server.js.map +1 -1
  292. package/dist/mcp/embed-app.d.ts.map +1 -1
  293. package/dist/mcp/embed-app.js +197 -7
  294. package/dist/mcp/embed-app.js.map +1 -1
  295. package/dist/mcp/oauth-route.d.ts.map +1 -1
  296. package/dist/mcp/oauth-route.js +95 -12
  297. package/dist/mcp/oauth-route.js.map +1 -1
  298. package/dist/server/attribution.d.ts +83 -0
  299. package/dist/server/attribution.d.ts.map +1 -0
  300. package/dist/server/attribution.js +178 -0
  301. package/dist/server/attribution.js.map +1 -0
  302. package/dist/server/auth.d.ts.map +1 -1
  303. package/dist/server/auth.js +7 -0
  304. package/dist/server/auth.js.map +1 -1
  305. package/dist/server/better-auth-instance.d.ts +9 -1
  306. package/dist/server/better-auth-instance.d.ts.map +1 -1
  307. package/dist/server/better-auth-instance.js +32 -2
  308. package/dist/server/better-auth-instance.js.map +1 -1
  309. package/dist/server/builder-browser.d.ts +4 -0
  310. package/dist/server/builder-browser.d.ts.map +1 -1
  311. package/dist/server/builder-browser.js +20 -1
  312. package/dist/server/builder-browser.js.map +1 -1
  313. package/dist/server/core-routes-plugin.d.ts +25 -0
  314. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  315. package/dist/server/core-routes-plugin.js +101 -0
  316. package/dist/server/core-routes-plugin.js.map +1 -1
  317. package/dist/server/google-oauth.d.ts.map +1 -1
  318. package/dist/server/google-oauth.js +16 -2
  319. package/dist/server/google-oauth.js.map +1 -1
  320. package/dist/server/onboarding-html.d.ts.map +1 -1
  321. package/dist/server/onboarding-html.js +35 -0
  322. package/dist/server/onboarding-html.js.map +1 -1
  323. package/dist/shared/mcp-embed-headers.js +1 -1
  324. package/dist/shared/mcp-embed-headers.js.map +1 -1
  325. package/dist/styles/agent-conversation.css +21 -0
  326. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  327. package/dist/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  328. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  329. package/docs/design/durable-agent-runs.md +217 -0
  330. package/package.json +1 -1
  331. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  332. package/src/templates/workspace-core/.agents/skills/reliable-mutations/SKILL.md +72 -0
  333. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  334. package/corpus/templates/analytics/app/pages/About.tsx +0 -108
  335. package/corpus/templates/analytics/app/routes/about.tsx +0 -9
  336. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
@@ -0,0 +1,377 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ }
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ background: transparent;
16
+ overflow: hidden;
17
+ font-family:
18
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
19
+ sans-serif;
20
+ }
21
+
22
+ #root {
23
+ width: 100%;
24
+ height: 100%;
25
+ }
26
+
27
+ /* ---------- Camera bubble ---------- */
28
+
29
+ .bubble {
30
+ position: absolute;
31
+ inset: 0;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ }
36
+
37
+ .bubble-ring {
38
+ width: 100%;
39
+ height: 100%;
40
+ border-radius: 50%;
41
+ overflow: hidden;
42
+ background: #0f172a;
43
+ box-shadow:
44
+ 0 10px 30px rgba(15, 23, 42, 0.35),
45
+ 0 0 0 4px rgba(255, 255, 255, 0.92);
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ }
50
+
51
+ .bubble-ring video {
52
+ width: 100%;
53
+ height: 100%;
54
+ object-fit: cover;
55
+ transform: scaleX(-1);
56
+ }
57
+
58
+ .bubble-empty {
59
+ color: rgba(255, 255, 255, 0.7);
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ }
64
+
65
+ .bubble-empty svg {
66
+ width: 38%;
67
+ height: 38%;
68
+ }
69
+
70
+ /* ---------- Countdown ---------- */
71
+
72
+ .countdown {
73
+ position: absolute;
74
+ inset: 0;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ background: rgba(2, 6, 23, 0.55);
79
+ backdrop-filter: blur(2px);
80
+ }
81
+
82
+ .countdown-controls {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ gap: clamp(28px, 8vmin, 72px);
87
+ }
88
+
89
+ .countdown-control {
90
+ display: inline-flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ flex: none;
94
+ width: clamp(56px, 9vmin, 76px);
95
+ height: clamp(56px, 9vmin, 76px);
96
+ border: 1px solid rgba(255, 255, 255, 0.25);
97
+ border-radius: 999px;
98
+ background: rgba(255, 255, 255, 0.06);
99
+ color: #fff;
100
+ cursor: pointer;
101
+ -webkit-backdrop-filter: blur(6px);
102
+ backdrop-filter: blur(6px);
103
+ transition:
104
+ background 0.15s ease,
105
+ border-color 0.15s ease,
106
+ transform 0.12s ease;
107
+ }
108
+
109
+ .countdown-control:hover {
110
+ background: rgba(255, 255, 255, 0.16);
111
+ border-color: rgba(255, 255, 255, 0.6);
112
+ }
113
+
114
+ .countdown-control:active {
115
+ transform: scale(0.94);
116
+ }
117
+
118
+ .countdown-control svg {
119
+ width: clamp(26px, 4vmin, 32px);
120
+ height: clamp(26px, 4vmin, 32px);
121
+ }
122
+
123
+ .countdown-number {
124
+ min-width: clamp(120px, 26vmin, 280px);
125
+ text-align: center;
126
+ font-size: clamp(96px, 22vmin, 240px);
127
+ font-weight: 700;
128
+ color: #fff;
129
+ text-shadow: 0 6px 30px rgba(2, 6, 23, 0.6);
130
+ line-height: 1;
131
+ animation: countdown-pop 1s ease-out forwards;
132
+ }
133
+
134
+ .countdown-go {
135
+ font-size: clamp(64px, 14vmin, 160px);
136
+ letter-spacing: 0.04em;
137
+ }
138
+
139
+ @keyframes countdown-pop {
140
+ 0% {
141
+ transform: scale(0.45);
142
+ opacity: 0;
143
+ }
144
+ 25% {
145
+ transform: scale(1);
146
+ opacity: 1;
147
+ }
148
+ 70% {
149
+ transform: scale(1);
150
+ opacity: 1;
151
+ }
152
+ 100% {
153
+ transform: scale(1.25);
154
+ opacity: 0;
155
+ }
156
+ }
157
+
158
+ .countdown-hint {
159
+ position: absolute;
160
+ bottom: 14%;
161
+ left: 50%;
162
+ transform: translateX(-50%);
163
+ color: rgba(255, 255, 255, 0.85);
164
+ font-size: 15px;
165
+ font-weight: 500;
166
+ background: rgba(2, 6, 23, 0.45);
167
+ padding: 8px 16px;
168
+ border-radius: 999px;
169
+ }
170
+
171
+ /* ---------- Toolbar (vertical left pill, desktop layout) ---------- */
172
+
173
+ @keyframes rec-pulse {
174
+ 0%,
175
+ 100% {
176
+ opacity: 1;
177
+ }
178
+ 50% {
179
+ opacity: 0.3;
180
+ }
181
+ }
182
+
183
+ .toolbar-v {
184
+ position: absolute;
185
+ top: 50%;
186
+ left: 50%;
187
+ transform: translate(-50%, -50%);
188
+ display: flex;
189
+ flex-direction: column;
190
+ align-items: center;
191
+ gap: 8px;
192
+ padding: 9px;
193
+ border-radius: 20px;
194
+ background: rgba(15, 23, 42, 0.94);
195
+ border: 1px solid rgba(255, 255, 255, 0.1);
196
+ box-shadow: 0 10px 28px rgba(2, 6, 23, 0.45);
197
+ color: #fff;
198
+ -webkit-user-select: none;
199
+ user-select: none;
200
+ }
201
+
202
+ .toolbar-v button {
203
+ border: none;
204
+ cursor: pointer;
205
+ color: inherit;
206
+ display: inline-flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ transition:
210
+ background 0.12s ease,
211
+ transform 0.1s ease;
212
+ }
213
+
214
+ .toolbar-v button:active {
215
+ transform: scale(0.92);
216
+ }
217
+
218
+ .toolbar-v-stop {
219
+ width: 46px;
220
+ height: 46px;
221
+ border-radius: 50%;
222
+ background: #ef4444;
223
+ }
224
+
225
+ .toolbar-v-stop:hover {
226
+ background: #dc2626;
227
+ }
228
+
229
+ .toolbar-v-stop-square {
230
+ width: 16px;
231
+ height: 16px;
232
+ border-radius: 4px;
233
+ background: #fff;
234
+ display: block;
235
+ }
236
+
237
+ .toolbar-v-time {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 6px;
241
+ font-variant-numeric: tabular-nums;
242
+ font-size: 13px;
243
+ font-weight: 600;
244
+ color: rgba(255, 255, 255, 0.94);
245
+ }
246
+
247
+ .toolbar-v-dot {
248
+ width: 8px;
249
+ height: 8px;
250
+ border-radius: 50%;
251
+ background: #ef4444;
252
+ animation: rec-pulse 1.4s ease-in-out infinite;
253
+ }
254
+
255
+ .toolbar-v-paused .toolbar-v-dot {
256
+ background: #f59e0b;
257
+ animation: none;
258
+ }
259
+
260
+ .toolbar-v-pause {
261
+ width: 40px;
262
+ height: 40px;
263
+ border-radius: 50%;
264
+ background: rgba(255, 255, 255, 0.1);
265
+ }
266
+
267
+ .toolbar-v-pause:hover {
268
+ background: rgba(255, 255, 255, 0.18);
269
+ }
270
+
271
+ .toolbar-v-pause svg {
272
+ width: 18px;
273
+ height: 18px;
274
+ }
275
+
276
+ .toolbar-v-hover-actions {
277
+ display: flex;
278
+ flex-direction: column;
279
+ align-items: center;
280
+ gap: 8px;
281
+ overflow: hidden;
282
+ max-height: 0;
283
+ opacity: 0;
284
+ transition:
285
+ max-height 0.18s ease,
286
+ opacity 0.18s ease;
287
+ }
288
+
289
+ .toolbar-v:hover .toolbar-v-hover-actions {
290
+ max-height: 120px;
291
+ opacity: 1;
292
+ }
293
+
294
+ .toolbar-v-action {
295
+ width: 40px;
296
+ height: 40px;
297
+ border-radius: 50%;
298
+ background: rgba(255, 255, 255, 0.08);
299
+ }
300
+
301
+ .toolbar-v-action:hover {
302
+ background: rgba(255, 255, 255, 0.16);
303
+ }
304
+
305
+ .toolbar-v-action svg {
306
+ width: 20px;
307
+ height: 20px;
308
+ }
309
+
310
+ .toolbar-v-action-danger:hover {
311
+ background: rgba(239, 68, 68, 0.22);
312
+ color: #fca5a5;
313
+ }
314
+
315
+ /* ---------- Saving card (bottom-left, mirrors desktop Finalizing) ---------- */
316
+
317
+ .saving-card {
318
+ position: absolute;
319
+ inset: 0;
320
+ display: flex;
321
+ flex-direction: column;
322
+ justify-content: center;
323
+ gap: 9px;
324
+ padding: 14px 16px;
325
+ border-radius: 14px;
326
+ background: rgba(15, 23, 42, 0.95);
327
+ border: 1px solid rgba(255, 255, 255, 0.1);
328
+ box-shadow: 0 10px 28px rgba(2, 6, 23, 0.45);
329
+ color: #fff;
330
+ }
331
+
332
+ .saving-card .saving-spinner {
333
+ position: absolute;
334
+ top: 14px;
335
+ right: 14px;
336
+ width: 16px;
337
+ height: 16px;
338
+ border-radius: 50%;
339
+ border: 2px solid rgba(255, 255, 255, 0.25);
340
+ border-top-color: #fff;
341
+ animation: saving-spin 0.8s linear infinite;
342
+ }
343
+
344
+ @keyframes saving-spin {
345
+ to {
346
+ transform: rotate(360deg);
347
+ }
348
+ }
349
+
350
+ .saving-caption {
351
+ font-size: 14px;
352
+ font-weight: 600;
353
+ }
354
+
355
+ .saving-bar {
356
+ height: 5px;
357
+ border-radius: 999px;
358
+ background: rgba(255, 255, 255, 0.14);
359
+ overflow: hidden;
360
+ }
361
+
362
+ .saving-bar-fill {
363
+ height: 100%;
364
+ width: 40%;
365
+ border-radius: 999px;
366
+ background: #6366f1;
367
+ animation: saving-indeterminate 1.1s ease-in-out infinite;
368
+ }
369
+
370
+ @keyframes saving-indeterminate {
371
+ 0% {
372
+ margin-left: -40%;
373
+ }
374
+ 100% {
375
+ margin-left: 100%;
376
+ }
377
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="color-scheme" content="light dark" />
6
+ <title>Clips overlay</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./overlay.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,330 @@
1
+ import "./overlay.css";
2
+
3
+ // The overlay runs as an extension-origin iframe injected into the page by the
4
+ // content script. Each iframe renders one "part" of the Loom-style recording UI
5
+ // (camera bubble, countdown, or control toolbar) selected via ?part=. Running at
6
+ // the chrome-extension:// origin gives us a persistent camera permission grant,
7
+ // CSS isolation from the host page, and direct chrome.runtime messaging with the
8
+ // background service worker.
9
+
10
+ type OverlayPhase = "idle" | "countdown" | "recording" | "paused" | "saving";
11
+
12
+ // Hover-expand heights for the vertical toolbar, posted to the content script
13
+ // which owns the iframe size (the iframe can't resize itself).
14
+ const TOOLBAR_COLLAPSED_H = 154;
15
+ const TOOLBAR_EXPANDED_H = 236;
16
+
17
+ function postToolbarSize(height: number): void {
18
+ try {
19
+ window.parent.postMessage(
20
+ { source: "clips-overlay", kind: "resize", part: "toolbar", height },
21
+ "*",
22
+ );
23
+ } catch {
24
+ /* parent gone */
25
+ }
26
+ }
27
+
28
+ type OverlayState = {
29
+ phase: OverlayPhase;
30
+ baseElapsedMs: number;
31
+ baseEpochMs: number;
32
+ countdownEndsAtMs: number;
33
+ };
34
+
35
+ const COUNTDOWN_FALLBACK = 3;
36
+ const params = new URLSearchParams(location.search);
37
+ const part = params.get("part");
38
+ const root = document.getElementById("root") as HTMLDivElement;
39
+
40
+ function icon(paths: string): string {
41
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
42
+ }
43
+
44
+ const ICONS = {
45
+ pause: icon(
46
+ '<rect x="6" y="5" width="4" height="14" rx="1" fill="currentColor" stroke="none"/><rect x="14" y="5" width="4" height="14" rx="1" fill="currentColor" stroke="none"/>',
47
+ ),
48
+ resume: icon('<path d="M7 5l12 7-12 7z" fill="currentColor" stroke="none"/>'),
49
+ stop: icon(
50
+ '<rect x="6" y="6" width="12" height="12" rx="2.5" fill="currentColor" stroke="none"/>',
51
+ ),
52
+ restart: icon('<path d="M3 11a9 9 0 1 1 2.6 6.3"/><path d="M3 4v7h7"/>'),
53
+ trash: icon(
54
+ '<path d="M4 7h16"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M5 7l1 13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-13"/><path d="M9 7V4h6v3"/>',
55
+ ),
56
+ cameraOff: icon(
57
+ '<path d="M2 2l20 20"/><path d="M7 7H4a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12"/><path d="M22 8l-6 4 6 4V8z" opacity="0.5"/>',
58
+ ),
59
+ cancel: icon('<path d="M6 6l12 12"/><path d="M18 6L6 18"/>'),
60
+ skipForward: icon(
61
+ '<path d="M6 5l10 7-10 7z" fill="currentColor" stroke="none"/><rect x="16" y="5" width="3" height="14" rx="1" fill="currentColor" stroke="none"/>',
62
+ ),
63
+ };
64
+
65
+ function send(type: string, extra: Record<string, unknown> = {}): void {
66
+ try {
67
+ chrome.runtime.sendMessage(
68
+ { type, ...extra },
69
+ () => void chrome.runtime.lastError,
70
+ );
71
+ } catch {
72
+ /* the background may be momentarily asleep; state will re-sync */
73
+ }
74
+ }
75
+
76
+ /* ---------------------------------------------------------------- bubble --- */
77
+
78
+ async function initBubble(): Promise<void> {
79
+ const bubble = document.createElement("div");
80
+ bubble.className = "bubble";
81
+ const ring = document.createElement("div");
82
+ ring.className = "bubble-ring";
83
+ bubble.appendChild(ring);
84
+ root.appendChild(bubble);
85
+
86
+ try {
87
+ const stream = await navigator.mediaDevices.getUserMedia({
88
+ video: {
89
+ width: { ideal: 640 },
90
+ height: { ideal: 640 },
91
+ facingMode: "user",
92
+ },
93
+ audio: false,
94
+ });
95
+ const video = document.createElement("video");
96
+ video.muted = true;
97
+ video.autoplay = true;
98
+ video.playsInline = true;
99
+ video.srcObject = stream;
100
+ ring.appendChild(video);
101
+ await video.play().catch(() => undefined);
102
+ } catch {
103
+ const empty = document.createElement("div");
104
+ empty.className = "bubble-empty";
105
+ empty.innerHTML = ICONS.cameraOff;
106
+ ring.appendChild(empty);
107
+ }
108
+ }
109
+
110
+ /* ------------------------------------------------------------- countdown --- */
111
+
112
+ // The countdown only *visualizes* the worker's clock (state.countdownEndsAtMs).
113
+ // The worker owns the real timer and starts the recorder, so this never *needs*
114
+ // to signal "done" — which is what lets recording work on pages where no overlay
115
+ // can be injected at all. The skip button is the one exception: it explicitly
116
+ // asks the worker to start now (CLIPS_OVERLAY_COUNTDOWN_DONE → beginNow), which
117
+ // is idempotent and harmless if the timer also fires.
118
+ function initCountdown(): void {
119
+ const wrap = document.createElement("div");
120
+ wrap.className = "countdown";
121
+
122
+ const controls = document.createElement("div");
123
+ controls.className = "countdown-controls";
124
+
125
+ const cancelBtn = document.createElement("button");
126
+ cancelBtn.type = "button";
127
+ cancelBtn.className = "countdown-control countdown-control-cancel";
128
+ cancelBtn.setAttribute("aria-label", "Cancel recording");
129
+ cancelBtn.innerHTML = ICONS.cancel;
130
+ cancelBtn.addEventListener("click", () => send("CLIPS_OVERLAY_CANCEL"));
131
+
132
+ const number = document.createElement("div");
133
+ number.className = "countdown-number";
134
+
135
+ const skipBtn = document.createElement("button");
136
+ skipBtn.type = "button";
137
+ skipBtn.className = "countdown-control countdown-control-skip";
138
+ skipBtn.setAttribute("aria-label", "Skip countdown and start recording now");
139
+ skipBtn.innerHTML = ICONS.skipForward;
140
+ skipBtn.addEventListener("click", () => send("CLIPS_OVERLAY_COUNTDOWN_DONE"));
141
+
142
+ controls.append(cancelBtn, number, skipBtn);
143
+
144
+ const hint = document.createElement("div");
145
+ hint.className = "countdown-hint";
146
+ hint.textContent = "Get ready…";
147
+ wrap.append(controls, hint);
148
+ root.appendChild(wrap);
149
+
150
+ let lastShown = "";
151
+ const render = (): void => {
152
+ const fallback =
153
+ Number(params.get("seconds") || String(COUNTDOWN_FALLBACK)) ||
154
+ COUNTDOWN_FALLBACK;
155
+ const endsAt =
156
+ state.countdownEndsAtMs > 0
157
+ ? state.countdownEndsAtMs
158
+ : Date.now() + fallback * 1000;
159
+ const remainingMs = endsAt - Date.now();
160
+ if (state.phase !== "countdown" || remainingMs <= 0) {
161
+ if (lastShown !== "Go") {
162
+ number.textContent = "Go";
163
+ number.classList.add("countdown-go");
164
+ lastShown = "Go";
165
+ }
166
+ return;
167
+ }
168
+ const text = String(Math.ceil(remainingMs / 1000));
169
+ if (text !== lastShown) {
170
+ number.textContent = text;
171
+ number.classList.remove("countdown-go");
172
+ number.style.animation = "none";
173
+ void number.offsetWidth;
174
+ number.style.animation = "";
175
+ lastShown = text;
176
+ }
177
+ };
178
+ countdownRender = render;
179
+ window.setInterval(render, 100);
180
+ render();
181
+ }
182
+
183
+ /* --------------------------------------------------------------- toolbar --- */
184
+
185
+ // Vertical pill anchored to the LEFT edge — mirrors the desktop app's toolbar.
186
+ // Big Stop on top, elapsed time, pause; on hover it grows to reveal restart +
187
+ // cancel. Pure command emitter; the background owns the recorder.
188
+ function initToolbar(): void {
189
+ const pill = document.createElement("div");
190
+ pill.className = "toolbar-v";
191
+
192
+ const makeBtn = (
193
+ cls: string,
194
+ title: string,
195
+ svg: string,
196
+ onClick: () => void,
197
+ ): HTMLButtonElement => {
198
+ const btn = document.createElement("button");
199
+ btn.type = "button";
200
+ btn.className = cls;
201
+ btn.title = title;
202
+ btn.setAttribute("aria-label", title);
203
+ btn.innerHTML = svg;
204
+ btn.addEventListener("click", onClick);
205
+ return btn;
206
+ };
207
+
208
+ const stopBtn = makeBtn(
209
+ "toolbar-v-stop",
210
+ "Stop & save",
211
+ '<span class="toolbar-v-stop-square"></span>',
212
+ () => send("CLIPS_OVERLAY_STOP"),
213
+ );
214
+
215
+ const time = document.createElement("div");
216
+ time.className = "toolbar-v-time";
217
+ const dot = document.createElement("span");
218
+ dot.className = "toolbar-v-dot";
219
+ const clock = document.createElement("span");
220
+ clock.textContent = "0:00";
221
+ time.append(dot, clock);
222
+
223
+ const pauseBtn = makeBtn("toolbar-v-pause", "Pause", ICONS.pause, () => {
224
+ if (state.phase === "paused") send("CLIPS_OVERLAY_RESUME");
225
+ else send("CLIPS_OVERLAY_PAUSE");
226
+ });
227
+
228
+ const hoverGroup = document.createElement("div");
229
+ hoverGroup.className = "toolbar-v-hover-actions";
230
+ const restartBtn = makeBtn("toolbar-v-action", "Restart", ICONS.restart, () =>
231
+ send("CLIPS_OVERLAY_RESTART"),
232
+ );
233
+ const cancelBtn = makeBtn(
234
+ "toolbar-v-action toolbar-v-action-danger",
235
+ "Discard",
236
+ ICONS.trash,
237
+ () => send("CLIPS_OVERLAY_CANCEL"),
238
+ );
239
+ hoverGroup.append(restartBtn, cancelBtn);
240
+
241
+ pill.append(stopBtn, time, pauseBtn, hoverGroup);
242
+ root.appendChild(pill);
243
+
244
+ // The iframe can't size itself, so ask the content script to grow/shrink it.
245
+ pill.addEventListener("mouseenter", () =>
246
+ postToolbarSize(TOOLBAR_EXPANDED_H),
247
+ );
248
+ pill.addEventListener("mouseleave", () =>
249
+ postToolbarSize(TOOLBAR_COLLAPSED_H),
250
+ );
251
+
252
+ const render = (): void => {
253
+ const paused = state.phase === "paused";
254
+ pill.classList.toggle("toolbar-v-paused", paused);
255
+ pauseBtn.title = paused ? "Resume" : "Pause";
256
+ pauseBtn.innerHTML = paused ? ICONS.resume : ICONS.pause;
257
+ const elapsed = paused
258
+ ? state.baseElapsedMs
259
+ : state.baseElapsedMs + Math.max(0, Date.now() - state.baseEpochMs);
260
+ clock.textContent = formatDuration(elapsed);
261
+ };
262
+
263
+ window.setInterval(render, 250);
264
+ toolbarRender = render;
265
+ render();
266
+ }
267
+
268
+ /* ---------------------------------------------------------------- saving --- */
269
+
270
+ // Bottom-left "Saving…" card shown from Stop until the clip opens, mirroring the
271
+ // desktop Finalizing overlay so the upload gap isn't a blank screen.
272
+ function initSaving(): void {
273
+ const card = document.createElement("div");
274
+ card.className = "saving-card";
275
+ const spinner = document.createElement("div");
276
+ spinner.className = "saving-spinner";
277
+ const caption = document.createElement("div");
278
+ caption.className = "saving-caption";
279
+ caption.textContent = "Saving clip…";
280
+ const bar = document.createElement("div");
281
+ bar.className = "saving-bar";
282
+ const fill = document.createElement("div");
283
+ fill.className = "saving-bar-fill";
284
+ bar.appendChild(fill);
285
+ card.append(spinner, caption, bar);
286
+ root.appendChild(card);
287
+ }
288
+
289
+ function formatDuration(ms: number): string {
290
+ const total = Math.max(0, Math.floor(ms / 1000));
291
+ const minutes = Math.floor(total / 60);
292
+ const seconds = total % 60;
293
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
294
+ }
295
+
296
+ /* ----------------------------------------------------------------- state --- */
297
+
298
+ const state: OverlayState = {
299
+ phase: "recording",
300
+ baseElapsedMs: 0,
301
+ baseEpochMs: Date.now(),
302
+ countdownEndsAtMs: 0,
303
+ };
304
+ let toolbarRender: (() => void) | null = null;
305
+ let countdownRender: (() => void) | null = null;
306
+
307
+ chrome.runtime.onMessage.addListener((message) => {
308
+ if (!message || typeof message !== "object") return;
309
+ if ((message as { type?: unknown }).type !== "CLIPS_OVERLAY_STATE") return;
310
+ const next = (message as { state?: Partial<OverlayState> }).state;
311
+ if (!next) return;
312
+ if (typeof next.phase === "string") state.phase = next.phase as OverlayPhase;
313
+ if (typeof next.baseElapsedMs === "number")
314
+ state.baseElapsedMs = next.baseElapsedMs;
315
+ if (typeof next.baseEpochMs === "number")
316
+ state.baseEpochMs = next.baseEpochMs;
317
+ if (typeof next.countdownEndsAtMs === "number")
318
+ state.countdownEndsAtMs = next.countdownEndsAtMs;
319
+ toolbarRender?.();
320
+ countdownRender?.();
321
+ });
322
+
323
+ if (part === "bubble") void initBubble();
324
+ else if (part === "countdown") initCountdown();
325
+ else if (part === "toolbar") initToolbar();
326
+ else if (part === "saving") initSaving();
327
+
328
+ // Ask the background for the current state so a freshly-injected toolbar (e.g.
329
+ // after the user navigated to a new page mid-recording) shows the right timer.
330
+ send("CLIPS_OVERLAY_HELLO", { part });