@nextclaw/ui 0.15.3 → 0.15.4

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 (184) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/assets/aiden0z-pptx-renderer.es-Bd99vut2.js +94 -0
  3. package/dist/assets/api-BOXOrpcJ.js +13 -0
  4. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +106 -0
  5. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
  6. package/dist/assets/appearance-settings-page-fLIJbx1C.js +1 -0
  7. package/dist/assets/{book-open-C3mJGwiy.js → book-open-DWUjS6aW.js} +1 -1
  8. package/dist/assets/channels-list-page-CvCTJraa.js +8 -0
  9. package/dist/assets/chat-page-B42UUIFR.js +1 -0
  10. package/dist/assets/chunk-DseTPa7n.js +1 -0
  11. package/dist/assets/config-split-page-DBgt9S8b.js +1 -0
  12. package/dist/assets/confirm-dialog-GSXIvoRU.js +5 -0
  13. package/dist/assets/desktop-update-config-7bmNeWIC.js +1 -0
  14. package/dist/assets/dist-DC7f4TJH.js +41 -0
  15. package/dist/assets/doc-browser-CgdoLUT92.js +1 -0
  16. package/dist/assets/doc-browser-D0monHjG.js +1 -0
  17. package/dist/assets/doc-browser-context-CGwEOhBy.js +1 -0
  18. package/dist/assets/docx-preview-eFH9DRsS.js +20 -0
  19. package/dist/assets/download-DiP9zx-J.js +1 -0
  20. package/dist/assets/{external-link-CU0kzzIP.js → external-link-C6ZeSPef.js} +1 -1
  21. package/dist/assets/eye-suvKmtwn.js +1 -0
  22. package/dist/assets/folder-CMELbQGG.js +1 -0
  23. package/dist/assets/{form-actions-BvCp_0lS.js → form-actions-CUAzpNe_.js} +1 -1
  24. package/dist/assets/index-BFeNDQ4R.css +1 -0
  25. package/dist/assets/index-D_6U-M94.js +2 -0
  26. package/dist/assets/jszip.min-Das2IAnb.js +2 -0
  27. package/dist/assets/loader-circle-tYHStefV.js +1 -0
  28. package/dist/assets/logo-badge-Cu5UGH_4.js +1 -0
  29. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +9 -0
  30. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +1 -0
  31. package/dist/assets/model-config-page-CWP6pyBm.js +1 -0
  32. package/dist/assets/plus-tywizkeY.js +1 -0
  33. package/dist/assets/popover-DImguRok.js +1 -0
  34. package/dist/assets/provider-models-C_yOh6DE.js +1 -0
  35. package/dist/assets/provider-scoped-model-input-CE7p4SbJ.js +1 -0
  36. package/dist/assets/providers-config-page-BKPo8SjH.js +1 -0
  37. package/dist/assets/react-5sjAT_6d.js +8 -0
  38. package/dist/assets/{refresh-cw-CILd1vAP.js → refresh-cw-CHLBzWWj.js} +1 -1
  39. package/dist/assets/remote-DXJryUxB.js +1 -0
  40. package/dist/assets/rotate-cw-BefO5brU.js +1 -0
  41. package/dist/assets/runtime-config-page-1Axv4QGu.js +1 -0
  42. package/dist/assets/{save-ByJqcPTP.js → save-BABzyrjB.js} +1 -1
  43. package/dist/assets/{search-DVIV6pfD.js → search-BTqxG3ZG.js} +1 -1
  44. package/dist/assets/search-config-page-BkKKmxmS.js +1 -0
  45. package/dist/assets/secrets-config-page-CWJMyu-k.js +3 -0
  46. package/dist/assets/security-config-DeQUIXfi.js +1 -0
  47. package/dist/assets/select-TYZDBNmq.js +1 -0
  48. package/dist/assets/setting-row-DR23Z_5I.js +1 -0
  49. package/dist/assets/{settings-2-CH5shGg_.js → settings-2-D8WWcBnw.js} +1 -1
  50. package/dist/assets/{skeleton-DeX1FKw7.js → skeleton-CloHeg0S.js} +1 -1
  51. package/dist/assets/tabs-CX-ckWoo.js +1 -0
  52. package/dist/assets/tag-chip-mCc23Rhb.js +1 -0
  53. package/dist/assets/trash-2-SAW1Z4Po.js +1 -0
  54. package/dist/assets/use-config-BucdaK23.js +1 -0
  55. package/dist/assets/use-confirm-dialog-C95F6sT1.js +1 -0
  56. package/dist/assets/use-viewport-layout-C6m3Nte7.js +1 -0
  57. package/dist/assets/x-DtCPHwCu.js +1 -0
  58. package/dist/assets/xlsx-B6zVt1RK.js +64 -0
  59. package/dist/index.html +34 -28
  60. package/package.json +11 -6
  61. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +41 -0
  62. package/src/features/chat/components/chat-popover-content.tsx +26 -0
  63. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
  64. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
  65. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
  66. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
  67. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
  68. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
  69. package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
  70. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  71. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
  72. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +3 -14
  73. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
  74. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
  75. package/src/features/chat/features/message/components/chat-message-list.container.tsx +16 -1
  76. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
  77. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
  78. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +4 -3
  79. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
  80. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
  81. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
  82. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  83. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +3 -3
  84. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -3
  85. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
  86. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +403 -106
  87. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
  88. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
  89. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
  90. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +9 -3
  91. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +7 -5
  92. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +57 -177
  93. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
  94. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
  95. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
  96. package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
  97. package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
  98. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
  99. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
  100. package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
  101. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
  102. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
  103. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
  104. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
  105. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
  106. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +17 -0
  107. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
  108. package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
  109. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
  110. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
  111. package/src/features/chat/index.ts +1 -0
  112. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
  113. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
  114. package/src/features/chat/managers/chat-thread.manager.ts +123 -126
  115. package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
  116. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
  117. package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
  118. package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
  119. package/src/features/chat/stores/chat-thread.store.ts +70 -13
  120. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
  121. package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
  122. package/src/index.css +36 -2
  123. package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
  124. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
  125. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
  126. package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
  127. package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
  128. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
  129. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
  130. package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
  131. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
  132. package/src/shared/components/file-type-icon.tsx +272 -0
  133. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
  134. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
  135. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
  136. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
  137. package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
  138. package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
  139. package/src/shared/lib/i18n/locales/en-US/chat.json +17 -1
  140. package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
  141. package/src/shared/lib/i18n/locales/zh-CN/chat.json +17 -1
  142. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
  143. package/dist/assets/api-iNBpppzj.js +0 -13
  144. package/dist/assets/appearance-settings-page-DMffQFR3.js +0 -1
  145. package/dist/assets/channels-list-page-CCtp0BRS.js +0 -8
  146. package/dist/assets/chat-page-H5a0dWvO.js +0 -1
  147. package/dist/assets/config-split-page-CrOJgB-Y.js +0 -1
  148. package/dist/assets/confirm-dialog-Chv50USy.js +0 -5
  149. package/dist/assets/desktop-update-config-KzTuXRf_.js +0 -1
  150. package/dist/assets/dist-BunzmC4B.js +0 -1
  151. package/dist/assets/dist-ChmGwvtK.js +0 -1
  152. package/dist/assets/doc-browser-DNRL4Zvd.js +0 -1
  153. package/dist/assets/doc-browser-Dg0pmkOg.js +0 -1
  154. package/dist/assets/doc-browser-context-BHY-0FSD.js +0 -1
  155. package/dist/assets/es2015-DQZ5JWwK.js +0 -41
  156. package/dist/assets/i18n-provider-Cu8XyZb_.js +0 -1
  157. package/dist/assets/index-CzfBdvK1.css +0 -1
  158. package/dist/assets/index-DGqHne1Q.js +0 -104
  159. package/dist/assets/loader-circle-CDSFuzmk.js +0 -1
  160. package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +0 -9
  161. package/dist/assets/mcp-marketplace-page-D6idOTLp.js +0 -1
  162. package/dist/assets/model-config-page-bNNo-XyG.js +0 -1
  163. package/dist/assets/plus-D4DsdLN-.js +0 -1
  164. package/dist/assets/provider-scoped-model-input-IlgQCsoT.js +0 -1
  165. package/dist/assets/providers-config-page-wLo6CAJw.js +0 -1
  166. package/dist/assets/react-BPIsQIlO.js +0 -8
  167. package/dist/assets/remote-uKEYp7AR.js +0 -1
  168. package/dist/assets/rotate-cw-wPFPg1kH.js +0 -1
  169. package/dist/assets/runtime-config-page-DDn1sP4G.js +0 -1
  170. package/dist/assets/search-config-page-B-Cf4iiB.js +0 -1
  171. package/dist/assets/secrets-config-page-DPKsY8Iw.js +0 -3
  172. package/dist/assets/security-config-e9SSBlKr.js +0 -1
  173. package/dist/assets/select-BdgczSNw.js +0 -1
  174. package/dist/assets/setting-row-BkTVUTKx.js +0 -1
  175. package/dist/assets/side-dock-BsBvcJ9i.js +0 -3
  176. package/dist/assets/switch-Cv8Ekb3x.js +0 -1
  177. package/dist/assets/tag-chip-RB9uKcoC.js +0 -1
  178. package/dist/assets/tooltip-oQlEN-l6.js +0 -1
  179. package/dist/assets/use-config-l3W7y8Qz.js +0 -1
  180. package/dist/assets/use-confirm-dialog-BAxaVOJI.js +0 -1
  181. package/dist/assets/x-BBMffEN0.js +0 -1
  182. /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
  183. /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
  184. /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
