@agent-native/core 0.80.10 → 0.81.0
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/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- 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/sse-event-processor.ts +9 -0
- 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/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -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/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +233 -52
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- 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 +76 -25
- package/corpus/templates/design/actions/apply-design-state.ts +17 -4
- package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
- 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/delete-design.ts +20 -0
- 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 +21 -26
- 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 +18 -5
- 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-design-states.ts +19 -1
- 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/open-component-source.ts +7 -2
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
- package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
- 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 +74 -10
- 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 +244 -3197
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
- package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
- 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 +22 -0
- package/corpus/templates/design/app/i18n-data.ts +369 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
- 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-token-edits-now-stay-visible-in-previews-and-token-li.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/global-setup.ts +94 -2
- 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/component-model.ts +35 -0
- package/corpus/templates/design/shared/motion-compiler.ts +137 -15
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- 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/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- 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/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.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/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.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/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- 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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- 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,6 +6,18 @@ import { z } from "zod";
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
7
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
8
8
|
|
|
9
|
+
function parseJsonRecord(raw: string | null): Record<string, unknown> | null {
|
|
10
|
+
if (!raw) return null;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
13
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
14
|
+
? (parsed as Record<string, unknown>)
|
|
15
|
+
: null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
export default defineAction({
|
|
10
22
|
description:
|
|
11
23
|
"List all design states, fixtures, and captures for a design. " +
|
|
@@ -52,6 +64,8 @@ export default defineAction({
|
|
|
52
64
|
kind: schema.designState.kind,
|
|
53
65
|
breakpoint: schema.designState.breakpoint,
|
|
54
66
|
route: schema.designState.route,
|
|
67
|
+
fixtureData: schema.designState.fixtureData,
|
|
68
|
+
captureData: schema.designState.captureData,
|
|
55
69
|
previewRef: schema.designState.previewRef,
|
|
56
70
|
createdAt: schema.designState.createdAt,
|
|
57
71
|
updatedAt: schema.designState.updatedAt,
|
|
@@ -66,7 +80,11 @@ export default defineAction({
|
|
|
66
80
|
|
|
67
81
|
return {
|
|
68
82
|
count: rows.length,
|
|
69
|
-
states: rows
|
|
83
|
+
states: rows.map((row) => ({
|
|
84
|
+
...row,
|
|
85
|
+
fixtureData: parseJsonRecord(row.fixtureData),
|
|
86
|
+
captureData: parseJsonRecord(row.captureData),
|
|
87
|
+
})),
|
|
70
88
|
};
|
|
71
89
|
},
|
|
72
90
|
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { executeProviderApiRequest } from "../server/lib/provider-api.js";
|
|
5
|
+
|
|
6
|
+
const FIGMA_FILE_KEY_RE = /^[A-Za-z0-9_-]{8,}$/;
|
|
7
|
+
|
|
8
|
+
const FigmaRenderFormatSchema = z.enum(["svg", "png"]).default("svg");
|
|
9
|
+
|
|
10
|
+
const schemaInput = z
|
|
11
|
+
.object({
|
|
12
|
+
fileUrl: z
|
|
13
|
+
.string()
|
|
14
|
+
.trim()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe(
|
|
17
|
+
"Figma file URL, for example https://www.figma.com/design/<fileKey>/...",
|
|
18
|
+
),
|
|
19
|
+
fileKey: z
|
|
20
|
+
.string()
|
|
21
|
+
.trim()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Figma file key. Used when fileUrl is omitted."),
|
|
24
|
+
query: z
|
|
25
|
+
.string()
|
|
26
|
+
.trim()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Optional case-insensitive search over names/descriptions."),
|
|
29
|
+
limit: z.coerce
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(64)
|
|
34
|
+
.default(48)
|
|
35
|
+
.describe("Maximum number of Figma library assets to return."),
|
|
36
|
+
renderFormat: FigmaRenderFormatSchema.describe(
|
|
37
|
+
"Image format for insertable render URLs.",
|
|
38
|
+
),
|
|
39
|
+
})
|
|
40
|
+
.refine((value) => value.fileUrl || value.fileKey, {
|
|
41
|
+
message: "Pass fileUrl or fileKey.",
|
|
42
|
+
path: ["fileUrl"],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type FigmaProviderEnvelope = {
|
|
46
|
+
response?: {
|
|
47
|
+
ok?: boolean;
|
|
48
|
+
status?: number;
|
|
49
|
+
statusText?: string;
|
|
50
|
+
json?: unknown;
|
|
51
|
+
text?: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type FigmaAssetKind = "component" | "component_set";
|
|
56
|
+
|
|
57
|
+
type FigmaComponentRecord = {
|
|
58
|
+
key?: unknown;
|
|
59
|
+
file_key?: unknown;
|
|
60
|
+
node_id?: unknown;
|
|
61
|
+
name?: unknown;
|
|
62
|
+
description?: unknown;
|
|
63
|
+
thumbnail_url?: unknown;
|
|
64
|
+
created_at?: unknown;
|
|
65
|
+
updated_at?: unknown;
|
|
66
|
+
containing_frame?: unknown;
|
|
67
|
+
user?: unknown;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type FigmaImageResponse = {
|
|
71
|
+
images?: Record<string, string | null | undefined>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function stringValue(value: unknown): string | null {
|
|
75
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function numberOrDefault(value: number | undefined, fallback: number): number {
|
|
79
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function parseFigmaFileKey(input: string | undefined): string | null {
|
|
83
|
+
const value = input?.trim();
|
|
84
|
+
if (!value) return null;
|
|
85
|
+
if (FIGMA_FILE_KEY_RE.test(value)) return value;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const url = new URL(value);
|
|
89
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
90
|
+
for (const segment of ["design", "file", "proto"]) {
|
|
91
|
+
const index = parts.indexOf(segment);
|
|
92
|
+
if (index >= 0) {
|
|
93
|
+
const candidate = parts[index + 1];
|
|
94
|
+
if (candidate && FIGMA_FILE_KEY_RE.test(candidate)) return candidate;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function figmaNodeUrl(
|
|
105
|
+
fileKey: string,
|
|
106
|
+
nodeId: string,
|
|
107
|
+
fileUrl?: string,
|
|
108
|
+
): string {
|
|
109
|
+
const nodeParam = nodeId.replace(/:/g, "-");
|
|
110
|
+
try {
|
|
111
|
+
const url = new URL(fileUrl || `https://www.figma.com/design/${fileKey}`);
|
|
112
|
+
url.searchParams.set("node-id", nodeParam);
|
|
113
|
+
return url.href;
|
|
114
|
+
} catch {
|
|
115
|
+
return `https://www.figma.com/design/${fileKey}?node-id=${encodeURIComponent(
|
|
116
|
+
nodeParam,
|
|
117
|
+
)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function recordSearchText(record: FigmaComponentRecord): string {
|
|
122
|
+
const frame =
|
|
123
|
+
record.containing_frame &&
|
|
124
|
+
typeof record.containing_frame === "object" &&
|
|
125
|
+
"name" in record.containing_frame
|
|
126
|
+
? stringValue((record.containing_frame as { name?: unknown }).name)
|
|
127
|
+
: null;
|
|
128
|
+
return [stringValue(record.name), stringValue(record.description), frame]
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join(" ")
|
|
131
|
+
.toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function matchesQuery(record: FigmaComponentRecord, query: string | undefined) {
|
|
135
|
+
const normalized = query?.trim().toLowerCase();
|
|
136
|
+
if (!normalized) return true;
|
|
137
|
+
return recordSearchText(record).includes(normalized);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeFigmaRecords(
|
|
141
|
+
value: unknown,
|
|
142
|
+
kind: FigmaAssetKind,
|
|
143
|
+
): FigmaComponentRecord[] {
|
|
144
|
+
if (!value || typeof value !== "object") return [];
|
|
145
|
+
const meta = (value as { meta?: unknown }).meta;
|
|
146
|
+
const key = kind === "component" ? "components" : "component_sets";
|
|
147
|
+
const records =
|
|
148
|
+
meta && typeof meta === "object"
|
|
149
|
+
? (meta as Record<string, unknown>)[key]
|
|
150
|
+
: undefined;
|
|
151
|
+
if (!Array.isArray(records)) return [];
|
|
152
|
+
return records.filter(
|
|
153
|
+
(record): record is FigmaComponentRecord =>
|
|
154
|
+
!!record && typeof record === "object",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function providerJson(envelope: unknown, label: string): unknown {
|
|
159
|
+
const response = (envelope as FigmaProviderEnvelope | null)?.response;
|
|
160
|
+
if (!response) throw new Error(`Figma ${label} response was empty.`);
|
|
161
|
+
if (response.ok === false) {
|
|
162
|
+
const detail =
|
|
163
|
+
stringValue(response.text) ||
|
|
164
|
+
response.statusText ||
|
|
165
|
+
`HTTP ${response.status ?? "error"}`;
|
|
166
|
+
throw new Error(`Figma ${label} request failed: ${detail}`);
|
|
167
|
+
}
|
|
168
|
+
return response.json;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function figmaGet(path: string, query?: Record<string, unknown>) {
|
|
172
|
+
return executeProviderApiRequest({
|
|
173
|
+
provider: "figma",
|
|
174
|
+
method: "GET",
|
|
175
|
+
path,
|
|
176
|
+
query,
|
|
177
|
+
maxBytes: 2 * 1024 * 1024,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default defineAction({
|
|
182
|
+
description:
|
|
183
|
+
"List reusable assets from a Figma file for the Design asset panel. Returns components and component sets with thumbnails, insertable render URLs, and source provenance. Requires a saved FIGMA_ACCESS_TOKEN secret.",
|
|
184
|
+
schema: schemaInput,
|
|
185
|
+
http: { method: "GET" },
|
|
186
|
+
readOnly: true,
|
|
187
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
188
|
+
run: async (args) => {
|
|
189
|
+
const fileKey =
|
|
190
|
+
parseFigmaFileKey(args.fileKey) ?? parseFigmaFileKey(args.fileUrl);
|
|
191
|
+
if (!fileKey) {
|
|
192
|
+
throw new Error("Could not find a Figma file key in the provided URL.");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const limit = numberOrDefault(args.limit, 48);
|
|
196
|
+
const [componentsEnvelope, componentSetsEnvelope] = await Promise.all([
|
|
197
|
+
figmaGet(`/files/${fileKey}/components`),
|
|
198
|
+
figmaGet(`/files/${fileKey}/component_sets`),
|
|
199
|
+
]);
|
|
200
|
+
const componentRecords = normalizeFigmaRecords(
|
|
201
|
+
providerJson(componentsEnvelope, "components"),
|
|
202
|
+
"component",
|
|
203
|
+
)
|
|
204
|
+
.filter((record) => matchesQuery(record, args.query))
|
|
205
|
+
.map((record) => ({ kind: "component" as const, record }));
|
|
206
|
+
const componentSetRecords = normalizeFigmaRecords(
|
|
207
|
+
providerJson(componentSetsEnvelope, "component sets"),
|
|
208
|
+
"component_set",
|
|
209
|
+
)
|
|
210
|
+
.filter((record) => matchesQuery(record, args.query))
|
|
211
|
+
.map((record) => ({ kind: "component_set" as const, record }));
|
|
212
|
+
|
|
213
|
+
const rawItems = [...componentRecords, ...componentSetRecords].slice(
|
|
214
|
+
0,
|
|
215
|
+
limit,
|
|
216
|
+
);
|
|
217
|
+
const renderIds = rawItems
|
|
218
|
+
.map((item) => stringValue(item.record.node_id))
|
|
219
|
+
.filter((id): id is string => !!id);
|
|
220
|
+
let rendered: Record<string, string | null | undefined> = {};
|
|
221
|
+
if (renderIds.length > 0) {
|
|
222
|
+
const imagesEnvelope = await figmaGet(`/images/${fileKey}`, {
|
|
223
|
+
ids: renderIds.join(","),
|
|
224
|
+
format: args.renderFormat,
|
|
225
|
+
svg_include_id: true,
|
|
226
|
+
});
|
|
227
|
+
const images = providerJson(
|
|
228
|
+
imagesEnvelope,
|
|
229
|
+
"images",
|
|
230
|
+
) as FigmaImageResponse;
|
|
231
|
+
rendered = images.images ?? {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const assets = rawItems
|
|
235
|
+
.map(({ kind, record }) => {
|
|
236
|
+
const nodeId = stringValue(record.node_id);
|
|
237
|
+
const key = stringValue(record.key);
|
|
238
|
+
const name = stringValue(record.name) ?? "Untitled Figma asset";
|
|
239
|
+
const description = stringValue(record.description);
|
|
240
|
+
const thumbnailUrl = stringValue(record.thumbnail_url);
|
|
241
|
+
const renderUrl = nodeId ? stringValue(rendered[nodeId]) : null;
|
|
242
|
+
const containingFrame =
|
|
243
|
+
record.containing_frame && typeof record.containing_frame === "object"
|
|
244
|
+
? {
|
|
245
|
+
name:
|
|
246
|
+
stringValue(
|
|
247
|
+
(record.containing_frame as { name?: unknown }).name,
|
|
248
|
+
) ?? null,
|
|
249
|
+
nodeId:
|
|
250
|
+
stringValue(
|
|
251
|
+
(record.containing_frame as { nodeId?: unknown }).nodeId,
|
|
252
|
+
) ??
|
|
253
|
+
stringValue(
|
|
254
|
+
(record.containing_frame as { node_id?: unknown }).node_id,
|
|
255
|
+
),
|
|
256
|
+
}
|
|
257
|
+
: null;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
id: `figma:${fileKey}:${nodeId ?? key ?? name}`,
|
|
261
|
+
kind,
|
|
262
|
+
fileKey,
|
|
263
|
+
nodeId,
|
|
264
|
+
componentKey: key,
|
|
265
|
+
name,
|
|
266
|
+
description,
|
|
267
|
+
thumbnailUrl,
|
|
268
|
+
renderUrl,
|
|
269
|
+
insertUrl: renderUrl ?? thumbnailUrl,
|
|
270
|
+
sourceUrl: nodeId
|
|
271
|
+
? figmaNodeUrl(fileKey, nodeId, args.fileUrl)
|
|
272
|
+
: null,
|
|
273
|
+
containingFrame,
|
|
274
|
+
createdAt: stringValue(record.created_at),
|
|
275
|
+
updatedAt: stringValue(record.updated_at),
|
|
276
|
+
};
|
|
277
|
+
})
|
|
278
|
+
.filter((asset) => asset.nodeId || asset.componentKey);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
source: "figma",
|
|
282
|
+
fileKey,
|
|
283
|
+
query: args.query ?? null,
|
|
284
|
+
renderFormat: args.renderFormat,
|
|
285
|
+
total: componentRecords.length + componentSetRecords.length,
|
|
286
|
+
returned: assets.length,
|
|
287
|
+
assets,
|
|
288
|
+
guidance:
|
|
289
|
+
"Use renderUrl/insertUrl with insert-asset and preserve fileKey, nodeId, componentKey, and sourceUrl for provenance. Components/component sets are reusable Figma library assets; styles and variables belong in design-system token sync.",
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* migrate-board-objects-to-file
|
|
3
|
+
*
|
|
4
|
+
* Lazy, idempotent migration that converts the legacy designs.data.boardObjects
|
|
5
|
+
* JSON blob into the new board file architecture.
|
|
6
|
+
*
|
|
7
|
+
* ## What it does
|
|
8
|
+
*
|
|
9
|
+
* 1. Guards on boardFileId already set — returns early if the migration has
|
|
10
|
+
* already run (idempotent).
|
|
11
|
+
* 2. Reads designs.data.boardObjects and converts each entry into an HTML
|
|
12
|
+
* fragment via boardObjectEntryToHtmlFragment, preserving negative left/top
|
|
13
|
+
* coordinates exactly (no clamping).
|
|
14
|
+
* 3. Inserts the fragments as direct <body> children of a new __board__.html
|
|
15
|
+
* design file (or merges into an existing one if somehow already present).
|
|
16
|
+
* 4. Stores the new file's id into designs.data.boardFileId in a transaction.
|
|
17
|
+
* 5. Nulls out designs.data.boardObjects so the old model no longer conflicts.
|
|
18
|
+
*
|
|
19
|
+
* ## Contract
|
|
20
|
+
*
|
|
21
|
+
* - Requires editor access on the design.
|
|
22
|
+
* - SCHEMA ADDITIVE ONLY — no columns are dropped or renamed; only designs.data
|
|
23
|
+
* JSON keys change.
|
|
24
|
+
* - Safe to call on designs with no board objects: creates the board file with
|
|
25
|
+
* an empty body and sets boardFileId.
|
|
26
|
+
*
|
|
27
|
+
* ## Trigger
|
|
28
|
+
*
|
|
29
|
+
* Called by DesignEditor on design open when designs.data.boardFileId is absent.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { defineAction } from "@agent-native/core";
|
|
33
|
+
import { seedFromText } from "@agent-native/core/collab";
|
|
34
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
35
|
+
import { and, eq } from "drizzle-orm";
|
|
36
|
+
import { nanoid } from "nanoid";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
|
|
39
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
40
|
+
import {
|
|
41
|
+
BOARD_FILENAME,
|
|
42
|
+
boardObjectEntryToHtmlFragment,
|
|
43
|
+
emptyBoardHtml,
|
|
44
|
+
} from "../shared/board-file.js";
|
|
45
|
+
import { parseBoardObjects } from "../shared/board-objects.js";
|
|
46
|
+
|
|
47
|
+
export default defineAction({
|
|
48
|
+
description:
|
|
49
|
+
"Migrate the legacy boardObjects blob (designs.data.boardObjects) into the " +
|
|
50
|
+
"new board file architecture. Creates a __board__.html design file, writes " +
|
|
51
|
+
"each board object as an absolute-positioned HTML element (preserving " +
|
|
52
|
+
"negative coordinates), stores boardFileId in designs.data, and nulls " +
|
|
53
|
+
"boardObjects. Idempotent — returns immediately if boardFileId is already " +
|
|
54
|
+
"set. Requires editor access on the design.",
|
|
55
|
+
schema: z.object({
|
|
56
|
+
designId: z
|
|
57
|
+
.string()
|
|
58
|
+
.describe("Design project ID to migrate board objects for."),
|
|
59
|
+
}),
|
|
60
|
+
run: async ({ designId }) => {
|
|
61
|
+
await assertAccess("design", designId, "editor");
|
|
62
|
+
|
|
63
|
+
const db = getDb();
|
|
64
|
+
|
|
65
|
+
// ── 1. Idempotency guard ──────────────────────────────────────────────
|
|
66
|
+
// Read the design data JSON to check whether boardFileId is already set.
|
|
67
|
+
// guard:allow-unscoped — assertAccess "editor" above scopes this read to
|
|
68
|
+
// the requesting user's accessible designs.
|
|
69
|
+
const [existing] = await db
|
|
70
|
+
.select({ data: schema.designs.data })
|
|
71
|
+
.from(schema.designs)
|
|
72
|
+
.where(eq(schema.designs.id, designId))
|
|
73
|
+
.limit(1);
|
|
74
|
+
|
|
75
|
+
if (!existing) {
|
|
76
|
+
throw new Error(`Design "${designId}" not found.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let parsed: Record<string, unknown>;
|
|
80
|
+
try {
|
|
81
|
+
const raw = JSON.parse(existing.data);
|
|
82
|
+
parsed =
|
|
83
|
+
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
84
|
+
? (raw as Record<string, unknown>)
|
|
85
|
+
: {};
|
|
86
|
+
} catch {
|
|
87
|
+
parsed = {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
typeof parsed["boardFileId"] === "string" &&
|
|
92
|
+
parsed["boardFileId"].length > 0
|
|
93
|
+
) {
|
|
94
|
+
return {
|
|
95
|
+
designId,
|
|
96
|
+
migrated: false,
|
|
97
|
+
boardFileId: parsed["boardFileId"] as string,
|
|
98
|
+
reason: "boardFileId already set — migration already complete.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── 2. Parse legacy board objects ────────────────────────────────────
|
|
103
|
+
const boardObjects = parseBoardObjects(parsed["boardObjects"]);
|
|
104
|
+
const entries = Object.values(boardObjects);
|
|
105
|
+
|
|
106
|
+
// ── 3. Build the board HTML ───────────────────────────────────────────
|
|
107
|
+
// Start from the canonical empty-board template and inject each entry as
|
|
108
|
+
// a direct <body> child. Negative left/top are preserved — the migration
|
|
109
|
+
// intentionally does NOT clamp coords (appendCanvasPrimitiveToHtml clamps
|
|
110
|
+
// to x/y >= 0 for new screen primitives; the board surface has no such
|
|
111
|
+
// restriction).
|
|
112
|
+
let boardHtml = emptyBoardHtml();
|
|
113
|
+
if (entries.length > 0) {
|
|
114
|
+
const fragments = entries
|
|
115
|
+
.sort((a, b) => {
|
|
116
|
+
// Stable render order: lower z first, then creation order.
|
|
117
|
+
const az = a.geometry.z ?? 0;
|
|
118
|
+
const bz = b.geometry.z ?? 0;
|
|
119
|
+
if (az !== bz) return az - bz;
|
|
120
|
+
return a.createdAt < b.createdAt ? -1 : 1;
|
|
121
|
+
})
|
|
122
|
+
.map((entry) => boardObjectEntryToHtmlFragment(entry))
|
|
123
|
+
.join("\n");
|
|
124
|
+
|
|
125
|
+
// Insert fragments before </body>.
|
|
126
|
+
boardHtml = boardHtml.replace("</body>", `${fragments}\n</body>`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
|
|
131
|
+
// ── 4. Upsert the board file and update designs.data atomically ───────
|
|
132
|
+
// Run in a transaction so concurrent opens cannot create two board files.
|
|
133
|
+
let boardFileId = "";
|
|
134
|
+
|
|
135
|
+
await db.transaction(async (tx) => {
|
|
136
|
+
// Re-read designs.data inside the transaction to guard against a
|
|
137
|
+
// concurrent migration that raced us.
|
|
138
|
+
const [fresh] = await tx
|
|
139
|
+
.select({ data: schema.designs.data })
|
|
140
|
+
.from(schema.designs)
|
|
141
|
+
.where(eq(schema.designs.id, designId))
|
|
142
|
+
.limit(1);
|
|
143
|
+
|
|
144
|
+
if (!fresh) {
|
|
145
|
+
throw new Error(`Design "${designId}" not found.`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let freshParsed: Record<string, unknown>;
|
|
149
|
+
try {
|
|
150
|
+
const raw = JSON.parse(fresh.data);
|
|
151
|
+
freshParsed =
|
|
152
|
+
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
153
|
+
? (raw as Record<string, unknown>)
|
|
154
|
+
: {};
|
|
155
|
+
} catch {
|
|
156
|
+
freshParsed = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Double-check inside the transaction.
|
|
160
|
+
if (
|
|
161
|
+
typeof freshParsed["boardFileId"] === "string" &&
|
|
162
|
+
freshParsed["boardFileId"].length > 0
|
|
163
|
+
) {
|
|
164
|
+
boardFileId = freshParsed["boardFileId"] as string;
|
|
165
|
+
return; // Another concurrent call already finished — skip.
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if a __board__.html file was somehow already created (shouldn't
|
|
169
|
+
// happen in normal flow, but be defensive).
|
|
170
|
+
const [existingBoardFile] = await tx
|
|
171
|
+
.select({ id: schema.designFiles.id })
|
|
172
|
+
.from(schema.designFiles)
|
|
173
|
+
.where(
|
|
174
|
+
and(
|
|
175
|
+
eq(schema.designFiles.designId, designId),
|
|
176
|
+
eq(schema.designFiles.filename, BOARD_FILENAME),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
.limit(1);
|
|
180
|
+
|
|
181
|
+
if (existingBoardFile) {
|
|
182
|
+
// Board file exists but boardFileId was not set — link it and update
|
|
183
|
+
// its content.
|
|
184
|
+
boardFileId = existingBoardFile.id;
|
|
185
|
+
await tx
|
|
186
|
+
.update(schema.designFiles)
|
|
187
|
+
.set({ content: boardHtml, updatedAt: now })
|
|
188
|
+
.where(eq(schema.designFiles.id, boardFileId));
|
|
189
|
+
} else {
|
|
190
|
+
// Create the board file.
|
|
191
|
+
boardFileId = nanoid();
|
|
192
|
+
await tx.insert(schema.designFiles).values({
|
|
193
|
+
id: boardFileId,
|
|
194
|
+
designId,
|
|
195
|
+
filename: BOARD_FILENAME,
|
|
196
|
+
fileType: "html",
|
|
197
|
+
content: boardHtml,
|
|
198
|
+
createdAt: now,
|
|
199
|
+
updatedAt: now,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Write boardFileId into designs.data; null out boardObjects.
|
|
204
|
+
const nextData: Record<string, unknown> = {
|
|
205
|
+
...freshParsed,
|
|
206
|
+
boardFileId,
|
|
207
|
+
boardObjects: null,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
await tx
|
|
211
|
+
.update(schema.designs)
|
|
212
|
+
.set({
|
|
213
|
+
data: JSON.stringify(nextData),
|
|
214
|
+
updatedAt: now,
|
|
215
|
+
})
|
|
216
|
+
.where(eq(schema.designs.id, designId));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ── 5. Seed collab state for the new board file ───────────────────────
|
|
220
|
+
// (Best-effort: collab seeding after the file row is committed.)
|
|
221
|
+
try {
|
|
222
|
+
await seedFromText(boardFileId, boardHtml);
|
|
223
|
+
} catch {
|
|
224
|
+
// Non-fatal — the board file still renders from SQL content.
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
designId,
|
|
229
|
+
migrated: true,
|
|
230
|
+
boardFileId,
|
|
231
|
+
migratedObjectCount: entries.length,
|
|
232
|
+
message:
|
|
233
|
+
entries.length > 0
|
|
234
|
+
? `Migrated ${entries.length} board object(s) to board file ${boardFileId}.`
|
|
235
|
+
: `Created empty board file ${boardFileId} (no legacy board objects).`,
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
});
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* --view View name (list, editor, design-systems, present, templates, settings)
|
|
19
19
|
* --designId Design ID (for editor/present views)
|
|
20
20
|
* --editorView Editor mode for designs: single or overview
|
|
21
|
-
* --inspectorTab Inspector tab for designs: design
|
|
21
|
+
* --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
|
|
22
|
+
* --leftPanel Left editor panel: file, agent, assets, tools, or tokens
|
|
22
23
|
* --fileId Screen/file id to focus in the design editor
|
|
23
24
|
* --filename Screen filename to focus in the design editor
|
|
24
25
|
* --tool Design editor tool to activate
|
|
@@ -47,9 +48,17 @@ const designEditorToolSchema = z.enum([
|
|
|
47
48
|
"scale",
|
|
48
49
|
]);
|
|
49
50
|
|
|
51
|
+
const designLeftPanelSchema = z.enum([
|
|
52
|
+
"file",
|
|
53
|
+
"agent",
|
|
54
|
+
"assets",
|
|
55
|
+
"tools",
|
|
56
|
+
"tokens",
|
|
57
|
+
]);
|
|
58
|
+
|
|
50
59
|
export default defineAction({
|
|
51
60
|
description:
|
|
52
|
-
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use
|
|
61
|
+
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens to focus the Figma-style left rail. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
|
|
53
62
|
schema: z
|
|
54
63
|
.object({
|
|
55
64
|
view: z
|
|
@@ -83,6 +92,10 @@ export default defineAction({
|
|
|
83
92
|
.enum(["design", "tweaks", "extensions"])
|
|
84
93
|
.optional()
|
|
85
94
|
.describe("Alias for inspectorTab"),
|
|
95
|
+
leftPanel: designLeftPanelSchema
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Design editor left rail panel to focus"),
|
|
98
|
+
panel: designLeftPanelSchema.optional().describe("Alias for leftPanel"),
|
|
86
99
|
fileId: z.string().optional().describe("Design file/screen ID to focus"),
|
|
87
100
|
screenId: z.string().optional().describe("Alias for fileId"),
|
|
88
101
|
filename: z
|
|
@@ -146,6 +159,8 @@ export default defineAction({
|
|
|
146
159
|
if (editorView) nav.editorView = editorView;
|
|
147
160
|
const inspectorTab = args.inspectorTab ?? args.inspector;
|
|
148
161
|
if (inspectorTab) nav.inspectorTab = inspectorTab;
|
|
162
|
+
const leftPanel = args.leftPanel ?? args.panel;
|
|
163
|
+
if (leftPanel) nav.leftPanel = leftPanel;
|
|
149
164
|
if (args.fileId) nav.fileId = args.fileId;
|
|
150
165
|
if (args.screenId) nav.screenId = args.screenId;
|
|
151
166
|
if (args.filename) nav.filename = args.filename;
|
|
@@ -159,7 +174,7 @@ export default defineAction({
|
|
|
159
174
|
args.designId ? ` (design: ${args.designId})` : ""
|
|
160
175
|
}${editorView ? ` (${editorView} view)` : ""}${
|
|
161
176
|
inspectorTab ? ` (${inspectorTab} inspector)` : ""
|
|
162
|
-
}${
|
|
177
|
+
}${leftPanel ? ` (${leftPanel} panel)` : ""}${
|
|
163
178
|
args.fileId || args.screenId || args.filename || args.screen
|
|
164
179
|
? ` (screen: ${args.fileId ?? args.screenId ?? args.filename ?? args.screen})`
|
|
165
180
|
: ""
|
|
@@ -36,7 +36,10 @@ import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
|
36
36
|
import { resolveSourceCapabilities } from "../shared/capability-resolver.js";
|
|
37
37
|
import { buildCodeLayerProjection } from "../shared/code-layer.js";
|
|
38
38
|
import type { CodeLayerSource } from "../shared/code-layer.js";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
componentNameFor,
|
|
41
|
+
componentNodeIdMatches,
|
|
42
|
+
} from "../shared/component-model.js";
|
|
40
43
|
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
41
44
|
import { normalizeDesignSourceType } from "../shared/source-mode.js";
|
|
42
45
|
|
|
@@ -148,7 +151,9 @@ export default defineAction({
|
|
|
148
151
|
source: codeLayerSource,
|
|
149
152
|
});
|
|
150
153
|
|
|
151
|
-
const node = projection.nodes.find((n) =>
|
|
154
|
+
const node = projection.nodes.find((n) =>
|
|
155
|
+
componentNodeIdMatches(n, nodeId),
|
|
156
|
+
);
|
|
152
157
|
if (!node) {
|
|
153
158
|
throw new Error(
|
|
154
159
|
`Node "${nodeId}" not found. Run get-code-layer-projection to list current ids.`,
|