dsh-coding-sidebar 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +27 -42
  2. package/lib/client-editor.js +315 -4179
  3. package/lib/client-registry.js +913 -1303
  4. package/lib/client-terminal.js +189 -261
  5. package/lib/client.js +911 -1301
  6. package/lib/index.js +13 -110
  7. package/lib/types/bundle-route.d.ts +1 -1
  8. package/lib/types/client/LiveView.d.ts +1 -0
  9. package/lib/types/client/SideCardSection.d.ts +3 -3
  10. package/lib/types/client/TextEditor.d.ts +20 -10
  11. package/lib/types/client/add-plugin-modal.d.ts +0 -4
  12. package/lib/types/client/api.d.ts +16 -11
  13. package/lib/types/client/builtins/index.d.ts +10 -10
  14. package/lib/types/client/chunk-loader.d.ts +7 -8
  15. package/lib/types/client/locales.d.ts +6 -26
  16. package/lib/types/client/native-avoid.d.ts +2 -0
  17. package/lib/types/client/plugins-shared.d.ts +6 -6
  18. package/lib/types/client/service.d.ts +13 -83
  19. package/lib/types/index.d.ts +1 -1
  20. package/lib/types/prefs-shared.d.ts +1 -24
  21. package/lib/types/wire.d.ts +1 -1
  22. package/package.json +26 -29
  23. package/src/bundle-route.ts +1 -1
  24. package/src/client/BrowserView.tsx +18 -2
  25. package/src/client/EditorHost.tsx +52 -122
  26. package/src/client/LiveView.tsx +229 -0
  27. package/src/client/SandboxStatusBar.tsx +12 -9
  28. package/src/client/SideCardSection.tsx +28 -81
  29. package/src/client/TextEditor.tsx +89 -266
  30. package/src/client/add-plugin-modal.tsx +16 -24
  31. package/src/client/api.ts +4 -25
  32. package/src/client/builtins/index.ts +10 -14
  33. package/src/client/chunk-loader.ts +7 -8
  34. package/src/client/index.tsx +12 -3
  35. package/src/client/locales-ar.ts +0 -6
  36. package/src/client/locales-de.ts +0 -6
  37. package/src/client/locales-fr.ts +0 -6
  38. package/src/client/locales-hi.ts +0 -6
  39. package/src/client/locales-id.ts +0 -6
  40. package/src/client/locales-it.ts +0 -6
  41. package/src/client/locales-ja.ts +0 -6
  42. package/src/client/locales-ko.ts +0 -6
  43. package/src/client/locales-nl.ts +0 -6
  44. package/src/client/locales-pl.ts +0 -6
  45. package/src/client/locales-pt.ts +0 -6
  46. package/src/client/locales-ru.ts +0 -6
  47. package/src/client/locales-sv.ts +0 -6
  48. package/src/client/locales-th.ts +0 -6
  49. package/src/client/locales-tr.ts +0 -6
  50. package/src/client/locales-vi.ts +0 -6
  51. package/src/client/locales-zh-HK.ts +0 -6
  52. package/src/client/locales-zh-MO.ts +0 -6
  53. package/src/client/locales-zh-TW.ts +0 -6
  54. package/src/client/locales.ts +12 -52
  55. package/src/client/native-avoid.ts +155 -0
  56. package/src/client/plugins-shared.ts +6 -6
  57. package/src/client/prefs.ts +0 -7
  58. package/src/client/service.ts +13 -150
  59. package/src/client/sidebar.module.css +28 -420
  60. package/src/config.ts +1 -4
  61. package/src/index.ts +17 -74
  62. package/src/prefs-shared.ts +1 -27
  63. package/src/wire.ts +1 -0
  64. package/lib/client-mermaid.js +0 -200776
  65. package/lib/types/client/MarkdownHtml.d.ts +0 -32
  66. package/lib/types/client/PdfView.d.ts +0 -6
  67. package/lib/types/client/builtins/viewers.d.ts +0 -3
  68. package/lib/types/client/chunks/mermaid.d.ts +0 -10
  69. package/lib/types/client/editor-load.d.ts +0 -66
  70. package/lib/types/client/image-types.d.ts +0 -3
  71. package/lib/types/client/markdown-html.d.ts +0 -96
  72. package/lib/types/client/markdown-images.d.ts +0 -45
  73. package/lib/types/client/md-toc.d.ts +0 -4
  74. package/lib/types/client/mermaid-blocks.d.ts +0 -47
  75. package/lib/types/client/mermaid-sanitize.d.ts +0 -2
  76. package/lib/types/client/mermaid.d.ts +0 -12
  77. package/lib/types/client/pdf-types.d.ts +0 -2
  78. package/lib/types/client/plugins-viewers.d.ts +0 -3
  79. package/lib/types/html-route.d.ts +0 -59
  80. package/src/client/MarkdownHtml.tsx +0 -296
  81. package/src/client/PdfView.tsx +0 -110
  82. package/src/client/builtins/viewers.tsx +0 -125
  83. package/src/client/chunks/mermaid.tsx +0 -10
  84. package/src/client/editor-load.ts +0 -92
  85. package/src/client/image-types.ts +0 -8
  86. package/src/client/markdown-html.ts +0 -331
  87. package/src/client/markdown-images.ts +0 -137
  88. package/src/client/md-toc.tsx +0 -127
  89. package/src/client/mermaid-blocks.ts +0 -110
  90. package/src/client/mermaid-sanitize.ts +0 -93
  91. package/src/client/mermaid.tsx +0 -406
  92. package/src/client/pdf-types.ts +0 -4
  93. package/src/client/plugins-viewers.ts +0 -30
  94. package/src/html-route.ts +0 -106
