@agent-native/core 0.92.10 → 0.92.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +214 -17
- package/corpus/core/src/agent/run-manager.ts +72 -14
- package/corpus/core/src/agent/run-store.ts +152 -27
- package/corpus/core/src/cli/design-connect.ts +433 -151
- package/corpus/core/src/cli/skills.ts +14 -0
- package/corpus/core/src/client/AssistantChat.tsx +25 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
- package/corpus/core/src/client/agent-chat-adapter.ts +56 -12
- package/corpus/core/src/client/agent-chat.ts +165 -1
- package/corpus/core/src/client/chat/index.ts +9 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
- package/corpus/core/src/client/index.ts +5 -0
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
- package/corpus/core/src/collab/awareness-store.ts +23 -12
- package/corpus/core/src/collab/client.ts +16 -5
- package/corpus/core/src/collab/index.ts +4 -0
- package/corpus/core/src/collab/recent-edits.ts +65 -2
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
- package/corpus/core/src/server/builder-design-systems.ts +30 -4
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/create-recording.ts +2 -0
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
- package/corpus/templates/clips/actions/request-transcript.ts +102 -33
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +75 -9
- package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
- package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
- package/corpus/templates/clips/app/routes/record.tsx +17 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +12 -2
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
- package/corpus/templates/clips/desktop/src/styles.css +6 -3
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
- package/corpus/templates/clips/shared/recording-core.ts +12 -0
- package/corpus/templates/content/AGENTS.md +7 -6
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
- package/corpus/templates/content/actions/_document-flush.ts +132 -17
- package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
- package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
- package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
- package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/link-notion-page.ts +2 -0
- package/corpus/templates/content/actions/pull-document.ts +5 -7
- package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
- package/corpus/templates/content/actions/push-notion-page.ts +10 -0
- package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
- package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
- package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/content/app/i18n-data.ts +18 -0
- package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
- package/corpus/templates/content/shared/api.ts +1 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
- package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
- package/corpus/templates/design/AGENTS.md +45 -10
- package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
- package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
- package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
- package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
- package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
- package/corpus/templates/design/actions/generate-design.ts +79 -12
- package/corpus/templates/design/actions/generate-screens.ts +57 -12
- package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
- package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
- package/corpus/templates/design/actions/import-design-source.ts +4 -31
- package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
- package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/design/actions/list-design-components.ts +104 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
- package/corpus/templates/design/actions/open-component-source.ts +2 -4
- package/corpus/templates/design/actions/present-design-variants.ts +412 -16
- package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
- package/corpus/templates/design/actions/run-design-audit.ts +25 -3
- package/corpus/templates/design/actions/show-design-questions.ts +10 -0
- package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
- package/corpus/templates/design/actions/write-local-file.ts +25 -1
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
- package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
- package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
- package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
- package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
- package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
- package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
- package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
- package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
- package/corpus/templates/design/app/components/design/types.ts +33 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
- package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
- package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
- package/corpus/templates/design/app/i18n-data.ts +1217 -10
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
- package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
- package/corpus/templates/design/app/lib/design-import.ts +24 -0
- package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
- package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
- package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
- package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
- package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
- package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
- package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +3 -6
- package/corpus/templates/design/e2e/helpers.ts +27 -6
- package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
- package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
- package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
- package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
- package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
- package/corpus/templates/design/server/plugins/db.ts +41 -0
- package/corpus/templates/design/server/source-workspace.ts +64 -0
- package/corpus/templates/design/shared/board-file.ts +13 -2
- package/corpus/templates/design/shared/canvas-math.ts +231 -33
- package/corpus/templates/design/shared/code-layer.ts +122 -8
- package/corpus/templates/design/shared/component-library.ts +161 -0
- package/corpus/templates/design/shared/db-conflict.ts +27 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
- package/corpus/templates/design/shared/figma-url.ts +149 -0
- package/corpus/templates/design/shared/pen-path.ts +16 -7
- package/corpus/templates/design/shared/source-mode.ts +1 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
- package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
- package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
- package/dist/agent/production-agent.d.ts +82 -7
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +180 -17
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +63 -14
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +46 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +129 -22
- package/dist/agent/run-store.js.map +1 -1
- package/dist/cli/design-connect.d.ts +6 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +336 -139
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +14 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +19 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +38 -18
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +48 -12
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +60 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +122 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +36 -2
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/collab/awareness-store.d.ts +5 -0
- package/dist/collab/awareness-store.d.ts.map +1 -1
- package/dist/collab/awareness-store.js +19 -12
- package/dist/collab/awareness-store.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +13 -5
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/index.d.ts +1 -0
- package/dist/collab/index.d.ts.map +1 -1
- package/dist/collab/index.js +1 -0
- package/dist/collab/index.js.map +1 -1
- package/dist/collab/recent-edits.d.ts +3 -1
- package/dist/collab/recent-edits.d.ts.map +1 -1
- package/dist/collab/recent-edits.js +57 -2
- package/dist/collab/recent-edits.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +87 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-design-systems.d.ts +15 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -1
- package/dist/server/builder-design-systems.js +9 -4
- package/dist/server/builder-design-systems.js.map +1 -1
- package/package.json +1 -1
|
@@ -1007,7 +1007,11 @@ function getAttribute(
|
|
|
1007
1007
|
|
|
1008
1008
|
function attributeValue(element: ParsedElement, name: string): string | null {
|
|
1009
1009
|
const value = getAttribute(element, name)?.value;
|
|
1010
|
-
|
|
1010
|
+
// Parsed attributes contain source text, so quoted values may still carry
|
|
1011
|
+
// entities emitted by an earlier deterministic patch. Decode before using
|
|
1012
|
+
// them semantically or reserializing; otherwise sequential style edits turn
|
|
1013
|
+
// `"` into `"` on every pass and corrupt quoted CSS url() values.
|
|
1014
|
+
if (typeof value === "string") return decodeBasicHtmlEntities(value);
|
|
1011
1015
|
if (value === true) return "";
|
|
1012
1016
|
return null;
|
|
1013
1017
|
}
|
|
@@ -3143,6 +3147,7 @@ function applyMoveNodeEdit(
|
|
|
3143
3147
|
element: ParsedElement,
|
|
3144
3148
|
anchor: ParsedElement,
|
|
3145
3149
|
intent: MoveNodeEditIntent,
|
|
3150
|
+
destinationParent?: ParsedElement,
|
|
3146
3151
|
): { content: string; capability: EditCapability } | PatchResultStatus {
|
|
3147
3152
|
if (element.index === anchor.index) return "conflict";
|
|
3148
3153
|
if (anchor.start >= element.start && anchor.end <= element.end) {
|
|
@@ -3152,7 +3157,14 @@ function applyMoveNodeEdit(
|
|
|
3152
3157
|
return "unsupported";
|
|
3153
3158
|
}
|
|
3154
3159
|
|
|
3155
|
-
const
|
|
3160
|
+
const sourceParentIndex = element.parentIndex;
|
|
3161
|
+
const entersNewParent =
|
|
3162
|
+
destinationParent !== undefined &&
|
|
3163
|
+
sourceParentIndex !== destinationParent.index;
|
|
3164
|
+
const rawFragment = html.slice(element.start, element.end);
|
|
3165
|
+
const fragment = entersNewParent
|
|
3166
|
+
? prepareMovedFragmentForParent(rawFragment, destinationParent)
|
|
3167
|
+
: rawFragment;
|
|
3156
3168
|
const withoutTarget = `${html.slice(0, element.start)}${html.slice(
|
|
3157
3169
|
element.end,
|
|
3158
3170
|
)}`;
|
|
@@ -3178,6 +3190,51 @@ function applyMoveNodeEdit(
|
|
|
3178
3190
|
};
|
|
3179
3191
|
}
|
|
3180
3192
|
|
|
3193
|
+
/**
|
|
3194
|
+
* Whether children of this element participate in normal flex/grid flow.
|
|
3195
|
+
* Inline style is authoritative for inspector-created auto layout; the class
|
|
3196
|
+
* checks cover authored Tailwind/utility layouts in standalone Alpine files.
|
|
3197
|
+
*/
|
|
3198
|
+
function isFlowLayoutContainer(element: ParsedElement | undefined): boolean {
|
|
3199
|
+
if (!element) return false;
|
|
3200
|
+
const display = parseStyle(attributeValue(element, "style")).display;
|
|
3201
|
+
if (
|
|
3202
|
+
display === "flex" ||
|
|
3203
|
+
display === "inline-flex" ||
|
|
3204
|
+
display === "grid" ||
|
|
3205
|
+
display === "inline-grid"
|
|
3206
|
+
) {
|
|
3207
|
+
return true;
|
|
3208
|
+
}
|
|
3209
|
+
const classes = new Set(classList(element));
|
|
3210
|
+
return (
|
|
3211
|
+
classes.has("flex") ||
|
|
3212
|
+
classes.has("inline-flex") ||
|
|
3213
|
+
classes.has("grid") ||
|
|
3214
|
+
classes.has("inline-grid")
|
|
3215
|
+
);
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
/**
|
|
3219
|
+
* A Figma auto-layout drop makes the moved layer a flow child. Carrying its
|
|
3220
|
+
* former `position:absolute` offsets into the new flex/grid parent leaves it
|
|
3221
|
+
* visually detached from ordering, gap, and alignment even though the layer
|
|
3222
|
+
* tree says it was reparented. Normalize only the moved fragment's root; its
|
|
3223
|
+
* descendants keep their own positioning contexts unchanged.
|
|
3224
|
+
*/
|
|
3225
|
+
function prepareMovedFragmentForParent(
|
|
3226
|
+
fragment: string,
|
|
3227
|
+
destinationParent: ParsedElement | undefined,
|
|
3228
|
+
): string {
|
|
3229
|
+
if (!isFlowLayoutContainer(destinationParent)) return fragment;
|
|
3230
|
+
const fragmentRoot = parseHtmlElements(fragment).find(
|
|
3231
|
+
(element) => element.parentIndex === undefined,
|
|
3232
|
+
);
|
|
3233
|
+
return fragmentRoot
|
|
3234
|
+
? stripAbsolutePositioningFromChild(fragment, fragmentRoot)
|
|
3235
|
+
: fragment;
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3181
3238
|
/** Generate a fresh unique data-agent-native-node-id value not already in the set. */
|
|
3182
3239
|
function freshNodeId(usedIds: Set<string>, basis: string): string {
|
|
3183
3240
|
const base = `an-${hashStable(basis)}`;
|
|
@@ -3264,11 +3321,42 @@ function stripAbsolutePositioningFromChild(
|
|
|
3264
3321
|
child: ParsedElement,
|
|
3265
3322
|
): string {
|
|
3266
3323
|
const currentStyle = attributeValue(child, "style");
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3324
|
+
let nextHtml = currentStyle
|
|
3325
|
+
? replaceOrInsertAttribute(
|
|
3326
|
+
html,
|
|
3327
|
+
child,
|
|
3328
|
+
"style",
|
|
3329
|
+
stripStyleProperties(currentStyle, [...AUTO_LAYOUT_STRIP_PROPS]),
|
|
3330
|
+
)
|
|
3331
|
+
: html;
|
|
3332
|
+
|
|
3333
|
+
// Source-backed Alpine/Tailwind designs commonly express positioning as
|
|
3334
|
+
// utility classes instead of inline CSS. Once the layer moves into a new
|
|
3335
|
+
// flex/grid parent, those utilities would keep it out of flow even though
|
|
3336
|
+
// the Layers tree shows it as a child. Remove only position-mode utilities;
|
|
3337
|
+
// inset utilities can remain because they are inert for a statically
|
|
3338
|
+
// positioned flex/grid item, and preserving them avoids needless source
|
|
3339
|
+
// churn if the user later makes the layer absolute again.
|
|
3340
|
+
const reparsedRoot = parseHtmlElements(nextHtml).find(
|
|
3341
|
+
(element) => element.parentIndex === undefined,
|
|
3342
|
+
);
|
|
3343
|
+
if (!reparsedRoot) return nextHtml;
|
|
3344
|
+
const classes = classList(reparsedRoot);
|
|
3345
|
+
const flowClasses = classes.filter((token) => {
|
|
3346
|
+
const variants = token.split(":");
|
|
3347
|
+
const utility = variants[variants.length - 1]?.replace(/^!/, "");
|
|
3348
|
+
return (
|
|
3349
|
+
utility !== "absolute" && utility !== "fixed" && utility !== "sticky"
|
|
3350
|
+
);
|
|
3351
|
+
});
|
|
3352
|
+
if (flowClasses.length === classes.length) return nextHtml;
|
|
3353
|
+
nextHtml = replaceOrInsertAttribute(
|
|
3354
|
+
nextHtml,
|
|
3355
|
+
reparsedRoot,
|
|
3356
|
+
"class",
|
|
3357
|
+
flowClasses.join(" "),
|
|
3358
|
+
);
|
|
3359
|
+
return nextHtml;
|
|
3272
3360
|
}
|
|
3273
3361
|
|
|
3274
3362
|
/**
|
|
@@ -4018,7 +4106,19 @@ export function applyVisualEdit(
|
|
|
4018
4106
|
const removedLength = element.end - element.start;
|
|
4019
4107
|
moveInsertAt =
|
|
4020
4108
|
element.start < rawInsertAt ? rawInsertAt - removedLength : rawInsertAt;
|
|
4021
|
-
|
|
4109
|
+
const destinationParent =
|
|
4110
|
+
intent.placement === "inside"
|
|
4111
|
+
? anchorElement
|
|
4112
|
+
: anchorResolution.node.parentId
|
|
4113
|
+
? initial.elementByNodeId.get(anchorResolution.node.parentId)
|
|
4114
|
+
: undefined;
|
|
4115
|
+
edit = applyMoveNodeEdit(
|
|
4116
|
+
html,
|
|
4117
|
+
element,
|
|
4118
|
+
anchorElement,
|
|
4119
|
+
intent,
|
|
4120
|
+
destinationParent,
|
|
4121
|
+
);
|
|
4022
4122
|
}
|
|
4023
4123
|
|
|
4024
4124
|
if (typeof edit === "string") {
|
|
@@ -4265,6 +4365,13 @@ export function moveNodeBetweenDocuments(
|
|
|
4265
4365
|
: destHtml.length;
|
|
4266
4366
|
anchorRedirected = true;
|
|
4267
4367
|
}
|
|
4368
|
+
const destinationParent =
|
|
4369
|
+
placement === "inside"
|
|
4370
|
+
? anchor
|
|
4371
|
+
: anchor.parentIndex === undefined
|
|
4372
|
+
? undefined
|
|
4373
|
+
: destElements[anchor.parentIndex];
|
|
4374
|
+
fragment = prepareMovedFragmentForParent(fragment, destinationParent);
|
|
4268
4375
|
nextDestHtml = `${destHtml.slice(0, insertAt)}${fragment}${destHtml.slice(insertAt)}`;
|
|
4269
4376
|
} else {
|
|
4270
4377
|
// Default: find <body> and append inside it, or append at end of doc.
|
|
@@ -4281,6 +4388,13 @@ export function moveNodeBetweenDocuments(
|
|
|
4281
4388
|
if (isOffsetInsideTemplateInterior(destHtml, insertAt)) {
|
|
4282
4389
|
insertAt = destHtml.length;
|
|
4283
4390
|
}
|
|
4391
|
+
// Appending inside <body> makes the moved node a flow child of the body,
|
|
4392
|
+
// exactly like the anchored `placement: "inside"` branch above. When the
|
|
4393
|
+
// destination body is a flex/grid container, carrying the fragment's
|
|
4394
|
+
// former absolute offsets along would leave it visually detached from
|
|
4395
|
+
// the body's ordering/gap/alignment, so run the same normalization
|
|
4396
|
+
// (prepareMovedFragmentForParent no-ops for non-flow bodies).
|
|
4397
|
+
fragment = prepareMovedFragmentForParent(fragment, bodyEl);
|
|
4284
4398
|
nextDestHtml = `${destHtml.slice(0, insertAt)}${fragment}${destHtml.slice(insertAt)}`;
|
|
4285
4399
|
}
|
|
4286
4400
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component library scan — cross-file component instance discovery.
|
|
3
|
+
*
|
|
4
|
+
* `index-components` (and `get-component-details`) only ever look at ONE
|
|
5
|
+
* design file at a time (the active file, or an explicit `fileId`).
|
|
6
|
+
* `component_index` metadata is therefore scoped to whichever file was last
|
|
7
|
+
* indexed, and its `runtimeSelectors` do not reliably span every screen in a
|
|
8
|
+
* multi-file design.
|
|
9
|
+
*
|
|
10
|
+
* "Go to main component" and "Swap instance" both need to find OTHER
|
|
11
|
+
* instances of a component name that may live on a different screen (design
|
|
12
|
+
* file) than the one currently open. This module scans a caller-supplied set
|
|
13
|
+
* of already-fetched design files (any order the caller wants — typically
|
|
14
|
+
* `createdAt` ascending, oldest/"most original" first) and returns every
|
|
15
|
+
* component instance found across all of them, in that same order.
|
|
16
|
+
*
|
|
17
|
+
* Pure — no DB / IO. Callers fetch file rows (respecting `accessFilter`) and
|
|
18
|
+
* pass the rows in here.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
buildCodeLayerProjection,
|
|
23
|
+
type CodeLayerSource,
|
|
24
|
+
} from "./code-layer.js";
|
|
25
|
+
import { componentNameFor, isComponentInstance } from "./component-model.js";
|
|
26
|
+
|
|
27
|
+
export interface ComponentLibraryFile {
|
|
28
|
+
id: string;
|
|
29
|
+
designId: string;
|
|
30
|
+
filename: string;
|
|
31
|
+
content: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ComponentLibraryEntry {
|
|
35
|
+
/** Component name from `data-agent-native-component`. */
|
|
36
|
+
name: string;
|
|
37
|
+
fileId: string;
|
|
38
|
+
filename: string;
|
|
39
|
+
/** `data-agent-native-node-id` (or fallback) of the instance root. */
|
|
40
|
+
nodeId: string;
|
|
41
|
+
/** CSS selector that addresses this instance on its screen. */
|
|
42
|
+
selector: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Scan every file for component instances, preserving the caller's file
|
|
47
|
+
* order and each file's document order. Files with empty/missing content are
|
|
48
|
+
* skipped rather than throwing.
|
|
49
|
+
*/
|
|
50
|
+
export function scanComponentLibrary(
|
|
51
|
+
files: ComponentLibraryFile[],
|
|
52
|
+
): ComponentLibraryEntry[] {
|
|
53
|
+
const entries: ComponentLibraryEntry[] = [];
|
|
54
|
+
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
const html = file.content ?? "";
|
|
57
|
+
if (!html) continue;
|
|
58
|
+
|
|
59
|
+
const codeLayerSource: CodeLayerSource = {
|
|
60
|
+
kind: "design-file",
|
|
61
|
+
designId: file.designId,
|
|
62
|
+
fileId: file.id,
|
|
63
|
+
filename: file.filename,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const projection = buildCodeLayerProjection(html, {
|
|
67
|
+
source: codeLayerSource,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
for (const node of projection.nodes) {
|
|
71
|
+
if (!isComponentInstance(node)) continue;
|
|
72
|
+
const name = componentNameFor(node);
|
|
73
|
+
if (!name) continue;
|
|
74
|
+
|
|
75
|
+
// `node.id` is an ephemeral id scoped to this one projection call — it
|
|
76
|
+
// is NOT the same as the durable `data-agent-native-node-id` attribute
|
|
77
|
+
// stamped into the HTML (see `ensureCodeLayerNodeIdsInHtml`). Callers
|
|
78
|
+
// that navigate to / re-select this entry on a LATER read (a different
|
|
79
|
+
// projection call — e.g. after a `navigate` round-trip) need the stable
|
|
80
|
+
// attribute value, not this run's ephemeral id, so prefer it here and
|
|
81
|
+
// only fall back to `node.id` for the rare case a component-annotated
|
|
82
|
+
// node hasn't been stamped with a durable id yet.
|
|
83
|
+
const nodeId =
|
|
84
|
+
node.dataAttributes["data-agent-native-node-id"] ?? node.id;
|
|
85
|
+
|
|
86
|
+
entries.push({
|
|
87
|
+
name,
|
|
88
|
+
fileId: file.id,
|
|
89
|
+
filename: file.filename,
|
|
90
|
+
nodeId,
|
|
91
|
+
selector: node.selector,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return entries;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Filter a scanned library to instances of one component name, optionally
|
|
101
|
+
* excluding a specific (fileId, nodeId) — typically the instance the caller
|
|
102
|
+
* is acting on, so it doesn't get offered back as its own "main"/swap
|
|
103
|
+
* source.
|
|
104
|
+
*/
|
|
105
|
+
export function entriesForComponent(
|
|
106
|
+
entries: ComponentLibraryEntry[],
|
|
107
|
+
name: string,
|
|
108
|
+
exclude?: { fileId: string; nodeId: string },
|
|
109
|
+
): ComponentLibraryEntry[] {
|
|
110
|
+
return entries.filter((entry) => {
|
|
111
|
+
if (entry.name !== name) return false;
|
|
112
|
+
if (
|
|
113
|
+
exclude &&
|
|
114
|
+
entry.fileId === exclude.fileId &&
|
|
115
|
+
entry.nodeId === exclude.nodeId
|
|
116
|
+
) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Aggregate scanned entries into one row per distinct component name — the
|
|
125
|
+
* shape the Swap Instance picker needs (a name to pick, a count, and a
|
|
126
|
+
* representative instance to source markup/props from).
|
|
127
|
+
*/
|
|
128
|
+
export interface ComponentLibrarySummary {
|
|
129
|
+
name: string;
|
|
130
|
+
instanceCount: number;
|
|
131
|
+
sampleFileId: string;
|
|
132
|
+
sampleFilename: string;
|
|
133
|
+
sampleNodeId: string;
|
|
134
|
+
sampleSelector: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function summarizeComponentLibrary(
|
|
138
|
+
entries: ComponentLibraryEntry[],
|
|
139
|
+
): ComponentLibrarySummary[] {
|
|
140
|
+
const byName = new Map<string, ComponentLibrarySummary>();
|
|
141
|
+
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
const existing = byName.get(entry.name);
|
|
144
|
+
if (existing) {
|
|
145
|
+
existing.instanceCount += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
byName.set(entry.name, {
|
|
149
|
+
name: entry.name,
|
|
150
|
+
instanceCount: 1,
|
|
151
|
+
sampleFileId: entry.fileId,
|
|
152
|
+
sampleFilename: entry.filename,
|
|
153
|
+
sampleNodeId: entry.nodeId,
|
|
154
|
+
sampleSelector: entry.selector,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return Array.from(byName.values()).sort((a, b) =>
|
|
159
|
+
a.name.localeCompare(b.name),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `err` is a UNIQUE / PRIMARY KEY constraint violation from either
|
|
3
|
+
* supported driver (Postgres 23505, SQLite SQLITE_CONSTRAINT_UNIQUE /
|
|
4
|
+
* _PRIMARYKEY). Shared by any action that inserts a row guarded by a
|
|
5
|
+
* best-effort unique index and needs to recover from a losing race instead of
|
|
6
|
+
* failing outright — see `design_files_design_filename_unique_idx` in
|
|
7
|
+
* `server/plugins/db.ts` for the index this is meant to catch a conflict
|
|
8
|
+
* against, and `add-localhost-screens.ts` / `present-design-variants.ts` for
|
|
9
|
+
* the adopt-the-winning-row recovery pattern built on top of this check.
|
|
10
|
+
*/
|
|
11
|
+
export function isUniqueConstraintViolation(err: unknown): boolean {
|
|
12
|
+
const e = err as { code?: unknown; message?: unknown } | null;
|
|
13
|
+
if (e?.code === "23505") return true;
|
|
14
|
+
const code = String(e?.code ?? "");
|
|
15
|
+
if (
|
|
16
|
+
code === "SQLITE_CONSTRAINT_PRIMARYKEY" ||
|
|
17
|
+
code === "SQLITE_CONSTRAINT_UNIQUE"
|
|
18
|
+
) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const msg = String(e?.message ?? "").toLowerCase();
|
|
22
|
+
return (
|
|
23
|
+
msg.includes("unique constraint") ||
|
|
24
|
+
msg.includes("primary key constraint") ||
|
|
25
|
+
msg.includes("duplicate key")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Relation to `source-mode.ts`:
|
|
9
9
|
* - `DesignBridgeOperation` ("select" | "resolveNodeToFile" | "readFile" |
|
|
10
|
-
* "applyEdit" | "writeFile" | "captureSnapshot" | "captureState"
|
|
10
|
+
* "applyEdit" | "writeFile" | "captureSnapshot" | "captureState" |
|
|
11
|
+
* "listFiles") describes
|
|
11
12
|
* the low-level bridge RPC surface.
|
|
12
13
|
* - `DesignCapabilityName` below is the *higher-level* capability vocabulary
|
|
13
14
|
* that UI panels and agent actions read. Several capabilities build on one
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Figma URL / node-id parsing helpers.
|
|
3
|
+
*
|
|
4
|
+
* Figma file URLs come in a few shapes:
|
|
5
|
+
* https://www.figma.com/design/<fileKey>/<name>
|
|
6
|
+
* https://www.figma.com/file/<fileKey>/<name> (legacy)
|
|
7
|
+
* https://www.figma.com/proto/<fileKey>/<name> (prototype share link)
|
|
8
|
+
* https://www.figma.com/design/<fileKey>/<name>/branch/<branchKey>/<name>
|
|
9
|
+
*
|
|
10
|
+
* The branch shape is the important gotcha: a branch is a *separate* file for
|
|
11
|
+
* REST API purposes. `/v1/files/:fileKey` etc. must be called with the
|
|
12
|
+
* **branch key**, not the parent file key that appears earlier in the path —
|
|
13
|
+
* calling with the parent key silently returns the parent file's content,
|
|
14
|
+
* not the branch the user is actually looking at. See
|
|
15
|
+
* https://developers.figma.com/docs/rest-api/ (branches share the same file
|
|
16
|
+
* shape as regular files once you have the branch's own key).
|
|
17
|
+
*
|
|
18
|
+
* `node-id` is carried as a query param and Figma writes it with dashes
|
|
19
|
+
* (`1-2`) even though the REST API's node ids use colons (`1:2`). Grouped /
|
|
20
|
+
* instance-swap node ids can look like `1:2;3:4` — only the plain
|
|
21
|
+
* `<number>-<number>` shape needs dash-to-colon conversion; anything already
|
|
22
|
+
* containing a colon is passed through untouched.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const FIGMA_FILE_KEY_RE = /^[A-Za-z0-9_-]{8,}$/;
|
|
26
|
+
const FILE_PATH_SEGMENTS = ["design", "file", "proto"] as const;
|
|
27
|
+
|
|
28
|
+
export interface ParsedFigmaUrl {
|
|
29
|
+
/** Effective file key to use for REST API calls (branch key when present). */
|
|
30
|
+
fileKey: string | null;
|
|
31
|
+
/** Node id in REST API colon form (e.g. "1:2"), or null when absent. */
|
|
32
|
+
nodeId: string | null;
|
|
33
|
+
/** True when the URL pointed at a branch and fileKey is that branch's key. */
|
|
34
|
+
isBranch: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function candidateFileKey(value: string): string | null {
|
|
38
|
+
return FIGMA_FILE_KEY_RE.test(value) ? value : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract the effective Figma file key from a raw file key or a Figma URL.
|
|
43
|
+
* Resolves `/branch/:branchKey/` to the branch key when present, since a
|
|
44
|
+
* branch is a distinct file from the REST API's point of view.
|
|
45
|
+
*/
|
|
46
|
+
export function parseFigmaFileKey(input: string | undefined): string | null {
|
|
47
|
+
const value = input?.trim();
|
|
48
|
+
if (!value) return null;
|
|
49
|
+
|
|
50
|
+
const direct = candidateFileKey(value);
|
|
51
|
+
if (direct) return direct;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const url = new URL(value);
|
|
55
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
56
|
+
|
|
57
|
+
const branchIndex = parts.indexOf("branch");
|
|
58
|
+
if (branchIndex >= 0) {
|
|
59
|
+
const branchKey = candidateFileKey(parts[branchIndex + 1] ?? "");
|
|
60
|
+
if (branchKey) return branchKey;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const segment of FILE_PATH_SEGMENTS) {
|
|
64
|
+
const index = parts.indexOf(segment);
|
|
65
|
+
if (index >= 0) {
|
|
66
|
+
const key = candidateFileKey(parts[index + 1] ?? "");
|
|
67
|
+
if (key) return key;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** True when the given file URL points at a branch (not the main file). */
|
|
78
|
+
export function isFigmaBranchUrl(input: string | undefined): boolean {
|
|
79
|
+
const value = input?.trim();
|
|
80
|
+
if (!value) return false;
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(value);
|
|
83
|
+
return url.pathname.split("/").filter(Boolean).includes("branch");
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalize a raw node-id-ish string to the REST API's colon form. Accepts:
|
|
91
|
+
* "1:2" -> "1:2" (already normalized)
|
|
92
|
+
* "1-2" -> "1:2" (dash form, as seen in `node-id` query params)
|
|
93
|
+
* "1-2;3-4" -> "1:2;3:4" (instance-swap grouped ids)
|
|
94
|
+
* "I1:2;3:4" -> "I1:2;3:4" (already normalized, leading instance marker)
|
|
95
|
+
*/
|
|
96
|
+
function normalizeNodeIdToken(token: string): string | null {
|
|
97
|
+
const trimmed = token.trim();
|
|
98
|
+
if (!trimmed) return null;
|
|
99
|
+
// Already colon form (optionally prefixed with "I" for instance overrides).
|
|
100
|
+
if (/^I?\d+:\d+$/.test(trimmed)) return trimmed;
|
|
101
|
+
// Dash form -> colon form.
|
|
102
|
+
if (/^I?\d+-\d+$/.test(trimmed)) return trimmed.replace("-", ":");
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeNodeId(raw: string): string | null {
|
|
107
|
+
const decoded = (() => {
|
|
108
|
+
try {
|
|
109
|
+
return decodeURIComponent(raw);
|
|
110
|
+
} catch {
|
|
111
|
+
return raw;
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
const parts = decoded
|
|
115
|
+
.split(";")
|
|
116
|
+
.map((part) => normalizeNodeIdToken(part))
|
|
117
|
+
.filter((part): part is string => part !== null);
|
|
118
|
+
return parts.length > 0 ? parts.join(";") : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extract a Figma node id from a `node-id` query param or a raw id string,
|
|
123
|
+
* converting Figma's URL dash form (`1-2`) to the REST API's colon form
|
|
124
|
+
* (`1:2`).
|
|
125
|
+
*/
|
|
126
|
+
export function parseFigmaNodeId(input: string | undefined): string | null {
|
|
127
|
+
const value = input?.trim();
|
|
128
|
+
if (!value) return null;
|
|
129
|
+
|
|
130
|
+
const direct = normalizeNodeId(value);
|
|
131
|
+
if (direct) return direct;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const url = new URL(value);
|
|
135
|
+
const nodeParam = url.searchParams.get("node-id");
|
|
136
|
+
if (nodeParam) return normalizeNodeId(nodeParam);
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Parse a Figma URL (or bare file key) into its effective file key and node id. */
|
|
145
|
+
export function parseFigmaUrl(input: string | undefined): ParsedFigmaUrl {
|
|
146
|
+
const fileKey = parseFigmaFileKey(input);
|
|
147
|
+
const nodeId = parseFigmaNodeId(input);
|
|
148
|
+
return { fileKey, nodeId, isBranch: isFigmaBranchUrl(input) };
|
|
149
|
+
}
|
|
@@ -115,13 +115,22 @@ export function snapPenAnchorPoint(
|
|
|
115
115
|
path: PenPath | null,
|
|
116
116
|
options: { hitRadius: number; zoom: number },
|
|
117
117
|
): PenPoint {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
// Nearest anchor within radius wins (matching hitTestPenAnchor), not the
|
|
119
|
+
// first one found in node order — two anchors can easily both sit within
|
|
120
|
+
// the hit radius (e.g. a tightly drawn shape), and a "first match" scan
|
|
121
|
+
// would snap to whichever happens to have the lower node index rather than
|
|
122
|
+
// the one actually closest to the cursor.
|
|
123
|
+
let nearestAnchor: PenPoint | null = null;
|
|
124
|
+
let nearestDistance = Infinity;
|
|
125
|
+
for (const node of path?.nodes ?? []) {
|
|
126
|
+
const distance = Math.hypot(node.point.x - point.x, node.point.y - point.y);
|
|
127
|
+
if (distance <= options.hitRadius && distance < nearestDistance) {
|
|
128
|
+
nearestDistance = distance;
|
|
129
|
+
nearestAnchor = node.point;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (nearestAnchor) {
|
|
133
|
+
return { ...nearestAnchor };
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
if (options.zoom >= 100) {
|
|
@@ -41,8 +41,6 @@ const useIsomorphicLayoutEffect =
|
|
|
41
41
|
const DEFAULT_VIEW = { zoom: 0.72, pan: { x: 96, y: 64 } };
|
|
42
42
|
const MIN_ZOOM = 0.18;
|
|
43
43
|
const MAX_ZOOM = 2.4;
|
|
44
|
-
/** Notched mouse-wheel fixed-ratio step (design-canvas.jsx feel). */
|
|
45
|
-
const WHEEL_ZOOM_STEP = 0.16;
|
|
46
44
|
/** Trackpad pinch sensitivity. */
|
|
47
45
|
const PINCH_ZOOM_SENSITIVITY = 0.01;
|
|
48
46
|
/** Base CSS grid cell, scaled by zoom. */
|
|
@@ -534,9 +532,11 @@ export function CanvasArea({
|
|
|
534
532
|
[buildMarkupContext, onCanvasMarkupCreate, pendingMarkup],
|
|
535
533
|
);
|
|
536
534
|
|
|
537
|
-
// Wheel: cursor-over-canvas never scrolls the page.
|
|
538
|
-
//
|
|
539
|
-
//
|
|
535
|
+
// Wheel: cursor-over-canvas never scrolls the page. Match Figma's input
|
|
536
|
+
// contract: unmodified wheel/trackpad gestures always pan, while an explicit
|
|
537
|
+
// ctrl/cmd modifier (including the ctrlKey emitted by trackpad pinch) zooms
|
|
538
|
+
// at the cursor. Never infer zoom intent from the delta shape — trackpad pan
|
|
539
|
+
// momentum can look exactly like a notched mouse wheel near a canvas edge.
|
|
540
540
|
useEffect(() => {
|
|
541
541
|
const element = viewportRef.current;
|
|
542
542
|
if (!element) return;
|
|
@@ -559,24 +559,12 @@ export function CanvasArea({
|
|
|
559
559
|
const deltaX = event.deltaX * lineScale;
|
|
560
560
|
const deltaY = event.deltaY * lineScale;
|
|
561
561
|
|
|
562
|
-
|
|
563
|
-
// horizontal component (Chrome/Safari). Fixed-ratio step per click.
|
|
564
|
-
const isNotchedWheel =
|
|
565
|
-
event.deltaMode !== 0 ||
|
|
566
|
-
(event.deltaX === 0 &&
|
|
567
|
-
Number.isInteger(event.deltaY) &&
|
|
568
|
-
Math.abs(event.deltaY) >= 40);
|
|
569
|
-
|
|
570
|
-
if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
562
|
+
if (event.ctrlKey || event.metaKey) {
|
|
571
563
|
// Trackpad pinch / explicit zoom modifier — smooth exponential.
|
|
572
564
|
zoomByFactor(Math.exp(-deltaY * PINCH_ZOOM_SENSITIVITY), anchor);
|
|
573
565
|
return;
|
|
574
566
|
}
|
|
575
|
-
|
|
576
|
-
zoomByFactor(Math.exp(-Math.sign(deltaY) * WHEEL_ZOOM_STEP), anchor);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
// Trackpad two-finger scroll → pan.
|
|
567
|
+
// Mouse wheel / trackpad two-finger scroll -> pan.
|
|
580
568
|
updateView((current) => ({
|
|
581
569
|
...current,
|
|
582
570
|
pan: {
|
|
@@ -13,8 +13,23 @@ import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-sys
|
|
|
13
13
|
|
|
14
14
|
const codeFileSchema = z.object({
|
|
15
15
|
filename: z.string().trim().min(1).describe("File name or relative path"),
|
|
16
|
-
content: z
|
|
16
|
+
content: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe(
|
|
19
|
+
"File content. Text files (code, CSS, markdown, JSON, SVG): raw text. " +
|
|
20
|
+
"Binary files -- most importantly `.fig` (a zip/kiwi binary container, " +
|
|
21
|
+
'never valid text) -- MUST be base64-encoded with encoding: "base64"; ' +
|
|
22
|
+
"sending raw/binary-as-string content with the default utf8 encoding " +
|
|
23
|
+
"corrupts the file before it reaches Builder.",
|
|
24
|
+
),
|
|
17
25
|
mimeType: z.string().trim().optional().describe("Optional MIME type"),
|
|
26
|
+
encoding: z
|
|
27
|
+
.enum(["utf8", "base64"])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe(
|
|
30
|
+
"Encoding of `content`. Defaults to utf8. Set to base64 for `.fig` and " +
|
|
31
|
+
"other binary files.",
|
|
32
|
+
),
|
|
18
33
|
});
|
|
19
34
|
|
|
20
35
|
export default defineAction({
|