@nextclaw/ui 0.15.3 → 0.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/assets/aiden0z-pptx-renderer.es-Bd99vut2.js +94 -0
- package/dist/assets/api-BOXOrpcJ.js +13 -0
- package/dist/assets/app-presenter-provider-CkHLbvZQ.js +106 -0
- package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
- package/dist/assets/appearance-settings-page-fLIJbx1C.js +1 -0
- package/dist/assets/{book-open-C3mJGwiy.js → book-open-DWUjS6aW.js} +1 -1
- package/dist/assets/channels-list-page-CvCTJraa.js +8 -0
- package/dist/assets/chat-page-B42UUIFR.js +1 -0
- package/dist/assets/chunk-DseTPa7n.js +1 -0
- package/dist/assets/config-split-page-DBgt9S8b.js +1 -0
- package/dist/assets/confirm-dialog-GSXIvoRU.js +5 -0
- package/dist/assets/desktop-update-config-7bmNeWIC.js +1 -0
- package/dist/assets/dist-DC7f4TJH.js +41 -0
- package/dist/assets/doc-browser-CgdoLUT92.js +1 -0
- package/dist/assets/doc-browser-D0monHjG.js +1 -0
- package/dist/assets/doc-browser-context-CGwEOhBy.js +1 -0
- package/dist/assets/docx-preview-eFH9DRsS.js +20 -0
- package/dist/assets/download-DiP9zx-J.js +1 -0
- package/dist/assets/{external-link-CU0kzzIP.js → external-link-C6ZeSPef.js} +1 -1
- package/dist/assets/eye-suvKmtwn.js +1 -0
- package/dist/assets/folder-CMELbQGG.js +1 -0
- package/dist/assets/{form-actions-BvCp_0lS.js → form-actions-CUAzpNe_.js} +1 -1
- package/dist/assets/index-BFeNDQ4R.css +1 -0
- package/dist/assets/index-D_6U-M94.js +2 -0
- package/dist/assets/jszip.min-Das2IAnb.js +2 -0
- package/dist/assets/loader-circle-tYHStefV.js +1 -0
- package/dist/assets/logo-badge-Cu5UGH_4.js +1 -0
- package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +9 -0
- package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +1 -0
- package/dist/assets/model-config-page-CWP6pyBm.js +1 -0
- package/dist/assets/plus-tywizkeY.js +1 -0
- package/dist/assets/popover-DImguRok.js +1 -0
- package/dist/assets/provider-models-C_yOh6DE.js +1 -0
- package/dist/assets/provider-scoped-model-input-CE7p4SbJ.js +1 -0
- package/dist/assets/providers-config-page-BKPo8SjH.js +1 -0
- package/dist/assets/react-5sjAT_6d.js +8 -0
- package/dist/assets/{refresh-cw-CILd1vAP.js → refresh-cw-CHLBzWWj.js} +1 -1
- package/dist/assets/remote-DXJryUxB.js +1 -0
- package/dist/assets/rotate-cw-BefO5brU.js +1 -0
- package/dist/assets/runtime-config-page-1Axv4QGu.js +1 -0
- package/dist/assets/{save-ByJqcPTP.js → save-BABzyrjB.js} +1 -1
- package/dist/assets/{search-DVIV6pfD.js → search-BTqxG3ZG.js} +1 -1
- package/dist/assets/search-config-page-BkKKmxmS.js +1 -0
- package/dist/assets/secrets-config-page-CWJMyu-k.js +3 -0
- package/dist/assets/security-config-DeQUIXfi.js +1 -0
- package/dist/assets/select-TYZDBNmq.js +1 -0
- package/dist/assets/setting-row-DR23Z_5I.js +1 -0
- package/dist/assets/{settings-2-CH5shGg_.js → settings-2-D8WWcBnw.js} +1 -1
- package/dist/assets/{skeleton-DeX1FKw7.js → skeleton-CloHeg0S.js} +1 -1
- package/dist/assets/tabs-CX-ckWoo.js +1 -0
- package/dist/assets/tag-chip-mCc23Rhb.js +1 -0
- package/dist/assets/trash-2-SAW1Z4Po.js +1 -0
- package/dist/assets/use-config-BucdaK23.js +1 -0
- package/dist/assets/use-confirm-dialog-C95F6sT1.js +1 -0
- package/dist/assets/use-viewport-layout-C6m3Nte7.js +1 -0
- package/dist/assets/x-DtCPHwCu.js +1 -0
- package/dist/assets/xlsx-B6zVt1RK.js +64 -0
- package/dist/index.html +34 -28
- package/package.json +11 -6
- package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +41 -0
- package/src/features/chat/components/chat-popover-content.tsx +26 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
- package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +3 -14
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +69 -54
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +16 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +4 -3
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -3
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +403 -106
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +9 -3
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +7 -5
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +57 -177
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +56 -6
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +29 -7
- package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
- package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
- package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +53 -10
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +68 -2
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +17 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +63 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +120 -38
- package/src/features/chat/index.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +76 -0
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +44 -60
- package/src/features/chat/managers/chat-thread.manager.ts +123 -126
- package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +78 -1
- package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
- package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
- package/src/features/chat/stores/chat-thread.store.ts +70 -13
- package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
- package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
- package/src/index.css +36 -2
- package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
- package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
- package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
- package/src/shared/components/file-type-icon.tsx +272 -0
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
- package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
- package/src/shared/lib/api/utils/server-path.utils.ts +19 -20
- package/src/shared/lib/i18n/locales/en-US/chat.json +17 -1
- package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +17 -1
- package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
- package/dist/assets/api-iNBpppzj.js +0 -13
- package/dist/assets/appearance-settings-page-DMffQFR3.js +0 -1
- package/dist/assets/channels-list-page-CCtp0BRS.js +0 -8
- package/dist/assets/chat-page-H5a0dWvO.js +0 -1
- package/dist/assets/config-split-page-CrOJgB-Y.js +0 -1
- package/dist/assets/confirm-dialog-Chv50USy.js +0 -5
- package/dist/assets/desktop-update-config-KzTuXRf_.js +0 -1
- package/dist/assets/dist-BunzmC4B.js +0 -1
- package/dist/assets/dist-ChmGwvtK.js +0 -1
- package/dist/assets/doc-browser-DNRL4Zvd.js +0 -1
- package/dist/assets/doc-browser-Dg0pmkOg.js +0 -1
- package/dist/assets/doc-browser-context-BHY-0FSD.js +0 -1
- package/dist/assets/es2015-DQZ5JWwK.js +0 -41
- package/dist/assets/i18n-provider-Cu8XyZb_.js +0 -1
- package/dist/assets/index-CzfBdvK1.css +0 -1
- package/dist/assets/index-DGqHne1Q.js +0 -104
- package/dist/assets/loader-circle-CDSFuzmk.js +0 -1
- package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +0 -9
- package/dist/assets/mcp-marketplace-page-D6idOTLp.js +0 -1
- package/dist/assets/model-config-page-bNNo-XyG.js +0 -1
- package/dist/assets/plus-D4DsdLN-.js +0 -1
- package/dist/assets/provider-scoped-model-input-IlgQCsoT.js +0 -1
- package/dist/assets/providers-config-page-wLo6CAJw.js +0 -1
- package/dist/assets/react-BPIsQIlO.js +0 -8
- package/dist/assets/remote-uKEYp7AR.js +0 -1
- package/dist/assets/rotate-cw-wPFPg1kH.js +0 -1
- package/dist/assets/runtime-config-page-DDn1sP4G.js +0 -1
- package/dist/assets/search-config-page-B-Cf4iiB.js +0 -1
- package/dist/assets/secrets-config-page-DPKsY8Iw.js +0 -3
- package/dist/assets/security-config-e9SSBlKr.js +0 -1
- package/dist/assets/select-BdgczSNw.js +0 -1
- package/dist/assets/setting-row-BkTVUTKx.js +0 -1
- package/dist/assets/side-dock-BsBvcJ9i.js +0 -3
- package/dist/assets/switch-Cv8Ekb3x.js +0 -1
- package/dist/assets/tag-chip-RB9uKcoC.js +0 -1
- package/dist/assets/tooltip-oQlEN-l6.js +0 -1
- package/dist/assets/use-config-l3W7y8Qz.js +0 -1
- package/dist/assets/use-confirm-dialog-BAxaVOJI.js +0 -1
- package/dist/assets/x-BBMffEN0.js +0 -1
- /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-fDrYfl0l.js} +0 -0
- /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-zZvY9U63.js} +0 -0
- /package/dist/assets/{middleware-BILWbJE4.js → middleware-BLMHv9YF.js} +0 -0
|
@@ -8,8 +8,13 @@ import type {
|
|
|
8
8
|
} from '@/shared/lib/api';
|
|
9
9
|
import {
|
|
10
10
|
normalizePersistedWorkspaceFileTab,
|
|
11
|
+
retainWorkspaceFileTabs,
|
|
11
12
|
toPersistedWorkspaceFileTab,
|
|
12
13
|
} from '@/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils';
|
|
14
|
+
import {
|
|
15
|
+
CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
|
|
16
|
+
normalizeChatWorkspacePanelWidth,
|
|
17
|
+
} from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
|
|
13
18
|
|
|
14
19
|
export type ChatChildSessionTab = {
|
|
15
20
|
sessionKey: string;
|
|
@@ -44,9 +49,21 @@ export type ChatWorkspaceFileTab = {
|
|
|
44
49
|
fullLines?: ChatFileOperationLineViewModel[];
|
|
45
50
|
};
|
|
46
51
|
|
|
52
|
+
export type ChatWorkspacePanelKind =
|
|
53
|
+
| 'overview'
|
|
54
|
+
| 'child-sessions'
|
|
55
|
+
| 'child-session'
|
|
56
|
+
| 'side-chat-draft'
|
|
57
|
+
| 'project-files'
|
|
58
|
+
| 'file'
|
|
59
|
+
| 'cron';
|
|
60
|
+
|
|
47
61
|
export type ChatWorkspaceNavigationEntry =
|
|
62
|
+
| { kind: 'overview' }
|
|
63
|
+
| { kind: 'child-sessions' }
|
|
48
64
|
| { kind: 'child-session'; key: string }
|
|
49
65
|
| { kind: 'side-chat-draft'; key: string }
|
|
66
|
+
| { kind: 'project-files' }
|
|
50
67
|
| { kind: 'file'; key: string }
|
|
51
68
|
| { kind: 'cron' };
|
|
52
69
|
|
|
@@ -69,7 +86,7 @@ export type ChatThreadSnapshot = {
|
|
|
69
86
|
parentSessionKey?: string | null;
|
|
70
87
|
parentSessionLabel?: string | null;
|
|
71
88
|
workspacePanelParentKey?: string | null;
|
|
72
|
-
activeWorkspacePanelKind?:
|
|
89
|
+
activeWorkspacePanelKind?: ChatWorkspacePanelKind | null;
|
|
73
90
|
childSessionTabs: ChatChildSessionTab[];
|
|
74
91
|
activeChildSessionKey?: string | null;
|
|
75
92
|
activeSideChatDraft?: ChatWorkspaceSideChatDraft | null;
|
|
@@ -77,6 +94,7 @@ export type ChatThreadSnapshot = {
|
|
|
77
94
|
activeWorkspaceFileKey?: string | null;
|
|
78
95
|
workspaceNavigationHistory: ChatWorkspaceNavigationEntry[];
|
|
79
96
|
workspaceNavigationHistoryIndex: number;
|
|
97
|
+
workspacePanelWidth: number;
|
|
80
98
|
contextWindow?: SessionContextWindowView | null;
|
|
81
99
|
};
|
|
82
100
|
|
|
@@ -98,6 +116,7 @@ type PersistedChatThreadStore = {
|
|
|
98
116
|
activeWorkspaceFileKey?: unknown;
|
|
99
117
|
workspaceNavigationHistory?: unknown;
|
|
100
118
|
workspaceNavigationHistoryIndex?: unknown;
|
|
119
|
+
workspacePanelWidth?: unknown;
|
|
101
120
|
};
|
|
102
121
|
};
|
|
103
122
|
|
|
@@ -110,9 +129,10 @@ type PersistedChatWorkspaceSnapshot = Pick<
|
|
|
110
129
|
| 'activeWorkspaceFileKey'
|
|
111
130
|
| 'workspaceNavigationHistory'
|
|
112
131
|
| 'workspaceNavigationHistoryIndex'
|
|
132
|
+
| 'workspacePanelWidth'
|
|
113
133
|
>;
|
|
114
134
|
|
|
115
|
-
type PersistedWorkspacePanelKind = '
|
|
135
|
+
type PersistedWorkspacePanelKind = Exclude<ChatWorkspacePanelKind, 'side-chat-draft'>;
|
|
116
136
|
|
|
117
137
|
const initialSnapshot: ChatThreadSnapshot = {
|
|
118
138
|
sessionTypeLabel: null,
|
|
@@ -141,6 +161,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
141
161
|
activeWorkspaceFileKey: null,
|
|
142
162
|
workspaceNavigationHistory: [],
|
|
143
163
|
workspaceNavigationHistoryIndex: 0,
|
|
164
|
+
workspacePanelWidth: CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
|
|
144
165
|
contextWindow: null
|
|
145
166
|
};
|
|
146
167
|
|
|
@@ -163,7 +184,14 @@ function normalizeHistoryIndex(value: unknown, maxIndex: number): number {
|
|
|
163
184
|
function isWorkspacePanelKind(
|
|
164
185
|
value: unknown,
|
|
165
186
|
): value is PersistedWorkspacePanelKind {
|
|
166
|
-
return
|
|
187
|
+
return (
|
|
188
|
+
value === 'overview' ||
|
|
189
|
+
value === 'child-sessions' ||
|
|
190
|
+
value === 'child-session' ||
|
|
191
|
+
value === 'project-files' ||
|
|
192
|
+
value === 'file' ||
|
|
193
|
+
value === 'cron'
|
|
194
|
+
);
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
function normalizePersistedWorkspaceNavigationEntry(
|
|
@@ -172,8 +200,13 @@ function normalizePersistedWorkspaceNavigationEntry(
|
|
|
172
200
|
if (!isRecord(value)) {
|
|
173
201
|
return null;
|
|
174
202
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
203
|
+
if (
|
|
204
|
+
value.kind === 'overview' ||
|
|
205
|
+
value.kind === 'child-sessions' ||
|
|
206
|
+
value.kind === 'project-files' ||
|
|
207
|
+
value.kind === 'cron'
|
|
208
|
+
) {
|
|
209
|
+
return { kind: value.kind };
|
|
177
210
|
}
|
|
178
211
|
const key = normalizeOptionalString(value.key);
|
|
179
212
|
if (!key || (value.kind !== 'child-session' && value.kind !== 'file')) {
|
|
@@ -194,6 +227,15 @@ function createWorkspaceNavigationEntryFromSnapshot({
|
|
|
194
227
|
activeChildSessionKey: string | null;
|
|
195
228
|
activeWorkspaceFileKey: string | null;
|
|
196
229
|
}): ChatWorkspaceNavigationEntry | null {
|
|
230
|
+
if (activeWorkspacePanelKind === 'overview') {
|
|
231
|
+
return { kind: 'overview' };
|
|
232
|
+
}
|
|
233
|
+
if (activeWorkspacePanelKind === 'child-sessions') {
|
|
234
|
+
return { kind: 'child-sessions' };
|
|
235
|
+
}
|
|
236
|
+
if (activeWorkspacePanelKind === 'project-files') {
|
|
237
|
+
return { kind: 'project-files' };
|
|
238
|
+
}
|
|
197
239
|
if (activeWorkspacePanelKind === 'cron') {
|
|
198
240
|
return { kind: 'cron' };
|
|
199
241
|
}
|
|
@@ -218,17 +260,25 @@ function normalizePersistedWorkspaceSnapshot(
|
|
|
218
260
|
if (!isRecord(value)) {
|
|
219
261
|
return null;
|
|
220
262
|
}
|
|
221
|
-
const
|
|
263
|
+
const workspacePanelParentKey = normalizeOptionalString(value.workspacePanelParentKey);
|
|
264
|
+
const activeWorkspaceFileKey = normalizeOptionalString(value.activeWorkspaceFileKey);
|
|
265
|
+
const normalizedWorkspaceFileTabs = Array.isArray(value.workspaceFileTabs)
|
|
222
266
|
? value.workspaceFileTabs
|
|
223
267
|
.map(normalizePersistedWorkspaceFileTab)
|
|
224
268
|
.filter((tab): tab is ChatWorkspaceFileTab => tab !== null)
|
|
225
|
-
.slice(-CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS)
|
|
226
269
|
: [];
|
|
227
|
-
const
|
|
270
|
+
const workspaceFileTabs = retainWorkspaceFileTabs(
|
|
271
|
+
normalizedWorkspaceFileTabs,
|
|
272
|
+
activeWorkspaceFileKey,
|
|
273
|
+
CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS,
|
|
274
|
+
);
|
|
275
|
+
const sessionWorkspaceFileTabs = workspaceFileTabs.filter(
|
|
276
|
+
(tab) => tab.parentSessionKey === workspacePanelParentKey,
|
|
277
|
+
);
|
|
228
278
|
const resolvedActiveWorkspaceFileKey =
|
|
229
|
-
activeWorkspaceFileKey &&
|
|
279
|
+
activeWorkspaceFileKey && sessionWorkspaceFileTabs.some((tab) => tab.key === activeWorkspaceFileKey)
|
|
230
280
|
? activeWorkspaceFileKey
|
|
231
|
-
:
|
|
281
|
+
: sessionWorkspaceFileTabs[0]?.key ?? null;
|
|
232
282
|
const activeWorkspacePanelKind = isWorkspacePanelKind(value.activeWorkspacePanelKind)
|
|
233
283
|
? value.activeWorkspacePanelKind
|
|
234
284
|
: null;
|
|
@@ -265,13 +315,16 @@ function normalizePersistedWorkspaceSnapshot(
|
|
|
265
315
|
: 0;
|
|
266
316
|
|
|
267
317
|
return {
|
|
268
|
-
workspacePanelParentKey
|
|
318
|
+
workspacePanelParentKey,
|
|
269
319
|
activeWorkspacePanelKind: resolvedActiveWorkspacePanelKind,
|
|
270
320
|
activeChildSessionKey,
|
|
271
321
|
workspaceFileTabs,
|
|
272
322
|
activeWorkspaceFileKey: resolvedActiveWorkspaceFileKey,
|
|
273
323
|
workspaceNavigationHistory,
|
|
274
324
|
workspaceNavigationHistoryIndex,
|
|
325
|
+
workspacePanelWidth: normalizeChatWorkspacePanelWidth(
|
|
326
|
+
value.workspacePanelWidth,
|
|
327
|
+
),
|
|
275
328
|
};
|
|
276
329
|
}
|
|
277
330
|
|
|
@@ -309,12 +362,16 @@ export const useChatThreadStore = create<ChatThreadStore>()(
|
|
|
309
362
|
? null
|
|
310
363
|
: state.snapshot.activeWorkspacePanelKind,
|
|
311
364
|
activeChildSessionKey: state.snapshot.activeChildSessionKey,
|
|
312
|
-
workspaceFileTabs:
|
|
313
|
-
.
|
|
365
|
+
workspaceFileTabs: retainWorkspaceFileTabs(
|
|
366
|
+
state.snapshot.workspaceFileTabs,
|
|
367
|
+
state.snapshot.activeWorkspaceFileKey,
|
|
368
|
+
CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS,
|
|
369
|
+
)
|
|
314
370
|
.map(toPersistedWorkspaceFileTab),
|
|
315
371
|
activeWorkspaceFileKey: state.snapshot.activeWorkspaceFileKey,
|
|
316
372
|
workspaceNavigationHistory,
|
|
317
373
|
workspaceNavigationHistoryIndex,
|
|
374
|
+
workspacePanelWidth: state.snapshot.workspacePanelWidth,
|
|
318
375
|
}
|
|
319
376
|
};
|
|
320
377
|
},
|
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from
|
|
2
|
-
import { beforeEach, describe, expect, it } from
|
|
3
|
-
import { AppearanceSettingsPage } from
|
|
4
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { AppearanceSettingsPage } from "@/features/settings/pages/appearance-settings-page";
|
|
4
|
+
import { useChatMessageLayoutStore } from "@/features/chat";
|
|
5
|
+
import { useSideDockStore } from "@/features/side-dock";
|
|
5
6
|
|
|
6
|
-
describe(
|
|
7
|
+
describe("AppearanceSettingsPage", () => {
|
|
7
8
|
beforeEach(() => {
|
|
8
9
|
window.localStorage.clear();
|
|
10
|
+
useChatMessageLayoutStore.getState().setLayout("card");
|
|
9
11
|
useSideDockStore.getState().setVisible(true);
|
|
10
12
|
useSideDockStore.getState().setPinnedItems([]);
|
|
11
13
|
});
|
|
12
14
|
|
|
13
|
-
it(
|
|
15
|
+
it("reopens the SideDock from the appearance settings switch", () => {
|
|
14
16
|
useSideDockStore.getState().setVisible(false);
|
|
15
17
|
|
|
16
18
|
render(<AppearanceSettingsPage />);
|
|
17
19
|
|
|
18
|
-
const visibilitySwitch = screen.getByRole(
|
|
19
|
-
|
|
20
|
+
const visibilitySwitch = screen.getByRole("switch", {
|
|
21
|
+
name: "Show SideDock",
|
|
22
|
+
});
|
|
23
|
+
expect(visibilitySwitch.getAttribute("aria-checked")).toBe("false");
|
|
20
24
|
|
|
21
25
|
fireEvent.click(visibilitySwitch);
|
|
22
26
|
|
|
23
27
|
expect(useSideDockStore.getState().isVisible).toBe(true);
|
|
24
28
|
});
|
|
29
|
+
|
|
30
|
+
it("switches the chat message layout from appearance settings", () => {
|
|
31
|
+
render(<AppearanceSettingsPage />);
|
|
32
|
+
|
|
33
|
+
const cardOption = screen.getByRole("radio", { name: /^Cards/ });
|
|
34
|
+
const flatOption = screen.getByRole("radio", { name: /^Flat/ });
|
|
35
|
+
expect(cardOption.getAttribute("aria-checked")).toBe("true");
|
|
36
|
+
|
|
37
|
+
fireEvent.click(flatOption);
|
|
38
|
+
|
|
39
|
+
expect(flatOption.getAttribute("aria-checked")).toBe("true");
|
|
40
|
+
expect(useChatMessageLayoutStore.getState().layout).toBe("flat");
|
|
41
|
+
});
|
|
25
42
|
});
|
|
@@ -1,33 +1,112 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
1
|
+
import type { ChatMessageLayout } from "@nextclaw/agent-chat-ui";
|
|
2
|
+
import { Check, Palette } from "lucide-react";
|
|
3
|
+
import { PageHeader, PageLayout } from "@/app/components/layout/page-layout";
|
|
4
|
+
import { useChatMessageLayoutStore } from "@/features/chat";
|
|
5
|
+
import { SettingRow } from "@/shared/components/settings/setting-row";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardHeader,
|
|
10
|
+
CardTitle,
|
|
11
|
+
} from "@/shared/components/ui/card";
|
|
12
|
+
import { Switch } from "@/shared/components/ui/switch";
|
|
13
|
+
import { useSideDockStore } from "@/features/side-dock";
|
|
14
|
+
import { t } from "@/shared/lib/i18n";
|
|
15
|
+
import { cn } from "@/shared/lib/utils";
|
|
16
|
+
|
|
17
|
+
const CHAT_MESSAGE_LAYOUT_OPTIONS: Array<{
|
|
18
|
+
value: ChatMessageLayout;
|
|
19
|
+
labelKey: string;
|
|
20
|
+
descriptionKey: string;
|
|
21
|
+
}> = [
|
|
22
|
+
{
|
|
23
|
+
value: "card",
|
|
24
|
+
labelKey: "chatMessageLayoutCard",
|
|
25
|
+
descriptionKey: "chatMessageLayoutCardDescription",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "flat",
|
|
29
|
+
labelKey: "chatMessageLayoutFlat",
|
|
30
|
+
descriptionKey: "chatMessageLayoutFlatDescription",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
8
33
|
|
|
9
34
|
export function AppearanceSettingsPage() {
|
|
35
|
+
const messageLayout = useChatMessageLayoutStore((state) => state.layout);
|
|
36
|
+
const setMessageLayout = useChatMessageLayoutStore(
|
|
37
|
+
(state) => state.setLayout,
|
|
38
|
+
);
|
|
10
39
|
const isSideDockVisible = useSideDockStore((state) => state.isVisible);
|
|
11
40
|
const setSideDockVisible = useSideDockStore((state) => state.setVisible);
|
|
12
41
|
|
|
13
42
|
return (
|
|
14
43
|
<PageLayout>
|
|
15
|
-
<PageHeader title={t(
|
|
44
|
+
<PageHeader title={t("appearance")} />
|
|
16
45
|
<Card>
|
|
17
46
|
<CardHeader>
|
|
18
47
|
<CardTitle className="flex items-center gap-2">
|
|
19
48
|
<Palette className="h-4 w-4" />
|
|
20
|
-
{t(
|
|
49
|
+
{t("appearance")}
|
|
21
50
|
</CardTitle>
|
|
22
51
|
</CardHeader>
|
|
23
|
-
<CardContent>
|
|
52
|
+
<CardContent className="space-y-4">
|
|
53
|
+
<SettingRow
|
|
54
|
+
title={t("chatMessageLayoutTitle")}
|
|
55
|
+
description={t("chatMessageLayoutDescription")}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
role="radiogroup"
|
|
59
|
+
aria-label={t("chatMessageLayoutTitle")}
|
|
60
|
+
className="grid gap-2 sm:grid-cols-2"
|
|
61
|
+
>
|
|
62
|
+
{CHAT_MESSAGE_LAYOUT_OPTIONS.map((option) => {
|
|
63
|
+
const selected = option.value === messageLayout;
|
|
64
|
+
return (
|
|
65
|
+
<button
|
|
66
|
+
key={option.value}
|
|
67
|
+
type="button"
|
|
68
|
+
role="radio"
|
|
69
|
+
aria-checked={selected}
|
|
70
|
+
onClick={() => setMessageLayout(option.value)}
|
|
71
|
+
className={cn(
|
|
72
|
+
"flex min-w-0 items-start gap-3 rounded-xl border px-3 py-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
|
|
73
|
+
selected
|
|
74
|
+
? "border-primary/35 bg-primary/5 text-foreground"
|
|
75
|
+
: "border-border bg-card text-muted-foreground hover:border-border/80 hover:bg-muted/45 hover:text-foreground",
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
<span
|
|
79
|
+
className={cn(
|
|
80
|
+
"mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full border",
|
|
81
|
+
selected
|
|
82
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
83
|
+
: "border-border bg-background",
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{selected ? (
|
|
87
|
+
<Check className="h-2.5 w-2.5" strokeWidth={3} />
|
|
88
|
+
) : null}
|
|
89
|
+
</span>
|
|
90
|
+
<span className="min-w-0">
|
|
91
|
+
<span className="block text-sm font-medium">
|
|
92
|
+
{t(option.labelKey)}
|
|
93
|
+
</span>
|
|
94
|
+
<span className="mt-1 block text-xs leading-5 text-muted-foreground">
|
|
95
|
+
{t(option.descriptionKey)}
|
|
96
|
+
</span>
|
|
97
|
+
</span>
|
|
98
|
+
</button>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
</SettingRow>
|
|
24
103
|
<SettingRow
|
|
25
|
-
title={t(
|
|
26
|
-
description={t(
|
|
104
|
+
title={t("sideDockVisibilityTitle")}
|
|
105
|
+
description={t("sideDockVisibilityDescription")}
|
|
27
106
|
control={
|
|
28
107
|
<Switch
|
|
29
108
|
id="appearance-side-dock-visible"
|
|
30
|
-
aria-label={t(
|
|
109
|
+
aria-label={t("sideDockVisibilityTitle")}
|
|
31
110
|
checked={isSideDockVisible}
|
|
32
111
|
onCheckedChange={setSideDockVisible}
|
|
33
112
|
/>
|
package/src/index.css
CHANGED
|
@@ -736,11 +736,16 @@
|
|
|
736
736
|
.chat-markdown h5,
|
|
737
737
|
.chat-markdown h6 {
|
|
738
738
|
margin: 0.2rem 0 0;
|
|
739
|
+
padding-bottom: 0.5em;
|
|
739
740
|
font-weight: 700;
|
|
740
741
|
line-height: 1.35;
|
|
741
742
|
letter-spacing: -0.01em;
|
|
742
743
|
}
|
|
743
744
|
|
|
745
|
+
.chat-markdown :is(h1, h2, h3, h4, h5, h6) + * {
|
|
746
|
+
margin-top: 0;
|
|
747
|
+
}
|
|
748
|
+
|
|
744
749
|
.chat-markdown h1 {
|
|
745
750
|
font-size: 1.22rem;
|
|
746
751
|
}
|
|
@@ -1338,9 +1343,38 @@
|
|
|
1338
1343
|
}
|
|
1339
1344
|
|
|
1340
1345
|
.chat-markdown img {
|
|
1346
|
+
display: block;
|
|
1347
|
+
width: auto;
|
|
1348
|
+
max-width: min(100%, 32rem);
|
|
1349
|
+
height: auto;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
.chat-markdown [data-chat-message-image-preview] {
|
|
1353
|
+
margin-block: 0.72rem;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.chat-markdown [data-chat-message-image-preview]:first-child,
|
|
1357
|
+
.chat-markdown [data-chat-message-image-preview]
|
|
1358
|
+
+ [data-chat-message-image-preview] {
|
|
1359
|
+
margin-top: 0;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.chat-markdown [data-chat-message-image-preview]:last-child {
|
|
1363
|
+
margin-bottom: 0;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.chat-markdown [data-chat-image-row="three-column"] {
|
|
1367
|
+
display: grid;
|
|
1368
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1369
|
+
align-items: start;
|
|
1370
|
+
gap: 0.72rem;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.chat-markdown
|
|
1374
|
+
[data-chat-image-row="three-column"]
|
|
1375
|
+
> [data-chat-message-image-preview] {
|
|
1341
1376
|
max-width: 100%;
|
|
1342
|
-
|
|
1343
|
-
border: 1px solid var(--md-border);
|
|
1377
|
+
margin: 0;
|
|
1344
1378
|
}
|
|
1345
1379
|
|
|
1346
1380
|
.chat-markdown blockquote {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { render } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { FileTypeIcon } from "@/shared/components/file-type-icon";
|
|
4
|
+
|
|
5
|
+
function readIcon(container: HTMLElement, name: string): SVGElement {
|
|
6
|
+
const icon = container.querySelector<SVGElement>(
|
|
7
|
+
`[data-file-type-icon="${name}"]`,
|
|
8
|
+
);
|
|
9
|
+
expect(icon).not.toBeNull();
|
|
10
|
+
expect(icon?.innerHTML).not.toContain("undefined");
|
|
11
|
+
expect(icon?.childElementCount).toBeGreaterThan(0);
|
|
12
|
+
return icon!;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("FileTypeIcon", () => {
|
|
16
|
+
it("renders recognizable SVG icons for common file types", () => {
|
|
17
|
+
const { container } = render(
|
|
18
|
+
<>
|
|
19
|
+
<FileTypeIcon fileName="src/app.tsx" />
|
|
20
|
+
<FileTypeIcon fileName="scripts/setup.js" />
|
|
21
|
+
<FileTypeIcon fileName="docs/guide.md" />
|
|
22
|
+
<FileTypeIcon fileName="assets/hero.png" />
|
|
23
|
+
<FileTypeIcon fileName="reports/data.xlsx" />
|
|
24
|
+
</>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(readIcon(container, "react-typescript").tagName).toBe("svg");
|
|
28
|
+
expect(readIcon(container, "javascript").tagName).toBe("svg");
|
|
29
|
+
expect(readIcon(container, "markdown").tagName).toBe("svg");
|
|
30
|
+
expect(readIcon(container, "image").tagName).toBe("svg");
|
|
31
|
+
expect(readIcon(container, "excel").tagName).toBe("svg");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("recognizes editor-style special file names", () => {
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<>
|
|
37
|
+
<FileTypeIcon fileName="package.json" />
|
|
38
|
+
<FileTypeIcon fileName="Dockerfile" />
|
|
39
|
+
<FileTypeIcon fileName=".env.local" />
|
|
40
|
+
<FileTypeIcon fileName="vitest.unit.config.ts" />
|
|
41
|
+
<FileTypeIcon fileName="PHOTO.JPEG" />
|
|
42
|
+
</>,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(readIcon(container, "npm")).toBeTruthy();
|
|
46
|
+
expect(readIcon(container, "docker")).toBeTruthy();
|
|
47
|
+
expect(readIcon(container, "dotenv")).toBeTruthy();
|
|
48
|
+
expect(readIcon(container, "vitest")).toBeTruthy();
|
|
49
|
+
expect(readIcon(container, "image")).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses the neutral SVG fallback and compact size predictably", () => {
|
|
53
|
+
const { container } = render(
|
|
54
|
+
<FileTypeIcon fileName="artifact.customtype" size="compact" />,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const fallback = readIcon(container, "default");
|
|
58
|
+
expect(fallback.classList.contains("h-3.5")).toBe(true);
|
|
59
|
+
expect(fallback.getAttribute("aria-hidden")).toBe("true");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -80,6 +80,25 @@ describe('DocBrowserManager layout notifications', () => {
|
|
|
80
80
|
|
|
81
81
|
expect(onRightPanelOpened).toHaveBeenCalledTimes(1);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
it('persists the docked global right panel width across rehydration', async () => {
|
|
85
|
+
const { result } = renderHook(() => useDocBrowser(), { wrapper });
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
result.current.setDockedWidth(610);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(readPersistedDocBrowserSnapshot()).toMatchObject({
|
|
93
|
+
state: { snapshot: { dockedWidth: 610 } },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const savedState = window.localStorage.getItem(docBrowserStorageKey);
|
|
98
|
+
await resetMemoryThenRehydrate(savedState);
|
|
99
|
+
|
|
100
|
+
expect(useDocBrowserStore.getState().snapshot.dockedWidth).toBe(610);
|
|
101
|
+
});
|
|
83
102
|
});
|
|
84
103
|
|
|
85
104
|
describe('DocBrowserProvider dedupe keys', () => {
|
|
@@ -24,6 +24,7 @@ const { docBrowserState } = vi.hoisted<{
|
|
|
24
24
|
docBrowserState: {
|
|
25
25
|
isOpen: true,
|
|
26
26
|
mode: "docked" as "docked" | "floating",
|
|
27
|
+
dockedWidth: 420,
|
|
27
28
|
tabs: [
|
|
28
29
|
{
|
|
29
30
|
id: "docs",
|
|
@@ -58,6 +59,7 @@ const { docBrowserState } = vi.hoisted<{
|
|
|
58
59
|
openNewTab: vi.fn(),
|
|
59
60
|
close: vi.fn(),
|
|
60
61
|
toggleMode: vi.fn(),
|
|
62
|
+
setDockedWidth: vi.fn(),
|
|
61
63
|
goBack: vi.fn(),
|
|
62
64
|
goForward: vi.fn(),
|
|
63
65
|
navigate: vi.fn(),
|
|
@@ -156,6 +158,20 @@ describe("DocBrowser", () => {
|
|
|
156
158
|
expect(screen.getByTestId("resizable-right-panel-handle")).toBeTruthy();
|
|
157
159
|
});
|
|
158
160
|
|
|
161
|
+
it("commits the resized docked width to the global browser state owner", () => {
|
|
162
|
+
render(<DocBrowser />);
|
|
163
|
+
|
|
164
|
+
firePointerEvent(
|
|
165
|
+
screen.getByTestId("resizable-right-panel-handle"),
|
|
166
|
+
"pointerdown",
|
|
167
|
+
{ clientX: 800, pointerId: 1 },
|
|
168
|
+
);
|
|
169
|
+
firePointerEvent(window, "pointermove", { clientX: 620, pointerId: 1 });
|
|
170
|
+
firePointerEvent(window, "pointerup", { clientX: 620, pointerId: 1 });
|
|
171
|
+
|
|
172
|
+
expect(docBrowserState.setDockedWidth).toHaveBeenCalledWith(600);
|
|
173
|
+
});
|
|
174
|
+
|
|
159
175
|
it("uses a full viewport panel without desktop resize controls in fullscreen mode", () => {
|
|
160
176
|
render(<DocBrowser displayMode="fullscreen" />);
|
|
161
177
|
|
|
@@ -28,6 +28,10 @@ import { ResizableRightPanel } from '@/shared/components/resizable-right-panel/r
|
|
|
28
28
|
import { cn } from '@/shared/lib/utils';
|
|
29
29
|
import { t } from '@/shared/lib/i18n';
|
|
30
30
|
import { GripVertical } from 'lucide-react';
|
|
31
|
+
import {
|
|
32
|
+
DOC_BROWSER_DOCKED_MAX_WIDTH,
|
|
33
|
+
DOC_BROWSER_DOCKED_MIN_WIDTH,
|
|
34
|
+
} from '@/shared/components/doc-browser/utils/doc-browser-state.utils';
|
|
31
35
|
|
|
32
36
|
type DocBrowserProps = {
|
|
33
37
|
customTabRenderers?: DocBrowserCustomTabRenderers;
|
|
@@ -159,6 +163,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
159
163
|
const {
|
|
160
164
|
isOpen,
|
|
161
165
|
mode,
|
|
166
|
+
dockedWidth,
|
|
162
167
|
tabs,
|
|
163
168
|
activeTabId,
|
|
164
169
|
activeHistory,
|
|
@@ -169,6 +174,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
169
174
|
openNewTab,
|
|
170
175
|
close,
|
|
171
176
|
toggleMode,
|
|
177
|
+
setDockedWidth,
|
|
172
178
|
goBack,
|
|
173
179
|
goForward,
|
|
174
180
|
navigate,
|
|
@@ -386,9 +392,11 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
|
|
|
386
392
|
return (
|
|
387
393
|
<ResizableRightPanel
|
|
388
394
|
data-testid="doc-browser-panel"
|
|
389
|
-
defaultWidth={
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
defaultWidth={dockedWidth}
|
|
396
|
+
width={dockedWidth}
|
|
397
|
+
minWidth={DOC_BROWSER_DOCKED_MIN_WIDTH}
|
|
398
|
+
maxWidth={DOC_BROWSER_DOCKED_MAX_WIDTH}
|
|
399
|
+
onWidthCommit={setDockedWidth}
|
|
392
400
|
>
|
|
393
401
|
{panelContent}
|
|
394
402
|
</ResizableRightPanel>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createDocBrowserActiveHistoryEntry,
|
|
12
12
|
createDefaultDocBrowserState,
|
|
13
13
|
createDocBrowserTab,
|
|
14
|
+
normalizeDocBrowserDockedWidth,
|
|
14
15
|
} from '@/shared/components/doc-browser/utils/doc-browser-state.utils';
|
|
15
16
|
import {
|
|
16
17
|
DOC_BROWSER_HOME_TAB_KIND,
|
|
@@ -297,6 +298,10 @@ export class DocBrowserManager {
|
|
|
297
298
|
this.onRightPanelOpened?.();
|
|
298
299
|
};
|
|
299
300
|
|
|
301
|
+
readonly setDockedWidth = (width: number): void => {
|
|
302
|
+
this.setSnapshot((prev) => ({ ...prev, dockedWidth: normalizeDocBrowserDockedWidth(width) }));
|
|
303
|
+
};
|
|
304
|
+
|
|
300
305
|
readonly navigate = (url: string): void => {
|
|
301
306
|
this.setSnapshot((prev) => {
|
|
302
307
|
if (!prev.tabs.length) {
|
|
@@ -8,7 +8,10 @@ import type {
|
|
|
8
8
|
DocBrowserStateUpdate,
|
|
9
9
|
DocBrowserTab,
|
|
10
10
|
} from '@/shared/components/doc-browser/types/doc-browser.types';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
createDefaultDocBrowserState,
|
|
13
|
+
normalizeDocBrowserDockedWidth,
|
|
14
|
+
} from '@/shared/components/doc-browser/utils/doc-browser-state.utils';
|
|
12
15
|
|
|
13
16
|
const DOC_BROWSER_STORAGE_KEY = 'nextclaw.doc-browser.state';
|
|
14
17
|
const DOC_BROWSER_STORAGE_VERSION = 1;
|
|
@@ -168,6 +171,7 @@ function normalizePersistedDocBrowserState(value: unknown): DocBrowserState | nu
|
|
|
168
171
|
return {
|
|
169
172
|
isOpen: value.isOpen === true,
|
|
170
173
|
mode: isDocBrowserMode(value.mode) ? value.mode : 'docked',
|
|
174
|
+
dockedWidth: normalizeDocBrowserDockedWidth(value.dockedWidth),
|
|
171
175
|
tabs,
|
|
172
176
|
activeTabId: resolvedActiveTabId,
|
|
173
177
|
activeHistory: resolvedActiveHistory,
|
|
@@ -198,6 +202,7 @@ export const useDocBrowserStore = create<DocBrowserStore>()(
|
|
|
198
202
|
snapshot: {
|
|
199
203
|
isOpen: state.snapshot.isOpen,
|
|
200
204
|
mode: state.snapshot.mode,
|
|
205
|
+
dockedWidth: state.snapshot.dockedWidth,
|
|
201
206
|
tabs: state.snapshot.tabs.slice(-DOC_BROWSER_MAX_PERSISTED_TABS),
|
|
202
207
|
activeTabId: state.snapshot.activeTabId,
|
|
203
208
|
activeHistory: state.snapshot.activeHistory,
|
|
@@ -76,6 +76,7 @@ export type DocBrowserOpenOptions = {
|
|
|
76
76
|
export type DocBrowserState = {
|
|
77
77
|
isOpen: boolean;
|
|
78
78
|
mode: DocBrowserMode;
|
|
79
|
+
dockedWidth: number;
|
|
79
80
|
tabs: DocBrowserTab[];
|
|
80
81
|
activeTabId: string;
|
|
81
82
|
activeHistory: DocBrowserActiveHistoryEntry[];
|
|
@@ -90,6 +91,7 @@ export type DocBrowserActions = {
|
|
|
90
91
|
openNewTab: () => void;
|
|
91
92
|
close: () => void;
|
|
92
93
|
toggleMode: () => void;
|
|
94
|
+
setDockedWidth: (width: number) => void;
|
|
93
95
|
navigate: (url: string) => void;
|
|
94
96
|
syncUrl: (url: string) => void;
|
|
95
97
|
goBack: () => void;
|