Skip to content
dsh.fish
Bundle

dsh-plan-build-toggle

DSH web client plugin: a persistent OpenChamber-style Plan|Build toggle on the composer (with Tab-to-switch), driving the native /plan command channel over the host plan projection

Source
jdqingm
License
MIT
Updated
Updated 6 days ago

Readme

# dsh-plan-build-toggle

A DeepSeek Harness web-client plugin: a **persistent, OpenChamber-style `Plan|Build` toggle** on the composer, driving DSH's native `/plan` channel over the host `plan` projection.

English | [简体中文](README.zh.md)

## Why

DSH already ships plan mode end-to-end (`@deepseek-ai/dsh-plan-mode`: the `/plan` / `/plan off` commands, the per-session `plan` projection, the `exit_plan_mode` review). Its built-in composer control (`dsh-client-ui-plan`), however, only reveals a "Plan ×" chip **after** plan mode is on — there is no visible mode control in the default Build state.

OpenChamber's form factor is different: a persistent mode control on the input bar, with bare **Tab** switching between Build and Plan. This plugin fills exactly that gap on DSH — same seat, same command channel, no second vocabulary of modes.

## What you get

- **Persistent segmented pill** (`Plan|Build`) in the composer's mode-control area (the built-in chip's own seat, `conversation.input.plan`). Active segment highlighted: Plan in warn colors, Build in neutral.
- **Two-way switching** through `command.execute` — clicking Plan runs `/plan`, clicking Build runs `/plan off`, exactly as if you typed them.
- **Tab / Shift+Tab to switch** (the OpenChamber/opencode convention) while the caret is in this session's composer editor. Strictly scoped:
  - bare Tab only — any ⌘/Ctrl/Alt chord passes through untouched;
  - yields to any earlier handler that already consumed the event (`defaultPrevented`), so a future autocomplete-over-Tab keeps priority;
  - fires only when focus is inside the session's own editor (popups, settings, other sessions are unaffected);
  - does nothing when the `plan` projection is absent (drafts, presets without plan mode) or while a switch is in flight.
- **Host projection is the source of truth**: the shown target is `pending ? !active : active` — a folded host value, not client optimism. Switches made while the agent runs queue as `pending` and self-correct as projection frames arrive.
- **Inline error state** when the command channel rejects a switch.
- **Replaces the built-in chip** cleanly: the bundle patch disables the stock `ui-plan` row, so the single-instance seat never collides. Uninstall to restore stock behavior.

## Boundaries (same as DSH plan mode)

- Plan mode is **soft guidance** (a policy prompt section), not an enforced sandbox. For hard read-only planning, compose separate sandbox / approval presets — this plugin only drives the mode vocabulary.
- No control on draft sessions (plan state is per-session).
- Exiting still happens via the model's `exit_plan_mode` review, rendered by the Web plan-review flow.

## Install

### Via the in-app Community Market

Once published, the plugin appears in **Market → Installable** on your selected catalog source (npm-backed, repository-backlink verified). Install there, or with the official CLI:

```bash
dsh plugin --profile desktop add dsh-plan-build-toggle@0.1.0
```

### From source (no npm publish needed)

```bash
git clone https://github.com/jdqingm/dsh-plan-build-toggle.git
cd dsh-plan-build-toggle
bash scripts/install.sh desktop   # copies into the profile + registers the bundle
```

Either way: **restart the DSH app** afterwards — the browser roster is computed at host boot.

## Uninstall

1. Remove `dsh-plan-build-toggle` from `dsh.profile.bundles` in `~/.dsh/profiles/<profile>/package.json` (or uninstall via Market/CLI if installed that way).
2. Delete `~/.dsh/profiles/<profile>/node_modules/dsh-plan-build-toggle`.
3. Restart the app — the stock "Plan ×" chip returns (the `ui-plan` row is no longer disabled).

## How it is built

Two halves, no build step:

- `lib/client.js` — the browser half: a hand-written `window.__ModuleLoader__.load` payload (the same module format the official client plugins ship). React comes from the shared module graph; CSS is injected once under a `data-plugin-css` guard.
- `lib/index.js` — an empty node-side `apply()`, so the package appears as a host roster row; `dsh.client` in `package.json` declares the browser half and its load-order deps.
- `cordis.patch.yml` — the bundle patch layer: inserts the roster row and disables the built-in `ui-plan` row (seat handover). Listed via `dsh.bundle.patch`, so adding the package to `dsh.profile.bundles` is all the mounting it needs.

Iteration: edit `lib/client.js`, re-run `scripts/install.sh`, refresh the page. The webserver serves plugin assets with `cache-control: no-cache`, so no app restart is needed unless the roster itself changes.

## License

MIT

Install

dsh plugin --profile web add github:jdqingm/dsh-plan-build-toggle

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source