@agent-native/core 0.119.1 → 0.119.3
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 +21 -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/cli/create.ts +44 -5
- 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/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 +11 -1
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +8 -1
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-07-22-fixed-dashboards-and-analyses-missing-a-copyable-share-link.md +6 -0
- 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/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/app/routes/settings.tsx +2 -0
- 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/cli/create.d.ts +2 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +43 -6
- package/dist/cli/create.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/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 +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- 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/realtime-token.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/cli/create.ts +44 -5
- 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/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
|
package/src/db/client.ts
CHANGED
|
@@ -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),
|
package/src/db/index.ts
CHANGED
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(() => {
|