@agent-native/core 0.148.1 → 0.149.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.
Files changed (145) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +12 -74
  44. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  45. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  46. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  47. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -0
  48. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  49. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  50. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  51. package/corpus/templates/slides/app/pages/DeckEditor.tsx +32 -14
  52. package/corpus/templates/slides/app/pages/Index.tsx +110 -128
  53. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  54. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  55. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  56. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  57. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  58. package/dist/agent/engine/registry.d.ts +8 -2
  59. package/dist/agent/engine/registry.js +63 -29
  60. package/dist/agent/production-agent.js +7 -0
  61. package/dist/agent/run-manager.d.ts +5 -4
  62. package/dist/agent/run-manager.js +6 -5
  63. package/dist/cli/claude-code-participant.d.ts +2 -1
  64. package/dist/cli/claude-code-participant.js +19 -0
  65. package/dist/cli/code-agent-executor.js +223 -0
  66. package/dist/cli/create-workspace.js +3 -3
  67. package/dist/cli/create.d.ts +3 -2
  68. package/dist/cli/create.js +20 -24
  69. package/dist/client/AgentPanel.js +8 -4
  70. package/dist/client/AssistantChat.js +34 -1
  71. package/dist/client/agent-chat-adapter.d.ts +1 -0
  72. package/dist/client/agent-chat-adapter.js +10 -2
  73. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  74. package/dist/client/chat/markdown-renderer.js +4 -0
  75. package/dist/client/chat/message-components.d.ts +1 -0
  76. package/dist/client/chat/message-components.js +9 -1
  77. package/dist/client/chat/tool-call-display.d.ts +8 -0
  78. package/dist/client/chat/tool-call-display.js +30 -21
  79. package/dist/client/chat-first/app-pane.js +1 -1
  80. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  81. package/dist/client/chat-first/apps-rail.js +11 -11
  82. package/dist/client/chat-first/chat-history.d.ts +1 -1
  83. package/dist/client/chat-first/chat-history.js +2 -0
  84. package/dist/client/chat-first/primary-nav.js +1 -1
  85. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  86. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  87. package/dist/client/settings/SettingsTabsPage.js +2 -2
  88. package/dist/client/sse-event-processor.d.ts +8 -0
  89. package/dist/client/sse-event-processor.js +13 -6
  90. package/dist/collab/awareness.d.ts +2 -2
  91. package/dist/collab/routes.d.ts +1 -1
  92. package/dist/collab/struct-routes.d.ts +1 -1
  93. package/dist/deploy/build.d.ts +5 -7
  94. package/dist/deploy/build.js +19 -12
  95. package/dist/localization/default-messages.js +1 -1
  96. package/dist/notifications/routes.d.ts +3 -3
  97. package/dist/observability/routes.d.ts +6 -6
  98. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  99. package/dist/resources/handlers.d.ts +1 -1
  100. package/dist/scripts/call-agent.js +3 -3
  101. package/dist/scripts/describe-workspace-apps.js +1 -1
  102. package/dist/secrets/routes.d.ts +3 -3
  103. package/dist/server/agent-capabilities.js +0 -0
  104. package/dist/server/agent-chat/context-tools.js +1 -1
  105. package/dist/server/agent-chat/prompt-resources.js +1 -1
  106. package/dist/server/agent-chat-plugin.js +1 -1
  107. package/dist/server/auth.js +190 -42
  108. package/dist/server/better-auth-migrations.js +53 -0
  109. package/dist/server/http-response-telemetry.js +7 -1
  110. package/dist/server/onboarding-html.js +119 -33
  111. package/dist/server/transcribe-voice.d.ts +1 -1
  112. package/dist/shared/password-policy.d.ts +2 -1
  113. package/dist/shared/password-policy.js +2 -1
  114. package/dist/styles/agent-conversation.css +20 -3
  115. package/dist/styles/agent-native.css +14 -2
  116. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  117. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  118. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  119. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  120. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  121. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  122. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  123. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  124. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  125. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  126. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  127. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  128. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  129. package/docs/content/cloneable-saas.mdx +6 -6
  130. package/docs/content/deployment.mdx +10 -8
  131. package/docs/content/environment-variables.mdx +4 -3
  132. package/package.json +2 -2
  133. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  134. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  135. package/src/templates/default/app/i18n/en-US.ts +1 -1
  136. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  137. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  138. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  139. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  140. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  141. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  142. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  143. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  144. package/src/templates/default/pnpm-workspace.yaml +1 -0
  145. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
