@agent-native/dispatch 0.23.5 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/list-connected-agents.d.ts +1 -0
- package/dist/actions/list-connected-agents.js +5 -1
- package/dist/actions/list-connected-agents.js.map +1 -1
- package/dist/actions/list-dispatch-usage-metrics.d.ts +2 -0
- package/dist/actions/list-dispatch-usage-metrics.js +11 -1
- package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +12 -12
- package/dist/actions/remix-workspace-template.js +4 -3
- package/dist/actions/remix-workspace-template.js.map +1 -1
- package/dist/actions/scaffold-workspace-app.js +3 -2
- package/dist/actions/scaffold-workspace-app.js.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +3 -2
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/actions/view-screen.js +11 -1
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +21 -5
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/app-icon.js +1 -1
- 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 +1 -17
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +1 -20
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-row-settings.d.ts +6 -0
- package/dist/components/app-row-settings.d.ts.map +1 -0
- package/dist/components/app-row-settings.js +11 -0
- package/dist/components/app-row-settings.js.map +1 -0
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +3 -2
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +3 -2
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +23 -7
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +5 -1
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/docs-link.d.ts +16 -0
- package/dist/components/docs-link.d.ts.map +1 -0
- package/dist/components/docs-link.js +34 -0
- package/dist/components/docs-link.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +43 -35
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +3 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/usage-alerts-panel.d.ts +11 -0
- package/dist/components/usage-alerts-panel.d.ts.map +1 -0
- package/dist/components/usage-alerts-panel.js +125 -0
- package/dist/components/usage-alerts-panel.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts +3 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +70 -29
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-search.d.ts +10 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -0
- package/dist/components/workspace-app-search.js +27 -0
- package/dist/components/workspace-app-search.js.map +1 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +3 -2
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +10 -15
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/hooks/use-navigation-state.d.ts +2 -0
- package/dist/hooks/use-navigation-state.d.ts.map +1 -1
- package/dist/hooks/use-navigation-state.js +10 -0
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/embed-session-recovery.d.ts +3 -0
- package/dist/lib/embed-session-recovery.d.ts.map +1 -0
- package/dist/lib/embed-session-recovery.js +18 -0
- package/dist/lib/embed-session-recovery.js.map +1 -0
- package/dist/lib/other-apps.d.ts +2 -0
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +19 -0
- package/dist/lib/workspace-app-layout.d.ts.map +1 -0
- package/dist/lib/workspace-app-layout.js +75 -0
- package/dist/lib/workspace-app-layout.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +14 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +66 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/lib/workspace-apps.test.d.ts +2 -0
- package/dist/lib/workspace-apps.test.d.ts.map +1 -0
- package/dist/lib/workspace-apps.test.js +33 -0
- package/dist/lib/workspace-apps.test.js.map +1 -0
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -2
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +2 -3
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +17 -13
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +31 -16
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +2 -1
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +7 -2
- package/dist/routes/pages/chat.js.map +1 -1
- package/dist/routes/pages/dreams.d.ts.map +1 -1
- package/dist/routes/pages/dreams.js +14 -18
- package/dist/routes/pages/dreams.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +51 -14
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +146 -17
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/operations.js +1 -1
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +19 -2
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +5 -8
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +1 -1
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +13 -10
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +12 -52
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +17 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
- package/dist/server/lib/usage-metrics-store.js +187 -30
- package/dist/server/lib/usage-metrics-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +2 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/integrations.d.ts.map +1 -1
- package/dist/server/plugins/integrations.js +1 -0
- package/dist/server/plugins/integrations.js.map +1 -1
- package/dist/shared/app-roles.d.ts +9 -0
- package/dist/shared/app-roles.d.ts.map +1 -0
- package/dist/shared/app-roles.js +7 -0
- package/dist/shared/app-roles.js.map +1 -0
- package/package.json +4 -3
- package/src/actions/list-connected-agents.spec.ts +73 -0
- package/src/actions/list-connected-agents.ts +6 -2
- package/src/actions/list-dispatch-usage-metrics.ts +11 -1
- package/src/actions/remix-workspace-template.spec.ts +29 -0
- package/src/actions/remix-workspace-template.ts +13 -6
- package/src/actions/scaffold-workspace-app.ts +12 -5
- package/src/actions/start-workspace-app-creation.ts +8 -2
- package/src/actions/view-screen.ts +13 -1
- package/src/components/admin-navigation.tsx +57 -11
- package/src/components/app-icon.spec.tsx +34 -0
- package/src/components/app-icon.tsx +1 -1
- package/src/components/app-list-row.tsx +1 -2
- package/src/components/app-open-actions.tsx +5 -114
- package/src/components/app-row-settings.tsx +54 -0
- package/src/components/automation-run-history-dialog.tsx +8 -4
- package/src/components/connected-app-card.tsx +3 -2
- package/src/components/create-app-popover.spec.tsx +1 -1
- package/src/components/create-app-popover.tsx +2 -2
- package/src/components/dispatch-control-plane.spec.tsx +44 -3
- package/src/components/dispatch-control-plane.tsx +83 -27
- package/src/components/dispatch-shell.tsx +7 -1
- package/src/components/docs-link.tsx +52 -0
- package/src/components/layout/Layout.spec.tsx +13 -0
- package/src/components/layout/Layout.tsx +63 -32
- package/src/components/messaging-setup-panel.tsx +10 -3
- package/src/components/usage-alerts-panel.tsx +445 -0
- package/src/components/workspace-app-card.spec.tsx +130 -9
- package/src/components/workspace-app-card.tsx +128 -71
- package/src/components/workspace-app-search.tsx +96 -0
- package/src/components/workspace-resource-effective-stack.tsx +10 -5
- package/src/components/workspace-template-card.spec.tsx +53 -24
- package/src/components/workspace-template-card.tsx +16 -26
- package/src/hooks/use-navigation-state.ts +12 -0
- package/src/lib/embed-session-recovery.spec.ts +63 -0
- package/src/lib/embed-session-recovery.ts +26 -0
- package/src/lib/other-apps.ts +2 -0
- package/src/lib/workspace-app-layout.spec.ts +47 -0
- package/src/lib/workspace-app-layout.ts +121 -0
- package/src/lib/workspace-apps.test.ts +64 -0
- package/src/lib/workspace-apps.ts +82 -0
- package/src/routes/pages/$appId.tsx +3 -6
- package/src/routes/pages/agents.tsx +6 -3
- package/src/routes/pages/apps.$appId.tsx +16 -17
- package/src/routes/pages/apps.tsx +116 -62
- package/src/routes/pages/audit.tsx +28 -8
- package/src/routes/pages/chat.spec.tsx +10 -0
- package/src/routes/pages/chat.tsx +8 -0
- package/src/routes/pages/dreams.tsx +92 -142
- package/src/routes/pages/integrations.tsx +147 -68
- package/src/routes/pages/metrics.tsx +460 -102
- package/src/routes/pages/operations.tsx +2 -2
- package/src/routes/pages/settings.tsx +27 -10
- package/src/routes/pages/thread-debug.tsx +18 -36
- package/src/routes/pages/vault.tsx +7 -2
- package/src/routes/pages/workspace.tsx +92 -56
- package/src/server/index.ts +4 -0
- package/src/server/lib/app-creation-store.spec.ts +90 -43
- package/src/server/lib/app-creation-store.ts +15 -61
- package/src/server/lib/usage-metrics-store.spec.ts +117 -0
- package/src/server/lib/usage-metrics-store.ts +249 -30
- package/src/server/plugins/agent-chat.spec.ts +4 -0
- package/src/server/plugins/agent-chat.ts +2 -0
- package/src/server/plugins/integrations.ts +1 -0
- package/src/shared/app-roles.ts +8 -0
- package/src/styles/dispatch.css +9 -0
|
@@ -1,114 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
DropdownMenuContent,
|
|
7
|
-
DropdownMenuItem,
|
|
8
|
-
DropdownMenuTrigger,
|
|
9
|
-
} from "./ui/dropdown-menu";
|
|
10
|
-
|
|
11
|
-
export interface AppOpenActionLabels {
|
|
12
|
-
addApp: string;
|
|
13
|
-
openApp: string;
|
|
14
|
-
openInline: string;
|
|
15
|
-
openInNewTab: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const DEFAULT_LABELS: AppOpenActionLabels = {
|
|
19
|
-
addApp: "Add app",
|
|
20
|
-
openApp: "Open app",
|
|
21
|
-
openInline: "Open inline",
|
|
22
|
-
openInNewTab: "Open in new tab",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export function AppOpenActions({
|
|
26
|
-
name,
|
|
27
|
-
href,
|
|
28
|
-
target,
|
|
29
|
-
rel,
|
|
30
|
-
labels: labelOverrides,
|
|
31
|
-
onAddApp,
|
|
32
|
-
onOpenInline,
|
|
33
|
-
showInlineOption = false,
|
|
34
|
-
showNewTabOption = false,
|
|
35
|
-
}: {
|
|
36
|
-
name: string;
|
|
37
|
-
href: string | null;
|
|
38
|
-
target?: "_blank";
|
|
39
|
-
rel?: string;
|
|
40
|
-
labels?: Partial<AppOpenActionLabels>;
|
|
41
|
-
onAddApp?: () => void;
|
|
42
|
-
onOpenInline?: () => void;
|
|
43
|
-
showInlineOption?: boolean;
|
|
44
|
-
showNewTabOption?: boolean;
|
|
45
|
-
}) {
|
|
46
|
-
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
47
|
-
const canOfferNewTab = showNewTabOption && target !== "_blank";
|
|
48
|
-
const hasMenu =
|
|
49
|
-
Boolean(onAddApp) ||
|
|
50
|
-
(Boolean(href) && (showInlineOption || canOfferNewTab));
|
|
51
|
-
|
|
52
|
-
if (!href && !hasMenu) {
|
|
53
|
-
return (
|
|
54
|
-
<Button size="sm" variant="outline" disabled>
|
|
55
|
-
{labels.openApp}
|
|
56
|
-
</Button>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div className="flex shrink-0 overflow-hidden rounded-md border border-border focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
|
|
62
|
-
<Button
|
|
63
|
-
asChild={Boolean(href)}
|
|
64
|
-
size="sm"
|
|
65
|
-
variant="outline"
|
|
66
|
-
className="rounded-none border-0 border-e border-border"
|
|
67
|
-
disabled={!href}
|
|
68
|
-
>
|
|
69
|
-
{href ? (
|
|
70
|
-
<a href={href} target={target} rel={rel}>
|
|
71
|
-
{labels.openApp}
|
|
72
|
-
</a>
|
|
73
|
-
) : (
|
|
74
|
-
<span>{labels.openApp}</span>
|
|
75
|
-
)}
|
|
76
|
-
</Button>
|
|
77
|
-
{hasMenu ? (
|
|
78
|
-
<DropdownMenu>
|
|
79
|
-
<DropdownMenuTrigger asChild>
|
|
80
|
-
<Button
|
|
81
|
-
type="button"
|
|
82
|
-
size="sm"
|
|
83
|
-
variant="outline"
|
|
84
|
-
className="size-9 rounded-none border-0 p-0"
|
|
85
|
-
aria-label={`Open options for ${name}`}
|
|
86
|
-
>
|
|
87
|
-
<IconChevronDown size={15} />
|
|
88
|
-
</Button>
|
|
89
|
-
</DropdownMenuTrigger>
|
|
90
|
-
<DropdownMenuContent align="end" className="min-w-40">
|
|
91
|
-
{onAddApp ? (
|
|
92
|
-
<DropdownMenuItem onSelect={onAddApp}>
|
|
93
|
-
<IconPlus size={14} className="mr-2" />
|
|
94
|
-
{labels.addApp}
|
|
95
|
-
</DropdownMenuItem>
|
|
96
|
-
) : null}
|
|
97
|
-
{showInlineOption && href ? (
|
|
98
|
-
<DropdownMenuItem onSelect={onOpenInline}>
|
|
99
|
-
{labels.openInline}
|
|
100
|
-
</DropdownMenuItem>
|
|
101
|
-
) : null}
|
|
102
|
-
{canOfferNewTab && href ? (
|
|
103
|
-
<DropdownMenuItem asChild>
|
|
104
|
-
<a href={href} target="_blank" rel="noreferrer">
|
|
105
|
-
{labels.openInNewTab}
|
|
106
|
-
</a>
|
|
107
|
-
</DropdownMenuItem>
|
|
108
|
-
) : null}
|
|
109
|
-
</DropdownMenuContent>
|
|
110
|
-
</DropdownMenu>
|
|
111
|
-
) : null}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
AppOpenActions,
|
|
3
|
+
type AppOpenActionLabels,
|
|
4
|
+
type AppOpenActionMenuItem,
|
|
5
|
+
} from "@agent-native/core/client/chat-first";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { APP_ACTION_MENU_CONTENT_CLASS } from "@agent-native/core/client/chat-first";
|
|
2
|
+
import { IconPlus, IconSettings } from "@tabler/icons-react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "./ui/dropdown-menu";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
|
12
|
+
|
|
13
|
+
const APP_ROW_ACTION_CLASS =
|
|
14
|
+
"size-7 rounded-md p-0 text-muted-foreground transition-[background-color,color] hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=open]:bg-accent data-[state=open]:text-foreground";
|
|
15
|
+
|
|
16
|
+
export function AppRowSettings({
|
|
17
|
+
name,
|
|
18
|
+
addAppLabel = "Add app",
|
|
19
|
+
onAddApp,
|
|
20
|
+
}: {
|
|
21
|
+
name: string;
|
|
22
|
+
addAppLabel?: string;
|
|
23
|
+
onAddApp: () => void;
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<DropdownMenu>
|
|
27
|
+
<Tooltip>
|
|
28
|
+
<TooltipTrigger asChild>
|
|
29
|
+
<DropdownMenuTrigger asChild>
|
|
30
|
+
<Button
|
|
31
|
+
type="button"
|
|
32
|
+
variant="ghost"
|
|
33
|
+
size="sm"
|
|
34
|
+
aria-label={`Settings for ${name}`}
|
|
35
|
+
className={APP_ROW_ACTION_CLASS}
|
|
36
|
+
>
|
|
37
|
+
<IconSettings size={15} />
|
|
38
|
+
</Button>
|
|
39
|
+
</DropdownMenuTrigger>
|
|
40
|
+
</TooltipTrigger>
|
|
41
|
+
<TooltipContent>App settings</TooltipContent>
|
|
42
|
+
</Tooltip>
|
|
43
|
+
<DropdownMenuContent
|
|
44
|
+
align="end"
|
|
45
|
+
className={APP_ACTION_MENU_CONTENT_CLASS}
|
|
46
|
+
>
|
|
47
|
+
<DropdownMenuItem onSelect={onAddApp}>
|
|
48
|
+
<IconPlus size={14} aria-hidden="true" />
|
|
49
|
+
{addAppLabel}
|
|
50
|
+
</DropdownMenuItem>
|
|
51
|
+
</DropdownMenuContent>
|
|
52
|
+
</DropdownMenu>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
IconAlertTriangle,
|
|
4
4
|
IconClock,
|
|
5
5
|
IconExternalLink,
|
|
6
|
-
IconLoader2,
|
|
7
6
|
} from "@tabler/icons-react";
|
|
8
7
|
import { Link } from "react-router";
|
|
9
8
|
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
DialogHeader,
|
|
22
21
|
DialogTitle,
|
|
23
22
|
} from "./ui/dialog";
|
|
23
|
+
import { Skeleton } from "./ui/skeleton";
|
|
24
24
|
|
|
25
25
|
interface AutomationRun {
|
|
26
26
|
id: string;
|
|
@@ -178,9 +178,13 @@ export function AutomationRunHistoryDialog({
|
|
|
178
178
|
|
|
179
179
|
<div className="mt-3">
|
|
180
180
|
{runsQuery.isLoading ? (
|
|
181
|
-
<div className="
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
<div className="space-y-3 rounded-lg border p-4">
|
|
182
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
183
|
+
<div key={index} className="space-y-2">
|
|
184
|
+
<Skeleton className="h-4 w-2/3" />
|
|
185
|
+
<Skeleton className="h-3 w-1/2" />
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
184
188
|
</div>
|
|
185
189
|
) : runsQuery.isError ? (
|
|
186
190
|
<ActionQueryError
|
|
@@ -12,6 +12,8 @@ export function ConnectedAppCard({
|
|
|
12
12
|
app: ConnectedAppSummary;
|
|
13
13
|
className?: string;
|
|
14
14
|
}) {
|
|
15
|
+
const launchUrl = app.homeUrl ?? app.url;
|
|
16
|
+
|
|
15
17
|
return (
|
|
16
18
|
<AppListRow className={className}>
|
|
17
19
|
<AppIcon id={app.id} name={app.name} color={app.color} size="sm" />
|
|
@@ -25,11 +27,10 @@ export function ConnectedAppCard({
|
|
|
25
27
|
</div>
|
|
26
28
|
<AppOpenActions
|
|
27
29
|
name={app.name}
|
|
28
|
-
href={withBuilderUtmTrackingParams(
|
|
30
|
+
href={withBuilderUtmTrackingParams(launchUrl, {
|
|
29
31
|
campaign: "product",
|
|
30
32
|
content: "dispatch_app",
|
|
31
33
|
})}
|
|
32
|
-
target="_blank"
|
|
33
34
|
rel="noopener noreferrer"
|
|
34
35
|
showNewTabOption
|
|
35
36
|
/>
|
|
@@ -339,7 +339,7 @@ describe("CreateAppFlow", () => {
|
|
|
339
339
|
});
|
|
340
340
|
});
|
|
341
341
|
const branchLink = Array.from(container.querySelectorAll("a")).find(
|
|
342
|
-
(candidate) => candidate.textContent?.includes("Open Builder
|
|
342
|
+
(candidate) => candidate.textContent?.includes("Open in Builder"),
|
|
343
343
|
);
|
|
344
344
|
expect(branchLink?.getAttribute("href")).toBe(
|
|
345
345
|
"https://branch.example.test",
|
|
@@ -319,7 +319,7 @@ export function CreateAppFlow({
|
|
|
319
319
|
</div>
|
|
320
320
|
<Button asChild className="w-full sm:w-auto">
|
|
321
321
|
<a href={branchUrl} target="_blank" rel="noreferrer">
|
|
322
|
-
Open Builder
|
|
322
|
+
Open in Builder <IconArrowUpRight aria-hidden="true" />
|
|
323
323
|
</a>
|
|
324
324
|
</Button>
|
|
325
325
|
</div>
|
|
@@ -583,7 +583,7 @@ export function CreateAppFlow({
|
|
|
583
583
|
rel="noreferrer"
|
|
584
584
|
className="inline-flex items-center gap-1 font-medium text-foreground underline"
|
|
585
585
|
>
|
|
586
|
-
Open
|
|
586
|
+
Open in Builder <IconArrowUpRight className="h-3 w-3" />
|
|
587
587
|
</a>
|
|
588
588
|
) : null}
|
|
589
589
|
</div>
|
|
@@ -33,6 +33,9 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
|
33
33
|
path: string,
|
|
34
34
|
options?: unknown,
|
|
35
35
|
) => clientState.navigateWithTransition(navigate, path, options),
|
|
36
|
+
orderChatFirstAppIds: (appIds: string[]) => appIds,
|
|
37
|
+
readChatFirstAppLayout: () => ({ pinnedIds: [], orderedIds: [] }),
|
|
38
|
+
writeChatFirstAppLayout: () => ({ ok: true }),
|
|
36
39
|
useChatModels: clientState.useChatModels,
|
|
37
40
|
}));
|
|
38
41
|
|
|
@@ -53,6 +56,11 @@ vi.mock("@agent-native/core/client/composer", () => ({
|
|
|
53
56
|
},
|
|
54
57
|
}));
|
|
55
58
|
|
|
59
|
+
vi.mock("@agent-native/core/client/application-state", () => ({
|
|
60
|
+
readClientAppState: vi.fn(async () => null),
|
|
61
|
+
writeClientAppState: vi.fn(async () => null),
|
|
62
|
+
}));
|
|
63
|
+
|
|
56
64
|
vi.mock("@agent-native/core/client/hooks", () => ({
|
|
57
65
|
useActionQuery: (name: string) => ({
|
|
58
66
|
data:
|
|
@@ -129,6 +137,15 @@ describe("DispatchControlPlane", () => {
|
|
|
129
137
|
expect(container.textContent).not.toContain("Open chat");
|
|
130
138
|
expect(container.textContent).not.toContain("Also");
|
|
131
139
|
expect(container.textContent).not.toContain("active");
|
|
140
|
+
expect(container.textContent).not.toContain(
|
|
141
|
+
"Summarize the current workspace health",
|
|
142
|
+
);
|
|
143
|
+
expect(container.textContent).toContain(
|
|
144
|
+
"Create an app for onboarding requests",
|
|
145
|
+
);
|
|
146
|
+
expect(container.textContent).toContain(
|
|
147
|
+
"Check which agents can help with analytics",
|
|
148
|
+
);
|
|
132
149
|
expect(container.querySelector("nav")).toBeNull();
|
|
133
150
|
expect(
|
|
134
151
|
container.querySelector('[data-placeholder="Ask Dispatch anything..."]'),
|
|
@@ -231,7 +248,9 @@ describe("DispatchControlPlane", () => {
|
|
|
231
248
|
id: "clips",
|
|
232
249
|
name: "Clips",
|
|
233
250
|
description: "Record and share",
|
|
234
|
-
url: "https://clips.agent-native.com",
|
|
251
|
+
url: "https://clips.agent-native.com/share/WrA8ZQ3oxa2T?ref=clip_share",
|
|
252
|
+
homeUrl: "https://clips.agent-native.com",
|
|
253
|
+
source: "builtin",
|
|
235
254
|
},
|
|
236
255
|
{
|
|
237
256
|
id: "onboarding",
|
|
@@ -272,15 +291,37 @@ describe("DispatchControlPlane", () => {
|
|
|
272
291
|
expect(container.textContent).toContain("Analytics");
|
|
273
292
|
expect(container.textContent).toContain("Apps");
|
|
274
293
|
expect(container.textContent).toContain("New");
|
|
294
|
+
const viewAllLink = Array.from(container.querySelectorAll("a")).find(
|
|
295
|
+
(link) => link.textContent?.trim() === "View all",
|
|
296
|
+
);
|
|
297
|
+
expect(viewAllLink?.className).toContain("text-muted-foreground");
|
|
275
298
|
expect(container.textContent).not.toContain("Other apps");
|
|
276
299
|
expect(container.textContent).not.toContain("available");
|
|
277
300
|
expect(container.textContent).not.toContain("Archived app");
|
|
278
301
|
expect(container.textContent).not.toContain("Duplicate onboarding");
|
|
279
302
|
expect(container.textContent).not.toContain("CRM");
|
|
280
303
|
expect(
|
|
281
|
-
|
|
282
|
-
|
|
304
|
+
container.querySelectorAll(
|
|
305
|
+
'button[aria-label="Open options for Onboarding"]',
|
|
283
306
|
),
|
|
284
307
|
).toHaveLength(1);
|
|
308
|
+
await act(async () => {
|
|
309
|
+
container
|
|
310
|
+
.querySelector<HTMLButtonElement>(
|
|
311
|
+
'button[aria-label="Open options for Onboarding"]',
|
|
312
|
+
)
|
|
313
|
+
?.dispatchEvent(
|
|
314
|
+
new MouseEvent("pointerdown", { bubbles: true, button: 0 }),
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
const onboardingNewTabLink = document.querySelector<HTMLAnchorElement>(
|
|
318
|
+
'a[href="/onboarding"][target="_blank"]',
|
|
319
|
+
);
|
|
320
|
+
expect(onboardingNewTabLink).not.toBeNull();
|
|
321
|
+
const clipsHref = Array.from(container.querySelectorAll("a"))
|
|
322
|
+
.map((anchor) => anchor.getAttribute("href"))
|
|
323
|
+
.find((href) => href?.includes("clips.agent-native.com"));
|
|
324
|
+
expect(clipsHref).toContain("https://clips.agent-native.com");
|
|
325
|
+
expect(clipsHref).not.toContain("/share/");
|
|
285
326
|
});
|
|
286
327
|
});
|
|
@@ -12,6 +12,11 @@ import { Link, useNavigate } from "react-router";
|
|
|
12
12
|
|
|
13
13
|
import type { ConnectedAppSummary } from "../lib/other-apps";
|
|
14
14
|
import { cn } from "../lib/utils";
|
|
15
|
+
import {
|
|
16
|
+
orderWorkspaceApps,
|
|
17
|
+
useWorkspaceAppLayout,
|
|
18
|
+
workspaceAppMatchesQuery,
|
|
19
|
+
} from "../lib/workspace-app-layout";
|
|
15
20
|
import {
|
|
16
21
|
isWorkspaceAppVisibleInDefaultLaunchers,
|
|
17
22
|
type WorkspaceAppSummary,
|
|
@@ -33,6 +38,10 @@ import {
|
|
|
33
38
|
} from "./ui/collapsible";
|
|
34
39
|
import { Skeleton } from "./ui/skeleton";
|
|
35
40
|
import { WorkspaceAppCard } from "./workspace-app-card";
|
|
41
|
+
import {
|
|
42
|
+
WorkspaceAppSearch,
|
|
43
|
+
WorkspaceAppSearchEmpty,
|
|
44
|
+
} from "./workspace-app-search";
|
|
36
45
|
import type { CuratedWorkspaceTemplatesResult } from "./workspace-template-card";
|
|
37
46
|
|
|
38
47
|
function SectionHeader({
|
|
@@ -65,9 +74,6 @@ function CommandPanel() {
|
|
|
65
74
|
} = useChatModels({ storageKey: "dispatch" });
|
|
66
75
|
const navigate = useNavigate();
|
|
67
76
|
const promptSuggestions = [
|
|
68
|
-
t("dispatch.pages.suggestionWorkspaceHealth", {
|
|
69
|
-
defaultValue: "Summarize the current workspace health",
|
|
70
|
-
}),
|
|
71
77
|
t("dispatch.pages.suggestionOnboardingApp", {
|
|
72
78
|
defaultValue: "Create an app for onboarding requests",
|
|
73
79
|
}),
|
|
@@ -165,11 +171,23 @@ function AppsPanel({
|
|
|
165
171
|
}) {
|
|
166
172
|
const t = useT();
|
|
167
173
|
const [showPending, setShowPending] = useState(false);
|
|
174
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
175
|
+
const { layout, persistenceError, togglePinned } = useWorkspaceAppLayout();
|
|
168
176
|
const visibleApps = apps.filter(
|
|
169
177
|
(app) => isWorkspaceAppVisibleInDefaultLaunchers(app) && !app.archived,
|
|
170
178
|
);
|
|
171
179
|
const activeApps = visibleApps.filter((app) => app.status !== "pending");
|
|
172
180
|
const pendingApps = visibleApps.filter((app) => app.status === "pending");
|
|
181
|
+
const orderedActiveApps = orderWorkspaceApps(activeApps, layout);
|
|
182
|
+
const orderedPendingApps = orderWorkspaceApps(pendingApps, layout);
|
|
183
|
+
const filteredActiveApps = orderedActiveApps.filter((app) =>
|
|
184
|
+
workspaceAppMatchesQuery(app, searchQuery),
|
|
185
|
+
);
|
|
186
|
+
const filteredPendingApps = orderedPendingApps.filter((app) =>
|
|
187
|
+
workspaceAppMatchesQuery(app, searchQuery),
|
|
188
|
+
);
|
|
189
|
+
const hasSearchResults =
|
|
190
|
+
filteredActiveApps.length > 0 || filteredPendingApps.length > 0;
|
|
173
191
|
const otherAppEntries = mergeOtherAppEntries({
|
|
174
192
|
templates: curatedTemplates,
|
|
175
193
|
connectedApps,
|
|
@@ -183,14 +201,23 @@ function AppsPanel({
|
|
|
183
201
|
<SectionHeader
|
|
184
202
|
title="Apps"
|
|
185
203
|
action={
|
|
186
|
-
<div className="flex items-center gap-1.5">
|
|
187
|
-
<Button variant="
|
|
188
|
-
<Link to="/apps"
|
|
204
|
+
<div className="flex min-w-0 flex-wrap items-center justify-end gap-1.5">
|
|
205
|
+
<Button variant="ghost" size="sm" asChild>
|
|
206
|
+
<Link to="/apps" className="text-muted-foreground">
|
|
207
|
+
View all
|
|
208
|
+
</Link>
|
|
189
209
|
</Button>
|
|
210
|
+
{!showSkeletons && visibleApps.length > 0 ? (
|
|
211
|
+
<WorkspaceAppSearch
|
|
212
|
+
className="w-[220px]"
|
|
213
|
+
query={searchQuery}
|
|
214
|
+
onQueryChange={setSearchQuery}
|
|
215
|
+
/>
|
|
216
|
+
) : null}
|
|
190
217
|
<CreateAppPopover
|
|
191
218
|
align="end"
|
|
192
219
|
trigger={
|
|
193
|
-
<Button variant="
|
|
220
|
+
<Button variant="default" size="sm" className="gap-1.5">
|
|
194
221
|
<IconPlus size={14} />
|
|
195
222
|
{t("dispatch.pages.newApp", { defaultValue: "New" })}
|
|
196
223
|
</Button>
|
|
@@ -199,19 +226,38 @@ function AppsPanel({
|
|
|
199
226
|
</div>
|
|
200
227
|
}
|
|
201
228
|
/>
|
|
229
|
+
{!showSkeletons && visibleApps.length > 0 && persistenceError ? (
|
|
230
|
+
<p className="text-xs text-muted-foreground" role="status">
|
|
231
|
+
{persistenceError === "both"
|
|
232
|
+
? t("dispatch.pages.appPinSaveFailed", {
|
|
233
|
+
defaultValue: "App pins could not be saved.",
|
|
234
|
+
})
|
|
235
|
+
: t("dispatch.pages.appPinSavedLocally", {
|
|
236
|
+
defaultValue: "App pins are saved on this device only.",
|
|
237
|
+
})}
|
|
238
|
+
</p>
|
|
239
|
+
) : null}
|
|
202
240
|
{showSkeletons ? (
|
|
203
241
|
<OverviewAppsSkeleton />
|
|
242
|
+
) : searchQuery.trim() && !hasSearchResults ? (
|
|
243
|
+
<WorkspaceAppSearchEmpty
|
|
244
|
+
query={searchQuery}
|
|
245
|
+
onClear={() => setSearchQuery("")}
|
|
246
|
+
/>
|
|
204
247
|
) : (
|
|
205
248
|
<>
|
|
206
249
|
<AppList className={APP_LIST_GRID_CLASS}>
|
|
207
|
-
{
|
|
250
|
+
{filteredActiveApps.map((app) => (
|
|
208
251
|
<WorkspaceAppCard
|
|
209
252
|
key={app.id}
|
|
210
253
|
app={app}
|
|
211
254
|
className={APP_LIST_GRID_ROW_CLASS}
|
|
255
|
+
isPinned={layout.pinnedIds.includes(app.id)}
|
|
256
|
+
onTogglePinned={() => togglePinned(app.id)}
|
|
212
257
|
/>
|
|
213
258
|
))}
|
|
214
|
-
{
|
|
259
|
+
{filteredActiveApps.length === 0 &&
|
|
260
|
+
!searchQuery.trim() &&
|
|
215
261
|
otherAppEntries.length === 0 &&
|
|
216
262
|
!curatedTemplatesLoading &&
|
|
217
263
|
!connectedAppsLoading &&
|
|
@@ -223,24 +269,30 @@ function AppsPanel({
|
|
|
223
269
|
})}
|
|
224
270
|
</p>
|
|
225
271
|
) : null}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
272
|
+
{!searchQuery.trim() ? (
|
|
273
|
+
<OtherAppsSection
|
|
274
|
+
templates={curatedTemplates}
|
|
275
|
+
connectedApps={connectedApps}
|
|
276
|
+
workspaceApps={apps}
|
|
277
|
+
templatesLoading={curatedTemplatesLoading}
|
|
278
|
+
connectedAppsLoading={connectedAppsLoading}
|
|
279
|
+
templatesError={curatedTemplatesError}
|
|
280
|
+
connectedAppsError={connectedAppsError}
|
|
281
|
+
onRetryTemplates={onRetryCuratedTemplates}
|
|
282
|
+
onRetryConnectedApps={onRetryConnectedApps}
|
|
283
|
+
heading={null}
|
|
284
|
+
embeddedInList
|
|
285
|
+
/>
|
|
286
|
+
) : null}
|
|
239
287
|
</AppList>
|
|
240
288
|
</>
|
|
241
289
|
)}
|
|
242
|
-
{pendingApps.length > 0
|
|
243
|
-
|
|
290
|
+
{pendingApps.length > 0 &&
|
|
291
|
+
(!searchQuery.trim() || filteredPendingApps.length > 0) ? (
|
|
292
|
+
<Collapsible
|
|
293
|
+
open={showPending || Boolean(searchQuery.trim())}
|
|
294
|
+
onOpenChange={setShowPending}
|
|
295
|
+
>
|
|
244
296
|
<div className="space-y-3 border-t pt-3">
|
|
245
297
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
246
298
|
<div className="flex min-w-0 items-center gap-2">
|
|
@@ -266,19 +318,23 @@ function AppsPanel({
|
|
|
266
318
|
size="sm"
|
|
267
319
|
className="h-7 gap-1.5 px-2 text-xs"
|
|
268
320
|
>
|
|
269
|
-
{showPending
|
|
321
|
+
{showPending || Boolean(searchQuery.trim())
|
|
270
322
|
? t("dispatch.pages.hidePendingApps")
|
|
271
323
|
: t("dispatch.pages.showPendingApps")}
|
|
272
324
|
<IconChevronDown
|
|
273
325
|
size={13}
|
|
274
|
-
className={
|
|
326
|
+
className={
|
|
327
|
+
showPending || searchQuery.trim()
|
|
328
|
+
? "rotate-180"
|
|
329
|
+
: undefined
|
|
330
|
+
}
|
|
275
331
|
/>
|
|
276
332
|
</Button>
|
|
277
333
|
</CollapsibleTrigger>
|
|
278
334
|
</div>
|
|
279
335
|
<CollapsibleContent>
|
|
280
336
|
<AppList className={APP_LIST_GRID_CLASS}>
|
|
281
|
-
{
|
|
337
|
+
{filteredPendingApps.map((app) => (
|
|
282
338
|
<WorkspaceAppCard
|
|
283
339
|
key={app.id}
|
|
284
340
|
app={app}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client/i18n";
|
|
2
2
|
import { IconInfoCircle } from "@tabler/icons-react";
|
|
3
3
|
import { type ReactNode } from "react";
|
|
4
|
+
import { useLocation } from "react-router";
|
|
4
5
|
|
|
6
|
+
import { dispatchDocsHrefForPath, DocsLink } from "./docs-link";
|
|
5
7
|
import { useSetPageTitle } from "./layout/HeaderActions";
|
|
6
8
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|
7
9
|
|
|
@@ -21,12 +23,16 @@ export function DispatchShell({
|
|
|
21
23
|
children: ReactNode;
|
|
22
24
|
}) {
|
|
23
25
|
const t = useT();
|
|
26
|
+
const location = useLocation();
|
|
27
|
+
const docsHref = dispatchDocsHrefForPath(location.pathname);
|
|
24
28
|
useSetPageTitle(
|
|
25
29
|
<div className="flex items-center gap-2 min-w-0">
|
|
26
30
|
<h1 className="text-lg font-semibold tracking-tight truncate text-foreground">
|
|
27
31
|
{title}
|
|
28
32
|
</h1>
|
|
29
|
-
{
|
|
33
|
+
{docsHref ? (
|
|
34
|
+
<DocsLink href={docsHref} label={`Open ${title} documentation`} />
|
|
35
|
+
) : description ? (
|
|
30
36
|
<Popover>
|
|
31
37
|
<PopoverTrigger asChild>
|
|
32
38
|
<button
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IconHelpCircle } from "@tabler/icons-react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
export const DISPATCH_DOCS = {
|
|
6
|
+
dispatch: "https://agent-native.com/docs/dispatch#what-it-does",
|
|
7
|
+
workspace: "https://agent-native.com/docs/agent-resources#global-resources",
|
|
8
|
+
integrations: "https://agent-native.com/docs/dispatch#secret-vault",
|
|
9
|
+
audit: "https://agent-native.com/docs/audit-log",
|
|
10
|
+
threadDebug: "https://agent-native.com/docs/dispatch#reliability",
|
|
11
|
+
dreams: "https://agent-native.com/docs/dispatch#dreams",
|
|
12
|
+
settings: "https://agent-native.com/docs/agent-surfaces",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export function dispatchDocsHrefForPath(pathname: string): string | undefined {
|
|
16
|
+
if (pathname === "/admin/workspace") return DISPATCH_DOCS.workspace;
|
|
17
|
+
if (pathname === "/admin/integrations") return DISPATCH_DOCS.integrations;
|
|
18
|
+
if (pathname === "/admin/audit") return DISPATCH_DOCS.audit;
|
|
19
|
+
if (pathname === "/admin/thread-debug") return DISPATCH_DOCS.threadDebug;
|
|
20
|
+
if (pathname === "/admin/dreams") return DISPATCH_DOCS.dreams;
|
|
21
|
+
if (pathname.startsWith("/admin/")) return DISPATCH_DOCS.dispatch;
|
|
22
|
+
if (pathname === "/settings" || pathname.startsWith("/settings/")) {
|
|
23
|
+
return DISPATCH_DOCS.settings;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DocsLink({
|
|
29
|
+
href,
|
|
30
|
+
label,
|
|
31
|
+
className,
|
|
32
|
+
}: {
|
|
33
|
+
href: string;
|
|
34
|
+
label: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<a
|
|
39
|
+
href={href}
|
|
40
|
+
target="_blank"
|
|
41
|
+
rel="noopener noreferrer"
|
|
42
|
+
aria-label={label}
|
|
43
|
+
title={label}
|
|
44
|
+
className={cn(
|
|
45
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent/60 hover:text-foreground",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<IconHelpCircle className="size-3" />
|
|
50
|
+
</a>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
formatThreadAge,
|
|
12
12
|
isElectronEmbeddedSearch,
|
|
13
13
|
NavContent,
|
|
14
|
+
shouldAutoCollapseDispatchSidebar,
|
|
14
15
|
} from "./Layout";
|
|
15
16
|
|
|
16
17
|
const clientState = vi.hoisted(() => ({
|
|
@@ -128,6 +129,15 @@ describe("Electron control-plane mode", () => {
|
|
|
128
129
|
});
|
|
129
130
|
});
|
|
130
131
|
|
|
132
|
+
describe("Dispatch workspace app sidebar", () => {
|
|
133
|
+
it("auto-collapses for app host routes but not the app catalog", () => {
|
|
134
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps/mail")).toBe(true);
|
|
135
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps/mail/settings")).toBe(true);
|
|
136
|
+
expect(shouldAutoCollapseDispatchSidebar("/apps")).toBe(false);
|
|
137
|
+
expect(shouldAutoCollapseDispatchSidebar("/chat")).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
131
141
|
describe("Dispatch NavContent", () => {
|
|
132
142
|
let container: HTMLDivElement;
|
|
133
143
|
let root: Root;
|
|
@@ -302,6 +312,9 @@ describe("Dispatch NavContent", () => {
|
|
|
302
312
|
);
|
|
303
313
|
expect(sidebarLabel?.textContent?.trim()).toBe("Dispatch");
|
|
304
314
|
expect(container.textContent).not.toContain("Agent-Native Dispatch");
|
|
315
|
+
expect(
|
|
316
|
+
sidebarLabel?.closest('a[data-dispatch-logo][href="/overview"]'),
|
|
317
|
+
).not.toBeNull();
|
|
305
318
|
|
|
306
319
|
const settingsLink = container.querySelector('a[href="/settings"]');
|
|
307
320
|
const adminLink = container.querySelector('a[href="/admin"]');
|