@agent-native/dispatch 0.18.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/actions/create-workspace-resource.d.ts +13 -13
  2. package/dist/actions/delete-workspace-resource.d.ts +13 -13
  3. package/dist/actions/grant-vault-secrets-to-app.js +1 -1
  4. package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
  5. package/dist/actions/list-vault-audit.js +1 -1
  6. package/dist/actions/list-vault-audit.js.map +1 -1
  7. package/dist/actions/list-vault-secret-options.js +2 -9
  8. package/dist/actions/list-vault-secret-options.js.map +1 -1
  9. package/dist/actions/list-vault-secrets.js +1 -1
  10. package/dist/actions/list-vault-secrets.js.map +1 -1
  11. package/dist/actions/provider-api-register.d.ts +13 -13
  12. package/dist/actions/set-app-creation-settings.js +1 -1
  13. package/dist/actions/set-app-creation-settings.js.map +1 -1
  14. package/dist/actions/set-vault-access-settings.js +1 -1
  15. package/dist/actions/set-vault-access-settings.js.map +1 -1
  16. package/dist/actions/sync-vault-to-app.js +1 -1
  17. package/dist/actions/sync-vault-to-app.js.map +1 -1
  18. package/dist/actions/view-screen.js +4 -3
  19. package/dist/actions/view-screen.js.map +1 -1
  20. package/dist/components/admin-navigation.d.ts +15 -0
  21. package/dist/components/admin-navigation.d.ts.map +1 -0
  22. package/dist/components/admin-navigation.js +174 -0
  23. package/dist/components/admin-navigation.js.map +1 -0
  24. package/dist/components/automation-run-history-dialog.js +1 -1
  25. package/dist/components/automation-run-history-dialog.js.map +1 -1
  26. package/dist/components/index.d.ts +1 -0
  27. package/dist/components/index.d.ts.map +1 -1
  28. package/dist/components/index.js +1 -0
  29. package/dist/components/index.js.map +1 -1
  30. package/dist/components/layout/Header.d.ts.map +1 -1
  31. package/dist/components/layout/Header.js +9 -0
  32. package/dist/components/layout/Header.js.map +1 -1
  33. package/dist/components/layout/Layout.d.ts +4 -1
  34. package/dist/components/layout/Layout.d.ts.map +1 -1
  35. package/dist/components/layout/Layout.js +14 -117
  36. package/dist/components/layout/Layout.js.map +1 -1
  37. package/dist/components/layout/workspace-apps-rail.d.ts +5 -0
  38. package/dist/components/layout/workspace-apps-rail.d.ts.map +1 -0
  39. package/dist/components/layout/workspace-apps-rail.js +76 -0
  40. package/dist/components/layout/workspace-apps-rail.js.map +1 -0
  41. package/dist/hooks/use-navigation-state.js +39 -16
  42. package/dist/hooks/use-navigation-state.js.map +1 -1
  43. package/dist/lib/automation-display.js +1 -1
  44. package/dist/lib/automation-display.js.map +1 -1
  45. package/dist/routes/index.d.ts.map +1 -1
  46. package/dist/routes/index.js +22 -0
  47. package/dist/routes/index.js.map +1 -1
  48. package/dist/routes/pages/admin._index.d.ts +5 -0
  49. package/dist/routes/pages/admin._index.d.ts.map +1 -0
  50. package/dist/routes/pages/admin._index.js +19 -0
  51. package/dist/routes/pages/admin._index.js.map +1 -0
  52. package/dist/routes/pages/admin.d.ts +5 -0
  53. package/dist/routes/pages/admin.d.ts.map +1 -0
  54. package/dist/routes/pages/admin.js +10 -0
  55. package/dist/routes/pages/admin.js.map +1 -0
  56. package/dist/routes/pages/approval.js +2 -2
  57. package/dist/routes/pages/approval.js.map +1 -1
  58. package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
  59. package/dist/routes/pages/apps.$appId.js +3 -2
  60. package/dist/routes/pages/apps.$appId.js.map +1 -1
  61. package/dist/routes/pages/integrations.d.ts.map +1 -1
  62. package/dist/routes/pages/integrations.js +2 -1
  63. package/dist/routes/pages/integrations.js.map +1 -1
  64. package/dist/routes/pages/operations.js +3 -3
  65. package/dist/routes/pages/operations.js.map +1 -1
  66. package/dist/routes/pages/settings.d.ts.map +1 -1
  67. package/dist/routes/pages/settings.js +1 -1
  68. package/dist/routes/pages/settings.js.map +1 -1
  69. package/dist/routes/pages/team.js +1 -1
  70. package/dist/routes/pages/team.js.map +1 -1
  71. package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -1
  72. package/dist/routes/pages/transactional-email.$appId.$id.js +5 -2
  73. package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -1
  74. package/dist/routes/pages/transactional-email.d.ts.map +1 -1
  75. package/dist/routes/pages/transactional-email.js +6 -2
  76. package/dist/routes/pages/transactional-email.js.map +1 -1
  77. package/dist/routes/pages/vault.d.ts.map +1 -1
  78. package/dist/routes/pages/vault.js +20 -16
  79. package/dist/routes/pages/vault.js.map +1 -1
  80. package/dist/server/lib/app-creation-store.js +2 -2
  81. package/dist/server/lib/app-creation-store.js.map +1 -1
  82. package/dist/server/lib/thread-debug-store.d.ts.map +1 -1
  83. package/dist/server/lib/thread-debug-store.js +2 -1
  84. package/dist/server/lib/thread-debug-store.js.map +1 -1
  85. package/dist/server/lib/vault-store.d.ts +15 -0
  86. package/dist/server/lib/vault-store.d.ts.map +1 -1
  87. package/dist/server/lib/vault-store.js +110 -17
  88. package/dist/server/lib/vault-store.js.map +1 -1
  89. package/dist/server/plugins/agent-chat.js +1 -1
  90. package/dist/server/plugins/agent-chat.js.map +1 -1
  91. package/package.json +3 -3
  92. package/src/actions/grant-vault-secrets-to-app.ts +1 -1
  93. package/src/actions/list-vault-audit.ts +1 -1
  94. package/src/actions/list-vault-secret-options.ts +2 -9
  95. package/src/actions/list-vault-secrets.ts +1 -1
  96. package/src/actions/set-app-creation-settings.ts +1 -1
  97. package/src/actions/set-vault-access-settings.ts +1 -1
  98. package/src/actions/sync-vault-to-app.ts +1 -1
  99. package/src/actions/view-screen.spec.ts +1 -0
  100. package/src/actions/view-screen.ts +5 -3
  101. package/src/components/admin-navigation.tsx +278 -0
  102. package/src/components/automation-run-history-dialog.tsx +1 -1
  103. package/src/components/index.ts +1 -0
  104. package/src/components/layout/Header.tsx +8 -0
  105. package/src/components/layout/Layout.spec.tsx +85 -6
  106. package/src/components/layout/Layout.tsx +62 -206
  107. package/src/components/layout/workspace-apps-rail.tsx +154 -0
  108. package/src/hooks/use-navigation-state.spec.ts +11 -0
  109. package/src/hooks/use-navigation-state.ts +37 -16
  110. package/src/lib/automation-display.spec.ts +1 -1
  111. package/src/lib/automation-display.ts +1 -1
  112. package/src/routes/index.spec.ts +8 -0
  113. package/src/routes/index.ts +25 -0
  114. package/src/routes/pages/admin._index.tsx +39 -0
  115. package/src/routes/pages/admin.tsx +15 -0
  116. package/src/routes/pages/approval.tsx +2 -2
  117. package/src/routes/pages/apps.$appId.tsx +7 -2
  118. package/src/routes/pages/integrations.tsx +3 -2
  119. package/src/routes/pages/operations.tsx +3 -3
  120. package/src/routes/pages/settings.tsx +7 -3
  121. package/src/routes/pages/team.tsx +1 -1
  122. package/src/routes/pages/transactional-email.$appId.$id.tsx +9 -2
  123. package/src/routes/pages/transactional-email.tsx +6 -2
  124. package/src/routes/pages/vault.tsx +152 -89
  125. package/src/server/lib/app-creation-store.ts +2 -2
  126. package/src/server/lib/approval-fencing.spec.ts +69 -0
  127. package/src/server/lib/thread-debug-store.spec.ts +7 -3
  128. package/src/server/lib/thread-debug-store.ts +2 -1
  129. package/src/server/lib/vault-store.spec.ts +59 -0
  130. package/src/server/lib/vault-store.ts +123 -17
  131. package/src/server/plugins/agent-chat.ts +1 -1
