@agent-native/core 0.129.1 → 0.130.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (596) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +78 -0
  3. package/corpus/core/docs/content/cloneable-saas.mdx +114 -0
  4. package/corpus/core/docs/content/creating-templates.mdx +28 -2
  5. package/corpus/core/docs/content/syncing-template-changes.mdx +7 -0
  6. package/corpus/core/docs/content/template-clips-developers.mdx +12 -5
  7. package/corpus/core/docs/content/template-dispatch-developers.mdx +1 -1
  8. package/corpus/core/docs/content/template-dispatch.mdx +1 -1
  9. package/corpus/core/package.json +1 -1
  10. package/corpus/core/src/action-ui.ts +1 -0
  11. package/corpus/core/src/action.ts +5 -1
  12. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +15 -5
  13. package/corpus/core/src/agent/harness/runner.ts +5 -0
  14. package/corpus/core/src/agent/production-agent.ts +33 -0
  15. package/corpus/core/src/agent/run-loop-with-resume.ts +5 -0
  16. package/corpus/core/src/agent/run-manager.ts +126 -1
  17. package/corpus/core/src/agent/thread-data-builder.ts +47 -2
  18. package/corpus/core/src/agent/types.ts +2 -0
  19. package/corpus/core/src/chat-threads/store.ts +4 -5
  20. package/corpus/core/src/cli/create.ts +1139 -65
  21. package/corpus/core/src/cli/design-connect.ts +3 -7
  22. package/corpus/core/src/cli/index.ts +5 -3
  23. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
  24. package/corpus/core/src/client/AssistantChat.tsx +27 -16
  25. package/corpus/core/src/client/agent-chat-adapter.ts +24 -0
  26. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +17 -2
  27. package/corpus/core/src/client/chat/index.ts +1 -0
  28. package/corpus/core/src/client/chat/message-components.tsx +104 -1
  29. package/corpus/core/src/client/chat/run-recovery.tsx +15 -3
  30. package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +10 -29
  31. package/corpus/core/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
  32. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
  33. package/corpus/core/src/client/chat/widgets/workspace-file-result.ts +49 -0
  34. package/corpus/core/src/client/embed-auth.ts +2 -0
  35. package/corpus/core/src/client/org/InvitationBanner.tsx +14 -1
  36. package/corpus/core/src/client/org/OrgSwitcher.tsx +23 -0
  37. package/corpus/core/src/client/org/TeamPage.tsx +130 -0
  38. package/corpus/core/src/client/org/WorkspaceNotice.tsx +85 -0
  39. package/corpus/core/src/client/org/hooks.ts +14 -0
  40. package/corpus/core/src/client/org/index.ts +2 -0
  41. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -40
  42. package/corpus/core/src/client/resources/ResourcesPanel.tsx +134 -51
  43. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +7 -0
  44. package/corpus/core/src/client/resources/use-resources.ts +6 -0
  45. package/corpus/core/src/client/use-action.ts +3 -1
  46. package/corpus/core/src/client/widgets/index.ts +1 -0
  47. package/corpus/core/src/coding-tools/run-code.ts +2 -1
  48. package/corpus/core/src/db/client.ts +28 -4
  49. package/corpus/core/src/deploy/build.ts +31 -3
  50. package/corpus/core/src/eject/remote-mcp-presets.ts +1 -1
  51. package/corpus/core/src/index.browser.ts +1 -0
  52. package/corpus/core/src/index.ts +1 -0
  53. package/corpus/core/src/integrations/google-docs-poller.ts +3 -0
  54. package/corpus/core/src/integrations/webhook-handler.ts +28 -11
  55. package/corpus/core/src/jobs/scheduler.ts +4 -0
  56. package/corpus/core/src/localization/default-messages.ts +3 -2
  57. package/corpus/core/src/mcp/build-server.ts +20 -2
  58. package/corpus/core/src/observability/traces.ts +48 -12
  59. package/corpus/core/src/org/handlers.ts +52 -1
  60. package/corpus/core/src/org/migrations.ts +4 -0
  61. package/corpus/core/src/org/plugin.ts +14 -0
  62. package/corpus/core/src/org/schema.ts +1 -0
  63. package/corpus/core/src/org/types.ts +8 -0
  64. package/corpus/core/src/org/workspace-url.ts +74 -0
  65. package/corpus/core/src/resources/handlers.ts +35 -5
  66. package/corpus/core/src/scripts/runner.ts +183 -5
  67. package/corpus/core/src/server/action-routes.ts +32 -2
  68. package/corpus/core/src/server/agent-chat-plugin.ts +29 -0
  69. package/corpus/core/src/server/agent-teams.ts +10 -1
  70. package/corpus/core/src/server/auth.ts +33 -7
  71. package/corpus/core/src/server/embed-route.ts +9 -0
  72. package/corpus/core/src/server/embed-session.ts +62 -3
  73. package/corpus/core/src/server/open-route.ts +20 -6
  74. package/corpus/core/src/server/prompts/framework-core-compact.ts +1 -1
  75. package/corpus/core/src/server/prompts/framework-core.ts +1 -0
  76. package/corpus/core/src/server/request-context.ts +36 -1
  77. package/corpus/core/src/shared/embed-auth.ts +1 -0
  78. package/corpus/core/src/shared/html-document.ts +115 -0
  79. package/corpus/core/src/shared/index.ts +1 -0
  80. package/corpus/core/src/sharing/access.ts +8 -1
  81. package/corpus/core/src/sharing/registry.ts +11 -2
  82. package/corpus/core/src/workspace-files/actions.ts +100 -0
  83. package/corpus/core/src/workspace-files/tool.ts +4 -0
  84. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +4 -0
  85. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +9 -1
  86. package/corpus/templates/analytics/AGENTS.md +17 -24
  87. package/corpus/templates/analytics/actions/data-source-status.ts +11 -1
  88. package/corpus/templates/analytics/app/i18n-data.ts +34 -0
  89. package/corpus/templates/analytics/app/lib/data-source-status.ts +86 -0
  90. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +9 -0
  91. package/corpus/templates/analytics/app/pages/DataSources.tsx +184 -20
  92. package/corpus/templates/analytics/changelog/2026-07-28-native-analytics-data-sources-no-longer-show-duplicate-mc.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-07-29-exports-now-appear-in-chat-with-a-direct-download-instead-of.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-29-queued-chat-prompts-no-longer-resend-after-analytics-navigat.md +6 -0
  95. package/corpus/templates/analytics/changelog/2026-07-29-scheduled-dashboard-emails-load-every-panel-reliably-without.md +6 -0
  96. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +6 -3
  97. package/corpus/templates/analytics/server/lib/credential-keys.ts +1 -1
  98. package/corpus/templates/analytics/server/lib/hubspot.ts +1 -1
  99. package/corpus/templates/analytics/server/lib/provider-credentials.ts +55 -1
  100. package/corpus/templates/analytics/server/plugins/agent-chat.ts +123 -27
  101. package/corpus/templates/analytics/server/routes/[...page].get.ts +2 -7
  102. package/corpus/templates/analytics/vite.config.ts +7 -0
  103. package/corpus/templates/brain/actions/list-connection-providers.ts +100 -2
  104. package/corpus/templates/brain/app/lib/brain.ts +10 -0
  105. package/corpus/templates/brain/app/routes/sources.tsx +15 -4
  106. package/corpus/templates/brain/changelog/2026-07-29-brain-automatically-retries-failed-source-syncs-and-guides-y.md +6 -0
  107. package/corpus/templates/brain/jobs/process-ingest-queue.ts +13 -0
  108. package/corpus/templates/brain/server/jobs/sync-sources.ts +27 -22
  109. package/corpus/templates/brain/server/plugins/agent-chat.ts +2 -1
  110. package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +7 -1
  111. package/corpus/templates/clips/AGENTS.md +1 -1
  112. package/corpus/templates/clips/actions/create-recording-agent-link.ts +11 -1
  113. package/corpus/templates/clips/actions/update-comment.ts +68 -0
  114. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +0 -1
  115. package/corpus/templates/clips/app/components/player/comments-panel.tsx +221 -65
  116. package/corpus/templates/clips/app/components/player/video-player.tsx +101 -27
  117. package/corpus/templates/clips/app/i18n/en-US.ts +2 -1
  118. package/corpus/templates/clips/app/routes/bug-report.done.tsx +48 -27
  119. package/corpus/templates/clips/app/routes/bug-report.tsx +43 -4
  120. package/corpus/templates/clips/app/routes/record.tsx +2 -0
  121. package/corpus/templates/clips/changelog/2026-07-29-clip-playback-now-recovers-instead-of-getting-stuck-when-a-b.md +6 -0
  122. package/corpus/templates/clips/changelog/2026-07-29-comments-can-now-be-edited-after-posting.md +6 -0
  123. package/corpus/templates/clips/changelog/2026-07-29-download-actions-now-use-a-shorter-clearer-label.md +6 -0
  124. package/corpus/templates/clips/changelog/2026-07-29-embedded-bug-reports-now-return-a-temporary-agent-readable.md +6 -0
  125. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  126. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +21 -1
  127. package/corpus/templates/clips/shared/bug-report.ts +99 -0
  128. package/corpus/templates/content/actions/_content-database-personal-view.ts +7 -1
  129. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -2
  130. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +20 -8
  131. package/corpus/templates/content/changelog/2026-07-29-missing-databases-no-longer-trigger-repeated-personal-view-e.md +6 -0
  132. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +83 -47
  133. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +474 -55
  134. package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +3 -0
  135. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +18 -1
  136. package/corpus/templates/design/.generated/bridge/source-location.generated.ts +348 -0
  137. package/corpus/templates/design/actions/add-localhost-screens.ts +2 -7
  138. package/corpus/templates/design/actions/apply-visual-edit.ts +7 -1
  139. package/corpus/templates/design/actions/connect-localhost.ts +5 -9
  140. package/corpus/templates/design/actions/create-component.ts +16 -13
  141. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +2 -7
  142. package/corpus/templates/design/actions/list-design-source-capabilities.ts +4 -7
  143. package/corpus/templates/design/actions/list-local-files.ts +26 -50
  144. package/corpus/templates/design/actions/list-localhost-connections.ts +2 -7
  145. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +2 -2
  146. package/corpus/templates/design/actions/open-visual-edit.ts +234 -135
  147. package/corpus/templates/design/actions/read-local-file.ts +26 -50
  148. package/corpus/templates/design/actions/request-localhost-write-consent.ts +2 -7
  149. package/corpus/templates/design/actions/write-local-file.ts +31 -73
  150. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +307 -133
  151. package/corpus/templates/design/app/components/design/EditPanel.tsx +94 -22
  152. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +137 -60
  153. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +75 -14
  154. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +755 -97
  155. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +8 -0
  156. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +24 -1
  157. package/corpus/templates/design/app/components/design/bridge/source-location.bridge.ts +530 -0
  158. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbenchLoader.tsx +278 -0
  159. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +32 -12
  160. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +14 -3
  161. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +87 -24
  162. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +36 -0
  163. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +2 -1
  164. package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +48 -8
  165. package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +3 -13
  166. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +4 -2
  167. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +3 -13
  168. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +3 -1
  169. package/corpus/templates/design/app/components/design/edit-panel/inspect-code-source.ts +93 -0
  170. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +4 -2
  171. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +35 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +24 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -0
  174. package/corpus/templates/design/app/components/design/types.ts +29 -0
  175. package/corpus/templates/design/app/components/layout/Layout.tsx +7 -4
  176. package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -0
  177. package/corpus/templates/design/app/i18n-data.ts +153 -46
  178. package/corpus/templates/design/app/i18n-responsive-interact.ts +12 -12
  179. package/corpus/templates/design/app/lib/design-editor-route.ts +23 -0
  180. package/corpus/templates/design/app/pages/VisualEdit.tsx +19 -6
  181. package/corpus/templates/design/app/pages/design-editor/action-error.ts +19 -0
  182. package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +29 -0
  183. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +6 -0
  184. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +277 -15
  185. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +75 -9
  186. package/corpus/templates/design/app/pages/design-editor/layer-state-scope.ts +28 -0
  187. package/corpus/templates/design/app/pages/design-editor/live-screen-layer-drop.ts +41 -0
  188. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +496 -41
  189. package/corpus/templates/design/app/pages/design-editor/pending-live-edit-unload-guard.ts +27 -0
  190. package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +10 -0
  191. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +168 -9
  192. package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +41 -3
  193. package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +5 -0
  194. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +42 -13
  195. package/corpus/templates/design/app/pages/design-editor/source-location.ts +180 -0
  196. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +17 -0
  197. package/corpus/templates/design/app/public-routes.ts +1 -0
  198. package/corpus/templates/design/app/routes/visual-edit.$id.tsx +15 -0
  199. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +1 -1
  200. package/corpus/templates/design/changelog/2026-07-28-deleting-a-selected-element-now-removes-it-from-the-live-pre.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-28-deleting-an-element-on-a-connected-localhost-screen-now-queu.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-shows-a-responsive-device-preview-with-pre.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-28-layer-hide-and-lock-now-apply-to-the-live-preview-instead-of.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-28-layers-panel-selections-on-a-connected-localhost-screen-can-.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-28-locked-layers-now-show-a-subtle-dashed-outline-in-the-live-p.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-28-the-apply-design-updates-button-now-counts-pending-live-text.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-28-the-design-canvas-no-longer-stalls-on-a-render-loop-while-a-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-28-visual-edit-on-a-local-dev-server-no-longer-requires-signing.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-28-visual-edits-on-a-react-19-app-no-longer-report-a-source-lin.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-29-an-edit-that-cannot-be-written-to-source-now-names-the-reaso.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-29-applying-a-primitive-dropped-onto-a-connected-localhost-scre.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-29-changing-a-style-in-the-inspector-now-updates-a-connected-lo.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-29-choosing-edit-or-annotate-from-a-focused-screen-now-returns-.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-29-concurrent-source-edits-now-return-a-clear-conflict-response.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-29-creating-a-component-from-an-element-that-repeats-in-the-sou.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-29-deleting-an-element-you-just-dropped-onto-a-live-screen-no-l.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-29-dragging-a-layer-across-screens-no-longer-dies-when-the-poin.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-29-dragging-an-element-from-a-stored-screen-into-a-live-localho.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-29-drawing-a-box-on-the-canvas-and-dragging-it-into-a-connected.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-29-fixed-a-loading-screen-flicker-when-reopening-designs-with-a.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-29-interact-mode-has-edit-and-annotate-buttons-again-so-you-can.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-29-layer-groups-can-be-dropped-onto-live-localhost-screens.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-29-localhost-source-errors-now-say-what-actually-went-wrong-whi.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-29-opening-or-refreshing-a-live-localhost-screen-in-single-scre.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-29-redo-after-undoing-a-drop-onto-a-live-screen-puts-the-elemen.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-29-shapes-can-be-drawn-directly-onto-live-localhost-screens.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-29-shapes-drawn-on-the-canvas-board-now-appear-immediately-inst.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-29-style-edits-on-a-running-localhost-screen-now-preview-live-a.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-29-the-apply-design-updates-control-now-appears-as-a-clean-conn.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-29-visual-edits-on-a-mapped-component-instance-now-tell-the-age.md +6 -0
  231. package/corpus/templates/design/server/lib/design-data-access.ts +21 -7
  232. package/corpus/templates/design/server/lib/localhost-connection.ts +240 -0
  233. package/corpus/templates/design/server/routes/[...page].get.ts +2 -5
  234. package/corpus/templates/design/server/source-workspace.ts +2 -0
  235. package/corpus/templates/design/shared/code-layer.ts +43 -0
  236. package/corpus/templates/design/shared/html-content.ts +26 -0
  237. package/corpus/templates/design/shared/html-integrity.ts +21 -1
  238. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +15 -0
  239. package/corpus/templates/design/shared/source-mode.ts +76 -0
  240. package/corpus/templates/dispatch/changelog/2026-07-29-workspace-members-without-admin-access-now-see-a-clear-permi.md +6 -0
  241. package/corpus/templates/dispatch/server/lib/usage-metrics.ts +2 -1
  242. package/corpus/templates/plan/app/lib/plan-annotation-runtime.ts +3 -4
  243. package/corpus/templates/plan/server/routes/[...page].get.ts +2 -5
  244. package/corpus/templates/slides/actions/patch-deck.ts +29 -17
  245. package/corpus/toolkit/CHANGELOG.md +6 -0
  246. package/corpus/toolkit/package.json +1 -1
  247. package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +3 -2
  248. package/dist/action-ui.d.ts +1 -0
  249. package/dist/action-ui.d.ts.map +1 -1
  250. package/dist/action-ui.js +1 -0
  251. package/dist/action-ui.js.map +1 -1
  252. package/dist/action.d.ts +5 -1
  253. package/dist/action.d.ts.map +1 -1
  254. package/dist/action.js.map +1 -1
  255. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  256. package/dist/agent/engine/ai-sdk-engine.js +13 -5
  257. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  258. package/dist/agent/harness/runner.d.ts.map +1 -1
  259. package/dist/agent/harness/runner.js +5 -0
  260. package/dist/agent/harness/runner.js.map +1 -1
  261. package/dist/agent/production-agent.d.ts +16 -0
  262. package/dist/agent/production-agent.d.ts.map +1 -1
  263. package/dist/agent/production-agent.js +18 -1
  264. package/dist/agent/production-agent.js.map +1 -1
  265. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  266. package/dist/agent/run-loop-with-resume.js +6 -0
  267. package/dist/agent/run-loop-with-resume.js.map +1 -1
  268. package/dist/agent/run-manager.d.ts +14 -0
  269. package/dist/agent/run-manager.d.ts.map +1 -1
  270. package/dist/agent/run-manager.js +91 -1
  271. package/dist/agent/run-manager.js.map +1 -1
  272. package/dist/agent/thread-data-builder.d.ts +3 -0
  273. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  274. package/dist/agent/thread-data-builder.js +35 -3
  275. package/dist/agent/thread-data-builder.js.map +1 -1
  276. package/dist/agent/types.d.ts +2 -0
  277. package/dist/agent/types.d.ts.map +1 -1
  278. package/dist/agent/types.js.map +1 -1
  279. package/dist/chat-threads/store.d.ts.map +1 -1
  280. package/dist/chat-threads/store.js +4 -6
  281. package/dist/chat-threads/store.js.map +1 -1
  282. package/dist/cli/create.d.ts +66 -6
  283. package/dist/cli/create.d.ts.map +1 -1
  284. package/dist/cli/create.js +765 -66
  285. package/dist/cli/create.js.map +1 -1
  286. package/dist/cli/design-connect.d.ts.map +1 -1
  287. package/dist/cli/design-connect.js +2 -7
  288. package/dist/cli/design-connect.js.map +1 -1
  289. package/dist/cli/index.js +5 -3
  290. package/dist/cli/index.js.map +1 -1
  291. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  292. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
  293. package/dist/cli/skills-content/design-visual-edit-skill.js +83 -47
  294. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
  295. package/dist/client/AssistantChat.d.ts.map +1 -1
  296. package/dist/client/AssistantChat.js +16 -9
  297. package/dist/client/AssistantChat.js.map +1 -1
  298. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  299. package/dist/client/agent-chat-adapter.js +21 -0
  300. package/dist/client/agent-chat-adapter.js.map +1 -1
  301. package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
  302. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  303. package/dist/client/blocks/library/wireframe-kit.js +8 -2
  304. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  305. package/dist/client/chat/index.d.ts +1 -1
  306. package/dist/client/chat/index.d.ts.map +1 -1
  307. package/dist/client/chat/index.js +1 -1
  308. package/dist/client/chat/index.js.map +1 -1
  309. package/dist/client/chat/message-components.d.ts +17 -0
  310. package/dist/client/chat/message-components.d.ts.map +1 -1
  311. package/dist/client/chat/message-components.js +24 -2
  312. package/dist/client/chat/message-components.js.map +1 -1
  313. package/dist/client/chat/run-recovery.d.ts +6 -0
  314. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  315. package/dist/client/chat/run-recovery.js +13 -3
  316. package/dist/client/chat/run-recovery.js.map +1 -1
  317. package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
  318. package/dist/client/chat/widgets/DataTableWidget.js +2 -3
  319. package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
  320. package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts +5 -0
  321. package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts.map +1 -0
  322. package/dist/client/chat/widgets/WorkspaceFileWidget.js +34 -0
  323. package/dist/client/chat/widgets/WorkspaceFileWidget.js.map +1 -0
  324. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  325. package/dist/client/chat/widgets/builtin-tool-renderers.js +13 -1
  326. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  327. package/dist/client/chat/widgets/workspace-file-result.d.ts +12 -0
  328. package/dist/client/chat/widgets/workspace-file-result.d.ts.map +1 -0
  329. package/dist/client/chat/widgets/workspace-file-result.js +34 -0
  330. package/dist/client/chat/widgets/workspace-file-result.js.map +1 -0
  331. package/dist/client/embed-auth.d.ts.map +1 -1
  332. package/dist/client/embed-auth.js +2 -1
  333. package/dist/client/embed-auth.js.map +1 -1
  334. package/dist/client/org/InvitationBanner.d.ts +5 -2
  335. package/dist/client/org/InvitationBanner.d.ts.map +1 -1
  336. package/dist/client/org/InvitationBanner.js +9 -2
  337. package/dist/client/org/InvitationBanner.js.map +1 -1
  338. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  339. package/dist/client/org/OrgSwitcher.js +3 -2
  340. package/dist/client/org/OrgSwitcher.js.map +1 -1
  341. package/dist/client/org/TeamPage.d.ts.map +1 -1
  342. package/dist/client/org/TeamPage.js +27 -3
  343. package/dist/client/org/TeamPage.js.map +1 -1
  344. package/dist/client/org/WorkspaceNotice.d.ts +18 -0
  345. package/dist/client/org/WorkspaceNotice.d.ts.map +1 -0
  346. package/dist/client/org/WorkspaceNotice.js +56 -0
  347. package/dist/client/org/WorkspaceNotice.js.map +1 -0
  348. package/dist/client/org/hooks.d.ts +1 -0
  349. package/dist/client/org/hooks.d.ts.map +1 -1
  350. package/dist/client/org/hooks.js +12 -0
  351. package/dist/client/org/hooks.js.map +1 -1
  352. package/dist/client/org/index.d.ts +2 -1
  353. package/dist/client/org/index.d.ts.map +1 -1
  354. package/dist/client/org/index.js +2 -1
  355. package/dist/client/org/index.js.map +1 -1
  356. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  357. package/dist/client/resources/McpIntegrationDialog.js +8 -12
  358. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  359. package/dist/client/resources/ResourcesPanel.d.ts +3 -0
  360. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  361. package/dist/client/resources/ResourcesPanel.js +49 -20
  362. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  363. package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
  364. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  365. package/dist/client/resources/mcp-integration-catalog.js +3 -0
  366. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  367. package/dist/client/resources/use-resources.d.ts +1 -0
  368. package/dist/client/resources/use-resources.d.ts.map +1 -1
  369. package/dist/client/resources/use-resources.js +3 -0
  370. package/dist/client/resources/use-resources.js.map +1 -1
  371. package/dist/client/use-action.d.ts +3 -1
  372. package/dist/client/use-action.d.ts.map +1 -1
  373. package/dist/client/use-action.js +3 -1
  374. package/dist/client/use-action.js.map +1 -1
  375. package/dist/client/widgets/index.d.ts +1 -1
  376. package/dist/client/widgets/index.d.ts.map +1 -1
  377. package/dist/client/widgets/index.js +1 -1
  378. package/dist/client/widgets/index.js.map +1 -1
  379. package/dist/coding-tools/run-code.js +2 -1
  380. package/dist/coding-tools/run-code.js.map +1 -1
  381. package/dist/collab/awareness.d.ts +2 -2
  382. package/dist/collab/awareness.d.ts.map +1 -1
  383. package/dist/collab/routes.d.ts +1 -1
  384. package/dist/db/client.d.ts +1 -0
  385. package/dist/db/client.d.ts.map +1 -1
  386. package/dist/db/client.js +24 -3
  387. package/dist/db/client.js.map +1 -1
  388. package/dist/deploy/build.d.ts.map +1 -1
  389. package/dist/deploy/build.js +25 -3
  390. package/dist/deploy/build.js.map +1 -1
  391. package/dist/eject/remote-mcp-presets.js +1 -1
  392. package/dist/eject/remote-mcp-presets.js.map +1 -1
  393. package/dist/index.browser.d.ts +1 -1
  394. package/dist/index.browser.d.ts.map +1 -1
  395. package/dist/index.browser.js +1 -1
  396. package/dist/index.browser.js.map +1 -1
  397. package/dist/index.d.ts +1 -1
  398. package/dist/index.d.ts.map +1 -1
  399. package/dist/index.js +1 -1
  400. package/dist/index.js.map +1 -1
  401. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  402. package/dist/integrations/google-docs-poller.js +4 -1
  403. package/dist/integrations/google-docs-poller.js.map +1 -1
  404. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  405. package/dist/integrations/webhook-handler.js +23 -11
  406. package/dist/integrations/webhook-handler.js.map +1 -1
  407. package/dist/jobs/scheduler.js +4 -0
  408. package/dist/jobs/scheduler.js.map +1 -1
  409. package/dist/localization/default-messages.d.ts +3 -2
  410. package/dist/localization/default-messages.d.ts.map +1 -1
  411. package/dist/localization/default-messages.js +3 -2
  412. package/dist/localization/default-messages.js.map +1 -1
  413. package/dist/mcp/build-server.d.ts.map +1 -1
  414. package/dist/mcp/build-server.js +10 -1
  415. package/dist/mcp/build-server.js.map +1 -1
  416. package/dist/notifications/routes.d.ts +1 -1
  417. package/dist/observability/traces.d.ts.map +1 -1
  418. package/dist/observability/traces.js +27 -7
  419. package/dist/observability/traces.js.map +1 -1
  420. package/dist/org/handlers.d.ts +15 -0
  421. package/dist/org/handlers.d.ts.map +1 -1
  422. package/dist/org/handlers.js +45 -1
  423. package/dist/org/handlers.js.map +1 -1
  424. package/dist/org/migrations.d.ts.map +1 -1
  425. package/dist/org/migrations.js +4 -0
  426. package/dist/org/migrations.js.map +1 -1
  427. package/dist/org/plugin.d.ts +1 -0
  428. package/dist/org/plugin.d.ts.map +1 -1
  429. package/dist/org/plugin.js +10 -1
  430. package/dist/org/plugin.js.map +1 -1
  431. package/dist/org/schema.d.ts +19 -0
  432. package/dist/org/schema.d.ts.map +1 -1
  433. package/dist/org/schema.js +1 -0
  434. package/dist/org/schema.js.map +1 -1
  435. package/dist/org/types.d.ts +8 -0
  436. package/dist/org/types.d.ts.map +1 -1
  437. package/dist/org/types.js.map +1 -1
  438. package/dist/org/workspace-url.d.ts +32 -0
  439. package/dist/org/workspace-url.d.ts.map +1 -0
  440. package/dist/org/workspace-url.js +63 -0
  441. package/dist/org/workspace-url.js.map +1 -0
  442. package/dist/progress/routes.d.ts +1 -1
  443. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  444. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  445. package/dist/resources/handlers.d.ts +2 -3
  446. package/dist/resources/handlers.d.ts.map +1 -1
  447. package/dist/resources/handlers.js +25 -5
  448. package/dist/resources/handlers.js.map +1 -1
  449. package/dist/scripts/runner.d.ts +17 -0
  450. package/dist/scripts/runner.d.ts.map +1 -1
  451. package/dist/scripts/runner.js +130 -5
  452. package/dist/scripts/runner.js.map +1 -1
  453. package/dist/secrets/routes.d.ts +9 -9
  454. package/dist/server/action-routes.d.ts.map +1 -1
  455. package/dist/server/action-routes.js +24 -2
  456. package/dist/server/action-routes.js.map +1 -1
  457. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  458. package/dist/server/agent-chat-plugin.js +26 -1
  459. package/dist/server/agent-chat-plugin.js.map +1 -1
  460. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  461. package/dist/server/agent-teams.d.ts.map +1 -1
  462. package/dist/server/agent-teams.js +10 -1
  463. package/dist/server/agent-teams.js.map +1 -1
  464. package/dist/server/auth.d.ts +22 -0
  465. package/dist/server/auth.d.ts.map +1 -1
  466. package/dist/server/auth.js +33 -7
  467. package/dist/server/auth.js.map +1 -1
  468. package/dist/server/embed-route.d.ts.map +1 -1
  469. package/dist/server/embed-route.js +6 -1
  470. package/dist/server/embed-route.js.map +1 -1
  471. package/dist/server/embed-session.d.ts +7 -0
  472. package/dist/server/embed-session.d.ts.map +1 -1
  473. package/dist/server/embed-session.js +46 -4
  474. package/dist/server/embed-session.js.map +1 -1
  475. package/dist/server/open-route.d.ts.map +1 -1
  476. package/dist/server/open-route.js +17 -7
  477. package/dist/server/open-route.js.map +1 -1
  478. package/dist/server/prompts/framework-core-compact.js +1 -1
  479. package/dist/server/prompts/framework-core-compact.js.map +1 -1
  480. package/dist/server/prompts/framework-core.d.ts.map +1 -1
  481. package/dist/server/prompts/framework-core.js +1 -0
  482. package/dist/server/prompts/framework-core.js.map +1 -1
  483. package/dist/server/request-context.d.ts +26 -0
  484. package/dist/server/request-context.d.ts.map +1 -1
  485. package/dist/server/request-context.js +18 -1
  486. package/dist/server/request-context.js.map +1 -1
  487. package/dist/shared/embed-auth.d.ts +1 -0
  488. package/dist/shared/embed-auth.d.ts.map +1 -1
  489. package/dist/shared/embed-auth.js +1 -0
  490. package/dist/shared/embed-auth.js.map +1 -1
  491. package/dist/shared/html-document.d.ts +12 -0
  492. package/dist/shared/html-document.d.ts.map +1 -0
  493. package/dist/shared/html-document.js +92 -0
  494. package/dist/shared/html-document.js.map +1 -0
  495. package/dist/shared/index.d.ts +1 -0
  496. package/dist/shared/index.d.ts.map +1 -1
  497. package/dist/shared/index.js +1 -0
  498. package/dist/shared/index.js.map +1 -1
  499. package/dist/sharing/access.d.ts +1 -0
  500. package/dist/sharing/access.d.ts.map +1 -1
  501. package/dist/sharing/access.js +8 -2
  502. package/dist/sharing/access.js.map +1 -1
  503. package/dist/sharing/registry.d.ts +3 -0
  504. package/dist/sharing/registry.d.ts.map +1 -1
  505. package/dist/sharing/registry.js.map +1 -1
  506. package/dist/workspace-files/actions.d.ts +28 -0
  507. package/dist/workspace-files/actions.d.ts.map +1 -0
  508. package/dist/workspace-files/actions.js +75 -0
  509. package/dist/workspace-files/actions.js.map +1 -0
  510. package/dist/workspace-files/tool.d.ts.map +1 -1
  511. package/dist/workspace-files/tool.js +4 -0
  512. package/dist/workspace-files/tool.js.map +1 -1
  513. package/docs/content/cloneable-saas.mdx +114 -0
  514. package/docs/content/creating-templates.mdx +28 -2
  515. package/docs/content/syncing-template-changes.mdx +7 -0
  516. package/docs/content/template-clips-developers.mdx +12 -5
  517. package/docs/content/template-dispatch-developers.mdx +1 -1
  518. package/docs/content/template-dispatch.mdx +1 -1
  519. package/package.json +2 -2
  520. package/src/action-ui.ts +1 -0
  521. package/src/action.ts +5 -1
  522. package/src/agent/engine/ai-sdk-engine.ts +15 -5
  523. package/src/agent/harness/runner.ts +5 -0
  524. package/src/agent/production-agent.ts +33 -0
  525. package/src/agent/run-loop-with-resume.ts +5 -0
  526. package/src/agent/run-manager.ts +126 -1
  527. package/src/agent/thread-data-builder.ts +47 -2
  528. package/src/agent/types.ts +2 -0
  529. package/src/chat-threads/store.ts +4 -5
  530. package/src/cli/create.ts +1139 -65
  531. package/src/cli/design-connect.ts +3 -7
  532. package/src/cli/index.ts +5 -3
  533. package/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
  534. package/src/client/AssistantChat.tsx +27 -16
  535. package/src/client/agent-chat-adapter.ts +24 -0
  536. package/src/client/blocks/library/wireframe-kit.tsx +17 -2
  537. package/src/client/chat/index.ts +1 -0
  538. package/src/client/chat/message-components.tsx +104 -1
  539. package/src/client/chat/run-recovery.tsx +15 -3
  540. package/src/client/chat/widgets/DataTableWidget.tsx +10 -29
  541. package/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
  542. package/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
  543. package/src/client/chat/widgets/workspace-file-result.ts +49 -0
  544. package/src/client/embed-auth.ts +2 -0
  545. package/src/client/org/InvitationBanner.tsx +14 -1
  546. package/src/client/org/OrgSwitcher.tsx +23 -0
  547. package/src/client/org/TeamPage.tsx +130 -0
  548. package/src/client/org/WorkspaceNotice.tsx +85 -0
  549. package/src/client/org/hooks.ts +14 -0
  550. package/src/client/org/index.ts +2 -0
  551. package/src/client/resources/McpIntegrationDialog.tsx +27 -40
  552. package/src/client/resources/ResourcesPanel.tsx +134 -51
  553. package/src/client/resources/mcp-integration-catalog.ts +7 -0
  554. package/src/client/resources/use-resources.ts +6 -0
  555. package/src/client/use-action.ts +3 -1
  556. package/src/client/widgets/index.ts +1 -0
  557. package/src/coding-tools/run-code.ts +2 -1
  558. package/src/db/client.ts +28 -4
  559. package/src/deploy/build.ts +31 -3
  560. package/src/eject/remote-mcp-presets.ts +1 -1
  561. package/src/index.browser.ts +1 -0
  562. package/src/index.ts +1 -0
  563. package/src/integrations/google-docs-poller.ts +3 -0
  564. package/src/integrations/webhook-handler.ts +28 -11
  565. package/src/jobs/scheduler.ts +4 -0
  566. package/src/localization/default-messages.ts +3 -2
  567. package/src/mcp/build-server.ts +20 -2
  568. package/src/observability/traces.ts +48 -12
  569. package/src/org/handlers.ts +52 -1
  570. package/src/org/migrations.ts +4 -0
  571. package/src/org/plugin.ts +14 -0
  572. package/src/org/schema.ts +1 -0
  573. package/src/org/types.ts +8 -0
  574. package/src/org/workspace-url.ts +74 -0
  575. package/src/resources/handlers.ts +35 -5
  576. package/src/scripts/runner.ts +183 -5
  577. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +2 -1
  578. package/src/server/action-routes.ts +32 -2
  579. package/src/server/agent-chat-plugin.ts +29 -0
  580. package/src/server/agent-teams.ts +10 -1
  581. package/src/server/auth.ts +33 -7
  582. package/src/server/embed-route.ts +9 -0
  583. package/src/server/embed-session.ts +62 -3
  584. package/src/server/open-route.ts +20 -6
  585. package/src/server/prompts/framework-core-compact.ts +1 -1
  586. package/src/server/prompts/framework-core.ts +1 -0
  587. package/src/server/request-context.ts +36 -1
  588. package/src/shared/embed-auth.ts +1 -0
  589. package/src/shared/html-document.ts +115 -0
  590. package/src/shared/index.ts +1 -0
  591. package/src/sharing/access.ts +8 -1
  592. package/src/sharing/registry.ts +11 -2
  593. package/src/workspace-files/actions.ts +100 -0
  594. package/src/workspace-files/tool.ts +4 -0
  595. package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +0 -6
  596. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +0 -6
