@agent-native/core 0.82.0 → 0.84.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 (474) hide show
  1. package/README.md +2 -2
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +34 -0
  4. package/corpus/core/README.md +2 -2
  5. package/corpus/core/docs/content/cloneable-saas.mdx +2 -2
  6. package/corpus/core/docs/content/faq.mdx +1 -1
  7. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  8. package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
  9. package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  10. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  11. package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
  12. package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
  13. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  14. package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
  15. package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
  16. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  17. package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
  18. package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  19. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  20. package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
  21. package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  22. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  23. package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
  24. package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  25. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  26. package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
  27. package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  28. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  29. package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
  30. package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  31. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  32. package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
  33. package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  34. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  35. package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
  36. package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  37. package/corpus/core/docs/content/template-chat.mdx +1 -1
  38. package/corpus/core/package.json +1 -1
  39. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  40. package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
  41. package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
  42. package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
  43. package/corpus/core/src/agent/harness/translate.ts +3 -0
  44. package/corpus/core/src/agent/harness/types.ts +1 -0
  45. package/corpus/core/src/agent/model-config.ts +34 -13
  46. package/corpus/core/src/agent/production-agent.ts +87 -10
  47. package/corpus/core/src/agent/thread-data-builder.ts +29 -4
  48. package/corpus/core/src/agent/tool-call-journal.ts +109 -48
  49. package/corpus/core/src/agent/types.ts +6 -1
  50. package/corpus/core/src/cli/design-connect.ts +106 -0
  51. package/corpus/core/src/cli/gateway-helpers.ts +17 -0
  52. package/corpus/core/src/cli/recap.ts +0 -2
  53. package/corpus/core/src/cli/skills.ts +54 -16
  54. package/corpus/core/src/cli/workspace-dev.ts +29 -5
  55. package/corpus/core/src/client/AssistantChat.tsx +89 -42
  56. package/corpus/core/src/client/CommandMenu.tsx +34 -12
  57. package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
  58. package/corpus/core/src/client/chat/message-components.tsx +49 -1
  59. package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
  60. package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
  61. package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
  62. package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
  63. package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
  64. package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
  65. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
  66. package/corpus/core/src/client/guided-questions.tsx +21 -27
  67. package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
  68. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
  69. package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
  70. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
  71. package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
  72. package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
  73. package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
  74. package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
  75. package/corpus/core/src/client/useProductionAgent.ts +6 -2
  76. package/corpus/core/src/deploy/build.ts +1 -1
  77. package/corpus/core/src/observability/traces.ts +3 -3
  78. package/corpus/core/src/onboarding/default-steps.ts +1 -1
  79. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  80. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  81. package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
  82. package/corpus/core/src/server/core-routes-plugin.ts +7 -1
  83. package/corpus/core/src/server/open-route.ts +59 -40
  84. package/corpus/core/src/shared/reasoning-effort.ts +2 -0
  85. package/corpus/core/src/sharing/access.ts +22 -2
  86. package/corpus/core/src/sharing/registry.ts +18 -0
  87. package/corpus/core/src/styles/agent-native.css +104 -20
  88. package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
  89. package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
  90. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
  91. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
  92. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
  93. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
  94. package/corpus/templates/analytics/app/global.css +4 -0
  95. package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
  96. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
  97. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
  98. package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
  99. package/corpus/templates/calendar/AGENTS.md +4 -0
  100. package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
  101. package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
  102. package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
  103. package/corpus/templates/content/.env.local.example +6 -0
  104. package/corpus/templates/content/AGENTS.md +10 -1
  105. package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
  106. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
  107. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
  108. package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
  109. package/corpus/templates/content/actions/_database-utils.ts +41 -0
  110. package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
  111. package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
  112. package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
  113. package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
  114. package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
  115. package/corpus/templates/content/actions/list-content-databases.ts +41 -33
  116. package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
  117. package/corpus/templates/content/actions/update-document.ts +93 -0
  118. package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
  119. package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
  120. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
  121. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +45 -4
  122. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
  123. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
  124. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
  125. package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
  126. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
  127. package/corpus/templates/content/app/global.css +16 -8
  128. package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
  129. package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
  130. package/corpus/templates/content/app/i18n-data.ts +212 -0
  131. package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
  132. package/corpus/templates/content/app/root.tsx +236 -8
  133. package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
  134. package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
  135. package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
  136. package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
  137. package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
  138. package/corpus/templates/content/package.json +3 -1
  139. package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
  140. package/corpus/templates/content/scripts/dev-database.mjs +83 -0
  141. package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
  142. package/corpus/templates/content/server/db/schema.ts +27 -0
  143. package/corpus/templates/content/server/plugins/db.ts +33 -0
  144. package/corpus/templates/content/shared/api.ts +45 -0
  145. package/corpus/templates/content/shared/builder-mdx.ts +905 -5
  146. package/corpus/templates/content/shared/nfm-registry.ts +2 -0
  147. package/corpus/templates/content/shared/source-component-block.ts +141 -0
  148. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
  149. package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
  150. package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
  151. package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
  152. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
  153. package/corpus/templates/design/actions/navigate.ts +3 -12
  154. package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
  155. package/corpus/templates/design/actions/view-screen.ts +1 -1
  156. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
  157. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +350 -15
  158. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
  159. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
  160. package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
  161. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
  162. package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
  163. package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
  164. package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
  165. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1193 -207
  166. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
  167. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
  168. package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
  169. package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
  170. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
  171. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1225 -154
  172. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
  173. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
  174. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
  175. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
  176. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
  177. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
  178. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  179. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
  180. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
  182. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
  183. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
  184. package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
  185. package/corpus/templates/design/app/components/design/types.ts +19 -0
  186. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
  187. package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
  188. package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
  189. package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
  190. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
  191. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  192. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
  193. package/corpus/templates/design/app/global.css +56 -0
  194. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
  195. package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
  196. package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
  197. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
  198. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
  199. package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
  200. package/corpus/templates/design/app/i18n-data.ts +63 -188
  201. package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
  202. package/corpus/templates/design/app/pages/DesignEditor.tsx +1755 -401
  203. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
  204. package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
  205. package/corpus/templates/design/app/pages/Index.tsx +49 -26
  206. package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
  207. package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
  208. package/corpus/templates/design/app/routes/examples.tsx +1 -7
  209. package/corpus/templates/design/app/routes/templates.tsx +7 -4
  210. package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
  214. package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
  215. package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
  216. package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
  217. package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
  218. package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
  219. package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
  220. package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
  222. package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
  223. package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
  224. package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
  225. package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
  226. package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
  227. package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
  228. package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
  229. package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
  230. package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
  231. package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
  232. package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
  233. package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
  234. package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
  235. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
  236. package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
  237. package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
  238. package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
  239. package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
  240. package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
  242. package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
  243. package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
  244. package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
  246. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
  247. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
  248. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
  249. package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
  250. package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
  251. package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
  252. package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
  253. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
  254. package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
  255. package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
  256. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
  257. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
  258. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
  259. package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
  260. package/corpus/templates/design/e2e/helpers.ts +15 -4
  261. package/corpus/templates/design/server/db/index.ts +52 -0
  262. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
  263. package/corpus/templates/design/server/plugins/auth.ts +1 -0
  264. package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
  265. package/corpus/templates/design/shared/board-file.ts +380 -5
  266. package/corpus/templates/design/shared/board-objects.ts +2 -2
  267. package/corpus/templates/design/shared/code-layer.ts +50 -6
  268. package/corpus/templates/design/shared/motion-compiler.ts +27 -0
  269. package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
  270. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  271. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  272. package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
  273. package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
  274. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  275. package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
  276. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
  277. package/corpus/templates/macros/app/routes/_index.tsx +5 -5
  278. package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
  279. package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
  280. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
  281. package/corpus/templates/mail/app/global.css +3 -2
  282. package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
  283. package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
  284. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
  285. package/corpus/templates/slides/app/global.css +10 -0
  286. package/corpus/templates/slides/app/pages/Index.tsx +55 -51
  287. package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
  288. package/dist/agent/engine/anthropic-engine.d.ts +2 -2
  289. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  290. package/dist/agent/engine/builder-engine.d.ts +2 -2
  291. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  292. package/dist/agent/engine/builtin.js +1 -1
  293. package/dist/agent/engine/builtin.js.map +1 -1
  294. package/dist/agent/engine/output-tokens.js +1 -1
  295. package/dist/agent/engine/output-tokens.js.map +1 -1
  296. package/dist/agent/harness/acp-adapter.d.ts +4 -1
  297. package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
  298. package/dist/agent/harness/acp-adapter.js +19 -2
  299. package/dist/agent/harness/acp-adapter.js.map +1 -1
  300. package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
  301. package/dist/agent/harness/ai-sdk-adapter.js +3 -0
  302. package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
  303. package/dist/agent/harness/translate.d.ts.map +1 -1
  304. package/dist/agent/harness/translate.js +3 -0
  305. package/dist/agent/harness/translate.js.map +1 -1
  306. package/dist/agent/harness/types.d.ts +1 -0
  307. package/dist/agent/harness/types.d.ts.map +1 -1
  308. package/dist/agent/harness/types.js.map +1 -1
  309. package/dist/agent/model-config.d.ts +21 -20
  310. package/dist/agent/model-config.d.ts.map +1 -1
  311. package/dist/agent/model-config.js +31 -13
  312. package/dist/agent/model-config.js.map +1 -1
  313. package/dist/agent/production-agent.d.ts.map +1 -1
  314. package/dist/agent/production-agent.js +85 -10
  315. package/dist/agent/production-agent.js.map +1 -1
  316. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  317. package/dist/agent/thread-data-builder.js +27 -6
  318. package/dist/agent/thread-data-builder.js.map +1 -1
  319. package/dist/agent/tool-call-journal.d.ts +11 -9
  320. package/dist/agent/tool-call-journal.d.ts.map +1 -1
  321. package/dist/agent/tool-call-journal.js +92 -40
  322. package/dist/agent/tool-call-journal.js.map +1 -1
  323. package/dist/agent/types.d.ts +5 -1
  324. package/dist/agent/types.d.ts.map +1 -1
  325. package/dist/agent/types.js.map +1 -1
  326. package/dist/cli/design-connect.d.ts.map +1 -1
  327. package/dist/cli/design-connect.js +84 -0
  328. package/dist/cli/design-connect.js.map +1 -1
  329. package/dist/cli/gateway-helpers.d.ts +4 -0
  330. package/dist/cli/gateway-helpers.d.ts.map +1 -1
  331. package/dist/cli/gateway-helpers.js +9 -0
  332. package/dist/cli/gateway-helpers.js.map +1 -1
  333. package/dist/cli/recap.d.ts.map +1 -1
  334. package/dist/cli/recap.js +0 -2
  335. package/dist/cli/recap.js.map +1 -1
  336. package/dist/cli/skills.d.ts.map +1 -1
  337. package/dist/cli/skills.js +54 -16
  338. package/dist/cli/skills.js.map +1 -1
  339. package/dist/cli/workspace-dev.d.ts.map +1 -1
  340. package/dist/cli/workspace-dev.js +33 -6
  341. package/dist/cli/workspace-dev.js.map +1 -1
  342. package/dist/client/AssistantChat.d.ts.map +1 -1
  343. package/dist/client/AssistantChat.js +72 -26
  344. package/dist/client/AssistantChat.js.map +1 -1
  345. package/dist/client/CommandMenu.d.ts +6 -2
  346. package/dist/client/CommandMenu.d.ts.map +1 -1
  347. package/dist/client/CommandMenu.js +25 -14
  348. package/dist/client/CommandMenu.js.map +1 -1
  349. package/dist/client/DefaultSpinner.d.ts.map +1 -1
  350. package/dist/client/DefaultSpinner.js +8 -1
  351. package/dist/client/DefaultSpinner.js.map +1 -1
  352. package/dist/client/chat/message-components.d.ts +7 -1
  353. package/dist/client/chat/message-components.d.ts.map +1 -1
  354. package/dist/client/chat/message-components.js +37 -1
  355. package/dist/client/chat/message-components.js.map +1 -1
  356. package/dist/client/chat/repo-helpers.d.ts +1 -0
  357. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  358. package/dist/client/chat/repo-helpers.js +59 -0
  359. package/dist/client/chat/repo-helpers.js.map +1 -1
  360. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  361. package/dist/client/chat/run-recovery.js +4 -4
  362. package/dist/client/chat/run-recovery.js.map +1 -1
  363. package/dist/client/components/ApiKeySettings.js +8 -8
  364. package/dist/client/components/ApiKeySettings.js.map +1 -1
  365. package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
  366. package/dist/client/components/CodeRequiredDialog.js +24 -15
  367. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  368. package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
  369. package/dist/client/components/MissingKeyCard.js +11 -6
  370. package/dist/client/components/MissingKeyCard.js.map +1 -1
  371. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  372. package/dist/client/composer/TiptapComposer.js +4 -3
  373. package/dist/client/composer/TiptapComposer.js.map +1 -1
  374. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  375. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  376. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  377. package/dist/client/guided-questions.d.ts.map +1 -1
  378. package/dist/client/guided-questions.js +6 -6
  379. package/dist/client/guided-questions.js.map +1 -1
  380. package/dist/client/integrations/IntegrationCard.js +1 -1
  381. package/dist/client/integrations/IntegrationCard.js.map +1 -1
  382. package/dist/client/integrations/IntegrationsPanel.js +1 -1
  383. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  384. package/dist/client/onboarding/OnboardingBanner.js +2 -2
  385. package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
  386. package/dist/client/onboarding/OnboardingPanel.js +23 -19
  387. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  388. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  389. package/dist/client/resources/ResourceEditor.js +13 -10
  390. package/dist/client/resources/ResourceEditor.js.map +1 -1
  391. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  392. package/dist/client/resources/ResourcesPanel.js +2 -1
  393. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  394. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  395. package/dist/client/settings/SettingsPanel.js +8 -3
  396. package/dist/client/settings/SettingsPanel.js.map +1 -1
  397. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  398. package/dist/client/settings/useBuilderStatus.js +6 -38
  399. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  400. package/dist/client/useProductionAgent.d.ts +2 -1
  401. package/dist/client/useProductionAgent.d.ts.map +1 -1
  402. package/dist/client/useProductionAgent.js.map +1 -1
  403. package/dist/deploy/build.js +1 -1
  404. package/dist/deploy/build.js.map +1 -1
  405. package/dist/notifications/routes.d.ts +3 -3
  406. package/dist/observability/routes.d.ts +3 -3
  407. package/dist/observability/traces.d.ts.map +1 -1
  408. package/dist/observability/traces.js.map +1 -1
  409. package/dist/onboarding/default-steps.js +1 -1
  410. package/dist/onboarding/default-steps.js.map +1 -1
  411. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  412. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  413. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  414. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  415. package/dist/server/agent-chat-plugin.d.ts +3 -0
  416. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  417. package/dist/server/agent-chat-plugin.js +134 -72
  418. package/dist/server/agent-chat-plugin.js.map +1 -1
  419. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  420. package/dist/server/core-routes-plugin.d.ts +3 -0
  421. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  422. package/dist/server/core-routes-plugin.js +4 -1
  423. package/dist/server/core-routes-plugin.js.map +1 -1
  424. package/dist/server/open-route.d.ts +9 -0
  425. package/dist/server/open-route.d.ts.map +1 -1
  426. package/dist/server/open-route.js +48 -38
  427. package/dist/server/open-route.js.map +1 -1
  428. package/dist/shared/reasoning-effort.js +3 -0
  429. package/dist/shared/reasoning-effort.js.map +1 -1
  430. package/dist/sharing/access.d.ts.map +1 -1
  431. package/dist/sharing/access.js +18 -2
  432. package/dist/sharing/access.js.map +1 -1
  433. package/dist/sharing/registry.d.ts +10 -0
  434. package/dist/sharing/registry.d.ts.map +1 -1
  435. package/dist/sharing/registry.js.map +1 -1
  436. package/dist/styles/agent-native.css +104 -20
  437. package/dist/styles/rich-markdown-editor.css +11 -10
  438. package/docs/content/cloneable-saas.mdx +2 -2
  439. package/docs/content/faq.mdx +1 -1
  440. package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  441. package/docs/content/locales/ar-SA/faq.mdx +1 -1
  442. package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  443. package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  444. package/docs/content/locales/de-DE/faq.mdx +1 -1
  445. package/docs/content/locales/de-DE/template-chat.mdx +1 -1
  446. package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  447. package/docs/content/locales/es-ES/faq.mdx +1 -1
  448. package/docs/content/locales/es-ES/template-chat.mdx +1 -1
  449. package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  450. package/docs/content/locales/fr-FR/faq.mdx +1 -1
  451. package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  452. package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  453. package/docs/content/locales/hi-IN/faq.mdx +1 -1
  454. package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  455. package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  456. package/docs/content/locales/ja-JP/faq.mdx +1 -1
  457. package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  458. package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  459. package/docs/content/locales/ko-KR/faq.mdx +1 -1
  460. package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  461. package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  462. package/docs/content/locales/pt-BR/faq.mdx +1 -1
  463. package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  464. package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  465. package/docs/content/locales/zh-CN/faq.mdx +1 -1
  466. package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  467. package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  468. package/docs/content/locales/zh-TW/faq.mdx +1 -1
  469. package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  470. package/docs/content/template-chat.mdx +1 -1
  471. package/package.json +1 -1
  472. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
  473. package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
  474. package/corpus/templates/design/app/pages/Templates.tsx +0 -279
