@nextclaw/ui 0.12.8 → 0.12.10

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 (227) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
  3. package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
  4. package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
  5. package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
  6. package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
  7. package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
  8. package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
  9. package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
  10. package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
  11. package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
  12. package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
  13. package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
  14. package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
  15. package/dist/assets/{book-open-DocgeQtR.js → book-open-DzdUViDm.js} +1 -1
  16. package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
  17. package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
  18. package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
  19. package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
  20. package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
  21. package/dist/assets/config-split-page-BUout_Ak.js +1 -0
  22. package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
  23. package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
  24. package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
  25. package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
  26. package/dist/assets/download-BD0ETkB-.js +1 -0
  27. package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
  28. package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
  29. package/dist/assets/i18n-CpTZLchQ.js +1 -0
  30. package/dist/assets/index-mW8W2FUu.css +1 -0
  31. package/dist/assets/index-zDZfXoI4.js +6 -0
  32. package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
  33. package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
  34. package/dist/assets/{logos-DEFUIR12.js → logos-B7gRObP8.js} +1 -1
  35. package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
  36. package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
  37. package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
  38. package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-0UcO9H9Z.js} +1 -1
  39. package/dist/assets/play-CKDjSQFL.js +1 -0
  40. package/dist/assets/plus-CG0QrVY_.js +1 -0
  41. package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
  42. package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
  43. package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
  44. package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
  45. package/dist/assets/{save-DHGmi2e9.js → save-EqJPOF0G.js} +1 -1
  46. package/dist/assets/search-BCAlB8nz.js +1 -0
  47. package/dist/assets/security-config-Slh0Mayz.js +1 -0
  48. package/dist/assets/select-CVz0t7MF.js +41 -0
  49. package/dist/assets/setting-row-CbVHAuQt.js +1 -0
  50. package/dist/assets/skeleton-D5rdKvzy.js +1 -0
  51. package/dist/assets/{status-dot-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
  52. package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
  53. package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
  54. package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
  55. package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
  56. package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
  57. package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
  58. package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
  59. package/dist/assets/x-Czwxm82I.js +1 -0
  60. package/dist/index.html +95 -21
  61. package/dist/manifest.webmanifest +30 -0
  62. package/dist/offline.html +102 -0
  63. package/dist/pwa-192.png +0 -0
  64. package/dist/pwa-512.png +0 -0
  65. package/dist/runtime-icons/claude.ico +0 -0
  66. package/dist/runtime-icons/codex-openai.svg +6 -0
  67. package/dist/runtime-icons/hermes-agent.png +0 -0
  68. package/dist/sw.js +80 -0
  69. package/index.html +73 -1
  70. package/package.json +5 -5
  71. package/public/manifest.webmanifest +30 -0
  72. package/public/offline.html +102 -0
  73. package/public/pwa-192.png +0 -0
  74. package/public/pwa-512.png +0 -0
  75. package/public/runtime-icons/claude.ico +0 -0
  76. package/public/runtime-icons/codex-openai.svg +6 -0
  77. package/public/runtime-icons/hermes-agent.png +0 -0
  78. package/public/sw.js +80 -0
  79. package/src/account/components/account-panel.tsx +217 -97
  80. package/src/account/managers/account.manager.ts +3 -2
  81. package/src/api/chat-session-type.types.ts +7 -0
  82. package/src/api/runtime-control.types.ts +8 -0
  83. package/src/api/server-path.ts +27 -4
  84. package/src/api/types.ts +25 -10
  85. package/src/app.tsx +227 -54
  86. package/src/components/agents/agent-dialogs.tsx +499 -0
  87. package/src/components/agents/agents-page.test.tsx +238 -0
  88. package/src/components/agents/agents-page.tsx +435 -0
  89. package/src/components/chat/ChatSidebar.test.tsx +43 -1
  90. package/src/components/chat/ChatSidebar.tsx +35 -35
  91. package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
  92. package/src/components/chat/adapters/file-operation/card.ts +9 -0
  93. package/src/components/chat/adapters/file-operation/diff.ts +14 -0
  94. package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
  95. package/src/components/chat/chat-conversation-panel.tsx +482 -0
  96. package/src/components/chat/chat-page-shell.tsx +19 -13
  97. package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
  98. package/src/components/chat/chat-session-type-option-item.tsx +68 -0
  99. package/src/components/chat/chat-session-workspace-file-preview.test.tsx +178 -0
  100. package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
  101. package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
  102. package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
  103. package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
  104. package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
  105. package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
  106. package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
  107. package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
  108. package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
  109. package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
  110. package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
  111. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
  112. package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
  113. package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
  114. package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
  115. package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
  116. package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
  117. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
  118. package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
  119. package/src/components/chat/stores/chat-input.store.ts +2 -1
  120. package/src/components/chat/stores/chat-thread.store.ts +27 -1
  121. package/src/components/chat/useChatSessionTypeState.ts +10 -1
  122. package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
  123. package/src/components/common/BrandHeader.tsx +3 -1
  124. package/src/components/common/session-context-icon.tsx +15 -2
  125. package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
  126. package/src/components/config/ChannelForm.test.tsx +89 -3
  127. package/src/components/config/ChannelForm.tsx +157 -188
  128. package/src/components/config/ChannelsList.test.tsx +163 -119
  129. package/src/components/config/ChannelsList.tsx +90 -101
  130. package/src/components/config/ProviderForm.tsx +108 -146
  131. package/src/components/config/ProvidersList.tsx +100 -123
  132. package/src/components/config/RuntimeConfig.tsx +141 -2
  133. package/src/components/config/SearchConfig.tsx +423 -393
  134. package/src/components/config/channel-form-fields-section.tsx +70 -37
  135. package/src/components/config/config-split-page.tsx +109 -0
  136. package/src/components/config/provider-enabled-field.tsx +17 -10
  137. package/src/components/config/runtime-control-card.test.tsx +56 -0
  138. package/src/components/config/runtime-control-card.tsx +25 -0
  139. package/src/components/config/runtime-presence-card.tsx +93 -79
  140. package/src/components/layout/AppLayout.tsx +25 -37
  141. package/src/components/layout/app-layout.test.tsx +46 -14
  142. package/src/components/layout/runtime-status-entry.test.tsx +157 -0
  143. package/src/components/layout/runtime-status-entry.tsx +143 -0
  144. package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
  145. package/src/components/marketplace/marketplace-list-card.tsx +288 -0
  146. package/src/components/marketplace/marketplace-page-data.ts +129 -0
  147. package/src/components/marketplace/marketplace-page.test.tsx +339 -0
  148. package/src/components/marketplace/marketplace-page.tsx +596 -0
  149. package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
  150. package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
  151. package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
  152. package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
  153. package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
  154. package/src/components/providers/ThemeProvider.tsx +5 -0
  155. package/src/components/remote/remote-access-page.test.tsx +105 -0
  156. package/src/components/remote/remote-access-page.tsx +248 -0
  157. package/src/components/ui/notice-card.tsx +129 -0
  158. package/src/components/ui/setting-row.tsx +51 -0
  159. package/src/components/ui/tag-chip.tsx +39 -0
  160. package/src/components/ui/textarea.tsx +19 -0
  161. package/src/hooks/server-path/use-server-path-read.ts +20 -0
  162. package/src/hooks/useConfig.ts +2 -1
  163. package/src/index.css +24 -0
  164. package/src/lib/app-resource-uri.test.ts +20 -0
  165. package/src/lib/app-resource-uri.ts +29 -0
  166. package/src/lib/chat-message.ts +14 -3
  167. package/src/lib/i18n.chat.ts +12 -1
  168. package/src/lib/i18n.pwa.ts +62 -0
  169. package/src/lib/i18n.remote.ts +1 -1
  170. package/src/lib/i18n.runtime-control.ts +31 -0
  171. package/src/lib/i18n.ts +7 -10
  172. package/src/lib/session-context.utils.test.ts +71 -0
  173. package/src/lib/session-context.utils.ts +28 -3
  174. package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
  175. package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
  176. package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
  177. package/src/pwa/components/pwa-install-entry.tsx +205 -0
  178. package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
  179. package/src/pwa/managers/pwa-install.manager.ts +232 -0
  180. package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
  181. package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
  182. package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
  183. package/src/pwa/pwa-install-banner.storage.ts +55 -0
  184. package/src/pwa/pwa.types.ts +22 -0
  185. package/src/pwa/register-pwa.ts +14 -0
  186. package/src/pwa/stores/pwa.store.ts +17 -0
  187. package/src/vite-env.d.ts +9 -0
  188. package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
  189. package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
  190. package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
  191. package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
  192. package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
  193. package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
  194. package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
  195. package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
  196. package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
  197. package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
  198. package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
  199. package/dist/assets/chat-page-Bph8M5zo.js +0 -58
  200. package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
  201. package/dist/assets/config-layout-DmlGaay2.js +0 -1
  202. package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
  203. package/dist/assets/i18n-CwHZ-9vt.js +0 -1
  204. package/dist/assets/index-DafCdM4F.css +0 -1
  205. package/dist/assets/index-DdksE6U3.js +0 -6
  206. package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
  207. package/dist/assets/play-DBQbBxTA.js +0 -1
  208. package/dist/assets/plus-DUOVbsyQ.js +0 -1
  209. package/dist/assets/popover-C_mWOFzI.js +0 -1
  210. package/dist/assets/search-MChQRYR1.js +0 -1
  211. package/dist/assets/security-config-CbXfPZzr.js +0 -1
  212. package/dist/assets/select-Caud8QvU.js +0 -41
  213. package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
  214. package/dist/assets/x-DuMhMATD.js +0 -1
  215. package/src/components/agents/AgentDialogs.tsx +0 -400
  216. package/src/components/agents/AgentsPage.test.tsx +0 -217
  217. package/src/components/agents/AgentsPage.tsx +0 -352
  218. package/src/components/chat/ChatConversationPanel.tsx +0 -256
  219. package/src/components/chat/chat-child-session-panel.tsx +0 -270
  220. package/src/components/config/config-layout.ts +0 -10
  221. package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
  222. package/src/components/marketplace/MarketplacePage.tsx +0 -827
  223. package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
  224. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
  225. package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
  226. package/src/components/remote/RemoteAccessPage.tsx +0 -144
  227. /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
- import { ChatChildSessionPanel } from "@/components/chat/chat-child-session-panel";
5
- import { ChatConversationPanel } from "@/components/chat/ChatConversationPanel";
4
+ import { ChatConversationPanel } from "@/components/chat/chat-conversation-panel";
5
+ import { ChatSessionWorkspacePanel } from "@/components/chat/chat-session-workspace-panel";
6
6
  import type { ResolvedChildSessionTab } from "@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view";
7
7
  import { useChatInputStore } from "@/components/chat/stores/chat-input.store";
8
8
  import { useChatSessionListStore } from "@/components/chat/stores/chat-session-list.store";
@@ -51,6 +51,14 @@ vi.mock("@/components/chat/containers/chat-message-list.container", () => ({
51
51
  ChatMessageListContainer: () => <div data-testid="child-chat-message-list" />,
52
52
  }));
53
53
 
54
+ vi.mock("@/components/chat/chat-session-workspace-file-preview", () => ({
55
+ ChatSessionWorkspaceFilePreview: ({
56
+ file,
57
+ }: {
58
+ file: { path: string };
59
+ }) => <div data-testid="workspace-file-preview">{file.path}</div>,
60
+ }));
61
+
54
62
  vi.mock("@/components/chat/ChatWelcome", () => ({
55
63
  ChatWelcome: ({
56
64
  onCreateSession,
@@ -75,9 +83,13 @@ vi.mock("@/components/chat/presenter/chat-presenter-context", () => ({
75
83
  chatThreadManager: {
76
84
  deleteSession: mocks.deleteSession,
77
85
  goToProviders: mocks.goToProviders,
86
+ openChildSessionPanel: vi.fn(),
87
+ openFilePreview: vi.fn(),
78
88
  openSessionFromToolAction: vi.fn(),
79
89
  selectChildSessionDetail: vi.fn(),
80
- closeChildSessionDetail: vi.fn(),
90
+ selectWorkspaceFile: vi.fn(),
91
+ closeWorkspaceFile: vi.fn(),
92
+ closeWorkspacePanel: vi.fn(),
81
93
  goToParentSession: vi.fn(),
82
94
  },
83
95
  chatSessionListManager: {
@@ -182,12 +194,15 @@ describe("ChatConversationPanel", () => {
182
194
  isAwaitingAssistantOutput: false,
183
195
  parentSessionKey: null,
184
196
  parentSessionLabel: null,
197
+ workspacePanelParentKey: null,
185
198
  availableAgents: [
186
199
  { id: "main", displayName: "Main", runtime: "native" },
187
200
  { id: "engineer", displayName: "Engineer", runtime: "codex" },
188
201
  ],
189
202
  childSessionTabs: [],
190
203
  activeChildSessionKey: null,
204
+ workspaceFileTabs: [],
205
+ activeWorkspaceFileKey: null,
191
206
  },
192
207
  });
193
208
  useChatSessionListStore.setState({
@@ -254,6 +269,67 @@ describe("ChatConversationPanel", () => {
254
269
  expect(screen.queryByText("Engineer")).toBeNull();
255
270
  });
256
271
 
272
+ it("renders a fuller loading skeleton before provider state settles", () => {
273
+ useChatThreadStore.setState({
274
+ snapshot: {
275
+ ...useChatThreadStore.getState().snapshot,
276
+ isProviderStateResolved: false,
277
+ },
278
+ });
279
+
280
+ render(<ChatConversationPanel />);
281
+
282
+ expect(screen.getByTestId("chat-conversation-skeleton")).toBeTruthy();
283
+ expect(
284
+ screen.getAllByTestId("chat-conversation-skeleton-bubble"),
285
+ ).toHaveLength(4);
286
+ expect(screen.queryByTestId("chat-input-bar")).toBeNull();
287
+ });
288
+
289
+ it("keeps the message area clean while a session history is hydrating", () => {
290
+ useChatThreadStore.setState({
291
+ snapshot: {
292
+ ...useChatThreadStore.getState().snapshot,
293
+ sessionKey: "session-1",
294
+ canDeleteSession: true,
295
+ isHistoryLoading: true,
296
+ messages: [],
297
+ },
298
+ });
299
+
300
+ render(<ChatConversationPanel />);
301
+
302
+ expect(
303
+ screen.queryByRole("status", { name: "Loading session history..." }),
304
+ ).toBeNull();
305
+ expect(screen.queryByText("No messages yet. Send one to start.")).toBeNull();
306
+ });
307
+
308
+ it("does not auto-open the child-session panel until the panel is explicitly opened", () => {
309
+ useChatThreadStore.setState({
310
+ snapshot: {
311
+ ...useChatThreadStore.getState().snapshot,
312
+ sessionKey: "parent-session-1",
313
+ sessionDisplayName: "Parent Session",
314
+ canDeleteSession: true,
315
+ childSessionTabs: [
316
+ {
317
+ sessionKey: "child-session-1",
318
+ parentSessionKey: "parent-session-1",
319
+ label: "北京天气",
320
+ agentId: "weather",
321
+ },
322
+ ],
323
+ activeChildSessionKey: "child-session-1",
324
+ workspacePanelParentKey: null,
325
+ },
326
+ });
327
+
328
+ render(<ChatConversationPanel />);
329
+
330
+ expect(screen.queryByLabelText("Close child session panel")).toBeNull();
331
+ });
332
+
257
333
  it("creates a draft session with the selected draft agent runtime", async () => {
258
334
  const user = userEvent.setup();
259
335
 
@@ -288,8 +364,8 @@ describe("ChatConversationPanel", () => {
288
364
  });
289
365
  });
290
366
 
291
- describe("ChatChildSessionPanel", () => {
292
- it("keeps the header compact for a single child session", () => {
367
+ describe("ChatSessionWorkspacePanel", () => {
368
+ it("renders child session tabs and active child metadata in the workspace sidebar", () => {
293
369
  mocks.resolvedChildTabs = [
294
370
  {
295
371
  sessionKey: "child-session-1",
@@ -305,9 +381,10 @@ describe("ChatChildSessionPanel", () => {
305
381
  projectRoot: "/Users/demo/project-alpha",
306
382
  },
307
383
  ];
384
+
308
385
  render(
309
- <ChatChildSessionPanel
310
- tabs={[
386
+ <ChatSessionWorkspacePanel
387
+ childSessionTabs={[
311
388
  {
312
389
  sessionKey: "child-session-1",
313
390
  parentSessionKey: "parent-session-1",
@@ -315,20 +392,26 @@ describe("ChatChildSessionPanel", () => {
315
392
  agentId: "weather",
316
393
  },
317
394
  ]}
318
- activeSessionKey="child-session-1"
395
+ activeChildSessionKey="child-session-1"
396
+ workspaceFileTabs={[]}
397
+ activeWorkspaceFileKey={null}
398
+ sessionProjectRoot="/Users/demo/project-alpha"
319
399
  onSelectSession={vi.fn()}
400
+ onSelectFile={vi.fn()}
401
+ onCloseFile={vi.fn()}
320
402
  onClose={vi.fn()}
321
403
  onBackToParent={vi.fn()}
404
+ onFileOpen={vi.fn()}
322
405
  />,
323
406
  );
324
407
 
325
- expect(screen.getByText("北京天气")).toBeTruthy();
408
+ expect(screen.queryByText("Child sessions")).toBeNull();
409
+ expect(screen.getAllByText("北京天气")).toHaveLength(2);
326
410
  expect(screen.getByText("Codex")).toBeTruthy();
327
411
  expect(screen.getByText("openai/gpt-5.3-codex")).toBeTruthy();
328
412
  expect(screen.getByText("project-alpha")).toBeTruthy();
329
413
  expect(screen.getByText("/Users/demo/project-alpha")).toBeTruthy();
330
- expect(screen.queryByText("Child Sessions")).toBeNull();
331
- expect(screen.queryByText("child-session-1")).toBeNull();
414
+ expect(screen.getByText("No child session messages yet.")).toBeTruthy();
332
415
  expect(mocks.stickyBottomScroll).toHaveBeenCalledWith(
333
416
  expect.objectContaining({
334
417
  resetKey: "child-session-1",
@@ -337,129 +420,7 @@ describe("ChatChildSessionPanel", () => {
337
420
  );
338
421
  });
339
422
 
340
- it("uses tabs as the only title layer when multiple child sessions are open", () => {
341
- mocks.resolvedChildTabs = [
342
- {
343
- sessionKey: "child-session-1",
344
- parentSessionKey: "parent-session-1",
345
- title: "北京天气",
346
- agentId: "weather",
347
- updatedAt: "2026-04-10T09:00:00.000Z",
348
- lastMessageAt: "2026-04-10T09:00:00.000Z",
349
- readAt: "2026-04-10T09:00:00.000Z",
350
- sessionTypeLabel: "Codex",
351
- preferredModel: "openai/gpt-5.3-codex",
352
- projectName: "project-alpha",
353
- projectRoot: "/Users/demo/project-alpha",
354
- },
355
- {
356
- sessionKey: "child-session-2",
357
- parentSessionKey: "parent-session-1",
358
- title: "上海天气",
359
- agentId: "weather",
360
- updatedAt: "2026-04-10T09:05:00.000Z",
361
- lastMessageAt: "2026-04-10T09:05:00.000Z",
362
- readAt: "2026-04-10T09:05:00.000Z",
363
- sessionTypeLabel: "Claude Code",
364
- preferredModel: "anthropic/claude-sonnet-4",
365
- projectName: "project-beta",
366
- projectRoot: "/Users/demo/project-beta",
367
- },
368
- ];
369
-
370
- render(
371
- <ChatChildSessionPanel
372
- tabs={[
373
- {
374
- sessionKey: "child-session-1",
375
- parentSessionKey: "parent-session-1",
376
- label: "北京天气",
377
- agentId: "weather",
378
- },
379
- {
380
- sessionKey: "child-session-2",
381
- parentSessionKey: "parent-session-1",
382
- label: "上海天气",
383
- agentId: "weather",
384
- },
385
- ]}
386
- activeSessionKey="child-session-1"
387
- onSelectSession={vi.fn()}
388
- onClose={vi.fn()}
389
- onBackToParent={vi.fn()}
390
- />,
391
- );
392
-
393
- expect(screen.getAllByText("北京天气")).toHaveLength(1);
394
- expect(screen.getByText("上海天气")).toBeTruthy();
395
- expect(screen.getByText("Codex")).toBeTruthy();
396
- expect(screen.getByText("openai/gpt-5.3-codex")).toBeTruthy();
397
- expect(screen.getByText("project-alpha")).toBeTruthy();
398
- expect(screen.getByText("/Users/demo/project-alpha")).toBeTruthy();
399
- const tabButtons = screen
400
- .getAllByRole("button")
401
- .filter((element) => element.getAttribute("aria-pressed") !== null);
402
- expect(tabButtons).toHaveLength(2);
403
- expect(tabButtons[0]?.getAttribute("aria-pressed")).toBe("true");
404
- expect(tabButtons[1]?.getAttribute("aria-pressed")).toBe("false");
405
- });
406
-
407
- it("shows an unread dot for inactive child tabs until the user opens them", () => {
408
- mocks.resolvedChildTabs = [
409
- {
410
- sessionKey: "child-session-1",
411
- parentSessionKey: "parent-session-1",
412
- title: "北京天气",
413
- agentId: "weather",
414
- updatedAt: "2026-04-10T09:00:00.000Z",
415
- lastMessageAt: "2026-04-10T09:00:00.000Z",
416
- readAt: "2026-04-10T09:00:00.000Z",
417
- sessionTypeLabel: "Codex",
418
- preferredModel: "openai/gpt-5.3-codex",
419
- projectName: "project-alpha",
420
- projectRoot: "/Users/demo/project-alpha",
421
- },
422
- {
423
- sessionKey: "child-session-2",
424
- parentSessionKey: "parent-session-1",
425
- title: "上海天气",
426
- agentId: "weather",
427
- updatedAt: "2026-04-10T09:05:00.000Z",
428
- lastMessageAt: "2026-04-10T09:05:00.000Z",
429
- readAt: "2026-04-10T09:05:00.000Z",
430
- runStatus: "running",
431
- sessionTypeLabel: "Claude Code",
432
- preferredModel: "anthropic/claude-sonnet-4",
433
- projectName: "project-beta",
434
- projectRoot: "/Users/demo/project-beta",
435
- },
436
- ];
437
-
438
- const { rerender } = render(
439
- <ChatChildSessionPanel
440
- tabs={[
441
- {
442
- sessionKey: "child-session-1",
443
- parentSessionKey: "parent-session-1",
444
- label: "北京天气",
445
- agentId: "weather",
446
- },
447
- {
448
- sessionKey: "child-session-2",
449
- parentSessionKey: "parent-session-1",
450
- label: "上海天气",
451
- agentId: "weather",
452
- },
453
- ]}
454
- activeSessionKey="child-session-1"
455
- onSelectSession={vi.fn()}
456
- onClose={vi.fn()}
457
- onBackToParent={vi.fn()}
458
- />,
459
- );
460
-
461
- expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
462
-
423
+ it("shows unread state for inactive child session tabs", () => {
463
424
  mocks.resolvedChildTabs = [
464
425
  {
465
426
  sessionKey: "child-session-1",
@@ -489,9 +450,9 @@ describe("ChatChildSessionPanel", () => {
489
450
  },
490
451
  ];
491
452
 
492
- rerender(
493
- <ChatChildSessionPanel
494
- tabs={[
453
+ render(
454
+ <ChatSessionWorkspacePanel
455
+ childSessionTabs={[
495
456
  {
496
457
  sessionKey: "child-session-1",
497
458
  parentSessionKey: "parent-session-1",
@@ -505,94 +466,54 @@ describe("ChatChildSessionPanel", () => {
505
466
  agentId: "weather",
506
467
  },
507
468
  ]}
508
- activeSessionKey="child-session-1"
469
+ activeChildSessionKey="child-session-1"
470
+ workspaceFileTabs={[]}
471
+ activeWorkspaceFileKey={null}
472
+ sessionProjectRoot="/Users/demo/project-alpha"
509
473
  onSelectSession={vi.fn()}
474
+ onSelectFile={vi.fn()}
475
+ onCloseFile={vi.fn()}
510
476
  onClose={vi.fn()}
511
477
  onBackToParent={vi.fn()}
478
+ onFileOpen={vi.fn()}
512
479
  />,
513
480
  );
514
481
 
515
482
  expect(screen.getByLabelText("Session has unread updates")).toBeTruthy();
516
-
517
- rerender(
518
- <ChatChildSessionPanel
519
- tabs={[
520
- {
521
- sessionKey: "child-session-1",
522
- parentSessionKey: "parent-session-1",
523
- label: "北京天气",
524
- agentId: "weather",
525
- },
526
- {
527
- sessionKey: "child-session-2",
528
- parentSessionKey: "parent-session-1",
529
- label: "上海天气",
530
- agentId: "weather",
531
- },
532
- ]}
533
- activeSessionKey="child-session-2"
534
- onSelectSession={vi.fn()}
535
- onClose={vi.fn()}
536
- onBackToParent={vi.fn()}
537
- />,
538
- );
539
-
540
- expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
541
483
  });
542
484
 
543
- it("does not show an unread dot for child tabs without a persisted ui read baseline", () => {
544
- mocks.resolvedChildTabs = [
545
- {
546
- sessionKey: "child-session-1",
547
- parentSessionKey: "parent-session-1",
548
- title: "北京天气",
549
- agentId: "weather",
550
- updatedAt: "2026-04-10T09:00:00.000Z",
551
- lastMessageAt: "2026-04-10T09:00:00.000Z",
552
- readAt: null,
553
- sessionTypeLabel: "Codex",
554
- preferredModel: "openai/gpt-5.3-codex",
555
- projectName: "project-alpha",
556
- projectRoot: "/Users/demo/project-alpha",
557
- },
558
- {
559
- sessionKey: "child-session-2",
560
- parentSessionKey: "parent-session-1",
561
- title: "上海天气",
562
- agentId: "weather",
563
- updatedAt: "2026-04-10T09:05:00.000Z",
564
- lastMessageAt: "2026-04-10T09:06:00.000Z",
565
- readAt: null,
566
- sessionTypeLabel: "Claude Code",
567
- preferredModel: "anthropic/claude-sonnet-4",
568
- projectName: "project-beta",
569
- projectRoot: "/Users/demo/project-beta",
570
- },
571
- ];
572
-
485
+ it("shows opened files as top tabs and renders the file preview pane", () => {
573
486
  render(
574
- <ChatChildSessionPanel
575
- tabs={[
576
- {
577
- sessionKey: "child-session-1",
578
- parentSessionKey: "parent-session-1",
579
- label: "北京天气",
580
- agentId: "weather",
581
- },
487
+ <ChatSessionWorkspacePanel
488
+ childSessionTabs={[]}
489
+ activeChildSessionKey={null}
490
+ workspaceFileTabs={[
582
491
  {
583
- sessionKey: "child-session-2",
492
+ key: "parent-session-1::preview::README.md",
584
493
  parentSessionKey: "parent-session-1",
585
- label: "上海天气",
586
- agentId: "weather",
494
+ path: "README.md",
495
+ label: "README.md",
496
+ viewMode: "preview",
587
497
  },
588
498
  ]}
589
- activeSessionKey="child-session-1"
499
+ activeWorkspaceFileKey="parent-session-1::preview::README.md"
500
+ sessionProjectRoot="/Users/demo/project-alpha"
590
501
  onSelectSession={vi.fn()}
502
+ onSelectFile={vi.fn()}
503
+ onCloseFile={vi.fn()}
591
504
  onClose={vi.fn()}
592
505
  onBackToParent={vi.fn()}
506
+ onFileOpen={vi.fn()}
593
507
  />,
594
508
  );
595
509
 
596
- expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
510
+ expect(screen.queryByText("Open files")).toBeNull();
511
+ expect(screen.getAllByText("README.md").length).toBeGreaterThan(0);
512
+ expect(screen.getByTestId("workspace-file-preview").textContent).toBe(
513
+ "README.md",
514
+ );
515
+ expect(screen.getByTestId("workspace-tabs-bar").className).toContain(
516
+ "workspace-horizontal-scrollbar",
517
+ );
597
518
  });
598
519
  });