@agent-native/core-corpus 0.219.0 → 0.220.0-nightly-20261023212848

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 (253) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +1 -0
  3. package/corpus/templates/analytics/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  4. package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  5. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +1 -0
  6. package/corpus/templates/assets/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  7. package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  8. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +1 -0
  9. package/corpus/templates/brain/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  10. package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  11. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +1 -0
  12. package/corpus/templates/calendar/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  13. package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  14. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +1 -0
  15. package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  16. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +1 -0
  17. package/corpus/templates/clips/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  18. package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  19. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +146 -14
  20. package/corpus/templates/clips/app/routes/record.tsx +11 -0
  21. package/corpus/templates/clips/desktop/src/app.tsx +74 -16
  22. package/corpus/templates/clips/desktop/src/lib/recorder.ts +12 -4
  23. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +54 -0
  24. package/corpus/templates/clips/desktop/src/lib/url.ts +7 -1
  25. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +54 -2
  26. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  27. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +95 -0
  28. package/corpus/templates/community/qr/package.json +1 -1
  29. package/corpus/templates/content/.agents/skills/actions/SKILL.md +1 -0
  30. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +12 -0
  31. package/corpus/templates/content/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  32. package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  33. package/corpus/templates/content/actions/_builder-docs-client.ts +75 -28
  34. package/corpus/templates/content/actions/_content-database-row-migration.ts +10 -2
  35. package/corpus/templates/content/actions/_content-save-outcomes.ts +1 -0
  36. package/corpus/templates/content/actions/_database-block-actions.ts +6 -2
  37. package/corpus/templates/content/actions/_database-source-utils.ts +36 -3
  38. package/corpus/templates/content/actions/_database-utils.ts +49 -1
  39. package/corpus/templates/content/actions/_document-edit-mutation.ts +280 -0
  40. package/corpus/templates/content/actions/_document-flush.ts +13 -0
  41. package/corpus/templates/content/actions/_github-source-freshness.ts +2 -0
  42. package/corpus/templates/content/actions/_github-source-metadata.ts +15 -1
  43. package/corpus/templates/content/actions/_github-source.ts +1031 -345
  44. package/corpus/templates/content/actions/_move-page-to-space.ts +5 -0
  45. package/corpus/templates/content/actions/_property-utils.ts +18 -11
  46. package/corpus/templates/content/actions/add-comment.ts +56 -29
  47. package/corpus/templates/content/actions/attach-content-database-source.ts +29 -14
  48. package/corpus/templates/content/actions/bind-content-database-source-field.ts +18 -5
  49. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +12 -5
  50. package/corpus/templates/content/actions/change-content-database-source-role.ts +13 -7
  51. package/corpus/templates/content/actions/create-content-database.ts +11 -4
  52. package/corpus/templates/content/actions/delete-document-property.ts +7 -2
  53. package/corpus/templates/content/actions/delete-document.ts +8 -0
  54. package/corpus/templates/content/actions/disconnect-content-database-source.ts +18 -5
  55. package/corpus/templates/content/actions/duplicate-database-item.ts +13 -7
  56. package/corpus/templates/content/actions/duplicate-database-items.ts +28 -22
  57. package/corpus/templates/content/actions/edit-document.ts +119 -4
  58. package/corpus/templates/content/actions/execute-builder-source-execution.ts +13 -5
  59. package/corpus/templates/content/actions/get-content-database.ts +30 -13
  60. package/corpus/templates/content/actions/import-content-source.ts +41 -18
  61. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +9 -3
  62. package/corpus/templates/content/actions/migrate-content-database-rows.ts +29 -3
  63. package/corpus/templates/content/actions/move-database-item.ts +12 -6
  64. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +8 -4
  65. package/corpus/templates/content/actions/preview-content-database-source-attach.ts +6 -3
  66. package/corpus/templates/content/actions/query-content-database-items.ts +24 -5
  67. package/corpus/templates/content/actions/remove-database-items.ts +19 -10
  68. package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +6 -2
  69. package/corpus/templates/content/actions/restore-document-version.ts +305 -65
  70. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +11 -4
  71. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +11 -4
  72. package/corpus/templates/content/actions/set-document-property.ts +6 -2
  73. package/corpus/templates/content/actions/set-image-alt-text.ts +10 -2
  74. package/corpus/templates/content/actions/set-live-body-switch.ts +4 -1
  75. package/corpus/templates/content/actions/share-local-file-document.ts +6 -2
  76. package/corpus/templates/content/actions/stage-builder-revision.ts +10 -4
  77. package/corpus/templates/content/actions/suggest-document-edit.ts +256 -15
  78. package/corpus/templates/content/actions/sync-local-folder-source.ts +28 -4
  79. package/corpus/templates/content/actions/transcribe-media.ts +16 -2
  80. package/corpus/templates/content/actions/update-content-database-view.ts +8 -2
  81. package/corpus/templates/content/actions/update-document.ts +11 -2
  82. package/corpus/templates/content/actions/validate-builder-source-execution.ts +11 -4
  83. package/corpus/templates/content/app/components/editor/CommentComposer.tsx +142 -2
  84. package/corpus/templates/content/app/components/editor/CommentEntry.tsx +30 -5
  85. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +707 -283
  86. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +38 -40
  87. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +2 -2
  88. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +27 -4
  89. package/corpus/templates/content/app/components/editor/SuggestionReviewCard.tsx +264 -0
  90. package/corpus/templates/content/app/components/editor/SuggestionReviewCardSkeleton.tsx +13 -0
  91. package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +3 -2
  92. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +3 -2
  93. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +16 -18
  94. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +149 -3
  95. package/corpus/templates/content/app/components/editor/database/github-source-text.ts +1 -0
  96. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -3
  97. package/corpus/templates/content/app/components/editor/database/settings.tsx +8 -8
  98. package/corpus/templates/content/app/components/editor/discussion-panel.tsx +504 -0
  99. package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +12 -0
  100. package/corpus/templates/content/app/discussion-messages.ts +270 -0
  101. package/corpus/templates/content/app/hooks/use-comments.ts +6 -0
  102. package/corpus/templates/content/app/hooks/use-content-database.ts +114 -110
  103. package/corpus/templates/content/app/hooks/use-github-folder.ts +15 -5
  104. package/corpus/templates/content/app/i18n-data.ts +19 -0
  105. package/corpus/templates/content/app/lib/content-database-query-cache.ts +60 -0
  106. package/corpus/templates/content/app/root.tsx +23 -8
  107. package/corpus/templates/content/app/routes/suggestions.$documentId.tsx +79 -0
  108. package/corpus/templates/content/changelog/2026-10-21-agents-suggested-edits-show-inline-in-mcp-apps-hosts-like-cl.md +6 -0
  109. package/corpus/templates/content/changelog/2026-10-21-comments-panel-is-now-discussion.md +6 -0
  110. package/corpus/templates/content/changelog/2026-10-22-a-big-github-folder-fills-its-collection-much-faster-and-kee.md +6 -0
  111. package/corpus/templates/content/changelog/2026-10-22-a-github-folder-sync-keeps-going-when-file-storage-fails-fil.md +6 -0
  112. package/corpus/templates/content/changelog/2026-10-22-collections-with-large-select-properties-open-faster-and-use.md +6 -0
  113. package/corpus/templates/content/changelog/2026-10-22-pressing-escape-while-writing-a-comment-reply-or-edit-cancel.md +6 -0
  114. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +7 -7
  115. package/corpus/templates/content/docs/product/capabilities/content.discussion.page.md +43 -28
  116. package/corpus/templates/content/docs/product/encyclopedia.md +7 -7
  117. package/corpus/templates/content/e2e/helpers.ts +1 -1
  118. package/corpus/templates/content/scripts/historical-icon-apply.ts +6 -2
  119. package/corpus/templates/content/scripts/migrate-historical-icon-urls.ts +10 -0
  120. package/corpus/templates/content/server/db/schema.ts +25 -0
  121. package/corpus/templates/content/server/lib/comment-ai.ts +13 -17
  122. package/corpus/templates/content/server/lib/comment-anchor-follow.ts +107 -0
  123. package/corpus/templates/content/server/lib/comment-notifications.ts +3 -3
  124. package/corpus/templates/content/server/lib/document-body-revision.ts +159 -2
  125. package/corpus/templates/content/server/lib/document-body-steps.ts +182 -0
  126. package/corpus/templates/content/server/lib/document-history.ts +44 -0
  127. package/corpus/templates/content/server/lib/document-title-propagation.ts +16 -3
  128. package/corpus/templates/content/server/lib/github-source-client.ts +100 -12
  129. package/corpus/templates/content/server/lib/live-body-writer.ts +96 -15
  130. package/corpus/templates/content/server/lib/notion-sync.ts +109 -11
  131. package/corpus/templates/content/server/lib/private-icon-references.ts +4 -2
  132. package/corpus/templates/content/server/lib/suggested-edits.ts +395 -353
  133. package/corpus/templates/content/server/lib/suggestion-live-accept.ts +284 -0
  134. package/corpus/templates/content/server/lib/suggestion-rebaseline.ts +237 -0
  135. package/corpus/templates/content/server/live-copy/anchor-text.ts +48 -0
  136. package/corpus/templates/content/server/live-copy/apply-suggestion-edit.ts +528 -0
  137. package/corpus/templates/content/server/live-copy/binding.ts +3 -0
  138. package/corpus/templates/content/server/live-copy/persist.ts +94 -30
  139. package/corpus/templates/content/server/live-copy/rebase.ts +330 -0
  140. package/corpus/templates/content/server/live-copy/switch.ts +5 -1
  141. package/corpus/templates/content/server/live-copy/task-names.ts +1 -0
  142. package/corpus/templates/content/server/live-copy/tasks.ts +160 -6
  143. package/corpus/templates/content/server/live-copy/write-cause.ts +9 -0
  144. package/corpus/templates/content/server/live-copy/write.ts +146 -3
  145. package/corpus/templates/content/server/plugins/db.ts +16 -0
  146. package/corpus/templates/content/server/test-support/live-copy-page.ts +76 -0
  147. package/corpus/templates/content/shared/api.ts +64 -5
  148. package/corpus/templates/content/shared/comment-ai.ts +9 -0
  149. package/corpus/templates/content/shared/comment-reanchor.ts +148 -2
  150. package/corpus/templates/content/shared/database-row-properties.ts +172 -0
  151. package/corpus/templates/content/shared/github-source.ts +5 -2
  152. package/corpus/templates/content/shared/suggestion-apply.ts +314 -0
  153. package/corpus/templates/content/shared/suggestion-link.ts +11 -0
  154. package/corpus/templates/content/shared/suggestion-rebase.ts +1 -1
  155. package/corpus/templates/content/shared/suggestion-rebaseline.ts +256 -0
  156. package/corpus/templates/content/vite.config.ts +1 -1
  157. package/corpus/templates/crm/.agents/skills/actions/SKILL.md +1 -0
  158. package/corpus/templates/crm/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  159. package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  160. package/corpus/templates/design/.agents/skills/actions/SKILL.md +1 -0
  161. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +46 -8
  162. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +12 -1
  163. package/corpus/templates/design/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  164. package/corpus/templates/design/.agents/skills/hubspot-email/SKILL.md +146 -0
  165. package/corpus/templates/design/.agents/skills/hubspot-email-review/SKILL.md +84 -0
  166. package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  167. package/corpus/templates/design/.generated/bridge/native-builtin-metadata.generated.ts +326 -0
  168. package/corpus/templates/design/.generated/bridge/native-worker-source.generated.ts +6 -0
  169. package/corpus/templates/design/AGENTS.md +22 -23
  170. package/corpus/templates/design/actions/create-file.ts +40 -4
  171. package/corpus/templates/design/actions/edit-design.ts +1 -1
  172. package/corpus/templates/design/actions/export-hubspot-html.ts +110 -0
  173. package/corpus/templates/design/actions/export-png.ts +42 -3
  174. package/corpus/templates/design/actions/generate-design.ts +84 -28
  175. package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
  176. package/corpus/templates/design/actions/get-design-surface-index.ts +1 -0
  177. package/corpus/templates/design/actions/take-design-screenshot.ts +217 -116
  178. package/corpus/templates/design/actions/update-file.ts +9 -0
  179. package/corpus/templates/design/actions/view-screen.ts +16 -1
  180. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +8 -1
  181. package/corpus/templates/design/app/components/design/EditPanel.tsx +1 -5
  182. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3 -3
  183. package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +13 -3
  184. package/corpus/templates/design/app/components/design/design-canvas/interact-document.ts +10 -3
  185. package/corpus/templates/design/app/i18n/en-US.ts +13 -0
  186. package/corpus/templates/design/app/pages/design-editor/commands/download-hubspot-html.ts +86 -0
  187. package/corpus/templates/design/app/pages/design-editor/design-editor-view.tsx +1 -0
  188. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-canvas-and-screens.ts +2 -1
  189. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-export-and-handoff.ts +18 -0
  190. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +1 -0
  191. package/corpus/templates/design/app/pages/design-editor/domains/use-hubspot-html-export.ts +124 -0
  192. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +15 -0
  193. package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +12 -1
  194. package/corpus/templates/design/app/pages/design-editor/view/editor-dialogs.tsx +10 -0
  195. package/corpus/templates/design/app/pages/design-editor/view/export-submenu-content.tsx +11 -0
  196. package/corpus/templates/design/app/pages/design-editor/view/hubspot-email-conversion-dialog.tsx +54 -0
  197. package/corpus/templates/design/changelog/2026-10-22-create-hubspot-email-frames-rebuild-existing-frames-for-emai.md +6 -0
  198. package/corpus/templates/design/changelog/2026-10-22-hubspot-conversion-exports-can-verify-that-original-link-des.md +6 -0
  199. package/corpus/templates/design/changelog/2026-10-22-hubspot-emails-receive-an-independent-content-and-mobile-lay.md +6 -0
  200. package/corpus/templates/design/changelog/2026-10-22-hug-frames-expand-to-show-their-full-content-in-the-canvas-a.md +6 -0
  201. package/corpus/templates/design/changelog/2026-10-22-preview-hubspot-emails-with-sample-recipient-names-and-butto.md +6 -0
  202. package/corpus/templates/design/changelog/2026-10-22-screenshot-previews-use-local-chromium-when-available-for-fa.md +6 -0
  203. package/corpus/templates/design/changelog/2026-10-22-verification-and-export-requests-finish-without-requiring-un.md +6 -0
  204. package/corpus/templates/design/changelog/2026-10-23-external-stylesheet-image-export.md +6 -0
  205. package/corpus/templates/design/changelog/2026-10-23-html-motion-edits-preserve-interactive-alpine-visibility-sta.md +6 -0
  206. package/corpus/templates/design/changelog/2026-10-23-motion-editing-works-again-on-designs-that-load-tailwind-and.md +6 -0
  207. package/corpus/templates/design/changelog/2026-10-23-promo-artwork-uses-delivery-size-typography.md +6 -0
  208. package/corpus/templates/design/server/lib/design-response-guard.ts +29 -1
  209. package/corpus/templates/design/server/lib/email-css-properties.ts +128 -0
  210. package/corpus/templates/design/server/lib/email-css-values.ts +36 -0
  211. package/corpus/templates/design/server/lib/email-css-visibility.ts +72 -0
  212. package/corpus/templates/design/server/lib/email-image-bytes.ts +87 -0
  213. package/corpus/templates/design/server/lib/email-text-color.ts +85 -0
  214. package/corpus/templates/design/server/lib/hubl-interpolation.ts +63 -0
  215. package/corpus/templates/design/server/lib/hubspot-email-export.ts +1005 -0
  216. package/corpus/templates/design/server/lib/hubspot-email-preview.ts +153 -0
  217. package/corpus/templates/design/server/lib/playwright-runtime.ts +6 -6
  218. package/corpus/templates/design/server/lib/protect-hubl-tokens.ts +31 -0
  219. package/corpus/templates/design/server/lib/screenshot-readiness.ts +176 -0
  220. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  221. package/corpus/templates/design/server/source-workspace.ts +22 -0
  222. package/corpus/templates/design/shared/hubl-tokens.ts +55 -0
  223. package/corpus/templates/design/shared/hubspot-email-authoring.ts +35 -0
  224. package/corpus/templates/design/shared/hubspot-email.ts +100 -0
  225. package/corpus/templates/design/shared/motion-html-source.ts +547 -16
  226. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +1 -0
  227. package/corpus/templates/dispatch/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  228. package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  229. package/corpus/templates/factory/.agents/skills/actions/SKILL.md +1 -0
  230. package/corpus/templates/factory/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  231. package/corpus/templates/factory/.agents/skills/review-prs/SKILL.md +27 -3
  232. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +1 -0
  233. package/corpus/templates/forms/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  234. package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  235. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +1 -0
  236. package/corpus/templates/mail/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  237. package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  238. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +1 -0
  239. package/corpus/templates/plan/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  240. package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  241. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +1 -0
  242. package/corpus/templates/slides/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  243. package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  244. package/corpus/templates/slides/actions/patch-deck.ts +17 -7
  245. package/corpus/templates/slides/changelog/2026-10-23-the-agent-retries-a-slide-edit-whose-text-it-could-not-find-.md +6 -0
  246. package/corpus/templates/slides/scripts/edit-fidelity/run-options.ts +26 -0
  247. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +25 -4
  248. package/corpus/templates/slides-2/.agents/skills/actions/SKILL.md +1 -0
  249. package/corpus/templates/slides-2/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  250. package/corpus/templates/slides-2/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
  251. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +1 -0
  252. package/corpus/templates/tasks/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
  253. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 14579
