# dsh-quote-to-chat Select text in a DSH reply and a **floating toolbar** appears: quote the fragment into your composer draft, open a side chat about it, or just copy it. The kind of action bar Codex and the ChatGPT desktop app show on selection. ![The toolbar on selection](docs/verify-light.png) [中文说明](README.md) · no runtime dependencies · the self test needs nothing but Node ![verify](https://github.com/QIN-SMART/dsh-quote-to-chat/actions/workflows/verify.yml/badge.svg) ## Install ```sh # from GitHub (recommended) dsh plugin --profile web add github:QIN-SMART/dsh-quote-to-chat # from npm, once published dsh plugin --profile web add dsh-quote-to-chat ``` Then reload the browser page. **Optional dependency**: [`dsh-better-sidebar`](https://github.com/omdsh-dev/DSH-better-sidebar) provides side-chat threads. Without it the "ask in a side chat" action is hidden entirely and everything else keeps working. ## Usage | Action | Result | |---|---| | Select text in a reply | `Add to chat \| Ask in a side chat \| Copy` appears above the selection | | **Add to chat** | Inserts `> selected text` plus a blank line into the draft, returns focus to the composer, confirms with "added" | | **Ask in a side chat** | Expands the right sidebar with a side-chat thread (inheriting the session context) and pre-fills the quote there, cursor at the end | | **Copy** | Copies the selection | | Escape / click away / scroll | Toolbar closes | | Selecting inside the composer | **Nothing appears** (the toolbar never fights itself) | | Selection is a code block | Quoted as a ` ```lang ` fence instead of a blockquote | | Selection is prose | `> line` blockquote; multi-paragraph selections keep block breaks | | `dsh-better-sidebar` missing | The action is **hidden entirely**, never a dead button | The toolbar uses the native DSH menu material (`--dsw-specific-menu`, `--dsw-menu-backdrop-filter`, `--dsw-elevation-prominent`) and follows the light/dark theme: After **Add to chat** the draft holds the quote and focus is back in the composer: ![Quote inserted into the draft](docs/verify-composer.png) ![Dark theme](docs/verify-dark.png) ## Why not simply the native menu's 58% transparency The native menu fill (`--dsw-specific-menu`) is only **58% opaque** (light `#f8f9fa94`, dark `#43454a73`). That is designed for a dropdown floating over a flat surface; a selection toolbar floats over **dense body text**, so using it alone lets the two layers bleed into each other. The first version had exactly that bug: ``` v1: background-color = rgba(248, 249, 250, 0.58) ← glyphs pressed straight through the labels ``` The theme ships a 94% variant (`#f8f9faf0`) but its selector is `html[data-platform=darwin] body`, and this build **never sets `data-platform`** — that rule is dead code the native menu does not benefit from either. The fix: lay the native fill over one opaque application-layer colour. ```css background-color: var(--dsw-alias-bg-layer-1, #fff); background-image: linear-gradient(var(--dsw-specific-menu, …), var(--dsw-specific-menu, …)); backdrop-filter: var(--dsw-menu-backdrop-filter, none); /* kept, so a future translucent fill degrades gracefully */ ``` The hue is still the native menu colour (light ≈ `#fbfbfc`, dark ≈ `#313235`) but nothing shows through, and `elementsFromPoint` reports plugin, then plugin, then transcript. ## How the side chat is wired DSH itself has no side threads; `dsh-better-sidebar` adds Codex-style side chats, so the action is an **optional dependency**: 1. **Open the thread** through its public service: `ctx.get('betterSidebar').openTab({ type: 'sidechat' }, { sessionId })`. `ctx.get` rather than `inject` is the correct way to read an optional dependency in DSH (missing returns `undefined` instead of throwing). An already-open side chat is reused rather than spawning a new thread per click. 2. **Write the quote** through the DOM: its composer is a plain React-controlled `