@agent-native/core 0.90.1 → 0.90.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -0,0 +1,763 @@
1
+ import { useActionMutation, useActionQuery } from "@agent-native/core/client";
2
+ import { propNameToDataAttribute } from "@shared/component-model";
3
+ import {
4
+ IconArrowRight,
5
+ IconCode,
6
+ IconExternalLink,
7
+ IconLoader2,
8
+ } from "@tabler/icons-react";
9
+ import { useQueryClient } from "@tanstack/react-query";
10
+ import { useCallback, useEffect, useRef } from "react";
11
+ import { toast } from "sonner";
12
+
13
+ import { Button } from "@/components/ui/button";
14
+ import { Input } from "@/components/ui/input";
15
+ import { Label } from "@/components/ui/label";
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from "@/components/ui/select";
23
+ import { Switch } from "@/components/ui/switch";
24
+ import {
25
+ Tooltip,
26
+ TooltipContent,
27
+ TooltipTrigger,
28
+ } from "@/components/ui/tooltip";
29
+
30
+ import {
31
+ canRebuildAlpineDataLosslessly,
32
+ isBooleanPropValue,
33
+ parseAlpineDataObject,
34
+ replaceAlpineDataKeyValue,
35
+ serializeAlpineDataObject,
36
+ } from "./code-inspect-helpers";
37
+
38
+ // ─── Make it real — inline upgrade card (§3, §6.6) ──────────────────────────
39
+
40
+ /**
41
+ * Payload shape returned by `connect-builder-app`. Only the fields used by
42
+ * the card UI are typed here; the action may return additional fields.
43
+ */
44
+ interface ConnectBuilderAppResult {
45
+ connected: boolean;
46
+ builderEnabled: boolean;
47
+ connectUrl: string;
48
+ appHost: string;
49
+ branchProjectId?: string;
50
+ cta: {
51
+ kind: "connect-builder" | "configure-project";
52
+ label: string;
53
+ description: string;
54
+ primaryAction: string;
55
+ connectUrl: string;
56
+ } | null;
57
+ message: string;
58
+ }
59
+
60
+ /**
61
+ * Inline "Make it real" upgrade card.
62
+ *
63
+ * Rendered wherever a real-app-only control is reached on an inline design
64
+ * (Component source jump, token write-back, live captures, etc.). Queries
65
+ * `connect-builder-app` to determine the current connection state, then
66
+ * offers the appropriate CTA:
67
+ *
68
+ * - Not connected → "Connect Builder.io" button (opens connectUrl)
69
+ * - Connected, no project → "Open Builder settings" (configure project ID)
70
+ * - Fully enabled → "Make it real" button (calls migrate-inline-design-to-app)
71
+ *
72
+ * The card is progressively disclosed: it only mounts when a gated control is
73
+ * actually reached, so it never appears for users who are already on a real-app
74
+ * source (`localhost` / `fusion`) or whose `sourceCapabilities` already include
75
+ * the needed capability.
76
+ *
77
+ * Matches the design-editor panel chrome: dashed-border, accent tint, small
78
+ * text at 10px — same idiom as the existing `ctaRequired` block in
79
+ * ComponentSection.
80
+ */
81
+ function MakeItRealCard({
82
+ designId,
83
+ featureLabel,
84
+ }: {
85
+ /** The active design id — required to call connect-builder-app. */
86
+ designId: string;
87
+ /**
88
+ * Short human-readable label for the gated feature (e.g. "token write-back",
89
+ * "component source jump", "live captures"). Shown in the card body so the
90
+ * user understands exactly what they're unlocking.
91
+ */
92
+ featureLabel: string;
93
+ }) {
94
+ const { data, isLoading } = useActionQuery<ConnectBuilderAppResult>(
95
+ "connect-builder-app",
96
+ { designId },
97
+ );
98
+
99
+ const migrateMutation = useActionMutation("migrate-inline-design-to-app");
100
+
101
+ // While fetching status, show a muted placeholder that matches the card
102
+ // height so the inspector doesn't jump when the data arrives.
103
+ if (isLoading || !data) {
104
+ return (
105
+ <div className="flex h-7 items-center rounded-[5px] bg-[var(--design-editor-control-bg)] px-2">
106
+ <div className="h-3 w-28 animate-pulse rounded bg-muted/40" />
107
+ </div>
108
+ );
109
+ }
110
+
111
+ // Determine which CTA to show.
112
+ const cta = data.cta;
113
+
114
+ // Already fully enabled — no CTA needed (caller should already have gated
115
+ // this component away, but guard here for safety).
116
+ if (!cta) return null;
117
+
118
+ const isPending = migrateMutation.isPending;
119
+ const migrateError = migrateMutation.error;
120
+
121
+ // "Make it real" primary action: open the connect URL or migrate.
122
+ const handlePrimary = () => {
123
+ if (cta.kind === "connect-builder") {
124
+ // Open the Builder OAuth connect flow in a new tab. The user completes
125
+ // it there and comes back; the card will re-query on next render.
126
+ window.open(cta.connectUrl, "_blank", "noopener,noreferrer");
127
+ return;
128
+ }
129
+ if (cta.kind === "configure-project") {
130
+ window.open(cta.connectUrl, "_blank", "noopener,noreferrer");
131
+ return;
132
+ }
133
+ };
134
+
135
+ const handleMigrate = () => {
136
+ migrateMutation.mutate({ designId });
137
+ };
138
+
139
+ // Migration result — show branch link.
140
+ const migrateResult = migrateMutation.data as
141
+ | {
142
+ status: "processing";
143
+ branchName?: string;
144
+ url?: string;
145
+ message?: string;
146
+ }
147
+ | undefined;
148
+
149
+ if (migrateResult?.status === "processing" && migrateResult.url) {
150
+ return (
151
+ <div className="flex items-center gap-2 rounded-[5px] border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 py-1.5">
152
+ <IconLoader2 className="size-3.5 shrink-0 animate-spin text-[var(--design-editor-accent-color)]" />
153
+ <p className="min-w-0 flex-1 truncate text-[10px] text-muted-foreground">
154
+ {migrateResult.message ??
155
+ `Generating ${migrateResult.branchName ?? "React app"}.`}
156
+ </p>
157
+ <a
158
+ href={migrateResult.url}
159
+ target="_blank"
160
+ rel="noopener noreferrer"
161
+ className="inline-flex h-6 shrink-0 items-center gap-1 rounded-md px-1.5 text-[10px] font-semibold text-[var(--design-editor-accent-color)] hover:bg-[var(--design-editor-panel-raised-bg)]"
162
+ >
163
+ {"Open" /* i18n-ignore make-it-real card */}
164
+ <IconExternalLink className="size-2.5" />
165
+ </a>
166
+ </div>
167
+ );
168
+ }
169
+
170
+ const summary =
171
+ cta.kind === "configure-project"
172
+ ? `Choose a Builder project to enable ${featureLabel}.`
173
+ : `Connect Builder to enable ${featureLabel}.`;
174
+ const primaryLabel =
175
+ cta.kind === "configure-project"
176
+ ? "Choose" /* i18n-ignore make-it-real card */
177
+ : "Connect"; /* i18n-ignore make-it-real card */
178
+
179
+ return (
180
+ <div className="space-y-1">
181
+ <div className="flex items-center gap-2 rounded-[5px] border border-dashed border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)]/70 px-2 py-1.5">
182
+ <span
183
+ className="size-1.5 shrink-0 rounded-full bg-[var(--design-editor-accent-color)]"
184
+ aria-hidden="true"
185
+ />
186
+ <p
187
+ className="min-w-0 flex-1 truncate text-[10px] text-muted-foreground"
188
+ title={summary}
189
+ >
190
+ {summary}
191
+ </p>
192
+ <Button
193
+ type="button"
194
+ size="sm"
195
+ onClick={handlePrimary}
196
+ title={cta.primaryAction}
197
+ className="h-6 shrink-0 gap-1 rounded-md bg-[var(--design-editor-accent-color)] px-1.5 text-[10px] font-semibold text-white hover:bg-[var(--design-editor-accent-hover-color)]"
198
+ >
199
+ {primaryLabel}
200
+ <IconArrowRight className="size-2.5" />
201
+ </Button>
202
+
203
+ {/* When Builder is fully connected, also offer direct migration */}
204
+ {data.connected && data.builderEnabled && (
205
+ <Button
206
+ type="button"
207
+ variant="ghost"
208
+ size="sm"
209
+ onClick={handleMigrate}
210
+ disabled={isPending}
211
+ className="h-6 shrink-0 gap-1 rounded-md px-1.5 text-[10px] font-semibold text-muted-foreground hover:text-foreground disabled:cursor-wait disabled:opacity-60"
212
+ >
213
+ {isPending ? (
214
+ <>
215
+ <IconLoader2 className="size-2.5 animate-spin" />
216
+ {"Generating" /* i18n-ignore make-it-real card */}
217
+ </>
218
+ ) : (
219
+ <>{"Generate" /* i18n-ignore make-it-real card */}</>
220
+ )}
221
+ </Button>
222
+ )}
223
+ </div>
224
+ {migrateError ? (
225
+ <p className="px-2 text-[10px] text-destructive">
226
+ {migrateError instanceof Error
227
+ ? migrateError.message
228
+ : /* i18n-ignore make-it-real card */ "Migration failed. Please try again."}
229
+ </p>
230
+ ) : null}
231
+ </div>
232
+ );
233
+ }
234
+
235
+ // ─── Component section (§6.1) ─────────────────────────────────────────────────
236
+
237
+ /**
238
+ * Shape returned by `get-component-details`. Only the fields the UI needs are
239
+ * typed here; the action may return additional fields.
240
+ */
241
+ interface ComponentDetailsResult {
242
+ nodeId: string;
243
+ name: string;
244
+ sourceType: string;
245
+ observedProps: Array<{ name: string; value: string }>;
246
+ persistedVariants: Record<string, string[]>;
247
+ sourceLocation?: { filePath: string; exportName?: string } | null;
248
+ /** Component instance shape, including the Alpine `x-data` expression. */
249
+ instance?: {
250
+ alpineData?: string | null;
251
+ nodeId?: string;
252
+ selector?: string;
253
+ } | null;
254
+ capabilities: {
255
+ canResolveToFile: boolean;
256
+ hasFullIndex: boolean;
257
+ canEditProps: boolean;
258
+ ctaRequired: boolean;
259
+ ctaMessage?: string;
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Contextual COMPONENT section rendered inside the Design tab when the
265
+ * selected element is a component instance (carries
266
+ * `data-agent-native-component`).
267
+ *
268
+ * Shows: component name, source path (when capability available), observed
269
+ * prop values, variant/size/state controls from `get-component-details`, and
270
+ * an "Edit component source" action. Real-app features are gated by the
271
+ * capabilities returned by the action; Alpine gets a lightweight read-only
272
+ * view plus a Connect-Builder CTA.
273
+ *
274
+ * Matches the workbench artboard spec in DESIGN-STUDIO-PLAN.md §6.1.
275
+ */
276
+ export function ComponentSection({
277
+ designId,
278
+ fileId,
279
+ activeContent,
280
+ activeFileUpdatedAt,
281
+ nodeId,
282
+ onComponentPropApplied,
283
+ sourceCapabilities = [],
284
+ }: {
285
+ designId: string;
286
+ fileId?: string;
287
+ activeContent?: string;
288
+ activeFileUpdatedAt?: string | null;
289
+ nodeId: string;
290
+ onComponentPropApplied?: (
291
+ fileId: string,
292
+ content: string,
293
+ updatedAt?: string,
294
+ ) => void;
295
+ /** Capability names advertised by the current source. */
296
+ sourceCapabilities?: string[];
297
+ }) {
298
+ const queryClient = useQueryClient();
299
+ const detailsParams = { designId, nodeId, ...(fileId ? { fileId } : {}) };
300
+ const detailsKey = ["action", "get-component-details", detailsParams];
301
+ const latestSourceRef = useRef<{
302
+ content: string;
303
+ revision?: string | null;
304
+ }>({
305
+ content: activeContent ?? "",
306
+ revision: activeFileUpdatedAt ?? null,
307
+ });
308
+
309
+ useEffect(() => {
310
+ latestSourceRef.current = {
311
+ content: activeContent ?? "",
312
+ revision: activeFileUpdatedAt ?? null,
313
+ };
314
+ }, [activeContent, activeFileUpdatedAt, fileId, nodeId]);
315
+
316
+ const { data, isLoading, error, refetch } =
317
+ useActionQuery<ComponentDetailsResult>(
318
+ "get-component-details",
319
+ detailsParams,
320
+ { refetchOnMount: "always" },
321
+ );
322
+
323
+ const openSourceMutation = useActionMutation("open-component-source");
324
+ const applyPropMutation = useActionMutation("apply-component-prop-edit");
325
+
326
+ const postComponentPropPreview = useCallback(
327
+ (attribute: string, value: string) => {
328
+ if (typeof document === "undefined") return;
329
+
330
+ const iframe = document.querySelector<HTMLIFrameElement>(
331
+ "iframe[data-design-preview-iframe]",
332
+ );
333
+ iframe?.contentWindow?.postMessage(
334
+ {
335
+ type: "style-change",
336
+ selector: data?.instance?.selector ?? "",
337
+ nodeId: data?.instance?.nodeId ?? nodeId,
338
+ attributeOverrides: { [attribute]: value },
339
+ },
340
+ "*",
341
+ );
342
+ },
343
+ [data?.instance?.nodeId, data?.instance?.selector, nodeId],
344
+ );
345
+
346
+ // Persist a single prop change through apply-component-prop-edit. Attribute
347
+ // props also preview immediately in the iframe so the selected component
348
+ // changes without waiting for the write/refetch round-trip.
349
+ const persistPropEdit = (
350
+ edit:
351
+ | { kind: "alpineData"; value: string }
352
+ | { kind: "attribute"; attribute: string; value: string },
353
+ optimistic: (prev: ComponentDetailsResult) => ComponentDetailsResult,
354
+ ) => {
355
+ queryClient.setQueryData<ComponentDetailsResult>(detailsKey, (prev) =>
356
+ prev ? optimistic(prev) : prev,
357
+ );
358
+ if (edit.kind === "attribute") {
359
+ postComponentPropPreview(edit.attribute, edit.value);
360
+ }
361
+ const latestSource = latestSourceRef.current;
362
+ applyPropMutation.mutate(
363
+ {
364
+ designId,
365
+ nodeId,
366
+ ...(fileId ? { fileId } : {}),
367
+ edit,
368
+ ...(latestSource.content
369
+ ? {
370
+ source: {
371
+ currentContent: latestSource.content,
372
+ ...(latestSource.revision
373
+ ? { revision: latestSource.revision }
374
+ : {}),
375
+ },
376
+ }
377
+ : {}),
378
+ },
379
+ {
380
+ onSuccess: (result) => {
381
+ const response = result as {
382
+ content?: unknown;
383
+ fileId?: unknown;
384
+ updatedAt?: unknown;
385
+ conflict?: unknown;
386
+ error?: unknown;
387
+ };
388
+ if (response.conflict) {
389
+ toast.error(
390
+ typeof response.error === "string"
391
+ ? response.error
392
+ : "This file changed since this component prop edit was prepared. Refresh and try again.",
393
+ );
394
+ return;
395
+ }
396
+ if (
397
+ typeof response.fileId === "string" &&
398
+ typeof response.content === "string"
399
+ ) {
400
+ const updatedAt =
401
+ typeof response.updatedAt === "string"
402
+ ? response.updatedAt
403
+ : undefined;
404
+ latestSourceRef.current = {
405
+ content: response.content,
406
+ revision: updatedAt ?? latestSourceRef.current.revision,
407
+ };
408
+ onComponentPropApplied?.(
409
+ response.fileId,
410
+ response.content,
411
+ updatedAt,
412
+ );
413
+ }
414
+ },
415
+ onSettled: () => {
416
+ void queryClient.invalidateQueries({
417
+ queryKey: ["action", "get-design"],
418
+ });
419
+ void queryClient.invalidateQueries({ queryKey: detailsKey });
420
+ void refetch();
421
+ },
422
+ },
423
+ );
424
+ };
425
+
426
+ useEffect(() => {
427
+ if (typeof document === "undefined") return;
428
+
429
+ const handleMessage = (event: MessageEvent) => {
430
+ if (
431
+ (event.data as { type?: unknown } | null)?.type === "element-select"
432
+ ) {
433
+ void refetch();
434
+ }
435
+ };
436
+ window.addEventListener("message", handleMessage);
437
+ return () => {
438
+ window.removeEventListener("message", handleMessage);
439
+ };
440
+ }, [refetch]);
441
+
442
+ // While loading, show a compact skeleton that matches the section width.
443
+ if (isLoading) {
444
+ return (
445
+ <section className="shrink-0 border-t border-[var(--design-editor-control-border)] first:border-t-0">
446
+ <div className="flex min-h-9 items-center gap-2 px-3">
447
+ <div className="h-3 w-24 animate-pulse rounded bg-muted/50" />
448
+ </div>
449
+ <div className="space-y-1.5 px-3 pb-3 pt-0.5">
450
+ <div className="h-5 w-full animate-pulse rounded bg-muted/40" />
451
+ <div className="h-5 w-3/4 animate-pulse rounded bg-muted/40" />
452
+ </div>
453
+ </section>
454
+ );
455
+ }
456
+
457
+ // Hard error (node not found, no access, etc.) — collapse silently so
458
+ // the rest of the inspector is not disrupted.
459
+ if (error || !data) return null;
460
+
461
+ const {
462
+ name,
463
+ sourceType,
464
+ sourceLocation,
465
+ observedProps,
466
+ persistedVariants,
467
+ instance,
468
+ capabilities,
469
+ } = data;
470
+
471
+ // ── Editable prop model ───────────────────────────────────────────────────
472
+ // Inline/Alpine designs persist through apply-component-prop-edit. Two write
473
+ // surfaces:
474
+ // • x-data keys → kind "alpineData" (rewrites the whole object)
475
+ // • data-prop-* attrs → kind "attribute" (data-agent-native-prop-<kebab>)
476
+ // Real-app sources keep the deeper source-prop controls gated as-is, so for
477
+ // non-inline sources the controls are read-only here.
478
+ const isInline = sourceType === "inline";
479
+ const editingEnabled = isInline && capabilities.canEditProps; // gated; real-app stays read-only for now
480
+ const alpineData = parseAlpineDataObject(instance?.alpineData);
481
+
482
+ // Each editable row: name + current value + how it persists + its options.
483
+ type PropRow = {
484
+ name: string;
485
+ value: string;
486
+ /** Variant/enum options when the prop is a known group. */
487
+ options?: string[];
488
+ /** Persist surface for this prop. */
489
+ surface: "alpineData" | "attribute";
490
+ };
491
+
492
+ const rows: PropRow[] = [];
493
+ const seen = new Set<string>();
494
+
495
+ // 1) Alpine x-data keys come first — they drive the live variant/state.
496
+ if (alpineData) {
497
+ for (const [key, value] of Object.entries(alpineData)) {
498
+ rows.push({
499
+ name: key,
500
+ value,
501
+ options: persistedVariants[key],
502
+ surface: "alpineData",
503
+ });
504
+ seen.add(key);
505
+ }
506
+ }
507
+
508
+ // 2) data-agent-native-prop-* attributes not already covered by x-data.
509
+ for (const prop of observedProps) {
510
+ if (seen.has(prop.name)) continue;
511
+ rows.push({
512
+ name: prop.name,
513
+ value: prop.value,
514
+ options: persistedVariants[prop.name],
515
+ surface: "attribute",
516
+ });
517
+ seen.add(prop.name);
518
+ }
519
+
520
+ // 3) persistedVariant groups with no observed value yet (default to first).
521
+ for (const [group, options] of Object.entries(persistedVariants)) {
522
+ if (seen.has(group)) continue;
523
+ rows.push({
524
+ name: group,
525
+ value: options[0] ?? "",
526
+ options,
527
+ surface: alpineData ? "alpineData" : "attribute",
528
+ });
529
+ seen.add(group);
530
+ }
531
+
532
+ const hasRows = rows.length > 0;
533
+
534
+ // Build the apply-component-prop-edit payload + optimistic cache patch for a
535
+ // single prop change.
536
+ const commitProp = (row: PropRow, nextValue: string) => {
537
+ if (!editingEnabled || nextValue === row.value) return;
538
+
539
+ if (row.surface === "alpineData") {
540
+ // Surgically replace only the edited key's value inside the original
541
+ // x-data string so methods, nested objects, escaped strings, quoted
542
+ // keys, and whitespace survive byte-for-byte. A full
543
+ // parse→mutate→serialize round-trip would drop anything
544
+ // parseAlpineDataObject can't model (e.g. `toggle() { … }`).
545
+ const original = instance?.alpineData ?? "";
546
+ const surgical = replaceAlpineDataKeyValue(original, row.name, nextValue);
547
+
548
+ let serialized: string;
549
+ if (surgical != null) {
550
+ serialized = surgical;
551
+ } else if (canRebuildAlpineDataLosslessly(original)) {
552
+ // The key isn't present yet (or there is no original literal). Rebuild
553
+ // from the flat map — safe here precisely because the original holds
554
+ // nothing richer than the flat literals serialize already preserves.
555
+ const nextData = { ...(alpineData ?? {}), [row.name]: nextValue };
556
+ serialized = serializeAlpineDataObject(nextData);
557
+ } else {
558
+ // The original carries content (methods / nested / expressions) we
559
+ // can't rewrite for this key without dropping it. Fail safe: skip the
560
+ // edit rather than persist a lossy rewrite, and tell the user why so
561
+ // the change doesn't silently vanish.
562
+ toast.error(
563
+ // i18n-ignore
564
+ "Can’t safely edit this prop inline — this component’s Alpine state is too complex. Edit the source instead.",
565
+ );
566
+ return;
567
+ }
568
+
569
+ const nextSerialized = serialized;
570
+ persistPropEdit(
571
+ { kind: "alpineData", value: nextSerialized },
572
+ (prev) => ({
573
+ ...prev,
574
+ instance: { ...(prev.instance ?? {}), alpineData: nextSerialized },
575
+ observedProps: prev.observedProps.map((p) =>
576
+ p.name === row.name ? { ...p, value: nextValue } : p,
577
+ ),
578
+ }),
579
+ );
580
+ } else {
581
+ persistPropEdit(
582
+ {
583
+ kind: "attribute",
584
+ attribute: propNameToDataAttribute(row.name),
585
+ value: nextValue,
586
+ },
587
+ (prev) => {
588
+ const exists = prev.observedProps.some((p) => p.name === row.name);
589
+ return {
590
+ ...prev,
591
+ observedProps: exists
592
+ ? prev.observedProps.map((p) =>
593
+ p.name === row.name ? { ...p, value: nextValue } : p,
594
+ )
595
+ : [...prev.observedProps, { name: row.name, value: nextValue }],
596
+ };
597
+ },
598
+ );
599
+ }
600
+ };
601
+
602
+ // ── Capability gates ──
603
+ const canJumpToSource =
604
+ capabilities.canResolveToFile &&
605
+ Boolean(sourceLocation?.filePath) &&
606
+ sourceCapabilities.includes("resolveNodeToFile");
607
+
608
+ // ── Source chip text ──
609
+ const sourceChip = sourceLocation?.exportName
610
+ ? `${sourceLocation.exportName} — ${sourceLocation.filePath}`
611
+ : (sourceLocation?.filePath ?? null);
612
+
613
+ return (
614
+ <section
615
+ className="shrink-0 border-t border-[var(--design-editor-control-border)] first:border-t-0"
616
+ data-testid="component-section"
617
+ >
618
+ {/* ── Section header ── */}
619
+ <div className="flex min-h-9 items-center gap-2 px-3">
620
+ {/* Accent diamond matching the workbench artboard component rows */}
621
+ <span
622
+ className="size-2 shrink-0 rotate-45 rounded-[2px] bg-[var(--design-editor-component-color)]"
623
+ aria-hidden="true"
624
+ />
625
+ <h3 className="min-w-0 flex-1 truncate !text-[11px] font-semibold text-foreground">
626
+ {name}
627
+ </h3>
628
+ {/* Jump-to-source action */}
629
+ <Tooltip>
630
+ <TooltipTrigger asChild>
631
+ <Button
632
+ type="button"
633
+ variant="ghost"
634
+ size="icon"
635
+ className="size-6 rounded-md text-muted-foreground hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40"
636
+ disabled={!canJumpToSource}
637
+ aria-label={
638
+ "Edit component source" /* i18n-ignore design inspector action */
639
+ }
640
+ onClick={() => {
641
+ openSourceMutation.mutate({
642
+ designId,
643
+ nodeId,
644
+ ...(fileId ? { fileId } : {}),
645
+ });
646
+ }}
647
+ >
648
+ <IconExternalLink className="size-3.5" />
649
+ </Button>
650
+ </TooltipTrigger>
651
+ <TooltipContent>
652
+ {
653
+ canJumpToSource
654
+ ? "Edit component source" /* i18n-ignore design inspector action */
655
+ : (capabilities.ctaMessage ??
656
+ "Source jump needs a connected app") /* i18n-ignore design inspector tooltip */
657
+ }
658
+ </TooltipContent>
659
+ </Tooltip>
660
+ </div>
661
+
662
+ {/* ── Body ── */}
663
+ <div className="space-y-1.5 px-3 pb-3 pt-0.5 !text-[11px]">
664
+ {/* Source path chip */}
665
+ {sourceChip && (
666
+ <div
667
+ className="flex items-center gap-1 rounded bg-[var(--design-editor-control-bg)] px-2 py-1"
668
+ title={sourceChip}
669
+ >
670
+ <IconCode className="size-3 shrink-0 text-muted-foreground/60" />
671
+ <span className="min-w-0 flex-1 truncate font-mono text-[10px] text-muted-foreground">
672
+ {sourceChip}
673
+ </span>
674
+ </div>
675
+ )}
676
+
677
+ {/* Typed prop controls. Inline/Alpine designs are editable and persist
678
+ through apply-component-prop-edit; real-app sources are read-only
679
+ until the deeper source-prop controls land. */}
680
+ {hasRows && (
681
+ <div className="space-y-1">
682
+ <p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70">
683
+ {"Props" /* i18n-ignore design inspector label */}
684
+ </p>
685
+ {rows.map((row) => {
686
+ const hasOptions = (row.options?.length ?? 0) > 0;
687
+ const isBoolean = !hasOptions && isBooleanPropValue(row.value);
688
+ const disabled = !editingEnabled || applyPropMutation.isPending;
689
+ return (
690
+ <div key={row.name} className="flex items-center gap-1.5">
691
+ <Label className="w-[64px] shrink-0 truncate !text-[11px] font-medium capitalize text-muted-foreground">
692
+ {row.name}
693
+ </Label>
694
+ {hasOptions ? (
695
+ // Dropdown for variant / enum groups.
696
+ <Select
697
+ value={row.value || row.options![0] || ""}
698
+ onValueChange={(v) => commitProp(row, v)}
699
+ disabled={disabled}
700
+ >
701
+ <SelectTrigger className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus:ring-1 focus:ring-[var(--design-editor-accent-color)]">
702
+ <SelectValue />
703
+ </SelectTrigger>
704
+ <SelectContent>
705
+ {row.options!.map((opt) => (
706
+ <SelectItem
707
+ key={opt}
708
+ value={opt}
709
+ className="!text-[11px]"
710
+ >
711
+ {opt}
712
+ </SelectItem>
713
+ ))}
714
+ </SelectContent>
715
+ </Select>
716
+ ) : isBoolean ? (
717
+ // Toggle for boolean props.
718
+ <div className="flex min-w-0 flex-1 items-center">
719
+ <Switch
720
+ checked={row.value.trim().toLowerCase() === "true"}
721
+ onCheckedChange={(checked) =>
722
+ commitProp(row, checked ? "true" : "false")
723
+ }
724
+ disabled={disabled}
725
+ className="h-4 w-7 [&>span]:size-3 [&>span]:data-[state=checked]:translate-x-3"
726
+ aria-label={
727
+ row.name /* i18n-ignore dynamic prop name */
728
+ }
729
+ />
730
+ </div>
731
+ ) : (
732
+ // Text input for string props (e.g. a label).
733
+ <Input
734
+ defaultValue={row.value}
735
+ key={`${row.name}:${row.value}`}
736
+ disabled={disabled}
737
+ onBlur={(e) => commitProp(row, e.target.value)}
738
+ onKeyDown={(e) => {
739
+ if (e.key === "Enter") {
740
+ e.preventDefault();
741
+ e.currentTarget.blur();
742
+ }
743
+ }}
744
+ className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-1.5 !text-[11px] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)] md:!text-[11px]"
745
+ />
746
+ )}
747
+ </div>
748
+ );
749
+ })}
750
+ </div>
751
+ )}
752
+
753
+ {/* Connect-Builder CTA (only when prop editing is actually gated). */}
754
+ {capabilities.ctaRequired && !editingEnabled && (
755
+ <MakeItRealCard
756
+ designId={designId}
757
+ featureLabel="component source jump and typed prop metadata"
758
+ />
759
+ )}
760
+ </div>
761
+ </section>
762
+ );
763
+ }