@agent-native/core 0.79.27 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +41 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +51 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +50 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +49 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +44 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +46 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +44 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +48 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +36 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +38 -0
  13. package/corpus/core/docs/content/template-design.mdx +45 -0
  14. package/corpus/core/package.json +2 -1
  15. package/corpus/core/src/agent/engine/index.ts +1 -0
  16. package/corpus/core/src/agent/engine/registry.ts +40 -0
  17. package/corpus/core/src/agent/production-agent.ts +6 -2
  18. package/corpus/core/src/cli/design-connect.ts +490 -0
  19. package/corpus/core/src/cli/index.ts +16 -0
  20. package/corpus/core/src/cli/skills.ts +126 -3
  21. package/corpus/core/src/client/AssistantChat.tsx +43 -7
  22. package/corpus/core/src/client/agent-chat.ts +27 -3
  23. package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +1 -1
  24. package/corpus/core/src/client/blocks/library/diagram.tsx +2 -2
  25. package/corpus/core/src/client/blocks/library/wireframe.tsx +1 -1
  26. package/corpus/core/src/client/chat/message-components.tsx +1 -1
  27. package/corpus/core/src/client/chat-model-groups.ts +1 -2
  28. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +33 -66
  29. package/corpus/core/src/client/composer/TiptapComposer.tsx +99 -24
  30. package/corpus/core/src/client/composer/useVoiceDictation.ts +429 -260
  31. package/corpus/core/src/client/frame-protocol.ts +17 -2
  32. package/corpus/core/src/client/rich-markdown-editor/RegistryBlockNode.tsx +2 -2
  33. package/corpus/core/src/client/sharing/ShareButton.tsx +20 -15
  34. package/corpus/core/src/client/use-agent-engine-configured.ts +80 -28
  35. package/corpus/core/src/server/transcribe-voice.ts +57 -22
  36. package/corpus/core/src/shared/mcp-embed-headers.ts +23 -1
  37. package/corpus/core/src/styles/agent-native.css +56 -7
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  39. package/corpus/core/src/vite/client.ts +2 -0
  40. package/corpus/core/src/voice/index.ts +18 -0
  41. package/corpus/core/src/voice/voice-cleanup-prompt.ts +38 -0
  42. package/corpus/core/src/voice/voice-context.ts +217 -0
  43. package/corpus/core/src/voice/voice-replacements.ts +75 -0
  44. package/corpus/templates/analytics/AGENTS.md +4 -3
  45. package/corpus/templates/analytics/actions/list-session-recordings.ts +6 -1
  46. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +557 -400
  47. package/corpus/templates/analytics/app/global.css +0 -4
  48. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  49. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +33 -8
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +7 -0
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +7 -0
  53. package/corpus/templates/analytics/changelog/2026-06-28-ask-tab-now-matches-the-darker-app-background.md +6 -0
  54. package/corpus/templates/analytics/changelog/2026-06-28-dashboard-email-reports-now-use-cleaner-light-mode-snapshots.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  56. package/corpus/templates/analytics/changelog/2026-06-28-sessions-now-show-only-signed-in-recordings-with-playable-re.md +6 -0
  57. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  58. package/corpus/templates/analytics/server/lib/dashboard-report.ts +29 -7
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +5 -2
  60. package/corpus/templates/analytics/server/lib/session-replay.ts +24 -4
  61. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -4
  62. package/corpus/templates/assets/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  63. package/corpus/templates/brain/app/components/layout/Layout.tsx +29 -2
  64. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +131 -42
  65. package/corpus/templates/brain/app/i18n/zh-TW.ts +2 -0
  66. package/corpus/templates/brain/app/i18n-data.ts +20 -0
  67. package/corpus/templates/brain/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  68. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +29 -1
  69. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +417 -299
  70. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  71. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  72. package/corpus/templates/calendar/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  73. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +6 -19
  74. package/corpus/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  75. package/corpus/templates/clips/AGENTS.md +4 -0
  76. package/corpus/templates/clips/actions/cleanup-transcript.ts +39 -9
  77. package/corpus/templates/clips/actions/finalize-meeting.ts +81 -12
  78. package/corpus/templates/clips/actions/lib/agents-md-context.ts +4 -1
  79. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
  80. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +1 -1
  81. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  82. package/corpus/templates/clips/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  83. package/corpus/templates/clips/desktop/src/lib/personal-vocabulary.ts +13 -8
  84. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +60 -1
  85. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1 -1
  86. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -1
  87. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -5
  88. package/corpus/templates/content/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +137 -0
  90. package/corpus/templates/design/AGENTS.md +95 -5
  91. package/corpus/templates/design/DESIGN.md +82 -0
  92. package/corpus/templates/design/DEVELOPING.md +43 -0
  93. package/corpus/templates/design/actions/add-localhost-screens.ts +426 -0
  94. package/corpus/templates/design/actions/apply-shader.ts +277 -0
  95. package/corpus/templates/design/actions/apply-visual-edit.ts +303 -0
  96. package/corpus/templates/design/actions/connect-localhost.ts +183 -0
  97. package/corpus/templates/design/actions/edit-design.ts +195 -42
  98. package/corpus/templates/design/actions/generate-design.ts +71 -1
  99. package/corpus/templates/design/actions/generate-screens.ts +193 -0
  100. package/corpus/templates/design/actions/get-code-layer-projection.ts +191 -0
  101. package/corpus/templates/design/actions/get-shader.ts +119 -0
  102. package/corpus/templates/design/actions/list-localhost-connections.ts +95 -0
  103. package/corpus/templates/design/actions/navigate.ts +55 -2
  104. package/corpus/templates/design/actions/view-screen.ts +68 -1
  105. package/corpus/templates/design/agent-native.app-skill.json +10 -0
  106. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +761 -0
  107. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +2057 -103
  108. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +74 -38
  109. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +399 -0
  110. package/corpus/templates/design/app/components/design/EditPanel.tsx +3185 -439
  111. package/corpus/templates/design/app/components/design/LayersPanel.tsx +1124 -0
  112. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3116 -147
  113. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +700 -13
  114. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +107 -63
  115. package/corpus/templates/design/app/components/design/index.ts +0 -5
  116. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +286 -0
  117. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +699 -0
  118. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +461 -0
  119. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +166 -0
  120. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +1420 -0
  121. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +420 -0
  122. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +215 -0
  123. package/corpus/templates/design/app/components/design/inspector/ShaderControls.stories.tsx +71 -0
  124. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +518 -0
  125. package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +106 -0
  126. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +361 -0
  127. package/corpus/templates/design/app/components/design/inspector/index.ts +60 -0
  128. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +201 -0
  129. package/corpus/templates/design/app/components/design/types.ts +23 -0
  130. package/corpus/templates/design/app/components/layout/Layout.tsx +24 -13
  131. package/corpus/templates/design/app/components/layout/Sidebar.tsx +4 -4
  132. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +474 -36
  133. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +22 -5
  134. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +24 -11
  135. package/corpus/templates/design/app/global.css +121 -3
  136. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -0
  137. package/corpus/templates/design/app/hooks/use-navigation-state.ts +130 -8
  138. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +329 -0
  139. package/corpus/templates/design/app/i18n/zh-TW.ts +264 -0
  140. package/corpus/templates/design/app/i18n-data.ts +2790 -1
  141. package/corpus/templates/design/app/pages/DesignEditor.tsx +5108 -851
  142. package/corpus/templates/design/changelog/2026-06-28-annotate-mode-now-combines-drawing-and-comment-pins-in-one-c.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-28-design-editor-panel-borders-are-cleaner-so-the-canvas-edge-s.md +6 -0
  144. package/corpus/templates/design/changelog/2026-06-28-drawing-prompts-now-reliably-open-and-submit-to-the-design-a.md +6 -0
  145. package/corpus/templates/design/changelog/2026-06-28-editor-app-menus-now-use-an-outline-agent-native-mark-with-b.md +6 -0
  146. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-28-layer-selection-now-updates-chat-context-without-opening-the.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-28-layer-selections-now-preserve-hidden-and-locked-canvas-state.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-handles-zooming-drawing-opening-and-dele.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-selects-screens-on-click-and-opens-them-.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-28-screen-overview-zooms-into-the-active-screen-with-smoother-e.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-28-selected-element-chips-in-chat-are-shorter-and-easier-to-sca.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-28-the-design-editor-toolbar-is-more-compact-and-the-app-menu-m.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-at-the-top-of-the-design-sidebar-instead-of-.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-in-the-right-inspector-beside-the-design-tab.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-active-sidebar-rows-use-soft-highlights.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-29-agents-can-now-open-screen-overview-focus-screens-and-add.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-edit-broad-copy-changes-more-reliably.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-29-design-editor-extensions-now-run-inline-beside-the-artboard.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-29-sidebars-use-darker-chrome-and-chat-warns-immediately.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-29-visual-edit-can-open-localhost-routes-as-url-backed-screens.md +6 -0
  165. package/corpus/templates/design/package.json +1 -0
  166. package/corpus/templates/design/server/db/schema.ts +26 -0
  167. package/corpus/templates/design/server/lib/design-snapshot.ts +1 -1
  168. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -0
  169. package/corpus/templates/design/shared/api.ts +27 -0
  170. package/corpus/templates/design/shared/canvas-frames.ts +103 -0
  171. package/corpus/templates/design/shared/canvas-math.ts +1058 -0
  172. package/corpus/templates/design/shared/code-layer.ts +2212 -0
  173. package/corpus/templates/design/shared/color-utils.ts +180 -0
  174. package/corpus/templates/design/shared/generation-session.ts +61 -0
  175. package/corpus/templates/design/shared/shader-presets.ts +782 -0
  176. package/corpus/templates/design/shared/shader-safety.ts +137 -0
  177. package/corpus/templates/design/shared/source-mode.ts +237 -0
  178. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +149 -6
  179. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  180. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  181. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  182. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  183. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  184. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  185. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  186. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  187. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  188. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  189. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  190. package/corpus/templates/forms/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  191. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
  192. package/corpus/templates/macros/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  193. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +408 -247
  194. package/corpus/templates/mail/app/i18n/ar-SA.ts +4 -0
  195. package/corpus/templates/mail/app/i18n/de-DE.ts +4 -0
  196. package/corpus/templates/mail/app/i18n/en-US.ts +4 -0
  197. package/corpus/templates/mail/app/i18n/es-ES.ts +4 -0
  198. package/corpus/templates/mail/app/i18n/fr-FR.ts +4 -0
  199. package/corpus/templates/mail/app/i18n/hi-IN.ts +4 -0
  200. package/corpus/templates/mail/app/i18n/ja-JP.ts +4 -0
  201. package/corpus/templates/mail/app/i18n/ko-KR.ts +4 -0
  202. package/corpus/templates/mail/app/i18n/pt-BR.ts +4 -0
  203. package/corpus/templates/mail/app/i18n/zh-CN.ts +4 -0
  204. package/corpus/templates/mail/app/i18n/zh-TW.ts +4 -0
  205. package/corpus/templates/mail/changelog/2026-06-28-pinned-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  206. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +6 -8
  207. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +1 -1
  208. package/corpus/templates/plan/app/global.css +61 -78
  209. package/corpus/templates/plan/app/pages/PlansPage.tsx +8 -0
  210. package/corpus/templates/plan/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  211. package/corpus/templates/plan/changelog/2026-06-28-plans-use-the-shared-neutral-light-and-dark-theme.md +6 -0
  212. package/corpus/templates/plan/changelog/2026-06-28-sending-open-feedback-from-a-plan-now-opens-the-inline-plan-.md +6 -0
  213. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +5 -5
  214. package/corpus/templates/slides/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  215. package/corpus/templates/videos/app/components/Sidebar.tsx +2 -2
  216. package/corpus/templates/videos/app/components/layout/Layout.tsx +29 -2
  217. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +113 -30
  218. package/corpus/templates/videos/app/i18n/ar-SA.ts +2 -0
  219. package/corpus/templates/videos/app/i18n/de-DE.ts +2 -0
  220. package/corpus/templates/videos/app/i18n/en-US.ts +2 -0
  221. package/corpus/templates/videos/app/i18n/es-ES.ts +2 -0
  222. package/corpus/templates/videos/app/i18n/fr-FR.ts +2 -0
  223. package/corpus/templates/videos/app/i18n/hi-IN.ts +2 -0
  224. package/corpus/templates/videos/app/i18n/ja-JP.ts +2 -0
  225. package/corpus/templates/videos/app/i18n/ko-KR.ts +2 -0
  226. package/corpus/templates/videos/app/i18n/pt-BR.ts +2 -0
  227. package/corpus/templates/videos/app/i18n/zh-CN.ts +2 -0
  228. package/corpus/templates/videos/app/i18n/zh-TW.ts +2 -0
  229. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  230. package/dist/agent/engine/index.d.ts +1 -1
  231. package/dist/agent/engine/index.d.ts.map +1 -1
  232. package/dist/agent/engine/index.js +1 -1
  233. package/dist/agent/engine/index.js.map +1 -1
  234. package/dist/agent/engine/registry.d.ts +9 -0
  235. package/dist/agent/engine/registry.d.ts.map +1 -1
  236. package/dist/agent/engine/registry.js +37 -0
  237. package/dist/agent/engine/registry.js.map +1 -1
  238. package/dist/agent/production-agent.d.ts.map +1 -1
  239. package/dist/agent/production-agent.js +4 -3
  240. package/dist/agent/production-agent.js.map +1 -1
  241. package/dist/cli/design-connect.d.ts +52 -0
  242. package/dist/cli/design-connect.d.ts.map +1 -0
  243. package/dist/cli/design-connect.js +409 -0
  244. package/dist/cli/design-connect.js.map +1 -0
  245. package/dist/cli/index.js +15 -0
  246. package/dist/cli/index.js.map +1 -1
  247. package/dist/cli/skills.d.ts +3 -0
  248. package/dist/cli/skills.d.ts.map +1 -1
  249. package/dist/cli/skills.js +121 -3
  250. package/dist/cli/skills.js.map +1 -1
  251. package/dist/client/AssistantChat.d.ts.map +1 -1
  252. package/dist/client/AssistantChat.js +38 -8
  253. package/dist/client/AssistantChat.js.map +1 -1
  254. package/dist/client/agent-chat.d.ts +6 -0
  255. package/dist/client/agent-chat.d.ts.map +1 -1
  256. package/dist/client/agent-chat.js +17 -3
  257. package/dist/client/agent-chat.js.map +1 -1
  258. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  259. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  260. package/dist/client/blocks/library/diagram.js +1 -1
  261. package/dist/client/blocks/library/diagram.js.map +1 -1
  262. package/dist/client/blocks/library/wireframe.js +1 -1
  263. package/dist/client/blocks/library/wireframe.js.map +1 -1
  264. package/dist/client/chat/message-components.js +1 -1
  265. package/dist/client/chat/message-components.js.map +1 -1
  266. package/dist/client/chat-model-groups.d.ts.map +1 -1
  267. package/dist/client/chat-model-groups.js +1 -2
  268. package/dist/client/chat-model-groups.js.map +1 -1
  269. package/dist/client/components/LiveCursorOverlay.d.ts +1 -2
  270. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  271. package/dist/client/components/LiveCursorOverlay.js +19 -40
  272. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  273. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  274. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  275. package/dist/client/composer/TiptapComposer.js +86 -24
  276. package/dist/client/composer/TiptapComposer.js.map +1 -1
  277. package/dist/client/composer/useVoiceDictation.d.ts +9 -0
  278. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
  279. package/dist/client/composer/useVoiceDictation.js +154 -32
  280. package/dist/client/composer/useVoiceDictation.js.map +1 -1
  281. package/dist/client/frame-protocol.d.ts +17 -2
  282. package/dist/client/frame-protocol.d.ts.map +1 -1
  283. package/dist/client/frame-protocol.js.map +1 -1
  284. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +2 -2
  285. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  286. package/dist/client/sharing/ShareButton.d.ts +2 -0
  287. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  288. package/dist/client/sharing/ShareButton.js +7 -6
  289. package/dist/client/sharing/ShareButton.js.map +1 -1
  290. package/dist/client/use-agent-engine-configured.d.ts +5 -0
  291. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  292. package/dist/client/use-agent-engine-configured.js +51 -25
  293. package/dist/client/use-agent-engine-configured.js.map +1 -1
  294. package/dist/collab/awareness.d.ts +2 -2
  295. package/dist/collab/awareness.d.ts.map +1 -1
  296. package/dist/collab/routes.d.ts +1 -1
  297. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  298. package/dist/notifications/routes.d.ts +3 -3
  299. package/dist/observability/routes.d.ts +8 -8
  300. package/dist/progress/routes.d.ts +1 -1
  301. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  302. package/dist/server/transcribe-voice.d.ts.map +1 -1
  303. package/dist/server/transcribe-voice.js +40 -24
  304. package/dist/server/transcribe-voice.js.map +1 -1
  305. package/dist/shared/mcp-embed-headers.d.ts +1 -0
  306. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  307. package/dist/shared/mcp-embed-headers.js +20 -1
  308. package/dist/shared/mcp-embed-headers.js.map +1 -1
  309. package/dist/styles/agent-native.css +56 -7
  310. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  311. package/dist/vite/client.d.ts.map +1 -1
  312. package/dist/vite/client.js +2 -0
  313. package/dist/vite/client.js.map +1 -1
  314. package/dist/voice/index.d.ts +5 -0
  315. package/dist/voice/index.d.ts.map +1 -0
  316. package/dist/voice/index.js +4 -0
  317. package/dist/voice/index.js.map +1 -0
  318. package/dist/voice/voice-cleanup-prompt.d.ts +6 -0
  319. package/dist/voice/voice-cleanup-prompt.d.ts.map +1 -0
  320. package/dist/voice/voice-cleanup-prompt.js +26 -0
  321. package/dist/voice/voice-cleanup-prompt.js.map +1 -0
  322. package/dist/voice/voice-context.d.ts +25 -0
  323. package/dist/voice/voice-context.d.ts.map +1 -0
  324. package/dist/voice/voice-context.js +156 -0
  325. package/dist/voice/voice-context.js.map +1 -0
  326. package/dist/voice/voice-replacements.d.ts +4 -0
  327. package/dist/voice/voice-replacements.d.ts.map +1 -0
  328. package/dist/voice/voice-replacements.js +52 -0
  329. package/dist/voice/voice-replacements.js.map +1 -0
  330. package/docs/content/locales/ar-SA/template-design.mdx +41 -0
  331. package/docs/content/locales/de-DE/template-design.mdx +51 -0
  332. package/docs/content/locales/es-ES/template-design.mdx +50 -0
  333. package/docs/content/locales/fr-FR/template-design.mdx +49 -0
  334. package/docs/content/locales/hi-IN/template-design.mdx +44 -0
  335. package/docs/content/locales/ja-JP/template-design.mdx +46 -0
  336. package/docs/content/locales/ko-KR/template-design.mdx +44 -0
  337. package/docs/content/locales/pt-BR/template-design.mdx +48 -0
  338. package/docs/content/locales/zh-CN/template-design.mdx +36 -0
  339. package/docs/content/locales/zh-TW/template-design.mdx +38 -0
  340. package/docs/content/template-design.mdx +45 -0
  341. package/package.json +2 -1
  342. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  343. package/corpus/templates/design/app/components/design/DesignToolbar.tsx +0 -339
  344. package/corpus/templates/design/app/components/design/PresentMode.tsx +0 -68
  345. package/corpus/templates/design/app/components/design/ViewportTabs.tsx +0 -149
  346. package/corpus/templates/design/app/components/design/ZoomControls.tsx +0 -157
