@agent-native/core 0.92.9 → 0.92.11
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 +181 -15
- package/corpus/core/src/agent/run-manager.ts +31 -4
- package/corpus/core/src/agent/run-store.ts +80 -0
- 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.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/deploy/build.ts +133 -5
- 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 +48 -4
- package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
- 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/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/_document-flush.ts +132 -17
- package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
- 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/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-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/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 +79 -6
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +161 -14
- 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 +29 -5
- 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 +61 -0
- 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.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/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +24 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +102 -5
- package/dist/deploy/build.js.map +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/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- 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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "legacy" Figma paste fallback: saves the visible clipboard HTML
|
|
3
|
+
* (markers stripped by `parseVisibleClipboardHtml`) as a new Design screen.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `import-design-source.ts`'s `figma-paste-html` branch so the
|
|
6
|
+
* REST-first clipboard import path (`import-figma-clipboard.ts`) can fall back
|
|
7
|
+
* to the exact same behavior when node resolution can't confidently match the
|
|
8
|
+
* pasted content to specific Figma nodes, instead of re-implementing it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
normalizeImportedHtmlDocument,
|
|
13
|
+
resolveImportDesignId,
|
|
14
|
+
saveImportedDesignFiles,
|
|
15
|
+
} from "./import-design-files.js";
|
|
16
|
+
import { parseVisibleClipboardHtml } from "./visible-clipboard-html.js";
|
|
17
|
+
|
|
18
|
+
export interface SaveFigmaPasteHtmlFallbackInput {
|
|
19
|
+
designId?: string;
|
|
20
|
+
clipboardHtml: string;
|
|
21
|
+
originalName?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function baseFilename(originalName: string | undefined, fallback: string) {
|
|
25
|
+
return (originalName?.trim() || fallback).replace(/\.[^.]+$/, "") + ".html";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function saveFigmaPasteHtmlFallback(
|
|
29
|
+
input: SaveFigmaPasteHtmlFallbackInput,
|
|
30
|
+
) {
|
|
31
|
+
const parsed = parseVisibleClipboardHtml(input.clipboardHtml);
|
|
32
|
+
if (!parsed.fallbackHtml) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"No visible HTML was found in the clipboard. Copy a frame, then paste into the canvas.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const designId = await resolveImportDesignId(input.designId);
|
|
38
|
+
const saved = await saveImportedDesignFiles({
|
|
39
|
+
designId,
|
|
40
|
+
sourceType: "figma-paste-html",
|
|
41
|
+
files: [
|
|
42
|
+
{
|
|
43
|
+
filename: baseFilename(input.originalName, "figma-paste"),
|
|
44
|
+
fileType: "html",
|
|
45
|
+
content: normalizeImportedHtmlDocument(
|
|
46
|
+
parsed.fallbackHtml,
|
|
47
|
+
"visible clipboard HTML",
|
|
48
|
+
),
|
|
49
|
+
source: {
|
|
50
|
+
sourceType: "figma-paste-html",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
...saved,
|
|
57
|
+
stats: {
|
|
58
|
+
sourceKind: "figma-paste",
|
|
59
|
+
format: "html",
|
|
60
|
+
frameCount: saved.files.length,
|
|
61
|
+
imageCount: 0,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* playwright-runtime.ts — shared headless-Chromium bootstrap used by every
|
|
3
|
+
* server-side action that needs a real rendered DOM (as opposed to static
|
|
4
|
+
* HTML/CSS analysis): `take-design-screenshot.ts`'s visual diagnostics pass
|
|
5
|
+
* and `design-to-figma-svg.ts`'s scene extractor for the Figma SVG export.
|
|
6
|
+
*
|
|
7
|
+
* Extracted out of `take-design-screenshot.ts` (which originally owned this
|
|
8
|
+
* logic) so `server/lib/*` modules can share it without an inverted
|
|
9
|
+
* lib -> action dependency. `take-design-screenshot.ts` re-exports these same
|
|
10
|
+
* names for backward compatibility with its existing spec/imports.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type PlaywrightModule = {
|
|
14
|
+
chromium: import("@playwright/test").BrowserType;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Dynamic import of a real Chromium-capable Playwright package. Tries the
|
|
19
|
+
* bare `"playwright"` package first (present when `@agent-native/core`'s
|
|
20
|
+
* optional dependency resolved), then falls back to `@playwright/test` (a
|
|
21
|
+
* direct devDependency of this template, used by its own e2e suite, which
|
|
22
|
+
* re-exports the same chromium/Browser API). Loaded via a non-literal
|
|
23
|
+
* specifier so bundlers don't try to statically resolve/include it — it's
|
|
24
|
+
* optional and can be entirely absent (e.g. in a hosted deploy).
|
|
25
|
+
*/
|
|
26
|
+
export async function importPlaywright(): Promise<PlaywrightModule> {
|
|
27
|
+
try {
|
|
28
|
+
const specifier = "playwright";
|
|
29
|
+
return (await import(
|
|
30
|
+
/* @vite-ignore */ specifier
|
|
31
|
+
)) as unknown as PlaywrightModule;
|
|
32
|
+
} catch {
|
|
33
|
+
return (await import("@playwright/test")) as unknown as PlaywrightModule;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const SYSTEM_CHROME_EXECUTABLES = [
|
|
38
|
+
"/usr/bin/google-chrome-stable",
|
|
39
|
+
"/usr/bin/google-chrome",
|
|
40
|
+
"/usr/bin/chromium-browser",
|
|
41
|
+
"/usr/bin/chromium",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** Pure classifier for "no Chromium binary available" errors. */
|
|
45
|
+
export function isMissingBrowserError(err: unknown): boolean {
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
return /Executable doesn't exist|playwright install|browser.*not found|chromium.*not found/i.test(
|
|
48
|
+
message,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Launches Chromium, falling back to a system Chrome/Chromium binary when
|
|
53
|
+
* Playwright's bundled browser isn't installed (hosted/serverless deploys). */
|
|
54
|
+
export async function launchChromium(
|
|
55
|
+
chromium: import("@playwright/test").BrowserType,
|
|
56
|
+
): Promise<import("@playwright/test").Browser> {
|
|
57
|
+
const launchOptions = { args: ["--no-sandbox"] };
|
|
58
|
+
try {
|
|
59
|
+
return await chromium.launch(launchOptions);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (!isMissingBrowserError(err)) throw err;
|
|
62
|
+
const { existsSync } = await import("node:fs");
|
|
63
|
+
for (const executablePath of SYSTEM_CHROME_EXECUTABLES) {
|
|
64
|
+
if (!existsSync(executablePath)) continue;
|
|
65
|
+
try {
|
|
66
|
+
return await chromium.launch({ ...launchOptions, executablePath });
|
|
67
|
+
} catch {
|
|
68
|
+
// Try the next candidate; the original error is rethrown below.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// NOTE: no shared `chromiumUnavailableReason` here on purpose — each caller's
|
|
76
|
+
// message should name ITS OWN fallback (e.g. `take-design-screenshot.ts`
|
|
77
|
+
// points at `run-design-audit`; the Figma SVG export points at `export-svg`),
|
|
78
|
+
// so that stays a small, action-local export next to each call site.
|
|
@@ -356,8 +356,49 @@ ALTER TABLE design_files ADD COLUMN IF NOT EXISTS content_operation_result_hash
|
|
|
356
356
|
* drops, renames, or retypes anything — and any failure here is logged and
|
|
357
357
|
* swallowed so it can never fail boot.
|
|
358
358
|
*/
|
|
359
|
+
/**
|
|
360
|
+
* Best-effort unique index guarding against the add-localhost-screens
|
|
361
|
+
* cross-request race: two concurrent calls placing the same localhost route
|
|
362
|
+
* each see "no existing design_files row" from their own snapshot and both
|
|
363
|
+
* insert a fresh row using the same deterministic filename for that route
|
|
364
|
+
* (see actions/add-localhost-screens.ts). A unique index on
|
|
365
|
+
* (design_id, filename) makes the losing insert fail instead of silently
|
|
366
|
+
* creating an overlapping duplicate screen.
|
|
367
|
+
*
|
|
368
|
+
* This intentionally does NOT live in `runDesignMigrations` above:
|
|
369
|
+
* `CREATE UNIQUE INDEX` fails outright against any database that already
|
|
370
|
+
* contains a duplicate (design_id, filename) pair — e.g. from this exact race
|
|
371
|
+
* having already fired before this fix shipped — and `runMigrations` has no
|
|
372
|
+
* way to tolerate that failure class (only lock-timeout / duplicate-column /
|
|
373
|
+
* permission errors are swallowed there). A failed migration entry blocks
|
|
374
|
+
* every later entry in the list forever: it crashes the process outright in
|
|
375
|
+
* local dev, and on serverless it silently stops applying anything past it on
|
|
376
|
+
* every cold start. So this runs as its own best-effort step instead: created
|
|
377
|
+
* when possible, safely skipped (with a warning) when legacy duplicates block
|
|
378
|
+
* it. `add-localhost-screens.ts`'s insert path tolerates either outcome — it
|
|
379
|
+
* catches a real unique-violation from this index and falls back to updating
|
|
380
|
+
* the row that won the race, and is a no-op/no-crash path when the index
|
|
381
|
+
* isn't present yet.
|
|
382
|
+
*/
|
|
383
|
+
async function ensureDesignFilesUniqueIndex(): Promise<void> {
|
|
384
|
+
try {
|
|
385
|
+
await getDbExec().execute(
|
|
386
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS design_files_design_filename_unique_idx ON design_files (design_id, filename)`,
|
|
387
|
+
);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
console.warn(
|
|
390
|
+
"[db] design_files_design_filename_unique_idx not created — likely " +
|
|
391
|
+
"pre-existing duplicate (design_id, filename) rows from the " +
|
|
392
|
+
"add-localhost-screens race predating this fix. New concurrent " +
|
|
393
|
+
"inserts remain best-effort until the duplicates are cleaned up:",
|
|
394
|
+
err instanceof Error ? err.message : err,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
359
399
|
export default async (nitroApp: any): Promise<void> => {
|
|
360
400
|
await runDesignMigrations(nitroApp);
|
|
401
|
+
await ensureDesignFilesUniqueIndex();
|
|
361
402
|
try {
|
|
362
403
|
const summary = await ensureAdditiveColumns({
|
|
363
404
|
db: getDbExec(),
|
|
@@ -211,6 +211,70 @@ export async function readLiveSourceFile(file: SourceWorkspaceFile): Promise<{
|
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* A caller-supplied editor snapshot has two distinct identities:
|
|
216
|
+
*
|
|
217
|
+
* - `content` is the working copy the mutation must transform (it may contain
|
|
218
|
+
* unsaved local edits), while
|
|
219
|
+
* - `expectedVersionHash` is the live source version that working copy is
|
|
220
|
+
* allowed to replace.
|
|
221
|
+
*
|
|
222
|
+
* Hashing `currentContent` for both roles creates a false conflict whenever a
|
|
223
|
+
* local working copy is legitimately ahead of the persisted/live base. This
|
|
224
|
+
* helper accepts that working copy only when its SQL revision still matches
|
|
225
|
+
* and the live document is either the persisted base it was derived from or
|
|
226
|
+
* the working copy itself (for callers that already published it to Yjs).
|
|
227
|
+
* Any third live value is a genuine concurrent edit and fails closed. The
|
|
228
|
+
* returned live hash must be passed to `writeInlineSourceFile`, whose
|
|
229
|
+
* read-check-write lock closes the race after this preparation step.
|
|
230
|
+
*/
|
|
231
|
+
export class SourceWorkspaceEditConflictError extends Error {
|
|
232
|
+
constructor(
|
|
233
|
+
message = "Source file changed since the editor snapshot was prepared.",
|
|
234
|
+
) {
|
|
235
|
+
super(message);
|
|
236
|
+
this.name = "SourceWorkspaceEditConflictError";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function prepareInlineSourceEdit(args: {
|
|
241
|
+
file: SourceWorkspaceFile;
|
|
242
|
+
currentContent?: string;
|
|
243
|
+
revision?: string;
|
|
244
|
+
}): Promise<{ content: string; expectedVersionHash: string }> {
|
|
245
|
+
const live = await readLiveSourceFile(args.file);
|
|
246
|
+
|
|
247
|
+
if (args.currentContent === undefined) {
|
|
248
|
+
return {
|
|
249
|
+
content: live.content,
|
|
250
|
+
expectedVersionHash: live.versionHash,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!args.revision) {
|
|
255
|
+
throw new SourceWorkspaceEditConflictError(
|
|
256
|
+
"A source revision is required with current editor content.",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (!args.file.updatedAt || args.revision !== args.file.updatedAt) {
|
|
260
|
+
throw new SourceWorkspaceEditConflictError();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const persistedVersionHash = sourceContentHash(args.file.content ?? "");
|
|
264
|
+
const workingVersionHash = sourceContentHash(args.currentContent);
|
|
265
|
+
if (
|
|
266
|
+
live.versionHash !== persistedVersionHash &&
|
|
267
|
+
live.versionHash !== workingVersionHash
|
|
268
|
+
) {
|
|
269
|
+
throw new SourceWorkspaceEditConflictError();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
content: args.currentContent,
|
|
274
|
+
expectedVersionHash: live.versionHash,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
214
278
|
export async function writeInlineSourceFile(args: {
|
|
215
279
|
designId: string;
|
|
216
280
|
file: SourceWorkspaceFile;
|
|
@@ -680,8 +680,19 @@ export function stripBoardSurfaceOffsetFromCoord(value: number): number {
|
|
|
680
680
|
* coordinate space. This is the pure seam used by
|
|
681
681
|
* `handleOverviewPrimitiveReparent` (DesignEditor.tsx).
|
|
682
682
|
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
683
|
+
* The flat subtraction below is only correct when both `source` and `target`
|
|
684
|
+
* are already expressed in the SAME coordinate space — historically that
|
|
685
|
+
* meant "both are direct children of the screen root", because callers used
|
|
686
|
+
* to read a node's own inline left/top verbatim (parent-relative, not
|
|
687
|
+
* root-relative). DesignEditor.tsx's `getAbsolutePositioningForNodeInHtml`
|
|
688
|
+
* now resolves both inputs with an ancestor walk
|
|
689
|
+
* (`authoredElementPosition`) up to the screen root before calling this, so
|
|
690
|
+
* the subtraction is valid regardless of how deeply either node is nested —
|
|
691
|
+
* dropping a node into a container that's itself nested inside another frame
|
|
692
|
+
* no longer produces a garbage delta.
|
|
693
|
+
*
|
|
694
|
+
* Both inputs are also normalized with `stripBoardSurfaceOffsetFromCoord`
|
|
695
|
+
* first: a source that was persisted in board-iframe viewport coordinates
|
|
685
696
|
* (boardCoord + 65536 — see BOARD_SURFACE_CONTENT_OFFSET_PX) is rebased to
|
|
686
697
|
* board space before subtracting the target's origin, so the child's new
|
|
687
698
|
* left/top always comes out parent-relative regardless of which upstream
|
|
@@ -1228,51 +1228,34 @@ function projectionsOverlap(
|
|
|
1228
1228
|
* For `origin.rotation` falsy this is identical to calling
|
|
1229
1229
|
* `resizeFrameFromDelta` directly.
|
|
1230
1230
|
*/
|
|
1231
|
-
|
|
1231
|
+
/**
|
|
1232
|
+
* Shared re-anchor step used by both resizeRotatedFrameFromDelta and its
|
|
1233
|
+
* snap-aware counterpart below: given the frame's ORIGIN geometry (still
|
|
1234
|
+
* carrying its rotation) and a candidate RESIZED-LOCAL geometry (unrotated,
|
|
1235
|
+
* already grown/shrunk along the frame's own local axes — optionally
|
|
1236
|
+
* adjusted by a snap pass in between), translates the resized-local geometry
|
|
1237
|
+
* so the same handle-opposite anchor point (or the frame's own center, for
|
|
1238
|
+
* an alt/option "resize from center" drag) stays fixed in WORLD space once
|
|
1239
|
+
* rotation is re-applied. See resizeRotatedFrameFromDelta's original inline
|
|
1240
|
+
* comment (kept there) for why alt-resize anchors on the center instead of
|
|
1241
|
+
* the opposite corner/edge.
|
|
1242
|
+
*/
|
|
1243
|
+
function reanchorRotatedResizeToWorld(
|
|
1232
1244
|
origin: FrameGeometry,
|
|
1233
1245
|
handle: ResizeHandle,
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
options: ResizeFrameOptions
|
|
1246
|
+
degrees: number,
|
|
1247
|
+
resizedLocal: FrameGeometry,
|
|
1248
|
+
options: Pick<ResizeFrameOptions, "resizeFromCenter">,
|
|
1237
1249
|
): FrameGeometry {
|
|
1238
|
-
const degrees = origin.rotation ?? 0;
|
|
1239
|
-
if (!degrees) {
|
|
1240
|
-
return resizeFrameFromDelta(origin, handle, worldDx, worldDy, options);
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
1250
|
const originCenter = {
|
|
1244
1251
|
x: origin.x + origin.width / 2,
|
|
1245
1252
|
y: origin.y + origin.height / 2,
|
|
1246
1253
|
};
|
|
1247
|
-
// Alt/option resize (`resizeFromCenter`) already grows the frame
|
|
1248
|
-
// symmetrically about its own center in LOCAL space (resizeFrameFromDelta
|
|
1249
|
-
// below keeps the center fixed on both axes for that mode). To keep that
|
|
1250
|
-
// center fixed in WORLD space too — matching Figma, where alt-resizing a
|
|
1251
|
-
// rotated frame grows around its visual center rather than pivoting off an
|
|
1252
|
-
// opposite corner — the re-anchor step below must use the center as its
|
|
1253
|
-
// fixed point instead of the handle's opposite corner/edge. Using the
|
|
1254
|
-
// center as the anchor is a no-op for the rotation (a point rotates around
|
|
1255
|
-
// itself unchanged), so it stays put in world space by construction.
|
|
1256
|
-
// Non-center (default) resizes keep anchoring on the opposite corner/edge
|
|
1257
|
-
// so that world-fixed-anchor behavior is unchanged.
|
|
1258
1254
|
const anchorLocalBefore = options.resizeFromCenter
|
|
1259
1255
|
? originCenter
|
|
1260
1256
|
: getResizeAnchorPoint(origin, handle);
|
|
1261
1257
|
const anchorWorld = rotatePoint(anchorLocalBefore, originCenter, degrees);
|
|
1262
1258
|
|
|
1263
|
-
// Map the world-space pointer delta into the frame's own unrotated axes so
|
|
1264
|
-
// dragging "outward along the handle" behaves the same regardless of how
|
|
1265
|
-
// the frame is rotated.
|
|
1266
|
-
const localDelta = rotateVector({ x: worldDx, y: worldDy }, -degrees);
|
|
1267
|
-
|
|
1268
|
-
const resizedLocal = resizeFrameFromDelta(
|
|
1269
|
-
{ ...origin, rotation: undefined },
|
|
1270
|
-
handle,
|
|
1271
|
-
localDelta.x,
|
|
1272
|
-
localDelta.y,
|
|
1273
|
-
options,
|
|
1274
|
-
);
|
|
1275
|
-
|
|
1276
1259
|
const centerAfterLocal = {
|
|
1277
1260
|
x: resizedLocal.x + resizedLocal.width / 2,
|
|
1278
1261
|
y: resizedLocal.y + resizedLocal.height / 2,
|
|
@@ -1298,6 +1281,221 @@ export function resizeRotatedFrameFromDelta(
|
|
|
1298
1281
|
};
|
|
1299
1282
|
}
|
|
1300
1283
|
|
|
1284
|
+
export function resizeRotatedFrameFromDelta(
|
|
1285
|
+
origin: FrameGeometry,
|
|
1286
|
+
handle: ResizeHandle,
|
|
1287
|
+
worldDx: number,
|
|
1288
|
+
worldDy: number,
|
|
1289
|
+
options: ResizeFrameOptions = {},
|
|
1290
|
+
): FrameGeometry {
|
|
1291
|
+
const degrees = origin.rotation ?? 0;
|
|
1292
|
+
if (!degrees) {
|
|
1293
|
+
return resizeFrameFromDelta(origin, handle, worldDx, worldDy, options);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Map the world-space pointer delta into the frame's own unrotated axes so
|
|
1297
|
+
// dragging "outward along the handle" behaves the same regardless of how
|
|
1298
|
+
// the frame is rotated.
|
|
1299
|
+
const localDelta = rotateVector({ x: worldDx, y: worldDy }, -degrees);
|
|
1300
|
+
|
|
1301
|
+
const resizedLocal = resizeFrameFromDelta(
|
|
1302
|
+
{ ...origin, rotation: undefined },
|
|
1303
|
+
handle,
|
|
1304
|
+
localDelta.x,
|
|
1305
|
+
localDelta.y,
|
|
1306
|
+
options,
|
|
1307
|
+
);
|
|
1308
|
+
|
|
1309
|
+
return reanchorRotatedResizeToWorld(
|
|
1310
|
+
origin,
|
|
1311
|
+
handle,
|
|
1312
|
+
degrees,
|
|
1313
|
+
resizedLocal,
|
|
1314
|
+
options,
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/** Resize handles in clockwise screen order (y grows downward), so rotating a
|
|
1319
|
+
* handle by one 90° quadrant clockwise is a +2 step through this ring. */
|
|
1320
|
+
const RESIZE_HANDLES_CLOCKWISE: readonly ResizeHandle[] = [
|
|
1321
|
+
"n",
|
|
1322
|
+
"ne",
|
|
1323
|
+
"e",
|
|
1324
|
+
"se",
|
|
1325
|
+
"s",
|
|
1326
|
+
"sw",
|
|
1327
|
+
"w",
|
|
1328
|
+
"nw",
|
|
1329
|
+
];
|
|
1330
|
+
|
|
1331
|
+
/** Maps a local resize handle to the world-space direction it points after
|
|
1332
|
+
* rotating the frame clockwise by `quadrants` 90° steps (CSS-positive
|
|
1333
|
+
* rotation is clockwise on screen): at 90°, the local east handle points
|
|
1334
|
+
* due south in world space, so "e" → "s". */
|
|
1335
|
+
function rotateHandleByQuadrants(
|
|
1336
|
+
handle: ResizeHandle,
|
|
1337
|
+
quadrants: number,
|
|
1338
|
+
): ResizeHandle {
|
|
1339
|
+
const index = RESIZE_HANDLES_CLOCKWISE.indexOf(handle);
|
|
1340
|
+
return RESIZE_HANDLES_CLOCKWISE[(index + 2 * ((quadrants % 4) + 4)) % 8]!;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Beyond this many degrees away from the nearest axis-aligned orientation
|
|
1345
|
+
* (0/90/180/270), resize snapping is skipped for rotated frames: the frame's
|
|
1346
|
+
* true edges diverge too far from any axis-aligned box for edge/center
|
|
1347
|
+
* alignment against siblings to mean anything, and a wrong-axis guide is
|
|
1348
|
+
* worse than none.
|
|
1349
|
+
*/
|
|
1350
|
+
const ROTATED_RESIZE_SNAP_MAX_OFF_AXIS_DEGREES = 30;
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Rotation-aware counterpart to resizeRotatedFrameFromDelta that also snaps
|
|
1354
|
+
* to nearby stationary siblings, matching computeResizeSnap's contract for
|
|
1355
|
+
* the unrotated group-resize path (CV-style: same threshold/guide shape).
|
|
1356
|
+
*
|
|
1357
|
+
* How rotated snapping works (see WORK ITEM 2 in the canvas bug pass this was
|
|
1358
|
+
* added for, reworked in the pre-ship review): the unsnapped rotated resize
|
|
1359
|
+
* is computed first, then its actual world-space AABB is snapped against
|
|
1360
|
+
* stationary siblings' world bounds with the exact same computeResizeSnap
|
|
1361
|
+
* machinery a non-rotated resize uses — with the resize handle mapped into
|
|
1362
|
+
* its world orientation by the frame's nearest 90° quadrant (at ~90° the
|
|
1363
|
+
* local east edge faces due south in world space, so "e" snaps the AABB's
|
|
1364
|
+
* bottom edge; at ~180° it faces west; etc.). Any snap offset is then fed
|
|
1365
|
+
* back into the rotated resize as extra world pointer travel, so anchoring
|
|
1366
|
+
* stays exact. This is edge-exact at 0/90/180/270 (the AABB edges ARE the
|
|
1367
|
+
* frame's edges there) and approximate in between (pointer travel maps to
|
|
1368
|
+
* AABB-edge movement with a cos² factor); once the rotation is more than
|
|
1369
|
+
* ROTATED_RESIZE_SNAP_MAX_OFF_AXIS_DEGREES from every axis-aligned
|
|
1370
|
+
* orientation, snapping is skipped entirely (no guides) rather than snapping
|
|
1371
|
+
* an axis that no longer matches what the user sees.
|
|
1372
|
+
*/
|
|
1373
|
+
export function resizeRotatedFrameFromDeltaWithSnap(
|
|
1374
|
+
origin: FrameGeometry,
|
|
1375
|
+
handle: ResizeHandle,
|
|
1376
|
+
worldDx: number,
|
|
1377
|
+
worldDy: number,
|
|
1378
|
+
stationary: FrameEntry[],
|
|
1379
|
+
snapOptions: ResizeSnapOptions,
|
|
1380
|
+
options: ResizeFrameOptions = {},
|
|
1381
|
+
): { frame: FrameGeometry; guides: AlignmentGuide[] } {
|
|
1382
|
+
const degrees = origin.rotation ?? 0;
|
|
1383
|
+
if (!degrees) {
|
|
1384
|
+
const resizedLocal = resizeFrameFromDelta(
|
|
1385
|
+
origin,
|
|
1386
|
+
handle,
|
|
1387
|
+
worldDx,
|
|
1388
|
+
worldDy,
|
|
1389
|
+
options,
|
|
1390
|
+
);
|
|
1391
|
+
return computeResizeSnap(resizedLocal, stationary, handle, snapOptions);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
const localDelta = rotateVector({ x: worldDx, y: worldDy }, -degrees);
|
|
1395
|
+
|
|
1396
|
+
const resizedLocal = resizeFrameFromDelta(
|
|
1397
|
+
{ ...origin, rotation: undefined },
|
|
1398
|
+
handle,
|
|
1399
|
+
localDelta.x,
|
|
1400
|
+
localDelta.y,
|
|
1401
|
+
options,
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
// Snapping is skipped outright for a rotated frame whenever the caller's
|
|
1405
|
+
// aspect-preserve flag is set (shift-held or the K scale tool):
|
|
1406
|
+
// independently snapping x and y against the local-bounds approximation
|
|
1407
|
+
// above would distort the locked ratio the aspect-preserving resize above
|
|
1408
|
+
// just computed, and computeAspectPreservingResizeSnap's own single-locked-
|
|
1409
|
+
// ratio reasoning only holds against the frame's true (rotated) shape, not
|
|
1410
|
+
// this approximation. No snap beats a wrong one here.
|
|
1411
|
+
if (snapOptions.preserveAspectRatio || snapOptions.bypass) {
|
|
1412
|
+
return {
|
|
1413
|
+
frame: reanchorRotatedResizeToWorld(
|
|
1414
|
+
origin,
|
|
1415
|
+
handle,
|
|
1416
|
+
degrees,
|
|
1417
|
+
resizedLocal,
|
|
1418
|
+
options,
|
|
1419
|
+
),
|
|
1420
|
+
guides: [],
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const unsnapped = reanchorRotatedResizeToWorld(
|
|
1425
|
+
origin,
|
|
1426
|
+
handle,
|
|
1427
|
+
degrees,
|
|
1428
|
+
resizedLocal,
|
|
1429
|
+
options,
|
|
1430
|
+
);
|
|
1431
|
+
|
|
1432
|
+
// Map the resize handle into its world orientation by the nearest 90°
|
|
1433
|
+
// quadrant (see the doc comment above). Far off-axis, skip snapping
|
|
1434
|
+
// outright — a wrong-axis snap/guide is worse than none.
|
|
1435
|
+
const normalizedDegrees = ((degrees % 360) + 360) % 360;
|
|
1436
|
+
const nearestQuadrant = Math.round(normalizedDegrees / 90) % 4;
|
|
1437
|
+
const offAxisDegrees = Math.abs(
|
|
1438
|
+
normalizedDegrees - Math.round(normalizedDegrees / 90) * 90,
|
|
1439
|
+
);
|
|
1440
|
+
if (offAxisDegrees > ROTATED_RESIZE_SNAP_MAX_OFF_AXIS_DEGREES) {
|
|
1441
|
+
return { frame: unsnapped, guides: [] };
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const worldHandle = rotateHandleByQuadrants(handle, nearestQuadrant);
|
|
1445
|
+
const aabb = getRotatedFrameAABB(unsnapped);
|
|
1446
|
+
const worldBox: FrameGeometry = {
|
|
1447
|
+
x: aabb.left,
|
|
1448
|
+
y: aabb.top,
|
|
1449
|
+
width: aabb.width,
|
|
1450
|
+
height: aabb.height,
|
|
1451
|
+
};
|
|
1452
|
+
// Min sizes are per-axis, so they swap with the axes at 90/270.
|
|
1453
|
+
const worldSnapOptions =
|
|
1454
|
+
nearestQuadrant % 2 === 1
|
|
1455
|
+
? {
|
|
1456
|
+
...snapOptions,
|
|
1457
|
+
minWidth: snapOptions.minHeight,
|
|
1458
|
+
minHeight: snapOptions.minWidth,
|
|
1459
|
+
}
|
|
1460
|
+
: snapOptions;
|
|
1461
|
+
|
|
1462
|
+
const snap = computeResizeSnap(
|
|
1463
|
+
worldBox,
|
|
1464
|
+
stationary,
|
|
1465
|
+
worldHandle,
|
|
1466
|
+
worldSnapOptions,
|
|
1467
|
+
);
|
|
1468
|
+
if (snap.guides.length === 0) {
|
|
1469
|
+
return { frame: unsnapped, guides: [] };
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// The snap moved the dragged world edge(s) of the AABB; express that
|
|
1473
|
+
// movement as extra world pointer travel and recompute the rotated resize,
|
|
1474
|
+
// so the anchor-fixing translation stays exact instead of being applied to
|
|
1475
|
+
// an already-snapped box.
|
|
1476
|
+
const snapDx = handleAffectsEast(worldHandle)
|
|
1477
|
+
? snap.frame.x + snap.frame.width - (worldBox.x + worldBox.width)
|
|
1478
|
+
: handleAffectsWest(worldHandle)
|
|
1479
|
+
? snap.frame.x - worldBox.x
|
|
1480
|
+
: 0;
|
|
1481
|
+
const snapDy = handleAffectsSouth(worldHandle)
|
|
1482
|
+
? snap.frame.y + snap.frame.height - (worldBox.y + worldBox.height)
|
|
1483
|
+
: handleAffectsNorth(worldHandle)
|
|
1484
|
+
? snap.frame.y - worldBox.y
|
|
1485
|
+
: 0;
|
|
1486
|
+
|
|
1487
|
+
return {
|
|
1488
|
+
frame: resizeRotatedFrameFromDelta(
|
|
1489
|
+
origin,
|
|
1490
|
+
handle,
|
|
1491
|
+
worldDx + snapDx,
|
|
1492
|
+
worldDy + snapDy,
|
|
1493
|
+
options,
|
|
1494
|
+
),
|
|
1495
|
+
guides: snap.guides,
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1301
1499
|
/** The local (unrotated) point that a given resize handle keeps fixed: the
|
|
1302
1500
|
* edge/corner opposite the handle, or the center of an axis the handle
|
|
1303
1501
|
* doesn't affect at all (e.g. "n" leaves the horizontal axis untouched). */
|