@agent-native/core 0.92.6 → 0.92.8

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 (297) 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 +2 -2
  42. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
  43. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  44. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  45. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  46. package/corpus/core/src/client/index.ts +4 -0
  47. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  48. package/corpus/core/src/client/use-action.ts +113 -1
  49. package/corpus/core/src/db/client.ts +12 -2
  50. package/corpus/core/src/db/create-get-db.ts +4 -1
  51. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  52. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  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.js +2 -2
  214. package/dist/cli/recap.js.map +1 -1
  215. package/dist/cli/sync-builder-starter-manifest.js +1 -1
  216. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  217. package/dist/client/AgentPanel.d.ts.map +1 -1
  218. package/dist/client/AgentPanel.js +6 -1
  219. package/dist/client/AgentPanel.js.map +1 -1
  220. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  221. package/dist/client/agent-chat-adapter.js +10 -5
  222. package/dist/client/agent-chat-adapter.js.map +1 -1
  223. package/dist/client/composer/TiptapComposer.js +1 -1
  224. package/dist/client/composer/TiptapComposer.js.map +1 -1
  225. package/dist/client/index.d.ts +1 -1
  226. package/dist/client/index.d.ts.map +1 -1
  227. package/dist/client/index.js +1 -1
  228. package/dist/client/index.js.map +1 -1
  229. package/dist/client/org/TeamPage.d.ts.map +1 -1
  230. package/dist/client/org/TeamPage.js +6 -5
  231. package/dist/client/org/TeamPage.js.map +1 -1
  232. package/dist/client/use-action.d.ts +32 -0
  233. package/dist/client/use-action.d.ts.map +1 -1
  234. package/dist/client/use-action.js +68 -1
  235. package/dist/client/use-action.js.map +1 -1
  236. package/dist/collab/awareness.d.ts +2 -2
  237. package/dist/collab/awareness.d.ts.map +1 -1
  238. package/dist/collab/routes.d.ts +1 -1
  239. package/dist/db/client.d.ts +3 -0
  240. package/dist/db/client.d.ts.map +1 -1
  241. package/dist/db/client.js +7 -2
  242. package/dist/db/client.js.map +1 -1
  243. package/dist/db/create-get-db.d.ts.map +1 -1
  244. package/dist/db/create-get-db.js +2 -2
  245. package/dist/db/create-get-db.js.map +1 -1
  246. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  247. package/dist/notifications/routes.d.ts +1 -1
  248. package/dist/observability/routes.d.ts +5 -5
  249. package/dist/progress/routes.d.ts +1 -1
  250. package/dist/resources/handlers.d.ts +1 -1
  251. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  252. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  253. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  254. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  255. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  256. package/dist/server/transcribe-voice.js +1 -1
  257. package/dist/server/transcribe-voice.js.map +1 -1
  258. package/dist/usage/store.d.ts.map +1 -1
  259. package/dist/usage/store.js +13 -4
  260. package/dist/usage/store.js.map +1 -1
  261. package/dist/vite/client.d.ts.map +1 -1
  262. package/dist/vite/client.js +36 -1
  263. package/dist/vite/client.js.map +1 -1
  264. package/docs/content/a2a-protocol.mdx +12 -0
  265. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  266. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  267. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  268. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  269. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  270. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  271. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  272. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  273. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  274. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  275. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  276. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  277. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  278. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  279. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  280. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  281. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  282. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  283. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  284. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  285. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  286. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  287. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  288. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  289. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  290. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  291. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  292. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  293. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  294. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  295. package/docs/content/pr-visual-recap.mdx +3 -3
  296. package/docs/content/pure-agent-apps.mdx +22 -0
  297. package/package.json +2 -2
@@ -105,6 +105,71 @@ interface Stroke {
105
105
  createdAt: number;
106
106
  }
107
107
 