package/dist/index.html CHANGED
@@ -186,34 +186,40 @@
186
186
  })();
187
187
  </script>
188
188
  <title>NextClaw</title>
189
- <script type="module" crossorigin src="/assets/index-DGqHne1Q.js"></script>
190
- <link rel="modulepreload" crossorigin href="/assets/react-BPIsQIlO.js">
191
- <link rel="modulepreload" crossorigin href="/assets/dist-BunzmC4B.js">
192
- <link rel="modulepreload" crossorigin href="/assets/dist-ChmGwvtK.js">
193
- <link rel="modulepreload" crossorigin href="/assets/tooltip-oQlEN-l6.js">
194
- <link rel="modulepreload" crossorigin href="/assets/book-open-C3mJGwiy.js">
195
- <link rel="modulepreload" crossorigin href="/assets/plus-D4DsdLN-.js">
196
- <link rel="modulepreload" crossorigin href="/assets/x-BBMffEN0.js">
197
- <link rel="modulepreload" crossorigin href="/assets/middleware-BILWbJE4.js">
198
- <link rel="modulepreload" crossorigin href="/assets/doc-browser-context-BHY-0FSD.js">
199
- <link rel="modulepreload" crossorigin href="/assets/es2015-DQZ5JWwK.js">
200
- <link rel="modulepreload" crossorigin href="/assets/switch-Cv8Ekb3x.js">
201
- <link rel="modulepreload" crossorigin href="/assets/confirm-dialog-Chv50USy.js">
202
- <link rel="modulepreload" crossorigin href="/assets/use-confirm-dialog-BAxaVOJI.js">
203
- <link rel="modulepreload" crossorigin href="/assets/host-capabilities-9bB_998Z.js">
204
- <link rel="modulepreload" crossorigin href="/assets/side-dock-BsBvcJ9i.js">
205
- <link rel="modulepreload" crossorigin href="/assets/select-BdgczSNw.js">
206
- <link rel="modulepreload" crossorigin href="/assets/api-iNBpppzj.js">
207
- <link rel="modulepreload" crossorigin href="/assets/search-DVIV6pfD.js">
208
- <link rel="modulepreload" crossorigin href="/assets/external-link-CU0kzzIP.js">
209
- <link rel="modulepreload" crossorigin href="/assets/refresh-cw-CILd1vAP.js">
210
- <link rel="modulepreload" crossorigin href="/assets/doc-browser-Dg0pmkOg.js">
211
- <link rel="modulepreload" crossorigin href="/assets/i18n-provider-Cu8XyZb_.js">
212
- <link rel="modulepreload" crossorigin href="/assets/loader-circle-CDSFuzmk.js">
213
- <link rel="modulepreload" crossorigin href="/assets/skeleton-DeX1FKw7.js">
214
- <link rel="modulepreload" crossorigin href="/assets/tag-chip-RB9uKcoC.js">
215
- <link rel="modulepreload" crossorigin href="/assets/mcp-marketplace-page-BeEQK0DL.js">
216
- <link rel="stylesheet" crossorigin href="/assets/index-CzfBdvK1.css">
189
+ <script type="module" crossorigin src="/assets/index-D_6U-M94.js"></script>
190
+ <link rel="modulepreload" crossorigin href="/assets/chunk-DseTPa7n.js">
191
+ <link rel="modulepreload" crossorigin href="/assets/react-5sjAT_6d.js">
192
+ <link rel="modulepreload" crossorigin href="/assets/dist-DC7f4TJH.js">
193
+ <link rel="modulepreload" crossorigin href="/assets/select-TYZDBNmq.js">
194
+ <link rel="modulepreload" crossorigin href="/assets/popover-DImguRok.js">
195
+ <link rel="modulepreload" crossorigin href="/assets/api-BOXOrpcJ.js">
196
+ <link rel="modulepreload" crossorigin href="/assets/x-DtCPHwCu.js">
197
+ <link rel="modulepreload" crossorigin href="/assets/confirm-dialog-GSXIvoRU.js">
198
+ <link rel="modulepreload" crossorigin href="/assets/search-BTqxG3ZG.js">
199
+ <link rel="modulepreload" crossorigin href="/assets/external-link-C6ZeSPef.js">
200
+ <link rel="modulepreload" crossorigin href="/assets/plus-tywizkeY.js">
201
+ <link rel="modulepreload" crossorigin href="/assets/refresh-cw-CHLBzWWj.js">
202
+ <link rel="modulepreload" crossorigin href="/assets/middleware-BLMHv9YF.js">
203
+ <link rel="modulepreload" crossorigin href="/assets/doc-browser-context-CGwEOhBy.js">
204
+ <link rel="modulepreload" crossorigin href="/assets/doc-browser-D0monHjG.js">
205
+ <link rel="modulepreload" crossorigin href="/assets/book-open-DWUjS6aW.js">
206
+ <link rel="modulepreload" crossorigin href="/assets/use-confirm-dialog-C95F6sT1.js">
207
+ <link rel="modulepreload" crossorigin href="/assets/download-DiP9zx-J.js">
208
+ <link rel="modulepreload" crossorigin href="/assets/eye-suvKmtwn.js">
209
+ <link rel="modulepreload" crossorigin href="/assets/folder-CMELbQGG.js">
210
+ <link rel="modulepreload" crossorigin href="/assets/tabs-CX-ckWoo.js">
211
+ <link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-C6m3Nte7.js">
212
+ <link rel="modulepreload" crossorigin href="/assets/loader-circle-tYHStefV.js">
213
+ <link rel="modulepreload" crossorigin href="/assets/skeleton-CloHeg0S.js">
214
+ <link rel="modulepreload" crossorigin href="/assets/trash-2-SAW1Z4Po.js">
215
+ <link rel="modulepreload" crossorigin href="/assets/logo-badge-Cu5UGH_4.js">
216
+ <link rel="modulepreload" crossorigin href="/assets/host-capabilities-zZvY9U63.js">
217
+ <link rel="modulepreload" crossorigin href="/assets/provider-models-C_yOh6DE.js">
218
+ <link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-CkHLbvZQ.js">
219
+ <link rel="modulepreload" crossorigin href="/assets/tag-chip-mCc23Rhb.js">
220
+ <link rel="modulepreload" crossorigin href="/assets/mcp-marketplace-page-BUzHDwW6.js">
221
+ <link rel="stylesheet" crossorigin href="/assets/app-presenter-provider-D3BuuOyZ.css">
222
+ <link rel="stylesheet" crossorigin href="/assets/index-BFeNDQ4R.css">
217
223
  </head>