@@ -8,6 +8,7 @@ import {
8
8
  IconDeviceFloppy,
9
9
  IconLayoutGrid,
10
10
  } from "@tabler/icons-react";
11
+ import { useEffect, useState } from "react";
11
12
  import { Link } from "react-router";
12
13
 
13
14
  import { Button } from "@/components/ui/button";
@@ -16,10 +17,22 @@ export default function VisualEditPage() {
16
17
  const t = useT();
17
18
  const { session } = useSession();
18
19
  const hasSession = Boolean(session?.email);
19
- const primaryHref = hasSession
20
- ? "/"
21
- : buildSignInReturnHref({ returnTo: "/visual-edit?intent=save" });
22
- const primaryAriaLabel = hasSession
20
+ const [hasHydrated, setHasHydrated] = useState(false);
21
+ const [signInHref, setSignInHref] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ setHasHydrated(true);
25
+ if (hasSession) return;
26
+ setSignInHref(
27
+ buildSignInReturnHref({ returnTo: "/visual-edit?intent=save" }),
28
+ );
29
+ }, [hasSession]);
30
+
31
+ // Server shells are intentionally anonymous; defer session-specific chrome
32
+ // until after hydration so the public entry never reconciles different links.
33
+ const isSignedIn = hasHydrated && hasSession;
34
+ const primaryHref = isSignedIn ? "/" : (signInHref ?? undefined);
35
+ const primaryAriaLabel = isSignedIn
23
36
  ? t("visualEdit.openDesign")
