@agent-native/core 0.119.0 → 0.119.2

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 (274) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +23 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +4 -0
  5. package/corpus/core/src/agent/production-agent.ts +1 -0
  6. package/corpus/core/src/client/AssistantChat.tsx +11 -50
  7. package/corpus/core/src/client/ErrorBoundary.tsx +25 -19
  8. package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +1 -1
  9. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +23 -0
  10. package/corpus/core/src/client/components/ui/message-scroller.tsx +1 -9
  11. package/corpus/core/src/client/resources/ResourceTree.tsx +3 -3
  12. package/corpus/core/src/client/resources/ResourcesPanel.tsx +64 -60
  13. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +228 -0
  14. package/corpus/core/src/client/settings/SecretsSection.tsx +7 -5
  15. package/corpus/core/src/client/settings/SettingsPanel.tsx +67 -244
  16. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +88 -21
  17. package/corpus/core/src/client/settings/agent-settings-search.ts +0 -2
  18. package/corpus/core/src/client/settings/index.ts +7 -0
  19. package/corpus/core/src/client/vite-dev-recovery-script.ts +18 -1
  20. package/corpus/core/src/db/client.ts +65 -15
  21. package/corpus/core/src/db/index.ts +2 -0
  22. package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
  23. package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
  24. package/corpus/core/src/ingestion/index.ts +20 -0
  25. package/corpus/core/src/localization/default-messages.ts +1 -0
  26. package/corpus/core/src/server/email.ts +33 -1
  27. package/corpus/core/src/styles/agent-native.css +23 -0
  28. package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  29. package/corpus/core/src/templates/default/app/routes/settings.tsx +2 -0
  30. package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  31. package/corpus/core/src/vite/client.ts +76 -8
  32. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +28 -0
  33. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +3 -1
  34. package/corpus/templates/analytics/app/components/layout/Layout.tsx +9 -13
  35. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +2 -2
  36. package/corpus/templates/analytics/app/global.css +0 -5
  37. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +0 -13
  38. package/corpus/templates/analytics/app/lib/sql-query.ts +15 -2
  39. package/corpus/templates/analytics/app/pages/Settings.tsx +2 -24
  40. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +6 -0
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
  42. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +1 -1
  43. package/corpus/templates/analytics/changelog/2026-07-23-ask-analytics-is-better-centered-with-a-quieter-chat-rail-an.md +6 -0
  44. package/corpus/templates/analytics/changelog/2026-07-23-daily-dashboard-emails-load-read-heavy-panels-reliably.md +6 -0
  45. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +2 -2
  46. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +39 -5
  47. package/corpus/templates/analytics/server/lib/dashboard-report.ts +186 -62
  48. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +3 -0
  49. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +18 -2
  50. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +3 -0
  51. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +28 -0
  52. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -4
  53. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +1 -1
  54. package/corpus/templates/assets/app/global.css +0 -5
  55. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +0 -22
  56. package/corpus/templates/assets/app/routes/library.tsx +17 -9
  57. package/corpus/templates/assets/app/routes/settings.tsx +2 -0
  58. package/corpus/templates/assets/changelog/2026-07-23-create-and-chat-surfaces-are-better-centered-with-quieter-ch.md +6 -0
  59. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +28 -0
  60. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  61. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
  62. package/corpus/templates/brain/app/global.css +0 -9
  63. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +0 -12
  64. package/corpus/templates/brain/app/i18n-data.ts +1 -0
  65. package/corpus/templates/brain/app/lib/settings-navigation.ts +1 -1
  66. package/corpus/templates/brain/app/routes/settings.tsx +21 -22
  67. package/corpus/templates/brain/changelog/2026-07-23-ask-brain-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
  68. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +28 -0
  69. package/corpus/templates/calendar/app/pages/Settings.tsx +2 -0
  70. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  71. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  72. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  73. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  74. package/corpus/templates/chat/app/routes/settings.tsx +2 -0
  75. package/corpus/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  76. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +28 -0
  77. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +2 -0
  78. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +28 -0
  79. package/corpus/templates/content/app/routes/_app.settings.tsx +2 -0
  80. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +28 -0
  81. package/corpus/templates/design/AGENTS.md +18 -5
  82. package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
  83. package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
  84. package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
  85. package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
  86. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
  87. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
  88. package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
  89. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
  90. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
  91. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
  92. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
  93. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  94. package/corpus/templates/design/app/i18n-data.ts +150 -2
  95. package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
  96. package/corpus/templates/design/app/lib/design-import.ts +19 -1
  97. package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
  98. package/corpus/templates/design/app/routes/settings.tsx +2 -0
  99. package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
  100. package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
  101. package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
  102. package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
  103. package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
  104. package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
  105. package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
  106. package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
  107. package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
  108. package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
  109. package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
  110. package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
  111. package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
  112. package/corpus/templates/design/server/register-secrets.ts +14 -2
  113. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +28 -0
  114. package/corpus/templates/dispatch/app/routes/settings.tsx +2 -0
  115. package/corpus/templates/dispatch/changelog/2026-07-23-quieter-progressive-disclosure.md +6 -0
  116. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +28 -0
  117. package/corpus/templates/forms/app/global.css +0 -5
  118. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +0 -7
  119. package/corpus/templates/forms/app/root.tsx +1 -7
  120. package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -0
  121. package/corpus/templates/forms/changelog/2026-07-23-ask-forms-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
  122. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +28 -0
  123. package/corpus/templates/macros/app/routes/settings.tsx +2 -0
  124. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +28 -0
  125. package/corpus/templates/mail/app/pages/SettingsPage.tsx +3 -1
  126. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +28 -0
  127. package/corpus/templates/plan/app/components/layout/Layout.tsx +2 -2
  128. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +1 -13
  129. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +0 -4
  130. package/corpus/templates/plan/app/routes/settings.tsx +2 -0
  131. package/corpus/templates/plan/changelog/2026-07-23-ask-plan-is-now-a-clean-full-page-chat-surface-with-better-c.md +6 -0
  132. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +28 -0
  133. package/corpus/templates/slides/app/routes/settings.tsx +2 -0
  134. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +28 -0
  135. package/corpus/toolkit/CHANGELOG.md +6 -0
  136. package/corpus/toolkit/package.json +1 -1
  137. package/corpus/toolkit/src/chat-history.css +2 -2
  138. package/corpus/toolkit/src/composer/PromptComposer.tsx +0 -31
  139. package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +7 -2
  140. package/corpus/toolkit/src/styles.css +183 -36
  141. package/dist/a2a/client.d.ts +2 -0
  142. package/dist/a2a/client.d.ts.map +1 -1
  143. package/dist/a2a/client.js +2 -0
  144. package/dist/a2a/client.js.map +1 -1
  145. package/dist/agent/production-agent.d.ts.map +1 -1
  146. package/dist/agent/production-agent.js +1 -0
  147. package/dist/agent/production-agent.js.map +1 -1
  148. package/dist/client/AssistantChat.d.ts.map +1 -1
  149. package/dist/client/AssistantChat.js +11 -42
  150. package/dist/client/AssistantChat.js.map +1 -1
  151. package/dist/client/ErrorBoundary.d.ts.map +1 -1
  152. package/dist/client/ErrorBoundary.js +3 -2
  153. package/dist/client/ErrorBoundary.js.map +1 -1
  154. package/dist/client/agent-page/AgentTabFrame.js +1 -1
  155. package/dist/client/agent-page/AgentTabFrame.js.map +1 -1
  156. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  157. package/dist/client/agent-page/AgentTabsPage.js +13 -1
  158. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  159. package/dist/client/components/ui/message-scroller.d.ts.map +1 -1
  160. package/dist/client/components/ui/message-scroller.js +2 -2
  161. package/dist/client/components/ui/message-scroller.js.map +1 -1
  162. package/dist/client/resources/ResourceTree.js +2 -2
  163. package/dist/client/resources/ResourceTree.js.map +1 -1
  164. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  165. package/dist/client/resources/ResourcesPanel.js +11 -11
  166. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  167. package/dist/client/settings/AccountSettingsCard.d.ts +9 -0
  168. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -0
  169. package/dist/client/settings/AccountSettingsCard.js +78 -0
  170. package/dist/client/settings/AccountSettingsCard.js.map +1 -0
  171. package/dist/client/settings/SecretsSection.d.ts.map +1 -1
  172. package/dist/client/settings/SecretsSection.js +2 -2
  173. package/dist/client/settings/SecretsSection.js.map +1 -1
  174. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  175. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  176. package/dist/client/settings/SettingsPanel.js +25 -77
  177. package/dist/client/settings/SettingsPanel.js.map +1 -1
  178. package/dist/client/settings/SettingsTabsPage.d.ts +5 -1
  179. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  180. package/dist/client/settings/SettingsTabsPage.js +35 -9
  181. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  182. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  183. package/dist/client/settings/agent-settings-search.js +0 -2
  184. package/dist/client/settings/agent-settings-search.js.map +1 -1
  185. package/dist/client/settings/index.d.ts +2 -1
  186. package/dist/client/settings/index.d.ts.map +1 -1
  187. package/dist/client/settings/index.js +2 -1
  188. package/dist/client/settings/index.js.map +1 -1
  189. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  190. package/dist/client/vite-dev-recovery-script.js +18 -1
  191. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  192. package/dist/collab/routes.d.ts +1 -1
  193. package/dist/db/client.d.ts +18 -8
  194. package/dist/db/client.d.ts.map +1 -1
  195. package/dist/db/client.js +34 -11
  196. package/dist/db/client.js.map +1 -1
  197. package/dist/db/index.d.ts +1 -1
  198. package/dist/db/index.d.ts.map +1 -1
  199. package/dist/db/index.js.map +1 -1
  200. package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
  201. package/dist/ingestion/figma-node-to-html.js +14 -88
  202. package/dist/ingestion/figma-node-to-html.js.map +1 -1
  203. package/dist/ingestion/figma-paint-math.d.ts +157 -0
  204. package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
  205. package/dist/ingestion/figma-paint-math.js +274 -0
  206. package/dist/ingestion/figma-paint-math.js.map +1 -0
  207. package/dist/ingestion/index.d.ts +1 -0
  208. package/dist/ingestion/index.d.ts.map +1 -1
  209. package/dist/ingestion/index.js +1 -0
  210. package/dist/ingestion/index.js.map +1 -1
  211. package/dist/localization/default-messages.d.ts +1 -0
  212. package/dist/localization/default-messages.d.ts.map +1 -1
  213. package/dist/localization/default-messages.js +1 -0
  214. package/dist/localization/default-messages.js.map +1 -1
  215. package/dist/notifications/routes.d.ts +2 -2
  216. package/dist/observability/routes.d.ts +3 -3
  217. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  218. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  219. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  220. package/dist/secrets/routes.d.ts +6 -6
  221. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  222. package/dist/server/email.d.ts +1 -0
  223. package/dist/server/email.d.ts.map +1 -1
  224. package/dist/server/email.js +29 -1
  225. package/dist/server/email.js.map +1 -1
  226. package/dist/server/transcribe-voice.d.ts +1 -1
  227. package/dist/styles/agent-native.css +23 -0
  228. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  229. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
  230. package/dist/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  231. package/dist/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  232. package/dist/templates/chat/app/routes/settings.tsx +2 -0
  233. package/dist/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  234. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  235. package/dist/templates/default/app/routes/settings.tsx +2 -0
  236. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  237. package/dist/vite/client.d.ts.map +1 -1
  238. package/dist/vite/client.js +68 -6
  239. package/dist/vite/client.js.map +1 -1
  240. package/package.json +2 -2
  241. package/src/a2a/client.ts +4 -0
  242. package/src/agent/production-agent.ts +1 -0
  243. package/src/client/AssistantChat.tsx +11 -50
  244. package/src/client/ErrorBoundary.tsx +25 -19
  245. package/src/client/agent-page/AgentTabFrame.tsx +1 -1
  246. package/src/client/agent-page/AgentTabsPage.tsx +23 -0
  247. package/src/client/components/ui/message-scroller.tsx +1 -9
  248. package/src/client/resources/ResourceTree.tsx +3 -3
  249. package/src/client/resources/ResourcesPanel.tsx +64 -60
  250. package/src/client/settings/AccountSettingsCard.tsx +228 -0
  251. package/src/client/settings/SecretsSection.tsx +7 -5
  252. package/src/client/settings/SettingsPanel.tsx +67 -244
  253. package/src/client/settings/SettingsTabsPage.tsx +88 -21
  254. package/src/client/settings/agent-settings-search.ts +0 -2
  255. package/src/client/settings/index.ts +7 -0
  256. package/src/client/vite-dev-recovery-script.ts +18 -1
  257. package/src/db/client.ts +65 -15
  258. package/src/db/index.ts +2 -0
  259. package/src/ingestion/figma-node-to-html.ts +20 -99
  260. package/src/ingestion/figma-paint-math.ts +371 -0
  261. package/src/ingestion/index.ts +20 -0
  262. package/src/localization/default-messages.ts +1 -0
  263. package/src/server/email.ts +33 -1
  264. package/src/styles/agent-native.css +23 -0
  265. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
  266. package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
  267. package/src/templates/chat/app/components/layout/Sidebar.tsx +1 -1
  268. package/src/templates/chat/app/hooks/use-navigation-state.ts +0 -15
  269. package/src/templates/chat/app/routes/settings.tsx +2 -0
  270. package/src/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  271. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
  272. package/src/templates/default/app/routes/settings.tsx +2 -0
  273. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
  274. package/src/vite/client.ts +76 -8