@@ -0,0 +1,278 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import {
3
+ IconActivity,
4
+ IconArrowUpRight,
5
+ IconBrain,
6
+ IconBrandTelegram,
7
+ IconChartBar,
8
+ IconFingerprint,
9
+ IconHistory,
10
+ IconKey,
11
+ IconLayersSubtract,
12
+ IconMail,
13
+ IconMessages,
14
+ IconPlugConnected,
15
+ IconPuzzle,
16
+ IconSettingsAutomation,
17
+ IconShield,
18
+ IconShieldCheck,
19
+ } from "@tabler/icons-react";
20
+ import { type ReactNode } from "react";
21
+ import { NavLink } from "react-router";
22
+
23
+ import { cn } from "../lib/utils";
24
+ import { useDispatchExtensions, type DispatchNavItem } from "./layout/Layout";
25
+
26
+ interface AdminNavGroup {
27
+ id: string;
28
+ label: string;
29
+ labelKey: string;
30
+ items: readonly DispatchNavItem[];
31
+ }
32
+
33
+ const BUILT_IN_ADMIN_NAV_GROUPS: readonly AdminNavGroup[] = [
34
+ {
35
+ id: "workspace",
36
+ label: "Workspace",
37
+ labelKey: "dispatch.pages.adminWorkspace",
38
+ items: [
39
+ {
40
+ id: "admin-overview",
41
+ to: "/admin",
42
+ label: "Overview",
43
+ icon: IconShield,
44
+ },
45
+ {
46
+ id: "admin-resources",
47
+ to: "/admin/workspace",
48
+ label: "Resources",
49
+ icon: IconLayersSubtract,
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ id: "operations",
55
+ label: "Operations",
56
+ labelKey: "dispatch.pages.adminOperations",
57
+ items: [
58
+ {
59
+ id: "operations",
60
+ to: "/admin/operations",
61
+ label: "Operations",
62
+ icon: IconActivity,
63
+ },
64
+ {
65
+ id: "metrics",
66
+ to: "/admin/metrics",
67
+ label: "Metrics",
68
+ icon: IconChartBar,
69
+ },
70
+ {
71
+ id: "audit",
72
+ to: "/admin/audit",
73
+ label: "Audit",
74
+ icon: IconHistory,
75
+ },
76
+ {
77
+ id: "thread-debug",
78
+ to: "/admin/thread-debug",
79
+ label: "Thread Debug",
80
+ icon: IconMessages,
81
+ },
82
+ ],
83
+ },
84
+ {
85
+ id: "automation",
86
+ label: "Automation & delivery",
87
+ labelKey: "dispatch.pages.adminAutomation",
88
+ items: [
89
+ {
90
+ id: "automations",
91
+ to: "/admin/automations",
92
+ label: "Automations",
93
+ icon: IconSettingsAutomation,
94
+ },
95
+ {
96
+ id: "approvals",
97
+ to: "/admin/approvals",
98
+ label: "Approvals",
99
+ icon: IconShieldCheck,
100
+ },
101
+ {
102
+ id: "destinations",
103
+ to: "/admin/destinations",
104
+ label: "Destinations",
105
+ icon: IconArrowUpRight,
106
+ },
107
+ {
108
+ id: "transactional-email",
109
+ to: "/admin/transactional-email",
110
+ label: "Transactional email",
111
+ icon: IconMail,
112
+ },
113
+ ],
114
+ },
115
+ {
116
+ id: "connections",
117
+ label: "Connections",
118
+ labelKey: "dispatch.pages.adminConnections",
119
+ items: [
120
+ {
121
+ id: "integrations",
122
+ to: "/admin/integrations",
123
+ label: "Integrations",
124
+ icon: IconPuzzle,
125
+ },
126
+ {
127
+ id: "vault",
128
+ to: "/admin/vault",
129
+ label: "Vault",
130
+ icon: IconKey,
131
+ },
132
+ {
133
+ id: "messaging",
134
+ to: "/admin/messaging",
135
+ label: "Messaging",
136
+ icon: IconBrandTelegram,
137
+ },
138
+ {
139
+ id: "identities",
140
+ to: "/admin/identities",
141
+ label: "Identities",
142
+ icon: IconFingerprint,
143
+ },
144
+ ],
145
+ },
146
+ {
147
+ id: "agent-platform",
148
+ label: "Agent platform",
149
+ labelKey: "dispatch.pages.adminAgentPlatform",
150
+ items: [
151
+ {
152
+ id: "agents",
153
+ to: "/admin/agents",
154
+ label: "Agents",
155
+ icon: IconPlugConnected,
156
+ },
157
+ {
158
+ id: "dreams",
159
+ to: "/admin/dreams",
160
+ label: "Dreams",
161
+ icon: IconBrain,
162
+ },
163
+ ],
164
+ },
165
+ ];
166
+
167
+ function itemLabel(t: ReturnType<typeof useT>, item: DispatchNavItem) {
168
+ return t(`dispatch.nav.${item.id}`, { defaultValue: item.label });
169
+ }
170
+
171
+ function AdminNavLink({
172
+ item,
173
+ onNavigate,
174
+ }: {
175
+ item: DispatchNavItem;
176
+ onNavigate?: () => void;
177
+ }) {
178
+ const t = useT();
179
+ const to = item.adminTo ?? item.to;
180
+ const label = itemLabel(t, item);
181
+ const Icon = item.icon;
182
+
183
+ return (
184
+ <li>
185
+ <NavLink
186
+ to={to}
187
+ end={item.id === "admin-overview"}
188
+ onClick={onNavigate}
189
+ className={({ isActive }) =>
190
+ cn(
191
+ "flex min-h-8 items-center gap-2 rounded-md px-2 text-sm transition-colors",
192
+ isActive
193
+ ? "bg-accent font-medium text-accent-foreground"
194
+ : "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
195
+ )
196
+ }
197
+ >
198
+ {Icon ? <Icon size={15} className="shrink-0" /> : null}
199
+ <span className="truncate">{label}</span>
200
+ </NavLink>
201
+ </li>
202
+ );
203
+ }
204
+
205
+ export function AdminShell({
206
+ children,
207
+ onNavigate,
208
+ }: {
209
+ children: ReactNode;
210
+ onNavigate?: () => void;
211
+ }) {
212
+ const t = useT();
213
+ const extensions = useDispatchExtensions();
214
+ const extensionItems = (extensions?.navItems ?? []).filter(
215
+ (item) => item.section === "operations",
216
+ );
217
+ const groups = extensionItems.length
218
+ ? [
219
+ ...BUILT_IN_ADMIN_NAV_GROUPS,
220
+ {
221
+ id: "workspace-extensions",
222
+ label: "Workspace extensions",
223
+ labelKey: "dispatch.pages.adminWorkspaceExtensions",
224
+ items: extensionItems,
225
+ },
226
+ ]
227
+ : BUILT_IN_ADMIN_NAV_GROUPS;
228
+
229
+ return (
230
+ <div
231
+ className="grid min-w-0 gap-6 lg:grid-cols-[13rem_minmax(0,1fr)]"
232
+ data-dispatch-admin-shell
233
+ >
234
+ <aside className="min-w-0 border-b pb-5 lg:sticky lg:top-2 lg:self-start lg:border-b-0 lg:border-e lg:pe-6">
235
+ <div className="px-2">
236
+ <div className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
237
+ {t("dispatch.nav.admin", { defaultValue: "Admin" })}
238
+ </div>
239
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">
240
+ {t("dispatch.pages.adminDescription", {
241
+ defaultValue: "Workspace controls and operations",
242
+ })}
243
+ </p>
244
+ </div>
245
+
246
+ <nav
247
+ aria-label={t("dispatch.pages.adminNavigation", {
248
+ defaultValue: "Admin navigation",
249
+ })}
250
+ className="mt-5 space-y-5"
251
+ >
252
+ {groups.map((group) => (
253
+ <section key={group.id}>
254
+ <h2 className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground/70">
255
+ {t(group.labelKey, { defaultValue: group.label })}
256
+ </h2>
257
+ <ul className="mt-1 space-y-0.5">
258
+ {group.items.map((item) => (
259
+ <AdminNavLink
260
+ key={item.id}
261
+ item={item}
262
+ onNavigate={onNavigate}
263
+ />
264
+ ))}
265
+ </ul>
266
+ </section>
267
+ ))}
268
+ </nav>
269
+ </aside>
270
+
271
+ <section className="min-w-0" data-dispatch-admin-content>
272
+ {children}
273
+ </section>
274
+ </div>
275
+ );
276
+ }
277
+
278
+ export { BUILT_IN_ADMIN_NAV_GROUPS };
@@ -65,7 +65,7 @@ function runDebugPath(run: AutomationRun): string | null {
65
65
  if (run.runId) params.set("runId", run.runId);
66
66
  else if (run.threadId) params.set("threadId", run.threadId);
67
67
  else return null;
68
- return `/thread-debug?${params.toString()}`;
68
+ return `/admin/thread-debug?${params.toString()}`;
69
69
  }
70
70
 
71
71
  export function AutomationRunHistoryDialog({
@@ -6,6 +6,7 @@
6
6
  * card list).
7
7
  */
8
8
  export { DispatchShell } from "./dispatch-shell.js";
9
+ export { AdminShell } from "./admin-navigation.js";
9
10
  export { Layout, NavContent } from "./layout/Layout.js";
10
11
  export type {
11
12
  DispatchExtensionConfig,
@@ -20,10 +20,18 @@ const pageTitles: Record<string, string> = {
20
20
  "/approvals": "Approvals",
21
21
  "/automations": "Automations",
22
22
  "/audit": "Audit",
23
+ "/dreams": "Dreams",
24
+ "/thread-debug": "Thread Debug",
25
+ "/transactional-email": "Transactional email",
23
26
  "/settings": "Settings",
24
27
  };
25
28
 
26
29
  function resolveTitle(pathname: string): string {
30
+ if (pathname === "/admin" || pathname === "/admin/") return "Admin";
31
+ if (pathname.startsWith("/admin/")) {
32
+ const adminPath = pathname.slice("/admin".length);
33
+ return pageTitles[adminPath] ?? "Admin";
34
+ }
27
35
  if (pageTitles[pathname]) return pageTitles[pathname];
28
36
 
29
37
  if (pathname.startsWith("/extensions")) return "Extensions";
@@ -4,6 +4,7 @@ import { createRoot, type Root } from "react-dom/client";
4
4
  import { MemoryRouter } from "react-router";
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
 
7
+ import { AdminShell } from "../admin-navigation";
7
8
  import { TooltipProvider } from "../ui/tooltip";
8
9
  import { formatThreadAge, NavContent } from "./Layout";
9
10
 
@@ -11,6 +12,7 @@ const clientState = vi.hoisted(() => ({
11
12
  createThread: vi.fn<() => Promise<string | null>>(),
12
13
  switchThread: vi.fn(),
13
14
  threads: [] as Array<Record<string, unknown>>,
15
+ workspaceApps: [] as Array<Record<string, unknown>>,
14
16
  }));
15
17
 
16
18
  vi.mock("@agent-native/core/client/agent-chat", () => ({
@@ -39,7 +41,11 @@ vi.mock("@agent-native/core/client/api-path", () => ({
39
41
  }));
40
42
 
41
43
  vi.mock("@agent-native/core/client/hooks", () => ({
42
- useActionQuery: () => ({ data: undefined }),
44
+ useActionQuery: (action: string) => ({
45
+ data:
46
+ action === "list-workspace-apps" ? clientState.workspaceApps : undefined,
47
+ isLoading: false,
48
+ }),
43
49
  }));
44
50
 
45
51
  vi.mock("@agent-native/core/client/i18n", () => ({
@@ -48,6 +54,7 @@ vi.mock("@agent-native/core/client/i18n", () => ({
48
54
  "dispatch.nav.chat": "Chat",
49
55
  "dispatch.nav.overview": "Overview",
50
56
  "dispatch.nav.apps": "Apps",
57
+ "dispatch.pages.workspaceApps": "Workspace apps",
51
58
  "dispatch.nav.operate": "Operate",
52
59
  "dispatch.nav.advanced": "Advanced",
53
60
  "dispatch.sidebar.newChat": "New chat",
@@ -118,6 +125,7 @@ describe("Dispatch NavContent", () => {
118
125
  source: { platform: "slack", url: "https://example.slack.com/thread" },
119
126
  },
120
127
  ];
128
+ clientState.workspaceApps = [];
121
129
  container = document.createElement("div");
122
130
  document.body.appendChild(container);
123
131
  root = createRoot(container);
@@ -160,16 +168,82 @@ describe("Dispatch NavContent", () => {
160
168
  });
161
169
 
162
170
  const lists = [...container.querySelectorAll("nav > ul")];
163
- expect(lists).toHaveLength(4);
171
+ expect(lists).toHaveLength(2);
164
172
  expect(lists[0].className).toContain("gap-1");
165
- expect(lists[1].className).toContain("mt-5");
166
173
  expect(lists[1].className).toContain("gap-1");
167
- expect(lists[2].className).toContain("mt-3");
168
- expect(lists[2].className).toContain("gap-1");
169
- expect(lists[3].querySelector('a[href="/settings"]')).not.toBeNull();
174
+ expect(lists[1].querySelector('a[href="/admin"]')).not.toBeNull();
175
+ expect(lists[1].querySelector('a[href="/settings"]')).not.toBeNull();
170
176
  expect(lists[0].querySelector("a")?.className).toContain("h-8 w-8");
171
177
  });
172
178
 
179
+ it("keeps management routes out of the primary navigation", async () => {
180
+ await act(async () => {
181
+ root.render(
182
+ <MemoryRouter initialEntries={["/overview"]}>
183
+ <TooltipProvider>
184
+ <NavContent />
185
+ </TooltipProvider>
186
+ </MemoryRouter>,
187
+ );
188
+ });
189
+
190
+ expect(container.querySelector('a[href="/admin"]')).not.toBeNull();
191
+ expect(container.querySelector('a[href="/operations"]')).toBeNull();
192
+ expect(container.querySelector('a[href="/metrics"]')).toBeNull();
193
+ expect(container.textContent).not.toContain("Automation & delivery");
194
+ });
195
+
196
+ it("renders the Admin control plane with grouped nested routes", async () => {
197
+ await act(async () => {
198
+ root.render(
199
+ <MemoryRouter initialEntries={["/admin/metrics"]}>
200
+ <TooltipProvider>
201
+ <AdminShell>
202
+ <div>Admin content</div>
203
+ </AdminShell>
204
+ </TooltipProvider>
205
+ </MemoryRouter>,
206
+ );
207
+ });
208
+
209
+ const shell = container.querySelector("[data-dispatch-admin-shell]");
210
+ expect(shell).not.toBeNull();
211
+ expect(shell?.textContent).toContain("Operations");
212
+ expect(shell?.textContent).toContain("Automation & delivery");
213
+ expect(shell?.querySelector('a[href="/admin/metrics"]')).not.toBeNull();
214
+ expect(
215
+ shell?.querySelector('a[href="/admin/metrics"][aria-current="page"]'),
216
+ ).not.toBeNull();
217
+ expect(shell?.querySelector('a[href="/metrics"]')).toBeNull();
218
+ expect(shell?.querySelector('a[href="/admin/apps"]')).toBeNull();
219
+ });
220
+
221
+ it("shows ready workspace apps as direct links and highlights the active app", async () => {
222
+ clientState.workspaceApps = [
223
+ { id: "calendar", name: "Calendar", path: "/calendar", status: "ready" },
224
+ { id: "pending", name: "Pending", path: "/pending", status: "pending" },
225
+ ];
226
+
227
+ await act(async () => {
228
+ root.render(
229
+ <MemoryRouter initialEntries={["/calendar/events"]}>
230
+ <TooltipProvider>
231
+ <NavContent />
232
+ </TooltipProvider>
233
+ </MemoryRouter>,
234
+ );
235
+ });
236
+
237
+ const rail = container.querySelector("[data-dispatch-apps-rail]");
238
+ expect(rail).not.toBeNull();
239
+ expect(rail?.textContent).toContain("Calendar");
240
+ expect(rail?.textContent).not.toContain("Pending");
241
+
242
+ const calendarLink = rail?.querySelector('a[href="/calendar"]');
243
+ expect(calendarLink).not.toBeNull();
244
+ expect(calendarLink?.getAttribute("aria-current")).toBe("page");
245
+ });
246
+
173
247
  it("keeps Dispatch branding and anchors Settings above the organization picker", async () => {
174
248
  await act(async () => {
175
249
  root.render(
@@ -188,6 +262,7 @@ describe("Dispatch NavContent", () => {
188
262
  expect(container.textContent).not.toContain("Agent-Native Dispatch");
189
263
 
190
264
  const settingsLink = container.querySelector('a[href="/settings"]');
265
+ const adminLink = container.querySelector('a[href="/admin"]');
191
266
  const organization = [...container.querySelectorAll("div")].find(
192
267
  (element) => element.textContent?.trim() === "Organization",
193
268
  );
@@ -196,11 +271,15 @@ describe("Dispatch NavContent", () => {
196
271
  );
197
272
 
198
273
  expect(settingsLink).not.toBeNull();
274
+ expect(adminLink).not.toBeNull();
199
275
  expect(organization).toBeDefined();
200
276
  expect(footerActions).not.toBeNull();
201
277
  expect(settingsLink!.compareDocumentPosition(organization!)).toBe(
202
278
  Node.DOCUMENT_POSITION_FOLLOWING,
203
279
  );
280
+ expect(adminLink!.compareDocumentPosition(settingsLink!)).toBe(
281
+ Node.DOCUMENT_POSITION_FOLLOWING,
282
+ );
204
283
  expect(organization!.compareDocumentPosition(footerActions!)).toBe(
205
284
  Node.DOCUMENT_POSITION_FOLLOWING,
206
285
  );