@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,521 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { buildDeepLink } from "@agent-native/core/server";
3
+ import {
4
+ CODE_MAX_FILES,
5
+ CODE_MAX_TOTAL_BYTES,
6
+ analyzeCodeFile,
7
+ createCodeAnalysisState,
8
+ extractCodeColors,
9
+ extractCodeFonts,
10
+ extractCssVars,
11
+ extractDocumentColors,
12
+ extractDocumentFonts,
13
+ } from "@agent-native/core/server/design-token-utils";
14
+ import { assertAccess, resolveAccess } from "@agent-native/core/sharing";
15
+ import { eq } from "drizzle-orm";
16
+ import { z } from "zod";
17
+
18
+ import { getDb, schema } from "../server/db/index.js";
19
+ import "../server/db/index.js"; // ensure registerShareableResource runs
20
+ import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
21
+
22
+ type ImportedTokenType =
23
+ | "color"
24
+ | "typography"
25
+ | "spacing"
26
+ | "radius"
27
+ | "shadow"
28
+ | "other";
29
+
30
+ interface ImportedDesignToken {
31
+ name: string;
32
+ cssVar: string;
33
+ value: string;
34
+ type: ImportedTokenType;
35
+ source: string;
36
+ }
37
+
38
+ const tokenFileSchema = z.object({
39
+ filename: z.string().trim().min(1).describe("File name or relative path"),
40
+ content: z.string().describe("Raw text content"),
41
+ });
42
+
43
+ const tokenImportSchema = z
44
+ .object({
45
+ designId: z.string().describe("Design project ID"),
46
+ source: z
47
+ .enum(["files", "paste", "current-design"])
48
+ .describe(
49
+ "Where to import from: uploaded files, pasted text, or the current design files.",
50
+ ),
51
+ files: z
52
+ .array(tokenFileSchema)
53
+ .optional()
54
+ .describe("Files to parse when source is files"),
55
+ text: z
56
+ .string()
57
+ .optional()
58
+ .describe("Pasted CSS, JSON, Tailwind config, or design.md content"),
59
+ })
60
+ .superRefine((args, ctx) => {
61
+ if (args.source === "files" && (!args.files || args.files.length === 0)) {
62
+ ctx.addIssue({
63
+ code: z.ZodIssueCode.custom,
64
+ path: ["files"],
65
+ message: "files are required when source is files",
66
+ });
67
+ }
68
+ if (args.source === "paste" && !args.text?.trim()) {
69
+ ctx.addIssue({
70
+ code: z.ZodIssueCode.custom,
71
+ path: ["text"],
72
+ message: "text is required when source is paste",
73
+ });
74
+ }
75
+ });
76
+
77
+ function designDeepLink(designId: string): string {
78
+ return buildDeepLink({
79
+ app: "design",
80
+ view: "editor",
81
+ params: { designId },
82
+ });
83
+ }
84
+
85
+ function classifyVar(name: string, value: string): ImportedTokenType {
86
+ const n = name.toLowerCase();
87
+ if (
88
+ /color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
89
+ n,
90
+ ) ||
91
+ isColorValue(value)
92
+ ) {
93
+ return "color";
94
+ }
95
+ if (/font|size|leading|tracking|weight|heading|body|type/i.test(n)) {
96
+ return "typography";
97
+ }
98
+ if (/radius|rounded/i.test(n)) return "radius";
99
+ if (/spacing|gap|padding|margin|space/i.test(n)) return "spacing";
100
+ if (/shadow|blur|drop/i.test(n)) return "shadow";
101
+ return "other";
102
+ }
103
+
104
+ function isColorValue(value: string): boolean {
105
+ const normalized = value.trim().toLowerCase();
106
+ if (
107
+ /^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\(|color\()/i.test(normalized)
108
+ ) {
109
+ return true;
110
+ }
111
+ return [
112
+ "red",
113
+ "blue",
114
+ "green",
115
+ "yellow",
116
+ "orange",
117
+ "purple",
118
+ "pink",
119
+ "cyan",
120
+ "magenta",
121
+ "teal",
122
+ "navy",
123
+ "maroon",
124
+ "coral",
125
+ "salmon",
126
+ "gold",
127
+ "silver",
128
+ "gray",
129
+ "grey",
130
+ "indigo",
131
+ "violet",
132
+ "lime",
133
+ "olive",
134
+ "aqua",
135
+ "fuchsia",
136
+ "crimson",
137
+ "turquoise",
138
+ "ivory",
139
+ "beige",
140
+ "lavender",
141
+ "tan",
142
+ "khaki",
143
+ "plum",
144
+ "orchid",
145
+ "sienna",
146
+ ].includes(normalized);
147
+ }
148
+
149
+ function toKebab(value: string): string {
150
+ return (
151
+ value
152
+ .trim()
153
+ .replace(/^--/, "")
154
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
155
+ .toLowerCase()
156
+ .replace(/[^a-z0-9]+/g, "-")
157
+ .replace(/^-+|-+$/g, "")
158
+ .slice(0, 48) || "token"
159
+ );
160
+ }
161
+
162
+ function friendlyName(cssVar: string): string {
163
+ return cssVar
164
+ .replace(/^--/, "")
165
+ .replace(/[-_]/g, " ")
166
+ .replace(/\b\w/g, (c) => c.toUpperCase());
167
+ }
168
+
169
+ function normalizeValue(value: string): string {
170
+ return value
171
+ .trim()
172
+ .replace(/;$/, "")
173
+ .replace(/^["']|["']$/g, "")
174
+ .trim();
175
+ }
176
+
177
+ function isSafeTokenValue(value: string): boolean {
178
+ return value.length > 0 && value.length <= 300 && !/[}<]/.test(value);
179
+ }
180
+
181
+ function tokenVar(prefix: string, key: string): string {
182
+ if (key.startsWith("--")) return key;
183
+ const stem = toKebab(key);
184
+ if (stem === "default" && prefix === "radius") return "--radius";
185
+ if (stem.startsWith(prefix)) return `--${stem}`;
186
+ return `--${prefix}-${stem}`;
187
+ }
188
+
189
+ function addLooseTextSignals(
190
+ state: ReturnType<typeof createCodeAnalysisState>,
191
+ filename: string,
192
+ content: string,
193
+ ): void {
194
+ extractCssVars(state, content);
195
+ extractCodeColors(state, content);
196
+ extractCodeFonts(state, content, filename);
197
+
198
+ for (const color of extractDocumentColors(content)) {
199
+ state.colors[color] = color;
200
+ }
201
+ for (const font of extractDocumentFonts(content)) {
202
+ const key = font.toLowerCase().replace(/\s+/g, "-");
203
+ if (!state.fonts.some((entry) => entry.family === font)) {
204
+ state.fonts.push({ family: font, source: filename });
205
+ state.seenFonts.add(key);
206
+ }
207
+ }
208
+ }
209
+
210
+ function parseNamedLines(
211
+ files: { filename: string; content: string }[],
212
+ ): ImportedDesignToken[] {
213
+ const tokens: ImportedDesignToken[] = [];
214
+
215
+ for (const file of files) {
216
+ for (const rawLine of file.content.split(/\r?\n/)) {
217
+ const line = rawLine.trim();
218
+ if (!line || line.length > 220) continue;
219
+
220
+ const match = line.match(
221
+ /^(?:[-*]\s*)?(?:\*\*)?([A-Za-z][\w\s/.-]{1,64}?)(?:\*\*)?\s*[:=]\s*(.+?)\s*$/,
222
+ );
223
+ if (!match) continue;
224
+
225
+ const label = match[1].replace(/\*\*/g, "").trim();
226
+ const value = normalizeValue(match[2]);
227
+ if (!isSafeTokenValue(value)) continue;
228
+
229
+ const lower = label.toLowerCase();
230
+ let cssVar: string | null = null;
231
+ if (
232
+ isColorValue(value) ||
233
+ /color|background|surface|accent|brand/.test(lower)
234
+ ) {
235
+ cssVar = tokenVar("color", label.replace(/\bcolor\b/gi, ""));
236
+ } else if (/radius|rounded/.test(lower)) {
237
+ cssVar = tokenVar("radius", label);
238
+ } else if (/spacing|gap|padding|margin|space/.test(lower)) {
239
+ cssVar = tokenVar("spacing", label);
240
+ } else if (/font|typeface|typography/.test(lower)) {
241
+ cssVar = tokenVar("font", label);
242
+ }
243
+
244
+ if (!cssVar) continue;
245
+ tokens.push({
246
+ name: friendlyName(cssVar),
247
+ cssVar,
248
+ value,
249
+ type: classifyVar(cssVar, value),
250
+ source: file.filename,
251
+ });
252
+ }
253
+ }
254
+
255
+ return tokens;
256
+ }
257
+
258
+ function uniqueTokenList(tokens: ImportedDesignToken[]): ImportedDesignToken[] {
259
+ const byVar = new Map<string, ImportedDesignToken>();
260
+ const namedValues = new Set<string>();
261
+
262
+ for (const token of tokens) {
263
+ if (!/^--[-_a-zA-Z0-9]+$/.test(token.cssVar)) continue;
264
+ if (!isSafeTokenValue(token.value)) continue;
265
+ const previous = byVar.get(token.cssVar);
266
+ byVar.set(token.cssVar, token);
267
+ if (!previous && !/^--color-imported-\d+$/.test(token.cssVar)) {
268
+ namedValues.add(token.value.toLowerCase());
269
+ }
270
+ }
271
+
272
+ return [...byVar.values()].filter((token) => {
273
+ if (!/^--color-imported-\d+$/.test(token.cssVar)) return true;
274
+ return !namedValues.has(token.value.toLowerCase());
275
+ });
276
+ }
277
+
278
+ function tokensFromFiles(files: { filename: string; content: string }[]) {
279
+ const accepted: { filename: string; content: string }[] = [];
280
+ let totalBytes = 0;
281
+
282
+ for (const file of files.slice(0, CODE_MAX_FILES)) {
283
+ const size = new TextEncoder().encode(file.content).byteLength;
284
+ if (totalBytes + size > CODE_MAX_TOTAL_BYTES) break;
285
+ totalBytes += size;
286
+ accepted.push(file);
287
+ }
288
+
289
+ const state = createCodeAnalysisState();
290
+ for (const file of accepted) {
291
+ analyzeCodeFile(state, file.filename, file.content);
292
+ addLooseTextSignals(state, file.filename, file.content);
293
+ }
294
+
295
+ const tokens: ImportedDesignToken[] = parseNamedLines(accepted);
296
+
297
+ for (const [cssVar, value] of Object.entries(state.cssCustomProperties)) {
298
+ const normalized = normalizeValue(value);
299
+ tokens.push({
300
+ name: friendlyName(cssVar),
301
+ cssVar,
302
+ value: normalized,
303
+ type: classifyVar(cssVar, normalized),
304
+ source: "CSS variables",
305
+ });
306
+ }
307
+
308
+ let importedColorIndex = 1;
309
+ for (const [key, value] of Object.entries(state.colors)) {
310
+ if (key.startsWith("--")) continue;
311
+ const normalized = normalizeValue(value);
312
+ const cssVar =
313
+ key === value
314
+ ? `--color-imported-${importedColorIndex++}`
315
+ : tokenVar("color", key);
316
+ tokens.push({
317
+ name: friendlyName(cssVar),
318
+ cssVar,
319
+ value: normalized,
320
+ type: "color",
321
+ source: "Colors",
322
+ });
323
+ }
324
+
325
+ for (const [key, value] of Object.entries(state.spacing)) {
326
+ if (key.startsWith("--")) continue;
327
+ const cssVar = tokenVar("spacing", key);
328
+ tokens.push({
329
+ name: friendlyName(cssVar),
330
+ cssVar,
331
+ value: normalizeValue(value),
332
+ type: "spacing",
333
+ source: "Spacing",
334
+ });
335
+ }
336
+
337
+ for (const [key, value] of Object.entries(state.borderRadius)) {
338
+ if (key.startsWith("--")) continue;
339
+ const cssVar = tokenVar("radius", key);
340
+ tokens.push({
341
+ name: friendlyName(cssVar),
342
+ cssVar,
343
+ value: normalizeValue(value),
344
+ type: "radius",
345
+ source: "Radius",
346
+ });
347
+ }
348
+
349
+ state.fonts.slice(0, 8).forEach((font, index) => {
350
+ const cssVar =
351
+ index === 0
352
+ ? "--font-heading"
353
+ : index === 1
354
+ ? "--font-body"
355
+ : tokenVar("font", font.family);
356
+ tokens.push({
357
+ name: friendlyName(cssVar),
358
+ cssVar,
359
+ value: font.family,
360
+ type: "typography",
361
+ source: font.source ?? "Fonts",
362
+ });
363
+ });
364
+
365
+ return {
366
+ filesAnalyzed: accepted.map((file) => file.filename),
367
+ tokens: uniqueTokenList(tokens).slice(0, 120),
368
+ };
369
+ }
370
+
371
+ export default defineAction({
372
+ description:
373
+ "Import design tokens into a design from pasted text, uploaded token/code files, " +
374
+ "or the current design files. Parses CSS variables, design.md-style notes, " +
375
+ "Tailwind/theme JSON, hard-coded colors, spacing, radii, and fonts, then " +
376
+ "persists the imported values through the same CSS-var tweak bridge used by " +
377
+ "apply-design-token-edit. For Figma/.fig and full local-code indexing, use " +
378
+ "the Builder-backed design-system import flow.",
379
+ schema: tokenImportSchema,
380
+ run: async ({ designId, source, files, text }) => {
381
+ await assertAccess("design", designId, "editor");
382
+
383
+ const db = getDb();
384
+ let importFiles: { filename: string; content: string }[] = [];
385
+ let sourceLabel = "Imported tokens";
386
+
387
+ if (source === "current-design") {
388
+ const designFiles = await db
389
+ .select({
390
+ filename: schema.designFiles.filename,
391
+ content: schema.designFiles.content,
392
+ })
393
+ .from(schema.designFiles)
394
+ .where(eq(schema.designFiles.designId, designId));
395
+ importFiles = designFiles;
396
+ sourceLabel = "Current design";
397
+ } else if (source === "paste") {
398
+ importFiles = [{ filename: "pasted-tokens.txt", content: text ?? "" }];
399
+ sourceLabel = "Pasted tokens";
400
+ } else {
401
+ importFiles = files ?? [];
402
+ sourceLabel =
403
+ importFiles.length === 1 ? importFiles[0].filename : "Imported files";
404
+ }
405
+
406
+ const { filesAnalyzed, tokens } = tokensFromFiles(importFiles);
407
+ if (tokens.length === 0) {
408
+ return {
409
+ designId,
410
+ source,
411
+ importedCount: 0,
412
+ skippedCount: importFiles.length - filesAnalyzed.length,
413
+ filesAnalyzed,
414
+ tokens: [],
415
+ resolvedCssVars: {},
416
+ deepLink: designDeepLink(designId),
417
+ };
418
+ }
419
+
420
+ const access = await resolveAccess("design", designId);
421
+ if (!access) throw new Error("Design not found");
422
+
423
+ let prevData: Record<string, unknown> = {};
424
+ if (access.resource.data) {
425
+ try {
426
+ const parsed = JSON.parse(access.resource.data);
427
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
428
+ prevData = parsed;
429
+ }
430
+ } catch {
431
+ // Stale/invalid JSON — preserve the write with a fresh object.
432
+ }
433
+ }
434
+
435
+ type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
436
+ const tweaks: TweakDef[] = Array.isArray(prevData.tweaks)
437
+ ? (prevData.tweaks as TweakDef[])
438
+ : [];
439
+ const cssVarToTweakId = new Map<string, string>();
440
+ for (const tweak of tweaks) {
441
+ if (tweak.cssVar) cssVarToTweakId.set(tweak.cssVar, tweak.id);
442
+ }
443
+
444
+ const prevSelections: Record<string, string | number | boolean> =
445
+ prevData.tweakSelections &&
446
+ typeof prevData.tweakSelections === "object" &&
447
+ !Array.isArray(prevData.tweakSelections)
448
+ ? (prevData.tweakSelections as Record<
449
+ string,
450
+ string | number | boolean
451
+ >)
452
+ : {};
453
+ const nextSelections = { ...prevSelections };
454
+
455
+ for (const token of tokens) {
456
+ const tweakId = cssVarToTweakId.get(token.cssVar);
457
+ nextSelections[tweakId ?? token.cssVar] = token.value;
458
+ }
459
+
460
+ const previousSources =
461
+ prevData.tokenImportSources &&
462
+ typeof prevData.tokenImportSources === "object" &&
463
+ !Array.isArray(prevData.tokenImportSources)
464
+ ? (prevData.tokenImportSources as Record<string, string>)
465
+ : {};
466
+ const tokenImportSources = { ...previousSources };
467
+ for (const token of tokens) {
468
+ tokenImportSources[token.cssVar] =
469
+ token.source === "CSS variables" || token.source === "Colors"
470
+ ? sourceLabel
471
+ : token.source;
472
+ }
473
+
474
+ const now = new Date().toISOString();
475
+ const previousImports = Array.isArray(prevData.tokenImports)
476
+ ? (prevData.tokenImports as unknown[])
477
+ : [];
478
+ const nextData: Record<string, unknown> = {
479
+ ...prevData,
480
+ tweakSelections: nextSelections,
481
+ tokenImportSources,
482
+ tokenImports: [
483
+ ...previousImports.slice(-9),
484
+ {
485
+ source,
486
+ sourceLabel,
487
+ tokenCount: tokens.length,
488
+ filesAnalyzed,
489
+ importedAt: now,
490
+ },
491
+ ],
492
+ tweaksAppliedAt: now,
493
+ };
494
+
495
+ await db
496
+ .update(schema.designs)
497
+ .set({ data: JSON.stringify(nextData), updatedAt: now })
498
+ .where(eq(schema.designs.id, designId));
499
+
500
+ return {
501
+ designId,
502
+ source,
503
+ importedCount: tokens.length,
504
+ skippedCount: importFiles.length - filesAnalyzed.length,
505
+ filesAnalyzed,
506
+ tokens,
507
+ resolvedCssVars: resolveTweaksToCssVars(tweaks, nextSelections),
508
+ deepLink: designDeepLink(designId),
509
+ };
510
+ },
511
+ link: ({ result }) => {
512
+ if (!result || typeof result !== "object") return null;
513
+ const designId = (result as { designId?: string }).designId;
514
+ if (!designId) return null;
515
+ return {
516
+ url: designDeepLink(designId),
517
+ label: "Open design",
518
+ view: "editor",
519
+ };
520
+ },
521
+ });
@@ -220,6 +220,12 @@ export default defineAction({
220
220
  string | number | boolean
221
221
  >)
222
222
  : {};
223
+ const tokenImportSources =
224
+ designData.tokenImportSources &&
225
+ typeof designData.tokenImportSources === "object" &&
226
+ !Array.isArray(designData.tokenImportSources)
227
+ ? (designData.tokenImportSources as Record<string, string>)
228
+ : {};
223
229
 
224
230
  // Cast tweaks array to the shape resolveTweaksToCssVars expects
225
231
  type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
@@ -232,7 +238,10 @@ export default defineAction({
232
238
  ...Object.keys(tweakSelections).filter(isDirectCssVarSelectionKey),
233
239
  ]);
234
240
  for (const [cssVar, value] of Object.entries(resolvedOverrides)) {
235
- rawTokens.set(cssVar, { value, source: "Tweaks" });
241
+ rawTokens.set(cssVar, {
242
+ value,
243
+ source: tokenImportSources[cssVar] ?? "Tweaks",
244
+ });
236
245
  }
237
246
 
238
247
  // ------------------------------------------------------------------