dsh-sidecard-ask 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,151 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ **主题:浮层不再压住主对话——「A 胶囊 + C 右栏」定案,一个算宽度的纯函数 + 一个默认收起的形态。**
6
+
7
+ 用户实测反馈(截图:原文被卡片尾部盖住):右下角卡片从 `right:16px` 往左固定铺 400px,
8
+ 截图上(1400px 宽的窗口)对话区右边界 x≈971,而**卡片左边界 x≈858,压进对话区约 113px**。
9
+ 如实记录一点:这个位置关系随帧宽变化——1400px 下理论值是 `1400−16−400=984`,本身并不越界;
10
+ 问题是宽度写死 400px,窗口一窄就必然压住正文(这正是截图里的情形)。
11
+ 同时定案 A(悬浮胶囊,可随时收起/隐藏)+ C(停靠原生右侧栏)。
12
+
13
+ ### 位置:宽度由预算决定,不再固定 400px
14
+
15
+ - A1 新增纯函数 `floatPlacement({frameWidth, laneRight, railLeft, maxWidth})` →
16
+ `{width, avail, capped, bleeds}`:`avail = 帧宽 − 8 − 对话区右边界`,有右栏时再取
17
+ `min(avail, 帧宽 − 右栏左边界 − 10)`;`width = min(上限, max(240, avail))`。
18
+ 语义是**浮层只使用对话区右侧那条预留带,不越过对话区右边界**;只有带宽跌破 240px(胶囊再窄就不可用)
19
+ 才允许压入,并把这种情况如实报成 `bleeds`。
20
+ - A2 浮层 `right` 锚定帧右边缘,宽度写进 CSS 变量 `--dsa-float-w`(不再有 `width:min(400px,92vw)`);
21
+ 挂载、窗口 resize、右栏开合(`ResizeObserver`)时重算,流式刷新只重渲染不清算几何。
22
+ - A3 新增配置 `floatMaxWidth`(240–560,默认 **340**):实际宽度是 `min(上限, 可用带宽)`。
23
+ 旧的 400px 在多数窗口下本就超预算,故默认下调到 340。
24
+ - A4 浮层节点带 `data-layer-width` 与 `data-dsa-float-mode`:出问题时截图即可定位几何与形态。
25
+
26
+ ### 形态:默认胶囊,随时收起或隐藏
27
+
28
+ - A5 新增配置 `floatMode`(`capsule` | `full` | `off`,默认 **`capsule`**):作答期只有一行,
29
+ 完成后显示问题首句;`full` 展开为完整卡片栈;`off` 只留一个带计数的圆点启动器。
30
+ - A6 运行时可即时切换且**不写回配置**:卡片标题栏右侧新增两个**图标按钮**「⇥ 收起为胶囊 / × 隐藏浮层」
31
+ (完整文案放在 `title` 与 `aria-label`——中文全称 12 个字以上,写成文字会把标题栏挤成两行),
32
+ 它们只改会话内状态(`state.floatMode`),刷新后回到设置页的默认形态——按一下隐藏不会变成永久设置。
33
+ 这两个按钮只有浮层里的卡片才有;被原生右侧栏 tab 承载的同一张卡不传该回调,因此不出现(宿主 tab 不会遮挡对话区)。
34
+ - A7 设置页新增「浮层默认形态」「浮层宽度上限」两项控件。
35
+
36
+ ### 视觉
37
+
38
+ - A8 卡片阴影 `0 10px 30px rgba(0,0,0,.22)` → `0 4px 16px rgba(0,0,0,.10)`
39
+ (DSH 主题**没有**阴影令牌,只能硬编码 rgba;数值参照其 14 个 `--dsw-alias-*` 令牌的对比度下调)。
40
+
41
+ ### 测试
42
+
43
+ - 新增 `test/float-placement.mjs`:把几何写成断言——旧实现固定 400px,在**帧宽收窄**时左边界必然越到
44
+ 对话区右边界左侧(用例 fixture:帧 1380 / 边界 980 / 右栏左界 1010 → 旧左边界 964 < 980,新实现同组不越界);
45
+ 并如实保留一条「上报帧宽 1400 时旧实现 984 其实并未越界」,防止把一个随窗口变化的位置关系说成永远成立。
46
+ 另含 8 种帧宽 × 4 种右栏宽的不变量(有空间就绝不越界);带宽条带与极端值(390px 带宽得 390 且 `capped`
47
+ 为 false、`avail` 取夹取前的原始预算、NaN 退化到 240 而非 NaN、上限 9999 夹到 560、上限 10 夹到 240);
48
+ 纯函数性(不改输入、同入同出);默认形态必须是 `capsule`;以及源码里旧固定 400px 规则确实已消失
49
+ (防止只改测试不改实现)。
50
+ - `test/contract-test.mjs` 的两面默认值等价断言(`pure.CLIENT_DEFAULTS` 对 `host.DEFAULT_CONFIG`)覆盖新键。
51
+ - **既有契约用例改为显式要求 `floatMode: 'full'`**:`test/contract-test.mjs` 里读卡片正文的三处
52
+ (思考过程、刷新后重放、渲染证明回退)本来依赖「浮层就是卡片栈」,而新的默认形态是胶囊——
53
+ 这些用例断言的是**卡片内容**,所以它们显式要 `full`;胶囊这个默认值本身由 `float-placement` 断言。
54
+ 不改它们的话,默认值一改就会三处变红,而那不是回归。
55
+ - 版本三处同步到 1.5.0:`package.json` 的 `version`、`index.js` 的 `PLUGIN_VERSION`、`client.js` 的
56
+ `CLIENT_VERSION`(`test/verify.mjs` 会同时断言这三处)。
57
+
58
+ ### 本机实测结果(权威)
59
+
60
+ - `node --check` 对 `index.js`、`client.js` 与全部 `test/*.mjs`、`tools/compat-probe.mjs` 通过(exit 0)。
61
+ - `node test/verify.mjs` → **53 passed, 0 failed**(含 zh/en 两本词典的 `t()` 键覆盖断言)。
62
+ - `node test/contract-test.mjs` → **296 passed, 0 failed**。
63
+ - `node test/float-placement.mjs` → **38 passed, 0 failed**。
64
+ - `node test/smoke-test.mjs` → **138 passed, 0 failed**。
65
+ - 诚实记录:`float-placement` 第一轮报 **7 条红**,逐条查完**全部是我把测试写错**,实现未因此改动:
66
+ ① 旧实现越界的 fixture 手算错(1420/991 那组其实不越界);② 一条断言把左右关系写成了负值恒假;
67
+ ③④ 退化右栏用例期望 340/false,实测 240/true;⑤ 硬上限用例的 `avail` 只有 400,压根碰不到 560;
68
+ ⑥ 新增的 `capped` 断言把语义写反(`capped` 指"被可用带宽限制",命中硬上限时为 false);
69
+ ⑦ `avail` 断言误用了带右栏的用例(390 而非 421);⑧ 源码钩子去找根本不存在的
70
+ `setFloatMode('capsule')` 调用——控制项实际走 `floatControl('capsule','floatCollapse','⇥')` 的
71
+ `onFloatMode` 回调,正则已改。
72
+ - 本版一度新增 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个「替代检查」,**现已删除**:
73
+ 它们本身是错的——前者用「正则字面量 vs 除号」的启发式,把 8 个源文件全部误报为残损;
74
+ 后者定位词典块的缩进假设写错,解析出 0 个键并误报上百条缺失。会撒谎的检查器比没有检查器更糟。
75
+
76
+ > 仍未验证:**浏览器内的视觉观感**(浮层实际宽度是否等于预算、是否真的不越过对话区右边界)。
77
+ > 请按 README 第九节 **A 路径第 8 条**在真实页面确认(读 `data-dsa-float-mode` 与 `data-layer-width`),
78
+ > 单测替代不了这一步。1.5.0 前半段本工作区 shell 工具整体不可用
79
+ > (`SetNamedSecurityInfoW failed (Win32 5)`),当时的改动确实没跑过任何命令;故障解除后才补跑上述四项。
80
+
81
+ ## 1.4.0
82
+
83
+ **主题:一次系统性的代码走查——19 项改进,功能缺口、正确性边界、i18n、代码卫生一次补齐。**
84
+
85
+ ### 功能缺口(A)
86
+
87
+ - **A1 设置页补齐控件**:`sideTimeoutMs`(5000–3600000,即宿主侧接受的范围)与 `sideProvider`
88
+ (下拉,选项来自 `/state` 实际报告的 provider 列表,默认 `auto`)此前宿主接受但设置页没有控件,
89
+ 只能手改 `config.json`——现在设置页直接可改。
90
+ - **A2 思考过程可见**:子代理的 `reasoning` 帧此前被丢弃;现在卡片上以**可折叠的「思考过程」区**展示,
91
+ 流式期间与答案同窗口合并提交,复制答案不含思考文本。
92
+ - **A3 原生右侧栏 tab 可关闭**:此前的「关闭」只撤掉渲染证明、tab 留成空壳;现在真正关闭 tab
93
+ 并携带空态文案,关闭失败的兜底路径(回退关 tab)一并处理。
94
+ - **A4 卡片持久化**:已完成(含已停止且非空)的侧边卡片镜像进 `localStorage`(schema `v1`,
95
+ 最多 10 张、单卡文本上限 64K),页面刷新后重放进浮层卡片栈(原生/better 的 tab 无法跨刷新存活);
96
+ 隐私模式、配额满、数据被篡改一律降级为「无历史」,不阻断启动。
97
+ - **A5 流式自动滚动**:侧边卡片答案流式增长时自动滚动到底部(仅当用户已在底部附近,不打断上翻)。
98
+ - **A6 markdown-lite 扩展**:行内新增斜体与链接(链接协议白名单 http/https、`noopener` 打开,
99
+ 模型输出不可能造出 `javascript:` URL);块级新增引用块与表格(此前行内只有代码/粗体,
100
+ 块级只有代码块/列表/标题)。
101
+
102
+ ### 正确性与边界(B)
103
+
104
+ - **B7 渲染证明竞态**:同一卡片在两个承载面(原生右侧栏与 better-sidebar)同时出现过的窗口期,
105
+ 第二面的渲染证明会顶掉第一面;现在挂载即撤销旧证明,回退路径改为关闭 tab 而非仅撤证明。
106
+ - **B8 码点计数**:截断、徽标、`minChars` 全部从 UTF-16 单元计数改为 **Unicode 码点**计数
107
+ (两端 `truncateSelection` 同步,契约测试断言等价):emoji 或扩展区汉字不再被数成两个字符、
108
+ 截断不再切半个代理对。
109
+ - **B9 流式增量节流合并**:每个流式 delta 曾触发一次全卡重渲染(整卡 re-render + 全文重解析,
110
+ 长答案下是二次方开销);现在 50ms 窗口内合并为一次提交,终止事件(done/error/cancel/close)
111
+ 先冲刷缓冲再落终态,**不丢任何流式文本**;关闭卡片会丢弃未冲刷的缓冲(冲刷 tick 不复活已关卡片)。
112
+
113
+ ### i18n 与一致性(C)
114
+
115
+ - **C10 宿主错误消息本地化**:含义完全已知的错误码(`too-large`/`busy`/`aborted` 等)在英文界面
116
+ 显示英文文案,不再显示宿主拼接的中文消息;携带实时诊断的错误码(`engine-error`/`blocked-step` 等)
117
+ **保留宿主原文**(那是原因唯一所在,替换即丢失)。
118
+ - **C11 子代理提示词统一英文**:`buildSidePrompt` 生成的指令段落统一英文,与 persona 语言一致。
119
+ - **C12 语言解析集中化**:`resolveLanguage()` 集中解析,优先 `navigator.languages` 逐项回退,
120
+ 不再散落多处手写判断。
121
+
122
+ ### 代码卫生(D)
123
+
124
+ - **D13 停止/关闭真正终止子代理**:此前只中断浏览器端流;现在同时 `POST /cancel`,宿主侧运行
125
+ 登记表真正清空(子代理不再跑到超时)。
126
+ - **D14 移除载荷死字段 `cross`**:ask 载荷里宿主从不读取的字段,连同客户端构造一并删除
127
+ (`zoneLabel` 的 `candidate.cross` 显示逻辑保留——那是客户端自用)。
128
+ - **D15/D17 dispose 对称清理**:所有 `ctx.inject` 的返回反注册都保存并在 dispose 时调用,
129
+ 不再只清理部分订阅;补齐一处缩进格式问题。
130
+ - **D16 历史轮数常量化**:卡片继续追问携带的历史轮数从两处裸 `6` 提为导出常量 `HISTORY_TURNS`
131
+ (客户端与宿主各自导出,契约测试断言两值相等,且宿主提示词只保留最近 N 轮)。
132
+ - **D18 触发按钮宽度自适应**:视口钳制宽度从按中文估的硬编码 132px 改为按**当前语言文案**估算
133
+ (CJK 全宽、其余 ~0.55em + 固定 chrome 余量):英文按钮("Ask about selection")比中文宽约 50px,
134
+ 此前选区靠右时会溢出视口右缘;样式加 `white-space:nowrap` 杜绝挤压折行。
135
+
136
+ ### 可选加固(E)
137
+
138
+ - **E19 信任围栏端口比较**:`isTrustedRequest` 的 Origin 与 Host 比较改为**完整 authority(host:port)**——
139
+ 同主机名不同端口的 Origin 是不同源,此前只比主机名会放行;`trustedHosts` 条目带端口时只信任该端口,
140
+ 不带端口则信任该主机全部端口(管理员语义)。
141
+
142
+ ### 其他
143
+
144
+ - 修复契约测试中 B9 用例的定时器竞态(Windows 定时器粒度 ~15.6ms 下,80ms 的 done 门与 65ms 的
145
+ 检查点余量不足;门移至 130ms 并放宽后续等待)。
146
+
147
+ 自测 336 → **487 项**(verify 53 / contract 296 / smoke 138),契约测试连跑 5 次全绿。
148
+
3
149
  ## 1.3.2
