@agent-native/dispatch 0.40.0 → 0.40.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +48 -19
  2. package/dist/actions/provider-api-register.d.ts +7 -7
  3. package/dist/components/app-open-actions.d.ts +1 -1
  4. package/dist/components/app-open-actions.d.ts.map +1 -1
  5. package/dist/components/app-open-actions.js +1 -1
  6. package/dist/components/app-open-actions.js.map +1 -1
  7. package/dist/components/app-row-settings.js +1 -1
  8. package/dist/components/app-row-settings.js.map +1 -1
  9. package/dist/components/automation-create-dialog.js +2 -2
  10. package/dist/components/automation-create-dialog.js.map +1 -1
  11. package/dist/components/automation-details-panel.js +1 -1
  12. package/dist/components/automation-details-panel.js.map +1 -1
  13. package/dist/components/chat-home-apps.js +1 -1
  14. package/dist/components/chat-home-apps.js.map +1 -1
  15. package/dist/components/create-app-popover.js +3 -3
  16. package/dist/components/create-app-popover.js.map +1 -1
  17. package/dist/components/dispatch-control-plane.d.ts.map +1 -1
  18. package/dist/components/dispatch-control-plane.js +9 -30
  19. package/dist/components/dispatch-control-plane.js.map +1 -1
  20. package/dist/components/layout/Header.js +2 -2
  21. package/dist/components/layout/Header.js.map +1 -1
  22. package/dist/components/layout/Layout.d.ts +6 -5
  23. package/dist/components/layout/Layout.d.ts.map +1 -1
  24. package/dist/components/layout/Layout.js +73 -113
  25. package/dist/components/layout/Layout.js.map +1 -1
  26. package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
  27. package/dist/components/layout/workspace-apps-rail.js +7 -19
  28. package/dist/components/layout/workspace-apps-rail.js.map +1 -1
  29. package/dist/components/messaging-setup-panel.js +2 -1
  30. package/dist/components/messaging-setup-panel.js.map +1 -1
  31. package/dist/components/workspace-app-card.js +2 -2
  32. package/dist/components/workspace-app-card.js.map +1 -1
  33. package/dist/components/workspace-app-host.d.ts +1 -1
  34. package/dist/components/workspace-app-host.d.ts.map +1 -1
  35. package/dist/components/workspace-app-host.js +7 -74
  36. package/dist/components/workspace-app-host.js.map +1 -1
  37. package/dist/hooks/use-navigation-state.js +1 -1
  38. package/dist/hooks/use-navigation-state.js.map +1 -1
  39. package/dist/lib/workspace-apps.js +1 -1
  40. package/dist/lib/workspace-apps.js.map +1 -1
  41. package/dist/routes/pages/$appId.js +1 -1
  42. package/dist/routes/pages/$appId.js.map +1 -1
  43. package/dist/routes/pages/_index.js +1 -1
  44. package/dist/routes/pages/_index.js.map +1 -1
  45. package/dist/routes/pages/admin._index.js +1 -1
  46. package/dist/routes/pages/admin._index.js.map +1 -1
  47. package/dist/routes/pages/agent-integrations.js +1 -1
  48. package/dist/routes/pages/agent-integrations.js.map +1 -1
  49. package/dist/routes/pages/apps.d.ts.map +1 -1
  50. package/dist/routes/pages/apps.js +3 -43
  51. package/dist/routes/pages/apps.js.map +1 -1
  52. package/dist/routes/pages/browser-chat.js +1 -1
  53. package/dist/routes/pages/browser-chat.js.map +1 -1
  54. package/dist/routes/pages/chat.d.ts.map +1 -1
  55. package/dist/routes/pages/chat.js +24 -43
  56. package/dist/routes/pages/chat.js.map +1 -1
  57. package/dist/routes/pages/extensions.$id.js +1 -1
  58. package/dist/routes/pages/extensions.$id.js.map +1 -1
  59. package/dist/routes/pages/extensions._index.js +1 -1
  60. package/dist/routes/pages/extensions._index.js.map +1 -1
  61. package/dist/routes/pages/integrations.js +1 -1
  62. package/dist/routes/pages/integrations.js.map +1 -1
  63. package/dist/routes/pages/new-app.js +1 -1
  64. package/dist/routes/pages/new-app.js.map +1 -1
  65. package/dist/routes/pages/operations.js +2 -2
  66. package/dist/routes/pages/operations.js.map +1 -1
  67. package/dist/routes/pages/overview.d.ts +2 -2
  68. package/dist/routes/pages/settings-page.d.ts +10 -0
  69. package/dist/routes/pages/settings-page.d.ts.map +1 -0
  70. package/dist/routes/pages/settings-page.js +104 -0
  71. package/dist/routes/pages/settings-page.js.map +1 -0
  72. package/dist/routes/pages/settings.d.ts +4 -12
  73. package/dist/routes/pages/settings.d.ts.map +1 -1
  74. package/dist/routes/pages/settings.js +4 -88
  75. package/dist/routes/pages/settings.js.map +1 -1
  76. package/dist/routes/pages/tools.$id.js +1 -1
  77. package/dist/routes/pages/tools.$id.js.map +1 -1
  78. package/dist/routes/pages/tools._index.js +1 -1
  79. package/dist/routes/pages/tools._index.js.map +1 -1
  80. package/dist/routes/pages/vault.d.ts +1 -1
  81. package/dist/routes/pages/vault.js +1 -1
  82. package/dist/routes/pages/vault.js.map +1 -1
  83. package/dist/shared/app-roles.js.map +1 -1
  84. package/package.json +3 -3
  85. package/src/components/app-open-actions.tsx +1 -1
  86. package/src/components/app-row-settings.tsx +1 -1
  87. package/src/components/automation-create-dialog.tsx +7 -7
  88. package/src/components/automation-details-panel.tsx +1 -1
  89. package/src/components/chat-home-apps.tsx +1 -1
  90. package/src/components/create-app-popover.spec.tsx +5 -7
  91. package/src/components/create-app-popover.tsx +3 -3
  92. package/src/components/dispatch-control-plane.spec.tsx +12 -19
  93. package/src/components/dispatch-control-plane.tsx +10 -84
  94. package/src/components/layout/Header.tsx +2 -2
  95. package/src/components/layout/Layout.app-chat.spec.ts +1 -0
  96. package/src/components/layout/Layout.spec.tsx +49 -30
  97. package/src/components/layout/Layout.tsx +153 -205
  98. package/src/components/layout/workspace-apps-rail.spec.tsx +79 -0
  99. package/src/components/layout/workspace-apps-rail.tsx +6 -30
  100. package/src/components/messaging-setup-panel.tsx +1 -1
  101. package/src/components/workspace-app-card.tsx +2 -2
  102. package/src/components/workspace-app-chat-rail.spec.tsx +2 -2
  103. package/src/components/workspace-app-host.spec.tsx +19 -19
  104. package/src/components/workspace-app-host.tsx +12 -129
  105. package/src/hooks/use-navigation-state.ts +1 -1
  106. package/src/lib/workspace-apps.ts +1 -1
  107. package/src/routes/pages/$appId.tsx +1 -1
  108. package/src/routes/pages/_index.tsx +1 -1
  109. package/src/routes/pages/admin._index.tsx +1 -1
  110. package/src/routes/pages/agent-integrations.tsx +1 -1
  111. package/src/routes/pages/apps.tsx +2 -86
  112. package/src/routes/pages/automations.spec.tsx +1 -1
  113. package/src/routes/pages/browser-chat.spec.tsx +1 -1
  114. package/src/routes/pages/browser-chat.tsx +1 -1
  115. package/src/routes/pages/chat.spec.tsx +23 -46
  116. package/src/routes/pages/chat.tsx +20 -57
  117. package/src/routes/pages/extensions.$id.tsx +1 -1
  118. package/src/routes/pages/extensions._index.tsx +1 -1
  119. package/src/routes/pages/integrations.tsx +1 -1
  120. package/src/routes/pages/new-app.tsx +1 -1
  121. package/src/routes/pages/operations.tsx +2 -2
  122. package/src/routes/pages/settings-page.tsx +250 -0
  123. package/src/routes/pages/settings.spec.tsx +30 -8
  124. package/src/routes/pages/settings.tsx +4 -184
  125. package/src/routes/pages/tools.$id.tsx +1 -1
  126. package/src/routes/pages/tools._index.tsx +1 -1
  127. package/src/routes/pages/vault.tsx +1 -1
  128. package/src/shared/app-roles.ts +1 -1
