luzonghao/dsh-sidebar-light ↗★ 0
dsh-sidebar-light
侧栏 Workspaces | Tags 双 Tab(标签树 / 视图选项 / 拖拽 / 搜索)+ 工作区四态状态灯(树形逐级穿透)+ 会话行标签按钮与「标为未读」+ 系统按钮区与刷新按钮(⌘R)。纯客户端 DSH 插件,零配置。
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:luzonghao/dsh-sidebar-lightドキュメント
README 全文を読む ↗dsh-sidebar-light
Adds Workspaces | Tags tabs to the sidebar, one status light per workspace, and tags for
sessions. Client-only plugin for DeepSeek Harness (dsh) —
no host-side behaviour. Bilingual (en / zh).
Works out of the box
No settings to touch: the sidebar header simply gains Workspaces | Tags.
- Tags tab: tag tree (names nest on
/), counts, status lights, search, view options, double-click to rename, delete (inline two-step confirmation), drag to reorder. - The header's three buttons stay the host's own (search / view options / new). In the Tags tab clicking them is redirected to tag behaviour — so both tabs use the same buttons, not lookalikes.
- System button row: the row under the logo becomes
[Plugins] [Automation tasks] […] [refresh]— plugin panels on the left, refresh on the right, icon-only with tooltips.
Status lights
| Light | Meaning | Priority |
|---|---|---|
| 🟡 | a session in this workspace is waiting for you (question / approval) | 1 (highest) |
| 🔄 | a session is running (animated) | 2 |
| 🟢 | a session finished and you haven't looked at it | 3 |
| ⏰ | only scheduled work remains | 4 (lowest) |
Priority mirrors the host's own rule, so this light never contradicts the dots you already see.
Roll-up in tree mode: a parent aggregates every descendant workspace — and it keeps working when the parent is collapsed (the judgement comes from the host's own grouping state, not from "the indentation currently visible").
Tags
- Hierarchy is naming, not inheritance: attaching
test/test1does not attachtest. Selecting a tag lists only the sessions explicitly tagged with it, and the number is its own usage; an implicit level (no tag record) is empty. The status light still aggregates the subtree. - View options (mirrors the host's Workspaces view options; the choice is remembered within a session): Group by tag tree / in one list; Order by manual (drag to reorder) / last updated / by name / by usage; Filter sessions hide archived / all (show archived) / archived only.
- Rename: double-click a tag name to rename in place (Enter commits, Esc cancels). A
/-separated name creates hierarchy; attachments are keyed by id and stay intact. - Delete: the trash button turns the row into a red Delete / Cancel pair; only Delete removes it. Clicking outside, Esc, or switching tabs cancels. Deleting a tag never deletes sessions.
- Row tag button: hover a session row and the tag button appears at its right
(the native pin / archive buttons are hidden — both actions remain in the
⋯menu). Tagged rows keep it visible and carry a count. - Refresh the page: the refresh button at the right of the system row, or ⌘R (see below).
Keyboard shortcuts
| Chord | Action |
|---|---|
| ⌘1 | sidebar-light.tabWorkspaces — switch to the Workspaces tab |
| ⌘2 | sidebar-light.tabTags — switch to the Tags tab |
| ⌘D | sidebar-light.markUnread — mark the current session unread |
| ⌘R | refresh the page (handled by this plugin, via a user override on the host's shortcut system) |
The plugin registers two ctx.shortcuts commands, neither with a default binding (defaults: {}):
- At registration
parseShortcutDefinitionschecks default-chord collisions per profile, and the host's built-inpage.refreshalready owns ⌘R — declaring it here throwsConflicting or reserved shortcut default. - So the binding travels only through user overrides (
keybindings.json). For example:
// ~/Library/Application Support/@deepseek-ai/dsh-desktop/keybindings.json
{
"schemaVersion": 2,
"profiles": {
"desktop:macos": {
"sidebar-light.markUnread": { "code": "KeyD", "modifiers": ["meta"] },
"sidebar-light.refresh": { "code": "KeyR", "modifiers": ["meta"] },
"sidebar-light.tabWorkspaces": { "code": "Digit1", "modifiers": ["meta"] },
"sidebar-light.tabTags": { "code": "Digit2", "modifiers": ["meta"] },
}
}
}
Why refresh is a plugin command instead of the built-in
page.refresh: the host's shortcut service captures ⌘R before page scripts (it registers earlier), and the built-inpage.refreshdoes nothing in the desktop app — hence "⌘R never worked". Registeringsidebar-light.refreshand binding ⌘R to it in the user overrides makes the host service dispatch to this command, and refresh finally works.Keybinding changes need a DSH restart (the desktop build has no file watcher).
How the target session is found: a shortcut has no menu context, so the plugin reads a semantic
attribute on the host's session row — [data-row-key^="session:"][aria-selected="true"] (not a CSS-module
hashed class name). With no selected session it returns blocked (no session selected).
⚠️ The command id is a user-visible contract (
keybindings.jsonis keyed by it); renaming it leaves existing bindings silently dormant.
Language: the plugin's strings (including the Tags view-options menu) follow DSH's language setting — the source of truth is the host's own ``, so switching DSH to Chinese switches the plugin immediately (no restart; a refresh is enough).
Each tab pill carries a tooltip: hovering shows its chord (e.g.
Tags (⌘2)). If you rebind it in Settings the hint follows (read from the host; falls back to the shipped chord).
Install
dsh plugin --profile add \
https://github.com/luzonghao/dsh-sidebar-light/releases/latest/download/dsh-sidebar-light.tgz
A new bundle needs a DSH restart; after that, code changes only need a page refresh. Other sources (folder / npm / git), uninstall and the manual equivalent are in INSTALL.md.
Why it won't go stale
The indicators are not hand-drawn copies of the host's styling — they are clones of the host's own
StateDot component inserted into the row. The search box, the menu and the header buttons likewise
reuse the host's own implementation (reading its class names / measuring its computed styles / using its
buttons directly), so a DSH upgrade carries the plugin along instead of breaking it.
The plugin does only three kinds of things to the host, all bounded:
- insert cloned status lights and the tag button into rows;
- mount its own overlay on
document.body(Tags view, menu, refresh button) — never inside the host's React subtrees; - when it must make room, write inline styles (restorable to their original values) and never `` attributes — those trigger a host layout recompute (once squeezing the main column from 1402px to 546px; see index.md §63).
Self-check and diagnostics
node test/logic.test.mjs # 173 offline unit tests, no DSH needed
It also offers channels that do not need a console (the desktop app has none):
- the refresh button at the right of the system row;
- ⌥-click the refresh button = copy diagnostics (
safety/probe/panel) to the clipboard; - errors surface themselves: when the plugin catches one, the text appears bottom-right instead of hiding in a tooltip.
__dshSidebarLight.probe() // anchors / overlay / element rects
__dshSidebarLight.safety() // circuit-breaker state and captured errors
__dshSidebarLight.layoutSelfTest() // withdraws each plugin trace and measures the main column
__dshSidebarLight.surface('sidebarTab', false) // disable the Tags tab (effective after refresh)
Known limitations
- Cosmetic blemish (the Tags view-options menu): to match the host, the menu is rendered from a clone of the host's own menu (same structure, classes, icons, spacing). The template is captured the first time you open Workspaces → View options; before that (or if the capture fails) the plugin falls back to a self-drawn menu with measured styles, where row height and the group-label font size / spacing can differ slightly from the host. It is a visual difference only — group / order / filter all work. To get the cloned version immediately: open Workspaces → View options once.
- The Tags view renders in the plugin's own overlay (the host has no "sidebar view tab" slot). If a host release reshapes the sidebar DOM (class names / nesting), the view may stop working — it then hides itself rather than leaving a blank sidebar.
- Membership comes from the React side (
useWorkspaces/sessionStatus), so collapsed workspace groups still count toward the aggregate; - Tags and unread marks live in browser-local
localStorage(the active tab insessionStorage) and do not sync across machines; - If the host's
⋯menu slot changes shape, the menu entry disappears silently (the lights are unaffected); - When a session row already shows a dot of the host's own, this plugin does not stack an unread dot on top.
Docs
| File | Contents |
|---|---|
| INSTALL.md | install / uninstall / verify / publish |
| index.md | design, decisions and the full revision history (post-mortems included) |
| LICENSE | MIT |
Disclaimer
Third-party plugin, not affiliated with DeepSeek. Installing a third-party dsh plugin runs its code on
your machine with your own permissions — read the source first.