@nacocx/dsh-ui-context-menu
English | 中文
An optional out-of-tree DeepSeek Harness bundle that replaces the browser contextmenu gesture with the app's right-click policy. Real Workspace rows get a fully plugin-owned menu (Open in file manager / Rename / Delete), non-blank Session rows get Rename / Fork / Archive, blank space opens New Session / New Workspace / Settings, a text selection under the pointer gets Copy / Cut / Search / Web search, and editable fields get a paste-first editing menu. Every surface is independently switchable in Settings → Plugins, and turning one off restores the native browser menu for it exactly as if the plugin were absent.
Behavior
- Workspace rows. A real Workspace row opens a fully plugin-owned menu: Open in file manager, Rename, Delete. Open in file manager resolves the row's canonical directory through
workspace.listand opens it in the system default file manager through the harness host's native opener; Rename and Delete open plugin-owned dialogs (same rules and copy as the default menu: unchanged drafts are blocked, duplicate titles are refused, deletion explains what is kept) and commit through the host's public/api(workspace.rename/workspace.delete). The row's own ellipsis menu still exists and is unchanged; it is simply no longer the right-click entry. Ungrouped keeps the native menu. - Session rows. A non-blank Session row opens a fully plugin-owned menu: Rename, Fork session, Archive session. Rename opens a plugin-owned dialog (the default's rules: a blank draft is blocked, an unchanged title is allowed — confirming it pins the title) and commits through
session.rename; Fork replicates the default's durable-title increment on the child (session.fork+session.rename) and best-effort opens the child row; Archive commits directly throughworkspace.archiveSession. A row whose display title cannot be resolved to exactly one session falls back to that row's own action menu. Blank New Session and search-result rows keep the native browser menu. - Blank space. A right-click on space that holds no text, no selection, and no interactive chrome opens the app menu: New Session (the New Session button of the group containing the current session, else the first workspace group's, else the sidebar's global one), New Workspace (the workspace header's add button), and Settings (the sidebar settings trigger). Entries whose trigger is absent render disabled.
- Selected text. A right-click on a non-collapsed text selection that covers the pointer opens Copy / Cut / Search / Web search. Search prefills the workspace session search with the selection (expanding the sidebar first in the rail) and falls back to find-in-page when no search surface exists; Web search opens a Bing search for the selection in the system default browser — through
window.openin browsers, the Tauri opener plugin or Electron shell when the packaged app exposes them, and otherwise through the harness host's own native opener (POST /api/host.openPath, the same route the GUI uses for file opens), which makes the stock Tauri desktop work without any app change. - Editable fields. A right-click inside a text input, textarea, or
contenteditableopens Paste / Copy / Cut / Select All. Text entries use the browser editing commands, with clipboard-API fallbacks for copy, cut, and paste. - Everything else. Other interactive chrome and text content keep the native browser menu.
Removing the bundle restores native context menus everywhere without removing the ellipsis entries.
Configuration
A Context menu card inside Settings → Plugins → the configurable-plugins page carries one switch per surface (workspace rows, session rows, blank space, editable fields, text selection) plus Restore defaults. Turning a surface off restores the native browser context menu for that surface exactly as if the plugin were absent — everything else keeps working. The policy is persisted in browser-local storage, so it follows the browser or WebView, not the host settings file.
Compatibility
This plugin targets the accessible Workspace tree structure shipped by @deepseek-ai/dsh-client-ui-workspace (>=0.1.0-rc.5 <0.2.0) and the sidebar shell's New Session and Settings triggers. It does not require an unpublished Harness service or a patched built-in package, so the same bundle works in the browser and in the packaged Tauri WebView.
App-trigger discovery pins the labels of the two shipped dictionaries (Simplified Chinese and English). A future locale addition or a label change in ui-workspace or ui-sidebar may require a compatibility update.
Security model
The plugin never reaches the harness host beyond what the GUI itself already calls: menu actions ride the same-origin /api RPC gateway the web GUI uses (workspace.list, host.openPath, workspace.rename, workspace.delete, session.list, session.rename, session.fork, workspace.archiveSession). Privileged methods (host.openPath) are loopback-gated by the host's own trust fence, so a GUI served to a non-loopback client silently degrades to the fallback paths instead. No secret, credential, or model input is read or written; the policy lives only in browser-local storage.
Install from the registry
dsh plugin --profile web add @nacocx/dsh-ui-context-menu
dsh --profile webThe package declares dsh.bundle, so the install adds its cordis.patch.yml layer to the profile. Registry packages contain prebuilt lib/ files and require no install-time build allowance. To update to a newer release, run the same add command again. Remove it with:
dsh plugin --profile web remove @nacocx/dsh-ui-context-menuInstall from a checkout
Build and verify the repository:
pnpm install
pnpm run checkInstall the checkout into the Web profile from this repository directory:
dsh plugin --profile web add .
dsh --profile webThe package declares dsh.bundle, so the install adds its cordis.patch.yml layer to the profile. Remove it with:
dsh plugin --profile web remove @nacocx/dsh-ui-context-menuInstall from Git
Git installs run the package's self-contained prepare script. pnpm 10 and newer require the profile to allow that build explicitly; pin a reviewed commit and follow the exact allowBuilds diagnostic printed by dsh plugin before retrying the install.
dsh plugin --profile web add github:<owner>/dsh-ui-context-menu#<commit>Registry packages or pnpm pack tarballs contain prebuilt lib/ files and do not require an install-time build allowance.
Troubleshooting
- The context menu still behaves as before after installing. The harness resolves bundles when the server starts: restart
dsh --profile web(a page refresh alone does not reload the bundle list). - Web search or Open in file manager does nothing. These reuse the harness host's loopback-gated native opener; on a GUI served to a non-loopback client they keep their fallback paths (anchor click for the browser, nothing for the file manager), and clipboard-restricted WebViews may refuse paste.
- Configuration resets. The policy is browser-local storage; clearing the browser profile resets every switch to on.
Development
pnpm run typecheck
pnpm run test
pnpm run build
pnpm run pack:checkThe browser bundle is a DeepSeek Harness module-loader closure. It has no runtime imports (React is the loader's shell-own module and stays unbundled). One document-level capture listener classifies the pointer surface — Workspace row, Session row, editable field, selection at the pointer, interactive chrome, or blank space — and either opens the plugin-owned overlay (menu, and dialogs for rename/delete) or reuses the surface's existing trigger. Every menu is a declarative entry table per surface and every Host action rides one shared /api RPC helper, so a future entry is one table row plus a one-line call. The listener, the overlay, the dialogs, a pending search retry, and the injected stylesheet are all registered through ctx.effect() and removed on unload; the peer dependency pins the tested ui-workspace range, and the jsdom suite covers every branch plus unloading.
See [CONTRIBUTING.en.md](CONTRIBUTING.en.md) for the architecture walkthrough, the entry-extension guide, and the release process.
Model Experience
None. This browser-only input gesture registers no prompt, tool, message, or provider input.
Known Limitations
- Row menus remain anchored to their ellipsis button rather than the exact pointer coordinate; only the plugin-owned overlay anchors at the pointer.
- Paste and the clipboard fallbacks depend on the browser or WebView granting clipboard access; when access is refused the entry stays but the operation may do nothing.
- Web search and Open in file manager fall back to the harness host's native opener (
/api/host.openPath, andworkspace.listfor the directory) only when the page is served by the harness host itself; a GUI served to a non-loopback client keeps the anchor fallback instead, because the loopback trust fence refuses privileged methods there. - Overlay copy follows the visible workspace tree's localized
aria-labeland falls back to Chinese while no tree is mounted (the collapsed rail). - The label-pinned trigger discovery is a compatibility boundary with the tested
ui-workspaceand sidebar ranges; a future major release may require an update. - The configuration policy lives in browser-local storage; it does not sync across browsers and it resets when the browser profile is cleared.
License
[MIT](LICENSE) — Copyright (c) 2026 Nacocx