@agent-native/core 0.92.10 → 0.92.12
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 +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +214 -17
- package/corpus/core/src/agent/run-manager.ts +72 -14
- package/corpus/core/src/agent/run-store.ts +152 -27
- package/corpus/core/src/cli/design-connect.ts +433 -151
- package/corpus/core/src/cli/skills.ts +14 -0
- package/corpus/core/src/client/AssistantChat.tsx +25 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
- package/corpus/core/src/client/agent-chat-adapter.ts +56 -12
- package/corpus/core/src/client/agent-chat.ts +165 -1
- package/corpus/core/src/client/chat/index.ts +9 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
- package/corpus/core/src/collab/awareness-store.ts +23 -12
- package/corpus/core/src/collab/client.ts +16 -5
- package/corpus/core/src/collab/index.ts +4 -0
- package/corpus/core/src/collab/recent-edits.ts +65 -2
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
- package/corpus/core/src/server/builder-design-systems.ts +30 -4
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/create-recording.ts +2 -0
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
- package/corpus/templates/clips/actions/request-transcript.ts +102 -33
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +75 -9
- package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
- package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
- package/corpus/templates/clips/app/routes/record.tsx +17 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +12 -2
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +6 -3
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
- package/corpus/templates/clips/shared/recording-core.ts +12 -0
- package/corpus/templates/content/AGENTS.md +7 -6
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
- package/corpus/templates/content/actions/_document-flush.ts +132 -17
- package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
- package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
- package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/pull-document.ts +5 -7
- package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
- package/corpus/templates/content/actions/push-notion-page.ts +10 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
- package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
- package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/content/app/i18n-data.ts +18 -0
- package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
- package/corpus/templates/content/shared/api.ts +1 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
- package/corpus/templates/design/AGENTS.md +45 -10
- package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
- package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
- package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
- package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
- package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
- package/corpus/templates/design/actions/generate-design.ts +79 -12
- package/corpus/templates/design/actions/generate-screens.ts +57 -12
- package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
- package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
- package/corpus/templates/design/actions/import-design-source.ts +4 -31
- package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
- package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/design/actions/list-design-components.ts +104 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
- package/corpus/templates/design/actions/open-component-source.ts +2 -4
- package/corpus/templates/design/actions/present-design-variants.ts +412 -16
- package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
- package/corpus/templates/design/actions/run-design-audit.ts +25 -3
- package/corpus/templates/design/actions/show-design-questions.ts +10 -0
- package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
- package/corpus/templates/design/actions/write-local-file.ts +25 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
- package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
- package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
- package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
- package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
- package/corpus/templates/design/app/components/design/types.ts +33 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
- package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
- package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
- package/corpus/templates/design/app/i18n-data.ts +1217 -10
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
- package/corpus/templates/design/app/lib/design-import.ts +24 -0
- package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
- package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
- package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
- package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
- package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
- package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +3 -6
- package/corpus/templates/design/e2e/helpers.ts +27 -6
- package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
- package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
- package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
- package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
- package/corpus/templates/design/server/plugins/db.ts +41 -0
- package/corpus/templates/design/server/source-workspace.ts +64 -0
- package/corpus/templates/design/shared/board-file.ts +13 -2
- package/corpus/templates/design/shared/canvas-math.ts +231 -33
- package/corpus/templates/design/shared/code-layer.ts +122 -8
- package/corpus/templates/design/shared/component-library.ts +161 -0
- package/corpus/templates/design/shared/db-conflict.ts +27 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
- package/corpus/templates/design/shared/figma-url.ts +149 -0
- package/corpus/templates/design/shared/pen-path.ts +16 -7
- package/corpus/templates/design/shared/source-mode.ts +1 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
- package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
- package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
- package/dist/agent/production-agent.d.ts +82 -7
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +180 -17
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +63 -14
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +46 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +129 -22
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +336 -139
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +14 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +19 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +38 -18
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +48 -12
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +60 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +122 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +36 -2
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +5 -0
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +19 -12
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +13 -5
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +1 -0
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +1 -0
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +3 -1
- package/dist/collab/recent-edits.d.ts.map +1 -1
- package/dist/collab/recent-edits.js +57 -2
- package/dist/collab/recent-edits.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +87 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-design-systems.d.ts +15 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +9 -4
- package/dist/server/builder-design-systems.js.map +1 -1
- package/package.json +1 -1
|
@@ -733,6 +733,92 @@ export function dropDescendantsOfSelectedAncestors(
|
|
|
733
733
|
});
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
+
// BUG-LAYERS-MULTISELECT — Figma-parity multi-select: Cmd/Ctrl+Click toggles
|
|
737
|
+
// one row's membership in the selection; Shift+Click selects the visible
|
|
738
|
+
// range between the anchor row (the last row selected via a PLAIN click —
|
|
739
|
+
// Shift+Click never moves the anchor, so consecutive range clicks keep
|
|
740
|
+
// pivoting from the same row, matching Figma) and the clicked row; a plain
|
|
741
|
+
// click replaces the selection with just the clicked row. Extracted out of
|
|
742
|
+
// the row click handler (`selectNode` below) as a pure function so the
|
|
743
|
+
// range/toggle computation itself — anchor fallback when the anchor row
|
|
744
|
+
// scrolled out of view/was deleted, additive range-merge, and dropping a
|
|
745
|
+
// selected descendant whose ancestor is also selected — is unit-testable
|
|
746
|
+
// without mounting the panel.
|
|
747
|
+
export function computeLayerMultiSelectIds(args: {
|
|
748
|
+
id: string;
|
|
749
|
+
additive: boolean;
|
|
750
|
+
range: boolean;
|
|
751
|
+
currentSelectedIds: readonly string[];
|
|
752
|
+
anchor: string | null;
|
|
753
|
+
selectableVisibleIds: readonly string[];
|
|
754
|
+
visibleRows: readonly FlatLayerRow[];
|
|
755
|
+
// Source for the stale-anchor fallback search below. Defaults to
|
|
756
|
+
// `currentSelectedIds`. The real panel passes its own `selectedIds` prop
|
|
757
|
+
// here instead — pointer clicks pass a `currentSelectedIds` freshly
|
|
758
|
+
// re-read from the DOM (readSelectedIdsFromTree), which can transiently
|
|
759
|
+
// differ from the panel's own selection state, and the fallback has always
|
|
760
|
+
// pivoted off the latter.
|
|
761
|
+
anchorFallbackSelectedIds?: readonly string[];
|
|
762
|
+
}): { nextIds: string[]; nextAnchor: string | null } {
|
|
763
|
+
const {
|
|
764
|
+
id,
|
|
765
|
+
additive,
|
|
766
|
+
range,
|
|
767
|
+
currentSelectedIds,
|
|
768
|
+
anchor,
|
|
769
|
+
selectableVisibleIds,
|
|
770
|
+
visibleRows,
|
|
771
|
+
anchorFallbackSelectedIds = currentSelectedIds,
|
|
772
|
+
} = args;
|
|
773
|
+
const currentSelectedIdSet = new Set(currentSelectedIds);
|
|
774
|
+
let nextIds: string[];
|
|
775
|
+
// Only advance the anchor on plain clicks; Shift+clicks extend from the
|
|
776
|
+
// existing anchor so the pivot stays fixed across consecutive range
|
|
777
|
+
// clicks (returning `anchor` unchanged, including when it was never set).
|
|
778
|
+
// The one exception is the stale-anchor fallback just below, which DOES
|
|
779
|
+
// move the anchor even on a range click — it's re-pivoting onto a
|
|
780
|
+
// still-valid row, not starting a fresh selection.
|
|
781
|
+
let nextAnchor = range ? anchor : id;
|
|
782
|
+
if (range && anchor) {
|
|
783
|
+
let effectiveAnchor = anchor;
|
|
784
|
+
if (selectableVisibleIds.indexOf(effectiveAnchor) < 0) {
|
|
785
|
+
// Stale anchor (deleted / filtered / collapsed out of view): pivot from
|
|
786
|
+
// the last selected layer that is still visible & selectable, matching
|
|
787
|
+
// Figma's behavior instead of dropping the range to a single select.
|
|
788
|
+
const fallback = [...anchorFallbackSelectedIds]
|
|
789
|
+
.reverse()
|
|
790
|
+
.find((sid) => selectableVisibleIds.includes(sid));
|
|
791
|
+
if (fallback) {
|
|
792
|
+
effectiveAnchor = fallback;
|
|
793
|
+
nextAnchor = fallback;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const from = selectableVisibleIds.indexOf(effectiveAnchor);
|
|
797
|
+
const to = selectableVisibleIds.indexOf(id);
|
|
798
|
+
if (from >= 0 && to >= 0) {
|
|
799
|
+
const [start, end] = from < to ? [from, to] : [to, from];
|
|
800
|
+
const rangeIds = selectableVisibleIds.slice(start, end + 1);
|
|
801
|
+
const merged = additive
|
|
802
|
+
? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
|
|
803
|
+
: rangeIds;
|
|
804
|
+
// A range that spans an expanded parent and some of its children would
|
|
805
|
+
// otherwise co-select both. Normalize so a selected descendant whose
|
|
806
|
+
// ancestor is also selected gets dropped — the ancestor selection
|
|
807
|
+
// already implies it.
|
|
808
|
+
nextIds = dropDescendantsOfSelectedAncestors(merged, visibleRows);
|
|
809
|
+
} else {
|
|
810
|
+
nextIds = [id];
|
|
811
|
+
}
|
|
812
|
+
} else if (additive) {
|
|
813
|
+
nextIds = currentSelectedIdSet.has(id)
|
|
814
|
+
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
815
|
+
: [...currentSelectedIds, id];
|
|
816
|
+
} else {
|
|
817
|
+
nextIds = [id];
|
|
818
|
+
}
|
|
819
|
+
return { nextIds, nextAnchor };
|
|
820
|
+
}
|
|
821
|
+
|
|
736
822
|
export function getDraggedLayerIdsForRows(args: {
|
|
737
823
|
selectedIds: readonly string[];
|
|
738
824
|
nodeId: string;
|
|
@@ -745,12 +831,21 @@ export function getDraggedLayerIdsForRows(args: {
|
|
|
745
831
|
// extract them from the parent being dragged. Optional only for
|
|
746
832
|
// call-site/back-compat convenience; always pass it in the real panel.
|
|
747
833
|
ancestorIdMap?: ReadonlyMap<string, string[]>;
|
|
834
|
+
// Full-tree node lookup used to keep locked layers out of a multi-layer
|
|
835
|
+
// drag payload. Figma lets a locked row remain selected alongside unlocked
|
|
836
|
+
// rows, but dragging one of the unlocked rows must not silently move the
|
|
837
|
+
// locked selection too. Optional for pure-helper/back-compat callers; the
|
|
838
|
+
// real panel always passes it from the same full roots used above.
|
|
839
|
+
nodeById?: ReadonlyMap<string, LayersPanelNode>;
|
|
748
840
|
}): string[] {
|
|
749
841
|
const rawDraggedIds = args.selectedIds.includes(args.nodeId)
|
|
750
842
|
? getTreeOrderedLayerIds(args.selectedIds, args.visibleRows)
|
|
751
843
|
: [args.nodeId];
|
|
752
|
-
const
|
|
753
|
-
|
|
844
|
+
const movableDraggedIds = rawDraggedIds.filter(
|
|
845
|
+
(id) => args.nodeById?.get(id)?.locked !== true,
|
|
846
|
+
);
|
|
847
|
+
const draggedIdSet = new Set(movableDraggedIds);
|
|
848
|
+
return movableDraggedIds.filter((id) => {
|
|
754
849
|
const ancestorIds =
|
|
755
850
|
args.ancestorIdMap?.get(id) ??
|
|
756
851
|
args.visibleRows.find((row) => row.node.id === id)?.ancestorIds;
|
|
@@ -758,6 +853,21 @@ export function getDraggedLayerIdsForRows(args: {
|
|
|
758
853
|
});
|
|
759
854
|
}
|
|
760
855
|
|
|
856
|
+
/** Builds an id-to-node lookup over the full tree. Kept separate from the
|
|
857
|
+
* ancestor map because drag-start needs both node state (locked) and ancestry,
|
|
858
|
+
* while other callers only need one or the other. */
|
|
859
|
+
export function buildLayerNodeMap(
|
|
860
|
+
nodes: readonly LayersPanelNode[],
|
|
861
|
+
): Map<string, LayersPanelNode> {
|
|
862
|
+
const map = new Map<string, LayersPanelNode>();
|
|
863
|
+
const visit = (node: LayersPanelNode) => {
|
|
864
|
+
map.set(node.id, node);
|
|
865
|
+
node.children?.forEach(visit);
|
|
866
|
+
};
|
|
867
|
+
nodes.forEach(visit);
|
|
868
|
+
return map;
|
|
869
|
+
}
|
|
870
|
+
|
|
761
871
|
// Row context-menu actions (copy/duplicate/delete/group/reorder) operate on
|
|
762
872
|
// the whole current selection when the right-clicked row is already part of
|
|
763
873
|
// it (matching Figma), or on just that row otherwise (right-clicking an
|
|
@@ -1010,57 +1120,25 @@ function LayersPanelImpl(
|
|
|
1010
1120
|
) => {
|
|
1011
1121
|
const currentSelectedIds =
|
|
1012
1122
|
options.currentSelectedIds ?? selectedIdsRef.current;
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
}
|
|
1029
|
-
const from = selectableVisibleIds.indexOf(anchor);
|
|
1030
|
-
const to = selectableVisibleIds.indexOf(id);
|
|
1031
|
-
if (from >= 0 && to >= 0) {
|
|
1032
|
-
const [start, end] = from < to ? [from, to] : [to, from];
|
|
1033
|
-
const rangeIds = selectableVisibleIds.slice(start, end + 1);
|
|
1034
|
-
const merged = options.additive
|
|
1035
|
-
? Array.from(new Set([...currentSelectedIds, ...rangeIds]))
|
|
1036
|
-
: rangeIds;
|
|
1037
|
-
// A range that spans an expanded parent and some of its children
|
|
1038
|
-
// would otherwise co-select both. Normalize so a selected
|
|
1039
|
-
// descendant whose ancestor is also selected gets dropped — the
|
|
1040
|
-
// ancestor selection already implies it.
|
|
1041
|
-
nextIds = dropDescendantsOfSelectedAncestors(
|
|
1042
|
-
merged,
|
|
1043
|
-
visibleRowsRef.current,
|
|
1044
|
-
);
|
|
1045
|
-
} else {
|
|
1046
|
-
nextIds = [id];
|
|
1047
|
-
}
|
|
1048
|
-
} else if (options.additive) {
|
|
1049
|
-
nextIds = currentSelectedIdSet.has(id)
|
|
1050
|
-
? currentSelectedIds.filter((selectedId) => selectedId !== id)
|
|
1051
|
-
: [...currentSelectedIds, id];
|
|
1052
|
-
} else {
|
|
1053
|
-
nextIds = [id];
|
|
1054
|
-
}
|
|
1055
|
-
// Only advance the anchor on plain clicks; Shift+clicks extend from the
|
|
1056
|
-
// existing anchor so the pivot stays fixed across consecutive range clicks.
|
|
1057
|
-
if (!options.range) {
|
|
1058
|
-
lastSelectionAnchorRef.current = id;
|
|
1059
|
-
}
|
|
1123
|
+
// NOTE: the stale-anchor fallback intentionally searches the PANEL'S
|
|
1124
|
+
// OWN `selectedIds` prop, not `currentSelectedIds` (which pointer
|
|
1125
|
+
// clicks pass in freshly re-read from the DOM via readSelectedIdsFromTree
|
|
1126
|
+
// and can transiently differ) — matches the pre-extraction behavior.
|
|
1127
|
+
const { nextIds, nextAnchor } = computeLayerMultiSelectIds({
|
|
1128
|
+
id,
|
|
1129
|
+
additive: options.additive,
|
|
1130
|
+
range: options.range,
|
|
1131
|
+
currentSelectedIds,
|
|
1132
|
+
anchor: lastSelectionAnchorRef.current,
|
|
1133
|
+
selectableVisibleIds,
|
|
1134
|
+
visibleRows: visibleRowsRef.current,
|
|
1135
|
+
anchorFallbackSelectedIds: selectedIds,
|
|
1136
|
+
});
|
|
1137
|
+
lastSelectionAnchorRef.current = nextAnchor;
|
|
1060
1138
|
lastPanelSelectionSignatureRef.current = nextIds.join("\0");
|
|
1061
1139
|
onSelectionChange(nextIds, { id, selectedIds: nextIds, ...options });
|
|
1062
1140
|
},
|
|
1063
|
-
[onSelectionChange, selectableVisibleIds],
|
|
1141
|
+
[onSelectionChange, selectableVisibleIds, selectedIds],
|
|
1064
1142
|
);
|
|
1065
1143
|
|
|
1066
1144
|
const commitRename = useCallback(
|
|
@@ -1606,6 +1684,7 @@ const LayerRow = memo(function LayerRow({
|
|
|
1606
1684
|
onFlipHorizontal,
|
|
1607
1685
|
onFlipVertical,
|
|
1608
1686
|
}: LayerRowProps) {
|
|
1687
|
+
const t = useT();
|
|
1609
1688
|
const { node, depth, hasChildren, canAcceptChildren } = row;
|
|
1610
1689
|
const isComponentLayer = layerNodeIsComponent(node);
|
|
1611
1690
|
const selectable = node.selectable !== false;
|
|
@@ -1734,12 +1813,20 @@ const LayerRow = memo(function LayerRow({
|
|
|
1734
1813
|
// descendant nested inside a currently-collapsed dragged ancestor is
|
|
1735
1814
|
// still correctly recognized as a descendant and excluded from the drag
|
|
1736
1815
|
// payload (see L13 / buildAncestorIdMap).
|
|
1737
|
-
const
|
|
1816
|
+
const roots = rootsRef.current;
|
|
1817
|
+
const ancestorIdMap = buildAncestorIdMap(roots);
|
|
1818
|
+
const nodeById = buildLayerNodeMap(roots);
|
|
1819
|
+
// Search/collapse only changes which rows are painted; it must not change
|
|
1820
|
+
// the structural order of an existing multi-selection's drag payload.
|
|
1821
|
+
// Flatten the complete tree for ordering, while the visible rows remain
|
|
1822
|
+
// the interaction surface that actually started the gesture.
|
|
1823
|
+
const allRows = flattenRows(roots, new Set(), true);
|
|
1738
1824
|
const draggedIds = getDraggedLayerIdsForRows({
|
|
1739
1825
|
selectedIds: selectedIdsRef.current,
|
|
1740
1826
|
nodeId: node.id,
|
|
1741
|
-
visibleRows:
|
|
1827
|
+
visibleRows: allRows,
|
|
1742
1828
|
ancestorIdMap,
|
|
1829
|
+
nodeById,
|
|
1743
1830
|
});
|
|
1744
1831
|
event.dataTransfer.effectAllowed = "move";
|
|
1745
1832
|
event.dataTransfer.setData("application/x-design-layer-id", node.id);
|
|
@@ -1747,6 +1834,27 @@ const LayerRow = memo(function LayerRow({
|
|
|
1747
1834
|
"application/x-design-layer-ids",
|
|
1748
1835
|
JSON.stringify(draggedIds),
|
|
1749
1836
|
);
|
|
1837
|
+
// Figma parity: dragging 2+ selected layers shows a small "N layers"
|
|
1838
|
+
// count badge following the cursor instead of the browser's default
|
|
1839
|
+
// drag image — a screenshot of just the ONE row that received this
|
|
1840
|
+
// native dragstart event, even though every selected row moves together.
|
|
1841
|
+
// setDragImage requires the image element to be attached to the DOM at
|
|
1842
|
+
// the moment it's called, but not after — build an offscreen node here,
|
|
1843
|
+
// wire it up, and detach it on the next frame. Single-layer drags are
|
|
1844
|
+
// unaffected (kept exactly as before: the browser's own row snapshot).
|
|
1845
|
+
if (draggedIds.length > 1) {
|
|
1846
|
+
const ghost = document.createElement("div");
|
|
1847
|
+
ghost.textContent = t("layersPanel.dragGhostCount", {
|
|
1848
|
+
count: draggedIds.length,
|
|
1849
|
+
});
|
|
1850
|
+
ghost.className =
|
|
1851
|
+
"fixed left-[-9999px] top-[-9999px] pointer-events-none select-none whitespace-nowrap rounded-full border border-[var(--design-editor-control-border)] bg-[var(--design-editor-panel-bg)] px-2.5 py-1 text-[11px] font-medium leading-none text-foreground shadow-[0_4px_16px_rgba(0,0,0,0.16),0_0_0_0.5px_rgba(0,0,0,0.08)]";
|
|
1852
|
+
document.body.appendChild(ghost);
|
|
1853
|
+
event.dataTransfer.setDragImage(ghost, -12, -12);
|
|
1854
|
+
requestAnimationFrame(() => {
|
|
1855
|
+
ghost.remove();
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1750
1858
|
// Store drag state at module level so handleDragOver can read it.
|
|
1751
1859
|
// dataTransfer.getData() returns "" during dragover per the HTML spec.
|
|
1752
1860
|
activeDragState = { sourceId: node.id, draggedIds };
|
|
@@ -2435,7 +2543,11 @@ function LayerGlyph({
|
|
|
2435
2543
|
case "board-element":
|
|
2436
2544
|
case "shape":
|
|
2437
2545
|
case "rectangle":
|
|
2438
|
-
return
|
|
2546
|
+
return shapeLayerUsesLayoutGlyph(node) ? (
|
|
2547
|
+
<LayoutLayerGlyph node={node} className={common} />
|
|
2548
|
+
) : (
|
|
2549
|
+
<RectangleLayerGlyph className={common} />
|
|
2550
|
+
);
|
|
2439
2551
|
case "vector":
|
|
2440
2552
|
return <VectorLayerGlyph className={common} />;
|
|
2441
2553
|
case "line":
|
|
@@ -2462,6 +2574,21 @@ function LayerGlyph({
|
|
|
2462
2574
|
}
|
|
2463
2575
|
}
|
|
2464
2576
|
|
|
2577
|
+
/**
|
|
2578
|
+
* A canvas rectangle starts as a shape, but nest-on-drop can promote it to a
|
|
2579
|
+
* flex/grid container. Once promoted, show the same auto-layout glyph as a
|
|
2580
|
+
* frame so the Layers tree reflects the layer's real layout behavior instead
|
|
2581
|
+
* of continuing to advertise it as a leaf rectangle.
|
|
2582
|
+
*/
|
|
2583
|
+
export function shapeLayerUsesLayoutGlyph(
|
|
2584
|
+
node: Pick<LayersPanelNode, "type" | "layout">,
|
|
2585
|
+
): boolean {
|
|
2586
|
+
return (
|
|
2587
|
+
["board-element", "shape", "rectangle"].includes(node.type ?? "") &&
|
|
2588
|
+
Boolean(node.layout?.isFlexContainer || node.layout?.isGridContainer)
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2465
2592
|
function layerNodeTagName(
|
|
2466
2593
|
node: Pick<LayersPanelNode, "tagName" | "detail">,
|
|
2467
2594
|
): string | null {
|
|
@@ -21,7 +21,6 @@ export interface LocalhostWriteConsentPayload {
|
|
|
21
21
|
/** Pending callback to invoke after user grants consent. */
|
|
22
22
|
onGranted: (grant: {
|
|
23
23
|
grantId: string;
|
|
24
|
-
bridgeToken: string;
|
|
25
24
|
rootPath: string;
|
|
26
25
|
grantedUntil: string;
|
|
27
26
|
}) => void;
|
|
@@ -58,7 +57,6 @@ export function LocalhostWriteConsentDialog({
|
|
|
58
57
|
try {
|
|
59
58
|
const result = await callAction<{
|
|
60
59
|
grantId: string;
|
|
61
|
-
bridgeToken: string;
|
|
62
60
|
rootPath: string;
|
|
63
61
|
grantedUntil: string;
|
|
64
62
|
}>("grant-localhost-write-consent", {
|
|
@@ -139,6 +139,21 @@ function formatMsTick(ms: number): string {
|
|
|
139
139
|
return `${s}s`;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Whether committing on this key should also suppress the blur handler's own
|
|
144
|
+
* commit. Enter/Escape both trigger an explicit `.blur()` call right after
|
|
145
|
+
* committing so the field visually defocuses; without this guard, that
|
|
146
|
+
* synchronous blur re-invokes the (still stale, same-render) commit callback
|
|
147
|
+
* a second time in the same tick — a harmless-looking no-op for identical
|
|
148
|
+
* values, but it double-fires side effects like `onPlayheadChange` /
|
|
149
|
+
* `onDurationChange` and the preview postMessage. Mirrors
|
|
150
|
+
* `propInputKeyRequiresBlurGuard` in edit-panel/panel-primitives.tsx (the
|
|
151
|
+
* same bug class, fixed there for style-property fields).
|
|
152
|
+
*/
|
|
153
|
+
export function motionFieldKeyRequiresBlurGuard(key: string): boolean {
|
|
154
|
+
return key === "Enter" || key === "Escape";
|
|
155
|
+
}
|
|
156
|
+
|
|
142
157
|
/** Human label for an ease value: preset name when recognised, else raw. */
|
|
143
158
|
function easeLabel(
|
|
144
159
|
ease: MotionEase | undefined,
|
|
@@ -330,6 +345,12 @@ export function MotionDock({
|
|
|
330
345
|
|
|
331
346
|
// Current-time field draft (null = displaying the live playhead).
|
|
332
347
|
const [timeDraft, setTimeDraft] = useState<string | null>(null);
|
|
348
|
+
// See motionFieldKeyRequiresBlurGuard: set right before an Enter-triggered
|
|
349
|
+
// `.blur()` so the blur handler that fires in the same synchronous tick
|
|
350
|
+
// skips its own commit instead of double-invoking commitTimeDraft.
|
|
351
|
+
const skipNextTimeBlurCommitRef = useRef(false);
|
|
352
|
+
// Same guard for the duration field's Enter -> blur -> onBlur-commit path.
|
|
353
|
+
const skipNextDurationBlurCommitRef = useRef(false);
|
|
333
354
|
/** Internal high-frequency playhead update — does NOT notify the parent. */
|
|
334
355
|
const setPlayheadLocal = useCallback(
|
|
335
356
|
(next: number) => {
|
|
@@ -750,6 +771,16 @@ export function MotionDock({
|
|
|
750
771
|
timeDraft,
|
|
751
772
|
]);
|
|
752
773
|
|
|
774
|
+
// ── Duration field ────────────────────────────────────────────────────────
|
|
775
|
+
const commitDurationDraft = useCallback(() => {
|
|
776
|
+
const ms = parseInt(durationInput, 10);
|
|
777
|
+
if (!isNaN(ms) && ms >= 50) {
|
|
778
|
+
onDurationChange?.(ms);
|
|
779
|
+
} else {
|
|
780
|
+
setDurationInput(String(durationMs));
|
|
781
|
+
}
|
|
782
|
+
}, [durationInput, durationMs, onDurationChange]);
|
|
783
|
+
|
|
753
784
|
// ── Space plays/pauses while the dock has focus (Figma parity) ───────────
|
|
754
785
|
const handleDockKeyDown = useCallback(
|
|
755
786
|
(e: ReactKeyboardEvent<HTMLDivElement>) => {
|
|
@@ -947,11 +978,32 @@ export function MotionDock({
|
|
|
947
978
|
setTimeDraft(String(Math.round(playhead * durationMs)))
|
|
948
979
|
}
|
|
949
980
|
onChange={(e) => setTimeDraft(e.target.value)}
|
|
950
|
-
onBlur={
|
|
981
|
+
onBlur={() => {
|
|
982
|
+
if (skipNextTimeBlurCommitRef.current) {
|
|
983
|
+
skipNextTimeBlurCommitRef.current = false;
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
commitTimeDraft();
|
|
987
|
+
}}
|
|
951
988
|
onKeyDown={(e) => {
|
|
952
989
|
if (e.key === "Enter") {
|
|
953
990
|
e.preventDefault();
|
|
954
991
|
commitTimeDraft();
|
|
992
|
+
// Without this, the blur triggered below re-enters
|
|
993
|
+
// commitTimeDraft() a second time in the same
|
|
994
|
+
// synchronous tick (see motionFieldKeyRequiresBlurGuard).
|
|
995
|
+
skipNextTimeBlurCommitRef.current =
|
|
996
|
+
motionFieldKeyRequiresBlurGuard(e.key);
|
|
997
|
+
(e.target as HTMLInputElement).blur();
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (e.key === "Escape") {
|
|
1001
|
+
e.preventDefault();
|
|
1002
|
+
// Cancel the in-progress edit — revert to displaying the
|
|
1003
|
+
// live playhead instead of committing the draft.
|
|
1004
|
+
setTimeDraft(null);
|
|
1005
|
+
skipNextTimeBlurCommitRef.current =
|
|
1006
|
+
motionFieldKeyRequiresBlurGuard(e.key);
|
|
955
1007
|
(e.target as HTMLInputElement).blur();
|
|
956
1008
|
}
|
|
957
1009
|
}}
|
|
@@ -973,11 +1025,31 @@ export function MotionDock({
|
|
|
973
1025
|
value={durationInput}
|
|
974
1026
|
onChange={(e) => setDurationInput(e.target.value)}
|
|
975
1027
|
onBlur={() => {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
1028
|
+
if (skipNextDurationBlurCommitRef.current) {
|
|
1029
|
+
skipNextDurationBlurCommitRef.current = false;
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
commitDurationDraft();
|
|
1033
|
+
}}
|
|
1034
|
+
onKeyDown={(e) => {
|
|
1035
|
+
if (e.key === "Enter") {
|
|
1036
|
+
e.preventDefault();
|
|
1037
|
+
commitDurationDraft();
|
|
1038
|
+
// Same double-commit guard as the current-time field —
|
|
1039
|
+
// see motionFieldKeyRequiresBlurGuard.
|
|
1040
|
+
skipNextDurationBlurCommitRef.current =
|
|
1041
|
+
motionFieldKeyRequiresBlurGuard(e.key);
|
|
1042
|
+
(e.target as HTMLInputElement).blur();
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (e.key === "Escape") {
|
|
1046
|
+
e.preventDefault();
|
|
1047
|
+
// Cancel the in-progress edit — revert to the last
|
|
1048
|
+
// committed duration instead of persisting the draft.
|
|
980
1049
|
setDurationInput(String(durationMs));
|
|
1050
|
+
skipNextDurationBlurCommitRef.current =
|
|
1051
|
+
motionFieldKeyRequiresBlurGuard(e.key);
|
|
1052
|
+
(e.target as HTMLInputElement).blur();
|
|
981
1053
|
}
|
|
982
1054
|
}}
|
|
983
1055
|
className="h-5 w-16 px-1 !text-[11px] md:!text-[11px]"
|
|
@@ -2106,6 +2178,68 @@ interface CurveEditorProps {
|
|
|
2106
2178
|
onChange: (value: [number, number, number, number]) => void;
|
|
2107
2179
|
}
|
|
2108
2180
|
|
|
2181
|
+
/** Format a bezier control-point axis value for display (2 decimal places). */
|
|
2182
|
+
export function formatCurveAxisValue(n: number): string {
|
|
2183
|
+
return String(Math.round(n * 100) / 100);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
interface CurveNumberFieldProps {
|
|
2187
|
+
label: string;
|
|
2188
|
+
value: number;
|
|
2189
|
+
onChange: (n: number) => void;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/**
|
|
2193
|
+
* One x1/y1/x2/y2 control-point number field in the bezier CurveEditor.
|
|
2194
|
+
* Keeps its own draft string instead of being fully controlled by
|
|
2195
|
+
* `formatCurveAxisValue(value)` so a user can type an in-progress value
|
|
2196
|
+
* ("-", "0.", "1.5") without the field snapping back to the last-committed,
|
|
2197
|
+
* rounded value on every keystroke. A prior version bound `value` directly to
|
|
2198
|
+
* the rounded prop: typing "0" then "." called onChange(0) (parseFloat("0.")
|
|
2199
|
+
* is a finite 0), which re-rendered the field back to "0" and silently
|
|
2200
|
+
* dropped the "." the instant it was typed — decimals (and a bare "-" before
|
|
2201
|
+
* a negative number) could never be entered. Mirrors the `focusedRef` resync
|
|
2202
|
+
* guard in edit-panel/panel-primitives.tsx's LengthField (same "mid-edit prop
|
|
2203
|
+
* stomp" bug class fixed there for style-property fields).
|
|
2204
|
+
*/
|
|
2205
|
+
function CurveNumberField({ label, value, onChange }: CurveNumberFieldProps) {
|
|
2206
|
+
const [draft, setDraft] = useState(() => formatCurveAxisValue(value));
|
|
2207
|
+
const focusedRef = useRef(false);
|
|
2208
|
+
|
|
2209
|
+
useEffect(() => {
|
|
2210
|
+
if (!focusedRef.current) setDraft(formatCurveAxisValue(value));
|
|
2211
|
+
}, [value]);
|
|
2212
|
+
|
|
2213
|
+
return (
|
|
2214
|
+
<label className="flex flex-col items-stretch gap-0.5">
|
|
2215
|
+
<span className="text-center text-[9px] text-muted-foreground">
|
|
2216
|
+
{label}
|
|
2217
|
+
</span>
|
|
2218
|
+
<Input
|
|
2219
|
+
type="number"
|
|
2220
|
+
step={0.01}
|
|
2221
|
+
value={draft}
|
|
2222
|
+
onFocus={() => {
|
|
2223
|
+
focusedRef.current = true;
|
|
2224
|
+
}}
|
|
2225
|
+
onChange={(e) => {
|
|
2226
|
+
setDraft(e.target.value);
|
|
2227
|
+
const n = parseFloat(e.target.value);
|
|
2228
|
+
if (Number.isFinite(n)) onChange(n);
|
|
2229
|
+
}}
|
|
2230
|
+
onBlur={() => {
|
|
2231
|
+
focusedRef.current = false;
|
|
2232
|
+
// Snap back to the canonical rounded string — clears any
|
|
2233
|
+
// unparsed/partial trailing input ("-", "1.") left over from typing.
|
|
2234
|
+
setDraft(formatCurveAxisValue(value));
|
|
2235
|
+
}}
|
|
2236
|
+
className="h-5 px-1 !text-[10px] md:!text-[10px]"
|
|
2237
|
+
aria-label={label}
|
|
2238
|
+
/>
|
|
2239
|
+
</label>
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2109
2243
|
function CurveEditor({ value, onChange }: CurveEditorProps) {
|
|
2110
2244
|
const t = useT();
|
|
2111
2245
|
const [x1, y1, x2, y2] = value;
|
|
@@ -2151,22 +2285,11 @@ function CurveEditor({ value, onChange }: CurveEditorProps) {
|
|
|
2151
2285
|
fieldValue: number,
|
|
2152
2286
|
apply: (n: number) => [number, number, number, number],
|
|
2153
2287
|
) => (
|
|
2154
|
-
<
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
type="number"
|
|
2160
|
-
step={0.01}
|
|
2161
|
-
value={String(Math.round(fieldValue * 100) / 100)}
|
|
2162
|
-
onChange={(e) => {
|
|
2163
|
-
const n = parseFloat(e.target.value);
|
|
2164
|
-
if (Number.isFinite(n)) onChange(apply(n));
|
|
2165
|
-
}}
|
|
2166
|
-
className="h-5 px-1 !text-[10px] md:!text-[10px]"
|
|
2167
|
-
aria-label={label}
|
|
2168
|
-
/>
|
|
2169
|
-
</label>
|
|
2288
|
+
<CurveNumberField
|
|
2289
|
+
label={label}
|
|
2290
|
+
value={fieldValue}
|
|
2291
|
+
onChange={(n) => onChange(apply(n))}
|
|
2292
|
+
/>
|
|
2170
2293
|
);
|
|
2171
2294
|
|
|
2172
2295
|
return (
|