4
150
 
5
151
  **主题:核对并适配 DSH 桌面版(Electron,0.2.0-rc.2)。**
package/README.md CHANGED
@@ -8,6 +8,17 @@
8
8
 
9
9
  聊天记录、任务详情、右侧栏内容……只要是能选中的文本都能用;**没装侧边卡片插件也能跑**(自动回退到内置浮层卡片)。
10
10
 
11
+ > **1.5.0 增强一览**(详见 CHANGELOG):内置浮层不再压住主对话——宽度改由纯函数 `floatPlacement`
12
+ > 按「对话区右侧可用带宽」计算(旧实现是固定 400px,在用户截图上压入正文约 113px);浮层默认是**一行高的胶囊**,
13
+ > 可随时「收起 / 隐藏」,运行时的形态切换不写回配置;新增 `floatMode`(默认 `capsule`)与
14
+ > `floatMaxWidth`(默认 340)两项设置;卡片阴影减半。
15
+
16
+ > **1.4.0 增强一览**(详见 CHANGELOG):卡片**思考过程**(reasoning)可折叠展示并随答案流式渲染;
17
+ > **已完成卡片写入 localStorage,刷新页面不丢**;答案渲染扩展链接/斜体/引用/表格;流式输出自动滚动到底;
18
+ > 设置页补齐 `sideTimeoutMs` / `sideProvider` 控件;原生右侧栏 tab 可关闭(不再残留空壳);
19
+ > 界面语言解析支持 `navigator.languages` 优先级回退,英文界面显示英文错误文案;
20
+ > 长答案的流式增量按 50ms 窗口合并提交(避免整卡重渲染的二次方开销)。
21
+
11
22
  > ## 名字改过两次,原因都写在这里(避免再撞名)