218
224
 
219
225
  <body>
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
+ "@aiden0z/pptx-renderer": "1.2.4",
9
+ "@iconify/icons-vscode-icons": "^1.2.29",
10
+ "@iconify/react": "^6.0.2",
8
11
  "@radix-ui/react-dialog": "^1.1.2",
9
12
  "@radix-ui/react-label": "^2.1.0",
10
13
  "@radix-ui/react-popover": "^1.1.15",
@@ -16,6 +19,7 @@
16
19
  "@tanstack/react-query": "^5.59.20",
17
20
  "class-variance-authority": "^0.7.1",
18
21
  "clsx": "^2.1.1",
22
+ "docx-preview": "0.4.0",
19
23
  "lucide-react": "^0.462.0",
20
24
  "qrcode": "^1.5.4",
21
25
  "react": "^18.3.1",
@@ -26,16 +30,17 @@
26
30
  "rxjs": "^7.8.2",
27
31
  "sonner": "^1.7.1",
28
32
  "tailwind-merge": "^2.5.4",
33
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
29
34
  "zod": "^3.23.8",
30
35
  "zustand": "^5.0.2",
31
- "@nextclaw/agent-chat-ui": "0.6.3",
32
- "@nextclaw/client-sdk": "0.5.3",
33
- "@nextclaw/ncp-http-agent-client": "0.4.3",
36
+ "@nextclaw/agent-chat-ui": "0.6.4",
37
+ "@nextclaw/client-sdk": "0.5.4",
34
38
  "@nextclaw/ncp": "0.7.3",