@@ -1,8 +1,29 @@
1
1
  import {
2
- GuidedQuestionFlow,
2
+ getOtherGuidedAnswerText,
3
+ hasGuidedAnswer,
4
+ isOtherGuidedAnswer,
5
+ makeOtherGuidedAnswer,
6
+ normalizeGuidedAnswers,
7
+ useT,
3
8
  type GuidedQuestion,
9
+ type GuidedQuestionOption,
4
10
  } from "@agent-native/core/client";
5
11
  import type { QuestionFlowQuestion } from "@shared/api";
12
+ import {
13
+ IconCheck,
14
+ IconChevronRight,
15
+ IconPalette,
16
+ IconSparkles,
17
+ IconUpload,
18
+ IconX,
19
+ } from "@tabler/icons-react";
20
+ import { useCallback, useEffect, useMemo, useState } from "react";
21
+
22
+ import { Button } from "@/components/ui/button";
23
+ import { Progress } from "@/components/ui/progress";
24
+ import { Slider } from "@/components/ui/slider";
25
+ import { Textarea } from "@/components/ui/textarea";
26
+ import { cn } from "@/lib/utils";
6
27
 
7
28
  interface QuestionFlowProps {
8
29
  questions: QuestionFlowQuestion[];
@@ -23,18 +44,684 @@ export function QuestionFlow({
23
44
  skipLabel,
24
45
  submitLabel,
25
46
  }: QuestionFlowProps) {
47
+ const t = useT();
48
+ const guidedQuestions = questions as GuidedQuestion[];
49
+ const [answers, setAnswers] = useState<Record<string, unknown>>({});
50
+ const questionsFingerprint = useMemo(
51
+ () => questionFlowFingerprint(guidedQuestions),
52
+ [guidedQuestions],
53
+ );
54
+
55
+ useEffect(() => {
56
+ setAnswers({});
57
+ }, [questionsFingerprint]);
58
+
59
+ const setAnswer = useCallback((id: string, value: unknown) => {
60
+ setAnswers((prev) => ({ ...prev, [id]: value }));
61
+ }, []);
62
+
63
+ const answeredCount = guidedQuestions.filter((question) =>
64
+ hasGuidedAnswer(answers[question.id]),
65
+ ).length;
66
+ const requiredQuestions = guidedQuestions.filter(
67
+ (question) => question.required,
68
+ );
69
+ const requiredAnswered = requiredQuestions.filter((question) =>
70
+ hasGuidedAnswer(answers[question.id]),
71
+ ).length;
72
+ const allRequiredAnswered = requiredAnswered === requiredQuestions.length;
73
+ const progress =
74
+ guidedQuestions.length === 0
75
+ ? 0
76
+ : Math.round((answeredCount / guidedQuestions.length) * 100);
77
+
78
+ return (
79
+ <div className="flex h-full w-full items-center justify-center bg-background px-4 py-5 text-foreground sm:px-8 sm:py-8">
80
+ <div className="grid h-full max-h-[820px] w-full max-w-6xl overflow-hidden rounded-lg border border-border bg-card shadow-2xl lg:grid-cols-[280px_minmax(0,1fr)]">
81
+ <aside className="flex min-h-0 flex-col border-b border-border bg-muted/25 p-4 lg:border-b-0 lg:border-e lg:p-5">
82
+ <div className="flex items-start gap-3">
83
+ <div className="flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background text-primary shadow-sm">
84
+ <IconSparkles className="size-5" />
85
+ </div>
86
+ <div className="min-w-0">
87
+ <h2 className="text-lg font-semibold tracking-normal text-foreground">
88
+ {title ?? t("questionFlow.defaultTitle")}
89
+ </h2>
90
+ <p className="mt-1 text-sm leading-5 text-muted-foreground">
91
+ {description ?? t("questionFlow.defaultDescription")}
92
+ </p>
93
+ </div>
94
+ </div>
95
+
96
+ <div className="mt-5">
97
+ <div className="mb-2 flex items-center justify-between text-xs text-muted-foreground">
98
+ <span>
99
+ {t("questionFlow.answeredCount", {
100
+ answered: answeredCount,
101
+ total: guidedQuestions.length,
102
+ })}
103
+ </span>
104
+ {requiredQuestions.length > 0 && (
105
+ <span>
106
+ {t("questionFlow.requiredCount", {
107
+ answered: requiredAnswered,
108
+ total: requiredQuestions.length,
109
+ })}
110
+ </span>
111
+ )}
112
+ </div>
113
+ <Progress value={progress} className="h-1.5 bg-muted" />
114
+ </div>
115
+
116
+ <ol className="mt-5 hidden min-h-0 flex-1 overflow-y-auto pe-1 lg:block">
117
+ {guidedQuestions.map((question, index) => {
118
+ const answered = hasGuidedAnswer(answers[question.id]);
119
+ return (
120
+ <li
121
+ key={question.id}
122
+ className={cn(
123
+ "mb-2 flex items-start gap-2 rounded-md border px-2.5 py-2 text-xs",
124
+ answered
125
+ ? "border-primary/25 bg-primary/5 text-foreground"
126
+ : "border-transparent bg-background/45 text-muted-foreground",
127
+ )}
128
+ >
129
+ <span
130
+ className={cn(
131
+ "mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full border text-[10px]",
132
+ answered
133
+ ? "border-primary bg-primary text-primary-foreground"
134
+ : "border-border bg-muted text-muted-foreground",
135
+ )}
136
+ >
137
+ {answered ? <IconCheck className="size-3" /> : index + 1}
138
+ </span>
139
+ <span className="line-clamp-2">
140
+ {question.header ?? question.question}
141
+ </span>
142
+ </li>
143
+ );
144
+ })}
145
+ </ol>
146
+
147
+ <div className="mt-4 hidden rounded-md border border-border bg-background/60 p-3 text-xs leading-5 text-muted-foreground lg:block">
148
+ {t("questionFlow.multiSelectHelp")}
149
+ </div>
150
+ </aside>
151
+
152
+ <main className="flex min-h-0 flex-col">
153
+ <div className="min-h-0 flex-1 overflow-y-auto px-4 py-4 sm:px-6 sm:py-6">
154
+ <div className="grid gap-3 xl:grid-cols-2">
155
+ {guidedQuestions.map((question, index) => (
156
+ <QuestionCard
157
+ key={question.id}
158
+ index={index}
159
+ question={question}
160
+ value={answers[question.id]}
161
+ onChange={(value) => setAnswer(question.id, value)}
162
+ />
163
+ ))}
164
+ </div>
165
+ </div>
166
+
167
+ <div className="flex shrink-0 flex-col gap-3 border-t border-border bg-background/80 px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:px-6">
168
+ <div className="text-xs text-muted-foreground">
169
+ {allRequiredAnswered
170
+ ? t("questionFlow.ready")
171
+ : t("questionFlow.answerRequired")}
172
+ </div>
173
+ <div className="flex items-center justify-end gap-2">
174
+ <Button
175
+ type="button"
176
+ variant="ghost"
177
+ size="sm"
178
+ onClick={onSkip}
179
+ className="cursor-pointer"
180
+ >
181
+ {skipLabel ?? t("questionFlow.skip")}
182
+ </Button>
183
+ <Button
184
+ type="button"
185
+ size="sm"
186
+ onClick={() => onSubmit(normalizeGuidedAnswers(answers))}
187
+ disabled={!allRequiredAnswered}
188
+ className="cursor-pointer"
189
+ >
190
+ {submitLabel ?? t("questionFlow.continue")}
191
+ <IconChevronRight className="size-4 rtl:-scale-x-100" />
192
+ </Button>
193
+ </div>
194
+ </div>
195
+ </main>
196
+ </div>
197
+ </div>
198
+ );
199
+ }
200
+
201
+ function QuestionCard({
202
+ index,
203
+ question,
204
+ value,
205
+ onChange,
206
+ }: {
207
+ index: number;
208
+ question: GuidedQuestion;
209
+ value: unknown;
210
+ onChange: (value: unknown) => void;
211
+ }) {
212
+ const t = useT();
213
+ const answered = hasGuidedAnswer(value);
214
+ const guidance = question.multiSelect
215
+ ? t("questionFlow.chooseAny")
216
+ : t("questionFlow.chooseOne");
217
+
218
+ return (
219
+ <section
220
+ className={cn(
221
+ "min-w-0 rounded-lg border bg-background p-4 shadow-sm transition-colors",
222
+ answered ? "border-primary/35" : "border-border",
223
+ )}
224
+ >
225
+ <div className="mb-3 flex items-start gap-3">
226
+ <div
227
+ className={cn(
228
+ "flex size-7 shrink-0 items-center justify-center rounded-md border text-xs font-medium",
229
+ answered
230
+ ? "border-primary bg-primary text-primary-foreground"
231
+ : "border-border bg-muted text-muted-foreground",
232
+ )}
233
+ >
234
+ {answered ? <IconCheck className="size-4" /> : index + 1}
235
+ </div>
236
+ <div className="min-w-0 flex-1">
237
+ <div className="mb-1 flex flex-wrap items-center gap-1.5">
238
+ {question.header && (
239
+ <p className="text-[11px] font-medium uppercase text-muted-foreground">
240
+ {question.header}
241
+ </p>
242
+ )}
243
+ <span className="rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
244
+ {guidance}
245
+ </span>
246
+ {question.required && (
247
+ <span className="rounded-full border border-destructive/25 bg-destructive/10 px-2 py-0.5 text-[10px] font-medium text-destructive">
248
+ {t("questionFlow.required")}
249
+ </span>
250
+ )}
251
+ </div>
252
+ <h3 className="text-sm font-semibold leading-5 text-foreground">
253
+ {question.question}
254
+ </h3>
255
+ {question.description && (
256
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
257
+ {question.description}
258
+ </p>
259
+ )}
260
+ </div>
261
+ </div>
262
+
263
+ {question.type === "text-options" && (
264
+ <TextOptions question={question} value={value} onChange={onChange} />
265
+ )}
266
+ {question.type === "color-options" && (
267
+ <ColorOptions question={question} value={value} onChange={onChange} />
268
+ )}
269
+ {question.type === "slider" && (
270
+ <SliderQuestion question={question} value={value} onChange={onChange} />
271
+ )}
272
+ {question.type === "file" && (
273
+ <FileDropZone value={value} onChange={onChange} />
274
+ )}
275
+ {question.type === "freeform" && (
276
+ <Textarea
277
+ value={typeof value === "string" ? value : ""}
278
+ onChange={(event) => onChange(event.target.value)}
279
+ placeholder={
280
+ question.placeholder ?? t("questionFlow.textPlaceholder")
281
+ }
282
+ className="min-h-[92px] resize-none bg-muted/35 text-sm"
283
+ />
284
+ )}
285
+ </section>
286
+ );
287
+ }
288
+
289
+ function TextOptions({
290
+ question,
291
+ value,
292
+ onChange,
293
+ }: {
294
+ question: GuidedQuestion;
295
+ value: unknown;
296
+ onChange: (value: unknown) => void;
297
+ }) {
298
+ const t = useT();
299
+ const options = useMemo(() => withDefaultOptions(question, t), [question, t]);
300
+ const multiSelect = question.multiSelect === true;
301
+ const selectedValues = Array.isArray(value) ? value : [];
302
+ const otherSelected = multiSelect
303
+ ? selectedValues.some(isOtherGuidedAnswer)
304
+ : isOtherGuidedAnswer(value);
305
+ const otherText = multiSelect
306
+ ? getOtherGuidedAnswerText(selectedValues.find(isOtherGuidedAnswer))
307
+ : getOtherGuidedAnswerText(value);
308
+ const allowOther = question.allowOther !== false;
309
+ const selectedCount = multiSelect
310
+ ? selectedValues.filter((item) => hasGuidedAnswer(item)).length
311
+ : hasGuidedAnswer(value)
312
+ ? 1
313
+ : 0;
314
+ const compact = options.every(
315
+ (option) =>
316
+ // i18n-ignore scanner false positive
317
+ !option.description && !option.preview && option.label.length <= 24, // i18n-ignore scanner false positive
318
+ );
319
+
320
+ const isSelected = (optionValue: string) =>
321
+ multiSelect ? selectedValues.includes(optionValue) : value === optionValue;
322
+
323
+ const toggleOption = (optionValue: string) => {
324
+ if (!multiSelect) {
325
+ onChange(optionValue);
326
+ return;
327
+ }
328
+ const next = selectedValues.includes(optionValue)
329
+ ? selectedValues.filter((item) => item !== optionValue)
330
+ : [...selectedValues, optionValue];
331
+ onChange(next);
332
+ };
333
+
334
+ const toggleOther = () => {
335
+ if (!multiSelect) {
336
+ onChange(otherSelected ? "" : makeOtherGuidedAnswer());
337
+ return;
338
+ }
339
+ if (otherSelected) {
340
+ onChange(selectedValues.filter((item) => !isOtherGuidedAnswer(item)));
341
+ return;
342
+ }
343
+ onChange([...selectedValues, makeOtherGuidedAnswer()]);
344
+ };
345
+
346
+ const setOtherText = (text: string) => {
347
+ const nextOther = makeOtherGuidedAnswer(text);
348
+ if (!multiSelect) {
349
+ onChange(nextOther);
350
+ return;
351
+ }
352
+ onChange([
353
+ ...selectedValues.filter((item) => !isOtherGuidedAnswer(item)),
354
+ nextOther,
355
+ ]);
356
+ };
357
+
358
+ return (
359
+ <div className="space-y-3">
360
+ {multiSelect && (
361
+ <p className="text-[11px] leading-4 text-muted-foreground">
362
+ {selectedCount > 0
363
+ ? t("questionFlow.selectedCount", { count: selectedCount })
364
+ : t("questionFlow.selectUseful")}
365
+ </p>
366
+ )}
367
+ <div
368
+ className={cn(
369
+ "grid gap-2",
370
+ compact ? "grid-cols-2 sm:grid-cols-3" : "grid-cols-1 sm:grid-cols-2",
371
+ )}
372
+ >
373
+ {options.map((option) => (
374
+ <OptionButton
375
+ key={`${option.value}:${option.label}`}
376
+ option={option}
377
+ selected={isSelected(option.value)}
378
+ compact={compact}
379
+ multiSelect={multiSelect}
380
+ onClick={() => toggleOption(option.value)}
381
+ />
382
+ ))}
383
+ {allowOther && (
384
+ <OptionButton
385
+ option={{
386
+ label: t("questionFlow.other"),
387
+ value: "__other__",
388
+ description: compact
389
+ ? undefined
390
+ : t("questionFlow.otherDescription"),
391
+ }}
392
+ selected={otherSelected}
393
+ compact={compact}
394
+ multiSelect={multiSelect}
395
+ onClick={toggleOther}
396
+ />
397
+ )}
398
+ </div>
399
+ {allowOther && otherSelected && (
400
+ <Textarea
401
+ autoFocus
402
+ value={otherText}
403
+ onChange={(event) => setOtherText(event.target.value)}
404
+ placeholder={
405
+ question.placeholder ?? t("questionFlow.customPlaceholder")
406
+ }
407
+ className="min-h-[72px] resize-none bg-muted/35 text-sm"
408
+ />
409
+ )}
410
+ </div>
411
+ );
412
+ }
413
+
414
+ function OptionButton({
415
+ option,
416
+ selected,
417
+ compact,
418
+ multiSelect,
419
+ onClick,
420
+ }: {
421
+ option: GuidedQuestionOption;
422
+ selected: boolean;
423
+ compact: boolean;
424
+ multiSelect?: boolean;
425
+ onClick: () => void;
426
+ }) {
427
+ const t = useT();
428
+ return (
429
+ <button
430
+ type="button"
431
+ onClick={onClick}
432
+ aria-pressed={selected}
433
+ className={cn(
434
+ "group flex min-w-0 cursor-pointer items-start gap-2 rounded-md border text-start transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
435
+ compact ? "min-h-10 px-2.5 py-2" : "min-h-[68px] px-3 py-2.5",
436
+ selected
437
+ ? "border-primary bg-primary/10 text-foreground ring-1 ring-primary/25"
438
+ : "border-border bg-muted/25 text-muted-foreground hover:border-muted-foreground/45 hover:bg-muted/45 hover:text-foreground",
439
+ )}
440
+ >
441
+ <span
442
+ className={cn(
443
+ "mt-0.5 flex size-4 shrink-0 items-center justify-center border",
444
+ multiSelect ? "rounded-sm" : "rounded-full",
445
+ selected
446
+ ? "border-primary bg-primary text-primary-foreground"
447
+ : "border-border bg-background",
448
+ )}
449
+ aria-hidden
450
+ >
451
+ {selected && <IconCheck className="size-3" />}
452
+ </span>
453
+ <span className="min-w-0 flex-1">
454
+ <span className="flex min-w-0 flex-wrap items-center gap-1.5 text-sm font-medium leading-5">
455
+ <span className="truncate">{option.label}</span>
456
+ {option.recommended && (
457
+ <span className="rounded-full bg-primary/15 px-1.5 py-0.5 text-[10px] font-medium uppercase text-primary">
458
+ {t("questionFlow.recommended")}
459
+ </span>
460
+ )}
461
+ </span>
462
+ {option.description && (
463
+ <span className="mt-0.5 block text-xs leading-4 text-muted-foreground">
464
+ {option.description}
465
+ </span>
466
+ )}
467
+ {option.preview && (
468
+ <span className="mt-2 block max-h-36 overflow-auto whitespace-pre-wrap rounded-md border border-border/60 bg-background/70 px-2 py-1.5 font-mono text-[11px] leading-4 text-muted-foreground">
469
+ {option.preview}
470
+ </span>
471
+ )}
472
+ </span>
473
+ </button>
474
+ );
475
+ }
476
+
477
+ function ColorOptions({
478
+ question,
479
+ value,
480
+ onChange,
481
+ }: {
482
+ question: GuidedQuestion;
483
+ value: unknown;
484
+ onChange: (value: unknown) => void;
485
+ }) {
486
+ const options = question.options ?? question.choices ?? [];
487
+ const multiSelect = question.multiSelect === true;
488
+ const selectedValues = Array.isArray(value) ? value : [];
489
+ const isSelected = (optionValue: string) =>
490
+ multiSelect ? selectedValues.includes(optionValue) : value === optionValue;
491
+
492
+ const toggleOption = (optionValue: string) => {
493
+ if (!multiSelect) {
494
+ onChange(optionValue);
495
+ return;
496
+ }
497
+ onChange(
498
+ selectedValues.includes(optionValue)
499
+ ? selectedValues.filter((item) => item !== optionValue)
500
+ : [...selectedValues, optionValue],
501
+ );
502
+ };
503
+
504
+ return (
505
+ <div className="grid grid-cols-3 gap-2 sm:grid-cols-4">
506
+ {options.map((option) => {
507
+ const selected = isSelected(option.value);
508
+ return (
509
+ <button
510
+ type="button"
511
+ key={`${option.value}:${option.label}`}
512
+ onClick={() => toggleOption(option.value)}
513
+ aria-pressed={selected}
514
+ className={cn(
515
+ "group flex min-w-0 cursor-pointer items-center gap-2 rounded-md border px-2.5 py-2 text-start transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
516
+ selected
517
+ ? "border-primary bg-primary/10"
518
+ : "border-border bg-muted/25 hover:border-muted-foreground/45 hover:bg-muted/45",
519
+ )}
520
+ >
521
+ <span
522
+ className={cn(
523
+ "size-7 shrink-0 rounded-full border border-border",
524
+ selected &&
525
+ "ring-2 ring-primary ring-offset-2 ring-offset-background",
526
+ )}
527
+ style={{ backgroundColor: option.color || option.value }}
528
+ />
529
+ <span className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
530
+ {option.label}
531
+ </span>
532
+ {selected && (
533
+ <IconPalette className="size-3.5 shrink-0 text-primary" />
534
+ )}
535
+ </button>
536
+ );
537
+ })}
538
+ </div>
539
+ );
540
+ }
541
+
542
+ function SliderQuestion({
543
+ question,
544
+ value,
545
+ onChange,
546
+ }: {
547
+ question: GuidedQuestion;
548
+ value: unknown;
549
+ onChange: (value: unknown) => void;
550
+ }) {
551
+ const min = question.min ?? 0;
552
+ const max = question.max ?? 100;
553
+ const step = question.step ?? 1;
554
+ const current =
555
+ typeof value === "number" ? value : Math.round((min + max) / 2);
556
+
557
+ return (
558
+ <div className="rounded-md border border-border bg-muted/25 px-3 py-3">
559
+ <div className="mb-3 flex items-center justify-between text-xs text-muted-foreground">
560
+ <span>{min}</span>
561
+ <span className="rounded-full bg-background px-2 py-0.5 font-medium tabular-nums text-foreground">
562
+ {current}
563
+ </span>
564
+ <span>{max}</span>
565
+ </div>
566
+ <Slider
567
+ min={min}
568
+ max={max}
569
+ step={step}
570
+ value={[current]}
571
+ onValueChange={(next) => onChange(next[0] ?? current)}
572
+ />
573
+ </div>
574
+ );
575
+ }
576
+
577
+ function FileDropZone({
578
+ value,
579
+ onChange,
580
+ }: {
581
+ value: unknown;
582
+ onChange: (value: unknown) => void;
583
+ }) {
584
+ const t = useT();
585
+ const [dragOver, setDragOver] = useState(false);
586
+ const files: File[] = Array.isArray(value) ? (value as File[]) : [];
587
+
588
+ const addFiles = (incoming: File[]) => onChange([...files, ...incoming]);
589
+ const removeFile = (index: number) =>
590
+ onChange(files.filter((_, fileIndex) => fileIndex !== index));
591
+
26
592
  return (
27
- <GuidedQuestionFlow
28
- questions={questions as GuidedQuestion[]}
29
- onSubmit={onSubmit}
30
- onSkip={onSkip}
31
- title={title ?? "Shape the design first"}
32
- description={
33
- description ??
34
- "Choose the direction that matters. Use Other for anything specific, or let the agent decide."
35
- }
36
- skipLabel={skipLabel}
37
- submitLabel={submitLabel}
38
- />
593
+ <div>
594
+ <div
595
+ onDragOver={(event) => {
596
+ event.preventDefault();
597
+ setDragOver(true);
598
+ }}
599
+ onDragLeave={() => setDragOver(false)}
600
+ onDrop={(event) => {
601
+ event.preventDefault();
602
+ setDragOver(false);
603
+ addFiles(Array.from(event.dataTransfer.files));
604
+ }}
605
+ className={cn(
606
+ "flex cursor-pointer flex-col items-center justify-center rounded-md border-2 border-dashed p-5 transition-colors",
607
+ dragOver
608
+ ? "border-primary bg-primary/5"
609
+ : "border-border bg-muted/25 hover:border-muted-foreground/50",
610
+ )}
611
+ >
612
+ <IconUpload className="mb-2 size-5 text-muted-foreground" />
613
+ <p className="text-sm text-muted-foreground">
614
+ {t("questionFlow.dragFiles")}{" "}
615
+ <label className="cursor-pointer text-primary hover:underline">
616
+ {t("questionFlow.browse")}
617
+ <input
618
+ type="file"
619
+ multiple
620
+ onChange={(event) => {
621
+ if (event.target.files)
622
+ addFiles(Array.from(event.target.files));
623
+ event.currentTarget.value = "";
624
+ }}
625
+ className="hidden"
626
+ />
627
+ </label>
628
+ </p>
629
+ </div>
630
+
631
+ {files.length > 0 && (
632
+ <div className="mt-2 space-y-1">
633
+ {files.map((file, index) => (
634
+ <div
635
+ key={`${file.name}:${index}`}
636
+ className="flex items-center gap-2 rounded-md bg-muted/50 px-2 py-1 text-xs text-muted-foreground"
637
+ >
638
+ <IconCheck className="size-3 text-primary" />
639
+ <span className="min-w-0 flex-1 truncate">{file.name}</span>
640
+ <button
641
+ type="button"
642
+ onClick={() => removeFile(index)}
643
+ className="cursor-pointer text-muted-foreground/70 hover:text-foreground"
644
+ aria-label={t("questionFlow.removeFile", { name: file.name })}
645
+ >
646
+ <IconX className="size-3" />
647
+ </button>
648
+ </div>
649
+ ))}
650
+ </div>
651
+ )}
652
+ </div>
653
+ );
654
+ }
655
+
656
+ function optionKey(option: GuidedQuestionOption): string {
657
+ return `${option.value.toLowerCase()}::${option.label.toLowerCase()}`;
658
+ }
659
+
660
+ function questionFlowFingerprint(questions: GuidedQuestion[]): string {
661
+ return JSON.stringify(
662
+ questions.map((question) => ({
663
+ id: question.id,
664
+ type: question.type,
665
+ header: question.header ?? null,
666
+ question: question.question,
667
+ description: question.description ?? null,
668
+ multiSelect: question.multiSelect ?? false,
669
+ required: question.required ?? false,
670
+ allowOther: question.allowOther ?? null,
671
+ includeExplore: question.includeExplore ?? null,
672
+ includeDecide: question.includeDecide ?? null,
673
+ min: question.min ?? null,
674
+ max: question.max ?? null,
675
+ step: question.step ?? null,
676
+ placeholder: question.placeholder ?? null,
677
+ options: (question.options ?? question.choices ?? []).map((option) => ({
678
+ label: option.label,
679
+ value: option.value,
680
+ color: option.color ?? null,
681
+ description: option.description ?? null,
682
+ recommended: option.recommended ?? false,
683
+ })),
684
+ })),
685
+ );
686
+ }
687
+
688
+ function withDefaultOptions(
689
+ question: GuidedQuestion,
690
+ t: (key: string, options?: Record<string, unknown>) => string,
691
+ ): GuidedQuestionOption[] {
692
+ const base = question.options ?? question.choices ?? [];
693
+ const seen = new Set(base.map(optionKey));
694
+ const result = [...base];
695
+ const maybePush = (option: GuidedQuestionOption, enabled: boolean) => {
696
+ if (!enabled) return;
697
+ const key = optionKey(option);
698
+ const label = option.label.toLowerCase();
699
+ const value = option.value.toLowerCase();
700
+ const duplicate = result.some(
701
+ (existing) =>
702
+ optionKey(existing) === key ||
703
+ existing.label.toLowerCase() === label ||
704
+ existing.value.toLowerCase() === value,
705
+ );
706
+ if (duplicate || seen.has(key)) return;
707
+ seen.add(key);
708
+ result.push(option);
709
+ };
710
+ maybePush(
711
+ {
712
+ label: t("questionFlow.exploreLabel"),
713
+ value: "__explore__",
714
+ description: t("questionFlow.exploreDescription"),
715
+ },
716
+ question.includeExplore !== false,
717
+ );
718
+ maybePush(
719
+ {
720
+ label: t("questionFlow.decideLabel"),
721
+ value: "__decide__",
722
+ description: t("questionFlow.decideDescription"),
723
+ },
724
+ question.includeDecide !== false,
39
725
  );
726
+ return result;
40
727
  }