@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,562 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ import type { InspectCodeData } from "../EditPanel";
4
+
5
+ const VOID_HTML_TAGS = new Set([
6
+ "area",
7
+ "base",
8
+ "br",
9
+ "col",
10
+ "embed",
11
+ "hr",
12
+ "img",
13
+ "input",
14
+ "link",
15
+ "meta",
16
+ "param",
17
+ "source",
18
+ "track",
19
+ "wbr",
20
+ ]);
21
+
22
+ export function normalizedElementTagName(
23
+ tagName: string | null | undefined,
24
+ ): string {
25
+ return tagName?.trim().toLowerCase() || "element";
26
+ }
27
+
28
+ /** Build a `vscode://file/...` deep link for an absolute path + position. */
29
+ export function vscodeDeepLink(
30
+ absolutePath: string,
31
+ line?: number,
32
+ column?: number,
33
+ ): string {
34
+ const base = `vscode://file/${absolutePath}`;
35
+ if (line == null) return base;
36
+ return column == null ? `${base}:${line}` : `${base}:${line}:${column}`;
37
+ }
38
+
39
+ /**
40
+ * Extract the *opening tag* from an element's outer HTML for an at-a-glance
41
+ * summary (e.g. `<main class="hero" data-x="y">`). Self-closing tags keep
42
+ * their `/>`. Returns `null` when no tag can be parsed.
43
+ *
44
+ * Pure — exported for tests.
45
+ */
46
+ export function openingTagOf(html: string | null | undefined): string | null {
47
+ if (!html) return null;
48
+ const trimmed = html.trimStart();
49
+ // Match the first `<tag ...>` (greedy up to the first unquoted `>`), allowing
50
+ // quoted attribute values to contain `>`.
51
+ const match = /^<([a-zA-Z][\w-]*)((?:"[^"]*"|'[^']*'|[^>])*?)\/?>/.exec(
52
+ trimmed,
53
+ );
54
+ if (!match) return null;
55
+ return match[0];
56
+ }
57
+
58
+ /**
59
+ * Collapse long attribute values in an opening tag so the at-a-glance summary
60
+ * stays readable. Values longer than `max` chars are truncated with an
61
+ * ellipsis (the surrounding quotes are preserved).
62
+ *
63
+ * Pure — exported for tests.
64
+ */
65
+ export function truncateOpeningTag(openTag: string, max = 32): string {
66
+ return openTag.replace(
67
+ /("|')((?:\\.|(?!\1)[^\\])*)\1/g,
68
+ (full, quote, value) => {
69
+ if (typeof value !== "string" || value.length <= max) return full;
70
+ return `${quote}${value.slice(0, max - 1)}…${quote}`;
71
+ },
72
+ );
73
+ }
74
+
75
+ function tagNameFromOpeningTag(openTag: string): string | null {
76
+ const match = /^<\/?\s*([a-zA-Z][\w:-]*)/.exec(openTag.trim());
77
+ return match?.[1]?.toLowerCase() ?? null;
78
+ }
79
+
80
+ function isSelfClosingOpeningTag(openTag: string, tagName: string): boolean {
81
+ return /\/>\s*$/.test(openTag) || VOID_HTML_TAGS.has(tagName);
82
+ }
83
+
84
+ function escapeHtmlAttribute(value: string): string {
85
+ return value
86
+ .replace(/&/g, "&amp;")
87
+ .replace(/"/g, "&quot;")
88
+ .replace(/</g, "&lt;")
89
+ .replace(/>/g, "&gt;");
90
+ }
91
+
92
+ function fallbackOpeningTag(
93
+ data: Pick<InspectCodeData, "tagName" | "id" | "classes">,
94
+ ) {
95
+ const tag = normalizedElementTagName(data.tagName);
96
+ const attrs: string[] = [];
97
+ const id = data.id?.trim();
98
+ const classes = data.classes?.map((item) => item.trim()).filter(Boolean);
99
+ if (id) attrs.push(`id="${escapeHtmlAttribute(id)}"`);
100
+ if (classes?.length) {
101
+ attrs.push(`class="${escapeHtmlAttribute(classes.join(" "))}"`);
102
+ }
103
+ return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>`;
104
+ }
105
+
106
+ export function elementHtmlPreview(
107
+ data: Pick<InspectCodeData, "html" | "tagName" | "id" | "classes">,
108
+ ): string | null {
109
+ const openingTag = openingTagOf(data.html);
110
+ const hasFallbackMetadata = Boolean(
111
+ data.tagName?.trim() ||
112
+ data.id?.trim() ||
113
+ data.classes?.some((item) => item.trim()),
114
+ );
115
+ if (!openingTag && !hasFallbackMetadata) return null;
116
+ const previewOpeningTag = openingTag ?? fallbackOpeningTag(data);
117
+ const tagName =
118
+ tagNameFromOpeningTag(previewOpeningTag) ??
119
+ normalizedElementTagName(data.tagName);
120
+ if (isSelfClosingOpeningTag(previewOpeningTag, tagName)) {
121
+ return previewOpeningTag;
122
+ }
123
+ return `${previewOpeningTag}\n ...\n</${tagName}>`;
124
+ }
125
+
126
+ type HtmlTokenKind = "plain" | "punctuation" | "tag" | "attribute" | "value";
127
+
128
+ interface HtmlToken {
129
+ text: string;
130
+ kind: HtmlTokenKind;
131
+ }
132
+
133
+ function tokenizeHtmlAttributes(source: string): HtmlToken[] {
134
+ const tokens: HtmlToken[] = [];
135
+ const attrPattern =
136
+ /(\s+)([^\s=/>]+)(?:\s*(=)\s*("[^"]*"|'[^']*'|[^\s"'=<>`]+))?/g;
137
+ let cursor = 0;
138
+ for (const match of source.matchAll(attrPattern)) {
139
+ const index = match.index ?? 0;
140
+ if (index > cursor) {
141
+ tokens.push({ text: source.slice(cursor, index), kind: "plain" });
142
+ }
143
+ tokens.push({ text: match[1] ?? "", kind: "plain" });
144
+ tokens.push({ text: match[2] ?? "", kind: "attribute" });
145
+ if (match[3]) tokens.push({ text: match[3], kind: "punctuation" });
146
+ if (match[4]) tokens.push({ text: match[4], kind: "value" });
147
+ cursor = index + match[0].length;
148
+ }
149
+ if (cursor < source.length) {
150
+ tokens.push({ text: source.slice(cursor), kind: "plain" });
151
+ }
152
+ return tokens;
153
+ }
154
+
155
+ function tokenizeHtmlTag(source: string): HtmlToken[] {
156
+ const match = /^(<\/?)([a-zA-Z][\w:-]*)([\s\S]*?)(\/?>)$/.exec(source);
157
+ if (!match) return [{ text: source, kind: "plain" }];
158
+ return [
159
+ { text: match[1] ?? "", kind: "punctuation" },
160
+ { text: match[2] ?? "", kind: "tag" },
161
+ ...tokenizeHtmlAttributes(match[3] ?? ""),
162
+ { text: match[4] ?? "", kind: "punctuation" },
163
+ ];
164
+ }
165
+
166
+ function tokenizeHtml(source: string): HtmlToken[] {
167
+ const tokens: HtmlToken[] = [];
168
+ const tagPattern = /<\/?[a-zA-Z][\w:-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g;
169
+ let cursor = 0;
170
+ for (const match of source.matchAll(tagPattern)) {
171
+ const index = match.index ?? 0;
172
+ if (index > cursor) {
173
+ tokens.push({ text: source.slice(cursor, index), kind: "plain" });
174
+ }
175
+ tokens.push(...tokenizeHtmlTag(match[0]));
176
+ cursor = index + match[0].length;
177
+ }
178
+ if (cursor < source.length) {
179
+ tokens.push({ text: source.slice(cursor), kind: "plain" });
180
+ }
181
+ return tokens;
182
+ }
183
+
184
+ function htmlTokenClassName(kind: HtmlTokenKind): string {
185
+ switch (kind) {
186
+ case "punctuation":
187
+ return "text-muted-foreground/70";
188
+ case "tag":
189
+ return "text-[var(--design-editor-accent-color)]";
190
+ case "attribute":
191
+ return "text-foreground/90";
192
+ case "value":
193
+ return "text-[var(--design-editor-measure-color)]";
194
+ default:
195
+ return "text-muted-foreground";
196
+ }
197
+ }
198
+
199
+ export function highlightedHtml(source: string): ReactNode {
200
+ return tokenizeHtml(source).map((token, index) => (
201
+ <span
202
+ key={`${index}:${token.kind}`}
203
+ className={htmlTokenClassName(token.kind)}
204
+ >
205
+ {token.text}
206
+ </span>
207
+ ));
208
+ }
209
+
210
+ /**
211
+ * Parse the top-level `key: value` pairs from an Alpine `x-data` object literal
212
+ * (e.g. `{ variant: 'outline', size: 'lg', disabled: false }`).
213
+ *
214
+ * Best-effort: only handles a flat object of simple string / boolean / number
215
+ * literals — exactly the shape used for component variant + state props. Nested
216
+ * objects, methods, and computed expressions are ignored. Returns `null` when
217
+ * the value is not a recognizable flat object literal.
218
+ *
219
+ * Pure — exported for tests.
220
+ */
221
+ export function parseAlpineDataObject(
222
+ xData: string | null | undefined,
223
+ ): Record<string, string> | null {
224
+ if (!xData) return null;
225
+ const trimmed = xData.trim();
226
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
227
+ const inner = trimmed.slice(1, -1).trim();
228
+ if (!inner) return {};
229
+
230
+ const out: Record<string, string> = {};
231
+ // Split on top-level commas only (no nesting / quotes inside values here).
232
+ const pairRe =
233
+ /(?:^|,)\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_$][\w$]*))\s*:\s*('[^']*'|"[^"]*"|true|false|-?\d+(?:\.\d+)?)/g;
234
+ let m: RegExpExecArray | null;
235
+ let matched = false;
236
+ while ((m = pairRe.exec(inner)) !== null) {
237
+ matched = true;
238
+ const key = m[1] ?? m[2] ?? m[3];
239
+ let raw = m[4]!;
240
+ // Unwrap quotes for string literals; keep booleans / numbers verbatim.
241
+ if (
242
+ (raw.startsWith("'") && raw.endsWith("'")) ||
243
+ (raw.startsWith('"') && raw.endsWith('"'))
244
+ ) {
245
+ raw = raw.slice(1, -1);
246
+ }
247
+ if (key) out[key] = raw;
248
+ }
249
+ // If there was content but nothing parsed, the shape is too complex to edit
250
+ // safely — bail so the caller falls back to attribute-based prop edits.
251
+ if (!matched) return null;
252
+ return out;
253
+ }
254
+
255
+ /**
256
+ * Re-serialize a flat Alpine data object back into an `x-data` literal,
257
+ * preserving boolean / number literals unquoted and single-quoting strings.
258
+ *
259
+ * Pure — exported for tests.
260
+ */
261
+ export function serializeAlpineDataObject(obj: Record<string, string>): string {
262
+ const parts = Object.entries(obj).map(([key, value]) => {
263
+ const isBoolean = value === "true" || value === "false";
264
+ const isNumber = /^-?\d+(\.\d+)?$/.test(value);
265
+ const literal =
266
+ isBoolean || isNumber ? value : `'${value.replace(/'/g, "\\'")}'`;
267
+ return `${key}: ${literal}`;
268
+ });
269
+ return parts.length ? `{ ${parts.join(", ")} }` : "{}";
270
+ }
271
+
272
+ /**
273
+ * Format a single editable prop value as an `x-data` literal: bare for
274
+ * boolean / number values, single-quoted (with escaping) for strings.
275
+ *
276
+ * Pure — exported for tests.
277
+ */
278
+ export function alpineDataValueLiteral(value: string): string {
279
+ const isBoolean = value === "true" || value === "false";
280
+ const isNumber = /^-?\d+(\.\d+)?$/.test(value);
281
+ return isBoolean || isNumber ? value : `'${value.replace(/'/g, "\\'")}'`;
282
+ }
283
+
284
+ /**
285
+ * Surgically replace a single top-level key's value inside an Alpine `x-data`
286
+ * object literal, preserving everything else byte-for-byte — methods
287
+ * (`toggle() { … }`), nested objects, escaped strings, quoted keys, comments,
288
+ * and whitespace are all left untouched.
289
+ *
290
+ * Unlike a `parseAlpineDataObject` → mutate → `serializeAlpineDataObject`
291
+ * round-trip (which only understands a flat object of simple literals and so
292
+ * *drops* anything it can't model), this walks the original string, finds the
293
+ * `key:` token at the top level (depth 0, not inside a string/comment), and
294
+ * rewrites only the value literal that immediately follows it.
295
+ *
296
+ * Returns `null` when the key cannot be located surgically (e.g. the value is
297
+ * an expression/function/object rather than a simple string/boolean/number, or
298
+ * the literal isn't a `{ … }` object) so the caller can fail safe instead of
299
+ * persisting a lossy rewrite.
300
+ *
301
+ * Pure — exported for tests.
302
+ */
303
+ export function replaceAlpineDataKeyValue(
304
+ xData: string | null | undefined,
305
+ key: string,
306
+ nextValue: string,
307
+ ): string | null {
308
+ if (!xData) return null;
309
+ const trimmed = xData.trim();
310
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
311
+
312
+ const s = xData;
313
+ const n = s.length;
314
+ // Walk the whole string tracking nesting depth and skipping over strings,
315
+ // template literals, regex-ish slashes are not handled (Alpine x-data does
316
+ // not use them at the object-key level), and line / block comments. Only at
317
+ // object depth 1 (directly inside the outermost `{ … }`) do we look for the
318
+ // target `key :` token.
319
+ let depth = 0;
320
+ let i = 0;
321
+
322
+ /** Advance `i` past a quoted string starting at `i` (handles escapes). */
323
+ const skipString = (quote: string): void => {
324
+ i += 1; // opening quote
325
+ while (i < n) {
326
+ const c = s[i];
327
+ if (c === "\\") {
328
+ i += 2;
329
+ continue;
330
+ }
331
+ if (c === quote) {
332
+ i += 1;
333
+ return;
334
+ }
335
+ i += 1;
336
+ }
337
+ };
338
+
339
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
340
+ // Bare identifier key at a token boundary: `key` then optional ws then `:`.
341
+ const bareRe = new RegExp(`^(${escapedKey})(\\s*:\\s*)`);
342
+ // Quoted key: `'key'` or `"key"` then optional ws then `:`.
343
+ const quotedRe = new RegExp(`^(['"]${escapedKey}['"])(\\s*:\\s*)`);
344
+
345
+ while (i < n) {
346
+ const c = s[i];
347
+
348
+ // At the top level, a `{` / `,` (or the string start) opens a fresh value
349
+ // slot. Try to match the target key here *before* treating a quote as an
350
+ // opaque string — this is how quoted keys (`'size': …`) are recognised.
351
+ if (depth === 1) {
352
+ const prev = lastNonSpaceBefore(s, i);
353
+ if (prev === "{" || prev === ",") {
354
+ const rest = s.slice(i);
355
+ const m = bareRe.exec(rest) ?? quotedRe.exec(rest);
356
+ if (m) {
357
+ const valueStart = i + m[1].length + m[2].length;
358
+ const valueEnd = simpleValueEnd(s, valueStart);
359
+ if (valueEnd === null) return null; // value is not a simple literal
360
+ return (
361
+ s.slice(0, valueStart) +
362
+ alpineDataValueLiteral(nextValue) +
363
+ s.slice(valueEnd)
364
+ );
365
+ }
366
+ }
367
+ }
368
+
369
+ // Skip strings / template literals wholesale.
370
+ if (c === '"' || c === "'" || c === "`") {
371
+ skipString(c);
372
+ continue;
373
+ }
374
+ // Skip comments.
375
+ if (c === "/" && s[i + 1] === "/") {
376
+ i += 2;
377
+ while (i < n && s[i] !== "\n") i += 1;
378
+ continue;
379
+ }
380
+ if (c === "/" && s[i + 1] === "*") {
381
+ i += 2;
382
+ while (i < n && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
383
+ i += 2;
384
+ continue;
385
+ }
386
+
387
+ if (c === "{" || c === "[" || c === "(") {
388
+ depth += 1;
389
+ i += 1;
390
+ continue;
391
+ }
392
+ if (c === "}" || c === "]" || c === ")") {
393
+ depth -= 1;
394
+ i += 1;
395
+ continue;
396
+ }
397
+
398
+ i += 1;
399
+ }
400
+
401
+ return null;
402
+ }
403
+
404
+ /** Last non-whitespace char strictly before index `i` (or `""`). */
405
+ function lastNonSpaceBefore(s: string, i: number): string {
406
+ let j = i - 1;
407
+ while (j >= 0 && /\s/.test(s[j]!)) j -= 1;
408
+ return j >= 0 ? s[j]! : "";
409
+ }
410
+
411
+ /**
412
+ * Given the start index of a value in an `x-data` literal, return the index
413
+ * just past a *simple* literal value (single/double-quoted string with
414
+ * escapes, boolean, or number). Returns `null` when the value is anything else
415
+ * (an expression, function, object, array, template literal, etc.) so the
416
+ * caller can fail safe rather than mangle it.
417
+ */
418
+ function simpleValueEnd(s: string, start: number): number | null {
419
+ const c = s[start];
420
+ if (c === "'" || c === '"') {
421
+ let i = start + 1;
422
+ while (i < s.length) {
423
+ if (s[i] === "\\") {
424
+ i += 2;
425
+ continue;
426
+ }
427
+ if (s[i] === c) return i + 1;
428
+ i += 1;
429
+ }
430
+ return null; // unterminated string
431
+ }
432
+ // Boolean / number: read the bare token, then confirm it is exactly one.
433
+ const m = /^[A-Za-z0-9_.+-]+/.exec(s.slice(start));
434
+ if (!m) return null;
435
+ const token = m[0];
436
+ const isBoolean = token === "true" || token === "false";
437
+ const isNumber = /^-?\d+(\.\d+)?$/.test(token);
438
+ if (!isBoolean && !isNumber) return null;
439
+ return start + token.length;
440
+ }
441
+
442
+ /**
443
+ * True when an `x-data` literal can be rebuilt from its flat parsed map with
444
+ * no loss — i.e. there is nothing richer than the simple `key: literal` pairs
445
+ * that `serializeAlpineDataObject` already round-trips. Used as the gate for
446
+ * falling back to a full rebuild when a surgical single-key replace is not
447
+ * possible (e.g. when adding a brand-new key).
448
+ *
449
+ * Returns `true` for an empty / absent literal (nothing to lose) and for a
450
+ * flat object whose `parse → serialize` round-trip is semantically stable.
451
+ * Returns `false` when the original holds methods, nested objects, comments,
452
+ * or expressions that a rebuild would silently drop.
453
+ *
454
+ * Pure — exported for tests.
455
+ */
456
+ export function canRebuildAlpineDataLosslessly(
457
+ xData: string | null | undefined,
458
+ ): boolean {
459
+ const trimmed = (xData ?? "").trim();
460
+ // No object literal at all → there is nothing richer to preserve.
461
+ if (!trimmed || trimmed === "{}" || trimmed === "{ }") return true;
462
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return false;
463
+
464
+ const parsed = parseAlpineDataObject(trimmed);
465
+ if (!parsed) return false;
466
+
467
+ // Re-serialize and re-parse; if the round-trip is stable AND the parsed map
468
+ // accounts for every top-level key actually present in the original, a
469
+ // rebuild loses nothing.
470
+ const reserialized = serializeAlpineDataObject(parsed);
471
+ const reparsed = parseAlpineDataObject(reserialized);
472
+ if (!reparsed) return false;
473
+ const keysA = Object.keys(parsed).sort().join(",");
474
+ const keysB = Object.keys(reparsed).sort().join(",");
475
+ if (keysA !== keysB) return false;
476
+
477
+ // Guard against dropped content the flat parser ignores (e.g. a trailing
478
+ // method): the number of top-level `key:` tokens in the original must match
479
+ // the number of parsed keys. Count top-level `:` separators conservatively.
480
+ return countTopLevelKeys(trimmed) === Object.keys(parsed).length;
481
+ }
482
+
483
+ /**
484
+ * Count top-level `key:` entries in an `x-data` object literal, skipping
485
+ * strings, comments, and nested braces/brackets/parens. A method like
486
+ * `toggle() { … }` is counted as a key too (its `:`-less form still occupies a
487
+ * top-level slot), so a mismatch against the flat parser's key count reveals
488
+ * dropped content.
489
+ */
490
+ function countTopLevelKeys(xData: string): number {
491
+ const s = xData;
492
+ const n = s.length;
493
+ let depth = 0;
494
+ let i = 0;
495
+ let count = 0;
496
+ let sawTokenInSlot = false;
497
+
498
+ const skipString = (quote: string): void => {
499
+ i += 1;
500
+ while (i < n) {
501
+ if (s[i] === "\\") {
502
+ i += 2;
503
+ continue;
504
+ }
505
+ if (s[i] === quote) {
506
+ i += 1;
507
+ return;
508
+ }
509
+ i += 1;
510
+ }
511
+ };
512
+
513
+ while (i < n) {
514
+ const c = s[i]!;
515
+ if (c === '"' || c === "'" || c === "`") {
516
+ if (depth === 1) sawTokenInSlot = true;
517
+ skipString(c);
518
+ continue;
519
+ }
520
+ if (c === "/" && s[i + 1] === "/") {
521
+ i += 2;
522
+ while (i < n && s[i] !== "\n") i += 1;
523
+ continue;
524
+ }
525
+ if (c === "/" && s[i + 1] === "*") {
526
+ i += 2;
527
+ while (i < n && !(s[i] === "*" && s[i + 1] === "/")) i += 1;
528
+ i += 2;
529
+ continue;
530
+ }
531
+ if (c === "{" || c === "[" || c === "(") {
532
+ depth += 1;
533
+ i += 1;
534
+ continue;
535
+ }
536
+ if (c === "}" || c === "]" || c === ")") {
537
+ if (depth === 1 && c === "}" && sawTokenInSlot) {
538
+ count += 1;
539
+ sawTokenInSlot = false;
540
+ }
541
+ depth -= 1;
542
+ i += 1;
543
+ continue;
544
+ }
545
+ if (depth === 1) {
546
+ if (c === ",") {
547
+ if (sawTokenInSlot) count += 1;
548
+ sawTokenInSlot = false;
549
+ } else if (!/\s/.test(c)) {
550
+ sawTokenInSlot = true;
551
+ }
552
+ }
553
+ i += 1;
554
+ }
555
+ return count;
556
+ }
557
+
558
+ /** A boolean-ish prop value (`"true"` / `"false"`), case-insensitive. */
559
+ export function isBooleanPropValue(value: string): boolean {
560
+ const v = value.trim().toLowerCase();
561
+ return v === "true" || v === "false";
562
+ }