35
- "@nextclaw/ncp-toolkit": "0.6.3",
39
+ "@nextclaw/ncp-react": "0.5.4",
40
+ "@nextclaw/ncp-toolkit": "0.6.4",
36
41
  "@nextclaw/shared": "0.4.3",
37
42
  "@nextclaw/agent-chat": "0.3.2",
38
- "@nextclaw/ncp-react": "0.5.3"
43
+ "@nextclaw/ncp-http-agent-client": "0.4.3"
39
44
  },
40
45
  "devDependencies": {
41
46
  "@testing-library/react": "^16.3.0",
@@ -0,0 +1,41 @@
1
+ import { useState } from "react";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ChatPopoverContent } from "@/features/chat/components/chat-popover-content";
5
+ import { Popover, PopoverTrigger } from "@/shared/components/ui/popover";
6
+
7
+ function ChatPopoverHarness() {
8
+ const [open, setOpen] = useState(true);
9
+ return (
10
+ <Popover open={open} onOpenChange={setOpen}>
11
+ <PopoverTrigger>Sessions</PopoverTrigger>
12
+ <ChatPopoverContent>Session search</ChatPopoverContent>
13
+ </Popover>
14
+ );
15
+ }
16
+
17
+ function appendComposerFocusTarget(): HTMLElement {
18
+ const shell = document.createElement("div");
19
+ shell.className = "nextclaw-chat-input-bar-shell";
20
+ const composer = document.createElement("div");
21
+ composer.setAttribute("contenteditable", "true");
22
+ composer.setAttribute("role", "textbox");
23
+ shell.appendChild(composer);
24
+ document.body.appendChild(shell);
25
+ return composer;
26
+ }
27
+
28
+ describe("ChatPopoverContent", () => {
29
+ it("keeps chat popovers open when the composer receives programmatic focus", () => {
30
+ const composer = appendComposerFocusTarget();
31
+ try {
32
+ render(<ChatPopoverHarness />);
33
+
34
+ fireEvent.focusIn(composer);
35
+
36
+ expect(screen.getByText("Session search")).toBeTruthy();
37
+ } finally {
38
+ composer.closest(".nextclaw-chat-input-bar-shell")?.remove();
39
+ }
40
+ });
41
+ });
@@ -0,0 +1,26 @@
1
+ import { forwardRef, type ComponentPropsWithoutRef, type ElementRef } from "react";
2
+ import { PopoverContent } from "@/shared/components/ui/popover";
3
+
4
+ function isChatComposerFocusTarget(target: EventTarget | null): boolean {
5
+ return target instanceof HTMLElement && Boolean(
6
+ target.closest('.nextclaw-chat-input-bar-shell [role="textbox"][contenteditable="true"]'),
7
+ );
8
+ }
9
+
10
+ export const ChatPopoverContent = forwardRef<
11
+ ElementRef<typeof PopoverContent>,
12
+ ComponentPropsWithoutRef<typeof PopoverContent>
13
+ >(function ChatPopoverContent({ onFocusOutside, ...props }, ref) {
14
+ return (
15
+ <PopoverContent
16
+ {...props}
17
+ ref={ref}
18
+ onFocusOutside={(event) => {
19
+ onFocusOutside?.(event);
20
+ if (!event.defaultPrevented && isChatComposerFocusTarget(event.detail.originalEvent.target)) {
21
+ event.preventDefault();
22
+ }
23
+ }}
24
+ />
25
+ );
26
+ });
@@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
3
3
  import type { NcpMessage } from "@nextclaw/ncp";
