@agent-native/core 0.92.10 → 0.92.11

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 (334) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +23 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/index.browser.ts +1 -0
  21. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  22. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  23. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  24. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  25. package/corpus/templates/clips/AGENTS.md +4 -0
  26. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  27. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  28. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  29. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  30. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  31. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  33. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  34. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  44. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  45. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  46. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  47. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  57. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  58. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  59. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  60. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  61. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  62. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  63. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  64. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  65. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  66. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  67. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  68. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  69. package/corpus/templates/content/AGENTS.md +7 -6
  70. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  71. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  72. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  73. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/pull-document.ts +5 -7
  75. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  76. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  77. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  78. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  79. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  80. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  81. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  82. package/corpus/templates/content/app/i18n-data.ts +18 -0
  83. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  84. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  85. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  86. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  87. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  88. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  89. package/corpus/templates/design/AGENTS.md +45 -10
  90. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  91. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  92. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  93. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  94. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  95. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  96. package/corpus/templates/design/actions/generate-design.ts +79 -12
  97. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  98. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  99. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  100. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  101. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  102. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  103. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  104. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  105. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  106. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  108. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  109. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  110. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  111. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  112. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  113. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  114. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  115. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  116. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  117. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  118. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  119. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  120. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  122. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  123. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  124. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  125. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  126. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  127. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  129. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  130. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  131. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  132. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  135. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  138. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  139. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  140. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  141. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  142. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  144. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  145. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  147. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  148. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  149. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  150. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  151. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  152. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  153. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  154. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  156. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  157. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  158. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  159. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  160. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  161. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  162. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  163. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  164. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  166. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  167. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  168. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  169. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  170. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  171. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  172. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  173. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  178. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  179. package/corpus/templates/design/app/components/design/types.ts +33 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  181. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  182. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  183. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  184. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  185. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  186. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  187. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  188. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  189. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  190. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  191. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  192. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  193. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  194. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  196. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  198. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  199. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  200. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  201. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  202. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  239. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  240. package/corpus/templates/design/e2e/helpers.ts +27 -6
  241. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  242. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  243. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  244. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  245. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  246. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  247. package/corpus/templates/design/server/plugins/db.ts +41 -0
  248. package/corpus/templates/design/server/source-workspace.ts +64 -0
  249. package/corpus/templates/design/shared/board-file.ts +13 -2
  250. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  251. package/corpus/templates/design/shared/code-layer.ts +122 -8
  252. package/corpus/templates/design/shared/component-library.ts +161 -0
  253. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  254. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  255. package/corpus/templates/design/shared/figma-url.ts +149 -0
  256. package/corpus/templates/design/shared/pen-path.ts +16 -7
  257. package/corpus/templates/design/shared/source-mode.ts +1 -0
  258. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  259. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  260. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  261. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  262. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  263. package/dist/agent/production-agent.d.ts +79 -6
  264. package/dist/agent/production-agent.d.ts.map +1 -1
  265. package/dist/agent/production-agent.js +161 -14
  266. package/dist/agent/production-agent.js.map +1 -1
  267. package/dist/agent/run-manager.d.ts.map +1 -1
  268. package/dist/agent/run-manager.js +29 -5
  269. package/dist/agent/run-manager.js.map +1 -1
  270. package/dist/agent/run-store.d.ts +46 -0
  271. package/dist/agent/run-store.d.ts.map +1 -1
  272. package/dist/agent/run-store.js +61 -0
  273. package/dist/agent/run-store.js.map +1 -1
  274. package/dist/cli/design-connect.d.ts +6 -0
  275. package/dist/cli/design-connect.d.ts.map +1 -1
  276. package/dist/cli/design-connect.js +336 -139
  277. package/dist/cli/design-connect.js.map +1 -1
  278. package/dist/cli/skills.d.ts.map +1 -1
  279. package/dist/cli/skills.js +14 -0
  280. package/dist/cli/skills.js.map +1 -1
  281. package/dist/client/AssistantChat.d.ts +2 -0
  282. package/dist/client/AssistantChat.d.ts.map +1 -1
  283. package/dist/client/AssistantChat.js +19 -3
  284. package/dist/client/AssistantChat.js.map +1 -1
  285. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.js +38 -18
  287. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  288. package/dist/client/agent-chat.d.ts +60 -0
  289. package/dist/client/agent-chat.d.ts.map +1 -1
  290. package/dist/client/agent-chat.js +122 -1
  291. package/dist/client/agent-chat.js.map +1 -1
  292. package/dist/client/chat/index.d.ts +1 -1
  293. package/dist/client/chat/index.d.ts.map +1 -1
  294. package/dist/client/chat/index.js +1 -1
  295. package/dist/client/chat/index.js.map +1 -1
  296. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  297. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  298. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  299. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  300. package/dist/client/index.d.ts +1 -1
  301. package/dist/client/index.d.ts.map +1 -1
  302. package/dist/client/index.js +1 -1
  303. package/dist/client/index.js.map +1 -1
  304. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  307. package/dist/collab/awareness-store.d.ts +5 -0
  308. package/dist/collab/awareness-store.d.ts.map +1 -1
  309. package/dist/collab/awareness-store.js +19 -12
  310. package/dist/collab/awareness-store.js.map +1 -1
  311. package/dist/collab/client.d.ts.map +1 -1
  312. package/dist/collab/client.js +13 -5
  313. package/dist/collab/client.js.map +1 -1
  314. package/dist/collab/index.d.ts +1 -0
  315. package/dist/collab/index.d.ts.map +1 -1
  316. package/dist/collab/index.js +1 -0
  317. package/dist/collab/index.js.map +1 -1
  318. package/dist/collab/recent-edits.d.ts +3 -1
  319. package/dist/collab/recent-edits.d.ts.map +1 -1
  320. package/dist/collab/recent-edits.js +57 -2
  321. package/dist/collab/recent-edits.js.map +1 -1
  322. package/dist/index.browser.d.ts +1 -1
  323. package/dist/index.browser.d.ts.map +1 -1
  324. package/dist/index.browser.js +1 -1
  325. package/dist/index.browser.js.map +1 -1
  326. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  327. package/dist/server/agent-chat-plugin.js +87 -10
  328. package/dist/server/agent-chat-plugin.js.map +1 -1
  329. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  330. package/dist/server/builder-design-systems.d.ts +15 -0
  331. package/dist/server/builder-design-systems.d.ts.map +1 -1
  332. package/dist/server/builder-design-systems.js +9 -4
  333. package/dist/server/builder-design-systems.js.map +1 -1
  334. package/package.json +1 -1
