@agent-native/dispatch 0.22.0 → 0.23.0
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/dist/components/app-icon.d.ts +2 -1
- package/dist/components/app-icon.d.ts.map +1 -1
- package/dist/components/app-icon.js +4 -2
- package/dist/components/app-icon.js.map +1 -1
- package/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +2 -1
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +2 -2
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +16 -17
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +91 -57
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -1
- package/dist/components/layout/workspace-apps-rail.js +1 -1
- package/dist/components/layout/workspace-apps-rail.js.map +1 -1
- package/dist/components/other-apps-section.d.ts +3 -1
- package/dist/components/other-apps-section.d.ts.map +1 -1
- package/dist/components/other-apps-section.js +9 -6
- package/dist/components/other-apps-section.js.map +1 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +7 -2
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +2 -5
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/lib/overview-chat.d.ts.map +1 -1
- package/dist/lib/overview-chat.js +0 -8
- package/dist/lib/overview-chat.js.map +1 -1
- package/package.json +3 -3
- package/src/components/app-icon.tsx +6 -2
- package/src/components/app-list-row.tsx +1 -1
- package/src/components/app-open-actions.tsx +4 -2
- package/src/components/dispatch-control-plane.spec.tsx +43 -7
- package/src/components/dispatch-control-plane.tsx +56 -75
- package/src/components/layout/Layout.spec.tsx +121 -1
- package/src/components/layout/Layout.tsx +187 -114
- package/src/components/layout/workspace-apps-rail.tsx +1 -0
- package/src/components/other-apps-section.tsx +61 -40
- package/src/components/workspace-app-card.spec.tsx +63 -66
- package/src/components/workspace-app-card.tsx +10 -3
- package/src/components/workspace-template-card.spec.tsx +7 -8
- package/src/components/workspace-template-card.tsx +21 -11
- package/src/lib/overview-chat.spec.ts +0 -19
- package/src/lib/overview-chat.ts +0 -9
- package/src/styles/dispatch.css +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overview-chat.js","sourceRoot":"","sources":["../../src/lib/overview-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAEhB,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"overview-chat.js","sourceRoot":"","sources":["../../src/lib/overview-chat.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAEhB,MAAM,sCAAsC,CAAC;AAQ9C,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,aAA6B,EAC7B,OAA6B;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,OAAO,eAAe,CAAC;QACrB,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI;QACZ,KAAK,EAAE,aAAa,IAAI,SAAS;QACjC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC,CAAC;AACL,CAAC","sourcesContent":["import {\n sendToAgentChat,\n type AgentChatMessage,\n} from \"@agent-native/core/client/agent-chat\";\n\ninterface OverviewChatOptions {\n openSidebar?: boolean;\n selectedEngine?: string | null;\n selectedEffort?: AgentChatMessage[\"effort\"];\n}\n\nexport function submitOverviewPrompt(\n message: string,\n selectedModel?: string | null,\n options?: OverviewChatOptions,\n): string | null {\n const trimmed = message.trim();\n if (!trimmed) return null;\n\n return sendToAgentChat({\n message: trimmed,\n submit: true,\n newTab: true,\n model: selectedModel || undefined,\n ...(options?.selectedEngine ? { engine: options.selectedEngine } : {}),\n ...(options?.selectedEffort ? { effort: options.selectedEffort } : {}),\n ...(options?.openSidebar === false ? { openSidebar: false } : {}),\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"tailwind-merge": "^3.5.0",
|
|
85
85
|
"vaul": "^1.1.2",
|
|
86
86
|
"zod": "^4.3.6",
|
|
87
|
-
"@agent-native/toolkit": "0.13.
|
|
87
|
+
"@agent-native/toolkit": "0.13.8"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@react-router/dev": "^8.1.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
99
99
|
"vite": "8.1.0",
|
|
100
100
|
"vitest": "^4.1.5",
|
|
101
|
-
"@agent-native/core": "0.
|
|
101
|
+
"@agent-native/core": "0.149.0"
|
|
102
102
|
},
|
|
103
103
|
"peerDependencies": {
|
|
104
104
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -95,6 +95,7 @@ export function AppIcon({
|
|
|
95
95
|
color,
|
|
96
96
|
className,
|
|
97
97
|
size = "md",
|
|
98
|
+
monochrome = false,
|
|
98
99
|
}: {
|
|
99
100
|
id: string;
|
|
100
101
|
name: string;
|
|
@@ -102,6 +103,7 @@ export function AppIcon({
|
|
|
102
103
|
color?: string;
|
|
103
104
|
className?: string;
|
|
104
105
|
size?: "sm" | "md";
|
|
106
|
+
monochrome?: boolean;
|
|
105
107
|
}) {
|
|
106
108
|
const Icon = appIconComponent(id, name, icon);
|
|
107
109
|
const customColor = safeHexColor(color);
|
|
@@ -126,10 +128,12 @@ export function AppIcon({
|
|
|
126
128
|
className={cn(
|
|
127
129
|
"flex shrink-0 items-center justify-center rounded-xl",
|
|
128
130
|
size === "sm" ? "size-8" : "size-10",
|
|
129
|
-
|
|
131
|
+
monochrome
|
|
132
|
+
? "bg-transparent text-sidebar-foreground/70"
|
|
133
|
+
: !customColor && tone,
|
|
130
134
|
className,
|
|
131
135
|
)}
|
|
132
|
-
style={style}
|
|
136
|
+
style={monochrome ? undefined : style}
|
|
133
137
|
>
|
|
134
138
|
<Icon size={size === "sm" ? 16 : 19} stroke={1.8} />
|
|
135
139
|
</span>
|
|
@@ -3,7 +3,7 @@ import type { ReactNode } from "react";
|
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
4
|
|
|
5
5
|
export const APP_LIST_GRID_CLASS =
|
|
6
|
-
"xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
|
|
6
|
+
"mx-auto w-full max-w-[1000px] xl:grid xl:grid-cols-2 xl:gap-3 xl:overflow-visible xl:rounded-none xl:bg-transparent";
|
|
7
7
|
export const APP_LIST_GRID_ROW_CLASS = "xl:rounded-2xl xl:border-0 xl:bg-card";
|
|
8
8
|
|
|
9
9
|
export function AppList({
|
|
@@ -29,6 +29,7 @@ export function AppOpenActions({
|
|
|
29
29
|
rel,
|
|
30
30
|
labels: labelOverrides,
|
|
31
31
|
onAddApp,
|
|
32
|
+
onOpenInline,
|
|
32
33
|
showInlineOption = false,
|
|
33
34
|
showNewTabOption = false,
|
|
34
35
|
}: {
|
|
@@ -38,6 +39,7 @@ export function AppOpenActions({
|
|
|
38
39
|
rel?: string;
|
|
39
40
|
labels?: Partial<AppOpenActionLabels>;
|
|
40
41
|
onAddApp?: () => void;
|
|
42
|
+
onOpenInline?: () => void;
|
|
41
43
|
showInlineOption?: boolean;
|
|
42
44
|
showNewTabOption?: boolean;
|
|
43
45
|
}) {
|
|
@@ -92,8 +94,8 @@ export function AppOpenActions({
|
|
|
92
94
|
</DropdownMenuItem>
|
|
93
95
|
) : null}
|
|
94
96
|
{showInlineOption && href ? (
|
|
95
|
-
<DropdownMenuItem
|
|
96
|
-
|
|
97
|
+
<DropdownMenuItem onSelect={onOpenInline}>
|
|
98
|
+
{labels.openInline}
|
|
97
99
|
</DropdownMenuItem>
|
|
98
100
|
) : null}
|
|
99
101
|
{showNewTabOption && href ? (
|
|
@@ -8,6 +8,7 @@ import { DispatchControlPlane } from "./dispatch-control-plane";
|
|
|
8
8
|
import { TooltipProvider } from "./ui/tooltip";
|
|
9
9
|
|
|
10
10
|
const clientState = vi.hoisted(() => ({
|
|
11
|
+
inBuilderFrame: false,
|
|
11
12
|
navigateWithTransition: vi.fn(),
|
|
12
13
|
promptComposerProps: null as Record<string, unknown> | null,
|
|
13
14
|
workspaceApps: [] as Array<Record<string, unknown>>,
|
|
@@ -69,7 +70,7 @@ vi.mock("@agent-native/core/client/hooks", () => ({
|
|
|
69
70
|
}));
|
|
70
71
|
|
|
71
72
|
vi.mock("@agent-native/core/client/host", () => ({
|
|
72
|
-
isInBuilderFrame: () =>
|
|
73
|
+
isInBuilderFrame: () => clientState.inBuilderFrame,
|
|
73
74
|
}));
|
|
74
75
|
|
|
75
76
|
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
@@ -79,7 +80,12 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
79
80
|
}));
|
|
80
81
|
|
|
81
82
|
vi.mock("./create-app-popover", () => ({
|
|
82
|
-
CreateAppPopover: () =>
|
|
83
|
+
CreateAppPopover: ({ trigger }: { trigger?: React.ReactNode }) => (
|
|
84
|
+
<div>
|
|
85
|
+
{trigger}
|
|
86
|
+
<span>Create app</span>
|
|
87
|
+
</div>
|
|
88
|
+
),
|
|
83
89
|
}));
|
|
84
90
|
|
|
85
91
|
describe("DispatchControlPlane", () => {
|
|
@@ -88,6 +94,7 @@ describe("DispatchControlPlane", () => {
|
|
|
88
94
|
|
|
89
95
|
beforeEach(() => {
|
|
90
96
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
97
|
+
clientState.inBuilderFrame = false;
|
|
91
98
|
clientState.navigateWithTransition.mockReset();
|
|
92
99
|
clientState.promptComposerProps = null;
|
|
93
100
|
clientState.workspaceApps = [];
|
|
@@ -155,6 +162,36 @@ describe("DispatchControlPlane", () => {
|
|
|
155
162
|
);
|
|
156
163
|
});
|
|
157
164
|
|
|
165
|
+
it("keeps overview submissions on Dispatch Chat inside Builder frames", async () => {
|
|
166
|
+
clientState.inBuilderFrame = true;
|
|
167
|
+
|
|
168
|
+
await act(async () => {
|
|
169
|
+
root.render(
|
|
170
|
+
<MemoryRouter initialEntries={["/overview"]}>
|
|
171
|
+
<TooltipProvider>
|
|
172
|
+
<DispatchControlPlane />
|
|
173
|
+
</TooltipProvider>
|
|
174
|
+
</MemoryRouter>,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await act(async () => {
|
|
179
|
+
container.querySelector<HTMLButtonElement>("[data-placeholder]")?.click();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(clientState.navigateWithTransition).toHaveBeenCalledWith(
|
|
183
|
+
expect.any(Function),
|
|
184
|
+
"/chat",
|
|
185
|
+
expect.objectContaining({
|
|
186
|
+
state: {
|
|
187
|
+
dispatchPrompt: expect.objectContaining({
|
|
188
|
+
message: "Route onboarding work",
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
158
195
|
it("shows mounted and connected apps together without duplicates", async () => {
|
|
159
196
|
clientState.workspaceApps = [
|
|
160
197
|
{
|
|
@@ -230,11 +267,10 @@ describe("DispatchControlPlane", () => {
|
|
|
230
267
|
expect(container.textContent).toContain("Mail");
|
|
231
268
|
expect(container.textContent).toContain("Clips");
|
|
232
269
|
expect(container.textContent).toContain("Analytics");
|
|
233
|
-
expect(container.textContent).toContain("
|
|
234
|
-
expect(container.textContent).toContain("
|
|
235
|
-
expect(container.textContent
|
|
236
|
-
|
|
237
|
-
);
|
|
270
|
+
expect(container.textContent).toContain("Apps");
|
|
271
|
+
expect(container.textContent).toContain("New");
|
|
272
|
+
expect(container.textContent).not.toContain("Other apps");
|
|
273
|
+
expect(container.textContent).not.toContain("available");
|
|
238
274
|
expect(container.textContent).not.toContain("Archived app");
|
|
239
275
|
expect(container.textContent).not.toContain("Duplicate onboarding");
|
|
240
276
|
expect(container.textContent).not.toContain("CRM");
|
|
@@ -4,20 +4,13 @@ import {
|
|
|
4
4
|
} from "@agent-native/core/client/agent-chat";
|
|
5
5
|
import { PromptComposer } from "@agent-native/core/client/composer";
|
|
6
6
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
7
|
-
import { isInBuilderFrame } from "@agent-native/core/client/host";
|
|
8
7
|
import { useT } from "@agent-native/core/client/i18n";
|
|
9
|
-
import {
|
|
10
|
-
IconArrowUpRight,
|
|
11
|
-
IconChevronDown,
|
|
12
|
-
IconClockHour4,
|
|
13
|
-
IconApps,
|
|
14
|
-
} from "@tabler/icons-react";
|
|
8
|
+
import { IconChevronDown, IconClockHour4, IconPlus } from "@tabler/icons-react";
|
|
15
9
|
import type { ReactNode } from "react";
|
|
16
10
|
import { useState } from "react";
|
|
17
11
|
import { Link, useNavigate } from "react-router";
|
|
18
12
|
|
|
19
13
|
import type { ConnectedAppSummary } from "../lib/other-apps";
|
|
20
|
-
import { submitOverviewPrompt } from "../lib/overview-chat";
|
|
21
14
|
import { cn } from "../lib/utils";
|
|
22
15
|
import type { WorkspaceAppSummary } from "../lib/workspace-apps";
|
|
23
16
|
import { ActionQueryError } from "./action-query-error";
|
|
@@ -28,7 +21,7 @@ import {
|
|
|
28
21
|
} from "./app-list-row";
|
|
29
22
|
import { CreateAppPopover } from "./create-app-popover";
|
|
30
23
|
import { useSetPageTitle } from "./layout/HeaderActions";
|
|
31
|
-
import { OtherAppsSection } from "./other-apps-section";
|
|
24
|
+
import { mergeOtherAppEntries, OtherAppsSection } from "./other-apps-section";
|
|
32
25
|
import { Button } from "./ui/button";
|
|
33
26
|
import {
|
|
34
27
|
Collapsible,
|
|
@@ -56,26 +49,6 @@ function SectionHeader({
|
|
|
56
49
|
);
|
|
57
50
|
}
|
|
58
51
|
|
|
59
|
-
function AppGroupHeader({
|
|
60
|
-
title,
|
|
61
|
-
description,
|
|
62
|
-
}: {
|
|
63
|
-
title: string;
|
|
64
|
-
description: string;
|
|
65
|
-
}) {
|
|
66
|
-
return (
|
|
67
|
-
<div className="flex min-w-0 items-start gap-2">
|
|
68
|
-
<IconApps size={16} className="mt-0.5 shrink-0 text-muted-foreground" />
|
|
69
|
-
<div className="min-w-0">
|
|
70
|
-
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
71
|
-
{title}
|
|
72
|
-
</h3>
|
|
73
|
-
<p className="mt-0.5 text-xs text-muted-foreground">{description}</p>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
52
|
function CommandPanel() {
|
|
80
53
|
const t = useT();
|
|
81
54
|
const {
|
|
@@ -104,11 +77,6 @@ function CommandPanel() {
|
|
|
104
77
|
const trimmed = message.trim();
|
|
105
78
|
if (!trimmed) return;
|
|
106
79
|
|
|
107
|
-
if (isInBuilderFrame()) {
|
|
108
|
-
submitOverviewPrompt(trimmed, selectedModel);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
80
|
navigateWithAgentChatViewTransition(navigate, "/chat", {
|
|
113
81
|
state: {
|
|
114
82
|
dispatchPrompt: {
|
|
@@ -124,7 +92,7 @@ function CommandPanel() {
|
|
|
124
92
|
|
|
125
93
|
return (
|
|
126
94
|
<section className="flex flex-col">
|
|
127
|
-
<div className="mx-auto flex w-full max-w-
|
|
95
|
+
<div className="mx-auto flex w-full max-w-[750px] flex-col pt-4 sm:pt-6">
|
|
128
96
|
<div className="mb-5 text-center">
|
|
129
97
|
<h2 className="text-2xl font-semibold tracking-tight text-foreground sm:text-3xl">
|
|
130
98
|
{t("dispatch.pages.chatAcrossApps", {
|
|
@@ -196,60 +164,73 @@ function AppsPanel({
|
|
|
196
164
|
const visibleApps = apps.filter((app) => !app.isDispatch && !app.archived);
|
|
197
165
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
198
166
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
167
|
+
const otherAppEntries = mergeOtherAppEntries({
|
|
168
|
+
templates: curatedTemplates,
|
|
169
|
+
connectedApps,
|
|
170
|
+
workspaceApps: apps,
|
|
171
|
+
});
|
|
199
172
|
const showSkeletons =
|
|
200
173
|
isLoading && activeApps.length === 0 && pendingApps.length === 0;
|
|
201
174
|
|
|
202
175
|
return (
|
|
203
|
-
<section className="flex flex-col gap-3">
|
|
176
|
+
<section className="mx-auto flex w-full max-w-[1000px] flex-col gap-3">
|
|
204
177
|
<SectionHeader
|
|
205
178
|
title="Apps"
|
|
206
179
|
action={
|
|
207
|
-
<
|
|
208
|
-
<
|
|
209
|
-
View all
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
180
|
+
<div className="flex items-center gap-1.5">
|
|
181
|
+
<Button variant="outline" size="sm" asChild>
|
|
182
|
+
<Link to="/apps">View all</Link>
|
|
183
|
+
</Button>
|
|
184
|
+
<CreateAppPopover
|
|
185
|
+
align="end"
|
|
186
|
+
trigger={
|
|
187
|
+
<Button variant="ghost" size="sm" className="gap-1.5">
|
|
188
|
+
<IconPlus size={14} />
|
|
189
|
+
{t("dispatch.pages.newApp", { defaultValue: "New" })}
|
|
190
|
+
</Button>
|
|
191
|
+
}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
213
194
|
}
|
|
214
195
|
/>
|
|
215
196
|
{showSkeletons ? (
|
|
216
197
|
<OverviewAppsSkeleton />
|
|
217
198
|
) : (
|
|
218
199
|
<>
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
description={t("dispatch.pages.activeCount", {
|
|
226
|
-
count: activeApps.length,
|
|
227
|
-
})}
|
|
200
|
+
<AppList className={APP_LIST_GRID_CLASS}>
|
|
201
|
+
{activeApps.map((app) => (
|
|
202
|
+
<WorkspaceAppCard
|
|
203
|
+
key={app.id}
|
|
204
|
+
app={app}
|
|
205
|
+
className={APP_LIST_GRID_ROW_CLASS}
|
|
228
206
|
/>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
207
|
+
))}
|
|
208
|
+
{activeApps.length === 0 &&
|
|
209
|
+
otherAppEntries.length === 0 &&
|
|
210
|
+
!curatedTemplatesLoading &&
|
|
211
|
+
!connectedAppsLoading &&
|
|
212
|
+
!curatedTemplatesError &&
|
|
213
|
+
!connectedAppsError ? (
|
|
214
|
+
<p className="px-4 py-3 text-sm text-muted-foreground">
|
|
215
|
+
{t("dispatch.pages.noApps", {
|
|
216
|
+
defaultValue: "No apps yet.",
|
|
217
|
+
})}
|
|
218
|
+
</p>
|
|
219
|
+
) : null}
|
|
220
|
+
<OtherAppsSection
|
|
221
|
+
templates={curatedTemplates}
|
|
222
|
+
connectedApps={connectedApps}
|
|
223
|
+
workspaceApps={apps}
|
|
224
|
+
templatesLoading={curatedTemplatesLoading}
|
|
225
|
+
connectedAppsLoading={connectedAppsLoading}
|
|
226
|
+
templatesError={curatedTemplatesError}
|
|
227
|
+
connectedAppsError={connectedAppsError}
|
|
228
|
+
onRetryTemplates={onRetryCuratedTemplates}
|
|
229
|
+
onRetryConnectedApps={onRetryConnectedApps}
|
|
230
|
+
heading={null}
|
|
231
|
+
embeddedInList
|
|
232
|
+
/>
|
|
233
|
+
</AppList>
|
|
253
234
|
</>
|
|
254
235
|
)}
|
|
255
236
|
{pendingApps.length > 0 ? (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
import React, { act } from "react";
|
|
3
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
|
-
import { MemoryRouter } from "react-router";
|
|
4
|
+
import { MemoryRouter, useLocation } from "react-router";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
|
|
7
7
|
import { AdminShell } from "../admin-navigation";
|
|
@@ -87,6 +87,14 @@ vi.mock("@agent-native/core/client/org", () => ({
|
|
|
87
87
|
OrgSwitcher: () => <div>Organization</div>,
|
|
88
88
|
}));
|
|
89
89
|
|
|
90
|
+
function LocationProbe({ onChange }: { onChange: (path: string) => void }) {
|
|
91
|
+
const location = useLocation();
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
onChange(location.pathname);
|
|
94
|
+
}, [location.pathname, onChange]);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
90
98
|
describe("formatThreadAge", () => {
|
|
91
99
|
const now = 2_000_000_000_000;
|
|
92
100
|
|
|
@@ -310,6 +318,118 @@ describe("Dispatch NavContent", () => {
|
|
|
310
318
|
);
|
|
311
319
|
});
|
|
312
320
|
|
|
321
|
+
it("keeps Admin above Settings in the chat-first left sidebar", async () => {
|
|
322
|
+
await act(async () => {
|
|
323
|
+
root.render(
|
|
324
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
325
|
+
<TooltipProvider>
|
|
326
|
+
<NavContent chatFirstMode />
|
|
327
|
+
</TooltipProvider>
|
|
328
|
+
</MemoryRouter>,
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const adminLink = container.querySelector('a[href="/admin"]');
|
|
333
|
+
const settingsLink = container.querySelector('a[href="/settings"]');
|
|
334
|
+
const organization = [...container.querySelectorAll("div")].find(
|
|
335
|
+
(element) => element.textContent?.trim() === "Organization",
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(adminLink).not.toBeNull();
|
|
339
|
+
expect(settingsLink).not.toBeNull();
|
|
340
|
+
expect(organization).toBeDefined();
|
|
341
|
+
expect(adminLink!.compareDocumentPosition(settingsLink!)).toBe(
|
|
342
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
343
|
+
);
|
|
344
|
+
expect(settingsLink!.compareDocumentPosition(organization!)).toBe(
|
|
345
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("keeps an All apps destination below the chat-first app list", async () => {
|
|
350
|
+
const paths: string[] = [];
|
|
351
|
+
const apps = Array.from({ length: 6 }, (_, index) => ({
|
|
352
|
+
id: `app-${index}`,
|
|
353
|
+
name: `App ${index}`,
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
await act(async () => {
|
|
357
|
+
root.render(
|
|
358
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
359
|
+
<TooltipProvider>
|
|
360
|
+
<NavContent chatFirstMode chatFirstApps={apps} />
|
|
361
|
+
<LocationProbe onChange={(path) => paths.push(path)} />
|
|
362
|
+
</TooltipProvider>
|
|
363
|
+
</MemoryRouter>,
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(container.querySelectorAll("[data-chat-first-app]")).toHaveLength(5);
|
|
368
|
+
const showMore = [...container.querySelectorAll("button")].find(
|
|
369
|
+
(button) => button.textContent?.trim() === "Show more",
|
|
370
|
+
);
|
|
371
|
+
expect(showMore).toBeDefined();
|
|
372
|
+
await act(async () => {
|
|
373
|
+
showMore?.click();
|
|
374
|
+
});
|
|
375
|
+
expect(container.querySelectorAll("[data-chat-first-app]")).toHaveLength(6);
|
|
376
|
+
|
|
377
|
+
const allApps = container.querySelector<HTMLButtonElement>(
|
|
378
|
+
"[data-chat-first-all-apps]",
|
|
379
|
+
);
|
|
380
|
+
expect(allApps?.textContent?.trim()).toBe("All apps");
|
|
381
|
+
await act(async () => {
|
|
382
|
+
allApps?.click();
|
|
383
|
+
});
|
|
384
|
+
expect(paths.at(-1)).toBe("/apps");
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("omits the Chats section when chat-first has no chats", async () => {
|
|
388
|
+
clientState.threads = [];
|
|
389
|
+
await act(async () => {
|
|
390
|
+
root.render(
|
|
391
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
392
|
+
<TooltipProvider>
|
|
393
|
+
<NavContent
|
|
394
|
+
chatFirstMode
|
|
395
|
+
chatFirstApps={[{ id: "mail", name: "Mail" }]}
|
|
396
|
+
/>
|
|
397
|
+
</TooltipProvider>
|
|
398
|
+
</MemoryRouter>,
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(container.textContent).not.toContain("Chats");
|
|
403
|
+
expect(container.querySelector("[data-chat-first-app]")).not.toBeNull();
|
|
404
|
+
expect(
|
|
405
|
+
container.querySelector("[data-chat-first-app] span[style]"),
|
|
406
|
+
).toBeNull();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("opens a workspace app in the main app route from the left rail", async () => {
|
|
410
|
+
const paths: string[] = [];
|
|
411
|
+
await act(async () => {
|
|
412
|
+
root.render(
|
|
413
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
414
|
+
<TooltipProvider>
|
|
415
|
+
<NavContent
|
|
416
|
+
chatFirstMode
|
|
417
|
+
chatFirstApps={[{ id: "mail", name: "Mail" }]}
|
|
418
|
+
onChatFirstAppOpen={(app) => paths.push(`/apps/${app.id}`)}
|
|
419
|
+
/>
|
|
420
|
+
</TooltipProvider>
|
|
421
|
+
</MemoryRouter>,
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await act(async () => {
|
|
426
|
+
container
|
|
427
|
+
.querySelector<HTMLButtonElement>("[data-chat-first-app] button")
|
|
428
|
+
?.click();
|
|
429
|
+
});
|
|
430
|
+
expect(paths).toEqual(["/apps/mail"]);
|
|
431
|
+
});
|
|
432
|
+
|
|
313
433
|
it("uses the shared chat history rail and retains thread actions", async () => {
|
|
314
434
|
await act(async () => {
|
|
315
435
|
root.render(
|