@nextclaw/ui 0.14.1 → 0.14.3

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 (224) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/{api-DPcvCLfa.js → api-BP_8IaxB.js} +7 -7
  3. package/dist/assets/channels-list-page-DEh5Jjqx.js +8 -0
  4. package/dist/assets/chat-page-BRB0N5WW.js +105 -0
  5. package/dist/assets/config-split-page-CRhlZ_pH.js +1 -0
  6. package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-DsivFJ4r.js} +2 -2
  7. package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-B0w9p2ep.js} +1 -1
  8. package/dist/assets/{dist-DPrgcEC0.js → dist-CikkhOlq.js} +1 -1
  9. package/dist/assets/{dist-DsE4OsSS.js → dist-PVA_xL1D.js} +1 -1
  10. package/dist/assets/doc-browser-BoNJ7-tE.js +1 -0
  11. package/dist/assets/doc-browser-CdJ_d4ik.js +1 -0
  12. package/dist/assets/doc-browser-context-MTw4EvKd.js +1 -0
  13. package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-y6vDtq4M.js} +1 -1
  14. package/dist/assets/{external-link-C_dmG-WJ.js → external-link-DVJIQf_G.js} +1 -1
  15. package/dist/assets/index-BiE-K8-q.css +1 -0
  16. package/dist/assets/index-D9BAE_L_.js +105 -0
  17. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +1 -0
  19. package/dist/assets/middleware-CkFT3R4v.js +1 -0
  20. package/dist/assets/model-config-page-DotaTUHb.js +1 -0
  21. package/dist/assets/plus-BbZqquJe.js +1 -0
  22. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +1 -0
  23. package/dist/assets/providers-config-page-DW4tQSLN.js +1 -0
  24. package/dist/assets/{react-sZz5HSCm.js → react-VNqT5Zp7.js} +1 -1
  25. package/dist/assets/remote-B8TiUfwW.js +1 -0
  26. package/dist/assets/rotate-cw-DjHDTkCv.js +1 -0
  27. package/dist/assets/runtime-config-page-S6ErRHR8.js +1 -0
  28. package/dist/assets/{save-MzjoiHri.js → save-BOJ477H-.js} +1 -1
  29. package/dist/assets/{search-B29dE4GN.js → search-BEti5beA.js} +1 -1
  30. package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-DGj5am03.js} +1 -1
  31. package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page--IzrIOid.js} +2 -2
  32. package/dist/assets/{select-BKmh05hd.js → select-hoj8dK4K.js} +2 -2
  33. package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-T8-h-die.js} +1 -1
  34. package/dist/assets/use-config-CjRV2hwL.js +1 -0
  35. package/dist/assets/x-D9PXs_xP.js +1 -0
  36. package/dist/index.html +70 -19
  37. package/index.html +55 -5
  38. package/package.json +9 -9
  39. package/src/app/components/app-presenter-provider.tsx +11 -2
  40. package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
  41. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
  42. package/src/app/components/layout/app-layout.tsx +18 -4
  43. package/src/app/components/layout/header.tsx +3 -3
  44. package/src/app/components/layout/page-layout.tsx +2 -2
  45. package/src/app/components/layout/runtime-status-entry.tsx +7 -7
  46. package/src/app/components/layout/sidebar-items.tsx +194 -71
  47. package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
  48. package/src/app/components/layout/sidebar.tsx +340 -83
  49. package/src/app/managers/viewport-layout.manager.ts +10 -0
  50. package/src/app/stores/viewport-layout.store.ts +39 -3
  51. package/src/app/styles/design-system.css +388 -26
  52. package/src/app/test/vitest-setup.ts +31 -0
  53. package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
  54. package/src/features/agents/components/agents-page.tsx +10 -6
  55. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +3 -3
  56. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
  57. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +4 -2
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
  59. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
  60. package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
  61. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +15 -16
  62. package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -6
  63. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +19 -19
  64. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +13 -13
  65. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
  66. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +18 -16
  68. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  69. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
  70. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +93 -20
  71. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +93 -36
  72. package/src/features/chat/components/layout/chat-sidebar.tsx +60 -93
  73. package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
  74. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +225 -0
  75. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +199 -0
  76. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +308 -0
  77. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +439 -0
  78. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +194 -0
  79. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
  80. package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +51 -20
  81. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
  82. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
  83. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
  84. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
  85. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
  86. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
  87. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
  88. package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
  89. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
  90. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
  91. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  92. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
  93. package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
  94. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
  95. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
  96. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
  97. package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
  98. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
  99. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +7 -7
  100. package/src/features/chat/features/message/components/chat-message-list.container.tsx +99 -18
  101. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
  102. package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
  103. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +6 -6
  106. package/src/features/chat/features/session/components/session-context-icon.tsx +2 -2
  107. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +2 -2
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +1 -1
  109. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  110. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  111. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  112. package/src/features/chat/features/session/components/session-run-badge.tsx +1 -1
  113. package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
  114. package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
  115. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
  116. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
  117. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
  118. package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
  119. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
  120. package/src/features/chat/features/session-type/hooks/use-chat-session-type-state.ts +10 -2
  121. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
  122. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -9
  123. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
  124. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +3 -3
  125. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +4 -4
  126. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +14 -19
  127. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +5 -5
  128. package/src/features/chat/features/welcome/components/chat-welcome.tsx +6 -6
  129. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +6 -8
  130. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +1 -16
  131. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
  132. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
  133. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  134. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
  135. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
  136. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
  137. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
  138. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
  139. package/src/features/chat/index.ts +0 -1
  140. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
  141. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
  142. package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
  143. package/src/features/chat/managers/chat-thread.manager.ts +108 -34
  144. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
  145. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
  146. package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
  147. package/src/features/chat/presenters/chat.presenter.ts +2 -19
  148. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
  149. package/src/features/chat/stores/chat-thread.store.ts +42 -26
  150. package/src/features/panel-apps/components/panel-app-list-item.tsx +8 -8
  151. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  152. package/src/features/panel-apps/components/panel-apps-list.tsx +20 -20
  153. package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
  154. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +70 -6
  155. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +10 -5
  156. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +8 -8
  157. package/src/features/service-apps/components/service-apps-panel.tsx +24 -24
  158. package/src/features/settings/components/config/provider-advanced-settings-section.tsx +5 -5
  159. package/src/features/settings/components/config/provider-auth-section.tsx +7 -7
  160. package/src/features/settings/components/config/provider-form.tsx +10 -10
  161. package/src/features/settings/components/config/provider-models-section.tsx +16 -16
  162. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  163. package/src/features/settings/pages/providers-config-page.tsx +17 -17
  164. package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
  165. package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
  166. package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
  167. package/src/shared/components/config-split-page.tsx +11 -11
  168. package/src/shared/components/doc-browser/doc-browser-home-page.tsx +2 -2
  169. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +4 -4
  170. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +4 -4
  171. package/src/shared/components/doc-browser/doc-browser.tsx +3 -3
  172. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +1 -1
  173. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  174. package/src/shared/components/ui/button.tsx +4 -4
  175. package/src/shared/components/ui/card.tsx +4 -4
  176. package/src/shared/components/ui/dialog.tsx +5 -5
  177. package/src/shared/components/ui/input.tsx +1 -1
  178. package/src/shared/components/ui/label.tsx +1 -1
  179. package/src/shared/components/ui/popover.tsx +1 -1
  180. package/src/shared/components/ui/select.tsx +3 -3
  181. package/src/shared/components/ui/switch.tsx +3 -3
  182. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
  183. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
  184. package/src/shared/components/ui/tabs.tsx +4 -4
  185. package/src/shared/components/ui/textarea.tsx +1 -1
  186. package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
  187. package/src/shared/lib/i18n/locales/en-US/core.json +9 -2
  188. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
  189. package/src/shared/lib/i18n/locales/zh-CN/core.json +9 -2
  190. package/src/shared/lib/theme/index.ts +94 -52
  191. package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
  192. package/src/shared/lib/ui-document-title/index.ts +50 -21
  193. package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
  194. package/dist/assets/chat-page-BBnNJoKG.js +0 -105
  195. package/dist/assets/config-split-page-BCJhqdK9.js +0 -1
  196. package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
  197. package/dist/assets/doc-browser-DgQedeAn.js +0 -1
  198. package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
  199. package/dist/assets/index-7o1WdUbU.js +0 -103
  200. package/dist/assets/index-DQWmEWzk.css +0 -1
  201. package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
  202. package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
  203. package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
  204. package/dist/assets/plus-Bnt56oqG.js +0 -1
  205. package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
  206. package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
  207. package/dist/assets/remote-B0LMs8u7.js +0 -1
  208. package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
  209. package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
  210. package/dist/assets/use-config-DZkBWIdL.js +0 -1
  211. package/dist/assets/x-CW2KO-Hz.js +0 -1
  212. package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
  213. package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
  214. package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
  215. package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
  216. package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
  217. package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
  218. package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
  219. package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
  220. package/src/features/chat/managers/chat-input.manager.ts +0 -430
  221. package/src/features/chat/managers/chat-run.manager.ts +0 -110
  222. package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
  223. package/src/features/chat/stores/chat-input.store.ts +0 -104
  224. /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