@@ -3,6 +3,7 @@ import {
3
3
  IconEraser,
4
4
  IconArrowBackUp,
5
5
  IconArrowForwardUp,
6
+ IconLoader2,
6
7
  IconSend,
7
8
  IconCursorText,
8
9
  IconX,
@@ -12,6 +13,7 @@ import {
12
13
  useRef,
13
14
  useCallback,
14
15
  useEffect,
16
+ useLayoutEffect,
15
17
  type ReactNode,
16
18
  } from "react";
17
19
  import { createPortal } from "react-dom";
@@ -69,6 +71,42 @@ interface DrawOverlayProps {
69
71
  ) => void;
70
72
  /** Called when the user cancels / closes the draw mode */
71
73
  onClose: () => void;
74
+ /**
75
+ * True while the caller is capturing/compositing/uploading the annotated
76
+ * screenshot for a just-submitted drawing (see the design app's
77
+ * design-canvas/annotation-snapshot.ts). Disables Send and swaps its icon
78
+ * for a spinner so a slow capture can't be triggered twice from the same
79
+ * drawing. The entire overlay becomes inert while true so edits made after
80
+ * the submitted snapshot cannot be erased when that submission is later
81
+ * confirmed and the caller clears the batch.
82
+ */
83
+ sending?: boolean;
84
+ /**
85
+ * Bump this (e.g. a counter) exactly when the caller wants to discard the
86
+ * current strokes/text annotations — after `onClose` or a confirmed Send.
87
+ * Strokes/text are intentionally NOT cleared merely because `visible`
88
+ * turns false: `visible` can toggle off for reasons that have nothing to
89
+ * do with the user wanting to discard their work (switching tools, views,
90
+ * or side panels), and doing so silently would destroy annotations the
91
+ * user never got a chance to send. Only an in-progress (uncommitted)
92
+ * gesture is reset on hide; a pending text label is committed so its typed
93
+ * content survives.
94
+ */
95
+ clearSignal?: number;
96
+ /**
97
+ * Identity of the canvas this annotation batch belongs to. When the host
98
+ * reuses one DrawOverlay instance for a different screen, the old batch is
99
+ * cleared with a warning instead of being silently reinterpreted against
100
+ * the new screen's pixels and submitted with the wrong screenshot.
101
+ */
102
+ scopeKey?: string;
103
+ /**
104
+ * Keep the canvas DOM/bitmap alive while hidden. Use for the one active
105
+ * editor overlay whose work must survive mode/view toggles; leave false for
106
+ * large preview grids so every dormant screen does not allocate a canvas
107
+ * and ResizeObserver.
108
+ */
109
+ retainSurfaceWhenHidden?: boolean;
72
110
  }
