@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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, synchronous math helpers for Figma gradient geometry and blend-mode
|
|
3
|
+
* normalisation. No DOM, no CSS, no network.
|
|
4
|
+
*
|
|
5
|
+
* Supports two gradient-transform sources:
|
|
6
|
+
* - REST API `gradientHandlePositions` (3-element Vec2 array, already in
|
|
7
|
+
* normalized 0..1 node-space).
|
|
8
|
+
* - 2×3 affine transform in **either** the Kiwi/fig-file object form
|
|
9
|
+
* `{m00..m12}` or the REST/Plugin API array form `[[a,b,tx],[c,d,ty]]`.
|
|
10
|
+
* Both encode the **node-to-gradient** mapping (same convention as Figma's
|
|
11
|
+
* own `gradientTransform` field); invert to obtain handle positions.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface Vec2 {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GradientHandles {
|
|
24
|
+
start: Vec2;
|
|
25
|
+
end: Vec2;
|
|
26
|
+
width: Vec2;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 2×3 affine transform in the row-major object form used by Kiwi/fig-file
|
|
31
|
+
* decoded paint nodes:
|
|
32
|
+
* [m00, m01, m02]
|
|
33
|
+
* [m10, m11, m12]
|
|
34
|
+
*/
|
|
35
|
+
export interface Mat2x3Object {
|
|
36
|
+
m00: number;
|
|
37
|
+
m01: number;
|
|
38
|
+
m02: number;
|
|
39
|
+
m10: number;
|
|
40
|
+
m11: number;
|
|
41
|
+
m12: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 2×3 affine transform in the nested-array form used by the Figma REST API
|
|
46
|
+
* and Plugin API `gradientTransform` field:
|
|
47
|
+
* [[a, b, tx], [c, d, ty]]
|
|
48
|
+
*/
|
|
49
|
+
export type Mat2x3Array = [[number, number, number], [number, number, number]];
|
|
50
|
+
|
|
51
|
+
export type BlendVerdict = "exact" | "approximated";
|
|
52
|
+
|
|
53
|
+
export interface BlendModeResult {
|
|
54
|
+
cssMode: string;
|
|
55
|
+
verdict: BlendVerdict;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type GradientKind = "LINEAR" | "RADIAL" | "ANGULAR" | "DIAMOND";
|
|
59
|
+
|
|
60
|
+
export interface GradientGeometry {
|
|
61
|
+
kind: GradientKind;
|
|
62
|
+
handles: GradientHandles;
|
|
63
|
+
start: Vec2;
|
|
64
|
+
end: Vec2;
|
|
65
|
+
center: Vec2;
|
|
66
|
+
rx: number;
|
|
67
|
+
ry: number;
|
|
68
|
+
rotationDeg: number;
|
|
69
|
+
fromDeg: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Matrix helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/** Convert the REST/Plugin nested-array form to the object form. */
|
|
77
|
+
export function mat2x3FromArray(m: Mat2x3Array): Mat2x3Object {
|
|
78
|
+
return {
|
|
79
|
+
m00: m[0][0],
|
|
80
|
+
m01: m[0][1],
|
|
81
|
+
m02: m[0][2],
|
|
82
|
+
m10: m[1][0],
|
|
83
|
+
m11: m[1][1],
|
|
84
|
+
m12: m[1][2],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Invert a 2×3 affine transform. Returns null when the matrix is singular
|
|
90
|
+
* (determinant near zero, i.e. the gradient has collapsed to a line or point).
|
|
91
|
+
*
|
|
92
|
+
* The 2×2 rotation/scale sub-matrix is [[m00,m01],[m10,m11]]; the
|
|
93
|
+
* standard 2×2 inverse is applied and the translation is back-solved:
|
|
94
|
+
* inv_tx = (-m11*m02 + m01*m12) / det
|
|
95
|
+
* inv_ty = ( m10*m02 - m00*m12) / det
|
|
96
|
+
*/
|
|
97
|
+
export function invert2x3(m: Mat2x3Object): Mat2x3Object | null {
|
|
98
|
+
const det = m.m00 * m.m11 - m.m01 * m.m10;
|
|
99
|
+
if (Math.abs(det) < 1e-8) return null;
|
|
100
|
+
const inv00 = m.m11 / det;
|
|
101
|
+
const inv01 = -m.m01 / det;
|
|
102
|
+
const inv10 = -m.m10 / det;
|
|
103
|
+
const inv11 = m.m00 / det;
|
|
104
|
+
return {
|
|
105
|
+
m00: inv00,
|
|
106
|
+
m01: inv01,
|
|
107
|
+
m02: (-m.m11 * m.m02 + m.m01 * m.m12) / det,
|
|
108
|
+
m10: inv10,
|
|
109
|
+
m11: inv11,
|
|
110
|
+
m12: (m.m10 * m.m02 - m.m00 * m.m12) / det,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Apply a 2×3 transform to a 2-D point.
|
|
116
|
+
* The point is treated as a homogeneous [x, y, 1]^T column vector.
|
|
117
|
+
*/
|
|
118
|
+
function applyMat2x3(m: Mat2x3Object, v: Vec2): Vec2 {
|
|
119
|
+
return {
|
|
120
|
+
x: m.m00 * v.x + m.m01 * v.y + m.m02,
|
|
121
|
+
y: m.m10 * v.x + m.m11 * v.y + m.m12,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Handle positions from transforms
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Derive REST-style `GradientHandles` from a 2×3 **node-to-gradient**
|
|
131
|
+
* transform in Kiwi/fig-file object form.
|
|
132
|
+
*
|
|
133
|
+
* Figma's gradient transform encodes where the gradient's natural coordinate
|
|
134
|
+
* system sits inside the node's normalized [0,1]² box. The three canonical
|
|
135
|
+
* gradient-space points (start, end, width) map back to node-space by
|
|
136
|
+
* inverting the transform:
|
|
137
|
+
* handles = inv(M) * {(0,0), (1,0), (0,1)}
|
|
138
|
+
*/
|
|
139
|
+
export function handlePositionsFromObjectTransform(
|
|
140
|
+
t: Mat2x3Object,
|
|
141
|
+
): GradientHandles | null {
|
|
142
|
+
const inv = invert2x3(t);
|
|
143
|
+
if (!inv) return null;
|
|
144
|
+
return {
|
|
145
|
+
start: applyMat2x3(inv, { x: 0, y: 0 }),
|
|
146
|
+
end: applyMat2x3(inv, { x: 1, y: 0 }),
|
|
147
|
+
width: applyMat2x3(inv, { x: 0, y: 1 }),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function gradientGeometryFromTransform(
|
|
152
|
+
kind: GradientKind,
|
|
153
|
+
transform: Mat2x3Object,
|
|
154
|
+
box: { width: number; height: number },
|
|
155
|
+
): GradientGeometry | null {
|
|
156
|
+
const inverse = invert2x3(transform);
|
|
157
|
+
if (!inverse) return null;
|
|
158
|
+
const toPixels = (point: Vec2): Vec2 => ({
|
|
159
|
+
x: point.x * box.width,
|
|
160
|
+
y: point.y * box.height,
|
|
161
|
+
});
|
|
162
|
+
const startNormalized = applyMat2x3(inverse, { x: 0, y: 0.5 });
|
|
163
|
+
const endNormalized = applyMat2x3(inverse, { x: 1, y: 0.5 });
|
|
164
|
+
const widthNormalized = applyMat2x3(inverse, { x: 1, y: 0 });
|
|
165
|
+
const center = toPixels(applyMat2x3(inverse, { x: 0.5, y: 0.5 }));
|
|
166
|
+
const vertex = toPixels(applyMat2x3(inverse, { x: 1, y: 0.5 }));
|
|
167
|
+
const covertex = toPixels(applyMat2x3(inverse, { x: 0.5, y: 1 }));
|
|
168
|
+
const vertexDx = vertex.x - center.x;
|
|
169
|
+
const vertexDy = vertex.y - center.y;
|
|
170
|
+
return {
|
|
171
|
+
kind,
|
|
172
|
+
handles: {
|
|
173
|
+
start: startNormalized,
|
|
174
|
+
end: endNormalized,
|
|
175
|
+
width: widthNormalized,
|
|
176
|
+
},
|
|
177
|
+
start: toPixels(startNormalized),
|
|
178
|
+
end: toPixels(endNormalized),
|
|
179
|
+
center,
|
|
180
|
+
rx: Math.hypot(vertexDx, vertexDy),
|
|
181
|
+
ry: Math.hypot(covertex.x - center.x, covertex.y - center.y),
|
|
182
|
+
rotationDeg: (Math.atan2(vertexDy, vertexDx) * 180) / Math.PI,
|
|
183
|
+
fromDeg: (Math.atan2(-transform.m10, transform.m00) * 180) / Math.PI,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Derive REST-style `GradientHandles` from a 2×3 **node-to-gradient**
|
|
189
|
+
* transform in REST/Plugin API nested-array form.
|
|
190
|
+
*/
|
|
191
|
+
export function handlePositionsFromArrayTransform(
|
|
192
|
+
t: Mat2x3Array,
|
|
193
|
+
): GradientHandles | null {
|
|
194
|
+
return handlePositionsFromObjectTransform(mat2x3FromArray(t));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Gradient geometry from REST handle positions
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Resolve `GradientHandles` from a raw `gradientHandlePositions` array as
|
|
203
|
+
* returned by the Figma REST API. Returns null when the array is too short.
|
|
204
|
+
*/
|
|
205
|
+
export function resolveGradientHandles(
|
|
206
|
+
gradientHandlePositions: Array<Vec2> | undefined,
|
|
207
|
+
): GradientHandles | null {
|
|
208
|
+
if (!gradientHandlePositions || gradientHandlePositions.length < 3)
|
|
209
|
+
return null;
|
|
210
|
+
return {
|
|
211
|
+
start: gradientHandlePositions[0]!,
|
|
212
|
+
end: gradientHandlePositions[1]!,
|
|
213
|
+
width: gradientHandlePositions[2]!,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Gradient angle
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Derive a CSS `linear-gradient()` angle (degrees) from Figma's normalized
|
|
223
|
+
* `gradientHandlePositions`. Handle positions are normalized independently in
|
|
224
|
+
* x and y (0..1 relative to the node's bounding box), so the angle must be
|
|
225
|
+
* computed in actual pixel space using the node's real width/height —
|
|
226
|
+
* otherwise a non-square box silently distorts the angle.
|
|
227
|
+
*
|
|
228
|
+
* Identity: left-to-right handles (start=(0,0.5), end=(1,0.5)) → 90 deg.
|
|
229
|
+
* Top-to-bottom handles (start=(0.5,0), end=(0.5,1)) → 180 deg.
|
|
230
|
+
*
|
|
231
|
+
* This is the same function previously inlined in figma-node-to-html.ts and
|
|
232
|
+
* is preserved here verbatim for public API compatibility.
|
|
233
|
+
*/
|
|
234
|
+
export function gradientAngleDegrees(
|
|
235
|
+
paint: { gradientHandlePositions?: Array<Vec2> },
|
|
236
|
+
box: { width: number; height: number },
|
|
237
|
+
): number | null {
|
|
238
|
+
const handles = resolveGradientHandles(paint.gradientHandlePositions);
|
|
239
|
+
if (!handles) return null;
|
|
240
|
+
return gradientAngleDegreesFromHandles(handles, box);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Same calculation as `gradientAngleDegrees` but accepts already-resolved
|
|
245
|
+
* `GradientHandles` — useful when handles come from a transform inversion.
|
|
246
|
+
*/
|
|
247
|
+
export function gradientAngleDegreesFromHandles(
|
|
248
|
+
handles: GradientHandles,
|
|
249
|
+
box: { width: number; height: number },
|
|
250
|
+
): number {
|
|
251
|
+
const dx = (handles.end.x - handles.start.x) * box.width;
|
|
252
|
+
const dy = (handles.end.y - handles.start.y) * box.height;
|
|
253
|
+
const angleRad = Math.atan2(dy, dx);
|
|
254
|
+
const angleDeg = (angleRad * 180) / Math.PI + 90;
|
|
255
|
+
return ((angleDeg % 360) + 360) % 360;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Linear stop position remapping
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* CSS `linear-gradient(angle, ...)` always stretches its 0%/100% stops across
|
|
264
|
+
* the box's full diagonal at that angle (the CSS "gradient line" always spans
|
|
265
|
+
* corner-to-corner). Figma's stop positions are fractions of the literal
|
|
266
|
+
* handle-to-handle distance, which only coincides with the CSS span when the
|
|
267
|
+
* handles are dragged exactly corner-to-corner.
|
|
268
|
+
*
|
|
269
|
+
* This function returns a remap closure that projects each Figma stop's real
|
|
270
|
+
* pixel position onto the CSS gradient line and re-expresses it as a
|
|
271
|
+
* percentage of the CSS line's length, so a partial/offset gradient renders at
|
|
272
|
+
* the same pixel positions Figma draws it at.
|
|
273
|
+
*/
|
|
274
|
+
export function remapLinearStopPosition(
|
|
275
|
+
handles: GradientHandles,
|
|
276
|
+
box: { width: number; height: number },
|
|
277
|
+
angleDeg: number,
|
|
278
|
+
): (position: number) => number {
|
|
279
|
+
const angleRad = (angleDeg * Math.PI) / 180;
|
|
280
|
+
const ux = Math.sin(angleRad);
|
|
281
|
+
const uy = -Math.cos(angleRad);
|
|
282
|
+
const lineLength = box.width * Math.abs(ux) + box.height * Math.abs(uy);
|
|
283
|
+
if (lineLength < 1e-6) return (position) => position;
|
|
284
|
+
const startPx = {
|
|
285
|
+
x: handles.start.x * box.width,
|
|
286
|
+
y: handles.start.y * box.height,
|
|
287
|
+
};
|
|
288
|
+
const endPx = { x: handles.end.x * box.width, y: handles.end.y * box.height };
|
|
289
|
+
const cx = box.width / 2;
|
|
290
|
+
const cy = box.height / 2;
|
|
291
|
+
return (position: number): number => {
|
|
292
|
+
const px = startPx.x + position * (endPx.x - startPx.x);
|
|
293
|
+
const py = startPx.y + position * (endPx.y - startPx.y);
|
|
294
|
+
const projected = (px - cx) * ux + (py - cy) * uy;
|
|
295
|
+
return (projected + lineLength / 2) / lineLength;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Vector length (pixel-space)
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Euclidean distance between two normalized-coordinate points after scaling
|
|
305
|
+
* into actual pixel space.
|
|
306
|
+
*/
|
|
307
|
+
export function vectorLength(
|
|
308
|
+
from: Vec2,
|
|
309
|
+
to: Vec2,
|
|
310
|
+
box: { width: number; height: number },
|
|
311
|
+
): number {
|
|
312
|
+
const dx = (to.x - from.x) * box.width;
|
|
313
|
+
const dy = (to.y - from.y) * box.height;
|
|
314
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// CSS blend mode mapping
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
const CSS_BLEND_MODES = new Set([
|
|
322
|
+
"multiply",
|
|
323
|
+
"screen",
|
|
324
|
+
"overlay",
|
|
325
|
+
"darken",
|
|
326
|
+
"lighten",
|
|
327
|
+
"color-dodge",
|
|
328
|
+
"color-burn",
|
|
329
|
+
"hard-light",
|
|
330
|
+
"soft-light",
|
|
331
|
+
"difference",
|
|
332
|
+
"exclusion",
|
|
333
|
+
"hue",
|
|
334
|
+
"saturation",
|
|
335
|
+
"color",
|
|
336
|
+
"luminosity",
|
|
337
|
+
]);
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Figma-only blend modes that have no exact CSS equivalent. The value is the
|
|
341
|
+
* closest CSS mode (approximation) and callers should record the verdict.
|
|
342
|
+
*/
|
|
343
|
+
const FIGMA_ONLY_BLEND_MODE_FALLBACK: Record<string, string> = {
|
|
344
|
+
LINEAR_BURN: "plus-darker",
|
|
345
|
+
LINEAR_DODGE: "plus-lighter",
|
|
346
|
+
LIGHTER: "plus-lighter",
|
|
347
|
+
DARKER: "darken",
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Map a Figma blend mode string to a CSS `mix-blend-mode` value with an
|
|
352
|
+
* explicit fidelity verdict:
|
|
353
|
+
* - `"exact"` — CSS supports the mode natively.
|
|
354
|
+
* - `"approximated"` — mapped to the closest CSS equivalent.
|
|
355
|
+
*
|
|
356
|
+
* Returns `null` for `PASS_THROUGH`, `NORMAL`, and unrecognised modes (caller
|
|
357
|
+
* should omit the CSS property entirely in those cases).
|
|
358
|
+
*/
|
|
359
|
+
export function cssBlendMode(figmaBlendMode: string): BlendModeResult | null {
|
|
360
|
+
if (
|
|
361
|
+
!figmaBlendMode ||
|
|
362
|
+
figmaBlendMode === "PASS_THROUGH" ||
|
|
363
|
+
figmaBlendMode === "NORMAL"
|
|
364
|
+
)
|
|
365
|
+
return null;
|
|
366
|
+
const cssMode = figmaBlendMode.toLowerCase().replace(/_/g, "-");
|
|
367
|
+
if (CSS_BLEND_MODES.has(cssMode)) return { cssMode, verdict: "exact" };
|
|
368
|
+
const fallback = FIGMA_ONLY_BLEND_MODE_FALLBACK[figmaBlendMode];
|
|
369
|
+
if (fallback) return { cssMode: fallback, verdict: "approximated" };
|
|
370
|
+
return null;
|
|
371
|
+
}
|
package/src/ingestion/index.ts
CHANGED
|
@@ -53,6 +53,26 @@ export {
|
|
|
53
53
|
type FigmaRgba,
|
|
54
54
|
type SummarizeFigmaNodeResult,
|
|
55
55
|
} from "./figma.js";
|
|
56
|
+
export {
|
|
57
|
+
cssBlendMode,
|
|
58
|
+
gradientAngleDegreesFromHandles,
|
|
59
|
+
gradientGeometryFromTransform,
|
|
60
|
+
handlePositionsFromArrayTransform,
|
|
61
|
+
handlePositionsFromObjectTransform,
|
|
62
|
+
invert2x3,
|
|
63
|
+
mat2x3FromArray,
|
|
64
|
+
remapLinearStopPosition,
|
|
65
|
+
resolveGradientHandles,
|
|
66
|
+
vectorLength,
|
|
67
|
+
type BlendModeResult,
|
|
68
|
+
type BlendVerdict,
|
|
69
|
+
type GradientGeometry,
|
|
70
|
+
type GradientHandles,
|
|
71
|
+
type GradientKind,
|
|
72
|
+
type Mat2x3Array,
|
|
73
|
+
type Mat2x3Object,
|
|
74
|
+
type Vec2,
|
|
75
|
+
} from "./figma-paint-math.js";
|
|
56
76
|
export {
|
|
57
77
|
assertFigmaNodeTreeComplexity,
|
|
58
78
|
collectFallbackNodeIds,
|
package/src/server/email.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface SendEmailArgs {
|
|
|
36
36
|
inReplyTo?: string;
|
|
37
37
|
references?: string;
|
|
38
38
|
attachments?: EmailAttachment[];
|
|
39
|
+
timeoutMs?: number;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
let cachedAgentNativeLogo: Buffer | undefined;
|
|
@@ -125,8 +126,12 @@ function getFromAddress(
|
|
|
125
126
|
return "Agent Native <onboarding@resend.dev>";
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
async function sendEmailWithSignal(
|
|
130
|
+
args: SendEmailArgs,
|
|
131
|
+
signal?: AbortSignal,
|
|
132
|
+
): Promise<void> {
|
|
129
133
|
const config = await resolveEmailTransport();
|
|
134
|
+
signal?.throwIfAborted();
|
|
130
135
|
const provider = config.provider;
|
|
131
136
|
const from = getFromAddress(config, args.from);
|
|
132
137
|
const attachments = resolveAttachments(args);
|
|
@@ -164,6 +169,7 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
|
164
169
|
"Content-Type": "application/json",
|
|
165
170
|
},
|
|
166
171
|
body: JSON.stringify(payload),
|
|
172
|
+
signal,
|
|
167
173
|
});
|
|
168
174
|
if (!res.ok) {
|
|
169
175
|
const body = await res.text().catch(() => "");
|
|
@@ -215,6 +221,7 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
|
215
221
|
"Content-Type": "application/json",
|
|
216
222
|
},
|
|
217
223
|
body: JSON.stringify(sgPayload),
|
|
224
|
+
signal,
|
|
218
225
|
});
|
|
219
226
|
if (!res.ok) {
|
|
220
227
|
const body = await res.text().catch(() => "");
|
|
@@ -239,6 +246,31 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
|
239
246
|
);
|
|
240
247
|
}
|
|
241
248
|
|
|
249
|
+
export async function sendEmail(args: SendEmailArgs): Promise<void> {
|
|
250
|
+
const requestedTimeoutMs = Number(args.timeoutMs);
|
|
251
|
+
if (!Number.isFinite(requestedTimeoutMs) || requestedTimeoutMs <= 0) {
|
|
252
|
+
return sendEmailWithSignal(args);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const timeoutMs = Math.floor(requestedTimeoutMs);
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const timeoutError = new Error(`Email send timed out after ${timeoutMs}ms`);
|
|
258
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
259
|
+
try {
|
|
260
|
+
await Promise.race([
|
|
261
|
+
sendEmailWithSignal(args, controller.signal),
|
|
262
|
+
new Promise<never>((_, reject) => {
|
|
263
|
+
timeout = setTimeout(() => {
|
|
264
|
+
controller.abort(timeoutError);
|
|
265
|
+
reject(timeoutError);
|
|
266
|
+
}, timeoutMs);
|
|
267
|
+
}),
|
|
268
|
+
]);
|
|
269
|
+
} finally {
|
|
270
|
+
if (timeout) clearTimeout(timeout);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
242
274
|
function parseSendGridFrom(from: string): { email: string; name?: string } {
|
|
243
275
|
const m = from.match(/^\s*(.*?)\s*<(.+)>\s*$/);
|
|
244
276
|
if (m && m[2]) return { name: m[1] || undefined, email: m[2] };
|
|
@@ -139,6 +139,28 @@
|
|
|
139
139
|
background: inherit;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
.message-scroller-viewport--top-fade {
|
|
143
|
+
--message-scroller-top-fade-size: 48px;
|
|
144
|
+
-webkit-mask-image: linear-gradient(
|
|
145
|
+
to bottom,
|
|
146
|
+
transparent 0,
|
|
147
|
+
black var(--message-scroller-top-fade-size),
|
|
148
|
+
black 100%
|
|
149
|
+
);
|
|
150
|
+
mask-image: linear-gradient(
|
|
151
|
+
to bottom,
|
|
152
|
+
transparent 0,
|
|
153
|
+
black var(--message-scroller-top-fade-size),
|
|
154
|
+
black 100%
|
|
155
|
+
);
|
|
156
|
+
-webkit-mask-position: 0 0;
|
|
157
|
+
mask-position: 0 0;
|
|
158
|
+
-webkit-mask-repeat: no-repeat;
|
|
159
|
+
mask-repeat: no-repeat;
|
|
160
|
+
-webkit-mask-size: 100% 100%;
|
|
161
|
+
mask-size: 100% 100%;
|
|
162
|
+
}
|
|
163
|
+
|
|
142
164
|
.agent-sidebar-resize-handle {
|
|
143
165
|
background: transparent;
|
|
144
166
|
}
|
|
@@ -888,6 +910,7 @@
|
|
|
888
910
|
|
|
889
911
|
[data-agent-empty-state="centered"] {
|
|
890
912
|
justify-content: center;
|
|
913
|
+
padding-bottom: clamp(3rem, 8vh, 6rem);
|
|
891
914
|
}
|
|
892
915
|
|
|
893
916
|
[data-agent-empty-state="centered"] > .agent-chat-scroll {
|
|
@@ -35,6 +35,34 @@ Then implement working code that is cohesive, accessible, responsive, and polish
|
|
|
35
35
|
|
|
36
36
|
Default to Apple/Linear-level restraint: make the primary workflow obvious, then remove everything that does not help that workflow right now. A polished UI often has fewer visible controls, fewer borders, fewer labels, and fewer explanatory surfaces than the first reasonable implementation.
|
|
37
37
|
|
|
38
|
+
### Progressive Disclosure Is A Design Requirement
|
|
39
|
+
|
|
40
|
+
Treat an all-at-once interface as a defect to fix during design, not as a
|
|
41
|
+
styling preference. Before coding, inventory every piece of content and action
|
|
42
|
+
on the surface, then assign each one to the smallest useful visibility level:
|
|
43
|
+
|
|
44
|
+
1. **Immediate** — the page title, current state, one primary action, and the
|
|
45
|
+
compact context needed to choose what to do next.
|
|
46
|
+
2. **Expanded** — the details needed for the selected item or active workflow.
|
|
47
|
+
3. **On demand** — advanced settings, diagnostics, credentials, metadata,
|
|
48
|
+
destructive actions, documentation, and rarely used tools.
|
|
49
|
+
|
|
50
|
+
Use single-select accordions or simple disclosure rows for sibling panels when
|
|
51
|
+
the user is choosing one item at a time. Inside an expanded panel, keep another
|
|
52
|
+
layer for independent concerns instead of dumping every form, explanation, and
|
|
53
|
+
secondary action into the first reveal. Prefer one flat panel with alignment,
|
|
54
|
+
dividers, and whitespace over nested cards; provider or product icons should
|
|
55
|
+
not receive decorative borders or containers unless the container communicates
|
|
56
|
+
state or interaction. A collapsed row should still show the item name, status,
|
|
57
|
+
and a concise summary so the user can scan the whole surface without opening
|
|
58
|
+
everything.
|
|
59
|
+
|
|
60
|
+
Before shipping, ask: “What can disappear until the user asks for it?” Then
|
|
61
|
+
verify collapsed, expanded, loading, empty, error, and narrow-width states.
|
|
62
|
+
If the first viewport contains multiple forms, repeated explanatory copy,
|
|
63
|
+
documentation links, and controls for unrelated tasks, the surface has not
|
|
64
|
+
passed this requirement yet.
|
|
65
|
+
|
|
38
66
|
- **Start by subtracting**: Before adding a visible control, banner, toolbar row, card, or explanatory block, ask what can be removed, merged, renamed, or moved into an existing affordance.
|
|
39
67
|
- **One primary action**: Each surface should have one dominant next action. Secondary actions belong in menus, popovers, command palettes, disclosure rows, or contextual hover/focus states unless they are used constantly.
|
|
40
68
|
- **Progressively disclose rare work**: Advanced options, diagnostics, metadata, settings, import/export, destructive actions, and inspection tools should stay tucked away until requested. Prefer small icon triggers with tooltips, popovers, drawers, or detail panels over permanent chrome.
|
|
@@ -60,7 +60,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
60
60
|
useAgentChatHomeHandoffLinks({
|
|
61
61
|
storageKey: "chat",
|
|
62
62
|
isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
|
|
63
|
-
requireActiveHandoff:
|
|
63
|
+
requireActiveHandoff: true,
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
useEffect(() => {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { markAgentChatHomeHandoff } from "@agent-native/core/client/agent-chat";
|
|
2
1
|
import { appBasePath, appPath } from "@agent-native/core/client/api-path";
|
|
3
2
|
import { useAgentRouteState } from "@agent-native/core/client/navigation";
|
|
4
|
-
import { useLocation } from "react-router";
|
|
5
3
|
|
|
6
4
|
import { TAB_ID } from "@/lib/tab-id";
|
|
7
5
|
|
|
@@ -12,7 +10,6 @@ export interface NavigationState {
|
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
export function useNavigationState() {
|
|
15
|
-
const location = useLocation();
|
|
16
13
|
useAgentRouteState<NavigationState>({
|
|
17
14
|
browserTabId: TAB_ID,
|
|
18
15
|
requestSource: TAB_ID,
|
|
@@ -26,21 +23,9 @@ export function useNavigationState() {
|
|
|
26
23
|
},
|
|
27
24
|
getCommandPath: (command) =>
|
|
28
25
|
routerPath(command.path || pathForCommand(command)),
|
|
29
|
-
onNavigate: (_command, path) => {
|
|
30
|
-
if (
|
|
31
|
-
isChatPath(location.pathname) &&
|
|
32
|
-
!isChatPath(pathnameFromPath(path))
|
|
33
|
-
) {
|
|
34
|
-
markAgentChatHomeHandoff("chat");
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
26
|
});
|
|
38
27
|
}
|
|
39
28
|
|
|
40
|
-
function pathnameFromPath(path: string): string {
|
|
41
|
-
return path.split(/[?#]/, 1)[0] || "/";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
29
|
function threadIdFromPath(pathname: string): string | null {
|
|
45
30
|
const match = pathname.match(/^\/chat\/([^/]+)/);
|
|
46
31
|
if (!match) return null;
|
|
@@ -2,6 +2,7 @@ import { ChangelogSettingsCard } from "@agent-native/core/client/changelog";
|
|
|
2
2
|
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
3
3
|
import { TeamPage } from "@agent-native/core/client/org";
|
|
4
4
|
import {
|
|
5
|
+
AccountSettingsCard,
|
|
5
6
|
SettingsTabsPage,
|
|
6
7
|
useAgentSettingsTabs,
|
|
7
8
|
type SettingsSearchEntry,
|
|
@@ -44,6 +45,7 @@ export default function SettingsRoute() {
|
|
|
44
45
|
|
|
45
46
|
return (
|
|
46
47
|
<SettingsTabsPage
|
|
48
|
+
account={<AccountSettingsCard />}
|
|
47
49
|
teamLabel={t("navigation.team")}
|
|
48
50
|
extraTabs={agentSettingsTabs}
|
|
49
51
|
generalSearchEntries={generalSearchEntries}
|
|
@@ -35,6 +35,34 @@ Then implement working code that is cohesive, accessible, responsive, and polish
|
|
|
35
35
|
|
|
36
36
|
Default to Apple/Linear-level restraint: make the primary workflow obvious, then remove everything that does not help that workflow right now. A polished UI often has fewer visible controls, fewer borders, fewer labels, and fewer explanatory surfaces than the first reasonable implementation.
|
|
37
37
|
|
|
38
|
+
### Progressive Disclosure Is A Design Requirement
|
|
39
|
+
|
|
40
|
+
Treat an all-at-once interface as a defect to fix during design, not as a
|
|
41
|
+
styling preference. Before coding, inventory every piece of content and action
|
|
42
|
+
on the surface, then assign each one to the smallest useful visibility level:
|
|
43
|
+
|
|
44
|
+
1. **Immediate** — the page title, current state, one primary action, and the
|
|
45
|
+
compact context needed to choose what to do next.
|
|
46
|
+
2. **Expanded** — the details needed for the selected item or active workflow.
|
|
47
|
+
3. **On demand** — advanced settings, diagnostics, credentials, metadata,
|
|
48
|
+
destructive actions, documentation, and rarely used tools.
|
|
49
|
+
|
|
50
|
+
Use single-select accordions or simple disclosure rows for sibling panels when
|
|
51
|
+
the user is choosing one item at a time. Inside an expanded panel, keep another
|
|
52
|
+
layer for independent concerns instead of dumping every form, explanation, and
|
|
53
|
+
secondary action into the first reveal. Prefer one flat panel with alignment,
|
|
54
|
+
dividers, and whitespace over nested cards; provider or product icons should
|
|
55
|
+
not receive decorative borders or containers unless the container communicates
|
|
56
|
+
state or interaction. A collapsed row should still show the item name, status,
|
|
57
|
+
and a concise summary so the user can scan the whole surface without opening
|
|
58
|
+
everything.
|
|
59
|
+
|
|
60
|
+
Before shipping, ask: “What can disappear until the user asks for it?” Then
|
|
61
|
+
verify collapsed, expanded, loading, empty, error, and narrow-width states.
|
|
62
|
+
If the first viewport contains multiple forms, repeated explanatory copy,
|
|
63
|
+
documentation links, and controls for unrelated tasks, the surface has not
|
|
64
|
+
passed this requirement yet.
|
|
65
|
+
|
|
38
66
|
- **Start by subtracting**: Before adding a visible control, banner, toolbar row, card, or explanatory block, ask what can be removed, merged, renamed, or moved into an existing affordance.
|
|
39
67
|
- **One primary action**: Each surface should have one dominant next action. Secondary actions belong in menus, popovers, command palettes, disclosure rows, or contextual hover/focus states unless they are used constantly.
|
|
40
68
|
- **Progressively disclose rare work**: Advanced options, diagnostics, metadata, settings, import/export, destructive actions, and inspection tools should stay tucked away until requested. Prefer small icon triggers with tooltips, popovers, drawers, or detail panels over permanent chrome.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
|
|
2
2
|
import {
|
|
3
|
+
AccountSettingsCard,
|
|
3
4
|
SettingsTabsPage,
|
|
4
5
|
useAgentSettingsTabs,
|
|
5
6
|
} from "@agent-native/core/client/settings";
|
|
@@ -32,6 +33,7 @@ export default function SettingsPage() {
|
|
|
32
33
|
</div>
|
|
33
34
|
|
|
34
35
|
<SettingsTabsPage
|
|
36
|
+
account={<AccountSettingsCard />}
|
|
35
37
|
extraTabs={agentSettingsTabs}
|
|
36
38
|
general={
|
|
37
39
|
<section className="mx-auto w-full max-w-2xl rounded-lg border border-border/50 bg-card px-4 py-4">
|