# Common Word Box — DeepSeek Harness Client Plugin

English | [中文](README.md)

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A **web client plugin** for DeepSeek Harness (`dsh web`): a "词/W" button sits to the right of the message input; clicking it opens a **persistent** slide-up panel where you manage your frequently used words/phrases and insert them into the input with one click.

![dsh-wordbox](docs/cover_en.png)

Words live in two buckets: **Global** (shared across all workspaces) and **Current project** (isolated per workspace directory).

## Features

- 🖱️ Click the "词/W" button → slide-up panel with an entrance animation, **stays open** (click again / click outside / press Esc to close)
- 🔀 Three display scopes: **All / Global / Current** (switch control at the far right of the "＋ Add" row)
- ⏎ Click a word → **appended to the input** (focus preserved, one undo step, panel stays open for consecutive inserts)
- 🌐 Bilingual zh/en (follows the DSH UI language)

## Install

Prerequisites: DeepSeek Harness `dsh web` + `pnpm` (the `dsh plugin` command forwards to pnpm, so pnpm must be on PATH).

This is a standard **DSH bundle plugin** (`dsh.bundle.patch` declaration with the plugin-row patch shipped inside the package), so both install and uninstall are a **single command**:

### Install

```sh
# Prerequisite: install the official CLI (skip if already installed)
npm install -g @deepseek-ai/dsh

# Install the plugin
# Option 1: from npm (recommended, published on npm)
dsh plugin --profile web add dsh-wordbox

# Option 2: from GitHub (the repo root is the plugin package; no build step, works directly)
dsh plugin --profile web add github:arcmosin/dsh-wordbox

# Option 3: local development (link to this repo)
dsh plugin --profile web add link:D:/path/to/dsh-wordbox
```

After installing, **restart `dsh web`** and the "词/W" button appears next to the input.

> `dsh plugin add` installs the package into the profile and **automatically mounts** it into `dsh.profile.bundles` thanks to the `dsh.bundle` declaration; no patch file editing required.

### Uninstall

```sh
dsh plugin --profile web remove dsh-wordbox
```

Then **restart `dsh web`** and the button disappears. Words saved in the browser (localStorage) are not removed on uninstall; to wipe them, delete these keys manually: `dsh.common-word-box.words.v1`, `dsh.common-word-box.words.project.v1`, `dsh.common-word-box.mode.v1`.

> In some cases `remove` leaves an empty directory/link at `profiles\web\node_modules\dsh-wordbox`; deleting it by hand is harmless.

## Usage

1. Click the "词/W" button next to the input; the panel slides up and stays open;
2. Switch between All / Global / Current at the bottom-right;
3. Click a word to append it to the input (several in a row); hover a row to reveal the × delete button;
4. In "Current" mode, hovering a row reveals an "Add to global" button that copies the word into the global bucket in one click (the current project's word list is untouched; the button is hidden once the word is already global);
5. Hovering a long word slowly flows through the full text at a fixed speed.

## Storage

| Data | localStorage key |
|---|---|
| Global words | `dsh.common-word-box.words.v1` (string[]) |
| Project words | `dsh.common-word-box.words.project.v1` (`{normalized-dir: string[]}`, empty buckets are pruned) |
| Display scope | `dsh.common-word-box.mode.v1` (`all` / `global` / `current`) |

Words are stored in the browser (isolated per browser profile) and synced across workspaces and tabs via the `storage` event.

## Development

- Local debug install: `dsh plugin --profile web add link:<absolute-path-to-repo>`; no reinstall needed after code changes;
- Edits to `lib/client.js` need **no restart**: the host `client-hmr` polls the bundle hash every 500ms and hot-reloads on change (React state resets); a restart is only required after the first install/uninstall or changes to `cordis.patch.yml`.
- The bundle is plain JS in `window.__ModuleLoader__.load({ id, factory })` format — **no build step**, edits take effect immediately.
- Long-word flow speed constant: `FLOW_SPEED` in `lib/client.js` (default 60 px/s).

## Architecture

| Part | File | Description |
|---|---|---|
| bundle patch | `cordis.patch.yml` | The plugin row declared via `dsh.bundle.patch`; auto-mounted into the profile by `dsh plugin add` |
| node half | `lib/index.js` | Empty `apply` so the loader entry activates on the host |
| browser half | `lib/client.js` | Client bundle: `conversation.input.right` slot registration + all interaction logic |
| manifest | `package.json` | `dsh.bundle` + `dsh.client` (`platform: "web"` + inject) + `exports["./client"]` |

- Mount point: the `conversation.input.right` slot (declared by `@deepseek-ai/dsh-client-ui-conversation`, list kind, session scope), rendered in the composer card's trailing area.
- Writing to the input: standard kit `inputActions.setDraft(text)`; reading the draft with `useInput((s) => s.draft)`.
- Waiting for the slot declaration: `ctx.slots.inject("conversation.input.right", () => ctx.slots.register({...}, Component))`.
- Reference shape: `@deepseek-ai/dsh-client-ui-model-selection` (official pure-UI client plugin).

## License

[MIT](LICENSE)