@@ -93,8 +93,9 @@ const COMMUNITY_OPTION = {
93
93
  /**
94
94
  * Main entry for `agent-native create [name]`.
95
95
  *
96
- * Default behavior: scaffold a workspace at <name>/ with a multi-select
97
- * template picker. Use --standalone for the single-app standalone flow.
96
+ * Default behavior: ask for a starting shape, with Chat and first-party
97
+ * templates creating a workspace at <name>/. Use --standalone for the
98
+ * single-app standalone flow.
98
99
  *
99
100
  * If run *inside* an existing workspace, falls through to the add-app
100
101
  * flow that scaffolds one new app under apps/<name>/.
@@ -126,8 +127,8 @@ export async function createApp(name, opts) {
126
127
  }
127
128
  // When exactly one template is specified explicitly, treat it as a
128
129
  // standalone scaffold (script-friendly, matches historic behavior).
129
- // Use `--template a,b` or pass no --template to opt into the workspace
130
- // flow with the multi-select picker.
130
+ // Use `--template a,b` to opt into the workspace flow with the multi-select
131
+ // picker. Bare `create` asks for the starting shape first.
131
132
  const parsed = parseTemplateList(opts?.template);
132
133
  // Headless can't live in a workspace, so reject it when more than one
133
134
  // template is requested or when workspace semantics are forced.
@@ -146,9 +147,8 @@ export async function createApp(name, opts) {
146
147
  // No template specified: ask what shape to start from before diving into
147
148
  // "which templates?". The on-ramp choice implies the project structure, so
148
149
  // we don't ask a separate "workspace or standalone?" question — Chat and
149
- // Headless scaffold a single standalone app (the lightest starts; headless
150
- // cannot live in a workspace), while Template continues into the workspace
151
- // multi-select.
150
+ // Template creates a workspace, while Community and Headless scaffold a
151
+ // single standalone app.
152
152
  if (parsed.length === 0) {
153
153
  // The deprecated `create-workspace` alias forces workspace semantics, so
154
154
  // it must skip the start-shape prompt and scaffold a workspace directly.
@@ -157,10 +157,17 @@ export async function createApp(name, opts) {
157
157
  return;
158
158
  }
159
159
  const shape = await promptStartShape(clack);
160
- if (shape === "headless" || shape === "chat") {
160
+ if (shape === "headless") {
161
161
  await createStandaloneApp(name, { ...opts, template: shape }, clack);
162
162
  return;
163
163
  }
164
+ if (shape === "chat") {
165
+ // Chat is the default workspace on-ramp. Keep the minimal chat app in
166
+ // the same workspace shape as every other first-party app so the next
167
+ // documented step, `add-app`, works immediately.
168
+ await createWorkspaceInteractive(name, { ...opts, template: shape }, clack);
169
+ return;
170
+ }
164
171
  if (shape === "community") {
165
172
  const template = await promptCommunityTemplate(clack);
166
173
  await createStandaloneApp(name, { ...opts, template }, clack);
@@ -178,12 +185,11 @@ export async function createApp(name, opts) {
178
185
  * choice made here implies the project structure, so we deliberately avoid a
179
186
  * separate "workspace or standalone?" question:
180
187
  * - "template" → full app(s) in a workspace (the multi-select picker)
188
+ * - "chat" → a minimal Chat app in a workspace with Dispatch
181
189
  * - "community" → a single standalone app from a public GitHub repository
182
- * - "chat" → a single standalone chat UI app
183
190
  * - "headless" → a single standalone action-first app with no UI shell
184
- * Chat and headless are standalone on purpose: a monorepo is unnecessary
185
- * ceremony for the lightest on-ramps, and headless cannot be a workspace
186
- * member. Either can grow into a workspace later via `add-app`.
191
+ * Headless cannot be a workspace member. Use `--standalone --template chat`
192
+ * when a standalone Chat app is the intended shape.
187
193
  */
188
194
  async function promptStartShape(clack) {
189
195
  const choice = await clack.select(startShapePromptOptions());
@@ -201,7 +207,7 @@ function startShapePromptOptions() {
201
207
  {
202
208
  value: "chat",
203
209
  label: "Chat",
204
- hint: "A single app with a minimal chat UI and the browser shell wired up",
210
+ hint: "A minimal chat app in a workspace with the browser shell wired up",
205
211
  },
206
212
  {
207
213
  value: "template",
@@ -1420,20 +1426,9 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
1420
1426
  }
1421
1427
  }
1422
1428
  }
1423
- // Ensure pnpm.onlyBuiltDependencies is set so native packages
1424
- // (better-sqlite3, esbuild, node-pty) compile their postinstall scripts
1425
- // under pnpm 10+ without prompting for `pnpm approve-builds`.
1426
1429
  pkg.dependencies = pkg.dependencies ?? {};
1427
1430
  pkg.dependencies.postgres ??= POSTGRES_DEPENDENCY_VERSION;
1428
1431
  ensureReactRouterBuildDependencies(pkg);
1429
- const requiredBuilt = ["better-sqlite3", "esbuild", "node-pty"];
1430
- if (!pkg.pnpm || typeof pkg.pnpm !== "object") {
1431
- pkg.pnpm = {};
1432
- }
1433
- const existing = Array.isArray(pkg.pnpm.onlyBuiltDependencies)
1434
- ? pkg.pnpm.onlyBuiltDependencies
1435
- : [];
1436
- pkg.pnpm.onlyBuiltDependencies = Array.from(new Set([...existing, ...requiredBuilt]));
1437
1432
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1438
1433
  }
1439
1434
  catch { }
@@ -1452,6 +1447,7 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
1452
1447
  "better-sqlite3": "true",
1453
1448
  esbuild: "true",
1454
1449
  "node-pty": "true",
1450
+ "tesseract.js": "true",
1455
1451
  },
1456
1452
  };