12
23
  >
13
24
  > 1. **`dsh-selection-ask` → ✗**:npm 上已被 `chestnut23` 占用(仓库 `lzbaclz/dsh-selection-ask`)。
@@ -54,10 +65,15 @@ dsh-sidecard-ask/
54
65
  ├── README.md 本文件
55
66
  ├── CHANGELOG.md 版本变更记录
56
67
  ├── LICENSE MIT
68
+ ├── design-preview/ 1.5.0 的方案讨论资料,**不参与发布**:00–02 是手绘 SVG 示意图(非截图),
69
+ │ 03-prototype-ac.html 是可直接双击打开的可点按原型(自带近似色板,非真实观感)
70
+ ├── tools/
71
+ │ └── compat-probe.mjs DSH 版本能力探测(§7 矩阵的来源)
57
72
  └── test/
58
73
  ├── harness.mjs 测试替身:假宿主 ctx / 假 req·res / 假子代理 provider / 合成浏览器 + 迷你 React
59
74
  ├── verify.mjs 静态完整性:清单、补丁、两半导出面、i18n 键覆盖、零依赖承诺
60
75
  ├── contract-test.mjs 两端契约:常量一致性、响应信封形状、SSE 逐帧往返、纯函数等价、槽位注册与渲染
76
+ ├── float-placement.mjs 浮层几何:宽度预算不越过对话区右边界、胶囊默认形态、源码钩子(1.5.0 新增)
61
77
  └── smoke-test.mjs 宿主端到端:流式、截断、取消、持久化与重启、失败分支、并发上限、传输围栏