@@ -1,23 +1,21 @@
1
1
  import {
2
- AgentChatHome,
3
- type AgentChatHomeProps,
4
2
  insertAgentComposerReference,
5
3
  markAgentChatHomeHandoff,
6
4
  readChatFirstMode,
7
- useActiveAgentChatRunId,
8
5
  } from "@agent-native/core/client/agent-chat";
9
6
  import { appBasePath, appPath } from "@agent-native/core/client/api-path";
10
- import { writeClipboardText } from "@agent-native/core/client/clipboard";
11
7
  import { useActionQuery } from "@agent-native/core/client/hooks";
12
8
  import { useT } from "@agent-native/core/client/i18n";
13
- import { IconCheck, IconCopy } from "@tabler/icons-react";
9
+ import {
10
+ AgentChatHome,
11
+ type AgentChatHomeProps,
12
+ } from "@agent-native/toolkit/app/chat";
14
13
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
15
14
  import { useLocation, useNavigate } from "react-router";
16
15
 
17
16
  import { ActionQueryError } from "../../components/action-query-error";
18
17
  import { DispatchChatHomeApps } from "../../components/chat-home-apps";
19
18
  import { useDispatchExtensions } from "../../components/layout/Layout";
20
- import { Button } from "../../components/ui/button";
21
19
  import { Skeleton } from "../../components/ui/skeleton";