1457
1453
  if (templateName !== "headless") {
@@ -689,7 +689,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
689
689
  return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7" }));
690
690
  })(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
691
691
  "bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, showFullViewAction && agentPageHref ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: agentPageHref, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
692
- showFullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: toggleHistory, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
692
+ showFullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: () => {
693
+ // Let the menu finish restoring focus before mounting the
694
+ // history popover; otherwise Radix dismisses the new overlay.
695
+ setTimeout(() => toggleHistory(), 0);
696
+ }, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
693
697
  ? t("agentPanel.hideChats")
694
698
  : t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 0, limit: 12, showRecent: true, onOpenThread: openRunThread })), mode === "chat" && _jsx(DropdownMenuSeparator, {}), mode === "cli" && availableClis.length > 0 && (_jsxs(_Fragment, { children: [availableClis.map((cli) => (_jsxs(DropdownMenuItem, { onSelect: () => selectCli(cli.command), className: cn(cli.command === selectedCli
695
699
  ? "font-medium"
@@ -942,7 +946,7 @@ const SIDEBAR_OVERLAY_Z_INDEX = 70;
942
946
  const SIDEBAR_DRAWER_Z_INDEX = 80;
943
947
  const SIDEBAR_DRAWER_VIEW_TRANSITION_NAME = "agent-native-sidebar-drawer";
944
948
  /** Shared max width of the centered fullscreen chat column and composer. */
945
- const FULLSCREEN_CHAT_COLUMN_MAX_PX = 684;
949
+ const FULLSCREEN_CHAT_COLUMN_MAX_PX = 750;
946
950
  export function getActiveTabScrollDelta(containerRect, tabRect, margin = 24) {
947
951
  if (tabRect.left < containerRect.left + margin) {
948
952
  return tabRect.left - containerRect.left - margin;
@@ -1842,7 +1846,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1842
1846
  }
1843
1847
  // Mount the live chat surface only while visible or actively needed. Keeping
1844
1848
  // it mounted while closed starts app-state polling on every public page view.
1845
- const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS, mobileAnimationEnabled && "shadow-2xl"), "data-agent-sidebar-animation": wideDrawerEnabled
1849
+ const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS), "data-agent-sidebar-animation": wideDrawerEnabled
1846
1850
  ? "drawer"
1847
1851
  : mobileAnimationEnabled
1848
1852
  ? "mobile"
@@ -1851,7 +1855,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1851
1855
  : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": position, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
1852
1856
  ? getAgentChatViewTransitionStyle(panelStyle)
1853
1857
  : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, threadFooterSlot: threadFooterSlot, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
1854
- const drawerPlaceholder = wideDrawerEnabled && !presentationMode && shouldRenderPanel ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
1858
+ const drawerPlaceholder = wideDrawerEnabled && !presentationMode && panelOpen ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
1855
1859
  return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": position, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
1856
1860
  !presentationMode &&
1857
1861
  (mobileAnimationEnabled ? shouldRenderPanel : open) && (_jsx("div", { className: cn("agent-sidebar-backdrop fixed inset-0 bg-foreground/40", mobileAnimationEnabled && !panelOpen && "pointer-events-none"), "data-agent-sidebar-animation": mobileAnimationEnabled ? "mobile" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", style: { zIndex: SIDEBAR_OVERLAY_Z_INDEX - 1 }, onClick: () => setOpenPersisted(false) })), shouldMountPanel ? _jsx(URLSync, { browserTabId: browserTabId }) : null, isLeft && !presentationMode ? sidebar : null, isLeft && !presentationMode ? drawerPlaceholder : null, _jsx("div", { className: "agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0", "data-agent-sidebar-main-position": position, "data-agent-sidebar-main-state": !isMobile && !presentationMode && panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: _jsx(ScreenRefreshBoundary, { children: children }) }), !isLeft && !presentationMode ? drawerPlaceholder : null, !isLeft && !presentationMode ? sidebar : null] })] }) }));
@@ -1346,6 +1346,9 @@ export function useAutoResumeStatus(tabId, forceStopped) {
1346
1346
  }, [clearAutoResume, forceStopped]);
1347
1347
  return { isAutoResuming, clearAutoResume };
1348
1348
  }
1349
+ function approvalResolutionIdentity(approvalKey, toolCallId) {
1350
+ return `${toolCallId ?? ""}\u0000${approvalKey}`;
1351
+ }
1349
1352
  const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", }, ref) {
1350
1353
  const thread = useThread();
1351
1354
  const threadRuntime = useThreadRuntime();
@@ -3974,11 +3977,34 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3974
3977
  composerContextItems.length > 0 ||
3975
3978
  showPlanModeCallout ||
3976
3979
  showInlineMissingKeySetup);
3980
+ const approvalResolutionScope = threadId ?? tabId ?? "default";
3981
+ const [approvalResolutionState, setApprovalResolutionState] = useState(() => ({
3982
+ scope: approvalResolutionScope,
3983
+ byIdentity: new Map(),
3984
+ }));
3985
+ const getApprovalResolution = useCallback((approvalKey, toolCallId) => {
3986
+ if (approvalResolutionState.scope !== approvalResolutionScope) {
3987
+ return null;
3988
+ }
3989
+ return (approvalResolutionState.byIdentity.get(approvalResolutionIdentity(approvalKey, toolCallId)) ?? null);
3990
+ }, [approvalResolutionScope, approvalResolutionState]);
3991
+ const recordApprovalResolution = useCallback((approvalKey, resolution, toolCallId) => {
3992
+ setApprovalResolutionState((previous) => {
3993
+ const byIdentity = previous.scope === approvalResolutionScope
3994
+ ? previous.byIdentity
3995
+ : new Map();
3996
+ const next = new Map(byIdentity);
3997
+ next.set(approvalResolutionIdentity(approvalKey, toolCallId), resolution);
3998
+ return { scope: approvalResolutionScope, byIdentity: next };
3999
+ });
4000
+ }, [approvalResolutionScope]);
3977
4001
  // Human-in-the-loop approvals: when the user approves a paused `needsApproval`
3978
4002
  // tool call, re-issue the turn carrying the call's approval key so the server
3979
4003
  // gate lets that specific call run. Reuses the same append path as recovery /
3980
4004
  // queued messages (no hand-written fetch).
3981
4005
  const approvalCtx = useMemo(() => ({
4006
+ getApprovalResolution,
4007
+ onApprovalResolved: recordApprovalResolution,
3982
4008
  onApprove: (approvalKey) => {
3983
4009
  markOptimisticRunning();
3984
4010
  appendThreadMessage({
@@ -4000,7 +4026,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4000
4026
  ...(approvalActions?.onAlwaysAllow
4001
4027
  ? { onAlwaysAllow: approvalActions.onAlwaysAllow }
4002
4028
  : {}),
4003
- }), [appendThreadMessage, execMode, markOptimisticRunning, approvalActions]);
4029
+ }), [
4030
+ appendThreadMessage,
4031
+ execMode,
4032
+ getApprovalResolution,
4033
+ markOptimisticRunning,
4034
+ approvalActions,
4035
+ recordApprovalResolution,
4036
+ ]);
4004
4037
  return (_jsx(CheckpointContext.Provider, { value: checkpointCtx, children: _jsx(MessageActionsContext.Provider, { value: messageActionsCtx, children: _jsx(ApprovalContext.Provider, { value: approvalCtx, children: _jsx(ChatRunDurationContext.Provider, { value: lastChatRunDurationMs, children: _jsx(ServerRunActiveContext.Provider, { value: serverRunActive, children: _jsx(ChatRunningContext.Provider, { value: showRunningInUI, children: _jsx(TextStreamingContext.Provider, { value: textStreaming, children: _jsxs("div", { "data-agent-empty-state": centeredEmptyState
4005
4038
  ? "centered"
4006
4039
  : compactMissingKeyEmptyState
@@ -13,6 +13,7 @@ export type AgentChatSurfaceKind =
13
13
  | "dev-frame"
14
14
  /** Chat hosted by Desktop for a local app with explicit code access. */
15
15
  | "desktop";
16
+ export declare const BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS = 90000;
16
17
  export declare const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 210000;
17
18
  export declare const MAX_FOLLOWED_BACKGROUND_RUNS = 24;
18
19
  export declare const MAX_BACKGROUND_FOLLOW_WALL_TIME_MS: number;
@@ -96,6 +96,10 @@ const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
96
96
  // reports that the self-dispatch itself failed before a successor claimed the
97
97
  // run, the existing client auto-continue POST remains the fallback.
98
98
  const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
99
+ // This watchdog belongs to a read-only reattach, not to the server run. A
100
+ // shorter reader window lets the follow loop re-poll `/runs/active` while a
101
+ // completed run is still inside the server's terminal replay window.
102
+ export const BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS = 90_000;
99
103
  // How long the follow loop tolerates seeing NO active run for this turn before
100
104
  // treating the turn as ended. The server pre-inserts the successor row before
101
105
  // the old chunk completes, so a healthy chain never shows an idle gap; allow a
@@ -1691,11 +1695,12 @@ export function createAgentChatAdapter(options) {
1691
1695
  preparingActionStatesByRun.set(id, state);
1692
1696
  return state;
1693
1697
  };
1694
- const currentSSEOptions = () => ({
1698
+ const currentSSEOptions = (overrides = {}) => ({
1695
1699
  durableBackgroundRun: currentRunDispatchMode?.startsWith("background") === true,
1696
1700
  ...(runId
1697
1701
  ? { preparingActionState: preparingActionStateForRun(runId) }
1698
1702
  : {}),
1703
+ ...overrides,
1699
1704
  });
1700
1705
  const captureChatClientError = (error, phase, extra = {}) => {
1701
1706
  captureError(error, {
@@ -2102,7 +2107,10 @@ export function createAgentChatAdapter(options) {
2102
2107
  rememberRunSeq(seq);
2103
2108
  if (threadId)
2104
2109
  updateActiveRunSeq(seq);
2105
- }, runId, currentSSEOptions())) {
2110
+ }, runId, currentSSEOptions({
2111
+ noProgressTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
2112
+ actionPreparationStallTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
2113
+ }))) {
2106
2114
  yield withRequestModeMetadata(result);
2107
2115
  }
2108
2116
  // readSSEStream returned normally: a terminal done/error was
@@ -31,6 +31,7 @@ export declare function markdownUrlTransform(value: string): string;
31
31
  export declare function extractCodeText(child: React.ReactNode): string;
32
32
  export declare const markdownComponents: {
33
33
  a(props: React.AnchorHTMLAttributes<HTMLAnchorElement>): React.JSX.Element;
34
+ table(props: React.TableHTMLAttributes<HTMLTableElement>): React.JSX.Element;
34
35
  pre(props: React.HTMLAttributes<HTMLPreElement>): React.JSX.Element;
35
36
  };
36
37
  export declare function renderMarkdownToClipboardHtml(markdown: string): string | null;
@@ -158,6 +158,10 @@ export const markdownComponents = {
158
158
  }
159
159
  return (_jsxs("a", { href: href, target: "_blank", rel: "noreferrer", className: cn(CTA_BUTTON_CLASSES, className), ...rest, children: [_jsx("span", { children: children }), _jsx(IconExternalLink, { size: 13, strokeWidth: 2, "aria-hidden": "true" })] }));
160
160
  },
161
+ table(props) {
162
+ const { children, ...rest } = props;
163
+ return (_jsx("div", { className: "agent-markdown-table-wrap", children: _jsx("table", { ...rest, children: children }) }));
164
+ },
161
165
  pre(props) {
162
166
  const { children, ...rest } = props;
163
167
  if (React.isValidElement(children)) {
@@ -97,6 +97,7 @@ export declare function shouldShowAssistantWorkSummary({ isLast, isComplete, has
97
97
  hasUnresolvedTool: boolean;
98
98
  chatRunning: boolean;
99
99
  }): boolean;
100
+ export declare function getAssistantWorkSummaryDurationMs(durationMs: number | null | undefined, groupStartIndex: number, firstWorkPartIndex: number): number | null | undefined;
100
101
  export declare function isAlwaysVisibleAssistantTool(part: {
101
102
  type?: unknown;
102
103
  toolName?: unknown;
@@ -667,6 +667,9 @@ export function shouldShowAssistantWorkSummary({ isLast, isComplete, hasCollapsi
667
667
  // disclosure state to the default-open value on every new submission.
668
668
  return isComplete || !isLast;
669
669
  }
670
+ export function getAssistantWorkSummaryDurationMs(durationMs, groupStartIndex, firstWorkPartIndex) {
671
+ return groupStartIndex === firstWorkPartIndex ? durationMs : null;
672
+ }
670
673
  function ReasoningMessagePart() {
671
674
  const part = useMessagePartReasoning();
672
675
  const partRuntime = useMessagePartRuntime();
@@ -958,6 +961,11 @@ export function AssistantMessage() {
958
961
  msgContent.some((p, index) => !isCallAgentToolCallShadowed(msgContent, index) &&
959
962
  (p.type !== "tool-call" || p.activity !== true) &&
960
963
  isCollapsibleAssistantWorkPart(p));
964
+ const firstWorkPartIndex = Array.isArray(msgContent)
965
+ ? msgContent.findIndex((p, index) => !isCallAgentToolCallShadowed(msgContent, index) &&
966
+ (p.type !== "tool-call" || p.activity !== true) &&
967
+ isCollapsibleAssistantWorkPart(p))
968
+ : -1;
961
969
  const shadowedToolCallIds = Array.isArray(msgContent)
962
970
  ? shadowedCallAgentToolCallIds(msgContent)
963
971
  : new Set();
@@ -979,7 +987,7 @@ export function AssistantMessage() {
979
987
  });
980
988
  if (!showSummary)
981
989
  return _jsx(_Fragment, { children: children });
982
- return (_jsx(WorkedForSummary, { durationMs: capturedDurationMs ?? persistedDurationMs, defaultOpen: hasCustomUi, autoCollapse: animateCollapse && !hasCustomUi, children: children }));
990
+ return (_jsx(WorkedForSummary, { durationMs: getAssistantWorkSummaryDurationMs(capturedDurationMs ?? persistedDurationMs, part.indices[0] ?? -1, firstWorkPartIndex), defaultOpen: hasCustomUi, autoCollapse: animateCollapse && !hasCustomUi, children: children }));
983
991
  }
984
992
  case "group-ran-tools": {
985
993
  const toolCount = part.indices.filter((index) => msgContent?.[index]?.type === "tool-call").length;
@@ -24,9 +24,17 @@ export declare function ToolCallStackMotion({ children, className, }: {
24
24
  * provided, and "Always allow" only renders when `onAlwaysAllow` is provided
25
25
  * — both are additive so existing action-approval consumers are unaffected.
26
26
  */
27
+ export type ApprovalResolution = "approved" | "denied";
27
28
  export type ApprovalContextValue = {
28
29
  /** Re-issue the turn so the server runs the approved call. */
29
30
  onApprove: (approvalKey: string) => void;
31
+ /**
32
+ * Keep the visible resolution stable while the chat repository refreshes or
33
+ * remounts the message containing this approval card.
34
+ */
35
+ onApprovalResolved?: (approvalKey: string, resolution: ApprovalResolution, toolCallId?: string) => void;
36
+ /** Read a resolution retained by the owning chat surface. */
37
+ getApprovalResolution?: (approvalKey: string, toolCallId?: string) => ApprovalResolution | null;
30
38
  /**
31
39
  * Optional host hook invoked in addition to the local "denied" state, e.g.
32
40
  * so a Code session can also resolve its own pending approval as denied.
@@ -75,6 +75,7 @@ function prepareToolStackExitClone(clone, snapshot) {
75
75
  export function ToolCallStackMotion({ children, className, }) {
76
76
  const stackRef = useRef(null);
77
77
  const previousRef = useRef(new Map());
78
+ const previousStructureRef = useRef([]);
78
79
  const activeMotionsRef = useRef(new Map());
79
80
  const exitAnimationsRef = useRef(new Map());
80
81
  useLayoutEffect(() => {
@@ -104,6 +105,11 @@ export function ToolCallStackMotion({ children, className, }) {
104
105
  }
105
106
  exitAnimationsRef.current.clear();
106
107
  const elements = Array.from(stack.querySelectorAll("[data-agent-tool-call-id], [data-agent-tool-summary]"));
108
+ const structure = elements.map((element, index) => toolStackMotionKey(element, index));
109
+ const previousStructure = previousStructureRef.current;
110
+ const structureChanged = structure.length !== previousStructure.length ||
111
+ structure.some((key, index) => key !== previousStructure[index]);
112
+ previousStructureRef.current = structure;
107
113
  const current = new Map();
108
114
  for (const [index, element] of elements.entries()) {
109
115
  const rect = element.getBoundingClientRect();
@@ -116,7 +122,11 @@ export function ToolCallStackMotion({ children, className, }) {
116
122
  });
117
123
  }
118
124
  previousRef.current = current;
119
- if (previous.size === 0 || prefersReducedMotionForToolStack())
125
+ // Entry motion changes the measured transform without changing the stack
126
+ // structure. Do not turn that animation's cleanup into a second FLIP pass.
127
+ if (previous.size === 0 ||
128
+ !structureChanged ||
129
+ prefersReducedMotionForToolStack())
120
130
  return;
121
131
  const summary = elements.find((element) => element.dataset.agentToolSummary !== undefined);
122
132
  const summaryRect = summary?.getBoundingClientRect();
@@ -227,13 +237,6 @@ export function ToolActivityPresentation({ toolName, isRunning, isActiveTail, to
227
237
  // Presentation follows the active chat tail rather than execution state so
228
238
  // that newly revealed completed tools still get their entrance motion.
229
239
  const [animateEntry, setAnimateEntry] = useState(isActiveTail);
230
- const previousActiveTailRef = useRef(isActiveTail);
231
- useEffect(() => {
232
- if (isActiveTail && !previousActiveTailRef.current) {
233
- setAnimateEntry(true);
234
- }
235
- previousActiveTailRef.current = isActiveTail;
236
- }, [isActiveTail]);
237
240
  useEffect(() => {
238
241
  if (!animateEntry)
239
242
  return;
@@ -438,29 +441,35 @@ export function AnimatedCollapse({ open, children, }) {
438
441
  * turn. Approve re-issues the turn with the call's `approvalKey`; Deny dismisses
439
442
  * the prompt locally (the action stays un-run).
440
443
  */
441
- function ApprovalAffordance({ toolName, approval, }) {
444
+ function ApprovalAffordance({ toolName, toolCallId, approval, }) {
442
445
  const ctx = React.useContext(ApprovalContext);
443
- const [approved, setApproved] = useState(false);
444
- const [denied, setDenied] = useState(false);
445
- // Once approved, the turn is re-issued; collapse to a quiet note so the user
446
- // can't double-fire the approval.
447
- if (approved) {
446
+ const [localResolution, setLocalResolution] = useState(null);
447
+ const retainedResolution = ctx?.getApprovalResolution?.(approval.approvalKey, toolCallId) ?? null;
448
+ const resolution = retainedResolution ??
449
+ localResolution ??
450
+ (approval.dismissed === true ? "denied" : null);
451
+ // Once resolved, collapse to a quiet note so a repository refresh cannot
452
+ // restore the action buttons while the continuation is running.
453
+ if (resolution === "approved") {
448
454
  return (_jsxs("div", { className: "mt-1.5 text-xs text-muted-foreground", children: ["Approved. Re-running ", toolName, "..."] }));
449
455
  }
450
456
  // Deny defaults to local-only (the action simply stays un-run). When the
451
457
  // host also provided `onDeny` (e.g. a Code session resolving its own
452
458
  // pending approval), it fires alongside the local state.
453
- if (denied) {
459
+ if (resolution === "denied") {
454
460
  return (_jsxs("div", { className: "mt-1.5 text-xs text-muted-foreground", children: ["Denied. ", toolName, " did not run."] }));
455
461
  }
456
462
  return (_jsxs("div", { className: "mt-1.5 flex items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5", children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "mr-auto text-xs text-muted-foreground", children: ["Approve to run ", toolName, "?"] }), ctx && (_jsxs("button", { type: "button", onClick: () => {
457
- setApproved(true);
463
+ setLocalResolution("approved");
464
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
458
465
  ctx.onApprove(approval.approvalKey);
459
466
  }, className: cn("inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Approve"] })), ctx?.onAlwaysAllow && (_jsxs("button", { type: "button", onClick: () => {
460
- setApproved(true);
467
+ setLocalResolution("approved");
468
+ ctx.onApprovalResolved?.(approval.approvalKey, "approved", toolCallId);
461
469
  ctx.onAlwaysAllow?.(approval.approvalKey);
462
470
  }, title: "Approve and always allow this exact command", className: cn("inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconShieldCheck, { className: "h-3.5 w-3.5" }), "Always allow"] })), _jsxs("button", { type: "button", onClick: () => {
463
- setDenied(true);
471
+ setLocalResolution("denied");
472
+ ctx?.onApprovalResolved?.(approval.approvalKey, "denied", toolCallId);
464
473
  ctx?.onDeny?.(approval.approvalKey);
465
474
  }, className: cn("inline-flex items-center gap-1 rounded-md border border-border px-2.5 py-1 text-xs font-medium transition-colors", "text-foreground hover:bg-muted"), children: [_jsx(IconX, { className: "h-3.5 w-3.5" }), "Deny"] })] }));
466
475
  }
@@ -481,9 +490,9 @@ export function ToolCallDisplay({ toolName, toolCallId, argsText, args, result,
481
490
  if (toolKind === "write") {
482
491
  return wrapToolDisplay(_jsx(WriteCell, { meta: structuredMeta, isRunning: isRunning }));
483
492
  }
484
- return wrapToolDisplay(_jsx(ToolCallDisplayGeneric, { toolName: toolName, argsText: argsText, args: args, result: result, mcpApp: mcpApp, chatUI: chatUI, isRunning: isRunning, outcome: outcome, isActiveTail: showActiveTail, structuredMeta: structuredMeta, approval: approval, repeatCount: repeatCount }));
493
+ return wrapToolDisplay(_jsx(ToolCallDisplayGeneric, { toolName: toolName, toolCallId: toolCallId, argsText: argsText, args: args, result: result, mcpApp: mcpApp, chatUI: chatUI, isRunning: isRunning, outcome: outcome, isActiveTail: showActiveTail, structuredMeta: structuredMeta, approval: approval, repeatCount: repeatCount }));
485
494
  }
486
- function ToolCallDisplayGeneric({ toolName, argsText, args, result, mcpApp, chatUI, isRunning, outcome, isActiveTail, structuredMeta, approval, repeatCount, }) {
495
+ function ToolCallDisplayGeneric({ toolName, toolCallId, argsText, args, result, mcpApp, chatUI, isRunning, outcome, isActiveTail, structuredMeta, approval, repeatCount, }) {
487
496
  const isRawCallAgent = toolName === "call-agent";
488
497
  const isAgentCall = toolName.startsWith("agent:") || isRawCallAgent;
489
498
  const [expanded, setExpanded] = useState(isAgentCall);
@@ -585,7 +594,7 @@ function ToolCallDisplayGeneric({ toolName, argsText, args, result, mcpApp, chat
585
594
  ? structuredMeta.agentDurationMs
586
595
  : agentActivity?.durationMs }));
587
596
  }
588
- return (_jsxs("div", { className: "group/tool my-0.5 w-full overflow-hidden", children: [mcpApp && _jsx(McpAppRenderer, { app: mcpApp, className: "mb-1.5" }), _jsxs("button", { type: "button", onClick: () => canExpand && setExpanded(!isExpanded), "aria-expanded": canExpand ? isExpanded : undefined, className: cn("flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors", canExpand && "hover:text-foreground", isRunning && "text-muted-foreground"), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isAgentError ? (_jsx(IconCircleX, { className: "size-3.5 text-destructive" })) : isUnknownOutcome ? (_jsx(IconAlertTriangle, { className: "size-3.5 text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx(ToolIcon, { className: cn("size-3.5 transition-opacity", canExpand && "group-hover/tool:opacity-0") }), canExpand && (_jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/tool:opacity-100", isExpanded && "rotate-90") }))] })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: displayName }), repeatCount && repeatCount > 1 && (_jsxs("span", { className: "shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground", title: `Repeated ${repeatCount} times`, children: [repeatCount, "x"] }))] }), _jsx(AnimatedCollapse, { open: isExpanded && !isAgentCall && (hasArgs || result !== undefined), children: _jsxs("div", { className: "mt-1 space-y-2 pl-5", children: [inputPayload && (_jsx(SimpleCodeViewer, { text: inputPayload.text, lang: inputPayload.lang })), resultPayload && (_jsx(ToolOutputPopover, { open: outputOpen, onOpenChange: setOutputOpen, title: outputTitle, payload: resultPayload, children: _jsx("button", { type: "button", "aria-label": `View ${toolName} output`, className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: "Interrupted before this finished reporting \u2014 it may or may not have completed. Check before retrying." })), approval && (_jsx(ApprovalAffordance, { toolName: toolName, approval: approval }))] }));
597
+ return (_jsxs("div", { className: "group/tool my-0.5 w-full overflow-hidden", children: [mcpApp && _jsx(McpAppRenderer, { app: mcpApp, className: "mb-1.5" }), _jsxs("button", { type: "button", onClick: () => canExpand && setExpanded(!isExpanded), "aria-expanded": canExpand ? isExpanded : undefined, className: cn("flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors", canExpand && "hover:text-foreground", isRunning && "text-muted-foreground"), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isAgentError ? (_jsx(IconCircleX, { className: "size-3.5 text-destructive" })) : isUnknownOutcome ? (_jsx(IconAlertTriangle, { className: "size-3.5 text-muted-foreground" })) : (_jsxs(_Fragment, { children: [_jsx(ToolIcon, { className: cn("size-3.5 transition-opacity", canExpand && "group-hover/tool:opacity-0") }), canExpand && (_jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/tool:opacity-100", isExpanded && "rotate-90") }))] })) }), _jsx("span", { className: cn("min-w-0 truncate font-normal", isActiveTail && "agent-running-shimmer"), children: displayName }), repeatCount && repeatCount > 1 && (_jsxs("span", { className: "shrink-0 rounded border border-border/60 px-1.5 py-0.5 text-[10px] leading-none text-muted-foreground", title: `Repeated ${repeatCount} times`, children: [repeatCount, "x"] }))] }), _jsx(AnimatedCollapse, { open: isExpanded && !isAgentCall && (hasArgs || result !== undefined), children: _jsxs("div", { className: "mt-1 space-y-2 pl-5", children: [inputPayload && (_jsx(SimpleCodeViewer, { text: inputPayload.text, lang: inputPayload.lang })), resultPayload && (_jsx(ToolOutputPopover, { open: outputOpen, onOpenChange: setOutputOpen, title: outputTitle, payload: resultPayload, children: _jsx("button", { type: "button", "aria-label": `View ${toolName} output`, className: "inline-flex size-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-accent hover:text-foreground", children: _jsx(IconCode, { className: "size-3.5" }) }) }))] }) }), isUnknownOutcome && (_jsx("p", { role: "status", className: "ps-5 text-xs text-muted-foreground", children: "Interrupted before this finished reporting \u2014 it may or may not have completed. Check before retrying." })), approval && (_jsx(ApprovalAffordance, { toolName: toolName, toolCallId: toolCallId, approval: approval }))] }));
589
598
  }
590
599
  function AgentCallCell({ agentName, activity, progress, responseText, isRunning, isError, durationMs, }) {
591
600
  const t = useT();
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Skeleton } from "@agent-native/toolkit/ui";
3
3
  import { defaultChatFirstCopy } from "./copy.js";
4
4
  export function ChatFirstAppPane({ app, status, embedUrl, errorMessage, onRetry, renderEmbed, copy = defaultChatFirstCopy, }) {
5
- return (_jsx("section", { "data-chat-first-app-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": app ? `${app.name} app` : copy("appUnavailable"), children: status === "loading" ? (_jsxs("div", { className: "space-y-3 p-4", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-2/3" })] })) : status === "ready" && app && embedUrl ? (_jsx("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: renderEmbed({ url: embedUrl, title: app.name }) })) : status === "error" ? (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: errorMessage || copy("appUnavailable") }), onRetry ? (_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-xs text-foreground hover:bg-accent", onClick: onRetry, children: copy("retry") })) : null] })) : (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground", children: copy("appUnavailable") })) }));
5
+ return (_jsx("section", { "data-chat-first-app-pane": true, className: "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background", "aria-label": app ? `${app.name} app` : copy("appUnavailable"), children: status === "loading" ? (_jsxs("div", { className: "space-y-3 p-4", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-2/3" })] })) : status === "ready" && app && embedUrl ? (_jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: renderEmbed({ url: embedUrl, title: app.name }) })) : status === "error" ? (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-6 text-center", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: errorMessage || copy("appUnavailable") }), onRetry ? (_jsx("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-xs text-foreground hover:bg-accent", onClick: onRetry, children: copy("retry") })) : null] })) : (_jsx("div", { className: "flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground", children: copy("appUnavailable") })) }));
6
6
  }
@@ -1,2 +1,2 @@
1
1
  import type { ChatFirstAppRailProps } from "./types.js";
2
- export declare function ChatFirstAppsRail({ apps, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps): import("react").JSX.Element;
2
+ export declare const ChatFirstAppsRail: import("react").MemoExoticComponent<({ apps, activeAppId, loading, error, collapsed, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy, }: ChatFirstAppRailProps) => import("react").JSX.Element>;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, Skeleton, Tooltip, TooltipContent, TooltipTrigger, } from "@agent-native/toolkit/ui";
3
3
  import { IconApps, IconChevronDown, IconChevronUp, IconPin, IconPlus, } from "@tabler/icons-react";
4
- import { useMemo, useState } from "react";
4
+ import { memo, useMemo, useState } from "react";
5
5
  import { orderChatFirstAppIds, readChatFirstAppLayout, writeChatFirstAppLayout, } from "../chat-first.js";
6
6
  import { cn } from "../utils.js";
7
7
  import { defaultChatFirstCopy } from "./copy.js";
@@ -11,16 +11,16 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
11
11
  const orderedApps = orderedIds
12
12
  .map((id) => appsById.get(id))
13
13
  .filter((app) => Boolean(app));
14
- return (_jsx("ul", { className: "space-y-px", children: orderedApps.map((app) => {
14
+ return (_jsx("ul", { className: "space-y-1", children: orderedApps.map((app) => {
15
15
  const active = activeAppId === app.id;
16
16
  const pinned = layout.pinnedIds.includes(app.id);
17
17
  const index = orderedApps.indexOf(app);
18
- return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-6 w-full min-w-0 items-center gap-1 rounded-md px-1 text-sm transition-colors", active
18
+ return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsxs("li", { draggable: true, "data-chat-first-app": true, "data-app-id": app.id, className: cn("group flex h-8 w-full min-w-0 items-center gap-1 rounded-md px-1 text-sm", active
19
19
  ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
20
20
  : "text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onDragStart: (event) => {
21
21
  event.dataTransfer.effectAllowed = "move";
22
22
  onDragStart(app.id);
23
- }, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex min-w-0 flex-1 items-center gap-2 px-1 text-start", onClick: () => onOpenApp(app), onKeyDown: (event) => {
23
+ }, onDragOver: (event) => event.preventDefault(), onDrop: () => onDrop(app.id), onDragEnd: onDragEnd, children: [_jsxs("button", { type: "button", className: "flex h-full min-w-0 flex-1 items-center gap-2 px-1 text-start", onClick: () => onOpenApp(app), onKeyDown: (event) => {
24
24
  if (!event.altKey)
25
25
  return;
26
26
  if (event.key === "ArrowUp") {
@@ -31,12 +31,12 @@ function AppRows({ apps, activeAppId, layout, onDragStart, onDrop, onDragEnd, on
31
31
  event.preventDefault();
32
32
  onMove(app.id, 1);
33
33
  }
34
- }, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), title: app.name, children: [renderIcon(app), _jsx("span", { className: "truncate", children: app.name })] }), _jsx("button", { type: "button", className: cn("flex size-6 shrink-0 items-center justify-center rounded text-sidebar-foreground/45 opacity-0 transition-opacity hover:bg-sidebar-accent hover:text-sidebar-foreground group-hover:opacity-100 focus-visible:opacity-100", pinned && "text-sidebar-foreground/70 opacity-100"), "aria-label": copy(pinned ? "unpinApp" : "pinApp", {
34
+ }, "aria-keyshortcuts": "Alt+ArrowUp Alt+ArrowDown", "aria-label": copy("openApp", { name: app.name }), children: [renderIcon(app), _jsx("span", { className: "truncate", children: app.name })] }), _jsx("button", { type: "button", className: cn("flex size-6 shrink-0 items-center justify-center rounded text-sidebar-foreground/45 opacity-0 hover:bg-sidebar-accent hover:text-sidebar-foreground group-hover:opacity-100 focus-visible:opacity-100", pinned && "text-sidebar-foreground/70 opacity-100"), "aria-label": copy(pinned ? "unpinApp" : "pinApp", {
35
35
  name: app.name,
36
- }), "aria-pressed": pinned, title: pinned ? copy("removePinned") : copy("pinTop"), onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] })] })] }, app.id));
36
+ }), "aria-pressed": pinned, onClick: () => onTogglePinned(app.id), children: _jsx(IconPin, { size: 13, strokeWidth: pinned ? 2.2 : 1.6, "aria-hidden": "true" }) }), _jsx("span", { className: "sr-only", children: `${index + 1} of ${orderedApps.length}` })] }) }), _jsxs(ContextMenuContent, { children: [_jsxs(ContextMenuItem, { onSelect: () => onTogglePinned(app.id), children: [_jsx(IconPin, { size: 14, "aria-hidden": "true" }), pinned ? copy("removePinned") : copy("pinTop")] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { disabled: index === 0, onSelect: () => onMove(app.id, -1), children: [_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" }), copy("moveUp")] }), _jsxs(ContextMenuItem, { disabled: index === orderedApps.length - 1, onSelect: () => onMove(app.id, 1), children: [_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" }), copy("moveDown")] })] })] }, app.id));
37
37
  }) }));
38
38
  }
39
- export function ChatFirstAppsRail({ apps, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
39
+ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeAppId, loading = false, error, collapsed = false, layout: controlledLayout, onLayoutChange, onLayoutError, onRetry, onOpenApp, onOpenAllApps, onCreateApp, createAppTrigger, renderIcon, copy = defaultChatFirstCopy, }) {
40
40
  const [localLayout, setLocalLayout] = useState(() => readChatFirstAppLayout());
41
41
  const layout = controlledLayout ?? localLayout;
42
42
  const [draggedAppId, setDraggedAppId] = useState(null);
@@ -95,9 +95,9 @@ export function ChatFirstAppsRail({ apps, activeAppId, loading = false, error, c
95
95
  if (collapsed) {
96
96
  return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "flex flex-col items-center gap-1 px-1.5 pt-2", "aria-label": copy("workspaceApps"), children: [createTrigger, loading && apps.length === 0
97
97
  ? [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "size-9 rounded-md" }, index)))
98
- : visibleApps.map((app) => (_jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md transition-colors", activeAppId === app.id
98
+ : visibleApps.map((app) => (_jsx("button", { type: "button", "data-chat-first-app": true, "data-app-id": app.id, className: cn("flex size-9 items-center justify-center rounded-md", activeAppId === app.id
99
99
  ? "bg-sidebar-accent text-sidebar-accent-foreground"
100
- : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: renderIcon(app) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
100
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), onClick: () => onOpenApp(app), "aria-label": copy("openApp", { name: app.name }), title: app.name, children: renderIcon(app) }, app.id))), onOpenAllApps ? (_jsx("button", { type: "button", "data-chat-first-all-apps": true, className: "flex size-9 items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", onClick: onOpenAllApps, "aria-label": copy("allApps"), title: copy("allApps"), children: _jsx(IconApps, { size: 16, "aria-hidden": "true" }) })) : null, error ? (_jsx("span", { className: "size-1.5 rounded-full bg-destructive", title: error, "aria-label": error })) : null] }));
101
101
  }
102
- return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-1 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-1", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-7 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-1", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-6 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-7 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-1 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
103
- }
102
+ return (_jsxs("section", { "data-chat-first-apps-rail": true, className: "mt-3 px-2 pb-2 pt-2", "aria-label": copy("workspaceApps"), children: [_jsxs("div", { className: "mb-1 flex items-center gap-1.5 px-1 text-[11px] font-medium text-sidebar-foreground/50", children: [_jsx("span", { children: copy("workspaceApps") }), _jsx("span", { className: "ml-auto", children: createTrigger })] }), loading && apps.length === 0 ? (_jsx("div", { className: "space-y-0.5 px-1", children: [0, 1, 2].map((index) => (_jsx(Skeleton, { className: "h-8 w-full rounded-md" }, index))) })) : apps.length === 0 ? (_jsxs("div", { className: "px-1", children: [_jsx("p", { className: "text-xs text-sidebar-foreground/55", children: copy("noWorkspaceApps") }), createAppTrigger ? (createTrigger) : onCreateApp ? (_jsxs("button", { type: "button", className: "mt-1 inline-flex h-6 items-center gap-1.5 rounded-md border border-sidebar-border px-2 text-xs text-sidebar-foreground/75 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, children: [_jsx(IconPlus, { size: 13, "aria-hidden": "true" }), copy("createApp")] })) : (createTrigger)] })) : (_jsx(AppRows, { apps: visibleApps, activeAppId: activeAppId, layout: layout, onDragStart: setDraggedAppId, onDrop: reorderApps, onDragEnd: () => setDraggedAppId(null), onOpenApp: onOpenApp, onTogglePinned: togglePinned, onMove: moveApp, renderIcon: renderIcon, copy: copy })), hasMoreApps || showAllApps ? (_jsxs("button", { type: "button", className: "mt-0.5 flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: () => setShowAllApps((value) => !value), children: [showAllApps ? (_jsx(IconChevronUp, { size: 14, "aria-hidden": "true" })) : (_jsx(IconChevronDown, { size: 14, "aria-hidden": "true" })), showAllApps ? copy("showLess") : copy("showMore")] })) : null, onOpenAllApps ? (_jsx("div", { className: "mt-1 border-t border-sidebar-border/60 pt-1", children: _jsxs("button", { type: "button", "data-chat-first-all-apps": true, className: "flex h-8 w-full items-center gap-2 rounded-md px-2 text-xs text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onOpenAllApps, children: [_jsx(IconApps, { size: 14, "aria-hidden": "true" }), _jsx("span", { children: copy("allApps") })] }) })) : null, error ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "mt-1 flex items-center gap-1 px-1 text-[10px] text-destructive/80", onClick: onRetry, "aria-label": error, children: [_jsx("span", { className: "size-1.5 rounded-full bg-destructive" }), onRetry ? copy("retry") : copy("appsLoadError")] }) }), _jsx(TooltipContent, { side: "right", children: error })] })) : null] }));
103
+ });
@@ -9,4 +9,4 @@ export interface ChatFirstChatHistoryProps extends Omit<ChatHistoryListProps, "i
9
9
  * host. Hosts still own thread fetching and actions, but row rhythm and the
10
10
  * Chats section treatment stay identical.
11
11
  */
12
- export declare function ChatFirstChatHistory({ items, label, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element;
12
+ export declare function ChatFirstChatHistory({ items, label, className, ...props }: ChatFirstChatHistoryProps): import("react").JSX.Element | null;
@@ -6,5 +6,7 @@ import { ChatHistoryList, } from "../chat/ChatHistoryList.js";
6
6
  * Chats section treatment stay identical.
7
7
  */
8
8
  export function ChatFirstChatHistory({ items, label = "Chats", className, ...props }) {
9
+ if (items.length === 0)
10
+ return null;
9
11
  return (_jsxs("section", { "data-chat-first-chat-history": true, className: ["min-h-0 min-w-0", className].filter(Boolean).join(" "), children: [_jsx("p", { className: "mb-1 px-2 text-[11px] font-medium text-sidebar-foreground/50", children: label }), _jsx(ChatHistoryList, { ...props, items: items, variant: "rail", className: "min-w-0" })] }));
10
12
  }