package/lib/index.js CHANGED
@@ -42,15 +42,12 @@ const SIDEBAR_PREFS_DEFAULTS = {
42
42
  customCss: "",
43
43
  titleBarCompat: false,
44
44
  titleBarStripPx: 40,
45
- htmlViewerNoSandbox: false,
46
- htmlViewerDefaultUnsafe: false,
47
45
  browserNoSandbox: false,
48
46
  browserInterceptLinks: true,
49
47
  browserInterceptHttp: true,
50
48
  browserInterceptHttps: false,
51
49
  browserAllowedLoopback: "",
52
50
  tabsEnabled: {},
53
- viewersEnabled: {},
54
51
  pluginSettings: {}
55
52
  };
56
53
  //#endregion
@@ -114,15 +111,12 @@ const PrefsSchema = z.object({
114
111
  customCss: z.string(),
115
112
  titleBarCompat: z.boolean().default(false),
116
113
  titleBarStripPx: z.number().step(1).min(0).max(120).default(40),
117
- htmlViewerNoSandbox: z.boolean().default(false),
118
- htmlViewerDefaultUnsafe: z.boolean().default(false),
119
114
  browserNoSandbox: z.boolean().default(false),
120
115
  browserInterceptLinks: z.boolean().default(true),
121
116
  browserInterceptHttp: z.boolean().default(true),
122
117
  browserInterceptHttps: z.boolean().default(false),
123
118
  browserAllowedLoopback: z.string().default(""),
124
119
  tabsEnabled: z.dict(z.boolean()).default({}),
125
- viewersEnabled: z.dict(z.boolean()).default({}),
126
120
  pluginSettings: z.dict(z.dict(z.any())).default({})
127
121
  });
128
122
  //#endregion
@@ -541,60 +535,6 @@ async function searchFiles(root, query, opts = {}) {
541
535
  truncated
542
536
  };
543
537
  }