62
78
  ```
63
79
 
@@ -133,6 +149,8 @@ plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sideca
133
149
  | `defaultCarrier` | `main` \| `side` | `side` | **默认作答位置**:提问框里仍可临时切换 | 立即 |
134
150
  | `sideSurface` | `auto` \| `native-rightbar` \| `better-sidebar` \| `flow` | `auto` | **窗口模式(侧边承载面)**:自动挑选 / 强制 DSH 原生右侧栏 / 强制 dsh-better-sidebar / 强制内置浮层卡片 | 立即 |
135
151
  | `maxChars` | 200–60000 | `4000` | **最大字符数**:超出部分头尾保留、中间截断并标注 | 立即 |
152
+ | `floatMode` | `capsule` \| `full` \| `off` | `capsule` | **内置浮层的默认形态**:胶囊(一行,最小遮挡)/ 完整卡片栈 / 只留一个计数圆点。运行时可即时收起、隐藏,但不写回本项——刷新后仍回到这里设的默认 | 立即 |
153
+ | `floatMaxWidth` | 240–560 | `340` | **内置浮层宽度上限**(px):实际宽度 = `min(本项, 对话区右侧可用带宽)`,可用带宽不足 240px 时才会压入对话区 | 立即 |
136
154
  | `shortcut` | 形如 `Alt+Q`、`Ctrl+Shift+K` | `Alt+Q` | **快捷键**:唤起提问框(`trigger` 允许时) | 立即 |
137
155
  | `captureZones` | `auto` \| `chat` \| `task` \| `chat+task` | `auto` | 只在哪些区域触发 | 立即 |
138
156
  | `showInUnclassified` | 布尔 | `true` | 无法归类的区域是否也触发 | 立即 |
@@ -144,6 +162,30 @@ plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sideca
144
162
 
145
163
  非法值会被拒绝(设置页报错、接口返回 `invalid-config`),并把被忽略的项列在 `/state` 的 `problems` 里——不会静默吞掉。
146
164
 
165
+ ### 4.1 内置浮层的位置规则(`floatPlacement`)
166
+
167
+ 用户实测报告过一个问题:浮层是 `position:fixed; right:16px; width:min(400px,92vw)` 的实心卡片。
168
+ 实测那一次(1400px 宽的窗口)对话区右边界在 x≈971,而浮层左边界在 x≈858,**压住正文约 113px**,
169
+ 最后几行答案被盖住。两点如实说明:**旧实现并不是任何窗口宽度下都越界**——1400px 窗口理论上
170
+ `1400-16-400=984`,还在 971 右侧;越界与否取决于当时的帧宽与对话区带宽,而固定 400px 的选择
171
+ 让「窗口一窄就必然压住正文」。这条读数(971 / 858 / 113px)是截图上的像素测量,不是日志里的一手数据。
172
+ 现在的位置由 `client.js` 里的纯函数 `floatPlacement` 决定:
173
+
174
+ ```js
175
+ // floatPlacement({ frameWidth, laneRight, railLeft = null, maxWidth = floatMaxWidth })
176
+ avail = 帧宽 - 8 - 对话区右边界 // 对话区右侧那条「预留带」
177
+ if (右栏存在) avail = min(avail, 帧宽 - 右栏左边界 - 10)
178
+ avail = max(240, floor(avail)) // 240px 以下胶囊也不可用,故为硬下限
179
+ width = min(floatMaxWidth, avail)
180
+ left = 帧宽 - 8 - width // 右锚定
181
+ bleeds = 对话区右边界 - left > 1 // 只有带宽 < 240px 时才可能为 true
182
+ ```
183
+
184
+ - 浮层**只使用对话区右侧的预留带**(右栏也在这条带里),窗口越窄它越窄,而不是越靠左。
185
+ - `capped: true` 表示「上限没用满,是被可用带宽压住的」——这是正常的自适应,不是错误。
186
+ - 挂载时、窗口 resize 时、右栏开合时(`ResizeObserver`)都会重算;流式刷新只重渲染,不动几何。
187
+ - 浮层根节点带 `data-layer-width`(实际宽度)与 `data-dsa-float-mode`:报告问题时可直接读这两个值。
188
+
147
189
  ---
148
190
 
149
191
  ## 五、侧边承载面与"侧边卡片插件"适配
@@ -157,7 +199,10 @@ plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sideca
157
199
  | 3 | **内置浮层卡片**(默认兜底) | 只需要 `shell.overlay` 槽位 | ——(这是保底面,永远可用) |
158
200
 
159
201
  - 装了 `dsh-better-sidebar`:卡片以它的 tab 形式出现在它的面板里,关闭卡片会同时 `closeTab`,不残留空 tab。
160
- - 没装:自动使用内置浮层卡片(右下角卡片栈),功能完全一致——**这条路径是本插件的默认与保底路径**。
202
+ - 没装:自动使用内置浮层卡片,功能完全一致——**这条路径是本插件的默认与保底路径**。
203
+ 浮层默认是**贴帧右边缘的胶囊**(一行高,只有状态与问题首句),点击才展开成完整卡片,
204
+ 标题栏右侧的「收起 / 隐藏」可随时收回;它的宽度由 `floatPlacement` 计算,
205
+ 只会占用**对话区右侧那条预留带**,不会压住正文(细节见 §4.1)。
161
206
  - 强制指定了一个不可用的承载面:回退到内置浮层,并在卡片上标注「已回退」。
162
207
 
163
208
  ### 5.1 与 dsh-better-sidebar 0.22.1 的适配核对(2026-09-28)
@@ -401,6 +446,14 @@ node tools/compat-probe.mjs --json tools/.cache/matrix.json
401
446
  5. 结束后状态变「已完成」;底部出现「复制 / 继续追问 / 关闭」;
402
447
  6. 点「复制」→ 出现「已复制」提示;点「继续追问」→ 输入第二条问题,卡片内容**重置换行**后继续流式;
403
448
  7. 点「关闭」→ 卡片消失;若承载面是 better-sidebar/原生右侧栏,对应 tab 也应关闭。
449
+ 8. **浮层形态与位置(1.5.0)**:默认应是一个**一行高的胶囊**(状态 + 问题首句),点它才展开成完整卡片;
450
+ 展开后标题栏右侧有「收起为胶囊」(⇥)与「隐藏浮层」(×)——隐藏后只剩一个带计数的圆点,点它恢复。
451
+ 用 F12 选中浮层根节点(`[data-dsa-root]`)读两个值自检:
452
+ - `data-dsa-float-mode` 应为 `capsule` / `full` / `off`;
453
+ - `data-layer-width` 应等于 `min(floatMaxWidth, 对话区右边界到帧右边缘的可用带宽)`。
454
+ **关键判据:胶囊/卡片的左边缘不得越过对话区(`[data-slot^="conversation"]`)的右边界**。
455
+ 把窗口从 1920px 拖到 900px、再开关一次右侧栏,浮层应随之变窄而不是往左压住正文;
456
+ 只有窗口窄到可用带宽不足 240px 时才允许压入(此时是设计内的降级,不是 bug)。
404
457
 
405
458
  **失败信号**:卡片停在「作答中…」不动 = SSE 帧没到达(看 console 是否有 `/sidecard-ask/api/ask` 报错);`done.streaming=false` = 该版本没有流式帧(属预期降级,卡片会写明)。
406
459
 
@@ -434,13 +487,20 @@ node tools/compat-probe.mjs --json tools/.cache/matrix.json
434
487
 
435
488
  ```powershell
