@agent-native/core 0.92.7 → 0.92.9

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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  5. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  6. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  7. package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  8. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  9. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  10. package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  11. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  12. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  13. package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  14. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  15. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  16. package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  17. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  18. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  19. package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  20. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  21. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  22. package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  23. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  24. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  25. package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  26. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  27. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  28. package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  29. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  30. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  31. package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  32. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  33. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  34. package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
  35. package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
  36. package/corpus/core/package.json +1 -1
  37. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  38. package/corpus/core/src/agent/model-config.ts +30 -30
  39. package/corpus/core/src/agent/production-agent.ts +129 -7
  40. package/corpus/core/src/cli/design-connect.ts +438 -83
  41. package/corpus/core/src/cli/recap.ts +17 -3
  42. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  43. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  44. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  45. package/corpus/core/src/client/index.ts +4 -0
  46. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  47. package/corpus/core/src/client/use-action.ts +113 -1
  48. package/corpus/core/src/db/client.ts +12 -2
  49. package/corpus/core/src/db/create-get-db.ts +4 -1
  50. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  51. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  52. package/corpus/core/src/server/ssr-handler.ts +7 -0
  53. package/corpus/core/src/server/transcribe-voice.ts +1 -1
  54. package/corpus/core/src/usage/store.ts +13 -4
  55. package/corpus/core/src/vite/client.ts +38 -1
  56. package/corpus/templates/analytics/AGENTS.md +3 -0
  57. package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
  58. package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
  59. package/corpus/templates/analytics/app/global.css +11 -0
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
  61. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  62. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
  64. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
  66. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
  67. package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
  71. package/corpus/templates/analytics/server/db/index.ts +6 -0
  72. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
  73. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
  75. package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
  76. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  77. package/corpus/templates/content/app/i18n-data.ts +28 -0
  78. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
  79. package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
  80. package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
  81. package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
  82. package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
  83. package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
  84. package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
  85. package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
  86. package/corpus/templates/design/actions/capture-design-state.ts +2 -2
  87. package/corpus/templates/design/actions/connect-localhost.ts +58 -12
  88. package/corpus/templates/design/actions/create-component.ts +2 -18
  89. package/corpus/templates/design/actions/create-design-branch.ts +38 -18
  90. package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
  91. package/corpus/templates/design/actions/delete-file.ts +19 -34
  92. package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
  93. package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
  94. package/corpus/templates/design/actions/export-pdf.ts +2 -1
  95. package/corpus/templates/design/actions/export-zip.ts +9 -3
  96. package/corpus/templates/design/actions/generate-design.ts +107 -70
  97. package/corpus/templates/design/actions/get-component-details.ts +2 -18
  98. package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
  99. package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
  100. package/corpus/templates/design/actions/get-design.ts +2 -1
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
  102. package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
  103. package/corpus/templates/design/actions/index-components.ts +2 -18
  104. package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
  105. package/corpus/templates/design/actions/list-local-files.ts +9 -2
  106. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -18
  108. package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
  109. package/corpus/templates/design/actions/present-design-variants.ts +70 -70
  110. package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
  111. package/corpus/templates/design/actions/read-local-file.ts +9 -2
  112. package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
  113. package/corpus/templates/design/actions/rename-screen.ts +425 -0
  114. package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
  115. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
  116. package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
  117. package/corpus/templates/design/actions/update-design.ts +376 -61
  118. package/corpus/templates/design/actions/update-file.ts +455 -188
  119. package/corpus/templates/design/actions/write-local-file.ts +10 -2
  120. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
  122. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
  123. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
  124. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
  125. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
  126. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
  127. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
  128. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
  129. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
  130. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
  131. package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
  132. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
  133. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
  134. package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
  135. package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
  136. package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
  137. package/corpus/templates/design/app/components/design/index.ts +2 -2
  138. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
  139. package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
  140. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
  141. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
  142. package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
  143. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
  144. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
  145. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
  146. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
  147. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
  148. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
  149. package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
  150. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
  151. package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
  152. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
  153. package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
  154. package/corpus/templates/design/app/i18n-data.ts +223 -0
  155. package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
  156. package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
  157. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
  158. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
  159. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
  160. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
  161. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
  162. package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
  163. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
  164. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
  165. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
  166. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
  167. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
  168. package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
  169. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
  170. package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
  171. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
  172. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
  173. package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
  174. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
  175. package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
  176. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
  177. package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
  178. package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
  179. package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
  180. package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
  181. package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
  184. package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
  185. package/corpus/templates/design/server/db/index.ts +2 -52
  186. package/corpus/templates/design/server/db/schema.ts +16 -0
  187. package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
  188. package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
  189. package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
  190. package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
  191. package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
  192. package/corpus/templates/design/server/plugins/db.ts +19 -0
  193. package/corpus/templates/design/server/source-workspace.ts +12 -13
  194. package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
  195. package/corpus/templates/design/shared/screen-rename.ts +42 -0
  196. package/corpus/templates/design/shared/source-mode.ts +84 -4
  197. package/dist/agent/engine/builder-engine.d.ts +1 -1
  198. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  199. package/dist/agent/engine/builtin.js +1 -1
  200. package/dist/agent/engine/builtin.js.map +1 -1
  201. package/dist/agent/model-config.d.ts +9 -9
  202. package/dist/agent/model-config.d.ts.map +1 -1
  203. package/dist/agent/model-config.js +30 -30
  204. package/dist/agent/model-config.js.map +1 -1
  205. package/dist/agent/production-agent.d.ts +60 -1
  206. package/dist/agent/production-agent.d.ts.map +1 -1
  207. package/dist/agent/production-agent.js +106 -7
  208. package/dist/agent/production-agent.js.map +1 -1
  209. package/dist/cli/design-connect.d.ts +30 -1
  210. package/dist/cli/design-connect.d.ts.map +1 -1
  211. package/dist/cli/design-connect.js +359 -75
  212. package/dist/cli/design-connect.js.map +1 -1
  213. package/dist/cli/recap.d.ts.map +1 -1
  214. package/dist/cli/recap.js +13 -3
  215. package/dist/cli/recap.js.map +1 -1
  216. package/dist/client/AgentPanel.d.ts.map +1 -1
  217. package/dist/client/AgentPanel.js +6 -1
  218. package/dist/client/AgentPanel.js.map +1 -1
  219. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  220. package/dist/client/agent-chat-adapter.js +10 -5
  221. package/dist/client/agent-chat-adapter.js.map +1 -1
  222. package/dist/client/composer/TiptapComposer.js +1 -1
  223. package/dist/client/composer/TiptapComposer.js.map +1 -1
  224. package/dist/client/index.d.ts +1 -1
  225. package/dist/client/index.d.ts.map +1 -1
  226. package/dist/client/index.js +1 -1
  227. package/dist/client/index.js.map +1 -1
  228. package/dist/client/org/TeamPage.d.ts.map +1 -1
  229. package/dist/client/org/TeamPage.js +6 -5
  230. package/dist/client/org/TeamPage.js.map +1 -1
  231. package/dist/client/use-action.d.ts +32 -0
  232. package/dist/client/use-action.d.ts.map +1 -1
  233. package/dist/client/use-action.js +68 -1
  234. package/dist/client/use-action.js.map +1 -1
  235. package/dist/collab/struct-routes.d.ts +1 -1
  236. package/dist/db/client.d.ts +3 -0
  237. package/dist/db/client.d.ts.map +1 -1
  238. package/dist/db/client.js +7 -2
  239. package/dist/db/client.js.map +1 -1
  240. package/dist/db/create-get-db.d.ts.map +1 -1
  241. package/dist/db/create-get-db.js +2 -2
  242. package/dist/db/create-get-db.js.map +1 -1
  243. package/dist/notifications/routes.d.ts +3 -3
  244. package/dist/observability/routes.d.ts +1 -1
  245. package/dist/resources/handlers.d.ts +3 -3
  246. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  247. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  248. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  249. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  250. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  251. package/dist/server/ssr-handler.d.ts.map +1 -1
  252. package/dist/server/ssr-handler.js +7 -0
  253. package/dist/server/ssr-handler.js.map +1 -1
  254. package/dist/server/transcribe-voice.js +1 -1
  255. package/dist/server/transcribe-voice.js.map +1 -1
  256. package/dist/usage/store.d.ts.map +1 -1
  257. package/dist/usage/store.js +13 -4
  258. package/dist/usage/store.js.map +1 -1
  259. package/dist/vite/client.d.ts.map +1 -1
  260. package/dist/vite/client.js +36 -1
  261. package/dist/vite/client.js.map +1 -1
  262. package/docs/content/a2a-protocol.mdx +12 -0
  263. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  264. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  265. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  266. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  267. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  268. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  269. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  270. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  271. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  272. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  273. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  274. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  275. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  276. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  277. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  278. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  279. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  280. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  281. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  282. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  283. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  284. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  285. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  286. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  287. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  288. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  289. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  290. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  291. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  292. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  293. package/docs/content/pr-visual-recap.mdx +3 -3
  294. package/docs/content/pure-agent-apps.mdx +22 -0
  295. package/package.json +2 -2
