Bundle
dsh-client-ui-selection-add
Web selection-add feature: float an 'Add to conversation' button over a text selection in the conversation body and prefill the composer draft
- Source
- pangzi-club
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-client-ui-selection-add
English | [中文](README.zh.md)
> Source: [pangzi-club/dsh-client-ui-selection-add](https://github.com/pangzi-club/dsh-client-ui-selection-add)
Web selection-add feature plugin: its browser half contributes a floating
**Add to conversation** button into the conversation-owned
`conversation.input.dock` slot. While a session's composer is mounted the
plugin listens for text selection; a non-empty, non-editable selection floats a
small button just above its bounding box, and clicking the button fills the
composer **draft** (`inputActions.setDraft`) without sending — the user reviews
and hits Enter (or the send button) themselves. Its host half is empty on
purpose: capturing a selection and prefilling the draft is pure browser-surface
presentation, so there is nothing for the Host to own.
The component is mounted in a **session-scope** list slot, so the framework
delivers the session standard kit (`useSession`, `sessionId`, `useProjection`,
`useInput`, `inputActions`) plus the `session`/`input` owner share and the
`selection` locale seat; the component only reaches for `inputActions` and `t`.
Because the button is a body-level portal, the dock entry renders nothing itself
— the composer layout is untouched.
Two behaviors worth calling out:
- **Prefill, not auto-send.** The click is `setDraft(text)`; `submit()` is
deliberately not called, so the user can edit the quoted text before it goes
to the model. (Sending on click is a one-line change in `SelectionAddButton`.)
- **Editable-field guard.** Selections inside an `input`, `textarea`, or
`contenteditable` never show the button — selecting inside the composer is
editing the draft, not quoting the conversation, and the button would fight
the caret.
The button copy is bilingual under the `selection` namespace of `dsh-client-locale`.
The anchor capture lives in a framework-free `selection.ts` module
(`selectionAnchorOf` / `isEditableField`), unit-tested on its own.
## Model Experience
None. Everything here is a browser interaction: the model never sees a tool,
a session event, or a schema. The plugin is a pure presentation surface over
the input machine's public `inputActions` face.
#### KV Cache effect
No invalidation. The plugin reads no session data and publishes no session
events, so the conversation log, projections, and KV caches are untouched.
## Known Limitations and Deferred Work
- **Not scoped to the transcript body.** The selection listener is `document`-wide;
the only guard today is the editable-field check. Selecting in the sidebar or
settings shows the button because the plugin cannot identify the transcript
scroll container without changing `ui-conversation` (the no-core-change choice
above). Scoping it requires either a proper `conversation.selection.*` slot
(see below) or a `data-*` hook the conversation skeleton already owns.
- **Prefill only.** The user must confirm before send; there is no "send now"
affordance on the floating button.
- **Multi-line selections** anchor above the bounding box's top (the first
line); a selection that starts near the viewport top may clip the button.
- **View-chat only, by construction.** The button only mounts while a session
composer is mounted, so it does not appear in the no-session hero. It does not
distinguish which view tab is active.
## Extension point (the "proper slot") upgrade
The current path rides the existing `conversation.input.dock`, so it requires
zero changes to `ui-conversation`. To make the capture a first-class extension
point instead, add a session-scope slot to `ui-conversation`
(`packages/client/ui-conversation/src/client/apply.ts` `children` plus the
`contract/slots.ts` `SlotMap` merge), e.g. `conversation.selection.action`
`{ kind: 'list', scope: 'session' }`, and register this plugin's entry there.
## Build and publish independently (community plugin)
The package is a **buildable, independently publishable DSH client plugin**. It
does **not** need the deepseek-harness monorepo to compile: `package.json`,
`tsconfig.json`, and `tsdown.config.ts` are all standalone, and the
`@deepseek-ai/*` imports are `import type` only, so they resolve from
`node_modules` at their published versions.
### Build
```bash
pnpm install
pnpm bundle # tsc (emits lib/types/**/*.d.ts) + tsdown (emits lib/*.js)
pnpm watch # tsdown --watch
```
Output:
- `lib/index.js` — host half (empty `apply`; the browser half does all the work)
- `lib/invariant.js` — invariant companion
- `lib/client.js` — the browser module-table bundle
(`window.__ModuleLoader__.load({ id, factory })`)
- `lib/types/**/*.d.ts` — TypeScript declarations
### Publish
`pnpm bundle` first (or rely on `prepack`, which runs it automatically), then:
```bash
npm publish # or pnpm publish
```
The published surface is the `files` list: `lib/*.js` (+ maps) and
`lib/types/**/*.d.ts`, plus `cordis.patch.yml`.
### Install into DSH (community plugin)
This package declares the `dsh.bundle` manifest, so it installs via the same
channel as any community plugin (installing it from npm):
```bash
npm install dsh-client-ui-selection-add
dsh plugin --profile web add dsh-client-ui-selection-add
```
or, for a local build:
1. Symlink/junction this directory into
`$DSH_HOME/profiles/node_modules/dsh-client-ui-selection-add`.
2. Append the host row to `$DSH_HOME/profiles/web/cordis.patch.yml`:
```yaml
- insert:
- id: ui-selection-add
name: dsh-client-ui-selection-add
```
(or just `dsh plugin add` writes it for you via `dsh.bundle.patch` →
`cordis.patch.yml`).
The client half registers into the framework-owned `conversation.input.dock`
slot and pulls the `locale` service. Both are provided by the stock `dsh web`
build (`@deepseek-ai/dsh-client-ui-slots`, `@deepseek-ai/dsh-client-locale`,
`@deepseek-ai/dsh-client-ui-conversation`), so a stock `dsh web` build resolves
the runtime. Because these framework packages are **not** all baseline
module-table rows (they're separate `dsh.client` graph rows), a bare
community install only works against a DSH Web build that already activates
them — which is the case for upstream `dsh web`.
### Cross-reference: deepseek-harness monorepo
This repository is the standalone home of the package. The same sources are also
wired into the deepseek-harness monorepo as
`@deepseek-ai/dsh-client-ui-selection-add` at `packages/client/ui-selection-add`;
there the tsdown preset and the three registration surfaces
(`tsconfig.client.json` reference, `packages/bundle/web-app/cordis.patch.yml`
`dsh.client` roster row, `packages/bundle/web-app/package.json` dependency) come
from the monorepo itself. This checkout builds and publishes from the
self-contained configs above instead.
Install
dsh plugin --profile web add github:pangzi-club/dsh-client-ui-selection-add#9d225e41a69a951fabbbde4e6a339cca948df2c2
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-client-ui-selection-add from the hub