dsh-coding-sidebar 1.0.2 → 1.0.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/README.md +27 -42
- package/lib/client-editor.js +449 -4278
- package/lib/client-registry.js +641 -1343
- package/lib/client-terminal.js +172 -262
- package/lib/client.js +645 -1347
- package/lib/index.js +24 -122
- package/lib/types/bundle-route.d.ts +1 -1
- package/lib/types/client/SideCardSection.d.ts +3 -3
- package/lib/types/client/TextEditor.d.ts +20 -10
- package/lib/types/client/add-plugin-modal.d.ts +0 -4
- package/lib/types/client/api.d.ts +8 -11
- package/lib/types/client/builtins/index.d.ts +10 -10
- package/lib/types/client/chunk-loader.d.ts +7 -8
- package/lib/types/client/locales.d.ts +0 -26
- package/lib/types/client/native-avoid.d.ts +2 -0
- package/lib/types/client/plugins-shared.d.ts +6 -6
- package/lib/types/client/service.d.ts +13 -83
- package/lib/types/context-types.d.ts +19 -10
- package/lib/types/index.d.ts +1 -1
- package/lib/types/prefs-shared.d.ts +1 -24
- package/package.json +26 -30
- package/src/bundle-route.ts +1 -1
- package/src/client/EditorHost.tsx +52 -122
- package/src/client/SandboxStatusBar.tsx +12 -9
- package/src/client/SideCardSection.module.css +2 -2
- package/src/client/SideCardSection.tsx +28 -81
- package/src/client/SideChatView.module.css +12 -12
- package/src/client/TextEditor.tsx +89 -266
- package/src/client/add-plugin-modal.tsx +16 -24
- package/src/client/api.ts +1 -25
- package/src/client/builtins/index.ts +10 -14
- package/src/client/chunk-loader.ts +7 -8
- package/src/client/index.tsx +12 -3
- package/src/client/locales-ar.ts +0 -6
- package/src/client/locales-de.ts +0 -6
- package/src/client/locales-fr.ts +0 -6
- package/src/client/locales-hi.ts +0 -6
- package/src/client/locales-id.ts +0 -6
- package/src/client/locales-it.ts +0 -6
- package/src/client/locales-ja.ts +0 -6
- package/src/client/locales-ko.ts +0 -6
- package/src/client/locales-nl.ts +0 -6
- package/src/client/locales-pl.ts +0 -6
- package/src/client/locales-pt.ts +0 -6
- package/src/client/locales-ru.ts +0 -6
- package/src/client/locales-sv.ts +0 -6
- package/src/client/locales-th.ts +0 -6
- package/src/client/locales-tr.ts +0 -6
- package/src/client/locales-vi.ts +0 -6
- package/src/client/locales-zh-HK.ts +0 -6
- package/src/client/locales-zh-MO.ts +0 -6
- package/src/client/locales-zh-TW.ts +0 -6
- package/src/client/locales.ts +0 -52
- package/src/client/native-avoid.ts +155 -0
- package/src/client/plugins-shared.ts +6 -6
- package/src/client/prefs.ts +0 -7
- package/src/client/service.ts +13 -150
- package/src/client/sidebar.module.css +1 -425
- package/src/config.ts +1 -4
- package/src/context-types.ts +12 -7
- package/src/index.ts +12 -78
- package/src/jobs-routes.ts +3 -2
- package/src/prefs-shared.ts +1 -27
- package/src/sidechat-routes.ts +13 -8
- package/src/subagent-live-route.ts +2 -1
- package/lib/client-mermaid.js +0 -200753
- package/lib/types/client/MarkdownHtml.d.ts +0 -32
- package/lib/types/client/PdfView.d.ts +0 -6
- package/lib/types/client/builtins/viewers.d.ts +0 -3
- package/lib/types/client/chunks/mermaid.d.ts +0 -10
- package/lib/types/client/editor-load.d.ts +0 -66
- package/lib/types/client/image-types.d.ts +0 -3
- package/lib/types/client/markdown-html.d.ts +0 -96
- package/lib/types/client/markdown-images.d.ts +0 -45
- package/lib/types/client/md-toc.d.ts +0 -4
- package/lib/types/client/mermaid-blocks.d.ts +0 -47
- package/lib/types/client/mermaid-sanitize.d.ts +0 -2
- package/lib/types/client/mermaid.d.ts +0 -12
- package/lib/types/client/pdf-types.d.ts +0 -2
- package/lib/types/client/plugins-viewers.d.ts +0 -3
- package/lib/types/html-route.d.ts +0 -59
- package/src/client/MarkdownHtml.tsx +0 -296
- package/src/client/PdfView.tsx +0 -110
- package/src/client/builtins/viewers.tsx +0 -125
- package/src/client/chunks/mermaid.tsx +0 -10
- package/src/client/editor-load.ts +0 -92
- package/src/client/image-types.ts +0 -8
- package/src/client/markdown-html.ts +0 -331
- package/src/client/markdown-images.ts +0 -137
- package/src/client/md-toc.tsx +0 -127
- package/src/client/mermaid-blocks.ts +0 -110
- package/src/client/mermaid-sanitize.ts +0 -93
- package/src/client/mermaid.tsx +0 -406
- package/src/client/pdf-types.ts +0 -4
- package/src/client/plugins-viewers.ts +0 -30
- package/src/html-route.ts +0 -106
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The BetterSidebar client service: a registry that external plugins use
|
|
3
|
-
* to contribute sidebar tab types
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* to contribute sidebar tab types. The service is published to the cordis
|
|
4
|
+
* context as `ctx.betterSidebar` (see {@link ../context-types.ts});
|
|
5
|
+
* consumers declare it in `inject` and call `registerTab`, which returns a
|
|
6
|
+
* disposer that cordis auto-invokes on fiber disposal (HMR-safe).
|
|
7
|
+
*
|
|
8
|
+
* (v1.0.4: the file-viewer registry — `registerFileViewer` /
|
|
9
|
+
* `matchFileViewer` — was retired; file preview is the host's job now and
|
|
10
|
+
* the sidebar edits text only.)
|
|
8
11
|
*
|
|
9
12
|
* Design notes:
|
|
10
13
|
* - The registry is synchronous-snapshot (Map + listener set) so React
|
|
@@ -15,9 +18,6 @@
|
|
|
15
18
|
* `single: true` is sugar for `dedupeKey: () => id`.
|
|
16
19
|
* - `createTab` lets a descriptor own tab instantiation (the terminal
|
|
17
20
|
* builtin uses it to mint `terminal:<n>` ids and bump `nextTerminal`).
|
|
18
|
-
* - `matchFileViewer` walks descriptors in priority order (desc, stable):
|
|
19
|
-
* per descriptor it tries `detect` first (when `head` bytes are given),
|
|
20
|
-
* then `exts`; `exts: []` is a catch-all that matches any path.
|
|
21
21
|
*/
|
|
22
22
|
import type { ReactNode } from 'react';
|
|
23
23
|
import type { Context } from '../context-types.ts';
|
|
@@ -50,7 +50,7 @@ export interface SidebarSettingSelectOption {
|
|
|
50
50
|
* option's icon too; without icons both are a single line of text. */
|
|
51
51
|
icon?: ReactNode | ((size: number) => ReactNode);
|
|
52
52
|
}
|
|
53
|
-
/** One declarative setting of a tab
|
|
53
|
+
/** One declarative setting of a registered tab, rendered as a nested row in the
|
|
54
54
|
* Side card settings page (e.g. the Subagent page's "auto-open when a
|
|
55
55
|
* subagent appears" switch, or the terminal's custom font rows). `type`
|
|
56
56
|
* selects the control: 'switch' (default) renders the custom switch,
|
|
@@ -93,7 +93,7 @@ export interface SidebarSettingsRenderProps {
|
|
|
93
93
|
/** Close the settings popup. */
|
|
94
94
|
close(): void;
|
|
95
95
|
}
|
|
96
|
-
/** Declarative settings of one registered tab
|
|
96
|
+
/** Declarative settings of one registered tab. */
|
|
97
97
|
export interface SidebarSettingsDeclaration {
|
|
98
98
|
/**
|
|
99
99
|
* Extra settings rows rendered under the feature's own row in the
|
|
@@ -223,78 +223,17 @@ export interface TabDescriptor {
|
|
|
223
223
|
onClose?: (tab: SidebarTab, scope: SessionScope) => void;
|
|
224
224
|
component: (props: TabComponentProps) => ReactNode;
|
|
225
225
|
}
|
|
226
|
-
/**
|
|
227
|
-
export type FileFetchStrategy = 'none' | 'fsRead' | 'mediaUrl' | 'custom' | 'binary-download';
|
|
228
|
-
/** Props every file viewer component receives. */
|
|
229
|
-
export interface FileViewerProps {
|
|
230
|
-
ctx: Context;
|
|
231
|
-
store: SidebarStore;
|
|
232
|
-
scope: SessionScope;
|
|
233
|
-
path: string;
|
|
234
|
-
title: string;
|
|
235
|
-
/** The matching descriptor's id (`'code'`, `'my-plugin:csv'`). */
|
|
236
|
-
viewerId: string;
|
|
237
|
-
/** fsRead text content (fetchStrategy='fsRead'). */
|
|
238
|
-
content?: string;
|
|
239
|
-
truncated?: boolean;
|
|
240
|
-
/** mediaUrl for the path (fetchStrategy='mediaUrl'). */
|
|
241
|
-
mediaUrl?: string;
|
|
242
|
-
/** custom load() return value (fetchStrategy='custom'). */
|
|
243
|
-
customData?: unknown;
|
|
244
|
-
/** Internal (built-in text editor): 'host' asks the viewer to skip its own
|
|
245
|
-
* toolbar row — the editor host's merged-mode header renders it instead,
|
|
246
|
-
* fed through the two callbacks below. Viewers that ignore these fields
|
|
247
|
-
* render exactly as before. */
|
|
248
|
-
toolbar?: 'self' | 'host';
|
|
249
|
-
/** Internal: the viewer reports its toolbar state (mode/dirty/save). */
|
|
250
|
-
onToolbarState?: (state: EditorToolbarState) => void;
|
|
251
|
-
/** Internal: the viewer registers its toolbar commands on mount (null on
|
|
252
|
-
* unmount). */
|
|
253
|
-
onToolbarControls?: (controls: EditorToolbarControls | null) => void;
|
|
254
|
-
}
|
|
255
|
-
/** The toolbar state a text editor reports to the host's merged-mode header. */
|
|
226
|
+
/** The toolbar state the text editor reports to the host's merged-mode header. */
|
|
256
227
|
export interface EditorToolbarState {
|
|
257
|
-
/** Whether the preview/edit mode toggle applies (markdown/html). */
|
|
258
|
-
modes: boolean;
|
|
259
|
-
mode: 'preview' | 'edit';
|
|
260
228
|
dirty: boolean;
|
|
261
229
|
/** Whether saving applies (text content loaded). */
|
|
262
230
|
editable: boolean;
|
|
263
231
|
saveState: 'idle' | 'saving' | 'saved' | 'failed';
|
|
264
232
|
}
|
|
265
|
-
/** The commands the host's merged-mode header sends back to the
|
|
233
|
+
/** The commands the host's merged-mode header sends back to the editor. */
|
|
266
234
|
export interface EditorToolbarControls {
|
|
267
|
-
setMode(mode: 'preview' | 'edit'): void;
|
|
268
235
|
save(): void;
|
|
269
236
|
}
|
|
270
|
-
/** Describes one file previewer (builtins register themselves too). */
|
|
271
|
-
export interface FileViewerDescriptor {
|
|
272
|
-
/** Unique id (`'image'`, `'pdf'`, `'my-plugin:csv'`). */
|
|
273
|
-
id: string;
|
|
274
|
-
/** Display name for the settings inventory (falls back to `id` when absent). */
|
|
275
|
-
title?: string | (() => string);
|
|
276
|
-
/** Icon shown in the settings inventory. */
|
|
277
|
-
icon?: ReactNode | ((size: number) => ReactNode);
|
|
278
|
-
/** Lowercase extensions without leading dot (`['png','jpg']`). `[]` = match any (catch-all). */
|
|
279
|
-
exts: readonly string[];
|
|
280
|
-
/** Higher wins; default 0. Builtins use 0; the catch-all `code` viewer uses -100. */
|
|
281
|
-
priority?: number;
|
|
282
|
-
fetchStrategy: FileFetchStrategy;
|
|
283
|
-
/**
|
|
284
|
-
* Content sniff: when `head` bytes are available the descriptor's `detect`
|
|
285
|
-
* is consulted before its `exts` (per-descriptor, in priority order).
|
|
286
|
-
*/
|
|
287
|
-
detect?: (path: string, head: Uint8Array) => boolean;
|
|
288
|
-
/** fetchStrategy='custom' loader. `signal` (v0.12.0+) aborts on viewer
|
|
289
|
-
* teardown / re-match; loaders that ignore it keep working. */
|
|
290
|
-
load?: (path: string, scope: SessionScope, signal?: AbortSignal) => Promise<unknown>;
|
|
291
|
-
/**
|
|
292
|
-
* Declarative settings shown in the Side card settings page: every
|
|
293
|
-
* registered viewer gets an enable/disable switch (icon + title + exts).
|
|
294
|
-
*/
|
|
295
|
-
settings?: SidebarSettingsDeclaration;
|
|
296
|
-
component: (props: FileViewerProps) => ReactNode;
|
|
297
|
-
}
|
|
298
237
|
/** One `openTab` request. */
|
|
299
238
|
export interface OpenTabSeed {
|
|
300
239
|
type: string;
|
|
@@ -316,9 +255,7 @@ export interface OpenTabSeed {
|
|
|
316
255
|
*/
|
|
317
256
|
export interface BetterSidebarService {
|
|
318
257
|
registerTab(descriptor: TabDescriptor): () => void;
|
|
319
|
-
registerFileViewer(descriptor: FileViewerDescriptor): () => void;
|
|
320
258
|
getTabs(): readonly TabDescriptor[];
|
|
321
|
-
getFileViewers(): readonly FileViewerDescriptor[];
|
|
322
259
|
/** Find a tab descriptor by id (undefined if not registered). */
|
|
323
260
|
getTab(id: string): TabDescriptor | undefined;
|
|
324
261
|
/**
|
|
@@ -328,13 +265,6 @@ export interface BetterSidebarService {
|
|
|
328
265
|
* derived flows gate on it).
|
|
329
266
|
*/
|
|
330
267
|
isTabEnabled(id: string): boolean;
|
|
331
|
-
/** Whether a file viewer is enabled (absent `viewersEnabled[id]` = enabled). */
|
|
332
|
-
isViewerEnabled(id: string): boolean;
|
|
333
|
-
/**
|
|
334
|
-
* Find a file viewer for a path (priority desc; detect first, then exts).
|
|
335
|
-
* Disabled viewers are skipped, so files fall through to the next match.
|
|
336
|
-
*/
|
|
337
|
-
matchFileViewer(path: string, head?: Uint8Array): FileViewerDescriptor | undefined;
|
|
338
268
|
/**
|
|
339
269
|
* Open a tab (used by external tabs and the + menu). `title` overrides
|
|
340
270
|
* the descriptor's title when given (the editor tab shows the file name);
|
|
@@ -433,7 +363,7 @@ export declare const SIDEBAR_SERVICE_VERSION: string;
|
|
|
433
363
|
export declare const SIDEBAR_FEATURES: readonly ["badge", "tabLifecycle", "updateTab", "openFile", "targetedOpen", "stateSubscription", "tabMeta", "pluginSettings", "urlTarget", "settingSelect", "floatWindows"];
|
|
434
364
|
/**
|
|
435
365
|
* Create one BetterSidebar service bound to a store. The service owns the
|
|
436
|
-
* tab
|
|
366
|
+
* tab registry (Map + listener set) and proxies openTab/closeTab
|
|
437
367
|
* to the store's reducer. One instance per client plugin activation.
|
|
438
368
|
*/
|
|
439
369
|
export declare function createBetterSidebarService(store: SidebarStore): BetterSidebarService;
|
|
@@ -78,11 +78,11 @@ export interface SidebarSessionStore {
|
|
|
78
78
|
get(id: string): {
|
|
79
79
|
header: SidebarSessionHeader;
|
|
80
80
|
/**
|
|
81
|
-
* The live session's
|
|
81
|
+
* The live session's immutable event snapshot (0.1.5 Session API; absent
|
|
82
82
|
* on sessions the runtime has not hydrated). Read-only access — the
|
|
83
83
|
* jobs.output route replays `job_output` tool/result rows from it.
|
|
84
84
|
*/
|
|
85
|
-
|
|
85
|
+
snapshotEvents?(fromSeq?: number, toSeqExclusive?: number): readonly SidebarSessionEvent[];
|
|
86
86
|
} | undefined;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
@@ -276,16 +276,25 @@ export interface SidebarSessionTitleService {
|
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
278
|
/** The host session-persistence face (mirror of the sessionPersistence
|
|
279
|
-
* service
|
|
280
|
-
* recorded preset when a Side Chat
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
279
|
+
* service, 0.1.5 handle-seam form): a short-lived read handle over one
|
|
280
|
+
* persisted session, used to compose the recorded preset when a Side Chat
|
|
281
|
+
* thread cold-resumes. */
|
|
282
|
+
export interface SidebarSessionPersistenceHandle {
|
|
283
|
+
readonly header: {
|
|
284
|
+
cwd?: string;
|
|
285
|
+
agentPreset?: string;
|
|
286
|
+
};
|
|
287
|
+
read(fromSeq?: number, toSeqExclusive?: number, options?: {
|
|
288
|
+
signal?: AbortSignal;
|
|
289
|
+
}): Promise<{
|
|
287
290
|
events: readonly SidebarSessionEvent[];
|
|
288
291
|
}>;
|
|
292
|
+
close(): Promise<void>;
|
|
293
|
+
}
|
|
294
|
+
export interface SidebarSessionPersistenceService {
|
|
295
|
+
open(sessionId: string, access: 'read', options?: {
|
|
296
|
+
signal?: AbortSignal;
|
|
297
|
+
}): Promise<SidebarSessionPersistenceHandle>;
|
|
289
298
|
}
|
|
290
299
|
/** RPC result slot mirror (`RpcResult<T>` on the wire). */
|
|
291
300
|
export type SidebarRpcResult<T> = {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Config, type ResolvedSidebarConfig, type SidebarConfig } from './config
|
|
|
3
3
|
export { Config };
|
|
4
4
|
export type { SidebarConfig, ResolvedSidebarConfig };
|
|
5
5
|
export type { Context } from './context-types.ts';
|
|
6
|
-
export type { BetterSidebarService, TabDescriptor, TabComponentProps,
|
|
6
|
+
export type { BetterSidebarService, TabDescriptor, TabComponentProps, } from './client/service.ts';
|
|
7
7
|
/** Plugin identity for cordis.yml rows. */
|
|
8
8
|
export declare const name = "dsh-coding-sidebar";
|
|
9
9
|
/** Services required before mounting: the webserver routes, the session store, the web runtime's trusted hosts, and the tool registry. */
|
|
@@ -124,22 +124,6 @@ export interface SidebarPrefs {
|
|
|
124
124
|
* starts `strip` px below its top edge.
|
|
125
125
|
*/
|
|
126
126
|
titleBarStripPx: number;
|
|
127
|
-
/**
|
|
128
|
-
* Whether the HTML previewer drops its sandboxed iframe. Sandbox ON (the
|
|
129
|
-
* default) renders previewed HTML in an opaque-origin iframe that cannot
|
|
130
|
-
* touch the GUI; turning it OFF runs the previewed page with the GUI's
|
|
131
|
-
* own origin — full read/write access to session files and internal
|
|
132
|
-
* APIs. Only for trusted local content; the setting copy warns.
|
|
133
|
-
*/
|
|
134
|
-
htmlViewerNoSandbox: boolean;
|
|
135
|
-
/**
|
|
136
|
-
* Whether a newly opened HTML preview starts UNSANDBOXED (the per-surface
|
|
137
|
-
* temporary unlock pre-applied). Off by default: previews open sandboxed
|
|
138
|
-
* and the status row offers the one-tap unlock; when on, previews open
|
|
139
|
-
* in the red unsandboxed state and the status row offers a one-tap
|
|
140
|
-
* restore for the current file.
|
|
141
|
-
*/
|
|
142
|
-
htmlViewerDefaultUnsafe: boolean;
|
|
143
127
|
/**
|
|
144
128
|
* Whether the browser tab drops its sandboxed iframe. Sandbox ON (the
|
|
145
129
|
* default) keeps browsed sites in an opaque origin with no GUI access;
|
|
@@ -194,16 +178,9 @@ export interface SidebarPrefs {
|
|
|
194
178
|
* own layouts" rule.
|
|
195
179
|
*/
|
|
196
180
|
tabsEnabled: Record<string, boolean>;
|
|
197
|
-
/**
|
|
198
|
-
* Per-viewer enable switches, keyed by file viewer descriptor id
|
|
199
|
-
* (`'image'`, `'my-plugin:csv'`). An ABSENT key means enabled; a disabled
|
|
200
|
-
* viewer is skipped by `matchFileViewer` so files fall through to the
|
|
201
|
-
* next matching viewer (or the download button when none match).
|
|
202
|
-
*/
|
|
203
|
-
viewersEnabled: Record<string, boolean>;
|
|
204
181
|
/**
|
|
205
182
|
* Plugin-owned settings blobs (v0.12.0+), keyed by descriptor id: each
|
|
206
|
-
* registered tab
|
|
183
|
+
* registered tab that declares `settings.pluginToggles` (or writes
|
|
207
184
|
* through `settings.render`'s `updatePluginSetting`) persists its values
|
|
208
185
|
* here — an open map, so third-party keys need no host PrefsSchema field.
|
|
209
186
|
* Values are JSON-serializable (the row controls produce strings /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-coding-sidebar",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "DSH web plugin: a VSCode-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes the betterSidebar service for other plugins to register sidebar tabs and file viewers. KCoder-maintained fork of DSH-better-sidebar 0.17.2 (bottom panel removed, upstream-decoupled release line).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"lib/client-registry.js",
|
|
62
62
|
"lib/client-terminal.js",
|
|
63
63
|
"lib/client-editor.js",
|
|
64
|
-
"lib/client-mermaid.js",
|
|
65
64
|
"lib/types/**/*.d.ts",
|
|
66
65
|
"src",
|
|
67
66
|
"scripts/install.sh",
|
|
@@ -85,19 +84,18 @@
|
|
|
85
84
|
"license": "MIT",
|
|
86
85
|
"peerDependencies": {
|
|
87
86
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
88
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
89
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
90
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
91
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
94
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
95
|
-
"@deepseek-ai/dsh-
|
|
96
|
-
"@deepseek-ai/dsh-
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
99
|
-
"@deepseek-ai/dsh-
|
|
100
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
87
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.5-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-subagent": "^0.1.5-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
101
99
|
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
|
102
100
|
"react": "^18.2.0",
|
|
103
101
|
"react-dom": "^18.2.0"
|
|
@@ -131,8 +129,6 @@
|
|
|
131
129
|
"@codemirror/view": "^6.43.8",
|
|
132
130
|
"@lezer/highlight": "^1.2.3",
|
|
133
131
|
"clsx": "^2.1.1",
|
|
134
|
-
"dompurify": "^3.4.14",
|
|
135
|
-
"mermaid": "^11.16.1",
|
|
136
132
|
"node-pty": "^1.1.0",
|
|
137
133
|
"react-icons": "5.7.0",
|
|
138
134
|
"rxjs": "^7.8.2",
|
|
@@ -140,20 +136,20 @@
|
|
|
140
136
|
"ws": "^8.18.0"
|
|
141
137
|
},
|
|
142
138
|
"devDependencies": {
|
|
139
|
+
"@deepseek-ai/dsh-invariants": "0.1.5-rc.2",
|
|
143
140
|
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
|
|
144
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
145
|
-
"@deepseek-ai/dsh-client-locale": "0.1.
|
|
146
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.1.
|
|
147
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.
|
|
148
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.
|
|
149
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.
|
|
150
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
151
|
-
"@deepseek-ai/dsh-
|
|
152
|
-
"@deepseek-ai/dsh-
|
|
153
|
-
"@deepseek-ai/dsh-
|
|
154
|
-
"@deepseek-ai/dsh-
|
|
155
|
-
"@deepseek-ai/dsh-
|
|
156
|
-
"@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
|
|
141
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
142
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
143
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.2",
|
|
144
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
145
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
146
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
|
|
147
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
|
|
148
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
149
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
150
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
151
|
+
"@deepseek-ai/dsh-subagent": "0.1.5-rc.2",
|
|
152
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
157
153
|
"@huanlin/dsh-plugin-better-locale": "^0.1.0",
|
|
158
154
|
"@types/node": "^24.0.0",
|
|
159
155
|
"@types/react": "~18.3.1",
|
package/src/bundle-route.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
19
19
|
import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts'
|
|
20
20
|
|
|
21
21
|
/** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
|
|
22
|
-
export const CHUNK_NAMES = ['terminal', 'editor'
|
|
22
|
+
export const CHUNK_NAMES = ['terminal', 'editor'] as const
|
|
23
23
|
export type ChunkName = (typeof CHUNK_NAMES)[number]
|
|
24
24
|
|
|
25
25
|
/** Directory of this host-half module (lib/ — the chunk scripts live next to it). */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The editor tab host: the single FILES WINDOW. It
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* The editor tab host: the single FILES WINDOW. It fetches a file's text
|
|
3
|
+
* through the host fs.read — text renders in the (chunk-lazy) sidebar text
|
|
4
|
+
* editor, a binary file renders the shared download pane. (v1.0.4: the
|
|
5
|
+
* file-viewer registry/matching was retired — file preview is the host's
|
|
6
|
+
* job now, the sidebar edits text only.) A tab without a path (the seeded
|
|
7
|
+
* "Files" home) renders an empty-state hint instead of the loading flow;
|
|
8
|
+
* that path-less window IS the file explorer.
|
|
8
9
|
*
|
|
9
10
|
* The chrome depends on the `editorExplorer` mode (read reactively so
|
|
10
11
|
* toggling it re-renders without a reload):
|
|
@@ -18,18 +19,14 @@
|
|
|
18
19
|
* The tree's context menu offers the explicit escapes in both modes: open
|
|
19
20
|
* in a new tab (per-path dedupe) or to the side (a fresh tab in a fresh
|
|
20
21
|
* rightward split of the current pane).
|
|
21
|
-
*
|
|
22
|
-
* The strategy dispatch is pure (planFirstMatch / planFsReadOutcome in
|
|
23
|
-
* editor-load.ts); this component only wires it to the host APIs.
|
|
24
22
|
*/
|
|
25
23
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
|
|
26
|
-
import {
|
|
24
|
+
import type { ComponentType } from 'react'
|
|
27
25
|
import clsx from 'clsx'
|
|
28
26
|
import { IconCheckOutline16, IconFolderOpen16, IconRefreshOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
29
27
|
import type { Context } from '../context-types.ts'
|
|
30
|
-
import { api,
|
|
28
|
+
import { api, type SessionScope } from './api.ts'
|
|
31
29
|
import { BinaryDownload } from './binary-download.tsx'
|
|
32
|
-
import { planFirstMatch, planFsReadOutcome, type EditorLoadAction } from './editor-load.ts'
|
|
33
30
|
import { baseName } from './FileTree.tsx'
|
|
34
31
|
import { createFrameBatcher } from './frame-batcher.ts'
|
|
35
32
|
import { openSidebarFile } from './intercept.tsx'
|
|
@@ -39,16 +36,27 @@ import { TreePanel } from './TreePanel.tsx'
|
|
|
39
36
|
import { t } from './locales.ts'
|
|
40
37
|
import { relativeTo } from './paths.ts'
|
|
41
38
|
import { resolveSidebarPath } from './produced-files.ts'
|
|
42
|
-
import type { EditorToolbarControls, EditorToolbarState
|
|
39
|
+
import type { EditorToolbarControls, EditorToolbarState } from './service.ts'
|
|
40
|
+
import { lazyChunkComponent } from './lazy-chunk.tsx'
|
|
41
|
+
import type { TextEditorProps } from './TextEditor.tsx'
|
|
43
42
|
import { firstLeaf, insertLeafAt, leafWithTab, mintTabId, treeOf, type SidebarStore, type SidebarTab } from './state.ts'
|
|
44
43
|
import css from './sidebar.module.css'
|
|
45
44
|
|
|
46
45
|
type EditorLoad =
|
|
47
46
|
| { status: 'loading' }
|
|
48
47
|
| { status: 'error'; message: string }
|
|
49
|
-
| { status: 'ready';
|
|
48
|
+
| { status: 'ready'; content: string; truncated: boolean }
|
|
50
49
|
| { status: 'binary' }
|
|
51
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Lazy wrapper over the chunk-resident text editor: CodeMirror and the
|
|
53
|
+
* language packs are fetched only when a file tab first renders (see
|
|
54
|
+
* chunk-loader.ts). The `pick` function is module-level (stable identity —
|
|
55
|
+
* the wrapper effect depends on it); the cast bridges the chunk exports
|
|
56
|
+
* record to the prop shape.
|
|
57
|
+
*/
|
|
58
|
+
const LazyTextEditor = lazyChunkComponent<TextEditorProps>('editor', (mod) => mod.TextEditor as ComponentType<TextEditorProps> | undefined)
|
|
59
|
+
|
|
52
60
|
/** The docked tree panel's width bounds (drag-resize clamps into them). */
|
|
53
61
|
const TREE_WIDTH_DEFAULT = 240
|
|
54
62
|
const TREE_WIDTH_MIN = 160
|
|
@@ -103,11 +111,10 @@ export function EditorHost(props: {
|
|
|
103
111
|
}) {
|
|
104
112
|
const { ctx, store, scope, tab, expanded, revealed, onToggleDir, onReferenceFile } = props
|
|
105
113
|
const path = tab.path ?? ''
|
|
106
|
-
const title = tab.title
|
|
107
114
|
// A folder window: the model's `sidebar_open` (or any caller) opens a
|
|
108
115
|
// directory as an editor tab carrying `meta.dir: true` with the directory
|
|
109
116
|
// as its path. It renders the file tree rooted at that folder instead of
|
|
110
|
-
// the
|
|
117
|
+
// the file loading flow (a directory is not a file).
|
|
111
118
|
const isDir = metaOf(tab).dir === true
|
|
112
119
|
const [load, setLoad] = useState<EditorLoad>({ status: 'loading' })
|
|
113
120
|
// Manual refresh (issue #167): bumping the sequence re-runs the load effect
|
|
@@ -223,9 +230,8 @@ export function EditorHost(props: {
|
|
|
223
230
|
})
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
// The
|
|
227
|
-
// its state and registers its commands (
|
|
228
|
-
// without a toolbar — image, pdf, binary download).
|
|
233
|
+
// The editor's toolbar, hoisted into THIS header: the text editor reports
|
|
234
|
+
// its state and registers its commands (null while nothing is loaded).
|
|
229
235
|
const [toolbar, setToolbar] = useState<EditorToolbarState | null>(null)
|
|
230
236
|
const controlsRef = useRef<EditorToolbarControls | null>(null)
|
|
231
237
|
const onToolbarState = useCallback((next: EditorToolbarState) => {
|
|
@@ -279,77 +285,27 @@ export function EditorHost(props: {
|
|
|
279
285
|
|
|
280
286
|
useEffect(() => {
|
|
281
287
|
// A (re)load or a path-less tab clears any hoisted toolbar state — the
|
|
282
|
-
// fresh
|
|
288
|
+
// fresh editor re-registers its own.
|
|
283
289
|
setToolbar(null)
|
|
284
|
-
// The seeded home tab (no path) never loads a
|
|
290
|
+
// The seeded home tab (no path) never loads a file — the empty-state
|
|
285
291
|
// hint renders until the user picks a file. A folder tab never loads a
|
|
286
|
-
//
|
|
292
|
+
// file either — its tree is rooted at the folder.
|
|
287
293
|
if (showEmpty || isDir) return
|
|
288
294
|
let cancelled = false
|
|
289
|
-
// Aborts the matched viewer's `load` when the editor tears down (tab
|
|
290
|
-
// closed, path changed, session switched) or re-matches the viewer.
|
|
291
|
-
const controller = new AbortController()
|
|
292
295
|
setLoad({ status: 'loading' })
|
|
293
|
-
|
|
294
|
-
const apply = (action: EditorLoadAction): void => {
|
|
296
|
+
api.fsRead(scope, path).then((result) => {
|
|
295
297
|
if (cancelled) return
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return
|
|
300
|
-
case 'render':
|
|
301
|
-
setLoad({
|
|
302
|
-
status: 'ready',
|
|
303
|
-
viewer: action.viewer,
|
|
304
|
-
content: action.content,
|
|
305
|
-
truncated: action.truncated,
|
|
306
|
-
mediaUrl: action.mediaUrl,
|
|
307
|
-
customData: action.customData,
|
|
308
|
-
})
|
|
309
|
-
return
|
|
310
|
-
case 'customLoad':
|
|
311
|
-
void action.viewer.load?.(path, scope, controller.signal).then((data) => {
|
|
312
|
-
if (cancelled) return
|
|
313
|
-
setLoad({ status: 'ready', viewer: action.viewer, customData: data })
|
|
314
|
-
}).catch((error: unknown) => {
|
|
315
|
-
if (cancelled) return
|
|
316
|
-
setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
|
|
317
|
-
})
|
|
318
|
-
return
|
|
319
|
-
case 'fetchFsRead':
|
|
320
|
-
api.fsRead(scope, path).then((result) => {
|
|
321
|
-
if (cancelled) return
|
|
322
|
-
// Binary reads carry the head bytes for the detect re-match.
|
|
323
|
-
const outcome = planFsReadOutcome(action.viewer, {
|
|
324
|
-
binary: result.kind === 'binary',
|
|
325
|
-
content: result.kind === 'text' ? result.content : '',
|
|
326
|
-
truncated: result.truncated,
|
|
327
|
-
head: result.kind === 'binary' ? result.head : undefined,
|
|
328
|
-
}, (head) => ctx.get('betterSidebar')?.matchFileViewer(path, head), mediaUrlOf)
|
|
329
|
-
apply(outcome)
|
|
330
|
-
}).catch((error: unknown) => {
|
|
331
|
-
if (cancelled) return
|
|
332
|
-
setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
|
|
333
|
-
})
|
|
334
|
-
return
|
|
298
|
+
if (result.kind === 'binary') {
|
|
299
|
+
setLoad({ status: 'binary' })
|
|
300
|
+
return
|
|
335
301
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// a preview-mode Ctrl+S shows the fresh content immediately. Edit mode is
|
|
344
|
-
// left alone — reloading would remount the editor and drop the caret.
|
|
345
|
-
const prevSaveState = useRef<EditorToolbarState['saveState'] | undefined>(undefined)
|
|
346
|
-
useEffect(() => {
|
|
347
|
-
const current = toolbar?.saveState
|
|
348
|
-
if (prevSaveState.current !== 'saved' && current === 'saved' && toolbar?.mode === 'preview') {
|
|
349
|
-
setReloadSeq(sequence => sequence + 1)
|
|
350
|
-
}
|
|
351
|
-
prevSaveState.current = current
|
|
352
|
-
}, [toolbar?.saveState, toolbar?.mode])
|
|
302
|
+
setLoad({ status: 'ready', content: result.content, truncated: result.truncated })
|
|
303
|
+
}).catch((error: unknown) => {
|
|
304
|
+
if (cancelled) return
|
|
305
|
+
setLoad({ status: 'error', message: error instanceof Error ? error.message : String(error) })
|
|
306
|
+
})
|
|
307
|
+
return () => { cancelled = true }
|
|
308
|
+
}, [scope.sessionId, scope.cwd, path, showEmpty, isDir, reloadSeq])
|
|
353
309
|
|
|
354
310
|
const treeOpen = treeOpenOf(tab)
|
|
355
311
|
/** Persist the panel flag on the tab (survives reloads with the layout). */
|
|
@@ -392,33 +348,6 @@ export function EditorHost(props: {
|
|
|
392
348
|
<div className={css.editor}>
|
|
393
349
|
<div className={css.editorHeader}>
|
|
394
350
|
<EditorPathInput key={path} path={path} cwd={scope.cwd} onOpen={openFile} />
|
|
395
|
-
{toolbar?.modes === true && (
|
|
396
|
-
<div className={css.editorModeToggle}>
|
|
397
|
-
<button
|
|
398
|
-
type="button"
|
|
399
|
-
className={clsx(css.editorModeButton, toolbar.mode === 'preview' && css.editorModeActive)}
|
|
400
|
-
onClick={() => {
|
|
401
|
-
// Issue #167 part B: returning from edit to preview reloads so
|
|
402
|
-
// the preview renders the just-saved content. A dirty draft
|
|
403
|
-
// (or a failed save) suppresses the reload — the draft only
|
|
404
|
-
// lives in the editor instance and a remount would drop it.
|
|
405
|
-
if (toolbar.mode === 'edit' && toolbar.dirty !== true && toolbar.saveState !== 'failed') {
|
|
406
|
-
setReloadSeq(sequence => sequence + 1)
|
|
407
|
-
}
|
|
408
|
-
controlsRef.current?.setMode('preview')
|
|
409
|
-
}}
|
|
410
|
-
>
|
|
411
|
-
{t('preview')}
|
|
412
|
-
</button>
|
|
413
|
-
<button
|
|
414
|
-
type="button"
|
|
415
|
-
className={clsx(css.editorModeButton, toolbar.mode === 'edit' && css.editorModeActive)}
|
|
416
|
-
onClick={() => { controlsRef.current?.setMode('edit') }}
|
|
417
|
-
>
|
|
418
|
-
{t('edit')}
|
|
419
|
-
</button>
|
|
420
|
-
</div>
|
|
421
|
-
)}
|
|
422
351
|
{toolbar?.dirty === true && <span className={css.dirtyDot} title={t('unsaved')} />}
|
|
423
352
|
{toolbar?.editable === true && (
|
|
424
353
|
<button
|
|
@@ -462,18 +391,19 @@ export function EditorHost(props: {
|
|
|
462
391
|
{!showEmpty && load.status === 'loading' && <div className={css.editorPlaceholder}>{t('loading')}</div>}
|
|
463
392
|
{!showEmpty && load.status === 'error' && <div className={css.editorError}>{load.message}</div>}
|
|
464
393
|
{!showEmpty && load.status === 'binary' && <BinaryDownload scope={scope} path={path} />}
|
|
465
|
-
{!showEmpty && load.status === 'ready' &&
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
394
|
+
{!showEmpty && load.status === 'ready' && (
|
|
395
|
+
<LazyTextEditor
|
|
396
|
+
ctx={ctx}
|
|
397
|
+
store={store}
|
|
398
|
+
scope={scope}
|
|
399
|
+
path={path}
|
|
400
|
+
content={load.content}
|
|
401
|
+
truncated={load.truncated}
|
|
402
|
+
toolbar="host"
|
|
403
|
+
onToolbarState={onToolbarState}
|
|
404
|
+
onToolbarControls={onToolbarControls}
|
|
405
|
+
/>
|
|
406
|
+
)}
|
|
477
407
|
</div>
|
|
478
408
|
{treeOpen && (
|
|
479
409
|
<div className={css.editorTreeDock} style={{ width: treeWidth }}>
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The live sandbox status row of the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The live sandbox status row of the built-in browser tab: a green
|
|
3
|
+
* "sandbox on" state with a one-tap TEMPORARY unlock, or a RED "sandbox
|
|
4
|
+
* off" state (global setting or the temporary unlock) with a restore
|
|
5
|
+
* action.
|
|
6
6
|
*
|
|
7
7
|
* The temporary unlock is component state only — it never writes the
|
|
8
|
-
* global side card setting (`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* global side card setting (`browserNoSandbox`); it lasts until the
|
|
9
|
+
* surface unmounts (tab switch) or the user restores the sandbox from the
|
|
10
|
+
* row. When the global setting already drops the sandbox, no
|
|
11
|
+
* unlock/restore action is offered (changing the global setting is the
|
|
12
|
+
* settings page's job) — the red warning stands.
|
|
13
|
+
*
|
|
14
|
+
* (v1.0.4: the HTML preview surface was retired with the file-viewer line;
|
|
15
|
+
* this row now serves the browser tab only.)
|
|
13
16
|
*/
|
|
14
17
|
import clsx from 'clsx'
|
|
15
18
|
import { t } from './locales.ts'
|