* feat(release): publish package source, declaration maps, and documentation Every release member publishes its TypeScript source and the declaration maps that resolve into it, and @deepseek-ai/dsh publishes the repository Markdown documentation staged from docs/ at prepack time. The ./src/* export most members already declared now resolves for an installed consumer, and a consumer editor follows a declaration to the implementation. Bundled JavaScript maps stay unpublished; the vendored family and the Desktop runtime file policy are unchanged. Rationale: .agents/notes/implemented/process/2026-10-08-published-source-and-docs.md * feat(release): publish JavaScript maps and compress bundles Every emitted JavaScript map ships beside the bundle it describes, and the workspace build and the client preset compress bundles without mangling identifiers. The documentation payload now mirrors the website publication manifest and is removed again by `postpack`; Desktop and the Python executable exclude the published source, documentation, and maps; `clean` removes a staged copy. Typert's publication contract gains the Remote declaration map. * fix(release): keep node bundles readable for the worker image The browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, so compressed node-side entries either fail that lowering or lower to unparseable code. Only the browser bundles keep compression, which removes about a third of their text. The bundle-purity assertion now matches the module-loader call regardless of the quote style compression picks, and the release-pack fixture exercises the installed package's new documentation requirement. * feat(release): publish the whole documentation tree `@deepseek-ai/dsh` copies `docs/` in full instead of the website's page set: the maintainer documents, the generated migration records, the translation guidelines, and the `*.i18n.yaml` consistency records ship with the installed package, so the documentation's own links resolve without a checkout. * feat(release): publish the upgrade guides with the documentation The staged documentation keeps the website's page set and adds every `docs/upgrade-guide/` document, which is what an installed reader consults when a released change breaks something. The rest of the maintainer tree stays out. * test(release): expect source and maps in master's new payload cases * feat(release): keep installed docs and source readable in Desktop Desktop keeps the published src/ and docs/ trees inside app.asar, and every model shell call receives DSH_INSTALL_DIR and DSH_NODE_EXECUTABLE so an agent can read them through Electron's Node mode. Staging also generates docs/upgrade-guide/README.md, an index of the upgrade guides grouped by the release they upgrade from. Minified client bundles print string literals with backticks: the worker image packer now recognizes those chunk requests, client license banners are prepended after minification so their text survives, and the affected artifact tests accept either quote. * test(desktop): read archived docs through the Windows PowerShell form Regenerate the config catalog for the shifted shell-env source line. * test(desktop): wait for the GUI-subsystem Electron process from PowerShell * build(release): publish source and maps from the message-quotes package * build(release): publish source and maps from packages new on master * build(release): publish source and maps from the tool-office package
description, kind
| description | kind |
|---|---|
| Add actions to right-click menus without importing a React component | package-reference |
@deepseek-ai/dsh-client-ui-context-menu
English | 中文
Summary
Add actions to right-click menus without importing a React component. Targets retain the clicked Session, Workspace, file, directory, URL, image, or pane even when selection changes. Contributions can discover metadata asynchronously and disappear safely when their plugin unloads.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
The Web application bundle mounts this Client plugin. External Client plugins declare the package in dsh.client.inject, import its /client types, and inject the contextMenus service. resolutionTimeoutMs bounds each provider’s preparation (1000ms by default); timed-out providers are aborted and omitted with visible failure feedback.
The plugin injects useContextMenu into every Slot scope through ctx.slots.provideRoot. Call const { getTriggerProps } = useContextMenu() once in the component and spread getTriggerProps(target, handlers?) onto each trigger. The factory is an ordinary function and can run in loops. It handles right-click, Shift+F10, the ContextMenu key, viewport coordinates, and focus without scrolling. Existing handlers run first; preventDefault() keeps their ownership and prevents duplicate handling by composed or nested bindings. Repeated, composing, and unrelated key events remain untouched. Pass undefined to retain native behavior, or a target resolver to read business state when the gesture begins. The resolved object is captured before focus changes. Plain children receive event props or a factory through props or an existing delegate; no additional React Context is needed. Special editors and canvases can call ctx.contextMenus.open() directly.
Use renderFactorySlot('contextMenu.button', { target, label, className?, size? }) for an ellipsis trigger. label is localized by the caller; size: 'sm' selects the compact toolbar button with a label tooltip, while omission retains caller-styled row geometry without a tooltip. The factory owns focus, pointer and click isolation, the bottom-edge anchor, and expanded state. Each rendered button toggles only the popup it opened: activating it again closes that popup even after the button moves or its target's display fields change, while another button for an equal target opens its own popup. Repeated right-click and keyboard gestures keep the 4-pixel rule. Target families need no identity rule here. It hides only when the target kind has no registered contribution, without resolving actions early. The stable ctx.contextMenus.registeredKinds observable publishes registration and withdrawal immediately, including providers activated after the button mounts. A registered provider returning no actions still makes its button available.
Register actions
Call ctx.effect(() => ctx.contextMenus.register({ id, target, order, resolve })) in the contributing plugin. resolve receives the captured target and its cancellation signal; return localized actions with stable IDs, optional icons, disabled state, submenus, and execution callbacks. Contributions sort by order, then ID. Duplicate contribution IDs within one target family fail registration; duplicate action paths reject that provider’s result. IDs are encoded by segment before publication.
An action’s optional commandId associates it with an existing shortcut command. The overlay subscribes to ctx.shortcuts.catalog, including configured, removed, and reset bindings; nested and footer actions use the same presentation. This association never dispatches the command: selection invokes only the action’s captured run callback. The menu plugin requires the shortcuts service.
A contribution can declare observe(target) sources for permissions, membership, or localized labels. Source updates re-resolve that contribution against the same captured target and replace its callbacks; outdated asynchronous preparations are cancelled. Dismissal and plugin unload release every subscription.
Extend ContextMenuTargetMap through declaration merging on @deepseek-ai/dsh-client-ui-context-menu/client for a new target family; ContextMenuTarget derives the { kind, value } union from that map. Open a target with ctx.contextMenus.open(target, { x, y }). Keep target values as immutable JSON-compatible identifiers and metadata. Execution callbacks capture that value; they must recheck mutable resources before acting. New registrations participate in the next opening. Unregistering removes visible actions and cancels pending metadata.
ctx.contextMenus.resolve(target) gives a separate occurrence for another renderer. Subscribe to its immutable action views, select a published ID, and dispose it when its owner closes. Views contain presentation fields only. Callbacks remain in the registry and execute at most once. Dismissal and replacement cancel metadata, but an action already selected owns its own execution and cancellation. Rejected providers and actions produce a persistent overlay toast; an action’s localized failureLabel supplies operation-specific recovery guidance.
ctx.contextMenus.notify(text, success, action?) can attach a localized text action through the shared Toast. The action provides label, run, and failureLabel; clicking dismisses the toast before execution, and rejection reports its failure label. A dismissed or replaced toast cannot execute its action again.
Shared popups wait for every contribution to settle or time out before appearing. A repeated gesture within four CSS pixels closes the same target; a farther gesture replaces it with a separate fade. ready gives custom renderers the same preparation point. Structured action icons align within each divider group; component contributions retain their own layout. Normal dismissal retains the popup for 80ms, replacement for 10ms; opening waits at least 20ms and fades in over 40ms. Reduced motion suppresses the fade.
Actions carry icon?: { key: string; src?: string }. Register the matching glyph component with ctx.slots.inject('contextMenu.icon', () => ctx.slots.register({ name: 'contextMenu.icon', key: 'my-plugin.action' }, Icon)). The overlay dispatches renderSlot by that key for ordinary, nested, and footer actions; the component receives the captured icon metadata, including an optional image URL. Registrations follow the contributing plugin and menu Slot declaration lifetimes. Action views contain no JSX nodes.
The optional keyed contextMenu.content Slot hosts component contributions. Existing Session and tab Slot names remain owned by their feature adapters. Keyboard context keys and Shift+F10 use the focused trigger’s lower edge. afterContent places a data action after those rows; ordinary actions render before them.
Understand the implementation
Implementation internals — click to expand
The registry separates resolution and callback lifetimes from presentation. The shared overlay uses the existing Menu primitive for keyboard movement, focus return, dismissal, submenus, and viewport positioning. Registry owns cancellation and ordering; target and action types own the public fields.
Further Exploration
- Slots — component contributions.
- File actions — original content transfer.
- Open in app — shipped resource actions.
Model Experience
None, as popup presentation and registration do not construct model input.
KV Cache effect
None; contributed feature actions own any subsequent model interaction.
Known Limitations and Deferred Work
- The shipped renderer is the shared Web Menu, including inside Desktop. Native menu rendering is not supplied.
- Component-only legacy actions do not appear in the plain-data resolution API.
- Menus are window-local and transient; they are not persisted in Session data.
Dev Note
Working context for maintainers — click to expand
None.