@@ -1,12 +1,11 @@
1
1
  import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
2
- import { useChatInputStore } from '@/features/chat/stores/chat-input.store';
3
2
  import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
4
3
  import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
5
4
  import type { SetStateAction } from 'react';
6
- import type { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
7
5
  import { normalizeSessionProjectRootValue } from '@/shared/lib/session-project';
8
6
  import { updateNcpSession } from '@/shared/lib/api';
9
7
  import { CHAT_DRAFT_SESSION_PATH } from '@/features/chat/features/session/utils/chat-session-route.utils';
8
+ import { DEFAULT_SESSION_TYPE, normalizeSessionType } from '@/features/chat/features/session-type/utils/chat-session-type.utils';
10
9
 
11
10
  type WorkspaceChildReadState = {
12
11
  sessionKey: string | null | undefined;
@@ -15,21 +14,13 @@ type WorkspaceChildReadState = {
15
14
  runStatus?: string | null;
16
15
  };
17
16
  export class ChatSessionListManager {
18
- constructor(
19
- private uiManager: ChatUiManager,
20
- private chatRunManager: ChatRunManager
21
- ) {}
17
+ constructor(private uiManager: ChatUiManager) {}
22
18
 
23
- private syncDraftThreadState = (hasSubmittedDraftMessage = false) => {
19
+ private syncDraftThreadState = () => {
24
20
  useChatThreadStore.getState().setSnapshot({
25
21
  sessionKey: null,
26
22
  sessionDisplayName: undefined,
27
23
  canDeleteSession: false,
28
- isHistoryLoading: false,
29
- messages: [],
30
- isSending: false,
31
- isAwaitingAssistantOutput: false,
32
- hasSubmittedDraftMessage,
33
24
  parentSessionKey: null,
34
25
  parentSessionLabel: null,
35
26
  workspacePanelParentKey: null,
@@ -107,7 +98,6 @@ export class ChatSessionListManager {
107
98
  }
108
99
  if (selectedSessionKey !== null) {
109
100
  this.setSelectedSessionKey(null);
110
- this.chatRunManager.clearRunState();
111
101
  }
112
102
  };
113
103
 
@@ -142,26 +132,21 @@ export class ChatSessionListManager {
142
132
  };
143
133
 
144
134
  createSession = (sessionType?: string, projectRoot?: string | null): void => {
145
- const { snapshot } = useChatInputStore.getState();
146
- const { defaultSessionType: configuredDefaultSessionType } = snapshot;
147
- const defaultSessionType = configuredDefaultSessionType || 'native';
148
- const nextSessionType =
149
- typeof sessionType === 'string' && sessionType.trim().length > 0
150
- ? sessionType.trim()
151
- : defaultSessionType;
135
+ const nextSessionType = normalizeSessionType(sessionType ?? DEFAULT_SESSION_TYPE);
152
136
  const normalizedProjectRoot = normalizeSessionProjectRootValue(projectRoot);
153
- this.chatRunManager.clearRunState();
154
137
  useChatSessionListStore.getState().setSnapshot({
155
138
  selectedSessionKey: null,
156
139
  });
157
140
  this.syncDraftThreadState();
158
- useChatInputStore.getState().setSnapshot({
159
- pendingSessionType: nextSessionType,
160
- selectedSessionType: nextSessionType,
161
- pendingProjectRoot: normalizedProjectRoot,
162
- pendingProjectRootSessionKey: null
141
+ this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH, {
142
+ replace: this.uiManager.isAtChatRoot(),
143
+ state: {
144
+ chatDraft: {
145
+ sessionType: nextSessionType,
146
+ projectRoot: normalizedProjectRoot,
147
+ },
148
+ },
163
149
  });
164
- this.uiManager.navigateTo(CHAT_DRAFT_SESSION_PATH);
165
150
  };
166
151
 
167
152
  startAgentDraftChat = (agentId: string, sessionType: string): void => {
@@ -170,22 +155,6 @@ export class ChatSessionListManager {
170
155
  this.setSelectedAgentId(normalizedAgentId);
171
156
  };
172
157
 
173
- ensureDraftSession = (sessionType?: string): string | null => {
174
- const { snapshot } = useChatSessionListStore.getState();
175
- if (snapshot.selectedSessionKey) {
176
- return snapshot.selectedSessionKey;
177
- }
178
- const normalizedSessionType =
179
- typeof sessionType === 'string' && sessionType.trim().length > 0
180
- ? sessionType.trim()
181
- : null;
182
- this.syncDraftThreadState(true);
183
- if (normalizedSessionType) {
184
- useChatInputStore.getState().setSnapshot({ pendingSessionType: normalizedSessionType });
185
- }
186
- return null;
187
- };
188
-
189
158
  selectSession = (sessionKey: string) => {
190
159
  this.uiManager.goToSession(sessionKey);
191
160
  };
@@ -10,37 +10,33 @@ import type {
10
10
  } from '@nextclaw/agent-chat-ui';
11
11
  import type { UiShowContentEventPayload } from '@nextclaw/shared';
12
12
  import type { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
13
- import type { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
14
13
  import type { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
15
14
  import { useChatSessionListStore } from '@/features/chat/stores/chat-session-list.store';
16
15
  import type {
16
+ ChatChildSessionTab,
17
17
  ChatThreadSnapshot,
18
18
  ChatWorkspaceNavigationEntry,
19
19
  ChatWorkspaceFileTab,
20
20
  } from '@/features/chat/stores/chat-thread.store';
21
21
  import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
22
22
  import { t } from '@/shared/lib/i18n';
23
+ import {
24
+ areWorkspaceNavigationEntriesEqual,
25
+ createSideChatDraft,
26
+ materializeSideChatDraftSnapshot,
27
+ upsertChildSessionTab,
28
+ } from '@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils';
23
29
  import {
24
30
  filterNavigationHistoryEntries,
25
31
  pushNavigationHistoryEntry,
26
32
  stepNavigationHistory,
27
33
  } from '@/shared/lib/navigation-history';
28
34
 
29
- function areWorkspaceNavigationEntriesEqual(
30
- current: ChatWorkspaceNavigationEntry,
31
- next: ChatWorkspaceNavigationEntry,
32
- ): boolean {
33
- if (current.kind !== next.kind) {
34
- return false;
35
- }
36
- if (current.kind === 'cron') {
37
- return true;
38
- }
39
- return next.kind !== 'cron' && current.key === next.key;
40
- }
41
-
42
35
  type WorkspaceChildReadState = Parameters<ChatSessionListManager['markVisibleWorkspaceChildRead']>[0];
43
- export type ChatVisibleWorkspaceSelection = { kind: 'child-session'; tab: WorkspaceChildReadState } | { kind: 'file' | 'cron' } | null;
36
+ export type ChatVisibleWorkspaceSelection =
37
+ | { kind: 'child-session'; tab: WorkspaceChildReadState }
38
+ | { kind: 'file' | 'side-chat-draft' | 'cron' }
39
+ | null;
44
40
 
45
41
  export class ChatThreadManager {
46
42
  private readonly handledUiShowContentEventIds = new Set<string>();
@@ -48,7 +44,6 @@ export class ChatThreadManager {
48
44
  constructor(
49
45
  private uiManager: ChatUiManager,
50
46
  private sessionListManager: ChatSessionListManager,
51
- private chatRunManager: ChatRunManager,
52
47
  ) {}
53
48
 
54
49
  private hasSnapshotChanges = (patch: Partial<ChatThreadSnapshot>): boolean => {
@@ -80,16 +75,13 @@ export class ChatThreadManager {
80
75
  sessionProjectRoot: null,
81
76
  sessionProjectName: null,
82
77
  canDeleteSession: false,
83
- isHistoryLoading: false,
84
- messages: [],
85
- isSending: false,
86
- isAwaitingAssistantOutput: false,
87
78
  parentSessionKey: null,
88
79
  parentSessionLabel: null,
89
80
  workspacePanelParentKey: null,
90
81
  activeWorkspacePanelKind: null,
91
82
  childSessionTabs: [],
92
83
  activeChildSessionKey: null,
84
+ activeSideChatDraft: null,
93
85
  workspaceFileTabs: [],
94
86
  activeWorkspaceFileKey: null,
95
87
  workspaceNavigationHistory: [],
@@ -98,6 +90,10 @@ export class ChatThreadManager {
98
90
  };
99
91
 
100
92
  private resolveWorkspaceParentSessionKey = (): string | null => {
93
+ const selectedSessionKey = useChatSessionListStore.getState().snapshot.selectedSessionKey?.trim();
94
+ if (selectedSessionKey) {
95
+ return selectedSessionKey;
96
+ }
101
97
  const threadSessionKey = useChatThreadStore.getState().snapshot.sessionKey?.trim();
102
98
  if (threadSessionKey) {
103
99
  return threadSessionKey;
@@ -169,6 +165,7 @@ export class ChatThreadManager {
169
165
  return {
170
166
  activeWorkspacePanelKind: 'cron',
171
167
  activeChildSessionKey: null,
168
+ activeSideChatDraft: null,
172
169
  activeWorkspaceFileKey: null,
173
170
  };
174
171
  }
@@ -179,15 +176,29 @@ export class ChatThreadManager {
179
176
  return {
180
177
  activeWorkspacePanelKind: 'file',
181
178
  activeChildSessionKey: null,
179
+ activeSideChatDraft: null,
182
180
  activeWorkspaceFileKey: entry.key,
183
181
  };
184
182
  }
183
+ if (entry.kind === 'side-chat-draft') {
184
+ const { activeSideChatDraft } = snapshot;
185
+ if (activeSideChatDraft?.draftKey !== entry.key) {
186
+ return null;
187
+ }
188
+ return {
189
+ activeWorkspacePanelKind: 'side-chat-draft',
190
+ activeChildSessionKey: null,
191
+ activeSideChatDraft,
192
+ activeWorkspaceFileKey: null,
193
+ };
194
+ }
185
195
  if (!entry.key) {
186
196
  return null;
187
197
  }
188
198
  return {
189
199
  activeWorkspacePanelKind: 'child-session',
190
200
  activeChildSessionKey: entry.key,
201
+ activeSideChatDraft: null,
191
202
  activeWorkspaceFileKey: null,
192
203
  };
193
204
  };
@@ -215,18 +226,36 @@ export class ChatThreadManager {
215
226
  openChildSessionPanel = (params: {
216
227
  parentSessionKey: string;
217
228
  activeChildSessionKey?: string | null;
229
+ childSessionTab?: Pick<ChatChildSessionTab, 'label' | 'agentId'> | null;
218
230
  }) => {
219
- const parentSessionKey = params.parentSessionKey.trim();
231
+ const {
232
+ activeChildSessionKey: rawActiveChildSessionKey,
233
+ childSessionTab,
234
+ parentSessionKey: rawParentSessionKey,
235
+ } = params;
236
+ const parentSessionKey = rawParentSessionKey.trim();
220
237
  if (!parentSessionKey) {
221
238
  return;
222
239
  }
223
- const activeChildSessionKey = params.activeChildSessionKey?.trim() || null;
240
+ const activeChildSessionKey = rawActiveChildSessionKey?.trim() || null;
224
241
  const patch: Partial<ChatThreadSnapshot> = {
225
242
  workspacePanelParentKey: parentSessionKey,
226
243
  activeWorkspacePanelKind: 'child-session',
227
244
  activeChildSessionKey,
245
+ activeSideChatDraft: null,
228
246
  activeWorkspaceFileKey: null,
229
247
  };
248
+ if (activeChildSessionKey && childSessionTab) {
249
+ patch.childSessionTabs = upsertChildSessionTab(
250
+ useChatThreadStore.getState().snapshot.childSessionTabs,
251
+ {
252
+ sessionKey: activeChildSessionKey,
253
+ parentSessionKey,
254
+ label: childSessionTab.label?.trim() || null,
255
+ agentId: childSessionTab.agentId?.trim() || null,
256
+ },
257
+ );
258
+ }
230
259
  if (activeChildSessionKey) {
231
260
  this.setWorkspaceSelection(patch, {
232
261
  kind: 'child-session',
@@ -238,6 +267,43 @@ export class ChatThreadManager {
238
267
  this.ensureWorkspaceParentRoute(parentSessionKey);
239
268
  };
240
269
 
270
+ openSideChatDraft = (parentSessionKey?: string | null) => {
271
+ const resolvedParentSessionKey =
272
+ parentSessionKey?.trim() || this.resolveWorkspaceParentSessionKey()?.trim();
273
+ if (!resolvedParentSessionKey) {
274
+ return;
275
+ }
276
+ const activeSideChatDraft = createSideChatDraft(resolvedParentSessionKey);
277
+ this.setWorkspaceSelection({
278
+ workspacePanelParentKey: resolvedParentSessionKey,
279
+ activeWorkspacePanelKind: 'side-chat-draft',
280
+ activeChildSessionKey: null,
281
+ activeSideChatDraft,
282
+ activeWorkspaceFileKey: null,
283
+ }, {
284
+ kind: 'side-chat-draft',
285
+ key: activeSideChatDraft.draftKey,
286
+ });
287
+ this.ensureWorkspaceParentRoute(resolvedParentSessionKey);
288
+ };
289
+
290
+ materializeSideChatDraft = (params: {
291
+ draftKey: string;
292
+ sessionKey: string;
293
+ label?: string | null;
294
+ agentId?: string | null;
295
+ }) => {
296
+ const materialized = materializeSideChatDraftSnapshot({
297
+ snapshot: useChatThreadStore.getState().snapshot,
298
+ ...params,
299
+ });
300
+ if (!materialized) {
301
+ return;
302
+ }
303
+ useChatThreadStore.getState().setSnapshot(materialized.patch);
304
+ this.ensureWorkspaceParentRoute(materialized.parentSessionKey);
305
+ };
306
+
241
307
  openFilePreview = (action: ChatFileOpenActionViewModel) => {
242
308
  const parentSessionKey = this.resolveWorkspaceParentSessionKey();
243
309
  const nextTab = this.buildWorkspaceFileTab(action, parentSessionKey);
@@ -250,6 +316,7 @@ export class ChatThreadManager {
250
316
  workspaceFileTabs: this.upsertWorkspaceFileTab(nextTab),
251
317
  activeWorkspaceFileKey: nextTab.key,
252
318
  activeChildSessionKey: null,
319
+ activeSideChatDraft: null,
253
320
  }, {
254
321
  kind: 'file',
255
322
  key: nextTab.key,
@@ -261,6 +328,10 @@ export class ChatThreadManager {
261
328
  if (action.kind !== 'open-session') {
262
329
  return;
263
330
  }
331
+ const sessionId = action.sessionId.trim();
332
+ if (!sessionId) {
333
+ return;
334
+ }
264
335
  if (action.sessionKind === 'child' && !this.uiManager.isCompactViewport()) {
265
336
  const parentSessionKey =
266
337
  action.parentSessionId?.trim() ||
@@ -269,13 +340,17 @@ export class ChatThreadManager {
269
340
  if (parentSessionKey) {
270
341
  this.openChildSessionPanel({
271
342
  parentSessionKey,
272
- activeChildSessionKey: action.sessionId,
343
+ activeChildSessionKey: sessionId,
344
+ childSessionTab: {
345
+ label: action.label,
346
+ agentId: action.agentId,
347
+ },
273
348
  });
274
349
  return;
275
350
  }
276
351
  }
277
352
  this.closeWorkspacePanel();
278
- this.uiManager.goToSession(action.sessionId);
353
+ this.uiManager.goToSession(sessionId);
279
354
  };
280
355
 
281
356
  handleToolAction = async (action: ChatToolActionViewModel): Promise<void> => {
@@ -331,14 +406,11 @@ export class ChatThreadManager {
331
406
  if (!normalizedSessionKey) {
332
407
  return;
333
408
  }
334
- const { childSessionTabs } = useChatThreadStore.getState().snapshot;
335
- if (!childSessionTabs.some((tab) => tab.sessionKey === normalizedSessionKey)) {
336
- return;
337
- }
338
409
  this.setWorkspaceSelection({
339
410
  activeChildSessionKey: normalizedSessionKey,
340
411
  activeWorkspaceFileKey: null,
341
412
  activeWorkspacePanelKind: 'child-session',
413
+ activeSideChatDraft: null,
342
414
  }, {
343
415
  kind: 'child-session',
344
416
  key: normalizedSessionKey,
@@ -358,6 +430,7 @@ export class ChatThreadManager {
358
430
  activeWorkspaceFileKey: normalizedFileKey,
359
431
  activeChildSessionKey: null,
360
432
  activeWorkspacePanelKind: 'file',
433
+ activeSideChatDraft: null,
361
434
  }, {
362
435
  kind: 'file',
363
436
  key: normalizedFileKey,
@@ -397,6 +470,7 @@ export class ChatThreadManager {
397
470
  : null;
398
471
  Object.assign(nextPatch, restorePatch ?? {
399
472
  activeWorkspacePanelKind: null,
473
+ activeSideChatDraft: null,
400
474
  activeWorkspaceFileKey: null,
401
475
  });
402
476
  }
@@ -408,6 +482,7 @@ export class ChatThreadManager {
408
482
  workspacePanelParentKey: null,
409
483
  activeWorkspacePanelKind: null,
410
484
  activeChildSessionKey: null,
485
+ activeSideChatDraft: null,
411
486
  activeWorkspaceFileKey: null,
412
487
  workspaceNavigationHistory: [],
413
488
  workspaceNavigationHistoryIndex: 0,
@@ -423,6 +498,7 @@ export class ChatThreadManager {
423
498
  workspacePanelParentKey: parentSessionKey,
424
499
  activeWorkspacePanelKind: 'cron',
425
500
  activeChildSessionKey: null,
501
+ activeSideChatDraft: null,
426
502
  activeWorkspaceFileKey: null,
427
503
  }, {
428
504
  kind: 'cron',
@@ -461,13 +537,10 @@ export class ChatThreadManager {
461
537
  });
462
538
  };
463
539
 
464
- closeChildSessionDetail = () => {
465
- this.closeWorkspacePanel();
466
- };
467
-
468
540
  goToParentSession = () => {
469
541
  const {
470
542
  parentSessionKey,
543
+ activeSideChatDraft,
471
544
  childSessionTabs,
472
545
  activeChildSessionKey,
473
546
  } = useChatThreadStore.getState().snapshot;
@@ -475,7 +548,9 @@ export class ChatThreadManager {
475
548
  childSessionTabs.find((tab) => tab.sessionKey === activeChildSessionKey)
476
549
  ?.parentSessionKey ?? null;
477
550
  const resolvedParentSessionKey =
478
- parentSessionKey ?? activeChildParentSessionKey;
551
+ parentSessionKey ??
552
+ activeSideChatDraft?.parentSessionKey ??
553
+ activeChildParentSessionKey;
479
554
  if (!resolvedParentSessionKey) {
480
555
  return;
481
556
  }
@@ -503,7 +578,6 @@ export class ChatThreadManager {
503
578
  await deleteNcpSessionApi(selectedSessionKey);
504
579
  deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
505
580
  appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
506
- this.chatRunManager.clearRunState();
507
581
  this.clearDeletedSessionState(selectedSessionKey);
508
582
  this.uiManager.goToChatRoot({ replace: true });
509
583
  } finally {
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { RUNTIME_DEFAULT_MODEL_VALUE } from '@nextclaw/shared';
2
+ import { createChatComposerTokenNode } from '@nextclaw/agent-chat-ui';
3
+ import {
4
+ CHAT_SESSION_MATERIALIZATION_METADATA_KEY,
5
+ RUNTIME_DEFAULT_MODEL_VALUE,
6
+ } from '@nextclaw/shared';
3
7
  import type { SessionEntryView, ThinkingLevel } from '@/shared/lib/api';
4
8
  import type { ChatModelOption } from '@/features/chat/types/chat-input.types';
5
9
  import {
@@ -185,6 +189,49 @@ describe('buildChatRunMetadata', () => {
185
189
  requested_skill_refs: ['project:/tmp/project-alpha/.agents/skills/review'],
186
190
  });
187
191
  });
192
+
193
+ it('serializes panel app references as text without run metadata', () => {
194
+ expect(
195
+ buildChatRunMetadata({
196
+ composerNodes: [
197
+ createChatComposerTokenNode({
198
+ tokenKind: 'panel_app',
199
+ tokenKey: 'task-board',
200
+ label: 'Task Board',
201
+ }),
202
+ ],
203
+ }),
204
+ ).not.toHaveProperty('referenced_panel_apps');
205
+ expect(
206
+ buildChatRunMetadata({
207
+ composerNodes: [
208
+ createChatComposerTokenNode({
209
+ tokenKind: 'panel_app',
210
+ tokenKey: 'task-board',
211
+ label: 'Task Board',
212
+ }),
213
+ ],
214
+ }),
215
+ ).not.toHaveProperty('ui_inline_tokens');
216
+ });
217
+
218
+ it('serializes child session materialization metadata for side chat drafts', () => {
219
+ expect(
220
+ buildChatRunMetadata({
221
+ sessionMaterialization: {
222
+ kind: 'child',
223
+ parentSessionId: 'parent-session-1',
224
+ inheritContext: true,
225
+ },
226
+ }),
227
+ ).toMatchObject({
228
+ [CHAT_SESSION_MATERIALIZATION_METADATA_KEY]: {
229
+ kind: 'child',
230
+ parentSessionId: 'parent-session-1',
231
+ inheritContext: true,
232
+ },
233
+ });
234
+ });
188
235
  });
189
236
 
190
237
  function createSession(overrides: Partial<SessionEntryView> & Pick<SessionEntryView, 'key'>): SessionEntryView {
@@ -0,0 +1,72 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { MemoryRouter } from 'react-router-dom';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ import { AppPresenterProvider } from '@/app/components/app-presenter-provider';
6
+ import { NcpChatPage } from '@/features/chat/pages/ncp-chat-page';
7
+
8
+ const mocks = vi.hoisted(() => ({
9
+ confirm: vi.fn(),
10
+ consumePending: vi.fn(() => null),
11
+ markConsumed: vi.fn(),
12
+ subscribe: vi.fn(() => vi.fn()),
13
+ useChatSessionSync: vi.fn(),
14
+ }));
15
+
16
+ vi.mock('@/app/presenters/app.presenter', () => ({
17
+ getAppPresenter: () => ({
18
+ docBrowserManager: {},
19
+ chatDraftIntentManager: {
20
+ consumePending: mocks.consumePending,
21
+ markConsumed: mocks.markConsumed,
22
+ subscribe: mocks.subscribe,
23
+ },
24
+ }),
25
+ }));
26
+
27
+ vi.mock('@/shared/hooks/use-confirm-dialog', () => ({
28
+ useConfirmDialog: () => ({
29
+ confirm: mocks.confirm,
30
+ ConfirmDialog: () => <div data-testid="confirm-dialog" />,
31
+ }),
32
+ }));
33
+
34
+ vi.mock('@/features/chat/components/layout/chat-page-shell', () => ({
35
+ ChatPageLayout: () => <div data-testid="chat-page-layout" />,
36
+ useChatSessionSync: (params: {
37
+ syncRouteSessionSelection: (value: {
38
+ isChatView: boolean;
39
+ routeSessionKey: string | null;
40
+ }) => void;
41
+ }) => {
42
+ mocks.useChatSessionSync(params);
43
+ const { syncRouteSessionSelection } = params;
44
+ syncRouteSessionSelection({
45
+ isChatView: true,
46
+ routeSessionKey: null,
47
+ });
48
+ },
49
+ }));
50
+
51
+ vi.mock('@/features/chat/features/ncp/hooks/use-ncp-chat-query-store-sync', () => ({
52
+ useChatQueryStoreSync: () => undefined,
53
+ }));
54
+
55
+ vi.mock('@/features/chat/features/ncp/hooks/use-ui-show-content-event', () => ({
56
+ useUiShowContentEvent: () => undefined,
57
+ }));
58
+
59
+ describe('NcpChatPage render boundary', () => {
60
+ it('creates its chat presenter from the global app presenter provider', () => {
61
+ render(
62
+ <MemoryRouter initialEntries={['/chat']}>
63
+ <AppPresenterProvider>
64
+ <NcpChatPage view="chat" />
65
+ </AppPresenterProvider>
66
+ </MemoryRouter>,
67
+ );
68
+
69
+ expect(screen.getByTestId('chat-page-layout')).toBeTruthy();
70
+ expect(mocks.useChatSessionSync).toHaveBeenCalledOnce();
71
+ });
72
+ });