dsh-sidebar-entries

uitested: passnpmMIT

Sidebar overhaul for the dsh web UI: long-press drag to reorder the panel icon rail, one arrow to fold it away, whole-row height caps on both the panel list and the pinned section (30% of the rail, rounded down to whole rows), a pinned-session section that mirrors the shipped row's live status (spin

下载量 / Downloads
3,008
依赖体积 / node_modules
0.59 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-sidebar-entries

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-sidebar-entries

![npm version](https://www.npmjs.com/package/dsh-sidebar-entries)

A DeepSeek Harness plugin that makes the

left sidebar behave the way a long list should: drag to reorder the panel icons, **one

arrow to fold them all away** so the workspace / session list gets its height back, a

pinned-session section at the top of the workspace list that keeps its live status,

whole-row height caps on both lists so a crowded rail can never eat the session tree,

archived Sessions sunk to the bottom instead of interleaving with live work, **draggable

conversation view tabs, and a generic lift** that pulls every plugin's sidebar-footer entry

up into the same rail so nothing is split between the top and the bottom of the column.

The sidebar panel list is a stack of icon rows (插件, 插件市场, 自动化任务, …). Once a

handful of plugins are installed the stack eats the column and pushes the session list into a

narrow strip. This plugin adds three things to that stack:

  • Long-press drag to reorder. Press an icon for ~0.3 s, then drag. The row lifts and follows

the cursor; the neighbours slide out of the way with a short FLIP animation; the order is

written to localStorage on release. A short click still selects the panel — only a deliberate

long press starts a drag, so nothing navigates by accident.

  • A fold arrow at the top of the list. A plain chevron (v when everything is shown, ^

when folded) that hides every panel icon in one click. The state survives a reload. The arrow

itself never moves and can never be dragged, so there is always a way back.

  • Footer entries lifted into the rail. Any plugin that registers an action in the sidebar

footer gets a row up here automatically — see below.

And one thing to the list below it:

  • A 置顶 section. Pinned Sessions are gathered into one group at the very top of the

workspace list and their originals are hidden, so a pinned Session appears exactly once no

matter which workspace it belongs to. Unpinning from the copy works; the copy is rebuilt

whenever the store changes.

Three things to the tab bar above the conversation:

  • Drag the conversation view tabs to reorder them. 对话 / 轨迹 / 上下文 / … reorder by

dragging, with the same lift-and-FLIP feel as the rail. The order is kept in localStorage

and reapplied on every render. The 对话 tab cannot be moved — the shipped header pins it

first — and how many tabs fit is decided by the bar's own width, not by this plugin.

And two that are about not letting the list get long:

  • Archived Sessions sink to the bottom of their group. The shipped row for an archived

Session never even attaches draggable, so those rows used to sit wherever they landed and

interleave with live work. This plugin reorders them by updatedAt (newest first) to the end

of each group and draws a hairline above them, leaving live rows in exactly the order the

Host chose.

  • A whole-row height cap. Both the panel-icon stack and the 置顶 section are capped at 30%

of the rail's height, rounded down to whole rows — six 36px rows, not "six and a sliced

seventh". The cap is recomputed on resize, so folding the window never leaves a half row

hanging.

Layout

lib/index.js     host half  — diagnostics routes (see below), no runtime dependencies
lib/client.js    browser half — wraps in window.__ModuleLoader__.load; requires('react') from the host
cordis.patch.yml bundle patch — inserts the host row under the id `sidebar-entries`

The two halves are independent: the browser half is the actual feature, the host half only

provides the /sidebar-entries/* routes used for self-diagnostics and for hot-reloading the

client bundle while developing.

How the reordering works (and why it is done in the DOM)

DeepSeek Harness ships a pin/unpin model for sidebar panels (createPanelPins), but it is

memory-only — it resets on every reload — and it permanently pins the plugins row. This plugin

therefore keeps its own order in localStorage (dse.sidebar.order.v2) and replays it onto the

list on every render:

  • rows are identified by aria-label, grouped so that two rows sharing a label move together and

keep their relative order;

  • the "More" row is never draggable and never moves — it is the insertion anchor;
  • a MutationObserver on the list re-applies the saved order after React re-renders, with an

"already in order → do nothing" short-circuit so the plugin cannot ping-pong with itself.

Nothing in the shipped app is patched; the plugin only manipulates the rendered list.

How the pinned section works

The shipped list already sorts pinned Sessions to the front of their own group, but they stay

scattered across workspaces. This plugin lifts them into one group:

  • a MutationObserver on [class*=listArea] re-derives everything after each render;
  • whether a row is pinned is read from the pin indicator the row component itself renders, so no

cached list can drift out of date;

  • the original rows are display:none (not removed — React owns them), and a copy of each is

appended to a 置顶 section inserted as the first child of the list area;

  • a copy carries the markup but none of React's handlers, so a click on one is replayed on the

same element of the original by walking the child index path;

  • the exception is the pin toggle: clicking it in a copy calls uiWorkspace.unpinSession(id)

directly, the same service the shipped button calls. Replaying a click into a row that is

currently display:none is exactly what made "取消置顶" dead from inside the section;

  • the copy's ⋯ menu button is h

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::21,最后更新 2026-11-05

Links

GitHub · Homepage · npm

Same category