dsh-sidecard-ask 1.8.0 → 1.8.1

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,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.1
4
+
5
+ **主题:对 1.8.0 交付做实证复核后的三项修正——1 个已复现的真实缺陷、1 处开关失效、1 条缺证据的声明。**
6
+
7
+ 复核方式:不采信文档,独立跑四套测试、读全部新增代码、对可疑点写最小复现脚本并落盘证据。
8
+
9
+ - **P0(真实缺陷,已复现)「清空全部历史」刷新后复活**:`clearHistory()` 只删了当前的
10
+ `:cards:v3` 键,而 `readPersisted()` 在 v3 缺席时会回落到 `:cards:v2` → `:cards:v1`
11
+ (这条回落正是"非破坏式迁移"的实现方式)。于是**从 1.7.x 升级上来的用户**点「清空全部历史」
12
+ 后刷新页面,旧卡片会从遗留的 v2 键整份读回来——2026-10-17 用独立脚本复现
13
+ (清空前 history=1 → 清空后=0、v3 键为 null、v2 键仍在 → 重载后 history=1,卡片 id 为旧数据)。
14
+ 修法:`clearHistory` 同时删除 v3/v2/v1 三代键(显式清空本就是破坏性操作,它是唯一允许丢弃旧键的动作)。
15
+ 测试为什么原先没拦住:旧用例只断言"v3 键被删掉",且只播种 v3 键、清空后**不重载客户端**——
16
+ 钉的是实现细节而非用户可见结果。新增用例覆盖"遗留 v2 + 清空 + 重载仍为空"。
17
+ - **P2(开关失效)建议追问芯片未跟随 `followUpSuggestions`**:只要答案里存在 `<followups>` 块,
18
+ 芯片就渲染,与默认关闭的 opt-in 语义不符。现在渲染跟随开关;**块剥离保持无条件**
19
+ (模型乱写标记时,正文仍不会被污染成散文)。旧用例正是"开关关闭却断言芯片出现",已改为开启态,
20
+ 并补关闭态回归守卫。
21
+ - **P1(证据链 + 勘误)`inputActions` 写入面**:1.8.0 把"官方写入面存在
22
+ `captureInsertion()` / `insertText()` / `setDraft()`"写成探针的第④条结论,但
23
+ `.compat-report/scripts/composer-probe.mjs` 的正文**从未探测 `inputActions`**(只有脚本头注释
24
+ 承诺过),仓库内也没有任何证据文件出现过这两个标识符。经复核,**声明本身成立**,现补产物层证据:
25
+ `npm pack @deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2` 与 `@0.2.1-rc.2` 解包后,
26
+ `lib/types/client/contract/input.d.ts` 含 `captureInsertion(): TokenSpan` 与
27
+ `insertText(text: string, span: TokenSpan): boolean`,`lib/client.js` 含 `var SessionInputShell = class`。
28
+ 逐条命中、行号与官方契约原文见 `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`;
29
+ README §6.3 与 1.8.0 条目已就地勘误,探针脚本头也改成了它真正做的事。
30
+ - 版本号三处同步到 1.8.1。
31
+
32
+ 本机实测(四项均 exit 0):verify **57** / contract **956** / float-placement **38** / smoke **226**
33
+ = **1277 项全部通过**(较 1.8.0 的 1267 新增 10 项)。
34
+
35
+ > 未验证(如实记录):本次修的是逻辑与测试,**没有在真实页面上点过任何按钮**。
36
+ > 1.8.0 未验证栏的 ①–⑤ 全部继续有效(写回按钮的真实点击与手感、原文高亮的观感与虚拟列表命中率、
37
+ > 导出的真实落盘、真实模型对风格与 `<followups>` 的遵循度、Electron 外壳表现)。
38
+ > 另外:`inputActions` 的证据核到 0.2.0-rc.2 与 0.2.1-rc.2 两个版本,未逐版遍历。
39
+
3
40
  ## 1.8.0
4
41
 
5
42
  **主题:P2 能力补齐——回答风格档位、答案写回输入框、原文回看定位、模型生成的追问建议、导出与多版本答案。**
@@ -21,9 +58,13 @@
21
58
  contenteditable**(`[data-composer-input][data-lexical-editor="true"]`),不是
