概览
dsh-workspace-groups
README / ZH
插件文档
English · 简体中文
dsh-workspace-groups
A DeepSeek Harness (DSH) web client plugin for sidebar workspace grouping. Upgrades the DSH web sidebar list into a three-level Category folder → Project folder → Session tree with drag-and-drop ordering, attention filters (status, color, recency), and tree search. Backed by full group management (create, rename, delete), rule-based auto-classification, sidecar YAML configuration, and runtime overlay persistence — with zero intrusion on official core data.
Quick Install
dsh plugin --profile web add github:PavelLizunov/dsh-workspace-groups
Activation requires restarting the existing web profile through your normal service or process manager. See Installation for verification and uninstall instructions.
Current UI
Captured from the current DSH Web build; workspace and session labels use privacy-safe demo names.
| Grouped sidebar | Bulk tree controls | Finder-style filters |
|---|---|---|
Features
Grouped tree browsing
- Group folder → project folder → session row, both levels collapsible; expansion state
persists independently (
dsh.workspace.groups.view.v1, survives refresh/restart) - Bulk tree controls & shortcuts: global Collapse all, Expand groups only, and Expand all controls manage tree density; per-group recursive commands and Option-click (macOS) / Alt-click (Windows/Linux) on a Group disclosure chevron recursively expand or collapse a group and all contained project folders (see BULK_TREE_CONTROLS_SPEC.md)
- Top-level project rows: ungrouped projects (matching no rule, dragged out of a group, or returned by a group delete) render as plain rows right after the group folders, at the same level — there is no "Uncategorized" bucket
- Visible attention markers: an amber Awaiting pill marks a pending interaction or an SDD approval response, while a red Error pill marks error, interrupted, and max-token turn endings; collapsed project and group rows bubble up the highest-priority state
- Bounded session lists: expanded projects show five session rows by default (plus the selected session when needed), with transient Show all / Collapse controls
Group management (full lifecycle)
- Create groups manually: the "New group" button in the section header shows the group immediately (empty groups render too)
- Rename / delete any group: every group row (rule categories included) has a hover
⋯menu; deleting a group sends all of its projects back to the top level; rule-category rename/delete rides the overlay (renamed/hidden), the rule YAML stays untouched - Rule-based auto-classification: the sidecar YAML declares category rules (
pathPrefix/pathExact/nameContains/basenameContains); edit the config to adjust grouping without touching code
Drag-and-drop grouping + ordering
- Drag projects into groups: drop on any group row or on a project row inside a group (cross-group move = overrides the rule classification)
- Drag projects OUT of a group: the entire top-level area is the move-out drop target while dragging, shown with an insertion line (not a highlight box) — drop on any top-level row (reorder before/after it), on the blank space below the last row (append), or, when the top level is empty, a standalone line under the last group folder; grouped projects also have a "Move out of group" menu item (rule-classified ones included)
- Reorder projects inside a group: top half of a project row = insert before it, bottom half = insert after it
- Reorder top-level projects: top-level rows are draggable too — top half = insert
before, bottom half = insert after; the top-level order persists under
workspaceOrder["__topLevel__"] - Reorder groups: group rows are draggable — top half of another group row = move before it, bottom half = move after it
- Insertion position indicator: a 2px line (above/below the row) shows the exact drop point while dragging — what you see is where it lands
- Stable expansion while dragging: project and group rows keep their current expansion state throughout drag-and-drop, so rows never move under the pointer during native dragstart
- Distinct row icons: group rows use a folder glyph, project rows a project glyph (same as the official workspace browser) — groups and projects are easy to tell apart
Search, filtering & operations
- Tree-shaped search: results keep the three-level structure (category → project → matched session), matched rows highlighted with a content snippet, 250ms debounce
- Finder-style filtering: the status scope switches between All / Needs attention / Running / New; Needs attention includes both Awaiting and Error sessions. The Filter menu adds one group/project color and an optional 24-hour, 7-day, or 30-day recency window. Text, status, color, and recency narrow the result together.
- Profile-persisted filters: status, color, and recency are stored in the active DSH profile and restored after refresh or in another browser. Already-open browsers pick up changes on the next page load.
- Visible criteria, transient expansion: active filters stay visible with one Reset action; empty branches hide, while the filtered tree keeps the current expansion and remains collapsible. Expansion changes made while filtering are temporary and isolated from the persisted idle state. Filtered search results keep the five-session preview and Show all / Collapse controls.
- Fixed filter controls & active chips: status scope bar, filter controls, and active summary chips remain fixed at the top of the workspace browser while the tree list scrolls underneath.
- No regression on workspace/session actions: Add Workspace, project rename/delete, session new/open/rename/fork/archive.
Persistence & zero intrusion
- Every manual action (groups, grouping, ordering, rename, hide) is written to the plugin's own
overlay (
~/.dsh/workspace-groups.manual.json), validated by the host and written atomically (a malformed write returns 400 and keeps the previous file) - Filter selection uses the official profile settings service; no settings file is edited directly.
- Attention detection uses existing
pendingInteractionstate plus one framework-managed incremental session projection; it never scans transcripts or adds session-list requests. - Zero intrusion: never touches
~/.dsh/storages/workspace.json, session on-disk structures, or the official@deepseek-ai/dsh-client-ui-workspacepackage; the rule YAML is never rewritten - Self-contained artifact:
lib/is prebuilt and shipped with the repo — installing from Git runs no dependency scripts
How it works
- The plugin is a client plugin registered into the official sidebar shell's
sidebar.workspacesslot (kind: 'single') atpriority: -1, replacing the official WorkspaceBrowser (registered at priority 0; lowest priority wins in a single slot). - All data comes from the runtime API: the
useWorkspaces/useSessionsglobal hooks andctx.workspaces.*/ctx.sessions.*— grouping is purely a presentation-layer transform. - The host half does two things: parses the sidecar YAML and merges it with the runtime
overlay, served to the client via
GET /workspace-groups/config(Cache-Control: no-cache); andPUT /workspace-groups/manualaccepts the full overlay (manual groups, per-workspace grouping overrides, group/project ordering, rule-category renames and hides), validates it and writes it atomically to$DSH_HOME/workspace-groups.manual.json. - Classification priority: manual override (written by drag/menu;
null= forced top-level, rules ignored) → YAML rule classification (hidden rule categories are inert) → top level (ungrouped projects render as top-level rows). The YAML is never rewritten.
Installation (GitHub distribution)
Prerequisite: DeepSeek Harness installed (
dshavailable) with a target profile initialized (e.g. the built-inweb).
dsh plugin --profile web add github:PavelLizunov/dsh-workspace-groups
This automatically:
- Adds
"dsh-workspace-groups": "github:PavelLizunov/dsh-workspace-groups"(pinned to version/commit) todependenciesin~/.dsh/profiles/web/package.json - Appends
"dsh-workspace-groups"todsh.profile.bundles - Runs pnpm install and validates the bundle layer
Restart the existing web profile after installing (both the bundle and the host half only
load on restart). Use the service/process manager that owns the current instance; for a foreground
setup, stop the existing dsh web command before starting it again.
Verify the install:
dsh --profile web --dump-config | grep -A3 workspace-groups
# expect: - id: workspace-groups / name: dsh-workspace-groups / config: {}
curl http://127.0.0.1:3080/workspace-groups/config
# expect: the sidecar YAML parsed as JSON
Uninstall
dsh plugin --profile web remove dsh-workspace-groups
This removes the dependency from dependencies and the matching line from
dsh.profile.bundles. A web profile restart is required for it to take effect.
Manual equivalent (pick one, don't repeat): edit
~/.dsh/profiles/web/package.json, remove thedsh-workspace-groupsline fromdependenciesand"dsh-workspace-groups"fromdsh.profile.bundles, then runpnpm installin that directory.
Classification config (sidecar)
Default location ~/.dsh/workspace-groups.yaml (override the home dir with the
$DSH_HOME env var). Template: workspace-groups.example.yaml at the repo root.
categories:
- name: DSH Plugins
rules:
- pathPrefix: /home/user/projects/SkillsManagePlugins
- nameContains: plugin
- basenameContains: plugin
- name: Personal Projects
rules:
- pathPrefix: /home/user/projects/yeluzi
Rule fields (each rule is an OR — any match classifies; categories are matched in order, first match wins):
| Field | Meaning |
|---|---|
pathPrefix |
Project absolute path prefix |
pathExact |
Project absolute path exact match |
nameContains |
Project display title contains (case-insensitive) |
basenameContains |
Project directory name contains (case-insensitive) |
Projects matching no category — or moved out of a group — render as top-level project rows (same level as the group folders), never hidden.
Manual groups & drag-and-drop grouping (runtime overlay)
Besides the rule YAML there is a plugin-owned runtime overlay, recording only manual
UI operations, at $DSH_HOME/workspace-groups.manual.json (e.g. ~/.dsh/workspace-groups.manual.json):
{
"categories": ["Scratch", "Archive"],
"assignments": {
"a1b2c3d4-e5f6-7890-abcd-ef1234567890": "Scratch",
"a1b2c3d4-e5f6-7890-abcd-ef1234567891": null
},
"categoryOrder": ["Scratch", "DSH Plugins"],
"workspaceOrder": { "Scratch": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] },
"renamed": { "DSH Plugins": "Plugin Collection" },
"hidden": ["Docs"]
}
categories— manually created group names (no rules; empty groups render too).assignments— workspace → group classification overrides keyed by the stable workspace id (renames don't affect it). Takes precedence over YAML rules; a value ofnullmeans forced top-level (even when a rule would match).categoryOrder— group display order (top-level rows are not listed here; they always render after the group folders).workspaceOrder— per-group manual ordering of projects (written by drag ordering).renamed/hidden— UI rename/delete of rule categories (a hidden category's rules become inert and its matches go top-level); the rule YAML stays untouched.- The file is written in full by the browser UI (
PUT /workspace-groups/manual, atomic replace); manual edits also take effect on next load. A malformed write returns 400 and keeps the previous file — the rule YAML is never at risk. - The PUT contract is fail-closed: wrapped writes require both a non-empty
expectedRevisionand a completemanualobject; legacy flat writes require explicitcategoriesandassignments. Missing, incomplete, or mixed formats are rejected before validation or I/O.
| Action | How |
|---|---|
| Create group | "New group" button in the section header (folder icon), enter a name in the dialog |
| Rename/delete group | hover ⋯ menu on any group (rule categories included); deleting sends its projects back to the top level |
| Set group/project color | hover the row and use its color button; the compact portal menu stays inside the viewport |
| Drag project into group | drag a project row onto a target group row / any project row inside a group, release to move |
| Reorder projects | drag a project row onto another project row in the same group: top half = insert before, bottom half = insert after (indicator shows the spot); expansion stays unchanged while dragging |
| Reorder top-level projects | drag a top-level row onto another top-level row: top half = insert before, bottom half = insert after; order persists under workspaceOrder["__topLevel__"] |
| Move out of a group | drop anywhere on the top-level area (an insertion line shows the spot — reorder before/after a top-level row, or append below the last row; when the top level is empty a line shows under the last group), or the project row's "Move out of group" menu (forced top-level) |
| Reorder groups | drag a group row onto another group row: top half = move before, bottom half = move after (indicator shows the spot; expansion stays unchanged) |
| Bulk expand / collapse | header controls for Collapse all, Expand groups only, or Expand all; Option/Alt-click on a group disclosure chevron recursively toggles that group and its nested projects |
Topics
This repo targets automatic discovery by the DSH plugin ecosystem (community marketplaces scan GitHub topics). Already set:
dsh-plugin(core tag; 1024Store and similar marketplaces discover by this topic periodically, validatingpackage.json+ the plugin bundle manifest (cordis.patch.yml))deepseek-harness/deepseek-harness-plugin/dshsidebar/workspace/workspace-groups
package.json also provides keywords for npm/search indexing.
Development
pnpm install
pnpm typecheck # host + client dual-program type checking
pnpm test # core rules, overlay, tree derivation unit tests
pnpm build # build lib/ (node half + client bundle)
pnpm watch # tsdown watch (client HMR)
node scripts/verify-groups.mjs # real-browser CDP verification (host restarted; self-spawns a headless Chrome, auto-restores the scene)
Artifact contract (mirrors the official client packages):
lib/index.js— host half (ESM; reads the sidecar +/workspace-groups/configroute; js-yaml inlined, no runtime dependencies)lib/client.js— browser half (window.__ModuleLoader__.load({id, factory}); only requires platform seeds: react / react/jsx-runtime / @deepseek-ai/dsh-client-runtime/client / @deepseek-ai/dsh-client-ui-primitives; cross-plugin value imports are rejected at build time by the purity gate)lib/types/**— declaration files
Release strategy:
lib/build artifacts are committed (nopreparescript), sodsh plugin add github:...never runs third-party build scripts — install and use.
Repository layout
src/
index.ts # host half: config snapshot route + manual write route
host-config.ts # sidecar YAML reading/validation
host-manual.ts # runtime overlay read/write/validation (atomic publish)
context-types.ts # host-side cordis service structure types
core/
types.ts # config types (shared by both halves)
matcher.ts # classification + manual override priority + ordering pure functions (shared)
client/
index.ts # apply: registers sidebar.workspaces (priority -1)
contract.ts # injected surface types
stores.ts # expansion-state store (persist: dsh.workspace.groups.view.v1)
tree.ts # three-level tree derivation + tree search derivation
tree-filter.ts # pure status/color/recency filtering and counts
GroupsBrowser.tsx # browser region component (group dialogs + drag grouping/ordering + insertion indicator)
rows.tsx # category/project/session/search-result rows (drag sources/targets)
locales.ts # locale key contract and dictionary exports
locales/en.ts # primary English dictionary
locales/zh.ts # optional Simplified Chinese dictionary
styles.css # inline styles
tests/
core.test.ts # classification rules + override priority + moveBefore/moveAfter + config parsing
manual.test.ts # overlay validation + atomic file round-trip
tree.test.ts # tree derivation rendering contract (manual group empty render / override priority)
store.test.ts # expansion semantics (collapse writes false, never deletes the key)
scripts/
verify-groups.mjs # real-browser CDP verification (self-spawns headless Chrome, auto-restores the scene)
Root
AGENTS.mdgives coding agents the architecture, verification commands, generated-artifact rule, and repository safety constraints. Internaldocs/remain untracked.
Verification
pnpm verify: TypeScript checks, the full Vitest suite, and isolated consumer package verification.pnpm build: reproducible Host/client bundles and declaration artifacts inlib/.node scripts/verify-groups.mjs: optional real-browser CDP suite with scene restoration; it requires a compatible local browser and an already activated plugin build.- The current
mainbuild is automation-verified and confirmed in a live-GUI smoke test.
Upstream & credits
This project is derived from the original upstream repository z-col/dsh-workspace-groups. Copyright and license attribution remain with the original author as specified in LICENSE.
License
MIT
