@nextclaw/ui 0.5.37 → 0.5.38

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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/{ChannelsList-DtvhbEV9.js → ChannelsList-3B_zyiKA.js} +1 -1
  3. package/dist/assets/{ChatPage-Bw_aXB4R.js → ChatPage-DusH09PT.js} +1 -1
  4. package/dist/assets/{CronConfig-BZLXcDbm.js → CronConfig-5GTz5wPt.js} +1 -1
  5. package/dist/assets/DocBrowser-BtqGmg0N.js +1 -0
  6. package/dist/assets/MarketplacePage-BEW4M9BT.js +49 -0
  7. package/dist/assets/{ModelConfig-Bi8Q4_NG.js → ModelConfig-CwxXYqME.js} +1 -1
  8. package/dist/assets/{ProvidersList-D2OB0siE.js → ProvidersList-D4oaYHpJ.js} +1 -1
  9. package/dist/assets/{RuntimeConfig-Bz9aUkwu.js → RuntimeConfig-BwTxGi_U.js} +1 -1
  10. package/dist/assets/{SecretsConfig-Bqi-biOL.js → SecretsConfig-x36MY4ym.js} +1 -1
  11. package/dist/assets/{SessionsConfig-DcWT2QvI.js → SessionsConfig-qEffYDZ0.js} +1 -1
  12. package/dist/assets/{card-DwZkVl7S.js → card-Bq6uwDJQ.js} +1 -1
  13. package/dist/assets/index-DMEuanmd.css +1 -0
  14. package/dist/assets/index-wB2uPrKu.js +2 -0
  15. package/dist/assets/{label-BBDuC6Nm.js → label-Cq1vSfWg.js} +1 -1
  16. package/dist/assets/{logos-DMFt4YDI.js → logos-BKBMs40Q.js} +1 -1
  17. package/dist/assets/{page-layout-hPFzCUTQ.js → page-layout-D8MW2vP-.js} +1 -1
  18. package/dist/assets/{switch-CwkcbkEs.js → switch-CycMxy31.js} +1 -1
  19. package/dist/assets/{tabs-custom-TUrWRyYy.js → tabs-custom-N4olWJSw.js} +1 -1
  20. package/dist/assets/{useConfig-DZVUrqQz.js → useConfig-tR_KAfMV.js} +1 -1
  21. package/dist/assets/{useConfirmDialog-D5X0Iqid.js → useConfirmDialog-DE0Yp8Ai.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +1 -1
  24. package/src/api/marketplace.ts +24 -0
  25. package/src/api/types.ts +28 -0
  26. package/src/components/config/ModelConfig.tsx +1 -0
  27. package/src/components/config/ProviderForm.tsx +1 -0
  28. package/src/components/doc-browser/DocBrowser.tsx +382 -323
  29. package/src/components/doc-browser/DocBrowserContext.tsx +389 -157
  30. package/src/components/layout/Sidebar.tsx +1 -1
  31. package/src/components/marketplace/MarketplacePage.tsx +252 -12
  32. package/src/lib/i18n.ts +21 -2
  33. package/dist/assets/DocBrowser-BY0TiFOc.js +0 -1
  34. package/dist/assets/MarketplacePage-BDlAw7fO.js +0 -1
  35. package/dist/assets/index-C1NAfZSm.js +0 -2
  36. package/dist/assets/index-DWgSvrx4.css +0 -1
@@ -4,203 +4,435 @@ import { getLanguage, type I18nLanguage } from '@/lib/i18n';
4
4
  const DOCS_PRIMARY_DOMAIN = 'docs.nextclaw.io';
5
5
  const DOCS_PAGES_DEV = 'nextclaw-docs.pages.dev';
6
6
  const DOCS_HOSTS = new Set([
7
- DOCS_PRIMARY_DOMAIN,
8
- `www.${DOCS_PRIMARY_DOMAIN}`,
9
- DOCS_PAGES_DEV,
10
- `www.${DOCS_PAGES_DEV}`,
7
+ DOCS_PRIMARY_DOMAIN,
8
+ `www.${DOCS_PRIMARY_DOMAIN}`,
9
+ DOCS_PAGES_DEV,
10
+ `www.${DOCS_PAGES_DEV}`,
11
11
  ]);
12
12
 
13
13
  export const DOCS_DEFAULT_BASE_URL = `https://${DOCS_PRIMARY_DOMAIN}`;
14
14
  const DOCS_DEFAULT_GUIDE_PATH = '/guide/getting-started';
15
15
 
16
16
  export type DocBrowserMode = 'floating' | 'docked';
17
+ export type DocBrowserTabKind = 'docs' | 'content';
18
+
19
+ export type DocBrowserTab = {
20
+ id: string;
21
+ kind: DocBrowserTabKind;
22
+ title: string;
23
+ currentUrl: string;
24
+ history: string[];
25
+ historyIndex: number;
26
+ /** Increments on parent-initiated navigation to trigger iframe remount */
27
+ navVersion: number;
28
+ };
29
+
30
+ export type DocBrowserOpenOptions = {
31
+ newTab?: boolean;
32
+ title?: string;
33
+ kind?: DocBrowserTabKind;
34
+ };
35
+
36
+ type DocBrowserState = {
37
+ isOpen: boolean;
38
+ mode: DocBrowserMode;
39
+ tabs: DocBrowserTab[];
40
+ activeTabId: string;
41
+ };
42
+
43
+ interface DocBrowserActions {
44
+ open: (url?: string, options?: DocBrowserOpenOptions) => void;
45
+ close: () => void;
46
+ toggleMode: () => void;
47
+ setMode: (mode: DocBrowserMode) => void;
48
+ navigate: (url: string) => void;
49
+ syncUrl: (url: string) => void;
50
+ goBack: () => void;
51
+ goForward: () => void;
52
+ openNewTab: (url?: string, options?: Omit<DocBrowserOpenOptions, 'newTab'>) => void;
53
+ closeTab: (tabId: string) => void;
54
+ setActiveTab: (tabId: string) => void;
55
+ canGoBack: boolean;
56
+ canGoForward: boolean;
57
+ }
58
+
59
+ type DocBrowserContextValue = DocBrowserState & DocBrowserActions & {
60
+ currentUrl: string;
61
+ currentTab?: DocBrowserTab;
62
+ navVersion: number;
63
+ };
64
+
65
+ const DocBrowserContext = createContext<DocBrowserContextValue | null>(null);
66
+
67
+ let tabCounter = 0;
68
+
69
+ function nextTabId(): string {
70
+ tabCounter += 1;
71
+ return `doc-tab-${Date.now()}-${tabCounter}`;
72
+ }
17
73
 
18
74
  /** Normalize URL for comparison: strip .html and trailing slash */
19
75
  function normalizeDocUrl(u: string): string {
20
- try { return new URL(u).pathname.replace(/\.html$/, '').replace(/\/$/, ''); } catch { return u; }
76
+ try {
77
+ return new URL(u).pathname.replace(/\.html$/, '').replace(/\/$/, '');
78
+ } catch {
79
+ return u;
80
+ }
21
81
  }
22
82
 
23
83
  function toDocsLocale(language: I18nLanguage): 'en' | 'zh' {
24
- return language === 'zh' ? 'zh' : 'en';
84
+ return language === 'zh' ? 'zh' : 'en';
25
85
  }
26
86
 
27
87
  function ensureLocalizedDocsPath(pathname: string, locale: 'en' | 'zh'): string {
28
- const normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;
88
+ const normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;
29
89
 
30
- if (normalized === '/' || normalized === '') {
31
- return `/${locale}/`;
32
- }
90
+ if (normalized === '/' || normalized === '') {
91
+ return `/${locale}/`;
92
+ }
33
93
 
34
- if (/^\/(en|zh)(\/|$)/.test(normalized)) {
35
- return normalized;
36
- }
94
+ if (/^\/(en|zh)(\/|$)/.test(normalized)) {
95
+ return normalized;
96
+ }
37
97
 
38
- return `/${locale}${normalized}`;
98
+ return `/${locale}${normalized}`;
39
99
  }
40
100
 
41
101
  function resolveLocalizedDocsUrl(url: string): string {
42
- const locale = toDocsLocale(getLanguage());
102
+ const locale = toDocsLocale(getLanguage());
43
103
 
44
- try {
45
- const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
46
- if (!DOCS_HOSTS.has(parsed.hostname)) {
47
- return parsed.toString();
48
- }
49
-
50
- parsed.pathname = ensureLocalizedDocsPath(parsed.pathname, locale);
51
- return parsed.toString();
52
- } catch {
53
- return new URL(`/${locale}${DOCS_DEFAULT_GUIDE_PATH}`, DOCS_DEFAULT_BASE_URL).toString();
104
+ try {
105
+ const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
106
+ if (!DOCS_HOSTS.has(parsed.hostname)) {
107
+ return parsed.toString();
54
108
  }
109
+
110
+ parsed.pathname = ensureLocalizedDocsPath(parsed.pathname, locale);
111
+ return parsed.toString();
112
+ } catch {
113
+ return new URL(`/${locale}${DOCS_DEFAULT_GUIDE_PATH}`, DOCS_DEFAULT_BASE_URL).toString();
114
+ }
55
115
  }
56
116
 
57
117
  function getDefaultDocsUrl(): string {
58
- return resolveLocalizedDocsUrl(DOCS_DEFAULT_GUIDE_PATH);
118
+ return resolveLocalizedDocsUrl(DOCS_DEFAULT_GUIDE_PATH);
59
119
  }
60
120
 
61
- interface DocBrowserState {
62
- isOpen: boolean;
63
- mode: DocBrowserMode;
64
- currentUrl: string;
65
- history: string[];
66
- historyIndex: number;
67
- /** Increments on parent-initiated navigation (navigate/goBack/goForward) */
68
- navVersion: number;
121
+ function inferTabTitle(url: string, kind: DocBrowserTabKind, fallback = 'Docs'): string {
122
+ try {
123
+ const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
124
+ if (parsed.protocol === 'data:') {
125
+ return kind === 'docs' ? fallback : 'Detail';
126
+ }
127
+
128
+ const segments = parsed.pathname.split('/').filter(Boolean);
129
+ const leaf = segments[segments.length - 1] ?? fallback;
130
+ return decodeURIComponent(leaf).replace(/[-_]/g, ' ').slice(0, 40) || fallback;
131
+ } catch {
132
+ return fallback;
133
+ }
69
134
  }
70
135
 
71
- interface DocBrowserActions {
72
- open: (url?: string) => void;
73
- close: () => void;
74
- toggleMode: () => void;
75
- setMode: (mode: DocBrowserMode) => void;
76
- /** Parent-initiated navigation — will cause iframe to reload to this URL */
77
- navigate: (url: string) => void;
78
- /** Iframe-initiated sync — records URL to history without reloading iframe */
79
- syncUrl: (url: string) => void;
80
- goBack: () => void;
81
- goForward: () => void;
82
- canGoBack: boolean;
83
- canGoForward: boolean;
84
- }
85
-
86
- type DocBrowserContextValue = DocBrowserState & DocBrowserActions;
136
+ function createTab(url: string, kind: DocBrowserTabKind, title?: string): DocBrowserTab {
137
+ const tabTitle = title?.trim() || inferTabTitle(url, kind, kind === 'docs' ? 'Docs' : 'Detail');
87
138
 
88
- const DocBrowserContext = createContext<DocBrowserContextValue | null>(null);
139
+ return {
140
+ id: nextTabId(),
141
+ kind,
142
+ title: tabTitle,
143
+ currentUrl: url,
144
+ history: [url],
145
+ historyIndex: 0,
146
+ navVersion: 0,
147
+ };
148
+ }
149
+
150
+ function updateActiveTab(state: DocBrowserState, updater: (tab: DocBrowserTab) => DocBrowserTab): DocBrowserState {
151
+ return {
152
+ ...state,
153
+ tabs: state.tabs.map((tab) => (tab.id === state.activeTabId ? updater(tab) : tab)),
154
+ };
155
+ }
89
156
 
90
157
  export function useDocBrowser(): DocBrowserContextValue {
91
- const ctx = useContext(DocBrowserContext);
92
- if (!ctx) throw new Error('useDocBrowser must be used within DocBrowserProvider');
93
- return ctx;
158
+ const ctx = useContext(DocBrowserContext);
159
+ if (!ctx) throw new Error('useDocBrowser must be used within DocBrowserProvider');
160
+ return ctx;
94
161
  }
95
162
 
96
163
  /** Check if a URL belongs to the docs domain */
97
164
  export function isDocsUrl(url: string): boolean {
98
- try {
99
- const parsed = new URL(url, window.location.origin);
100
- return DOCS_HOSTS.has(parsed.hostname);
101
- } catch {
102
- return false;
103
- }
165
+ try {
166
+ const parsed = new URL(url, window.location.origin);
167
+ return DOCS_HOSTS.has(parsed.hostname);
168
+ } catch {
169
+ return false;
170
+ }
171
+ }
172
+
173
+ function inferTabKind(url: string): DocBrowserTabKind {
174
+ return isDocsUrl(url) ? 'docs' : 'content';
175
+ }
176
+
177
+ function normalizeUrlByKind(url: string, kind: DocBrowserTabKind): string {
178
+ if (kind === 'docs') {
179
+ return resolveLocalizedDocsUrl(url);
180
+ }
181
+ return url;
182
+ }
183
+
184
+ function resolveOpenTargetUrl(params: {
185
+ url?: string;
186
+ kind: DocBrowserTabKind;
187
+ activeTab?: DocBrowserTab;
188
+ }): string {
189
+ if (params.url && params.url.trim().length > 0) {
190
+ return normalizeUrlByKind(params.url, params.kind);
191
+ }
192
+
193
+ if (params.kind === 'docs') {
194
+ return getDefaultDocsUrl();
195
+ }
196
+
197
+ return params.activeTab?.currentUrl ?? getDefaultDocsUrl();
104
198
  }
105
199
 
106
200
  export function DocBrowserProvider({ children }: { children: ReactNode }) {
107
- const [state, setState] = useState<DocBrowserState>({
108
- isOpen: false,
109
- mode: 'docked',
110
- currentUrl: getDefaultDocsUrl(),
111
- history: [getDefaultDocsUrl()],
112
- historyIndex: 0,
113
- navVersion: 0,
201
+ const initialUrl = getDefaultDocsUrl();
202
+ const initialTab = createTab(initialUrl, 'docs', 'Docs');
203
+
204
+ const [state, setState] = useState<DocBrowserState>({
205
+ isOpen: false,
206
+ mode: 'docked',
207
+ tabs: [initialTab],
208
+ activeTabId: initialTab.id,
209
+ });
210
+
211
+ const currentTab = useMemo(() => {
212
+ return state.tabs.find((tab) => tab.id === state.activeTabId) ?? state.tabs[0];
213
+ }, [state.tabs, state.activeTabId]);
214
+
215
+ const open = useCallback((url?: string, options?: DocBrowserOpenOptions) => {
216
+ setState((prev) => {
217
+ const activeTab = prev.tabs.find((tab) => tab.id === prev.activeTabId) ?? prev.tabs[0];
218
+ const targetKind = options?.kind ?? (url ? inferTabKind(url) : activeTab?.kind ?? 'docs');
219
+ const targetUrl = resolveOpenTargetUrl({
220
+ url,
221
+ kind: targetKind,
222
+ activeTab
223
+ });
224
+
225
+ const shouldOpenNewTab = Boolean(options?.newTab || !activeTab || activeTab.kind !== targetKind);
226
+
227
+ if (shouldOpenNewTab) {
228
+ const newTab = createTab(targetUrl, targetKind, options?.title);
229
+ return {
230
+ ...prev,
231
+ isOpen: true,
232
+ tabs: [...prev.tabs, newTab],
233
+ activeTabId: newTab.id,
234
+ };
235
+ }
236
+
237
+ const next = updateActiveTab(prev, (tab) => {
238
+ if (normalizeDocUrl(targetUrl) === normalizeDocUrl(tab.currentUrl)) {
239
+ return options?.title ? { ...tab, title: options.title } : tab;
240
+ }
241
+
242
+ return {
243
+ ...tab,
244
+ title: options?.title || tab.title,
245
+ kind: targetKind,
246
+ currentUrl: targetUrl,
247
+ history: [...tab.history.slice(0, tab.historyIndex + 1), targetUrl],
248
+ historyIndex: tab.historyIndex + 1,
249
+ navVersion: tab.navVersion + 1,
250
+ };
251
+ });
252
+
253
+ return {
254
+ ...next,
255
+ isOpen: true,
256
+ };
257
+ });
258
+ }, []);
259
+
260
+ const close = useCallback(() => {
261
+ setState((prev) => ({ ...prev, isOpen: false }));
262
+ }, []);
263
+
264
+ const toggleMode = useCallback(() => {
265
+ setState((prev) => ({ ...prev, mode: prev.mode === 'floating' ? 'docked' : 'floating' }));
266
+ }, []);
267
+
268
+ const setMode = useCallback((mode: DocBrowserMode) => {
269
+ setState((prev) => ({ ...prev, mode }));
270
+ }, []);
271
+
272
+ const navigate = useCallback((url: string) => {
273
+ setState((prev) => {
274
+ if (!prev.tabs.length) {
275
+ const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
276
+ return {
277
+ ...prev,
278
+ tabs: [fallbackTab],
279
+ activeTabId: fallbackTab.id,
280
+ isOpen: true,
281
+ };
282
+ }
283
+
284
+ return updateActiveTab(prev, (tab) => {
285
+ if (tab.kind !== 'docs') {
286
+ return tab;
287
+ }
288
+
289
+ const targetUrl = normalizeUrlByKind(url, 'docs');
290
+ if (normalizeDocUrl(targetUrl) === normalizeDocUrl(tab.currentUrl)) {
291
+ return tab;
292
+ }
293
+
294
+ return {
295
+ ...tab,
296
+ currentUrl: targetUrl,
297
+ history: [...tab.history.slice(0, tab.historyIndex + 1), targetUrl],
298
+ historyIndex: tab.historyIndex + 1,
299
+ navVersion: tab.navVersion + 1,
300
+ };
301
+ });
114
302
  });
303
+ }, []);
304
+
305
+ const syncUrl = useCallback((url: string) => {
306
+ setState((prev) => {
307
+ if (!prev.tabs.length) {
308
+ const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
309
+ return {
310
+ ...prev,
311
+ tabs: [fallbackTab],
312
+ activeTabId: fallbackTab.id,
313
+ };
314
+ }
315
+
316
+ return updateActiveTab(prev, (tab) => {
317
+ if (tab.kind !== 'docs') {
318
+ return tab;
319
+ }
320
+
321
+ if (normalizeDocUrl(url) === normalizeDocUrl(tab.currentUrl)) {
322
+ return tab;
323
+ }
324
+
325
+ return {
326
+ ...tab,
327
+ currentUrl: url,
328
+ history: [...tab.history.slice(0, tab.historyIndex + 1), url],
329
+ historyIndex: tab.historyIndex + 1,
330
+ };
331
+ });
332
+ });
333
+ }, []);
334
+
335
+ const goBack = useCallback(() => {
336
+ setState((prev) => updateActiveTab(prev, (tab) => {
337
+ if (tab.kind !== 'docs' || tab.historyIndex <= 0) return tab;
338
+ const newIndex = tab.historyIndex - 1;
339
+ return { ...tab, historyIndex: newIndex, currentUrl: tab.history[newIndex] };
340
+ }));
341
+ }, []);
342
+
343
+ const goForward = useCallback(() => {
344
+ setState((prev) => updateActiveTab(prev, (tab) => {
345
+ if (tab.kind !== 'docs' || tab.historyIndex >= tab.history.length - 1) return tab;
346
+ const newIndex = tab.historyIndex + 1;
347
+ return { ...tab, historyIndex: newIndex, currentUrl: tab.history[newIndex] };
348
+ }));
349
+ }, []);
350
+
351
+ const openNewTab = useCallback((url?: string, options?: Omit<DocBrowserOpenOptions, 'newTab'>) => {
352
+ open(url, { ...(options ?? {}), newTab: true });
353
+ }, [open]);
354
+
355
+ const closeTab = useCallback((tabId: string) => {
356
+ setState((prev) => {
357
+ if (prev.tabs.length <= 1) {
358
+ const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
359
+ return {
360
+ ...prev,
361
+ isOpen: false,
362
+ tabs: [fallbackTab],
363
+ activeTabId: fallbackTab.id,
364
+ };
365
+ }
366
+
367
+ const index = prev.tabs.findIndex((tab) => tab.id === tabId);
368
+ if (index < 0) {
369
+ return prev;
370
+ }
371
+
372
+ const nextTabs = prev.tabs.filter((tab) => tab.id !== tabId);
373
+ const nextActiveId = prev.activeTabId === tabId
374
+ ? nextTabs[Math.max(0, index - 1)]?.id ?? nextTabs[0].id
375
+ : prev.activeTabId;
376
+
377
+ return {
378
+ ...prev,
379
+ tabs: nextTabs,
380
+ activeTabId: nextActiveId,
381
+ };
382
+ });
383
+ }, []);
384
+
385
+ const setActiveTab = useCallback((tabId: string) => {
386
+ setState((prev) => {
387
+ if (!prev.tabs.some((tab) => tab.id === tabId)) {
388
+ return prev;
389
+ }
390
+ return { ...prev, activeTabId: tabId, isOpen: true };
391
+ });
392
+ }, []);
393
+
394
+ const canGoBack = Boolean(currentTab && currentTab.kind === 'docs' && currentTab.historyIndex > 0);
395
+ const canGoForward = Boolean(currentTab && currentTab.kind === 'docs' && currentTab.historyIndex < currentTab.history.length - 1);
396
+
397
+ const value = useMemo<DocBrowserContextValue>(() => ({
398
+ ...state,
399
+ currentTab,
400
+ currentUrl: currentTab?.currentUrl ?? getDefaultDocsUrl(),
401
+ navVersion: currentTab?.navVersion ?? 0,
402
+ open,
403
+ close,
404
+ toggleMode,
405
+ setMode,
406
+ navigate,
407
+ syncUrl,
408
+ goBack,
409
+ goForward,
410
+ openNewTab,
411
+ closeTab,
412
+ setActiveTab,
413
+ canGoBack,
414
+ canGoForward,
415
+ }), [
416
+ state,
417
+ currentTab,
418
+ open,
419
+ close,
420
+ toggleMode,
421
+ setMode,
422
+ navigate,
423
+ syncUrl,
424
+ goBack,
425
+ goForward,
426
+ openNewTab,
427
+ closeTab,
428
+ setActiveTab,
429
+ canGoBack,
430
+ canGoForward,
431
+ ]);
115
432
 
116
- const open = useCallback((url?: string) => {
117
- const targetUrl = resolveLocalizedDocsUrl(url || state.currentUrl || getDefaultDocsUrl());
118
- setState(prev => ({
119
- ...prev,
120
- isOpen: true,
121
- currentUrl: targetUrl,
122
- history: [...prev.history.slice(0, prev.historyIndex + 1), targetUrl],
123
- historyIndex: prev.historyIndex + 1,
124
- navVersion: prev.navVersion + 1,
125
- }));
126
- }, [state.currentUrl]);
127
-
128
- const close = useCallback(() => {
129
- setState(prev => ({ ...prev, isOpen: false }));
130
- }, []);
131
-
132
- const toggleMode = useCallback(() => {
133
- setState(prev => ({ ...prev, mode: prev.mode === 'floating' ? 'docked' : 'floating' }));
134
- }, []);
135
-
136
- const setMode = useCallback((mode: DocBrowserMode) => {
137
- setState(prev => ({ ...prev, mode }));
138
- }, []);
139
-
140
- /** Parent-initiated: push to history AND bump navVersion so iframe reloads */
141
- const navigate = useCallback((url: string) => {
142
- const targetUrl = resolveLocalizedDocsUrl(url);
143
- setState(prev => {
144
- if (normalizeDocUrl(targetUrl) === normalizeDocUrl(prev.currentUrl)) return prev;
145
- return {
146
- ...prev,
147
- currentUrl: targetUrl,
148
- history: [...prev.history.slice(0, prev.historyIndex + 1), targetUrl],
149
- historyIndex: prev.historyIndex + 1,
150
- navVersion: prev.navVersion + 1,
151
- };
152
- });
153
- }, []);
154
-
155
- /** Iframe-initiated: push to history but do NOT bump navVersion (no iframe reload) */
156
- const syncUrl = useCallback((url: string) => {
157
- setState(prev => {
158
- if (normalizeDocUrl(url) === normalizeDocUrl(prev.currentUrl)) return prev;
159
- return {
160
- ...prev,
161
- currentUrl: url,
162
- history: [...prev.history.slice(0, prev.historyIndex + 1), url],
163
- historyIndex: prev.historyIndex + 1,
164
- };
165
- });
166
- }, []);
167
-
168
- const goBack = useCallback(() => {
169
- setState(prev => {
170
- if (prev.historyIndex <= 0) return prev;
171
- const newIndex = prev.historyIndex - 1;
172
- return { ...prev, historyIndex: newIndex, currentUrl: prev.history[newIndex] };
173
- });
174
- }, []);
175
-
176
- const goForward = useCallback(() => {
177
- setState(prev => {
178
- if (prev.historyIndex >= prev.history.length - 1) return prev;
179
- const newIndex = prev.historyIndex + 1;
180
- return { ...prev, historyIndex: newIndex, currentUrl: prev.history[newIndex] };
181
- });
182
- }, []);
183
-
184
- const canGoBack = state.historyIndex > 0;
185
- const canGoForward = state.historyIndex < state.history.length - 1;
186
-
187
- const value = useMemo<DocBrowserContextValue>(() => ({
188
- ...state,
189
- open,
190
- close,
191
- toggleMode,
192
- setMode,
193
- navigate,
194
- syncUrl,
195
- goBack,
196
- goForward,
197
- canGoBack,
198
- canGoForward,
199
- }), [state, open, close, toggleMode, setMode, navigate, syncUrl, goBack, goForward, canGoBack, canGoForward]);
200
-
201
- return (
202
- <DocBrowserContext.Provider value={value}>
203
- {children}
204
- </DocBrowserContext.Provider>
205
- );
433
+ return (
434
+ <DocBrowserContext.Provider value={value}>
435
+ {children}
436
+ </DocBrowserContext.Provider>
437
+ );
206
438
  }
@@ -167,7 +167,7 @@ export function Sidebar() {
167
167
  </Select>
168
168
  </div>
169
169
  <button
170
- onClick={() => docBrowser.open()}
170
+ onClick={() => docBrowser.open(undefined, { kind: 'docs', newTab: true, title: 'Docs' })}
171
171
  className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-800"
172
172
  >
173
173
  <BookOpen className="h-[17px] w-[17px] text-gray-400" />