@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
@@ -832,6 +832,38 @@ function contentAfterContinuationPrefix(
832
832
  return [...delta, ...content.slice(contentIndex)];
833
833
  }
834
834
 
835
+ // Concrete "ship a compact first version, then refine incrementally" guidance
836
+ // keyed by the large-payload action the model was cut off while preparing. A run
837
+ // cut off mid-stream while streaming one big tool input (extension HTML, a full
838
+ // design file set, an entire dashboard config) re-streams the SAME oversized
839
+ // payload on every continuation and never finishes inside the soft-timeout
840
+ // window — the classic thrash loop. Pointing the resumed model at the
841
+ // incremental-edit path for that specific action is what breaks the loop.
842
+ // Returns undefined for tools with no known incremental counterpart so the
843
+ // caller falls back to generic compact-first advice.
844
+ function incrementalActionGuidance(tool: string): string | undefined {
845
+ switch (tool) {
846
+ case "create-extension":
847
+ case "update-extension":
848
+ return "create a compact working v1 with `create-extension`, then use focused `update-extension` edits for refinements";
849
+ case "generate-design":
850
+ case "update-design":
851
+ return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
852
+ case "create-visual-plan":
853
+ case "create-ui-plan":
854
+ case "create-plan-design":
855
+ case "create-prototype-plan":
856
+ return "create the plan with its core sections or first screen, then expand it with `update-visual-plan`/`patch-visual-plan-source` follow-up edits";
857
+ case "update-visual-plan":
858
+ case "patch-visual-plan-source":
859
+ return "apply smaller, targeted `patch-visual-plan-source` edits rather than rewriting the whole plan in one call";
860
+ case "update-dashboard":
861
+ return "save a small dashboard first, then add panels one at a time with `update-dashboard` incremental `ops` edits instead of authoring the whole config in one call";
862
+ default:
863
+ return undefined;
864
+ }
865
+ }
866
+
835
867
  function autoContinueMessage(signal: AgentAutoContinueSignal): string {
836
868
  const tool = lastActivityTool(signal.activityTrail);
837
869
  const reason =
@@ -845,15 +877,20 @@ function autoContinueMessage(signal: AgentAutoContinueSignal): string {
845
877
  ? "The previous stream ended before the agent sent a final completion signal."
846
878
  : "The previous run reached an internal execution budget.";
847
879
  // A run_timeout or a mid-stream cutoff while assembling one large action
848
- // payload (e.g. inlining a big pasted HTML file into `create-extension`) is
849
- // the classic trigger for the repetition/cutoff loop. Nudge the model toward
850
- // a compact first version it can actually finish in a single run.
880
+ // payload (e.g. inlining a big pasted HTML file into `create-extension`, a
881
+ // full file set into `generate-design`, or a whole `update-dashboard` config)
882
+ // is the classic trigger for the repetition/cutoff loop. Nudge the model
883
+ // toward a compact first version it can actually finish in a single run, then
884
+ // incremental refinements — never re-streaming the same oversized payload.
851
885
  const cutoffPreparingAction =
852
886
  signal.reason === "run_timeout" || signal.reason === "stream_ended";
853
- const actionInputNote =
854
- cutoffPreparingAction && tool
855
- ? `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid spending another whole run assembling one large tool payload. If this is \`create-extension\`, create a compact working v1 first, then use focused \`update-extension\` edits for refinements.`
856
- : "";
887
+ let actionInputNote = "";
888
+ if (cutoffPreparingAction && tool) {
889
+ const guidance = incrementalActionGuidance(tool);
890
+ actionInputNote = guidance
891
+ ? `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid spending another whole run assembling one large tool payload — ${guidance}.`
892
+ : `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid re-assembling one large tool payload: produce a compact first result you can finish in a single run, then refine it with smaller follow-up edits.`;
893
+ }
857
894
  return `${AUTO_CONTINUE_PROMPT}\n\n${AUTO_CONTINUE_COMPLETION_GUARD}\n\nInternal note: ${reason}${actionInputNote}`;
858
895
  }
859
896
 
@@ -61,6 +61,44 @@ const LLM_CONNECTION_CACHE_TTL_MS = 5 * 60 * 1000;
61
61
  // session over multiple visits.
62
62
  const SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
63
63
 
64
+ // First-touch referral attribution (viral attribution). Captured once on the
65
+ // visitor's first page load and persisted across the signup boundary so the
66
+ // server-side `signup` event can record where the user came from. First-write
67
+ // wins — an existing value is never overwritten.
68
+ const FIRST_TOUCH_STORAGE_KEY = "an_attribution";
69
+ const FIRST_TOUCH_COOKIE_NAME = "an_ft";
70
+ // 30 days, matching the session cookie lifetime — long enough to bridge a
71
+ // "land today, sign up next week" path without retaining attribution forever.
72
+ const FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS = 2592000;
73
+ const FIRST_TOUCH_MAX_FIELD_LENGTH = 120;
74
+ // Keep the serialized cookie well under the ~4KB browser cap; we bail rather
75
+ // than write a runaway cookie if some field combination blows past this.
76
+ const FIRST_TOUCH_MAX_COOKIE_BYTES = 1500;
77
+ const FIRST_TOUCH_QUERY_FIELDS = [
78
+ "ref",
79
+ "via",
80
+ "utm_source",
81
+ "utm_medium",
82
+ "utm_campaign",
83
+ "utm_content",
84
+ "utm_term",
85
+ ] as const;
86
+
87
+ let _firstTouchCaptured = false;
88
+
89
+ export interface FirstTouchAttribution {
90
+ ref?: string;
91
+ via?: string;
92
+ utm_source?: string;
93
+ utm_medium?: string;
94
+ utm_campaign?: string;
95
+ utm_content?: string;
96
+ utm_term?: string;
97
+ landing_path?: string;
98
+ landing_referrer?: string;
99
+ landed_at?: string;
100
+ }
101
+
64
102
  function generateVisitorId(): string {
65
103
  try {
66
104
  if (
@@ -214,6 +252,148 @@ function getOrCreateSessionId(): string | undefined {
214
252
  return id;
215
253
  }
216
254
 
255
+ function truncateFirstTouchField(value: string | null | undefined): string {
256
+ if (!value) return "";
257
+ const trimmed = value.trim();
258
+ if (!trimmed) return "";
259
+ return trimmed.slice(0, FIRST_TOUCH_MAX_FIELD_LENGTH);
260
+ }
261
+
262
+ /**
263
+ * Extract just the host of a referrer URL — never the full URL or query
264
+ * string (those can carry tokens). Returns "" when there's no usable host or
265
+ * the referrer is same-origin (a same-site navigation isn't a referral).
266
+ */
267
+ function scrubReferrerHost(referrer: string | undefined): string {
268
+ if (!referrer) return "";
269
+ try {
270
+ const url = new URL(referrer);
271
+ const host = url.host;
272
+ if (!host) return "";
273
+ if (
274
+ typeof window !== "undefined" &&
275
+ host.toLowerCase() === window.location.host.toLowerCase()
276
+ ) {
277
+ return "";
278
+ }
279
+ return truncateFirstTouchField(host);
280
+ } catch {
281
+ return "";
282
+ }
283
+ }
284
+
285
+ function buildFirstTouchAttribution(): FirstTouchAttribution {
286
+ const attribution: FirstTouchAttribution = {};
287
+ let params: URLSearchParams | null = null;
288
+ try {
289
+ params = new URLSearchParams(window.location.search);
290
+ } catch {
291
+ params = null;
292
+ }
293
+ if (params) {
294
+ for (const field of FIRST_TOUCH_QUERY_FIELDS) {
295
+ const value = truncateFirstTouchField(params.get(field));
296
+ if (value) attribution[field] = value;
297
+ }
298
+ }
299
+ const landingPath = truncateFirstTouchField(window.location.pathname);
300
+ if (landingPath) attribution.landing_path = landingPath;
301
+ const landingReferrer =
302
+ typeof document !== "undefined" ? scrubReferrerHost(document.referrer) : "";
303
+ if (landingReferrer) attribution.landing_referrer = landingReferrer;
304
+ attribution.landed_at = new Date().toISOString();
305
+ return attribution;
306
+ }
307
+
308
+ function readFirstTouchCookie(): string | null {
309
+ if (typeof document === "undefined") return null;
310
+ try {
311
+ const cookies = document.cookie ? document.cookie.split(";") : [];
312
+ for (const part of cookies) {
313
+ const eq = part.indexOf("=");
314
+ if (eq === -1) continue;
315
+ const name = part.slice(0, eq).trim();
316
+ if (name === FIRST_TOUCH_COOKIE_NAME) {
317
+ return part.slice(eq + 1).trim();
318
+ }
319
+ }
320
+ } catch {
321
+ // document.cookie can throw in sandboxed iframes — best-effort.
322
+ }
323
+ return null;
324
+ }
325
+
326
+ function writeFirstTouchCookie(encodedValue: string): void {
327
+ if (typeof document === "undefined") return;
328
+ // Non-sensitive, written by client JS, so no HttpOnly. SameSite=Lax keeps it
329
+ // on top-level navigations (which is how share links arrive) without leaking
330
+ // it to cross-site subresource requests.
331
+ const cookie =
332
+ `${FIRST_TOUCH_COOKIE_NAME}=${encodedValue}; path=/; ` +
333
+ `max-age=${FIRST_TOUCH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
334
+ if (cookie.length > FIRST_TOUCH_MAX_COOKIE_BYTES) return;
335
+ try {
336
+ document.cookie = cookie;
337
+ } catch {
338
+ // best-effort
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Capture the visitor's first-touch referral attribution exactly once. Reads
344
+ * the current URL query params + landing info and, IF no attribution is
345
+ * already stored (first-write-wins), persists it to both `localStorage`
346
+ * (`an_attribution`) and the first-party `an_ft` cookie. Fully defensive and
347
+ * SSR-safe — any failure is swallowed so it can never break app boot.
348
+ */
349
+ function captureFirstTouchAttribution(): void {
350
+ if (_firstTouchCaptured) return;
351
+ _firstTouchCaptured = true;
352
+ if (typeof window === "undefined") return;
353
+ try {
354
+ const existing = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
355
+ if (existing) {
356
+ // Already captured in a prior visit. Backfill the cookie if it expired
357
+ // or was cleared so the signup boundary still sees first-touch data, but
358
+ // never overwrite the stored value itself (first-write-wins).
359
+ if (!readFirstTouchCookie()) {
360
+ try {
361
+ writeFirstTouchCookie(encodeURIComponent(existing));
362
+ } catch {
363
+ // ignore
364
+ }
365
+ }
366
+ return;
367
+ }
368
+ const attribution = buildFirstTouchAttribution();
369
+ const json = JSON.stringify(attribution);
370
+ safeStorageSet(FIRST_TOUCH_STORAGE_KEY, json);
371
+ writeFirstTouchCookie(encodeURIComponent(json));
372
+ } catch {
373
+ // Attribution is best-effort telemetry; never let it break boot.
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Return the parsed first-touch referral attribution captured for this
379
+ * visitor, or `null` when none is stored. Reads from `localStorage`
380
+ * (`an_attribution`). SSR-safe and defensive.
381
+ */
382
+ export function getFirstTouchAttribution(): FirstTouchAttribution | null {
383
+ if (typeof window === "undefined") return null;
384
+ try {
385
+ const raw = safeStorageGet(FIRST_TOUCH_STORAGE_KEY);
386
+ if (!raw) return null;
387
+ const parsed = JSON.parse(raw) as unknown;
388
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
389
+ return null;
390
+ }
391
+ return parsed as FirstTouchAttribution;
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+
217
397
  function isLocalAnalyticsHostname(hostname: string | undefined): boolean {
218
398
  const h = (hostname || "").toLowerCase();
219
399
  return (
@@ -531,6 +711,7 @@ export function configureTracking(options: {
531
711
  if (typeof window !== "undefined") {
532
712
  ensureSentry();
533
713
  ensureAmplitude();
714
+ captureFirstTouchAttribution();
534
715
  installLlmConnectionRefresh();
535
716
  installPageviewTracking();
536
717
  }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Changelog UI — renders an app's CHANGELOG.md as an in-app "What's new"
3
+ * surface. Core owns all of this; a template just passes its own
4
+ * `CHANGELOG.md?raw` content in (Vite inlines it at build time, so this works
5
+ * on every host with no runtime file access or server route).
6
+ *
7
+ * Surfaces:
8
+ * - <ChangelogDialog> a self-contained modal listing every release.
9
+ * - <ChangelogSettingsCard> a settings-page card with the latest updates.
10
+ * - useChangelogSeen() tracks the last release a user has seen (so the
11
+ * command menu can show an "unseen" dot).
12
+ *
13
+ * The command menu's built-in `changelog` prop (see CommandMenu.tsx) wires the
14
+ * dialog automatically — most templates never touch these directly.
15
+ */
16
+
17
+ import React, { useEffect, useMemo, useState } from "react";
18
+ import { IconX, IconHistory } from "@tabler/icons-react";
19
+
20
+ import { parseChangelog, type ChangelogEntry } from "../../changelog/parse.js";
21
+ import {
22
+ markdownModule,
23
+ remarkGfmFn,
24
+ useMarkdownReady,
25
+ markdownUrlTransform,
26
+ } from "../chat/markdown-renderer.js";
27
+ import { cn } from "../utils.js";
28
+
29
+ // ─── Date formatting ──────────────────────────────────────────────────────────
30
+
31
+ function formatEntryHeading(entry: ChangelogEntry): string {
32
+ if (entry.date) {
33
+ // Parse as a plain calendar date (avoid TZ shifting YYYY-MM-DD back a day).
34
+ const [y, m, d] = entry.date.split("-").map(Number);
35
+ if (y && m && d) {
36
+ const formatted = new Date(y, m - 1, d).toLocaleDateString(undefined, {
37
+ year: "numeric",
38
+ month: "long",
39
+ day: "numeric",
40
+ });
41
+ return entry.version ? `${entry.version} · ${formatted}` : formatted;
42
+ }
43
+ }
44
+ return entry.title;
45
+ }
46
+
47
+ // ─── Markdown body ────────────────────────────────────────────────────────────
48
+ // A small, self-contained renderer for a release body. Avoids depending on the
49
+ // typography plugin (`prose`) being generated in the host template by applying
50
+ // explicit utility classes that Tailwind scans from core's compiled output.
51
+
52
+ const changelogMarkdownComponents = {
53
+ h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
54
+ <h3
55
+ {...props}
56
+ className="mb-1.5 mt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground first:mt-0"
57
+ />
58
+ ),
59
+ ul: (props: React.HTMLAttributes<HTMLUListElement>) => (
60
+ <ul
61
+ {...props}
62
+ className="mb-2 ml-1 list-disc space-y-1 pl-4 text-sm text-foreground marker:text-muted-foreground"
63
+ />
64
+ ),
65
+ ol: (props: React.HTMLAttributes<HTMLOListElement>) => (
66
+ <ol
67
+ {...props}
68
+ className="mb-2 ml-1 list-decimal space-y-1 pl-4 text-sm text-foreground marker:text-muted-foreground"
69
+ />
70
+ ),
71
+ li: (props: React.HTMLAttributes<HTMLLIElement>) => (
72
+ <li {...props} className="leading-relaxed" />
73
+ ),
74
+ p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (
75
+ <p {...props} className="mb-2 text-sm leading-relaxed text-foreground" />
76
+ ),
77
+ a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
78
+ <a
79
+ {...props}
80
+ target="_blank"
81
+ rel="noreferrer"
82
+ className="font-medium underline underline-offset-2"
83
+ />
84
+ ),
85
+ code: (props: React.HTMLAttributes<HTMLElement>) => (
86
+ <code
87
+ {...props}
88
+ className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]"
89
+ />
90
+ ),
91
+ };
92
+
93
+ function ChangelogBody({ markdown }: { markdown: string }) {
94
+ const ready = useMarkdownReady();
95
+ const ReactMarkdown = markdownModule?.default;
96
+ const gfm = remarkGfmFn;
97
+
98
+ if (!ready || !ReactMarkdown || !gfm) {
99
+ // The react-markdown chunk loads on module eval; this is typically only one
100
+ // frame. Show readable plain text rather than nothing in the meantime.
101
+ return (
102
+ <div className="whitespace-pre-wrap text-sm text-foreground">
103
+ {markdown}
104
+ </div>
105
+ );
106
+ }
107
+ return (
108
+ <ReactMarkdown
109
+ remarkPlugins={[gfm]}
110
+ components={changelogMarkdownComponents}
111
+ urlTransform={markdownUrlTransform}
112
+ >
113
+ {markdown}
114
+ </ReactMarkdown>
115
+ );
116
+ }
117
+
118
+ // ─── Unseen tracking ──────────────────────────────────────────────────────────
119
+
120
+ function seenStorageKey(appKey: string): string {
121
+ return `an:changelog-seen:${appKey}`;
122
+ }
123
+
124
+ /**
125
+ * Tracks the latest release a user has already seen (per browser, via
126
+ * localStorage). Returns whether there's an unseen release and a `markSeen`
127
+ * callback to clear the indicator once the changelog is opened.
128
+ */
129
+ export function useChangelogSeen(
130
+ appKey: string,
131
+ latestId: string | undefined,
132
+ ): { unseen: boolean; markSeen: () => void } {
133
+ const [seenId, setSeenId] = useState<string | null>(null);
134
+ const [hydrated, setHydrated] = useState(false);
135
+
136
+ useEffect(() => {
137
+ try {
138
+ setSeenId(window.localStorage.getItem(seenStorageKey(appKey)));
139
+ } catch {
140
+ // Private mode / disabled storage — treat as "nothing seen yet".
141
+ }
142
+ setHydrated(true);
143
+ }, [appKey]);
144
+
145
+ const markSeen = React.useCallback(() => {
146
+ if (!latestId) return;
147
+ setSeenId(latestId);
148
+ try {
149
+ window.localStorage.setItem(seenStorageKey(appKey), latestId);
150
+ } catch {
151
+ // Ignore storage failures; the dot just won't persist.
152
+ }
153
+ }, [appKey, latestId]);
154
+
155
+ // Don't flag "unseen" until hydrated, and never on a first-ever visit (no
156
+ // stored value) — only once the user has seen *something* and a newer
157
+ // release appears. This avoids nagging brand-new users.
158
+ const unseen =
159
+ hydrated && !!latestId && seenId !== null && seenId !== latestId;
160
+
161
+ return { unseen, markSeen };
162
+ }
163
+
164
+ // ─── Shared markup ────────────────────────────────────────────────────────────
165
+
166
+ function ChangelogEntries({
167
+ entries,
168
+ emptyText,
169
+ }: {
170
+ entries: ChangelogEntry[];
171
+ emptyText: string;
172
+ }) {
173
+ if (entries.length === 0) {
174
+ return <p className="text-sm text-muted-foreground">{emptyText}</p>;
175
+ }
176
+ return (
177
+ <div className="space-y-6">
178
+ {entries.map((entry) => (
179
+ <section key={entry.id}>
180
+ <h4 className="mb-2 text-sm font-semibold text-foreground">
181
+ {formatEntryHeading(entry)}
182
+ </h4>
183
+ <ChangelogBody markdown={entry.body} />
184
+ </section>
185
+ ))}
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // ─── Dialog ───────────────────────────────────────────────────────────────────
191
+
192
+ export interface ChangelogDialogProps {
193
+ open: boolean;
194
+ onOpenChange: (open: boolean) => void;
195
+ /** Raw CHANGELOG.md contents (e.g. `import md from "../CHANGELOG.md?raw"`). */
196
+ markdown: string;
197
+ /** Dialog heading. Default: "What's new". */
198
+ title?: string;
199
+ }
200
+
201
+ export function ChangelogDialog({
202
+ open,
203
+ onOpenChange,
204
+ markdown,
205
+ title = "What's new",
206
+ }: ChangelogDialogProps) {
207
+ const entries = useMemo(() => parseChangelog(markdown), [markdown]);
208
+
209
+ useEffect(() => {
210
+ if (!open) return;
211
+ const onKey = (e: KeyboardEvent) => {
212
+ if (e.key === "Escape") {
213
+ e.preventDefault();
214
+ onOpenChange(false);
215
+ }
216
+ };
217
+ document.addEventListener("keydown", onKey);
218
+ return () => document.removeEventListener("keydown", onKey);
219
+ }, [open, onOpenChange]);
220
+
221
+ if (!open) return null;
222
+
223
+ return (
224
+ <div
225
+ className="fixed inset-0 z-50 flex items-start justify-center bg-black/50 p-4"
226
+ onMouseDown={(e) => {
227
+ if (e.target === e.currentTarget) onOpenChange(false);
228
+ }}
229
+ >
230
+ <div
231
+ role="dialog"
232
+ aria-modal="true"
233
+ aria-label={title}
234
+ className="mt-[8vh] flex max-h-[80vh] w-full max-w-xl flex-col rounded-lg border border-border bg-popover text-popover-foreground shadow-lg"
235
+ >
236
+ <div className="flex items-center justify-between border-b border-border px-5 py-3.5">
237
+ <div className="flex items-center gap-2">
238
+ <IconHistory className="h-4 w-4 text-muted-foreground" />
239
+ <h3 className="text-sm font-semibold">{title}</h3>
240
+ </div>
241
+ <button
242
+ type="button"
243
+ onClick={() => onOpenChange(false)}
244
+ aria-label="Close"
245
+ className="rounded-sm p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
246
+ >
247
+ <IconX className="h-4 w-4" />
248
+ </button>
249
+ </div>
250
+ <div className="overflow-y-auto px-5 py-4">
251
+ <ChangelogEntries
252
+ entries={entries}
253
+ emptyText="No updates have been published yet."
254
+ />
255
+ </div>
256
+ </div>
257
+ </div>
258
+ );
259
+ }
260
+
261
+ // ─── Settings card ────────────────────────────────────────────────────────────
262
+
263
+ export interface ChangelogSettingsCardProps {
264
+ /** Raw CHANGELOG.md contents (e.g. `import md from "../CHANGELOG.md?raw"`). */
265
+ markdown: string;
266
+ /** How many recent releases to show inline before "View all". Default: 2. */
267
+ limit?: number;
268
+ /** Card heading. Default: "What's new". */
269
+ title?: string;
270
+ className?: string;
271
+ }
272
+
273
+ export function ChangelogSettingsCard({
274
+ markdown,
275
+ limit = 2,
276
+ title = "What's new",
277
+ className,
278
+ }: ChangelogSettingsCardProps) {
279
+ const entries = useMemo(() => parseChangelog(markdown), [markdown]);
280
+ const [dialogOpen, setDialogOpen] = useState(false);
281
+
282
+ if (entries.length === 0) return null;
283
+
284
+ const shown = entries.slice(0, limit);
285
+ const hasMore = entries.length > shown.length;
286
+
287
+ return (
288
+ <div
289
+ className={cn(
290
+ "rounded-lg border border-border bg-card text-card-foreground",
291
+ className,
292
+ )}
293
+ >
294
+ <div className="flex items-center gap-2 border-b border-border px-5 py-4">
295
+ <IconHistory className="h-4 w-4 text-muted-foreground" />
296
+ <h3 className="text-sm font-semibold">{title}</h3>
297
+ </div>
298
+ <div className="px-5 py-4">
299
+ <ChangelogEntries entries={shown} emptyText="No updates yet." />
300
+ {hasMore && (
301
+ <button
302
+ type="button"
303
+ onClick={() => setDialogOpen(true)}
304
+ className="mt-4 text-sm font-medium text-foreground underline underline-offset-2"
305
+ >
306
+ View all updates
307
+ </button>
308
+ )}
309
+ </div>
310
+ <ChangelogDialog
311
+ open={dialogOpen}
312
+ onOpenChange={setDialogOpen}
313
+ markdown={markdown}
314
+ title={title}
315
+ />
316
+ </div>
317
+ );
318
+ }
319
+
320
+ export { parseChangelog };
321
+ export type { ChangelogEntry };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The runtime shell the web UI is rendering inside. Used as pass-through
3
+ * feedback metadata so form owners can tell whether a submission came from the
4
+ * Agent Native desktop app (Electron), a Tauri desktop shell (e.g. Clips), or a
5
+ * plain browser — without surfacing it as a visible form field.
6
+ */
7
+ export type ClientSurface = "web" | "electron" | "tauri";
8
+
9
+ interface SurfaceGlobals {
10
+ // Tauri v2 always injects this into the webview; `__TAURI__` only exists when
11
+ // the app opts into `withGlobalTauri`, so check both.
12
+ __TAURI_INTERNALS__?: unknown;
13
+ __TAURI__?: unknown;
14
+ // Exposed by the Agent Native desktop (Electron) preload bridges.
15
+ agentNativeDesktop?: unknown;
16
+ electronAPI?: unknown;
17
+ }
18
+
19
+ /**
20
+ * Best-effort, side-effect-free detection of the current client surface.
21
+ * Returns "web" during SSR and in any plain browser.
22
+ *
23
+ * Electron detection keys off the `AgentNativeDesktop` User-Agent token that the
24
+ * desktop shell appends app-wide (see `app.userAgentFallback`), plus the preload
25
+ * globals, so it stays specific to our app rather than every Electron webview.
26
+ */
27
+ export function getClientSurface(): ClientSurface {
28
+ if (typeof window === "undefined") return "web";
29
+ const w = window as unknown as SurfaceGlobals;
30
+ if (w.__TAURI_INTERNALS__ || w.__TAURI__) return "tauri";
31
+ const ua = typeof navigator !== "undefined" ? navigator.userAgent || "" : "";
32
+ if (
33
+ /AgentNativeDesktop/i.test(ua) ||
34
+ /\bElectron\b/i.test(ua) ||
35
+ w.agentNativeDesktop ||
36
+ w.electronAPI
37
+ ) {
38
+ return "electron";
39
+ }
40
+ return "web";
41
+ }