dsh-sidecard-ask 1.8.0 → 1.8.2
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 +79 -3
- package/README.md +37 -16
- package/client.js +113 -24
- package/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.8.2
|
|
4
|
+
|
|
5
|
+
**主题:提问框底部的「作答位置」从"两枚看不出选中哪一侧的标签"改成一眼可辨的分段芯片;并把 1.8.1 之后写在本地、尚未提交的弹层对比度修补一并发布。**
|
|
6
|
+
|
|
7
|
+
起因是用户截图:弹层底部「作答位置 | 主对话 | 侧边卡片」两个选项长得几乎一样,看不出答案会去主对话还是侧边卡片。
|
|
8
|
+
|
|
9
|
+
- **选中态不可辨(用户反馈,已复现)**:旧选中态只有 `background: var(--dsw-alias-bg-layer-2)`,
|
|
10
|
+
叠在弹层 `--dsw-alias-bg-overlay` 上几乎是同色(本机皮肤下 `rgba(243,247,253,.95)` on
|
|
11
|
+
`rgba(248,251,255,.98)`),选中与未选中的差别只剩字重。
|
|
12
|
+
改法:`.dsa-seg` 变成带中灰描边(`rgba(127,127,127,.5)`)与内凹阴影的轨道;两个选项各带一枚 13px
|
|
13
|
+
内联 SVG 图标(气泡=主对话,带右轨的框=侧边卡片),**选中侧为品牌实底 + 白色 600 字 + 内白环 + 投影**,
|
|
14
|
+
未选侧为 `label-secondary` + 透明底;补 `:hover` 与 `:focus-visible`(品牌色 outline)。
|
|
15
|
+
组件抽成 `CarrierSeg`(client §9),带 `role="group"`、`aria-pressed`、`title` 说明;文案仍走 i18n。
|
|
16
|
+
- **深色主题里白字压品牌色只有 2.79:1(复核时发现的,非用户反馈)**:本机皮肤深色把
|
|
17
|
+
`--dsw-alias-brand-primary` 提亮到 `#5b9cf5`,白色 600 字在其上低于 4.5:1 正文下限、也低于 3:1 大字下限。
|
|
18
|
+
改法:`[data-ds-dark-theme]` 下选中侧改用深墨 `#0a1428`(实测 6.57:1);浅色保持白字
|
|
19
|
+
(`#2563c0` 上 5.79:1)。令牌值取自本机 `uiskin-theme`,对比度按 WCAG 相对亮度公式实算,不是目测。
|
|
20
|
+
- **弹层边框/阴影对比度修补(1.8.1 之后写在本地、未提交,本次一并发布)**:`.dsa-pop` 的 1px 边
|
|
21
|
+
改显式中灰 `rgba(127,127,127,.55)`(`border-l2` 落在弹层表面只量到 ≈1.3:1),阴影改两层衰减
|
|
22
|
+
`0 14px 36px rgba(0,0,0,.30), 0 3px 10px rgba(0,0,0,.14)`;`.dsa-pop` 内的次级文案改用
|
|
23
|
+
`label-primary` 并保留 `.78` 不透明度。
|
|
24
|
+
- 测试:contract 新增 **16** 项钉住这个控件(唯一 `.dsa-seg`、恰好两个按钮、恰好一个
|
|
25
|
+
`aria-pressed="true"`、每个选项带一枚内联 `svg`、`data-on` 只有一个真值、两个去向的标签不互换、
|
|
26
|
+
品牌实底规则与深色覆盖规则必须出现在样式表里)。**点击切换本身仍然不可测**:harness 的
|
|
27
|
+
`useState` 只回队列式 setter、不触发重渲染,注释里写明了原因,没有假装测过。
|
|
28
|
+
- 版本号三处同步到 1.8.2。
|
|
29
|
+
|
|
30
|
+
本机实测(四项均 exit 0):verify **57** / contract **972** / float-placement **38** / smoke **226**
|
|
31
|
+
= **1293 项全部通过**(1.8.1 为 1277,contract 956 → 972)。
|
|
32
|
+
|
|
33
|
+
> 未验证(如实记录):浅色/深色两张对比图(`design-preview/07-carrier-seg-{light,dark}.png`)是
|
|
34
|
+
> **离屏渲染**——无头 Edge 加载"从 `client.js` 抽取的真实 CSS + 本机皮肤真实令牌值"的静态页,
|
|
35
|
+
> 不是运行中 DSH 窗口的截图。真实页面里的点击手感、字体栅格化差异、弹层背后背景图的影响,
|
|
36
|
+
> 仍需刷新页面人工看一次。此前一轮口头汇报里说"新增 8 条"是数错了,实际新增 16 条。
|
|
37
|
+
|
|
38
|
+
## 1.8.1
|
|
39
|
+
|
|
40
|
+
**主题:对 1.8.0 交付做实证复核后的三项修正——1 个已复现的真实缺陷、1 处开关失效、1 条缺证据的声明。**
|
|
41
|
+
|
|
42
|
+
复核方式:不采信文档,独立跑四套测试、读全部新增代码、对可疑点写最小复现脚本并落盘证据。
|
|
43
|
+
|
|
44
|
+
- **P0(真实缺陷,已复现)「清空全部历史」刷新后复活**:`clearHistory()` 只删了当前的
|
|
45
|
+
`:cards:v3` 键,而 `readPersisted()` 在 v3 缺席时会回落到 `:cards:v2` → `:cards:v1`
|
|
46
|
+
(这条回落正是"非破坏式迁移"的实现方式)。于是**从 1.7.x 升级上来的用户**点「清空全部历史」
|
|
47
|
+
后刷新页面,旧卡片会从遗留的 v2 键整份读回来——2026-10-17 用独立脚本复现
|
|
48
|
+
(清空前 history=1 → 清空后=0、v3 键为 null、v2 键仍在 → 重载后 history=1,卡片 id 为旧数据)。
|
|
49
|
+
修法:`clearHistory` 同时删除 v3/v2/v1 三代键(显式清空本就是破坏性操作,它是唯一允许丢弃旧键的动作)。
|
|
50
|
+
测试为什么原先没拦住:旧用例只断言"v3 键被删掉",且只播种 v3 键、清空后**不重载客户端**——
|
|
51
|
+
钉的是实现细节而非用户可见结果。新增用例覆盖"遗留 v2 + 清空 + 重载仍为空"。
|
|
52
|
+
- **P2(开关失效)建议追问芯片未跟随 `followUpSuggestions`**:只要答案里存在 `<followups>` 块,
|
|
53
|
+
芯片就渲染,与默认关闭的 opt-in 语义不符。现在渲染跟随开关;**块剥离保持无条件**
|
|
54
|
+
(模型乱写标记时,正文仍不会被污染成散文)。旧用例正是"开关关闭却断言芯片出现",已改为开启态,
|
|
55
|
+
并补关闭态回归守卫。
|
|
56
|
+
- **P1(证据链 + 勘误)`inputActions` 写入面**:1.8.0 把"官方写入面存在
|
|
57
|
+
`captureInsertion()` / `insertText()` / `setDraft()`"写成探针的第④条结论,但
|
|
58
|
+
`.compat-report/scripts/composer-probe.mjs` 的正文**从未探测 `inputActions`**(只有脚本头注释
|
|
59
|
+
承诺过),仓库内也没有任何证据文件出现过这两个标识符。经复核,**声明本身成立**,现补产物层证据:
|
|
60
|
+
`npm pack @deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2` 与 `@0.2.1-rc.2` 解包后,
|
|
61
|
+
`lib/types/client/contract/input.d.ts` 含 `captureInsertion(): TokenSpan` 与
|
|
62
|
+
`insertText(text: string, span: TokenSpan): boolean`,`lib/client.js` 含 `var SessionInputShell = class`。
|
|
63
|
+
逐条命中、行号与官方契约原文见 `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`;
|
|
64
|
+
README §6.3 与 1.8.0 条目已就地勘误,探针脚本头也改成了它真正做的事。
|
|
65
|
+
- 版本号三处同步到 1.8.1。
|
|
66
|
+
|
|
67
|
+
本机实测(四项均 exit 0):verify **57** / contract **956** / float-placement **38** / smoke **226**
|
|
68
|
+
= **1277 项全部通过**(较 1.8.0 的 1267 新增 10 项)。
|
|
69
|
+
|
|
70
|
+
> 未验证(如实记录):本次修的是逻辑与测试,**没有在真实页面上点过任何按钮**。
|
|
71
|
+
> 1.8.0 未验证栏的 ①–⑤ 全部继续有效(写回按钮的真实点击与手感、原文高亮的观感与虚拟列表命中率、
|
|
72
|
+
> 导出的真实落盘、真实模型对风格与 `<followups>` 的遵循度、Electron 外壳表现)。
|
|
73
|
+
> 另外:`inputActions` 的证据核到 0.2.0-rc.2 与 0.2.1-rc.2 两个版本,未逐版遍历。
|
|
74
|
+
|
|
3
75
|
## 1.8.0
|
|
4
76
|
|
|
5
77
|
**主题:P2 能力补齐——回答风格档位、答案写回输入框、原文回看定位、模型生成的追问建议、导出与多版本答案。**
|
|
@@ -21,9 +93,13 @@
|
|
|
21
93
|
contenteditable**(`[data-composer-input][data-lexical-editor="true"]`),不是
|
|
22
94
|
textarea;② `document.execCommand('insertText')` 返回 `false` 且**完全不生效**;
|
|
23
95
|
③ 合成 `beforeinput`(`inputType:'insertText'`)会被 Lexical 接受——文本落地、
|
|
24
|
-
|
|
25
|
-
`captureInsertion()` + `insertText(text, span)` + `setDraft(text)
|
|
26
|
-
|
|
96
|
+
发送按钮由禁用转可用。另有一条**不在探针内**的结论:DSH 的槽位标准 prop `inputActions`
|
|
97
|
+
暴露官方写入面 `captureInsertion()` + `insertText(text, span)` + `setDraft(text)`。
|
|
98
|
+
> **1.8.1 勘误**:本条原先被写成"探针的第④条结论",但探针脚本的正文从未实现该探测
|
|
99
|
+
> (只有脚本头注释承诺过),当时没有可核验的证据。经产物层复核,声明本身**成立**:
|
|
100
|
+
> `@deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2 / 0.2.1-rc.2` 的
|
|
101
|
+
> `lib/types/client/contract/input.d.ts` 含该契约,逐条命中见
|
|
102
|
+
> `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`。
|
|
27
103
|
据此实现三级写入阶梯:**官方 `insertText`**(重新 `captureInsertion` 后可重试一次)
|
|
28
104
|
→ **DOM `beforeinput`**(contenteditable 追加 / textarea 用原生 value setter + input
|
|
29
105
|
事件)→ 明确失败。改写类动作(`builtin:polish` / `builtin:fix`)的已完成答案上出现
|
package/README.md
CHANGED
|
@@ -72,8 +72,10 @@ dsh-sidecard-ask/
|
|
|
72
72
|
├── README.md 本文件
|
|
73
73
|
├── CHANGELOG.md 版本变更记录
|
|
74
74
|
├── LICENSE MIT
|
|
75
|
-
├── design-preview/
|
|
76
|
-
│ 03-prototype-ac.html
|
|
75
|
+
├── design-preview/ 方案讨论资料,**不参与发布**:00–02 是手绘 SVG 示意图(非截图),
|
|
76
|
+
│ 03-prototype-ac.html 是可直接双击打开的可点按原型(自带近似色板,非真实观感),
|
|
77
|
+
│ 07-carrier-seg-{light,dark}.png 是 1.8.2 的离屏渲染对比图(无头 Edge + 真实 CSS
|
|
78
|
+
│ 与真实令牌值,仍非运行中窗口截图)
|
|
77
79
|
├── tools/
|
|
78
80
|
│ └── compat-probe.mjs DSH 版本能力探测(§7 矩阵的来源)
|
|
79
81
|
└── test/
|
|
@@ -362,13 +364,24 @@ ctx.inject(['sidebarRightTabs', 'sidebarRight'], (injected) => { ... })
|
|
|
362
364
|
### 3. `client.js` §8c —— 答案写回输入框(1.8.0,已做真机探针)
|
|
363
365
|
|
|
364
366
|
2026-10-17 用**隔离 `DSH_HOME` + 无头 Edge**(插件不参与)对真实 DSH 页面做了探针,
|
|
365
|
-
脚本见 `.compat-report/scripts/composer-probe.mjs
|
|
367
|
+
脚本见 `.compat-report/scripts/composer-probe.mjs`,证据见
|
|
368
|
+
`.compat-report/evidence/p2-probe-2026-10-17.json`,三条硬结论:
|
|
366
369
|
|
|
367
|
-
| 问题 | 结论 |
|
|
368
|
-
|
|
369
|
-
| composer 的真实 DOM | **Lexical contenteditable**:`<div contenteditable="true" role="textbox" data-composer-input="true" data-lexical-editor="true">`,槽位 `conversation.composer` |
|
|
370
|
-
|
|
|
371
|
-
|
|
|
370
|
+
| 问题 | 结论 | 证据来源 |
|
|
371
|
+
|---|---|---|
|
|
372
|
+
| composer 的真实 DOM | **Lexical contenteditable**:`<div contenteditable="true" role="textbox" data-composer-input="true" data-lexical-editor="true">`,槽位 `conversation.composer` | 页面探针 |
|
|
373
|
+
| native setter + input 事件能否同步 | **N/A 且无效**:不是 textarea,没有 value setter;`document.execCommand('insertText')` 返回 `false`、文本不变。**有效的是**合成 `beforeinput`(`inputType:'insertText'`):文本落地、发送按钮由禁用转可用 | 页面探针 |
|
|
374
|
+
| 有没有官方写入面 | **有**。槽位标准 prop `inputActions` 暴露 `captureInsertion()` / `insertText(text, span)` / `setDraft(text)` / `submit()`。`sessions.using(...).binding.session.prompt` 只负责**提交**,不能写草稿 | **产物层核验**(见下),**不是**页面探针 |
|
|
375
|
+
|
|
376
|
+
> **1.8.1 勘误**:上面第三行原先被写成"探针的第④条结论",但 `composer-probe.mjs` 的正文
|
|
377
|
+
> 从未探测 `inputActions`(只有脚本头的注释承诺过)——这条声明当时**没有可核验的证据**。
|
|
378
|
+
> 经复核,声明本身**成立**,现改由产物层证据背书:`npm pack
|
|
379
|
+
> @deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2` 与 `@0.2.1-rc.2` 解包后,
|
|
380
|
+
> `lib/types/client/contract/input.d.ts` 含 `captureInsertion(): TokenSpan` 与
|
|
381
|
+
> `insertText(text: string, span: TokenSpan): boolean`(且文档明确 *"returns false when the
|
|
382
|
+
> draft changed"*,与实现"拒绝先于写入、故可安全回落 DOM 路径"的假设一致),
|
|
383
|
+
> `lib/client.js` 含 `var SessionInputShell = class`。逐条命中与行号见
|
|
384
|
+
> `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`。
|
|
372
385
|
|
|
373
386
|
因此实现是三级阶梯:`inputActions.insertText`(重新 `captureInsertion` 后可重试一次)→
|
|
374
387
|
DOM `beforeinput`(追加到末尾)→ 失败时 toast。**脚本设定的 Range 不被 Lexical 采纳**,
|
|
@@ -504,6 +517,9 @@ node tools/compat-probe.mjs --json tools/.cache/matrix.json
|
|
|
504
517
|
2. 选区末端应浮出动作条:「💬 追问选中内容」按钮 + 「解释 / 翻译 / 总结 / 润色 / 纠错」芯片,
|
|
505
518
|
按钮带区域徽标(聊天区 / 任务区 / 跨区选择);**点任一芯片即跳过提问框直接在侧边卡片作答**;
|
|
506
519
|
3. 点主按钮后提问框出现(顶部同样有一排芯片),焦点在输入框,引文预览显示选中文本;
|
|
520
|
+
底部「作答位置」是两枚带内联图标的芯片(气泡=主对话,带右轨的框=侧边卡片):**选中侧为品牌实底 + 白字 600 + 内白环**,
|
|
521
|
+
未选侧只有描边;深色主题下选中侧改深墨字(品牌色在深色被提亮到 `#5b9cf5`,白字只有 2.79:1,深墨字 6.57:1);
|
|
522
|
+
控件带 `role="group"` 与 `aria-pressed`,键盘 Tab 可见品牌色焦点环;
|
|
507
523
|
4. 输入问题按 Enter → 提问框关闭,右下角出现卡片,状态先为「作答中…」并**逐字增长**;
|
|
508
524
|
5. 结束后状态变「已完成」;底部出现「改问 / 复制 / 继续追问 / 关闭」;答案里代码块的右上角有「⧉ 复制代码」;
|
|
509
525
|
6. 点「复制」→ 出现「已复制」提示;点「继续追问」→ 输入第二条问题(追加一轮),卡片内容**重置换行**后继续流式;
|
|
@@ -588,15 +604,17 @@ node test/smoke-test.mjs # 端到端:流式/截断/取消/持久化/失
|
|
|
588
604
|
```
|
|
589
605
|
|
|
590
606
|
四个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
|
|
591
|
-
当前规模(1.8.
|
|
592
|
-
smoke **226** 项 = **
|
|
593
|
-
|
|
594
|
-
|
|
607
|
+
当前规模(1.8.2 本机实测,四项均 exit 0):verify **57** 项 + contract **972** 项 + float-placement **38** 项 +
|
|
608
|
+
smoke **226** 项 = **1293 项全部通过**(1.8.1 为 1277 项,其中 contract 由 956 → 972:新增 16 项钉住提问框
|
|
609
|
+
「作答位置」分段芯片——唯一 `.dsa-seg`、两个按钮、恰好一个 `aria-pressed`、各带一枚内联图标、
|
|
610
|
+
品牌实底规则与深色覆盖规则;点击切换本身不可测,原因见 1.8.2 的 CHANGELOG)。
|
|
595
611
|
|
|
596
612
|
**真机探针(不属于四套自测)**:`.compat-report/scripts/composer-probe.mjs` 需要
|
|
597
|
-
`playwright-core` 与本机 Edge,对**真实 DSH 页面**回答 composer DOM /
|
|
598
|
-
|
|
599
|
-
|
|
613
|
+
`playwright-core` 与本机 Edge,对**真实 DSH 页面**回答 composer DOM / 哪条写入路径真能改变
|
|
614
|
+
编辑器状态 / `CSS.highlights` 是否可用三个问题(证据 `.compat-report/evidence/p2-probe-2026-10-17.json`)。
|
|
615
|
+
它**不**探测 `inputActions`——那是通过槽位 prop 交给插件的面,页面侧脚本读不到;该写入面的存在性
|
|
616
|
+
改由**产物层证据**背书(`.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`)。
|
|
617
|
+
探针是 P2-2 的落地依据,**不能**被四套自测替代,也不替代人工观感确认。
|
|
600
618
|
|
|
601
619
|
> 1.5.0 之前这里还放过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个「替代检查」。
|
|
602
620
|
> 它们已被**删除**:前者用「正则字面量 vs 除号」的启发式判断,把 8 个源文件全部误报为残损;
|
|
@@ -778,5 +796,8 @@ npm deprecate "<旧包名>@*" "Renamed to <新包名> - <原因>" # 旧名
|
|
|
778
796
|
10. **建议追问(1.8.0)依赖模型遵循格式**:模型不写 `<followups>`、写半个、或写得过长时,
|
|
779
797
|
客户端只会"少几个芯片",答案正文照常显示;这是设计内的降级,不是错误。该功能不额外发请求。
|
|
780
798
|
11. **多版本(1.8.0)只在「重试 / 改问」时产生**:普通「继续追问」把旧问答存进 `history`(作为上下文),
|
|
781
|
-
不进 `versions`;每卡上限 5 条,超出丢最旧。v3
|
|
799
|
+
不进 `versions`;每卡上限 5 条,超出丢最旧。v3 持久化**默认保留** v2/v1 旧键(回退旧版本仍可读
|
|
800
|
+
自己的数据);**唯一的例外是「清空全部历史」**——显式清空会同时删除 v3/v2/v1 三代键(1.8.1)。
|
|
801
|
+
这条例外是必需的:只删当前键会让 `readPersisted()` 的 v3→v2→v1 回落把刚清掉的历史从旧键读回来,
|
|
802
|
+
刷新一次就复活(1.8.1 复现并修复)。
|
|
782
803
|
|
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.
|
|
50
|
+
const CLIENT_VERSION = '1.8.2'
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Tab kind served by the DSH native right rail (also its implementation id).
|
|
@@ -1919,10 +1919,17 @@ window.__ModuleLoader__.load({
|
|
|
1919
1919
|
trigger drops its own fixed positioning, chips join the row directly. */
|
|
1920
1920
|
.dsa-trigger-embedded{position:static;box-shadow:0 2px 10px rgba(0,0,0,.18)}
|
|
1921
1921
|
.dsa-quickbar-chips{display:contents}
|
|
1922
|
+
/* Contrast pass, driven by a light-theme reference screenshot: every 1px edge
|
|
1923
|
+
inside the popover measured only ≈1.3–1.7:1 against the card surface, which
|
|
1924
|
+
is why the card read as a translucent sticker instead of a framed popover.
|
|
1925
|
+
The frame is therefore drawn explicitly here rather than through border-l2,
|
|
1926
|
+
which the host resolves below that level; mid grey is used instead of a
|
|
1927
|
+
black/white alpha so the stroke stays visible on both themes. The shadow is
|
|
1928
|
+
a two-layer falloff so the card lifts off the page as well. */
|
|
1922
1929
|
.dsa-pop{pointer-events:auto;position:fixed;width:min(420px, 92vw);display:flex;flex-direction:column;gap:8px;
|
|
1923
|
-
padding:12px;border-radius:12px;border:1px solid
|
|
1930
|
+
padding:12px;border-radius:12px;border:1px solid rgba(127,127,127,.55);
|
|
1924
1931
|
background:var(--dsw-alias-bg-overlay, #fff);color:var(--dsw-alias-label-primary, #111);
|
|
1925
|
-
box-shadow:0
|
|
1932
|
+
box-shadow:0 14px 36px rgba(0,0,0,.30),0 3px 10px rgba(0,0,0,.14);font-size:13px;line-height:19px}
|
|
1926
1933
|
.dsa-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
1927
1934
|
.dsa-title{font-weight:600;font-size:13px}
|
|
1928
1935
|
.dsa-quote{margin:0;padding:6px 8px;max-height:96px;overflow:auto;white-space:pre-wrap;word-break:break-word;
|
|
@@ -1946,11 +1953,43 @@ window.__ModuleLoader__.load({
|
|
|
1946
1953
|
/* Icon-only variant (the floating layer's collapse/hide controls): square and
|
|
1947
1954
|
quiet so the card title bar stays one line and the title keeps the weight. */
|
|
1948
1955
|
.dsa-btn-icon{padding:4px 7px;line-height:1;font-size:13px;color:var(--dsw-alias-label-secondary, #666)}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1956
|
+
/* Segmented control (main chat / side card). These are the two destinations an
|
|
1957
|
+
answer can land in, so the pair has to read as a radio choice, not as two
|
|
1958
|
+
quiet labels: the group gets a recessed track with an explicit mid-grey edge
|
|
1959
|
+
(border-l1/l2 measured ≈1.3:1 against the popover surface and disappeared),
|
|
1960
|
+
each option carries a 13px label plus an inline glyph, and the selected side
|
|
1961
|
+
is a solid brand chip whose white 600-weight text cannot be confused with the
|
|
1962
|
+
unselected side. The inner white ring keeps the chip from flattening on the
|
|
1963
|
+
brand colour in either theme. */
|
|
1964
|
+
.dsa-seg{display:inline-flex;padding:3px;gap:3px;
|
|
1965
|
+
border:1px solid rgba(127,127,127,.5);border-radius:10px;
|
|
1966
|
+
background:var(--dsw-alias-bg-layer-1, rgba(127,127,127,.10));
|
|
1967
|
+
box-shadow:inset 0 1px 2px rgba(0,0,0,.10)}
|
|
1968
|
+
.dsa-seg button{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;
|
|
1969
|
+
border:1px solid transparent;border-radius:7px;background:transparent;
|
|
1970
|
+
color:var(--dsw-alias-label-secondary, #666);font:inherit;font-size:13px;font-weight:500;
|
|
1971
|
+
line-height:18px;cursor:pointer;white-space:nowrap;
|
|
1972
|
+
transition:background .12s ease,color .12s ease,box-shadow .12s ease}
|
|
1973
|
+
.dsa-seg button svg{flex:none;opacity:.8}
|
|
1974
|
+
.dsa-seg button:hover{background:var(--dsw-alias-bg-layer-2, rgba(127,127,127,.16));
|
|
1975
|
+
color:var(--dsw-alias-label-primary, #222)}
|
|
1976
|
+
.dsa-seg button:hover svg{opacity:1}
|
|
1977
|
+
.dsa-seg button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary, #4c8dff);outline-offset:1px}
|
|
1978
|
+
.dsa-seg button[data-on="true"]{background:var(--dsw-alias-brand-primary, #4c8dff);
|
|
1979
|
+
border-color:var(--dsw-alias-brand-primary, #4c8dff);color:#fff;font-weight:600;
|
|
1980
|
+
box-shadow:0 1px 4px rgba(0,0,0,.22),inset 0 0 0 1px rgba(255,255,255,.22)}
|
|
1981
|
+
.dsa-seg button[data-on="true"] svg{opacity:1}
|
|
1982
|
+
.dsa-seg button[data-on="true"]:hover{background:var(--dsw-alias-brand-primary, #4c8dff);color:#fff}
|
|
1983
|
+
/* Dark themes lighten the brand accent (the local skin goes #2563c0 -> #5b9cf5),
|
|
1984
|
+
where white 600 text on the chip measures 2.79:1 — below the 4.5:1 floor for
|
|
1985
|
+
body text and below even the 3:1 large-text floor. Invert the on-brand label
|
|
1986
|
+
there instead of betting on one fixed foreground.
|
|
1987
|
+
DSH flags dark mode with [data-ds-dark-theme] on the root element. */
|
|
1988
|
+
html[data-ds-dark-theme] .dsa-seg button[data-on="true"],
|
|
1989
|
+
body[data-ds-dark-theme] .dsa-seg button[data-on="true"],
|
|
1990
|
+
html[data-ds-dark-theme] .dsa-seg button[data-on="true"]:hover,
|
|
1991
|
+
body[data-ds-dark-theme] .dsa-seg button[data-on="true"]:hover{
|
|
1992
|
+
color:#0a1428;text-shadow:0 1px 0 rgba(255,255,255,.25)}
|
|
1954
1993
|
.dsa-card{pointer-events:auto;display:flex;flex-direction:column;gap:8px;padding:12px;border-radius:12px;
|
|
1955
1994
|
border:1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.35));
|
|
1956
1995
|
background:var(--dsw-alias-bg-overlay, #fff);color:var(--dsw-alias-label-primary, #111);
|
|
@@ -2014,6 +2053,13 @@ window.__ModuleLoader__.load({
|
|
|
2014
2053
|
.dsa-code{padding:0 4px;border-radius:4px;background:var(--dsw-alias-bg-layer-2, rgba(127,127,127,.12));
|
|
2015
2054
|
font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
|
|
2016
2055
|
.dsa-muted{color:var(--dsw-alias-label-secondary, #666);font-size:11px;line-height:16px}
|
|
2056
|
+
/* Same contrast pass, applied to the popover's body copy: label-secondary
|
|
2057
|
+
measures ≈6:1 here and reads washed out at 11–13px. Secondary text inside
|
|
2058
|
+
.dsa-pop is re-derived from the primary label token instead, held one step
|
|
2059
|
+
quieter than the title through opacity so the hierarchy survives. */
|
|
2060
|
+
.dsa-pop .dsa-quote,
|
|
2061
|
+
.dsa-pop .dsa-muted{color:var(--dsw-alias-label-primary, #111)}
|
|
2062
|
+
.dsa-pop .dsa-muted{opacity:.78}
|
|
2017
2063
|
.dsa-error{color:var(--dsw-alias-state-error-primary, #c0392b);font-size:12px}
|
|
2018
2064
|
.dsa-toast{pointer-events:auto;position:fixed;left:50%;transform:translateX(-50%);bottom:24px;padding:6px 12px;
|
|
2019
2065
|
border-radius:999px;font-size:12px;background:var(--dsw-alias-bg-overlay, #222);
|
|
@@ -3260,6 +3306,43 @@ window.__ModuleLoader__.load({
|
|
|
3260
3306
|
}, h('span', null, action.label))))
|
|
3261
3307
|
}
|
|
3262
3308
|
|
|
3309
|
+
/** Where the answer should land: the main conversation, or an independent
|
|
3310
|
+
* side card. Rendered as a two-chip switch with an inline glyph per option
|
|
3311
|
+
* so the pair is scannable at a glance, and the selected chip is painted
|
|
3312
|
+
* with the brand fill (see .dsa-seg) instead of a whisper-grey tint. The
|
|
3313
|
+
* hint text moves into title/aria-label rather than a third line, because
|
|
3314
|
+
* the popover already has quote + chips + composer and a longer hint would
|
|
3315
|
+
* push the submit row out of view. */
|
|
3316
|
+
function CarrierSeg({ value, onChange }) {
|
|
3317
|
+
const glyph = path => h('svg', {
|
|
3318
|
+
viewBox: '0 0 16 16', width: 13, height: 13, 'aria-hidden': 'true', focusable: 'false',
|
|
3319
|
+
}, h('path', {
|
|
3320
|
+
d: path, fill: 'none', stroke: 'currentColor', strokeWidth: 1.3,
|
|
3321
|
+
strokeLinejoin: 'round', strokeLinecap: 'round',
|
|
3322
|
+
}))
|
|
3323
|
+
const options = [
|
|
3324
|
+
// speech balloon = the main conversation
|
|
3325
|
+
{ id: 'main', label: t('carrierMain'), hint: t('carrierMainHint'),
|
|
3326
|
+
icon: glyph('M2.5 3.5h11v8H8.4L5.2 14.3V11.5H2.5z') },
|
|
3327
|
+
// frame with a rail on the right = a card docked in the side panel
|
|
3328
|
+
{ id: 'side', label: t('carrierSide'), hint: t('carrierSideHint'),
|
|
3329
|
+
icon: glyph('M2.5 3.5h11v9h-11zM10 3.5v9') },
|
|
3330
|
+
]
|
|
3331
|
+
return h('div', {
|
|
3332
|
+
className: 'dsa-seg', role: 'group', 'aria-label': t('carrier'),
|
|
3333
|
+
}, options.map(option => {
|
|
3334
|
+
const on = value === option.id
|
|
3335
|
+
return h('button', {
|
|
3336
|
+
key: option.id,
|
|
3337
|
+
type: 'button',
|
|
3338
|
+
'data-on': String(on),
|
|
3339
|
+
'aria-pressed': String(on),
|
|
3340
|
+
title: option.hint,
|
|
3341
|
+
onClick: () => onChange(option.id),
|
|
3342
|
+
}, option.icon, option.label)
|
|
3343
|
+
}))
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3263
3346
|
/** The question popover. */
|
|
3264
3347
|
function AskPopover({ candidate, config, onClose, onSubmit, onQuick }) {
|
|
3265
3348
|
const [question, setQuestion] = React.useState('')
|
|
@@ -3341,19 +3424,7 @@ window.__ModuleLoader__.load({
|
|
|
3341
3424
|
}),
|
|
3342
3425
|
h('div', { className: 'dsa-row' },
|
|
3343
3426
|
h('span', { className: 'dsa-muted' }, t('carrier')),
|
|
3344
|
-
h(
|
|
3345
|
-
h('button', {
|
|
3346
|
-
type: 'button',
|
|
3347
|
-
'data-on': String(carrier === 'main'),
|
|
3348
|
-
title: t('carrierMainHint'),
|
|
3349
|
-
onClick: () => setCarrier('main'),
|
|
3350
|
-
}, t('carrierMain')),
|
|
3351
|
-
h('button', {
|
|
3352
|
-
type: 'button',
|
|
3353
|
-
'data-on': String(carrier === 'side'),
|
|
3354
|
-
title: t('carrierSideHint'),
|
|
3355
|
-
onClick: () => setCarrier('side'),
|
|
3356
|
-
}, t('carrierSide')))),
|
|
3427
|
+
h(CarrierSeg, { value: carrier, onChange: setCarrier })),
|
|
3357
3428
|
h('div', { className: 'dsa-row', style: { justifyContent: 'flex-end' } },
|
|
3358
3429
|
h('span', { className: 'dsa-muted', style: { marginRight: 'auto' } },
|
|
3359
3430
|
t('askShortcutHint', { key: config.shortcut })),
|
|
@@ -3688,7 +3759,13 @@ window.__ModuleLoader__.load({
|
|
|
3688
3759
|
// P2-4: chips the MODEL proposed for this answer. The block has
|
|
3689
3760
|
// already been stripped from the body; a missing/half/oversized block
|
|
3690
3761
|
// simply yields none. Clicking one submits an ordinary follow-up turn.
|
|
3691
|
-
|
|
3762
|
+
//
|
|
3763
|
+
// The ROW follows `followUpSuggestions`, while the STRIP above stays
|
|
3764
|
+
// unconditional: the toggle owns "do we offer the feature", and a
|
|
3765
|
+
// stray block from a model must never render as raw prose even when
|
|
3766
|
+
// the user never asked for chips.
|
|
3767
|
+
terminalWithAnswer && viewingVersion === null && config.followUpSuggestions === true
|
|
3768
|
+
&& parsedFollowUps.followups.length > 0
|
|
3692
3769
|
? h('div', { className: 'dsa-sug' },
|
|
3693
3770
|
h('span', { className: 'dsa-muted' }, t('suggestionsLabel')),
|
|
3694
3771
|
h(QuickChips, {
|
|
@@ -4682,10 +4759,22 @@ window.__ModuleLoader__.load({
|
|
|
4682
4759
|
})
|
|
4683
4760
|
toast(t('historyDeleted'))
|
|
4684
4761
|
},
|
|
4685
|
-
/**
|
|
4762
|
+
/**
|
|
4763
|
+
* Empty the durable history without touching running cards.
|
|
4764
|
+
*
|
|
4765
|
+
* Removes EVERY generation key, not just the current one:
|
|
4766
|
+
* {@link readPersisted} falls back to v2 → v1 whenever the current
|
|
4767
|
+
* key is absent (that fallback is exactly what makes the
|
|
4768
|
+
* non-destructive migration work), so deleting only v3 let a legacy
|
|
4769
|
+
* payload resurrect the "cleared" history on the next page load —
|
|
4770
|
+
* reproduced live, 2026-10-17. An explicit user clear is destructive
|
|
4771
|
+
* by definition: it is the one action allowed to drop the old keys.
|
|
4772
|
+
*/
|
|
4686
4773
|
clearHistory() {
|
|
4687
4774
|
try {
|
|
4688
|
-
|
|
4775
|
+
for (const key of [PERSIST_KEY, PERSIST_V2_KEY, PERSIST_LEGACY_KEY]) {
|
|
4776
|
+
window.localStorage?.removeItem(key)
|
|
4777
|
+
}
|
|
4689
4778
|
} catch (error) {
|
|
4690
4779
|
console.warn(`[${PLUGIN_ID}] history clear failed:`, error)
|
|
4691
4780
|
}
|
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.
|
|
45
|
+
export const PLUGIN_VERSION = '1.8.2'
|
|
46
46
|
|
|
47
47
|
/** Route prefix of the plugin's own API. */
|
|
48
48
|
export const ROUTE_PREFIX = '/sidecard-ask/api'
|