@agent-native/dispatch 0.22.1 → 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 +84 -56
- 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 +46 -0
- package/src/components/layout/Layout.tsx +149 -84
- 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,6 +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
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
6
|
|
|
6
7
|
import { TooltipProvider } from "./ui/tooltip";
|
|
@@ -44,22 +45,24 @@ describe("WorkspaceAppCard", () => {
|
|
|
44
45
|
it("uses one explicit open action and one settings control", async () => {
|
|
45
46
|
await act(async () => {
|
|
46
47
|
root.render(
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
<MemoryRouter>
|
|
49
|
+
<TooltipProvider>
|
|
50
|
+
<WorkspaceAppCard
|
|
51
|
+
app={{
|
|
52
|
+
id: "analytics",
|
|
53
|
+
name: "Analytics",
|
|
54
|
+
path: "/analytics",
|
|
55
|
+
description: "Explore product and growth performance.",
|
|
56
|
+
status: "ready",
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</TooltipProvider>
|
|
60
|
+
</MemoryRouter>,
|
|
58
61
|
);
|
|
59
62
|
});
|
|
60
63
|
|
|
61
64
|
const appLink = container.querySelector<HTMLAnchorElement>(
|
|
62
|
-
'a[href="/analytics"]',
|
|
65
|
+
'a[href="/apps/analytics"]',
|
|
63
66
|
);
|
|
64
67
|
expect(appLink).not.toBeNull();
|
|
65
68
|
expect(appLink?.textContent).toContain("Open app");
|
|
@@ -67,25 +70,13 @@ describe("WorkspaceAppCard", () => {
|
|
|
67
70
|
expect(appLink?.getAttribute("target")).toBeNull();
|
|
68
71
|
expect(appLink?.getAttribute("rel")).toBeNull();
|
|
69
72
|
expect(appLink?.querySelector("svg")).toBeNull();
|
|
70
|
-
expect(container.textContent).not.toContain("/analytics");
|
|
73
|
+
expect(container.textContent).not.toContain("/apps/analytics");
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
openOptionsButton?.dispatchEvent(
|
|
78
|
-
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
79
|
-
);
|
|
80
|
-
});
|
|
81
|
-
const openInlineLink = Array.from(
|
|
82
|
-
document.querySelectorAll<HTMLAnchorElement>('[role="menu"] a'),
|
|
83
|
-
).find((anchor) => anchor.textContent?.includes("Open inline"));
|
|
84
|
-
const openNewTabLink = Array.from(
|
|
85
|
-
document.querySelectorAll<HTMLAnchorElement>('[role="menu"] a'),
|
|
86
|
-
).find((anchor) => anchor.textContent?.includes("Open in new tab"));
|
|
87
|
-
expect(openInlineLink?.getAttribute("target")).toBeNull();
|
|
88
|
-
expect(openNewTabLink?.getAttribute("target")).toBe("_blank");
|
|
75
|
+
expect(
|
|
76
|
+
container.querySelector(
|
|
77
|
+
'button[aria-label="Open options for Analytics"]',
|
|
78
|
+
),
|
|
79
|
+
).toBeNull();
|
|
89
80
|
|
|
90
81
|
const settingsButton = container.querySelector<HTMLButtonElement>(
|
|
91
82
|
'button[aria-label="Settings for Analytics"]',
|
|
@@ -107,17 +98,19 @@ describe("WorkspaceAppCard", () => {
|
|
|
107
98
|
it("labels per-app context as agent resources while preserving workspace scope", async () => {
|
|
108
99
|
await act(async () => {
|
|
109
100
|
root.render(
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
<MemoryRouter>
|
|
102
|
+
<TooltipProvider>
|
|
103
|
+
<WorkspaceAppCard
|
|
104
|
+
app={{
|
|
105
|
+
id: "analytics",
|
|
106
|
+
name: "Analytics",
|
|
107
|
+
path: "/analytics",
|
|
108
|
+
description: "Explore product and growth performance.",
|
|
109
|
+
status: "ready",
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
</TooltipProvider>
|
|
113
|
+
</MemoryRouter>,
|
|
121
114
|
);
|
|
122
115
|
});
|
|
123
116
|
|
|
@@ -149,17 +142,19 @@ describe("WorkspaceAppCard", () => {
|
|
|
149
142
|
it("opens pending Builder apps in a new tab", async () => {
|
|
150
143
|
await act(async () => {
|
|
151
144
|
root.render(
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
145
|
+
<MemoryRouter>
|
|
146
|
+
<TooltipProvider>
|
|
147
|
+
<WorkspaceAppCard
|
|
148
|
+
app={{
|
|
149
|
+
id: "new-app",
|
|
150
|
+
name: "New app",
|
|
151
|
+
path: "/new-app",
|
|
152
|
+
builderUrl: "https://builder.example.com/projects/new-app",
|
|
153
|
+
status: "pending",
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
</TooltipProvider>
|
|
157
|
+
</MemoryRouter>,
|
|
163
158
|
);
|
|
164
159
|
});
|
|
165
160
|
|
|
@@ -177,20 +172,22 @@ describe("WorkspaceAppCard", () => {
|
|
|
177
172
|
it("shows ownership metadata in the settings menu", async () => {
|
|
178
173
|
await act(async () => {
|
|
179
174
|
root.render(
|
|
180
|
-
<
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
175
|
+
<MemoryRouter>
|
|
176
|
+
<TooltipProvider>
|
|
177
|
+
<WorkspaceAppCard
|
|
178
|
+
app={{
|
|
179
|
+
id: "analytics",
|
|
180
|
+
name: "Analytics",
|
|
181
|
+
path: "/analytics",
|
|
182
|
+
createdAt: "2026-07-28T12:00:00.000Z",
|
|
183
|
+
createdBy: "creator@example.com",
|
|
184
|
+
owner: "owner@example.com",
|
|
185
|
+
teams: ["Growth", "Operations"],
|
|
186
|
+
status: "ready",
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
</TooltipProvider>
|
|
190
|
+
</MemoryRouter>,
|
|
194
191
|
);
|
|
195
192
|
});
|
|
196
193
|
|
|
@@ -20,12 +20,14 @@ import {
|
|
|
20
20
|
IconWorld,
|
|
21
21
|
} from "@tabler/icons-react";
|
|
22
22
|
import { useEffect, useState, type FormEvent } from "react";
|
|
23
|
+
import { useNavigate } from "react-router";
|
|
23
24
|
import { toast } from "sonner";
|
|
24
25
|
|
|
25
26
|
import { cn } from "../lib/utils";
|
|
26
27
|
import {
|
|
27
28
|
isPendingBuilderHref,
|
|
28
29
|
workspaceAppHref,
|
|
30
|
+
workspaceAppRoute,
|
|
29
31
|
type WorkspaceAppSummary,
|
|
30
32
|
} from "../lib/workspace-apps";
|
|
31
33
|
import { ActionQueryError } from "./action-query-error";
|
|
@@ -273,7 +275,9 @@ function WorkspaceAppOpenActions({
|
|
|
273
275
|
app: WorkspaceAppSummary;
|
|
274
276
|
href: string | null;
|
|
275
277
|
}) {
|
|
278
|
+
const navigate = useNavigate();
|
|
276
279
|
const openInNewTab = isPendingBuilderHref(app);
|
|
280
|
+
const appRoute = workspaceAppRoute(app.id);
|
|
277
281
|
|
|
278
282
|
if (!href) {
|
|
279
283
|
return (
|
|
@@ -286,11 +290,14 @@ function WorkspaceAppOpenActions({
|
|
|
286
290
|
return (
|
|
287
291
|
<AppOpenActions
|
|
288
292
|
name={app.name}
|
|
289
|
-
href={href}
|
|
293
|
+
href={openInNewTab ? href : appRoute}
|
|
290
294
|
target={openInNewTab ? "_blank" : undefined}
|
|
291
295
|
rel={openInNewTab ? "noreferrer" : undefined}
|
|
292
|
-
showInlineOption
|
|
293
|
-
|
|
296
|
+
showInlineOption={openInNewTab}
|
|
297
|
+
onOpenInline={() => {
|
|
298
|
+
navigate(appRoute);
|
|
299
|
+
}}
|
|
300
|
+
showNewTabOption={openInNewTab}
|
|
294
301
|
/>
|
|
295
302
|
);
|
|
296
303
|
}
|
|
@@ -153,16 +153,15 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
153
153
|
);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
it("keeps add app
|
|
156
|
+
it("keeps add app out of the catalog split-button menu", async () => {
|
|
157
157
|
await act(async () => {
|
|
158
158
|
root.render(<WorkspaceTemplateCard template={template} catalog />);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
).toBe(false);
|
|
161
|
+
const addButton = Array.from(container.querySelectorAll("button")).find(
|
|
162
|
+
(button) => button.textContent?.includes("Add app"),
|
|
163
|
+
);
|
|
164
|
+
expect(addButton).toBeDefined();
|
|
166
165
|
|
|
167
166
|
const optionsButton = container.querySelector<HTMLButtonElement>(
|
|
168
167
|
'button[aria-label="Open options for Weekly report"]',
|
|
@@ -177,8 +176,8 @@ describe("WorkspaceTemplateCard", () => {
|
|
|
177
176
|
const addItem = Array.from(
|
|
178
177
|
document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
|
|
179
178
|
).find((item) => item.textContent?.includes("Add app"));
|
|
180
|
-
expect(addItem).
|
|
181
|
-
await act(async () =>
|
|
179
|
+
expect(addItem).toBeUndefined();
|
|
180
|
+
await act(async () => addButton?.click());
|
|
182
181
|
expect(document.body.textContent).toContain("Choose the URL-safe id");
|
|
183
182
|
});
|
|
184
183
|
|
|
@@ -214,17 +214,27 @@ export function WorkspaceTemplateCard({
|
|
|
214
214
|
</div>
|
|
215
215
|
<div className="flex shrink-0 items-center gap-2">
|
|
216
216
|
{catalog ? (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
<>
|
|
218
|
+
{liveUrl ? (
|
|
219
|
+
<AppOpenActions
|
|
220
|
+
name={template.name}
|
|
221
|
+
href={liveUrl}
|
|
222
|
+
target="_blank"
|
|
223
|
+
rel="noreferrer"
|
|
224
|
+
labels={{ openApp: labels.openApp }}
|
|
225
|
+
showNewTabOption
|
|
226
|
+
/>
|
|
227
|
+
) : null}
|
|
228
|
+
<Button
|
|
229
|
+
type="button"
|
|
230
|
+
variant="outline"
|
|
231
|
+
size="sm"
|
|
232
|
+
onClick={() => setOpen(true)}
|
|
233
|
+
>
|
|
234
|
+
<IconPlus />
|
|
235
|
+
{labels.remix}
|
|
236
|
+
</Button>
|
|
237
|
+
</>
|
|
228
238
|
) : liveUrl ? (
|
|
229
239
|
<Button variant="ghost" size="sm" className="shrink-0" asChild>
|
|
230
240
|
<a href={liveUrl} target="_blank" rel="noreferrer">
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
const frameState = vi.hoisted(() => ({ inBuilderFrame: false }));
|
|
4
3
|
const sendToAgentChatMock = vi.hoisted(() => vi.fn(() => "chat-tab"));
|
|
5
4
|
|
|
6
5
|
vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
7
6
|
sendToAgentChat: sendToAgentChatMock,
|
|
8
7
|
}));
|
|
9
8
|
|
|
10
|
-
vi.mock("@agent-native/core/client/host", () => ({
|
|
11
|
-
isInBuilderFrame: () => frameState.inBuilderFrame,
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
9
|
const { submitOverviewPrompt } = await import("./overview-chat.js");
|
|
15
10
|
|
|
16
11
|
describe("submitOverviewPrompt", () => {
|
|
17
12
|
beforeEach(() => {
|
|
18
|
-
frameState.inBuilderFrame = false;
|
|
19
13
|
sendToAgentChatMock.mockClear();
|
|
20
14
|
});
|
|
21
15
|
|
|
@@ -50,19 +44,6 @@ describe("submitOverviewPrompt", () => {
|
|
|
50
44
|
});
|
|
51
45
|
});
|
|
52
46
|
|
|
53
|
-
it("routes overview prompts to Builder chat inside Builder", () => {
|
|
54
|
-
frameState.inBuilderFrame = true;
|
|
55
|
-
|
|
56
|
-
const tabId = submitOverviewPrompt("ship the onboarding flow", "auto");
|
|
57
|
-
|
|
58
|
-
expect(tabId).toBe("chat-tab");
|
|
59
|
-
expect(sendToAgentChatMock).toHaveBeenCalledWith({
|
|
60
|
-
message: "ship the onboarding flow",
|
|
61
|
-
submit: true,
|
|
62
|
-
type: "code",
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
47
|
it("ignores empty prompts", () => {
|
|
67
48
|
expect(submitOverviewPrompt(" ", "auto")).toBeNull();
|
|
68
49
|
expect(sendToAgentChatMock).not.toHaveBeenCalled();
|
package/src/lib/overview-chat.ts
CHANGED
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
sendToAgentChat,
|
|
3
3
|
type AgentChatMessage,
|
|
4
4
|
} from "@agent-native/core/client/agent-chat";
|
|
5
|
-
import { isInBuilderFrame } from "@agent-native/core/client/host";
|
|
6
5
|
|
|
7
6
|
interface OverviewChatOptions {
|
|
8
7
|
openSidebar?: boolean;
|
|
@@ -18,14 +17,6 @@ export function submitOverviewPrompt(
|
|
|
18
17
|
const trimmed = message.trim();
|
|
19
18
|
if (!trimmed) return null;
|
|
20
19
|
|
|
21
|
-
if (isInBuilderFrame()) {
|
|
22
|
-
return sendToAgentChat({
|
|
23
|
-
message: trimmed,
|
|
24
|
-
submit: true,
|
|
25
|
-
type: "code",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
20
|
return sendToAgentChat({
|
|
30
21
|
message: trimmed,
|
|
31
22
|
submit: true,
|
package/src/styles/dispatch.css
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
.dispatch-chat-panel [data-agent-empty-state="centered"] .dispatch-chat-intro {
|
|
50
50
|
display: grid;
|
|
51
51
|
width: 100%;
|
|
52
|
-
max-width: min(
|
|
52
|
+
max-width: min(750px, 90%);
|
|
53
53
|
gap: 0.5rem;
|
|
54
54
|
margin: 0 auto 1rem;
|
|
55
55
|
text-align: center;
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
.dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
|
|
81
81
|
width: 100%;
|
|
82
|
-
max-width: min(
|
|
82
|
+
max-width: min(750px, 90%);
|
|
83
83
|
padding: 0;
|
|
84
84
|
}
|
|
85
85
|
|