108
+ interface PendingTextInput {
109
+ /** Fractional x (0..1) of the visual rect. */
110
+ xFrac: number;
111
+ /** Fractional y (0..1) of the visual rect. */
112
+ yFrac: number;
113
+ value: string;
114
+ }
115
+
116
+ /**
117
+ * Keep long pen/stylus gestures bounded before they become agent prompt data.
118
+ * Once this limit is reached, older samples are progressively decimated while
119
+ * preserving the first, latest, and future points. At normal pointer rates the
120
+ * sub-pixel filter below is the only sampling users will notice.
121
+ */
122
+ const MAX_STROKE_POINTS = 2048;
123
+ const MIN_POINT_DISTANCE_PX = 0.35;
124
+
125
+ function pointFromClient(
126
+ clientX: number,
127
+ clientY: number,
128
+ rect: DOMRect,
129
+ ): Point | null {
130
+ if (rect.width <= 0 || rect.height <= 0) return null;
131
+ return {
132
+ x: Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)),
133
+ y: Math.max(0, Math.min(1, (clientY - rect.top) / rect.height)),
134
+ };
135
+ }
136
+
137
+ function appendStrokePoint(
138
+ points: Point[],
139
+ point: Point,
140
+ rect: DOMRect,
141
+ force = false,
142
+ ): Point[] {
143
+ const last = points[points.length - 1];
144
+ if (last) {
145
+ const dx = (point.x - last.x) * rect.width;
146
+ const dy = (point.y - last.y) * rect.height;
147
+ const distanceSquared = dx * dx + dy * dy;
148
+ if (
149
+ distanceSquared === 0 ||
150
+ (!force && distanceSquared < MIN_POINT_DISTANCE_PX ** 2)
151
+ ) {
152
+ return points;
153
+ }
154
+ }
155
+
156
+ let next = points;
157
+ if (next.length >= MAX_STROKE_POINTS) {
158
+ // Preserve the full trajectory instead of dropping the tail of a long
159
+ // gesture. Repeated compaction gradually lowers only the oldest sampling
160
+ // density, which is visually preferable to a path that suddenly stops.
161
+ const compacted: Point[] = [next[0]];
162
+ for (let index = 2; index < next.length - 1; index += 2) {
163
+ compacted.push(next[index]);
164
+ }
165
+ compacted.push(next[next.length - 1]);
166
+ next = compacted;
167
+ }
168
+
169
+ next.push(point);
170
+ return next;
171
+ }
172
+
108
173
  /**
109
174
  * Draw-to-prompt overlay for the slide canvas.
110
175
  *
@@ -142,21 +207,26 @@ export function DrawOverlay({
142
207
  const [color, setColor] = useState(PRESET_COLORS[0].color);
143
208
  const [lineWidth, setLineWidth] = useState(LINE_WIDTHS[1].value);
144
209
  const [strokes, setStrokes] = useState<Stroke[]>([]);
210
+ const strokesRef = useRef<Stroke[]>([]);
145
211
  const [textAnnotations, setTextAnnotations] = useState<DrawAnnotation[]>([]);
212
+ const textAnnotationsRef = useRef<DrawAnnotation[]>([]);
146
213
  // Unified redo stack. Each entry is either a Stroke or a DrawAnnotation so
147
214
  // undo/redo work in creation order across both types.
148
215
  const [redoStack, setRedoStack] = useState<Array<Stroke | DrawAnnotation>>(
149
216
  [],
150
217
  );
151
218
  const [currentStroke, setCurrentStroke] = useState<Point[] | null>(null);
219
+ // Pointer events can arrive down -> move -> up before React renders once.
220
+ // This ref is the authoritative in-progress gesture; state is only a
221
+ // frame-throttled snapshot used to redraw the canvas.
222
+ const currentStrokeRef = useRef<Point[] | null>(null);
223
+ const currentStrokeFrameRef = useRef<number | null>(null);
224
+ const activePointerIdRef = useRef<number | null>(null);
225
+ const activeStrokeStyleRef = useRef({ color, lineWidth });
226
+ const lastCreatedAtRef = useRef(0);
152
227
  const [textMode, setTextMode] = useState(false);
153
- const [textInput, setTextInput] = useState<{
154
- /** Fractional x (0..1) of the visual rect. */
155
- xFrac: number;
156
- /** Fractional y (0..1) of the visual rect. */
157
- yFrac: number;
158
- value: string;
159
- } | null>(null);
228
+ const [textInput, setTextInput] = useState<PendingTextInput | null>(null);
229
+ const textInputStateRef = useRef<PendingTextInput | null>(null);
160
230
  const textInputRef = useRef<HTMLInputElement>(null);
