@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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Figma clipboard helpers: decode the `figmeta` marker Figma
|
|
3
|
+
* writes into a copy's clipboard HTML, and decide which action a paste
|
|
4
|
+
* should route through.
|
|
5
|
+
*
|
|
6
|
+
* Figma wraps a small base64 JSON payload (`{"fileKey","pasteID","dataType"}`)
|
|
7
|
+
* between literal `(figmeta)`/`(/figmeta)` markers — the same markers
|
|
8
|
+
* `hasFigmaClipboardPayload` in `design-import.ts` already scans for to detect
|
|
9
|
+
* a Figma paste at all. Some Figma clients may instead put that same base64
|
|
10
|
+
* blob directly in the `data-metadata` attribute with no wrapper markers, so
|
|
11
|
+
* `extractFigmeta` tries both, defensively, and never throws — any malformed
|
|
12
|
+
* input just means "not a decodable Figma paste".
|
|
13
|
+
*
|
|
14
|
+
* Important caveat carried through to the server: `figmeta` has no node ids
|
|
15
|
+
* (only a `fileKey`), and `pasteID` is an ephemeral, server-side identifier
|
|
16
|
+
* that isn't resolvable to a node through the public REST API. So a plain
|
|
17
|
+
* clipboard paste can only ever get an exact node import on a best-effort,
|
|
18
|
+
* heuristic match (see `server/lib/figma-clipboard-match.ts`) — a copied
|
|
19
|
+
* frame LINK (which carries a real `node-id`) is the only guaranteed-exact
|
|
20
|
+
* path (`import-figma-frame`).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface FigmetaPayload {
|
|
24
|
+
fileKey: string;
|
|
25
|
+
pasteID?: number;
|
|
26
|
+
dataType?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FIGMETA_MARKER_RE = /\(figmeta\)([^(]*?)\(\/figmeta\)/i;
|
|
30
|
+
const DATA_METADATA_ATTR_RE = /\bdata-metadata\s*=\s*(["'])([\s\S]*?)\1/i;
|
|
31
|
+
|
|
32
|
+
function decodeBase64Json(raw: string): unknown {
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
if (!trimmed) return null;
|
|
35
|
+
try {
|
|
36
|
+
const decoded =
|
|
37
|
+
typeof atob === "function"
|
|
38
|
+
? atob(trimmed)
|
|
39
|
+
: Buffer.from(trimmed, "base64").toString("utf8");
|
|
40
|
+
return JSON.parse(decoded);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isFigmetaPayload(value: unknown): value is FigmetaPayload {
|
|
47
|
+
if (!value || typeof value !== "object") return false;
|
|
48
|
+
const fileKey = (value as { fileKey?: unknown }).fileKey;
|
|
49
|
+
return typeof fileKey === "string" && fileKey.trim().length > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeFigmetaPayload(value: FigmetaPayload): FigmetaPayload {
|
|
53
|
+
const payload: FigmetaPayload = { fileKey: value.fileKey.trim() };
|
|
54
|
+
if (typeof value.pasteID === "number") payload.pasteID = value.pasteID;
|
|
55
|
+
if (typeof value.dataType === "string") payload.dataType = value.dataType;
|
|
56
|
+
return payload;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extracts and decodes the `figmeta` payload from a clipboard's raw HTML (or
|
|
61
|
+
* plain text), if present. Returns `null` for anything that isn't a
|
|
62
|
+
* decodable Figma `figmeta` marker — this never throws, so callers can run it
|
|
63
|
+
* unconditionally on every paste.
|
|
64
|
+
*/
|
|
65
|
+
export function extractFigmeta(
|
|
66
|
+
html: string | null | undefined,
|
|
67
|
+
): FigmetaPayload | null {
|
|
68
|
+
if (!html) return null;
|
|
69
|
+
|
|
70
|
+
const markerMatch = html.match(FIGMETA_MARKER_RE);
|
|
71
|
+
if (markerMatch?.[1]) {
|
|
72
|
+
const parsed = decodeBase64Json(markerMatch[1]);
|
|
73
|
+
if (isFigmetaPayload(parsed)) return normalizeFigmetaPayload(parsed);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Defensive fallback: some clients may emit data-metadata as a bare base64
|
|
77
|
+
// blob with no (figmeta)...(/figmeta) wrapper markers.
|
|
78
|
+
const attrMatch = html.match(DATA_METADATA_ATTR_RE);
|
|
79
|
+
if (attrMatch?.[2]) {
|
|
80
|
+
const parsed = decodeBase64Json(attrMatch[2]);
|
|
81
|
+
if (isFigmetaPayload(parsed)) return normalizeFigmetaPayload(parsed);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type FigmaApiKeyStatus = "configured" | "missing" | "unknown";
|
|
88
|
+
export type FigmaPasteStrategy = "rest" | "html-fallback" | "not-figma";
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Pure decision matrix for what a paste should attempt, given whether it
|
|
92
|
+
* decoded a `figmeta` payload and (if known) whether the Figma access token
|
|
93
|
+
* is configured:
|
|
94
|
+
*
|
|
95
|
+
* - No `figmeta` -> not a Figma paste at all (`"not-figma"`).
|
|
96
|
+
* - `figmeta` present but the key is known-missing -> skip the REST attempt
|
|
97
|
+
* entirely and go straight to the legacy HTML path (`"html-fallback"`).
|
|
98
|
+
* - `figmeta` present and the key is configured, or its status isn't known
|
|
99
|
+
* client-side -> attempt the REST import (`"rest"`). The server is the
|
|
100
|
+
* authority on whether the key actually works; passing `"unknown"` here
|
|
101
|
+
* just means "try REST, let the server fall back for real if it can't."
|
|
102
|
+
*/
|
|
103
|
+
export function decideFigmaPasteStrategy(
|
|
104
|
+
figmeta: FigmetaPayload | null,
|
|
105
|
+
apiKeyStatus: FigmaApiKeyStatus = "unknown",
|
|
106
|
+
): FigmaPasteStrategy {
|
|
107
|
+
if (!figmeta) return "not-figma";
|
|
108
|
+
if (apiKeyStatus === "missing") return "html-fallback";
|
|
109
|
+
return "rest";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type FigmaPasteImportCall =
|
|
113
|
+
| {
|
|
114
|
+
action: "import-figma-clipboard";
|
|
115
|
+
payload: {
|
|
116
|
+
figmetaFileKey: string;
|
|
117
|
+
clipboardHtml: string;
|
|
118
|
+
originalName?: string;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
| {
|
|
122
|
+
action: "import-design-source";
|
|
123
|
+
payload: {
|
|
124
|
+
sourceType: "figma-paste-html";
|
|
125
|
+
content: string;
|
|
126
|
+
originalName?: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolves the actual action name + payload a paste should call, given the
|
|
132
|
+
* raw clipboard HTML. Used by both the canvas-level paste listener and the
|
|
133
|
+
* Import panel's Figma-paste target so the two call sites stay in lockstep.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveFigmaPasteImportCall(
|
|
136
|
+
content: string,
|
|
137
|
+
originalName = "figma-paste.html",
|
|
138
|
+
): FigmaPasteImportCall {
|
|
139
|
+
const figmeta = extractFigmeta(content);
|
|
140
|
+
const strategy = decideFigmaPasteStrategy(figmeta, "unknown");
|
|
141
|
+
|
|
142
|
+
if (strategy === "not-figma") {
|
|
143
|
+
return {
|
|
144
|
+
action: "import-design-source",
|
|
145
|
+
payload: { sourceType: "figma-paste-html", content, originalName },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
action: "import-figma-clipboard",
|
|
151
|
+
payload: {
|
|
152
|
+
figmetaFileKey: figmeta!.fileKey,
|
|
153
|
+
clipboardHtml: content,
|
|
154
|
+
originalName,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { callAction } from "@agent-native/core/client";
|
|
2
|
+
|
|
3
|
+
export type FigmaSvgCopyErrorCode =
|
|
4
|
+
| "unsupported"
|
|
5
|
+
| "blocked"
|
|
6
|
+
| "write-failed"
|
|
7
|
+
| "render-failed";
|
|
8
|
+
|
|
9
|
+
export class FigmaSvgCopyError extends Error {
|
|
10
|
+
readonly code: FigmaSvgCopyErrorCode;
|
|
11
|
+
|
|
12
|
+
constructor(code: FigmaSvgCopyErrorCode, cause?: unknown) {
|
|
13
|
+
super(`Figma SVG copy ${code}`);
|
|
14
|
+
this.name = "FigmaSvgCopyError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
if (cause !== undefined) {
|
|
17
|
+
Object.defineProperty(this, "cause", {
|
|
18
|
+
configurable: true,
|
|
19
|
+
value: cause,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ClipboardWriter = Pick<Clipboard, "write"> & {
|
|
26
|
+
writeText?: Clipboard["writeText"];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ClipboardItemConstructor = {
|
|
30
|
+
new (items: Record<string, Blob | Promise<Blob>>): ClipboardItem;
|
|
31
|
+
supports?: (type: string) => boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface FigmaSvgExportActionResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
reason?: string;
|
|
37
|
+
svg?: string;
|
|
38
|
+
filename?: string;
|
|
39
|
+
report?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FigmaSvgExportParams {
|
|
43
|
+
designId?: string;
|
|
44
|
+
fileId?: string;
|
|
45
|
+
filename?: string;
|
|
46
|
+
nodeId?: string;
|
|
47
|
+
embedImages?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface FigmaSvgCopyEnvironment {
|
|
51
|
+
clipboard?: ClipboardWriter | null;
|
|
52
|
+
ClipboardItem?: ClipboardItemConstructor | null;
|
|
53
|
+
/** Injectable override for tests — defaults to the real `callAction`. */
|
|
54
|
+
callExportAction?: (
|
|
55
|
+
params: FigmaSvgExportParams,
|
|
56
|
+
) => Promise<FigmaSvgExportActionResult>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function defaultFigmaSvgCopyEnvironment(): FigmaSvgCopyEnvironment {
|
|
60
|
+
return {
|
|
61
|
+
clipboard:
|
|
62
|
+
typeof navigator === "undefined" ? null : (navigator.clipboard ?? null),
|
|
63
|
+
ClipboardItem:
|
|
64
|
+
typeof globalThis.ClipboardItem === "undefined"
|
|
65
|
+
? null
|
|
66
|
+
: globalThis.ClipboardItem,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function defaultCallExportAction(
|
|
71
|
+
params: FigmaSvgExportParams,
|
|
72
|
+
): Promise<FigmaSvgExportActionResult> {
|
|
73
|
+
// Same cast-to-loose-signature pattern as design-save-outbox.ts's
|
|
74
|
+
// `invokeAction` default: the action registry's generated `ActionName`
|
|
75
|
+
// union doesn't need to be threaded through this small client module.
|
|
76
|
+
return (
|
|
77
|
+
callAction as (
|
|
78
|
+
name: string,
|
|
79
|
+
params: Record<string, unknown>,
|
|
80
|
+
) => Promise<FigmaSvgExportActionResult>
|
|
81
|
+
)("export-design-as-figma-svg", params as unknown as Record<string, unknown>);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function canCopyFigmaSvgToClipboard(
|
|
85
|
+
environment: FigmaSvgCopyEnvironment = defaultFigmaSvgCopyEnvironment(),
|
|
86
|
+
): boolean {
|
|
87
|
+
// `text/plain` (the proven Figma-paste MIME — see the export-handoff skill's
|
|
88
|
+
// "Export to Figma (SVG)" section) only needs a plain `clipboard.write` or
|
|
89
|
+
// `writeText`; ClipboardItem is optional (only gates the extra
|
|
90
|
+
// `image/svg+xml` representation).
|
|
91
|
+
return Boolean(
|
|
92
|
+
(environment.clipboard?.write && environment.ClipboardItem) ||
|
|
93
|
+
environment.clipboard?.writeText,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function supportsClipboardType(
|
|
98
|
+
ClipboardItemCtor: ClipboardItemConstructor,
|
|
99
|
+
type: string,
|
|
100
|
+
): boolean {
|
|
101
|
+
try {
|
|
102
|
+
return (
|
|
103
|
+
typeof ClipboardItemCtor.supports !== "function" ||
|
|
104
|
+
ClipboardItemCtor.supports(type)
|
|
105
|
+
);
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function classifyClipboardWriteError(error: unknown): FigmaSvgCopyErrorCode {
|
|
112
|
+
const name =
|
|
113
|
+
error && typeof error === "object" && "name" in error
|
|
114
|
+
? String(error.name)
|
|
115
|
+
: "";
|
|
116
|
+
if (name === "NotAllowedError" || name === "SecurityError") return "blocked";
|
|
117
|
+
if (name === "NotSupportedError" || error instanceof TypeError)
|
|
118
|
+
return "unsupported";
|
|
119
|
+
return "write-failed";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface FigmaSvgCopyResult {
|
|
123
|
+
filename: string;
|
|
124
|
+
report: unknown;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Exports a design screen (or a selected element's subtree via `nodeId`) as
|
|
129
|
+
* a genuinely vector SVG through the `export-design-as-figma-svg` action,
|
|
130
|
+
* then writes it to the system clipboard as BOTH:
|
|
131
|
+
*
|
|
132
|
+
* - `text/plain` — the raw SVG markup. This is the MIME Figma's own paste
|
|
133
|
+
* handler reads for "paste as vector shapes"; a `image/svg+xml`-only
|
|
134
|
+
* clipboard write is NOT enough on its own for a reliable Figma paste.
|
|
135
|
+
* - `image/svg+xml` — the same markup as a typed image representation,
|
|
136
|
+
* for any other paste target that specifically requests SVG images.
|
|
137
|
+
*
|
|
138
|
+
* Call this from a user-gesture handler (e.g. a context-menu "Copy as SVG"
|
|
139
|
+
* item) — `clipboard.write` requires transient activation in most browsers,
|
|
140
|
+
* the same reason `copyPngPromiseToClipboard` in `png-clipboard.ts` is
|
|
141
|
+
* gesture-scoped.
|
|
142
|
+
*/
|
|
143
|
+
export async function copyDesignAsFigmaSvg(
|
|
144
|
+
params: FigmaSvgExportParams,
|
|
145
|
+
environment: FigmaSvgCopyEnvironment = defaultFigmaSvgCopyEnvironment(),
|
|
146
|
+
): Promise<FigmaSvgCopyResult> {
|
|
147
|
+
if (!canCopyFigmaSvgToClipboard(environment)) {
|
|
148
|
+
throw new FigmaSvgCopyError("unsupported");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const callExportAction =
|
|
152
|
+
environment.callExportAction ?? defaultCallExportAction;
|
|
153
|
+
const clipboard = environment.clipboard;
|
|
154
|
+
const ClipboardItemCtor = environment.ClipboardItem;
|
|
155
|
+
|
|
156
|
+
let renderError: unknown;
|
|
157
|
+
const exportPromise = callExportAction(params)
|
|
158
|
+
.then((result) => {
|
|
159
|
+
if (!result.ok || !result.svg) {
|
|
160
|
+
throw new FigmaSvgCopyError(
|
|
161
|
+
"render-failed",
|
|
162
|
+
new Error(result.reason ?? "Figma SVG export failed"),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return result as FigmaSvgExportActionResult & { svg: string };
|
|
166
|
+
})
|
|
167
|
+
.catch((error: unknown) => {
|
|
168
|
+
renderError =
|
|
169
|
+
error instanceof FigmaSvgCopyError
|
|
170
|
+
? error
|
|
171
|
+
: new FigmaSvgCopyError("render-failed", error);
|
|
172
|
+
throw renderError;
|
|
173
|
+
});
|
|
174
|
+
// ClipboardItem owns the promises below in real browsers. Keep a separate
|
|
175
|
+
// observer so test doubles or an early clipboard rejection cannot surface an
|
|
176
|
+
// unhandled action/render rejection.
|
|
177
|
+
void exportPromise.catch(() => undefined);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
if (clipboard?.write && ClipboardItemCtor) {
|
|
181
|
+
// Call clipboard.write while the initiating click/key event still owns
|
|
182
|
+
// transient activation. ClipboardItem deliberately receives pending
|
|
183
|
+
// Blob promises, matching the proven PNG clipboard path; awaiting the
|
|
184
|
+
// server render first makes slow exports fail in Safari and hardened
|
|
185
|
+
// Chromium even though the user invoked the command correctly.
|
|
186
|
+
const textBlobPromise = exportPromise.then(
|
|
187
|
+
(result) => new Blob([result.svg], { type: "text/plain" }),
|
|
188
|
+
);
|
|
189
|
+
void textBlobPromise.catch(() => undefined);
|
|
190
|
+
const items: Record<string, Blob | Promise<Blob>> = {
|
|
191
|
+
"text/plain": textBlobPromise,
|
|
192
|
+
};
|
|
193
|
+
if (supportsClipboardType(ClipboardItemCtor, "image/svg+xml")) {
|
|
194
|
+
const svgBlobPromise = exportPromise.then(
|
|
195
|
+
(result) => new Blob([result.svg], { type: "image/svg+xml" }),
|
|
196
|
+
);
|
|
197
|
+
void svgBlobPromise.catch(() => undefined);
|
|
198
|
+
items["image/svg+xml"] = svgBlobPromise;
|
|
199
|
+
}
|
|
200
|
+
await clipboard.write([new ClipboardItemCtor(items)]);
|
|
201
|
+
} else if (clipboard?.writeText) {
|
|
202
|
+
// No ClipboardItem constructor available — still deliver the SVG
|
|
203
|
+
// markup as text/plain, which is the proven Figma-paste path anyway.
|
|
204
|
+
const result = await exportPromise;
|
|
205
|
+
await clipboard.writeText(result.svg);
|
|
206
|
+
} else {
|
|
207
|
+
throw new FigmaSvgCopyError("unsupported");
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof FigmaSvgCopyError) throw error;
|
|
211
|
+
if (renderError !== undefined) throw renderError;
|
|
212
|
+
throw new FigmaSvgCopyError(classifyClipboardWriteError(error), error);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const result = await exportPromise;
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
filename: result.filename ?? "design-figma.svg",
|
|
219
|
+
report: result.report,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -306,11 +306,41 @@ export function bridgeSourceIdForCodeLayerNode(node: CodeLayerNode): string {
|
|
|
306
306
|
);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
function positiveIntegerDataAttribute(
|
|
310
|
+
value: string | undefined,
|
|
311
|
+
): number | undefined {
|
|
312
|
+
const trimmed = value?.trim();
|
|
313
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) return undefined;
|
|
314
|
+
const parsed = Number(trimmed);
|
|
315
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function provenanceForCodeLayerNode(
|
|
319
|
+
node: CodeLayerNode,
|
|
320
|
+
): ElementInfo["provenance"] {
|
|
321
|
+
const sourceFile = node.dataAttributes["data-source-file"]?.trim();
|
|
322
|
+
const line = positiveIntegerDataAttribute(
|
|
323
|
+
node.dataAttributes["data-source-line"],
|
|
324
|
+
);
|
|
325
|
+
const column = positiveIntegerDataAttribute(
|
|
326
|
+
node.dataAttributes["data-source-column"],
|
|
327
|
+
);
|
|
328
|
+
const component = node.dataAttributes["data-component-name"]?.trim();
|
|
329
|
+
if (!sourceFile && !line && !column && !component) return undefined;
|
|
330
|
+
return {
|
|
331
|
+
...(sourceFile ? { sourceFile } : {}),
|
|
332
|
+
...(line ? { line } : {}),
|
|
333
|
+
...(column ? { column } : {}),
|
|
334
|
+
...(component ? { component } : {}),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
309
338
|
export function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
|
|
310
339
|
return {
|
|
311
340
|
tagName: node.tag,
|
|
312
341
|
id: typeof node.attributes.id === "string" ? node.attributes.id : undefined,
|
|
313
342
|
sourceId: bridgeSourceIdForCodeLayerNode(node),
|
|
343
|
+
provenance: provenanceForCodeLayerNode(node),
|
|
314
344
|
selector: preferredCodeLayerSelector(node),
|
|
315
345
|
classes: node.classes,
|
|
316
346
|
computedStyles: Object.fromEntries(
|
|
@@ -347,15 +377,83 @@ export function cssStyleAliases(
|
|
|
347
377
|
return result;
|
|
348
378
|
}
|
|
349
379
|
|
|
380
|
+
// BUG-UNDO-RESIZE-GEOMETRY: width/height are the two computedStyles keys the
|
|
381
|
+
// Layout panel's W/H fields actually read (edit-panel/element-classification.ts's
|
|
382
|
+
// cssElementSize parses element.computedStyles.width/height first, falling
|
|
383
|
+
// back to boundingRect only when that's missing/unparseable). Every other
|
|
384
|
+
// computedStyles property is fine to carry over additively when the fresh
|
|
385
|
+
// (reverted/replayed) source doesn't mention it — most style properties come
|
|
386
|
+
// from a CSS class the string-parse can't see, so keeping the previously
|
|
387
|
+
// known value is the right default. Width/height are different: an undo or
|
|
388
|
+
// redo of a resize commit is EXACTLY a change to (or removal of) an inline
|
|
389
|
+
// width/height, so a stale carried-over value here isn't just incomplete —
|
|
390
|
+
// it's actively wrong (it's the pre-undo/pre-redo box size).
|
|
391
|
+
const GEOMETRY_STYLE_PROPERTIES = ["width", "height"] as const;
|
|
392
|
+
|
|
350
393
|
export function refreshedComputedStyles(
|
|
351
394
|
info: ElementInfo,
|
|
352
395
|
sourceStyles: Record<string, string>,
|
|
353
396
|
sourceClasses: readonly string[],
|
|
354
397
|
): Record<string, string> {
|
|
355
398
|
const sourceWithAliases = cssStyleAliases(sourceStyles);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
399
|
+
const merged: Record<string, string> =
|
|
400
|
+
sourceClasses.length > 0
|
|
401
|
+
? { ...info.computedStyles, ...sourceWithAliases }
|
|
402
|
+
: { ...sourceWithAliases };
|
|
403
|
+
GEOMETRY_STYLE_PROPERTIES.forEach((property) => {
|
|
404
|
+
if (!(property in sourceWithAliases)) delete merged[property];
|
|
405
|
+
});
|
|
406
|
+
return merged;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Companion to `refreshedComputedStyles` for the same undo/redo/remote-sync
|
|
411
|
+
* resync path — `ElementInfo.boundingRect` is otherwise left completely
|
|
412
|
+
* untouched by `refreshElementInfoFromContent` (both its node-match branch,
|
|
413
|
+
* via `canonicalElementInfoForCodeLayerNode`'s `{...info}` spread, and its
|
|
414
|
+
* DOM-parse fallback, via its own `{...info}` spread), so it keeps showing
|
|
415
|
+
* whatever rect was live-measured before the content change. `cssElementSize`
|
|
416
|
+
* falls back to `boundingRect.width`/`height` whenever computedStyles has no
|
|
417
|
+
* parseable value for that axis, so a permanently-stale boundingRect can
|
|
418
|
+
* still surface the pre-undo/redo size even after `refreshedComputedStyles`
|
|
419
|
+
* is fixed. Reuses the SAME freshly-resolved computedStyles (already merged
|
|
420
|
+
* above) so both fields agree, instead of re-deriving from a different
|
|
421
|
+
* source.
|
|
422
|
+
*
|
|
423
|
+
* Exported for unit testing.
|
|
424
|
+
*/
|
|
425
|
+
export function refreshedBoundingRectSize(
|
|
426
|
+
info: ElementInfo,
|
|
427
|
+
computedStyles: Record<string, string>,
|
|
428
|
+
): ElementInfo["boundingRect"] {
|
|
429
|
+
const parsedWidth = parseFloat(computedStyles.width ?? "");
|
|
430
|
+
const parsedHeight = parseFloat(computedStyles.height ?? "");
|
|
431
|
+
return {
|
|
432
|
+
...info.boundingRect,
|
|
433
|
+
width:
|
|
434
|
+
Number.isFinite(parsedWidth) && parsedWidth >= 0
|
|
435
|
+
? parsedWidth
|
|
436
|
+
: info.boundingRect.width,
|
|
437
|
+
height:
|
|
438
|
+
Number.isFinite(parsedHeight) && parsedHeight >= 0
|
|
439
|
+
? parsedHeight
|
|
440
|
+
: info.boundingRect.height,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function codeLayerNodeMatchesSourceId(
|
|
445
|
+
node: CodeLayerNode,
|
|
446
|
+
sourceId: string,
|
|
447
|
+
): boolean {
|
|
448
|
+
return (
|
|
449
|
+
node.id === sourceId ||
|
|
450
|
+
node.dataAttributes["data-agent-native-node-id"] === sourceId ||
|
|
451
|
+
node.dataAttributes["data-code-layer-id"] === sourceId ||
|
|
452
|
+
node.dataAttributes["data-layer-id"] === sourceId ||
|
|
453
|
+
node.dataAttributes["data-builder-id"] === sourceId ||
|
|
454
|
+
node.dataAttributes["data-loc"] === sourceId ||
|
|
455
|
+
node.attributes.id === sourceId
|
|
456
|
+
);
|
|
359
457
|
}
|
|
360
458
|
|
|
361
459
|
export function codeLayerNodeMatchesBridgeTarget(
|
|
@@ -363,19 +461,7 @@ export function codeLayerNodeMatchesBridgeTarget(
|
|
|
363
461
|
selector?: string,
|
|
364
462
|
sourceId?: string,
|
|
365
463
|
): boolean {
|
|
366
|
-
if (sourceId)
|
|
367
|
-
if (node.id === sourceId) return true;
|
|
368
|
-
if (
|
|
369
|
-
node.dataAttributes["data-agent-native-node-id"] === sourceId ||
|
|
370
|
-
node.dataAttributes["data-code-layer-id"] === sourceId ||
|
|
371
|
-
node.dataAttributes["data-layer-id"] === sourceId ||
|
|
372
|
-
node.dataAttributes["data-builder-id"] === sourceId ||
|
|
373
|
-
node.dataAttributes["data-loc"] === sourceId ||
|
|
374
|
-
node.attributes.id === sourceId
|
|
375
|
-
) {
|
|
376
|
-
return true;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
464
|
+
if (sourceId && codeLayerNodeMatchesSourceId(node, sourceId)) return true;
|
|
379
465
|
return codeLayerSelectorMatches(node, selector);
|
|
380
466
|
}
|
|
381
467
|
|
|
@@ -384,11 +470,34 @@ export function resolveCodeLayerNodeFromBridge(
|
|
|
384
470
|
selector?: string,
|
|
385
471
|
sourceId?: string,
|
|
386
472
|
): CodeLayerNode | null {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
473
|
+
// Id-based match first, across the WHOLE projection (not just up to
|
|
474
|
+
// whichever node the old combined-predicate `.find()` reached first) — a
|
|
475
|
+
// sourceId identifies one node by its own stable id/data-attribute, which
|
|
476
|
+
// is unique in a well-formed projection, so this branch alone can never be
|
|
477
|
+
// ambiguous. Checking it before the selector fallback also fixes a subtler
|
|
478
|
+
// pre-existing bug: the old single-pass `.find()` could match an EARLIER
|
|
479
|
+
// node purely by selector before ever reaching the correct sourceId match
|
|
480
|
+
// later in iteration order.
|
|
481
|
+
if (sourceId) {
|
|
482
|
+
const idMatch = projection.nodes.find((node) =>
|
|
483
|
+
codeLayerNodeMatchesSourceId(node, sourceId),
|
|
484
|
+
);
|
|
485
|
+
if (idMatch) return idMatch;
|
|
486
|
+
}
|
|
487
|
+
// No sourceId (or no node carries it yet, e.g. a bridge target minted a
|
|
488
|
+
// fresh pending id the projection hasn't picked up) — fall back to the
|
|
489
|
+
// bridge's structural selector. Unlike an id, a generic short selector
|
|
490
|
+
// (e.g. a 2-segment "div > p" suffix path) can legitimately match several
|
|
491
|
+
// repeated list/card/row instances. The server-side resolver for the same
|
|
492
|
+
// problem (code-layer.ts's resolveTarget) explicitly refuses to resolve a
|
|
493
|
+
// selector that matches more than one node rather than silently picking
|
|
494
|
+
// the first DOM-order match — mirror that discipline here so a duplicate/
|
|
495
|
+
// move/style edit can never silently land on the wrong sibling instance.
|
|
496
|
+
if (!selector) return null;
|
|
497
|
+
const selectorMatches = projection.nodes.filter((node) =>
|
|
498
|
+
codeLayerSelectorMatches(node, selector),
|
|
391
499
|
);
|
|
500
|
+
return selectorMatches.length === 1 ? selectorMatches[0]! : null;
|
|
392
501
|
}
|
|
393
502
|
|
|
394
503
|
export function collapsedElementText(value: string | null | undefined): string {
|
|
@@ -606,13 +715,15 @@ export function refreshElementInfoFromContent(
|
|
|
606
715
|
);
|
|
607
716
|
if (node) {
|
|
608
717
|
const sourceInfo = elementInfoFromCodeLayerNode(node);
|
|
718
|
+
const computedStyles = refreshedComputedStyles(
|
|
719
|
+
info,
|
|
720
|
+
sourceInfo.computedStyles,
|
|
721
|
+
sourceInfo.classes,
|
|
722
|
+
);
|
|
609
723
|
return {
|
|
610
724
|
...canonicalElementInfoForCodeLayerNode(info, node),
|
|
611
|
-
computedStyles
|
|
612
|
-
|
|
613
|
-
sourceInfo.computedStyles,
|
|
614
|
-
sourceInfo.classes,
|
|
615
|
-
),
|
|
725
|
+
computedStyles,
|
|
726
|
+
boundingRect: refreshedBoundingRectSize(info, computedStyles),
|
|
616
727
|
textContent: sourceInfo.textContent,
|
|
617
728
|
childElementCount: sourceInfo.childElementCount,
|
|
618
729
|
isFlexChild: sourceInfo.isFlexChild,
|
|
@@ -626,14 +737,16 @@ export function refreshElementInfoFromContent(
|
|
|
626
737
|
const element = queryUniqueSelector(doc, info.selector);
|
|
627
738
|
if (!element) return null;
|
|
628
739
|
const classes = Array.from(element.classList);
|
|
740
|
+
const computedStyles = refreshedComputedStyles(
|
|
741
|
+
info,
|
|
742
|
+
parseInlineStyleAttribute(element.getAttribute("style")),
|
|
743
|
+
classes,
|
|
744
|
+
);
|
|
629
745
|
return {
|
|
630
746
|
...info,
|
|
631
747
|
classes,
|
|
632
|
-
computedStyles
|
|
633
|
-
|
|
634
|
-
parseInlineStyleAttribute(element.getAttribute("style")),
|
|
635
|
-
classes,
|
|
636
|
-
),
|
|
748
|
+
computedStyles,
|
|
749
|
+
boundingRect: refreshedBoundingRectSize(info, computedStyles),
|
|
637
750
|
textContent: element.textContent?.slice(0, 200) ?? info.textContent,
|
|
638
751
|
childElementCount: element.children.length,
|
|
639
752
|
};
|
|
@@ -41,3 +41,18 @@ export function resolveScreenCollabSyncTarget({
|
|
|
41
41
|
overviewDocConnected && overviewPresenceFileId === fileId;
|
|
42
42
|
return { writeLiveDoc, syncCollab: !writeLiveDoc };
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/** A local transaction already updated the preview optimistically, and a
|
|
46
|
+
* same-content remote transaction is only an acknowledgement echo. Only a
|
|
47
|
+
* genuinely different remote snapshot should touch the live document. */
|
|
48
|
+
export function shouldApplyRemotePreviewContent({
|
|
49
|
+
isLocalEdit,
|
|
50
|
+
previousContent,
|
|
51
|
+
nextContent,
|
|
52
|
+
}: {
|
|
53
|
+
isLocalEdit: boolean;
|
|
54
|
+
previousContent: string | null;
|
|
55
|
+
nextContent: string;
|
|
56
|
+
}): boolean {
|
|
57
|
+
return !isLocalEdit && nextContent !== previousContent;
|
|
58
|
+
}
|
|
@@ -18,3 +18,36 @@ export function createEditorSaveOperationSource(
|
|
|
18
18
|
|
|
19
19
|
/** Yjs origin tracked by the local undo manager. */
|
|
20
20
|
export const LOCAL_EDIT_ORIGIN = `${TAB_ID}:local`;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TIE-BREAK: decides whether polled DB content should be adopted into the
|
|
24
|
+
* live editor during the file-content reconcile effect (DesignEditor.tsx).
|
|
25
|
+
* Callers only reach this decision once BOTH of the effect's own
|
|
26
|
+
* "already reflecting this exact content" early-returns have already ruled
|
|
27
|
+
* out `dbContent` matching what's currently rendered — so every call here
|
|
28
|
+
* represents a genuine content difference.
|
|
29
|
+
*
|
|
30
|
+
* A strict `dbUpdatedAt > applied` used to be the only "is this newer"
|
|
31
|
+
* check, which silently DROPPED a real external write whenever it landed in
|
|
32
|
+
* the same millisecond as the previously-applied one (timestamp
|
|
33
|
+
* resolution) — the tie was never treated as "newer," so the second write's
|
|
34
|
+
* content just never rendered. That's only safe to fix by treating a tie as
|
|
35
|
+
* adoptable while `agentActive` is false: when the agent IS active, a tied
|
|
36
|
+
* timestamp with different content is more likely a live self-echo race
|
|
37
|
+
* (the agent's own Yjs-tracked edit still mid-flight), which the caller's
|
|
38
|
+
* `staleAgentEchoPossible` debounced recovery timer already handles
|
|
39
|
+
* separately and more carefully — forcing immediate adoption for that case
|
|
40
|
+
* too would skip the debounce and reintroduce the exact race it exists to
|
|
41
|
+
* prevent.
|
|
42
|
+
*/
|
|
43
|
+
export function shouldAdoptExternalReconcileContent(args: {
|
|
44
|
+
appliedUpdatedAt: string | null | undefined;
|
|
45
|
+
dbUpdatedAt: string | null | undefined;
|
|
46
|
+
agentActive: boolean;
|
|
47
|
+
}): boolean {
|
|
48
|
+
const { appliedUpdatedAt, dbUpdatedAt, agentActive } = args;
|
|
49
|
+
if (!appliedUpdatedAt) return true;
|
|
50
|
+
if (!dbUpdatedAt) return false;
|
|
51
|
+
if (dbUpdatedAt > appliedUpdatedAt) return true;
|
|
52
|
+
return dbUpdatedAt === appliedUpdatedAt && !agentActive;
|
|
53
|
+
}
|