@agent-native/core 0.80.11 → 0.81.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -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/AgentPanel.tsx +33 -1
  6. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  7. package/corpus/core/src/client/chat/index.ts +1 -0
  8. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  9. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  10. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  11. package/corpus/core/src/client/index.ts +1 -0
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  13. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  14. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  15. package/corpus/core/src/db/client.ts +29 -2
  16. package/corpus/core/src/provider-api/index.ts +29 -0
  17. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  18. package/corpus/core/src/styles/agent-native.css +54 -0
  19. package/corpus/core/src/usage/store.ts +2 -2
  20. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  21. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  22. package/corpus/templates/analytics/app/global.css +8 -0
  23. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  24. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  25. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  27. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  28. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  29. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  34. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  35. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  36. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  37. package/corpus/templates/assets/app/global.css +15 -0
  38. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  39. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  40. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  41. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  42. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  43. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  44. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  46. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  47. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  48. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  49. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  50. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  51. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  52. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  55. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  56. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  57. package/corpus/templates/design/AGENTS.md +27 -0
  58. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  59. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  60. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  61. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  62. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  63. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  64. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  65. package/corpus/templates/design/actions/export-zip.ts +23 -8
  66. package/corpus/templates/design/actions/generate-design.ts +53 -1
  67. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  68. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  69. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  70. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  71. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  72. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  73. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  74. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  75. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  76. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  77. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  78. package/corpus/templates/design/actions/navigate.ts +18 -3
  79. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  80. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  81. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  82. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  83. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  84. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  85. package/corpus/templates/design/actions/view-screen.ts +1 -1
  86. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  87. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  88. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  89. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  90. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  91. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  92. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  93. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  94. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  95. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  96. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  97. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  98. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  99. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  100. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  101. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  102. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  103. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  104. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  105. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  106. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  107. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  108. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  109. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  110. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  111. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  112. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  113. package/corpus/templates/design/app/global.css +7 -0
  114. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  115. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  116. package/corpus/templates/design/app/i18n-data.ts +358 -0
  117. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  118. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  119. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  120. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  144. package/corpus/templates/design/e2e/helpers.ts +168 -65
  145. package/corpus/templates/design/package.json +3 -1
  146. package/corpus/templates/design/server/db/schema.ts +23 -0
  147. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  148. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  149. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  150. package/corpus/templates/design/server/plugins/db.ts +39 -0
  151. package/corpus/templates/design/server/register-secrets.ts +38 -0
  152. package/corpus/templates/design/shared/board-file.ts +228 -0
  153. package/corpus/templates/design/shared/board-objects.ts +288 -0
  154. package/corpus/templates/design/shared/code-layer.ts +52 -0
  155. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  156. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  157. package/corpus/templates/design/tsconfig.json +2 -1
  158. package/corpus/templates/slides/AGENTS.md +9 -0
  159. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  160. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  161. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  162. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  163. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  164. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  166. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  167. package/corpus/templates/slides/package.json +1 -0
  168. package/corpus/templates/slides/vite.config.ts +1 -0
  169. package/dist/cli/design-connect.d.ts +25 -0
  170. package/dist/cli/design-connect.d.ts.map +1 -1
  171. package/dist/cli/design-connect.js +357 -18
  172. package/dist/cli/design-connect.js.map +1 -1
  173. package/dist/client/AgentPanel.d.ts +1 -1
  174. package/dist/client/AgentPanel.d.ts.map +1 -1
  175. package/dist/client/AgentPanel.js +18 -3
  176. package/dist/client/AgentPanel.js.map +1 -1
  177. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  178. package/dist/client/ConnectBuilderCard.js +1 -0
  179. package/dist/client/ConnectBuilderCard.js.map +1 -1
  180. package/dist/client/chat/index.d.ts +1 -1
  181. package/dist/client/chat/index.d.ts.map +1 -1
  182. package/dist/client/chat/index.js +1 -1
  183. package/dist/client/chat/index.js.map +1 -1
  184. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  185. package/dist/client/chat/run-recovery.js +5 -5
  186. package/dist/client/chat/run-recovery.js.map +1 -1
  187. package/dist/client/chat-view-transition.d.ts +6 -0
  188. package/dist/client/chat-view-transition.d.ts.map +1 -1
  189. package/dist/client/chat-view-transition.js +23 -2
  190. package/dist/client/chat-view-transition.js.map +1 -1
  191. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  192. package/dist/client/composer/PromptComposer.js +3 -2
  193. package/dist/client/composer/PromptComposer.js.map +1 -1
  194. package/dist/client/index.d.ts +1 -1
  195. package/dist/client/index.d.ts.map +1 -1
  196. package/dist/client/index.js +1 -1
  197. package/dist/client/index.js.map +1 -1
  198. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  199. package/dist/client/org/OrgSwitcher.js +11 -4
  200. package/dist/client/org/OrgSwitcher.js.map +1 -1
  201. package/dist/client/sharing/ShareButton.js +3 -3
  202. package/dist/client/sharing/ShareButton.js.map +1 -1
  203. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  204. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  205. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  206. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  207. package/dist/collab/routes.d.ts +1 -1
  208. package/dist/db/client.d.ts +1 -0
  209. package/dist/db/client.d.ts.map +1 -1
  210. package/dist/db/client.js +28 -2
  211. package/dist/db/client.js.map +1 -1
  212. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  213. package/dist/notifications/routes.d.ts +2 -2
  214. package/dist/observability/routes.d.ts +8 -8
  215. package/dist/progress/routes.d.ts +1 -1
  216. package/dist/provider-api/index.d.ts +5 -4
  217. package/dist/provider-api/index.d.ts.map +1 -1
  218. package/dist/provider-api/index.js +28 -0
  219. package/dist/provider-api/index.js.map +1 -1
  220. package/dist/resources/handlers.d.ts +3 -3
  221. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +25 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +43 -18
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/dist/server/transcribe-voice.d.ts +1 -1
  227. package/dist/styles/agent-native.css +54 -0
  228. package/dist/usage/store.js +2 -2
  229. package/dist/usage/store.js.map +1 -1
  230. package/package.json +1 -1
