@agent-native/core 0.80.11 → 0.81.1
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 +53 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AgentPanel.tsx +33 -1
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +18 -3
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.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/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.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/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,31 +6,22 @@
|
|
|
6
6
|
* 2. Persist the `motion_timeline` row (insert or update).
|
|
7
7
|
* 3. Compile the tracks into deterministic CSS.
|
|
8
8
|
* 4. Inject/replace the managed `<style data-agent-native-motion>` block inside
|
|
9
|
-
* the design's HTML content
|
|
10
|
-
* (Yjs/collab + SQL, via agentEnterDocument / applyText / seedFromText).
|
|
9
|
+
* the design's durable HTML content.
|
|
11
10
|
* 5. Update `compiledHash` on the row to guard against drift.
|
|
12
11
|
* 6. Return a diff summary (bytes before/after, track count, hash).
|
|
13
12
|
*
|
|
14
13
|
* Never writes unless all steps succeed. Scrubbing/preview is handled by the
|
|
15
14
|
* separate `motion-preview` postMessage path on the frontend — this action is
|
|
16
|
-
* the
|
|
15
|
+
* the durable autosave/persist path for edited timelines.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
import { defineAction } from "@agent-native/core";
|
|
20
|
-
import {
|
|
21
|
-
agentEnterDocument,
|
|
22
|
-
agentLeaveDocument,
|
|
23
|
-
applyText,
|
|
24
|
-
getText,
|
|
25
|
-
hasCollabState,
|
|
26
|
-
seedFromText,
|
|
27
|
-
} from "@agent-native/core/collab";
|
|
28
19
|
import {
|
|
29
20
|
getRequestOrgId,
|
|
30
21
|
getRequestUserEmail,
|
|
31
22
|
} from "@agent-native/core/server/request-context";
|
|
32
23
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
33
|
-
import { and, eq } from "drizzle-orm";
|
|
24
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
34
25
|
import { nanoid } from "nanoid";
|
|
35
26
|
import { z } from "zod";
|
|
36
27
|
|
|
@@ -121,21 +112,6 @@ function injectMotionStyle(html: string, css: string): string {
|
|
|
121
112
|
return block + "\n" + html;
|
|
122
113
|
}
|
|
123
114
|
|
|
124
|
-
async function liveFileContent(
|
|
125
|
-
fileId: string,
|
|
126
|
-
storedContent: string,
|
|
127
|
-
): Promise<string> {
|
|
128
|
-
try {
|
|
129
|
-
if (await hasCollabState(fileId)) {
|
|
130
|
-
const live = await getText(fileId, "content");
|
|
131
|
-
if (typeof live === "string") return live;
|
|
132
|
-
}
|
|
133
|
-
} catch {
|
|
134
|
-
// Best-effort; SQL is the fallback.
|
|
135
|
-
}
|
|
136
|
-
return storedContent;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
115
|
async function persistFileContent(
|
|
140
116
|
fileId: string,
|
|
141
117
|
designId: string,
|
|
@@ -143,29 +119,21 @@ async function persistFileContent(
|
|
|
143
119
|
now: string,
|
|
144
120
|
): Promise<void> {
|
|
145
121
|
const db = getDb();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// invoked; this only touches the addressed design row's updatedAt.
|
|
162
|
-
await db
|
|
163
|
-
.update(schema.designs)
|
|
164
|
-
.set({ updatedAt: now })
|
|
165
|
-
.where(eq(schema.designs.id, designId));
|
|
166
|
-
} finally {
|
|
167
|
-
agentLeaveDocument(fileId);
|
|
168
|
-
}
|
|
122
|
+
await db
|
|
123
|
+
.update(schema.designFiles)
|
|
124
|
+
.set({ content, updatedAt: now })
|
|
125
|
+
.where(eq(schema.designFiles.id, fileId));
|
|
126
|
+
|
|
127
|
+
// Keep SQL as the source of truth for this atomic write. The editor adopts
|
|
128
|
+
// the returned HTML content without re-saving it; applying the whole document
|
|
129
|
+
// through an existing collab text snapshot can merge against stale iframe
|
|
130
|
+
// state and duplicate the managed motion stylesheet.
|
|
131
|
+
// guard:allow-unscoped — editor access on this design is asserted in run()
|
|
132
|
+
// before this helper is invoked; this only bumps the addressed design row.
|
|
133
|
+
await db
|
|
134
|
+
.update(schema.designs)
|
|
135
|
+
.set({ updatedAt: now })
|
|
136
|
+
.where(eq(schema.designs.id, designId));
|
|
169
137
|
}
|
|
170
138
|
|
|
171
139
|
// ─── Action ───────────────────────────────────────────────────────────────────
|
|
@@ -176,7 +144,7 @@ export default defineAction({
|
|
|
176
144
|
"Persists the motion_timeline row, compiles tracks to CSS, injects the " +
|
|
177
145
|
"managed <style data-agent-native-motion> block into the design's HTML, " +
|
|
178
146
|
"and updates compiledHash — all in one atomic step. " +
|
|
179
|
-
"This is the
|
|
147
|
+
"This is the durable timeline persist path; preview/scrubbing uses the " +
|
|
180
148
|
"motion-preview postMessage bridge, NOT this action.",
|
|
181
149
|
schema: z.object({
|
|
182
150
|
designId: z.string().describe("Design project ID."),
|
|
@@ -231,6 +199,14 @@ export default defineAction({
|
|
|
231
199
|
.optional()
|
|
232
200
|
.default(false)
|
|
233
201
|
.describe("Include the full patched HTML in the response (large)."),
|
|
202
|
+
currentContent: z
|
|
203
|
+
.string()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe(
|
|
206
|
+
"Current open editor HTML for the target file. When supplied, the " +
|
|
207
|
+
"managed motion CSS is patched into this content instead of the " +
|
|
208
|
+
"last SQL snapshot so in-flight local edits are preserved.",
|
|
209
|
+
),
|
|
234
210
|
}),
|
|
235
211
|
run: async ({
|
|
236
212
|
designId,
|
|
@@ -241,6 +217,7 @@ export default defineAction({
|
|
|
241
217
|
durationMs,
|
|
242
218
|
defaultEase,
|
|
243
219
|
includeContent,
|
|
220
|
+
currentContent: currentContentInput,
|
|
244
221
|
}) => {
|
|
245
222
|
await assertAccess("design", designId, "editor");
|
|
246
223
|
|
|
@@ -282,7 +259,11 @@ export default defineAction({
|
|
|
282
259
|
}
|
|
283
260
|
|
|
284
261
|
const fileId = file.id;
|
|
285
|
-
const
|
|
262
|
+
const resolvedSourceRef = sourceRef ?? fileId;
|
|
263
|
+
const currentContent =
|
|
264
|
+
currentContentInput !== undefined
|
|
265
|
+
? currentContentInput
|
|
266
|
+
: (file.content ?? "");
|
|
286
267
|
|
|
287
268
|
// ── 2. Compile tracks → CSS ─────────────────────────────────────────────
|
|
288
269
|
const typedTracks = tracks as MotionTrack[];
|
|
@@ -304,7 +285,7 @@ export default defineAction({
|
|
|
304
285
|
const { css, hash } = compile({
|
|
305
286
|
id: timelineId ?? "",
|
|
306
287
|
designId,
|
|
307
|
-
sourceRef:
|
|
288
|
+
sourceRef: resolvedSourceRef,
|
|
308
289
|
filePath: null,
|
|
309
290
|
tracks: typedTracks,
|
|
310
291
|
durationMs,
|
|
@@ -323,7 +304,7 @@ export default defineAction({
|
|
|
323
304
|
// Resolve everything that can fail (existence + ownership) BEFORE touching
|
|
324
305
|
// content, so we never persist HTML for a row that can't be written.
|
|
325
306
|
const tracksJson = JSON.stringify(typedTracks);
|
|
326
|
-
|
|
307
|
+
let existingTimelineId = timelineId;
|
|
327
308
|
|
|
328
309
|
let insertOwnerEmail: string | null = null;
|
|
329
310
|
let insertOrgId: string | null = null;
|
|
@@ -347,21 +328,37 @@ export default defineAction({
|
|
|
347
328
|
);
|
|
348
329
|
}
|
|
349
330
|
} else {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
331
|
+
const [existingForSource] = await db
|
|
332
|
+
.select({ id: schema.motionTimeline.id })
|
|
333
|
+
.from(schema.motionTimeline)
|
|
334
|
+
.where(
|
|
335
|
+
and(
|
|
336
|
+
eq(schema.motionTimeline.designId, designId),
|
|
337
|
+
eq(schema.motionTimeline.sourceRef, resolvedSourceRef),
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
.orderBy(desc(schema.motionTimeline.updatedAt))
|
|
341
|
+
.limit(1);
|
|
342
|
+
|
|
343
|
+
if (existingForSource) {
|
|
344
|
+
existingTimelineId = existingForSource.id;
|
|
345
|
+
} else {
|
|
346
|
+
// Insert new row — derive ownership from the request context (same
|
|
347
|
+
// pattern as create-design-state and other create actions).
|
|
348
|
+
insertOwnerEmail = getRequestUserEmail() ?? null;
|
|
349
|
+
if (!insertOwnerEmail) throw new Error("no authenticated user");
|
|
350
|
+
insertOrgId = getRequestOrgId() ?? null;
|
|
351
|
+
}
|
|
355
352
|
}
|
|
356
353
|
|
|
354
|
+
const resolvedTimelineId = existingTimelineId ?? nanoid();
|
|
355
|
+
|
|
357
356
|
// ── 5. Persist the motion_timeline row FIRST (atomic SQL portion) ───────
|
|
358
357
|
// The timeline row is written before the HTML so that a failure in the
|
|
359
|
-
// HTML
|
|
360
|
-
// corresponding row.
|
|
361
|
-
// guarantee: if the row write failed after the HTML write, the managed
|
|
362
|
-
// <style> block would be permanently out of sync with the DB state.
|
|
358
|
+
// HTML write step cannot leave the design content mutated without a
|
|
359
|
+
// corresponding row.
|
|
363
360
|
await db.transaction(async (tx) => {
|
|
364
|
-
if (
|
|
361
|
+
if (existingTimelineId) {
|
|
365
362
|
await tx
|
|
366
363
|
.update(schema.motionTimeline)
|
|
367
364
|
.set({
|
|
@@ -369,15 +366,15 @@ export default defineAction({
|
|
|
369
366
|
durationMs,
|
|
370
367
|
defaultEase,
|
|
371
368
|
compiledHash: hash,
|
|
372
|
-
sourceRef:
|
|
369
|
+
sourceRef: resolvedSourceRef,
|
|
373
370
|
updatedAt: now,
|
|
374
371
|
})
|
|
375
|
-
.where(eq(schema.motionTimeline.id,
|
|
372
|
+
.where(eq(schema.motionTimeline.id, existingTimelineId));
|
|
376
373
|
} else {
|
|
377
374
|
await tx.insert(schema.motionTimeline).values({
|
|
378
375
|
id: resolvedTimelineId,
|
|
379
376
|
designId,
|
|
380
|
-
sourceRef:
|
|
377
|
+
sourceRef: resolvedSourceRef,
|
|
381
378
|
filePath: null,
|
|
382
379
|
tracks: tracksJson,
|
|
383
380
|
durationMs,
|
|
@@ -391,8 +388,8 @@ export default defineAction({
|
|
|
391
388
|
}
|
|
392
389
|
});
|
|
393
390
|
|
|
394
|
-
// ── 6. Persist the patched HTML content SECOND
|
|
395
|
-
// Written after the row so a
|
|
391
|
+
// ── 6. Persist the patched HTML content SECOND ─────────────────────────
|
|
392
|
+
// Written after the row so a SQL failure here leaves the timeline row
|
|
396
393
|
// accurate (correct tracks + hash) and the stale HTML can be recompiled on
|
|
397
394
|
// the next apply-motion-edit call via compiledHash drift detection.
|
|
398
395
|
await persistFileContent(fileId, designId, patchedContent, now);
|
|
@@ -401,6 +398,7 @@ export default defineAction({
|
|
|
401
398
|
timelineId: resolvedTimelineId,
|
|
402
399
|
designId,
|
|
403
400
|
fileId,
|
|
401
|
+
sourceRef: resolvedSourceRef,
|
|
404
402
|
trackCount: typedTracks.length,
|
|
405
403
|
compiledHash: hash,
|
|
406
404
|
bytesBefore,
|
|
@@ -19,24 +19,15 @@
|
|
|
19
19
|
* - The descriptor is validated against the preset manifest first, so callers
|
|
20
20
|
* get clear errors before any round-trip.
|
|
21
21
|
*
|
|
22
|
-
* The write
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the resolved node — no structural inserts, no new owned rows.
|
|
22
|
+
* The write persists durable SQL content directly. The edit is a single
|
|
23
|
+
* inline-style `style.background` change on the resolved node — no structural
|
|
24
|
+
* inserts, no new owned rows.
|
|
26
25
|
*
|
|
27
26
|
* Plan reference: DESIGN-STUDIO-PLAN.md §6.7 + §7 (shader fill apply).
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
29
|
import { defineAction } from "@agent-native/core";
|
|
31
|
-
import {
|
|
32
|
-
agentEnterDocument,
|
|
33
|
-
agentLeaveDocument,
|
|
34
|
-
agentUpdateSelection,
|
|
35
|
-
applyText,
|
|
36
|
-
getText,
|
|
37
|
-
hasCollabState,
|
|
38
|
-
seedFromText,
|
|
39
|
-
} from "@agent-native/core/collab";
|
|
30
|
+
import { agentUpdateSelection } from "@agent-native/core/collab";
|
|
40
31
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
41
32
|
import { and, eq } from "drizzle-orm";
|
|
42
33
|
import { z } from "zod";
|
|
@@ -65,6 +56,15 @@ const PRESET_NAMES = Object.keys(SHADER_PRESET_MAP) as [
|
|
|
65
56
|
ShaderPresetName,
|
|
66
57
|
...ShaderPresetName[],
|
|
67
58
|
];
|
|
59
|
+
const REVISION_CONFLICT_MESSAGE =
|
|
60
|
+
"This file changed since this shader fill was previewed. Refresh the editor and try again.";
|
|
61
|
+
|
|
62
|
+
class ShaderFillRevisionConflictError extends Error {
|
|
63
|
+
constructor() {
|
|
64
|
+
super(REVISION_CONFLICT_MESSAGE);
|
|
65
|
+
this.name = "ShaderFillRevisionConflictError";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
68
|
|
|
69
69
|
const descriptorSchema = z.object({
|
|
70
70
|
preset: z
|
|
@@ -101,6 +101,16 @@ const sourceSchema = z
|
|
|
101
101
|
fileId: z.string().optional(),
|
|
102
102
|
filename: z.string().optional(),
|
|
103
103
|
revision: z.string().optional(),
|
|
104
|
+
currentContent: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe(
|
|
108
|
+
"Current open editor HTML for the target file. When supplied, the " +
|
|
109
|
+
"shader background is patched into this content instead of the " +
|
|
110
|
+
"last SQL snapshot so in-flight local edits are preserved. " +
|
|
111
|
+
"source.revision must also be supplied as the file updatedAt value " +
|
|
112
|
+
"that currentContent was based on.",
|
|
113
|
+
),
|
|
104
114
|
})
|
|
105
115
|
.describe(
|
|
106
116
|
"Design source. Only kind=design-file (HTML) is persisted. Provide " +
|
|
@@ -132,24 +142,11 @@ interface ResolvedDesignFile {
|
|
|
132
142
|
designId: string;
|
|
133
143
|
filename: string;
|
|
134
144
|
content: string;
|
|
145
|
+
updatedAt: string | null;
|
|
146
|
+
expectedUpdatedAt?: string;
|
|
135
147
|
codeLayerSource: CodeLayerSource;
|
|
136
148
|
}
|
|
137
149
|
|
|
138
|
-
async function liveContent(
|
|
139
|
-
fileId: string,
|
|
140
|
-
storedContent: string,
|
|
141
|
-
): Promise<string> {
|
|
142
|
-
try {
|
|
143
|
-
if (await hasCollabState(fileId)) {
|
|
144
|
-
const live = await getText(fileId, "content");
|
|
145
|
-
if (typeof live === "string") return live;
|
|
146
|
-
}
|
|
147
|
-
} catch {
|
|
148
|
-
// Collab reads are best-effort; SQL content remains the fallback.
|
|
149
|
-
}
|
|
150
|
-
return storedContent;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
150
|
/**
|
|
154
151
|
* Resolve the target HTML design file with an access-scoped read, then assert
|
|
155
152
|
* editor access. Mirrors `resolveEditableDesignFile` in apply-visual-edit.ts so
|
|
@@ -160,6 +157,7 @@ async function resolveEditableDesignFile(source: {
|
|
|
160
157
|
fileId?: string;
|
|
161
158
|
filename?: string;
|
|
162
159
|
revision?: string;
|
|
160
|
+
currentContent?: string;
|
|
163
161
|
}): Promise<ResolvedDesignFile> {
|
|
164
162
|
if (!source.fileId && !source.designId) {
|
|
165
163
|
throw new Error(
|
|
@@ -187,6 +185,7 @@ async function resolveEditableDesignFile(source: {
|
|
|
187
185
|
filename: schema.designFiles.filename,
|
|
188
186
|
fileType: schema.designFiles.fileType,
|
|
189
187
|
content: schema.designFiles.content,
|
|
188
|
+
updatedAt: schema.designFiles.updatedAt,
|
|
190
189
|
})
|
|
191
190
|
.from(schema.designFiles)
|
|
192
191
|
.innerJoin(
|
|
@@ -217,11 +216,31 @@ async function resolveEditableDesignFile(source: {
|
|
|
217
216
|
|
|
218
217
|
await assertAccess("design", file.designId, "editor");
|
|
219
218
|
|
|
219
|
+
if (source.currentContent !== undefined && !source.revision) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
"source.revision is required when source.currentContent is provided.",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
source.currentContent !== undefined &&
|
|
226
|
+
source.revision &&
|
|
227
|
+
file.updatedAt &&
|
|
228
|
+
source.revision !== file.updatedAt
|
|
229
|
+
) {
|
|
230
|
+
throw new ShaderFillRevisionConflictError();
|
|
231
|
+
}
|
|
232
|
+
|
|
220
233
|
return {
|
|
221
234
|
id: file.id,
|
|
222
235
|
designId: file.designId,
|
|
223
236
|
filename: file.filename,
|
|
224
|
-
|
|
237
|
+
updatedAt: file.updatedAt,
|
|
238
|
+
expectedUpdatedAt:
|
|
239
|
+
source.currentContent !== undefined ? source.revision : undefined,
|
|
240
|
+
content:
|
|
241
|
+
source.currentContent !== undefined
|
|
242
|
+
? source.currentContent
|
|
243
|
+
: (file.content ?? ""),
|
|
225
244
|
codeLayerSource: {
|
|
226
245
|
kind: "design-file",
|
|
227
246
|
designId: file.designId,
|
|
@@ -236,35 +255,65 @@ async function persistDesignFileEdit(file: {
|
|
|
236
255
|
id: string;
|
|
237
256
|
designId: string;
|
|
238
257
|
content: string;
|
|
239
|
-
|
|
258
|
+
expectedUpdatedAt?: string;
|
|
259
|
+
}): Promise<string> {
|
|
240
260
|
await assertAccess("design", file.designId, "editor");
|
|
241
261
|
|
|
242
262
|
const db = getDb();
|
|
243
263
|
const now = new Date().toISOString();
|
|
244
264
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
.
|
|
249
|
-
.
|
|
250
|
-
.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
if (file.expectedUpdatedAt) {
|
|
266
|
+
const [latest] = await db
|
|
267
|
+
.select({ updatedAt: schema.designFiles.updatedAt })
|
|
268
|
+
.from(schema.designFiles)
|
|
269
|
+
.where(eq(schema.designFiles.id, file.id))
|
|
270
|
+
.limit(1);
|
|
271
|
+
if (!latest) {
|
|
272
|
+
throw new Error("Design HTML file not found.");
|
|
273
|
+
}
|
|
274
|
+
if (latest.updatedAt && latest.updatedAt !== file.expectedUpdatedAt) {
|
|
275
|
+
throw new ShaderFillRevisionConflictError();
|
|
256
276
|
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await db
|
|
280
|
+
.update(schema.designFiles)
|
|
281
|
+
.set({ content: file.content, updatedAt: now })
|
|
282
|
+
.where(
|
|
283
|
+
file.expectedUpdatedAt
|
|
284
|
+
? and(
|
|
285
|
+
eq(schema.designFiles.id, file.id),
|
|
286
|
+
eq(schema.designFiles.updatedAt, file.expectedUpdatedAt),
|
|
287
|
+
)
|
|
288
|
+
: eq(schema.designFiles.id, file.id),
|
|
289
|
+
);
|
|
257
290
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
.
|
|
265
|
-
|
|
266
|
-
|
|
291
|
+
if (file.expectedUpdatedAt) {
|
|
292
|
+
const [saved] = await db
|
|
293
|
+
.select({
|
|
294
|
+
content: schema.designFiles.content,
|
|
295
|
+
updatedAt: schema.designFiles.updatedAt,
|
|
296
|
+
})
|
|
297
|
+
.from(schema.designFiles)
|
|
298
|
+
.where(eq(schema.designFiles.id, file.id))
|
|
299
|
+
.limit(1);
|
|
300
|
+
if (!saved || saved.updatedAt !== now || saved.content !== file.content) {
|
|
301
|
+
throw new ShaderFillRevisionConflictError();
|
|
302
|
+
}
|
|
267
303
|
}
|
|
304
|
+
|
|
305
|
+
// Keep SQL as the source of truth for this guarded server write. The editor
|
|
306
|
+
// already has the live shader preview applied; feeding a full HTML document
|
|
307
|
+
// back through an existing collab text snapshot can merge against stale
|
|
308
|
+
// iframe state and corrupt the saved source.
|
|
309
|
+
// guard:allow-unscoped — editor access on this design is asserted above
|
|
310
|
+
// before this helper is invoked; this only bumps the addressed design row.
|
|
311
|
+
await db
|
|
312
|
+
.update(schema.designs)
|
|
313
|
+
.set({ updatedAt: now })
|
|
314
|
+
.where(eq(schema.designs.id, file.designId));
|
|
315
|
+
|
|
316
|
+
return now;
|
|
268
317
|
}
|
|
269
318
|
|
|
270
319
|
// ─── Action ──────────────────────────────────────────────────────────────────
|
|
@@ -350,7 +399,25 @@ snippet (WebGL canvas / JSX component), call apply-shader.
|
|
|
350
399
|
};
|
|
351
400
|
}
|
|
352
401
|
|
|
353
|
-
|
|
402
|
+
let file: ResolvedDesignFile;
|
|
403
|
+
try {
|
|
404
|
+
file = await resolveEditableDesignFile(source);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (error instanceof ShaderFillRevisionConflictError) {
|
|
407
|
+
return {
|
|
408
|
+
ok: false,
|
|
409
|
+
persisted: false,
|
|
410
|
+
conflict: true,
|
|
411
|
+
descriptor,
|
|
412
|
+
background,
|
|
413
|
+
fallbackCss,
|
|
414
|
+
colors,
|
|
415
|
+
error: error.message,
|
|
416
|
+
note: error.message,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
354
421
|
|
|
355
422
|
const intent: StyleEditIntent = {
|
|
356
423
|
kind: "style",
|
|
@@ -374,13 +441,36 @@ snippet (WebGL canvas / JSX component), call apply-shader.
|
|
|
374
441
|
|
|
375
442
|
const changed =
|
|
376
443
|
patch.result.status === "applied" && patch.result.changed === true;
|
|
444
|
+
let updatedAt: string | undefined;
|
|
377
445
|
|
|
378
446
|
if (changed) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
447
|
+
try {
|
|
448
|
+
updatedAt = await persistDesignFileEdit({
|
|
449
|
+
id: file.id,
|
|
450
|
+
designId: file.designId,
|
|
451
|
+
content: patch.content,
|
|
452
|
+
expectedUpdatedAt: file.expectedUpdatedAt,
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (error instanceof ShaderFillRevisionConflictError) {
|
|
456
|
+
return {
|
|
457
|
+
ok: false,
|
|
458
|
+
persisted: false,
|
|
459
|
+
conflict: true,
|
|
460
|
+
descriptor,
|
|
461
|
+
background,
|
|
462
|
+
fallbackCss,
|
|
463
|
+
colors,
|
|
464
|
+
designId: file.designId,
|
|
465
|
+
fileId: file.id,
|
|
466
|
+
filename: file.filename,
|
|
467
|
+
result: patch.result,
|
|
468
|
+
error: error.message,
|
|
469
|
+
note: error.message,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
throw error;
|
|
473
|
+
}
|
|
384
474
|
}
|
|
385
475
|
|
|
386
476
|
return {
|
|
@@ -393,7 +483,9 @@ snippet (WebGL canvas / JSX component), call apply-shader.
|
|
|
393
483
|
designId: file.designId,
|
|
394
484
|
fileId: file.id,
|
|
395
485
|
filename: file.filename,
|
|
486
|
+
updatedAt,
|
|
396
487
|
result: patch.result,
|
|
488
|
+
patchedContent: patch.content,
|
|
397
489
|
bytesBefore: file.content.length,
|
|
398
490
|
bytesAfter: patch.content.length,
|
|
399
491
|
note: changed
|
|
@@ -12,6 +12,11 @@ import { getDb, schema } from "../server/db/index.js";
|
|
|
12
12
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
13
13
|
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
14
14
|
import type { DesignSourceCapabilities } from "../shared/design-source-capabilities.js";
|
|
15
|
+
import {
|
|
16
|
+
normalizeDesignSourceType,
|
|
17
|
+
resolveDescriptorCapabilities,
|
|
18
|
+
type DesignSourceDescriptor,
|
|
19
|
+
} from "../shared/source-mode.js";
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* Resolve the capabilities for the design's source type.
|
|
@@ -19,17 +24,35 @@ import type { DesignSourceCapabilities } from "../shared/design-source-capabilit
|
|
|
19
24
|
*/
|
|
20
25
|
function resolveCapabilities(
|
|
21
26
|
designData: string | null,
|
|
22
|
-
): DesignSourceCapabilities
|
|
23
|
-
if (!designData)
|
|
27
|
+
): DesignSourceCapabilities {
|
|
28
|
+
if (!designData) {
|
|
29
|
+
return resolveDescriptorCapabilities({ sourceType: "inline" });
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
try {
|
|
25
33
|
const parsed = JSON.parse(designData) as Record<string, unknown>;
|
|
26
34
|
if (parsed.capabilities && typeof parsed.capabilities === "object") {
|
|
27
35
|
return parsed.capabilities as DesignSourceCapabilities;
|
|
28
36
|
}
|
|
37
|
+
const sourceType = normalizeDesignSourceType(parsed.sourceType) ?? "inline";
|
|
38
|
+
const descriptor: DesignSourceDescriptor =
|
|
39
|
+
sourceType === "fusion"
|
|
40
|
+
? { sourceType, connected: parsed.connected === true }
|
|
41
|
+
: sourceType === "localhost"
|
|
42
|
+
? {
|
|
43
|
+
sourceType,
|
|
44
|
+
connectionId:
|
|
45
|
+
typeof parsed.connectionId === "string"
|
|
46
|
+
? parsed.connectionId
|
|
47
|
+
: "unknown",
|
|
48
|
+
}
|
|
49
|
+
: { sourceType };
|
|
50
|
+
return resolveDescriptorCapabilities(descriptor);
|
|
29
51
|
} catch {
|
|
30
52
|
// ignore parse errors
|
|
31
53
|
}
|
|
32
|
-
|
|
54
|
+
|
|
55
|
+
return resolveDescriptorCapabilities({ sourceType: "inline" });
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
/**
|
|
@@ -167,7 +190,7 @@ export default defineAction({
|
|
|
167
190
|
}
|
|
168
191
|
|
|
169
192
|
const caps = resolveCapabilities(design.data);
|
|
170
|
-
if (
|
|
193
|
+
if (!hasCapability(caps, "captureState")) {
|
|
171
194
|
throw new Error(
|
|
172
195
|
"The design's source does not support captureState. " +
|
|
173
196
|
"Connect Builder or a localhost bridge to enable live captures.",
|