161
231
  // Escape cancels the pending text annotation, but unmounting the input also
162
232
  // fires its blur handler, which would commit the very annotation the user
@@ -169,33 +239,100 @@ export function DrawOverlay({
169
239
  // the canvas element's CSS size changes (e.g. device frame switch).
170
240
  const [resizeTick, setResizeTick] = useState(0);
171
241
 
172
- const scale = zoom / 100;
242
+ const scale = Math.max(zoom / 100, 0.01);
243
+
244
+ const cancelScheduledStrokeFrame = useCallback(() => {
245
+ if (currentStrokeFrameRef.current === null) return;
246
+ window.cancelAnimationFrame(currentStrokeFrameRef.current);
247
+ currentStrokeFrameRef.current = null;
248
+ }, []);
249
+
250
+ const scheduleCurrentStrokeRedraw = useCallback(() => {
251
+ if (currentStrokeFrameRef.current !== null) return;
252
+ currentStrokeFrameRef.current = window.requestAnimationFrame(() => {
253
+ currentStrokeFrameRef.current = null;
254
+ const points = currentStrokeRef.current;
255
+ setCurrentStroke(points ? [...points] : null);
256
+ });
257
+ }, []);
258
+
259
+ const resetActiveStroke = useCallback(() => {
260
+ drawing.current = false;
261
+ activePointerIdRef.current = null;
262
+ currentStrokeRef.current = null;
263
+ cancelScheduledStrokeFrame();
264
+ setCurrentStroke(null);
265
+ }, [cancelScheduledStrokeFrame]);
266
+
267
+ const nextCreatedAt = useCallback(() => {
268
+ const next = Math.max(Date.now(), lastCreatedAtRef.current + 1);
269
+ lastCreatedAtRef.current = next;
270
+ return next;
271
+ }, []);
272
+
273
+ const setPendingTextInput = useCallback(
274
+ (
275
+ update:
276
+ | PendingTextInput
277
+ | null
278
+ | ((current: PendingTextInput | null) => PendingTextInput | null),
279
+ ) => {
280
+ const next =
281
+ typeof update === "function"
282
+ ? update(textInputStateRef.current)
283
+ : update;
284
+ textInputStateRef.current = next;
285
+ setTextInput(next);
286
+ },
287
+ [],
288
+ );
173
289
 
174
290
  // Clear all state on hide so the next open starts fresh.
175
291
  useEffect(() => {
176
292
  if (!visible) {
293
+ resetActiveStroke();
294
+ strokesRef.current = [];
295
+ textAnnotationsRef.current = [];
177
296
  setStrokes([]);
178
297
  setTextAnnotations([]);
179
298
  setRedoStack([]);
180
- setCurrentStroke(null);
181
- setTextInput(null);
299
+ setPendingTextInput(null);
300
+ setTextMode(false);
182
301
  setInstruction("");
302
+ cancelingTextRef.current = false;
303
+ lastCreatedAtRef.current = 0;
183
304
  }
184
- }, [visible]);
305
+ }, [resetActiveStroke, setPendingTextInput, visible]);
185
306
 
307
+ // Comment-pin mode can temporarily leave the toolbar visible while making
308
+ // the drawing surface inert. Never retain a half-finished gesture across
309
+ // that tool switch.
186
310
  useEffect(() => {
187
- if (!textInput) return;
311
+ if (!canvasInteractive) resetActiveStroke();
312
+ }, [canvasInteractive, resetActiveStroke]);
313
+
314
+ useEffect(
315
+ () => () => {
316
+ cancelScheduledStrokeFrame();
317
+ },
318
+ [cancelScheduledStrokeFrame],
319
+ );
320
+
321
+ const shouldFocusTextInput = textInput !== null;
322
+ useEffect(() => {
323
+ if (!shouldFocusTextInput) return;
188
324
  const id = window.requestAnimationFrame(() => {
189
325
  textInputRef.current?.focus();
190
326
  });
191
327
  return () => window.cancelAnimationFrame(id);
192
- }, [textInput?.xFrac, textInput?.yFrac]);
328
+ }, [shouldFocusTextInput]);
193
329
 