@@ -25,6 +25,8 @@ import {
25
25
  IconChevronRight,
26
26
  IconCircle,
27
27
  IconCode,
28
+ IconFileText,
29
+ IconFolder,
28
30
  IconLetterCase,
29
31
  IconPalette,
30
32
  IconPlus,
@@ -32,6 +34,7 @@ import {
32
34
  IconRuler,
33
35
  IconSpacingVertical,
34
36
  IconShadow,
37
+ IconUpload,
35
38
  } from "@tabler/icons-react";
36
39
  import { useEffect, useRef, useState } from "react";
37
40
 
@@ -43,6 +46,7 @@ import {
43
46
  PopoverContent,
44
47
  PopoverTrigger,
45
48
  } from "@/components/ui/popover";
49
+ import { Textarea } from "@/components/ui/textarea";
46
50
  import {
47
51
  Tooltip,
48
52
  TooltipContent,
@@ -75,6 +79,18 @@ interface IndexDesignTokensResult {
75
79
  tokens: DesignToken[];
76
80
  }
77
81
 
82
+ interface ImportDesignTokensResult {
83
+ designId: string;
84
+ importedCount: number;
85
+ filesAnalyzed: string[];
86
+ resolvedCssVars?: Record<string, string>;
87
+ }
88
+
89
+ interface TokenImportFile {
90
+ filename: string;
91
+ content: string;
92
+ }
93
+
78
94
  // ---------------------------------------------------------------------------
79
95
  // Props
80
96
  // ---------------------------------------------------------------------------
@@ -388,6 +404,200 @@ function NewTokenPopover({ onAdd }: NewTokenPopoverProps) {
388
404
  );
389
405
  }
390
406
 
407
+ // ---------------------------------------------------------------------------
408
+ // Import popover
409
+ // ---------------------------------------------------------------------------
410
+
411
+ const TOKEN_IMPORT_ACCEPT = [
412
+ ".css",
413
+ ".scss",
414
+ ".sass",
415
+ ".less",
416
+ ".json",
417
+ ".ts",
418
+ ".tsx",
419
+ ".js",
420
+ ".jsx",
421
+ ".md",
422
+ ".mdx",
423
+ ".txt",
424
+ ].join(",");
425
+
426
+ async function readImportFiles(fileList: FileList): Promise<TokenImportFile[]> {
427
+ const selected = [...fileList].slice(0, 20);
428
+ const files = await Promise.all(
429
+ selected.map(async (file) => ({
430
+ filename:
431
+ (file as File & { webkitRelativePath?: string }).webkitRelativePath ||
432
+ file.name,
433
+ content: await file.text(),
434
+ })),
435
+ );
436
+
437
+ return files.filter((file) => file.content.trim().length > 0);
438
+ }
439
+
440
+ interface ImportTokensPopoverProps {
441
+ onImportFiles: (
442
+ files: TokenImportFile[],
443
+ ) => Promise<ImportDesignTokensResult>;
444
+ onImportText: (text: string) => Promise<ImportDesignTokensResult>;
445
+ onImportCurrentDesign: () => Promise<ImportDesignTokensResult>;
446
+ isPending: boolean;
447
+ }
448
+
449
+ function ImportTokensPopover({
450
+ onImportFiles,
451
+ onImportText,
452
+ onImportCurrentDesign,
453
+ isPending,
454
+ }: ImportTokensPopoverProps) {
455
+ const t = useT();
456
+ const fileInputRef = useRef<HTMLInputElement>(null);
457
+ const folderInputRef = useRef<HTMLInputElement>(null);
458
+ const [open, setOpen] = useState(false);
459
+ const [text, setText] = useState("");
460
+ const [status, setStatus] = useState<string | null>(null);
461
+
462
+ const runImport = async (
463
+ importer: () => Promise<ImportDesignTokensResult>,
464
+ ) => {
465
+ setStatus(null);
466
+ try {
467
+ const result = await importer();
468
+ setStatus(
469
+ t("designEditor.tokens.importedCount", {
470
+ count: result.importedCount,
471
+ }),
472
+ );
473
+ if (result.importedCount > 0) {
474
+ setText("");
475
+ }
476
+ } catch (error) {
477
+ setStatus(error instanceof Error ? error.message : String(error));
478
+ }
479
+ };
480
+
481
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
482
+ const files = event.currentTarget.files;
483
+ event.currentTarget.value = "";
484
+ if (!files?.length) return;
485
+ void runImport(async () => onImportFiles(await readImportFiles(files)));
486
+ };
487
+
488
+ return (
489
+ <Popover open={open} onOpenChange={setOpen}>
490
+ <Tooltip>
491
+ <TooltipTrigger asChild>
492
+ <PopoverTrigger asChild>
493
+ <Button
494
+ type="button"
495
+ variant="ghost"
496
+ size="icon"
497
+ className="size-6 cursor-pointer text-muted-foreground/60 hover:text-foreground"
498
+ aria-label={t("designEditor.tokens.import")}
499
+ >
500
+ <IconUpload className="size-3" />
501
+ </Button>
502
+ </PopoverTrigger>
503
+ </TooltipTrigger>
504
+ <TooltipContent>{t("designEditor.tokens.import")}</TooltipContent>
505
+ </Tooltip>
506
+ <PopoverContent align="end" className="w-72 p-3 text-[12px]">
507
+ <div className="space-y-3">
508
+ <div className="space-y-1">
509
+ <p className="text-[11px] font-medium text-foreground">
510
+ {t("designEditor.tokens.importTitle")}
511
+ </p>
512
+ <p className="text-[10px] leading-snug text-muted-foreground">
513
+ {t("designEditor.tokens.importHint")}
514
+ </p>
515
+ </div>
516
+
517
+ <div className="grid grid-cols-3 gap-1.5">
518
+ <Button
519
+ type="button"
520
+ variant="outline"
521
+ size="sm"
522
+ className="h-7 cursor-pointer gap-1 px-2 text-[10px]"
523
+ disabled={isPending}
524
+ onClick={() => fileInputRef.current?.click()}
525
+ >
526
+ <IconFileText className="size-3" />
527
+ {t("designEditor.tokens.files")}
528
+ </Button>
529
+ <Button
530
+ type="button"
531
+ variant="outline"
532
+ size="sm"
533
+ className="h-7 cursor-pointer gap-1 px-2 text-[10px]"
534
+ disabled={isPending}
535
+ onClick={() => folderInputRef.current?.click()}
536
+ >
537
+ <IconFolder className="size-3" />
538
+ {t("designEditor.tokens.folder")}
539
+ </Button>
540
+ <Button
541
+ type="button"
542
+ variant="outline"
543
+ size="sm"
544
+ className="h-7 cursor-pointer px-2 text-[10px]"
545
+ disabled={isPending}
546
+ onClick={() => void runImport(onImportCurrentDesign)}
547
+ >
548
+ {t("designEditor.tokens.current")}
549
+ </Button>
550
+ </div>
551
+
552
+ <input
553
+ ref={fileInputRef}
554
+ type="file"
555
+ multiple
556
+ accept={TOKEN_IMPORT_ACCEPT}
557
+ className="hidden"
558
+ onChange={handleFileChange}
559
+ />
560
+ <input
561
+ ref={folderInputRef}
562
+ type="file"
563
+ multiple
564
+ accept={TOKEN_IMPORT_ACCEPT}
565
+ className="hidden"
566
+ onChange={handleFileChange}
567
+ {...({ directory: "", webkitdirectory: "" } as Record<
568
+ string,
569
+ string
570
+ >)}
571
+ />
572
+
573
+ <div className="space-y-1.5">
574
+ <Textarea
575
+ value={text}
576
+ onChange={(event) => setText(event.target.value)}
577
+ placeholder={t("designEditor.tokens.pastePlaceholder")}
578
+ className="min-h-24 resize-none font-mono text-[11px]"
579
+ />
580
+ <Button
581
+ type="button"
582
+ className="h-7 w-full cursor-pointer text-[11px]"
583
+ disabled={isPending || !text.trim()}
584
+ onClick={() => void runImport(() => onImportText(text))}
585
+ >
586
+ {t("designEditor.tokens.importPasted")}
587
+ </Button>
588
+ </div>
589
+
590
+ {status && (
591
+ <p className="text-[10px] leading-snug text-muted-foreground">
592
+ {status}
593
+ </p>
594
+ )}
595
+ </div>
596
+ </PopoverContent>
597
+ </Popover>
598
+ );
599
+ }
600
+
391
601
  // ---------------------------------------------------------------------------