24
37
  : t("designEditor.signUpToSave");
25
38
 
@@ -35,7 +48,7 @@ export default function VisualEditPage() {
35
48
  </Link>
36
49
  <Button asChild variant="outline" size="sm">
37
50
  <a href={primaryHref} aria-label={primaryAriaLabel}>
38
- {hasSession
51
+ {isSignedIn
39
52
  ? t("visualEdit.openDesign")
40
53
  : t("designEditor.signUpToSave")}
41
54
  <IconArrowUpRight className="size-4" />
@@ -59,7 +72,7 @@ export default function VisualEditPage() {
59
72
  <Button asChild size="lg" className="gap-2">
60
73
  <a href={primaryHref} aria-label={primaryAriaLabel}>
61
74
  <IconDeviceFloppy className="size-4" />
62
- {hasSession
75
+ {isSignedIn
63
76
  ? t("visualEdit.openDesign")
64
77
  : t("designEditor.signUpToSave")}
65
78
  </a>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The action HTTP surface echoes a real message only for explicit client
3
+ * errors; an unclassified failure comes back as a 5xx "Internal server error"
4
+ * with the cause server-log-only. Show the former as toast detail and drop the
5
+ * latter, so a toast never reads as if it explained something when it didn't.
6
+ */
7
+
8
+ /** `callAction` prefixes every rejection with `Action <name> failed: `. */
9
+ const CALL_ACTION_PREFIX = /^Action [\w.-]+ failed:\s*/;
10
+
11
+ export function actionErrorDetail(error: unknown): string | undefined {
12
+ const status = (error as { status?: unknown } | null | undefined)?.status;
13
+ if (typeof status !== "number" || status < 400 || status >= 500) {
14
+ return undefined;
15
+ }
16
+ const message = error instanceof Error ? error.message : "";
17
+ const detail = message.replace(CALL_ACTION_PREFIX, "").trim();
18
+ return detail || undefined;
19
+ }
@@ -13,6 +13,7 @@ import {
13
13
  } from "./canvas-primitives";
14
14
  import { BOARD_TEXT_AUTO_COLOR_MARKER } from "./cross-screen-text-color";
15
15
  import { escapeHtmlAttributeValue, escapeHtmlText } from "./dom-utils";
16
+ import { isStandaloneHttpUrl } from "./editor-state";
16
17
  import type { DesignFile } from "./types";
17
18
 
18
19
  export function nextDuplicatedFilename(
@@ -188,6 +189,11 @@ export function appendCanvasPrimitiveToHtml(
188
189
  options?: { preserveNegativePosition?: boolean; isBoardTarget?: boolean },
189
190
  ): string | null {
190
191
  if (typeof window === "undefined") return null;
192
+ // A live/localhost screen stores its route URL here, not a document.
193
+ // Appending to it parses the URL as body text and returns a whole HTML file,
194
+ // which the caller then persists OVER the URL — the screen stops being live
195
+ // and the route is gone. There is no correct append for this shape.
196
+ if (isStandaloneHttpUrl(content)) return null;
191
197
  try {
192
198
  const doc = new DOMParser().parseFromString(content, "text/html");
193
199
  if (!doc.body) return null;
@@ -477,3 +483,26 @@ export function appendCanvasPrimitiveToHtml(
477
483
  return null;
478
484
  }
479
485
  }
486
+
487
+ /**
488
+ * Extract one newly-created primitive from a temporary document as markup the
489
+ * live iframe bridge can insert. URL-backed screens keep their route URL in the
490
+ * Design file, so their creation path must serialize a node without ever
491
+ * rewriting that file content.
492
+ */
493
+ export function extractCanvasPrimitiveHtml(
494
+ content: string,
495
+ nodeId: string,
496
+ ): string | null {
497
+ if (typeof window === "undefined") return null;
498
+ try {
499
+ const doc = new DOMParser().parseFromString(content, "text/html");
500
+ const safeNodeId = nodeId.replace(/["\\]/g, "\\$&");
501
+ return (
502
+ doc.querySelector(`[data-agent-native-node-id="${safeNodeId}"]`)
503
+ ?.outerHTML ?? null
504
+ );
505
+ } catch {
506
+ return null;
507
+ }
508
+ }
@@ -14,6 +14,7 @@ import {
14
14
  import { uniqueLayerId } from "./canvas-primitive-insert";
15
15
  import { reassignClonedAuthoredIds } from "./clone-idrefs";
16
16
  import { queryUniqueSelector } from "./dom-utils";
17
+ import { isStandaloneHttpUrl } from "./editor-state";
17
18
  import {
18
19
  applyPortableStyles,
19
20
  elementAtPortableStylePath,
@@ -236,6 +237,11 @@ export function insertClonedHtmlLayers(
236
237
  nodeIdMap: Map<string, string>;
237
238
  } | null {
238
239
  if (typeof window === "undefined" || layerHtmls.length === 0) return null;
240
+ // Same hazard appendCanvasPrimitiveToHtml guards: a live screen's stored
241
+ // content is its route URL, and returning an "edited document" for it means
242
+ // the caller persists HTML over the URL. Paste, duplicate, and image-drop
243
+ // all land here, so refuse the shape once rather than at each of them.
244
+ if (isStandaloneHttpUrl(content)) return null;
239
245
  try {
240
246
  const doc = new DOMParser().parseFromString(content, "text/html");
241
247
  if (!doc.body) return null;
@@ -6,6 +6,11 @@ import {
6
6
  removeCodeLayerNodeFromHtml,
7
7
  } from "@shared/code-layer";
8
8
  import { isComponentInstance } from "@shared/component-model";
9
+ import {
10
+ ELEMENT_PROVENANCE_METHODS,
11
+ type ElementProvenanceFramework,
12
+ type ElementProvenanceMethod,
13
+ } from "@shared/source-mode";
9
14
  export {
10
15
  renameFilenamePreservingExtension,
11
16
  replaceDataScreenReferences,
@@ -83,6 +88,69 @@ export function codeLayerSelectorAliases(
83
88
  );
84
89
  }
85
90
 
91
+ /**
92
+ * Selector candidate groups for a host-initiated removal in the LIVE iframe
93
+ * (the bridge's `delete-element`), one group per node to remove.
94
+ *
95
+ * A live/localhost screen runs two different node-id namespaces: the document
96
+ * carries the ids the injected bridge assigned there, while every host-side
97
+ * projection is built from the fetched source snapshot that
98
+ * `ensureCodeLayerNodeIdsInHtml` stamped separately. A selector taken from the
99
+ * source projection therefore matches nothing in the running document — and
100
+ * `delete-element` is fire-and-forget, so the editor reported success and
101
+ * cleared the selection while the element stayed on screen.
102
+ *
103
+ * Order of trust: the runtime layer model's aliases (Layers-panel selection on
104
+ * a runtime-projected screen), then the source-projection selectors carrying
105
+ * the bridge-reported identities as extra candidates, then those identities
106
+ * alone. `findRuntimeTarget` in the bridge takes the first candidate that
107
+ * resolves, so a group may safely mix namespaces.
108
+ */
109
+ export function liveDeleteSelectorGroups(args: {
110
+ runtimeAliasGroups: readonly (readonly string[])[];
111
+ liveSelectionSelectors: readonly string[];
112
+ fallbackSelectors: readonly string[];
113
+ }): string[][] {
114
+ const runtimeGroups = args.runtimeAliasGroups
115
+ .map((aliases) => aliases.filter(Boolean))
116
+ .filter((aliases) => aliases.length > 0);
117
+ if (runtimeGroups.length > 0) return runtimeGroups;
118
+ const liveSelectors = Array.from(
119
+ new Set(args.liveSelectionSelectors.filter(Boolean)),
120
+ );
121
+ if (args.fallbackSelectors.length > 0) {
122
+ return args.fallbackSelectors.map((selector) =>
123
+ Array.from(new Set([selector, ...liveSelectors])),
124
+ );
125
+ }
126
+ return liveSelectors.length > 0 ? [liveSelectors] : [];
127
+ }
128
+
129
+ /**
130
+ * Whether Delete must run the LIVE-screen removal path (delete the running
131
+ * DOM node + queue a pending live edit for the coding agent) instead of
132
+ * rewriting the screen's stored source, mirroring the `localhost` branch
133
+ * handleVisualStructureChange already takes for a drag-move.
134
+ *
135
+ * Deliberately does NOT require a source-snapshot node: a Layers-panel
136
+ * selection on a runtime-projected screen only exists in the runtime id
137
+ * namespace, so gating the delete on a resolved source snapshot meant such a
138
+ * row could be selected and never deleted at all. The runtime alias groups
139
+ * ARE the target here.
140
+ */
141
+ export function shouldDeleteThroughLiveScreen(args: {
142
+ screenSourceType: string | null | undefined;
143
+ runtimeAliasGroups: readonly (readonly string[])[];
144
+ liveSelectionSelectors: readonly string[];
145
+ }): boolean {
146
+ if (args.screenSourceType !== "localhost") return false;
147
+ return (
148
+ args.runtimeAliasGroups.some((aliases) =>
149
+ aliases.some((alias) => Boolean(alias)),
150
+ ) || args.liveSelectionSelectors.some((selector) => Boolean(selector))
151
+ );
152
+ }
153
+
86
154
  export function normalizeCodeLayerSelector(selector: string): string {
87
155
  return (
88
156
  selector
@@ -326,12 +394,84 @@ function provenanceForCodeLayerNode(
326
394
  node.dataAttributes["data-source-column"],
327
395
  );
328
396
  const component = node.dataAttributes["data-component-name"]?.trim();
329
- if (!sourceFile && !line && !column && !component) return undefined;
397
+ const declaredFramework =
398
+ node.dataAttributes["data-source-framework"]?.trim();
399
+ const framework =
400
+ declaredFramework === "html" ||
401
+ declaredFramework === "react" ||
402
+ declaredFramework === "vue" ||
403
+ declaredFramework === "svelte"
404
+ ? (declaredFramework as ElementProvenanceFramework)
405
+ : undefined;
406
+ const ownerSourceFile = node.dataAttributes["data-source-owner-file"]?.trim();
407
+ const ownerLine = positiveIntegerDataAttribute(
408
+ node.dataAttributes["data-source-owner-line"],
409
+ );
410
+ const ownerColumn = positiveIntegerDataAttribute(
411
+ node.dataAttributes["data-source-owner-column"],
412
+ );
413
+ const ownerComponentName =
414
+ node.dataAttributes["data-source-owner-component"]?.trim();
415
+ const ownerKey = node.dataAttributes["data-source-owner-key"]?.trim();
416
+ const declaredMethod = node.dataAttributes["data-source-method"]?.trim();
417
+ const declaredOwnerMethod =
418
+ node.dataAttributes["data-source-owner-method"]?.trim();
419
+ // The owner site has its own tier: an authored `data-attribute` element can
420
+ // still owe its owner line to a transformed React 19 owner stack. Unlike
421
+ // `method` below there is no build-time convention to fall back on — this
422
+ // bridge is the only writer of data-source-owner-*, and it always labels the
423
+ // tier — so an unlabelled owner position stays "unknown" rather than
424
+ // inheriting a claim of authored coordinates.
425
+ const ownerMethod = ELEMENT_PROVENANCE_METHODS.includes(
426
+ declaredOwnerMethod as ElementProvenanceMethod,
427
+ )
428
+ ? (declaredOwnerMethod as ElementProvenanceMethod)
429
+ : undefined;
430
+ // The bridge stamps the tier alongside the position; a projection carrying a
431
+ // position with no tier came from a build-time transform's own attributes.
432
+ const method = ELEMENT_PROVENANCE_METHODS.includes(
433
+ declaredMethod as ElementProvenanceMethod,
434
+ )
435
+ ? (declaredMethod as ElementProvenanceMethod)
436
+ : sourceFile
437
+ ? "data-attribute"
438
+ : undefined;
439
+ const unavailable = node.dataAttributes["data-source-unavailable"]?.trim();
440
+ // The bridge only stamps a reason when the node resolved to no location, so
441
+ // a resolved anchor must never carry one back out of the projection.
442
+ const unavailableReason =
443
+ !sourceFile &&
444
+ (unavailable === "not-framework" ||
445
+ unavailable === "not-react" ||
446
+ unavailable === "no-debug-info")
447
+ ? unavailable
448
+ : undefined;
449
+ if (
450
+ !sourceFile &&
451
+ !line &&
452
+ !column &&
453
+ !component &&
454
+ !framework &&
455
+ !ownerSourceFile &&
456
+ !ownerKey &&
457
+ !unavailableReason
458
+ ) {
459
+ return undefined;
460
+ }
330
461
  return {
462
+ ...(framework ? { framework } : {}),
331
463
  ...(sourceFile ? { sourceFile } : {}),
332
464
  ...(line ? { line } : {}),
333
465
  ...(column ? { column } : {}),
334
466
  ...(component ? { component } : {}),
467
+ ...(ownerSourceFile ? { ownerSourceFile } : {}),
468
+ ...(ownerLine ? { ownerLine } : {}),
469
+ ...(ownerColumn ? { ownerColumn } : {}),
470
+ ...(ownerComponentName ? { ownerComponentName } : {}),
471
+ ...(ownerKey ? { ownerKey } : {}),
472
+ ...(method ? { method } : {}),
473
+ ...(ownerMethod ? { ownerMethod } : {}),
474
+ ...(unavailableReason ? { unavailableReason } : {}),
335
475
  };
336
476
  }
337
477
 
@@ -465,11 +605,44 @@ export function codeLayerNodeMatchesBridgeTarget(
465
605
  return codeLayerSelectorMatches(node, selector);
466
606
  }
467
607
 
468
- export function resolveCodeLayerNodeFromBridge(
608
+ /**
609
+ * `absent` and `ambiguous` must stay distinguishable all the way to the UI —
610
+ * five matching instances is a scoping question, a gone element is a different
611
+ * failure. Collapsing both into `null` is what made a style commit on a repeated
612
+ * component claim the element no longer existed while it was on screen.
613
+ */
614
+ export type CodeLayerResolution =
615
+ | { status: "resolved"; node: CodeLayerNode }
616
+ | { status: "absent" }
617
+ | { status: "ambiguous"; candidates: CodeLayerNode[] };
618
+
619
+ /**
620
+ * A client-rendered app serves `<html><body><div id="root">`, so its projection
621
+ * holds none of the markup the user can select and every selection reads as
622
+ * "absent" — a different fact, with a different remedy, from a removed element.
623
+ * The bound is deliberate: an authored page worth editing has more nodes, and a
624
+ * false positive only costs a more specific refusal message.
625
+ */
626
+ const MOUNT_SHELL_MAX_NODES = 4;
627
+
628
+ export function isClientRenderedMountShell(projection: {
629
+ nodes: CodeLayerNode[];
630
+ }): boolean {
631
+ if (projection.nodes.length > MOUNT_SHELL_MAX_NODES) return false;
632
+ return projection.nodes.every(
633
+ (node) =>
634
+ node.tag === "html" ||
635
+ node.tag === "head" ||
636
+ node.tag === "body" ||
637
+ (node.children.length === 0 && !collapsedElementText(node.textSnippet)),
638
+ );
639
+ }
640
+
641
+ export function resolveCodeLayerTargetFromBridge(
469
642
  projection: { nodes: CodeLayerNode[] },
470
643
  selector?: string,
471
644
  sourceId?: string,
472
- ): CodeLayerNode | null {
645
+ ): CodeLayerResolution {
473
646
  // Id-based match first, across the WHOLE projection (not just up to
474
647
  // whichever node the old combined-predicate `.find()` reached first) — a
475
648
  // sourceId identifies one node by its own stable id/data-attribute, which
@@ -482,7 +655,7 @@ export function resolveCodeLayerNodeFromBridge(
482
655
  const idMatch = projection.nodes.find((node) =>
483
656
  codeLayerNodeMatchesSourceId(node, sourceId),
484
657
  );
485
- if (idMatch) return idMatch;
658
+ if (idMatch) return { status: "resolved", node: idMatch };
486
659
  }
487
660
  // No sourceId (or no node carries it yet, e.g. a bridge target minted a
488
661
  // fresh pending id the projection hasn't picked up) — fall back to the
@@ -493,29 +666,49 @@ export function resolveCodeLayerNodeFromBridge(
493
666
  // selector that matches more than one node rather than silently picking
494
667
  // the first DOM-order match — mirror that discipline here so a duplicate/
495
668
  // move/style edit can never silently land on the wrong sibling instance.
496
- if (!selector) return null;
669
+ if (!selector) return { status: "absent" };
497
670
  const selectorMatches = projection.nodes.filter((node) =>
498
671
  codeLayerSelectorMatches(node, selector),
499
672
  );
500
- return selectorMatches.length === 1 ? selectorMatches[0]! : null;
673
+ if (selectorMatches.length === 1) {
674
+ return { status: "resolved", node: selectorMatches[0]! };
675
+ }
676
+ return selectorMatches.length === 0
677
+ ? { status: "absent" }
678
+ : { status: "ambiguous", candidates: selectorMatches };
679
+ }
680
+
681
+ export function resolveCodeLayerNodeFromBridge(
682
+ projection: { nodes: CodeLayerNode[] },
683
+ selector?: string,
684
+ sourceId?: string,
685
+ ): CodeLayerNode | null {
686
+ const resolution = resolveCodeLayerTargetFromBridge(
687
+ projection,
688
+ selector,
689
+ sourceId,
690
+ );
691
+ return resolution.status === "resolved" ? resolution.node : null;
501
692
  }
502
693
 
503
694
  export function collapsedElementText(value: string | null | undefined): string {
504
695
  return value?.replace(/\s+/g, " ").trim() ?? "";
505
696
  }
506
697
 
507
- export function resolveCodeLayerNodeFromElementInfo(
698
+ export function resolveCodeLayerTargetFromElementInfo(
508
699
  projection: { nodes: CodeLayerNode[] },
509
700
  info: ElementInfo | null | undefined,
510
- ): CodeLayerNode | null {
511
- if (!info) return null;
512
- const direct = resolveCodeLayerNodeFromBridge(
701
+ ): CodeLayerResolution {
702
+ if (!info) return { status: "absent" };
703
+ const direct = resolveCodeLayerTargetFromBridge(
513
704
  projection,
514
705
  info.selector,
515
706
  info.sourceId ?? info.id,
516
707
  );
517
- if (direct) return direct;
708
+ if (direct.status === "resolved") return direct;
518
709
 
710
+ // Score even an ambiguous selector: text/class/id evidence it does not carry
711
+ // can single out one instance. Ambiguity only survives if scoring ties too.
519
712
  const tagName = info.tagName.toLowerCase();
520
713
  const text = collapsedElementText(info.textContent);
521
714
  const classes = new Set(info.classes);
@@ -541,11 +734,25 @@ export function resolveCodeLayerNodeFromElementInfo(
541
734
  .filter((candidate) => candidate.score >= 4)
542
735
  .sort((a, b) => b.score - a.score);
543
736
 
544
- if (scored.length === 0) return null;
545
737
  const [best, next] = scored;
546
- if (!best) return null;
547
- if (next && next.score === best.score) return null;
548
- return best.node;
738
+ if (!best) return direct;
739
+ if (next && next.score === best.score) {
740
+ return {
741
+ status: "ambiguous",
742
+ candidates: scored
743
+ .filter((candidate) => candidate.score === best.score)
744
+ .map((candidate) => candidate.node),
745
+ };
746
+ }
747
+ return { status: "resolved", node: best.node };
748
+ }
749
+
750
+ export function resolveCodeLayerNodeFromElementInfo(
751
+ projection: { nodes: CodeLayerNode[] },
752
+ info: ElementInfo | null | undefined,
753
+ ): CodeLayerNode | null {
754
+ const resolution = resolveCodeLayerTargetFromElementInfo(projection, info);
755
+ return resolution.status === "resolved" ? resolution.node : null;
549
756
  }
550
757
 
551
758
  export function canonicalElementInfoForCodeLayerNode(
@@ -554,6 +761,11 @@ export function canonicalElementInfoForCodeLayerNode(
554
761
  ): ElementInfo {
555
762
  return {
556
763
  ...info,
764
+ // Keep the bridge's own identity before overwriting it — it is the only
765
+ // one that resolves in a live document. Idempotent: re-canonicalizing an
766
+ // already-canonicalized info must not overwrite it with the source id.
767
+ runtimeSelector: info.runtimeSelector ?? info.selector,
768
+ runtimeSourceId: info.runtimeSourceId ?? info.sourceId,
557
769
  sourceId: bridgeSourceIdForCodeLayerNode(node),
558
770
  selector: preferredCodeLayerSelector(node),
559
771
  classes: node.classes,
@@ -592,6 +804,56 @@ export function elementInfoIsRuntimeOnly(
592
804
  );
593
805
  }
594
806
 
807
+ /**
808
+ * Per-node refinement of a file's "layers panel is showing the runtime
809
+ * (localhost-hydrated) tree" flag into an actual "this node has no
810
+ * resolvable source counterpart" signal.
811
+ *
812
+ * `fileIsRuntimeProjected` alone is true for basically every hydrated
813
+ * localhost screen, React or not — see shouldPreferRuntimeLayerProjection's
814
+ * doc comment in design-editor/pending-edits.ts — so callers that gated a
815
+ * React-semantic-handoff requirement on that file-level flag treated every
816
+ * node on every localhost screen as unresolvable, including on a page with
817
+ * no React at all.
818
+ *
819
+ * Match on the stamped `data-agent-native-node-id` attribute, NOT
820
+ * CodeLayerNode.id: `id` is `hashStable(sourceKey:...)` (see nodeIdFor in
821
+ * shared/code-layer.ts), and sourceKey differs between the runtime and
822
+ * source projection parses of the very same content, so `id` never lines up
823
+ * even for the identical element — the stamped DOM attribute does.
824
+ */
825
+ export function isCodeLayerNodeRuntimeOnly(args: {
826
+ fileIsRuntimeProjected: boolean;
827
+ nodeIdAttr: string | undefined;
828
+ sourceNodeIdAttrs: ReadonlySet<string>;
829
+ }): boolean {
830
+ if (!args.fileIsRuntimeProjected) return false;
831
+ if (!args.nodeIdAttr) return true;
832
+ return !args.sourceNodeIdAttrs.has(args.nodeIdAttr);
833
+ }
834
+
835
+ /**
836
+ * Whether a Layers-panel hide/lock toggle on a runtime (localhost-hydrated)
837
+ * node can be handed off to the agent to make durable in React source.
838
+ *
839
+ * The handoff is about DURABILITY — writing `data-agent-native-hidden` /
840
+ * `-locked` into JSX. The visual effect is host state that DesignCanvas
841
+ * mirrors into the iframe as a `layer-states` message; the source attribute
842
+ * has no rendering power of its own. A localhost target with no React
843
+ * debug-source provenance (plain HTML, or a React build without the source
844
+ * plugin) can never resolve an anchor, so gating the preview on the handoff
845
+ * made hide/lock a total no-op there and reported "source anchors still
846
+ * loading" for a load that will never happen. "Nothing to write into" and
847
+ * "source not loaded yet" must stay different answers.
848
+ */
849
+ export function runtimeLayerStateHandoffMode(args: {
850
+ runtimeOnly: boolean;
851
+ provenanceSourceFile: string | null | undefined;
852
+ }): "handoff" | "preview-only" {
853
+ if (!args.runtimeOnly) return "preview-only";
854
+ return args.provenanceSourceFile?.trim() ? "handoff" : "preview-only";
855
+ }
856
+
595
857
  export function codeLayerPatchMessage(
596
858
  message: string | null | undefined,
597
859
  fallback: string,
@@ -1,18 +1,32 @@
1
+ import { isStandaloneHttpUrl } from "@shared/html-content";
2
+
1
3
  import {
4
+ type DesignFile,
2
5
  type DesignLeftPanel,
3
6
  type DesignTool,
7
+ type EditorMode,
4
8
  SHOW_DESIGN_CODE_LEFT_PANEL,
5
9
  } from "./types";
6
10
 
7
- function isStandaloneHttpUrl(value: string): boolean {
8
- const trimmed = value.trim();
9
- if (!/^https?:\/\//i.test(trimmed)) return false;
10
- try {
11
- const parsed = new URL(trimmed);
12
- return parsed.protocol === "http:" || parsed.protocol === "https:";
13
- } catch {
14
- return false;
15
- }
11
+ // Definition moved to shared/ so the server write path and code-layer's
12
+ // document transforms guard on the same predicate. Re-exported here because
13
+ // the editor's URL-backed-screen refusals all import it from this module.
14
+ export { isStandaloneHttpUrl };
15
+
16
+ /**
17
+ * A live-screen route intentionally refuses whole-document replacement. That
18
+ * is a successful no-op, unlike an unavailable preview bridge, which callers
19
+ * must recover by rebuilding their render state.
20
+ */
21
+ export type PreviewContentReplaceResult =
22
+ | "applied"
23
+ | "skipped-live-route"
24
+ | "unavailable";
25
+
26
+ export function previewContentReplaceNeedsRenderFallback(
27
+ result: PreviewContentReplaceResult,
28
+ ): boolean {
29
+ return result === "unavailable";
16
30
  }
17
31
 
18
32
  /**
@@ -110,6 +124,7 @@ export function getDesignEditorStateUrlSearch(args: {
110
124
  leftPanel?: DesignLeftPanel | null;
111
125
  zoom?: number | null;
112
126
  tool?: DesignTool | null;
127
+ mode?: EditorMode | null;
113
128
  }) {
114
129
  const params = new URLSearchParams(args.currentSearch);
115
130
  const leftPanel =
@@ -155,6 +170,11 @@ export function getDesignEditorStateUrlSearch(args: {
155
170
  } else {
156
171
  params.delete("tool");
157
172
  }
173
+ if (args.viewMode === "single") {
174
+ params.set("mode", "interact");
175
+ } else {
176
+ params.delete("mode");
177
+ }
158
178
  const query = params.toString();
159
179
  return query ? `?${query}` : "";
160
180
  }
@@ -491,3 +511,49 @@ export function shouldSendKeepalive(
491
511
  ): boolean {
492
512
  return hashKnown || !collabLive;
493
513
  }
514
+
515
+ const EMPTY_DESIGN_FILES: DesignFile[] = [];
516
+
517
+ /**
518
+ * Identity-stable `design.files` read for the editor render body.
519
+ *
520
+ * `design` is null until the `get-design` query resolves, and a literal `??
521
+ * []` there mints a new array on every render of that window. That identity
522
+ * feeds `files` → `proposalFileIds` → the pending-node-rewrite effect, whose
523
+ * empty-files branch commits a fresh `[]` — a passive-effect update that
524
+ * re-renders, remints the array, and re-fires itself until the query lands
525
+ * ("Maximum update depth exceeded" on cold loads). Return the shared empty
526
+ * array so the no-files render is stable.
527
+ */
528
+ export function resolveServerFiles(
529
+ design: { files?: DesignFile[] } | null | undefined,
530
+ ): DesignFile[] {
531
+ return design?.files ?? EMPTY_DESIGN_FILES;
532
+ }
533
+
534
+ /**
535
+ * Pure decision helper (exported for unit testing) for the pending-local-write
536
+ * reconcile: now that `file` arrived from the server, may the editor stop
537
+ * overlaying this file's pending local content and trust the query cache?
538
+ *
539
+ * Matching content alone does not prove the server took the write. Every
540
+ * optimistic writer also mirrors its content into the cached `get-design`
541
+ * payload while deliberately keeping the file's prior server-clock
542
+ * `updatedAt`, so an echo of our own cache write is indistinguishable from an
543
+ * acknowledgement by content. A writer that records `baseUpdatedAt` keeps its
544
+ * overlay until `updatedAt` actually advances; retiring it early leaves the
545
+ * cache as the only carrier of the edit, and a `get-design` response already
546
+ * in flight when the write happened then lands with pre-write content and the
547
+ * edit vanishes until a reload.
548
+ */
549
+ export function shouldRetirePendingLocalFileContent(
550
+ pending: { content: string; baseUpdatedAt?: string | null } | undefined,
551
+ file: { content?: string | null; updatedAt?: string | null },
552
+ ): boolean {
553
+ if (!pending) return false;
554
+ if ((file.content ?? "") !== pending.content) return false;
555
+ return (
556
+ pending.baseUpdatedAt === undefined ||
557
+ file.updatedAt !== pending.baseUpdatedAt
558
+ );
559
+ }
@@ -0,0 +1,28 @@
1
+ const LAYER_STATE_SCOPE_SEPARATOR = "\u001f";
2
+
3
+ export function scopedLayerStateId(screenId: string, layerId: string): string {
4
+ return layerId === screenId
5
+ ? screenId
6
+ : `${screenId}${LAYER_STATE_SCOPE_SEPARATOR}${layerId}`;
7
+ }
8
+
9
+ export function layerStateIdsForScreen(
10
+ stateIds: ReadonlySet<string>,
11
+ screenId: string,
12
+ ): Set<string> {
13
+ const prefix = `${screenId}${LAYER_STATE_SCOPE_SEPARATOR}`;
14
+ const result = new Set<string>();
15
+ if (stateIds.has(screenId)) result.add(screenId);
16
+ stateIds.forEach((id) => {
17
+ if (id.startsWith(prefix)) result.add(id.slice(prefix.length));
18
+ });
19
+ return result;
20
+ }
21
+
22
+ export function hasScopedLayerState(
23
+ stateIds: ReadonlySet<string>,
24
+ screenId: string,
25
+ layerId: string,
26
+ ): boolean {
27
+ return stateIds.has(scopedLayerStateId(screenId, layerId));
28
+ }