# DSH Mobile Keys [中文](./README.md) | **English** A **mobile hotkey mode** plugin for DeepSeek Harness (DSH) — a browser-side bundle plugin. It auto-detects mobile devices and switches the send/newline key bindings, fixing the fact that mobile soft keyboards cannot produce `Shift+Enter`, which makes the composer impossible to wrap lines on. Preferences persist in browser `localStorage`; no backend needed. ## ⌨️ Key bindings | Mode | Send | Newline | Notes | | --- | --- | --- | --- | | Desktop (default) | `Enter` | `Shift+Enter` | Identical to native DSH; the plugin stays out of the way | | Mobile mode | `Ctrl+Enter` | `Enter` | Send rides DSH's native accelerated gesture; newline intercepts plain `Enter` and re-dispatches a synthetic `Shift+Enter` through DSH's own line-break path | ## ✨ Features - 🔍 **Auto-detection**: defaults to `auto` mode — classifies the device by UA / touch points / pointer type / viewport width and follows rotation or monitor changes live. Touch-screen laptops and kiosk touch displays are never misclassified. - 🎛️ **Settings toggle**: DSH Settings → General → "**Mobile hotkey mode**" switch, live status line, one-tap "restore auto-detect" after a manual override. - 💬 **`/mobilekeys` command**: check status and switch Auto / On / Off right from the composer. - 🛠️ **Console API**: `window.__dshMobileKeys.getStatus() / setMode() / getBindings()`. - 🛡️ **Zero intrusion elsewhere**: IME composition, open overlays (menus/dialogs/command palette), question-card inputs, and anything outside the composer all pass through untouched; in desktop mode the plugin does nothing at all. - 💾 **No backend**: preference stored in `localStorage` (key `dsh.mobilekeys.v1`), remembered per device. ## 🤖 AI install Hand the following prompt to your AI (DeepSeek / Claude, etc.) and it will install the plugin for you: > Please install the `dsh-mobile-keys` plugin into the DeepSeek Harness web profile: run > `dsh plugin --profile web add git+https://github.com/liujuntao123/dsh-mobile-keys.git`, > then restart `dsh web` and hard-refresh the browser (Ctrl+F5), then run > `window.__dshMobileKeys.getStatus()` in the browser console to confirm it returns > `{ mode, detected, effective, bindings }`, and confirm the "Mobile hotkey mode" toggle > appears in Settings → General. Full AI install instructions (manual method, troubleshooting, verification): **[AI_INSTALL.md](./AI_INSTALL.md)**. ## 📦 Installation ### Method 1: install from GitHub (recommended) ```bash dsh plugin --profile web add git+https://github.com/liujuntao123/dsh-mobile-keys.git ``` DSH will automatically: pnpm-install the git dependency → detect the `dsh.bundle` declaration → add `dsh-mobile-keys` to `dsh.profile.bundles` in `package.json`. > Requires `pnpm`. If missing: `npm install -g pnpm`. ### Method 2: manual install ```bash cd ~/.dsh/profiles/web # Windows PowerShell: cd $env:USERPROFILE\.dsh\profiles\web pnpm add git+https://github.com/liujuntao123/dsh-mobile-keys.git ``` Then you **must** add `"dsh-mobile-keys"` to the `dsh.profile.bundles` array in that directory's `package.json`: ```jsonc "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-mobile-keys" // ← required, otherwise it won't load ] } } ``` > ⚠️ `pnpm add` does not touch the `bundles` list; DSH loads plugins from that list, so missing this line means it won't take effect. ### Method 3: local clone + link (for development) ```bash git clone https://github.com/liujuntao123/dsh-mobile-keys.git ``` Point a link dependency at it in the web profile's `package.json`: ```jsonc "dependencies": { "dsh-mobile-keys": "link:/absolute/path/to/dsh-mobile-keys" } ``` and likewise add `"dsh-mobile-keys"` to `bundles`. ### After installing: restart + hard refresh (all methods) ```bash bash scripts/restart-dsh-web.sh # if you cloned this repo; otherwise restart dsh web your way ``` Then hard-refresh the browser with **Ctrl+F5**. ## ⚙️ Usage ### Modes | store.mode | Meaning | | --- | --- | | `auto` (default) | Detect the device and follow it | | `on` | Always mobile bindings (`Ctrl+Enter` send / `Enter` newline) | | `off` | Always desktop bindings (`Enter` send / `Shift+Enter` newline) | ### Settings panel DSH sidebar → gear (Settings) → General → "Mobile hotkey mode", with a two-line description: ``` Current keys: send Ctrl+Enter / newline Enter Detected device: mobile · Recommended on mobile devices to avoid being unable to insert newlines ``` - The switch shows and controls the **effective** mode; flipping it writes a manual value (`on` / `off`). - Line 1: the currently effective bindings, live with the mode. - Line 2: the device verdict and a recommendation; after a manual override a "restore auto-detect" action appears at the end of this line, clearing the override back to `auto`. ### Command & console - Type `/mobilekeys` in the composer → pick Auto / On / Off. - Browser console: ```js window.__dshMobileKeys.getStatus() // { mode, detected, effective, bindings } window.__dshMobileKeys.setMode('on') // 'auto' | 'on' | 'off' window.__dshMobileKeys.getBindings() // { send, newline } ``` ### Verification checklist 1. Settings → General shows the "Mobile hotkey mode" row with a working switch and live status. 2. `/mobilekeys` opens the mode menu. 3. With mobile mode on: `Enter` inserts a newline, `Ctrl+Enter` sends; turning it off restores `Enter`-to-send. 4. With the command menu open, `Enter` still selects an item instead of inserting a newline. ## 🔍 How it works The DSH composer is a **Lexical** editor. `Enter` semantics come from a `KEY_ENTER_COMMAND` handler registered at CRITICAL priority: - `event.shiftKey === true` → the handler returns `false`, falling through to `@lexical/plain-text`'s default → **line break inserted**; - otherwise → **submit** (`ctrlKey/metaKey` count as an accelerated send). So this plugin: - **changes nothing on the send side**: mobile mode's `Ctrl+Enter` *is* the native accelerated gesture; - **touches the newline side minimally**: only while mobile mode is active, it intercepts a plain `Enter` inside the composer at the `document` **capture phase** (`preventDefault + stopImmediatePropagation`), then re-dispatches a synthetic `keydown` with `shiftKey: true` onto the Lexical root (`[data-lexical-editor="true"]`), reusing DSH's own line-break path (selection, undo stack, and controlled updates all behave exactly like a real Shift+Enter). Guard rails: - Only `key === 'Enter'` is considered; `Shift/Ctrl/Cmd/Alt+Enter`, IME composition (`isComposing` / `keyCode 229`), and visible overlays (`[role="menu"|"listbox"|"dialog"|…]`) always pass through; - Targets outside `[data-lexical-editor="true"]` (buttons, question-card inputs, …) always pass through; - Synthetic events carry a `__dshMobileKeysSynthetic` marker to prevent re-entry. Mobile detection (`auto` mode): ``` mobile ⇐ UA matches Android/iPhone/iPad/iPod/Mobile/Silk ⇐ iPadOS 13+ desktop UA (Macintosh + maxTouchPoints > 1) ⇐ (pointer: coarse) AND has touch AND (max-width: 820px) ``` ## 🧪 Development ```bash npm test # dependency-free Node tests: loads the real lib/client.js in a VM, end to end node --check lib/client.js ``` Layout: ``` lib/index.js host-side minimal Cordis plugin (mount/unmount shell) lib/client.js all browser logic (ModuleLoader bundle, no build step) tests/ dependency-free Node tests (node --test) scripts/ restart-dsh-web.sh: restart dsh web with its original command line cordis.patch.yml bundle patch: inserts the plugin into a profile composition ``` No build step — edit `lib/client.js`, restart `dsh web`, hard-refresh. ## ❓ FAQ **Q: Relationship with `dsh-hotkeys`?** A: This plugin borrowed the bundle-plugin shape and conventions from [ctenni/dsh-hotkeys](https://github.com/ctenni/dsh-hotkeys) but focuses on one thing: automatic per-device key-binding switching. No recording — the device is the switch. The two can coexist. **Q: Phones have no Ctrl key — how do I press `Ctrl+Enter`?** A: Tablets with keyboards and phones with external keyboards work out of the box; soft-keyboard-only users can turn the mobile mode off in Settings, or wait for a future "custom mobile send key". **Q: Does the ask_user_question card input change?** A: No. Its `Enter` means "submit/continue" and is unrelated to the composer; the plugin does not touch it. **Q: I don't see the toggle in Settings.** A: Make sure `dsh-mobile-keys` is listed in `dsh.profile.bundles`, `dsh web` was restarted, and the browser was hard-refreshed; `window.__dshMobileKeys` should resolve in the console. ## 📄 License [MIT](./LICENSE)