194
330
  // ResizeObserver: bump resizeTick whenever the canvas changes CSS size so
195
331
  // the redraw effect re-runs and the backing store is resized correctly.
196
332
  useEffect(() => {
197
333
  const canvas = canvasRef.current;
198
334
  if (!canvas) return;
335
+ if (typeof ResizeObserver === "undefined") return;
199
336
  const ro = new ResizeObserver(() => setResizeTick((t) => t + 1));
200
337
  ro.observe(canvas);
201
338
  return () => ro.disconnect();
@@ -235,14 +372,15 @@ export function DrawOverlay({
235
372
  }
236
373
 
237
374
  if (currentStroke && currentStroke.length > 0) {
375
+ const activeStyle = activeStrokeStyleRef.current;
238
376
  drawStroke(
239
377
  ctx,
240
378
  currentStroke.map((p) => ({
241
379
  x: p.x * rect.width,
242
380
  y: p.y * rect.height,
243
381
  })),
244
- color,
245
- lineWidth,
382
+ activeStyle.color,
383
+ activeStyle.lineWidth,
246
384
  );
247
385
  }
248
386
  // `zoom` is a dependency because it scales the canvas via a CSS transform,
@@ -254,77 +392,151 @@ export function DrawOverlay({
254
392
  const handlePointerDown = useCallback(
255
393
  (e: React.PointerEvent) => {
256
394
  const rect = canvasRef.current?.getBoundingClientRect();
257
- if (!rect) return;
395
+ if (!rect || rect.width <= 0 || rect.height <= 0) return;
396
+ if (!canvasInteractive || drawing.current) return;
397
+ if (e.pointerType === "mouse" && e.button !== 0) return;
398
+
258
399
  if (textMode) {
400
+ e.preventDefault();
259
401
  cancelingTextRef.current = false;
260
- setTextInput({
402
+ setPendingTextInput({
261
403
  xFrac: (e.clientX - rect.left) / rect.width,
262
404
  yFrac: (e.clientY - rect.top) / rect.height,
263
405
  value: "",
264
406
  });
265
407
  return;
266
408
  }
409
+
410
+ const point = pointFromClient(e.clientX, e.clientY, rect);
411
+ if (!point) return;
412
+ e.preventDefault();
267
413
  drawing.current = true;
268
- // Store as fractions so the point survives zoom/resize changes.
269
- const point = {
270
- x: (e.clientX - rect.left) / rect.width,
271
- y: (e.clientY - rect.top) / rect.height,
272
- };
414
+ activePointerIdRef.current = e.pointerId;
415
+ activeStrokeStyleRef.current = { color, lineWidth };
416
+ currentStrokeRef.current = [point];
273
417
  setCurrentStroke([point]);
274
- (e.target as HTMLElement).setPointerCapture(e.pointerId);
418
+ try {
419
+ e.currentTarget.setPointerCapture(e.pointerId);
420
+ } catch {
421
+ // Pointer capture may fail if the browser already cancelled the
422
+ // gesture. The pointer-id guard still prevents cross-pointer mixing.
423
+ }
275
424
  },
276
- [textMode],
425
+ [canvasInteractive, color, lineWidth, setPendingTextInput, textMode],
277
426
  );
278
427
 
279
428
  const handlePointerMove = useCallback(
280
429
  (e: React.PointerEvent) => {
281
430
  if (!drawing.current || textMode) return;
431
+ if (activePointerIdRef.current !== e.pointerId) return;
282
432
  const rect = canvasRef.current?.getBoundingClientRect();
283
- if (!rect) return;
284
- const point = {
285
- x: (e.clientX - rect.left) / rect.width,
286
- y: (e.clientY - rect.top) / rect.height,
287
- };
288
- setCurrentStroke((prev) => (prev ? [...prev, point] : [point]));
433
+ if (!rect || !currentStrokeRef.current) return;
434
+
435
+ const nativeEvent = e.nativeEvent;
436
+ const coalesced = nativeEvent.getCoalescedEvents?.() ?? [];
437
+ const samples = [...coalesced, nativeEvent];
438
+ let points = currentStrokeRef.current;
439
+ let changed = false;
440
+ for (const sample of samples) {
441
+ const point = pointFromClient(sample.clientX, sample.clientY, rect);
442
+ if (!point) continue;
443
+ const previousLength = points.length;
444
+ const next = appendStrokePoint(points, point, rect);
445
+ if (next !== points || next.length !== previousLength) changed = true;
446
+ points = next;
447
+ }
448
+ currentStrokeRef.current = points;
449
+ if (changed) scheduleCurrentStrokeRedraw();
289
450
  },
290
- [textMode],
451
+ [scheduleCurrentStrokeRedraw, textMode],
291
452
  );
292
453
 
293
- const handlePointerUp = useCallback(() => {
294
- if (!drawing.current) return;
295
- drawing.current = false;
296
- if (currentStroke && currentStroke.length > 1) {
297
- setStrokes((prev) => [
298
- ...prev,
299
- {
454
+ const handlePointerUp = useCallback(
455
+ (e: React.PointerEvent<HTMLCanvasElement>) => {
456
+ if (!drawing.current || activePointerIdRef.current !== e.pointerId)
457
+ return;
458
+
459
+ e.preventDefault();
460
+ const rect = canvasRef.current?.getBoundingClientRect();
461
+ let points = currentStrokeRef.current;
462
+ if (rect && points) {
463
+ const coalesced = e.nativeEvent.getCoalescedEvents?.() ?? [];
464
+ for (const sample of [...coalesced, e.nativeEvent]) {
465
+ const point = pointFromClient(sample.clientX, sample.clientY, rect);
466
+ if (point) points = appendStrokePoint(points, point, rect, true);
467
+ }
468
+ currentStrokeRef.current = points;
469
+ }
470
+
471
+ drawing.current = false;
472
+ activePointerIdRef.current = null;
473
+ cancelScheduledStrokeFrame();
474
+ if (points && points.length > 1) {
475
+ const activeStyle = activeStrokeStyleRef.current;
476
+ const stroke: Stroke = {
300
477
  id: crypto.randomUUID(),
301
- points: currentStroke,
302
- color,
303
- lineWidth,
304
- createdAt: Date.now(),
305
- },
306
- ]);
307
- // A new stroke clears the redo stack (standard editor convention).
308
- setRedoStack([]);
309
- }
310
- setCurrentStroke(null);
311
- }, [currentStroke, color, lineWidth]);
478
+ points: [...points],
479
+ color: activeStyle.color,
480
+ lineWidth: activeStyle.lineWidth,
481
+ createdAt: nextCreatedAt(),
482
+ };
483
+ const nextStrokes = [...strokesRef.current, stroke];
484
+ strokesRef.current = nextStrokes;
485
+ setStrokes(nextStrokes);
486
+ // A new stroke clears the redo stack (standard editor convention).
487
+ setRedoStack([]);
488
+ }
489
+ currentStrokeRef.current = null;
490
+ setCurrentStroke(null);
312
491
 
313
- const handlePointerCancel = useCallback(() => {
314
- drawing.current = false;
315
- setCurrentStroke(null);
316
- }, []);
492
+ try {
493
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
494
+ e.currentTarget.releasePointerCapture(e.pointerId);
495
+ }
496
+ } catch {
497
+ // The browser can release capture before React handles pointerup.
498
+ }
499
+ },
500
+ [cancelScheduledStrokeFrame, nextCreatedAt],
501
+ );
502
+
503
+ const handlePointerCancel = useCallback(
504
+ (e: React.PointerEvent<HTMLCanvasElement>) => {
505
+ if (activePointerIdRef.current !== e.pointerId) return;
506
+ resetActiveStroke();
507
+ try {
508
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
509
+ e.currentTarget.releasePointerCapture(e.pointerId);
510
+ }
511
+ } catch {
512
+ // Pointer cancellation often releases capture before this event.
513
+ }
514
+ },
515
+ [resetActiveStroke],
516
+ );
517
+
518
+ const handleLostPointerCapture = useCallback(
519
+ (e: React.PointerEvent<HTMLCanvasElement>) => {
520
+ if (drawing.current && activePointerIdRef.current === e.pointerId) {
521
+ resetActiveStroke();
522
+ }
523
+ },
524
+ [resetActiveStroke],
525
+ );
317
526
 
318
527
  /**
319
528
  * Undo removes the most recently created annotation (stroke or text) in
320
529
  * creation order and pushes it onto the unified redo stack.
321
530
  */
322
531
  const undo = () => {
323
- const lastStroke = strokes.length > 0 ? strokes[strokes.length - 1] : null;
324
- const lastText =
325
- textAnnotations.length > 0
326
- ? textAnnotations[textAnnotations.length - 1]
532
+ const currentStrokes = strokesRef.current;
533
+ const currentTexts = textAnnotationsRef.current;
534
+ const lastStroke =
535
+ currentStrokes.length > 0
536
+ ? currentStrokes[currentStrokes.length - 1]
327
537
  : null;
538
+ const lastText =
539
+ currentTexts.length > 0 ? currentTexts[currentTexts.length - 1] : null;
328
540
 
329
541
  if (!lastStroke && !lastText) return;
330
542
 
@@ -333,10 +545,14 @@ export function DrawOverlay({
333
545
  const textTime = lastText?.createdAt ?? 0;
334
546
 
335
547
  if (lastStroke && strokeTime >= textTime) {
336
- setStrokes((prev) => prev.slice(0, -1));
548
+ const nextStrokes = currentStrokes.slice(0, -1);
549
+ strokesRef.current = nextStrokes;
550
+ setStrokes(nextStrokes);
337
551
  setRedoStack((stack) => [...stack, lastStroke]);
338
552
  } else if (lastText) {
339
- setTextAnnotations((prev) => prev.slice(0, -1));
553
+ const nextTexts = currentTexts.slice(0, -1);
554
+ textAnnotationsRef.current = nextTexts;
555
+ setTextAnnotations(nextTexts);
340
556
  setRedoStack((stack) => [...stack, lastText]);
341
557
  }
342
558
  };
@@ -348,10 +564,17 @@ export function DrawOverlay({
348
564
  const remaining = stack.slice(0, -1);
349
565
  if ("points" in top) {
350
566
  // It's a Stroke
351
- setStrokes((prev) => [...prev, top as Stroke]);
567
+ const nextStrokes = [...strokesRef.current, top as Stroke];
568
+ strokesRef.current = nextStrokes;
569
+ setStrokes(nextStrokes);
352
570
  } else {
353
571
  // It's a DrawAnnotation (text)
354
- setTextAnnotations((prev) => [...prev, top as DrawAnnotation]);
572
+ const nextTexts = [
573
+ ...textAnnotationsRef.current,
574
+ top as DrawAnnotation,
575
+ ];
576
+ textAnnotationsRef.current = nextTexts;
577
+ setTextAnnotations(nextTexts);
355
578
  }
356
579
  return remaining;
357
580
  });
@@ -359,9 +582,11 @@ export function DrawOverlay({
359
582
 
360
583
  const clear = () => {
361
584
  if (strokes.length === 0 && textAnnotations.length === 0) return;
362
- const prevStrokes = strokes;
363
- const prevTexts = textAnnotations;
585
+ const prevStrokes = strokesRef.current;
586
+ const prevTexts = textAnnotationsRef.current;
364
587
  const prevRedo = redoStack;
588
+ strokesRef.current = [];
589
+ textAnnotationsRef.current = [];
365
590
  setStrokes([]);
366
591
  setTextAnnotations([]);
367
592
  setRedoStack([]);
@@ -371,8 +596,12 @@ export function DrawOverlay({
371
596
  onClick: () => {
372
597
  // Merge snapshot with any strokes/texts drawn during the toast window
373
598
  // so new work is not discarded; also restore the pre-clear redo stack.
374
- setStrokes((cur) => [...prevStrokes, ...cur]);
375
- setTextAnnotations((cur) => [...prevTexts, ...cur]);
599
+ const restoredStrokes = [...prevStrokes, ...strokesRef.current];
600
+ const restoredTexts = [...prevTexts, ...textAnnotationsRef.current];
601
+ strokesRef.current = restoredStrokes;
602
+ textAnnotationsRef.current = restoredTexts;
603
+ setStrokes(restoredStrokes);
604
+ setTextAnnotations(restoredTexts);
376
605
  setRedoStack(prevRedo);
377
606
  },
378
607
  },
@@ -381,35 +610,42 @@ export function DrawOverlay({
381
610
  };
382
611
 
383
612
  const commitTextAnnotation = () => {
384
- if (!textInput || !textInput.value.trim()) {
385
- setTextInput(null);
386
- return;
387
- }
613
+ const pendingText = textInputStateRef.current;
614
+ // Clear the authoritative pending value first. Enter, blur, and Send can
615
+ // all occur in the same browser turn; any later handler becomes a no-op
616
+ // instead of duplicating the label.
617
+ setPendingTextInput(null);
618
+ if (!pendingText || !pendingText.value.trim()) return;
619
+
388
620
  const ann: DrawAnnotation = {
389
621
  id: crypto.randomUUID(),
390
622
  type: "text",
391
- text: textInput.value.trim(),
623
+ text: pendingText.value.trim(),
392
624
  // Store fractional position so the label stays anchored across zoom changes.
393
- position: { x: textInput.xFrac, y: textInput.yFrac },
625
+ position: { x: pendingText.xFrac, y: pendingText.yFrac },
394
626
  color,
395
627
  lineWidth,
396
- createdAt: Date.now(),
628
+ createdAt: nextCreatedAt(),
397
629
  };
398
- setTextAnnotations((prev) => [...prev, ann]);
630
+ const nextTexts = [...textAnnotationsRef.current, ann];
631
+ textAnnotationsRef.current = nextTexts;
632
+ setTextAnnotations(nextTexts);
399
633
  // A new text annotation also clears the redo stack.
400
634
  setRedoStack([]);
401
- setTextInput(null);
402
635
  };
403
636
 
404
637
  const send = () => {
405
638
  const canvas = canvasRef.current;
406
639
  if (!canvas) return;
640
+ // A focused text label normally commits on blur before the Send click, but
641
+ // keyboard/programmatic activation does not guarantee that ordering.
642
+ commitTextAnnotation();
407
643
  const rect = canvas.getBoundingClientRect();
408
644
  // Layout-space dimensions = visual rect / scale factor.
409
645
  const layoutW = rect.width / scale;
410
646
  const layoutH = rect.height / scale;
411
647
 
412
- const pathAnnotations: DrawAnnotation[] = strokes.map((s) => ({
648
+ const pathAnnotations: DrawAnnotation[] = strokesRef.current.map((s) => ({
413
649
  id: s.id,
414
650
  type: "path",
415
651
  // Convert fractional points to layout-space absolute pixels for the agent.
@@ -425,18 +661,18 @@ export function DrawOverlay({
425
661
  }));
426
662
 
427
663
  // Convert fractional text positions to layout-space absolute pixels.
428
- const layoutTextAnnotations: DrawAnnotation[] = textAnnotations.map(
429
- (a) => ({
664
+ const layoutTextAnnotations: DrawAnnotation[] =
665
+ textAnnotationsRef.current.map((a) => ({
430
666
  ...a,
431
667
  position: {
432
668
  x: a.position.x * layoutW,
433
669
  y: a.position.y * layoutH,
434
670
  },
435
- }),
436
- );
671
+ }));
437
672
 
438
673
  const all = [...pathAnnotations, ...layoutTextAnnotations];
439
- if (all.length === 0 && !instruction.trim()) return;
674
+ if (all.length === 0 && !instruction.trim() && queuedAnnotationCount === 0)
675
+ return;
440
676
 
441
677
  onSend(all, instruction.trim(), {
442
678
  width: layoutW,
@@ -449,6 +685,7 @@ export function DrawOverlay({
449
685
  const hasContent =
450
686
  strokes.length > 0 ||
451
687
  textAnnotations.length > 0 ||
688
+ !!textInput?.value.trim() ||
452
689
  instruction.trim() ||
453
690
  queuedAnnotationCount > 0;
454
691
 
@@ -648,7 +885,7 @@ export function DrawOverlay({
648
885
  ref={canvasRef}
649
886
  data-draw-canvas
650
887
  className={cn(
651
- "absolute inset-0 h-full w-full",
888
+ "absolute inset-0 h-full w-full touch-none",
652
889
  textMode ? "cursor-text" : "cursor-crosshair",
653
890
  !canvasInteractive && "pointer-events-none",
654
891
  )}
@@ -656,6 +893,7 @@ export function DrawOverlay({
656
893
  onPointerMove={handlePointerMove}
657
894
  onPointerUp={handlePointerUp}
658
895
  onPointerCancel={handlePointerCancel}
896
+ onLostPointerCapture={handleLostPointerCapture}
659
897
  />
660
898
 
661
899
  {/* Rendered text annotations.
@@ -708,7 +946,7 @@ export function DrawOverlay({
708
946
  ref={textInputRef}
709
947
  value={textInput.value}
710
948
  onChange={(e) =>
711
- setTextInput((prev) =>
949
+ setPendingTextInput((prev) =>
712
950
  prev ? { ...prev, value: e.target.value } : null,
713
951
  )
714
952
  }
@@ -719,16 +957,19 @@ export function DrawOverlay({
719
957
  cancelingTextRef.current = false;
720
958
  return;
721
959
  }
722
- if (textInput.value.trim()) commitTextAnnotation();
960
+ commitTextAnnotation();
723
961
  }}
724
962
  onKeyDown={(e) => {
725
963
  if (e.key === "Enter") {
726
964
  e.preventDefault();
965
+ // Committing unmounts the focused input, which can emit a
966
+ // blur event in the same turn. Skip that second commit.
967
+ cancelingTextRef.current = true;
727
968
  commitTextAnnotation();
728
969
  }
729
970
  if (e.key === "Escape") {
730
971
  cancelingTextRef.current = true;
731
- setTextInput(null);
972
+ setPendingTextInput(null);
732
973
  }
733
974
  }}
734
975
  className="h-7 w-48 border-primary bg-background text-sm"
@@ -95,6 +95,20 @@ export function designSelectionStateKeysForTab(
95
95
  : ["design-selection"];
96
96
  }
97
97
 
98
+ /**
99
+ * Route-level cleanup only owns this tab's scoped selection. The editor's
100
+ * owner-aware unmount cleanup is responsible for the global compatibility
101
+ * mirror; clearing that mirror here would let any tab that leaves /design
102
+ * erase another still-open editor tab's current agent context.
103
+ */
104
+ export function designSelectionCleanupKeysForTab(
105
+ browserTabId?: string,
106
+ ): string[] {
107
+ return [
108
+ browserTabId ? `design-selection:${browserTabId}` : "design-selection",
109
+ ];
110
+ }
111
+
98
112
  function normalizeEditorView(
99
113
  value: unknown,
100
114
  ): "single" | "overview" | undefined {
@@ -206,7 +220,7 @@ export function useNavigationState(enabled = true) {
206
220
  useEffect(() => {
207
221
  if (!enabled) return;
208
222
  if (location.pathname.startsWith("/design/")) return;
209
- for (const key of designSelectionStateKeysForTab(browserTabId)) {
223
+ for (const key of designSelectionCleanupKeysForTab(browserTabId)) {
210
224
  setClientAppState(key, null).catch(() => {});
211
225
  }
212
226
  }, [browserTabId, enabled, location.pathname]);