22
59
  textarea;② `document.execCommand('insertText')` 返回 `false` 且**完全不生效**;
23
60
  ③ 合成 `beforeinput`(`inputType:'insertText'`)会被 Lexical 接受——文本落地、
24
- 发送按钮由禁用转可用;④ DSH 的槽位标准 prop `inputActions` 暴露官方写入面
25
- `captureInsertion()` + `insertText(text, span)` + `setDraft(text)`
26
- (源码:`dsh-client-ui-conversation` 的 `SessionInputShell.actions`)。
61
+ 发送按钮由禁用转可用。另有一条**不在探针内**的结论:DSH 的槽位标准 prop `inputActions`
62
+ 暴露官方写入面 `captureInsertion()` + `insertText(text, span)` + `setDraft(text)`。
63
+ > **1.8.1 勘误**:本条原先被写成"探针的第④条结论",但探针脚本的正文从未实现该探测
64
+ > (只有脚本头注释承诺过),当时没有可核验的证据。经产物层复核,声明本身**成立**:
65
+ > `@deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2 / 0.2.1-rc.2` 的
66
+ > `lib/types/client/contract/input.d.ts` 含该契约,逐条命中见
67
+ > `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`。
27
68
  据此实现三级写入阶梯:**官方 `insertText`**(重新 `captureInsertion` 后可重试一次)
28
69
  → **DOM `beforeinput`**(contenteditable 追加 / textarea 用原生 value setter + input
29
70
  事件)→ 明确失败。改写类动作(`builtin:polish` / `builtin:fix`)的已完成答案上出现
package/README.md CHANGED
@@ -362,13 +362,24 @@ ctx.inject(['sidebarRightTabs', 'sidebarRight'], (injected) => { ... })
362
362
  ### 3. `client.js` §8c —— 答案写回输入框(1.8.0,已做真机探针)
363
363
 
364
364
  2026-10-17 用**隔离 `DSH_HOME` + 无头 Edge**(插件不参与)对真实 DSH 页面做了探针,
365
- 脚本见 `.compat-report/scripts/composer-probe.mjs`,三条硬结论:
365
+ 脚本见 `.compat-report/scripts/composer-probe.mjs`,证据见
366
+ `.compat-report/evidence/p2-probe-2026-10-17.json`,三条硬结论:
366
367
 
