Files
apes-sidebar/README_EN.md
T
Atlas Zeng 5e416eccef feat(mermaid): Markdown 预览安全渲染 Mermaid 图表(strict 清洗 + 懒加载 chunk + 点击放大) (#164)
* feat(mermaid): 新增 mermaid 懒加载 chunk(构建链四处同步 + mermaid 依赖)

tsdown CHUNKS / bundle-route CHUNK_NAMES / chunk-loader ChunkName /
package.json files 同步注册 mermaid chunk;mermaid ^11.16.1 进依赖与
lockfile;chunk 构建注入 uuid 浏览器入口别名,绕开 node:crypto 纯度门。

* feat(mermaid): Markdown 预览安全渲染 mermaid 图表 + 点击放大弹窗

- splitMermaidBlocks 纯函数按 fence 切块,无 mermaid 文件零行为变化;
- mermaid.initialize 走 securityLevel strict + htmlLabels:false,不应用
  bindFunctions(静态图);渲染 SVG 经 mermaid-sanitize 二次清洗:
  删 foreignObject/script/外来 HTML 元素、剥 @*/on* 属性、移除全部
  href/xlink:href,仅接受 SVG 根,解析失败返回空串;
- 点击图表弹出放大弹窗(滚轮缩放/拖拽平移/快捷键,借鉴 PR #75 交互,
  基于已清洗 SVG 克隆重写,不新增攻击面);
- 深浅色跟随重渲、失败回退错误条 + 原码、CodeBlock 同款 chrome。

* test(mermaid): splitter/sanitize 单测 + chunk 清单同步 + 挂载冒烟 diagram.md

- mermaid-blocks.spec 10 例 fence 切块语义;
- mermaid-sanitize.spec 8 例 XSS(foreignObject/on*/@*/href/xlink:href/
  script/外来元素/畸形 XML/非 SVG 根);
- bundle-route / chunk-artifact / manifest-consistency 清单加入 mermaid
  (chunk-artifact 补 addEventListener stub);
- 挂载冒烟新增 diagram.md:client-mermaid.js 往返、SVG 节点文字、
  点击弹窗 Esc 关闭、preview 模式编辑器隐藏。

* docs(mermaid): 安全渲染设计文档 + lazy-chunks 表 + README 双语更新

设计文档记录基线决策(PR #99 架构 + PR #75 点击放大)、安全模型
(strict/htmlLabels/清洗/href 剥离/无 bindFunctions)、验证与取舍。

* fix(mermaid): sanitizer 属性名大小写归一,堵住混合大小写属性绕过(CR P1)

XML 属性名保留大小写,而输出会经 HTML 解析归一化(dangerouslySetInnerHTML),
oNload/HREF/xlink:HREF 这类混合大小写属性此前会漏过清洗。比较前先
toLowerCase(),按原名删除;新增混合大小写事件/链接属性回归用例。

* fix(mermaid): sanitizer 元素黑名单大小写归一,堵住混合大小写元素绕过(CR P1)

XML 元素名保留大小写,querySelectorAll('script') 等选择器匹配不到
<sCrIpT>/<foreignobject>,而输出经 HTML 解析会被归一化为 SVG
script/foreignObject(可执行/可注入 HTML)。改为遍历全部元素按
localName.toLowerCase() 命中小写黑名单后整节点删除;新增混合大小写
script/foreignObject/外来 HTML 元素回归用例(jsdom 实证:旧选择器
命中 0,新逻辑命中 2)。

* fix(mermaid): 单次 Markdown 解析 + 占位交换(CR P1)+ CommonMark fence 规则(CR P2)

P1(跨 fence 语义):弃用逐块 MarkdownText 拆分,改为整篇文档单次
MarkdownText 渲染,布局后把 language-mermaid 代码块交换为 MermaidDiagram
(React 管理的 .md-code-block 宿主保留在树中、display:contents 抑制盒子,
协调安全;块不再 mermaid 时还原原 children)。新增
tests/mermaid-markdown.spec.tsx 与 e2e 跨 fence 引用链接断言。

P2(fence 匹配):支持 3+ 反引号与波浪号开 fence;闭 fence 须同字符且
长度不小于开 fence;反引号 info string 含反引号不视为开 fence;修复
带缩进开 fence 的 info 提取。blocks 单测 +3 例。
2026-08-18 01:22:28 +08:00

22 KiB
Raw Blame History

dsh-better-sidebar

A service-oriented sidebar framework, and a complete workbench out of the box

License: MIT dshfind

File management Edit & preview Embedded browser Real terminal Git panel Background tasks Plugin integration

A dual workbench (right sidebar + bottom panel) that opens its ctx.betterSidebar service to every plugin —
register new sidebar pages and file viewers via registerTab / registerFileViewer.
🌏 中文 · English
dsh-better-sidebar workbench

✨ Features

  • 🗂️ File Workbench: file explorer (lazy-loading tree; symlinks show their target kind — directory links expand, dangling links flagged) + CodeMirror editor; inline preview for images / Markdown (incl. Mermaid diagrams, strict-mode safe rendering + click-to-zoom) / HTML / PDF / Office
  • 🌐 Embedded Browser: multiple web tabs with back / forward / refresh; content runs in a sandboxed iframe; external links are routed by protocol by default — HTTP opens in the sidebar, HTTPS goes to the system browser (both adjustable in settings)
  • 💻 Real Terminal: xterm.js + node-pty real shell, reconnect with transcript replay; optionally injects terminal_* tools for the model
  • 🌿 Git Panel: real diff + VSCode-style diff tabs, history, right-click to stage / commit / revert
  • 🧩 Background Tasks: agent topology + background tasks (exit codes / live output / force-kill)
  • 🪟 Dual Workbench: right sidebar + bottom panel; drag tabs to split / merge panes (cross-panel), mobile auto-merges into a full-width drawer
  • 🔁 Session Isolation: layout / tabs / panels persisted per session, stale state auto-purged
  • ⚙️ Declarative Settings: per-item toggles in the "Side Cards" settings section, secondary settings via the gear dialog
  • ⚡ On-demand Loading: only ~325KB core at startup; heavy deps (terminal / editor / mermaid diagrams) load on demand (design)
  • 🌏 i18n: UI text follows DSH's language (zh / en) with live switching

🔌 Core principle: service-first — the 7 built-in tabs + 6 viewers register through the same ctx.betterSidebar API as third-party plugins, with fully equal capabilities; anything the ecosystem can provide better is delegated to ecosystem plugins. See the "🔌 Service" section below and the external plugin guide.

🆕 Recent Updates

Service API base screenshot Add Plugins screenshot

v0.13.0

✨ New features

  • 📁 Files window merged with the explorer (#151): new editorExplorer setting (default on, editor card gear) — file tabs gain a path-input header plus a toggleable right-docked file tree (per-tab open/width memory, drag-resize 160–480px from the left edge, global filename search via the host fs.search route with a hard budget, skipping .git and symlink dirs); in merged mode tree clicks / Enter in the path input switch the current tab in place, separate mode opens by path; fresh sessions seed an empty Files window instead of the explorer tab, and a path-less window is a chrome'd empty file window in merged mode / a bare file manager in separate mode; the tree context menu offers "Open in new tab" and "Open to the side" (split)
  • 🎛️ Select rows for declarative settings (#151): settings rows gain type: 'select' (options with value/title/desc/icon, multi stores the picked values as an array); options with icons render big-icon option cards and keep the icon in the closed anchor; editorExplorer became an iconed select (merged vs separate); the capability list gained settingSelect
  • 🔀 Mutual exclusion with the dsh-web-ui family right panel (#181): reads the aionui-panel settings namespace's provider choice — when "Use aionui-panel" is selected, the whole better-sidebar (right sidebar / bottom panel / floating entry / all takeovers) does not mount; with DSH-better-sidebar (or no aionui installed) it behaves as before. Takes effect live after a settings save (settings-document push), no reload needed

📝 Other

  • Install / update commands unified to dsh-better-sidebar@latest (bilingual README sync)

v0.12.3

✨ New features

  • 🎨 Skin compatibility (token-driven): fully consumes DSH design tokens and follows the dsh-web-ui skin center's 10 skins automatically; terminal/editor surfaces fall back to opaque backgrounds under transparent/translucent-glass token values so text never scrolls over the skin art (#110, fixes #106 #105 #90 #60, also #52 #57 #92)
  • 🗂️ Unified path handling: UNC / symlink classification (directory symlinks expandable, broken links highlighted) + HTML-route platform guards (#134, #65 #67 #43 #79 #115)
  • 🖥️ Configurable terminal shell: custom shell setting with Windows pwsh auto-probe (#95)
  • 📝 Editor languages: C# / Kotlin / Swift syntax highlighting (#120)
  • 🧭 Settings nav icon: settings-page navigation icon and layout polish (#114)
  • ➕ Recommended-plugin catalog: added dsh-git-remotes — Git Remotes tab (branches/upstream/ahead-behind, fetch with prune, ff-only pull, confirm-before-push; does not replace the built-in stage/commit tab) (#91); and dsh-video-preview — inline video preview (.mp4/.webm/.mov/.mkv/.avi etc.) backed by a /video host route with HTTP Range (206) scrubbing, not capped by the 20MB mediaLimit (#126)

🐛 Fixes

  • 🔧 xterm migration: deprecated xterm dependency migrated to @xterm/xterm (Closes #122, #128)
  • 📝 Markdown editor: selection-to-conversation popup restored (#24)
  • 🐛 node-pty load failure no longer crashes the server (#140): the host half now lazy-loads node-pty — when it is missing the plugin still mounts, the terminal shows a repair banner (copyable command + Retry button), and agent terminal tools are skipped
  • 🧪 Test engineering: unit spec split (#141) + flaky smoke cleanup fix

🚀 Engineering

  • npm publishing wired to GitHub Releases (Trusted Publishing, provenance-attached tarballs); tagging a release publishes automatically (#148)

v0.12.2

  • 📐 Position compat mode: new "Position compatibility mode" setting: reserves top space for the native Windows title bar (top-right) so the sidebar buttons and content sit below it (off by default); the shift distance is customizable in the gear popup (0–120px)
  • 🔌 Service API base: complete type exports + version/features capability detection, state subscription (getSnapshot/subscribeState), tab badge, onOpen/onActivate/onClose lifecycle callbacks, updateTab/activateTab/openFile, targeted open, meta persisted across reloads, plugin-owned settings (pluginToggles/render), external-link claim (urlTarget)
  • ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
  • 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
  • 🐛 Fixes: remote access 403 (trust fence now uses trustedHosts), sidebar crash #31, Windows HTML-preview drive-path

v0.12.1

  • 🔌 Service API base: complete type exports + version/features capability detection, state subscription (getSnapshot/subscribeState), tab badge, onOpen/onActivate/onClose lifecycle callbacks, updateTab/activateTab/openFile, targeted open, meta persisted across reloads, plugin-owned settings (pluginToggles/render)
  • ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
  • 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
  • 🐛 Fixes: remote access 403 (trust fence now uses trustedHosts), sidebar crash #31, Windows HTML-preview drive-path

📝 Note: the 0.12.0 final could not be reused (npm reported the version as already published), so the public release became 0.12.1 — both carry identical content.

v0.12.0

  • 🔌 Service API base: complete type exports + version/features capability detection, state subscription, tab badges, lifecycle callbacks, targeted open, meta persisted across reloads, plugin-owned settings
  • ➕ Add Plugins: recommended plugin catalog in settings + one-click copy install command; built-in Office preview moved to the recommended plugin
  • 🖱️ Tab-bar scroll: mouse-wheel horizontal scrolling on the tab bar
  • 🐛 Fixes: remote access 403 (trust fence now uses trustedHosts), sidebar crash #31, Windows HTML-preview drive-path

🚀 Installation

Prerequisites: DSH installed (dsh web boots), Node.js ≥ 20, pnpm ≥ 10.

dsh plugin --profile web add dsh-better-sidebar@latest

Then hard-refresh the browser (Cmd/Ctrl+Shift+R) to see the sidebar (DSH hot-reloads client changes; only host-half updates need a restart).

Updating
dsh plugin --profile web add dsh-better-sidebar@latest

or bump the version in ~/.dsh/profiles/web/package.json (e.g. "^0.13.0") and run pnpm install. Then hard-refresh the browser (Cmd/Ctrl+Shift+R) — client changes do not need a DSH restart.

Troubleshooting
Symptom Cause & fix
Ignored build scripts pnpm 11 blocked build scripts. Run pnpm approve-builds --all in the profile directory (~/.dsh/profiles/web).
minimum release age / version < 24h The release is younger than 24 hours. Wait, or re-run once (pnpm auto-adds minimumReleaseAgeExclude).
"profile directory not found" Run dsh web once so it initializes ~/.dsh/profiles/web.
Two sidebars on the page Double-mount: ~/.dsh/profiles/web/cordis.patch.yml still has the old hand-written - insert: ... better-sidebar ... line — delete it.
Terminal fails on Windows node-pty relies on prebuilt binaries; if none match your Node version, install a build toolchain (VS Build Tools). Mainstream Node versions are usually covered.
Terminal shows "node-pty failed to load" The node-pty install is missing or broken (e.g. pnpm skipped its build script). The terminal banner shows a repair command — copy it into a terminal/cmd on the DSH machine and run it (in ~/.dsh/profiles/web: pnpm approve-builds --all && pnpm rebuild node-pty), then restart DSH and click Retry. The plugin and DSH core share the same node-pty@^1.1.0, so the repair restores both.
dsh: command not found Install DSH first, or run npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar@latest.
Install from source / develop (optional — alternative to the npm flow)

To debug local changes or track the dev branch, point the dependency at a local clone and build it yourself:

1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar
   cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build
2. In ~/.dsh/profiles/web/package.json dependencies write "dsh-better-sidebar": "link:<absolute path of the clone>"
3. Append this mount line to ~/.dsh/profiles/web/cordis.patch.yml:
   - insert:
       - id: better-sidebar
         name: 'dsh-better-sidebar'
4. Run pnpm install in ~/.dsh/profiles/web
5. Restart DSH and hard-refresh

Update: git pull && pnpm install && pnpm build → just hard-refresh the browser (client changes hot-reload; only host-half changes need a DSH restart). To switch back to the npm channel, restore "dsh-better-sidebar": "^0.13.0" and re-run pnpm install.

Install via plugin-registry (optional — use either this or the main flow)

Prerequisite: DSH with plugin-registry integrated (dsh registry available). Enabling both channels double-mounts (the Node half loads twice, the page gets two sidebars).

git clone https://github.com/omdsh-dev/DSH-better-sidebar.git && cd DSH-better-sidebar
pnpm install && pnpm build
node scripts/package-registry.mjs   # assemble the registry/ staging (manifest + artifacts + README, not committed)
dsh registry install ./registry     # install (disabled by default)
dsh registry enable dsh-external/dsh-better-sidebar

Update: git pull && pnpm install && pnpm build → node scripts/package-registry.mjs → dsh registry uninstall/install/enable. Remove the other channel's mount before switching.

⌨️ Keyboard Shortcuts

Action Keys
Save edits Ctrl/Cmd + S
Git commit Ctrl + Enter
Close tab Middle mouse button
Split / merge panes Drag tab to pane edge / middle
Reference file to input Hover the @file button at end of line
Copy file path Right-click row → copy relative/absolute path

🔌 Service: register tabs & file viewers

Since v0.4.0 the plugin exposes the ctx.betterSidebar service — other plugins can register sidebar pages and file viewers (the 7 built-in tabs + 6 viewers register through the same service):

import type {} from 'dsh-better-sidebar'  // triggers the ctx.betterSidebar type merge
export const inject = ['betterSidebar']
export function apply(ctx: Context) {
  ctx.effect(() => ctx.betterSidebar.registerTab({
    id: 'my-plugin:db', title: 'Database', component: ({ scope }) => <DbView sessionId={scope.sessionId} />,
  }))
}

v0.12.1+ base capabilities (complete type exports, capability detection, state subscription, tab badge, lifecycle callbacks, targeted open, plugin-owned settings, etc.) — see the integration docs below.

Full integration docs:

  • AGENTS.md — the in-repo integration doc (full fields, matching algorithm, HMR pitfalls, declarative settings, version detection);
  • docs/external-plugin-guide.md — the external-plugin guide (with a complete minimal example).

The dashed cards at the end of the "Sidebar content" / "File viewers" grids in the "Side Cards" settings section open the Add tab plugins / Add preview plugins modals: each declares its open extension point, offers a "Browse more plugins on GitHub" button (the GitHub topic dsh-better-sidebar), and lists the recommended catalog (name / repo / description / install script) — "Open" jumps to the repo, "Copy" writes the install command to the clipboard.

Curating a new plugin: append a PluginEntry to src/client/plugins-tabs.ts (tab registrations) or src/client/plugins-viewers.ts (file-previewer registrations) and tag your repo with the dsh-better-sidebar topic; data integrity is guarded by tests/plugin-list.spec.ts.

🛠️ Development & Build

pnpm install      # @deepseek-ai/* resolved from npm (^0.1.0-rc.6, published) — no token needed
pnpm typecheck    # tsc --noEmit
pnpm build        # → lib/index.js + lib/invariant.js + lib/client.js + lib/client-registry.js + lib/types
pnpm test         # vitest (includes manifest consistency guard; build first)
pnpm watch        # tsdown --watch

Architecture: a single npm package with host/client halves — host (src/index.ts): /sidebar/api/* JSON API, /sidebar/file media route, /sidebar/html preview route, /sidebar/ws/terminal WebSocket (fs / git / pty / preview, all session-scoped with a trust fence); client (src/client/index.tsx): portal sidebar + views + interception; state persisted per session in localStorage. Organized per DSH official conventions (no default export, dual client bundles); no dependency on npm / checkout at runtime (@deepseek-ai/* provided by the web profile).

🔐 Security

  • Routes protected by a Host-header trust fence (same as /api); fs.write is atomic; media/preview routes only serve files inside the session cwd; git only shells out to the CLI and never sets identity
  • HTML preview and browser tab content render in opaque-origin sandboxed iframes (no allow-same-origin/allow-top-navigation, no-referrer, all permission policies disabled); the /sidebar/html route carries a CSP sandbox + size/path bounds; the address bar rejects javascript:/data:/file: and local addresses like localhost
  • The UI shows the sandbox status live (red warning when off) and can temporarily unlock the current page; the settings page can disable the sandbox per feature (disabled by default, with a warning) — when off, content shares the origin with the UI; only recommended for fully trusted content

⚠️ Known Limitations

  • Git has no push/pull/fetch; no file watcher (manual refresh); tool inline file-open buttons cannot be intercepted
  • Dragging a terminal tab to another pane remounts it (shell restarts)
  • Office-suite preview (.docx/.xlsx/.pptx) moved to the recommended office plugin (see the "Add plugins" modals in settings); without it these files fall through to the code/download fallbacks
  • Browser sandbox has no login state / third-party cookies are restricted; some sites need popup login; sites that refuse embedding via X-Frame-Options/frame-ancestors (e.g. arxiv.org) show a reason panel (with "Open in browser"); in-iframe navigation does not enter the back stack
  • HTML preview renders the saved file (not unsaved drafts)
  • No bottom panel on mobile (<768px): on narrow screens its tabs merge into the right sidebar once (after migrating back to desktop they stay in the right sidebar); the desktop bottom panel is only available on wide viewports; auto-open terminal on first bottom-panel expand does not trigger on mobile

🖥️ Platform Support

Windows / Linux / macOS (macOS validated daily; the rest covered by unit tests); node-pty prefers prebuilt binaries, otherwise a build toolchain is required (Windows VS Build Tools / Linux make+g+++python3 / macOS Xcode CLT).

🔗 Friends

  • dsh-tianshu-tui: an interactive terminal UI plugin for DeepSeek Harness (its rendering core evolved from the self-developed harness agent Tianshu-Tui), adding TDD and evidence-gate workflows on top of the official harness
  • dsh-TUI: a Claude Code-style fullscreen interactive TUI plugin — pixel-whale top bar, live working-status row, streaming thought expansion, double-Esc rollback, context progress bar + TPS meter; one-command npm install
  • dshfind Plugin Market: a third-party plugin marketplace — a listing of public repos under the GitHub topic dsh-plugin, with stars, contributors and growth data synced daily
  • DeepSeek Harness Desktop: a modern desktop client for the DeepSeek Harness ecosystem — start and manage a local Harness service without configuring Node.js or running commands; official site