@@ -11,6 +11,11 @@ import {
11
11
  parseRegistryBlockData,
12
12
  serializeRegistryBlockToMdx,
13
13
  } from "./nfm-registry";
14
+ import type {
15
+ SourceComponentData,
16
+ SourceComponentPreview,
17
+ SourceComponentPreviewTable,
18
+ } from "./source-component-block";
14
19
 
15
20
  export const BUILDER_DOCS_CONTENT_ROOT = "content/builder";
16
21
  export const BUILDER_DOCS_RAW_ROOT = `${BUILDER_DOCS_CONTENT_ROOT}/.raw`;
@@ -72,6 +77,11 @@ export interface BuilderBlocksFromMdxResult {
72
77
  warnings: string[];
73
78
  }
74
79
 
80
+ export interface BuilderReadableBodyMergeResult {
81
+ blocks: unknown[] | null;
82
+ warnings: string[];
83
+ }
84
+
75
85
  type MdxNode = {
76
86
  type: string;
77
87
  name?: string;
@@ -376,6 +386,160 @@ function childBlocks(block: unknown): unknown[] {
376
386
  return Array.isArray(block.children) ? block.children : [];
377
387
  }
378
388
 
389
+ function isReadableUnsupportedBuilderPlaceholder(value: string) {
390
+ const trimmed = value.trim();
391
+ return (
392
+ /^>\s*Builder .+ component preserved from source\.$/.test(trimmed) ||
393
+ /^<SourceComponent\b/.test(trimmed)
394
+ );
395
+ }
396
+
397
+ function countReadableSourceComponentMarkers(markdown: string) {
398
+ return markdownUnits(markdown).filter(isReadableUnsupportedBuilderPlaceholder)
399
+ .length;
400
+ }
401
+
402
+ function sourceComponentMarkerIdForBlock(block: unknown) {
403
+ return `source-component-builder-${safePathPart(
404
+ builderBlockStableId(block, stableHash(block).slice(0, 12)),
405
+ )}`;
406
+ }
407
+
408
+ function builderImageMarkdown(options: Record<string, unknown>) {
409
+ const imageUrl =
410
+ typeof options.image === "string" ? options.image.trim() : "";
411
+ if (!imageUrl) return "";
412
+ const altText =
413
+ typeof options.altText === "string"
414
+ ? options.altText.trim()
415
+ : typeof options.alt === "string"
416
+ ? options.alt.trim()
417
+ : "";
418
+ return `![${escapeMarkdownImageAlt(altText)}](${escapeMarkdownImageUrl(
419
+ imageUrl,
420
+ )})`;
421
+ }
422
+
423
+ function escapeMarkdownImageAlt(value: string) {
424
+ return value.replace(/\\/g, "\\\\").replace(/\]/g, "\\]");
425
+ }
426
+
427
+ function unescapeMarkdownImageAlt(value: string) {
428
+ return value.replace(/\\]/g, "]").replace(/\\\\/g, "\\");
429
+ }
430
+
431
+ function escapeMarkdownImageUrl(value: string) {
432
+ return value.replace(/\\/g, "%5C").replace(/\)/g, "%29");
433
+ }
434
+
435
+ function unescapeMarkdownImageUrl(value: string) {
436
+ return value.replace(/%29/gi, ")").replace(/%5C/gi, "\\");
437
+ }
438
+
439
+ function parseMarkdownImage(markdown: string) {
440
+ const trimmed = markdown.trim();
441
+ const match = trimmed.match(
442
+ /^!\[((?:\\.|[^\]\\])*)\]\((\S+?)(?:\s+"[^"]*")?\)$/,
443
+ );
444
+ if (!match) return null;
445
+ return {
446
+ alt: unescapeMarkdownImageAlt(match[1]),
447
+ src: unescapeMarkdownImageUrl(match[2]),
448
+ };
449
+ }
450
+
451
+ async function readableLayoutFingerprint(markdown: string) {
452
+ const units = markdownUnits(markdown);
453
+ const fingerprint: string[] = [];
454
+ for (const unit of units) {
455
+ if (/^>\s*Builder .+ component preserved from source\.$/.test(unit)) {
456
+ fingerprint.push("source:legacy");
457
+ continue;
458
+ }
459
+ if (/^<SourceComponent\b/.test(unit.trim())) {
460
+ const parsed = await parseRegistryBlockData(unit);
461
+ const id = parsed?.base.id || "missing-id";
462
+ fingerprint.push(`source:${id}`);
463
+ continue;
464
+ }
465
+ fingerprint.push("prose");
466
+ }
467
+ return fingerprint;
468
+ }
469
+
470
+ async function expectedReadableLayoutFingerprint(blocks: unknown[]) {
471
+ const fingerprint: string[] = [];
472
+ for (const block of blocks) {
473
+ if (!isRecord(block)) continue;
474
+ const name = componentName(block);
475
+ const options = componentOptions(block);
476
+ if (name === "Text") {
477
+ for (const unit of markdownUnits(
478
+ htmlToMarkdown(String(options.text ?? "")),
479
+ )) {
480
+ if (unit) fingerprint.push("prose");
481
+ }
482
+ continue;
483
+ }
484
+ if (name === "Code Block" || name === "Blog Code Block") {
485
+ if (readableCodeBlockMarkdown(options).trim()) fingerprint.push("prose");
486
+ continue;
487
+ }
488
+ if (name === "Image") {
489
+ if (builderImageMarkdown(options)) fingerprint.push("prose");
490
+ continue;
491
+ }
492
+ if (name === "Tabbed Content") {
493
+ const tabs = Array.isArray(options.tabs) ? options.tabs : [];
494
+ for (const tab of tabs) {
495
+ const content =
496
+ isRecord(tab) && Array.isArray(tab.content) ? tab.content : [];
497
+ if (content.some(builderBlockHasReadableOutput)) {
498
+ fingerprint.push("prose");
499
+ fingerprint.push(
500
+ ...(await expectedReadableLayoutFingerprint(content)),
501
+ );
502
+ }
503
+ }
504
+ continue;
505
+ }
506
+ const children = childBlocks(block);
507
+ if (children.length && children.some(builderBlockHasReadableOutput)) {
508
+ fingerprint.push(...(await expectedReadableLayoutFingerprint(children)));
509
+ continue;
510
+ }
511
+ fingerprint.push(`source:${sourceComponentMarkerIdForBlock(block)}`);
512
+ }
513
+ return fingerprint;
514
+ }
515
+
516
+ async function validateReadableSourceComponentMarkers(
517
+ markdown: string,
518
+ sidecars: Record<string, string>,
519
+ ) {
520
+ const warnings: string[] = [];
521
+ const markers = markdownUnits(markdown).filter((unit) =>
522
+ /^<SourceComponent\b/.test(unit.trim()),
523
+ );
524
+ for (const marker of markers) {
525
+ try {
526
+ const parsed = await parseRegistryBlockData(marker);
527
+ if (parsed?.type !== "source-component") {
528
+ warnings.push("Readable Builder body has an invalid source marker.");
529
+ continue;
530
+ }
531
+ rawBlockForData(parsed.data as SourceComponentData, sidecars);
532
+ } catch (error) {
533
+ warnings.push(
534
+ error instanceof Error
535
+ ? error.message
536
+ : "Readable Builder body has a source marker that could not be validated.",
537
+ );
538
+ }
539
+ }
540
+ return warnings;
541
+ }
542
+
379
543
  function symbolContentEntry(
380
544
  symbol: Record<string, unknown>,
381
545
  ): BuilderContentEntry | null {
@@ -426,9 +590,319 @@ function blockSummary(block: unknown) {
426
590
  if (name === "Text" && typeof options.text === "string") {
427
591
  return stripHtml(options.text).slice(0, 160);
428
592
  }
593
+ if (typeof options.url === "string" && options.url.trim()) {
594
+ return options.url.trim().slice(0, 160);
595
+ }
596
+ if (typeof options.title === "string" && options.title.trim()) {
597
+ return options.title.trim().slice(0, 160);
598
+ }
429
599
  return name ?? "Builder block";
430
600
  }
431
601
 
602
+ function countArrayColumns(rows: unknown[]) {
603
+ return rows.reduce<number>((max, row) => {
604
+ if (Array.isArray(row)) return Math.max(max, row.length);
605
+ if (!isRecord(row)) return max;
606
+ for (const key of ["cells", "columns", "values"]) {
607
+ const value = row[key];
608
+ if (Array.isArray(value)) return Math.max(max, value.length);
609
+ }
610
+ return Math.max(max, Object.keys(row).length);
611
+ }, 0);
612
+ }
613
+
614
+ function tableShapeFromOptions(options: Record<string, unknown>) {
615
+ if (Array.isArray(options.bodyRows)) {
616
+ const columns = options.bodyRows.reduce<number>(
617
+ (max, row) => {
618
+ if (!isRecord(row) || !Array.isArray(row.columns)) return max;
619
+ return Math.max(max, row.columns.length);
620
+ },
621
+ Array.isArray(options.headColumns) ? options.headColumns.length : 0,
622
+ );
623
+ return {
624
+ rows: options.bodyRows.length,
625
+ columns,
626
+ };
627
+ }
628
+ const candidates = [
629
+ options.rows,
630
+ options.data,
631
+ isRecord(options.table) ? options.table.rows : undefined,
632
+ isRecord(options.tableData) ? options.tableData.rows : undefined,
633
+ ];
634
+ for (const candidate of candidates) {
635
+ if (Array.isArray(candidate)) {
636
+ return {
637
+ rows: candidate.length,
638
+ columns: countArrayColumns(candidate),
639
+ };
640
+ }
641
+ }
642
+ const columns = Array.isArray(options.columns) ? options.columns.length : 0;
643
+ if (columns) return { rows: 0, columns };
644
+ return null;
645
+ }
646
+
647
+ function normalizePreviewScalar(value: unknown) {
648
+ if (value === null || value === undefined) return "";
649
+ if (typeof value === "string") return value;
650
+ if (typeof value === "number" || typeof value === "boolean") {
651
+ return String(value);
652
+ }
653
+ return stableJson(value);
654
+ }
655
+
656
+ function textFromBuilderBlocks(blocks: unknown[]): string {
657
+ return blocks
658
+ .map((block) => {
659
+ const name = componentName(block);
660
+ const options = componentOptions(block);
661
+ if (name === "Text") {
662
+ return stripHtml(String(options.text ?? "")).trim();
663
+ }
664
+ return textFromBuilderBlocks(childBlocks(block));
665
+ })
666
+ .filter(Boolean)
667
+ .join(" ")
668
+ .trim();
669
+ }
670
+
671
+ function materialTablePreviewFromOptions(
672
+ options: Record<string, unknown>,
673
+ ): SourceComponentPreviewTable | undefined {
674
+ const bodyRows = Array.isArray(options.bodyRows) ? options.bodyRows : [];
675
+ if (!bodyRows.length) return undefined;
676
+ const headColumns = Array.isArray(options.headColumns)
677
+ ? options.headColumns
678
+ : [];
679
+ const columnCount = bodyRows.reduce<number>((max, row) => {
680
+ if (!isRecord(row) || !Array.isArray(row.columns)) return max;
681
+ return Math.max(max, row.columns.length);
682
+ }, headColumns.length);
683
+ const columns = Array.from({ length: columnCount }, (_, index) => {
684
+ const head = headColumns[index];
685
+ const label =
686
+ isRecord(head) && typeof head.label === "string" && head.label.trim()
687
+ ? head.label.trim()
688
+ : `Column ${index + 1}`;
689
+ return { id: `column-${index + 1}`, label };
690
+ });
691
+ const rows = bodyRows.slice(0, 6).map((row) => {
692
+ const rowColumns =
693
+ isRecord(row) && Array.isArray(row.columns) ? row.columns : [];
694
+ return Object.fromEntries(
695
+ columns.map((column, index) => {
696
+ const cell = rowColumns[index];
697
+ const content =
698
+ isRecord(cell) && Array.isArray(cell.content) ? cell.content : [];
699
+ return [column.id, textFromBuilderBlocks(content)];
700
+ }),
701
+ );
702
+ });
703
+ return {
704
+ columns,
705
+ rows,
706
+ truncated: bodyRows.length > 6,
707
+ };
708
+ }
709
+
710
+ function tablePreviewFromRows(rows: unknown[]): SourceComponentPreviewTable {
711
+ const records = rows.slice(0, 6).map((row) => {
712
+ if (Array.isArray(row)) {
713
+ return Object.fromEntries(
714
+ row.map((cell, index) => [
715
+ `column-${index + 1}`,
716
+ normalizePreviewScalar(cell),
717
+ ]),
718
+ );
719
+ }
720
+ if (!isRecord(row)) return { value: normalizePreviewScalar(row) };
721
+ if (Array.isArray(row.cells)) {
722
+ return Object.fromEntries(
723
+ row.cells.map((cell, index) => [
724
+ `column-${index + 1}`,
725
+ normalizePreviewScalar(cell),
726
+ ]),
727
+ );
728
+ }
729
+ return Object.fromEntries(
730
+ Object.entries(row)
731
+ .slice(0, 12)
732
+ .map(([key, value]) => [key, normalizePreviewScalar(value)]),
733
+ );
734
+ });
735
+ const columnIds = Array.from(
736
+ new Set(records.flatMap((record) => Object.keys(record))),
737
+ ).slice(0, 12);
738
+ return {
739
+ columns: columnIds.map((id, index) => ({
740
+ id,
741
+ label: id.startsWith("column-") ? `Column ${index + 1}` : id,
742
+ })),
743
+ rows: records.map((record) =>
744
+ Object.fromEntries(columnIds.map((id) => [id, record[id] ?? ""])),
745
+ ),
746
+ truncated: rows.length > 6 || columnIds.length > 12,
747
+ };
748
+ }
749
+
750
+ function tablePreviewFromOptions(
751
+ options: Record<string, unknown>,
752
+ ): SourceComponentPreviewTable | undefined {
753
+ const materialTable = materialTablePreviewFromOptions(options);
754
+ if (materialTable) return materialTable;
755
+ const candidates = [
756
+ options.rows,
757
+ options.data,
758
+ isRecord(options.table) ? options.table.rows : undefined,
759
+ isRecord(options.tableData) ? options.tableData.rows : undefined,
760
+ ];
761
+ for (const candidate of candidates) {
762
+ if (Array.isArray(candidate)) return tablePreviewFromRows(candidate);
763
+ }
764
+ const columns = Array.isArray(options.columns)
765
+ ? options.columns.slice(0, 12).map((column, index) => {
766
+ if (typeof column === "string" && column.trim()) {
767
+ return { id: column, label: column };
768
+ }
769
+ if (isRecord(column)) {
770
+ const id =
771
+ stringFromRecord(column, ["id", "key", "name", "field"]) ??
772
+ `column-${index + 1}`;
773
+ const label =
774
+ stringFromRecord(column, ["label", "title", "name"]) ?? id;
775
+ return { id, label };
776
+ }
777
+ return { id: `column-${index + 1}`, label: `Column ${index + 1}` };
778
+ })
779
+ : [];
780
+ return columns.length ? { columns, rows: [], truncated: false } : undefined;
781
+ }
782
+
783
+ function pluralize(count: number, singular: string) {
784
+ return `${count} ${singular}${count === 1 ? "" : "s"}`;
785
+ }
786
+
787
+ function builderSourceComponentPreview(
788
+ block: unknown,
789
+ ): Pick<
790
+ SourceComponentData,
791
+ "previewKind" | "previewUrl" | "previewItems" | "preview" | "summary"
792
+ > {
793
+ const name = componentName(block);
794
+ const options = componentOptions(block);
795
+ const url = typeof options.url === "string" ? options.url.trim() : "";
796
+ if (name === "Embed") {
797
+ const summary = url ? `Embedded URL: ${url}` : "Embedded Builder content.";
798
+ return {
799
+ previewKind: "embed",
800
+ previewUrl: url || undefined,
801
+ previewItems: url ? [url] : undefined,
802
+ preview: {
803
+ status: url ? "available" : "unavailable",
804
+ kind: "embed",
805
+ label: "Builder Embed",
806
+ summary,
807
+ url: url || undefined,
808
+ },
809
+ summary,
810
+ };
811
+ }
812
+ if (name === "Image") {
813
+ const imageUrl =
814
+ typeof options.image === "string" ? options.image.trim() : "";
815
+ const altText =
816
+ typeof options.altText === "string" ? options.altText.trim() : "";
817
+ const summary = altText || "Builder image.";
818
+ return {
819
+ previewKind: "component",
820
+ previewUrl: imageUrl || undefined,
821
+ previewItems: altText ? [altText] : undefined,
822
+ preview: {
823
+ status: imageUrl ? "available" : "unavailable",
824
+ kind: "component",
825
+ label: "Builder Image",
826
+ summary,
827
+ url: imageUrl || undefined,
828
+ fields: altText ? [{ label: "Alt", value: altText }] : undefined,
829
+ },
830
+ summary,
831
+ };
832
+ }
833
+ if (name === "Table" || name?.toLowerCase().includes("table")) {
834
+ const shape = tableShapeFromOptions(options);
835
+ const items = shape
836
+ ? [pluralize(shape.rows, "row"), pluralize(shape.columns, "column")]
837
+ : undefined;
838
+ const summary = shape
839
+ ? `Builder table with ${items?.join(", ")}.`
840
+ : "Builder table component preserved from source.";
841
+ return {
842
+ previewKind: "table",
843
+ previewItems: items,
844
+ preview: {
845
+ status: shape ? "available" : "unavailable",
846
+ kind: "table",
847
+ label: "Builder Table",
848
+ summary,
849
+ fields: items?.map((item) => {
850
+ const [value, ...label] = item.split(" ");
851
+ return { label: label.join(" "), value };
852
+ }),
853
+ table: tablePreviewFromOptions(options),
854
+ },
855
+ summary,
856
+ };
857
+ }
858
+ if (name === "Symbol") {
859
+ const symbol = isRecord(options.symbol) ? options.symbol : {};
860
+ const entry = typeof symbol.entry === "string" ? symbol.entry : "";
861
+ const model = typeof symbol.model === "string" ? symbol.model : "";
862
+ const fields = [
863
+ model ? { label: "Model", value: model } : null,
864
+ entry ? { label: "Entry", value: entry } : null,
865
+ ].filter((field): field is { label: string; value: string } =>
866
+ Boolean(field),
867
+ );
868
+ const summary =
869
+ model || entry
870
+ ? `Builder symbol ${[model, entry].filter(Boolean).join(" / ")}.`
871
+ : "Builder symbol reference.";
872
+ return {
873
+ previewKind: "symbol",
874
+ previewItems: [model, entry].filter(Boolean),
875
+ preview: {
876
+ status: fields.length ? "available" : "unavailable",
877
+ kind: "symbol",
878
+ label: "Builder Symbol",
879
+ summary,
880
+ fields: fields.length ? fields : undefined,
881
+ },
882
+ summary,
883
+ };
884
+ }
885
+ const summary = blockSummary(block);
886
+ return {
887
+ previewKind: "component",
888
+ preview: {
889
+ status: name ? "available" : "unavailable",
890
+ kind: "component",
891
+ label: name ? `Builder ${name}` : "Builder component",
892
+ summary,
893
+ },
894
+ summary,
895
+ };
896
+ }
897
+
898
+ function builderBlockStableId(block: unknown, fallback: string) {
899
+ if (isRecord(block)) {
900
+ const id = stringFromRecord(block, ["id"]);
901
+ if (id) return id;
902
+ }
903
+ return fallback;
904
+ }
905
+
432
906
  function rawRefData(args: {
433
907
  block: unknown;
434
908
  rawRef: string;
@@ -462,6 +936,29 @@ async function builderBlocksToMdxBody(
462
936
  return mdxBlocks.filter(Boolean).join("\n\n").trim();
463
937
  }
464
938
 
939
+ async function builderBlocksToReadableMdxBody(
940
+ blocks: unknown[],
941
+ ctx: BlocksToMdxContext,
942
+ ) {
943
+ const mdxBlocks: string[] = [];
944
+ for (let index = 0; index < blocks.length; index += 1) {
945
+ const block = blocks[index];
946
+ const rawRef = rawSidecarPath({ rawRoot: ctx.rawRoot, block, index });
947
+ ctx.files[rawRef] = stableJson(block);
948
+ mdxBlocks.push(await builderBlockToReadableMdx(block, rawRef, ctx));
949
+ }
950
+ return mdxBlocks.filter(Boolean).join("\n\n").trim();
951
+ }
952
+
953
+ export async function builderBlocksToReadableMarkdown(blocks: unknown[]) {
954
+ return builderBlocksToReadableMdxBody(blocks, {
955
+ rawRoot: `${BUILDER_DOCS_RAW_ROOT}/readable`,
956
+ files: {},
957
+ warnings: [],
958
+ emitReferencedEntry: async (entry) => builderMdxPathForEntry(entry),
959
+ });
960
+ }
961
+
465
962
  async function builderBlockToMdx(
466
963
  block: unknown,
467
964
  rawRef: string,
@@ -578,6 +1075,12 @@ async function builderBlockToMdx(
578
1075
  });
579
1076
  }
580
1077
 
1078
+ const children = childBlocks(block);
1079
+ if (children.length) {
1080
+ const childBody = await builderBlocksToMdxBody(children, ctx);
1081
+ if (childBody) return childBody;
1082
+ }
1083
+
581
1084
  const data: BuilderRawBlockData = {
582
1085
  ...raw,
583
1086
  summary: blockSummary(block),
@@ -586,7 +1089,6 @@ async function builderBlockToMdx(
586
1089
  id,
587
1090
  data,
588
1091
  });
589
- const children = childBlocks(block);
590
1092
  if (children.length) {
591
1093
  ctx.warnings.push(
592
1094
  `${name ?? "Unmodeled block"} has child blocks that are preserved only in the raw sidecar.`,
@@ -595,6 +1097,351 @@ async function builderBlockToMdx(
595
1097
  return serialized;
596
1098
  }
597
1099
 
1100
+ async function builderBlockToReadableMdx(
1101
+ block: unknown,
1102
+ rawRef: string,
1103
+ ctx: BlocksToMdxContext,
1104
+ ) {
1105
+ const name = componentName(block);
1106
+ const options = componentOptions(block);
1107
+
1108
+ if (name === "Text") {
1109
+ return htmlToMarkdown(String(options.text ?? "")).trim();
1110
+ }
1111
+
1112
+ if (name === "Code Block" || name === "Blog Code Block") {
1113
+ const language =
1114
+ typeof options.language === "string" ? options.language.trim() : "";
1115
+ const code = String(options.code ?? "").trimEnd();
1116
+ return code ? `\`\`\`${language}\n${code}\n\`\`\`` : "";
1117
+ }
1118
+
1119
+ if (name === "Image") {
1120
+ return builderImageMarkdown(options);
1121
+ }
1122
+
1123
+ if (name === "Tabbed Content") {
1124
+ const tabs = Array.isArray(options.tabs) ? options.tabs : [];
1125
+ const convertedTabs = await Promise.all(
1126
+ tabs.map(async (tab, tabIndex) => {
1127
+ const tabRecord = isRecord(tab) ? tab : {};
1128
+ const content = Array.isArray(tabRecord.content)
1129
+ ? tabRecord.content
1130
+ : [];
1131
+ const label =
1132
+ typeof tabRecord.label === "string" && tabRecord.label.trim()
1133
+ ? tabRecord.label.trim()
1134
+ : `Tab ${tabIndex + 1}`;
1135
+ const body = await builderBlocksToReadableMdxBody(content, ctx);
1136
+ return body ? `### ${label}\n\n${body}` : "";
1137
+ }),
1138
+ );
1139
+ return convertedTabs.filter(Boolean).join("\n\n").trim();
1140
+ }
1141
+
1142
+ const children = childBlocks(block);
1143
+ if (children.length) {
1144
+ const childBody = await builderBlocksToReadableMdxBody(children, ctx);
1145
+ if (childBody) return childBody;
1146
+ }
1147
+
1148
+ if (name) {
1149
+ ctx.warnings.push(
1150
+ `${name} is preserved in the Builder raw sidecar and shown as a read-only source component.`,
1151
+ );
1152
+ }
1153
+ const data: SourceComponentData = {
1154
+ provider: "builder",
1155
+ componentName: name || "Builder component",
1156
+ rawRef,
1157
+ rawHash: stableHash(block),
1158
+ sourceLabel: "Builder body",
1159
+ previewStatus: name ? "available" : "unavailable",
1160
+ title: name ? `Builder ${name}` : "Builder component",
1161
+ ...builderSourceComponentPreview(block),
1162
+ };
1163
+ const id = sourceComponentMarkerIdForBlock(block);
1164
+ return serializeRegistryBlockToMdx("source-component", { id, data });
1165
+ }
1166
+
1167
+ function markdownUnits(markdown: string) {
1168
+ const units: string[] = [];
1169
+ const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
1170
+ let current: string[] = [];
1171
+ let fence: string | null = null;
1172
+ const flush = () => {
1173
+ const value = current.join("\n").trim();
1174
+ if (value) units.push(value);
1175
+ current = [];
1176
+ };
1177
+
1178
+ for (const line of lines) {
1179
+ const fenceMatch = line.match(/^(```|~~~)/);
1180
+ if (fenceMatch) {
1181
+ current.push(line);
1182
+ fence = fence ? null : fenceMatch[1];
1183
+ if (!fence) flush();
1184
+ continue;
1185
+ }
1186
+ if (fence) {
1187
+ current.push(line);
1188
+ continue;
1189
+ }
1190
+ if (!line.trim()) {
1191
+ flush();
1192
+ continue;
1193
+ }
1194
+ current.push(line);
1195
+ }
1196
+ flush();
1197
+ return units;
1198
+ }
1199
+
1200
+ function fencedCodeFromMarkdown(markdown: string) {
1201
+ const trimmed = markdown.trim();
1202
+ const match = trimmed.match(/^```[^\n]*\n([\s\S]*?)\n```$/);
1203
+ if (match) return match[1];
1204
+ return trimmed;
1205
+ }
1206
+
1207
+ interface ReadableEditableSegment {
1208
+ kind: "text" | "code" | "image" | "tab-label";
1209
+ block: Record<string, unknown>;
1210
+ baseline: string;
1211
+ }
1212
+
1213
+ function readableCodeBlockMarkdown(options: Record<string, unknown>) {
1214
+ const language =
1215
+ typeof options.language === "string" ? options.language.trim() : "";
1216
+ const code = String(options.code ?? "").trimEnd();
1217
+ return code ? `\`\`\`${language}\n${code}\n\`\`\`` : "";
1218
+ }
1219
+
1220
+ function collectReadableEditableSegments(
1221
+ blocks: unknown[],
1222
+ segments: ReadableEditableSegment[] = [],
1223
+ ) {
1224
+ for (const block of blocks) {
1225
+ if (!isRecord(block)) continue;
1226
+ const name = componentName(block);
1227
+ const options = componentOptions(block);
1228
+ if (name === "Text") {
1229
+ const baseline = htmlToMarkdown(String(options.text ?? "")).trim();
1230
+ if (baseline) segments.push({ kind: "text", block, baseline });
1231
+ continue;
1232
+ }
1233
+ if (name === "Code Block" || name === "Blog Code Block") {
1234
+ const baseline = readableCodeBlockMarkdown(options);
1235
+ if (baseline) segments.push({ kind: "code", block, baseline });
1236
+ continue;
1237
+ }
1238
+ if (name === "Image") {
1239
+ const baseline = builderImageMarkdown(options);
1240
+ if (baseline) segments.push({ kind: "image", block, baseline });
1241
+ continue;
1242
+ }
1243
+ if (name === "Tabbed Content") {
1244
+ const tabs = Array.isArray(options.tabs) ? options.tabs : [];
1245
+ for (let index = 0; index < tabs.length; index += 1) {
1246
+ const tab = tabs[index];
1247
+ const tabRecord = isRecord(tab) ? tab : {};
1248
+ const content = Array.isArray(tabRecord.content)
1249
+ ? tabRecord.content
1250
+ : [];
1251
+ if (!content.some(builderBlockHasReadableOutput)) continue;
1252
+ const label =
1253
+ typeof tabRecord.label === "string" && tabRecord.label.trim()
1254
+ ? tabRecord.label.trim()
1255
+ : `Tab ${index + 1}`;
1256
+ segments.push({
1257
+ kind: "tab-label",
1258
+ block: tabRecord,
1259
+ baseline: `### ${label}`,
1260
+ });
1261
+ collectReadableEditableSegments(content, segments);
1262
+ }
1263
+ continue;
1264
+ }
1265
+ collectReadableEditableSegments(childBlocks(block), segments);
1266
+ }
1267
+ return segments;
1268
+ }
1269
+
1270
+ function builderBlockHasReadableOutput(block: unknown): boolean {
1271
+ if (!isRecord(block)) return false;
1272
+ const name = componentName(block);
1273
+ const options = componentOptions(block);
1274
+ if (name === "Text") {
1275
+ return htmlToMarkdown(String(options.text ?? "")).trim().length > 0;
1276
+ }
1277
+ if (name === "Code Block" || name === "Blog Code Block") {
1278
+ return readableCodeBlockMarkdown(options).trim().length > 0;
1279
+ }
1280
+ if (name === "Image") {
1281
+ return builderImageMarkdown(options).trim().length > 0;
1282
+ }
1283
+ if (name === "Tabbed Content") {
1284
+ const tabs = Array.isArray(options.tabs) ? options.tabs : [];
1285
+ return tabs.some((tab) => {
1286
+ const content =
1287
+ isRecord(tab) && Array.isArray(tab.content) ? tab.content : [];
1288
+ return content.some(builderBlockHasReadableOutput);
1289
+ });
1290
+ }
1291
+ const children = childBlocks(block);
1292
+ return children.length ? children.some(builderBlockHasReadableOutput) : true;
1293
+ }
1294
+
1295
+ function countExpectedReadableSourceComponentMarkers(
1296
+ blocks: unknown[],
1297
+ ): number {
1298
+ let count = 0;
1299
+ for (const block of blocks) {
1300
+ if (!isRecord(block)) continue;
1301
+ const name = componentName(block);
1302
+ if (
1303
+ name === "Text" ||
1304
+ name === "Code Block" ||
1305
+ name === "Blog Code Block" ||
1306
+ name === "Image"
1307
+ ) {
1308
+ continue;
1309
+ }
1310
+ const options = componentOptions(block);
1311
+ if (name === "Tabbed Content") {
1312
+ const tabs = Array.isArray(options.tabs) ? options.tabs : [];
1313
+ for (const tab of tabs) {
1314
+ const content =
1315
+ isRecord(tab) && Array.isArray(tab.content) ? tab.content : [];
1316
+ count += countExpectedReadableSourceComponentMarkers(content);
1317
+ }
1318
+ continue;
1319
+ }
1320
+ const children = childBlocks(block);
1321
+ if (children.length && children.some(builderBlockHasReadableOutput)) {
1322
+ count += countExpectedReadableSourceComponentMarkers(children);
1323
+ continue;
1324
+ }
1325
+ count += 1;
1326
+ }
1327
+ return count;
1328
+ }
1329
+
1330
+ export async function builderReadableBodyToBuilderBlocks(args: {
1331
+ localContent: string;
1332
+ losslessContent: string;
1333
+ sidecars: Record<string, string>;
1334
+ }): Promise<BuilderReadableBodyMergeResult> {
1335
+ const baselineBlocks = await builderMdxBodyToBuilderBlocks(
1336
+ args.losslessContent,
1337
+ args.sidecars,
1338
+ );
1339
+ const segments = collectReadableEditableSegments(baselineBlocks);
1340
+ const expectedSourceMarkerCount =
1341
+ countExpectedReadableSourceComponentMarkers(baselineBlocks);
1342
+ const currentSourceMarkerCount = countReadableSourceComponentMarkers(
1343
+ args.localContent,
1344
+ );
1345
+ if (expectedSourceMarkerCount !== currentSourceMarkerCount) {
1346
+ return {
1347
+ blocks: null,
1348
+ warnings: [
1349
+ `Readable Builder body changed preserved source component markers from ${expectedSourceMarkerCount} to ${currentSourceMarkerCount}; refresh or review in Builder before pushing.`,
1350
+ ],
1351
+ };
1352
+ }
1353
+ const sourceMarkerWarnings = await validateReadableSourceComponentMarkers(
1354
+ args.localContent,
1355
+ args.sidecars,
1356
+ );
1357
+ if (sourceMarkerWarnings.length) {
1358
+ return {
1359
+ blocks: null,
1360
+ warnings: sourceMarkerWarnings,
1361
+ };
1362
+ }
1363
+ const [expectedLayout, currentLayout] = await Promise.all([
1364
+ expectedReadableLayoutFingerprint(baselineBlocks),
1365
+ readableLayoutFingerprint(args.localContent),
1366
+ ]);
1367
+ if (expectedLayout.join("\n") !== currentLayout.join("\n")) {
1368
+ return {
1369
+ blocks: null,
1370
+ warnings: [
1371
+ "Readable Builder body changed structure or moved or restructured preserved source component markers; refresh or review in Builder before pushing.",
1372
+ ],
1373
+ };
1374
+ }
1375
+ if (!segments.length) {
1376
+ return { blocks: baselineBlocks, warnings: [] };
1377
+ }
1378
+
1379
+ const baselineUnitCounts = segments.map(
1380
+ (segment) => markdownUnits(segment.baseline).length,
1381
+ );
1382
+ const currentUnits = markdownUnits(args.localContent).filter(
1383
+ (unit) => !isReadableUnsupportedBuilderPlaceholder(unit),
1384
+ );
1385
+ const expectedUnitCount = baselineUnitCounts.reduce(
1386
+ (sum, count) => sum + count,
1387
+ 0,
1388
+ );
1389
+ if (currentUnits.length !== expectedUnitCount) {
1390
+ return {
1391
+ blocks: null,
1392
+ warnings: [
1393
+ `Readable Builder body changed structure from ${expectedUnitCount} markdown blocks to ${currentUnits.length}; review in Builder before pushing.`,
1394
+ ],
1395
+ };
1396
+ }
1397
+
1398
+ let offset = 0;
1399
+ for (let index = 0; index < segments.length; index += 1) {
1400
+ const segment = segments[index];
1401
+ const count = baselineUnitCounts[index];
1402
+ const nextMarkdown = currentUnits
1403
+ .slice(offset, offset + count)
1404
+ .join("\n\n");
1405
+ offset += count;
1406
+ if (segment.kind === "tab-label") {
1407
+ const match = nextMarkdown.trim().match(/^#{3}\s+(.+)$/);
1408
+ if (!match?.[1]?.trim()) {
1409
+ return {
1410
+ blocks: null,
1411
+ warnings: [
1412
+ "Readable Builder tab heading changed into unsupported markdown; keep it as a level-3 heading before pushing.",
1413
+ ],
1414
+ };
1415
+ }
1416
+ segment.block.label = match[1].trim();
1417
+ } else if (segment.kind === "text") {
1418
+ const options = ensureComponentOptions(segment.block, "Text");
1419
+ options.text = markdownToBuilderTextHtml(nextMarkdown);
1420
+ } else if (segment.kind === "image") {
1421
+ const options = ensureComponentOptions(segment.block, "Image");
1422
+ const image = parseMarkdownImage(nextMarkdown);
1423
+ if (!image?.src) {
1424
+ return {
1425
+ blocks: null,
1426
+ warnings: [
1427
+ "Readable Builder image changed into unsupported markdown; keep it as a standard image block before pushing.",
1428
+ ],
1429
+ };
1430
+ }
1431
+ options.image = image.src;
1432
+ options.altText = image.alt;
1433
+ } else {
1434
+ const options = ensureComponentOptions(
1435
+ segment.block,
1436
+ componentName(segment.block) ?? "Code Block",
1437
+ );
1438
+ options.code = fencedCodeFromMarkdown(nextMarkdown);
1439
+ }
1440
+ }
1441
+
1442
+ return { blocks: baselineBlocks, warnings: [] };
1443
+ }
1444
+
598
1445
  export async function builderEntryToMdxBundle(
599
1446
  entry: BuilderContentEntry,
600
1447
  ): Promise<BuilderMdxBundle> {
@@ -611,18 +1458,37 @@ export async function builderEntryToMdxBundle(
611
1458
  return { mdx, files, blocks: builderEntryBlocks(entry) };
612
1459
  }
613
1460
 
1461
+ export async function builderEntryToReadableMdxBundle(
1462
+ entry: BuilderContentEntry,
1463
+ ): Promise<BuilderMdxBundle> {
1464
+ const files: Record<string, string> = {};
1465
+ const warnings: string[] = [];
1466
+ const emitted = new Set<string>();
1467
+ const mdx = await emitBuilderEntryToMdxFile({
1468
+ entry,
1469
+ files,
1470
+ warnings,
1471
+ emitted,
1472
+ symbolPath: false,
1473
+ readableBody: true,
1474
+ });
1475
+ return { mdx, files, blocks: builderEntryBlocks(entry) };
1476
+ }
1477
+
614
1478
  async function emitBuilderEntryToMdxFile({
615
1479
  entry,
616
1480
  files,
617
1481
  warnings,
618
1482
  emitted,
619
1483
  symbolPath,
1484
+ readableBody = false,
620
1485
  }: {
621
1486
  entry: BuilderContentEntry;
622
1487
  files: Record<string, string>;
623
1488
  warnings: string[];
624
1489
  emitted: Set<string>;
625
1490
  symbolPath: boolean;
1491
+ readableBody?: boolean;
626
1492
  }): Promise<BuilderMdxFile> {
627
1493
  const path = symbolPath
628
1494
  ? builderSymbolMdxPathForEntry(entry)
@@ -663,11 +1529,14 @@ async function emitBuilderEntryToMdxFile({
663
1529
  warnings,
664
1530
  emitted,
665
1531
  symbolPath: true,
1532
+ readableBody,
666
1533
  });
667
1534
  return nested.path;
668
1535
  },
669
1536
  };
670
- const body = await builderBlocksToMdxBody(blocks, ctx);
1537
+ const body = readableBody
1538
+ ? await builderBlocksToReadableMdxBody(blocks, ctx)
1539
+ : await builderBlocksToMdxBody(blocks, ctx);
671
1540
  const blocksHash = builderBlocksHash(blocks);
672
1541
  const sourceHash = builderSourceHash(entry);
673
1542
  const frontmatter = frontmatterForEntry({
@@ -829,6 +1698,30 @@ function freshTextBlock(markdown: string): unknown {
829
1698
  };
830
1699
  }
831
1700
 
1701
+ function freshImageBlock(markdown: string): unknown | null {
1702
+ const image = parseMarkdownImage(markdown);
1703
+ if (!image?.src) return null;
1704
+ return {
1705
+ "@type": "@builder.io/sdk:Element",
1706
+ "@version": 2,
1707
+ id: `builder-mdx-image-${stableHash(markdown).slice(0, 16)}`,
1708
+ component: {
1709
+ name: "Image",
1710
+ options: {
1711
+ image: image.src,
1712
+ altText: image.alt,
1713
+ },
1714
+ },
1715
+ responsiveStyles: {
1716
+ large: {
1717
+ display: "flex",
1718
+ flexDirection: "column",
1719
+ position: "relative",
1720
+ },
1721
+ },
1722
+ };
1723
+ }
1724
+
832
1725
  function rawBlockForData(
833
1726
  data: BuilderRawRefData,
834
1727
  sidecars: Record<string, string>,
@@ -948,7 +1841,7 @@ async function applyTabbedContentData(
948
1841
  return {
949
1842
  ...rawTab,
950
1843
  label: tab.label,
951
- content: await builderBodyToBlocks(tab.body, sidecars),
1844
+ content: await builderMdxBodyToBuilderBlocks(tab.body, sidecars),
952
1845
  };
953
1846
  }),
954
1847
  );
@@ -1010,12 +1903,14 @@ async function blockFromMdxComponent(
1010
1903
  return applySymbolData(parsed.data as BuilderSymbolData, sidecars);
1011
1904
  case "builder-raw-block":
1012
1905
  return rawBlockForData(parsed.data as BuilderRawBlockData, sidecars);
1906
+ case "source-component":
1907
+ return rawBlockForData(parsed.data as SourceComponentData, sidecars);
1013
1908
  default:
1014
1909
  return null;
1015
1910
  }
1016
1911
  }
1017
1912
 
1018
- async function builderBodyToBlocks(
1913
+ export async function builderMdxBodyToBuilderBlocks(
1019
1914
  body: string,
1020
1915
  sidecars: Record<string, string>,
1021
1916
  ) {
@@ -1042,6 +1937,11 @@ async function builderBodyToBlocks(
1042
1937
  "Unsupported Builder MDX syntax: import/export statements cannot be pushed to Builder.",
1043
1938
  );
1044
1939
  }
1940
+ const imageBlock = freshImageBlock(raw);
1941
+ if (imageBlock) {
1942
+ blocks.push(imageBlock);
1943
+ continue;
1944
+ }
1045
1945
  blocks.push(freshTextBlock(raw));
1046
1946
  }
1047
1947
  return blocks;
@@ -1053,7 +1953,7 @@ export async function builderMdxToBuilderBlocks(args: {
1053
1953
  sidecars: Record<string, string>;
1054
1954
  }): Promise<BuilderBlocksFromMdxResult> {
1055
1955
  const mdx = parseBuilderMdxFile(args.path, args.source);
1056
- const blocks = await builderBodyToBlocks(mdx.body, args.sidecars);
1956
+ const blocks = await builderMdxBodyToBuilderBlocks(mdx.body, args.sidecars);
1057
1957
  const blocksHash = builderBlocksHash(blocks);
1058
1958
  const sourceHash = stableHash({
1059
1959
  model: mdx.metadata.model,