@vertesia/ui 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/lib/core/components/SidePanel.js +1 -1
  2. package/lib/core/components/SidePanel.js.map +1 -1
  3. package/lib/core/components/shadcn/button.d.ts +10 -3
  4. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  5. package/lib/core/components/shadcn/button.js +41 -9
  6. package/lib/core/components/shadcn/button.js.map +1 -1
  7. package/lib/core/components/shadcn/modal/dialog.js +1 -1
  8. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  9. package/lib/core/components/shadcn/resizeable.d.ts +1 -1
  10. package/lib/core/components/shadcn/resizeable.d.ts.map +1 -1
  11. package/lib/core/components/shadcn/resizeable.js +12 -2
  12. package/lib/core/components/shadcn/resizeable.js.map +1 -1
  13. package/lib/core/components/shadcn/tabs.d.ts +12 -1
  14. package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
  15. package/lib/core/components/shadcn/tabs.js +12 -4
  16. package/lib/core/components/shadcn/tabs.js.map +1 -1
  17. package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
  18. package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
  19. package/lib/core/hooks/useIntersectionObserver.d.ts.map +1 -1
  20. package/lib/core/hooks/useIntersectionObserver.js +3 -1
  21. package/lib/core/hooks/useIntersectionObserver.js.map +1 -1
  22. package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
  23. package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
  24. package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
  25. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  26. package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
  27. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  28. package/lib/features/agent/chat/AgentRightPanel.js +1 -1
  29. package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
  30. package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
  31. package/lib/features/agent/chat/AskUserWidget.js +4 -3
  32. package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
  33. package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
  34. package/lib/features/agent/chat/ImageLightbox.js +1 -1
  35. package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
  36. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  37. package/lib/features/agent/chat/ModernAgentConversation.js +57 -12
  38. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  39. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +8 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  41. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +45 -36
  42. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  43. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  44. package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
  45. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  46. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  47. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +7 -3
  48. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  49. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  50. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
  51. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  52. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.d.ts.map +1 -1
  53. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +158 -8
  54. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js.map +1 -1
  55. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
  56. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  57. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
  58. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  59. package/lib/features/agent/chat/hooks/useFileProcessing.d.ts +1 -0
  60. package/lib/features/agent/chat/hooks/useFileProcessing.d.ts.map +1 -1
  61. package/lib/features/agent/chat/hooks/useFileProcessing.js +37 -3
  62. package/lib/features/agent/chat/hooks/useFileProcessing.js.map +1 -1
  63. package/lib/features/magic-pdf/AnnotatedImageSlider.js +13 -13
  64. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  65. package/lib/features/magic-pdf/DownloadPopover.js +2 -2
  66. package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
  67. package/lib/features/magic-pdf/MagicPdfView.js +3 -3
  68. package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
  69. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  70. package/lib/features/oauth/McpConnectionsButton.js +2 -2
  71. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  72. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  73. package/lib/features/oauth/McpConnectionsDialog.js +1 -1
  74. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  75. package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
  76. package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
  77. package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
  78. package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
  79. package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
  80. package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
  81. package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
  82. package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
  83. package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
  84. package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
  85. package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
  86. package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  87. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  88. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  89. package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
  90. package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
  91. package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
  92. package/lib/features/store/objects/components/ContentOverview.js +3 -3
  93. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  94. package/lib/features/store/objects/components/DocumentIcon.js +1 -1
  95. package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
  96. package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  97. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  98. package/lib/features/store/objects/layout/renderers.js +2 -2
  99. package/lib/features/store/objects/layout/renderers.js.map +1 -1
  100. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  101. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  102. package/lib/features/store/objects/selection/actions/StartWorkflowComponent.js +4 -4
  103. package/lib/features/store/objects/selection/actions/StartWorkflowComponent.js.map +1 -1
  104. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  105. package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
  106. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  107. package/lib/features/store/types/SelectContentTypeModal.js +1 -1
  108. package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
  109. package/lib/features/user/UserInfo.js +7 -3
  110. package/lib/features/user/UserInfo.js.map +1 -1
  111. package/lib/router/HistoryNavigator.d.ts.map +1 -1
  112. package/lib/router/HistoryNavigator.js +5 -0
  113. package/lib/router/HistoryNavigator.js.map +1 -1
  114. package/lib/router/Nav.d.ts.map +1 -1
  115. package/lib/router/Nav.js +9 -1
  116. package/lib/router/Nav.js.map +1 -1
  117. package/lib/vertesia-ui-core.js +1 -1
  118. package/lib/vertesia-ui-core.js.map +1 -1
  119. package/lib/vertesia-ui-features.js +1 -1
  120. package/lib/vertesia-ui-features.js.map +1 -1
  121. package/lib/vertesia-ui-router.js +1 -1
  122. package/lib/vertesia-ui-router.js.map +1 -1
  123. package/lib/vertesia-ui-widgets.js +1 -1
  124. package/lib/vertesia-ui-widgets.js.map +1 -1
  125. package/lib/widgets/form/Form.js +1 -1
  126. package/lib/widgets/form/Form.js.map +1 -1
  127. package/lib/widgets/json-view/JSONSwitcher.js +1 -1
  128. package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
  129. package/lib/widgets/json-view/JSONView.js +6 -6
  130. package/lib/widgets/json-view/JSONView.js.map +1 -1
  131. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  132. package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
  133. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  134. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
  135. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
  136. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
  137. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
  138. package/package.json +29 -30
  139. package/src/__tests__/a11y.test.tsx +73 -0
  140. package/src/core/components/SidePanel.tsx +1 -1
  141. package/src/core/components/shadcn/button.tsx +67 -12
  142. package/src/core/components/shadcn/modal/dialog.tsx +1 -1
  143. package/src/core/components/shadcn/resizeable.test.tsx +77 -0
  144. package/src/core/components/shadcn/resizeable.tsx +28 -2
  145. package/src/core/components/shadcn/tabs.test.tsx +65 -0
  146. package/src/core/components/shadcn/tabs.tsx +25 -3
  147. package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
  148. package/src/core/hooks/useIntersectionObserver.tsx +3 -1
  149. package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
  150. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
  151. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
  152. package/src/features/agent/chat/AgentRightPanel.tsx +1 -1
  153. package/src/features/agent/chat/AskUserWidget.test.tsx +49 -0
  154. package/src/features/agent/chat/AskUserWidget.tsx +32 -22
  155. package/src/features/agent/chat/ImageLightbox.tsx +0 -1
  156. package/src/features/agent/chat/ModernAgentConversation.test.tsx +246 -1
  157. package/src/features/agent/chat/ModernAgentConversation.tsx +62 -17
  158. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
  159. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +81 -49
  160. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
  161. package/src/features/agent/chat/ModernAgentOutput/MessageInput.test.tsx +25 -0
  162. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +5 -3
  163. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
  164. package/src/features/agent/chat/ModernAgentOutput/WorkstreamTabs.tsx +328 -42
  165. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
  166. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
  167. package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +70 -0
  168. package/src/features/agent/chat/hooks/useFileProcessing.ts +37 -6
  169. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
  170. package/src/features/magic-pdf/DownloadPopover.tsx +2 -2
  171. package/src/features/magic-pdf/MagicPdfView.tsx +3 -3
  172. package/src/features/oauth/McpConnectionsButton.tsx +0 -2
  173. package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
  174. package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
  175. package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
  176. package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
  177. package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
  178. package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
  179. package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
  180. package/src/features/store/objects/components/ContentOverview.tsx +11 -11
  181. package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
  182. package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
  183. package/src/features/store/objects/layout/renderers.tsx +1 -2
  184. package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
  185. package/src/features/store/objects/selection/actions/StartWorkflowComponent.tsx +6 -5
  186. package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
  187. package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
  188. package/src/features/user/UserInfo.tsx +7 -7
  189. package/src/router/HistoryNavigator.ts +5 -0
  190. package/src/router/Nav.tsx +12 -3
  191. package/src/widgets/form/Form.tsx +1 -1
  192. package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
  193. package/src/widgets/json-view/JSONView.tsx +7 -7
  194. package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
  195. package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
  196. package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
