@agent-native/dispatch 0.20.4 → 0.22.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/actions/get-agent-thread-debug.d.ts +2 -0
- package/dist/actions/provider-api-register.d.ts +7 -7
- package/dist/actions/search-agent-threads.js +2 -2
- package/dist/actions/search-agent-threads.js.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +1 -1
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/components/create-app-popover.d.ts.map +1 -1
- package/dist/components/create-app-popover.js +5 -71
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/layout/HeaderActions.d.ts +1 -17
- package/dist/components/layout/HeaderActions.d.ts.map +1 -1
- package/dist/components/layout/HeaderActions.js +1 -51
- package/dist/components/layout/HeaderActions.js.map +1 -1
- package/dist/components/layout/Layout.d.ts +23 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +707 -18
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +1 -19
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +1 -22
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts +1 -27
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.js +1 -35
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/popover.d.ts +2 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +2 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/select.d.ts +1 -13
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/select.js +1 -26
- package/dist/components/ui/select.js.map +1 -1
- package/dist/hooks/use-mobile.d.ts +1 -1
- package/dist/hooks/use-mobile.d.ts.map +1 -1
- package/dist/hooks/use-mobile.js +1 -15
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/lib/automation-display.js +1 -1
- package/dist/lib/automation-display.js.map +1 -1
- package/dist/lib/workspace-apps.d.ts +6 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +21 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +21 -1
- 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 +288 -118
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +13 -0
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/thread-debug-store.d.ts +2 -0
- package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
- package/dist/server/lib/thread-debug-store.js +6 -2
- package/dist/server/lib/thread-debug-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +1 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/integrations.js +1 -1
- package/dist/server/plugins/integrations.js.map +1 -1
- package/package.json +3 -3
- package/src/actions/search-agent-threads.ts +2 -2
- package/src/actions/start-workspace-app-creation.ts +1 -1
- package/src/components/create-app-popover.spec.tsx +22 -0
- package/src/components/create-app-popover.tsx +30 -94
- package/src/components/layout/HeaderActions.tsx +1 -84
- package/src/components/layout/Layout.spec.tsx +35 -10
- package/src/components/layout/Layout.tsx +1394 -74
- package/src/components/ui/dialog.tsx +1 -120
- package/src/components/ui/dropdown-menu.tsx +1 -198
- package/src/components/ui/popover.tsx +3 -1
- package/src/components/ui/select.tsx +1 -158
- package/src/hooks/use-mobile.tsx +1 -21
- package/src/lib/automation-display.spec.ts +1 -1
- package/src/lib/automation-display.ts +1 -1
- package/src/lib/workspace-apps.ts +24 -0
- package/src/routes/pages/settings.tsx +72 -0
- package/src/routes/pages/thread-debug.spec.tsx +103 -5
- package/src/routes/pages/thread-debug.tsx +1051 -451
- package/src/server/lib/app-creation-store.spec.ts +13 -0
- package/src/server/lib/app-creation-store.ts +13 -0
- package/src/server/lib/mcp-gateway.spec.ts +18 -0
- package/src/server/lib/thread-debug-store.spec.ts +17 -0
- package/src/server/lib/thread-debug-store.ts +9 -2
- package/src/server/plugins/agent-chat.ts +1 -0
- package/src/server/plugins/integrations.ts +1 -1
- package/src/styles/dispatch.css +29 -0
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
import { PromptComposer } from "@agent-native/core/client/composer";
|
|
8
8
|
import { isInBuilderFrame } from "@agent-native/core/client/host";
|
|
9
9
|
import { useBuilderConnectFlow } from "@agent-native/core/client/settings/useBuilderStatus";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
buildChatFirstAppCreationPrompt,
|
|
12
|
+
getWorkspaceAppIdValidationError,
|
|
13
|
+
titleFromChatFirstAppPrompt,
|
|
14
|
+
} from "@agent-native/core/shared";
|
|
11
15
|
import {
|
|
12
16
|
IconAlertTriangle,
|
|
13
17
|
IconArrowLeft,
|
|
@@ -57,87 +61,6 @@ interface CreateAppPopoverProps {
|
|
|
57
61
|
align?: "start" | "center" | "end";
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
function slugify(value: string): string {
|
|
61
|
-
return value
|
|
62
|
-
.toLowerCase()
|
|
63
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
64
|
-
.replace(/^-+|-+$/g, "")
|
|
65
|
-
.replace(/^[^a-z]+/, "")
|
|
66
|
-
.slice(0, 48);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function titleFromPrompt(prompt: string): string {
|
|
70
|
-
const cleaned = prompt
|
|
71
|
-
.replace(/\b(build|create|make|an?|the|app|tool|dashboard)\b/gi, " ")
|
|
72
|
-
.replace(/\s+/g, " ")
|
|
73
|
-
.trim();
|
|
74
|
-
return slugify(cleaned || "new-app") || "new-app";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function buildAppCreationPrompt(input: {
|
|
78
|
-
appId: string;
|
|
79
|
-
prompt: string;
|
|
80
|
-
selectedKeys: string[];
|
|
81
|
-
selectedResources: WorkspaceResourceOption[];
|
|
82
|
-
vaultAccessMode: VaultAccessMode;
|
|
83
|
-
}): string {
|
|
84
|
-
const keyList = input.selectedKeys.join(", ");
|
|
85
|
-
const grantRequest =
|
|
86
|
-
input.vaultAccessMode === "all-apps"
|
|
87
|
-
? `Dispatch vault access: all saved vault keys are available to every workspace app by default. No per-app vault grants are needed.`
|
|
88
|
-
: keyList
|
|
89
|
-
? `Requested Dispatch vault key grants for this app: ${keyList}`
|
|
90
|
-
: `Requested Dispatch vault key grants for this app: none`;
|
|
91
|
-
const resourceList = input.selectedResources.length
|
|
92
|
-
? input.selectedResources
|
|
93
|
-
.map(
|
|
94
|
-
(resource) =>
|
|
95
|
-
`- ${resource.name} (${resource.kind}, ${resource.path})`,
|
|
96
|
-
)
|
|
97
|
-
.join("\n")
|
|
98
|
-
: "none";
|
|
99
|
-
|
|
100
|
-
return [
|
|
101
|
-
`Create a new agent-native app in this workspace.`,
|
|
102
|
-
`This is a new workspace app request, not a feature request for the current app.`,
|
|
103
|
-
``,
|
|
104
|
-
`Suggested app name: ${input.appId} (you may adjust the slug if it conflicts)`,
|
|
105
|
-
`User prompt: ${input.prompt.trim()}`,
|
|
106
|
-
`Generate a concise one-sentence app description from the user prompt before coding; save it in apps/${input.appId}/package.json "description" so Dispatch and A2A can describe the app.`,
|
|
107
|
-
`If the user mentions a product or company such as Granola, Loom, Superhuman, Linear, or Notion, treat it as product inspiration unless they explicitly ask to connect to that service. Do not invent or require third-party API keys like GRANOLA_API_KEY just because a product is named.`,
|
|
108
|
-
grantRequest,
|
|
109
|
-
`Requested Dispatch workspace resources for this app:\n${resourceList}`,
|
|
110
|
-
`Dispatch workspace resources with scope=all are inherited workspace context. Do not copy or sync them into the new app; every workspace app reads them at runtime and may override with app shared or personal resources.`,
|
|
111
|
-
``,
|
|
112
|
-
`Pick a UI template that fits the user's prompt — analytics, assets, brain, calendar, chat, content, design, dispatch, forms, mail, slides, or clips when none of the others fit.`,
|
|
113
|
-
`If you use the chat template, treat it as scaffolding only: the finished app must use the requested app's real name, home screen, navigation, package metadata, and manifest, and it must not leave visible "Chat", "Starter", "Blank app", or "New app" UI behind.`,
|
|
114
|
-
`Use the workspace app layout: create it under apps/${input.appId}, mount it at /${input.appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
|
|
115
|
-
`Important routing rule: from outside the app, link to /${input.appId}; inside apps/${input.appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/${input.appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs like /${input.appId}/${input.appId}/review.`,
|
|
116
|
-
`Prefer useActionQuery/useActionMutation for actions. If you must raw-fetch framework endpoints, wrap them with agentNativePath("/_agent-native/actions/<name>") so mounted apps call the right URL.`,
|
|
117
|
-
`Use relative workspace links like /${input.appId}. Do not hardcode localhost, 127.0.0.1, 8080, 8100, or any dev port; the active workspace gateway/browser origin owns the port.`,
|
|
118
|
-
`Use the framework/template UI stack: shadcn/ui components and @tabler/icons-react. Do not add lucide-react or another icon library for standard UI.`,
|
|
119
|
-
`Existing first-party apps are neighbors, not implementation details for this app. If the user's prompt mentions Mail, Calendar, Analytics, Brain, Assets, Dispatch, or other templates, treat them as existing hosted/connected apps that this app can link to or call through A2A/default connected agents. For example, Mail, Calendar, Analytics, Brain, and Assets already exist at https://mail.agent-native.com, https://calendar.agent-native.com, https://analytics.agent-native.com, https://brain.agent-native.com, and https://assets.agent-native.com.`,
|
|
120
|
-
`Do not create wrapper apps or scaffold child apps/routes for Mail, Calendar, Analytics, Brain, Assets, etc. inside apps/${input.appId} just so this app can access them. If the request is a cross-app dashboard or overview, build only the new dashboard/overview app and delegate to the existing apps for domain work.`,
|
|
121
|
-
`Only create another first-party app when the user explicitly asks for a customized app from that template; otherwise keep using the hosted/shared app so improvements to the base app keep flowing to users.`,
|
|
122
|
-
`Do not satisfy this by adding a route, page, component, or file inside apps/chat or another existing app unless the user explicitly asks to modify that existing app.`,
|
|
123
|
-
input.vaultAccessMode === "all-apps"
|
|
124
|
-
? `Do not create per-app Dispatch vault grants unless the workspace switches vault access to manual or the user explicitly asks for manual grants.`
|
|
125
|
-
: keyList
|
|
126
|
-
? `After the app exists, grant the selected Dispatch vault keys to appId "${input.appId}" and sync them once the app server is available. Treat these as requested grants, not active grants before creation succeeds.`
|
|
127
|
-
: `Do not grant any Dispatch vault keys unless the user asks later.`,
|
|
128
|
-
input.selectedResources.length
|
|
129
|
-
? `After the app exists, grant the selected Dispatch workspace resources to appId "${input.appId}". Do not sync All-app workspace resources; they are inherited.`
|
|
130
|
-
: `Do not grant any selected-only Dispatch workspace resources unless the user asks later.`,
|
|
131
|
-
``,
|
|
132
|
-
`App readiness requirements before handing off:`,
|
|
133
|
-
`- Ensure apps/${input.appId}/package.json exists with displayName/name and a concise description; Dispatch discovers workspace apps from apps/<app-id>/package.json, not a separate app registry.`,
|
|
134
|
-
`- Update the app manifest/package/deploy metadata needed by the existing workspace deployment model.`,
|
|
135
|
-
`- Ensure the React Router client entry preserves APP_BASE_PATH/VITE_APP_BASE_PATH via appBasePath() so /${input.appId} hydrates correctly.`,
|
|
136
|
-
`- Verify the app's agent card/A2A metadata is ready so Dispatch can discover and delegate to the app after deployment. Every sibling workspace app is available over A2A by default through call-agent, with names and descriptions from the workspace app registry.`,
|
|
137
|
-
`When it is ready, start or update the workspace dev server and navigate the user to the absolute path /${input.appId} on the workspace origin. Do not prefix with /dispatch/, /apps/, /workspace/, or any other Dispatch tab — the new app is mounted at the workspace root, not under Dispatch. If you have a navigate tool available, pass /${input.appId} verbatim; if you only have a window.location-style escape hatch, set it to /${input.appId}.`,
|
|
138
|
-
].join("\n");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
64
|
async function fetchJson(url: string, init?: RequestInit): Promise<any> {
|
|
142
65
|
const res = await fetch(url, init);
|
|
143
66
|
const data = await res.json().catch(() => null);
|
|
@@ -168,6 +91,8 @@ const ERROR_FAILURE_REASONS = new Set([
|
|
|
168
91
|
"credential-store-unavailable",
|
|
169
92
|
"settings-management-required",
|
|
170
93
|
]);
|
|
94
|
+
const LOCAL_APP_DOCS_URL =
|
|
95
|
+
"https://agent-native.com/docs/multi-app-workspace#adding-a-new-app";
|
|
171
96
|
|
|
172
97
|
function isErrorFailureReason(reason: string | null): boolean {
|
|
173
98
|
return !!reason && ERROR_FAILURE_REASONS.has(reason);
|
|
@@ -292,14 +217,14 @@ export function CreateAppFlow({
|
|
|
292
217
|
async function submit(rawPrompt: string) {
|
|
293
218
|
const trimmed = rawPrompt.trim();
|
|
294
219
|
if (!trimmed || isSubmitting) return;
|
|
295
|
-
const appId =
|
|
220
|
+
const appId = titleFromChatFirstAppPrompt(trimmed);
|
|
296
221
|
const validationError = getWorkspaceAppIdValidationError(appId);
|
|
297
222
|
if (validationError) {
|
|
298
223
|
setStatusMessage(validationError);
|
|
299
224
|
return;
|
|
300
225
|
}
|
|
301
226
|
|
|
302
|
-
const message =
|
|
227
|
+
const message = buildChatFirstAppCreationPrompt({
|
|
303
228
|
appId,
|
|
304
229
|
prompt: trimmed,
|
|
305
230
|
selectedKeys:
|
|
@@ -653,16 +578,27 @@ export function CreateAppFlow({
|
|
|
653
578
|
) : null}
|
|
654
579
|
</div>
|
|
655
580
|
{failureReason === "builder-not-connected" ? (
|
|
656
|
-
<
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
581
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
582
|
+
<Button
|
|
583
|
+
type="button"
|
|
584
|
+
size="sm"
|
|
585
|
+
variant="outline"
|
|
586
|
+
onClick={() => connectFlow.start()}
|
|
587
|
+
disabled={connectFlow.connecting}
|
|
588
|
+
className="w-fit"
|
|
589
|
+
>
|
|
590
|
+
{connectFlow.connecting ? "Connecting..." : "Connect Builder"}
|
|
591
|
+
</Button>
|
|
592
|
+
<a
|
|
593
|
+
href={LOCAL_APP_DOCS_URL}
|
|
594
|
+
target="_blank"
|
|
595
|
+
rel="noreferrer"
|
|
596
|
+
data-create-app-local-link
|
|
597
|
+
className="inline-flex items-center gap-1 text-xs font-medium text-foreground underline underline-offset-2"
|
|
598
|
+
>
|
|
599
|
+
Create locally <IconArrowUpRight className="h-3 w-3" />
|
|
600
|
+
</a>
|
|
601
|
+
</div>
|
|
666
602
|
) : null}
|
|
667
603
|
{failureReason === "credential-store-unavailable" ||
|
|
668
604
|
failureReason === "builder-error" ? (
|
|
@@ -1,84 +1 @@
|
|
|
1
|
-
|
|
2
|
-
useEffect,
|
|
3
|
-
useSyncExternalStore,
|
|
4
|
-
type ReactNode,
|
|
5
|
-
type FC,
|
|
6
|
-
} from "react";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* External store for the page's header title + actions. We use an external
|
|
10
|
-
* store (not React context) so that pages can inject ReactNode without
|
|
11
|
-
* subscribing to the header state themselves — subscribing would trigger a
|
|
12
|
-
* re-render on every update, which in turn creates new JSX, which updates
|
|
13
|
-
* the store again, which re-renders… an infinite loop.
|
|
14
|
-
*
|
|
15
|
-
* Only <Header /> reads the store via useSyncExternalStore; pages only write.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
type Listener = () => void;
|
|
19
|
-
|
|
20
|
-
let currentTitle: ReactNode = null;
|
|
21
|
-
let currentActions: ReactNode = null;
|
|
22
|
-
const listeners = new Set<Listener>();
|
|
23
|
-
|
|
24
|
-
function notify() {
|
|
25
|
-
for (const l of listeners) l();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function subscribe(l: Listener): () => void {
|
|
29
|
-
listeners.add(l);
|
|
30
|
-
return () => {
|
|
31
|
-
listeners.delete(l);
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Consumed only by <Header /> — returns the current title. */
|
|
36
|
-
export function useHeaderTitle(): ReactNode {
|
|
37
|
-
return useSyncExternalStore(
|
|
38
|
-
subscribe,
|
|
39
|
-
() => currentTitle,
|
|
40
|
-
() => currentTitle,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Consumed only by <Header /> — returns the current actions slot. */
|
|
45
|
-
export function useHeaderActions(): ReactNode {
|
|
46
|
-
return useSyncExternalStore(
|
|
47
|
-
subscribe,
|
|
48
|
-
() => currentActions,
|
|
49
|
-
() => currentActions,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Provider is now a no-op wrapper for backwards compatibility — the state
|
|
55
|
-
* lives in the module-level store above. Kept as a component so callers of
|
|
56
|
-
* <HeaderActionsProvider> don't need to change.
|
|
57
|
-
*/
|
|
58
|
-
export const HeaderActionsProvider: FC<{ children: ReactNode }> = ({
|
|
59
|
-
children,
|
|
60
|
-
}) => <>{children}</>;
|
|
61
|
-
|
|
62
|
-
/** Mount a custom title into the app header. Cleans up on unmount. */
|
|
63
|
-
export function useSetPageTitle(node: ReactNode) {
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
currentTitle = node;
|
|
66
|
-
notify();
|
|
67
|
-
return () => {
|
|
68
|
-
currentTitle = null;
|
|
69
|
-
notify();
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Mount ReactNode into the header's actions slot. Cleans up on unmount. */
|
|
75
|
-
export function useSetHeaderActions(node: ReactNode) {
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
currentActions = node;
|
|
78
|
-
notify();
|
|
79
|
-
return () => {
|
|
80
|
-
currentActions = null;
|
|
81
|
-
notify();
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
}
|
|
1
|
+
export * from "@agent-native/toolkit/app-shell";
|
|
@@ -6,7 +6,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
|
|
7
7
|
import { AdminShell } from "../admin-navigation";
|
|
8
8
|
import { TooltipProvider } from "../ui/tooltip";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildChatFirstEmbedSessionInput,
|
|
11
|
+
formatThreadAge,
|
|
12
|
+
NavContent,
|
|
13
|
+
} from "./Layout";
|
|
10
14
|
|
|
11
15
|
const clientState = vi.hoisted(() => ({
|
|
12
16
|
createThread: vi.fn<() => Promise<string | null>>(),
|
|
@@ -36,6 +40,7 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
|
|
|
36
40
|
}));
|
|
37
41
|
|
|
38
42
|
vi.mock("@agent-native/core/client/api-path", () => ({
|
|
43
|
+
agentNativePath: (path: string) => path,
|
|
39
44
|
appBasePath: () => "",
|
|
40
45
|
appPath: (path: string) => path,
|
|
41
46
|
}));
|
|
@@ -96,6 +101,16 @@ describe("formatThreadAge", () => {
|
|
|
96
101
|
});
|
|
97
102
|
});
|
|
98
103
|
|
|
104
|
+
describe("chat-first embed sessions", () => {
|
|
105
|
+
it("keeps the granted app id on app-relative embed requests", () => {
|
|
106
|
+
expect(buildChatFirstEmbedSessionInput("mail", "/mail/inbox")).toEqual({
|
|
107
|
+
app: "mail",
|
|
108
|
+
path: "/mail/inbox",
|
|
109
|
+
chrome: "minimal",
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
99
114
|
describe("Dispatch NavContent", () => {
|
|
100
115
|
let container: HTMLDivElement;
|
|
101
116
|
let root: Root;
|
|
@@ -218,7 +233,7 @@ describe("Dispatch NavContent", () => {
|
|
|
218
233
|
expect(shell?.querySelector('a[href="/admin/apps"]')).toBeNull();
|
|
219
234
|
});
|
|
220
235
|
|
|
221
|
-
it("
|
|
236
|
+
it("keeps workspace app discovery in the Apps destination", async () => {
|
|
222
237
|
clientState.workspaceApps = [
|
|
223
238
|
{ id: "calendar", name: "Calendar", path: "/calendar", status: "ready" },
|
|
224
239
|
{ id: "pending", name: "Pending", path: "/pending", status: "pending" },
|
|
@@ -226,7 +241,7 @@ describe("Dispatch NavContent", () => {
|
|
|
226
241
|
|
|
227
242
|
await act(async () => {
|
|
228
243
|
root.render(
|
|
229
|
-
<MemoryRouter initialEntries={["/
|
|
244
|
+
<MemoryRouter initialEntries={["/overview"]}>
|
|
230
245
|
<TooltipProvider>
|
|
231
246
|
<NavContent />
|
|
232
247
|
</TooltipProvider>
|
|
@@ -234,14 +249,24 @@ describe("Dispatch NavContent", () => {
|
|
|
234
249
|
);
|
|
235
250
|
});
|
|
236
251
|
|
|
237
|
-
|
|
238
|
-
expect(
|
|
239
|
-
expect(
|
|
240
|
-
expect(
|
|
252
|
+
expect(container.querySelector("[data-dispatch-apps-rail]")).toBeNull();
|
|
253
|
+
expect(container.querySelector('a[href="/apps"]')).not.toBeNull();
|
|
254
|
+
expect(container.textContent).not.toContain("Calendar");
|
|
255
|
+
expect(container.textContent).not.toContain("Pending");
|
|
241
256
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
257
|
+
await act(async () => {
|
|
258
|
+
root.render(
|
|
259
|
+
<MemoryRouter initialEntries={["/apps/calendar"]}>
|
|
260
|
+
<TooltipProvider>
|
|
261
|
+
<NavContent />
|
|
262
|
+
</TooltipProvider>
|
|
263
|
+
</MemoryRouter>,
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(container.querySelector('a[href="/apps"]')?.className).toContain(
|
|
268
|
+
"bg-sidebar-accent",
|
|
269
|
+
);
|
|
245
270
|
});
|
|
246
271
|
|
|
247
272
|
it("keeps Dispatch branding and anchors Settings above the organization picker", async () => {
|