@agent-native/dispatch 0.8.18 → 0.8.19

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 (40) hide show
  1. package/dist/actions/navigate.js +1 -1
  2. package/dist/actions/navigate.js.map +1 -1
  3. package/dist/actions/view-screen.d.ts.map +1 -1
  4. package/dist/actions/view-screen.js +6 -0
  5. package/dist/actions/view-screen.js.map +1 -1
  6. package/dist/components/layout/Layout.d.ts.map +1 -1
  7. package/dist/components/layout/Layout.js +91 -14
  8. package/dist/components/layout/Layout.js.map +1 -1
  9. package/dist/hooks/use-navigation-state.js +5 -0
  10. package/dist/hooks/use-navigation-state.js.map +1 -1
  11. package/dist/lib/overview-chat.d.ts +3 -1
  12. package/dist/lib/overview-chat.d.ts.map +1 -1
  13. package/dist/lib/overview-chat.js +2 -1
  14. package/dist/lib/overview-chat.js.map +1 -1
  15. package/dist/routes/index.d.ts.map +1 -1
  16. package/dist/routes/index.js +1 -0
  17. package/dist/routes/index.js.map +1 -1
  18. package/dist/routes/pages/_index.js +1 -1
  19. package/dist/routes/pages/_index.js.map +1 -1
  20. package/dist/routes/pages/chat.d.ts +5 -0
  21. package/dist/routes/pages/chat.d.ts.map +1 -0
  22. package/dist/routes/pages/chat.js +55 -0
  23. package/dist/routes/pages/chat.js.map +1 -0
  24. package/dist/routes/pages/overview.d.ts.map +1 -1
  25. package/dist/routes/pages/overview.js +20 -7
  26. package/dist/routes/pages/overview.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/actions/navigate.ts +1 -1
  29. package/src/actions/view-screen.ts +7 -0
  30. package/src/components/layout/Layout.tsx +187 -18
  31. package/src/hooks/use-navigation-state.spec.ts +7 -0
  32. package/src/hooks/use-navigation-state.ts +4 -0
  33. package/src/lib/overview-chat.spec.ts +15 -0
  34. package/src/lib/overview-chat.ts +2 -0
  35. package/src/routes/index.ts +1 -0
  36. package/src/routes/pages/_index.tsx +1 -1
  37. package/src/routes/pages/chat.tsx +99 -0
  38. package/src/routes/pages/overview.tsx +21 -5
  39. package/src/styles/dispatch-css.spec.ts +9 -0
  40. package/src/styles/dispatch.css +103 -0
@@ -1,11 +1,19 @@
1
- import { useState, type ComponentType, type ReactNode } from "react";
2
- import { NavLink, useLocation } from "react-router";
1
+ import {
2
+ useEffect,
3
+ useMemo,
4
+ useState,
5
+ type ComponentType,
6
+ type ReactNode,
7
+ } from "react";
8
+ import { NavLink, useLocation, useNavigate } from "react-router";
3
9
  import {
4
10
  AgentSidebar,
5
11
  FeedbackButton,
6
12
  appBasePath,
7
13
  appPath,
8
14
  useActionQuery,
15
+ useChatThreads,
16
+ type ChatThreadSummary,
9
17
  } from "@agent-native/core/client";
10
18
  import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
11
19
  import { InvitationBanner, OrgSwitcher } from "@agent-native/core/client/org";