@@ -6,7 +6,7 @@ import {
6
6
  type JSONSchema,
7
7
  type Plan,
8
8
  } from '@vertesia/common';
9
- import { Badge, Button, cn, VTooltip } from '@vertesia/ui/core';
9
+ import { Badge, Button, cn } from '@vertesia/ui/core';
10
10
  import { i18nInstance, NAMESPACE, useUITranslation } from '@vertesia/ui/i18n';
11
11
  import { NavLink } from '@vertesia/ui/router';
12
12
  import { MarkdownRenderer, type MarkdownRendererProps } from '@vertesia/ui/widgets';
@@ -49,6 +49,7 @@ import {
49
49
  getRequestInputDisplayText,
50
50
  getRequestInputMessageKey,
51
51
  getResolvedToolApprovalKeys,
52
+ getToolApprovalResponseMetadata,
52
53
  hasRequestInputUx,
53
54
  isRequestInputAnswered,
54
55
  isToolApprovalAnswerHidden,
@@ -763,7 +764,9 @@ function SummaryMessage({
763
764
  options={uxConfig.options}
764
765
  variant={uxConfig.variant}
765
766
  multiSelect={uxConfig.multiSelect}
766
- onSelect={(optionId) => onSendMessage?.(optionId)}
767
+ onSelect={(optionId) =>
768
+ onSendMessage?.(optionId, getToolApprovalResponseMetadata(message, optionId))
769
+ }
767
770
  onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
768
771
  allowFreeResponse={!uxConfig.options?.length || !!uxConfig.free_response}
769
772
  placeholder={uxConfig.free_response?.placeholder}
@@ -2240,7 +2243,12 @@ function SummaryActivityRow({
2240
2243
  options={uxConfig.options}
2241
2244
  variant={uxConfig.variant}
2242
2245
  multiSelect={uxConfig.multiSelect}
2243
- onSelect={(optionId) => onSendMessage?.(optionId)}
2246
+ onSelect={(optionId) =>
2247
+ onSendMessage?.(
2248
+ optionId,
2249
+ getToolApprovalResponseMetadata(message, optionId),
2250
+ )
2251
+ }
2244
2252
  onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
2245
2253
  allowFreeResponse={
2246
2254
  !uxConfig.options?.length || !!uxConfig.free_response
@@ -2328,8 +2336,43 @@ class MessageErrorBoundary extends Component<{ children: ReactNode }, { hasError
2328
2336
  }
2329
2337
  }
2330
2338
 
2339
+ // Sort chronologically, drop document-panel events + hidden types, and dedupe adjacent
2340
+ // identical messages. Shared by the rendered transcript and the workstream tab derivation.
2341
+ function sortAndDedupeMessages(messages: AgentMessage[], hiddenMessageTypes?: AgentMessageType[]): AgentMessage[] {
2342
+ const filtered = messages.filter(
2343
+ (message) => !isDocumentPanelEventMessage(message) && !hiddenMessageTypes?.includes(message.type),
2344
+ );
2345
+
2346
+ const sorted = [...filtered].sort((a, b) => {
2347
+ const timeA = typeof a.timestamp === 'number' ? a.timestamp : new Date(a.timestamp).getTime();
2348
+ const timeB = typeof b.timestamp === 'number' ? b.timestamp : new Date(b.timestamp).getTime();
2349
+ return timeA - timeB;
2350
+ });
2351
+
2352
+ const deduped: AgentMessage[] = [];
2353
+ for (const msg of sorted) {
2354
+ const previous = deduped[deduped.length - 1];
2355
+ if (previous && shouldCollapseAdjacentRenderedMessage(previous, msg)) {
2356
+ continue;
2357
+ }
2358
+
2359
+ if (previous && shouldDedupeAdjacentCompletedToolMessage(previous, msg)) {
2360
+ continue;
2361
+ }
2362
+ deduped.push(msg);
2363
+ }
2364
+ return deduped;
2365
+ }
2366
+
2331
2367
  interface AllMessagesMixedProps {
2332
2368
  messages: AgentMessage[];
2369
+ /**
2370
+ * Full, workstream-unfiltered message set used to derive the workstream tab list, per-tab
2371
+ * counts, and completion status. Falls back to `messages` when omitted. The parent filters
2372
+ * `messages` down to the active workstream, so deriving the tab list from `messages` alone
2373
+ * makes the tabs collapse (and vanish once `main` is selected) as you navigate between them.
2374
+ */
2375
+ workstreamSourceMessages?: AgentMessage[];
2333
2376
  bottomRef: React.RefObject<HTMLDivElement>;
2334
2377
  viewMode?: 'stacked' | 'sliding';
2335
2378
  isCompleted?: boolean;
@@ -2397,6 +2440,7 @@ const SCROLL_THROTTLE_MS = 100; // Max 10 scrolls per second
2397
2440
 
2398
2441
  function AllMessagesMixedComponent({
2399
2442
  messages,
2443
+ workstreamSourceMessages,
2400
2444
  bottomRef,
2401
2445
  viewMode = 'stacked',
2402
2446
  isCompleted = false,
@@ -2536,36 +2580,26 @@ function AllMessagesMixedComponent({
2536
2580
 
2537
2581
  // Sort all messages chronologically and dedupe adjacent identical messages
2538
2582
  // Low-signal messages are suppressed at the source (server-side) via shouldSuppressLowSignalMessage
2539
- const sortedMessages = React.useMemo(() => {
2540
- const filtered = messages.filter(
2541
- (message) => !isDocumentPanelEventMessage(message) && !hiddenMessageTypes?.includes(message.type),
2542
- );
2543
-
2544
- const sorted = [...filtered].sort((a, b) => {
2545
- const timeA = typeof a.timestamp === 'number' ? a.timestamp : new Date(a.timestamp).getTime();
2546
- const timeB = typeof b.timestamp === 'number' ? b.timestamp : new Date(b.timestamp).getTime();
2547
- return timeA - timeB;
2548
- });
2549
-
2550
- const deduped: AgentMessage[] = [];
2551
- for (const msg of sorted) {
2552
- const previous = deduped[deduped.length - 1];
2553
- if (previous && shouldCollapseAdjacentRenderedMessage(previous, msg)) {
2554
- continue;
2555
- }
2583
+ const sortedMessages = React.useMemo(
2584
+ () => sortAndDedupeMessages(messages, hiddenMessageTypes),
2585
+ [messages, hiddenMessageTypes],
2586
+ );
2556
2587
 
2557
- if (previous && shouldDedupeAdjacentCompletedToolMessage(previous, msg)) {
2558
- continue;
2559
- }
2560
- deduped.push(msg);
2561
- }
2562
- return deduped;
2563
- }, [messages, hiddenMessageTypes]);
2588
+ // The workstream tabs are navigation over the WHOLE conversation, so derive them from the
2589
+ // unfiltered source rather than `messages` (which the parent filters to the active workstream).
2590
+ // Falling back to `sortedMessages` keeps callers that don't pass the source working unchanged.
2591
+ const workstreamSourceSorted = React.useMemo(
2592
+ () =>
2593
+ workstreamSourceMessages
2594
+ ? sortAndDedupeMessages(workstreamSourceMessages, hiddenMessageTypes)
2595
+ : sortedMessages,
2596
+ [workstreamSourceMessages, hiddenMessageTypes, sortedMessages],
2597
+ );
2564
2598
 
2565
2599
  const workstreams = React.useMemo(() => {
2566
- const extractedWorkstreams = extractWorkstreams(sortedMessages);
2600
+ const extractedWorkstreams = extractWorkstreams(workstreamSourceSorted);
2567
2601
 
2568
- sortedMessages.forEach((message) => {
2602
+ workstreamSourceSorted.forEach((message) => {
2569
2603
  const details = getWorkstreamLaunchDetails(message) ?? getWorkstreamActivityDetails(message);
2570
2604
  if (!details) return;
2571
2605
  extractedWorkstreams.set(
@@ -2575,7 +2609,7 @@ function AllMessagesMixedComponent({
2575
2609
  });
2576
2610
 
2577
2611
  return extractedWorkstreams;
2578
- }, [sortedMessages]);
2612
+ }, [workstreamSourceSorted]);
2579
2613
 
2580
2614
  const activeWorkstreamName = React.useMemo(() => {
2581
2615
  if (activeWorkstream === 'all') return undefined;
@@ -2616,17 +2650,17 @@ function AllMessagesMixedComponent({
2616
2650
  }
2617
2651
  }, [activeWorkstream, workstreams, setActiveWorkstream]);
2618
2652
 
2619
- // Count messages per workstream
2653
+ // Count messages per workstream (from the full source so counts don't shrink to the active tab)
2620
2654
  const workstreamCounts = React.useMemo(() => {
2621
2655
  const counts = new Map<string, number>();
2622
- counts.set('all', sortedMessages.length);
2656
+ counts.set('all', workstreamSourceSorted.length);
2623
2657
 
2624
2658
  // Count main messages
2625
- const mainMessages = filterMessagesByWorkstream(sortedMessages, 'main');
2659
+ const mainMessages = filterMessagesByWorkstream(workstreamSourceSorted, 'main');
2626
2660
  counts.set('main', mainMessages.length);
2627
2661
 
2628
2662
  // Count other workstreams
2629
- sortedMessages.forEach((msg) => {
2663
+ workstreamSourceSorted.forEach((msg) => {
2630
2664
  const workstreamId = getWorkstreamId(msg);
2631
2665
  if (workstreamId !== 'main') {
2632
2666
  counts.set(workstreamId, (counts.get(workstreamId) || 0) + 1);
@@ -2634,7 +2668,7 @@ function AllMessagesMixedComponent({
2634
2668
  });
2635
2669
 
2636
2670
  return counts;
2637
- }, [sortedMessages]);
2671
+ }, [workstreamSourceSorted]);
2638
2672
 
2639
2673
  // Filter messages based on active workstream
2640
2674
  const displayMessages = React.useMemo(() => {
@@ -2869,7 +2903,7 @@ function AllMessagesMixedComponent({
2869
2903
  // Group messages by workstream
2870
2904
  const workstreamMessages = new Map<string, AgentMessage[]>();
2871
2905
 
2872
- sortedMessages.forEach((message) => {
2906
+ workstreamSourceSorted.forEach((message) => {
2873
2907
  const workstreamId = getWorkstreamId(message);
2874
2908
  if (!workstreamMessages.has(workstreamId)) {
2875
2909
  workstreamMessages.set(workstreamId, []);
@@ -2905,7 +2939,7 @@ function AllMessagesMixedComponent({
2905
2939
  }
2906
2940
 
2907
2941
  return statusMap;
2908
- }, [sortedMessages]);
2942
+ }, [workstreamSourceSorted]);
2909
2943
 
2910
2944
  return (
2911
2945
  <div
@@ -3220,18 +3254,16 @@ function AllMessagesMixedComponent({
3220
3254
  {isSummaryView && activeWorkstream !== 'all' && activeWorkstreamName && (
3221
3255
  <div className="sticky top-0 z-20 -mx-2 bg-background/95 px-2 pb-2 pt-1 backdrop-blur sm:-mx-4 sm:px-4">
3222
3256
  <div className="mx-auto flex w-full max-w-3xl items-center gap-2 border-b border-border/70 pb-3">
3223
- <VTooltip description={t('agent.backToMainAgent')} asChild>
3224
- <Button
3225
- type="button"
3226
- variant="ghost"
3227
- size="icon"
3228
- className="size-8 shrink-0 text-muted hover:text-foreground"
3229
- aria-label={t('agent.backToMainAgent')}
3230
- onClick={handleShowMainAgentChat}
3231
- >
3232
- <ArrowLeft className="size-4" aria-hidden="true" />
3233
- </Button>
3234
- </VTooltip>
3257
+ <Button
3258
+ type="button"
3259
+ variant="ghost"
3260
+ size="icon"
3261
+ className="size-8 shrink-0 text-muted hover:text-foreground"
3262
+ title={t('agent.backToMainAgent')}
3263
+ onClick={handleShowMainAgentChat}
3264
+ >
3265
+ <ArrowLeft className="size-4" aria-hidden="true" />
3266
+ </Button>
3235
3267
  <div className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">
3236
3268
  {activeWorkstreamName}
3237
3269
  </div>
@@ -100,7 +100,9 @@ export default function Header({
100
100
  const controls = (
101
101
  <div
102
102
  className={cn(
103
- isCompact ? 'flex flex-col items-center gap-1' : 'flex justify-end items-center gap-2 ms-auto',
103
+ isCompact
104
+ ? 'flex flex-row items-center gap-1 lg:flex-col'
105
+ : 'flex justify-end items-center gap-2 ms-auto',
104
106
  )}
105
107
  >
106
108
  {showPlanButton && (
@@ -177,7 +179,6 @@ export default function Header({
177
179
  variant={isPlaybackEnabled ? 'primary' : 'ghost'}
178
180
  onClick={onTogglePlayback}
179
181
  aria-pressed={isPlaybackEnabled}
180
- aria-label={t('agent.rewind.label')}
181
182
  title={t('agent.rewind.label')}
182
183
  className={cn(
183
184
  'transition-all duration-200 rounded-md',
@@ -376,7 +377,6 @@ function MoreDropdown({
376
377
  size={compact ? 'icon' : 'xs'}
377
378
  variant="ghost"
378
379
  title={t('agent.moreActions')}
379
- aria-label={t('agent.moreActions')}
380
380
  className={compact ? 'size-8 rounded-lg' : undefined}
381
381
  >
382
382
  <MoreVertical className="size-4" />
@@ -54,6 +54,31 @@ describe('MessageInput', () => {
54
54
  expect(onFilesSelected).toHaveBeenCalledWith([file]);
55
55
  });
56
56
 
57
+ it('does not send or clear the text when Enter is pressed while files are still processing', () => {
58
+ const onSend = vi.fn();
59
+ renderWithProviders(<MessageInput onSend={onSend} onFilesSelected={vi.fn()} hasProcessingFiles />);
60
+
61
+ const textbox = screen.getByRole('textbox') as HTMLTextAreaElement;
62
+ fireEvent.change(textbox, { target: { value: 'please analyze this' } });
63
+ fireEvent.keyDown(textbox, { key: 'Enter' });
64
+
65
+ expect(onSend).not.toHaveBeenCalled();
66
+ // The typed text must be retained so the user doesn't have to re-type it.
67
+ expect(textbox.value).toBe('please analyze this');
68
+ });
69
+
70
+ it('sends and clears the text on Enter once files have finished processing', () => {
71
+ const onSend = vi.fn();
72
+ renderWithProviders(<MessageInput onSend={onSend} onFilesSelected={vi.fn()} />);
73
+
74
+ const textbox = screen.getByRole('textbox') as HTMLTextAreaElement;
75
+ fireEvent.change(textbox, { target: { value: 'please analyze this' } });
76
+ fireEvent.keyDown(textbox, { key: 'Enter' });
77
+
78
+ expect(onSend).toHaveBeenCalledWith('please analyze this');
79
+ expect(textbox.value).toBe('');
80
+ });
81
+
57
82
  it('renders attachment actions before the MCP slot', () => {
58
83
  renderWithProviders(<MessageInput onSend={vi.fn()} mcpSlot={<button type="button">MCP</button>} isCompleted />);
59
84
 
@@ -346,7 +346,11 @@ export default function MessageInput({
346
346
 
347
347
  const handleSend = () => {
348
348
  const message = value.trim();
349
- if (!message || disabled || isSending) return;
349
+ // Mirror the send button's disabled condition so the keyboard (Enter) path can't
350
+ // dispatch a send that the parent will reject while files are still uploading —
351
+ // otherwise the text would be cleared below and lost. The disabled button + its
352
+ // "wait for files" tooltip already explain why Enter does nothing here.
353
+ if (!message || disabled || isSending || hasProcessingFiles) return;
350
354
 
351
355
  onSend(message);
352
356
  setValue('');
@@ -519,7 +523,6 @@ export default function MessageInput({
519
523
  variant="ghost"
520
524
  size="icon"
521
525
  className="size-8 rounded-full text-muted hover:bg-muted"
522
- aria-label={t('agent.addAttachment')}
523
526
  title={t('agent.addAttachment')}
524
527
  >
525
528
  <PlusIcon className="size-4" />
@@ -650,7 +653,6 @@ export default function MessageInput({
650
653
  '[&_svg]:text-destructive disabled:[&_svg]:text-muted',
651
654
  )}
652
655
  title={t('agent.stopTooltip')}
653
- aria-label={t('agent.stopTooltip')}
654
656
  >
655
657
  {isStopping ? (
656
658
  <Spinner size="sm" />
@@ -35,6 +35,7 @@ import { ThinkingMessages } from '../WaitingMessages';
35
35
  import { AttachmentPreviewList, parseUserMessageAttachments } from './AttachmentPreview';
36
36
  import { MessageDeliveryStatus } from './MessageDeliveryStatus';
37
37
  import { processContentForMarkdown } from './processContentForMarkdown';
38
+ import { getToolApprovalResponseMetadata } from './requestInputMessages';
38
39
  import { getWorkstreamId } from './utils';
39
40
 
40
41
  /** className overrides for MessageItem — single source of truth for all className overrides. */
@@ -649,7 +650,9 @@ function MessageItemComponent({
649
650
  options={uxConfig.options}
650
651
  variant={uxConfig.variant}
651
652
  multiSelect={uxConfig.multiSelect}
652
- onSelect={(optionId) => onSendMessage?.(optionId)}
653
+ onSelect={(optionId) =>
654
+ onSendMessage?.(optionId, getToolApprovalResponseMetadata(message, optionId))
655
+ }
653
656
  onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
654
657
  allowFreeResponse={!uxConfig.options?.length || !!uxConfig.free_response}
655
658
  placeholder={uxConfig.free_response?.placeholder}