# dsh-enter-swap [中文](README.md) | English A minimal DSH (DeepSeek Harness) web plugin that swaps the composer keys: **Enter inserts a newline, Shift+Enter sends.** Zero dependencies, install and go; a slide switch in the composer tool bar restores the native keys at any time. ## Features - **Enter → newline; Shift+Enter → send** - **Persistent keymap chips in the composer tool bar, left of the model selector: 〔Shift+Enter 发送消息〕〔Enter 换行〕** (since 0.1.2; moved to the native engine slot in 0.1.4 — never overlaps typed text, never intercepts the mouse, theme-neutral grays, follows the composer as it grows or remounts) - **Slide switch** next to the chips: turn the swap off and the native keys return immediately (Enter sends, Shift+Enter inserts a newline) with zero interception; the preference is stored per browser, survives reloads, and stays in sync across open tabs (since 0.1.5) - Ctrl/Cmd+Enter (native accelerated delivery) and Alt+Enter stay native - IME composition is never intercepted (Chinese/Japanese/Korean input safe) - While a candidate menu (slash commands / @mentions) is open, Enter keeps selecting menu items as usual - No settings page, no network requests, no file I/O, zero npm dependencies ## Install ```bash dsh plugin --profile web add github:xuqiuba888/dsh-enter-swap ``` Same for any other profile (`--profile `). Restart dsh afterwards (restart the Desktop/NEXT app), then hard-refresh the browser/UI (Ctrl+Shift+R) to activate. ### Update & uninstall ```bash dsh plugin --profile web update dsh-enter-swap # update to latest dsh plugin --profile web remove dsh-enter-swap # uninstall; refresh restores native keys ``` ## Repository layout ``` ├── package.json # dsh.client (web plugin) + dsh.bundle (profile patch) declarations ├── cordis.patch.yml # bundle patch: inserts the plugin line ├── pnpm-lock.yaml # dependency snapshot (zero deps; lockfile-only convention) ├── lib/ │ └── index.js # Host half: empty apply() (no host behavior needed) └── client/ └── client.js # Client half: capture-phase key swap + switch ``` ## How it works - Listens for `keydown` in the capture phase on `document`, and only acts when the target sits inside the composer (`[data-composer-input="true"]`) - Gesture rules: Alt/Ctrl/Cmd combos always pass through; `isComposing` / keyCode 229 passes through; a portal menu mounted directly under `` **and actually visible** (`role="menu"` / `"listbox"`, i.e. the slash/@ candidate popups) passes through so Enter selects the item — hidden leftover portals (e.g. the workspace picker after creating a new session) do NOT count as an open menu - On a hit: `preventDefault + stopPropagation`, then re-dispatches the swapped gesture as a synthetic KeyboardEvent to the engine — **the engine keeps owning the semantics (send / queue / newline)** - The switch persists to `localStorage['dsh-enter-swap:enabled']` (absent or unreadable counts as ON), syncs across tabs through the `storage` event, and short-circuits the keydown listener on its first line when off — with the switch off the plugin is inert - The client bundle is plain JS in the `__ModuleLoader__` factory format; no build step ## Tests ```bash node test/client-switch.test.mjs ``` No dependencies and no browser: stubbed React and DOM globals run the real client bundle. Covers default-on, interception and gesture swap, click-to-off, zero interception while off, cross-tab sync, garbage-value fallback, denied storage, and listener cleanup. ## Compatibility DSH 0.2.x (developed and verified against the 0.2.1-rc.2 web UI). Uses no engine-version-specific internal APIs. ## License MIT