544
- /**
545
- * Decode a route pathname into the session + absolute file path. Rejects
546
- * a wrong prefix (404), an empty path, malformed percent encoding, and a
547
- * missing sessionId or file path (400). The caller still must bound the
548
- * decoded path with the workspace real-path guard — a decoded `..`
549
- * segment resolves outside the cwd and is refused there.
550
- */
551
- function decodeHtmlUrl(pathname) {
552
- if (!pathname.startsWith("/sidebar/html/")) return {
553
- ok: false,
554
- status: 404,
555
- message: "not an html route"
556
- };
557
- const rest = pathname.slice(14);
558
- if (rest === "") return {
559
- ok: false,
560
- status: 400,
561
- message: "invalid html route path"
562
- };
563
- let segments;
564
- try {
565
- segments = rest.split("/").map((segment) => decodeURIComponent(segment));
566
- } catch {
567
- return {
568
- ok: false,
569
- status: 400,
570
- message: "malformed URL encoding"
571
- };
572
- }
573
- const [sessionId, ...pathSegments] = segments;
574
- if (sessionId === void 0 || sessionId === "") return {
575
- ok: false,
576
- status: 400,
577
- message: "sessionId and file path are required"
578
- };
579
- const unc = pathSegments[0] === "";
580
- const tail = unc ? pathSegments.slice(1) : pathSegments;
581
- if (tail.length === 0 || tail.some((segment) => segment === "")) return {
582
- ok: false,
583
- status: 400,
584
- message: "sessionId and file path are required"
585
- };
586
- let path;
587
- if (unc) path = `//${tail.join("/")}`;
588
- else if (/^[A-Za-z]:$/.test(tail[0] ?? "")) path = tail.join("/");
589
- else path = `/${tail.join("/")}`;
590
- return {
591
- ok: true,
592
- ref: {
593
- sessionId,
594
- path
595
- }
596
- };
597
- }
598
538
  //#endregion
599
539
  //#region src/browser-probe.ts
600
540
  /**
@@ -692,11 +632,7 @@ function isTrustedApiRequest(request, trustedHosts) {
692
632
  * only allowlisted chunk names are servable (no path traversal).
693
633
  */
694
634
  /** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
695
- const CHUNK_NAMES = [
696
- "terminal",
697
- "editor",
698
- "mermaid"
699
- ];
635
+ const CHUNK_NAMES = ["terminal", "editor"];
700
636
  /** Directory of this host-half module (lib/ — the chunk scripts live next to it). */
701
637
  const LIB_DIR = dirname(fileURLToPath(import.meta.url));
702
638
  /** sha1 content hash shortened to 12 hex chars (same shape as the client-modules rev). */
@@ -3689,8 +3625,8 @@ function buildSidechatApi(ctx) {
3689
3625
  //#region src/index.ts
3690
3626
  /**
3691
3627
  * dsh-coding-sidebar host half: the /sidebar JSON API (explorer listing, file
3692
- * read/write, git), the /sidebar/file media route (images), the /sidebar/html
3693
- * preview route, the /sidebar/bundle lazy-chunk route (client code splits),
3628
+ * read/write, git), the /sidebar/file media route (file bytes / downloads),
3629
+ * the /sidebar/bundle lazy-chunk route (client code splits),
3694
3630
  * and the terminal WebSocket upgrade. Every route passes the same
3695
3631
  * browser-trust fence as the /api gateway — Host-header loopback or the
3696
3632
  * web runtime's `trustedHosts` (LAN IP literals sampled at boot plus
@@ -3721,10 +3657,7 @@ const MEDIA_TYPES = {
3721
3657
  ".svg": "image/svg+xml",
3722
3658
  ".bmp": "image/bmp",
3723
3659
  ".ico": "image/x-icon",
3724
- ".avif": "image/avif",
3725
- ".pdf": "application/pdf",
3726
- ".html": "text/html",
3727
- ".htm": "text/html"
3660
+ ".avif": "image/avif"
3728
3661
  };
3729
3662
  /** Content type served by /sidebar/file (binary-safe fallback for unknowns). */
3730
3663
  function mediaTypeForPath(path) {
@@ -4038,6 +3971,15 @@ function buildApi(ctx, ptyManager, agentPtyRegistry, resolved, terminalShell, ge
4038
3971
  throw new SidebarError("settings-rejected", error instanceof Error ? error.message : String(error), 400);
4039
3972
  }
4040
3973
  },
3974
+ "cdp.targets": async () => {
3975
+ const res = await fetch("http://127.0.0.1:9223/json/list", { signal: AbortSignal.timeout(3e3) });
3976
+ if (!res.ok) throw new SidebarError("cdp-down", `browser host unreachable (${res.status})`, 502);
3977
+ return { targets: (await res.json()).filter((t) => t.type === "page").map((t) => ({
3978
+ id: t.id,
3979
+ url: t.url,
3980
+ title: t.title
3981
+ })) };
3982
+ },
4041
3983
  "browser.probe": async (payload) => {
4042
3984
  const raw = requireString(payload, "url");
4043
3985
  let parsed;
@@ -4302,45 +4244,6 @@ function apply(ctx, config) {
4302
4244
  }
4303
4245
  }
4304
4246
  }), "dsh-coding-sidebar: /sidebar/file media route");