392
602
  // Main panel
393
603
  // ---------------------------------------------------------------------------
@@ -409,6 +619,7 @@ export function TokensPanel({ designId, onTokensApplied }: TokensPanelProps) {
409
619
  );
410
620
 
411
621
  const applyMutation = useActionMutation("apply-design-token-edit");
622
+ const importMutation = useActionMutation("import-design-tokens");
412
623
 
413
624
  // ------------------------------------------------------------------
414
625
  // Local edit state
@@ -463,6 +674,40 @@ export function TokensPanel({ designId, onTokensApplied }: TokensPanelProps) {
463
674
  );
464
675
  };
465
676
 
677
+ const handleImportSuccess = (result: ImportDesignTokensResult) => {
678
+ void refetch();
679
+ if (result.resolvedCssVars && onTokensApplied) {
680
+ onTokensApplied(result.resolvedCssVars);
681
+ }
682
+ return result;
683
+ };
684
+
685
+ const importFiles = async (files: TokenImportFile[]) => {
686
+ const result = (await importMutation.mutateAsync({
687
+ designId,
688
+ source: "files",
689
+ files,
690
+ })) as ImportDesignTokensResult;
691
+ return handleImportSuccess(result);
692
+ };
693
+
694
+ const importText = async (text: string) => {
695
+ const result = (await importMutation.mutateAsync({
696
+ designId,
697
+ source: "paste",
698
+ text,
699
+ })) as ImportDesignTokensResult;
700
+ return handleImportSuccess(result);
701
+ };
702
+
703
+ const importCurrentDesign = async () => {
704
+ const result = (await importMutation.mutateAsync({
705
+ designId,
706
+ source: "current-design",
707
+ })) as ImportDesignTokensResult;
708
+ return handleImportSuccess(result);
709
+ };
710
+
466
711
  // ------------------------------------------------------------------