22
20
  import { submitOverviewPrompt } from "../../lib/overview-chat";
23
21
 
@@ -73,46 +71,6 @@ interface DispatchThreadUrlSync {
73
71
  navigate: (path: string, options?: { replace?: boolean }) => void;
74
72
  }
75
73
 
76
- function DispatchRequestIdButton({ requestId }: { requestId: string | null }) {
77
- const t = useT();
78
- const [copied, setCopied] = useState(false);
79
-
80
- const handleCopy = useCallback(() => {
81
- if (!requestId) return;
82
- void writeClipboardText(requestId).then((ok) => {
83
- if (!ok) return;
84
- setCopied(true);
85
- window.setTimeout(() => setCopied(false), 1000);
86
- });
87
- }, [requestId]);
88
-
89
- return (
90
- <Button
91
- type="button"
92
- variant="outline"
93
- size="sm"
94
- disabled={!requestId}
95
- onClick={handleCopy}
96
- aria-label={t("dispatch.pages.copyRequestId", {
97
- defaultValue: requestId ? "Copy request ID" : "Request ID unavailable",
98
- })}
99
- >
100
- {copied ? (
101
- <IconCheck aria-hidden="true" />
102
- ) : (
103
- <IconCopy aria-hidden="true" />
104
- )}
105
- {copied
106
- ? t("dispatch.pages.copied", { defaultValue: "Copied" })
107
- : t("dispatch.pages.copyRequestId", {
108
- defaultValue: requestId
109
- ? "Copy request ID"
110
- : "Request ID unavailable",
111
- })}
112
- </Button>
113
- );
114
- }
115
-
116
74
  interface DispatchChatLocationState {
117
75
  dispatchPrompt?: {
118
76
  id?: string | number;
@@ -136,7 +94,6 @@ export default function ChatRoute() {
136
94
  const location = useLocation();
137
95
  const navigate = useNavigate();
138
96
  const routeThreadId = threadIdFromPath(location.pathname);
139
- const activeRunId = useActiveAgentChatRunId(routeThreadId);
140
97
  const agentPath = new URLSearchParams(location.search).get("agent");
141
98
  const agentsQuery = useActionQuery<WorkspaceAgentResource[]>(
142
99
  "list-workspace-resources",
@@ -194,6 +151,14 @@ export default function ChatRoute() {
194
151
  const state = location.state as DispatchChatLocationState | null;
195
152
  const prompt = state?.dispatchPrompt;
196
153
  const thread = state?.dispatchThread;
154
+ const [pendingOverviewPrompt, setPendingOverviewPrompt] = useState<
155
+ DispatchChatLocationState["dispatchPrompt"] | null
156
+ >(null);
157
+ const activePrompt = prompt?.message ? prompt : pendingOverviewPrompt;
158
+
159
+ useEffect(() => {
160
+ if (routeThreadId) setPendingOverviewPrompt(null);
161
+ }, [routeThreadId]);
197
162
 
198
163
  useEffect(() => {
199
164
  const message = prompt?.message?.trim();
@@ -215,6 +180,7 @@ export default function ChatRoute() {
215
180
  );
216
181
  }
217
182
  if (message) {
183
+ setPendingOverviewPrompt(prompt ?? null);
218
184
  submitOverviewPrompt(message, prompt?.selectedModel, {
219
185
  openSidebar: false,
220
186
  selectedEngine: prompt?.selectedEngine,
@@ -303,15 +269,10 @@ export default function ChatRoute() {
303
269
 
304
270
  return (
305
271
  <div className="flex h-full min-h-0 flex-col bg-background">
306
- {threadUrlSync.routeThreadId ? (
307
- <div className="flex justify-end px-4 pt-4 sm:px-6">
308
- <DispatchRequestIdButton requestId={activeRunId} />
309
- </div>
310
- ) : null}
311
272
  <AgentChatHome
312
273
  key={agent ? `agent-${agent.id}` : "dispatch"}
313
274
  className="flex-1 min-h-0"
314
- contentClassName="max-w-6xl"
275
+ contentClassName="max-w-none"
315
276
  surfaceClassName="dispatch-chat-panel px-4 sm:px-6"
316
277
  chatViewTransition
317
278
  defaultMode="chat"
@@ -323,7 +284,7 @@ export default function ChatRoute() {
323
284
  dynamicSuggestions={false}
324
285
  suppressInlineOpenApp={suppressInlineOpenApp}
325
286
  suggestions={
326
- agent || routeThreadId || prompt?.message
287
+ agent || routeThreadId || activePrompt?.message
327
288
  ? []
328
289
  : [
329
290
  t("dispatch.pages.suggestionWorkspaceHealth"),
@@ -335,8 +296,10 @@ export default function ChatRoute() {
335
296
  defaultValue:
336
297
  "Route work, inspect status, or create something new from one place.",
337
298
  })}
338
- centerComposerWhenEmpty={!prompt?.message}
339
- composerLayoutVariant={prompt?.message ? "default" : "hero"}
299
+ centerComposerWhenEmpty={!activePrompt?.message && !routeThreadId}
300
+ composerLayoutVariant={
301
+ activePrompt?.message || routeThreadId ? "default" : "hero"
302
+ }
340
303
  composerPlaceholder={
341
304
  agent
342
305
  ? `Ask ${agent.name}...`
@@ -350,7 +313,7 @@ export default function ChatRoute() {
350
313
  <h1>{agent.name}</h1>
351
314
  {agent.description ? <p>{agent.description}</p> : null}
352
315
  </div>
353
- ) : !prompt?.message ? (
316
+ ) : !activePrompt?.message && !routeThreadId ? (
354
317
  <div className="dispatch-chat-intro">
355
318
  <h1>
356
319
  {t("dispatch.pages.chatHomeTitle", {
@@ -1,4 +1,4 @@
1
- import { ExtensionViewerPage } from "@agent-native/core/client/extensions";
1
+ import { ExtensionViewerPage } from "@agent-native/toolkit/app/extensions";
2
2
 
3
3
  export function meta() {
4
4
  return [{ title: "Extension \u2014 Dispatch" }];
@@ -1,4 +1,4 @@
1
- import { ExtensionsListPage } from "@agent-native/core/client/extensions";
1
+ import { ExtensionsListPage } from "@agent-native/toolkit/app/extensions";
2
2
 
3
3
  export function meta() {
4
4
  return [{ title: "Extensions \u2014 Dispatch" }];
@@ -1,5 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { McpIntegrationsLanding } from "@agent-native/core/client/integrations";
2
+ import { McpIntegrationsLanding } from "@agent-native/toolkit/app/integrations";
3
3
  import { Link } from "react-router";
4
4
 
5
5
  import { DispatchShell } from "../../components/dispatch-shell";
@@ -1,5 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
- import { NewWorkspaceAppFlow } from "@agent-native/core/client/ui";
2
+ import { NewWorkspaceAppFlow } from "@agent-native/toolkit/app/onboarding";
3
3
 
4
4
  import { DispatchShell } from "../../components/dispatch-shell";
5
5
 
@@ -1,6 +1,6 @@
1
- import { DbAdminPage } from "@agent-native/core/client/db-admin";
2
1
  import { useT } from "@agent-native/core/client/i18n";
3
- import { ObservabilityDashboard } from "@agent-native/core/client/observability";
2
+ import { DbAdminPage } from "@agent-native/toolkit/app/db-admin";
3
+ import { ObservabilityDashboard } from "@agent-native/toolkit/app/observability";
4
4
  import {
5
5
  IconActivity,
6
6
  IconArrowUpRight,
@@ -0,0 +1,250 @@
1
+ import {
2
+ CHAT_FIRST_MODE_CHANGED_EVENT,
3
+ readChatFirstModeState,
4
+ writeChatFirstMode,
5
+ } from "@agent-native/core/client/agent-chat";
6
+ import {
7
+ useFeatureFlag,
8
+ useFeatureFlagState,
9
+ } from "@agent-native/core/client/feature-flags";
10
+ import { useT } from "@agent-native/core/client/i18n";
11
+ import { SETTINGS_REDESIGN_FLAG } from "@agent-native/core/feature-flags/registry";
12
+ import { ChangelogSettingsCard } from "@agent-native/toolkit/app/changelog";
13
+ import { OrgMembersPage, TeamPage } from "@agent-native/toolkit/app/org";
14
+ import {
15
+ AccountSettingsCard,
16
+ CORE_SETTINGS_PAGES,
17
+ registerSettingsPages,
18
+ SettingsGroup,
19
+ SettingsRow,
20
+ SettingsTabsPage,
21
+ useAgentSettingsTabs,
22
+ type SettingsSearchEntry,
23
+ } from "@agent-native/toolkit/app/settings";
24
+ import { LanguagePicker } from "@agent-native/toolkit/app/shared";
25
+ import { IconShield } from "@tabler/icons-react";
26
+ import { useMemo, useState } from "react";
27
+ import { Link } from "react-router";
28
+
29
+ import { Button } from "../../components/ui/button";
30
+ import { Switch } from "../../components/ui/switch";
31
+ import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
32
+ import { DISPATCH_CONNECT_APPS_FLAG } from "../../shared/feature-flags.js";
33
+
34
+ const coreMembersPage = CORE_SETTINGS_PAGES.find(
35
+ (page) => page.id === "members",
36
+ );
37
+ if (!coreMembersPage) {
38
+ throw new Error("Core Settings has no members page for Dispatch app roles");
39
+ }
40
+
41
+ function DispatchMembersSettingsPage() {
42
+ return <OrgMembersPage appRoles={dispatchAccessDescriptor} />;
43
+ }
44
+
45
+ // The redesigned Settings shows Members from core; Dispatch's replaces it so
46
+ // the app-role column stays.
47
+ registerSettingsPages([
48
+ { ...coreMembersPage, component: DispatchMembersSettingsPage },
49
+ ]);
50
+
51
+ export function meta() {
52
+ return [{ title: "Settings - Dispatch" }];
53
+ }
54
+
55
+ export interface DispatchSettingsPageProps {
56
+ /** Raw CHANGELOG.md behind What's new. */
57
+ changelog: string;
58
+ }
59
+
60
+ /** The template route supplies the app's own changelog. */
61
+ export function DispatchSettingsPage({ changelog }: DispatchSettingsPageProps) {
62
+ const t = useT();
63
+ const connectAppsEnabled = useFeatureFlag(DISPATCH_CONNECT_APPS_FLAG.key);
64
+ // Core Preferences owns the interface language in the redesigned Settings,
65
+ // whose Dispatch General page shows `generalGroups` instead of `general`.
66
+ const redesign = useFeatureFlagState(SETTINGS_REDESIGN_FLAG.key).enabled;
67
+ const agentSettingsTabs = useAgentSettingsTabs({
68
+ usageAppId: "dispatch",
69
+ usageViewAllHref: "/admin/metrics",
70
+ organizationContent: (
71
+ <div className="mx-auto w-full max-w-3xl">
72
+ <TeamPage
73
+ showTitle={false}
74
+ appRoles={dispatchAccessDescriptor}
75
+ createOrgDescription="Set up a team to share dispatch destinations and approvals with your colleagues."
76
+ />
77
+ </div>
78
+ ),
79
+ });
80
+ const settingsTabs = [
81
+ ...agentSettingsTabs,
82
+ {
83
+ id: "admin",
84
+ label: t("dispatch.nav.admin", { defaultValue: "Admin" }),
85
+ icon: IconShield,
86
+ group: "Admin",
87
+ href: "/admin",
88
+ content: null,
89
+ },
90
+ ];
91
+ const [chatFirstModeState] = useState(() => readChatFirstModeState());
92
+ const [chatFirstMode, setChatFirstMode] = useState(
93
+ () => chatFirstModeState.enabled,
94
+ );
95
+ const [chatFirstStorageNotice, setChatFirstStorageNotice] = useState<
96
+ string | null
97
+ >(
98
+ chatFirstModeState.availability === "unavailable"
99
+ ? t("settings.chatFirstStorageUnavailable")
100
+ : null,
101
+ );
102
+
103
+ function updateChatFirstMode(enabled: boolean) {
104
+ const result = writeChatFirstMode(enabled);
105
+ if (!result.ok) {
106
+ setChatFirstStorageNotice(t("settings.chatFirstStorageBlocked"));
107
+ return;
108
+ }
109
+ setChatFirstStorageNotice(null);
110
+ setChatFirstMode(enabled);
111
+ window.dispatchEvent(
112
+ new CustomEvent(CHAT_FIRST_MODE_CHANGED_EVENT, {
113
+ detail: { enabled },
114
+ }),
115
+ );
116
+ }
117
+
118
+ const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
119
+ () => [
120
+ ...(redesign
121
+ ? []
122
+ : [
123
+ {
124
+ id: "dispatch-language",
125
+ label: t("settings.languageTitle"),
126
+ keywords: "language locale translation i18n",
127
+ hash: "language",
128
+ },
129
+ ]),
130
+ {
131
+ id: "dispatch-workspace",
132
+ label: redesign
133
+ ? t("settings.resourcesTitle")
134
+ : t("settings.workspaceTitle"),
135
+ keywords: "workspace resources integrations vault destinations",
136
+ hash: "workspace-resources",
137
+ },
138
+ {
139
+ id: "dispatch-chat-first",
140
+ label: t("settings.chatFirstTitle"),
141
+ keywords: "chat first codex t3 apps pane navigation",
142
+ hash: "chat-first",
143
+ },
144
+ ],
145
+ [redesign, t],
146
+ );
147
+
148
+ const chatFirstSwitch = (
149
+ <Switch
150
+ aria-label={t("settings.chatFirstAriaLabel")}
151
+ checked={chatFirstMode}
152
+ onCheckedChange={updateChatFirstMode}
153
+ />
154
+ );
155
+ const chatFirstStorageAlert = chatFirstStorageNotice ? (
156
+ <p className="text-sm text-destructive" role="alert">
157
+ {chatFirstStorageNotice}
158
+ </p>
159
+ ) : null;
160
+ const connectAppsRow = connectAppsEnabled ? (
161
+ <SettingsRow
162
+ id="connect-apps"
163
+ label={t("settings.connectApps")}
164
+ description={t("settings.connectAppsDescription")}
165
+ control={
166
+ <Button variant="outline" asChild>
167
+ <Link to="/connect">{t("settings.openConnectApps")}</Link>
168
+ </Button>
169
+ }
170
+ />
171
+ ) : null;
172
+ const resourceSettingsButton = (
173
+ <Button variant="outline" asChild>
174
+ <Link to="/workspace">{t("settings.openResourceSettings")}</Link>
175
+ </Button>
176
+ );
177
+
178
+ return (
179
+ <SettingsTabsPage
180
+ account={<AccountSettingsCard />}
181
+ extraTabs={settingsTabs}
182
+ generalSearchEntries={generalSearchEntries}
183
+ general={
184
+ <div className="mx-auto w-full max-w-2xl space-y-6">
185
+ <p className="text-sm leading-6 text-muted-foreground">
186
+ {t("settings.description")}
187
+ </p>
188
+
189
+ <SettingsGroup>
190
+ <SettingsRow
191
+ id="language"
192
+ label={t("settings.languageTitle")}
193
+ description={t("settings.languageDescription")}
194
+ control={
195
+ <div className="w-56">
196
+ <LanguagePicker label={t("settings.languageLabel")} />
197
+ </div>
198
+ }
199
+ />
200
+ <SettingsRow
201
+ id="workspace-resources"
202
+ label={t("settings.workspaceTitle")}
203
+ description={t("settings.workspaceDescription")}
204
+ control={resourceSettingsButton}
205
+ />
206
+ {connectAppsRow}
207
+ </SettingsGroup>
208
+
209
+ <SettingsGroup id="chat-first">
210
+ <SettingsRow
211
+ label={t("settings.chatFirstTitle")}
212
+ description={t("settings.chatFirstDescription")}
213
+ control={chatFirstSwitch}
214
+ >
215
+ <p className="text-sm leading-6 text-muted-foreground">
216
+ {t("settings.chatFirstSessionWatchDescription")}
217
+ </p>
218
+ {chatFirstStorageAlert}
219
+ </SettingsRow>
220
+ </SettingsGroup>
221
+ </div>
222
+ }
223
+ generalGroups={
224
+ <SettingsGroup id="workspace" title={t("settings.workspaceTitle")}>
225
+ <SettingsRow
226
+ id="chat-first"
227
+ label={t("settings.chatFirstTitle")}
228
+ description={t("settings.chatFirstDescription")}
229
+ control={chatFirstSwitch}
230
+ >
231
+ {chatFirstStorageAlert}
232
+ </SettingsRow>
233
+ <SettingsRow
234
+ id="workspace-resources"
235
+ label={t("settings.resourcesTitle")}
236
+ description={t("settings.workspaceDescription")}
237
+ control={resourceSettingsButton}
238
+ />
239
+ {connectAppsRow}
240
+ </SettingsGroup>
241
+ }
242
+ whatsNew={
243
+ <div className="mx-auto w-full max-w-2xl">
244
+ <ChangelogSettingsCard markdown={changelog} />
245
+ </div>
246
+ }
247
+ whatsNewMarkdown={changelog}
248
+ />
249
+ );
250
+ }
@@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
8
  import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
9
9
 
10
10
  const state = vi.hoisted(() => ({
11
+ redesign: false,
11
12
  registered: [] as Array<{
12
13
  id: string;
13
14
  component: ComponentType;
@@ -16,7 +17,6 @@ const state = vi.hoisted(() => ({
16
17
  membersAppRoles: undefined as unknown,
17
18
  pageProps: null as null | {
18
19
  general?: ReactNode;
19
- account?: ReactNode;
20
20
  generalGroups?: ReactNode;
21
21
  generalSearchEntries?: Array<{ id: string; hash?: string }>;
22
22
  extraTabs?: Array<{ id: string; href?: string }>;
@@ -30,7 +30,7 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
30
30
  writeChatFirstMode: () => ({ ok: true }),
31
31
  }));
32
32
 
33
- vi.mock("@agent-native/core/client/changelog", () => ({
33
+ vi.mock("@agent-native/toolkit/app/changelog", () => ({
34
34
  ChangelogSettingsCard: ({ markdown }: { markdown: string }) => (
35
35
  <div data-changelog>{markdown}</div>
36
36
  ),
@@ -38,10 +38,12 @@ vi.mock("@agent-native/core/client/changelog", () => ({
38
38
 
39
39
  vi.mock("@agent-native/core/client/feature-flags", () => ({
40
40
  useFeatureFlag: () => false,
41
+ useFeatureFlagState: () => ({ status: "ready", enabled: state.redesign }),
41
42
  }));
42
43
 
43
44
  vi.mock("@agent-native/core/feature-flags/registry", () => ({
44
45
  CONNECT_APPS_FLAG: { key: "connect-apps" },
46
+ SETTINGS_REDESIGN_FLAG: { key: "settings-redesign" },
45
47
  defineFeatureFlag: (flag: { key: string }) => flag,
46
48
  }));
47
49
 
@@ -49,7 +51,11 @@ vi.mock("@agent-native/core/client/i18n", () => ({
49
51
  useT: () => (key: string) => key,
50
52
  }));
51
53
 
52
- vi.mock("@agent-native/core/client/org", () => ({
54
+ vi.mock("@agent-native/toolkit/app/shared", () => ({
55
+ LanguagePicker: () => null,
56
+ }));
57
+
58
+ vi.mock("@agent-native/toolkit/app/org", () => ({
53
59
  TeamPage: () => null,
54
60
  OrgMembersPage: ({ appRoles }: { appRoles?: unknown }) => {
55
61
  state.membersAppRoles = appRoles;
@@ -57,7 +63,8 @@ vi.mock("@agent-native/core/client/org", () => ({
57
63
  },
58
64
  }));
59
65
 
60
- vi.mock("@agent-native/core/client/settings", () => ({
66
+ vi.mock("@agent-native/toolkit/app/settings", () => ({
67
+ AccountSettingsCard: () => null,
61
68
  CORE_SETTINGS_PAGES: [
62
69
  { id: "profile", component: () => null },
63
70
  {
@@ -88,7 +95,7 @@ vi.mock("@agent-native/core/client/settings", () => ({
88
95
  ),
89
96
  SettingsTabsPage: (props: NonNullable<typeof state.pageProps>) => {
90
97
  state.pageProps = props;
91
- return <main>{props.generalGroups}</main>;
98
+ return <main>{state.redesign ? props.generalGroups : props.general}</main>;
92
99
  },
93
100
  useAgentSettingsTabs: () => [{ id: "organization", label: "Organization" }],
94
101
  }));
@@ -111,6 +118,7 @@ describe("Dispatch settings route", () => {
111
118
 
112
119
  beforeEach(() => {
113
120
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
121
+ state.redesign = false;
114
122
  state.pageProps = null;
115
123
  state.membersAppRoles = undefined;
116
124
  container = document.createElement("div");
@@ -137,11 +145,25 @@ describe("Dispatch settings route", () => {
137
145
  expect(state.membersAppRoles).toBe(dispatchAccessDescriptor);
138
146
  });
139
147
 
140
- it("renders Dispatch rows in the app's General groups", () => {
148
+ it("keeps the language row and Admin link on today's General tab", () => {
149
+ render();
150
+
151
+ expect(container.querySelector('[data-row="language"]')).not.toBeNull();
152
+ expect(container.textContent).toContain(
153
+ "settings.chatFirstSessionWatchDescription",
154
+ );
155
+ expect(
156
+ state.pageProps?.generalSearchEntries?.map((entry) => entry.id),
157
+ ).toContain("dispatch-language");
158
+ expect(
159
+ state.pageProps?.extraTabs?.find((tab) => tab.id === "admin")?.href,
160
+ ).toBe("/admin");
161
+ });
162
+
163
+ it("drops the language row in the redesigned Settings", () => {
164
+ state.redesign = true;
141
165
  render();
142
166
 
143
- expect(state.pageProps?.general).toBeUndefined();
144
- expect(state.pageProps?.account).toBeUndefined();
145
167
  expect(container.querySelector('[data-row="language"]')).toBeNull();
146
168
  expect(container.querySelector('[data-row="chat-first"]')).not.toBeNull();
147
169
  expect(