4305
- ctx.effect(() => ctx.webServer.register({
4306
- kind: "prefix",
4307
- path: "/sidebar/html",
4308
- handler: async (req, res) => {
4309
- if (!fence(req)) {
4310
- res.writeHead(403);
4311
- res.end("forbidden");
4312
- return;
4313
- }
4314
- if (req.method !== "GET") {
4315
- res.writeHead(405);
4316
- res.end();
4317
- return;
4318
- }
4319
- try {
4320
- const decoded = decodeHtmlUrl(new URL(req.url ?? "/", "http://dsh.internal").pathname);
4321
- if (!decoded.ok) {
4322
- writeError(res, new SidebarError("bad-request", decoded.message, decoded.status));
4323
- return;
4324
- }
4325
- const { sessionId, path } = decoded.ref;
4326
- const absolute = await ensureWorkspacePath(await sessionCwdOf(ctx, sessionId), path);
4327
- const info = await stat(absolute);
4328
- if (!info.isFile() || info.size > resolved.mediaLimit) throw new SidebarError("fs-error", "not a file or too large", 400);
4329
- const type = mediaTypeForPath(absolute);
4330
- const body = await readFile(absolute);
4331
- res.writeHead(200, {
4332
- "content-type": type === "text/html" ? "text/html; charset=utf-8" : type,
4333
- "cache-control": "no-cache",
4334
- "x-content-type-options": "nosniff",
4335
- "referrer-policy": "no-referrer",
4336
- "content-security-policy": "sandbox allow-scripts allow-popups allow-downloads allow-modals; object-src 'none'"
4337
- });
4338
- res.end(body);
4339
- } catch (error) {
4340
- writeError(res, error);
4341
- }
4342
- }
4343
- }), "dsh-coding-sidebar: /sidebar/html preview route");
4344
4247
  const wss = new WebSocketServer({ noServer: true });