@@ -15,9 +15,14 @@ import { nanoid } from "nanoid";
15
15
  import { z } from "zod";
16
16
 
17
17
  import { getDb, schema } from "../server/db/index.js";
18
+ import {
19
+ mutateDesignData,
20
+ type DesignDataRecord,
21
+ } from "../server/lib/design-data-mutation.js";
18
22
  import {
19
23
  mergeCanvasFramePlacements,
20
24
  parseCanvasFrameGeometryById,
25
+ type CanvasFrameGeometry,
21
26
  type CanvasFramePlacement,
22
27
  } from "../shared/canvas-frames.js";
23
28
  import {
@@ -32,6 +37,9 @@ const routeInputSchema = z.object({
32
37
  url: z.string().optional(),
33
38
  title: z.string().optional(),
34
39
  sourceFile: z.string().optional(),
40
+ sourceKind: z.enum(["react-router", "html", "manual"]).optional(),
41
+ screenshotUrl: z.string().optional(),
42
+ metadata: z.record(z.string(), z.unknown()).optional(),
35
43
  width: z.number().positive().optional(),
36
44
  height: z.number().positive().optional(),
37
45
  x: z.number().optional(),
@@ -63,12 +71,89 @@ function isRecord(value: unknown): value is Record<string, unknown> {
63
71
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
64
72
  }
65
73
 
74
+ function parseDesignDataSnapshot(
75
+ designId: string,
76
+ value: string | null,
77
+ ): DesignDataRecord {
78
+ if (value === null) return {};
79
+ try {
80
+ const parsed: unknown = JSON.parse(value);
81
+ if (isRecord(parsed)) return parsed;
82
+ } catch {
83
+ // The error below deliberately refuses to discard malformed legacy data.
84
+ }
85
+ throw new Error(
86
+ `Design "${designId}" has invalid data JSON. Refusing to overwrite it; repair or restore the design data before retrying.`,
87
+ );
88
+ }
89
+
90
+ function jsonValuesEqual(left: unknown, right: unknown): boolean {
91
+ if (Object.is(left, right)) return true;
92
+ try {
93
+ return JSON.stringify(left) === JSON.stringify(right);
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ interface PlacementIntent {
100
+ fileId: string;
101
+ filename: string;
102
+ fallback: CanvasFramePlacement;
103
+ existedAtStart: boolean;
104
+ owns: {
105
+ x: boolean;
106
+ y: boolean;
107
+ width: boolean;
108
+ height: boolean;
109
+ z: boolean;
110
+ };
111
+ }
112
+
113
+ function placementAgainstLatest(
114
+ intent: PlacementIntent,
115
+ latest: CanvasFrameGeometry | undefined,
116
+ ): CanvasFramePlacement {
117
+ const choose = (key: keyof PlacementIntent["owns"]): number | undefined =>
118
+ intent.owns[key]
119
+ ? intent.fallback[key]
120
+ : (latest?.[key] ?? intent.fallback[key]);
121
+
122
+ return {
123
+ fileId: intent.fileId,
124
+ filename: intent.filename,
125
+ x: choose("x"),
126
+ y: choose("y"),
127
+ width: choose("width"),
128
+ height: choose("height"),
129
+ z: choose("z"),
130
+ // add-localhost-screens never owns rotation. A concurrent/local canvas
131
+ // rotation therefore survives even when this action refreshes the route.
132
+ rotation: latest?.rotation,
133
+ };
134
+ }
135
+
66
136
  function normalizeBaseUrl(value: string): string {
67
137
  const parsed = new URL(value);
138
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
139
+ throw new Error("devServerUrl must be an http(s) URL");
140
+ }
68
141
  parsed.hash = "";
69
142
  return parsed.toString().replace(/\/$/, "");
70
143
  }
71
144
 
145
+ function isLoopbackHostname(hostname: string): boolean {
146
+ const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
147
+ if (
148
+ normalized === "localhost" ||
149
+ normalized === "::1" ||
150
+ normalized === "0.0.0.0"
151
+ ) {
152
+ return true;
153
+ }
154
+ return /^127(?:\.\d{1,3}){3}$/.test(normalized);
155
+ }
156
+
72
157
  function withLocalhostProtocol(value: string): string {
73
158
  const raw = value.trim();
74
159
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) return raw;
@@ -98,6 +183,25 @@ export function routeUrl(
98
183
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
99
184
  throw new Error(`Localhost screen URL must be an http(s) URL: ${raw}`);
100
185
  }
186
+ const base = new URL(baseUrl);
187
+ if (parsed.origin !== base.origin) {
188
+ const equivalentLoopbackOrigin =
189
+ parsed.protocol === base.protocol &&
190
+ parsed.port === base.port &&
191
+ isLoopbackHostname(parsed.hostname) &&
192
+ isLoopbackHostname(base.hostname);
193
+ if (!equivalentLoopbackOrigin) {
194
+ throw new Error(
195
+ `Localhost screen URL must stay on the connected dev server origin (${base.origin}): ${raw}`,
196
+ );
197
+ }
198
+ // localhost / 127.0.0.1 / ::1 aliases can point at the same loopback
199
+ // server, but the bridge enforces exact same-origin fetches. Canonicalize
200
+ // the alias to the registered dev-server origin so live edit does not fail
201
+ // later with an opaque bridge 400.
202
+ parsed.protocol = base.protocol;
203
+ parsed.host = base.host;
204
+ }
101
205
  parsed.hash = "";
102
206
  return parsed.toString();
103
207
  }
@@ -168,6 +272,36 @@ function metadataNumber(
168
272
  : undefined;
169
273
  }
170
274
 
275
+ function metadataForFile(
276
+ fileId: string,
277
+ screenMetadata: Record<string, unknown>,
278
+ localhostScreens: Record<string, unknown>,
279
+ ): Record<string, unknown> | undefined {
280
+ const primary = screenMetadata[fileId];
281
+ if (isRecord(primary)) return primary;
282
+ const legacy = localhostScreens[fileId];
283
+ return isRecord(legacy) ? legacy : undefined;
284
+ }
285
+
286
+ function metadataMatchesRoute(
287
+ metadata: Record<string, unknown> | undefined,
288
+ args: { connectionId: string; routeId: string; path: string; url: string },
289
+ ): boolean {
290
+ if (!metadata || metadata.sourceType !== "localhost") return false;
291
+ if (
292
+ typeof metadata.connectionId === "string" &&
293
+ metadata.connectionId !== args.connectionId
294
+ ) {
295
+ return false;
296
+ }
297
+ return (
298
+ metadata.routeId === args.routeId ||
299
+ metadata.url === args.url ||
300
+ metadata.previewUrl === args.url ||
301
+ metadata.path === args.path
302
+ );
303
+ }
304
+
171
305
  export default defineAction({
172
306
  description:
173
307
  "Create or refresh URL-backed localhost screens in a design project. " +
@@ -289,6 +423,10 @@ export default defineAction({
289
423
  routeId: route.id,
290
424
  path: route.path,
291
425
  title: route.title,
426
+ sourceFile: route.sourceFile,
427
+ sourceKind: route.sourceKind,
428
+ screenshotUrl: route.screenshotUrl,
429
+ metadata: route.metadata,
292
430
  width:
293
431
  typeof route.metadata?.width === "number"
294
432
  ? route.metadata.width
@@ -310,13 +448,19 @@ export default defineAction({
310
448
  .from(schema.designs)
311
449
  .where(eq(schema.designs.id, designId))
312
450
  .limit(1);
313
- const prevData = parseJson<Record<string, unknown>>(design?.data, {});
451
+ if (!design) throw new Error(`Design "${designId}" not found.`);
452
+ // Fail before touching files/collab when a legacy row contains malformed
453
+ // data. SQL NULL is the one supported legacy empty-data sentinel.
454
+ const prevData = parseDesignDataSnapshot(designId, design.data);
314
455
  const existingCanvasFrames = parseCanvasFrameGeometryById(
315
456
  prevData.canvasFrames,
316
457
  );
317
458
  const existingMetadata = isRecord(prevData.screenMetadata)
318
459
  ? (prevData.screenMetadata as Record<string, unknown>)
319
460
  : {};
461
+ const existingLocalhostScreens = isRecord(prevData.localhostScreens)
462
+ ? (prevData.localhostScreens as Record<string, unknown>)
463
+ : {};
320
464
  const existingFiles = await db
321
465
  .select()
322
466
  .from(schema.designFiles)
@@ -326,8 +470,9 @@ export default defineAction({
326
470
  );
327
471
  const usedFilenames = new Set(existingFiles.map((file) => file.filename));
328
472
  const now = new Date().toISOString();
329
- const effectiveDefaultWidth = defaultWidth ?? 1280;
330
- const effectiveDefaultHeight = defaultHeight ?? 900;
473
+ const layoutStartX = startX ?? 0;
474
+ const layoutStartY = startY ?? 0;
475
+ const layoutGap = gap ?? 160;
331
476
  const savedScreens: Array<{
332
477
  id: string;
333
478
  filename: string;
@@ -336,10 +481,13 @@ export default defineAction({
336
481
  url: string;
337
482
  routeId: string;
338
483
  sourceFile?: string;
484
+ sourceKind?: "react-router" | "html" | "manual";
485
+ screenshotUrl?: string;
486
+ routeMetadata?: Record<string, unknown>;
339
487
  width: number;
340
488
  height: number;
341
489
  }> = [];
342
- const placements: CanvasFramePlacement[] = [];
490
+ const placementIntents: PlacementIntent[] = [];
343
491
 
344
492
  for (let index = 0; index < requestedRoutes.length; index += 1) {
345
493
  const input = requestedRoutes[index]!;
@@ -360,19 +508,51 @@ export default defineAction({
360
508
  const title =
361
509
  input.title ?? manifestRoute?.title ?? titleFromRoutePath(path);
362
510
  const sourceFile = input.sourceFile ?? manifestRoute?.sourceFile;
363
- const width = input.width ?? effectiveDefaultWidth;
364
- const height = input.height ?? effectiveDefaultHeight;
511
+ const sourceKind = input.sourceKind ?? manifestRoute?.sourceKind;
512
+ const screenshotUrl = input.screenshotUrl ?? manifestRoute?.screenshotUrl;
513
+ const routeMetadata = {
514
+ ...(manifestRoute?.metadata ?? {}),
515
+ ...(input.metadata ?? {}),
516
+ };
365
517
  const basePreferredFilename = `localhost-${slugForPath(path)}.html`;
366
- const existingBase = existingByFilename.get(basePreferredFilename);
518
+ const routeMatchArgs = {
519
+ connectionId: connection.id,
520
+ routeId,
521
+ path,
522
+ url,
523
+ };
524
+ const routeCandidates = existingFiles.filter((file) =>
525
+ metadataMatchesRoute(
526
+ metadataForFile(file.id, existingMetadata, existingLocalhostScreens),
527
+ routeMatchArgs,
528
+ ),
529
+ );
530
+ const filenameBase = existingByFilename.get(basePreferredFilename);
531
+ const existingBase =
532
+ filenameBase &&
533
+ metadataMatchesRoute(
534
+ metadataForFile(
535
+ filenameBase.id,
536
+ existingMetadata,
537
+ existingLocalhostScreens,
538
+ ),
539
+ routeMatchArgs,
540
+ )
541
+ ? filenameBase
542
+ : routeCandidates.find(
543
+ (candidate) => candidate.filename === basePreferredFilename,
544
+ );
367
545
  const requestedViewportExplicitly =
368
546
  input.width !== undefined ||
369
547
  input.height !== undefined ||
370
548
  defaultWidth !== undefined ||
371
549
  defaultHeight !== undefined;
372
- const existingBaseMetadata = isRecord(
373
- existingBase ? existingMetadata[existingBase.id] : undefined,
374
- )
375
- ? (existingMetadata[existingBase!.id] as Record<string, unknown>)
550
+ const existingBaseMetadata = existingBase
551
+ ? metadataForFile(
552
+ existingBase.id,
553
+ existingMetadata,
554
+ existingLocalhostScreens,
555
+ )
376
556
  : undefined;
377
557
  const existingBaseFrame = existingBase
378
558
  ? existingCanvasFrames[existingBase.id]
@@ -383,20 +563,87 @@ export default defineAction({
383
563
  const existingBaseHeight =
384
564
  existingBaseFrame?.height ??
385
565
  metadataNumber(existingBaseMetadata, "height");
566
+ const requestedWidth =
567
+ input.width ??
568
+ defaultWidth ??
569
+ existingBaseWidth ??
570
+ metadataNumber(routeMetadata, "width") ??
571
+ 1280;
572
+ const requestedHeight =
573
+ input.height ??
574
+ defaultHeight ??
575
+ existingBaseHeight ??
576
+ metadataNumber(routeMetadata, "height") ??
577
+ 900;
386
578
  const viewportDiffersFromBase =
387
579
  (typeof existingBaseWidth === "number" &&
388
- existingBaseWidth !== width) ||
580
+ existingBaseWidth !== requestedWidth) ||
389
581
  (typeof existingBaseHeight === "number" &&
390
- existingBaseHeight !== height);
582
+ existingBaseHeight !== requestedHeight);
391
583
  const preferredFilename =
392
584
  existingBase && requestedViewportExplicitly && viewportDiffersFromBase
393
- ? viewportFilename(path, width, height)
585
+ ? viewportFilename(path, requestedWidth, requestedHeight)
394
586
  : basePreferredFilename;
395
- const existing = existingByFilename.get(preferredFilename);
587
+ const preferredExisting = existingByFilename.get(preferredFilename);
588
+ const matchingPreferredExisting =
589
+ preferredExisting &&
590
+ metadataMatchesRoute(
591
+ metadataForFile(
592
+ preferredExisting.id,
593
+ existingMetadata,
594
+ existingLocalhostScreens,
595
+ ),
596
+ routeMatchArgs,
597
+ )
598
+ ? preferredExisting
599
+ : undefined;
600
+ const existing =
601
+ matchingPreferredExisting ??
602
+ routeCandidates.find((candidate) => {
603
+ const frame = existingCanvasFrames[candidate.id];
604
+ const metadata = metadataForFile(
605
+ candidate.id,
606
+ existingMetadata,
607
+ existingLocalhostScreens,
608
+ );
609
+ const candidateWidth =
610
+ frame?.width ?? metadataNumber(metadata, "width");
611
+ const candidateHeight =
612
+ frame?.height ?? metadataNumber(metadata, "height");
613
+ return requestedViewportExplicitly
614
+ ? candidateWidth === requestedWidth &&
615
+ candidateHeight === requestedHeight
616
+ : candidate === existingBase;
617
+ }) ??
618
+ (!requestedViewportExplicitly ? routeCandidates[0] : undefined);
396
619
  const filename =
397
620
  existing?.filename ??
398
621
  uniqueFilename(path, usedFilenames, preferredFilename);
399
622
  const fileId = existing?.id ?? nanoid();
623
+ const existingScreenMetadata = existing
624
+ ? metadataForFile(
625
+ existing.id,
626
+ existingMetadata,
627
+ existingLocalhostScreens,
628
+ )
629
+ : undefined;
630
+ const existingFrame = existing
631
+ ? existingCanvasFrames[existing.id]
632
+ : undefined;
633
+ const width =
634
+ input.width ??
635
+ defaultWidth ??
636
+ existingFrame?.width ??
637
+ metadataNumber(existingScreenMetadata, "width") ??
638
+ metadataNumber(routeMetadata, "width") ??
639
+ 1280;
640
+ const height =
641
+ input.height ??
642
+ defaultHeight ??
643
+ existingFrame?.height ??
644
+ metadataNumber(existingScreenMetadata, "height") ??
645
+ metadataNumber(routeMetadata, "height") ??
646
+ 900;
400
647
 
401
648
  if (existing) {
402
649
  await db
@@ -429,65 +676,253 @@ export default defineAction({
429
676
  url,
430
677
  routeId,
431
678
  sourceFile,
679
+ sourceKind,
680
+ screenshotUrl,
681
+ routeMetadata,
432
682
  width,
433
683
  height,
434
684
  });
435
- placements.push({
685
+ const fallbackPlacement: CanvasFramePlacement = {
436
686
  fileId,
437
687
  filename,
438
- x: input.x ?? startX + index * (width + gap),
439
- y: input.y ?? startY,
688
+ x:
689
+ input.x ??
690
+ existingFrame?.x ??
691
+ layoutStartX + index * (width + layoutGap),
692
+ y: input.y ?? existingFrame?.y ?? layoutStartY,
440
693
  width,
441
694
  height,
442
- z: input.z ?? index,
695
+ z: input.z ?? existingFrame?.z ?? index,
696
+ };
697
+ placementIntents.push({
698
+ fileId,
699
+ filename,
700
+ fallback: fallbackPlacement,
701
+ existedAtStart: Boolean(existingFrame),
702
+ owns: {
703
+ x: input.x !== undefined,
704
+ y: input.y !== undefined,
705
+ width: input.width !== undefined || defaultWidth !== undefined,
706
+ height: input.height !== undefined || defaultHeight !== undefined,
707
+ z: input.z !== undefined,
708
+ },
443
709
  });
444
710
  }
445
711
 
446
- const mergedFrames = mergeCanvasFramePlacements({
447
- existing: prevData.canvasFrames,
448
- placements,
449
- resolveFileId: (placement) => placement.fileId,
450
- });
451
- const previousMetadata = isRecord(prevData.screenMetadata)
452
- ? { ...prevData.screenMetadata }
453
- : {};
454
- const previousLocalhostScreens = isRecord(prevData.localhostScreens)
455
- ? { ...prevData.localhostScreens }
456
- : {};
457
- for (const screen of savedScreens) {
458
- const metadata = {
459
- sourceType: "localhost",
460
- previewState: "live",
461
- title: screen.title,
462
- width: screen.width,
463
- height: screen.height,
464
- url: screen.url,
465
- previewUrl: screen.url,
466
- connectionId: connection.id,
467
- routeId: screen.routeId,
468
- sourceFile: screen.sourceFile,
469
- path: screen.path,
470
- bridgeUrl: connection.bridgeUrl ?? undefined,
471
- bridgeToken: connection.bridgeToken ?? undefined,
472
- };
473
- previousMetadata[screen.id] = metadata;
474
- previousLocalhostScreens[screen.id] = metadata;
475
- }
712
+ let lastOwnedMetadata = new Map<string, Record<string, unknown>>();
713
+ let lastOwnedFrameFields = new Map<string, Partial<CanvasFrameGeometry>>();
476
714
 
477
- await db
478
- .update(schema.designs)
479
- .set({
480
- data: JSON.stringify({
481
- ...prevData,
715
+ const { data: persistedData } = await mutateDesignData({
716
+ designId,
717
+ mutate: (currentData, { updatedAt }) => {
718
+ const latestFrames = parseCanvasFrameGeometryById(
719
+ currentData.canvasFrames,
720
+ );
721
+ const placements = placementIntents.map((intent) =>
722
+ placementAgainstLatest(intent, latestFrames[intent.fileId]),
723
+ );
724
+ const mergedFrames = mergeCanvasFramePlacements({
725
+ existing: currentData.canvasFrames,
726
+ placements,
727
+ resolveFileId: (placement) => placement.fileId,
728
+ });
729
+ const previousMetadata = isRecord(currentData.screenMetadata)
730
+ ? { ...currentData.screenMetadata }
731
+ : {};
732
+ const previousLocalhostScreens = isRecord(currentData.localhostScreens)
733
+ ? { ...currentData.localhostScreens }
734
+ : {};
735
+ const nextOwnedMetadata = new Map<string, Record<string, unknown>>();
736
+ const nextOwnedFrameFields = new Map<
737
+ string,
738
+ Partial<CanvasFrameGeometry>
739
+ >();
740
+
741
+ for (const screen of savedScreens) {
742
+ const currentMetadata = isRecord(previousMetadata[screen.id])
743
+ ? (previousMetadata[screen.id] as Record<string, unknown>)
744
+ : {};
745
+ const currentLocalhostMetadata = isRecord(
746
+ previousLocalhostScreens[screen.id],
747
+ )
748
+ ? (previousLocalhostScreens[screen.id] as Record<string, unknown>)
749
+ : {};
750
+ const frame = mergedFrames.canvasFrames[screen.id] ?? {};
751
+ const ownedMetadata: Record<string, unknown> = {
752
+ sourceType: "localhost",
753
+ previewState: "live",
754
+ title: screen.title,
755
+ width: frame.width ?? screen.width,
756
+ height: frame.height ?? screen.height,
757
+ url: screen.url,
758
+ previewUrl: screen.url,
759
+ connectionId: connection.id,
760
+ routeId: screen.routeId,
761
+ path: screen.path,
762
+ bridgeUrl: connection.bridgeUrl ?? undefined,
763
+ previewToken: connection.previewToken ?? undefined,
764
+ };
765
+ if (screen.sourceFile !== undefined) {
766
+ ownedMetadata.sourceFile = screen.sourceFile;
767
+ }
768
+ if (screen.sourceKind !== undefined) {
769
+ ownedMetadata.sourceKind = screen.sourceKind;
770
+ }
771
+ if (screen.screenshotUrl !== undefined) {
772
+ ownedMetadata.screenshotUrl = screen.screenshotUrl;
773
+ }
774
+
775
+ const mergedRouteMetadata = (
776
+ primary: Record<string, unknown>,
777
+ counterpart: Record<string, unknown>,
778
+ ) => ({
779
+ ...(isRecord(counterpart.routeMetadata)
780
+ ? counterpart.routeMetadata
781
+ : {}),
782
+ ...(isRecord(primary.routeMetadata) ? primary.routeMetadata : {}),
783
+ ...(screen.routeMetadata ?? {}),
784
+ });
785
+
786
+ // Preserve independently written legacy and canonical metadata keys.
787
+ // Each map keeps its own value on an unrelated same-key conflict,
788
+ // while localhost-owned fields above intentionally converge.
789
+ previousMetadata[screen.id] = {
790
+ ...currentLocalhostMetadata,
791
+ ...currentMetadata,
792
+ ...ownedMetadata,
793
+ routeMetadata: mergedRouteMetadata(
794
+ currentMetadata,
795
+ currentLocalhostMetadata,
796
+ ),
797
+ };
798
+ previousLocalhostScreens[screen.id] = {
799
+ ...currentMetadata,
800
+ ...currentLocalhostMetadata,
801
+ ...ownedMetadata,
802
+ routeMetadata: mergedRouteMetadata(
803
+ currentLocalhostMetadata,
804
+ currentMetadata,
805
+ ),
806
+ };
807
+ nextOwnedMetadata.set(screen.id, {
808
+ ...ownedMetadata,
809
+ routeMetadata: screen.routeMetadata ?? {},
810
+ });
811
+
812
+ const placementIntent = placementIntents.find(
813
+ (intent) => intent.fileId === screen.id,
814
+ );
815
+ const ownedFrameFields: Partial<CanvasFrameGeometry> = {};
816
+ if (placementIntent) {
817
+ for (const key of ["x", "y", "width", "height", "z"] as const) {
818
+ // A newly created screen owns its initial geometry. A refresh of
819
+ // an existing screen only owns fields explicitly supplied by the
820
+ // caller; current canvas movement/resizing wins for the rest.
821
+ if (
822
+ !placementIntent.existedAtStart ||
823
+ placementIntent.owns[key]
824
+ ) {
825
+ ownedFrameFields[key] = frame[key];
826
+ }
827
+ }
828
+ }
829
+ nextOwnedFrameFields.set(screen.id, ownedFrameFields);
830
+ }
831
+
832
+ lastOwnedMetadata = nextOwnedMetadata;
833
+ lastOwnedFrameFields = nextOwnedFrameFields;
834
+ return {
835
+ ...currentData,
836
+ sourceType: "localhost",
482
837
  sourceMode: "localhost",
838
+ connectionId: connection.id,
483
839
  canvasFrames: mergedFrames.canvasFrames,
484
840
  screenMetadata: previousMetadata,
485
841
  localhostScreens: previousLocalhostScreens,
486
- updatedAt: now,
487
- }),
488
- updatedAt: now,
489
- })
490
- .where(eq(schema.designs.id, designId));
842
+ updatedAt,
843
+ };
844
+ },
845
+ isApplied: (data) => {
846
+ if (
847
+ data.sourceType !== "localhost" ||
848
+ data.sourceMode !== "localhost" ||
849
+ data.connectionId !== connection.id
850
+ ) {
851
+ return false;
852
+ }
853
+ const frames = parseCanvasFrameGeometryById(data.canvasFrames);
854
+ const metadataById = isRecord(data.screenMetadata)
855
+ ? data.screenMetadata
856
+ : {};
857
+ const localhostById = isRecord(data.localhostScreens)
858
+ ? data.localhostScreens
859
+ : {};
860
+
861
+ for (const screen of savedScreens) {
862
+ const frame = frames[screen.id];
863
+ if (!frame) return false;
864
+ for (const [key, expected] of Object.entries(
865
+ lastOwnedFrameFields.get(screen.id) ?? {},
866
+ )) {
867
+ if (
868
+ !jsonValuesEqual(
869
+ frame[key as keyof CanvasFrameGeometry],
870
+ expected,
871
+ )
872
+ ) {
873
+ return false;
874
+ }
875
+ }
876
+
877
+ const expectedMetadata = lastOwnedMetadata.get(screen.id) ?? {};
878
+ for (const rawMetadata of [
879
+ metadataById[screen.id],
880
+ localhostById[screen.id],
881
+ ]) {
882
+ if (!isRecord(rawMetadata)) return false;
883
+ for (const [key, expected] of Object.entries(expectedMetadata)) {
884
+ if (key === "routeMetadata") {
885
+ if (!isRecord(rawMetadata.routeMetadata)) return false;
886
+ for (const [routeKey, routeValue] of Object.entries(
887
+ expected as Record<string, unknown>,
888
+ )) {
889
+ if (
890
+ !jsonValuesEqual(
891
+ rawMetadata.routeMetadata[routeKey],
892
+ routeValue,
893
+ )
894
+ ) {
895
+ return false;
896
+ }
897
+ }
898
+ } else if (!jsonValuesEqual(rawMetadata[key], expected)) {
899
+ return false;
900
+ }
901
+ }
902
+ }
903
+ }
904
+ return true;
905
+ },
906
+ });
907
+
908
+ const persistedFrames = parseCanvasFrameGeometryById(
909
+ persistedData.canvasFrames,
910
+ );
911
+ const resultScreens = savedScreens.map((screen) => ({
912
+ ...screen,
913
+ width: persistedFrames[screen.id]?.width ?? screen.width,
914
+ height: persistedFrames[screen.id]?.height ?? screen.height,
915
+ }));
916
+ const placedFrames = placementIntents.map((intent) => ({
917
+ fileId: intent.fileId,
918
+ filename: intent.filename,
919
+ frame:
920
+ persistedFrames[intent.fileId] ??
921
+ parseCanvasFrameGeometryById({
922
+ [intent.fileId]: intent.fallback,
923
+ })[intent.fileId] ??
924
+ {},
925
+ }));
491
926
 
492
927
  return {
493
928
  designId,
@@ -495,8 +930,8 @@ export default defineAction({
495
930
  devServerUrl,
496
931
  bridgeUrl: connection.bridgeUrl ?? null,
497
932
  screenCount: savedScreens.length,
498
- screens: savedScreens,
499
- placedFrames: mergedFrames.placedFrames,
933
+ screens: resultScreens,
934
+ placedFrames,
500
935
  overview: true,
501
936
  urlPath: `/design/${designId}`,
502
937
  };