73
111
 
74
112
  const PRESET_COLORS = [
@@ -111,6 +149,18 @@ interface PendingTextInput {
111
149
  /** Fractional y (0..1) of the visual rect. */
112
150
  yFrac: number;
113
151
  value: string;
152
+ /**
153
+ * Monotonic generation id. A click that opens a new pending text box while
154
+ * an earlier one is still focused fires pointerdown (which replaces the
155
+ * ref synchronously) before the outgoing box's native `blur` event runs.
156
+ * Without this, that trailing blur reads the *new* (empty) pending input
157
+ * out of the ref and wipes it via `commitTextAnnotation`'s unconditional
158
+ * `setPendingTextInput(null)` before the user can type anything. Callers
159
+ * pass the generation captured at their own render so a stale blur/Enter/
160
+ * Escape from a since-replaced box is a no-op instead of clobbering the
161
+ * newer one.
162
+ */
163
+ generation: number;
114
164
  }
115
165
 
116
166
  /**
@@ -200,6 +250,10 @@ export function DrawOverlay({
200
250
  zoom = 100,
201
251
  onSend,
202
252
  onClose,
253
+ sending = false,
254
+ clearSignal,
255
+ scopeKey,
256
+ retainSurfaceWhenHidden = false,
203
257
  }: DrawOverlayProps) {
204
258
  const t = useT();
205
259
  const containerRef = useRef<HTMLDivElement>(null);
@@ -227,12 +281,14 @@ export function DrawOverlay({
227
281
  const [textMode, setTextMode] = useState(false);
228
282
  const [textInput, setTextInput] = useState<PendingTextInput | null>(null);
229
283
  const textInputStateRef = useRef<PendingTextInput | null>(null);
284
+ const textInputGenerationRef = useRef(0);
230
285
  const textInputRef = useRef<HTMLInputElement>(null);
231
286
  // Escape cancels the pending text annotation, but unmounting the input also
232
287
  // fires its blur handler, which would commit the very annotation the user
233
288
  // just cancelled. This flag lets the blur handler skip that commit.
234
289
  const cancelingTextRef = useRef(false);
235
290
  const [instruction, setInstruction] = useState("");
291
+ const instructionRef = useRef("");
236
292
  const drawing = useRef(false);
237
293
  const canvasSizeRef = useRef({ w: 0, h: 0 });
238
294
  // Resize tick: bumped by ResizeObserver so the redraw effect re-runs when
@@ -241,6 +297,36 @@ export function DrawOverlay({
241
297
 
242
298
  const scale = Math.max(zoom / 100, 0.01);
243
299
 
300
+ // A host can still unmount the active editor entirely (route/design close,
301
+ // or focused canvas teardown) rather than merely hiding this component.
302
+ // Losing unsent work in that teardown must never be silent. Normal
303
+ // overview/focused visibility changes retain their active surface and do
304
+ // not hit this cleanup; only a real component unmount does.
305
+ useEffect(() => {
306
+ return () => {
307
+ // A true unmount never runs the `!visible` effect above (the prop
308
+ // never transitions — the component is simply gone), so a still-open
309
+ // pending text box never gets its usual commit-on-hide chance. Count
310
+ // it here too, or a label the user was mid-typing would vanish
311
+ // without even counting toward the warning.
312
+ const pendingText = textInputStateRef.current?.value.trim();
313
+ const discardedCount =
314
+ strokesRef.current.length +
315
+ textAnnotationsRef.current.length +
316
+ (pendingText ? 1 : 0) +
317
+ (instructionRef.current.trim() ? 1 : 0);
318
+ if (discardedCount > 0) {
319
+ toast(
320
+ t("visualEditor.annotationsDiscardedOnViewChange", {
321
+ count: discardedCount,
322
+ }),
323
+ );
324
+ }
325
+ };
326
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- must only run
327
+ // its cleanup on actual unmount, not on every `t`/locale change.
328
+ }, []);
329
+
244
330
  const cancelScheduledStrokeFrame = useCallback(() => {
245
331
  if (currentStrokeFrameRef.current === null) return;
246
332
  window.cancelAnimationFrame(currentStrokeFrameRef.current);
@@ -287,22 +373,35 @@ export function DrawOverlay({
287
373
  [],
288
374
  );
289
375
 
290
- // Clear all state on hide so the next open starts fresh.
376
+ const clearAnnotationState = useCallback(() => {
377
+ resetActiveStroke();
378
+ strokesRef.current = [];
379
+ textAnnotationsRef.current = [];
380
+ setStrokes([]);
381
+ setTextAnnotations([]);
382
+ setRedoStack([]);
383
+ setPendingTextInput(null);
384
+ setTextMode(false);
385
+ instructionRef.current = "";
386
+ setInstruction("");
387
+ cancelingTextRef.current = false;
388
+ lastCreatedAtRef.current = 0;
389
+ textInputGenerationRef.current = 0;
390
+ }, [resetActiveStroke, setPendingTextInput]);
391
+
392
+ // Hiding ends whatever the user was mid-gesture on, but must NOT discard
393
+ // already-committed strokes/text: `visible` can turn false for reasons
394
+ // that have nothing to do with the user wanting to abandon their
395
+ // annotations (switching tools, views, or side panels). Only reset the
396
+ // transient in-flight state here — the committed `strokes`/
397
+ // `textAnnotations` arrays are cleared exclusively by `clearSignal` below.
291
398
  useEffect(() => {
292
399
  if (!visible) {
293
400
  resetActiveStroke();
294
- strokesRef.current = [];
295
- textAnnotationsRef.current = [];
296
- setStrokes([]);
297
- setTextAnnotations([]);
298
- setRedoStack([]);
299
- setPendingTextInput(null);
300
401
  setTextMode(false);
301
- setInstruction("");
302
402
  cancelingTextRef.current = false;
303
- lastCreatedAtRef.current = 0;
304
403
  }
305
- }, [resetActiveStroke, setPendingTextInput, visible]);
404
+ }, [resetActiveStroke, visible]);
306
405
 
307
406
  // Comment-pin mode can temporarily leave the toolbar visible while making
308
407
  // the drawing surface inert. Never retain a half-finished gesture across
@@ -320,26 +419,34 @@ export function DrawOverlay({
320
419
 
321
420
  const shouldFocusTextInput = textInput !== null;
322
421
  useEffect(() => {
323
- if (!shouldFocusTextInput) return;
422
+ if (!visible || !shouldFocusTextInput) return;
324
423
  const id = window.requestAnimationFrame(() => {
325
424
  textInputRef.current?.focus();
326
425
  });
327
426
  return () => window.cancelAnimationFrame(id);
328
- }, [shouldFocusTextInput]);
427
+ }, [shouldFocusTextInput, visible]);
329
428
 
330
- // ResizeObserver: bump resizeTick whenever the canvas changes CSS size so
331
- // the redraw effect re-runs and the backing store is resized correctly.
429
+ // Retained editor surfaces keep their canvas mounted while hidden, so a
430
+ // hidden-first host installs its observer and the bitmap does not flash
431
+ // blank when the user re-enters annotate mode. Non-retained preview-grid
432
+ // overlays attach only while visible to avoid idle canvas/observer cost.
332
433
  useEffect(() => {
434
+ if (!visible && !retainSurfaceWhenHidden) return;
333
435
  const canvas = canvasRef.current;
334
436
  if (!canvas) return;
335
437
  if (typeof ResizeObserver === "undefined") return;
336
438
  const ro = new ResizeObserver(() => setResizeTick((t) => t + 1));
337
439
  ro.observe(canvas);
338
440
  return () => ro.disconnect();
339
- }, []);
340
-
341
- // Redraw canvas whenever strokes change or the canvas is resized.
342
- useEffect(() => {
441
+ }, [retainSurfaceWhenHidden, visible]);
442
+
443
+ // Redraw before paint whenever strokes change, the canvas is resized, or a
444
+ // preserved batch becomes visible again. The visible dependency is
445
+ // intentional even though the DOM stays mounted: browsers may discard a
446
+ // hidden canvas backing store under memory pressure, and re-entry must
447
+ // never expose a blank frame before the next pointer event.
448
+ useLayoutEffect(() => {
449
+ if (!visible) return;
343
450
  const canvas = canvasRef.current;
344
451
  if (!canvas) return;
345
452
  const ctx = canvas.getContext("2d");
@@ -387,22 +494,109 @@ export function DrawOverlay({
387
494
  // which changes getBoundingClientRect() without firing ResizeObserver — the
388
495
  // effect must re-run on zoom change to redraw the fraction-based strokes at
389
496
  // the new visual size.
390
- }, [strokes, currentStroke, color, lineWidth, resizeTick, zoom]);
497
+ }, [strokes, currentStroke, color, lineWidth, resizeTick, visible, zoom]);
498
+
499
+ /**
500
+ * Commits the pending text annotation (if any) and clears the pending
501
+ * input. `expectedGeneration`, when passed, guards against a stale
502
+ * blur/Enter/Escape belonging to a pending text box that has since been
503
+ * replaced or already cleared — see `PendingTextInput.generation`. Called
504
+ * with no argument to force-commit whatever is currently pending (e.g.
505
+ * before opening a new text box, or right before Send).
506
+ */
507
+ const commitTextAnnotation = useCallback(
508
+ (expectedGeneration?: number) => {
509
+ const pendingText = textInputStateRef.current;
510
+ if (
511
+ !pendingText ||
512
+ (expectedGeneration !== undefined &&
513
+ pendingText.generation !== expectedGeneration)
514
+ ) {
515
+ return;
516
+ }
517
+ // Clear the authoritative pending value first. Enter, blur, and Send can
518
+ // all occur in the same browser turn; any later handler becomes a no-op
519
+ // instead of duplicating the label.
520
+ setPendingTextInput(null);
521
+ if (!pendingText.value.trim()) return;
522
+
523
+ const ann: DrawAnnotation = {
524
+ id: crypto.randomUUID(),
525
+ type: "text",
526
+ text: pendingText.value.trim(),
527
+ // Store fractional position so the label stays anchored across zoom changes.
528
+ position: { x: pendingText.xFrac, y: pendingText.yFrac },
529
+ color,
530
+ lineWidth,
531
+ createdAt: nextCreatedAt(),
532
+ };
533
+ const nextTexts = [...textAnnotationsRef.current, ann];
534
+ textAnnotationsRef.current = nextTexts;
535
+ setTextAnnotations(nextTexts);
536
+ // A new text annotation also clears the redo stack.
537
+ setRedoStack([]);
538
+ },
539
+ [color, lineWidth, nextCreatedAt, setPendingTextInput],
540
+ );
541
+
542
+ // Hiding removes the pending-text input from interaction. Commit whatever
543
+ // was typed explicitly so a still-open label survives an unrelated exit
544
+ // instead of relying on incidental native blur ordering.
545
+ useEffect(() => {
546
+ if (!visible) commitTextAnnotation();
547
+ }, [visible, commitTextAnnotation]);
548
+
549
+ // The explicit-discard path: the caller bumps `clearSignal` exactly when
550
+ // the user deliberately closes the overlay (X/Escape) or a Send is
551
+ // confirmed delivered. A scope change below is the only other reset path,
552
+ // guarding against submitting one screen's geometry against another.
553
+ const lastClearSignalRef = useRef(clearSignal);
554
+ useLayoutEffect(() => {
555
+ if (clearSignal === lastClearSignalRef.current) return;
556
+ lastClearSignalRef.current = clearSignal;
557
+ clearAnnotationState();
558
+ }, [clearAnnotationState, clearSignal]);
559
+
560
+ const lastScopeKeyRef = useRef(scopeKey);
561
+ useLayoutEffect(() => {
562
+ if (scopeKey === lastScopeKeyRef.current) return;
563
+ lastScopeKeyRef.current = scopeKey;
564
+ const pendingText = textInputStateRef.current?.value.trim();
565
+ const discardedCount =
566
+ strokesRef.current.length +
567
+ textAnnotationsRef.current.length +
568
+ (pendingText ? 1 : 0) +
569
+ (instructionRef.current.trim() ? 1 : 0);
570
+ if (discardedCount > 0) {
571
+ toast(
572
+ t("visualEditor.annotationsDiscardedOnViewChange", {
573
+ count: discardedCount,
574
+ }),
575
+ );
576
+ }
577
+ clearAnnotationState();
578
+ }, [clearAnnotationState, scopeKey, t]);
391
579
 
392
580
  const handlePointerDown = useCallback(
393
581
  (e: React.PointerEvent) => {
394
582
  const rect = canvasRef.current?.getBoundingClientRect();
395
583
  if (!rect || rect.width <= 0 || rect.height <= 0) return;
396
- if (!canvasInteractive || drawing.current) return;
584
+ if (!canvasInteractive || sending || drawing.current) return;
397
585
  if (e.pointerType === "mouse" && e.button !== 0) return;
398
586
 
399
587
  if (textMode) {
400
588
  e.preventDefault();
401
589
  cancelingTextRef.current = false;
590
+ // Commit whatever text box was still open (using the current ref,
591
+ // which still holds the outgoing box at this point) before replacing
592
+ // it, so clicking a new spot never silently drops the previous label.
593
+ commitTextAnnotation();
594
+ textInputGenerationRef.current += 1;
402
595
  setPendingTextInput({
403
596
  xFrac: (e.clientX - rect.left) / rect.width,
404
597
  yFrac: (e.clientY - rect.top) / rect.height,
405
598
  value: "",
599
+ generation: textInputGenerationRef.current,
406
600
  });
407
601
  return;
408
602
  }
@@ -422,7 +616,15 @@ export function DrawOverlay({
422
616
  // gesture. The pointer-id guard still prevents cross-pointer mixing.
423
617
  }
424
618
  },
425
- [canvasInteractive, color, lineWidth, setPendingTextInput, textMode],
619
+ [
620
+ canvasInteractive,
621
+ color,
622
+ commitTextAnnotation,
623
+ lineWidth,
624
+ sending,
625
+ setPendingTextInput,
626
+ textMode,
627
+ ],
426
628
  );
427
629
 
428
630
  const handlePointerMove = useCallback(
@@ -529,6 +731,7 @@ export function DrawOverlay({
529
731
  * creation order and pushes it onto the unified redo stack.
530
732
  */
531
733
  const undo = () => {
734
+ if (sending) return;
532
735
  const currentStrokes = strokesRef.current;
533
736
  const currentTexts = textAnnotationsRef.current;
534
737
  const lastStroke =
@@ -558,6 +761,7 @@ export function DrawOverlay({
558
761
  };
559
762
 
560
763
  const redo = () => {
764
+ if (sending) return;
561
765
  setRedoStack((stack) => {
562
766
  if (stack.length === 0) return stack;
563
767
  const top = stack[stack.length - 1];
@@ -581,6 +785,7 @@ export function DrawOverlay({
581
785
  };
582
786
 
583
787
  const clear = () => {
788
+ if (sending) return;
584
789
  if (strokes.length === 0 && textAnnotations.length === 0) return;
585
790
  const prevStrokes = strokesRef.current;
586
791
  const prevTexts = textAnnotationsRef.current;
@@ -609,32 +814,8 @@ export function DrawOverlay({
609
814
  });
610
815
  };
611
816
 
612
- const commitTextAnnotation = () => {
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
-
620
- const ann: DrawAnnotation = {
621
- id: crypto.randomUUID(),
622
- type: "text",
623
- text: pendingText.value.trim(),
624
- // Store fractional position so the label stays anchored across zoom changes.
625
- position: { x: pendingText.xFrac, y: pendingText.yFrac },
626
- color,
627
- lineWidth,
628
- createdAt: nextCreatedAt(),
629
- };
630
- const nextTexts = [...textAnnotationsRef.current, ann];
631
- textAnnotationsRef.current = nextTexts;
632
- setTextAnnotations(nextTexts);
633
- // A new text annotation also clears the redo stack.
634
- setRedoStack([]);
635
- };
636
-
637
817
  const send = () => {
818
+ if (sending) return;
638
819
  const canvas = canvasRef.current;
639
820
  if (!canvas) return;
640
821
  // A focused text label normally commits on blur before the Send click, but
@@ -680,7 +861,7 @@ export function DrawOverlay({
680
861
  });
681
862
  };
682
863
 
683
- if (!visible) return null;
864
+ if (!visible && !retainSurfaceWhenHidden) return null;
684
865
 
685
866
  const hasContent =
686
867
  strokes.length > 0 ||
@@ -706,7 +887,10 @@ export function DrawOverlay({
706
887
  type="button"
707
888
  aria-label={preset.label}
708
889
  data-testid={`draw-color-${preset.label.toLowerCase()}`}
709
- onClick={() => setColor(preset.color)}
890
+ onClick={() => {
891
+ if (!sending) setColor(preset.color);
892
+ }}
893
+ disabled={sending}
710
894
  className={cn(
711
895
  "h-5 w-5 cursor-pointer rounded-full",
712
896
  color === preset.color
@@ -732,7 +916,10 @@ export function DrawOverlay({
732
916
  type="button"
733
917
  aria-label={lw.label}
734
918
  data-testid={`draw-line-width-${lw.label.toLowerCase()}`}
735
- onClick={() => setLineWidth(lw.value)}
919
+ onClick={() => {
920
+ if (!sending) setLineWidth(lw.value);
921
+ }}
922
+ disabled={sending}
736
923
  className={cn(
737
924
  "flex h-6 w-6 cursor-pointer items-center justify-center rounded",
738
925
  lineWidth === lw.value
@@ -760,7 +947,10 @@ export function DrawOverlay({
760
947
  type="button"
761
948
  aria-label={t("visualEditor.typeAnywhereOnCanvas")}
762
949
  data-testid="draw-text-mode"
763
- onClick={() => setTextMode(!textMode)}
950
+ onClick={() => {
951
+ if (!sending) setTextMode(!textMode);
952
+ }}
953
+ disabled={sending}
764
954
  className={cn(
765
955
  "flex h-6 w-6 cursor-pointer items-center justify-center rounded",
766
956
  textMode
@@ -784,7 +974,7 @@ export function DrawOverlay({
784
974
  aria-label={t("visualEditor.undoStroke")}
785
975
  data-testid="draw-undo"
786
976
  onClick={undo}
787
- disabled={!canUndo}
977
+ disabled={!canUndo || sending}
788
978
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:cursor-default disabled:opacity-30"
789
979
  >
790
980
  <IconArrowBackUp className="h-3.5 w-3.5" />
@@ -801,7 +991,7 @@ export function DrawOverlay({
801
991
  aria-label={t("visualEditor.redoStroke")}
802
992
  data-testid="draw-redo"
803
993
  onClick={redo}
804
- disabled={!canRedo}
994
+ disabled={!canRedo || sending}
805
995
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:cursor-default disabled:opacity-30"
806
996
  >
807
997
  <IconArrowForwardUp className="h-3.5 w-3.5" />
@@ -818,7 +1008,9 @@ export function DrawOverlay({
818
1008
  aria-label={t("visualEditor.clearAll")}
819
1009
  data-testid="draw-clear-all"
820
1010
  onClick={clear}
821
- disabled={strokes.length === 0 && textAnnotations.length === 0}
1011
+ disabled={
1012
+ sending || (strokes.length === 0 && textAnnotations.length === 0)
1013
+ }
822
1014
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground disabled:cursor-default disabled:opacity-30"
823
1015
  >
824
1016
  <IconEraser className="h-3.5 w-3.5" />
@@ -832,12 +1024,17 @@ export function DrawOverlay({
832
1024
  {/* Instruction input */}
833
1025
  <Input
834
1026
  value={instruction}
835
- onChange={(e) => setInstruction(e.target.value)}
1027
+ onChange={(e) => {
1028
+ if (sending) return;
1029
+ instructionRef.current = e.target.value;
1030
+ setInstruction(e.target.value);
1031
+ }}
836
1032
  onKeyDown={(e) => {
837
- if (e.key === "Enter" && hasContent) send();
1033
+ if (e.key === "Enter" && hasContent && !sending) send();
838
1034
  if (e.key === "Escape") onClose();
839
1035
  }}
840
1036
  placeholder={t("visualEditor.tellAgentWhatToDo")}
1037
+ disabled={sending}
841
1038
  className="h-7 w-56 border-border bg-background text-xs"
842
1039
  />
843
1040
 
@@ -847,10 +1044,14 @@ export function DrawOverlay({
847
1044
  data-testid="draw-send"
848
1045
  className="h-7 gap-1 px-3 !text-[11px] cursor-pointer"
849
1046
  onClick={send}
850
- disabled={!hasContent}
1047
+ disabled={!hasContent || sending}
851
1048
  >
852
- <IconSend className="h-3 w-3" />
853
- {t("visualEditor.send")}
1049
+ {sending ? (
1050
+ <IconLoader2 className="h-3 w-3 animate-spin" />
1051
+ ) : (
1052
+ <IconSend className="h-3 w-3" />
1053
+ )}
1054
+ {sending ? t("visualEditor.sendingDrawing") : t("visualEditor.send")}
854
1055
  </Button>
855
1056
 
856
1057
  {/* Close */}
@@ -861,6 +1062,7 @@ export function DrawOverlay({
861
1062
  aria-label={t("visualEditor.exitDrawMode")}
862
1063
  data-testid="draw-exit"
863
1064
  onClick={onClose}
1065
+ disabled={sending}
864
1066
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground"
865
1067
  >
866
1068
  <IconX className="h-3.5 w-3.5" />
@@ -875,9 +1077,13 @@ export function DrawOverlay({
875
1077
  <div
876
1078
  ref={containerRef}
877
1079
  data-draw-overlay
1080
+ aria-hidden={!visible}
878
1081
  className={cn(
879
1082
  "absolute inset-0 z-[100]",
880
- canvasInteractive ? "pointer-events-auto" : "pointer-events-none",
1083
+ visible ? "visible" : "invisible pointer-events-none",
1084
+ canvasInteractive && !sending
1085
+ ? "pointer-events-auto"
1086
+ : "pointer-events-none",
881
1087
  )}
882
1088
  >
883
1089
  {/* Drawing canvas */}
@@ -887,7 +1093,7 @@ export function DrawOverlay({
887
1093
  className={cn(
888
1094
  "absolute inset-0 h-full w-full touch-none",
889
1095
  textMode ? "cursor-text" : "cursor-crosshair",
890
- !canvasInteractive && "pointer-events-none",
1096
+ (!canvasInteractive || sending) && "pointer-events-none",
891
1097
  )}
892
1098
  onPointerDown={handlePointerDown}
893
1099
  onPointerMove={handlePointerMove}
@@ -946,6 +1152,7 @@ export function DrawOverlay({
946
1152
  ref={textInputRef}
947
1153
  value={textInput.value}
948
1154
  onChange={(e) =>
1155
+ !sending &&
949
1156
  setPendingTextInput((prev) =>
950
1157
  prev ? { ...prev, value: e.target.value } : null,
951
1158
  )
@@ -957,7 +1164,11 @@ export function DrawOverlay({
957
1164
  cancelingTextRef.current = false;
958
1165
  return;
959
1166
  }
960
- commitTextAnnotation();
1167
+ // Pass this render's generation so a trailing blur that
1168
+ // arrives after pointerdown has already opened a *newer*
1169
+ // text box (see PendingTextInput.generation) is a no-op
1170
+ // instead of wiping the box the user just started typing.
1171
+ commitTextAnnotation(textInput.generation);
961
1172
  }}
962
1173
  onKeyDown={(e) => {
963
1174
  if (e.key === "Enter") {
@@ -965,7 +1176,7 @@ export function DrawOverlay({
965
1176
  // Committing unmounts the focused input, which can emit a
966
1177
  // blur event in the same turn. Skip that second commit.
967
1178
  cancelingTextRef.current = true;
968
- commitTextAnnotation();
1179
+ commitTextAnnotation(textInput.generation);
969
1180
  }
970
1181
  if (e.key === "Escape") {
971
1182
  cancelingTextRef.current = true;
@@ -973,6 +1184,7 @@ export function DrawOverlay({
973
1184
  }
974
1185
  }}
975
1186
  className="h-7 w-48 border-primary bg-background text-sm"
1187
+ disabled={sending}
976
1188
  autoFocus
977
1189
  placeholder={t("visualEditor.typeAnnotationFancy")}
978
1190
  />
@@ -980,7 +1192,7 @@ export function DrawOverlay({
980
1192
  );
981
1193
  })()}
982
1194
 
983
- <DrawToolbarPortal>{toolbar}</DrawToolbarPortal>
1195
+ {visible ? <DrawToolbarPortal>{toolbar}</DrawToolbarPortal> : null}
984
1196
  </div>
985
1197
  );
986
1198
  }
@@ -54,13 +54,24 @@ export function useQuestionFlow(
54
54
 
55
55
  const sendContinuation = useCallback(
56
56
  (message: string, context?: string) => {
57
+ // Always request `newTab` (mirroring useAgentGenerating.submit's
58
+ // default). Without it, when there is no continuationTabId yet the
59
+ // message goes to whatever tab is currently active, but the id we
60
+ // return here would still be a freshly generated one that was never
61
+ // actually used — trackAgentGeneration/onContinue would then watch a
62
+ // tabId that never matches real chatRunning events, so the design
63
+ // "generating" UI silently desyncs (false "stopped, please retry"
64
+ // toasts, completion never detected). Passing tabId only when we have
65
+ // a continuationTabId still reuses that existing thread (addOptimistic
66
+ // thread is idempotent for known ids); omitting it lets a fresh id be
67
+ // generated and actually created, so the returned tabId is always the
68
+ // real destination thread.
57
69
  const tabId = sendToDesignAgentChat({
58
70
  message,
59
71
  context,
60
72
  submit: true,
61
- ...(continuationTabId
62
- ? { tabId: continuationTabId, newTab: true }
63
- : {}),
73
+ newTab: true,
74
+ ...(continuationTabId ? { tabId: continuationTabId } : {}),
64
75
  });
65
76
  onContinue?.(tabId);
66
77
  flow.clear();