@agent-native/core 0.80.11 → 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.
Files changed (225) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +47 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  6. package/corpus/core/src/client/chat/index.ts +1 -0
  7. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  8. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  10. package/corpus/core/src/client/index.ts +1 -0
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  12. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  13. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  14. package/corpus/core/src/db/client.ts +29 -2
  15. package/corpus/core/src/provider-api/index.ts +29 -0
  16. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  17. package/corpus/core/src/styles/agent-native.css +54 -0
  18. package/corpus/core/src/usage/store.ts +2 -2
  19. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  20. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  21. package/corpus/templates/analytics/app/global.css +8 -0
  22. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  23. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  24. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  26. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  27. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  28. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  33. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  34. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  35. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  36. package/corpus/templates/assets/app/global.css +15 -0
  37. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  38. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  39. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  40. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  41. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  42. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  43. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  45. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  46. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  47. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  48. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  49. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  50. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  51. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  52. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  55. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  56. package/corpus/templates/design/AGENTS.md +27 -0
  57. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  58. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  59. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  60. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  61. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  62. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  63. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  64. package/corpus/templates/design/actions/export-zip.ts +23 -8
  65. package/corpus/templates/design/actions/generate-design.ts +53 -1
  66. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  67. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  68. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  69. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  70. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  71. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  72. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  73. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  74. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  75. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  76. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  77. package/corpus/templates/design/actions/navigate.ts +18 -3
  78. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  79. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  80. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  81. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  82. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  83. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  84. package/corpus/templates/design/actions/view-screen.ts +1 -1
  85. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  86. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  87. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  88. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  89. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  90. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  91. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  92. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  93. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  94. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  95. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  96. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  97. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  98. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  99. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  100. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  101. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  102. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  103. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  104. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  105. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  106. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  107. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  108. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  109. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  110. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  111. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  112. package/corpus/templates/design/app/global.css +7 -0
  113. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  114. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  115. package/corpus/templates/design/app/i18n-data.ts +358 -0
  116. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  117. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  118. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  119. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  120. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  143. package/corpus/templates/design/e2e/helpers.ts +168 -65
  144. package/corpus/templates/design/package.json +3 -1
  145. package/corpus/templates/design/server/db/schema.ts +23 -0
  146. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  147. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  148. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  149. package/corpus/templates/design/server/plugins/db.ts +39 -0
  150. package/corpus/templates/design/server/register-secrets.ts +38 -0
  151. package/corpus/templates/design/shared/board-file.ts +228 -0
  152. package/corpus/templates/design/shared/board-objects.ts +288 -0
  153. package/corpus/templates/design/shared/code-layer.ts +52 -0
  154. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  155. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  156. package/corpus/templates/design/tsconfig.json +2 -1
  157. package/corpus/templates/slides/AGENTS.md +9 -0
  158. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  159. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  160. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  161. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  162. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  163. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  164. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  166. package/corpus/templates/slides/package.json +1 -0
  167. package/corpus/templates/slides/vite.config.ts +1 -0
  168. package/dist/cli/design-connect.d.ts +25 -0
  169. package/dist/cli/design-connect.d.ts.map +1 -1
  170. package/dist/cli/design-connect.js +357 -18
  171. package/dist/cli/design-connect.js.map +1 -1
  172. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  173. package/dist/client/ConnectBuilderCard.js +1 -0
  174. package/dist/client/ConnectBuilderCard.js.map +1 -1
  175. package/dist/client/chat/index.d.ts +1 -1
  176. package/dist/client/chat/index.d.ts.map +1 -1
  177. package/dist/client/chat/index.js +1 -1
  178. package/dist/client/chat/index.js.map +1 -1
  179. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  180. package/dist/client/chat/run-recovery.js +5 -5
  181. package/dist/client/chat/run-recovery.js.map +1 -1
  182. package/dist/client/chat-view-transition.d.ts +6 -0
  183. package/dist/client/chat-view-transition.d.ts.map +1 -1
  184. package/dist/client/chat-view-transition.js +23 -2
  185. package/dist/client/chat-view-transition.js.map +1 -1
  186. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  187. package/dist/client/composer/PromptComposer.js +3 -2
  188. package/dist/client/composer/PromptComposer.js.map +1 -1
  189. package/dist/client/index.d.ts +1 -1
  190. package/dist/client/index.d.ts.map +1 -1
  191. package/dist/client/index.js +1 -1
  192. package/dist/client/index.js.map +1 -1
  193. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  194. package/dist/client/org/OrgSwitcher.js +11 -4
  195. package/dist/client/org/OrgSwitcher.js.map +1 -1
  196. package/dist/client/sharing/ShareButton.js +3 -3
  197. package/dist/client/sharing/ShareButton.js.map +1 -1
  198. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  199. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  200. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  201. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  202. package/dist/collab/awareness.d.ts +2 -2
  203. package/dist/collab/awareness.d.ts.map +1 -1
  204. package/dist/collab/routes.d.ts +2 -2
  205. package/dist/db/client.d.ts +1 -0
  206. package/dist/db/client.d.ts.map +1 -1
  207. package/dist/db/client.js +28 -2
  208. package/dist/db/client.js.map +1 -1
  209. package/dist/notifications/routes.d.ts +3 -3
  210. package/dist/observability/routes.d.ts +2 -2
  211. package/dist/progress/routes.d.ts +1 -1
  212. package/dist/provider-api/index.d.ts +5 -4
  213. package/dist/provider-api/index.d.ts.map +1 -1
  214. package/dist/provider-api/index.js +28 -0
  215. package/dist/provider-api/index.js.map +1 -1
  216. package/dist/resources/handlers.d.ts +3 -3
  217. package/dist/server/core-routes-plugin.d.ts +25 -0
  218. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  219. package/dist/server/core-routes-plugin.js +43 -18
  220. package/dist/server/core-routes-plugin.js.map +1 -1
  221. package/dist/server/transcribe-voice.d.ts +1 -1
  222. package/dist/styles/agent-native.css +54 -0
  223. package/dist/usage/store.js +2 -2
  224. package/dist/usage/store.js.map +1 -1
  225. package/package.json +1 -1
@@ -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, "&amp;")
218
+ .replace(/"/g, "&quot;")
219
+ .replace(/</g, "&lt;")
220
+ .replace(/>/g, "&gt;");
221
+ }
222
+
223
+ function escapeHtml(value: string): string {
224
+ return value
225
+ .replace(/&/g, "&amp;")
226
+ .replace(/</g, "&lt;")
227
+ .replace(/>/g, "&gt;");
228
+ }