@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
- package/corpus/core/src/client/use-action.ts +39 -2
- package/corpus/core/src/client/use-db-sync.ts +44 -18
- package/corpus/core/src/db/create-get-db.ts +32 -2
- package/corpus/core/src/server/framework-request-handler.ts +52 -4
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
- package/corpus/templates/assets/AGENTS.md +23 -4
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
- package/corpus/templates/assets/actions/_helpers.ts +8 -1
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
- package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
- package/corpus/templates/assets/actions/generate-image.ts +342 -25
- package/corpus/templates/assets/actions/get-library.ts +4 -3
- package/corpus/templates/assets/actions/navigate.ts +2 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
- package/corpus/templates/assets/actions/update-asset.ts +1 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
- package/corpus/templates/assets/actions/view-screen.ts +8 -1
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
- package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/assets/app/i18n-data.ts +650 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
- package/corpus/templates/assets/app/routes/library.tsx +43 -1
- package/corpus/templates/assets/server/lib/generation.ts +366 -44
- package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
- package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
- package/corpus/templates/assets/shared/api.ts +47 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
- package/corpus/templates/clips/app/routes/record.tsx +157 -68
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
- package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
- package/corpus/templates/content/AGENTS.md +52 -52
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
- package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
- package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
- package/corpus/templates/content/actions/_database-utils.ts +44 -4
- package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
- package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
- package/corpus/templates/content/actions/delete-content-database.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +11 -2
- package/corpus/templates/content/actions/list-content-databases.ts +57 -9
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
- package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
- package/corpus/templates/content/actions/restore-content-database.ts +1 -0
- package/corpus/templates/content/actions/update-document.ts +43 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
- package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
- package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
- package/corpus/templates/content/app/global.css +41 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
- package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
- package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
- package/corpus/templates/content/app/i18n-data.ts +355 -0
- package/corpus/templates/content/app/root.tsx +22 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
- package/corpus/templates/content/shared/api.ts +13 -0
- package/corpus/templates/content/shared/builder-mdx.ts +16 -3
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
- package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
- package/corpus/templates/design/AGENTS.md +9 -2
- package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
- package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
- package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
- package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
- package/corpus/templates/design/actions/create-component.ts +29 -60
- package/corpus/templates/design/actions/create-file.ts +9 -2
- package/corpus/templates/design/actions/duplicate-design.ts +31 -2
- package/corpus/templates/design/actions/edit-design.ts +36 -38
- package/corpus/templates/design/actions/generate-design-title.ts +87 -0
- package/corpus/templates/design/actions/generate-design.ts +53 -18
- package/corpus/templates/design/actions/generate-screens.ts +108 -4
- package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
- package/corpus/templates/design/actions/insert-asset.ts +34 -29
- package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
- package/corpus/templates/design/actions/present-design-variants.ts +7 -2
- package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
- package/corpus/templates/design/actions/update-file.ts +190 -75
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
- package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
- package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
- package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
- package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
- package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
- package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
- package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
- package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
- package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
- package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
- package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
- package/corpus/templates/design/app/components/design/types.ts +15 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
- package/corpus/templates/design/app/global.css +8 -8
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
- package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
- package/corpus/templates/design/app/i18n-data.ts +1664 -152
- package/corpus/templates/design/app/i18n-motion.ts +563 -0
- package/corpus/templates/design/app/lib/design-import.ts +73 -0
- package/corpus/templates/design/app/pages/Index.tsx +41 -22
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
- package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
- package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
- package/corpus/templates/design/server/source-workspace.ts +139 -59
- package/corpus/templates/design/shared/board-file.ts +11 -2
- package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
- package/corpus/templates/design/shared/canvas-math.ts +354 -35
- package/corpus/templates/design/shared/code-layer.ts +277 -8
- package/corpus/templates/design/shared/design-state.ts +11 -4
- package/corpus/templates/design/shared/interaction-states.ts +548 -0
- package/corpus/templates/design/shared/motion-compiler.ts +186 -23
- package/corpus/templates/design/shared/motion-easing.ts +294 -0
- package/corpus/templates/design/shared/motion-timeline.ts +400 -34
- package/corpus/templates/design/shared/prompt-title.ts +95 -0
- package/corpus/templates/design/shared/responsive-classes.ts +448 -10
- package/corpus/templates/design/shared/screen-annotation.ts +46 -0
- package/corpus/templates/design/shared/shader-fills.ts +1009 -0
- package/corpus/templates/design/shared/shader-presets.ts +852 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
- package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
- package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/use-action.d.ts +3 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +34 -3
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +4 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +37 -18
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/create-get-db.d.ts +25 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +25 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +37 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
- 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, "&")
|
|
87
|
+
.replace(/"/g, """)
|
|
88
|
+
.replace(/</g, "<")
|
|
89
|
+
.replace(/>/g, ">");
|
|
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
|
+
}
|