@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +23 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +4 -0
- package/corpus/core/src/agent/production-agent.ts +1 -0
- package/corpus/core/src/client/AssistantChat.tsx +11 -50
- package/corpus/core/src/client/ErrorBoundary.tsx +25 -19
- package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +1 -1
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +23 -0
- package/corpus/core/src/client/components/ui/message-scroller.tsx +1 -9
- package/corpus/core/src/client/resources/ResourceTree.tsx +3 -3
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +64 -60
- package/corpus/core/src/client/settings/AccountSettingsCard.tsx +228 -0
- package/corpus/core/src/client/settings/SecretsSection.tsx +7 -5
- package/corpus/core/src/client/settings/SettingsPanel.tsx +67 -244
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +88 -21
- package/corpus/core/src/client/settings/agent-settings-search.ts +0 -2
- package/corpus/core/src/client/settings/index.ts +7 -0
- package/corpus/core/src/client/vite-dev-recovery-script.ts +18 -1
- package/corpus/core/src/db/client.ts +65 -15
- package/corpus/core/src/db/index.ts +2 -0
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/core/src/localization/default-messages.ts +1 -0
- package/corpus/core/src/server/email.ts +33 -1
- package/corpus/core/src/styles/agent-native.css +23 -0
- package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/core/src/templates/default/app/routes/settings.tsx +2 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/core/src/vite/client.ts +76 -8
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +3 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +9 -13
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +2 -2
- package/corpus/templates/analytics/app/global.css +0 -5
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +0 -13
- package/corpus/templates/analytics/app/lib/sql-query.ts +15 -2
- package/corpus/templates/analytics/app/pages/Settings.tsx +2 -24
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +6 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-07-23-ask-analytics-is-better-centered-with-a-quieter-chat-rail-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-daily-dashboard-emails-load-read-heavy-panels-reliably.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +2 -2
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +39 -5
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +186 -62
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +3 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +18 -2
- package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +3 -0
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -4
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/assets/app/global.css +0 -5
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +0 -22
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/assets/app/routes/settings.tsx +2 -0
- package/corpus/templates/assets/changelog/2026-07-23-create-and-chat-surfaces-are-better-centered-with-quieter-ch.md +6 -0
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/brain/app/global.css +0 -9
- package/corpus/templates/brain/app/hooks/use-navigation-state.ts +0 -12
- package/corpus/templates/brain/app/i18n-data.ts +1 -0
- package/corpus/templates/brain/app/lib/settings-navigation.ts +1 -1
- package/corpus/templates/brain/app/routes/settings.tsx +21 -22
- package/corpus/templates/brain/changelog/2026-07-23-ask-brain-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +2 -0
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +0 -15
- package/corpus/templates/chat/app/routes/settings.tsx +2 -0
- package/corpus/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +2 -0
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/content/app/routes/_app.settings.tsx +2 -0
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/app/routes/settings.tsx +2 -0
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/dispatch/app/routes/settings.tsx +2 -0
- package/corpus/templates/dispatch/changelog/2026-07-23-quieter-progressive-disclosure.md +6 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/forms/app/global.css +0 -5
- package/corpus/templates/forms/app/hooks/use-navigation-state.ts +0 -7
- package/corpus/templates/forms/app/root.tsx +1 -7
- package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -0
- package/corpus/templates/forms/changelog/2026-07-23-ask-forms-is-better-centered-with-quieter-chat-history-and-a.md +6 -0
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/macros/app/routes/settings.tsx +2 -0
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +3 -1
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +2 -2
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +1 -13
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +0 -4
- package/corpus/templates/plan/app/routes/settings.tsx +2 -0
- package/corpus/templates/plan/changelog/2026-07-23-ask-plan-is-now-a-clean-full-page-chat-surface-with-better-c.md +6 -0
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/templates/slides/app/routes/settings.tsx +2 -0
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +28 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/chat-history.css +2 -2
- package/corpus/toolkit/src/composer/PromptComposer.tsx +0 -31
- package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +7 -2
- package/corpus/toolkit/src/styles.css +183 -36
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +2 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +1 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -42
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ErrorBoundary.d.ts.map +1 -1
- package/dist/client/ErrorBoundary.js +3 -2
- package/dist/client/ErrorBoundary.js.map +1 -1
- package/dist/client/agent-page/AgentTabFrame.js +1 -1
- package/dist/client/agent-page/AgentTabFrame.js.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +13 -1
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/components/ui/message-scroller.d.ts.map +1 -1
- package/dist/client/components/ui/message-scroller.js +2 -2
- package/dist/client/components/ui/message-scroller.js.map +1 -1
- package/dist/client/resources/ResourceTree.js +2 -2
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +11 -11
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/AccountSettingsCard.d.ts +9 -0
- package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -0
- package/dist/client/settings/AccountSettingsCard.js +78 -0
- package/dist/client/settings/AccountSettingsCard.js.map +1 -0
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +2 -2
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +25 -77
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts +5 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +35 -9
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
- package/dist/client/settings/agent-settings-search.js +0 -2
- package/dist/client/settings/agent-settings-search.js.map +1 -1
- package/dist/client/settings/index.d.ts +2 -1
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +2 -1
- package/dist/client/settings/index.js.map +1 -1
- package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
- package/dist/client/vite-dev-recovery-script.js +18 -1
- package/dist/client/vite-dev-recovery-script.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +18 -8
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +34 -11
- package/dist/db/client.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +1 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/email.d.ts +1 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +29 -1
- package/dist/server/email.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +23 -0
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
- package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +1 -1
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +0 -15
- package/dist/templates/chat/app/routes/settings.tsx +2 -0
- package/dist/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
- package/dist/templates/default/app/routes/settings.tsx +2 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +68 -6
- package/dist/vite/client.js.map +1 -1
- package/package.json +2 -2
- package/src/a2a/client.ts +4 -0
- package/src/agent/production-agent.ts +1 -0
- package/src/client/AssistantChat.tsx +11 -50
- package/src/client/ErrorBoundary.tsx +25 -19
- package/src/client/agent-page/AgentTabFrame.tsx +1 -1
- package/src/client/agent-page/AgentTabsPage.tsx +23 -0
- package/src/client/components/ui/message-scroller.tsx +1 -9
- package/src/client/resources/ResourceTree.tsx +3 -3
- package/src/client/resources/ResourcesPanel.tsx +64 -60
- package/src/client/settings/AccountSettingsCard.tsx +228 -0
- package/src/client/settings/SecretsSection.tsx +7 -5
- package/src/client/settings/SettingsPanel.tsx +67 -244
- package/src/client/settings/SettingsTabsPage.tsx +88 -21
- package/src/client/settings/agent-settings-search.ts +0 -2
- package/src/client/settings/index.ts +7 -0
- package/src/client/vite-dev-recovery-script.ts +18 -1
- package/src/db/client.ts +65 -15
- package/src/db/index.ts +2 -0
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
- package/src/localization/default-messages.ts +1 -0
- package/src/server/email.ts +33 -1
- package/src/styles/agent-native.css +23 -0
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +28 -0
- package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/src/templates/chat/app/components/layout/Sidebar.tsx +1 -1
- package/src/templates/chat/app/hooks/use-navigation-state.ts +0 -15
- package/src/templates/chat/app/routes/settings.tsx +2 -0
- package/src/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +28 -0
- package/src/templates/default/app/routes/settings.tsx +2 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +28 -0
- 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
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1487
|
+
timeoutMs,
|
|
1438
1488
|
);
|
|
1439
1489
|
return {
|
|
1440
1490
|
rows: Array.from(result),
|
|
@@ -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
|
-
|
|
426
|
-
|
|
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
|
-
|
|
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:
|
|
477
|
+
geometry: GradientHandles,
|
|
500
478
|
box: { width: number; height: number },
|
|
501
479
|
angleDeg: number,
|
|
502
480
|
): (position: number) => number {
|
|
503
|
-
|
|
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
|
|
994
|
-
const
|
|
995
|
-
if (
|
|
996
|
-
|
|
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: ${
|
|
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
|
|
926
|
+
return result.cssMode;
|
|
1006
927
|
}
|
|
1007
928
|
|
|
1008
929
|
// ---------------------------------------------------------------------------
|