4345
4248
  ctx.effect(() => ctx.webServer.registerUpgrade({
4346
4249
  path: "/sidebar/ws/terminal",
@@ -1,6 +1,6 @@
1
1
  import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts';
2
2
  /** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
3
- export declare const CHUNK_NAMES: readonly ["terminal", "editor", "mermaid"];
3
+ export declare const CHUNK_NAMES: readonly ["terminal", "editor"];
4
4
  export type ChunkName = (typeof CHUNK_NAMES)[number];
5
5
  /**
6
6
  * Build the /sidebar/bundle route handler. `fence` is the shared browser-
@@ -0,0 +1 @@
1
+ export declare function LiveView(): React.JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
2
  import { type SidebarPrefs } from '../prefs-shared.ts';
3
3
  import type { SidebarStore } from './state.ts';
4
- import type { BetterSidebarService, FileViewerDescriptor, SidebarSettingToggle, TabDescriptor } from './service.ts';
4
+ import type { BetterSidebarService, SidebarSettingToggle, TabDescriptor } from './service.ts';
5
5
  /** Injected business face: the shared store (prefs cache) + the sidebar service (registries). */
6
6
  export interface SideCardSectionInjected {
7
7
  store: SidebarStore;
@@ -43,7 +43,7 @@ export declare function FeatureSettingsRows(props: {
43
43
  valueSource?: (key: string) => unknown;
44
44
  }): import("react").JSX.Element;
45
45
  /**
46
- * The secondary settings popup body of one feature (tab or viewer):
46
+ * The secondary settings popup body of one registered tab:
47
47
  * - the host-prefs `toggles` rows, then the plugin-owned `pluginToggles`
48
48
  * rows (their values live in `pluginSettings[feature.id]`, projected onto
49
49
  * the prefs face so the shared row renderer reads them);
@@ -53,7 +53,7 @@ export declare function FeatureSettingsRows(props: {
53
53
  * open-behavior picker) and still ship a custom configuration area.
54
54
  */
55
55
  export declare function SettingsBody(props: {
56
- feature: TabDescriptor | FileViewerDescriptor;
56
+ feature: TabDescriptor;
57
57
  prefs: SidebarPrefs;
58
58
  store: SidebarStore;
59
59
  service: BetterSidebarService;
@@ -1,10 +1,20 @@
1
- import type { FileViewerProps } from './service.ts';
2
- /**
3
- * The sandbox tokens of the HTML preview iframe. NO allow-same-origin (the
4
- * preview must stay in an opaque origin — with the route's own origin it
5
- * could read session data) and NO allow-top-navigation (a previewed page
6
- * must not hijack the GUI). The user can disable the sandbox per-feature
7
- * in the side card settings (warned); the toggle below reflects it.
8
- */
9
- export declare const HTML_IFRAME_SANDBOX = "allow-scripts allow-popups allow-downloads allow-modals";
10
- export declare function TextEditor(props: FileViewerProps): import("react").JSX.Element;
1
+ import type { EditorToolbarControls, EditorToolbarState } from './service.ts';
2
+ import type { Context } from '../context-types.ts';
3
+ import type { SessionScope } from './api.ts';
4
+ import type { SidebarStore } from './state.ts';
5
+ /** Props of the sidebar text editor (the editor tab's content). */
6
+ export interface TextEditorProps {
7
+ ctx: Context;
8
+ store?: SidebarStore;
9
+ scope: SessionScope;
10
+ path: string;
11
+ /** fs.read text content (undefined while loading / for non-editable reads). */
12
+ content?: string;
13
+ truncated?: boolean;
14
+ /** 'host' skips the own toolbar row — the editor host's merged-mode header
15
+ * renders it instead, fed through the two callbacks below. */
16
+ toolbar?: 'self' | 'host';
17
+ onToolbarState?: (state: EditorToolbarState) => void;
18
+ onToolbarControls?: (controls: EditorToolbarControls | null) => void;
19
+ }
20
+ export declare function TextEditor(props: TextEditorProps): import("react").JSX.Element;
@@ -1,15 +1,11 @@
1
1
  import type { BetterSidebarService } from './service.ts';
2
- /** Which extension point the modal is adding a plugin for. */
3
- export type PluginKind = 'tab' | 'viewer';
4
2
  /** The modal body: the GitHub topic button + the recommended plugin list
5
3
  * with per-entry jump/copy buttons (extracted for direct testing). */
6
4
  export declare function PluginListBody(props: {
7
5
  service: BetterSidebarService;
8
- kind: PluginKind;
9
6
  }): import("react").JSX.Element;
10
7
  /** The modal itself (mounted only while open — see the module comment). */
11
8
  export declare function AddPluginModal(props: {
12
9
  service: BetterSidebarService;
13
10
  onClose: () => void;
14
- kind: PluginKind;
15
11
  }): import("react").JSX.Element;
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Typed fetch wrapper over the /sidebar JSON API. Every call posts to
3
+ * `/sidebar/api/<method>` with the sessionId and — when known — the session's
4
+ * cwd from the client's own list summary. The host prefers its attached
5
+ * session header and uses the summary cwd only while the session is still
6
+ * hydrating at page load (a detached session would otherwise fail the
7
+ * request). Failures surface as {@link SidebarApiError} with the wire code.
8
+ */
1
9
  import type { LastActivity } from '../subagent-activity.ts';
2
10
  import type { SidechatThreadInfo } from '../sidechat-core.ts';
3
11
  import type { BrowserProbeResult } from './browser.ts';
@@ -245,6 +253,14 @@ export declare const api: {
245
253
  /** Probe a URL's response headers (the sidebar browser's embeddability
246
254
  * check; see the host's browser.probe route). */
247
255
  browserProbe: (url: string, signal?: AbortSignal) => Promise<BrowserProbeResult>;
256
+ /** Agent 浏览器宿主的 page target 列表(host 代理 CDP /json/list)。 */
257
+ cdpTargets: (signal?: AbortSignal) => Promise<{
258
+ targets: Array<{
259
+ id: string;
260
+ url: string;
261
+ title: string;
262
+ }>;
263
+ }>;
248
264
  /** External open for the file tree's "open with" menu: reveal a path in
249
265
  * the OS file manager, or hand a custom-scheme URL (vscode://, cursor://,
250
266
  * zed://, custom editors) to its registered handler. The host launches
@@ -259,17 +275,6 @@ export declare const api: {
259
275
  started: boolean;
260
276
  }>;
261
277
  };
262
- /** Absolute URL of the media route for one path (images only). */
263
- export declare function mediaUrl(scope: SessionScope, path: string): string;
264
278
  /** Absolute URL of the download route: serves raw bytes (binary-safe) with
265
279
  * `Content-Disposition: attachment`, so the browser saves the file. */
266
280
  export declare function downloadUrl(scope: SessionScope, path: string): string;
267
- /**
268
- * Absolute URL of the HTML preview route (see html-route.ts): the path is
269
- * fully encoded so the previewed page's relative assets resolve back into
270
- * the same route with the session scope intact. The UNC marker is
271
- * platform-neutral — the host's requireAbsolute resolves the decoded
272
- * forward-slash `//server/share/...` form on both win32 and POSIX — so no
273
- * client-side platform signal is needed.
274
- */
275
- export declare function htmlUrl(scope: SessionScope, path: string): string;
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Built-in registration: the plugin registers its own tab pages and file
3
- * previewers through the same {@link BetterSidebarService} external plugins
4
- * use — eating its own dogfood. The descriptors live next to their feature
5
- * modules (tabs.tsx / viewers.tsx); this module only aggregates them and
6
- * owns the disposer lifecycle (cordis auto-invokes it on fiber disposal,
7
- * HMR-safe).
2
+ * Built-in registration: the plugin registers its own tab pages through the
3
+ * same {@link BetterSidebarService} external plugins use — eating its own
4
+ * dogfood. The descriptors live next to their feature modules (tabs.tsx);
5
+ * this module only aggregates them and owns the disposer lifecycle (cordis
6
+ * auto-invokes it on fiber disposal, HMR-safe).
7
+ *
8
+ * (v1.0.4: the built-in file viewers were retired with the file-viewer
9
+ * registry — file preview is the host's job now.)
8
10
  */
9
11
  import type { Context } from '../../context-types.ts';
10
12
  import type { BetterSidebarService } from '../service.ts';
11
13
  import { type BuiltinTabOptions } from './tabs.tsx';
12
14
  /**
13
- * Register all built-in tabs and viewers with the service. Returns a
14
- * disposer that unregisters everything (cordis auto-invokes it on fiber
15
- * disposal). The `ctx` is threaded into tab descriptors that need it
16
- * (EditorHost reads `ctx.betterSidebar` for file-viewer matching).
15
+ * Register all built-in tabs with the service. Returns a disposer that
16
+ * unregisters everything (cordis auto-invokes it on fiber disposal).
17
17
  */
18
18
  export declare function registerBuiltins(ctx: Context, service: BetterSidebarService, options?: BuiltinTabOptions): () => void;
@@ -1,11 +1,10 @@
1
1
  /**
2
- * Lazy chunk loader for the client bundle. The heavy preview/terminal
3
- * libraries (CodeMirror, xterm — the editor/terminal stacks, several MB)
4
- * live in separate build-time bundles (`lib/client-<name>.js`) fetched only
5
- * on first use of the feature that needs them, so startup downloads/parses
6
- * only the ~1MB core bundle. (The office stack Univer / docx-preview /
7
- * pptx-renderer is no longer bundled here: Office previews moved to the
8
- * recommended office plugin, see plugins-viewers.ts.)
2
+ * Lazy chunk loader for the client bundle. The heavy editor/terminal
3
+ * libraries (CodeMirror, xterm — several MB) live in separate build-time
4
+ * bundles (`lib/client-<name>.js`) fetched only on first use of the feature
5
+ * that needs them, so startup downloads/parses only the ~1MB core bundle.
6
+ * (v1.0.4: the mermaid chunk was retired with the file-viewer line; the
7
+ * office stack moved to the ecosystem even earlier.)
9
8
  *
10
9
  * How a chunk script works (see tsdown.config.ts chunkBundle):
11
10
  *
@@ -49,7 +48,7 @@
49
48
  * client.js); an edit that does land while a core HMR happens is caught by
50
49
  * the ETag comparison on the next activation.
51
50
  */
52
- export type ChunkName = 'terminal' | 'editor' | 'mermaid';
51
+ export type ChunkName = 'terminal' | 'editor';
53
52
  /** The module exports a chunk factory provides (namespace-ish record). */
54
53
  export type ChunkExports = Record<string, unknown>;
55
54
  /**
@@ -94,14 +94,6 @@ export declare const zh: {
94
94
  terminalDepsFailed: string;
95
95
  terminalDepsHint: string;
96
96
  terminalDepsProfile: string;
97
- preview: string;
98
- toc: string;
99
- edit: string;
100
- mermaidError: string;
101
- mermaidZoomIn: string;
102
- mermaidZoomOut: string;
103
- mermaidZoomReset: string;
104
- mermaidZoomHint: string;
105
97
  refresh: string;
106
98
  refreshUnsavedConfirm: string;
107
99
  save: string;
@@ -244,34 +236,27 @@ export declare const zh: {
244
236
  settingsShellArgsDesc: string;
245
237
  settingsShellArgsPlaceholder: string;
246
238
  settingsTabsTitle: string;
247
- settingsViewersTitle: string;
248
239
  settingsGeneralTitle: string;
249
240
  settingsPopup: string;
250
- settingsViewerCatchAll: string;
251
- viewerImage: string;
252
- viewerPdf: string;
253
- viewerMarkdown: string;
254
- viewerCode: string;
255
- viewerBinary: string;
256
- viewerHtml: string;
257
241
  browser: string;
258
242
  browserPlaceholder: string;
259
243
  browserGo: string;
260
244
  browserBack: string;
261
245
  browserForward: string;
262
246
  browserStart: string;
247
+ browserLive: string;
248
+ browserLiveFree: string;
249
+ browserLiveConnecting: string;
250
+ browserLiveDown: string;
251
+ browserLiveTargetNone: string;
252
+ browserLiveFollowLatest: string;
263
253
  browserBlockedScheme: string;
264
254
  browserBlockedLoopback: string;
265
255
  browserInvalid: string;
266
256
  browserNoSandboxWarning: string;
267
- htmlNoSandboxWarning: string;
268
257
  sandboxStatusOn: string;
269
258
  sandboxUnlock: string;
270
259
  sandboxRestore: string;
271
- settingsHtmlDefaultUnsafeTitle: string;
272
- settingsHtmlDefaultUnsafeDesc: string;
273
- settingsHtmlSandboxTitle: string;
274
- settingsHtmlSandboxDesc: string;
275
260
  settingsBrowserSandboxTitle: string;
276
261
  settingsBrowserSandboxDesc: string;
277
262
  settingsBrowserLinksTitle: string;
@@ -352,10 +337,7 @@ export declare const zh: {
352
337
  jobKillError: string;
353
338
  addPluginsTabCard: string;
354
339
  addPluginsTabCardDesc: string;
355
- addPluginsViewerCard: string;
356
- addPluginsViewerCardDesc: string;
357
340
  addPluginsTabDesc: string;
358
- addPluginsViewerDesc: string;
359
341
  addPluginsBrowseMore: string;
360
342
  addPluginsSearch: string;
361
343
  addPluginsNoMatch: string;
@@ -363,7 +345,6 @@ export declare const zh: {
363
345
  addPluginsEmpty: string;
364
346
  openPlugin: string;
365
347
  copyInstall: string;
366
- pluginOfficeDesc: string;
367
348
  pluginFlowglassDesc: string;
368
349
  pluginGitForgeDesc: string;
369
350
  pluginGitRemotesDesc: string;
@@ -371,7 +352,6 @@ export declare const zh: {
371
352
  pluginSidebarQaDesc: string;
372
353
  pluginSshTunnelDesc: string;
373
354
  pluginTurnReviewDesc: string;
374
- pluginVideoPreviewDesc: string;
375
355
  pluginDocsPanelDesc: string;
376
356
  pluginEgoBrowserDesc: string;
377
357
  };
@@ -0,0 +1,2 @@
1
+ import type { SidebarStore } from './state.ts';
2
+ export declare function installNativeSidebarAvoidance(store: SidebarStore): () => void;
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Shared vocabulary of the recommended plugin catalogs: the entry shape and
3
- * the GitHub topic URL. The two catalogs live in sibling modules —
4
- * `plugins-tabs.ts` (tab registrations) and `plugins-viewers.ts` (file
5
- * previewer registrations) and are shown in the two "add plugin" modals
6
- * (Side card settings → the dashed cards at the end of the 侧边栏内容 /
7
- * 文件预览 grids).
2
+ * Shared vocabulary of the recommended plugin catalog: the entry shape and
3
+ * the GitHub topic URL. The catalog lives in the sibling module
4
+ * `plugins-tabs.ts` (tab registrations) and is shown in the "add plugin"
5
+ * modal (Side card settings the dashed card at the end of the 侧边栏内容
6
+ * grid). (v1.0.4: the file-previewer catalog was retired with the
7
+ * file-viewer registry.)
8
8
  */
9
9
  /** The GitHub topic page listing every repo tagged `dsh-coding-sidebar`. */
10
10
  export declare const PLUGIN_TOPIC_URL = "https://github.com/topics/dsh-coding-sidebar";
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * The BetterSidebar client service: a registry that external plugins use
3
- * to contribute sidebar tab types and file previewers. The service is
4
- * published to the cordis context as `ctx.betterSidebar` (see
5
- * {@link ../context-types.ts}); consumers declare it in `inject` and call
6
- * `registerTab` / `registerFileViewer`, both returning a disposer that
7
- * cordis auto-invokes on fiber disposal (HMR-safe).
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/viewer, rendered as a nested row in the
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 or file viewer. */
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
- /** How the host loads a file's bytes for one viewer. */
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 viewer. */
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/viewer registries (Map + listener set) and proxies openTab/closeTab
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;
@@ -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, FileViewerDescriptor, FileViewerProps, FileFetchStrategy, } from './client/service.ts';
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. */