@@ -18,7 +26,9 @@ import {
18
26
  IconKey,
19
27
  IconChevronDown,
20
28
  IconLayersSubtract,
29
+ IconMessageQuestion,
21
30
  IconMessages,
31
+ IconPlus,
22
32
  IconPlugConnected,
23
33
  IconBroadcast,
24
34
  IconFingerprint,
@@ -34,6 +44,11 @@ import {
34
44
  SheetDescription,
35
45
  SheetTitle,
36
46
  } from "@/components/ui/sheet";
47
+ import {
48
+ Tooltip,
49
+ TooltipContent,
50
+ TooltipTrigger,
51
+ } from "@/components/ui/tooltip";
37
52
  import { Header } from "./Header";
38
53
  import { HeaderActionsProvider } from "./HeaderActions";
39
54
 
@@ -65,6 +80,13 @@ export interface DispatchExtensionConfig {
65
80
  }
66
81
 
67
82
  const PRIMARY_NAV_ITEMS = [
83
+ {
84
+ id: "chat",
85
+ to: "/chat",
86
+ label: "Chat",
87
+ icon: IconMessageQuestion,
88
+ section: "primary",
89
+ },
68
90
  {
69
91
  id: "overview",
70
92
  to: "/overview",
@@ -249,6 +271,142 @@ function dispatchNavLinkTarget(path: string): string {
249
271
  return routerHasBasename ? path : appPath(path);
250
272
  }
251
273
 
274
+ function formatThreadAge(updatedAt: number) {
275
+ const diffMs = Math.max(0, Date.now() - updatedAt);
276
+ const minutes = Math.floor(diffMs / 60_000);
277
+ if (minutes < 1) return "now";
278
+ if (minutes < 60) return `${minutes}m`;
279
+ const hours = Math.floor(minutes / 60);
280
+ if (hours < 24) return `${hours}h`;
281
+ const days = Math.floor(hours / 24);
282
+ if (days < 7) return `${days}d`;
283
+ return new Date(updatedAt).toLocaleDateString([], {
284
+ month: "short",
285
+ day: "numeric",
286
+ });
287
+ }
288
+
289
+ function threadTitle(thread: ChatThreadSummary) {
290
+ return thread.title || thread.preview || "New chat";
291
+ }
292
+
293
+ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
294
+ const navigate = useNavigate();
295
+ const {
296
+ threads,
297
+ activeThreadId,
298
+ createThread,
299
+ switchThread,
300
+ refreshThreads,
301
+ } = useChatThreads(undefined, undefined, undefined, { autoCreate: false });
302
+
303
+ const visibleThreads = useMemo(
304
+ () =>
305
+ threads
306
+ .filter(
307
+ (thread) => thread.messageCount > 0 || thread.id === activeThreadId,
308
+ )
309
+ .sort((a, b) => b.updatedAt - a.updatedAt)
310
+ .slice(0, 8),
311
+ [activeThreadId, threads],
312
+ );
313
+
314
+ useEffect(() => {
315
+ const refresh = () => refreshThreads();
316
+ const handleRunning = (event: Event) => {
317
+ const detail = (event as CustomEvent).detail as
318
+ | { isRunning?: unknown }
319
+ | undefined;
320
+ if (detail?.isRunning === false) refreshThreads();
321
+ };
322
+
323
+ window.addEventListener("agent-chat:threads-updated", refresh);
324
+ window.addEventListener("agentNative.chatRunning", handleRunning);
325
+ window.addEventListener("focus", refresh);
326
+ return () => {
327
+ window.removeEventListener("agent-chat:threads-updated", refresh);
328
+ window.removeEventListener("agentNative.chatRunning", handleRunning);
329
+ window.removeEventListener("focus", refresh);
330
+ };
331
+ }, [refreshThreads]);
332
+
333
+ function openThread(threadId: string, options?: { isNew?: boolean }) {
334
+ switchThread(threadId);
335
+ navigate(dispatchNavLinkTarget("/chat"));
336
+ onNavigate?.();
337
+ window.requestAnimationFrame(() => {
338
+ window.dispatchEvent(
339
+ new CustomEvent("agent-chat:open-thread", {
340
+ detail: { threadId, newThread: options?.isNew === true },
341
+ }),
342
+ );
343
+ });
344
+ }
345
+
346
+ async function handleNewChat() {
347
+ const threadId = await createThread();
348
+ if (threadId) openThread(threadId, { isNew: true });
349
+ }
350
+
351
+ return (
352
+ <div className="mt-2 border-l border-sidebar-border/70 pl-3">
353
+ <div className="mb-1 flex h-7 items-center gap-2 pr-1">
354
+ <div className="min-w-0 flex-1 text-xs font-medium text-sidebar-foreground/70">
355
+ Chats
356
+ </div>
357
+ <Tooltip>
358
+ <TooltipTrigger asChild>
359
+ <button
360
+ type="button"
361
+ onClick={handleNewChat}
362
+ className="flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
363
+ aria-label="New Dispatch chat"
364
+ >
365
+ <IconPlus className="size-3.5" />
366
+ </button>
367
+ </TooltipTrigger>
368
+ <TooltipContent>New chat</TooltipContent>
369
+ </Tooltip>
370
+ </div>
371
+ <div className="grid gap-0.5">
372
+ {visibleThreads.length > 0 ? (
373
+ visibleThreads.map((thread) => {
374
+ const isActive = thread.id === activeThreadId;
375
+ return (
376
+ <button
377
+ key={thread.id}
378
+ type="button"
379
+ onClick={() => openThread(thread.id)}
380
+ className={cn(
381
+ "flex h-8 min-w-0 cursor-pointer items-center gap-2 rounded-md px-2 text-left text-sm transition-colors",
382
+ isActive
383
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
384
+ : "text-sidebar-foreground/80 hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
385
+ )}
386
+ >
387
+ <span className="min-w-0 flex-1 truncate">
388
+ {threadTitle(thread)}
389
+ </span>
390
+ <span className="shrink-0 text-[11px] text-sidebar-foreground/50">
391
+ {isActive ? "" : formatThreadAge(thread.updatedAt)}
392
+ </span>
393
+ </button>
394
+ );
395
+ })
396
+ ) : (
397
+ <button
398
+ type="button"
399
+ onClick={handleNewChat}
400
+ className="flex h-8 cursor-pointer items-center rounded-md px-2 text-left text-sm text-sidebar-foreground/70 transition-colors hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground"
401
+ >
402
+ <span className="truncate">New chat</span>
403
+ </button>
404
+ )}
405
+ </div>
406
+ </div>
407
+ );
408
+ }
409
+
252
410
  export function NavContent({
253
411
  onNavigate,
254
412
  extensions,
@@ -302,6 +460,9 @@ export function NavContent({
302
460
  )}
303
461
  <span className="truncate">{item.label}</span>
304
462
  </NavLink>
463
+ {item.id === "chat" ? (
464
+ <DispatchChatsSection onNavigate={onNavigate} />
465
+ ) : null}
305
466
  </li>
306
467
  );
307
468
  };
@@ -384,17 +545,24 @@ export function Layout({
384
545
  }) {
385
546
  const location = useLocation();
386
547
  const [mobileOpen, setMobileOpen] = useState(false);
548
+ const localPathname = localDispatchPath(location.pathname);
387
549
 
388
- if (CHROMELESS_PATHS.some((path) => location.pathname === path)) {
550
+ if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
389
551
  return <>{children}</>;
390
552
  }
391
553
 
392
- const showHeader = !pageOwnsToolbar(location.pathname);
554
+ const isChatRoute = localPathname === "/chat";
555
+ const showHeader = !isChatRoute && !pageOwnsToolbar(localPathname);
393
556
  const appContent = (
394
- <div className="flex h-full flex-1 flex-col overflow-hidden">
557
+ <div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden">
395
558
  {showHeader ? <Header onOpenMobile={() => setMobileOpen(true)} /> : null}
396
559
  <InvitationBanner />
397
- <main className="flex-1 overflow-y-auto">
560
+ <main
561
+ className={cn(
562
+ "flex-1",
563
+ isChatRoute ? "min-h-0 overflow-hidden" : "overflow-y-auto",
564
+ )}
565
+ >
398
566
  {showHeader ? (
399
567
  <div className="mx-auto max-w-7xl space-y-10 px-4 py-6 sm:px-6">
400
568
  {children}
@@ -405,6 +573,18 @@ export function Layout({
405
573
  </main>
406
574
  </div>
407
575
  );
576
+ const content = isChatRoute ? (
577
+ appContent
578
+ ) : (
579
+ <AgentSidebar
580
+ position="right"
581
+ defaultOpen={false}
582
+ emptyStateText="Create apps, manage vault keys, and route work across the workspace."
583
+ suggestions={SIDEBAR_SUGGESTIONS}
584
+ >
585
+ {appContent}
586
+ </AgentSidebar>
587
+ );
408
588
 
409
589
  return (
410
590
  <HeaderActionsProvider>
@@ -431,18 +611,7 @@ export function Layout({
431
611
  </SheetContent>
432
612
  </Sheet>
433
613
 
434
- {/*
435
- * Always mount AgentSidebar so home composer's sendToAgentChat
436
- * fallback can pop it via agent-panel:open.
437
- */}
438
- <AgentSidebar
439
- position="right"
440
- defaultOpen={false}
441
- emptyStateText="Create apps, manage vault keys, and route work across the workspace."
442
- suggestions={SIDEBAR_SUGGESTIONS}
443
- >
444
- {appContent}
445
- </AgentSidebar>
614
+ {content}
446
615
  </div>
447
616
  </HeaderActionsProvider>
448
617
  );
@@ -2,6 +2,13 @@ import { describe, expect, it } from "vitest";
2
2
  import { buildDispatchNavigationState } from "./use-navigation-state.js";
3
3
 
4
4
  describe("buildDispatchNavigationState", () => {
5
+ it("recognizes the full-page chat route", () => {
6
+ expect(buildDispatchNavigationState("/chat")).toEqual({
7
+ view: "chat",
8
+ path: "/chat",
9
+ });
10
+ });
11
+
5
12
  it("exposes the current extension id from extension routes", () => {
6
13
  expect(
7
14
  buildDispatchNavigationState("/extensions/ext-1/github-stars-over-time"),
@@ -173,6 +173,7 @@ function resolveView(
173
173
  if (pathname === "/extensions" || pathname.startsWith("/extensions/")) {
174
174
  return "extensions";
175
175
  }
176
+ if (pathname.startsWith("/chat")) return "chat";
176
177
  if (pathname.startsWith("/apps")) return "apps";
177
178
  if (pathname.startsWith("/metrics")) return "metrics";
178
179
  if (pathname.startsWith("/new-app")) return "new-app";
@@ -197,6 +198,9 @@ function resolvePath(
197
198
  command?: Pick<NavigationState, "extensionId">,
198
199
  ): string | undefined {
199
200
  switch (view) {
201
+ case "chat":
202
+ case "ask":
203
+ return "/chat";
200
204
  case "overview":
201
205
  return "/overview";
202
206
  case "apps":
@@ -28,6 +28,21 @@ describe("submitOverviewPrompt", () => {
28
28
  });
29
29
  });
30
30
 
31
+ it("can submit to a mounted page chat without opening the sidebar", () => {
32
+ const tabId = submitOverviewPrompt(" build a metrics app ", "auto", {
33
+ openSidebar: false,
34
+ });
35
+
36
+ expect(tabId).toBe("chat-tab");
37
+ expect(sendToAgentChatMock).toHaveBeenCalledWith({
38
+ message: "build a metrics app",
39
+ submit: true,
40
+ newTab: true,
41
+ model: "auto",
42
+ openSidebar: false,
43
+ });
44
+ });
45
+
31
46
  it("routes overview prompts to Builder chat inside Builder", () => {
32
47
  frameState.inBuilderFrame = true;
33
48
 
@@ -3,6 +3,7 @@ import { isInBuilderFrame, sendToAgentChat } from "@agent-native/core/client";
3
3
  export function submitOverviewPrompt(
4
4
  message: string,
5
5
  selectedModel?: string | null,
6
+ options?: { openSidebar?: boolean },
6
7
  ): string | null {
7
8
  const trimmed = message.trim();
8
9
  if (!trimmed) return null;
@@ -20,5 +21,6 @@ export function submitOverviewPrompt(
20
21
  submit: true,
21
22
  newTab: true,
22
23
  model: selectedModel || undefined,
24
+ ...(options?.openSidebar === false ? { openSidebar: false } : {}),
23
25
  });
24
26
  }
@@ -31,6 +31,7 @@ import { type RouteConfig, route, index } from "@react-router/dev/routes";
31
31
  */
32
32
  export const dispatchRoutes: RouteConfig = [
33
33
  index("./pages/_index.js"),
34
+ route("chat", "./pages/chat.js"),
34
35
  route("overview", "./pages/overview.js"),
35
36
  route("metrics", "./pages/metrics.js"),
36
37
  route("apps", "./pages/apps.js"),
@@ -23,7 +23,7 @@ export function meta() {
23
23
  *
24
24
  * We preserve `?` and `#` so deep-links like `?thread=<id>` from a Slack
25
25
  * "Open thread" button survive the bounce — `useThreadDeepLink` in
26
- * `root.tsx` reads them after the redirect lands on `/overview`.
26
+ * `root.tsx` reads them after the redirect lands and opens `/chat`.
27
27
  */
28
28
  function buildTarget(request: Request): string {
29
29
  const url = new URL(request.url);
@@ -0,0 +1,99 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useLocation, useNavigate } from "react-router";
3
+ import { AgentChatSurface } from "@agent-native/core/client";
4
+ import { submitOverviewPrompt } from "@/lib/overview-chat";
5
+
6
+ interface DispatchChatLocationState {
7
+ dispatchPrompt?: {
8
+ id?: string | number;
9
+ message?: string;
10
+ selectedModel?: string | null;
11
+ };
12
+ dispatchThread?: {
13
+ id?: string | number;
14
+ threadId?: string;
15
+ };
16
+ }
17
+
18
+ export function meta() {
19
+ return [{ title: "Chat — Dispatch" }];
20
+ }
21
+
22
+ export default function ChatRoute() {
23
+ const location = useLocation();
24
+ const navigate = useNavigate();
25
+ const handledStateIds = useRef(new Set<string>());
26
+ const state = location.state as DispatchChatLocationState | null;
27
+ const prompt = state?.dispatchPrompt;
28
+ const thread = state?.dispatchThread;
29
+
30
+ useEffect(() => {
31
+ const message = prompt?.message?.trim();
32
+ const threadId = thread?.threadId?.trim();
33
+ if (!message && !threadId) return;
34
+
35
+ const stateId = String(
36
+ prompt?.id ?? thread?.id ?? `${message ?? ""}:${threadId ?? ""}`,
37
+ );
38
+ if (handledStateIds.current.has(stateId)) return;
39
+ handledStateIds.current.add(stateId);
40
+
41
+ const timer = window.setTimeout(() => {
42
+ if (threadId) {
43
+ window.dispatchEvent(
44
+ new CustomEvent("agent-chat:open-thread", {
45
+ detail: { threadId },
46
+ }),
47
+ );
48
+ }
49
+ if (message) {
50
+ submitOverviewPrompt(message, prompt?.selectedModel, {
51
+ openSidebar: false,
52
+ });
53
+ }
54
+ navigate(`${location.pathname}${location.search}${location.hash}`, {
55
+ replace: true,
56
+ state: null,
57
+ });
58
+ }, 0);
59
+
60
+ return () => window.clearTimeout(timer);
61
+ }, [
62
+ location.hash,
63
+ location.pathname,
64
+ location.search,
65
+ navigate,
66
+ prompt?.id,
67
+ prompt?.message,
68
+ prompt?.selectedModel,
69
+ thread?.id,
70
+ thread?.threadId,
71
+ ]);
72
+
73
+ return (
74
+ <div className="flex h-full min-h-0 flex-col bg-background">
75
+ <AgentChatSurface
76
+ mode="page"
77
+ className="dispatch-chat-panel"
78
+ defaultMode="chat"
79
+ showHeader={false}
80
+ showTabBar={false}
81
+ dynamicSuggestions={false}
82
+ suggestions={[]}
83
+ emptyStateText="Ask Dispatch to create apps, route work, or manage the workspace."
84
+ emptyStateDisplay="hidden"
85
+ centerComposerWhenEmpty
86
+ composerLayoutVariant="hero"
87
+ composerPlaceholder="Ask Dispatch..."
88
+ composerSlot={
89
+ <div className="dispatch-chat-intro">
90
+ <h1>What should Dispatch do next?</h1>
91
+ <p>
92
+ Create apps, manage shared keys, and route work across agents.
93
+ </p>
94
+ </div>
95
+ }
96
+ />
97
+ </div>
98
+ );
99
+ }
@@ -1,10 +1,11 @@
1
1
  import { useEffect, useMemo, useState } from "react";
2
- import { Link } from "react-router";
2
+ import { Link, useNavigate } from "react-router";
3
3
  import {
4
4
  PromptComposer,
5
5
  useActionQuery,
6
6
  useChatModels,
7
7
  agentNativePath,
8
+ isInBuilderFrame,
8
9
  } from "@agent-native/core/client";
9
10
  import {
10
11
  IconActivity,
@@ -75,9 +76,26 @@ const HOME_CHAT_SUGGESTIONS = [
75
76
 
76
77
  function HomeChatPanel() {
77
78
  const { selectedModel } = useChatModels();
79
+ const navigate = useNavigate();
78
80
 
79
81
  const send = (message: string) => {
80
- submitOverviewPrompt(message, selectedModel);
82
+ const trimmed = message.trim();
83
+ if (!trimmed) return;
84
+
85
+ if (isInBuilderFrame()) {
86
+ submitOverviewPrompt(trimmed, selectedModel);
87
+ return;
88
+ }
89
+
90
+ navigate("/chat", {
91
+ state: {
92
+ dispatchPrompt: {
93
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
94
+ message: trimmed,
95
+ selectedModel,
96
+ },
97
+ },
98
+ });
81
99
  };
82
100
 
83
101
  return (
@@ -90,9 +108,7 @@ function HomeChatPanel() {
90
108
  <PromptComposer
91
109
  placeholder="Message agent…"
92
110
  onSubmit={(text) => {
93
- const trimmed = text.trim();
94
- if (!trimmed) return;
95
- send(trimmed);
111
+ send(text);
96
112
  }}
97
113
  />
98
114
  <div className="flex flex-wrap justify-center gap-2">
@@ -52,4 +52,13 @@ describe("dispatch route shells", () => {
52
52
  expect(indexRoute).toContain("HydrateFallback");
53
53
  expect(indexRoute).toContain("@agent-native/dispatch/routes/pages/_index");
54
54
  });
55
+
56
+ it("re-exports the chat route from the Dispatch template", () => {
57
+ const chatRoute = fs.readFileSync(
58
+ path.join(repoRoot, "templates/dispatch/app/routes/chat.tsx"),
59
+ "utf-8",
60
+ );
61
+
62
+ expect(chatRoute).toContain("@agent-native/dispatch/routes/pages/chat");
63
+ });
55
64
  });
@@ -7,3 +7,106 @@
7
7
  @source "../components/**/*.{js,mjs,ts,tsx}";
8
8
  @source "../hooks/**/*.{js,mjs,ts,tsx}";
9
9
  @source "../routes/**/*.{js,mjs,ts,tsx}";
10
+
11
+ .dispatch-chat-panel [data-agent-empty-state="centered"] {
12
+ justify-content: center;
13
+ padding-bottom: clamp(4rem, 12vh, 8rem);
14
+ }
15
+
16
+ .dispatch-chat-intro {
17
+ display: none;
18
+ }
19
+
20
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .dispatch-chat-intro {
21
+ display: grid;
22
+ width: min(760px, calc(100vw - 3rem));
23
+ max-width: 760px;
24
+ gap: 0.5rem;
25
+ margin: 0 auto 1rem;
26
+ text-align: center;
27
+ }
28
+
29
+ .dispatch-chat-intro h1 {
30
+ margin: 0;
31
+ color: hsl(var(--foreground));
32
+ font-size: 2.5rem;
33
+ font-weight: 600;
34
+ letter-spacing: 0;
35
+ line-height: 1.12;
36
+ }
37
+
38
+ .dispatch-chat-intro p {
39
+ margin: 0;
40
+ color: hsl(var(--muted-foreground));
41
+ font-size: 0.9375rem;
42
+ line-height: 1.5;
43
+ }
44
+
45
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-chat-scroll {
46
+ flex: 0 0 auto;
47
+ min-height: 0;
48
+ overflow: visible;
49
+ }
50
+
51
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
52
+ width: min(760px, calc(100vw - 3rem));
53
+ max-width: 760px;
54
+ padding: 0;
55
+ }
56
+
57
+ .dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-root {
58
+ min-height: 10.5rem;
59
+ border-color: hsl(var(--border));
60
+ border-radius: 1rem;
61
+ background: hsl(var(--card));
62
+ box-shadow:
63
+ 0 20px 60px hsl(220 10% 2% / 0.12),
64
+ 0 0 0 1px hsl(var(--border) / 0.45);
65
+ }
66
+
67
+ .dark
68
+ .dispatch-chat-panel
69
+ [data-agent-empty-state="centered"]
70
+ .agent-composer-root {
71
+ box-shadow:
72
+ 0 24px 80px hsl(220 10% 2% / 0.4),
73
+ 0 0 0 1px hsl(var(--border) / 0.7);
74
+ }
75
+
76
+ .dispatch-chat-panel
77
+ [data-agent-empty-state="centered"]
78
+ .agent-composer-prosemirror {
79
+ min-height: 6.75rem;
80
+ font-size: 1rem;
81
+ line-height: 1.625rem;
82
+ }
83
+
84
+ @media (max-width: 767px) {
85
+ .dispatch-chat-panel [data-agent-empty-state="centered"] {
86
+ padding: 1rem;
87
+ padding-bottom: 5rem;
88
+ }
89
+
90
+ .dispatch-chat-panel
91
+ [data-agent-empty-state="centered"]
92
+ .dispatch-chat-intro {
93
+ width: 100%;
94
+ margin-bottom: 0.875rem;
95
+ }
96
+
97
+ .dispatch-chat-intro h1 {
98
+ font-size: 1.75rem;
99
+ }
100
+
101
+ .dispatch-chat-panel
102
+ [data-agent-empty-state="centered"]
103
+ .agent-composer-area {
104
+ width: 100%;
105
+ }
106
+
107
+ .dispatch-chat-panel
108
+ [data-agent-empty-state="centered"]
109
+ .agent-composer-root {
110
+ min-height: 9rem;
111
+ }
112
+ }