4
4
  import { beforeEach, expect, it, vi } from "vitest";
5
5
  import { ChatConversationContent } from "@/features/chat/components/conversation/chat-conversation-content";
6
+ import { useChatMessageLayoutStore } from "@/features/chat/stores/chat-message-layout.store";
6
7
 
7
8
  const captures = vi.hoisted(() => ({
8
9
  isAtBottom: true,
@@ -52,11 +53,23 @@ function renderContent(options: { bottomSlot?: React.ReactNode } = {}) {
52
53
  }
53
54
 
54
55
  beforeEach(() => {
56
+ useChatMessageLayoutStore.getState().setLayout("card");
55
57
  captures.isAtBottom = true;
56
58
  captures.onScroll.mockReset();
57
59
  captures.scrollToBottom.mockReset();
58
60
  });
59
61
 
62
+ it("uses the centered reading track in flat message layout", () => {
63
+ useChatMessageLayoutStore.getState().setLayout("flat");
64
+
65
+ renderContent();
66
+
67
+ const track = screen
68
+ .getByTestId("chat-message-list")
69
+ .closest('[data-chat-conversation-track="flat"]');
70
+ expect(track?.className).toContain("max-w-[min(52rem,100%)]");
71
+ });
72
+
60
73
  it("hides the scroll-to-bottom action while the conversation is already at the bottom", () => {
61
74
  renderContent();
62
75
 
@@ -13,8 +13,7 @@ import type { NcpSessionSummaryView, SessionEntryView } from "@/shared/lib/api";
13
13
 
14
14
  const mocks = vi.hoisted(() => ({
15
15
  deleteSession: vi.fn(),
16
- openChildSessionPanel: vi.fn(),
17
- openSessionCronPanel: vi.fn(),
16
+ toggleWorkspacePanel: vi.fn(),
18
17
  selectSession: vi.fn(),
19
18
  sessionItems: [] as NcpSessionListItemView[],
20
19
  isSessionListLoading: false,
@@ -24,8 +23,7 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
24
23
  usePresenter: () => ({
25
24
  chatThreadManager: {
26
25
  deleteSession: mocks.deleteSession,
27
- openChildSessionPanel: mocks.openChildSessionPanel,
28
- openSessionCronPanel: mocks.openSessionCronPanel,
26
+ toggleWorkspacePanel: mocks.toggleWorkspacePanel,
29
27
  },
30
28
  chatSessionListManager: {
31
29
  selectSession: mocks.selectSession,
@@ -120,8 +118,7 @@ function renderHeaderSection() {
120
118
  describe("ChatConversationHeaderSection", () => {
121
119
  beforeEach(() => {
122
120
  mocks.deleteSession.mockReset();
123
- mocks.openChildSessionPanel.mockReset();
124
- mocks.openSessionCronPanel.mockReset();
121
+ mocks.toggleWorkspacePanel.mockReset();
125
122
  mocks.selectSession.mockReset();
126
123
  mocks.isSessionListLoading = false;
127
124
  viewportLayoutManager.resetForTests();
@@ -195,16 +192,29 @@ describe("ChatConversationHeaderSection", () => {
195
192
  ];
196
193
  });
197
194
 
198
- it("shows the child-session entry from session query state before a tool card opens it", async () => {
195
+ it("opens the current session workspace overview from the header", async () => {
199
196
  const user = userEvent.setup();
200
197
 
201
198
  renderHeaderSection();
202
- await user.click(screen.getByRole("button", { name: "View child sessions" }));
199
+ await user.click(screen.getByRole("button", { name: "Open session workspace" }));
203
200
 
204
- expect(mocks.openChildSessionPanel).toHaveBeenCalledWith({
205
- parentSessionKey: "parent-session-1",
206
- activeChildSessionKey: "child-session-1",
201
+ expect(mocks.toggleWorkspacePanel).toHaveBeenCalledWith("parent-session-1");
202
+ });
203
+
204
+ it("shows the close workspace action while the current session panel is open", () => {
205
+ useChatThreadStore.setState({
206
+ snapshot: {
207
+ ...useChatThreadStore.getState().snapshot,
208
+ workspacePanelParentKey: "parent-session-1",
209
+ activeWorkspacePanelKind: "overview",
210
+ },
207
211
  });
212
+
213
+ renderHeaderSection();
214
+
215
+ expect(
216
+ screen.getByRole("button", { name: "Close session workspace" }),
217
+ ).toBeTruthy();
208
218
  });
209
219
 
210
220
  it("uses the collapsed desktop title as a session switcher", async () => {
@@ -51,8 +51,8 @@ describe("ChatConversationHeader", () => {
51
51
  sessionKey="session-1"
52
52
  canDeleteSession
53
53
  isDeletePending={false}
54
- childSessionCount={0}
55
- sessionCronJobCount={0}
54
+ isWorkspaceOpen={false}
55
+ onToggleWorkspace={vi.fn()}
56
56
  onDeleteSession={vi.fn()}
57
57
  />
58
58
  ),
@@ -14,16 +14,23 @@ vi.mock("@/features/chat/features/workspace/components/chat-session-workspace-pa
14
14
  ChatSessionWorkspacePanel: ({
15
15
  sessionProjectRoot,
16
16
  sessionWorkingDir,
17
+ workspacePanelWidth,
17
18
  }: {
18
19
  sessionProjectRoot: string | null;
19
20
  sessionWorkingDir: string | null;
21
+ workspacePanelWidth: number;
20
22
  }) => {
21
- mocks.panelProps({ sessionProjectRoot, sessionWorkingDir });
23
+ mocks.panelProps({
24
+ sessionProjectRoot,
25
+ sessionWorkingDir,
26
+ workspacePanelWidth,
27
+ });
22
28
  return (
23
29
  <div
24
30
  data-testid="workspace-panel"
25
31
  data-project-root={sessionProjectRoot ?? ""}
26
32
  data-working-dir={sessionWorkingDir ?? ""}
33
+ data-width={workspacePanelWidth}
27
34
  />
28
35
  );
29
36
  },
@@ -85,6 +92,7 @@ describe("ChatConversationWorkspaceSection", () => {
85
92
  workspaceNavigationHistoryIndex: 0,
86
93
  sessionProjectRoot: null,
87
94
  sessionWorkingDir: null,
95
+ workspacePanelWidth: 620,
88
96
  },
89
97
  });
90
98
  useChatQueryStore.setState({
@@ -122,11 +130,30 @@ describe("ChatConversationWorkspaceSection", () => {
122
130
  expect(panel.getAttribute("data-working-dir")).toBe(
123
131
  "/Users/peiwang/Projects/nextbot",
124
132
  );
133
+ expect(panel.getAttribute("data-width")).toBe("620");
125
134
  expect(mocks.panelProps).toHaveBeenCalledWith(
126
135
  expect.objectContaining({
127
136
  sessionProjectRoot: "/Users/peiwang/Projects/nextbot",
128
137
  sessionWorkingDir: "/Users/peiwang/Projects/nextbot",
138
+ workspacePanelWidth: 620,
129
139
  }),
130
140
  );
131
141
  });
142
+
143
+ it("renders an explicitly opened overview without requiring existing workspace resources", () => {
144
+ useChatThreadStore.getState().setSnapshot({
145
+ activeWorkspacePanelKind: "overview",
146
+ workspaceFileTabs: [],
147
+ activeWorkspaceFileKey: null,
148
+ });
149
+
150
+ render(
151
+ <ChatConversationWorkspaceSection
152
+ layoutMode="desktop"
153
+ sessionKey="session-1"
154
+ />,
155
+ );
156
+
157
+ expect(screen.getByTestId("workspace-panel")).toBeTruthy();
158
+ });
132
159
  });
@@ -3,6 +3,7 @@ import { useStickyBottomScroll } from "@nextclaw/agent-chat-ui";
3
3
  import type { NcpMessage } from "@nextclaw/ncp";
4
4
  import { ArrowDown } from "lucide-react";
5
5
  import { ChatMessageListContainer } from "@/features/chat/features/message/components/chat-message-list.container";
6
+ import { ChatConversationTrack } from "@/features/chat/components/conversation/chat-conversation-track";
6
7
  import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
7
8
  import { t } from "@/shared/lib/i18n";
8
9
 
@@ -60,17 +61,17 @@ export function ChatConversationContent({
60
61
  ) : (
61
62
  <>
62
63
  {hideEmptyHint || !hasMessages ? null : (
63
- <div className="mx-auto w-full max-w-[min(1120px,100%)] px-4 py-4 sm:px-6 sm:py-5">
64
+ <ChatConversationTrack className="py-4 sm:py-5">
64
65
  <ChatMessageListContainer
65
66
  messages={messages}
66
67
  isSending={hasMessages && isSending && isAwaitingAssistantOutput}
67
68
  />
68
- </div>
69
+ </ChatConversationTrack>
69
70
  )}
70
71
  {bottomSlot ? (
71
- <div className="mx-auto w-full max-w-[min(1120px,100%)] px-4 pb-4 sm:px-6 sm:pb-5">
72
+ <ChatConversationTrack className="pb-4 sm:pb-5">
72
73
  {bottomSlot}
73
- </div>
74
+ </ChatConversationTrack>
74
75
  ) : null}
75
76
  </>
76
77
  )}
@@ -5,7 +5,6 @@ import { ChatSessionProjectBadge } from "@/features/chat/features/session/compon
5
5
  import { ChatSessionTitleSwitcher } from "@/features/chat/features/session/components/session-header/chat-session-title-switcher";
6
6
  import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
7
7
  import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
8
- import { useChatConversationWorkspaceState } from "@/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state";
9
8
  import { useNcpChatSelectedSession } from "@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state";
10
9
  import { sessionDisplayName } from "@/features/chat/features/session/utils/chat-session-display.utils";
11
10
  import {
@@ -59,8 +58,6 @@ export function ChatConversationHeaderSection({
59
58
  const sessionProjectName =
60
59
  selectedSession?.projectName ?? getSessionProjectName(sessionProjectRoot);
61
60
  const canDeleteSession = Boolean(selectedSession);
62
- const { childSessionTabs, sessionCronJobs } =
63
- useChatConversationWorkspaceState(snapshot, sessionKey);
64
61
  const shouldShowSessionHeader = Boolean(sessionKey || sessionTypeLabel);
65
62
  const sessionHeaderTitle =
66
63
  (selectedSession ? sessionDisplayName(selectedSession) : undefined) ||
@@ -72,20 +69,16 @@ export function ChatConversationHeaderSection({
72
69
  selectedSession?.agentId?.trim() ?? selectedAgentId?.trim() ?? "";
73
70
  const shouldShowHeaderAgentAvatar =
74
71
  normalizedAgentId.length > 0 && normalizedAgentId.toLowerCase() !== "main";
75
- const openChildSessions = () => {
72
+ const isWorkspaceOpen = Boolean(
73
+ sessionKey &&
74
+ snapshot.workspacePanelParentKey === sessionKey &&
75
+ snapshot.activeWorkspacePanelKind,
76
+ );
77
+ const toggleWorkspace = () => {
76
78
  if (!sessionKey) {
77
79
  return;
78
80
  }
79
- presenter.chatThreadManager.openChildSessionPanel({
80
- parentSessionKey: sessionKey,
81
- activeChildSessionKey: childSessionTabs[0]?.sessionKey ?? null,
82
- });
83
- };
84
- const openSessionCronJobs = () => {
85
- if (!sessionKey || sessionCronJobs.length === 0) {
86
- return;
87
- }
88
- presenter.chatThreadManager.openSessionCronPanel(sessionKey);
81
+ presenter.chatThreadManager.toggleWorkspacePanel(sessionKey);
89
82
  };
90
83
 
91
84
  return (
@@ -148,10 +141,8 @@ export function ChatConversationHeaderSection({
148
141
  isDeletePending={snapshot.isDeletePending}
149
142
  projectRoot={sessionProjectRoot}
150
143
  metadata={selectedSession?.metadata ?? null}
151
- childSessionCount={childSessionTabs.length}
152
- sessionCronJobCount={sessionCronJobs.length}
153
- onOpenChildSessions={openChildSessions}
154
- onOpenSessionCronJobs={openSessionCronJobs}
144
+ isWorkspaceOpen={isWorkspaceOpen}
145
+ onToggleWorkspace={toggleWorkspace}
155
146
  onDeleteSession={presenter.chatThreadManager.deleteSession}
156
147
  />
157
148
  ) : null
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from "react";
2
+ import { useChatMessageLayoutStore } from "@/features/chat/stores/chat-message-layout.store";
3
+ import { cn } from "@/shared/lib/utils";
4
+
5
+ type ChatConversationTrackProps = {
6
+ children: ReactNode;
7
+ className?: string;
8
+ width?: "messages" | "composer";
9
+ };
10
+
11
+ export function ChatConversationTrack({
12
+ children,
13
+ className,
14
+ width = "messages",
15
+ }: ChatConversationTrackProps) {
16
+ const layout = useChatMessageLayoutStore((state) => state.layout);
17
+
18
+ return (
19
+ <div
20
+ data-chat-conversation-track={layout}
21
+ data-chat-conversation-track-width={width}
22
+ className={cn(
23
+ "mx-auto w-full px-4 sm:px-6",
24
+ layout === "flat"
25
+ ? width === "composer"
26
+ ? "max-w-[min(54rem,100%)]"
27
+ : "max-w-[min(52rem,100%)]"
28
+ : "max-w-[min(1120px,100%)]",
29
+ className,
30
+ )}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ }
@@ -42,6 +42,7 @@ export function ChatConversationWorkspaceSection({
42
42
  sessionWorkingDir={
43
43
  selectedSession?.workingDir ?? selectedSession?.projectRoot ?? null
44
44
  }
45
+ workspacePanelWidth={snapshot.workspacePanelWidth}
45
46
  displayMode={layoutMode === "mobile" ? "overlay" : "docked"}
46
47
  />
47
48
  );
@@ -3,9 +3,9 @@ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-but
3
3
  import { Input } from "@/shared/components/ui/input";
4
4
  import {
5
5
  Popover,
6
- PopoverContent,
7
6
  PopoverTrigger,
8
7
  } from "@/shared/components/ui/popover";
8
+ import { ChatPopoverContent } from "@/features/chat/components/chat-popover-content";
9
9
  import { SessionContextIconNode } from "@/features/chat/features/session/components/session-context-icon";
10
10
  import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
11
11
  import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
@@ -175,7 +175,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
175
175
  )}
176
176
  />
177
177
  </PopoverTrigger>
178
- <PopoverContent
178
+ <ChatPopoverContent
179
179
  align="start"
180
180
  side="right"
181
181
  className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
@@ -188,7 +188,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
188
188
  onCreateMenuOpenChange(false);
189
189
  }}
190
190
  />
191
- </PopoverContent>
191
+ </ChatPopoverContent>
192
192
  </Popover>
193
193
  ) : null}
194
194
  </div>
@@ -238,7 +238,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
238
238
  )}
239
239
  />
240
240
  </PopoverTrigger>
241
- <PopoverContent
241
+ <ChatPopoverContent
242
242
  align="end"
243
243
  className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
244
244
  >
@@ -250,7 +250,7 @@ export function ChatSidebarDesktopToolbar(props: ChatSidebarToolbarProps) {
250
250
  onCreateMenuOpenChange(false);
251
251
  }}
252
252
  />
253
- </PopoverContent>
253
+ </ChatPopoverContent>
254
254
  </Popover>
255
255
  ) : null}
256
256
  </div>
@@ -315,7 +315,7 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
315
315
  className="h-9 w-9 shrink-0 rounded-full bg-muted text-foreground shadow-none hover:bg-accent hover:text-accent-foreground"
316
316
  />
317
317
  </PopoverTrigger>
318
- <PopoverContent
318
+ <ChatPopoverContent
319
319
  align="end"
320
320
  className="w-60 rounded-3xl border border-border bg-popover p-2 text-popover-foreground shadow-[0_24px_70px_-30px_rgba(15,23,42,0.45)]"
321
321
  >
@@ -329,7 +329,7 @@ export function ChatSidebarMobileToolbar(props: ChatSidebarToolbarProps) {
329
329
  onCreateMenuOpenChange(false);
330
330
  }}
331
331
  />
332
- </PopoverContent>
332
+ </ChatPopoverContent>
333
333
  </Popover>
334
334
  ) : (
335
335
  <IconActionButton
@@ -1,9 +1,9 @@
1
1
  import { NavLink } from "react-router-dom";
2
2
  import {
3
3
  Popover,
4
- PopoverContent,
5
4
  PopoverTrigger,
6
5
  } from "@/shared/components/ui/popover";
6
+ import { ChatPopoverContent } from "@/features/chat/components/chat-popover-content";
7
7
  import {
8
8
  Select,
9
9
  SelectContent,
@@ -52,12 +52,6 @@ type ChatSidebarUtilityMenuProps = {
52
52
  collapsed?: boolean;
53
53
  };
54
54
 
55
- function isChatComposerFocusTarget(target: EventTarget | null): boolean {
56
- return target instanceof HTMLElement && Boolean(
57
- target.closest('.nextclaw-chat-input-bar-shell [role="textbox"][contenteditable="true"]'),
58
- );
59
- }
60
-
61
55
  export function ChatSidebarUtilityMenu({
62
56
  isOpen,
63
57
  onOpenChange,
@@ -118,15 +112,10 @@ export function ChatSidebarUtilityMenu({
118
112
  </button>
119
113
  </PopoverTrigger>
120
114
  )}
121
- <PopoverContent
115
+ <ChatPopoverContent
122
116
  side={collapsed ? "right" : "top"}
123
117
  align={collapsed ? "end" : "start"}
124
118
  className="w-64 p-2"
125
- onFocusOutside={(event) => {
126
- if (isChatComposerFocusTarget(event.detail.originalEvent.target)) {
127
- event.preventDefault();
128
- }
129
- }}
130
119
  >
131
120
  <div className="space-y-1">
132
121
  <NavLink
@@ -177,7 +166,7 @@ export function ChatSidebarUtilityMenu({
177
166
  onSelect={onSelectLanguage}
178
167
  onCloseMenu={() => onOpenChange(false)}
179
168
  />
180
- </PopoverContent>
169
+ </ChatPopoverContent>
181
170
  </Popover>
182
171
  );
183
172
  }
@@ -6,7 +6,7 @@ import {
6
6
  type MutableRefObject,
7
7
  } from 'react';
8
8
  import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
9
- import { describe, expect, it, vi } from 'vitest';
9
+ import { afterEach, describe, expect, it, vi } from 'vitest';
10
10
  import {
11
11
  createChatComposerTextNode,
12
12
  type ChatComposerNode,
@@ -26,9 +26,14 @@ import type {
26
26
  SessionConversationInputSnapshot,
27
27
  } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
28
28
  import { useSessionConversationInputState } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
29
+ import { useChatMessageLayoutStore } from '@/features/chat/stores/chat-message-layout.store';
29
30
 
30
31
  const uploadNcpAssetsMock = vi.hoisted(() => vi.fn());
31
32
 
33
+ afterEach(() => {
34
+ useChatMessageLayoutStore.getState().setLayout('card');
35
+ });
36
+
32
37
  vi.mock('@/app/hooks/use-viewport-layout', () => ({
33
38
  useViewportLayout: () => ({ isDesktop: true, isMobile: false }),
34
39
  }));
@@ -190,6 +195,20 @@ function StreamingSessionConversationInputHarness({
190
195
  }
191
196
 
192
197
  describe('SessionConversationInput streaming stability', () => {
198
+ it('aligns the default input with the flat message reading track', () => {
199
+ useChatMessageLayoutStore.getState().setLayout('flat');
200
+ const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
201
+
202
+ render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
203
+
204
+ const track = document.querySelector(
205
+ '[data-chat-conversation-track="flat"][data-chat-conversation-track-width="composer"]',
206
+ );
207
+ expect(track?.className).toContain('max-w-[min(54rem,100%)]');
208
+ expect(track?.querySelector('.nextclaw-chat-input-bar-shell')).toBeTruthy();
209
+ expect(track?.firstElementChild?.className).toContain('px-0');
210
+ });
211
+
193
212
  it('keeps IME composition stable while simulated streamed output rerenders the owner', async () => {
194
213
  const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
195
214
  render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);