@nextclaw/ui 0.15.24 → 0.15.25

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 (94) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/assets/{api-BxPNDp30.js → api-FK7tikI1.js} +3 -3
  3. package/dist/assets/appearance-settings-page-BYbvl2wj.js +1 -0
  4. package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CocdcIko.js} +2 -2
  5. package/dist/assets/chat-page-C5E-LLVd.js +1 -0
  6. package/dist/assets/{config-split-page-BVWVDbvG.js → config-split-page-CA_a0AJ5.js} +1 -1
  7. package/dist/assets/{confirm-dialog-mA2iVzxh.js → confirm-dialog-CxZfenjK.js} +1 -1
  8. package/dist/assets/{desktop-update-config-DyQb1LXr.js → desktop-update-config-BEKC8Ly9.js} +1 -1
  9. package/dist/assets/{dist-B4Pf5X3Q.js → dist-BH7W5yCi.js} +1 -1
  10. package/dist/assets/{dist-DHEWkGWX.js → dist-CaYdVVeO.js} +1 -1
  11. package/dist/assets/doc-browser-Cl2ldE3f.js +1 -0
  12. package/dist/assets/doc-browser-DXCSd63E.js +1 -0
  13. package/dist/assets/{doc-browser-context-CuGI6PQc.js → doc-browser-context-FDPs3gJf.js} +1 -1
  14. package/dist/assets/{form-actions-BOfIyG3O.js → form-actions-0c0aw_bl.js} +1 -1
  15. package/dist/assets/i18n-provider-DkITJNz8.js +1 -0
  16. package/dist/assets/index-CQ2LcI2p.css +1 -0
  17. package/dist/assets/index-CrdfvBVg.js +107 -0
  18. package/dist/assets/loader-circle-DHtUxCIA.js +1 -0
  19. package/dist/assets/{mcp-marketplace-page-ofDqbOIF.js → mcp-marketplace-page-Bc_jI5iH.js} +2 -2
  20. package/dist/assets/mcp-marketplace-page-CzCieD9r.js +1 -0
  21. package/dist/assets/{model-config-page-D73Tu3jW.js → model-config-page-C4jw-pcj.js} +1 -1
  22. package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-CVy0_kVl.js} +1 -1
  23. package/dist/assets/plus-CG1RwZQq.js +1 -0
  24. package/dist/assets/{provider-scoped-model-input-BaJJJYMX.js → provider-scoped-model-input-CmH4BFvc.js} +1 -1
  25. package/dist/assets/{providers-config-page-BL6IRVBm.js → providers-config-page-CJQjwzaw.js} +1 -1
  26. package/dist/assets/{react-BC_orpAj.js → react-DjFBVA0n.js} +1 -1
  27. package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-j2bl79Ea.js} +1 -1
  28. package/dist/assets/remote-BIlpD3yL.js +1 -0
  29. package/dist/assets/rotate-cw-bKG8zH8M.js +1 -0
  30. package/dist/assets/runtime-config-page-B_x2Glz5.js +1 -0
  31. package/dist/assets/{save-Cnr71JON.js → save-BKNrtK81.js} +1 -1
  32. package/dist/assets/{search-CCX11X2m.js → search-CkOm65W_.js} +1 -1
  33. package/dist/assets/{search-config-page-BB6d5gT7.js → search-config-page-BaIijtmO.js} +1 -1
  34. package/dist/assets/{secrets-config-page-DYsDeT9K.js → secrets-config-page-CJjtdFAl.js} +2 -2
  35. package/dist/assets/security-config-DB8apj09.js +1 -0
  36. package/dist/assets/{select-DvwBu99K.js → select-Ea7ffKCE.js} +1 -1
  37. package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-CtyN3m_V.js} +1 -1
  38. package/dist/assets/skeleton-Bt6zxcAB.js +1 -0
  39. package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-C9l4xsKp.js} +1 -1
  40. package/dist/assets/x-CowtCu9Z.js +1 -0
  41. package/dist/index.html +20 -20
  42. package/package.json +8 -8
  43. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +30 -1
  44. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +1 -1
  45. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
  46. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
  47. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
  48. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +573 -90
  49. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +389 -288
  50. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
  51. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
  52. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +149 -94
  53. package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
  54. package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
  55. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +411 -0
  56. package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
  57. package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
  58. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +88 -0
  59. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +335 -0
  60. package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
  61. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
  62. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
  63. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
  64. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
  65. package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
  66. package/src/features/chat/managers/chat-thread.manager.ts +132 -128
  67. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +20 -40
  68. package/src/features/chat/stores/chat-thread.store.ts +47 -74
  69. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
  70. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
  71. package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
  72. package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
  73. package/src/shared/lib/api/managers/client.manager.ts +31 -17
  74. package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
  75. package/src/shared/lib/api/types.ts +145 -116
  76. package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
  77. package/src/shared/lib/i18n/locales/en-US/chat.json +42 -0
  78. package/src/shared/lib/i18n/locales/zh-CN/chat.json +42 -0
  79. package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
  80. package/dist/assets/chat-page-BuFDmG5_.js +0 -1
  81. package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
  82. package/dist/assets/doc-browser-eMUagyBV.js +0 -1
  83. package/dist/assets/index-BjBdWHV1.js +0 -107
  84. package/dist/assets/index-DmpmjAGQ.css +0 -1
  85. package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
  86. package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
  87. package/dist/assets/plus-DVU5RLCS.js +0 -1
  88. package/dist/assets/remote-T24O58lH.js +0 -1
  89. package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
  90. package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
  91. package/dist/assets/security-config-CVPgqL7G.js +0 -1
  92. package/dist/assets/skeleton-C5tWata3.js +0 -1
  93. package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
  94. package/dist/assets/x-BEWSqeux.js +0 -1
