Repository navigation
Releases
v1.6.5 — unified toolbar and annotation layout fixes
Selection Toolbar 1.6.5
本次更新集中修复选区工具条、批注入口和侧边对话的交互问题。
- 单消息与跨消息选区共用一个工具条,包含「批注 / 引用 / 侧边对话」。独立引用浮窗已移除,不再通过隐藏第二个窗口实现合并。
- 修复跨消息选区经过复制、状态按钮时不弹框的问题,以及快速重选、旧按钮状态和重复文字锚点问题。
- 主动取消选区时清除旧摘录;流式重渲染造成选区缩短或丢失时保留原始摘录。
- 待发送批注入口移到输入区左侧的预留行,修复刚保存批注后覆盖消息操作栏、需要滚动才恢复的问题。
- 侧边对话核对目标线程后填入草稿,不自动发送;停用、热替换和切换会话时取消旧异步操作。
- 精简并更新中英文 README;开发环境要求与 DSH 对齐为
^22.19.0 || >=24.0.0,CI 改为 Node 22 / 24。
安装
在 DSH 插件页面更新 dsh-selection-group,或添加下方附件 dsh-selection-group-1.6.5.tgz 的下载地址。更新后完全退出并重启 DSH。
发布包包含已构建的 lib/ 和 client.js,以及构建输入、测试和许可文件,无需先自行构建。侧边对话动作需要启用 dsh-better-sidebar。
验证
- Node 22 / 24 的 CI 均通过全部 115 项自动化测试,覆盖选区、批注布局、流式恢复、线程归属和生命周期。
- 检查解包后的构建一致性和完整测试,核对附件与本地发布包的 SHA-256。
- 批注布局已用模拟本机 DSH 贴底结构的 Chrome 测试页验证;实际桌面效果仍需按使用环境核对。
完整变更见 CHANGELOG。
v1.6.4 — side chat action, chat-only scope, self-verifying package
Selection Toolbar 1.6.4
自 1.3.8 以来的全部变更。最要紧的两条:
- 新增第三个动作「◧ 侧边对话」:把选中文字原样放进
dsh-better-sidebar的侧边对话输入框,
不自动发送、零 token(上下文要你自己按发送才提交)。 - 插件只在对话里生效:侧边栏、以及任何文件内容上的选区都不再弹框。
完整变更见 CHANGELOG.md。
[1.6.4]
修复(发布包)
- npm 发布包缺构建输入,装下去无法自测:
files白名单里没有vendor/、patches/、scripts/、src/,
而scripts/build.mjs第一步就要读它们。实测在解包目录里跑npm test:
Error: ENOENT .../src/toolbar.js(补src/之前)与vendor/manifest.json(补之前)。
现在四者都在files里,发布包大小 103 KB → 204 KB(26 个文件),
并在解包出来的干净目录里验证过:npm test→ 69/69 通过,其中包含 vendor sha256 校验与补丁落盘断言。 build.mjs新增--check模式:只校验client.js是否与vendor/ + src/一致,不重写文件。
npm test/npm run verify与 CI 都改用它——否则测试流程可能先把产物"修好"再去断言它,漂移就被洗掉了。
已实测:无漂移时退出 0 并打印版本号与行数/字节数;人为改动后退出 1 并打印两边尺寸以及第一处差异
(行号、字符偏移、两侧各 24 个字符的上下文)——只报尺寸是不够的:版本号从 1.6.3 换成 1.6.4 时
两个尺寸完全相同,报告会变成"漂移了但看起来一模一样"。build.mjs读取构建输入失败时,现在抛出指名的诊断(哪个文件、哪个目录该带上),而不是深层 ENOENT。.gitignore忽略*.tgz(本地打包验证用,不提交)。
[1.6.3]
修复(范围收紧:只在聊天里生效)
- 在侧边栏打开的文件里选文字,也会弹出这个框。 现在整个插件只在对话内容里生效;右侧栏
(文件树 / 文件查看器 / 侧边对话 / 终端 / 任务)与任何文件内容——不管在右侧栏还是中间主区打开
——都不再出现这个框。 - 根因是我自己的补丁,不是上游:上游 annotation 的 settle 里有
if (rootEl === null) return这道
"这算不算可批注的根"的门;我为了"选中当回合立即出框"(immediate-toolbar)重写了那段,把这道门漏掉了。
而这个工具条只在点击动作时才用到ui.selectionRoot,画框从不检查它——所以任何选区都会被画上框。
现在把rootEl === null的判断原样保留在立即绘制之前。 - 同时按要求把文件正文路径也关掉:上游 annotation 本来主动支持宿主文本预览(
DOCUMENT_TEXT+
[data-textpreview-url]),而dsh-better-sidebar自己已经在文件查看器上提供「添加到对话」——
两套叠在一起既有噪音、动作又是错语境的,所以合并包彻底不做文件选区。 - 右侧栏整体拒绝:注解包里任何落在
[data-slot="sidebar"]/sidebar.*子树内的选区都返回"没有根",
这样即使是一个聊天行被渲染在右侧栏(例如侧边对话视图),也不会弹框。
测试
69 项(原 66 项)。新增三条:文件预览里选文字 → 完全没有框(且独立「引用」按钮保持隐藏);
先选文件再选对话 → 对话照常出框(确认不是一刀切关掉);右侧栏里的聊天行也不出框
(按列拒绝,而不是只按行类型判断)。
[1.6.2]
变更
- 按钮文案从「◧ 放进侧边对话」改回 「◧ 侧边对话」(英文
◧ Side chat)——功能不变,
只是名字更短。悬浮提示仍写明"不自动发出、发送与否由你决定"。
[1.6.1]
修复(实测两次踩空:输入框定位)
-
「放进侧边对话」在真机上完全没生效:提示"输入框 4 秒内没有出现"。我按结构定位那个输入框的四条规则全错,
而且每一条都被我自己写的过浅测试桩放过了:我写的规则 真实情况 卡片的第一个子元素是按钮 第一个子元素是 <textarea>,按钮在第 3 层 → 永远不匹配按钮的 closest('div')就是卡片落到了卡片内层的 bar,那里没有输入框 从 textarea 往上找"包含按钮的祖先" 爬过了界,把选中文字写进了同页无关的文本编辑器(探针实测) 容器必须直接拥有按钮 按钮是 bar 的子元素,不是卡片的 → 仍不匹配 -
现在的规则:从每个
<textarea>出发,它自己所在的那个容器里必须在两层内存在发送按钮
(title="发送"/"Send",这是那个插件自己的本地化文案);若多个候选,取离按钮最近的那个 textarea。
这样既匹配真实结构(字段与 bar 是兄弟,按钮是 bar 的子元素),又不会跨容器误抓。 -
测试桩换成真实嵌套:之前那个桩把按钮放在卡片第一层,让错规则也能"通过"。现在桩复刻真实结构
(textarea 是卡片第一个子元素、按钮在 bar 里),并新增两条:同页更早位置放一个无关编辑器 textarea
时只有侧边对话输入框被写、编辑器分毫不动;没有发送按钮时必须判定为"没有输入框",绝不猜一个。
[1.6.0]
已被 1.6.1 修复:1.6.0 的输入框定位有误,真机上不会生效。
变更
- 去掉引用外壳,选中文字原样放进侧边对话输入框:不再包
**引用自主对话:**标题与>前缀,
就是把选中文字当纯文本放进那个输入框(光标在末尾)。读起来和你自己粘进去的一样,随手可改。 - 按钮文案随之改为 「◧ 放进侧边对话」(英文
◧ Send to side chat);行为不变——不自动发送、
零 token,上下文仍要等你按发送才提交。 - 诊断里
sidechatLast的quoted改名为inserted(语义变准了)。
[1.5.0]
变更(行为变更:不再自动发送)
-
第三个动作改为「◧ 侧边引用」,且不再直接发出:选中文字后,新建一条空的侧边对话线程,并把
选中文字作为引用块写进那个 tab 自己的输入框,光标停在末尾——发不发、什么时候发由你决定。
点这个按钮不再消耗任何 token:线程是最小创建(question: ''),上下文要等你按发送才提交。
这修掉了 1.4.0 的行为:当时点一下就把选中文字当第一条消息发出去,等于替你付了一次上下文注入的钱。 -
写进输入框的引用块形状(那个输入框是纯
<textarea>,没有 chip 机制;1.6.0 已去掉这层外壳):**引用自主对话:** > 选中的第一行 > 选中的第二行输入框里已有草稿时追加,不覆盖。
实现要点
- React 受控输入框:那个输入框是 React 18 受控组件(元素上装了自己的
value访问器,
直接field.value = …会被下一次渲染抹掉)。写入必须走原型链上的valuesetter(绑定到元素本身)
再派发input事件,React 才会当成用户输入接收。 - 不靠类名定位输入框:该插件的 CSS 模块类名带哈希、重建即变。改为按结构定位——
"最后一个子元素是<textarea>、第一个子元素是按钮"的卡片;因此不会误抓会话输入框(contenteditable)
或侧栏文件编辑器。找不到时明确提示("请先展开右侧底部工作台"),不会假装引用放进去了。
诊断
sidechatLast取值改为quoted/no-composer/no-session/failed(1.4.0 的sent已不存在)。- 排障表新增一行「有按钮但引用没进输入框」;
window.__DSH_SELECTION_GROUP_SIDECHAT_POLL_MS__
可改等待输入框出现的时长(默认 4000ms,测试用)。
修复(实测到的崩溃)
SIDECHAT_DRAFT_POLL_MS is not defined:重构时把常量声明改成了函数,却把默认分支读的那个常量
留成了纯引用,于是第一次真机点按钮就报"侧边对话失败:SIDECHAT_DRAFT_POLL_MS is not defined"。
浏览器 bundle 没有构建期符号检查,而当时所有测试都设了 window 覆盖值,正好绕过出问题的那条路径
——所以 62 项全绿也没抓到。现在:常量补回;测试默认不再设置覆盖值(只在需要缩短等待的那条用例里设),
并新增一条不设任何覆盖的用例,断言默认路径会真的等满 4 秒后才报"输入框没出现"(若常量缺失,
会在同一个微任务里立刻变成failed);另加一条静态审计(test/contract.test.mjs),
扫描 glue 里所有大写_下划线标识符,凡是用到却没声明的一律测试失败。
测试
64 项(原 59 项)。新增:空线程创建(question 为空、没有任何 prompt 请求)、引用块内容与
「已有草稿则追加」、React 受控路径(模拟 React 18 的 own value 访问器,断言写入经原型 setter 生效)、
输入框不存在时提示、「选中文字绝不上线」、不设任何 window 覆盖值跑默认路径(崩过一次的正是这条),
以及 glue 常量声明的静态审计。
[1.4.0]
新增
- 第三个动作「◧ 侧边对话」(已被 1.5.0 取代:当时是"把选中文字作为第一条消息直接发出")
在dsh-better-sidebar的侧边对话里新建线程。对接方式(不去改那个插件):
POST sidebar/api/sidechat.start建线程 → 把线程 id 写进它自己的dsh-sidebar:v1:sidechat-thread键
(先写后开)→ 用它的扩展服务ctx.betterSidebar.openTab({ type: 'sidechat' }, { sessionId })打开 tab。 - 诊断新增
sidechatEnabled / sidechatService / sidechatLast / sidechatLastThread / sidechatLastError。
设计取舍(1.5.0 仍然成立的部分)
- 邻居是可选依赖,不是依赖:
dsh-better-sidebar不存在(或没启用)时,该动作根本不发布,
框里仍是「批注 + 引用」。因为服务在每次渲染时重查(而不是inject一个可选服务——那会让整个
fiber 等一个可能永远不来的邻居),所以它之后才挂载也能被接上。 - 失败一律出声:主会话没在运行时那条路由会拒绝(409)、路由取不到、答案不是 JSON、
childId缺失——每一种都给出带原因的提示并不开 tab;线程建好但 tab 打不开则明说"线程已建好、
tab 没打开"(它仍能从工作台的线程菜单里找到)。 - 截断:选中超过 4000 个码点只放前 4000 个(尾部
…)。 - 请求只在按下时发生:这是本包唯一的网络请求(同源一条路由);「批注 / 引用」两个动作不含网络请求。
v1.3.8 — a failing test can no longer hang the job
修复
- 一次失败不再让 CI 整个卡住:测试里断言失败会跳过清理,留下的 jsdom 窗口与插件定时器
会一直占住进程——所以 CI 从来只表现为"卡住",而不是红色失败(三次挂起、交替出现在两格上,
根因就是这个)。现在:插件里的所有定时器一律unref()(浏览器没有该 API,自动成为空操作),
测试用afterEach无条件关闭窗口。用一个"故意失败且不清理"的临时测试验证过:
进程正常退出并报红(pass 34 / fail 1),而不是挂住。 - 随之暴露并修掉的偶发失败:
diagnostics expire by themselves…用固定 500ms 等一个 150ms 的过期定时器,
在 CI 负载下定时器会晚到。测试里的固定等待改为等条件(waitFor,含 250ms 节流写入的最后一份快照)。
本地连跑 4 次全绿,并在 4 路 CPU 负载下再跑 2 次全绿。