@agent-native/core 0.92.10 → 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 +23 -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/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/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/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/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
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
} from "@agent-native/core/application-state";
|
|
7
7
|
import { seedFromText } from "@agent-native/core/collab";
|
|
8
8
|
import { buildDeepLink } from "@agent-native/core/server";
|
|
9
|
-
import { assertAccess } from "@agent-native/core/sharing";
|
|
10
|
-
import { eq } from "drizzle-orm";
|
|
9
|
+
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
10
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
11
11
|
import { nanoid } from "nanoid";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
mergeCanvasFramePlacements,
|
|
19
19
|
type CanvasFramePlacement,
|
|
20
20
|
} from "../shared/canvas-frames.js";
|
|
21
|
+
import { isUniqueConstraintViolation } from "../shared/db-conflict.js";
|
|
21
22
|
import { annotateScreenHtmlForPersist } from "../shared/screen-annotation.js";
|
|
22
23
|
|
|
23
24
|
const VARIANT_GAP = 96;
|
|
@@ -120,6 +121,336 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
120
121
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
const MAX_FILENAME_INSERT_ATTEMPTS = 5;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* True only when `rawSet` proves none of its screens has ever been removed by
|
|
128
|
+
* delete-file.ts. This action stamps every variant set it creates with
|
|
129
|
+
* `screenCount` — the exact number of screens it generated, which never
|
|
130
|
+
* changes afterward (present-design-variants.ts never mutates an existing
|
|
131
|
+
* set's `screenCount`, and delete-file.ts's `pruneDesignVariantSets` only
|
|
132
|
+
* ever shortens/removes a set's `screens` array, never touches `screenCount`).
|
|
133
|
+
* The moment any member is deleted, `screens.length` drops below the
|
|
134
|
+
* recorded `screenCount` (or the whole entry is removed once <=1 screens
|
|
135
|
+
* remain) — either way, `screens.length === screenCount` no longer holds. So
|
|
136
|
+
* this equality proves no delete-file call has EVER resolved against this
|
|
137
|
+
* set.
|
|
138
|
+
*
|
|
139
|
+
* IMPORTANT: this does NOT prove the user never picked a screen from the set.
|
|
140
|
+
* A pick only exists as a chat instruction (see `VARIANT_PICK_SUBMIT_MESSAGE`)
|
|
141
|
+
* until the agent's follow-up turn actually calls delete-file for the losers;
|
|
142
|
+
* there is no synchronous server-side seam that records a pick before that
|
|
143
|
+
* turn runs (an agent retry after a run cutoff, or a second
|
|
144
|
+
* present-design-variants call, can both land before the delete-file calls
|
|
145
|
+
* do). So `isUntouchedVariantSet` is used only to decide whether a set is
|
|
146
|
+
* eligible to be *marked* superseded — never as authority to delete its
|
|
147
|
+
* files. See `markSupersededVariantSets` / `deleteVariantSetsByIds` below.
|
|
148
|
+
* Sets without a numeric `screenCount` at all (legacy data predating this
|
|
149
|
+
* field) are NOT treated as untouched either, since we cannot prove those
|
|
150
|
+
* were never picked.
|
|
151
|
+
*/
|
|
152
|
+
function isUntouchedVariantSet(
|
|
153
|
+
rawSet: unknown,
|
|
154
|
+
): rawSet is { screens: unknown[]; screenCount: number } & Record<
|
|
155
|
+
string,
|
|
156
|
+
unknown
|
|
157
|
+
> {
|
|
158
|
+
return (
|
|
159
|
+
isRecord(rawSet) &&
|
|
160
|
+
Array.isArray(rawSet.screens) &&
|
|
161
|
+
typeof rawSet.screenCount === "number" &&
|
|
162
|
+
rawSet.screens.length === rawSet.screenCount
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function collectUnmarkedSupersededCandidateIds(
|
|
167
|
+
variantSets: Record<string, unknown>,
|
|
168
|
+
): string[] {
|
|
169
|
+
const setIds: string[] = [];
|
|
170
|
+
for (const [setId, rawSet] of Object.entries(variantSets)) {
|
|
171
|
+
if (!isUntouchedVariantSet(rawSet)) continue;
|
|
172
|
+
if (isRecord(rawSet) && rawSet.superseded === true) continue;
|
|
173
|
+
setIds.push(setId);
|
|
174
|
+
}
|
|
175
|
+
return setIds;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function screenFileIdsForSet(rawSet: { screens: unknown[] }): string[] {
|
|
179
|
+
const fileIds = new Set<string>();
|
|
180
|
+
for (const screen of rawSet.screens) {
|
|
181
|
+
const id = isRecord(screen)
|
|
182
|
+
? screen.id
|
|
183
|
+
: typeof screen === "string"
|
|
184
|
+
? screen
|
|
185
|
+
: undefined;
|
|
186
|
+
if (typeof id === "string") fileIds.add(id);
|
|
187
|
+
}
|
|
188
|
+
return Array.from(fileIds);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function pruneKeyedRecordForIds(
|
|
192
|
+
value: unknown,
|
|
193
|
+
ids: string[],
|
|
194
|
+
): Record<string, unknown> {
|
|
195
|
+
const next = isRecord(value) ? { ...value } : {};
|
|
196
|
+
for (const id of ids) delete next[id];
|
|
197
|
+
return next;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Marks each PREVIOUS present-design-variants set for this design that is
|
|
202
|
+
* proven untouched (see `isUntouchedVariantSet`) as `superseded: true`,
|
|
203
|
+
* whenever a new variant set is created for the same design. This is
|
|
204
|
+
* bookkeeping ONLY — it never deletes files, never touches `canvasFrames` or
|
|
205
|
+
* `screenMetadata`, and is always safe to run unconditionally. A set with
|
|
206
|
+
* `superseded: true` still renders normally; the flag only makes it eligible
|
|
207
|
+
* for the opt-in `deleteSupersededSetIds` path below.
|
|
208
|
+
*
|
|
209
|
+
* Deletion is intentionally NOT automatic here. The failure mode this
|
|
210
|
+
* guards against: set V1=[S1,S2,S3] is generated; the user picks S2 in chat;
|
|
211
|
+
* before the agent's delete-file turn for S1/S3 actually runs (run cutoff,
|
|
212
|
+
* retry, or a second present-design-variants call arriving first),
|
|
213
|
+
* `screens.length === screenCount` still holds for V1 — a pick is not
|
|
214
|
+
* observable server-side until delete-file resolves it. Auto-deleting on
|
|
215
|
+
* that inference would have hard-deleted the picked screen S2 with no
|
|
216
|
+
* recovery. So this function only records bookkeeping; real deletion
|
|
217
|
+
* requires the agent to explicitly name the set via `deleteSupersededSetIds`
|
|
218
|
+
* — see that action param's description for the discipline expected of
|
|
219
|
+
* callers.
|
|
220
|
+
*/
|
|
221
|
+
async function markSupersededVariantSets(
|
|
222
|
+
designId: string,
|
|
223
|
+
): Promise<{ markedSetIds: string[] }> {
|
|
224
|
+
const db = getDb();
|
|
225
|
+
|
|
226
|
+
// Cheap early-exit read so the common case (no stale variant set to mark)
|
|
227
|
+
// never bumps the design's updatedAt via a no-op mutateDesignData call —
|
|
228
|
+
// mutateDesignDataUnlocked always advances updatedAt on every commit, even
|
|
229
|
+
// when the payload is unchanged. The authoritative check still runs inside
|
|
230
|
+
// the mutate() callback below, so a race against this snapshot is harmless.
|
|
231
|
+
const [designRow] = await db
|
|
232
|
+
.select({ data: schema.designs.data })
|
|
233
|
+
.from(schema.designs)
|
|
234
|
+
.where(
|
|
235
|
+
and(
|
|
236
|
+
eq(schema.designs.id, designId),
|
|
237
|
+
accessFilter(schema.designs, schema.designShares),
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
if (!designRow) return { markedSetIds: [] };
|
|
241
|
+
let precheckVariantSets: Record<string, unknown> = {};
|
|
242
|
+
if (designRow.data) {
|
|
243
|
+
try {
|
|
244
|
+
const parsed: unknown = JSON.parse(designRow.data);
|
|
245
|
+
if (isRecord(parsed) && isRecord(parsed.designVariantSets)) {
|
|
246
|
+
precheckVariantSets = parsed.designVariantSets;
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
return { markedSetIds: [] };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (collectUnmarkedSupersededCandidateIds(precheckVariantSets).length === 0) {
|
|
253
|
+
return { markedSetIds: [] };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let markedSetIds: string[] = [];
|
|
257
|
+
|
|
258
|
+
await mutateDesignData({
|
|
259
|
+
designId,
|
|
260
|
+
mutate: (current, { updatedAt }) => {
|
|
261
|
+
const variantSets = isRecord(current.designVariantSets)
|
|
262
|
+
? current.designVariantSets
|
|
263
|
+
: {};
|
|
264
|
+
const setIds = collectUnmarkedSupersededCandidateIds(variantSets);
|
|
265
|
+
markedSetIds = setIds;
|
|
266
|
+
if (setIds.length === 0) return current;
|
|
267
|
+
|
|
268
|
+
const nextVariantSets = { ...variantSets };
|
|
269
|
+
for (const setId of setIds) {
|
|
270
|
+
const rawSet = variantSets[setId];
|
|
271
|
+
if (isRecord(rawSet)) {
|
|
272
|
+
nextVariantSets[setId] = { ...rawSet, superseded: true };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
...current,
|
|
278
|
+
designVariantSets: nextVariantSets,
|
|
279
|
+
updatedAt,
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
isApplied: (current) => {
|
|
283
|
+
const variantSets = isRecord(current.designVariantSets)
|
|
284
|
+
? current.designVariantSets
|
|
285
|
+
: {};
|
|
286
|
+
return markedSetIds.every(
|
|
287
|
+
(setId) =>
|
|
288
|
+
isRecord(variantSets[setId]) &&
|
|
289
|
+
variantSets[setId]!.superseded === true,
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return { markedSetIds };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Explicit, opt-in permanent deletion of specific variant sets by id. This is
|
|
299
|
+
* the ONLY path that hard-deletes variant-set files; it never runs
|
|
300
|
+
* automatically. The caller (the agent) must pass `deleteSupersededSetIds`
|
|
301
|
+
* naming the exact sets it knows were abandoned without any user pick — e.g.
|
|
302
|
+
* generating a brand-new set after the user asked to see different options.
|
|
303
|
+
* Never pass a previous set's id here on the strength of a guess or a retry;
|
|
304
|
+
* only do so when there is no reason to believe the user discussed or picked
|
|
305
|
+
* one of its screens.
|
|
306
|
+
*
|
|
307
|
+
* As a safety net, a requested id is only honored when the set is already
|
|
308
|
+
* flagged `superseded: true` by `markSupersededVariantSets` (i.e. proven
|
|
309
|
+
* untouched by any delete-file call as of this same run). A set that has
|
|
310
|
+
* already had even one delete-file call resolve against it — the normal sign
|
|
311
|
+
* that a pick is mid-flight — is never marked superseded and is therefore
|
|
312
|
+
* never eligible here, regardless of what the caller requests.
|
|
313
|
+
*/
|
|
314
|
+
async function deleteVariantSetsByIds(
|
|
315
|
+
designId: string,
|
|
316
|
+
requestedSetIds: string[],
|
|
317
|
+
): Promise<{ removedSetIds: string[]; removedFileIds: string[] }> {
|
|
318
|
+
const db = getDb();
|
|
319
|
+
|
|
320
|
+
// Same cheap early-exit as markSupersededVariantSets: skip the
|
|
321
|
+
// updatedAt-bumping mutateDesignData commit when none of the requested set
|
|
322
|
+
// ids currently qualifies for deletion. The authoritative check still runs
|
|
323
|
+
// inside the mutate() callback below.
|
|
324
|
+
const [designRow] = await db
|
|
325
|
+
.select({ data: schema.designs.data })
|
|
326
|
+
.from(schema.designs)
|
|
327
|
+
.where(
|
|
328
|
+
and(
|
|
329
|
+
eq(schema.designs.id, designId),
|
|
330
|
+
accessFilter(schema.designs, schema.designShares),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
if (!designRow) return { removedSetIds: [], removedFileIds: [] };
|
|
334
|
+
let precheckVariantSets: Record<string, unknown> = {};
|
|
335
|
+
if (designRow.data) {
|
|
336
|
+
try {
|
|
337
|
+
const parsed: unknown = JSON.parse(designRow.data);
|
|
338
|
+
if (isRecord(parsed) && isRecord(parsed.designVariantSets)) {
|
|
339
|
+
precheckVariantSets = parsed.designVariantSets;
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
return { removedSetIds: [], removedFileIds: [] };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
!requestedSetIds.some((setId) => {
|
|
347
|
+
const rawSet = precheckVariantSets[setId];
|
|
348
|
+
return (
|
|
349
|
+
isRecord(rawSet) &&
|
|
350
|
+
rawSet.superseded === true &&
|
|
351
|
+
Array.isArray(rawSet.screens)
|
|
352
|
+
);
|
|
353
|
+
})
|
|
354
|
+
) {
|
|
355
|
+
return { removedSetIds: [], removedFileIds: [] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let removedSetIds: string[] = [];
|
|
359
|
+
let removedFileIds: string[] = [];
|
|
360
|
+
|
|
361
|
+
await mutateDesignData({
|
|
362
|
+
designId,
|
|
363
|
+
mutate: (current, { updatedAt }) => {
|
|
364
|
+
const variantSets = isRecord(current.designVariantSets)
|
|
365
|
+
? current.designVariantSets
|
|
366
|
+
: {};
|
|
367
|
+
const setIds: string[] = [];
|
|
368
|
+
const fileIds = new Set<string>();
|
|
369
|
+
for (const setId of requestedSetIds) {
|
|
370
|
+
const rawSet = variantSets[setId];
|
|
371
|
+
if (
|
|
372
|
+
!isRecord(rawSet) ||
|
|
373
|
+
rawSet.superseded !== true ||
|
|
374
|
+
!Array.isArray(rawSet.screens)
|
|
375
|
+
) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
setIds.push(setId);
|
|
379
|
+
for (const id of screenFileIdsForSet(
|
|
380
|
+
rawSet as { screens: unknown[] },
|
|
381
|
+
)) {
|
|
382
|
+
fileIds.add(id);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
removedSetIds = setIds;
|
|
386
|
+
removedFileIds = Array.from(fileIds);
|
|
387
|
+
if (setIds.length === 0) return current;
|
|
388
|
+
|
|
389
|
+
const nextVariantSets = { ...variantSets };
|
|
390
|
+
for (const setId of setIds) delete nextVariantSets[setId];
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
...current,
|
|
394
|
+
canvasFrames: pruneKeyedRecordForIds(
|
|
395
|
+
current.canvasFrames,
|
|
396
|
+
removedFileIds,
|
|
397
|
+
),
|
|
398
|
+
screenMetadata: pruneKeyedRecordForIds(
|
|
399
|
+
current.screenMetadata,
|
|
400
|
+
removedFileIds,
|
|
401
|
+
),
|
|
402
|
+
designVariantSets: nextVariantSets,
|
|
403
|
+
updatedAt,
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
isApplied: (current) => {
|
|
407
|
+
const variantSets = isRecord(current.designVariantSets)
|
|
408
|
+
? current.designVariantSets
|
|
409
|
+
: {};
|
|
410
|
+
return removedSetIds.every((setId) => !(setId in variantSets));
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
if (removedFileIds.length > 0) {
|
|
415
|
+
await db
|
|
416
|
+
.delete(schema.designFiles)
|
|
417
|
+
.where(inArray(schema.designFiles.id, removedFileIds));
|
|
418
|
+
|
|
419
|
+
// Closes the small window between the metadata prune above and the
|
|
420
|
+
// physical row delete: mirrors delete-file.ts's before/delete/after
|
|
421
|
+
// pattern so a sibling request that refreshed canvasFrames/screenMetadata
|
|
422
|
+
// for one of these now-deleted ids in that window still gets swept.
|
|
423
|
+
await mutateDesignData({
|
|
424
|
+
designId,
|
|
425
|
+
mutate: (current, { updatedAt }) => ({
|
|
426
|
+
...current,
|
|
427
|
+
canvasFrames: pruneKeyedRecordForIds(
|
|
428
|
+
current.canvasFrames,
|
|
429
|
+
removedFileIds,
|
|
430
|
+
),
|
|
431
|
+
screenMetadata: pruneKeyedRecordForIds(
|
|
432
|
+
current.screenMetadata,
|
|
433
|
+
removedFileIds,
|
|
434
|
+
),
|
|
435
|
+
updatedAt,
|
|
436
|
+
}),
|
|
437
|
+
isApplied: (current) => {
|
|
438
|
+
const frames = isRecord(current.canvasFrames)
|
|
439
|
+
? current.canvasFrames
|
|
440
|
+
: {};
|
|
441
|
+
const metadata = isRecord(current.screenMetadata)
|
|
442
|
+
? current.screenMetadata
|
|
443
|
+
: {};
|
|
444
|
+
return removedFileIds.every(
|
|
445
|
+
(id) => !(id in frames) && !(id in metadata),
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { removedSetIds, removedFileIds };
|
|
452
|
+
}
|
|
453
|
+
|
|
123
454
|
function slugify(value: string, fallback: string) {
|
|
124
455
|
return (
|
|
125
456
|
value
|
|
@@ -432,7 +763,10 @@ export default defineAction({
|
|
|
432
763
|
"compact representative screen; pass concise labels/descriptions/features " +
|
|
433
764
|
"and omit content when full HTML would be too large. Design will render " +
|
|
434
765
|
"compact screens from the direction data. Expand the chosen direction " +
|
|
435
|
-
"after the user picks."
|
|
766
|
+
"after the user picks. Screens from an earlier variant set are never " +
|
|
767
|
+
"deleted automatically: if you are knowingly replacing your own earlier " +
|
|
768
|
+
"set that the user never picked from or discussed, pass its set id in " +
|
|
769
|
+
"deleteSupersededSetIds; otherwise leave old sets in place.",
|
|
436
770
|
schema: z.object({
|
|
437
771
|
designId: z.string().describe("Design project ID to show variants for"),
|
|
438
772
|
prompt: z
|
|
@@ -446,6 +780,19 @@ export default defineAction({
|
|
|
446
780
|
.describe(
|
|
447
781
|
"2-5 concise, visually distinct generated design options to place as overview screens (3 is the sweet spot). Prefer short label/description/features for each direction; include inline HTML content only when it is compact enough to finish.",
|
|
448
782
|
),
|
|
783
|
+
deleteSupersededSetIds: z
|
|
784
|
+
.array(z.string())
|
|
785
|
+
.optional()
|
|
786
|
+
.describe(
|
|
787
|
+
"OPT-IN permanent deletion of earlier variant sets you are knowingly " +
|
|
788
|
+
"replacing. Pass a previous variantSetId ONLY when you created that " +
|
|
789
|
+
"set yourself and the user never picked, kept, or discussed any of " +
|
|
790
|
+
"its screens (e.g. the user asked for a completely different set of " +
|
|
791
|
+
"directions). If the user may have picked a screen — even if the " +
|
|
792
|
+
"losing screens have not been deleted yet — do NOT pass the set id: " +
|
|
793
|
+
"deletion is permanent and would destroy the picked screen. Ids are " +
|
|
794
|
+
"only honored for sets provably untouched by any delete-file call.",
|
|
795
|
+
),
|
|
449
796
|
}),
|
|
450
797
|
mcpApp: {
|
|
451
798
|
compactCatalog: true,
|
|
@@ -458,9 +805,22 @@ export default defineAction({
|
|
|
458
805
|
height: 720,
|
|
459
806
|
}),
|
|
460
807
|
},
|
|
461
|
-
run: async ({ designId, prompt, variants }) => {
|
|
808
|
+
run: async ({ designId, prompt, variants, deleteSupersededSetIds }) => {
|
|
462
809
|
await assertAccess("design", designId, "editor");
|
|
463
810
|
|
|
811
|
+
// Non-destructive bookkeeping: flag earlier still-complete variant sets
|
|
812
|
+
// as superseded. Files are NEVER deleted automatically — a user's pick
|
|
813
|
+
// exists only as a chat instruction until the agent's delete-file turn
|
|
814
|
+
// runs, so an automatic sweep here could destroy the picked screen. Real
|
|
815
|
+
// deletion happens only for set ids the caller explicitly opts into via
|
|
816
|
+
// `deleteSupersededSetIds` below (and only when the set is provably
|
|
817
|
+
// untouched by any delete-file call).
|
|
818
|
+
await markSupersededVariantSets(designId);
|
|
819
|
+
const variantSetCleanup =
|
|
820
|
+
deleteSupersededSetIds && deleteSupersededSetIds.length > 0
|
|
821
|
+
? await deleteVariantSetsByIds(designId, deleteSupersededSetIds)
|
|
822
|
+
: { removedSetIds: [], removedFileIds: [] };
|
|
823
|
+
|
|
464
824
|
const db = getDb();
|
|
465
825
|
const now = new Date().toISOString();
|
|
466
826
|
const existingFiles = await db
|
|
@@ -475,9 +835,8 @@ export default defineAction({
|
|
|
475
835
|
const variant = variants[index]!;
|
|
476
836
|
const label = variant.label.trim() || optionName(index);
|
|
477
837
|
const slug = slugify(label, `option-${index + 1}`);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const fileId = nanoid();
|
|
838
|
+
let filename = uniqueFilename(`variant-${slug}.html`, usedFilenames);
|
|
839
|
+
let fileId = nanoid();
|
|
481
840
|
const providedContent = variant.content?.trim();
|
|
482
841
|
const initialSize = inferVariantSize(variant, prompt);
|
|
483
842
|
const rawContent =
|
|
@@ -491,15 +850,43 @@ export default defineAction({
|
|
|
491
850
|
// operations as soon as it lands on the overview board.
|
|
492
851
|
const content = annotateScreenHtmlForPersist(rawContent, "html");
|
|
493
852
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
853
|
+
// `usedFilenames` is a snapshot taken once at the top of run(), so a
|
|
854
|
+
// concurrent present-design-variants call (an agent retry after a
|
|
855
|
+
// timeout is the common trigger) can independently compute the same
|
|
856
|
+
// (designId, filename) pair and win the insert first. The
|
|
857
|
+
// `design_files_design_filename_unique_idx` unique index (see
|
|
858
|
+
// server/plugins/db.ts) turns the loser's insert into a constraint
|
|
859
|
+
// error instead of a silently duplicated screen; recover by refreshing
|
|
860
|
+
// the real filename list from the DB, picking a fresh unique name, and
|
|
861
|
+
// retrying — bounded so a persistent non-race failure still surfaces.
|
|
862
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
863
|
+
try {
|
|
864
|
+
await db.insert(schema.designFiles).values({
|
|
865
|
+
id: fileId,
|
|
866
|
+
designId,
|
|
867
|
+
filename,
|
|
868
|
+
fileType: "html",
|
|
869
|
+
content,
|
|
870
|
+
createdAt: now,
|
|
871
|
+
updatedAt: now,
|
|
872
|
+
});
|
|
873
|
+
break;
|
|
874
|
+
} catch (err) {
|
|
875
|
+
if (
|
|
876
|
+
!isUniqueConstraintViolation(err) ||
|
|
877
|
+
attempt >= MAX_FILENAME_INSERT_ATTEMPTS
|
|
878
|
+
) {
|
|
879
|
+
throw err;
|
|
880
|
+
}
|
|
881
|
+
const freshFiles = await db
|
|
882
|
+
.select({ filename: schema.designFiles.filename })
|
|
883
|
+
.from(schema.designFiles)
|
|
884
|
+
.where(eq(schema.designFiles.designId, designId));
|
|
885
|
+
for (const file of freshFiles) usedFilenames.add(file.filename);
|
|
886
|
+
filename = uniqueFilename(`variant-${slug}.html`, usedFilenames);
|
|
887
|
+
fileId = nanoid();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
503
890
|
await seedFromText(fileId, content);
|
|
504
891
|
|
|
505
892
|
screens.push({
|
|
@@ -542,6 +929,13 @@ export default defineAction({
|
|
|
542
929
|
id: variantSetId,
|
|
543
930
|
prompt: prompt ?? "Pick a direction",
|
|
544
931
|
createdAt: now,
|
|
932
|
+
// Immutable record of how many screens this set started with, so a
|
|
933
|
+
// later present-design-variants call can prove no delete-file call
|
|
934
|
+
// has ever resolved against any of this set's screens — see
|
|
935
|
+
// markSupersededVariantSets / isUntouchedVariantSet above.
|
|
936
|
+
// delete-file.ts's pruneDesignVariantSets only ever shortens or
|
|
937
|
+
// removes `screens`; it never rewrites this field.
|
|
938
|
+
screenCount: screens.length,
|
|
545
939
|
screens: screens.map((screen) => ({
|
|
546
940
|
id: screen.id,
|
|
547
941
|
variantId: screen.variantId,
|
|
@@ -638,6 +1032,8 @@ export default defineAction({
|
|
|
638
1032
|
screens,
|
|
639
1033
|
path: `/design/${encodeURIComponent(designId)}?view=overview`,
|
|
640
1034
|
embed: true,
|
|
1035
|
+
cleanedUpPreviousVariantScreens: variantSetCleanup.removedFileIds.length,
|
|
1036
|
+
deletedSupersededSetIds: variantSetCleanup.removedSetIds,
|
|
641
1037
|
fallbackInstructions: FALLBACK_INSTRUCTIONS,
|
|
642
1038
|
nextRequiredAction:
|
|
643
1039
|
'Wait for the user to pick a screen in chat. Then delete each unchosen variant screen with delete-file at most once, call get-design-snapshot exactly once with fileId for the chosen screen, and call edit-design with that same fileId in a bounded pass. Use mode "replace-file" to replace the representative direction screen with a complete but compact requested app/product UI in the chosen visual style. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the full feature list is too large for one reliable edit. Do not leave a direction board, variant brief, or summary card as the final result. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save.',
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
applySourceEdit,
|
|
11
11
|
previewSourceDiff,
|
|
12
|
+
sourceContentHash,
|
|
12
13
|
} from "../shared/source-workspace.js";
|
|
13
14
|
|
|
14
15
|
const sourceEditSchema = z.discriminatedUnion("kind", [
|
|
@@ -77,7 +78,19 @@ export default defineAction({
|
|
|
77
78
|
conflict:
|
|
78
79
|
workspace.sourceType === "inline" ? null : "unsupported-source-backend",
|
|
79
80
|
currentVersionHash: live.versionHash,
|
|
80
|
-
|
|
81
|
+
// The hash the file WOULD have after this edit is applied. Do NOT pass
|
|
82
|
+
// this as apply-source-edit's expectedVersionHash — that CAS compares
|
|
83
|
+
// against the CURRENT pre-edit hash, so the post-edit hash would always
|
|
84
|
+
// fail; pass `currentVersionHash` (above) instead to close the
|
|
85
|
+
// preview→apply race, exactly as the code workbench's inline-provider
|
|
86
|
+
// does. `nextVersionHash` is for detecting that the preview's target
|
|
87
|
+
// content changed (compare a later preview/read against it) and for
|
|
88
|
+
// optimistic client bookkeeping of the expected post-apply hash.
|
|
89
|
+
// When nothing changed, the content is still `live.content`, so there
|
|
90
|
+
// is no new hash to report.
|
|
91
|
+
nextVersionHash: next.changed
|
|
92
|
+
? sourceContentHash(next.content)
|
|
93
|
+
: undefined,
|
|
81
94
|
editsApplied: next.editsApplied,
|
|
82
95
|
diff: previewSourceDiff(live.content, next.content),
|
|
83
96
|
};
|
|
@@ -66,8 +66,20 @@ function extractSelector(tagHtml: string, tagName: string): string | undefined {
|
|
|
66
66
|
if (idMatch) return `#${idMatch[1] ?? idMatch[2]}`;
|
|
67
67
|
const classMatch = tagHtml.match(/\bclass\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
|
|
68
68
|
if (classMatch) {
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
const classNames = (classMatch[1] ?? classMatch[2] ?? "")
|
|
70
|
+
.trim()
|
|
71
|
+
.split(/\s+/)
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
if (classNames.length > 0) {
|
|
74
|
+
// Include every class, not just the first: Tailwind screens commonly
|
|
75
|
+
// have several sibling elements sharing one common utility class (e.g.
|
|
76
|
+
// two buttons both carrying "h-4" but differing in every other class).
|
|
77
|
+
// A first-class-only selector is ambiguous and apply-a11y-fix's
|
|
78
|
+
// deterministic edit engine (which treats every dot segment after the
|
|
79
|
+
// tag as a required class, see shared/code-layer.ts) would match the
|
|
80
|
+
// wrong element among several sharing only that one class.
|
|
81
|
+
return `${tagName.toLowerCase()}.${classNames.join(".")}`;
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
return undefined;
|
|
73
85
|
}
|
|
@@ -104,6 +116,13 @@ function checkMissingAlt(html: string): A11yFinding[] {
|
|
|
104
116
|
return findings;
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
/** Escape regex metacharacters so untrusted HTML attribute values (e.g. an
|
|
120
|
+
* author-supplied `id`) can be safely interpolated into a `RegExp` source
|
|
121
|
+
* string instead of crashing the audit or matching unintended text. */
|
|
122
|
+
function escapeRegExp(value: string): string {
|
|
123
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
/** Check form inputs without an associated <label> or aria-label/aria-labelledby. */
|
|
108
127
|
function checkMissingLabels(html: string): A11yFinding[] {
|
|
109
128
|
const findings: A11yFinding[] = [];
|
|
@@ -126,7 +145,10 @@ function checkMissingLabels(html: string): A11yFinding[] {
|
|
|
126
145
|
const idMatch = tag.match(/\bid\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
|
|
127
146
|
const inputId = idMatch?.[1] ?? idMatch?.[2];
|
|
128
147
|
const hasExplicitLabel = inputId
|
|
129
|
-
? new RegExp(
|
|
148
|
+
? new RegExp(
|
|
149
|
+
`for\\s*=\\s*(?:"${escapeRegExp(inputId)}"|'${escapeRegExp(inputId)}')`,
|
|
150
|
+
"i",
|
|
151
|
+
).test(html)
|
|
130
152
|
: false;
|
|
131
153
|
const hasImplicitLabel = isWrappedByLabel(html, inputStart, inputEnd);
|
|
132
154
|
|
|
@@ -87,6 +87,16 @@ export default defineAction({
|
|
|
87
87
|
.describe(
|
|
88
88
|
"1-8 focused design-intake questions. Prefer 4-6 concise questions " +
|
|
89
89
|
"with useful choices, Other enabled, and Decide for me where appropriate.",
|
|
90
|
+
)
|
|
91
|
+
.refine(
|
|
92
|
+
(questions) =>
|
|
93
|
+
new Set(questions.map((question) => question.id)).size ===
|
|
94
|
+
questions.length,
|
|
95
|
+
{
|
|
96
|
+
message:
|
|
97
|
+
"Question ids must be unique — duplicate ids share one answer " +
|
|
98
|
+
"slot in the form, silently discarding one question's answer.",
|
|
99
|
+
},
|
|
90
100
|
),
|
|
91
101
|
}),
|
|
92
102
|
mcpApp: {
|