@@ -1,8 +1,5 @@
1
1
  import { appQueryClient } from '@/app-query-client';
2
- import {
3
- deleteNcpSession as deleteNcpSessionApi,
4
- deleteNcpSessionSummaryInQueryClient,
5
- } from '@/shared/lib/api';
2
+ import { deleteNcpSession as deleteNcpSessionApi, deleteNcpSessionSummaryInQueryClient } from '@/shared/lib/api';
6
3
  import type {
7
4
  ChatFileOpenActionViewModel,
8
5
  ChatUiShowContentRequest,
@@ -20,7 +17,10 @@ import type {
20
17
  ChatWorkspaceFileTab,
21
18
  } from '@/features/chat/stores/chat-thread.store';
22
19
  import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
23
- import { normalizeChatWorkspacePanelWidth } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
20
+ import {
21
+ normalizeChatWorkspaceExplorerWidth,
22
+ normalizeChatWorkspacePanelWidth,
23
+ } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
24
24
  import { t } from '@/shared/lib/i18n';
25
25
  import {
26
26
  areWorkspaceNavigationEntriesEqual,
@@ -31,10 +31,7 @@ import {
31
31
  materializeSideChatDraftSnapshot,
32
32
  upsertChildSessionTab,
33
33
  } from '@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils';
34
- import {
35
- pushNavigationHistoryEntry,
36
- stepNavigationHistory,
37
- } from '@/shared/lib/navigation-history';
34
+ import { pushNavigationHistoryEntry, stepNavigationHistory } from '@/shared/lib/navigation-history';
38
35
  import {
39
36
  resolveAlternateWorkspaceFileViewer,
40
37
  type ChatWorkspaceFileViewer,
@@ -43,6 +40,10 @@ import {
43
40
  createWorkspaceFileTab,
44
41
  upsertWorkspaceFileTab,
45
42
  } from '@/features/chat/features/workspace/utils/chat-workspace-file-tab.utils';
43
+ import {
44
+ createRemovedWorkspacePathSnapshot,
45
+ createRenamedWorkspacePathPatch,
46
+ } from '@/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils';
46
47
 
47
48
  type WorkspaceChildReadState = Parameters<ChatSessionListManager['markVisibleWorkspaceChildRead']>[0];
48
49
  export type ChatVisibleWorkspaceSelection =
@@ -61,7 +62,9 @@ export class ChatThreadManager {
61
62
 
62
63
  private hasSnapshotChanges = (patch: Partial<ChatThreadSnapshot>): boolean => {
63
64
  const current = useChatThreadStore.getState().snapshot;
64
- for (const [key, value] of Object.entries(patch) as Array<[keyof ChatThreadSnapshot, ChatThreadSnapshot[keyof ChatThreadSnapshot]]>) {
65
+ for (const [key, value] of Object.entries(patch) as Array<
66
+ [keyof ChatThreadSnapshot, ChatThreadSnapshot[keyof ChatThreadSnapshot]]
67
+ >) {
65
68
  if (!Object.is(current[key], value)) {
66
69
  return true;
67
70
  }
@@ -112,27 +115,23 @@ export class ChatThreadManager {
112
115
  return useChatSessionListStore.getState().snapshot.selectedSessionKey ?? null;
113
116
  };
114
117
 
115
- private activateWorkspaceFileTab = (
116
- nextTab: ChatWorkspaceFileTab,
117
- adjacentToKey?: string,
118
- ) => {
118
+ private activateWorkspaceFileTab = (nextTab: ChatWorkspaceFileTab, adjacentToKey?: string) => {
119
119
  const { parentSessionKey } = nextTab;
120
120
  const { workspaceFileTabs } = useChatThreadStore.getState().snapshot;
121
- this.setWorkspaceSelection({
122
- workspacePanelParentKey: parentSessionKey,
123
- activeWorkspacePanelKind: 'file',
124
- workspaceFileTabs: upsertWorkspaceFileTab(
125
- workspaceFileTabs,
126
- nextTab,
127
- adjacentToKey,
128
- ),
129
- activeWorkspaceFileKey: nextTab.key,
130
- activeChildSessionKey: null,
131
- activeSideChatDraft: null,
132
- }, {
133
- kind: 'file',
134
- key: nextTab.key,
135
- });
121
+ this.setWorkspaceSelection(
122
+ {
123
+ workspacePanelParentKey: parentSessionKey,
124
+ activeWorkspacePanelKind: 'file',
125
+ workspaceFileTabs: upsertWorkspaceFileTab(workspaceFileTabs, nextTab, adjacentToKey),
126
+ activeWorkspaceFileKey: nextTab.key,
127
+ activeChildSessionKey: null,
128
+ activeSideChatDraft: null,
129
+ },
130
+ {
131
+ kind: 'file',
132
+ key: nextTab.key,
133
+ },
134
+ );
136
135
  this.ensureWorkspaceParentRoute(parentSessionKey);
137
136
  this.onWorkspacePanelOpened?.();
138
137
  };
@@ -149,10 +148,7 @@ export class ChatThreadManager {
149
148
  }
150
149
  };
151
150
 
152
- private setWorkspaceSelection = (
153
- patch: Partial<ChatThreadSnapshot>,
154
- entry: ChatWorkspaceNavigationEntry,
155
- ) => {
151
+ private setWorkspaceSelection = (patch: Partial<ChatThreadSnapshot>, entry: ChatWorkspaceNavigationEntry) => {
156
152
  const { snapshot } = useChatThreadStore.getState();
157
153
  const history = pushNavigationHistoryEntry(
158
154
  {
@@ -163,8 +159,7 @@ export class ChatThreadManager {
163
159
  areWorkspaceNavigationEntriesEqual,
164
160
  );
165
161
  const closedWorkspaceTabEntries =
166
- patch.workspacePanelParentKey &&
167
- patch.workspacePanelParentKey !== snapshot.workspacePanelParentKey
162
+ patch.workspacePanelParentKey && patch.workspacePanelParentKey !== snapshot.workspacePanelParentKey
168
163
  ? []
169
164
  : snapshot.closedWorkspaceTabEntries.filter(
170
165
  (candidate) => !areWorkspaceNavigationEntriesEqual(candidate, entry),
@@ -185,13 +180,16 @@ export class ChatThreadManager {
185
180
  if (!parentSessionKey && kind !== 'project-files') {
186
181
  return;
187
182
  }
188
- this.setWorkspaceSelection({
189
- workspacePanelParentKey: parentSessionKey,
190
- activeWorkspacePanelKind: kind,
191
- activeChildSessionKey: null,
192
- activeSideChatDraft: null,
193
- activeWorkspaceFileKey: null,
194
- }, { kind });
183
+ this.setWorkspaceSelection(
184
+ {
185
+ workspacePanelParentKey: parentSessionKey,
186
+ activeWorkspacePanelKind: kind,
187
+ activeChildSessionKey: null,
188
+ activeSideChatDraft: null,
189
+ activeWorkspaceFileKey: null,
190
+ },
191
+ { kind },
192
+ );
195
193
  this.ensureWorkspaceParentRoute(parentSessionKey);
196
194
  this.onWorkspacePanelOpened?.();
197
195
  };
@@ -203,10 +201,7 @@ export class ChatThreadManager {
203
201
  toggleWorkspacePanel = (sessionKey: string | null) => {
204
202
  const normalizedSessionKey = sessionKey?.trim() || null;
205
203
  const { snapshot } = useChatThreadStore.getState();
206
- if (
207
- snapshot.workspacePanelParentKey === normalizedSessionKey &&
208
- snapshot.activeWorkspacePanelKind
209
- ) {
204
+ if (snapshot.workspacePanelParentKey === normalizedSessionKey && snapshot.activeWorkspacePanelKind) {
210
205
  this.closeWorkspacePanel();
211
206
  return;
212
207
  }
@@ -223,6 +218,22 @@ export class ChatThreadManager {
223
218
  });
224
219
  };
225
220
 
221
+ setWorkspaceExplorerWidth = (width: number) => {
222
+ this.syncSnapshot({
223
+ workspaceExplorerWidth: normalizeChatWorkspaceExplorerWidth(width),
224
+ });
225
+ };
226
+
227
+ renameWorkspacePath = (params: { previousPath: string; path: string; label: string }) => {
228
+ const patch = createRenamedWorkspacePathPatch(useChatThreadStore.getState().snapshot, params);
229
+ if (patch) this.syncSnapshot(patch);
230
+ };
231
+
232
+ removeWorkspacePath = (path: string) => {
233
+ const snapshot = createRemovedWorkspacePathSnapshot(useChatThreadStore.getState().snapshot, path);
234
+ if (snapshot) useChatThreadStore.getState().setSnapshot(snapshot);
235
+ };
236
+
226
237
  openChildSessions = (sessionKey: string) => {
227
238
  this.openWorkspacePage(sessionKey, 'child-sessions');
228
239
  };
@@ -236,10 +247,7 @@ export class ChatThreadManager {
236
247
  if (!sessionKey || !this.uiManager.isAtChatRoot()) {
237
248
  return;
238
249
  }
239
- const patch = materializeDraftWorkspaceSnapshot(
240
- useChatThreadStore.getState().snapshot,
241
- sessionKey,
242
- );
250
+ const patch = materializeDraftWorkspaceSnapshot(useChatThreadStore.getState().snapshot, sessionKey);
243
251
  if (!patch) {
244
252
  return;
245
253
  }
@@ -271,15 +279,12 @@ export class ChatThreadManager {
271
279
  activeWorkspaceFileKey: null,
272
280
  };
273
281
  if (activeChildSessionKey && childSessionTab) {
274
- patch.childSessionTabs = upsertChildSessionTab(
275
- useChatThreadStore.getState().snapshot.childSessionTabs,
276
- {
277
- sessionKey: activeChildSessionKey,
278
- parentSessionKey,
279
- label: childSessionTab.label?.trim() || null,
280
- agentId: childSessionTab.agentId?.trim() || null,
281
- },
282
- );
282
+ patch.childSessionTabs = upsertChildSessionTab(useChatThreadStore.getState().snapshot.childSessionTabs, {
283
+ sessionKey: activeChildSessionKey,
284
+ parentSessionKey,
285
+ label: childSessionTab.label?.trim() || null,
286
+ agentId: childSessionTab.agentId?.trim() || null,
287
+ });
283
288
  }
284
289
  if (activeChildSessionKey) {
285
290
  this.setWorkspaceSelection(patch, {
@@ -294,22 +299,24 @@ export class ChatThreadManager {
294
299
  };
295
300
 
296
301
  openSideChatDraft = (parentSessionKey?: string | null) => {
297
- const resolvedParentSessionKey =
298
- parentSessionKey?.trim() || this.resolveWorkspaceParentSessionKey()?.trim();
302
+ const resolvedParentSessionKey = parentSessionKey?.trim() || this.resolveWorkspaceParentSessionKey()?.trim();
299
303
  if (!resolvedParentSessionKey) {
300
304
  return;
301
305
  }
302
306
  const activeSideChatDraft = createSideChatDraft(resolvedParentSessionKey);
303
- this.setWorkspaceSelection({
304
- workspacePanelParentKey: resolvedParentSessionKey,
305
- activeWorkspacePanelKind: 'side-chat-draft',
306
- activeChildSessionKey: null,
307
- activeSideChatDraft,
308
- activeWorkspaceFileKey: null,
309
- }, {
310
- kind: 'side-chat-draft',
311
- key: activeSideChatDraft.draftKey,
312
- });
307
+ this.setWorkspaceSelection(
308
+ {
309
+ workspacePanelParentKey: resolvedParentSessionKey,
310
+ activeWorkspacePanelKind: 'side-chat-draft',
311
+ activeChildSessionKey: null,
312
+ activeSideChatDraft,
313
+ activeWorkspaceFileKey: null,
314
+ },
315
+ {
316
+ kind: 'side-chat-draft',
317
+ key: activeSideChatDraft.draftKey,
318
+ },
319
+ );
313
320
  this.ensureWorkspaceParentRoute(resolvedParentSessionKey);
314
321
  this.onWorkspacePanelOpened?.();
315
322
  };
@@ -340,34 +347,32 @@ export class ChatThreadManager {
340
347
  this.activateWorkspaceFileTab(nextTab);
341
348
  };
342
349
 
343
- openWorkspaceFileViewer = (
344
- fileKey: string,
345
- viewer?: ChatWorkspaceFileViewer,
346
- ) => {
347
- const sourceTab = useChatThreadStore.getState().snapshot.workspaceFileTabs
348
- .find((tab) => tab.key === fileKey.trim());
350
+ openWorkspaceFileViewer = (fileKey: string, viewer?: ChatWorkspaceFileViewer) => {
351
+ const sourceTab = useChatThreadStore
352
+ .getState()
353
+ .snapshot.workspaceFileTabs.find((tab) => tab.key === fileKey.trim());
349
354
  if (!sourceTab || sourceTab.viewMode !== 'preview') {
350
355
  return;
351
356
  }
352
- const nextViewer = viewer ?? resolveAlternateWorkspaceFileViewer(
353
- sourceTab.path,
354
- sourceTab.previewViewer,
355
- );
357
+ const nextViewer = viewer ?? resolveAlternateWorkspaceFileViewer(sourceTab.path, sourceTab.previewViewer);
356
358
  if (!nextViewer) {
357
359
  return;
358
360
  }
359
- const nextTab = createWorkspaceFileTab({
360
- path: sourceTab.path,
361
- label: sourceTab.label ?? undefined,
362
- viewMode: 'preview',
363
- previewViewer: nextViewer,
364
- line: sourceTab.line ?? undefined,
365
- column: sourceTab.column ?? undefined,
366
- params: sourceTab.params ?? undefined,
367
- rawText: sourceTab.rawText ?? undefined,
368
- contentUrl: sourceTab.contentUrl ?? undefined,
369
- mimeType: sourceTab.mimeType ?? undefined,
370
- }, sourceTab.parentSessionKey);
361
+ const nextTab = createWorkspaceFileTab(
362
+ {
363
+ path: sourceTab.path,
364
+ label: sourceTab.label ?? undefined,
365
+ viewMode: 'preview',
366
+ previewViewer: nextViewer,
367
+ line: sourceTab.line ?? undefined,
368
+ column: sourceTab.column ?? undefined,
369
+ params: sourceTab.params ?? undefined,
370
+ rawText: sourceTab.rawText ?? undefined,
371
+ contentUrl: sourceTab.contentUrl ?? undefined,
372
+ mimeType: sourceTab.mimeType ?? undefined,
373
+ },
374
+ sourceTab.parentSessionKey,
375
+ );
371
376
  if (nextTab) {
372
377
  this.activateWorkspaceFileTab(nextTab, sourceTab.key);
373
378
  }
@@ -383,9 +388,7 @@ export class ChatThreadManager {
383
388
  }
384
389
  if (action.sessionKind === 'child' && !this.uiManager.isCompactViewport()) {
385
390
  const parentSessionKey =
386
- action.parentSessionId?.trim() ||
387
- useChatSessionListStore.getState().snapshot.selectedSessionKey ||
388
- null;
391
+ action.parentSessionId?.trim() || useChatSessionListStore.getState().snapshot.selectedSessionKey || null;
389
392
  if (parentSessionKey) {
390
393
  this.openChildSessionPanel({
391
394
  parentSessionKey,
@@ -457,15 +460,18 @@ export class ChatThreadManager {
457
460
  if (!normalizedSessionKey) {
458
461
  return;
459
462
  }
460
- this.setWorkspaceSelection({
461
- activeChildSessionKey: normalizedSessionKey,
462
- activeWorkspaceFileKey: null,
463
- activeWorkspacePanelKind: 'child-session',
464
- activeSideChatDraft: null,
465
- }, {
466
- kind: 'child-session',
467
- key: normalizedSessionKey,
468
- });
463
+ this.setWorkspaceSelection(
464
+ {
465
+ activeChildSessionKey: normalizedSessionKey,
466
+ activeWorkspaceFileKey: null,
467
+ activeWorkspacePanelKind: 'child-session',
468
+ activeSideChatDraft: null,
469
+ },
470
+ {
471
+ kind: 'child-session',
472
+ key: normalizedSessionKey,
473
+ },
474
+ );
469
475
  };
470
476
 
471
477
  selectWorkspaceFile = (fileKey: string) => {
@@ -477,15 +483,18 @@ export class ChatThreadManager {
477
483
  if (!workspaceFileTabs.some((tab) => tab.key === normalizedFileKey)) {
478
484
  return;
479
485
  }
480
- this.setWorkspaceSelection({
481
- activeWorkspaceFileKey: normalizedFileKey,
482
- activeChildSessionKey: null,
483
- activeWorkspacePanelKind: 'file',
484
- activeSideChatDraft: null,
485
- }, {
486
- kind: 'file',
487
- key: normalizedFileKey,
488
- });
486
+ this.setWorkspaceSelection(
487
+ {
488
+ activeWorkspaceFileKey: normalizedFileKey,
489
+ activeChildSessionKey: null,
490
+ activeWorkspacePanelKind: 'file',
491
+ activeSideChatDraft: null,
492
+ },
493
+ {
494
+ kind: 'file',
495
+ key: normalizedFileKey,
496
+ },
497
+ );
489
498
  };
490
499
 
491
500
  closeWorkspaceTab = (entry: ChatWorkspaceNavigationEntry) => {
@@ -545,19 +554,12 @@ export class ChatThreadManager {
545
554
  };
546
555
 
547
556
  goToParentSession = () => {
548
- const {
549
- parentSessionKey,
550
- activeSideChatDraft,
551
- childSessionTabs,
552
- activeChildSessionKey,
553
- } = useChatThreadStore.getState().snapshot;
557
+ const { parentSessionKey, activeSideChatDraft, childSessionTabs, activeChildSessionKey } =
558
+ useChatThreadStore.getState().snapshot;
554
559
  const activeChildParentSessionKey =
555
- childSessionTabs.find((tab) => tab.sessionKey === activeChildSessionKey)
556
- ?.parentSessionKey ?? null;
560
+ childSessionTabs.find((tab) => tab.sessionKey === activeChildSessionKey)?.parentSessionKey ?? null;
557
561
  const resolvedParentSessionKey =
558
- parentSessionKey ??
559
- activeSideChatDraft?.parentSessionKey ??
560
- activeChildParentSessionKey;
562
+ parentSessionKey ?? activeSideChatDraft?.parentSessionKey ?? activeChildParentSessionKey;
561
563
  if (!resolvedParentSessionKey) {
562
564
  return;
563
565
  }
@@ -567,7 +569,7 @@ export class ChatThreadManager {
567
569
 
568
570
  deleteSession = async () => {
569
571
  const {
570
- snapshot: { selectedSessionKey }
572
+ snapshot: { selectedSessionKey },
571
573
  } = useChatSessionListStore.getState();
572
574
  if (!selectedSessionKey) {
573
575
  return;
@@ -575,7 +577,7 @@ export class ChatThreadManager {
575
577
  const confirmed = await this.uiManager.confirm({
576
578
  title: t('chatDeleteSessionConfirm'),
577
579
  variant: 'destructive',
578
- confirmLabel: t('delete')
580
+ confirmLabel: t('delete'),
579
581
  });
580
582
  if (!confirmed) {
581
583
  return;
@@ -584,7 +586,9 @@ export class ChatThreadManager {
584
586
  try {
585
587
  await deleteNcpSessionApi(selectedSessionKey);
586
588
  deleteNcpSessionSummaryInQueryClient(appQueryClient, selectedSessionKey);
587
- appQueryClient.removeQueries({ queryKey: ['ncp-session-messages', selectedSessionKey] });
589
+ appQueryClient.removeQueries({
590
+ queryKey: ['ncp-session-messages', selectedSessionKey],
591
+ });
588
592
  this.clearDeletedSessionState();
589
593
  this.uiManager.goToChatRoot({ replace: true });
590
594
  } finally {
@@ -1,8 +1,9 @@
1
1
  import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
2
3
  import {
3
- useChatThreadStore,
4
- } from '@/features/chat/stores/chat-thread.store';
5
- import { CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
4
+ CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH,
5
+ CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
6
+ } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
6
7
 
7
8
  const chatThreadWorkspaceStorageKey = 'nextclaw.chat.workspace-panel.state';
8
9
 
@@ -10,7 +11,7 @@ function createLocalStoragePersistStorage() {
10
11
  return {
11
12
  getItem: (name: string) => JSON.parse(window.localStorage.getItem(name) ?? 'null'),
12
13
  setItem: (name: string, value: unknown) => window.localStorage.setItem(name, JSON.stringify(value)),
13
- removeItem: (name: string) => window.localStorage.removeItem(name)
14
+ removeItem: (name: string) => window.localStorage.removeItem(name),
14
15
  };
15
16
  }
16
17
 
@@ -31,6 +32,7 @@ function resetChatThreadStore() {
31
32
  closedWorkspaceTabEntries: [],
32
33
  workspaceNavigationHistory: [],
33
34
  workspaceNavigationHistoryIndex: 0,
35
+ workspaceExplorerWidth: CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH,
34
36
  workspacePanelWidth: CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
35
37
  },
36
38
  });
@@ -66,10 +68,7 @@ describe('chat thread workspace panel persistence', () => {
66
68
  },
67
69
  ],
68
70
  activeWorkspaceFileKey: 'session-1::preview::README.md',
69
- closedWorkspaceTabEntries: [
70
- { kind: 'project-files' },
71
- { kind: 'child-session', key: 'child-session-2' },
72
- ],
71
+ closedWorkspaceTabEntries: [{ kind: 'project-files' }, { kind: 'child-session', key: 'child-session-2' }],
73
72
  workspaceNavigationHistory: [
74
73
  { kind: 'child-session', key: 'child-session-1' },
75
74
  { kind: 'file', key: 'session-1::preview::README.md' },
@@ -77,18 +76,13 @@ describe('chat thread workspace panel persistence', () => {
77
76
  workspaceNavigationHistoryIndex: 1,
78
77
  });
79
78
 
80
- const persisted = JSON.parse(
81
- window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}',
82
- );
79
+ const persisted = JSON.parse(window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}');
83
80
 
84
81
  expect(persisted.state.snapshot).toMatchObject({
85
82
  workspacePanelParentKey: 'session-1',
86
83
  activeWorkspacePanelKind: 'file',
87
84
  activeWorkspaceFileKey: 'session-1::preview::README.md',
88
- closedWorkspaceTabEntries: [
89
- { kind: 'project-files' },
90
- { kind: 'child-session', key: 'child-session-2' },
91
- ],
85
+ closedWorkspaceTabEntries: [{ kind: 'project-files' }, { kind: 'child-session', key: 'child-session-2' }],
92
86
  workspaceNavigationHistory: [
93
87
  { kind: 'child-session', key: 'child-session-1' },
94
88
  { kind: 'file', key: 'session-1::preview::README.md' },
@@ -120,16 +114,12 @@ describe('chat thread workspace panel persistence', () => {
120
114
  workspaceNavigationHistoryIndex: 1,
121
115
  });
122
116
 
123
- const persisted = JSON.parse(
124
- window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}',
125
- );
117
+ const persisted = JSON.parse(window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}');
126
118
 
127
119
  expect(persisted.state.snapshot).toMatchObject({
128
120
  workspacePanelParentKey: 'session-1',
129
121
  activeWorkspacePanelKind: null,
130
- workspaceNavigationHistory: [
131
- { kind: 'child-session', key: 'child-session-1' },
132
- ],
122
+ workspaceNavigationHistory: [{ kind: 'child-session', key: 'child-session-1' }],
133
123
  workspaceNavigationHistoryIndex: 0,
134
124
  });
135
125
  expect(persisted.state.snapshot.activeSideChatDraft).toBeUndefined();
@@ -139,12 +129,9 @@ describe('chat thread workspace panel persistence', () => {
139
129
  useChatThreadStore.getState().setSnapshot({
140
130
  workspacePanelParentKey: 'session-1',
141
131
  activeWorkspacePanelKind: 'project-files',
142
- workspaceNavigationHistory: [
143
- { kind: 'overview' },
144
- { kind: 'child-sessions' },
145
- { kind: 'project-files' },
146
- ],
132
+ workspaceNavigationHistory: [{ kind: 'overview' }, { kind: 'child-sessions' }, { kind: 'project-files' }],
147
133
  workspaceNavigationHistoryIndex: 2,
134
+ workspaceExplorerWidth: 318,
148
135
  workspacePanelWidth: 620,
149
136
  });
150
137
 
@@ -158,12 +145,9 @@ describe('chat thread workspace panel persistence', () => {
158
145
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
159
146
  workspacePanelParentKey: 'session-1',
160
147
  activeWorkspacePanelKind: 'project-files',
161
- workspaceNavigationHistory: [
162
- { kind: 'overview' },
163
- { kind: 'child-sessions' },
164
- { kind: 'project-files' },
165
- ],
148
+ workspaceNavigationHistory: [{ kind: 'overview' }, { kind: 'child-sessions' }, { kind: 'project-files' }],
166
149
  workspaceNavigationHistoryIndex: 2,
150
+ workspaceExplorerWidth: 318,
167
151
  workspacePanelWidth: 620,
168
152
  });
169
153
  });
@@ -182,9 +166,7 @@ describe('chat thread workspace panel persistence', () => {
182
166
  activeWorkspacePanelKind: 'file',
183
167
  workspaceFileTabs,
184
168
  activeWorkspaceFileKey,
185
- workspaceNavigationHistory: [
186
- { kind: 'file', key: activeWorkspaceFileKey },
187
- ],
169
+ workspaceNavigationHistory: [{ kind: 'file', key: activeWorkspaceFileKey }],
188
170
  workspaceNavigationHistoryIndex: 0,
189
171
  });
190
172
 
@@ -202,9 +184,7 @@ describe('chat thread workspace panel persistence', () => {
202
184
  });
203
185
  expect(useChatThreadStore.getState().snapshot.workspaceFileTabs).toHaveLength(8);
204
186
  expect(
205
- useChatThreadStore.getState().snapshot.workspaceFileTabs.some(
206
- (tab) => tab.key === activeWorkspaceFileKey,
207
- ),
187
+ useChatThreadStore.getState().snapshot.workspaceFileTabs.some((tab) => tab.key === activeWorkspaceFileKey),
208
188
  ).toBe(true);
209
189
  });
210
190
 
@@ -223,6 +203,7 @@ describe('chat thread workspace panel persistence', () => {
223
203
  { kind: 'file', key: 'session-1::preview::README.md' },
224
204
  ],
225
205
  workspaceNavigationHistoryIndex: 1,
206
+ workspaceExplorerWidth: 999,
226
207
  workspaceFileTabs: [
227
208
  {
228
209
  key: 'session-1::preview::README.md',
@@ -252,10 +233,9 @@ describe('chat thread workspace panel persistence', () => {
252
233
  workspacePanelParentKey: 'session-1',
253
234
  activeWorkspacePanelKind: 'file',
254
235
  activeWorkspaceFileKey: 'session-1::preview::README.md',
236
+ workspaceExplorerWidth: 400,
255
237
  activeChildSessionKey: 'child-session-1',
256
- workspaceNavigationHistory: [
257
- { kind: 'file', key: 'session-1::preview::README.md' },
258
- ],
238
+ workspaceNavigationHistory: [{ kind: 'file', key: 'session-1::preview::README.md' }],
259
239
  workspaceNavigationHistoryIndex: 0,
260
240
  workspaceFileTabs: [
261
241
  {