467
712
  // Render
468
713
  // ------------------------------------------------------------------
@@ -500,6 +745,12 @@ export function TokensPanel({ designId, onTokensApplied }: TokensPanelProps) {
500
745
  </TooltipTrigger>
501
746
  <TooltipContent>{t("designEditor.tokens.refresh")}</TooltipContent>
502
747
  </Tooltip>
748
+ <ImportTokensPopover
749
+ onImportFiles={importFiles}
750
+ onImportText={importText}
751
+ onImportCurrentDesign={importCurrentDesign}
752
+ isPending={importMutation.isPending}
753
+ />
503
754
  <NewTokenPopover onAdd={handleNewToken} />
504
755
  </div>
505
756
  </div>
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Bridge codegen — compiles every *.bridge.ts in this directory to a self-contained
3
+ * injectable string and writes the result as a generated TypeScript module under
4
+ * .generated/bridge/<name>.generated.ts.
5
+ *
6
+ * Usage (from the design template root):
7
+ * pnpm exec tsx app/components/design/bridge/codegen.ts
8
+ *
9
+ * Called automatically by:
10
+ * - The freshness guard test (bridge.guard.spec.ts)
11
+ * - The project build script (see scripts.build in package.json)
12
+ *
13
+ * Mechanism
14
+ * ---------
15
+ * esbuild is invoked with bundle:true, format:'iife', platform:'browser',
16
+ * write:false for each .bridge.ts file. This means:
17
+ * • Any ES import/require that CAN'T be resolved by esbuild causes a build error
18
+ * (self-containment guarantee for non-local deps, e.g. npm packages).
19
+ * • Any ES import that CAN be resolved (e.g. a relative .ts file) gets bundled
20
+ * inline — the guard test's regex check on source prevents this.
21
+ * • The output is an IIFE-wrapped, browser-runnable JS string.
22
+ *
23
+ * The generated module is a plain committed .ts file that any bundler (Vite, Nitro,
24
+ * Rolldown) handles as a normal module — no Vite plugin, no SSR complications.
25
+ */
26
+
27
+ import { execSync } from "node:child_process";
28
+ import { readdirSync, readFileSync, writeFileSync } from "node:fs";
29
+ import { join, resolve } from "node:path";
30
+ import { fileURLToPath } from "node:url";
31
+
32
+ import * as esbuild from "esbuild";
33
+
34
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
35
+ const designRoot = resolve(__dirname, "../../../..");
36
+ const bridgeDir = __dirname;
37
+ const generatedDir = join(designRoot, ".generated", "bridge");
38
+
39
+ const GENERATED_BANNER = `// AUTO-GENERATED by bridge/codegen.ts — do not edit manually.
40
+ // Run: pnpm exec tsx app/components/design/bridge/codegen.ts
41
+ `;
42
+
43
+ async function compileBridge(srcFile: string): Promise<string> {
44
+ const result = await esbuild.build({
45
+ entryPoints: [srcFile],
46
+ bundle: true,
47
+ format: "iife",
48
+ platform: "browser",
49
+ target: "es2020",
50
+ write: false,
51
+ // No external packages — unresolved imports are a hard error.
52
+ // (Relative imports that CAN be resolved get bundled inline, which is also
53
+ // wrong; the guard test prevents those at the source level.)
54
+ external: [],
55
+ });
56
+
57
+ if (result.errors.length > 0) {
58
+ const msgs = await esbuild.formatMessages(result.errors, { kind: "error" });
59
+ throw new Error(`esbuild error for ${srcFile}:\n${msgs.join("\n")}`);
60
+ }
61
+
62
+ const output = result.outputFiles[0];
63
+ if (!output) throw new Error(`No output from esbuild for ${srcFile}`);
64
+ return output.text;
65
+ }
66
+
67
+ function bridgeName(filename: string): string {
68
+ return filename.replace(/\.bridge\.ts$/, "");
69
+ }
70
+
71
+ function generatedModuleSrc(name: string, compiled: string): string {
72
+ // Escape backticks and ${} in the compiled string so it embeds safely.
73
+ const escaped = compiled
74
+ .replace(/\\/g, "\\\\")
75
+ .replace(/`/g, "\\`")
76
+ .replace(/\$\{/g, "\\${");
77
+ return `${GENERATED_BANNER}
78
+ /** Compiled IIFE string for ${name}.bridge.ts — inject into an iframe via srcdoc or a <script> tag. */
79
+ export const ${camelCase(name)}BridgeScript: string = \`${escaped}\`;
80
+ `;
81
+ }
82
+
83
+ function camelCase(name: string): string {
84
+ return name.replace(/[-_]([a-z])/g, (_, c: string) => c.toUpperCase());
85
+ }
86
+
87
+ async function runCodegen(): Promise<void> {
88
+ const bridgeFiles = readdirSync(bridgeDir).filter((f) =>
89
+ f.endsWith(".bridge.ts"),
90
+ );
91
+
92
+ if (bridgeFiles.length === 0) {
93
+ console.log("[bridge/codegen] No *.bridge.ts files found.");
94
+ return;
95
+ }
96
+
97
+ for (const filename of bridgeFiles) {
98
+ const srcPath = join(bridgeDir, filename);
99
+ const name = bridgeName(filename);
100
+ const outPath = join(generatedDir, `${name}.generated.ts`);
101
+
102
+ console.log(`[bridge/codegen] Compiling ${filename} ...`);
103
+ const compiled = await compileBridge(srcPath);
104
+ const src = generatedModuleSrc(name, compiled);
105
+ writeFileSync(outPath, src, "utf-8");
106
+ console.log(`[bridge/codegen] → .generated/bridge/${name}.generated.ts`);
107
+ }
108
+
109
+ // Run oxfmt on generated files so they pass the formatter check.
110
+ try {
111
+ execSync(`pnpm exec oxfmt .generated/bridge/`, {
112
+ cwd: designRoot,
113
+ stdio: "pipe",
114
+ });
115
+ } catch {
116
+ // oxfmt not critical — formatting mismatches caught by CI linter, not here.
117
+ }
118
+
119
+ console.log("[bridge/codegen] Done.");
120
+ }
121
+
122
+ runCodegen().catch((err) => {
123
+ console.error("[bridge/codegen] FAILED:", err);
124
+ process.exit(1);
125
+ });