35
+ - template files: 14686
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -162,8 +162,15 @@ actions for Forms, Content, Clips, Slides, and Design. The action should return
162
162
  concise structured content plus the link; it should not dump large catalogs or
163
163
  HTML.
164
164
 
165
- Compatibility target: build to the standard once, not per-client shims. MCP
166
- Apps-capable hosts should include Claude/Claude Desktop/Claude Code, ChatGPT
167
- custom MCP apps, VS Code GitHub Copilot, Goose, Postman, MCPJam, Cursor, and
168
- any future host that follows the extension negotiation. Host support varies by
169
- plan, release channel, and client version, so keep the deep link fallback.
165
+ Compatibility target: build to the standard once, not per-client shims.
166
+ Hosts that render MCP Apps inline today include Claude chat on web, desktop, and
167
+ mobile, plus the Claude Desktop Code tab; ChatGPT; VS Code GitHub Copilot (1.109+);
168
+ Microsoft 365 Copilot; Cursor (2.6+); Goose; Postman; MCPJam; Codex Desktop (26.616+);
169
+ and T3 Code (in Codex threads). Desktop Code rendering is observed in
170
+ [widget bug reports](https://github.com/anthropics/claude-code/issues/103809).
171
+ Treat the Claude Code terminal, Cowork, and Code on claude.ai web as link-out hosts.
172
+ [Cowork doesn't declare MCP Apps yet](https://github.com/anthropics/claude-code/issues/103924),
173
+ and using it on the same connector can make inline apps disappear in Chat until
174
+ you reconnect the connector. Host support varies by plan, release channel, and client version,
175
+ so keep the deep link fallback. See the community-maintained
176
+ [MCP client matrix](https://modelcontextprotocol.io/extensions/client-matrix).
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","content-metrics-collection-jobs","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -162,8 +162,15 @@ actions for Forms, Content, Clips, Slides, and Design. The action should return
162
162
  concise structured content plus the link; it should not dump large catalogs or
163
163
  HTML.
164
164
 
165
- Compatibility target: build to the standard once, not per-client shims. MCP
166
- Apps-capable hosts should include Claude/Claude Desktop/Claude Code, ChatGPT
167
- custom MCP apps, VS Code GitHub Copilot, Goose, Postman, MCPJam, Cursor, and
168
- any future host that follows the extension negotiation. Host support varies by
169
- plan, release channel, and client version, so keep the deep link fallback.
165
+ Compatibility target: build to the standard once, not per-client shims.
166
+ Hosts that render MCP Apps inline today include Claude chat on web, desktop, and
167
+ mobile, plus the Claude Desktop Code tab; ChatGPT; VS Code GitHub Copilot (1.109+);
168
+ Microsoft 365 Copilot; Cursor (2.6+); Goose; Postman; MCPJam; Codex Desktop (26.616+);
169
+ and T3 Code (in Codex threads). Desktop Code rendering is observed in
170
+ [widget bug reports](https://github.com/anthropics/claude-code/issues/103809).
171
+ Treat the Claude Code terminal, Cowork, and Code on claude.ai web as link-out hosts.
172
+ [Cowork doesn't declare MCP Apps yet](https://github.com/anthropics/claude-code/issues/103924),
173
+ and using it on the same connector can make inline apps disappear in Chat until
174
+ you reconnect the connector. Host support varies by plan, release channel, and client version,
175
+ so keep the deep link fallback. See the community-maintained
176
+ [MCP client matrix](https://modelcontextprotocol.io/extensions/client-matrix).
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","creative-context","db","labs","onboarding","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -162,8 +162,15 @@ actions for Forms, Content, Clips, Slides, and Design. The action should return
162
162
  concise structured content plus the link; it should not dump large catalogs or
163
163
  HTML.
164
164
 
165
- Compatibility target: build to the standard once, not per-client shims. MCP
166
- Apps-capable hosts should include Claude/Claude Desktop/Claude Code, ChatGPT
167
- custom MCP apps, VS Code GitHub Copilot, Goose, Postman, MCPJam, Cursor, and
168
- any future host that follows the extension negotiation. Host support varies by
169
- plan, release channel, and client version, so keep the deep link fallback.
165
+ Compatibility target: build to the standard once, not per-client shims.
166
+ Hosts that render MCP Apps inline today include Claude chat on web, desktop, and
167
+ mobile, plus the Claude Desktop Code tab; ChatGPT; VS Code GitHub Copilot (1.109+);
168
+ Microsoft 365 Copilot; Cursor (2.6+); Goose; Postman; MCPJam; Codex Desktop (26.616+);
169
+ and T3 Code (in Codex threads). Desktop Code rendering is observed in
170
+ [widget bug reports](https://github.com/anthropics/claude-code/issues/103809).
171
+ Treat the Claude Code terminal, Cowork, and Code on claude.ai web as link-out hosts.
172
+ [Cowork doesn't declare MCP Apps yet](https://github.com/anthropics/claude-code/issues/103924),
173
+ and using it on the same connector can make inline apps disappear in Chat until
174
+ you reconnect the connector. Host support varies by plan, release channel, and client version,
175
+ so keep the deep link fallback. See the community-maintained
176
+ [MCP client matrix](https://modelcontextprotocol.io/extensions/client-matrix).
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","brain-jobs","core-routes","db","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -162,8 +162,15 @@ actions for Forms, Content, Clips, Slides, and Design. The action should return
162
162
  concise structured content plus the link; it should not dump large catalogs or
163
163
  HTML.
164
164
 
165
- Compatibility target: build to the standard once, not per-client shims. MCP
166
- Apps-capable hosts should include Claude/Claude Desktop/Claude Code, ChatGPT
167
- custom MCP apps, VS Code GitHub Copilot, Goose, Postman, MCPJam, Cursor, and
168
- any future host that follows the extension negotiation. Host support varies by
169
- plan, release channel, and client version, so keep the deep link fallback.
165
+ Compatibility target: build to the standard once, not per-client shims.
166
+ Hosts that render MCP Apps inline today include Claude chat on web, desktop, and
167
+ mobile, plus the Claude Desktop Code tab; ChatGPT; VS Code GitHub Copilot (1.109+);
168
+ Microsoft 365 Copilot; Cursor (2.6+); Goose; Postman; MCPJam; Codex Desktop (26.616+);
169
+ and T3 Code (in Codex threads). Desktop Code rendering is observed in
170
+ [widget bug reports](https://github.com/anthropics/claude-code/issues/103809).
171
+ Treat the Claude Code terminal, Cowork, and Code on claude.ai web as link-out hosts.
172
+ [Cowork doesn't declare MCP Apps yet](https://github.com/anthropics/claude-code/issues/103924),
173
+ and using it on the same connector can make inline apps disappear in Chat until
174
+ you reconnect the connector. Host support varies by plan, release channel, and client version,
175
+ so keep the deep link fallback. See the community-maintained
176
+ [MCP client matrix](https://modelcontextprotocol.io/extensions/client-matrix).
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","core-routes","db","event-rules","oauth-refresh","otel","transactional-emails"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","otel"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -172,6 +172,7 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
173
  - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
+ - **Do** throw `NoWriteError(message, { errorCode, statusCode })` instead of `fail()` for a rejection raised before the action's first write, such as an edit that matches nothing or a missing target. The agent then retries with corrected arguments instead of stopping on a write whose outcome is unknown. Throw it only before the first write: the framework rejects the claim when a nested write action ran, but it cannot see the handler's own writes.
175
176
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
177
  - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
178
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
@@ -162,8 +162,15 @@ actions for Forms, Content, Clips, Slides, and Design. The action should return
162
162
  concise structured content plus the link; it should not dump large catalogs or
163
163
  HTML.
164
164
 
165
- Compatibility target: build to the standard once, not per-client shims. MCP
166
- Apps-capable hosts should include Claude/Claude Desktop/Claude Code, ChatGPT
167
- custom MCP apps, VS Code GitHub Copilot, Goose, Postman, MCPJam, Cursor, and
168
- any future host that follows the extension negotiation. Host support varies by
169
- plan, release channel, and client version, so keep the deep link fallback.
165
+ Compatibility target: build to the standard once, not per-client shims.
166
+ Hosts that render MCP Apps inline today include Claude chat on web, desktop, and
167
+ mobile, plus the Claude Desktop Code tab; ChatGPT; VS Code GitHub Copilot (1.109+);
168
+ Microsoft 365 Copilot; Cursor (2.6+); Goose; Postman; MCPJam; Codex Desktop (26.616+);
169
+ and T3 Code (in Codex threads). Desktop Code rendering is observed in
170
+ [widget bug reports](https://github.com/anthropics/claude-code/issues/103809).
171
+ Treat the Claude Code terminal, Cowork, and Code on claude.ai web as link-out hosts.
172
+ [Cowork doesn't declare MCP Apps yet](https://github.com/anthropics/claude-code/issues/103924),
173
+ and using it on the same connector can make inline apps disappear in Chat until
174
+ you reconnect the connector. Host support varies by plan, release channel, and client version,
175
+ so keep the deep link fallback. See the community-maintained
176
+ [MCP client matrix](https://modelcontextprotocol.io/extensions/client-matrix).
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @agent-native/core deploy build
2
+ import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
3
+
4
+ const pluginStems = ["agent-chat","auth","calendar-jobs","config","core-routes","db","feature-flags","labs","onboarding","onboarding-calendar","otel","transactional-email-catalog"];
5
+
6
+ export default function markBuildDiscoveredPlugins(nitroApp) {
7
+ for (const stem of pluginStems) {
8
+ markDefaultPluginProvided(nitroApp, stem);
9
+ }
10
+ }
@@ -31,10 +31,11 @@ import {
31
31
  // success should finish this card's setup; a connection made long after the
32
32
  // cancel belongs to something else.
33
33
  const CANCELLED_SETUP_RECOVERY_MS = 60_000;
34
+ // The custom-keys form finishes in another tab with no signal back, so a
35
+ // status read on leaving or returning credits it only this long after it was
36
+ // opened; storage set up later belongs to something else.
37
+ const CUSTOM_KEYS_CREDIT_WINDOW_MS = 15 * 60_000;
34
38
 
35
- // The S3 path finishes in settings, outside this card, so only the choice is
36
- // recorded here; a later `recording_started.storage_connected` shows whether
37
- // it landed.
38
39
  function trackStorageStep(
39
40
  name:
40
41
  | "onboarding_step_viewed"
@@ -66,6 +67,8 @@ export interface StorageSetupCardProps {
66
67
  */
67
68
  recordingAttemptId?: string;
68
69
  outputId?: string;
70
+ /** Why the host is showing the card, recorded on every step event. */
71
+ setupReason?: string;
69
72
  }
70
73
 
71
74
  export function StorageSetupCard({
@@ -79,6 +82,7 @@ export function StorageSetupCard({
79
82
  connectFlow = "file_upload",
80
83
  recordingAttemptId,
81
84
  outputId,
85
+ setupReason,
82
86
  }: StorageSetupCardProps) {
83
87
  const t = useT();
84
88
  const storageSetupHref = useStorageSetupHref();
@@ -100,6 +104,9 @@ export function StorageSetupCard({
100
104
  const connectRequestedRef = useRef(false);
101
105
  const connectIntentExpiresAtRef = useRef<number | null>(null);
102
106
  const [stepViewId] = useState(createOnboardingCorrelationId);
107
+ // Fixed for the showing: the host's status can change while the card is up,
108
+ // and every event under one step_view_id must agree on why it showed.
109
+ const [viewSetupReason] = useState(setupReason);
103
110
  const stepProperties = useMemo(
104
111
  () => ({
105
112
  flow: connectSource,
@@ -108,8 +115,69 @@ export function StorageSetupCard({
108
115
  ? { recording_attempt_id: recordingAttemptId }
109
116
  : {}),
110
117
  ...(outputId ? { output_id: outputId } : {}),
118
+ ...(viewSetupReason ? { setup_reason: viewSetupReason } : {}),
111
119
  }),
112
- [connectSource, stepViewId, recordingAttemptId, outputId],
120
+ [connectSource, stepViewId, recordingAttemptId, outputId, viewSetupReason],
121
+ );
122
+ const stepPropertiesRef = useRef(stepProperties);
123
+ stepPropertiesRef.current = stepProperties;
124
+ const builderHandedOffRef = useRef(false);
125
+ const skippedRef = useRef(false);
126
+ const customKeysOpenedAtRef = useRef<number | null>(null);
127
+ const customKeysOpenedRecently = useCallback(() => {
128
+ const openedAt = customKeysOpenedAtRef.current;
129
+ return (
130
+ openedAt !== null && Date.now() - openedAt <= CUSTOM_KEYS_CREDIT_WINDOW_MS
131
+ );
132
+ }, []);
133
+ const completionTrackedRef = useRef(false);
134
+ const returnReadInFlightRef = useRef(false);
135
+ const storageBeforeSetupRef = useRef<Promise<
136
+ { status: VideoStorageStatus } | { error: unknown }
137
+ > | null>(null);
138
+ const readStorageBeforeSetup = useCallback(() => {
139
+ storageBeforeSetupRef.current ??= fetchVideoStorageStatus({
140
+ timeoutMs: 10_000,
141
+ }).then(
142
+ (status) => ({ status }),
143
+ (error: unknown) => ({ error }),
144
+ );
145
+ }, []);
146
+
147
+ // The route hosting this card can unmount it the moment the shared storage
148
+ // status reads configured, before this card's poll sees it, so the unmount
149
+ // path below reads the status once more and records through this too.
150
+ // Only a change to the workspace's own storage completes this step: free
151
+ // hosted clips also read as configured, and own storage that was already
152
+ // there at the showing's first setup click was not set up by this card.
153
+ // The read of storage before setup must start before any setup request
154
+ // can, since one-request account activation can finish before a read sent
155
+ // with it.
156
+ const trackCompletedIfConfigured = useCallback(
157
+ (status: VideoStorageStatus) => {
158
+ const before = storageBeforeSetupRef.current;
159
+ if (status.mode !== "own" || !before || completionTrackedRef.current) {
160
+ return;
161
+ }
162
+ // Claimed before the wait, so reads landing together record once.
163
+ completionTrackedRef.current = true;
164
+ void before.then((read) => {
165
+ if ("error" in read) {
166
+ console.warn(
167
+ "[storage-setup] storage before setup could not be read:",
168
+ read.error,
169
+ );
170
+ return;
171
+ }
172
+ if (read.status.mode === "own" || skippedRef.current) return;
173
+ const methodId = status.activeProvider?.id;
174
+ trackStorageStep("onboarding_step_completed", {
175
+ ...stepPropertiesRef.current,
176
+ ...(methodId ? { method_id: methodId } : {}),
177
+ });
178
+ });
179
+ },
180
+ [],
113
181
  );
114
182
 
115
183
  const clearConfiguredTimeout = useCallback(() => {
@@ -177,10 +245,7 @@ export function StorageSetupCard({
177
245
  stop();
178
246
  setConnecting(false);
179
247
  setConnected(true);
180
- trackStorageStep("onboarding_step_completed", {
181
- ...stepProperties,
182
- method_id: "builder",
183
- });
248
+ trackCompletedIfConfigured(status);
184
249
  clearConfiguredTimeout();
185
250
  configuredTimeoutRef.current = setTimeout(() => {
186
251
  configuredTimeoutRef.current = null;
@@ -211,9 +276,9 @@ export function StorageSetupCard({
211
276
  }, [
212
277
  clearConfiguredTimeout,
213
278
  onConfigured,
214
- stepProperties,
215
279
  stopVisibilityHandler,
216
280
  t,
281
+ trackCompletedIfConfigured,
217
282
  ]);
218
283
 
219
284
  const handleBuilderConnected = useCallback(() => {
@@ -222,6 +287,7 @@ export function StorageSetupCard({
222
287
  connectRequestedRef.current = false;
223
288
  connectIntentExpiresAtRef.current = null;
224
289
  if (expiresAt !== null && Date.now() > expiresAt) return;
290
+ builderHandedOffRef.current = true;
225
291
  startFileUploadPoll();
226
292
  }, [startFileUploadPoll]);
227
293
 
@@ -269,6 +335,7 @@ export function StorageSetupCard({
269
335
  setStatusIssue("connect-not-allowed");
270
336
  return;
271
337
  }
338
+ readStorageBeforeSetup();
272
339
  connectRequestedRef.current = true;
273
340
  connectIntentExpiresAtRef.current = null;
274
341
  setStatusIssue(null);
@@ -281,7 +348,12 @@ export function StorageSetupCard({
281
348
  scope: builderConnectionScope,
282
349
  });
283
350
  },
284
- [builderConnectionScope, builderConnect.start, stepProperties],
351
+ [
352
+ builderConnectionScope,
353
+ builderConnect.start,
354
+ readStorageBeforeSetup,
355
+ stepProperties,
356
+ ],
285
357
  );
286
358
  const handleBuilderCancel = useCallback(() => {
287
359
  connectIntentExpiresAtRef.current =
@@ -290,6 +362,7 @@ export function StorageSetupCard({
290
362
  }, [builderConnect.cancel]);
291
363
  const handleSkip = useCallback(() => {
292
364
  clearConfiguredTimeout();
365
+ skippedRef.current = true;
293
366
  trackStorageStep("onboarding_step_skipped", {
294
367
  ...stepProperties,
295
368
  });
@@ -307,8 +380,41 @@ export function StorageSetupCard({
307
380
  )
308
381
  : null;
309
382
  const builderConnecting = builderConnect.connecting;
383
+ const builderConnectingRef = useRef(builderConnecting);
384
+ builderConnectingRef.current = builderConnecting;
310
385
  const actionConnecting = connecting || builderConnecting;
311
386
 
387
+ // The custom-keys form opens in another tab, and some routes keep this card
388
+ // mounted after it saves, so a return to this tab reads the status too.
389
+ useEffect(() => {
390
+ const readOnReturn = () => {
391
+ if (
392
+ document.hidden ||
393
+ !customKeysOpenedRecently() ||
394
+ completionTrackedRef.current ||
395
+ skippedRef.current ||
396
+ returnReadInFlightRef.current
397
+ ) {
398
+ return;
399
+ }
400
+ returnReadInFlightRef.current = true;
401
+ void fetchVideoStorageStatus({ timeoutMs: 10_000 })
402
+ .then(trackCompletedIfConfigured)
403
+ .catch((err) => {
404
+ console.warn("[storage-setup] completion check failed:", err);
405
+ })
406
+ .finally(() => {
407
+ returnReadInFlightRef.current = false;
408
+ });
409
+ };
410
+ window.addEventListener("focus", readOnReturn);
411
+ document.addEventListener("visibilitychange", readOnReturn);
412
+ return () => {
413
+ window.removeEventListener("focus", readOnReturn);
414
+ document.removeEventListener("visibilitychange", readOnReturn);
415
+ };
416
+ }, [customKeysOpenedRecently, trackCompletedIfConfigured]);
417
+
312
418
  useEffect(() => {
313
419
  trackStorageStep("onboarding_step_viewed", {
314
420
  ...stepProperties,
@@ -319,6 +425,24 @@ export function StorageSetupCard({
319
425
  mountedRef.current = true;
320
426
  return () => {
321
427
  mountedRef.current = false;
428
+ // The route can unmount this card mid-setup: while the connect flow is
429
+ // still connecting, or after this card accepted the connect but before
430
+ // its storage poll saw it. After a cancel the flow keeps connecting
431
+ // through its popup-closed grace, so crediting then relies on that grace
432
+ // staying shorter than CANCELLED_SETUP_RECOVERY_MS.
433
+ const setupPending =
434
+ !completionTrackedRef.current &&
435
+ !skippedRef.current &&
436
+ (builderHandedOffRef.current ||
437
+ builderConnectingRef.current ||
438
+ customKeysOpenedRecently());
439
+ if (setupPending) {
440
+ void fetchVideoStorageStatus({ timeoutMs: 10_000 })
441
+ .then(trackCompletedIfConfigured)
442
+ .catch((err) => {
443
+ console.warn("[storage-setup] completion check failed:", err);
444
+ });
445
+ }
322
446
  clearConfiguredTimeout();
323
447
  if (pollRef.current) {
324
448
  clearInterval(pollRef.current);
@@ -326,7 +450,12 @@ export function StorageSetupCard({
326
450
  }
327
451
  stopVisibilityHandler();
328
452
  };
329
- }, [clearConfiguredTimeout, stopVisibilityHandler]);
453
+ }, [
454
+ clearConfiguredTimeout,
455
+ customKeysOpenedRecently,
456
+ stopVisibilityHandler,
457
+ trackCompletedIfConfigured,
458
+ ]);
330
459
 
331
460
  return (
332
461
  <div className="mx-auto flex w-full max-w-md flex-col gap-5 rounded-2xl border border-border bg-card p-6 shadow-lg">
@@ -357,6 +486,7 @@ export function StorageSetupCard({
357
486
  cancel: undefined,
358
487
  }}
359
488
  onConnect={handleBuilderConnect}
489
+ onTriggerClick={readStorageBeforeSetup}
360
490
  >
361
491
  <Button
362
492
  type="button"
@@ -379,12 +509,14 @@ export function StorageSetupCard({
379
509
  href={storageSetupHref}
380
510
  target="_blank"
381
511
  rel="noopener noreferrer"
382
- onClick={() =>
512
+ onClick={() => {
513
+ readStorageBeforeSetup();
514
+ customKeysOpenedAtRef.current = Date.now();
383
515
  trackStorageStep("onboarding_method_clicked", {
384
516
  ...stepProperties,
385
517
  method_id: "s3",
386
- })
387
- }
518
+ });
519
+ }}
388
520
  >
389
521
  {t("storageSetup.customKeys")}
390
522
  </a>
@@ -1279,6 +1279,16 @@ export function shouldRedirectToStorageSetupHome({
1279
1279
  );
1280
1280
  }
1281
1281
 
1282
+ export function storageSetupReason(
1283
+ status: Pick<VideoStorageStatus, "denyReason"> | undefined,
1284
+ ): string {
1285
+ if (!status) return "status_unavailable";
1286
+ // "disabled" means free hosted clips are off, not that a hosted clip was denied.
1287
+ return status.denyReason && status.denyReason !== "disabled"
1288
+ ? `hosted_${status.denyReason}`
1289
+ : "storage_required";
1290
+ }
1291
+
1282
1292
  export function shouldShowFirstRunStorageSetup({
1283
1293
  storageConfigured,
1284
1294
  storageStatusUnavailable,
@@ -4187,6 +4197,7 @@ export default function RecordRoute() {
4187
4197
  showBuilderStorageOffer={!hostedAllowanceExhausted}
4188
4198
  connectSource="clips_record_storage_setup_card"
4189
4199
  connectFlow="record"
4200
+ setupReason={storageSetupReason(storageQuery.data)}
4190
4201
  />
4191
4202
  </div>
4192
4203
  ) : showStorageStatusUnavailable ? (