UI Enhancements
dsh-sidebar-drawer
1dustycy/dsh-sidebar-drawer · ★ 0
Edge-hover drawer for the Web GUI left sidebar: reveal it by moving the pointer to the left screen edge, keep it while the pointer stays inside, retract it when the pointer leaves
安装
dsh plugin --profile web add 'dsh-sidebar-drawer@0.1.1'从 npm 安装预构建包
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
English | 简体中文
An edge-hover drawer for the left sidebar: while the sidebar is collapsed, moving the pointer into the leftmost 10 px of the screen and holding it there for 500 ms pulls the sidebar out; it stays out the whole time the pointer remains inside the drawer, and retracts by itself once the pointer leaves.
A pure browser-behavior plugin (the host half is empty). It only borrows DSH's own sidebar toggling and changes no built-in file.

What it does
- Dwelling is what counts — the pointer must stay inside the leftmost 10 px for 500 ms before anything happens; sweeping past the edge never triggers it by accident.
- Inside the drawer means staying — while the pointer rests inside the drawer (reading, clicking, idling) the drawer stays out and nothing rushes you.
- No interrupting an animation — a second toggle is never issued while an open/close transition is in flight, so the drawer neither jumps nor vanishes mid-flight.
- Slides, not jumps — the shipped frame writes the column width first and publishes its transition marker afterwards, so a plain toggle is instantaneous (a retraction looks like the drawer simply disappearing); before requesting a toggle the plugin pre-arms the frame's own marker, so both the reveal and the retraction really slide.
- A hand-opened sidebar is left alone — a sidebar you opened yourself with the button, Cmd+B, or a width drag is never touched and never retracted.
- Yields — width drags, window blur, and page scroll each have their own yield rule.
Row-by-row scenarios, state-machine invariants, and known gaps: docs/behavior.md (Chinese).
Install
This is a DSH plugin bundle: it ships its own cordis.patch.yml and declares dsh.bundle.patch in package.json, so Plugin Manager installs it and owns the mount row — you never hand-edit the profile's patch file.
# From npm (recommended: pins the version)
dsh plugin --profile desktop add dsh-sidebar-drawer@0.1.1
# From GitHub (also version-pinned, through the repo's v0.1.1 tag)
dsh plugin --profile desktop add "github:1dustycy/dsh-sidebar-drawer#v0.1.1"
# Track the latest commit on main (to pick up behavior changes early)
dsh plugin --profile desktop add github:1dustycy/dsh-sidebar-drawer
# From a local directory (for development: writes a link: dependency, edits take effect immediately)
dsh plugin --profile desktop add /path/to/dsh-sidebar-drawer
Replace desktop with the profile you want (e.g. web, tui). The command does two things: it adds the package to dependencies in ~/.dsh/profiles/<name>/package.json, and adds dsh-sidebar-drawer to that profile's dsh.profile.bundles. Once installed, refresh the page and it is live — no app restart needed (the client half is hot-loaded through the bundle's mtime polling plus /plugins/events).
Uninstall:
dsh plugin --profile desktop remove dsh-sidebar-drawer
Why not just add a row to the profile's
cordis.patch.yml? That file is shared with other sessions, and when it gets overwritten the symptom is a plugin that looks installed but does nothing — with nothing in the plugin itself to show for it. Rationale in ADR-0003.
Requirements: DSH Desktop (or any profile with a Web GUI). The client half injects @deepseek-ai/dsh-client-ui-layout, so the target profile must already mount the Web layout bundle. peerDependencies is @deepseek-ai/cordis >=4.0.4 <5 — when installing from a registry, Plugin Manager checks that range first and refuses before anything is downloaded if it does not match.
Development and verification
node test/client.test.mjs # unit suite: vm + DOM doubles + an advancing virtual clock, no browser needed
node test/browser.test.mjs # browser suite: real Chromium + CDP real mouse events and real CSS animations
npm test # runs both suites, in that order
node tools/probe.mjs # diagnostic tool: prints the decision trail of one edge dwell
npm pack --dry-run # confirm what gets published
After editing lib/client.js, refresh the page and the change is live (provided the profile's hmr row is enabled, which the desktop profile does by default).
test/browser.test.mjs brings its own HTTP server and browser process and exits when it is done; it needs Chromium on the machine (it skips itself when none is found). SHOT=<path> node test/browser.test.mjs saves the final "drawer open" frame as a PNG (that is where docs/harness-open.png comes from). The animation at the top is the other way round — a screen recording of the real GUI, not the fixture — and node tools/demo-gif.mjs <recording> regenerates it, which needs ffmpeg on the machine. test/harness.html is a faithful replica of the shipped three-column frame: open it in a browser with ?plugin=<URL of client.js> to try the behavior by hand.
Read docs/implementation.md before touching the test fixtures. Both fixtures must model the shipped shell's anchor contract exactly: once a fixture renders a conditional anchor unconditionally, it goes all-green on a broken bundle — which is precisely how this plugin once shipped a defect where a pointer resting motionless on the edge toggled the drawer forever.
Docs
| Document | Contents |
|---|---|
| docs/behavior.md | Behavior spec: row-by-row scenarios, state-machine invariants, known gaps |
| docs/implementation.md | Implementation notes: shipped anchor contract, decision logic, tunable constants |
| docs/adr/ | Decision records (why it is built this way) |
| CONTEXT.md | Glossary: Chinese wording mapped to code identifiers |
The detailed docs are Chinese-only for now; this file is the English entry point.
License
MIT