@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
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* both atomically.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
evaluateCssLinear,
|
|
12
|
+
parseSpringToken,
|
|
13
|
+
sampleSpring,
|
|
14
|
+
} from "./motion-easing";
|
|
15
|
+
|
|
10
16
|
export type MotionEase =
|
|
11
17
|
| "linear"
|
|
12
18
|
| "ease"
|
|
@@ -15,27 +21,64 @@ export type MotionEase =
|
|
|
15
21
|
| "ease-in-out"
|
|
16
22
|
| "step-start"
|
|
17
23
|
| "step-end"
|
|
18
|
-
| string; // cubic-bezier(...) or
|
|
24
|
+
| string; // cubic-bezier(...), steps(...), linear(...), or spring(...)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Timeline playback mode (Figma Motion parity):
|
|
28
|
+
* - "loop": repeats continuously.
|
|
29
|
+
* - "once": plays a single time and stops on the final frame.
|
|
30
|
+
* - "ping-pong": plays forward then backward, repeating.
|
|
31
|
+
*/
|
|
32
|
+
export type MotionPlaybackMode = "loop" | "once" | "ping-pong";
|
|
33
|
+
|
|
34
|
+
/** Playback mode applied when a timeline predates the playbackMode field. */
|
|
35
|
+
export const MOTION_DEFAULT_PLAYBACK_MODE: MotionPlaybackMode = "once";
|
|
19
36
|
|
|
20
37
|
export interface MotionKeyframe {
|
|
21
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Normalised time in [0, 1] within the TRACK's own span (which is the whole
|
|
40
|
+
* timeline unless the track sets `delayMs`/`durationMs`). 0 = span start,
|
|
41
|
+
* 1 = span end. Matches Figma's normalized `timelinePosition`.
|
|
42
|
+
*/
|
|
22
43
|
t: number;
|
|
23
44
|
/** CSS property value at this keyframe (e.g. "0px", "1", "#ff0000"). */
|
|
24
45
|
value: string;
|
|
25
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Easing of the SEGMENT that leaves this keyframe toward the next one
|
|
48
|
+
* (standard CSS keyframe semantics; equivalently, Figma's "easing into the
|
|
49
|
+
* following keyframe"). The last keyframe's ease has no effect.
|
|
50
|
+
*/
|
|
26
51
|
ease?: MotionEase;
|
|
27
52
|
}
|
|
28
53
|
|
|
29
54
|
/**
|
|
30
55
|
* One property track for a single target node.
|
|
31
|
-
* A node may have multiple tracks (e.g. opacity +
|
|
56
|
+
* A node may have multiple tracks (e.g. opacity + translate + rotate).
|
|
32
57
|
*/
|
|
33
58
|
export interface MotionTrack {
|
|
34
59
|
/** Matches `data-agent-native-node-id` stamped on the target DOM element. */
|
|
35
60
|
targetNodeId: string;
|
|
36
|
-
/** CSS property name being animated (e.g. "opacity", "
|
|
61
|
+
/** CSS property name being animated (e.g. "opacity", "translate", "rotate"). */
|
|
37
62
|
property: string;
|
|
38
63
|
keyframes: MotionKeyframe[];
|
|
64
|
+
/**
|
|
65
|
+
* Start-time offset of this track within the timeline, in milliseconds
|
|
66
|
+
* (compiled to `animation-delay`). Omitted / 0 = starts with the timeline.
|
|
67
|
+
*/
|
|
68
|
+
delayMs?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Duration of this track's own animation span in milliseconds (compiled to
|
|
71
|
+
* a per-track `animation-duration`). Omitted = the timeline's `durationMs`.
|
|
72
|
+
*/
|
|
73
|
+
durationMs?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Timeline-level playback mode, persisted on the FIRST track only so the
|
|
76
|
+
* stored `tracks` JSON column stays a plain array (additive, backward
|
|
77
|
+
* compatible — older readers simply ignore the extra field). Use
|
|
78
|
+
* `readTimelinePlaybackMode` / `withTimelinePlaybackMode`; do not read this
|
|
79
|
+
* field directly.
|
|
80
|
+
*/
|
|
81
|
+
timelinePlaybackMode?: MotionPlaybackMode;
|
|
39
82
|
}
|
|
40
83
|
|
|
41
84
|
/**
|
|
@@ -59,6 +102,11 @@ export interface MotionTimeline {
|
|
|
59
102
|
tracks: MotionTrack[];
|
|
60
103
|
/** Total animation duration in milliseconds. */
|
|
61
104
|
durationMs: number;
|
|
105
|
+
/**
|
|
106
|
+
* Timeline playback mode. Omitted on rows written before this field existed
|
|
107
|
+
* — treat as {@link MOTION_DEFAULT_PLAYBACK_MODE}.
|
|
108
|
+
*/
|
|
109
|
+
playbackMode?: MotionPlaybackMode;
|
|
62
110
|
/** Default easing applied to keyframe intervals that omit a per-keyframe ease. */
|
|
63
111
|
defaultEase: MotionEase;
|
|
64
112
|
/**
|
|
@@ -86,52 +134,100 @@ export interface MotionTimeline {
|
|
|
86
134
|
* `apply-motion-edit`).
|
|
87
135
|
*/
|
|
88
136
|
export interface MotionPropertyPreset {
|
|
89
|
-
/** CSS property animated by the track (e.g. "opacity", "
|
|
137
|
+
/** CSS property animated by the track (e.g. "opacity", "translate"). */
|
|
90
138
|
property: string;
|
|
91
|
-
/** Human-readable label for the picker
|
|
139
|
+
/** Human-readable label for the picker, verbatim from Figma's Add-motion menu. */
|
|
92
140
|
label: string;
|
|
93
141
|
/** Value at t = 0. */
|
|
94
142
|
from: string;
|
|
95
143
|
/** Value at t = 1. */
|
|
96
144
|
to: string;
|
|
145
|
+
/**
|
|
146
|
+
* Menu placement, matching Figma Motion's "Add motion" submenu: "primary"
|
|
147
|
+
* items are listed directly; "more" items live under the "More" submenu.
|
|
148
|
+
*/
|
|
149
|
+
group: "primary" | "more";
|
|
97
150
|
}
|
|
98
151
|
|
|
99
152
|
/**
|
|
100
|
-
* Built-in property presets for the "
|
|
101
|
-
*
|
|
102
|
-
*
|
|
153
|
+
* Built-in property presets for the "Add motion" picker, matching Figma
|
|
154
|
+
* Motion's submenu verbatim: Position / Scale / Rotation / Opacity, then
|
|
155
|
+
* More ▸ Corner radius / Fill / Stroke paint / Stroke weight / Drop shadow.
|
|
156
|
+
*
|
|
157
|
+
* CSS mapping uses the modern individual transform properties (`translate`,
|
|
158
|
+
* `scale`, `rotate`) instead of one shared `transform` string, so position,
|
|
159
|
+
* scale, and rotation tracks compose freely on the same node without
|
|
160
|
+
* colliding on a single (targetNodeId, property) pair.
|
|
161
|
+
*
|
|
162
|
+
* Every preset is a valid CSS identifier accepted by
|
|
163
|
+
* `assertSafeMotionCssProperty` and yields two safe keyframe values. Presets
|
|
164
|
+
* whose motion would be invisible with identical endpoints seed a small
|
|
165
|
+
* visible change; color-like presets seed equal endpoints (edit values via
|
|
166
|
+
* auto-keyframe or the inspector, as in Figma).
|
|
103
167
|
*/
|
|
104
168
|
export const MOTION_PROPERTY_PRESETS: MotionPropertyPreset[] = [
|
|
105
|
-
{ property: "opacity", label: "Fade (opacity)", from: "0", to: "1" },
|
|
106
169
|
{
|
|
107
|
-
property: "
|
|
108
|
-
label: "
|
|
109
|
-
from: "
|
|
110
|
-
to: "
|
|
170
|
+
property: "translate",
|
|
171
|
+
label: "Position",
|
|
172
|
+
from: "0px 16px",
|
|
173
|
+
to: "0px 0px",
|
|
174
|
+
group: "primary",
|
|
111
175
|
},
|
|
112
176
|
{
|
|
113
|
-
property: "
|
|
114
|
-
label: "Scale
|
|
115
|
-
from: "
|
|
116
|
-
to: "
|
|
177
|
+
property: "scale",
|
|
178
|
+
label: "Scale",
|
|
179
|
+
from: "0.8",
|
|
180
|
+
to: "1",
|
|
181
|
+
group: "primary",
|
|
117
182
|
},
|
|
118
183
|
{
|
|
119
|
-
property: "
|
|
120
|
-
label: "
|
|
121
|
-
from: "
|
|
122
|
-
to: "
|
|
184
|
+
property: "rotate",
|
|
185
|
+
label: "Rotation",
|
|
186
|
+
from: "0deg",
|
|
187
|
+
to: "360deg",
|
|
188
|
+
group: "primary",
|
|
123
189
|
},
|
|
124
190
|
{
|
|
125
|
-
property: "
|
|
126
|
-
label: "
|
|
127
|
-
from: "
|
|
128
|
-
to: "
|
|
191
|
+
property: "opacity",
|
|
192
|
+
label: "Opacity",
|
|
193
|
+
from: "0",
|
|
194
|
+
to: "1",
|
|
195
|
+
group: "primary",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
property: "border-radius",
|
|
199
|
+
label: "Corner radius",
|
|
200
|
+
from: "0px",
|
|
201
|
+
to: "16px",
|
|
202
|
+
group: "more",
|
|
129
203
|
},
|
|
130
204
|
{
|
|
131
205
|
property: "background-color",
|
|
132
|
-
label: "
|
|
206
|
+
label: "Fill",
|
|
133
207
|
from: "#ffffff",
|
|
134
208
|
to: "#ffffff",
|
|
209
|
+
group: "more",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
property: "border-color",
|
|
213
|
+
label: "Stroke paint",
|
|
214
|
+
from: "#000000",
|
|
215
|
+
to: "#000000",
|
|
216
|
+
group: "more",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
property: "border-width",
|
|
220
|
+
label: "Stroke weight",
|
|
221
|
+
from: "0px",
|
|
222
|
+
to: "2px",
|
|
223
|
+
group: "more",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
property: "box-shadow",
|
|
227
|
+
label: "Drop shadow",
|
|
228
|
+
from: "0px 0px 0px 0px rgba(0, 0, 0, 0)",
|
|
229
|
+
to: "0px 8px 24px 0px rgba(0, 0, 0, 0.25)",
|
|
230
|
+
group: "more",
|
|
135
231
|
},
|
|
136
232
|
];
|
|
137
233
|
|
|
@@ -281,10 +377,11 @@ function cubicBezierY(
|
|
|
281
377
|
* Evaluate a {@link MotionEase} timing function at linear progress `x ∈ [0, 1]`.
|
|
282
378
|
*
|
|
283
379
|
* Supports the CSS keywords (`linear`, `ease*`, `step-start`, `step-end`),
|
|
284
|
-
* `cubic-bezier(...)` (including overshoot control points,
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
380
|
+
* `cubic-bezier(...)` (including overshoot control points, used by the "back"
|
|
381
|
+
* curve presets), `steps(n, position)`, CSS `linear(...)` stop lists, and
|
|
382
|
+
* `spring(bounce[, settle])` tokens evaluated with the real damped-oscillator
|
|
383
|
+
* sampler from `motion-easing.ts`. Unknown values fall back to linear. The
|
|
384
|
+
* result may leave [0, 1] for overshoot beziers and bouncy springs by design.
|
|
288
385
|
*/
|
|
289
386
|
export function evaluateMotionEase(
|
|
290
387
|
ease: MotionEase | undefined,
|
|
@@ -334,9 +431,18 @@ export function evaluateMotionEase(
|
|
|
334
431
|
}
|
|
335
432
|
}
|
|
336
433
|
|
|
337
|
-
//
|
|
434
|
+
// Real spring physics: `spring(bounce[, settle])` tokens (and the bare
|
|
435
|
+
// `spring` keyword) sample the shared damped-oscillator solution.
|
|
338
436
|
if (raw.startsWith("spring")) {
|
|
339
|
-
|
|
437
|
+
const spring = parseSpringToken(raw);
|
|
438
|
+
if (spring) return sampleSpring(spring, clamped);
|
|
439
|
+
return clamped;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// CSS linear(...) stop lists (compiled springs / recovered timelines).
|
|
443
|
+
if (raw.startsWith("linear(")) {
|
|
444
|
+
const evaluated = evaluateCssLinear(raw, clamped);
|
|
445
|
+
if (evaluated !== null) return evaluated;
|
|
340
446
|
}
|
|
341
447
|
|
|
342
448
|
return clamped;
|
|
@@ -526,3 +632,263 @@ export function sampleMotionKeyframesAt(
|
|
|
526
632
|
}
|
|
527
633
|
return last.value;
|
|
528
634
|
}
|
|
635
|
+
|
|
636
|
+
// ─── Track timing (per-track offset + duration) ──────────────────────────────
|
|
637
|
+
|
|
638
|
+
export interface MotionTrackTiming {
|
|
639
|
+
/** Track start offset within the timeline, ms. */
|
|
640
|
+
startMs: number;
|
|
641
|
+
/** Track animation span, ms. */
|
|
642
|
+
durationMs: number;
|
|
643
|
+
/** Track end (startMs + durationMs), ms. */
|
|
644
|
+
endMs: number;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Resolve a track's absolute timing within its timeline. Tracks without
|
|
649
|
+
* explicit `delayMs`/`durationMs` span the whole timeline (legacy behavior).
|
|
650
|
+
*/
|
|
651
|
+
export function getMotionTrackTiming(
|
|
652
|
+
track: Pick<MotionTrack, "delayMs" | "durationMs">,
|
|
653
|
+
timelineDurationMs: number,
|
|
654
|
+
): MotionTrackTiming {
|
|
655
|
+
const startMs = Math.max(0, track.delayMs ?? 0);
|
|
656
|
+
const durationMs = Math.max(
|
|
657
|
+
1,
|
|
658
|
+
track.durationMs !== undefined && Number.isFinite(track.durationMs)
|
|
659
|
+
? track.durationMs
|
|
660
|
+
: timelineDurationMs,
|
|
661
|
+
);
|
|
662
|
+
return { startMs, durationMs, endMs: startMs + durationMs };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Map an absolute timeline time (ms) to a track-local normalised time in
|
|
667
|
+
* [0, 1], clamping outside the track's span (matching the compiled CSS's
|
|
668
|
+
* `animation-fill-mode: both`: the first keyframe value holds before the
|
|
669
|
+
* span, the last keyframe value holds after it).
|
|
670
|
+
*/
|
|
671
|
+
export function timelineTimeToTrackTime(
|
|
672
|
+
track: Pick<MotionTrack, "delayMs" | "durationMs">,
|
|
673
|
+
timelineTimeMs: number,
|
|
674
|
+
timelineDurationMs: number,
|
|
675
|
+
): number {
|
|
676
|
+
const timing = getMotionTrackTiming(track, timelineDurationMs);
|
|
677
|
+
return Math.min(
|
|
678
|
+
1,
|
|
679
|
+
Math.max(0, (timelineTimeMs - timing.startMs) / timing.durationMs),
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Sample a track's value at an absolute timeline time (ms), honouring the
|
|
685
|
+
* track's own offset/duration and per-segment easing.
|
|
686
|
+
*/
|
|
687
|
+
export function sampleMotionTrackAtTimelineTime(
|
|
688
|
+
track: MotionTrack,
|
|
689
|
+
timelineTimeMs: number,
|
|
690
|
+
timelineDurationMs: number,
|
|
691
|
+
defaultEase?: MotionEase,
|
|
692
|
+
): string {
|
|
693
|
+
return sampleMotionKeyframesAt(
|
|
694
|
+
track.keyframes,
|
|
695
|
+
timelineTimeToTrackTime(track, timelineTimeMs, timelineDurationMs),
|
|
696
|
+
defaultEase,
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ─── Timeline playback mode (persisted on the first track) ──────────────────
|
|
701
|
+
|
|
702
|
+
const MOTION_PLAYBACK_MODES: MotionPlaybackMode[] = [
|
|
703
|
+
"loop",
|
|
704
|
+
"once",
|
|
705
|
+
"ping-pong",
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
/** Narrow an unknown value to a {@link MotionPlaybackMode}, or null. */
|
|
709
|
+
export function parseMotionPlaybackMode(
|
|
710
|
+
value: unknown,
|
|
711
|
+
): MotionPlaybackMode | null {
|
|
712
|
+
return MOTION_PLAYBACK_MODES.includes(value as MotionPlaybackMode)
|
|
713
|
+
? (value as MotionPlaybackMode)
|
|
714
|
+
: null;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Read the timeline-level playback mode persisted in a `tracks` array
|
|
719
|
+
* (stamped on the first track — see {@link MotionTrack.timelinePlaybackMode}).
|
|
720
|
+
* Returns null when no track carries the field (pre-playbackMode timelines).
|
|
721
|
+
*/
|
|
722
|
+
export function readTimelinePlaybackMode(
|
|
723
|
+
tracks: MotionTrack[],
|
|
724
|
+
): MotionPlaybackMode | null {
|
|
725
|
+
for (const track of tracks) {
|
|
726
|
+
const mode = parseMotionPlaybackMode(track.timelinePlaybackMode);
|
|
727
|
+
if (mode) return mode;
|
|
728
|
+
}
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Return a copy of `tracks` with the timeline-level playback mode stamped on
|
|
734
|
+
* the first track only (and removed from every other track), keeping the
|
|
735
|
+
* persisted JSON canonical. An empty `tracks` array is returned unchanged.
|
|
736
|
+
*/
|
|
737
|
+
export function withTimelinePlaybackMode<T extends MotionTrack>(
|
|
738
|
+
tracks: T[],
|
|
739
|
+
mode: MotionPlaybackMode,
|
|
740
|
+
): T[] {
|
|
741
|
+
return tracks.map((track, index) => {
|
|
742
|
+
if (index === 0) return { ...track, timelinePlaybackMode: mode };
|
|
743
|
+
if (track.timelinePlaybackMode === undefined) return track;
|
|
744
|
+
const { timelinePlaybackMode: _drop, ...rest } = track;
|
|
745
|
+
return rest as T;
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ─── Auto-keyframe (pure part) ───────────────────────────────────────────────
|
|
750
|
+
|
|
751
|
+
export interface MotionAutoKeyframeEdit {
|
|
752
|
+
/** `data-agent-native-node-id` of the element whose property was edited. */
|
|
753
|
+
targetNodeId: string;
|
|
754
|
+
/** CSS property that was edited (e.g. "opacity", "translate"). */
|
|
755
|
+
property: string;
|
|
756
|
+
/** The new CSS value entered by the user. */
|
|
757
|
+
value: string;
|
|
758
|
+
/** Playhead position at edit time, normalised to the TIMELINE in [0, 1]. */
|
|
759
|
+
playheadT: number;
|
|
760
|
+
/** Timeline duration in ms (needed to map onto offset tracks). */
|
|
761
|
+
timelineDurationMs: number;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Auto-keyframe core logic (Figma Motion parity): when a keyframeable
|
|
766
|
+
* property value is edited while auto-keyframe is armed, upsert a keyframe at
|
|
767
|
+
* the playhead on the matching track — creating one at the playhead when the
|
|
768
|
+
* playhead is not already on an existing keyframe, or replacing the keyframe
|
|
769
|
+
* value when it is (within {@link MOTION_KEYFRAME_TIME_EPSILON}).
|
|
770
|
+
*
|
|
771
|
+
* Pure and UI-free: returns the updated tracks array, or `null` when no track
|
|
772
|
+
* animates `(targetNodeId, property)` — in that case the caller should apply
|
|
773
|
+
* the edit as a plain style change (Figma only auto-keys properties that
|
|
774
|
+
* already have motion; new properties are added via "Add motion").
|
|
775
|
+
*/
|
|
776
|
+
export function applyMotionAutoKeyframe(
|
|
777
|
+
tracks: MotionTrack[],
|
|
778
|
+
edit: MotionAutoKeyframeEdit,
|
|
779
|
+
defaultEase?: MotionEase,
|
|
780
|
+
): MotionTrack[] | null {
|
|
781
|
+
const index = tracks.findIndex(
|
|
782
|
+
(track) =>
|
|
783
|
+
track.targetNodeId === edit.targetNodeId &&
|
|
784
|
+
track.property === edit.property,
|
|
785
|
+
);
|
|
786
|
+
if (index === -1) return null;
|
|
787
|
+
const track = tracks[index];
|
|
788
|
+
const playheadMs =
|
|
789
|
+
Math.min(1, Math.max(0, edit.playheadT)) * edit.timelineDurationMs;
|
|
790
|
+
const localT = timelineTimeToTrackTime(
|
|
791
|
+
track,
|
|
792
|
+
playheadMs,
|
|
793
|
+
edit.timelineDurationMs,
|
|
794
|
+
);
|
|
795
|
+
const existing = track.keyframes.find(
|
|
796
|
+
(kf) => Math.abs(kf.t - localT) <= MOTION_KEYFRAME_TIME_EPSILON,
|
|
797
|
+
);
|
|
798
|
+
const keyframe: MotionKeyframe = {
|
|
799
|
+
t: existing ? existing.t : localT,
|
|
800
|
+
value: edit.value,
|
|
801
|
+
...(existing?.ease !== undefined
|
|
802
|
+
? { ease: existing.ease }
|
|
803
|
+
: defaultEase !== undefined
|
|
804
|
+
? { ease: defaultEase }
|
|
805
|
+
: {}),
|
|
806
|
+
};
|
|
807
|
+
const next = [...tracks];
|
|
808
|
+
next[index] = {
|
|
809
|
+
...track,
|
|
810
|
+
keyframes: upsertMotionKeyframeAtTime(track.keyframes, keyframe),
|
|
811
|
+
};
|
|
812
|
+
return next;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ─── Copy / paste animation + stagger (pure helpers) ─────────────────────────
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* A layer's animation, detached from its node id — the payload behind
|
|
819
|
+
* "Copy animation" / "Paste animation".
|
|
820
|
+
*/
|
|
821
|
+
export interface MotionAnimationClip {
|
|
822
|
+
tracks: Array<Omit<MotionTrack, "targetNodeId" | "timelinePlaybackMode">>;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Extract a node's tracks as a reusable {@link MotionAnimationClip}.
|
|
827
|
+
* Returns null when the node has no tracks.
|
|
828
|
+
*/
|
|
829
|
+
export function copyLayerAnimation(
|
|
830
|
+
tracks: MotionTrack[],
|
|
831
|
+
targetNodeId: string,
|
|
832
|
+
): MotionAnimationClip | null {
|
|
833
|
+
const layerTracks = tracks.filter(
|
|
834
|
+
(track) => track.targetNodeId === targetNodeId,
|
|
835
|
+
);
|
|
836
|
+
if (layerTracks.length === 0) return null;
|
|
837
|
+
return {
|
|
838
|
+
tracks: layerTracks.map(
|
|
839
|
+
({ targetNodeId: _node, timelinePlaybackMode: _mode, ...rest }) => ({
|
|
840
|
+
...rest,
|
|
841
|
+
keyframes: rest.keyframes.map((kf) => ({ ...kf })),
|
|
842
|
+
}),
|
|
843
|
+
),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Apply a {@link MotionAnimationClip} to a target node, replacing any tracks
|
|
849
|
+
* the node already has for the clip's properties (other properties are kept).
|
|
850
|
+
* The timeline-level playback mode stamp is re-normalised afterwards.
|
|
851
|
+
*/
|
|
852
|
+
export function pasteLayerAnimation(
|
|
853
|
+
tracks: MotionTrack[],
|
|
854
|
+
clip: MotionAnimationClip,
|
|
855
|
+
targetNodeId: string,
|
|
856
|
+
): MotionTrack[] {
|
|
857
|
+
const mode = readTimelinePlaybackMode(tracks);
|
|
858
|
+
const clipProperties = new Set(clip.tracks.map((track) => track.property));
|
|
859
|
+
const kept = tracks.filter(
|
|
860
|
+
(track) =>
|
|
861
|
+
!(
|
|
862
|
+
track.targetNodeId === targetNodeId &&
|
|
863
|
+
clipProperties.has(track.property)
|
|
864
|
+
),
|
|
865
|
+
);
|
|
866
|
+
const pasted: MotionTrack[] = clip.tracks.map((track) => ({
|
|
867
|
+
...track,
|
|
868
|
+
keyframes: track.keyframes.map((kf) => ({ ...kf })),
|
|
869
|
+
targetNodeId,
|
|
870
|
+
}));
|
|
871
|
+
const merged = [...kept, ...pasted];
|
|
872
|
+
return mode ? withTimelinePlaybackMode(merged, mode) : merged;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Stagger helper: offset each listed node's tracks by `index * stepMs`
|
|
877
|
+
* (Figma recommends 40–80ms between instances). Nodes keep their relative
|
|
878
|
+
* internal offsets; nodes not listed are untouched.
|
|
879
|
+
*/
|
|
880
|
+
export function staggerLayerTracks(
|
|
881
|
+
tracks: MotionTrack[],
|
|
882
|
+
orderedNodeIds: string[],
|
|
883
|
+
stepMs: number,
|
|
884
|
+
): MotionTrack[] {
|
|
885
|
+
const offsetByNode = new Map<string, number>();
|
|
886
|
+
orderedNodeIds.forEach((nodeId, index) => {
|
|
887
|
+
offsetByNode.set(nodeId, index * stepMs);
|
|
888
|
+
});
|
|
889
|
+
return tracks.map((track) => {
|
|
890
|
+
const offset = offsetByNode.get(track.targetNodeId);
|
|
891
|
+
if (offset === undefined || offset === 0) return track;
|
|
892
|
+
return { ...track, delayMs: Math.max(0, (track.delayMs ?? 0) + offset) };
|
|
893
|
+
});
|
|
894
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Title derivation for designs created from a generation prompt.
|
|
3
|
+
*
|
|
4
|
+
* Two title sources are used together:
|
|
5
|
+
* - `derivePromptTitle` produces an instant, purely local placeholder (first
|
|
6
|
+
* line of the prompt, truncated at a word boundary) so the UI has
|
|
7
|
+
* something to show the moment the design is created.
|
|
8
|
+
* - `sanitizeGeneratedDesignTitle` normalizes whatever a model returns for
|
|
9
|
+
* the real AI-generated title (see `generate-design-title` action) into
|
|
10
|
+
* the same short, Title-Case, punctuation-free style used for chat names.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PLACEHOLDER_MAX = 40;
|
|
14
|
+
const GENERATED_MAX = 60;
|
|
15
|
+
// Small words stay lowercase in Title Case unless they're the first word.
|
|
16
|
+
const TITLE_CASE_MINOR_WORDS = new Set([
|
|
17
|
+
"a",
|
|
18
|
+
"an",
|
|
19
|
+
"and",
|
|
20
|
+
"as",
|
|
21
|
+
"at",
|
|
22
|
+
"but",
|
|
23
|
+
"by",
|
|
24
|
+
"for",
|
|
25
|
+
"in",
|
|
26
|
+
"of",
|
|
27
|
+
"on",
|
|
28
|
+
"or",
|
|
29
|
+
"the",
|
|
30
|
+
"to",
|
|
31
|
+
"vs",
|
|
32
|
+
"with",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Derive a short, friendly placeholder title from a prompt. The full prompt
|
|
37
|
+
* still drives generation — this is just a label that shows up in the editor
|
|
38
|
+
* header and the design card until the AI-generated title resolves.
|
|
39
|
+
*
|
|
40
|
+
* Strategy: take the first line, strip trailing punctuation, then truncate
|
|
41
|
+
* at the nearest word boundary near 40 chars (with an ellipsis when cut).
|
|
42
|
+
*/
|
|
43
|
+
export function derivePromptTitle(prompt: string): string {
|
|
44
|
+
const firstLine = prompt
|
|
45
|
+
.split("\n")[0]
|
|
46
|
+
?.trim()
|
|
47
|
+
.replace(/[.!?]+$/, "");
|
|
48
|
+
if (!firstLine) return "Untitled Design";
|
|
49
|
+
if (firstLine.length <= PLACEHOLDER_MAX) return firstLine;
|
|
50
|
+
const slice = firstLine.slice(0, PLACEHOLDER_MAX);
|
|
51
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
52
|
+
const trimmed = lastSpace > 20 ? slice.slice(0, lastSpace) : slice;
|
|
53
|
+
return `${trimmed.trim()}…`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toTitleCase(value: string): string {
|
|
57
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
58
|
+
return words
|
|
59
|
+
.map((word, index) => {
|
|
60
|
+
const lower = word.toLowerCase();
|
|
61
|
+
// Preserve words that are already all-caps (acronyms like "AI", "UI").
|
|
62
|
+
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
63
|
+
if (index > 0 && TITLE_CASE_MINOR_WORDS.has(lower)) return lower;
|
|
64
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
65
|
+
})
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Normalize a model-generated design title into the framework's short-name
|
|
71
|
+
* style: no surrounding quotes, no trailing punctuation, collapsed
|
|
72
|
+
* whitespace, Title Case, and capped at a sane length. Returns `null` when
|
|
73
|
+
* the input doesn't yield a usable title so callers can fall back to the
|
|
74
|
+
* prompt-derived placeholder instead of persisting junk.
|
|
75
|
+
*/
|
|
76
|
+
export function sanitizeGeneratedDesignTitle(raw: string): string | null {
|
|
77
|
+
let value = raw.trim();
|
|
78
|
+
if (!value) return null;
|
|
79
|
+
|
|
80
|
+
// Strip a single layer of wrapping quotes the model sometimes adds.
|
|
81
|
+
value = value.replace(/^["'“”‘’]+|["'“”‘’]+$/g, "").trim();
|
|
82
|
+
// Drop a leading "Title:" style prefix some models add.
|
|
83
|
+
value = value.replace(/^(title|name)\s*:\s*/i, "").trim();
|
|
84
|
+
// Collapse whitespace/newlines.
|
|
85
|
+
value = value.replace(/\s+/g, " ").trim();
|
|
86
|
+
// Strip trailing punctuation (periods, exclamation/question marks, colons).
|
|
87
|
+
value = value.replace(/[.!?:;,]+$/, "").trim();
|
|
88
|
+
|
|
89
|
+
if (!value) return null;
|
|
90
|
+
if (value.length > GENERATED_MAX) {
|
|
91
|
+
value = value.slice(0, GENERATED_MAX).trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return toTitleCase(value);
|
|
95
|
+
}
|