@@ -3,6 +3,7 @@ import {
3
3
  IconHistory,
4
4
  IconSearch,
5
5
  IconSettings,
6
+ IconUserCircle,
6
7
  IconUsers,
7
8
  IconX,
8
9
  } from "@tabler/icons-react";
@@ -15,6 +16,7 @@ import {
15
16
  type ComponentType,
16
17
  type ReactNode,
17
18
  } from "react";
19
+ import { Link } from "react-router";
18
20
 
19
21
  import { cn } from "../utils.js";
20
22
 
@@ -50,6 +52,8 @@ export interface SettingsTabItem {
50
52
  label: string;
51
53
  icon?: SettingsTabIcon;
52
54
  content: ReactNode;
55
+ /** Optional route for settings that live on a canonical page elsewhere. */
56
+ href?: string;
53
57
  /** Whether a parent surface may expose a personal/organization scope for this tab. */
54
58
  scopeAware?: boolean;
55
59
  /**
@@ -66,10 +70,12 @@ export interface SettingsTabItem {
66
70
 
67
71
  export interface SettingsTabsPageProps {
68
72
  general: ReactNode;
73
+ account?: ReactNode;
69
74
  team?: ReactNode;
70
75
  whatsNew?: ReactNode;
71
76
  extraTabs?: SettingsTabItem[];
72
77
  generalLabel?: string;
78
+ accountLabel?: string;
73
79
  teamLabel?: string;
74
80
  whatsNewLabel?: string;
75
81
  ariaLabel?: string;
@@ -182,10 +188,12 @@ function isEditableElement(element: Element | null): boolean {
182
188
 
183
189
  export function SettingsTabsPage({
184
190
  general,
191
+ account,
185
192
  team,
186
193
  whatsNew,
187
194
  extraTabs = [],
188
195
  generalLabel = "General",
196
+ accountLabel = "Account",
189
197
  teamLabel = "Team",
190
198
  whatsNewLabel = "What's new",
191
199
  ariaLabel = "Settings sections",
@@ -217,6 +225,15 @@ export function SettingsTabsPage({
217
225
  searchEntries: generalSearchEntries,
218
226
  },
219
227
  ];
228
+ if (account) {
229
+ next.push({
230
+ id: "account",
231
+ label: accountLabel,
232
+ icon: IconUserCircle,
233
+ content: account,
234
+ keywords: "profile photo avatar identity signed in email name",
235
+ });
236
+ }
220
237
  next.push(...extraTabs);
221
238
  if (team && !hasOrganizationTab) {
222
239
  next.push({
@@ -238,6 +255,8 @@ export function SettingsTabsPage({
238
255
  }
239
256
  return next;
240
257
  }, [
258
+ account,
259
+ accountLabel,
241
260
  extraTabs,
242
261
  general,
243
262
  generalLabel,
@@ -271,7 +290,9 @@ export function SettingsTabsPage({
271
290
  const activeTab = isControlled ? value : internalTab;
272
291
  const [query, setQuery] = useState("");
273
292
  const designSystem = useDesignSystem();
274
- const hasCustomTabs = Boolean(designSystem?.components?.Tabs);
293
+ const hasLinkedTabs = tabs.some((tab) => Boolean(tab.href));
294
+ const hasCustomTabs =
295
+ Boolean(designSystem?.components?.Tabs) && !hasLinkedTabs;
275
296
 
276
297
  const changeTab = useCallback(
277
298
  (tabId: string) => {
@@ -493,15 +514,16 @@ export function SettingsTabsPage({
493
514
  ) : (
494
515
  results.map((entry) => {
495
516
  const Icon = entry.icon;
496
- return (
497
- <button
498
- key={entry.id}
499
- type="button"
500
- role="option"
501
- aria-selected={false}
502
- onClick={() => selectEntry(entry)}
503
- className="flex items-start gap-2 rounded-md px-2.5 py-2 text-start text-sm text-foreground transition-colors hover:bg-accent/60"
504
- >
517
+ const tab = tabs.find(
518
+ (candidate) => candidate.id === entry.tabId,
519
+ );
520
+ const resultHref = tab?.href
521
+ ? entry.hash
522
+ ? `${tab.href.split("#", 1)[0]}#${entry.hash.replace(/^#/, "")}`
523
+ : tab.href
524
+ : null;
525
+ const result = (
526
+ <>
505
527
  {Icon ? (
506
528
  <Icon className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
507
529
  ) : null}
@@ -513,6 +535,28 @@ export function SettingsTabsPage({
513
535
  {entry.description ?? entry.tabLabel}
514
536
  </span>
515
537
  </span>
538
+ </>
539
+ );
540
+ return resultHref ? (
541
+ <Link
542
+ key={entry.id}
543
+ to={resultHref}
544
+ role="option"
545
+ aria-selected={false}
546
+ className="flex items-start gap-2 rounded-md px-2.5 py-2 text-start text-sm text-foreground transition-colors hover:bg-accent/60"
547
+ >
548
+ {result}
549
+ </Link>
550
+ ) : (
551
+ <button
552
+ key={entry.id}
553
+ type="button"
554
+ role="option"
555
+ aria-selected={false}
556
+ onClick={() => selectEntry(entry)}
557
+ className="flex items-start gap-2 rounded-md px-2.5 py-2 text-start text-sm text-foreground transition-colors hover:bg-accent/60"
558
+ >
559
+ {result}
516
560
  </button>
517
561
  );
518
562
  })
@@ -558,6 +602,39 @@ export function SettingsTabsPage({
558
602
  {group.tabs.map((tab) => {
559
603
  const Icon = tab.icon;
560
604
  const selected = tab.id === selectedTab?.id;
605
+ const tabContent = (
606
+ <>
607
+ {Icon ? (
608
+ <Icon
609
+ className={cn(
610
+ "size-4 shrink-0",
611
+ selected
612
+ ? "text-foreground"
613
+ : "text-muted-foreground",
614
+ )}
615
+ />
616
+ ) : null}
617
+ <span className="truncate">{tab.label}</span>
618
+ </>
619
+ );
620
+ if (tab.href) {
621
+ return (
622
+ <Link
623
+ key={tab.id}
624
+ role="tab"
625
+ aria-selected={selected}
626
+ to={tab.href}
627
+ className={cn(
628
+ "flex min-h-9 shrink-0 items-center gap-2 rounded-md px-3 py-2 text-start text-sm font-medium transition-colors sm:w-full",
629
+ selected
630
+ ? "bg-accent text-foreground"
631
+ : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
632
+ )}
633
+ >
634
+ {tabContent}
635
+ </Link>
636
+ );
637
+ }
561
638
  return (
562
639
  <button
563
640
  key={tab.id}
@@ -577,17 +654,7 @@ export function SettingsTabsPage({
577
654
  : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
578
655
  )}
579
656
  >
580
- {Icon ? (
581
- <Icon
582
- className={cn(
583
- "size-4 shrink-0",
584
- selected
585
- ? "text-foreground"
586
- : "text-muted-foreground",
587
- )}
588
- />
589
- ) : null}
590
- <span className="truncate">{tab.label}</span>
657
+ {tabContent}
591
658
  </button>
592
659
  );
593
660
  })}
@@ -42,7 +42,6 @@ export const SETTINGS_SECTION_IDS = new Set<SettingsSectionId>([
42
42
  ]);
43
43
 
44
44
  export const ALL_SETTINGS_SECTIONS: readonly SettingsSectionId[] = [
45
- "account",
46
45
  "llm",
47
46
  "app-models",
48
47
  "limits",
@@ -81,7 +80,6 @@ export const CONNECTION_SETTINGS_SECTIONS: readonly SettingsSectionId[] = [
81
80
  ];
82
81
 
83
82
  export const WORKSPACE_SETTINGS_SECTIONS: readonly SettingsSectionId[] = [
84
- "account",
85
83
  "demo-mode",
86
84
  "hosting",
87
85
  "database",
@@ -1,4 +1,5 @@
1
1
  export {
2
+ AgentSettingsContent,
2
3
  SettingsPanel,
3
4
  useAgentSettingsTabs,
4
5
  type SettingsPanelProps,
@@ -13,6 +14,12 @@ export {
13
14
  type SettingsTabItem,
14
15
  type SettingsTabsPageProps,
15
16
  } from "./SettingsTabsPage.js";
17
+ export {
18
+ AccountSettingsCard,
19
+ AccountSettingsForm,
20
+ type AccountSettingsCardProps,
21
+ type AccountSettingsFormProps,
22
+ } from "./AccountSettingsCard.js";
16
23
  export {
17
24
  openBuilderConnectPopup,
18
25
  useBuilderConnectFlow,
@@ -22,8 +22,13 @@ export function getViteDevRecoveryScript(): string {
22
22
  if (params.has(${embedTokenParam}) || embedded === "1" || embedded === "true") return;
23
23
  } catch (e) {}
24
24
 
25
+ var INSTALL_KEY = "__agentNativeViteDevRecoveryInstalled";
26
+ if (window[INSTALL_KEY]) return;
27
+ window[INSTALL_KEY] = true;
28
+
25
29
  var RELOAD_KEY = "__an_optimize_reload";
26
30
  var MAX_RELOADS = 3;
31
+ var MIN_RELOAD_INTERVAL_MS = 2000;
27
32
  var RESET_AFTER_MS = 8000;
28
33
 
29
34
  var reloadTimer = null;
@@ -100,9 +105,21 @@ export function getViteDevRecoveryScript(): string {
100
105
  if (history.length >= 1) {
101
106
  showOverlay("Updating dev server\\u2026", "Reloading the page");
102
107
  }
103
- reloadTimer = setTimeout(function() { window.location.reload(); }, 300);
108
+ var lastReloadAt = history.length ? history[history.length - 1] : 0;
109
+ var delay = Math.max(
110
+ 300,
111
+ MIN_RELOAD_INTERVAL_MS - Math.max(0, Date.now() - lastReloadAt),
112
+ );
113
+ reloadTimer = setTimeout(function() { window.location.reload(); }, delay);
104
114
  }
105
115
 
116
+ window.addEventListener("vite:beforeFullReload", function() {
117
+ if (reloadTimer) {
118
+ clearTimeout(reloadTimer);
119
+ reloadTimer = null;
120
+ }
121
+ });
122
+
106
123
  function looksLikeViteFailureMessage(message) {
107
124
  if (!message) return false;
108
125
  return message.indexOf("Failed to fetch dynamically imported module") !== -1
@@ -24,13 +24,28 @@ const loggedNeonPools = new WeakSet<object>();
24
24
 
25
25
  export type Dialect = "sqlite" | "postgres" | "d1";
26
26
 
27
+ export interface DbExecQuery {
28
+ sql: string;
29
+ args?: unknown[];
30
+ /**
31
+ * Client-side wall-clock budget for this statement. Use only for idempotent
32
+ * reads unless the caller can safely tolerate a late write completing.
33
+ */
34
+ timeoutMs?: number;
35
+ /** Maximum connection-level attempts for this statement, including the first. */
36
+ maxAttempts?: number;
37
+ }
38
+
39
+ export type DbExecStatement = string | DbExecQuery;
40
+
27
41
  export interface DbExec {
28
- execute(
29
- sql: string | { sql: string; args?: unknown[] },
30
- ): Promise<{ rows: any[]; rowsAffected: number }>;
42
+ execute(sql: DbExecStatement): Promise<{
43
+ rows: any[];
44
+ rowsAffected: number;
45
+ }>;
31
46
  transaction?<T>(fn: (tx: DbExec) => Promise<T>): Promise<T>;
32
47
  atomicBatch?(
33
- statements: readonly (string | { sql: string; args?: unknown[] })[],
48
+ statements: readonly DbExecStatement[],
34
49
  ): Promise<Array<{ rows: any[]; rowsAffected: number }>>;
35
50
  /**
36
51
  * Release the underlying connection/pool held by this exec.
@@ -613,7 +628,7 @@ export function sqliteToPostgresParams(sql: string): string {
613
628
  return out;
614
629
  }
615
630
 
616
- function sqlAndArgs(sql: string | { sql: string; args?: unknown[] }): {
631
+ function sqlAndArgs(sql: DbExecStatement): {
617
632
  rawSql: string;
618
633
  args: unknown[];
619
634
  } {
@@ -622,6 +637,27 @@ function sqlAndArgs(sql: string | { sql: string; args?: unknown[] }): {
622
637
  : { rawSql: sql.sql, args: sql.args || [] };
623
638
  }
624
639
 
640
+ export function dbExecQueryBudget(statement: DbExecStatement): {
641
+ timeoutMs: number;
642
+ maxAttempts: number;
643
+ } {
644
+ if (typeof statement === "string") {
645
+ return { timeoutMs: dbOpTimeoutMs(), maxAttempts: 3 };
646
+ }
647
+ const timeoutMs = Number(statement.timeoutMs);
648
+ const maxAttempts = Number(statement.maxAttempts);
649
+ return {
650
+ timeoutMs:
651
+ Number.isFinite(timeoutMs) && timeoutMs > 0
652
+ ? Math.floor(timeoutMs)
653
+ : dbOpTimeoutMs(),
654
+ maxAttempts:
655
+ Number.isFinite(maxAttempts) && maxAttempts > 0
656
+ ? Math.floor(maxAttempts)
657
+ : 3,
658
+ };
659
+ }
660
+
625
661
  function explicitTransaction(
626
662
  execute: DbExec["execute"],
627
663
  begin = "BEGIN",
@@ -1173,8 +1209,10 @@ async function createDbExecInternal(
1173
1209
  async function queryNeonClient(
1174
1210
  client: any,
1175
1211
  sql: Parameters<DbExec["execute"]>[0],
1212
+ timeoutOverrideMs?: number,
1176
1213
  ) {
1177
1214
  const { rawSql, args } = sqlAndArgs(sql);
1215
+ const { timeoutMs } = dbExecQueryBudget(sql);
1178
1216
  const pgSql = sqliteToPostgresParams(rawSql);
1179
1217
  const result = await withDbTimeout(
1180
1218
  "query",
@@ -1183,7 +1221,7 @@ async function createDbExecInternal(
1183
1221
  rows: unknown[];
1184
1222
  rowCount?: number;
1185
1223
  }>,
1186
- dbOpTimeoutMs(),
1224
+ timeoutOverrideMs ?? timeoutMs,
1187
1225
  );
1188
1226
  return {
1189
1227
  rows: result.rows,
@@ -1192,6 +1230,7 @@ async function createDbExecInternal(
1192
1230
  }
1193
1231
  return {
1194
1232
  async execute(sql) {
1233
+ const { timeoutMs, maxAttempts } = dbExecQueryBudget(sql);
1195
1234
  if (bgHttp) {
1196
1235
  // HTTP-per-query path (poolQueryViaFetch=true): no pool.connect(), no
1197
1236
  // persistent socket to stall. queryNeonClient calls pool.query(),
@@ -1199,12 +1238,15 @@ async function createDbExecInternal(
1199
1238
  return retryOnConnectionError<{
1200
1239
  rows: unknown[];
1201
1240
  rowsAffected: number;
1202
- }>(() => queryNeonClient(pool, sql));
1241
+ }>(() => queryNeonClient(pool, sql), maxAttempts);
1203
1242
  }
1204
1243
  const result = await retryOnConnectionError<{
1205
1244
  rows: unknown[];
1206
1245
  rowsAffected: number;
1207
1246
  }>(async () => {
1247
+ const attemptStartedAt = Date.now();
1248
+ const remainingAttemptMs = () =>
1249
+ Math.max(1, timeoutMs - (Date.now() - attemptStartedAt));
1208
1250
  // Bound the pooled-connection ACQUIRE, not just the query below.
1209
1251
  // Neon's pooler can stall on `connect()` when cold or exhausted,
1210
1252
  // and that happens BEFORE `client.query`, so the query-level
@@ -1222,7 +1264,7 @@ async function createDbExecInternal(
1222
1264
  if (acquireTimedOut) c.release();
1223
1265
  return c;
1224
1266
  }),
1225
- dbOpTimeoutMs(),
1267
+ remainingAttemptMs(),
1226
1268
  () => {
1227
1269
  acquireTimedOut = true;
1228
1270
  },
@@ -1235,14 +1277,18 @@ async function createDbExecInternal(
1235
1277
  };
1236
1278
 
1237
1279
  try {
1238
- const result = await queryNeonClient(client, sql);
1280
+ const result = await queryNeonClient(
1281
+ client,
1282
+ sql,
1283
+ remainingAttemptMs(),
1284
+ );
1239
1285
  releaseClient();
1240
1286
  return result;
1241
1287
  } catch (err) {
1242
1288
  releaseClient(isConnectionError(err) ? true : undefined);
1243
1289
  throw err;
1244
1290
  }
1245
- });
1291
+ }, maxAttempts);
1246
1292
  return {
1247
1293
  rows: result.rows,
1248
1294
  rowsAffected: result.rowsAffected,
@@ -1310,6 +1356,7 @@ async function createDbExecInternal(
1310
1356
  try {
1311
1357
  const rawSql = typeof sql === "string" ? sql : sql.sql;
1312
1358
  const args = typeof sql === "string" ? [] : sql.args || [];
1359
+ const { timeoutMs } = dbExecQueryBudget(sql);
1313
1360
  const pgSql = sqliteToPostgresParams(rawSql);
1314
1361
  const result = await withDbTimeout<
1315
1362
  ArrayLike<unknown> & { count?: number }
@@ -1319,7 +1366,7 @@ async function createDbExecInternal(
1319
1366
  conn.unsafe(pgSql, args as any[]) as Promise<
1320
1367
  ArrayLike<unknown> & { count?: number }
1321
1368
  >,
1322
- dbOpTimeoutMs(),
1369
+ timeoutMs,
1323
1370
  () => {
1324
1371
  timedOut = true;
1325
1372
  disposePostgresPoolEventually(conn, "timed-out worker query");
@@ -1351,6 +1398,7 @@ async function createDbExecInternal(
1351
1398
  const tx: DbExec = {
1352
1399
  async execute(sql) {
1353
1400
  const { rawSql, args } = sqlAndArgs(sql);
1401
+ const { timeoutMs } = dbExecQueryBudget(sql);
1354
1402
  const pgSql = sqliteToPostgresParams(rawSql);
1355
1403
  const result = await withDbTimeout<
1356
1404
  ArrayLike<unknown> & { count?: number }
@@ -1360,7 +1408,7 @@ async function createDbExecInternal(
1360
1408
  txSql.unsafe(pgSql, args as any[]) as Promise<
1361
1409
  ArrayLike<unknown> & { count?: number }
1362
1410
  >,
1363
- dbOpTimeoutMs(),
1411
+ timeoutMs,
1364
1412
  );
1365
1413
  return {
1366
1414
  rows: Array.from(result),
@@ -1402,6 +1450,7 @@ async function createDbExecInternal(
1402
1450
  return {
1403
1451
  async execute(sql) {
1404
1452
  const { rawSql, args } = sqlAndArgs(sql);
1453
+ const { timeoutMs, maxAttempts } = dbExecQueryBudget(sql);
1405
1454
  const pgSql = sqliteToPostgresParams(rawSql);
1406
1455
  const result = await retryOnConnectionError<
1407
1456
  ArrayLike<unknown> & { count?: number }
@@ -1411,10 +1460,10 @@ async function createDbExecInternal(
1411
1460
  return withDbTimeout(
1412
1461
  "query",
1413
1462
  () => query,
1414
- dbOpTimeoutMs(),
1463
+ timeoutMs,
1415
1464
  () => recyclePool(queryPool),
1416
1465
  );
1417
- });
1466
+ }, maxAttempts);
1418
1467
  return {
1419
1468
  rows: Array.from(result),
1420
1469
  rowsAffected: result.count ?? 0,
@@ -1425,6 +1474,7 @@ async function createDbExecInternal(
1425
1474
  const tx: DbExec = {
1426
1475
  async execute(sql) {
1427
1476
  const { rawSql, args } = sqlAndArgs(sql);
1477
+ const { timeoutMs } = dbExecQueryBudget(sql);
1428
1478
  const pgSql = sqliteToPostgresParams(rawSql);
1429
1479
  const result = await withDbTimeout<
1430
1480
  ArrayLike<unknown> & { count?: number }
@@ -1434,7 +1484,7 @@ async function createDbExecInternal(
1434
1484
  txSql.unsafe(pgSql, args as any[]) as Promise<
1435
1485
  ArrayLike<unknown> & { count?: number }
1436
1486
  >,
1437
- dbOpTimeoutMs(),
1487
+ timeoutMs,
1438
1488
  );
1439
1489
  return {
1440
1490
  rows: Array.from(result),
@@ -46,6 +46,8 @@ export {
46
46
  closeDbExec,
47
47
  type DbExec,
48
48
  type DbExecConfig,
49
+ type DbExecQuery,
50
+ type DbExecStatement,
49
51
  type Dialect,
50
52
  } from "./client.js";
51
53
  export { table, text, integer, now } from "./schema.js";
@@ -62,6 +62,15 @@
62
62
  * mismatch.
63
63
  */
64
64
 
65
+ import {
66
+ cssBlendMode,
67
+ gradientAngleDegrees as gradientAngleDegreesMath,
68
+ remapLinearStopPosition as remapLinearStopPositionMath,
69
+ resolveGradientHandles,
70
+ vectorLength,
71
+ type GradientHandles,
72
+ } from "./figma-paint-math.js";
73
+
65
74
  export interface FigmaColor {
66
75
  r: number;
67
76
  g: number;
@@ -422,16 +431,8 @@ class FidelityTracker {
422
431
  // Gradient angle / position derivation
423
432
  // ---------------------------------------------------------------------------
424
433
 
425
- interface GradientGeometry {
426
- start: { x: number; y: number };
427
- end: { x: number; y: number };
428
- width: { x: number; y: number };
429
- }
430
-
431
- function resolveGradientGeometry(paint: FigmaPaint): GradientGeometry | null {
432
- const handles = paint.gradientHandlePositions;
433
- if (!handles || handles.length < 3) return null;
434
- return { start: handles[0]!, end: handles[1]!, width: handles[2]! };
434
+ function resolveGradientGeometry(paint: FigmaPaint): GradientHandles | null {
435
+ return resolveGradientHandles(paint.gradientHandlePositions);
435
436
  }
436
437
 
437
438
  /**
@@ -453,13 +454,7 @@ export function gradientAngleDegrees(
453
454
  paint: FigmaPaint,
454
455
  box: { width: number; height: number },
455
456
  ): number | null {
456
- const geometry = resolveGradientGeometry(paint);
457
- if (!geometry) return null;
458
- const dx = (geometry.end.x - geometry.start.x) * box.width;
459
- const dy = (geometry.end.y - geometry.start.y) * box.height;
460
- const angleRad = Math.atan2(dy, dx);
461
- const angleDeg = (angleRad * 180) / Math.PI + 90;
462
- return ((angleDeg % 360) + 360) % 360;
457
+ return gradientAngleDegreesMath(paint, box);
463
458
  }
464
459
 
465
460
  function gradientStopsCss(
@@ -478,59 +473,12 @@ function gradientStopsCss(
478
473
  .join(", ");
479
474
  }
480
475
 
481
- /**
482
- * CSS `linear-gradient(angle, ...)` always stretches its 0%/100% stops
483
- * across the box's FULL diagonal extent at that angle (the CSS spec's
484
- * "gradient line" always spans corner-to-corner) -- it has no way to say
485
- * "start partway in, end partway in" the way Figma's actual gradient handles
486
- * can (a designer can drag the start/end handles anywhere, including short
487
- * of the shape's edges, or past them). Figma's own stop positions are
488
- * fractions of the literal start-handle-to-end-handle distance, which only
489
- * happens to coincide with the CSS full-box span when the handles are
490
- * dragged exactly corner-to-corner -- a common case, but far from the only
491
- * one, and the divergence gets worse the more the box's aspect ratio departs
492
- * from square (rotated/skewed handles included, e.g. gradientTransform-authored
493
- * paints). This projects each Figma stop's real pixel position onto the same
494
- * angle CSS will use and re-expresses it as a percentage of the CSS line's
495
- * length, so a partial/offset gradient renders at the same actual pixel
496
- * positions Figma draws it at instead of silently stretching to fill the box.
497
- */
498
476
  function remapLinearStopPosition(
499
- geometry: GradientGeometry,
477
+ geometry: GradientHandles,
500
478
  box: { width: number; height: number },
501
479
  angleDeg: number,
502
480
  ): (position: number) => number {
503
- const angleRad = (angleDeg * Math.PI) / 180;
504
- const ux = Math.sin(angleRad);
505
- const uy = -Math.cos(angleRad);
506
- const lineLength = box.width * Math.abs(ux) + box.height * Math.abs(uy);
507
- if (lineLength < 1e-6) return (position) => position;
508
- const startPx = {
509
- x: geometry.start.x * box.width,
510
- y: geometry.start.y * box.height,
511
- };
512
- const endPx = {
513
- x: geometry.end.x * box.width,
514
- y: geometry.end.y * box.height,
515
- };
516
- const centerX = box.width / 2;
517
- const centerY = box.height / 2;
518
- return (position: number) => {
519
- const pointX = startPx.x + position * (endPx.x - startPx.x);
520
- const pointY = startPx.y + position * (endPx.y - startPx.y);
521
- const projected = (pointX - centerX) * ux + (pointY - centerY) * uy;
522
- return (projected + lineLength / 2) / lineLength;
523
- };
524
- }
525
-
526
- function vectorLength(
527
- from: { x: number; y: number },
528
- to: { x: number; y: number },
529
- box: { width: number; height: number },
530
- ): number {
531
- const dx = (to.x - from.x) * box.width;
532
- const dy = (to.y - from.y) * box.height;
533
- return Math.sqrt(dx * dx + dy * dy);
481
+ return remapLinearStopPositionMath(geometry, box, angleDeg);
534
482
  }
535
483
 
536
484
  /**
@@ -960,49 +908,22 @@ function buildEffects(
960
908
  // Blend modes
961
909
  // ---------------------------------------------------------------------------
962
910
 
963
- const CSS_BLEND_MODES = new Set([
964
- "multiply",
965
- "screen",
966
- "overlay",
967
- "darken",
968
- "lighten",
969
- "color-dodge",
970
- "color-burn",
971
- "hard-light",
972
- "soft-light",
973
- "difference",
974
- "exclusion",
975
- "hue",
976
- "saturation",
977
- "color",
978
- "luminosity",
979
- ]);
980
-
981
- const FIGMA_ONLY_BLEND_MODE_FALLBACK: Record<string, string> = {
982
- LINEAR_BURN: "multiply",
983
- LINEAR_DODGE: "plus-lighter",
984
- LIGHTER: "plus-lighter",
985
- DARKER: "darken",
986
- };
987
-
988
911
  function buildBlendMode(
989
912
  node: FigmaNode,
990
913
  tracker: FidelityTracker,
991
914
  ): string | undefined {
992
915
  const mode = node.blendMode;
993
- if (!mode || mode === "PASS_THROUGH" || mode === "NORMAL") return undefined;
994
- const cssMode = mode.toLowerCase().replace(/_/g, "-");
995
- if (CSS_BLEND_MODES.has(cssMode)) return cssMode;
996
- const fallback = FIGMA_ONLY_BLEND_MODE_FALLBACK[mode];
997
- if (fallback) {
916
+ if (!mode) return undefined;
917
+ const result = cssBlendMode(mode);
918
+ if (!result) return undefined;
919
+ if (result.verdict === "approximated") {
998
920
  tracker.record(
999
921
  node,
1000
922
  "approximated",
1001
- `Figma blend mode "${mode}" has no CSS equivalent; approximated as mix-blend-mode: ${fallback}.`,
923
+ `Figma blend mode "${mode}" has no CSS equivalent; approximated as mix-blend-mode: ${result.cssMode}.`,
1002
924
  );
1003
- return fallback;
1004
925
  }
1005
- return undefined;
926
+ return result.cssMode;
1006
927
  }
1007
928
 
1008
929
  // ---------------------------------------------------------------------------