dsh-annotate
点一下页面上的元素,AI 就知道你指的是哪儿
DSH 侧边栏标注插件 —— 在实时预览里点选元素、写批注,直接发进对话。 不用截图、不用描述"右边那个灰色的卡片"。
不需要装浏览器扩展。 这是一个 DSH 插件,装进 DSH 就能用,标注直接进对话。 (和浏览器扩展的区别)

十秒看完:点元素 → 写批注 → 发送
https://github.com/user-attachments/assets/347d20cc-51c7-471d-8911-8a82407ea761
视频托管在 GitHub 附件上。看不了的话,仓库里的 assets/demo.mp4 是同一段。
概览
dsh-annotate 是 DeepSeek Harness(DSH)的 Web 侧边栏插件。 你在实时页面预览里直接点选元素,写上"这里要改什么", 标注就作为结构化上下文随下一条消息发出去。
它解决的问题:你想让 AI 改某个界面元素,但说不清是哪一个。 截图加一句"右边那个灰色的卡片",AI 只能猜,猜错了再来一轮。 装上之后你点一下元素,AI 收到的就是这个元素的 CSS 选择器、真实文本和你的批注。
| 宿主 | DeepSeek Harness(DSH),Profile 必须是 web |
| 类型 | DSH 插件(不是独立应用,不是浏览器扩展) |
| 安装 | 加进 Web profile 的 dsh.profile.bundles,然后重启 DSH |
| Node | >= 20 |
| 平台 | Windows / macOS / Linux |
| 许可 | MIT |
| 依赖 | 无额外运行时依赖(@deepseek-ai/cordis 由 DSH 提供) |
| 网络 | 默认只预览回环地址与工作区文件;allowRemote 打开后可预览任意 http(s) 站点。代理监听 127.0.0.1 |
对口的使用场景:在网页上圈选/标注元素、把 UI 反馈给 AI、让 AI 精确定位 DOM 元素、
可视化地提修改意见、给前端改稿。
当前版本预览本地服务与工作区 HTML;访问在线网站由 allowRemote 配置项控制,
计划在后续版本开放。
不对口的:脱离 DSH 独立运行的标注工具(它必须装进 DSH 的 Web profile)、 跨域 iframe 内部的元素(浏览器的同源策略限制,不是本插件可以绕过的)、 自动改代码(它只负责把"改哪儿"说清楚,改是 AI 的事)。
怎么用
给 AI 改界面,卡住的从来不是"改",是说清楚改哪儿。
截图圈一下,再打字"就是那个卡片,右边那个灰色的"—— 它还是得猜,猜错了再来一轮。
这个插件把这一步压缩成两次点击:
- 侧边栏预览里 点元素 → 高亮框 + 编号针落在页面上
- (可选)写一句要改什么 → 回车
- 标注自动变成结构化信息跟着下一条消息发出去
AI 拿到的是 .lb-hero 和它的真实文本,不是一句模糊描述。
它和浏览器截图标注工具的区别:发出去的是结构化数据,不是图片。 选择器、文本、批注分开成字段,AI 能直接照着定位,不需要"看图猜"。
功能
- 两下点击完成标注 —— 悬停高亮并实时显示选择器,点一下即完成
- 标注 / 批注两种粒度 —— 只想说"看这里"就留空;要说明改什么就写一句
- 选择器经得起推敲 —— 同结构节点、嵌套
nth-child、动态 class 是标注工具最容易翻车的地方。 生成的结果会先验证唯一且指向你点的那一个,不唯一时用元素文本消歧; 实在定位不了会明确告诉你,而不是默默丢掉、留下一个对不上的编号 - 一次带上多条 —— 按页面从上到下排序,编号与页面里的标记针一一对应
- 换页不丢标注 —— 单页应用里点菜单换了路由,已经标好的东西照样挂着; 标记针只画在它当初所在的那个页面上,列表和"已附加"计数不受影响
- 地址栏显示真实地址 —— 显示的是页面现在的完整地址(含路径、
?query和#/hash), 而不是被代理分配的端口号,换页时跟着更新 - 不弄脏你的消息 —— 标注作为独立上下文投递,你打的字里看不到它,也不会被误删
- 技术栈识别 —— 自动探测本地端口并识别框架(Vite / Next / React / Vue 等)
- 本地代理 —— 不裸嵌目标页;与宿主同 site(所以登录态可用),存储按 origin 与宿主隔开
三个特色
下面三张都是实际运行时的截图。
1. 本地服务发现 —— 不用先记住端口号
点工具栏的 发现 按钮,插件扫一遍本机实际在监听的端口,把正在跑的页面列出来,
连标题和技术栈一起。3000 上的 Vite、8000 上的 Django、5173 上的 Vue,直接点开,
不用回忆也不用去翻终端。
每个技术栈带自己的 logo —— 扫一眼就知道哪个是哪个,不用读文字。

2. 标注配色 —— 让标记在多套主题下都看得见
工具栏的 颜色 里可以换标注色。这不只是好看:标记要同时满足"在任意页面内容上 是 2px 描边"、"是 6px 的圆点"、"是背后有白字的实心针"三种形态,还要在浅色和深色 主题下都够清楚。预设的 6 种全部通过 WCAG AA,选完立刻生效,不需要刷新。
左边是蓝色,右边换成橙色 —— 换的是标记的颜色,页面本身没有动。

3. 发出去的是附件,不是你的话
标注不会混进你打的字里。它作为一条独立的上下文条目附在消息上,对话里显示成 可折叠的一行「N element attached」,点开才是内容。你自己打的字和它完全分开, 想删掉标注不影响消息正文。

两种标注方式
这是它和"点一下写句评论"的工具最大的区别 —— 不写也是有效的意见:
| 怎么做 | 结果 | |
|---|---|---|
| 标注 | 点元素,输入框留空,保存 | 记录"就是这个元素",不带意见 |
| 批注 | 点元素,写一句说明,保存 | 记录元素 + 你的修改要求 |
留空不是取消。 点一下某个元素本身就是一条完整的意见,空评论会被原样保留为一条「标注」。 输入框上方的徽章随打字实时切换「标注 / 批注」,保存前就能看到它会变成哪一种。
安装
先说清楚:不需要装浏览器扩展
这是一个 DSH 插件,不是一个浏览器扩展。
| dsh-annotate | 浏览器扩展类工具 | |
|---|---|---|
| 装在哪 | DSH 的 Web profile | Chrome / Edge 扩展商店 |
| 要装扩展吗 | 不用 | 要 |
| 标注送到哪 | 直接进 DSH 对话,AI 立刻能看到 | 复制粘贴,或另配一个服务 |
| 标注是什么格式 | 结构化数据(选择器 + 文本 + 批注) | 通常是截图 + 文字 |
| 页面在哪打开 | DSH 侧边栏的预览里 | 你浏览器当前的标签页 |
区别不在"能不能标注在线网站",而在标注之后怎么走。 这个插件把选区变成结构化字段直接送进对话,而不是给你一张截图让你自己解释。
当前版本的预览目标:本地服务(自动探测端口)和工作区里的 HTML 文件。
访问在线网站由配置项 allowRemote 控制,计划在后续版本开放
(见 已知限制)。
一键安装(推荐)
把下面这段整段复制发给你的 DSH:
帮我安装 dsh-annotate 插件:
1. 克隆 https://github.com/TheYoungChen/dsh-annotate 到我的插件目录
2. 把 "dsh-annotate" 加进 Web profile 的 dsh.profile.bundles
3. 告诉我怎么重启 Web 端
DSH 会读这个 README 自己完成剩下的步骤 —— 不需要你手动改配置文件。
手动安装
第一步 —— 把包加进 Web profile 的 dsh.profile.bundles:
{
"dsh": {
"profile": {
"bundles": ["...", "dsh-annotate"]
}
}
}
第二步 —— 重启 Web 端。
必须在一个独立的终端里做。 agent 就跑在 DSH 里面,在会话里杀进程等于自杀:
Get-NetTCPConnection -LocalPort 3000 -State Listen | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
cd <你的 deepseek-harness 目录>
pnpm dsh web --port 3000
重启完打开右侧边栏,就能看到「标注」页签。
方式三:手动改 cordis.patch.yml
一般用不上。插件自带 cordis.patch.yml,通过 dsh.bundle.patch 自注册。
- insert:
- id: dsh-annotate
name: 'dsh-annotate'
config:
enabled: true
allowRemote: false
allowExternalFiles: true
只写
cordis.patch.yml不生效。 插件的客户端半边必须经 profile 的dsh.profile.bundles激活。只写在 patch 里,宿主半边会加载、客户端半边被静默跳过, 表现为侧边栏根本没有这个页签 —— 这是最常见的装不上的原因。
使用
- 侧边栏开「标注」页签,或按
⌘⇧B - 上方选一个本地服务(自动探测端口与技术栈),或直接填地址;工作区里的
.html也能直接开 - 点「标记」→ 在预览里悬停,高亮框显示选择器
- 点一下元素 → 卡片弹在元素下方
- 想写就写,不想写直接保存
- 列表按页面从上到下排序,编号与页面里的标记针一一对应
- 标完直接去输入框写你的要求,正常发送即可 —— 标注已经跟着走了
标注不会出现在你打的字里。发送后它作为一条可折叠的「上下文注入」行出现在对话里, 默认收起,点开才看得到具体内容。
设计取舍
选择器优先语义化 class。 按优先级取第一个能唯一命中的:#id → [data-testid] →
唯一的 class → 位置链(最多 6 层,兜底)。位置链对人没有信息量,页面一改就失效;
.lb-hero 一眼就知道改哪儿。
标注不写进输入框。 早先的做法是把标注拼成一段文本追加到草稿后面 —— 用户看到的自己 的消息里混着一大坨 DOM 结构,既难看又容易被误删。现在标注走宿主的运行时上下文通道, 以自己的身份投递:你的消息就是你的消息,标注是旁边一条独立记录。
载荷刻意保持精简。 选择器只出现一次;坐标只在选择器可能歧义时才带上;没有任何装饰性 图标。在同一组真实标注上实测:602 字符 → 192 字符,减少 68%。
没有需求就不发。 标注是上下文而非消息,草稿为空时没有可附着的对象,此时会提示你先写 要求,而不是替你发一条空消息。
"附上了什么"和"屏幕上有什么"是两个问题,答案不再共用一个。 标注按会话存一份,
每条再记下自己是在哪个路由上标的。于是换路由时列表、计数、"已附加"全都原样保留,而标记针
只画在它当初所在的那个页面上 —— 按路由存整份列表会让切页面等于丢标注,按会话存而不管路由
又会让针飘到别的页面上。列表里有多个页面的标注时,每条会多显示一行它来自哪个路由;
交给模型的那段文本同理,只有当标注横跨多个路由时才会带上 page: 一行。
换了页面要记得回来。 侧边栏切到别的标签页时,标注面板整个被卸载,回来就是全新的空状态 —— 连"刚才看的是哪个页面"都不记得了。所以打开的地址按会话记在小容量存储里,面板重建时自动重开。 重开不能清空列表:面板的镜像逻辑会把"从 N 条变成 0 条"读成用户主动清空并上报, 宿主随即丢弃这批标注并推进 epoch,dock 又会去清空页面 —— 一次切标签页就能毁掉它本该保住的东西。 所以只有真正换到另一个页面才清空。
宿主是那个"还欠着"的一侧,所以面板重建时要问它。 面板被卸载后没有任何状态可比对,
光"沉默不上报"只能避免清空宿主,自己仍然是空的,而输入框上的胶囊还在计数 —— 这就是
"侧边栏里所有标注都消失了"的来源。因此 /pending 除了数量还回传列表本身,面板挂载时
认领它。跨页面的 restore 消息让页面把宿主手里的那份接回去,这样预览被回收重建
(新的 origin,页内存储是空的)之后,标注仍然回得来。
工作原理
DSH 宿主
└─ lib/index.js 起本地代理(同 site、不同 origin)+ HTTP API + 注册运行时上下文
↓ 代理 ↑ 标注按会话存在这里
目标应用 │
lib/shim.js 改写 fetch/XHR/WebSocket,让子请求也走代理
lib/overlay.js 帧内:拾取、标记针、评论卡片、页内存储
↕ postMessage │
client.js 侧边栏:编号列表、排序、把标注报给宿主(只报原始字段,不拼文本)
标注从侧边栏到模型的完整路径:
侧边栏标注变化
└─ POST /__dsh-annotate/context { session, annotations[] } ← 只传原始字段
↓
宿主按 sessionId 存一份待发列表
↓ 下一轮对话组装 prompt 时
systemPrompt.context() 的 provider 求值
↓
以 plugin 身份产出快照消息(不是 user 消息)
↓ ↓
模型收到结构化上下文 界面显示一条可折叠的「上下文注入」行
↓
该轮 turn/start 后列表清空,不会重复出现在后续轮次
三个关键点:
- 本地代理 —— 不直接嵌目标页。插件在 loopback 上起一个小代理转发过去,侧边栏嵌的是代理。
代理与宿主同 hostname、端口不同,所以它是同 site 的另一个 origin:注入的拾取器能读 DOM,
目标应用的
localStorage/sessionStorage和 harness 隔开,而目标应用自己的登录 cookie 能正常存取(同 site 才不会被浏览器的跨站限制拒收)。 - 剥掉六个阻止嵌入的响应头 ——
x-frame-options、content-security-policy(含 report-only)、cross-origin-opener-policy、cross-origin-embedder-policy、cross-origin-resource-policy。 任何一个在,页面就是白屏。 - 标记针跟着元素走 —— 每次滚动/缩放都用实时的
getBoundingClientRect()重新定位,不存坐标。 元素被滚出容器或裁掉时,针直接隐藏,不会飘到别的组件上。
安全边界
- 只允许 loopback 目标。
allowRemote关着时,任何非localhost/127.0.0.1/::1的地址一律 403 —— 这个代理不会变成通往内网或云元数据地址的跳板。 - 静态文件默认读不出工作区。路径先
realpath再和根目录比对,符号链接指向外面也会被拒;.开头的路径段直接 403。需要预览工作区外的文件时,显式打开allowExternalFiles。 - 预览不能被别的站点读。带
Sec-Fetch-Site: cross-site的请求会被拒。 - cookie 按目标分区。回程
set-cookie会加目标前缀并改写路径,两个预览之间不会串味。 注意 cookie 的作用域是 site(hostname)而不是 origin(含端口):所以预览之间、 以及预览与宿主之间共用同一份 cookie 存储。前缀保证了不同目标不会互相覆盖。
已知限制
这些是这类方案共同的硬限制,不是 bug:
- 账号密码登录本身没有限制,能正常用。列在这里的是登录的几种特例:
- 走 OAuth 跳转、且跳到外部站点的流程(会离开预览)
- 依赖
SameSite=None; Secure的 cookie:预览是http://,这类 cookie 在非 HTTPS 下会被浏览器丢弃,所以要求它的站点登录不上
- 严格 CSP 的站点(
frame-ancestors被剥了,但页面内联脚本仍可能被 CSP 挡) - Service Worker 驱动的离线应用
- Shadow DOM 内部、Canvas 绘制的内容
- 跨域子 iframe 里的元素
- 在线网站:当前默认只接受 localhost /
127.0.0.1/::1与工作区页面。 把配置里的allowRemote打开即可预览任意 http(s) 站点 —— 这个开关已经可用, 但默认关着,因为要安全地开放还需要 SSRF 加固(拦截云元数据地址169.254.169.254和内网私有网段),而那部分计划在后续版本补上。 - 标注只跟着"下一条消息"走。它在那一轮开始后即被清空,不会一直挂着; 想改主意就在发送前直接在侧边栏删掉那一条。
- 草稿为空时不发送。标注是上下文不是消息,没有你写的字就没有可附着的对象 —— 这时会提示你先写要求,而不是替你发一条空消息。
- 一个会话最多同时开 4 个页面。侧边栏的标注面板里有标签页条和地址栏,像浏览器一样:
点标签切换、点 × 关闭,多个页面同时活着(切回去不是重新加载,滚动位置和页面里
没保存的输入都还在)。上限是 4 个 —— 每个标签页是一整个文档、有自己的一套定时器,
真正的开销是 iframe 数量而不是心跳。开到第 5 个时最早的那个自动退场,并会提示你。
关掉的页面不会丢标记:那些标记仍在列表和批次里,只是标注为"来自已关闭的页面",
并保留关闭前最后观测到的匹配数 —— 选择器只有原来那个文档才解析得了,
所以这里不假装它还定位得到。
- 怎么开第二个页面:标签条最右边的 +,点它清空并展开地址栏,输入地址回车即新增 一个标签。标签条从第一个页面起就在(否则"开第二个"这个动作本身没有入口)。
- 标签页是一组,地址栏是另外一条。 地址栏住在工具栏那一行("选取"和刷新/菜单之间), 标签页条独占下面一行 —— 两条互不从属。之前地址栏是标签条正下方的独立一行,两者叠在 一起像同一个东西,而工具栏中间空着一大块没用上的宽度。
- "选取"是整个面板共用的,不是每个页面一套。 打开选取之后切标签,它还是开着的, 新页面也照样能点着标注 —— 标签页之间只是内容不同,对标注来说它们都是页面。 区分"选取中"和"未开启"的只有这一个状态,所以它属于面板。
- 同一个应用的多个路由算多个页面。
localhost:5173/a和localhost:5173/b是两个 标签,各自有各自的标记 —— 预览身份用的是完整地址而不是origin。这一点曾经是错的: 按 origin 复用会让第二个地址拿回第一个的预览,表现就是永远只有一个标签页, 而且根本开不出第二个。 - 同一个文件夹里的多个本地页面也算多个页面。工作区页面按文件复用,
不是按它所在的目录 —— 否则放在一起的两个页面共用一个身份,第二个同样只能拿回第一个的
预览。这是上面那个 origin 缺陷的同类,发生在
file://那一支上。
localStorage活不过预览本身,登录态可以。 这两件事的存储作用域不同, 之前把它们并成一句是错的:localStorage/sessionStorage按 origin 划分,预览每次新建都监听随机端口, 就是新的 origin —— 所以页内存储是空的,被回收(默认 5 分钟不动)后重开也一样。 这是隔离的代价,也是刻意的:被标注的页面读不到这个工作区的任何东西。- cookie 按 site 划分,不含端口 —— 预览与宿主同 hostname,所以登录 cookie 在
预览重建后依然有效,长时间不动被回收、再打开通常仍是登录状态,不用重新登。
回程
set-cookie仍会加目标前缀转发,不会串味。 - 换句话说:登录能用,页内存储不保留。要重置登录态,清掉该 hostname 下的 cookie。
开发
# 回归测试(52 项)
node scripts/preflight-activation.mjs # 激活链(真实 composeEntries)
node scripts/preflight-shapes.mjs # 注册形状对真实 slot key 校验
node scripts/preflight-profile.mjs # profile 里恰好一行 annotate
node scripts/check-boot.mjs # overlay 挂载(head/body 两种注入位置)
node scripts/check-overlay.mjs # 标记 / 批注 / 空标注保留 / 卡片停靠 / 编号连续 / 空白处不算选中
node scripts/check-client.mjs # 客户端接线
node scripts/check-client-dom.mjs # 真实 DOM 渲染
node scripts/check-layout.mjs # 空间分配、地址栏占工具栏那一行、detect 唯一入口、浮层不被对话盖住、批量清除不会被滚出视野
node scripts/check-filepreview.mjs # 文件预览入口 URL
node scripts/check-stacks.mjs # 技术栈指纹(9 种,含"认不出就不猜")
node scripts/check-selectors.mjs # 选择器在真实页面上唯一
node scripts/check-block.mjs # 模型实际收到的文本(跑真实渲染函数)
node scripts/check-context.mjs # 标注以 plugin 身份投递,不进用户消息
node scripts/check-host-apply.mjs # 宿主 apply() 不会把 DSH 带崩
node scripts/check-host-http.mjs # HTTP 边界:畸形输入不落库、不抛错
node scripts/check-preview-reuse.mjs # 预览复用不因 null target 抛错
node scripts/check-report-map.mjs # 标注上报与列表渲染的健壮性
node scripts/check-attach-flow.mjs # 附上流程:跨源消息不被误收
node scripts/check-handover.mjs # 附上后宿主仍持有全部标注(含反向验证)
node scripts/check-capsule.mjs # 胶囊在输入框上方、计数跟随宿主
node scripts/check-capsule-wiring.mjs # 三种 sessionId 传参都能渲染
node scripts/check-repeat-guard.mjs # 每 turn 只写一行;交付即释放;epoch 只推进一次
node scripts/check-delivery-order.mjs # 交付时刻释放(不是 turn/end)
node scripts/check-accent.mjs # 配色:变量作用域、对比度、胶囊不透明且有颜色
node scripts/check-interaction.mjs # Esc 退出标记;只有一个交接动作
node scripts/check-payload.mjs # 客户端不再拼文本(交接边界守卫)
node scripts/check-selector-target.mjs # 选择器以目标元素结尾,不是它的祖先
node scripts/check-duplicate-nodes.mjs # 同结构节点上的两个标注不会解析到同一个元素
node scripts/check-anchor-shape.mjs # 复现读者页面的形状:两条路径各自锚定自己的元素
node scripts/check-reported-flow.mjs # 端到端:开文件 → 标注 → 保存 → 再开同一文件
node scripts/check-readme.mjs # README 里的图片、链接与两个数字都不是手写的
node scripts/check-sidebar-crash.mjs # 侧边栏在异常输入下不崩
node scripts/check-structure.mjs # 三个源文件的结构完整(头部没被截掉、括号配平)
node scripts/check-detection.mjs # 端口清单含 Tauri;每个技术栈都有 logo
node scripts/check-target-gate.mjs # 预览目标白名单与 README 的承诺一致(含 SSRF 缺口)
node scripts/check-route-key.mjs # 换页不丢标注:列表按会话存、pin 按路由画,编号跨页不变
node scripts/check-address-chip.mjs # 地址栏显示完整真实地址(含 #/hash),过长从左侧截断
node scripts/check-chip-dom.mjs # 批注胶囊真的挂进 DOM:计数、展开、异常 props 不崩
node scripts/check-chip.mjs # 已发送气泡下方的批注胶囊:读回宿主写的块、只认自己那条消息、落在折叠窗口之外
node scripts/check-count-popup.mjs # 计数弹窗:批量清除可及、编辑后弹窗自己关掉、清空真的上报空批次
node scripts/check-delete-stickiness.mjs # 在页面上删掉最后一条标注后,它不会又被宿主塞回来
node scripts/check-host-asset-freshness.mjs # overlay 改动必须进到"当前这一次"响应里,不能等到重启宿主
node scripts/check-preview-login.mjs # 预览 iframe 与宿主同 site,登录会话 cookie 才留得住
node scripts/check-tabs.mjs # 多标签页:加页面而不是换页面、标记按页面分、关页面留标记、地址栏入住工具栏那一行
node scripts/check-preview-reuse.mjs # 预览复用身份:同应用不同路由、同目录不同文件都是两个预览
node scripts/check-tabs-dom.mjs # 真的渲染一遍面板数标签:点 + 能开出同应用的第二个页面;关一个剩一个
node scripts/check-release.mjs # 更新日志直接当发布说明:没有内部脚本名、没有"读者报告"式转述、不是空壳
# 变异检测前先自检:每个变异都带一个字面锚点,源码一改就可能失配;
# 失配时变异根本没注入,受检代码等于好代码,"通过"是假的
node scripts/check-mutation-anchors.mjs
# 真 cordis、真 bundle:会话服务晚到时胶囊仍要注册(这正是它曾经整个不显示的成因),
# 服务永不到时插件仍要 ACTIVE —— 否则 dsh web 的启动自检会判定该条未激活
node scripts/check-real-registration.mjs
# 变异检测:注入 92 个故障,每一个都必须被抓到
node scripts/check-preflight-power.mjs
# 对一个真实运行中的实例复查(默认 3099,先起好)
node scripts/check-preview-inline.mjs http://127.0.0.1:3099
# 直接问正在跑的那个进程(默认 3080):它实际送出的是哪一版 overlay
node scripts/check-live-host.mjs http://127.0.0.1:3080
# 正在跑的进程送出的 overlay 里,这一轮的改动到底在不在(换个版本号骗不过它)
node scripts/check-live-overlay.mjs http://127.0.0.1:3080
# 刷新在代理已被回收之后仍然可用(这正是 "localhost 拒绝连接" 那条)
node scripts/check-reload.mjs http://127.0.0.1:3080
# 冒烟 / 诊断
node scripts/smoke-host.mjs
node scripts/smoke-proxy.mjs
手工注入过变异之后(node scripts/mutate.mjs . <name>),用这个还原:
node scripts/mutate.mjs . restore
check-preflight-power.mjs 会故意注入故障来验证测试本身有效 ——
因为一套永远绿的测试等于没有测试。
每个新断言都必须在注入对应故障后确实失败,才算数。这条规则不是形式主义: 本仓库出现过两次「断言字符串存在」而不是「断言值正确」的假测试,以及一次竞态导致 同一个测试三次里失败一次 —— 那一次最终被改成确定性实现,而不是放宽断言。
文档
CHANGELOG.md—— 每个版本用户可见的变化assets/README.md—— 截图与演示视频的来源,以及重新生成的步骤docs/compatibility.md—— DSH 版本兼容声明、依据,以及尚未完成的运行验收步骤docs/design-principles.md—— 设计原则docs/reference-element-facts.ts—— 元素信息采集参考实现(ARIA role 映射等), 当前版本未启用,保留供将来扩展
兼容性
| DSH 版本 | 状态 |
|---|---|
0.1.7-alpha.1 |
兼容 |
0.1.7-alpha.2 |
兼容 |
0.1.7-rc.1 |
兼容 |
Node.js >=20;平台 win32 / darwin / linux;Profile web。
兼容性声明的依据是 API 表面检查,可复现:
node scripts/check-compat.mjs 0.1.7-rc.1
它验证插件用到的服务与槽位在该版本中确实存在。这不等同于运行验收 ——
一次性 Profile 的安装/启动/卸载证据尚未采集,docs/compatibility.md 记录了具体步骤。
参与
- CONTRIBUTING.md —— 怎么在本机跑起来、改完怎么验证
- SECURITY.md —— 这个插件会碰哪些东西、不会碰哪些
- CHANGELOG.md —— 每个版本改了什么
- 有问题直接 开 issue, 有模板兜底,不确定选哪个就选 Bug 报告
No comments yet. Be the first to write one.