367
- | 问题 | 结论 |
368
- |---|---|
369
- | composer 的真实 DOM | **Lexical contenteditable**:`<div contenteditable="true" role="textbox" data-composer-input="true" data-lexical-editor="true">`,槽位 `conversation.composer` |
370
- | 有没有官方写入面 | **有**。槽位标准 prop `inputActions` 暴露 `captureInsertion()` / `insertText(text, span)` / `setDraft(text)` / `submit()`(源码 `dsh-client-ui-conversation` 的 `SessionInputShell.actions`)。`sessions.using(...).binding.session.prompt` 只负责**提交**,不能写草稿 |
371
- | native setter + input 事件能否同步 | **N/A 且无效**:不是 textarea,没有 value setter;`document.execCommand('insertText')` 返回 `false`、文本不变。**有效的是**合成 `beforeinput`(`inputType:'insertText'`):文本落地、发送按钮由禁用转可用 |
368
+ | 问题 | 结论 | 证据来源 |
369
+ |---|---|---|
370
+ | composer 的真实 DOM | **Lexical contenteditable**:`<div contenteditable="true" role="textbox" data-composer-input="true" data-lexical-editor="true">`,槽位 `conversation.composer` | 页面探针 |
371
+ | native setter + input 事件能否同步 | **N/A 且无效**:不是 textarea,没有 value setter;`document.execCommand('insertText')` 返回 `false`、文本不变。**有效的是**合成 `beforeinput`(`inputType:'insertText'`):文本落地、发送按钮由禁用转可用 | 页面探针 |
372
+ | 有没有官方写入面 | **有**。槽位标准 prop `inputActions` 暴露 `captureInsertion()` / `insertText(text, span)` / `setDraft(text)` / `submit()`。`sessions.using(...).binding.session.prompt` 只负责**提交**,不能写草稿 | **产物层核验**(见下),**不是**页面探针 |
373
+
374
+ > **1.8.1 勘误**:上面第三行原先被写成"探针的第④条结论",但 `composer-probe.mjs` 的正文
375
+ > 从未探测 `inputActions`(只有脚本头的注释承诺过)——这条声明当时**没有可核验的证据**。
376
+ > 经复核,声明本身**成立**,现改由产物层证据背书:`npm pack
377
+ > @deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2` 与 `@0.2.1-rc.2` 解包后,
378
+ > `lib/types/client/contract/input.d.ts` 含 `captureInsertion(): TokenSpan` 与
379
+ > `insertText(text: string, span: TokenSpan): boolean`(且文档明确 *"returns false when the
380
+ > draft changed"*,与实现"拒绝先于写入、故可安全回落 DOM 路径"的假设一致),
381
+ > `lib/client.js` 含 `var SessionInputShell = class`。逐条命中与行号见
382
+ > `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`。
372
383
 
373
384
  因此实现是三级阶梯:`inputActions.insertText`(重新 `captureInsertion` 后可重试一次)→
374
385
  DOM `beforeinput`(追加到末尾)→ 失败时 toast。**脚本设定的 Range 不被 Lexical 采纳**,
@@ -588,15 +599,16 @@ node test/smoke-test.mjs # 端到端:流式/截断/取消/持久化/失
588
599
  ```
589
600
 
590
601
  四个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
591
- 当前规模(1.8.0 本机实测,四项均 exit 0):verify **57** 项 + contract **946** 项 + float-placement **38** 项 +
592
- smoke **226** 项 = **1267 项全部通过**(1.7.2 为 1004 项;新增 263 项覆盖 P2-1 风格档位、
593
- P2-2 写回决策表与两级写入、P2-3 签名/遍历/失败分支、P2-4 宽容解析与芯片渲染、
594
- P2-5 导出内容与 v1/v2→v3 迁移)。
602
+ 当前规模(1.8.1 本机实测,四项均 exit 0):verify **57** 项 + contract **956** 项 + float-placement **38** 项 +
603
+ smoke **226** 项 = **1277 项全部通过**(1.8.0 为 1267 项,其中 contract 由 946 → 956:
604
+ 新增"清空历史后重载仍为空"与"建议芯片跟随开关"两组回归用例)。
595
605
 
596
606
  **真机探针(不属于四套自测)**:`.compat-report/scripts/composer-probe.mjs` 需要
597
- `playwright-core` 与本机 Edge,对**真实 DSH 页面**回答 composer DOM / 官方写入面 /
598
- `beforeinput` 是否生效 / `CSS.highlights` 是否可用四个问题。它是 P2-2 的落地依据,
599
- **不能**被四套自测替代,也不替代人工观感确认。
607
+ `playwright-core` 与本机 Edge,对**真实 DSH 页面**回答 composer DOM / 哪条写入路径真能改变
608
+ 编辑器状态 / `CSS.highlights` 是否可用三个问题(证据 `.compat-report/evidence/p2-probe-2026-10-17.json`)。
609
+ 它**不**探测 `inputActions`——那是通过槽位 prop 交给插件的面,页面侧脚本读不到;该写入面的存在性
610
+ 改由**产物层证据**背书(`.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`)。
611
+ 探针是 P2-2 的落地依据,**不能**被四套自测替代,也不替代人工观感确认。
600
612
 
601
613
  > 1.5.0 之前这里还放过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个「替代检查」。
602
614
  > 它们已被**删除**:前者用「正则字面量 vs 除号」的启发式判断,把 8 个源文件全部误报为残损;
@@ -778,5 +790,8 @@ npm deprecate "<旧包名>@*" "Renamed to <新包名> - <原因>" # 旧名
778
790
  10. **建议追问(1.8.0)依赖模型遵循格式**:模型不写 `<followups>`、写半个、或写得过长时,
779
791
  客户端只会"少几个芯片",答案正文照常显示;这是设计内的降级,不是错误。该功能不额外发请求。
780
792
  11. **多版本(1.8.0)只在「重试 / 改问」时产生**:普通「继续追问」把旧问答存进 `history`(作为上下文),
781
- 不进 `versions`;每卡上限 5 条,超出丢最旧。v3 持久化保留 v2/v1 旧键,回退旧版本仍可读自己的数据。
793
+ 不进 `versions`;每卡上限 5 条,超出丢最旧。v3 持久化**默认保留** v2/v1 旧键(回退旧版本仍可读
794
+ 自己的数据);**唯一的例外是「清空全部历史」**——显式清空会同时删除 v3/v2/v1 三代键(1.8.1)。
795
+ 这条例外是必需的:只删当前键会让 `readPersisted()` 的 v3→v2→v1 回落把刚清掉的历史从旧键读回来,
796
+ 刷新一次就复活(1.8.1 复现并修复)。
782
797
 
package/client.js CHANGED
@@ -47,7 +47,7 @@ window.__ModuleLoader__.load({
47
47
  * the Host and the module's `api.version` both read it, so a report can
48
48
  * never claim a generation the browser is not actually running.
49
49
  */
50
- const CLIENT_VERSION = '1.8.0'
50
+ const CLIENT_VERSION = '1.8.1'
51
51
 
52
52
  /**
53
53
  * Tab kind served by the DSH native right rail (also its implementation id).
@@ -3688,7 +3688,13 @@ window.__ModuleLoader__.load({
3688
3688
  // P2-4: chips the MODEL proposed for this answer. The block has
3689
3689
  // already been stripped from the body; a missing/half/oversized block
3690
3690
  // simply yields none. Clicking one submits an ordinary follow-up turn.
3691
- terminalWithAnswer && viewingVersion === null && parsedFollowUps.followups.length > 0
3691
+ //
3692
+ // The ROW follows `followUpSuggestions`, while the STRIP above stays
3693
+ // unconditional: the toggle owns "do we offer the feature", and a
3694
+ // stray block from a model must never render as raw prose even when
3695
+ // the user never asked for chips.
3696
+ terminalWithAnswer && viewingVersion === null && config.followUpSuggestions === true
3697
+ && parsedFollowUps.followups.length > 0
3692
3698
  ? h('div', { className: 'dsa-sug' },
3693
3699
  h('span', { className: 'dsa-muted' }, t('suggestionsLabel')),
3694
3700
  h(QuickChips, {
@@ -4682,10 +4688,22 @@ window.__ModuleLoader__.load({
4682
4688
  })
4683
4689
  toast(t('historyDeleted'))
4684
4690
  },
4685
- /** Empty the durable history without touching running cards. */
4691
+ /**
4692
+ * Empty the durable history without touching running cards.
4693
+ *
4694
+ * Removes EVERY generation key, not just the current one:
4695
+ * {@link readPersisted} falls back to v2 → v1 whenever the current
4696
+ * key is absent (that fallback is exactly what makes the
4697
+ * non-destructive migration work), so deleting only v3 let a legacy
4698
+ * payload resurrect the "cleared" history on the next page load —
4699
+ * reproduced live, 2026-10-17. An explicit user clear is destructive
4700
+ * by definition: it is the one action allowed to drop the old keys.
4701
+ */
4686
4702
  clearHistory() {
4687
4703
  try {
4688
- window.localStorage?.removeItem(PERSIST_KEY)
4704
+ for (const key of [PERSIST_KEY, PERSIST_V2_KEY, PERSIST_LEGACY_KEY]) {
4705
+ window.localStorage?.removeItem(key)
4706
+ }
4689
4707
  } catch (error) {
4690
4708
  console.warn(`[${PLUGIN_ID}] history clear failed:`, error)
4691
4709
  }
package/index.js CHANGED
@@ -42,7 +42,7 @@ export const name = 'dsh-sidecard-ask'
42
42
  export const inject = ['webServer']
43
43
 
44
44
  /** Version of this plugin (kept in step with package.json by test/verify.mjs). */
45
- export const PLUGIN_VERSION = '1.8.0'
45
+ export const PLUGIN_VERSION = '1.8.1'
46
46
 
47
47
  /** Route prefix of the plugin's own API. */
48
48
  export const ROUTE_PREFIX = '/sidecard-ask/api'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sidecard-ask",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "DSH 划词追问(侧边卡片作答):在聊天区与任务区选中文本就地追问,答案由独立子代理在侧边卡片里流式呈现,也可选择落回主对话。",
5
5
  "type": "module",
6
6
  "main": "index.js",