@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
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"build": "agent-native build",
|
|
9
9
|
"start": "agent-native start",
|
|
10
10
|
"test": "vitest --run",
|
|
11
|
-
"typecheck": "agent-native typecheck",
|
|
11
|
+
"typecheck": "agent-native typecheck && pnpm exec tsc --noEmit -p app/components/design/bridge/tsconfig.json",
|
|
12
|
+
"codegen:bridge": "tsx app/components/design/bridge/codegen.ts",
|
|
12
13
|
"action": "agent-native action",
|
|
13
14
|
"script": "agent-native script",
|
|
14
15
|
"e2e": "playwright test",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
"clsx": "^2.1.1",
|
|
79
80
|
"cmdk": "^1.1.1",
|
|
80
81
|
"embla-carousel-react": "^8.6.0",
|
|
82
|
+
"esbuild": "^0.27.7",
|
|
81
83
|
"input-otp": "^1.4.2",
|
|
82
84
|
"next-themes": "^0.4.6",
|
|
83
85
|
"oxfmt": "catalog:",
|
|
@@ -75,6 +75,7 @@ export const designLocalhostConnections = table(
|
|
|
75
75
|
.notNull()
|
|
76
76
|
.default("connected"),
|
|
77
77
|
lastSeenAt: text("last_seen_at"),
|
|
78
|
+
bridgeToken: text("bridge_token"),
|
|
78
79
|
ownerEmail: text("owner_email").notNull(),
|
|
79
80
|
orgId: text("org_id"),
|
|
80
81
|
createdAt: text("created_at").default(now()),
|
|
@@ -178,6 +179,28 @@ export const designState = table("design_state", {
|
|
|
178
179
|
...ownableColumns(),
|
|
179
180
|
});
|
|
180
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Localhost write-consent grants minted when the user explicitly allows file
|
|
184
|
+
* writes for a specific design + connection pair. Expire after 8 hours.
|
|
185
|
+
* Only HTML/CSS files may be written (enforced at the action layer).
|
|
186
|
+
*/
|
|
187
|
+
export const designLocalhostWriteGrants = table(
|
|
188
|
+
"design_localhost_write_grants",
|
|
189
|
+
{
|
|
190
|
+
id: text("id").primaryKey(),
|
|
191
|
+
designId: text("design_id").notNull(),
|
|
192
|
+
connectionId: text("connection_id").notNull(),
|
|
193
|
+
/** Filesystem root that was visible when the grant was created. */
|
|
194
|
+
rootPath: text("root_path").notNull(),
|
|
195
|
+
/** Short-lived token passed in the X-Bridge-Token header. */
|
|
196
|
+
bridgeToken: text("bridge_token").notNull(),
|
|
197
|
+
/** ISO timestamp after which the grant is considered expired (8 hours). */
|
|
198
|
+
grantedUntil: text("granted_until").notNull(),
|
|
199
|
+
createdAt: text("created_at").default(now()),
|
|
200
|
+
...ownableColumns(),
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
181
204
|
/**
|
|
182
205
|
* Cached accessibility audit + visual diff results for a design.
|
|
183
206
|
* Keyed by design + optional base/compare design_versions pair + source ref.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
|
|
5
6
|
import { getSession } from "@agent-native/core/server";
|
|
@@ -11,7 +12,27 @@ import {
|
|
|
11
12
|
} from "h3";
|
|
12
13
|
import { nanoid } from "nanoid";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
function isServerlessRuntime(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
16
|
+
return Boolean(
|
|
17
|
+
env.NETLIFY ||
|
|
18
|
+
env.VERCEL ||
|
|
19
|
+
env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
20
|
+
env.LAMBDA_TASK_ROOT ||
|
|
21
|
+
env.CF_PAGES,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function uploadsRootForRuntime(
|
|
26
|
+
cwd = process.cwd(),
|
|
27
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
28
|
+
): string {
|
|
29
|
+
const baseDir = isServerlessRuntime(env)
|
|
30
|
+
? path.join(os.tmpdir(), "agent-native-design")
|
|
31
|
+
: cwd;
|
|
32
|
+
return path.join(baseDir, "data", "uploads");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const UPLOADS_ROOT = uploadsRootForRuntime();
|
|
15
36
|
const MAX_EXTRACTED_TEXT_CHARS = 8_000;
|
|
16
37
|
const TEXT_EXTENSIONS = new Set([
|
|
17
38
|
".html",
|
|
@@ -19,18 +19,21 @@ export type { ProviderApiMethod, ProviderApiRequestArgs };
|
|
|
19
19
|
const resolveDesignCredential: ProviderApiCredentialResolver = async (
|
|
20
20
|
options,
|
|
21
21
|
) => {
|
|
22
|
-
|
|
22
|
+
const supported =
|
|
23
|
+
(options.provider === "github" && options.key === "GITHUB_TOKEN") ||
|
|
24
|
+
(options.provider === "figma" && options.key === "FIGMA_ACCESS_TOKEN");
|
|
25
|
+
if (!supported) {
|
|
23
26
|
return null;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
const value = await resolveSecret(
|
|
29
|
+
const value = await resolveSecret(options.key);
|
|
27
30
|
if (!value) return null;
|
|
28
31
|
|
|
29
32
|
return {
|
|
30
|
-
key:
|
|
33
|
+
key: options.key,
|
|
31
34
|
value,
|
|
32
35
|
source: `${DESIGN_APP_ID}_secret`,
|
|
33
|
-
provider:
|
|
36
|
+
provider: options.provider,
|
|
34
37
|
scope: "request",
|
|
35
38
|
};
|
|
36
39
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side helper: resolve and validate a localhost write-consent grant.
|
|
3
|
+
*
|
|
4
|
+
* Security model
|
|
5
|
+
* ==============
|
|
6
|
+
* - The grant row is loaded by designId + connectionId for the authenticated
|
|
7
|
+
* user.
|
|
8
|
+
* - An expired grant (grantedUntil < now) is rejected.
|
|
9
|
+
* - The target file path must be inside rootPath — validated using Node's
|
|
10
|
+
* path.resolve so that ".." traversal and absolute-path injection are
|
|
11
|
+
* blocked at this layer. The bridge also performs its own realpath check.
|
|
12
|
+
* - The function throws an Error with a descriptive message on any violation so
|
|
13
|
+
* callers can surface it cleanly to the agent.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
import { and, eq } from "drizzle-orm";
|
|
19
|
+
|
|
20
|
+
import { getDb, schema } from "../db/index.js";
|
|
21
|
+
|
|
22
|
+
export interface WriteGrantContext {
|
|
23
|
+
designId: string;
|
|
24
|
+
connectionId: string;
|
|
25
|
+
/** Email of the currently authenticated user (from request context). */
|
|
26
|
+
ownerEmail: string;
|
|
27
|
+
/** Target path relative to rootPath (or absolute — validated either way). */
|
|
28
|
+
targetPath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface WriteGrantResult {
|
|
32
|
+
rootPath: string;
|
|
33
|
+
bridgeToken: string;
|
|
34
|
+
grantId: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the active write-consent grant for a given design + connection.
|
|
39
|
+
* Throws if no valid grant exists or the target path escapes rootPath.
|
|
40
|
+
*/
|
|
41
|
+
export async function verifyWriteGrant(
|
|
42
|
+
ctx: WriteGrantContext,
|
|
43
|
+
): Promise<WriteGrantResult> {
|
|
44
|
+
const { designId, connectionId, ownerEmail, targetPath } = ctx;
|
|
45
|
+
|
|
46
|
+
const db = getDb();
|
|
47
|
+
const [grant] = await db
|
|
48
|
+
.select()
|
|
49
|
+
.from(schema.designLocalhostWriteGrants)
|
|
50
|
+
.where(
|
|
51
|
+
and(
|
|
52
|
+
eq(schema.designLocalhostWriteGrants.designId, designId),
|
|
53
|
+
eq(schema.designLocalhostWriteGrants.connectionId, connectionId),
|
|
54
|
+
eq(schema.designLocalhostWriteGrants.ownerEmail, ownerEmail),
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
.limit(1);
|
|
58
|
+
|
|
59
|
+
if (!grant) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"No localhost write-consent grant found for this design + connection. " +
|
|
62
|
+
"The user must approve writes via the LocalhostWriteConsentDialog first.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const now = new Date().toISOString();
|
|
67
|
+
if (grant.grantedUntil < now) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Localhost write-consent grant expired at ${grant.grantedUntil}. ` +
|
|
70
|
+
"Request a new grant via the LocalhostWriteConsentDialog.",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
assertPathInside(grant.rootPath, targetPath);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
rootPath: grant.rootPath,
|
|
78
|
+
bridgeToken: grant.bridgeToken,
|
|
79
|
+
grantId: grant.id,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Assert that targetPath is inside rootPath using path.resolve to block ".."
|
|
85
|
+
* traversal and absolute-path injection.
|
|
86
|
+
*
|
|
87
|
+
* The final realpath confinement (symlink escape prevention) is performed
|
|
88
|
+
* bridge-side using fs.realpath; this layer handles the logical check.
|
|
89
|
+
*
|
|
90
|
+
* Throws if the resolved target is not a descendant of the resolved root.
|
|
91
|
+
*/
|
|
92
|
+
export function assertPathInside(rootPath: string, targetPath: string): void {
|
|
93
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
94
|
+
// targetPath may be relative (to root) or absolute — resolve relative to root
|
|
95
|
+
const resolvedTarget = path.isAbsolute(targetPath)
|
|
96
|
+
? path.resolve(targetPath)
|
|
97
|
+
: path.resolve(rootPath, targetPath);
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
resolvedTarget !== resolvedRoot &&
|
|
101
|
+
!resolvedTarget.startsWith(resolvedRoot + path.sep)
|
|
102
|
+
) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Path "${targetPath}" is outside the consented root "${rootPath}". ` +
|
|
105
|
+
"Write access is restricted to files inside the granted root folder.",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -211,6 +211,45 @@ CREATE INDEX IF NOT EXISTS design_system_shares_resource_principal_idx ON design
|
|
|
211
211
|
visibility TEXT NOT NULL DEFAULT 'private'
|
|
212
212
|
)`,
|
|
213
213
|
},
|
|
214
|
+
// v16: localhost source connections and write-consent grants. The schema
|
|
215
|
+
// existed before this migration in source, but fresh/existing DBs still
|
|
216
|
+
// need the concrete tables and the bridge_token column used by localhost
|
|
217
|
+
// write-back.
|
|
218
|
+
{
|
|
219
|
+
version: 16,
|
|
220
|
+
sql: `CREATE TABLE IF NOT EXISTS design_localhost_connections (
|
|
221
|
+
id TEXT PRIMARY KEY,
|
|
222
|
+
name TEXT NOT NULL,
|
|
223
|
+
source_type TEXT NOT NULL DEFAULT 'localhost',
|
|
224
|
+
dev_server_url TEXT NOT NULL,
|
|
225
|
+
bridge_url TEXT,
|
|
226
|
+
root_path TEXT,
|
|
227
|
+
route_manifest TEXT NOT NULL DEFAULT '{}',
|
|
228
|
+
capabilities TEXT NOT NULL DEFAULT '[]',
|
|
229
|
+
status TEXT NOT NULL DEFAULT 'connected',
|
|
230
|
+
last_seen_at TEXT,
|
|
231
|
+
bridge_token TEXT,
|
|
232
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
233
|
+
org_id TEXT,
|
|
234
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
235
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
236
|
+
);
|
|
237
|
+
ALTER TABLE design_localhost_connections ADD COLUMN IF NOT EXISTS bridge_token TEXT;
|
|
238
|
+
CREATE TABLE IF NOT EXISTS design_localhost_write_grants (
|
|
239
|
+
id TEXT PRIMARY KEY,
|
|
240
|
+
design_id TEXT NOT NULL,
|
|
241
|
+
connection_id TEXT NOT NULL,
|
|
242
|
+
root_path TEXT NOT NULL,
|
|
243
|
+
bridge_token TEXT NOT NULL,
|
|
244
|
+
granted_until TEXT NOT NULL,
|
|
245
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
246
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
247
|
+
org_id TEXT,
|
|
248
|
+
visibility TEXT NOT NULL DEFAULT 'private'
|
|
249
|
+
);
|
|
250
|
+
CREATE INDEX IF NOT EXISTS design_localhost_connections_owner_idx ON design_localhost_connections (owner_email, org_id, updated_at);
|
|
251
|
+
CREATE INDEX IF NOT EXISTS design_localhost_write_grants_lookup_idx ON design_localhost_write_grants (design_id, connection_id, owner_email)`,
|
|
252
|
+
},
|
|
214
253
|
],
|
|
215
254
|
{ table: "design_migrations" },
|
|
216
255
|
);
|
|
@@ -43,3 +43,41 @@ registerRequiredSecret({
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
// Optional: enables browsing reusable Figma components/component sets in the
|
|
48
|
+
// Design asset rail. The provider API injects this server-side as
|
|
49
|
+
// X-Figma-Token; never pass it through action parameters or chat.
|
|
50
|
+
registerRequiredSecret({
|
|
51
|
+
key: "FIGMA_ACCESS_TOKEN",
|
|
52
|
+
label: "Figma access token",
|
|
53
|
+
description:
|
|
54
|
+
"Optional. Enables browsing and inserting Figma library components from files you can access.",
|
|
55
|
+
docsUrl: "https://developers.figma.com/docs/rest-api/personal-access-tokens/",
|
|
56
|
+
scope: "user",
|
|
57
|
+
kind: "api-key",
|
|
58
|
+
required: false,
|
|
59
|
+
validator: async (value) => {
|
|
60
|
+
if (!value) return true;
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch("https://api.figma.com/v1/me", {
|
|
63
|
+
headers: {
|
|
64
|
+
"X-Figma-Token": value,
|
|
65
|
+
"User-Agent": "AgentNative/1.0",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
if (res.ok) return true;
|
|
69
|
+
if (res.status === 401 || res.status === 403) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `Figma rejected this token (${res.status}). Check that it is active and has access to the file.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { ok: false, error: `Figma returned ${res.status}.` };
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: `Could not reach Figma: ${err?.message ?? err}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and helpers for the board file — a reserved design_file
|
|
3
|
+
* whose HTML document holds absolute-positioned board elements.
|
|
4
|
+
*
|
|
5
|
+
* The board file is identified by the filename "__board__.html" and its id is
|
|
6
|
+
* stored in designs.data.boardFileId. Board elements are direct children of
|
|
7
|
+
* <body style="margin:0;position:relative;background:transparent;overflow:visible">
|
|
8
|
+
* each with an absolute position derived from their original BoardObjectEntry
|
|
9
|
+
* geometry.
|
|
10
|
+
*
|
|
11
|
+
* This module is imported by the editor UI, actions, and migration code.
|
|
12
|
+
* It must stay free of React, Nitro, and database imports.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { BoardObjectEntry } from "./board-objects.js";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Constants
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/** Reserved filename for the board overlay file. */
|
|
22
|
+
export const BOARD_FILENAME = "__board__.html";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// isBoardFile
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns true when `filename` is the reserved board file.
|
|
30
|
+
* Comparison is case-sensitive to match the rest of the codebase.
|
|
31
|
+
*/
|
|
32
|
+
export function isBoardFile(filename: string): boolean {
|
|
33
|
+
return filename === BOARD_FILENAME;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// emptyBoardHtml
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The canonical empty-board document template.
|
|
42
|
+
*
|
|
43
|
+
* The <body> uses:
|
|
44
|
+
* - margin:0 — no browser default whitespace
|
|
45
|
+
* - position:relative — so absolute children are positioned within the body
|
|
46
|
+
* - background:transparent — the board layer sits behind screen iframes
|
|
47
|
+
* - overflow:visible — board elements may extend beyond the logical surface
|
|
48
|
+
*/
|
|
49
|
+
export function emptyBoardHtml(): string {
|
|
50
|
+
return `<!DOCTYPE html>
|
|
51
|
+
<html lang="en">
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="UTF-8">
|
|
54
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
55
|
+
<style>
|
|
56
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
57
|
+
body { margin: 0; position: relative; background: transparent; overflow: visible; }
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
</body>
|
|
62
|
+
</html>`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// boardObjectEntryToHtmlFragment
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert a BoardObjectEntry to an absolute-positioned HTML fragment for
|
|
71
|
+
* insertion into the board file's <body>.
|
|
72
|
+
*
|
|
73
|
+
* Negative left/top coordinates are intentionally preserved — board objects
|
|
74
|
+
* may live anywhere in the infinite canvas space, including at negative offsets
|
|
75
|
+
* (e.g. to the left of or above the primary frame cluster).
|
|
76
|
+
*
|
|
77
|
+
* The fragment sets `data-agent-native-node-id` so the bridge engine can
|
|
78
|
+
* select, style, and move elements exactly as it does for screen elements.
|
|
79
|
+
*/
|
|
80
|
+
export function boardObjectEntryToHtmlFragment(
|
|
81
|
+
entry: BoardObjectEntry,
|
|
82
|
+
): string {
|
|
83
|
+
const {
|
|
84
|
+
id,
|
|
85
|
+
kind,
|
|
86
|
+
geometry,
|
|
87
|
+
fill,
|
|
88
|
+
stroke,
|
|
89
|
+
strokeWidth,
|
|
90
|
+
text,
|
|
91
|
+
pathData,
|
|
92
|
+
points,
|
|
93
|
+
name,
|
|
94
|
+
} = entry;
|
|
95
|
+
const x = Math.round(geometry.x);
|
|
96
|
+
const y = Math.round(geometry.y);
|
|
97
|
+
const width = Math.max(1, Math.round(geometry.width));
|
|
98
|
+
const height = Math.max(1, Math.round(geometry.height));
|
|
99
|
+
|
|
100
|
+
const nodeId = id;
|
|
101
|
+
const layerName = name ?? kindToLayerName(kind);
|
|
102
|
+
|
|
103
|
+
// Base inline style — negative left/top are kept as-is.
|
|
104
|
+
const baseStyle = [
|
|
105
|
+
"position:absolute",
|
|
106
|
+
`left:${x}px`,
|
|
107
|
+
`top:${y}px`,
|
|
108
|
+
`width:${width}px`,
|
|
109
|
+
`height:${height}px`,
|
|
110
|
+
...(geometry.rotation ? [`transform:rotate(${geometry.rotation}deg)`] : []),
|
|
111
|
+
...(typeof geometry.z === "number" ? [`z-index:${geometry.z}`] : []),
|
|
112
|
+
].join(";");
|
|
113
|
+
|
|
114
|
+
const dataAttrs =
|
|
115
|
+
`data-agent-native-node-id="${escapeAttr(nodeId)}"` +
|
|
116
|
+
` data-agent-native-layer-name="${escapeAttr(layerName)}"` +
|
|
117
|
+
// Kind marker so the layers panel renders a shape/text/frame icon for the
|
|
118
|
+
// primitive (a rectangle looks like a rectangle), matching in-screen drawn
|
|
119
|
+
// primitives, instead of the generic code/element glyph.
|
|
120
|
+
` data-an-primitive="${escapeAttr(kind)}"`;
|
|
121
|
+
|
|
122
|
+
// Path / line / arrow kinds use an inline SVG.
|
|
123
|
+
if (kind === "path" || kind === "line" || kind === "arrow") {
|
|
124
|
+
const pts = points?.length
|
|
125
|
+
? points
|
|
126
|
+
: [
|
|
127
|
+
{ x: 0, y: height / 2 },
|
|
128
|
+
{ x: width, y: height / 2 },
|
|
129
|
+
];
|
|
130
|
+
const originX = Math.min(...pts.map((p) => p.x));
|
|
131
|
+
const originY = Math.min(...pts.map((p) => p.y));
|
|
132
|
+
const d =
|
|
133
|
+
pathData ??
|
|
134
|
+
pts
|
|
135
|
+
.map(
|
|
136
|
+
(p, i) =>
|
|
137
|
+
`${i === 0 ? "M" : "L"} ${Math.round(p.x - originX)} ${Math.round(p.y - originY)}`,
|
|
138
|
+
)
|
|
139
|
+
.join(" ");
|
|
140
|
+
const strokeColor = stroke ?? "var(--primary, #2563eb)";
|
|
141
|
+
const sw = strokeWidth ?? 3;
|
|
142
|
+
|
|
143
|
+
let markerDefs = "";
|
|
144
|
+
let markerEnd = "";
|
|
145
|
+
if (kind === "arrow") {
|
|
146
|
+
const markerId = `${nodeId}-arrow`;
|
|
147
|
+
markerDefs = `<defs><marker id="${escapeAttr(markerId)}" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto" markerUnits="strokeWidth"><path d="M 0 0 L 10 5 L 0 10 z" fill="${escapeAttr(strokeColor)}"/></marker></defs>`;
|
|
148
|
+
markerEnd = ` marker-end="url(#${escapeAttr(markerId)})"`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return `<svg style="${baseStyle}" xmlns="http://www.w3.org/2000/svg" overflow="visible" ${dataAttrs}>${markerDefs}<path d="${escapeAttr(d)}" fill="none" stroke="${escapeAttr(strokeColor)}" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round"${markerEnd}/></svg>`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Ellipse kind uses a <div> with border-radius.
|
|
155
|
+
if (kind === "ellipse") {
|
|
156
|
+
const bgColor = fill ?? "var(--primary, #2563eb)";
|
|
157
|
+
const borderStyle = stroke
|
|
158
|
+
? `border:${strokeWidth ?? 1}px solid ${stroke};`
|
|
159
|
+
: "";
|
|
160
|
+
const style = `${baseStyle};background:${bgColor};border-radius:50%;${borderStyle}`;
|
|
161
|
+
return `<div style="${style}" ${dataAttrs}></div>`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Text kind uses a <div> with text content.
|
|
165
|
+
if (kind === "text") {
|
|
166
|
+
const color = fill ?? "inherit";
|
|
167
|
+
const style = `${baseStyle};color:${color};white-space:pre-wrap;`;
|
|
168
|
+
return `<div style="${style}" ${dataAttrs}>${text ? escapeHtml(text) : ""}</div>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Frame / rectangle / polygon / star / default — basic colored <div>.
|
|
172
|
+
const bgColor =
|
|
173
|
+
fill ?? (kind === "frame" ? "transparent" : "var(--primary, #2563eb)");
|
|
174
|
+
const borderStyle = stroke
|
|
175
|
+
? `border:${strokeWidth ?? 1}px solid ${stroke};`
|
|
176
|
+
: "";
|
|
177
|
+
const style = `${baseStyle};background:${bgColor};${borderStyle}`;
|
|
178
|
+
|
|
179
|
+
if (kind === "frame") {
|
|
180
|
+
return `<div style="${style}" ${dataAttrs}>${text ? escapeHtml(text) : ""}</div>`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return `<div style="${style}" ${dataAttrs}>${text ? escapeHtml(text) : ""}</div>`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Private helpers
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
function kindToLayerName(kind: BoardObjectEntry["kind"]): string {
|
|
191
|
+
switch (kind) {
|
|
192
|
+
case "frame":
|
|
193
|
+
return "Frame";
|
|
194
|
+
case "rectangle":
|
|
195
|
+
return "Rectangle";
|
|
196
|
+
case "ellipse":
|
|
197
|
+
return "Ellipse";
|
|
198
|
+
case "polygon":
|
|
199
|
+
return "Polygon";
|
|
200
|
+
case "star":
|
|
201
|
+
return "Star";
|
|
202
|
+
case "line":
|
|
203
|
+
return "Line";
|
|
204
|
+
case "arrow":
|
|
205
|
+
return "Arrow";
|
|
206
|
+
case "text":
|
|
207
|
+
return "Text";
|
|
208
|
+
case "path":
|
|
209
|
+
return "Path";
|
|
210
|
+
default:
|
|
211
|
+
return "Shape";
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function escapeAttr(value: string): string {
|
|
216
|
+
return value
|
|
217
|
+
.replace(/&/g, "&")
|
|
218
|
+
.replace(/"/g, """)
|
|
219
|
+
.replace(/</g, "<")
|
|
220
|
+
.replace(/>/g, ">");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function escapeHtml(value: string): string {
|
|
224
|
+
return value
|
|
225
|
+
.replace(/&/g, "&")
|
|
226
|
+
.replace(/</g, "<")
|
|
227
|
+
.replace(/>/g, ">");
|
|
228
|
+
}
|