436
489
  cd D:\Users\34332\AI\dsh-sidecard-ask
437
- node test/verify.mjs # 静态:清单/补丁/导出面/i18n/零依赖
438
- node test/contract-test.mjs # 契约:常量、信封、SSE 逐帧、纯函数、槽位注册与渲染
439
- node test/smoke-test.mjs # 端到端:流式/截断/取消/持久化/失败分支/并发/围栏
490
+ node test/verify.mjs # 静态:清单/补丁/导出面/i18n/零依赖
491
+ node test/contract-test.mjs # 契约:常量、信封、SSE 逐帧、纯函数、槽位注册与渲染
492
+ node test/float-placement.mjs # 几何:浮层宽度不越过对话区右边界(1.5.0 新增)
493
+ node test/smoke-test.mjs # 端到端:流式/截断/取消/持久化/失败分支/并发/围栏
440
494
  ```
441
495
 
442
- 三个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
443
- 当前规模:verify 53 项 + contract 149 项 + smoke 134 项 = **336 项全部通过**。
496
+ 四个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
497
+ 当前规模(1.5.0 本机实测,四项均 exit 0):verify **53** 项 + contract **296** 项 + float-placement **38** 项 +
498
+ smoke **138** 项 = **525 项全部通过**。
499
+
500
+ > 1.5.0 之前这里还放过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个「替代检查」。
501
+ > 它们已被**删除**:前者用「正则字面量 vs 除号」的启发式判断,把 8 个源文件全部误报为残损;
502
+ > 后者定位词典块的缩进假设写错,解析出 0 个键并误报上百条缺失。**跑不了测试时宁可标注「未验证」,
503
+ > 也不要引入会撒谎的检查器**——真实把关始终只有上面那四个 `test/*.mjs`。
444
504
 
445
505
  ### 10.2 版本能力探测(§7 矩阵的来源)
446
506
 
@@ -547,9 +607,10 @@ Host: ctx.subagents.start('spawn')
547
607
 
548
608
  ```powershell
549
609
  npm whoami # 先确认登录态;本机用的是 granular Publish token
550
- node test/verify.mjs; node test/contract-test.mjs; node test/smoke-test.mjs
610
+ node test/verify.mjs; node test/contract-test.mjs; node test/float-placement.mjs; node test/smoke-test.mjs
551
611
  npm publish --access public # 1.0.1 那次被 npm 暂存(staged)后才转正;再遇到可用 --otp=<验证码>
552
612
  git push origin main # 仓库:https://github.com/HERO476/dsh-sidecard-ask
613
+ git tag v1.5.0; git push origin v1.5.0 # 版本号与 package.json 一致
553
614
  ```
554
615
 
555
616
  改名/迁移时的额外两步(1.1.0 实际做过):
@@ -576,6 +637,20 @@ npm deprecate "<旧包名>@*" "Renamed to <新包名> - <原因>" # 旧名
576
637
  本机实测能识别:`conversation.*`(聊天)、`rightbar` / `sidebar.right.*` / 名字含 task·todo·schedule·team·job·plan 的面板(任务)。
577
638
  2. **浏览器内的视觉与交互未在本机验证**:本工作区没有浏览器自动化工具,因此"按钮出现在选区旁""卡片逐字增长""浮层不挡操作"这些**只能由你按第九节点一遍**。
578
639
  已做的保障是:只使用 `--dsw-alias-*` 主题令牌、只在槽位内渲染(`shell.overlay` / `settings.section` / `conversation.input.right`)、不写 `document.body`、不 import harness 客户端包。
640
+ 2b. **1.5.0 的浮层改动:四个测试脚本已在本机实跑通过(各 exit 0),但页面里的观感仍未验证**:
641
+ 写这个版本的前半段,本工作区的 shell 工具整体不可用(任何命令都返回
642
+ `SetNamedSecurityInfoW failed (Win32 5): grantWrite(<工作区>)`),所以当时的改动**没有跑过任何命令**,
643
+ 我也没有资格说它"验证过"。后半段该故障解除后已补跑:`node --check` 对全部源文件通过,
644
+ `verify` 53 / `contract` 296 / `float-placement` 38 / `smoke` 138 **全部 0 failed**。
645
+ 其中 float-placement 第一轮曾报 7 条红,逐条查完**全部是我把测试写错**(旧实现的越界 fixture 算错、
646
+ `avail` 期望值用了带右栏的用例、`capped` 语义写反、以及去找一个源码里并不存在的
647
+ `setFloatMode('capsule')` 调用点——真实实现走 `floatControl('capsule','floatCollapse','⇥')` 的
648
+ `onFloatMode` 回调),实现本身没有因此改动。
649
+ 仍未验证的是**浏览器内的视觉观感**:浮层实际宽度是否等于预算、是否真的不越过对话区右边界,
650
+ 必须按第九节 **A 路径第 8 条**在真实页面上确认(读 `data-dsa-float-mode` 与 `data-layer-width`),测试替代不了。
651
+ 此外,本版一度新增过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个"替代检查",
652
+ **现已删除**:它们本身有错(前者把 8 个源文件全误报为残损,后者解析出 0 个词典键并误报上百条缺失),
653
+ 属于会撒谎的检查器——跑不了测试时应当标注"未验证",而不是用一个错的检查器去证明没问题。
579
654
  3. **原生右侧栏承载面未在真实宿主上验证**:本机 profile 虽已启用 `@deepseek-ai/dsh-client-ui-sidebar-right`,但没有浏览器控件去确认 tab 是否真的渲染。
580
655
  为此加了一道**渲染证明**:适配器接受了打开请求后 600ms 内若没有观察到我们的卡片组件挂载,就自动把卡片移到内置浮层(流式内容不丢,因为卡片数据在 store 里)。
581
656
  4. **主对话承载的"发送"依赖客户端会话服务**:`ctx.sessions.using(...).prompt(...)` 在会话未被保留/不可用时降级为草稿写入,