@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,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed element interaction-state block for the Design Studio.
|
|
3
|
+
*
|
|
4
|
+
* Element-level pseudo-class states (hover / focus / focus-visible / active /
|
|
5
|
+
* disabled) — NOT to be confused with `design-state.ts` / `StatesPanel`,
|
|
6
|
+
* which model whole-*screen* app states (Loading / Empty / Error, data
|
|
7
|
+
* fixtures, live captures). This module edits ONE element's interactive
|
|
8
|
+
* styling; that one edits which named app-level scenario the whole canvas is
|
|
9
|
+
* showing. Both features coexist: a screen state (e.g. "Empty") and an
|
|
10
|
+
* element interaction state (e.g. "Hover" on a button) are independent axes.
|
|
11
|
+
*
|
|
12
|
+
* Overrides persist into a single managed
|
|
13
|
+
* `<style data-agent-native-states>` block as real, plain CSS pseudo-class
|
|
14
|
+
* rules — readable and editable in the Code panel, and portable to any HTML
|
|
15
|
+
* export with zero runtime dependency:
|
|
16
|
+
*
|
|
17
|
+
* [data-agent-native-node-id="btn_1"]:hover {
|
|
18
|
+
* background-color: #111827;
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Follows the managed-block conventions of `breakpoint-media.ts` /
|
|
22
|
+
* `motion-compiler.ts`:
|
|
23
|
+
* - **Deterministic**: same model → byte-identical CSS. Node ids, states, and
|
|
24
|
+
* properties are sorted alphabetically; states within a node id follow the
|
|
25
|
+
* fixed `STATE_ORDER` (hover → focus → focus-visible → active → disabled)
|
|
26
|
+
* so cascade order is stable and predictable when two states could both
|
|
27
|
+
* apply (e.g. `:hover` and `:focus` at once).
|
|
28
|
+
* - **Targets by node id**: rules use `[data-agent-native-node-id="<id>"]`
|
|
29
|
+
* selectors — no class/id coupling, consistent with breakpoint overrides
|
|
30
|
+
* and motion keyframes.
|
|
31
|
+
* - **No dependencies**, pure string transforms. SSR-safe (no DOM APIs).
|
|
32
|
+
*
|
|
33
|
+
* ─── Forced-preview mechanism (the phase-2 bridge contract) ─────────────────
|
|
34
|
+
*
|
|
35
|
+
* The canvas iframe cannot reliably fake `:hover`/`:focus`/`:active` browser
|
|
36
|
+
* pseudo-classes on demand (no real pointer is over the element, focus would
|
|
37
|
+
* steal keyboard input, `:active` requires a held mouse button). Instead,
|
|
38
|
+
* `duplicateStatePreviewRules(html)` derives a **parallel twin rule** for
|
|
39
|
+
* every managed state rule, keyed off a plain HTML attribute instead of the
|
|
40
|
+
* pseudo-class:
|
|
41
|
+
*
|
|
42
|
+
* [data-agent-native-node-id="btn_1"]:hover { background-color: #111827; }
|
|
43
|
+
* [data-agent-native-node-id="btn_1"][data-an-state-preview="hover"] {
|
|
44
|
+
* background-color: #111827;
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* The twin rules are appended inside the SAME managed
|
|
48
|
+
* `<style data-agent-native-states>` block (so exported/standalone HTML
|
|
49
|
+
* always carries them — no separate stylesheet, no extra bridge script).
|
|
50
|
+
* Forcing a live preview of element `btn_1`'s Hover state is then just:
|
|
51
|
+
*
|
|
52
|
+
* element.setAttribute("data-an-state-preview", "hover")
|
|
53
|
+
*
|
|
54
|
+
* ...and clearing it is just removing that attribute. No JS re-evaluation of
|
|
55
|
+
* styles, no class toggling per property, no iframe-side CSS generation —
|
|
56
|
+
* the bridge's entire job is setting/clearing one attribute on the selected
|
|
57
|
+
* element whenever the inspector's active state changes. This module owns
|
|
58
|
+
* generating/refreshing the twin rules whenever a state rule changes; the
|
|
59
|
+
* bridge and DesignEditor own driving the attribute (phase 2).
|
|
60
|
+
*
|
|
61
|
+
* ─── Transitions ─────────────────────────────────────────────────────────────
|
|
62
|
+
*
|
|
63
|
+
* `transition` (and longhand `transition-*` properties) are ordinary CSS
|
|
64
|
+
* properties on the BASE element, not part of any state rule — they belong
|
|
65
|
+
* whichever way the base element's styling is already authored (inline
|
|
66
|
+
* `style` attribute or a class), same as any other non-interactive property.
|
|
67
|
+
* This module does not special-case them: set `transition: background-color
|
|
68
|
+
* 150ms ease` on the base element (via the existing inline-style / EditPanel
|
|
69
|
+
* commit path) and the managed `:hover` rule above will animate exactly as
|
|
70
|
+
* authored, in both the real `:hover` path and the forced-preview twin path
|
|
71
|
+
* (since the twin rule only changes which selector matches — the transition
|
|
72
|
+
* on the base element still applies either way).
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
// ─── Model ────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** Supported element interaction states, in fixed cascade/display order. */
|
|
78
|
+
export const INTERACTION_STATES = [
|
|
79
|
+
"hover",
|
|
80
|
+
"focus",
|
|
81
|
+
"focus-visible",
|
|
82
|
+
"active",
|
|
83
|
+
"disabled",
|
|
84
|
+
] as const;
|
|
85
|
+
|
|
86
|
+
export type InteractionState = (typeof INTERACTION_STATES)[number];
|
|
87
|
+
|
|
88
|
+
const STATE_SET: ReadonlySet<string> = new Set(INTERACTION_STATES);
|
|
89
|
+
|
|
90
|
+
export function isInteractionState(
|
|
91
|
+
value: string | null | undefined,
|
|
92
|
+
): value is InteractionState {
|
|
93
|
+
return typeof value === "string" && STATE_SET.has(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parsed managed block: node id → state → CSS property → value. Plain
|
|
98
|
+
* nested records so callers can serialize/diff cheaply.
|
|
99
|
+
*/
|
|
100
|
+
export type InteractionStatesModel = Record<
|
|
101
|
+
string,
|
|
102
|
+
Partial<Record<InteractionState, Record<string, string>>>
|
|
103
|
+
>;
|
|
104
|
+
|
|
105
|
+
/** One flattened managed rule, as returned by {@link listAllInteractionStateDeclarations}. */
|
|
106
|
+
export interface InteractionStateDeclaration {
|
|
107
|
+
nodeId: string;
|
|
108
|
+
state: InteractionState;
|
|
109
|
+
property: string;
|
|
110
|
+
value: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Validation ──────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
const CSS_VALUE_BREAKOUT_RE = /[;{}<>]|\/\*|\*\/|\burl\s*\(/i;
|
|
116
|
+
const CSS_VALUE_CONTROL_RE = /[\u0000-\u001f\u007f]/;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Reject caller-supplied CSS values before interpolation into the managed
|
|
120
|
+
* stylesheet. Allows useful CSS functions (`calc(...)`, `var(...)`,
|
|
121
|
+
* `rgb(...)`) but blocks declaration/rule breakouts and remote-resource
|
|
122
|
+
* hooks — same policy as the managed breakpoint/motion blocks.
|
|
123
|
+
*/
|
|
124
|
+
export function isSafeInteractionStateCssValue(value: string): boolean {
|
|
125
|
+
if (typeof value !== "string") return false;
|
|
126
|
+
const trimmed = value.trim();
|
|
127
|
+
if (trimmed.length === 0) return false;
|
|
128
|
+
if (CSS_VALUE_CONTROL_RE.test(value) || CSS_VALUE_BREAKOUT_RE.test(value)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Valid (optionally vendor-prefixed) CSS property identifier. */
|
|
135
|
+
export function isSafeInteractionStateCssProperty(property: string): boolean {
|
|
136
|
+
return /^-?[a-zA-Z][a-zA-Z0-9-]*$/.test(property);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── HTML block extraction / injection ───────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
const OPEN_RE = /<style\b(?=[^>]*\bdata-agent-native-states\b)[^>]*>/i;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extract the CSS body of the managed `<style data-agent-native-states>`
|
|
145
|
+
* block (includes both the real pseudo-class rules and their forced-preview
|
|
146
|
+
* twins — both live in the same block). Returns `null` when the document has
|
|
147
|
+
* no managed block.
|
|
148
|
+
*/
|
|
149
|
+
export function extractManagedInteractionStateCss(html: string): string | null {
|
|
150
|
+
const openMatch = OPEN_RE.exec(html);
|
|
151
|
+
if (!openMatch) return null;
|
|
152
|
+
const bodyStart = openMatch.index + openMatch[0].length;
|
|
153
|
+
const afterOpen = html.slice(bodyStart);
|
|
154
|
+
const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
|
|
155
|
+
if (!closeMatch) return null;
|
|
156
|
+
return afterOpen.slice(0, closeMatch.index).trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Inject or replace the managed block. Inserts before `</head>` when no
|
|
161
|
+
* managed block exists, or at the top of the document when there is no
|
|
162
|
+
* `<head>`. Passing empty CSS removes the block entirely.
|
|
163
|
+
*/
|
|
164
|
+
export function injectManagedInteractionStateCss(
|
|
165
|
+
html: string,
|
|
166
|
+
css: string,
|
|
167
|
+
): string {
|
|
168
|
+
const openMatch = OPEN_RE.exec(html);
|
|
169
|
+
const trimmed = css.trim();
|
|
170
|
+
const block =
|
|
171
|
+
trimmed.length > 0
|
|
172
|
+
? `<style data-agent-native-states>\n${trimmed}\n</style>`
|
|
173
|
+
: "";
|
|
174
|
+
|
|
175
|
+
if (openMatch) {
|
|
176
|
+
const bodyStart = openMatch.index + openMatch[0].length;
|
|
177
|
+
const afterOpen = html.slice(bodyStart);
|
|
178
|
+
const closeMatch = /<\s*\/\s*style\b[^>]*>/i.exec(afterOpen);
|
|
179
|
+
if (closeMatch) {
|
|
180
|
+
const closeEnd = bodyStart + closeMatch.index + closeMatch[0].length;
|
|
181
|
+
// Removing the block: also swallow one trailing newline so repeated
|
|
182
|
+
// add/remove cycles don't accumulate blank lines.
|
|
183
|
+
if (block === "") {
|
|
184
|
+
const after = html.slice(closeEnd);
|
|
185
|
+
return html.slice(0, openMatch.index) + after.replace(/^\n/, "");
|
|
186
|
+
}
|
|
187
|
+
return html.slice(0, openMatch.index) + block + html.slice(closeEnd);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (block === "") return html;
|
|
192
|
+
const headClose = html.lastIndexOf("</head>");
|
|
193
|
+
if (headClose !== -1) {
|
|
194
|
+
return html.slice(0, headClose) + block + "\n" + html.slice(headClose);
|
|
195
|
+
}
|
|
196
|
+
return block + "\n" + html;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ─── CSS body parse / serialize ──────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Matches ONE real pseudo-class state rule for a node id, e.g.
|
|
203
|
+
* `[data-agent-native-node-id="btn_1"]:focus-visible { ... }`. Deliberately
|
|
204
|
+
* does NOT match the `[data-an-state-preview="..."]` twin rules — those are
|
|
205
|
+
* derived output, not source of truth, and are re-generated by
|
|
206
|
+
* {@link duplicateStatePreviewRules} rather than parsed back in, so hand
|
|
207
|
+
* edits to a twin rule in the Code panel don't fork state.
|
|
208
|
+
*/
|
|
209
|
+
function stateRuleRegex(): RegExp {
|
|
210
|
+
return /\[data-agent-native-node-id="((?:\\.|[^"\\])*)"\]:(hover|focus-visible|focus|active|disabled)\s*\{([^}]*)\}/g;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse a managed CSS body into the model. Tolerant of hand edits made in
|
|
215
|
+
* the Code panel: unknown selectors (including the derived preview twins)
|
|
216
|
+
* and invalid declarations are skipped, not errors.
|
|
217
|
+
*/
|
|
218
|
+
export function parseInteractionStatesCss(css: string): InteractionStatesModel {
|
|
219
|
+
const model: InteractionStatesModel = {};
|
|
220
|
+
const ruleRe = stateRuleRegex();
|
|
221
|
+
let match: RegExpExecArray | null;
|
|
222
|
+
while ((match = ruleRe.exec(css)) !== null) {
|
|
223
|
+
const nodeId = unescAttr(match[1]);
|
|
224
|
+
const state = match[2];
|
|
225
|
+
if (!nodeId || !isInteractionState(state)) continue;
|
|
226
|
+
const declarations = match[3]
|
|
227
|
+
.split(";")
|
|
228
|
+
.map((decl) => decl.trim())
|
|
229
|
+
.filter(Boolean);
|
|
230
|
+
for (const declaration of declarations) {
|
|
231
|
+
const colon = declaration.indexOf(":");
|
|
232
|
+
if (colon <= 0) continue;
|
|
233
|
+
const property = declaration.slice(0, colon).trim();
|
|
234
|
+
const value = declaration.slice(colon + 1).trim();
|
|
235
|
+
if (!isSafeInteractionStateCssProperty(property)) continue;
|
|
236
|
+
if (!isSafeInteractionStateCssValue(value)) continue;
|
|
237
|
+
model[nodeId] ??= {};
|
|
238
|
+
const node = model[nodeId];
|
|
239
|
+
node[state] ??= {};
|
|
240
|
+
node[state]![property] = value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return model;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Serialize the real pseudo-class rules (model → CSS body), sorted node id
|
|
248
|
+
* then fixed state order then property, for byte-identical output. Does NOT
|
|
249
|
+
* include the forced-preview twins — call {@link duplicateStatePreviewRules}
|
|
250
|
+
* on the result (or use {@link serializeInteractionStatesModelWithPreviews})
|
|
251
|
+
* to append those.
|
|
252
|
+
*/
|
|
253
|
+
export function serializeInteractionStatesModel(
|
|
254
|
+
model: InteractionStatesModel,
|
|
255
|
+
): string {
|
|
256
|
+
const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
|
|
257
|
+
const rules: string[] = [];
|
|
258
|
+
for (const nodeId of nodeIds) {
|
|
259
|
+
const states = model[nodeId];
|
|
260
|
+
for (const state of INTERACTION_STATES) {
|
|
261
|
+
const declarations = states[state];
|
|
262
|
+
if (!declarations) continue;
|
|
263
|
+
const properties = Object.keys(declarations).sort((a, b) =>
|
|
264
|
+
a.localeCompare(b),
|
|
265
|
+
);
|
|
266
|
+
if (properties.length === 0) continue;
|
|
267
|
+
const lines = properties.map(
|
|
268
|
+
(property) => ` ${property}: ${declarations[property]};`,
|
|
269
|
+
);
|
|
270
|
+
rules.push(
|
|
271
|
+
`[data-agent-native-node-id="${escAttr(nodeId)}"]:${state} {\n` +
|
|
272
|
+
`${lines.join("\n")}\n` +
|
|
273
|
+
`}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return rules.join("\n\n");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Rebuild a managed CSS body's real rules + forced-preview twins from
|
|
282
|
+
* scratch, given the ALREADY-PARSED model. Re-serializing the real rules
|
|
283
|
+
* (rather than reusing the raw input body verbatim) is what keeps this
|
|
284
|
+
* idempotent: a body that already contains twins from a previous run parses
|
|
285
|
+
* back to the same model (twins are excluded by {@link stateRuleRegex}), so
|
|
286
|
+
* rebuilding from that model reproduces byte-identical output instead of
|
|
287
|
+
* accumulating duplicate twin rules on every call.
|
|
288
|
+
*/
|
|
289
|
+
function rebuildCssWithPreviews(model: InteractionStatesModel): string {
|
|
290
|
+
const baseBody = serializeInteractionStatesModel(model);
|
|
291
|
+
const previewRules: string[] = [];
|
|
292
|
+
const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
|
|
293
|
+
for (const nodeId of nodeIds) {
|
|
294
|
+
const states = model[nodeId];
|
|
295
|
+
for (const state of INTERACTION_STATES) {
|
|
296
|
+
const declarations = states[state];
|
|
297
|
+
if (!declarations) continue;
|
|
298
|
+
const properties = Object.keys(declarations).sort((a, b) =>
|
|
299
|
+
a.localeCompare(b),
|
|
300
|
+
);
|
|
301
|
+
if (properties.length === 0) continue;
|
|
302
|
+
const lines = properties.map(
|
|
303
|
+
(property) => ` ${property}: ${declarations[property]};`,
|
|
304
|
+
);
|
|
305
|
+
previewRules.push(
|
|
306
|
+
`[data-agent-native-node-id="${escAttr(nodeId)}"][data-an-state-preview="${state}"] {\n` +
|
|
307
|
+
`${lines.join("\n")}\n` +
|
|
308
|
+
`}`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (previewRules.length === 0) return baseBody;
|
|
313
|
+
return baseBody === ""
|
|
314
|
+
? previewRules.join("\n\n")
|
|
315
|
+
: `${baseBody}\n\n${previewRules.join("\n\n")}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Convenience: model → complete CSS body (real rules + forced-preview
|
|
320
|
+
* twins), ready to pass to {@link injectManagedInteractionStateCss}.
|
|
321
|
+
*/
|
|
322
|
+
export function serializeInteractionStatesModelWithPreviews(
|
|
323
|
+
model: InteractionStatesModel,
|
|
324
|
+
): string {
|
|
325
|
+
return rebuildCssWithPreviews(model);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Regenerate the forced-preview twin rules for a whole document from its
|
|
330
|
+
* current real state rules, and write the refreshed managed block back.
|
|
331
|
+
* Idempotent: re-running it just recomputes byte-identical twins (rebuilt
|
|
332
|
+
* from the parsed real-rule model, not by appending onto whatever twins were
|
|
333
|
+
* already present). This is the function phase-2 callers reach for after any
|
|
334
|
+
* state-rule mutation (or to backfill twins for a document that already has
|
|
335
|
+
* real rules but was authored before this mechanism existed) — it never
|
|
336
|
+
* touches anything outside the managed `<style data-agent-native-states>`
|
|
337
|
+
* block.
|
|
338
|
+
*/
|
|
339
|
+
export function duplicateStatePreviewRules(html: string): string {
|
|
340
|
+
const css = extractManagedInteractionStateCss(html);
|
|
341
|
+
if (css === null) return html;
|
|
342
|
+
const model = parseInteractionStatesCss(css);
|
|
343
|
+
return injectManagedInteractionStateCss(html, rebuildCssWithPreviews(model));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── High-level document operations ──────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Re-derive the managed block's CSS (real rules + fresh preview twins) from
|
|
350
|
+
* its current real rules and write it back. Callers that mutate the model
|
|
351
|
+
* directly should route through this instead of hand-assembling the CSS, so
|
|
352
|
+
* the preview twins never drift from the real rules.
|
|
353
|
+
*/
|
|
354
|
+
function writeModel(html: string, model: InteractionStatesModel): string {
|
|
355
|
+
return injectManagedInteractionStateCss(
|
|
356
|
+
html,
|
|
357
|
+
serializeInteractionStatesModelWithPreviews(model),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Read the current model out of the document (real rules only). */
|
|
362
|
+
function readModel(html: string): InteractionStatesModel {
|
|
363
|
+
return parseInteractionStatesCss(
|
|
364
|
+
extractManagedInteractionStateCss(html) ?? "",
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* List every state that has at least one declaration for `nodeId`, in fixed
|
|
370
|
+
* `INTERACTION_STATES` order. Empty array when the node has no overrides.
|
|
371
|
+
*/
|
|
372
|
+
export function listInteractionStates(
|
|
373
|
+
html: string,
|
|
374
|
+
nodeId: string,
|
|
375
|
+
): InteractionState[] {
|
|
376
|
+
const model = readModel(html);
|
|
377
|
+
const node = model[nodeId];
|
|
378
|
+
if (!node) return [];
|
|
379
|
+
return INTERACTION_STATES.filter((state) => {
|
|
380
|
+
const declarations = node[state];
|
|
381
|
+
return declarations && Object.keys(declarations).length > 0;
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* All declared CSS properties/values for one node's state. Returns `{}` when
|
|
387
|
+
* there is no managed block, node, or state entry.
|
|
388
|
+
*/
|
|
389
|
+
export function readStateStyles(
|
|
390
|
+
html: string,
|
|
391
|
+
nodeId: string,
|
|
392
|
+
state: InteractionState,
|
|
393
|
+
): Record<string, string> {
|
|
394
|
+
const model = readModel(html);
|
|
395
|
+
return { ...(model[nodeId]?.[state] ?? {}) };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Set (or overwrite) one managed declaration for a node's state, returning
|
|
400
|
+
* the updated HTML. Throws on unsafe property/value — the caller decides how
|
|
401
|
+
* to surface that (same contract as `setBreakpointMediaDeclaration`).
|
|
402
|
+
*/
|
|
403
|
+
export function upsertStateStyle(
|
|
404
|
+
html: string,
|
|
405
|
+
nodeId: string,
|
|
406
|
+
state: InteractionState,
|
|
407
|
+
property: string,
|
|
408
|
+
value: string,
|
|
409
|
+
): string {
|
|
410
|
+
return upsertStateStyles(html, nodeId, state, { [property]: value });
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Batched form of {@link upsertStateStyle} — sets multiple declarations for
|
|
415
|
+
* one node's state in a single parse/serialize/inject pass. Prefer this over
|
|
416
|
+
* multiple sequential `upsertStateStyle` calls when committing more than one
|
|
417
|
+
* property at once (e.g. a shorthand expansion), matching the
|
|
418
|
+
* `onStylesChange` batched-commit convention used elsewhere in EditPanel.
|
|
419
|
+
*/
|
|
420
|
+
export function upsertStateStyles(
|
|
421
|
+
html: string,
|
|
422
|
+
nodeId: string,
|
|
423
|
+
state: InteractionState,
|
|
424
|
+
styles: Record<string, string>,
|
|
425
|
+
): string {
|
|
426
|
+
if (!nodeId) throw new Error("upsertStateStyles requires a nodeId.");
|
|
427
|
+
if (!isInteractionState(state)) {
|
|
428
|
+
throw new Error(`Invalid interaction state: "${state}".`);
|
|
429
|
+
}
|
|
430
|
+
const normalized: Record<string, string> = {};
|
|
431
|
+
for (const [rawProperty, rawValue] of Object.entries(styles)) {
|
|
432
|
+
const property = normalizeCssPropertyName(rawProperty.trim());
|
|
433
|
+
if (!isSafeInteractionStateCssProperty(property)) {
|
|
434
|
+
throw new Error(`Invalid interaction-state property: "${rawProperty}".`);
|
|
435
|
+
}
|
|
436
|
+
if (!isSafeInteractionStateCssValue(rawValue)) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Invalid interaction-state value for "${property}": semicolons, braces, comments, angle brackets, control characters, and url(...) are not allowed.`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
normalized[property] = rawValue.trim();
|
|
442
|
+
}
|
|
443
|
+
const model = readModel(html);
|
|
444
|
+
model[nodeId] ??= {};
|
|
445
|
+
const node = model[nodeId];
|
|
446
|
+
node[state] = { ...(node[state] ?? {}), ...normalized };
|
|
447
|
+
return writeModel(html, model);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Remove one managed declaration (reset the property back to the base
|
|
452
|
+
* value). Prunes empty state entries, node entries, and — when nothing is
|
|
453
|
+
* left — the whole managed block.
|
|
454
|
+
*/
|
|
455
|
+
export function removeStateProperty(
|
|
456
|
+
html: string,
|
|
457
|
+
nodeId: string,
|
|
458
|
+
state: InteractionState,
|
|
459
|
+
property: string,
|
|
460
|
+
): string {
|
|
461
|
+
const model = readModel(html);
|
|
462
|
+
const node = model[nodeId];
|
|
463
|
+
const declarations = node?.[state];
|
|
464
|
+
if (!declarations) return html;
|
|
465
|
+
const normalizedProperty = normalizeCssPropertyName(property.trim());
|
|
466
|
+
if (!(normalizedProperty in declarations)) return html;
|
|
467
|
+
delete declarations[normalizedProperty];
|
|
468
|
+
if (Object.keys(declarations).length === 0) {
|
|
469
|
+
delete node![state];
|
|
470
|
+
}
|
|
471
|
+
if (node && Object.keys(node).length === 0) {
|
|
472
|
+
delete model[nodeId];
|
|
473
|
+
}
|
|
474
|
+
return writeModel(html, model);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Remove every managed declaration for a node's state (the state's full
|
|
479
|
+
* "reset" affordance — clears every overridden property at once).
|
|
480
|
+
*/
|
|
481
|
+
export function clearState(
|
|
482
|
+
html: string,
|
|
483
|
+
nodeId: string,
|
|
484
|
+
state: InteractionState,
|
|
485
|
+
): string {
|
|
486
|
+
const model = readModel(html);
|
|
487
|
+
const node = model[nodeId];
|
|
488
|
+
if (!node?.[state]) return html;
|
|
489
|
+
delete node[state];
|
|
490
|
+
if (Object.keys(node).length === 0) {
|
|
491
|
+
delete model[nodeId];
|
|
492
|
+
}
|
|
493
|
+
return writeModel(html, model);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* All managed declarations across every node/state, flattened and sorted
|
|
498
|
+
* node id → fixed state order → property. Pass `nodeId` to filter to one
|
|
499
|
+
* node (every state), matching `getBreakpointMediaDeclarations`'s shape.
|
|
500
|
+
*/
|
|
501
|
+
export function listAllInteractionStateDeclarations(
|
|
502
|
+
html: string,
|
|
503
|
+
nodeId?: string | null,
|
|
504
|
+
): InteractionStateDeclaration[] {
|
|
505
|
+
const model = readModel(html);
|
|
506
|
+
const declarations: InteractionStateDeclaration[] = [];
|
|
507
|
+
const nodeIds = Object.keys(model).sort((a, b) => a.localeCompare(b));
|
|
508
|
+
for (const ruleNodeId of nodeIds) {
|
|
509
|
+
if (nodeId != null && ruleNodeId !== nodeId) continue;
|
|
510
|
+
const states = model[ruleNodeId];
|
|
511
|
+
for (const state of INTERACTION_STATES) {
|
|
512
|
+
const props = states[state];
|
|
513
|
+
if (!props) continue;
|
|
514
|
+
const properties = Object.keys(props).sort((a, b) => a.localeCompare(b));
|
|
515
|
+
for (const property of properties) {
|
|
516
|
+
declarations.push({
|
|
517
|
+
nodeId: ruleNodeId,
|
|
518
|
+
state,
|
|
519
|
+
property,
|
|
520
|
+
value: props[property],
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return declarations;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
529
|
+
|
|
530
|
+
/** Escape a string for a CSS attribute selector value (`\` and `"`). */
|
|
531
|
+
function escAttr(value: string): string {
|
|
532
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function unescAttr(value: string): string {
|
|
536
|
+
return value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Normalize a CSS property name to kebab-case (accepts either `fontSize` or
|
|
541
|
+
* `font-size`). Local copy of the same normalization
|
|
542
|
+
* `responsive-classes.ts`/`breakpoint-media.ts` apply, kept dependency-free
|
|
543
|
+
* here since this module must stay a pure, standalone string transform.
|
|
544
|
+
*/
|
|
545
|
+
function normalizeCssPropertyName(property: string): string {
|
|
546
|
+
if (property.startsWith("--")) return property;
|
|
547
|
+
return property.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
548
|
+
}
|