@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
@@ -6,8 +6,8 @@ import {
6
6
  } from "@agent-native/core/application-state";
7
7
  import { seedFromText } from "@agent-native/core/collab";
8
8
  import { buildDeepLink } from "@agent-native/core/server";
9
- import { assertAccess } from "@agent-native/core/sharing";
10
- import { eq } from "drizzle-orm";
9
+ import { accessFilter, assertAccess } from "@agent-native/core/sharing";
10
+ import { and, eq, inArray } from "drizzle-orm";
11
11
  import { nanoid } from "nanoid";
12
12
  import { z } from "zod";
13
13
 
@@ -18,6 +18,7 @@ import {
18
18
  mergeCanvasFramePlacements,
19
19
  type CanvasFramePlacement,
20
20
  } from "../shared/canvas-frames.js";
21
+ import { isUniqueConstraintViolation } from "../shared/db-conflict.js";
21
22
  import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
22
23
 
23
24
  const VARIANT_GAP = 96;
@@ -120,6 +121,336 @@ function isRecord(value: unknown): value is Record<string, unknown> {
120
121
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
121
122
  }
122
123
 
124
+ const MAX_FILENAME_INSERT_ATTEMPTS = 5;
125
+
126
+ /**
127
+ * True only when `rawSet` proves none of its screens has ever been removed by
128
+ * delete-file.ts. This action stamps every variant set it creates with
129
+ * `screenCount` — the exact number of screens it generated, which never
130
+ * changes afterward (present-design-variants.ts never mutates an existing
131
+ * set's `screenCount`, and delete-file.ts's `pruneDesignVariantSets` only
132
+ * ever shortens/removes a set's `screens` array, never touches `screenCount`).
133
+ * The moment any member is deleted, `screens.length` drops below the
134
+ * recorded `screenCount` (or the whole entry is removed once <=1 screens
135
+ * remain) — either way, `screens.length === screenCount` no longer holds. So
136
+ * this equality proves no delete-file call has EVER resolved against this
137
+ * set.
138
+ *
139
+ * IMPORTANT: this does NOT prove the user never picked a screen from the set.
140
+ * A pick only exists as a chat instruction (see `VARIANT_PICK_SUBMIT_MESSAGE`)
141
+ * until the agent's follow-up turn actually calls delete-file for the losers;
142
+ * there is no synchronous server-side seam that records a pick before that
143
+ * turn runs (an agent retry after a run cutoff, or a second
144
+ * present-design-variants call, can both land before the delete-file calls
145
+ * do). So `isUntouchedVariantSet` is used only to decide whether a set is
146
+ * eligible to be *marked* superseded — never as authority to delete its
147
+ * files. See `markSupersededVariantSets` / `deleteVariantSetsByIds` below.
148
+ * Sets without a numeric `screenCount` at all (legacy data predating this
149
+ * field) are NOT treated as untouched either, since we cannot prove those
150
+ * were never picked.
151
+ */
152
+ function isUntouchedVariantSet(
153
+ rawSet: unknown,
154
+ ): rawSet is { screens: unknown[]; screenCount: number } & Record<
155
+ string,
156
+ unknown
157
+ > {
158
+ return (
159
+ isRecord(rawSet) &&
160
+ Array.isArray(rawSet.screens) &&
161
+ typeof rawSet.screenCount === "number" &&
162
+ rawSet.screens.length === rawSet.screenCount
163
+ );
164
+ }
165
+
166
+ function collectUnmarkedSupersededCandidateIds(
167
+ variantSets: Record<string, unknown>,
168
+ ): string[] {
169
+ const setIds: string[] = [];
170
+ for (const [setId, rawSet] of Object.entries(variantSets)) {
171
+ if (!isUntouchedVariantSet(rawSet)) continue;
172
+ if (isRecord(rawSet) && rawSet.superseded === true) continue;
173
+ setIds.push(setId);
174
+ }
175
+ return setIds;
176
+ }
177
+
178
+ function screenFileIdsForSet(rawSet: { screens: unknown[] }): string[] {
179
+ const fileIds = new Set<string>();
180
+ for (const screen of rawSet.screens) {
181
+ const id = isRecord(screen)
182
+ ? screen.id
183
+ : typeof screen === "string"
184
+ ? screen
185
+ : undefined;
186
+ if (typeof id === "string") fileIds.add(id);
187
+ }
188
+ return Array.from(fileIds);
189
+ }
190
+
191
+ function pruneKeyedRecordForIds(
192
+ value: unknown,
193
+ ids: string[],
194
+ ): Record<string, unknown> {
195
+ const next = isRecord(value) ? { ...value } : {};
196
+ for (const id of ids) delete next[id];
197
+ return next;
198
+ }
199
+
200
+ /**
201
+ * Marks each PREVIOUS present-design-variants set for this design that is
202
+ * proven untouched (see `isUntouchedVariantSet`) as `superseded: true`,
203
+ * whenever a new variant set is created for the same design. This is
204
+ * bookkeeping ONLY — it never deletes files, never touches `canvasFrames` or
205
+ * `screenMetadata`, and is always safe to run unconditionally. A set with
206
+ * `superseded: true` still renders normally; the flag only makes it eligible
207
+ * for the opt-in `deleteSupersededSetIds` path below.
208
+ *
209
+ * Deletion is intentionally NOT automatic here. The failure mode this
210
+ * guards against: set V1=[S1,S2,S3] is generated; the user picks S2 in chat;
211
+ * before the agent's delete-file turn for S1/S3 actually runs (run cutoff,
212
+ * retry, or a second present-design-variants call arriving first),
213
+ * `screens.length === screenCount` still holds for V1 — a pick is not
214
+ * observable server-side until delete-file resolves it. Auto-deleting on
215
+ * that inference would have hard-deleted the picked screen S2 with no
216
+ * recovery. So this function only records bookkeeping; real deletion
217
+ * requires the agent to explicitly name the set via `deleteSupersededSetIds`
218
+ * — see that action param's description for the discipline expected of
219
+ * callers.
220
+ */
221
+ async function markSupersededVariantSets(
222
+ designId: string,
223
+ ): Promise<{ markedSetIds: string[] }> {
224
+ const db = getDb();
225
+
226
+ // Cheap early-exit read so the common case (no stale variant set to mark)
227
+ // never bumps the design's updatedAt via a no-op mutateDesignData call —
228
+ // mutateDesignDataUnlocked always advances updatedAt on every commit, even
229
+ // when the payload is unchanged. The authoritative check still runs inside
230
+ // the mutate() callback below, so a race against this snapshot is harmless.
231
+ const [designRow] = await db
232
+ .select({ data: schema.designs.data })
233
+ .from(schema.designs)
234
+ .where(
235
+ and(
236
+ eq(schema.designs.id, designId),
237
+ accessFilter(schema.designs, schema.designShares),
238
+ ),
239
+ );
240
+ if (!designRow) return { markedSetIds: [] };
241
+ let precheckVariantSets: Record<string, unknown> = {};
242
+ if (designRow.data) {
243
+ try {
244
+ const parsed: unknown = JSON.parse(designRow.data);
245
+ if (isRecord(parsed) && isRecord(parsed.designVariantSets)) {
246
+ precheckVariantSets = parsed.designVariantSets;
247
+ }
248
+ } catch {
249
+ return { markedSetIds: [] };
250
+ }
251
+ }
252
+ if (collectUnmarkedSupersededCandidateIds(precheckVariantSets).length === 0) {
253
+ return { markedSetIds: [] };
254
+ }
255
+
256
+ let markedSetIds: string[] = [];
257
+
258
+ await mutateDesignData({
259
+ designId,
260
+ mutate: (current, { updatedAt }) => {
261
+ const variantSets = isRecord(current.designVariantSets)
262
+ ? current.designVariantSets
263
+ : {};
264
+ const setIds = collectUnmarkedSupersededCandidateIds(variantSets);
265
+ markedSetIds = setIds;
266
+ if (setIds.length === 0) return current;
267
+
268
+ const nextVariantSets = { ...variantSets };
269
+ for (const setId of setIds) {
270
+ const rawSet = variantSets[setId];
271
+ if (isRecord(rawSet)) {
272
+ nextVariantSets[setId] = { ...rawSet, superseded: true };
273
+ }
274
+ }
275
+
276
+ return {
277
+ ...current,
278
+ designVariantSets: nextVariantSets,
279
+ updatedAt,
280
+ };
281
+ },
282
+ isApplied: (current) => {
283
+ const variantSets = isRecord(current.designVariantSets)
284
+ ? current.designVariantSets
285
+ : {};
286
+ return markedSetIds.every(
287
+ (setId) =>
288
+ isRecord(variantSets[setId]) &&
289
+ variantSets[setId]!.superseded === true,
290
+ );
291
+ },
292
+ });
293
+
294
+ return { markedSetIds };
295
+ }
296
+
297
+ /**
298
+ * Explicit, opt-in permanent deletion of specific variant sets by id. This is
299
+ * the ONLY path that hard-deletes variant-set files; it never runs
300
+ * automatically. The caller (the agent) must pass `deleteSupersededSetIds`
301
+ * naming the exact sets it knows were abandoned without any user pick — e.g.
302
+ * generating a brand-new set after the user asked to see different options.
303
+ * Never pass a previous set's id here on the strength of a guess or a retry;
304
+ * only do so when there is no reason to believe the user discussed or picked
305
+ * one of its screens.
306
+ *
307
+ * As a safety net, a requested id is only honored when the set is already
308
+ * flagged `superseded: true` by `markSupersededVariantSets` (i.e. proven
309
+ * untouched by any delete-file call as of this same run). A set that has
310
+ * already had even one delete-file call resolve against it — the normal sign
311
+ * that a pick is mid-flight — is never marked superseded and is therefore
312
+ * never eligible here, regardless of what the caller requests.
313
+ */
314
+ async function deleteVariantSetsByIds(
315
+ designId: string,
316
+ requestedSetIds: string[],
317
+ ): Promise<{ removedSetIds: string[]; removedFileIds: string[] }> {
318
+ const db = getDb();
319
+
320
+ // Same cheap early-exit as markSupersededVariantSets: skip the
321
+ // updatedAt-bumping mutateDesignData commit when none of the requested set
322
+ // ids currently qualifies for deletion. The authoritative check still runs
323
+ // inside the mutate() callback below.
324
+ const [designRow] = await db
325
+ .select({ data: schema.designs.data })
326
+ .from(schema.designs)
327
+ .where(
328
+ and(
329
+ eq(schema.designs.id, designId),
330
+ accessFilter(schema.designs, schema.designShares),
331
+ ),
332
+ );
333
+ if (!designRow) return { removedSetIds: [], removedFileIds: [] };
334
+ let precheckVariantSets: Record<string, unknown> = {};
335
+ if (designRow.data) {
336
+ try {
337
+ const parsed: unknown = JSON.parse(designRow.data);
338
+ if (isRecord(parsed) && isRecord(parsed.designVariantSets)) {
339
+ precheckVariantSets = parsed.designVariantSets;
340
+ }
341
+ } catch {
342
+ return { removedSetIds: [], removedFileIds: [] };
343
+ }
344
+ }
345
+ if (
346
+ !requestedSetIds.some((setId) => {
347
+ const rawSet = precheckVariantSets[setId];
348
+ return (
349
+ isRecord(rawSet) &&
350
+ rawSet.superseded === true &&
351
+ Array.isArray(rawSet.screens)
352
+ );
353
+ })
354
+ ) {
355
+ return { removedSetIds: [], removedFileIds: [] };
356
+ }
357
+
358
+ let removedSetIds: string[] = [];
359
+ let removedFileIds: string[] = [];
360
+
361
+ await mutateDesignData({
362
+ designId,
363
+ mutate: (current, { updatedAt }) => {
364
+ const variantSets = isRecord(current.designVariantSets)
365
+ ? current.designVariantSets
366
+ : {};
367
+ const setIds: string[] = [];
368
+ const fileIds = new Set<string>();
369
+ for (const setId of requestedSetIds) {
370
+ const rawSet = variantSets[setId];
371
+ if (
372
+ !isRecord(rawSet) ||
373
+ rawSet.superseded !== true ||
374
+ !Array.isArray(rawSet.screens)
375
+ ) {
376
+ continue;
377
+ }
378
+ setIds.push(setId);
379
+ for (const id of screenFileIdsForSet(
380
+ rawSet as { screens: unknown[] },
381
+ )) {
382
+ fileIds.add(id);
383
+ }
384
+ }
385
+ removedSetIds = setIds;
386
+ removedFileIds = Array.from(fileIds);
387
+ if (setIds.length === 0) return current;
388
+
389
+ const nextVariantSets = { ...variantSets };
390
+ for (const setId of setIds) delete nextVariantSets[setId];
391
+
392
+ return {
393
+ ...current,
394
+ canvasFrames: pruneKeyedRecordForIds(
395
+ current.canvasFrames,
396
+ removedFileIds,
397
+ ),
398
+ screenMetadata: pruneKeyedRecordForIds(
399
+ current.screenMetadata,
400
+ removedFileIds,
401
+ ),
402
+ designVariantSets: nextVariantSets,
403
+ updatedAt,
404
+ };
405
+ },
406
+ isApplied: (current) => {
407
+ const variantSets = isRecord(current.designVariantSets)
408
+ ? current.designVariantSets
409
+ : {};
410
+ return removedSetIds.every((setId) => !(setId in variantSets));
411
+ },
412
+ });
413
+
414
+ if (removedFileIds.length > 0) {
415
+ await db
416
+ .delete(schema.designFiles)
417
+ .where(inArray(schema.designFiles.id, removedFileIds));
418
+
419
+ // Closes the small window between the metadata prune above and the
420
+ // physical row delete: mirrors delete-file.ts's before/delete/after
421
+ // pattern so a sibling request that refreshed canvasFrames/screenMetadata
422
+ // for one of these now-deleted ids in that window still gets swept.
423
+ await mutateDesignData({
424
+ designId,
425
+ mutate: (current, { updatedAt }) => ({
426
+ ...current,
427
+ canvasFrames: pruneKeyedRecordForIds(
428
+ current.canvasFrames,
429
+ removedFileIds,
430
+ ),
431
+ screenMetadata: pruneKeyedRecordForIds(
432
+ current.screenMetadata,
433
+ removedFileIds,
434
+ ),
435
+ updatedAt,
436
+ }),
437
+ isApplied: (current) => {
438
+ const frames = isRecord(current.canvasFrames)
439
+ ? current.canvasFrames
440
+ : {};
441
+ const metadata = isRecord(current.screenMetadata)
442
+ ? current.screenMetadata
443
+ : {};
444
+ return removedFileIds.every(
445
+ (id) => !(id in frames) && !(id in metadata),
446
+ );
447
+ },
448
+ });
449
+ }
450
+
451
+ return { removedSetIds, removedFileIds };
452
+ }
453
+
123
454
  function slugify(value: string, fallback: string) {
124
455
  return (
125
456
  value
@@ -432,7 +763,10 @@ export default defineAction({
432
763
  "compact representative screen; pass concise labels/descriptions/features " +
433
764
  "and omit content when full HTML would be too large. Design will render " +
434
765
  "compact screens from the direction data. Expand the chosen direction " +
435
- "after the user picks.",
766
+ "after the user picks. Screens from an earlier variant set are never " +
767
+ "deleted automatically: if you are knowingly replacing your own earlier " +
768
+ "set that the user never picked from or discussed, pass its set id in " +
769
+ "deleteSupersededSetIds; otherwise leave old sets in place.",
436
770
  schema: z.object({
437
771
  designId: z.string().describe("Design project ID to show variants for"),
438
772
  prompt: z
@@ -446,6 +780,19 @@ export default defineAction({
446
780
  .describe(
447
781
  "2-5 concise, visually distinct generated design options to place as overview screens (3 is the sweet spot). Prefer short label/description/features for each direction; include inline HTML content only when it is compact enough to finish.",
448
782
  ),
783
+ deleteSupersededSetIds: z
784
+ .array(z.string())
785
+ .optional()
786
+ .describe(
787
+ "OPT-IN permanent deletion of earlier variant sets you are knowingly " +
788
+ "replacing. Pass a previous variantSetId ONLY when you created that " +
789
+ "set yourself and the user never picked, kept, or discussed any of " +
790
+ "its screens (e.g. the user asked for a completely different set of " +
791
+ "directions). If the user may have picked a screen — even if the " +
792
+ "losing screens have not been deleted yet — do NOT pass the set id: " +
793
+ "deletion is permanent and would destroy the picked screen. Ids are " +
794
+ "only honored for sets provably untouched by any delete-file call.",
795
+ ),
449
796
  }),
450
797
  mcpApp: {
451
798
  compactCatalog: true,
@@ -458,9 +805,22 @@ export default defineAction({
458
805
  height: 720,
459
806
  }),
460
807
  },
461
- run: async ({ designId, prompt, variants }) => {
808
+ run: async ({ designId, prompt, variants, deleteSupersededSetIds }) => {
462
809
  await assertAccess("design", designId, "editor");
463
810
 
811
+ // Non-destructive bookkeeping: flag earlier still-complete variant sets
812
+ // as superseded. Files are NEVER deleted automatically — a user's pick
813
+ // exists only as a chat instruction until the agent's delete-file turn
814
+ // runs, so an automatic sweep here could destroy the picked screen. Real
815
+ // deletion happens only for set ids the caller explicitly opts into via
816
+ // `deleteSupersededSetIds` below (and only when the set is provably
817
+ // untouched by any delete-file call).
818
+ await markSupersededVariantSets(designId);
819
+ const variantSetCleanup =
820
+ deleteSupersededSetIds && deleteSupersededSetIds.length > 0
821
+ ? await deleteVariantSetsByIds(designId, deleteSupersededSetIds)
822
+ : { removedSetIds: [], removedFileIds: [] };
823
+
464
824
  const db = getDb();
465
825
  const now = new Date().toISOString();
466
826
  const existingFiles = await db
@@ -475,9 +835,8 @@ export default defineAction({
475
835
  const variant = variants[index]!;
476
836
  const label = variant.label.trim() || optionName(index);
477
837
  const slug = slugify(label, `option-${index + 1}`);
478
- const preferredFilename = `variant-${slug}.html`;
479
- const filename = uniqueFilename(preferredFilename, usedFilenames);
480
- const fileId = nanoid();
838
+ let filename = uniqueFilename(`variant-${slug}.html`, usedFilenames);
839
+ let fileId = nanoid();
481
840
  const providedContent = variant.content?.trim();
482
841
  const initialSize = inferVariantSize(variant, prompt);
483
842
  const rawContent =
@@ -491,15 +850,43 @@ export default defineAction({
491
850
  // operations as soon as it lands on the overview board.
492
851
  const content = annotateScreenHtmlForPersist(rawContent, "html");
493
852
 
494
- await db.insert(schema.designFiles).values({
495
- id: fileId,
496
- designId,
497
- filename,
498
- fileType: "html",
499
- content,
500
- createdAt: now,
501
- updatedAt: now,
502
- });
853
+ // `usedFilenames` is a snapshot taken once at the top of run(), so a
854
+ // concurrent present-design-variants call (an agent retry after a
855
+ // timeout is the common trigger) can independently compute the same
856
+ // (designId, filename) pair and win the insert first. The
857
+ // `design_files_design_filename_unique_idx` unique index (see
858
+ // server/plugins/db.ts) turns the loser's insert into a constraint
859
+ // error instead of a silently duplicated screen; recover by refreshing
860
+ // the real filename list from the DB, picking a fresh unique name, and
861
+ // retrying — bounded so a persistent non-race failure still surfaces.
862
+ for (let attempt = 0; ; attempt += 1) {
863
+ try {
864
+ await db.insert(schema.designFiles).values({
865
+ id: fileId,
866
+ designId,
867
+ filename,
868
+ fileType: "html",
869
+ content,
870
+ createdAt: now,
871
+ updatedAt: now,
872
+ });
873
+ break;
874
+ } catch (err) {
875
+ if (
876
+ !isUniqueConstraintViolation(err) ||
877
+ attempt >= MAX_FILENAME_INSERT_ATTEMPTS
878
+ ) {
879
+ throw err;
880
+ }
881
+ const freshFiles = await db
882
+ .select({ filename: schema.designFiles.filename })
883
+ .from(schema.designFiles)
884
+ .where(eq(schema.designFiles.designId, designId));
885
+ for (const file of freshFiles) usedFilenames.add(file.filename);
886
+ filename = uniqueFilename(`variant-${slug}.html`, usedFilenames);
887
+ fileId = nanoid();
888
+ }
889
+ }
503
890
  await seedFromText(fileId, content);
504
891
 
505
892
  screens.push({
@@ -542,6 +929,13 @@ export default defineAction({
542
929
  id: variantSetId,
543
930
  prompt: prompt ?? "Pick a direction",
544
931
  createdAt: now,
932
+ // Immutable record of how many screens this set started with, so a
933
+ // later present-design-variants call can prove no delete-file call
934
+ // has ever resolved against any of this set's screens — see
935
+ // markSupersededVariantSets / isUntouchedVariantSet above.
936
+ // delete-file.ts's pruneDesignVariantSets only ever shortens or
937
+ // removes `screens`; it never rewrites this field.
938
+ screenCount: screens.length,
545
939
  screens: screens.map((screen) => ({
546
940
  id: screen.id,
547
941
  variantId: screen.variantId,
@@ -638,6 +1032,8 @@ export default defineAction({
638
1032
  screens,
639
1033
  path: `/design/${encodeURIComponent(designId)}?view=overview`,
640
1034
  embed: true,
1035
+ cleanedUpPreviousVariantScreens: variantSetCleanup.removedFileIds.length,
1036
+ deletedSupersededSetIds: variantSetCleanup.removedSetIds,
641
1037
  fallbackInstructions: FALLBACK_INSTRUCTIONS,
642
1038
  nextRequiredAction:
643
1039
  'Wait for the user to pick a screen in chat. Then delete each unchosen variant screen with delete-file at most once, call get-design-snapshot exactly once with fileId for the chosen screen, and call edit-design with that same fileId in a bounded pass. Use mode "replace-file" to replace the representative direction screen with a complete but compact requested app/product UI in the chosen visual style. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the full feature list is too large for one reliable edit. Do not leave a direction board, variant brief, or summary card as the final result. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save.',
@@ -9,6 +9,7 @@ import {
9
9
  import {
10
10
  applySourceEdit,
11
11
  previewSourceDiff,
12
+ sourceContentHash,
12
13
  } from "../shared/source-workspace.js";
13
14
 
14
15
  const sourceEditSchema = z.discriminatedUnion("kind", [
@@ -77,7 +78,19 @@ export default defineAction({
77
78
  conflict:
78
79
  workspace.sourceType === "inline" ? null : "unsupported-source-backend",
79
80
  currentVersionHash: live.versionHash,
80
- nextVersionHash: next.changed ? undefined : live.versionHash,
81
+ // The hash the file WOULD have after this edit is applied. Do NOT pass
82
+ // this as apply-source-edit's expectedVersionHash — that CAS compares
83
+ // against the CURRENT pre-edit hash, so the post-edit hash would always
84
+ // fail; pass `currentVersionHash` (above) instead to close the
85
+ // preview→apply race, exactly as the code workbench's inline-provider
86
+ // does. `nextVersionHash` is for detecting that the preview's target
87
+ // content changed (compare a later preview/read against it) and for
88
+ // optimistic client bookkeeping of the expected post-apply hash.
89
+ // When nothing changed, the content is still `live.content`, so there
90
+ // is no new hash to report.
91
+ nextVersionHash: next.changed
92
+ ? sourceContentHash(next.content)
93
+ : undefined,
81
94
  editsApplied: next.editsApplied,
82
95
  diff: previewSourceDiff(live.content, next.content),
83
96
  };
@@ -66,8 +66,20 @@ function extractSelector(tagHtml: string, tagName: string): string | undefined {
66
66
  if (idMatch) return `#${idMatch[1] ?? idMatch[2]}`;
67
67
  const classMatch = tagHtml.match(/\bclass\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
68
68
  if (classMatch) {
69
- const first = (classMatch[1] ?? classMatch[2] ?? "").trim().split(/\s+/)[0];
70
- if (first) return `${tagName.toLowerCase()}.${first}`;
69
+ const classNames = (classMatch[1] ?? classMatch[2] ?? "")
70
+ .trim()
71
+ .split(/\s+/)
72
+ .filter(Boolean);
73
+ if (classNames.length > 0) {
74
+ // Include every class, not just the first: Tailwind screens commonly
75
+ // have several sibling elements sharing one common utility class (e.g.
76
+ // two buttons both carrying "h-4" but differing in every other class).
77
+ // A first-class-only selector is ambiguous and apply-a11y-fix's
78
+ // deterministic edit engine (which treats every dot segment after the
79
+ // tag as a required class, see shared/code-layer.ts) would match the
80
+ // wrong element among several sharing only that one class.
81
+ return `${tagName.toLowerCase()}.${classNames.join(".")}`;
82
+ }
71
83
  }
72
84
  return undefined;
73
85
  }
@@ -104,6 +116,13 @@ function checkMissingAlt(html: string): A11yFinding[] {
104
116
  return findings;
105
117
  }
106
118
 
119
+ /** Escape regex metacharacters so untrusted HTML attribute values (e.g. an
120
+ * author-supplied `id`) can be safely interpolated into a `RegExp` source
121
+ * string instead of crashing the audit or matching unintended text. */
122
+ function escapeRegExp(value: string): string {
123
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
124
+ }
125
+
107
126
  /** Check form inputs without an associated <label> or aria-label/aria-labelledby. */
108
127
  function checkMissingLabels(html: string): A11yFinding[] {
109
128
  const findings: A11yFinding[] = [];
@@ -126,7 +145,10 @@ function checkMissingLabels(html: string): A11yFinding[] {
126
145
  const idMatch = tag.match(/\bid\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
127
146
  const inputId = idMatch?.[1] ?? idMatch?.[2];
128
147
  const hasExplicitLabel = inputId
129
- ? new RegExp(`for\\s*=\\s*(?:"${inputId}"|'${inputId}')`, "i").test(html)
148
+ ? new RegExp(
149
+ `for\\s*=\\s*(?:"${escapeRegExp(inputId)}"|'${escapeRegExp(inputId)}')`,
150
+ "i",
151
+ ).test(html)
130
152
  : false;
131
153
  const hasImplicitLabel = isWrappedByLabel(html, inputStart, inputEnd);
132
154
 
@@ -87,6 +87,16 @@ export default defineAction({
87
87
  .describe(
88
88
  "1-8 focused design-intake questions. Prefer 4-6 concise questions " +
89
89
  "with useful choices, Other enabled, and Decide for me where appropriate.",
90
+ )
91
+ .refine(
92
+ (questions) =>
93
+ new Set(questions.map((question) => question.id)).size ===
94
+ questions.length,
95
+ {
96
+ message:
97
+ "Question ids must be unique — duplicate ids share one answer " +
98
+ "slot in the form, silently discarding one question's answer.",
99
+ },
90
100
  ),
91
101
  }),
92
102
  mcpApp: {