# dsh-plugin-type-to-compose **Type or paste anywhere in the DSH window and it goes straight into the current conversation's composer — no need to click the input box first.** Ever clicked a message to read it, then wanted to keep writing? Or selected some text and then started typing? Without this plugin the keystrokes go nowhere, because the composer no longer has focus. This plugin hands the focus back the moment you start writing. [中文说明](README.md) · MIT · no dependencies · no build step | Where you are | Keys | Result | |---|---|---| | Message area / sidebar button / page background (focus on `body`) | `a` `1` `/` `中`… | ✅ focus moves to the composer, the character lands in it | | Message area / sidebar button / page background | **⌘V / Ctrl+V / Shift+Insert** | ✅ focus moves to the composer, the pasted content lands in it (images and files included) | | The composer itself | anything | ✅ normal editing, nothing doubled | | Another editable (search box, custom answer field) | anything | ⛔ untouched, input stays where it is | | Terminal (`.xterm`) | anything | ⛔ untouched | | An open dialog / menu (`role=dialog[aria-modal]`, `role=menu`) | anything | ⛔ untouched, the layer owns the keyboard | | Composer disabled / not ready (Hero state, blocked session) | anything | ⛔ untouched | | — | every other ⌘/Ctrl/Alt chord, Tab, Enter, Esc, arrows, Backspace, Space | ⛔ untouched | ## Install ```bash # from a local clone dsh plugin --profile desktop add "link:$PWD" # or straight from GitHub dsh plugin --profile desktop add "github:daoyu1993-lab/dsh-plugin-type-to-compose" ``` Then **refresh the page (⌘R)**. `dsh-hmr` watches the profile manifest, so a newly added bundle is picked up without restarting the app. Uninstall: ```bash dsh plugin --profile desktop remove dsh-plugin-type-to-compose ``` The plugin never modifies a shipped file — remove it and the UI is exactly as before. ## How it works DSH's composer is a Lexical-owned `contenteditable` carrying the stable anchor `[data-composer-input]` (set by `ComposerContentEditable` in `@deepseek-ai/dsh-client-ui-conversation`). The plugin installs **one document-level `keydown` listener (bubble phase) plus one `paste` listener**, and all it does is move the focus: ``` keydown / paste (the user starts writing) → is this a text/paste intent, and does the target belong to someone else? → focus [data-composer-input] → the browser's own default action inserts the text into the newly focused element ``` No synthetic input events, no slot registration, no CSS. The worst case is "the condition stops matching and the plugin does nothing" — it can never break the composer. - **Bubble phase** so that React's handlers have already run: `defaultPrevented` is then a real signal, and anything DSH consumed is left alone. - **Paste moves focus only** for ⌘V / Ctrl+V / Shift+Insert, so the shipped `PASTE_COMMAND` handles text, images, files and history exactly as if the box had been clicked. Pastes that never produce a keydown (the native Edit menu on macOS, middle-click paste) are caught by the document `paste` listener, which re-delivers the **original `clipboardData`** to the composer. - **The caret** is handed over the way the shipped input bar does it: DOM focus plus Lexical's own `focus()` (which restores the caret the user left behind); a composer without a Lexical handle falls back to "caret at the end". - **IME** start keys (`keyCode 229` / `key: "Process"`) move the focus but are never `preventDefault`ed, so Pinyin composition is not disturbed. - **Space** is left alone: it is a text key, but it is also how readers page down. Flip `DEFER_SPACE` in `client.js` to take it over too. ## Verification `tools/test-type-to-compose.mjs` loads `client.js` verbatim into a DOM replica of the DSH shell (sidebar button, two conversation panes, search input, xterm-like terminal, modal layer) and drives real key events through the Chrome DevTools Protocol: ```bash node tools/test-type-to-compose.mjs # needs Node ≥ 21 and Chrome/Chromium ``` **43/43 checks pass, exit code 0** — typing from the page/sidebar, paste landing on the composer, the fallback forwarding with the original clipboard payload, caret behaviour with and without a Lexical handle, IME start keys, guards for other editables / terminal / dialogs / hidden panes, no duplicated insertion, and listener removal on unload. Two mechanism probes record the evidence behind the design choices: - `tools/type-to-compose-probe.mjs` — focus-only vs `preventDefault` + manual insertion (identical results; the plugin picks the side-effect-free one). - `tools/paste-to-compose-probe.mjs [--headful]` — where a paste lands with a real system clipboard: nothing takes it with no handler, the composer takes it after the ⌘V keydown focuses it, and the document listener forwards it otherwise. ## Compatibility - Depends on the `[data-composer-input]` anchor (a purpose-built data attribute, not a CSS-module hash). If upstream ever renames it the plugin silently stops matching — no error, no broken UI. - Web surface only; the host half is an intentional no-op (every profile bundle needs a Node-side module). - DSH currently has no unmodified single-key global shortcut (only arrows / Enter / Esc and modified chords), so taking over printable keys shadows nothing. If that ever changes, add the key to the skip list. ## License MIT