dsh-recall-plugin 2.4.4 → 2.4.6

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
@@ -2,6 +2,40 @@
2
2
 
3
3
  本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
4
4
 
5
+ ## [2.4.6] - 2026-09-29
6
+
7
+ ### 变更
8
+
9
+ - **兼容声明同步 dsh 0.2.0-rc.1(零破坏核验,无代码改动)**:全局实装 `@deepseek-ai/dsh@0.2.0-rc.1`(npm dist-tag `next`,0.2.0 系列首个候选版本、汇总自 `v0.1.7-rc.2`;`latest` 仍是 0.1.7-rc.2,**装最新版必须显式写版本号**)后,以**全树逐文件哈希 diff** 核验——npm 清理失败留下的 `.dsh-EBhnoWNL` 残留正好是 0.1.7-rc.2 整包副本(298MB),直接用作基线(比对后已删除);528 包中 310 包仅版本号 lockstep 变更,类型面 65 改 / 23 增 / 0 删,运行时 JS 71 改 / 11 增 / 2 删。插件消费面**零破坏**:`dsh-shell`/`dsh-settings`/`dsh-session-query`/`dsh-agent`/`dsh-client-connection`/`dsh-host-webserver`/`dsh-sandbox-policy`/`dsh-client-ui-slots`/`dsh-client-ui-renderer`/`dsh-client-ui-settings-plugins`/`dsh-pwsh-local`/`dsh-cordis-host-runner`(插件加载面)等内容字节级一致,消费面符号计数 11 项 0 差异(`readAttachment`/`updateQueue`/`archiveSession`/`openSession`/`createDrafts`/`releaseDraftAttachments`/`setDraft`/`addAttachments`/`stopActivity`/`increaseTitle`/`atSeq`),`plugins.bundle.config` 槽位定义与渲染调用逐行一致(`settings.plugin.item` 两版均 0 命中)。**唯一接口增量**是 `sessions.fork` 的可选 `onCreated`(向后兼容、插件不传);真增量集中在插件管理页 UI(刷新反馈/安装输入清洗)、`config-editor` 配置继承值计算、会话标题「未命名」语义、消息提交埋点,以及 `dsh-session` 的工具结果修复链重构(`ToolCallRecovery`,插件 `cutSeq` 恒取平衡前缀、不触发补闭合分支)。门禁:`check:dsh` 四层全绿、`test:probe` 52/52、`verify:host` 装配断言全过、`typecheck` 通过、`npm test` 436/436。**活体冒烟(真宿主 + 浏览器实弹,link 模式 / `D:\tmp\recall-h0`)逐项通过**:插件经启动期兼容门禁激活(同 profile 的第三方 `dshmarket@1.65.1` 因 peer 只到 `^0.1.2-alpha.2` 被跳过,反证门禁真实生效);撤回全链(面板清单、文件回退、安全快照 `snap-pre-rollback-*`、lineage、子会话打开、标题继承无递增、文本回填、无残留排队项、原会话归档)与设置页(配置卡完整渲染、`保存`/`恢复默认` 双向回读一致、快照树两级展开与版本家族聚族、`立即 gc`「gc 完成」)均正常,dsh stderr 零插件报错。同步项:**peer 范围必须新增 tuple** `|| >=0.2.0-rc.1 <0.2.1`(npm 的 prerelease 门槛要求同 tuple 且带 prerelease 的比较器,既有 `<0.1.8` 段放行不了 `0.2.0-rc.1`;不扩范围则启动期兼容门禁会整行跳过本 bundle)、`dshReleases` 补 `0.2.0-rc.1: compatible`、`docs/reference/` 镜像按 `dsh-v0.2.0-rc.1` tag 重拉(13 源中仅 `09-architecture.md` 有实质差异:新增一行「失败步骤会记录缺失的工具结果」,净 +121 字节)、`docs/dsh-contract.md` 对应版本与 compat-audit 头部核验段同步、README 双语徽章与兼容范围同步,评估实证见 `docs/upgrade-assessments/dsh-0.2.0-rc.1.md`。观察项(不阻塞):`fork.onCreated` 可更早拿到 childId(插件当前不需要);**自动化任务改由可选 bundle `dsh-experimental-schedule-bundle` 提供、默认关闭**,用户如需须在插件管理页开启。
10
+
11
+ - **兼容声明同步 dsh 0.1.7-rc.2(零破坏核验,无代码改动)**:全局实装 `@deepseek-ai/dsh@0.1.7-rc.2`(npm dist-tag `next`,rc.1 后 346 提交)后,以**聚焦消费面的内容级 diff** 核验——`npm pack` 下载 28 个包(全部 peer + 全部消费点所在包 + 官方 bundle)的 rc.1 版本,与 rc.2 树做全文件 SHA256 比对并逐行核对契约/实现差异;叠加**符号级复核**(13 包 × 23 符号在新旧树计数逐项相等,0 差异)。插件消费面**零破坏**:`api-session-controller` 的客户端契约目录(`lib/types/client/**`:fork/updateQueue/readAttachment/binding/archiveSession)**不在变更清单**,`dsh-shell`/`dsh-settings`/`dsh-sandbox-policy`/`dsh-host-webserver`/`dsh-pwsh-local`/`dsh-client-connection`/`dsh-client-modules`/`dsh-client-ui-renderer`/`dsh-client-ui-session`/`dsh-client-store`/`dsh-host-plugin-inventory`/`dsh-api-gateway` 12 个核心接缝包仅动版本号与 i18n 元数据;真增量集中在本版新能力与官方 UI 层——模型选择/账号(`initializeDefaultModel`/`hasProviderApiKey`/`requireModel`)、工具热更(`dsh-session` 纯新增 `ToolHistoryProjection`、`ui-chat` 新 node kind `developer-message`)、工作区快捷键与归档筛选三态、额度提示链、`app-boot` 的跳过报告时机(判定逻辑未变)、`ui-primitives` 样式重构,均为插件不消费处。门禁:`check:dsh` 四层全绿、`test:probe` 52/52、`verify:host` 装配断言全过、`typecheck` 通过、`npm test` 436/436。同步项:`dshReleases` 补 `0.1.7-rc.2: compatible`(peer 范围 `>=0.1.7-alpha.1 <0.1.8` 天然覆盖,无需新 tuple)、`docs/reference/` 镜像按 `dsh-v0.1.7-rc.2` tag 重拉(13 源中仅 `09-architecture.md` 有实质差异:3 处替换、净 +55 字符,含工具热更新增说明)、`docs/dsh-contract.md` 对应版本与事件集来源标注、compat-audit 头部核验段与 I33 增补、README 双语徽章与兼容范围同步,评估实证见 `docs/upgrade-assessments/dsh-0.1.7-rc.2.md`。观察项(不阻塞):`session-query.read()` 新增显式抛 `SESSION_QUERY_CORRUPT_SESSION`(降级链保持 try/catch);新 node kind `developer-message` 不被插件 key `['user','steering']` 命中;归档筛选三态不改 `archivedSessionIds` 语义(I37 判据继续有效);`initializeDefault(signal)` 签名破坏性变更(插件零消费);升级操作陷阱——Windows 上 npm 替换全局 bin shim 遇占用只留哈希名半成品,需手工改回标准名恢复 `dsh` 命令。
12
+
13
+ ## [2.4.5] - 2026-09-24
14
+
15
+ ### 新增
16
+
17
+ - **插件管理页图标(package.json 顶层 `icon`)**:新增 `assets/icon.svg`——形状沿用撤回按钮的 `UndoIcon`,画布 20×20、单色 `#658EFF`(官方品牌渐变的蓝端)。三处版面调整:① 两条侧翼从箭尖算起缩短 30%(对角分量 4 → 2.8,箭尖与尾部回环保持原位,不用整体缩放以免尾部一起变小);② 尾端水平线向左延长到 x=6.3,与侧翼端点在竖直方向对齐;③ 整组上移 1.1(组 `transform`,路径坐标不动),使几何 bbox 中心落在画布中心 (8, 8),上下留白对称。颜色必须写死:宿主把图标转成 base64 data URL 后以 `<img>` 隔离渲染,`currentColor` 不继承会退化成黑色;该色在浅底(对比 ≈3.1:1)与深底(≈5.5:1)都清晰,无需主题分支。宿主 `dsh-app-boot` 的 `readPluginMeta` 读**包导出的 `package.json` 顶层 `icon` 字段**(判据:相对路径、SVG/PNG/JPEG/WebP、≤256 KiB、realpath 后仍在包目录内、常规文件),读取失败只留 metadata error 且标题/描述照常;渲染位置是**插件管理页**的 bundle 卡片与详情页头部(36px),不是设置对话框的插件 tab(后者只解析 `meta.title/description` 多语言文本)。`files` 白名单加 `assets`,`package-layout` 单测补声明合法性 + 随包发布断言——否则图标文件不进发布包、宿主只报 metadata error,线上静默无图标。
18
+
19
+ - **设置页快照管理:搜索框与树节点整行展开**:搜索框加高 20%(34 → 41px)并在框内左侧内嵌搜索图标(绝对定位 + `pointer-events:none`,输入框以 `padding-left` 让位;高度覆写只 scope 到搜索行,排除配置的快速添加框维持原高);快照树的工作区/会话行整行可点即展开/收起(原先只有 18px 折叠箭头可命中,用户实测反馈难点),行内删除与「切换」按钮各自 `stopPropagation` 不连带折叠,折叠箭头仍是键盘与读屏入口(`aria-expanded` 不变)。
20
+
21
+ ### 变更
22
+
23
+ - **快照树删除按钮图标化并移到名称之后**:文本 chip「删除」改为垃圾桶图标(48 viewBox 路径、`stroke:currentColor` 随主题,20px 命中区、静息 `label-tertiary`、hover 转 error 色 + 危险底色),位置从行尾移到工作区/会话/快照名之后——原先删除 chip 逐行右对齐成一列、与行内容分离易点错行;「切换」留在行尾(导航动作与危险动作分开摆放)。`title` 与 `aria-label` 保留完整语义,图标 `aria-hidden`。
24
+
25
+ - **设置页整体重做,与 dsh 设计语言对齐**:去掉外层「撤回插件」可折叠卡与卡片外框,设置项直接平铺(两轮收敛:先去折叠头、再去外框,用户实测反馈两层包裹皆属冗余)。配置表单改三列网格(标签 | 控件 | 说明):说明文字原先按流跟在宽度不一的单位 tag 之后,起笔位置逐行参差(实测「gc 触发条数」与「gc 触发小时」对不齐),改由列宽统一给定,并以 `padding-top` 与 34px 控件行共用中轴;开关行右缘对齐数字行输入框的右边框(关系由 `--dsh-recall-input-w` / `--dsh-recall-switch-w` 两个令牌承载),开关行的状态 tag 因此与数字行的单位 tag 同起点;字段标签与控件行垂直居中(label 用 34px 行高对齐控件行中轴)。折叠分区(高级:基础排除表 / 排除配置 / 快照管理)标题升为与分组标题同款 14px/700,chevron 收到行尾使标题与分组标题共享同一左缘,首项折叠头加分割线(用绝对定位伪元素绘制——`border-top` 会沿 cardbtn 的 12px 圆角走、两端上翘);表单分组标题加粗放大(14px/700 + label-primary)。交互侧补齐:输入类 hover 中间态(rest `l4` → hover `l3` → focus `brand`)、按钮按下态、卡片与分区展开 `unfold` 入场(`prefers-reduced-motion` 同步豁免)、开关 hover 提亮、空状态居中留白;图标语言统一为 SVG(字符 `▸/▾` 跨平台字形不一,退役)。
26
+
27
+ - **设置项文案精简与标签改名**:九条字段说明改为短句(「填写 0 表示不限制 / 不启用」等语义与输入框右侧单位 tag 全部保留),「撤回后回填输入框 / 撤回后归档原会话」简化为「回填输入框 / 归档原会话」;顺带删掉基础排除表说明里遗留的计划代号。
28
+
29
+ - **撤回按钮图标同步新版形状**:`src/client/recall-node.ts` 的 `UndoIcon` 与 `assets/icon.svg` 取同一套坐标——两翼从箭尖算起缩短 30%、尾端水平线延到与侧翼端点同列(x=6.3)、整组上移 1.1 居中;内联规格刻意不变(16 声明尺寸、`stroke` 1.4、`currentColor` 随按钮文字色,即保持原有大小/粗细/颜色)。两处形状自此需同步改(`UndoIcon` 上方有同步注释)。本版 `lib/client.js` 重建为 120644 字节(含设置页 UI 重做)。
30
+
31
+ ### 修复
32
+
33
+ - **新版 Edge/Chromium 下开关与其它圆形元素被全局超椭圆圆角接管(观感「更方」)**:`dsh-client-ui-theme` 在 `@supports (corner-shape:superellipse(1.5))` 内给 `*,:before,:after` 下发 `corner-shape:var(--dsw-corner-shape)`——支持该属性的内核把**所有**元素渲染成超椭圆(squircle),旧内核(如 IDE 内置浏览器,不认该属性)则维持普通圆角,同一份 UI 因此在不同浏览器观感不同(用户实测反馈)。官方为此在 104 处圆形/胶囊元素上显式写回 `corner-shape:round`(含 `Switch.module.css` 的轨道与拇指、Tag 胶囊、StateDot、spinner),本插件漏写。修复=开关轨道、开关拇指、`ex-chip`、`health-pill`、消息 hover 圆形按钮五处补 `corner-shape:round`;矩形元素沿用全局 squircle(与官方同策略)。注意此类差异**量不出数值**:DevTools 里两侧的 `getBoundingClientRect` / `border-radius` / `padding` 完全一致,差异只在角部形状。
34
+
35
+ - **点击设置项标签会直接触发右侧控件**:`label[for]` 与控件建立关联后,点击标签等同点击控件——开关(`button` 属 labelable 元素)被误切换、数字输入框被抢焦点(用户实测反馈)。修复=标签改 `span`、控件改挂 `aria-labelledby` 指向标签 id(可访问名保留、读屏不弱化),numRow / boolRow / 基础排除表 textarea 三处同改;撤回确认面板的 scope 单选组保持 label 包裹写法(选项组的有意设计)。
36
+
37
+ - **字段标签与自己的输入框错开一行**:label / 控件行 / 说明文字各自为 grid item 时,说明占掉控件行的下一行会把后续 label 的自动放置顶到前一字段的说明行上(实测「gc 触发条数」标签偏上)。修复=三列网格下每字段恰好「标签 / 控件 / 说明」三个 item,label 与控件同排。
38
+
5
39
  ## [2.4.4] - 2026-09-24
6
40
 
7
41
  ### 修复
package/README.en.md CHANGED
@@ -7,13 +7,13 @@
7
7
  ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-blue)
8
8
  ![Build](https://img.shields.io/badge/pure%20JS-green)
9
9
 
10
- [![DSH](https://img.shields.io/badge/DSH-0.1.7--rc.1-blue)](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.7-rc.1)
10
+ [![DSH](https://img.shields.io/badge/DSH-0.2.0--rc.1-blue)](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.0-rc.1)
11
11
  ![DSH](https://img.shields.io/badge/DSH-Desktop-blue)
12
12
  ---
13
13
  Under any message you've sent, click "↶ Recall" — **your workspace files and the conversation history roll back to just before that message was sent**.
14
14
  ---
15
15
 
16
- Files and conversation roll back together: the workspace is first snapshotted into an independent shadow git repository, and a recall uses it to restore files to their state before that message; the conversation is rewound through DSH's official `sessions.fork` to the turn boundary before it, with the original session archived and recoverable. After a recall, the message's text and attachments are placed back into the input box, ready to edit and resend. Snapshots never touch your project's own git and live under `$DSH_HOME` by default. The main boundary: snapshots are created only **when a message is sent** — messages from before the plugin was enabled have no snapshot and show no recall button.
16
+ Files and conversation roll back together: as each message is sent, the workspace is first snapshotted into an independent shadow git repository, and a recall uses it to restore files to their state before that message; the conversation is rewound through DSH's official `sessions.fork` to the turn boundary before it, with the original session archived and recoverable. After a recall, the message's text and attachments are placed back into the input box, ready to edit and resend. Snapshots never touch your project's own git and live under `$DSH_HOME` by default. The main boundary: snapshots are created only **when a message is sent** — messages from before the plugin was enabled have no snapshot and show no recall button.
17
17
 
18
18
  [Changelog](CHANGELOG.md)
19
19
 
@@ -36,7 +36,6 @@ Files and conversation roll back together: the workspace is first snapshotted in
36
36
  | --- | --- |
37
37
  | ![Recall button appears on hover](docs/screenshots/recall-button.png) | ![Confirmation panel · file change list](docs/screenshots/confirm-panel-1.png) |
38
38
 
39
- - After a recall, the message's text and attachments are placed back into the input box for quick editing and resending (can be disabled in the settings card)
40
39
  - Settings · plugin config card (config form / exclusions / snapshot manager, saved changes apply live)
41
40
 
42
41
  ![Settings](docs/screenshots/settings-exclude-2.png)
@@ -46,7 +45,7 @@ Files and conversation roll back together: the workspace is first snapshotted in
46
45
  Abilities with a version in parentheses require that version or later; the rest have no special version requirement.
47
46
 
48
47
  - **Files + conversation, rolled back together**: recalling isn't just about chat history — files the agent modified go back to their original state too; immune to your project's `.gitattributes` conversion, with byte-level fidelity for line endings and binary content (2.1.1+).
49
- - **Just want a fresh conversation? Leave the files alone** (2.3.24+): the confirmation panel offers a recall-scope choice — the default "Roll back files & conversation" behaves as before; picking "Conversation only" keeps your project files byte-for-byte untouched (no safety snapshot either) and rewinds only the conversation, ideal when you dislike the reply but want to keep the file changes.
48
+ - **Just want a fresh conversation? Leave the files alone** (2.3.24+): the confirmation panel offers a recall-scope choice — the default "Roll back files & conversation" is the full rollback; picking "Conversation only" keeps your project files byte-for-byte untouched (no safety snapshot either) and rewinds only the conversation, ideal when you dislike the reply but the file changes are exactly what you want.
50
49
  - **Never touches your project's own git, and keeps it clean**: snapshots live in an independent shadow git repository — branches, staging area, and uncommitted changes are untouched; storage stays under `$DSH_HOME` regardless of the session's sandbox permission (workspace-write / read-only sessions work as usual), falling back to an in-project `.dsh-recall-snapshots` only when home itself is unwritable.
51
50
  - **See the list before you act — and change your mind as often as you like**: recall first shows the list of files that will change (modified / restored / deleted); nothing runs until you confirm. After a recall you can recall again to an even earlier point, and files overwritten during a recall always remain recoverable (up to 500 snapshots per workspace by default).
52
51
  - **Resend right after a recall** (2.3.15+): a recall places the message's text and attachments back into the input box — images and files return as well, so you can edit and send again without picking the attachments over.
@@ -69,7 +68,15 @@ Boundaries accepted by design and edge cases not yet covered — worth checking
69
68
 
70
69
  ## Installation
71
70
 
72
- Prerequisites: git CLI (without it the recall button won't appear and a notice shows at the top of the page — DSH itself keeps running); PowerShell 5.1 / 7 on Windows, bash + git on Linux/macOS; DSH `0.1.2-alpha.1` through `0.1.7-rc.1` (peerDependencies open a window per minor line: `>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8`, consistent with the `dsh.compatibility.dshReleases` declaration; each line is anchored at its first verified version with an exclusive upper bound at the next minor, so later prereleases/releases within a verified line are admitted without touching the peer declaration, while unverified new minor lines remain blocked). **0.1.7-alpha.1 is a breaking release** (`ShellExecutor.run`/`start` became `execute().result()`, and the settings surface became `SettingsForms`, addressed by profile entry id with `.volatile()` marked fields), and the plugin ships **both seams side by side**: the same release keeps working on every 0.1.2–0.1.6 line and on 0.1.7, with unchanged behaviour on older DSH versions. `0.1.1-rc.2` and earlier are no longer declared supported: their client runtime lacks the `sessions`/`workspaces`/`uiWorkspace` services, so the plugin UI silently fails to render.
71
+ Prerequisites:
72
+
73
+ - git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
74
+ - Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
75
+ - DSH version: `0.1.2-alpha.1` through `0.2.0-rc.1`. peerDependencies open a window per minor line (`>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1`, consistent with the `dsh.compatibility.dshReleases` declaration): each line is anchored at its first verified version with an upper bound at the next minor, so later prereleases/releases within a verified line are admitted without touching the peer declaration, while unverified new minor lines remain blocked (the 0.2.0 line needs its own tuple — npm's prerelease gate only admits a prerelease from a comparator with the same tuple, so the existing `<0.1.8` segment cannot admit `0.2.0-rc.1`).
76
+ - **0.1.7-alpha.1 is a breaking release** (`ShellExecutor.run`/`start` became `execute().result()`, and the settings surface became `SettingsForms`, addressed by profile entry id with `.volatile()` marked fields); the plugin ships **both seams side by side** — the same release works on every 0.1.2–0.1.6 line and on 0.1.7, with unchanged behaviour on older DSH versions.
77
+ - `0.1.1-rc.2` and earlier are not supported: their client runtime lacks the `sessions`/`workspaces`/`uiWorkspace` services, so the plugin UI silently fails to render.
78
+
79
+ Install & verify:
73
80
 
74
81
  - Official DSH plugin command: install and auto-mount into the web profile
75
82
  ```powershell
@@ -122,7 +129,7 @@ The plugin manages disk usage automatically — no manual housekeeping needed:
122
129
  - **Periodic gc**: every 50 snapshots or 24 hours since the last gc (whichever comes first, thresholds configurable), `git gc` runs in the background to pack loose objects. This is lossless — every snapshot remains recallable. The throttle token lives in `gc.stamp` inside the shadow repository, so restarting DSH does not reset the cycle.
123
130
  - **Cap & retention**: up to 500 snapshots per workspace by default (oldest pruned beyond the cap); optionally set `retentionDays` for age-based retention. The two triggers work independently and can both be adjusted or disabled in the config card.
124
131
  - **Session-deletion cleanup**: once a session is permanently deleted (its log gone from disk), the next maintenance pass automatically removes all of its snapshots and frees the space. **Archiving is not deletion** — logs of sessions archived by the recall feature itself still exist, so their snapshots are kept and recoverable from the archive. The check is conservative: a session that is merely cold (not in memory) is never cleaned, and when the log's state cannot be verified, it is left alone.
125
- - **User-defined exclusions**: open the "**Settings → Plugin Config → Recall Plugin**" card (collapsed by default; click the header to expand) to edit snapshot exclusions visually — type a path or pattern and press Enter to add it, one-click append for common patterns (`dist/`, `*.log`, `.env`, …), and saved changes take effect on the very next snapshot/recall, no restart needed. Alternatively, edit `dsh-recall-snapshots/exclude.txt` under home directly (i.e. `$DSH_HOME/dsh-recall-snapshots/exclude.txt`, or `~/.dsh/dsh-recall-snapshots/exclude.txt` when unset; UTF-8; one gitignore-style pattern per line; lines starting with `#` are comments) — both paths edit the same configuration, for example:
132
+ - **User-defined exclusions**: open the "**Settings → Plugin Config → Recall Plugin**" card (collapsed by default; click the header to expand) to edit snapshot exclusions visually — type a path or pattern and press Enter to add it, one-click append for common patterns (`dist/`, `*.log`, `.env`, …), and saved changes take effect on the very next snapshot/recall, no restart needed. Alternatively, edit `$DSH_HOME/dsh-recall-snapshots/exclude.txt` directly (or `~/.dsh/dsh-recall-snapshots/exclude.txt` when unset; UTF-8): one gitignore-style pattern per line, lines starting with `#` are comments — both paths edit the same configuration. For example:
126
133
 
127
134
  ```gitignore
128
135
  # keep build artifacts out of snapshots
@@ -165,7 +172,7 @@ Note: all source lives in `src/` (host in `src/host/`, browser side in `src/clie
165
172
 
166
173
  ### Tests
167
174
 
168
- - `npm test`: pure-logic unit tests (vitest, 34 files / 425 cases, no DSH dependency, runs identically in CI and locally) — config parsing, snapshot parsers, rescue orchestration, error classification, script-template same-name-export contract, shell execution dual-channel split and failure grading, settings bridge across both generations, client pure functions, published-package layout, snapshot index persistence, storage caps and retention, etc.;
175
+ - `npm test`: pure-logic unit tests (vitest, 34 files / 436 cases, no DSH dependency, runs identically in CI and locally) — config parsing, snapshot parsers, rescue orchestration, error classification, script-template same-name-export contract, shell execution dual-channel split and failure grading, settings bridge across both generations, client pure functions, published-package layout, snapshot index persistence, storage caps and retention, etc.;
169
176
  - `npm run test:probe`: official-API field probes (requires a local dsh installation; **must run after any dsh upgrade**) — pins fields like `renderMessageImages`/`node`/`cwd`, `atSeq`/`increaseTitle` and the fork-cut anchors of `sessions.fork`, `listSessions` record shape, `AgentRegistry`, the shell execution seam (`execute`/`ShellExecution.result`), the settings surface (`SettingsForms`/profile entry id/volatile gate) plus `loader/volatile-update` and the `Fiber.entry` shape, and goes red on violation;
170
177
  - `npm run verify:host`: assembly gate (requires a local dsh installation) — boots the plugin with a real cordis context in two passes (legacy settings stub and a modern-face-only stub), asserting inject declarations, endpoint registration, Config schema, teardown cleanliness and settings-face dispatch, catching assembly regressions before release;
171
178
  - `npm run build`: full host+client build (mandatory after any `src/` change); `npm run check:dsh`: dsh version inspection (pre-release).
package/README.md CHANGED
@@ -5,12 +5,12 @@
5
5
  [![npm version](https://img.shields.io/npm/v/dsh-recall-plugin?style=flat-square&label=npm&color=3178C6)](https://www.npmjs.com/package/dsh-recall-plugin)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/dsh-recall-plugin?style=flat-square&label=downloads&color=1F883D)](https://www.npmjs.com/package/dsh-recall-plugin)
7
7
  ![License](https://img.shields.io/badge/license-MIT-blue)
8
- [![DSH](https://img.shields.io/badge/DSH-0.1.7--rc.1-blue)](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.7-rc.1)
8
+ [![DSH](https://img.shields.io/badge/DSH-0.2.0--rc.1-blue)](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.0-rc.1)
9
9
  ---
10
10
  在任意一条你发过的消息下方点「↶ 撤回」——**工作区文件和对话历史一起回到那条消息发出之前的状态**。
11
11
  ---
12
12
 
13
- 撤回时文件与对话一起回退:工作区先被快照进一个独立的影子 git 仓库,撤回就用它把文件恢复到那条消息发出之前;对话通过 DSH 官方的 `sessions.fork` 切到该消息之前的 turn 边界,原会话归档保留、随时可找回。撤回后,这条消息的文本与附件会自动放回输入框,改完即可重发。快照全程不触碰项目自身的 git,存储默认落在 `$DSH_HOME` 下。最主要的边界:快照只在**消息发送时**创建,插件启用前的历史消息没有快照、不显示撤回按钮。
13
+ 文件与对话一起回退:每条消息发送时,工作区先被快照进一个独立的影子 git 仓库,撤回时用它把文件恢复到消息发出之前;对话通过 DSH 官方的 `sessions.fork` 切到该消息之前的 turn 边界,原会话归档保留、随时可找回。撤回后,这条消息的文本与附件自动放回输入框,改完即可重发。快照全程不触碰项目自身的 git,存储默认落在 `$DSH_HOME` 下。最主要的边界:快照只在**消息发送时**创建,插件启用前的历史消息没有快照、不显示撤回按钮。
14
14
 
15
15
  [更新日志](CHANGELOG.md)
16
16
 
@@ -31,20 +31,18 @@
31
31
 
32
32
  | 撤回按钮 | 确认面板 · 变更文件清单 |
33
33
  | --- | --- |
34
- | ![悬停出现撤回按钮](docs/screenshots/recall-button.png) | ![确认面板 · 变更文件清单](docs/screenshots/confirm-panel-1.png) |
34
+ | ![悬停出现撤回按钮](docs/screenshots/recall-button.png) | ![确认面板 · 变更文件清单](docs/screenshots/confirm-panel-1.png) |
35
35
 
36
- - 撤回后,这条消息的文本与附件自动放回输入框,改完即可重发(可在设置卡片关闭)
37
36
  - 设置页 · 插件配置卡片(配置表单 / 排除表 / 快照管理,保存即热生效)
38
37
 
39
- ![设置页](docs/screenshots/settings-exclude-2.png)
40
-
38
+ ![设置页](docs/screenshots/settings-exclude-2.png)
41
39
 
42
40
  ## 功能亮点
43
41
 
44
42
  带括号版本号的能力需要该版本或更高;其余无特殊版本要求。
45
43
 
46
44
  - **文件 + 对话,整段回退**:撤回的不只是聊天记录,agent 改过的文件也一并回到原样;不受项目 `.gitattributes` 转换影响,换行与二进制内容字节级保真(2.1.1+)。
47
- - **只想重来对话?文件可以不动**(2.3.24+):确认面板里可二选一撤回范围——默认「回退文件与对话」同现状;选「仅撤回对话」则项目文件一个字节都不动(也不打安全快照),只有对话回退,适合对生成结果不满意、但文件改动恰是想要的场景。
45
+ - **只想重来对话?文件可以不动**(2.3.24+):确认面板里可二选一撤回范围——默认「回退文件与对话」为完整回退;选「仅撤回对话」则项目文件一个字节都不动(也不打安全快照)、只回退对话,适合对生成结果不满意、但文件改动正是想要内容的场景。
48
46
  - **不碰你项目自己的 git,目录保持干净**:快照存在独立的影子 git 仓库里,分支、暂存区、未提交改动统统不受影响;存储默认落在 `$DSH_HOME` 下,与会话的沙箱权限无关(workspace-write / read-only 照常工作),仅当 home 不可写才降级到项目内 `.dsh-recall-snapshots`。
49
47
  - **先看清单再动手,可反复后悔**:撤回前展示将变更的文件清单(修改 / 恢复 / 删除),确认后才执行;撤回后还能再撤到更早,被覆盖的文件一直找得回来(默认每工作区保留 500 条)。
50
48
  - **撤回完就能重发**(2.3.15+):撤回会把这条消息的文本与附件一起放回输入框——图片、文件原样回来,改完直接发送,不必重新挑一遍附件。
@@ -67,24 +65,31 @@
67
65
 
68
66
  ## 安装
69
67
 
70
- 前置:git CLI(未装时撤回按钮不出现,页面顶部会提示安装 git,不影响 DSH 运行);Windows 上 PowerShell 5.1 / 7 均可,Linux/macOS 需 bash + git;DSH `0.1.2-alpha.1` 至 `0.1.7-rc.1`(peerDependencies 为按 minor 版本线开窗 `>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8`,与 `dsh.compatibility.dshReleases` 声明一致;每条线以首个核验版本为下限、开一条上界到下一 minor 的区间,同线内后续 prerelease/正式版自动放行、无需改 peer 声明,未核验的新 minor 线仍被拦截)。**0.1.7-alpha.1 是破坏性版本**(`ShellExecutor` 的 `run`/`start` 换成 `execute().result()`;settings 面换成 `SettingsForms`、按 profile 条目 id 寻址并要求字段标 `.volatile()`),插件已做**双分支共存适配**——同一次发布同时兼容 0.1.2–0.1.6 各线段与 0.1.7,老版本 DSH 上的行为不变。`0.1.1-rc.2` 及更早不再声明支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
68
+ 前置:
71
69
 
70
+ - git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
71
+ - shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
72
+ - DSH 版本:`0.1.2-alpha.1` 至 `0.2.0-rc.1`。peerDependencies 按 minor 版本线开窗(`>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1`,与 `dsh.compatibility.dshReleases` 声明一致):每条线以首个核验版本为下限、上界开到下一 minor,同线内后续 prerelease/正式版自动放行、无需改 peer 声明;未核验的新 minor 线仍被拦截(0.2.0 线必须显式开窗——npm 的 prerelease 门槛要求同 tuple 且带 prerelease 的比较器,既有 `<0.1.8` 段放行不了 `0.2.0-rc.1`)。
73
+ - **0.1.7-alpha.1 是破坏性版本**(`ShellExecutor` 的 `run`/`start` 换成 `execute().result()`;settings 面换成 `SettingsForms`、按 profile 条目 id 寻址并要求字段标 `.volatile()`),插件采用**双分支共存适配**——同一次发布同时兼容 0.1.2–0.1.6 各线与 0.1.7,老版本 DSH 上行为不变。
74
+ - `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
72
75
 
73
- - DSH 官方插件命令:安装并自动挂载进 web profile
74
- ```powershell
76
+ 安装与验证:
77
+
78
+ - DSH 官方插件命令:安装并自动挂载进profile
79
+
80
+ Web UI
81
+ ```
75
82
  dsh plugin --profile web add dsh-recall-plugin
76
83
  ```
84
+ Desktop
85
+ ```
86
+ dsh plugin --profile desktop add dsh-recall-plugin
87
+ ```
77
88
  - 也可从 git 直接安装:
78
89
  ```powershell
79
90
  dsh plugin --profile web add github:limbo947/dsh-recall-plugin
80
91
  ```
81
92
 
82
- - 重启 DSH 进程(按你的启动方式择一)
83
- ```powershell
84
- dsh web # 前台运行
85
- pm2 restart <your-dsh-name> # 由 pm2 托管
86
- ```
87
-
88
93
  - 验证:重启后硬刷新页面(Ctrl+Shift+R),悬停任意一条插件启用后发送的用户消息——复制按钮旁出现「↶」即生效。没有按钮?九成是没重启 DSH 进程,或 git CLI 不在 PATH 里。
89
94
 
90
95
  - 卸载:同时移除依赖与挂载,快照数据保留在 home 下 `dsh-recall-snapshots/`,想彻底清除手动删掉该目录即可。
@@ -92,8 +97,6 @@ pm2 restart <your-dsh-name> # 由 pm2 托管
92
97
  dsh plugin --profile web remove dsh-recall-plugin
93
98
  ```
94
99
 
95
-
96
-
97
100
  ## 使用
98
101
 
99
102
  1. 鼠标悬停任意**插件启用后发送**的用户消息(含 agent 运行中插入的转向指令消息),复制按钮左侧出现「↶ 撤回」。
@@ -125,7 +128,7 @@ dsh plugin --profile web remove dsh-recall-plugin
125
128
  - **定期 gc**:每 50 条快照或距上次 gc 24 小时(先到先触发,阈值可配),后台执行 `git gc` 把 loose 对象压成 pack。无损操作——所有快照照常可回退。节流凭据写在影子仓库内的 `gc.stamp`,重启 DSH 不会重置周期。
126
129
  - **条数上限与保留天数**:每工作区快照默认上限 500 条(超限清最旧);也可按 `retentionDays` 设保留天数,两者独立触发、都可在配置卡片调整或关闭。
127
130
  - **会话删除联动清理**:会话被彻底删除(会话日志从磁盘消失)后,下一次维护会自动删除该会话的全部快照并释放空间。**归档不算删除**——撤回功能自己归档的原会话日志仍在,快照保留、随时可从归档找回。判断很保守:会话只是冷着(不内存里)不会误清;无法核实日志状态时宁可不清。
128
- - **用户自定义排除**:打开「**设置 → 插件配置 → 撤回插件**」卡片(默认收起,点卡片头展开)即可可视化编辑快照排除项——输入路径或模式回车即加、常用模式(`dist/`、`*.log`、`.env` 等)一键追加、保存后下一次快照/回退立即生效,无需重启。也可以直接编辑 home 下 `dsh-recall-snapshots/exclude.txt`(即 `$DSH_HOME/dsh-recall-snapshots/exclude.txt`,未设置时为 `~/.dsh/dsh-recall-snapshots/exclude.txt`;UTF-8),一行一条 gitignore 风格 pattern(`#` 开头为注释),两种方式编辑的是同一份配置,例如:
131
+ - **用户自定义排除**:打开「**设置 → 插件配置 → 撤回插件**」卡片(默认收起,点卡片头展开)即可可视化编辑快照排除项——输入路径或模式回车即加、常用模式(`dist/`、`*.log`、`.env` 等)一键追加、保存后下一次快照/回退立即生效,无需重启。也可以直接编辑 `$DSH_HOME/dsh-recall-snapshots/exclude.txt`(未设置时为 `~/.dsh/dsh-recall-snapshots/exclude.txt`;UTF-8):一行一条 gitignore 风格 pattern(`#` 开头为注释),两种方式编辑的是同一份配置。例如:
129
132
 
130
133
  ```gitignore
131
134
  # 构建产物不进快照
@@ -168,7 +171,7 @@ pnpm install
168
171
 
169
172
  ### 测试
170
173
 
171
- - `npm test`:纯逻辑单测(vitest,34 个文件 425 例,无 DSH 依赖,CI 与本地同跑)——配置解析、快照解析器、救援编排、错误分类、脚本模板同名导出契约、执行通道双分支与失败分级、settings 双代桥接、客户端纯函数、发布包内容布局、快照索引持久化、存储上限与保留天数等;
174
+ - `npm test`:纯逻辑单测(vitest,34 个文件 436 例,无 DSH 依赖,CI 与本地同跑)——配置解析、快照解析器、救援编排、错误分类、脚本模板同名导出契约、执行通道双分支与失败分级、settings 双代桥接、客户端纯函数、发布包内容布局、快照索引持久化、存储上限与保留天数等;
172
175
  - `npm run test:probe`:官方 API 字段探针(依赖本机 dsh 安装;dsh 升级后本地必跑)——钉住 `renderMessageImages`/`node`/`cwd`、`sessions.fork` 的 `atSeq`/`increaseTitle` 与切点锚点、`listSessions` 记录结构、`AgentRegistry`、shell 执行接缝(`execute`/`ShellExecution.result`)、settings 面(`SettingsForms`/profile 条目 id/volatile 门槛)与 `loader/volatile-update`/`Fiber.entry` 形状等字段,违反即红;
173
176
  - `npm run verify:host`:装配门禁(依赖本机 dsh 安装)——用真实 cordis 起插件跑两个 pass(旧面桩 + 只给新面的桩),断言 inject 声明、端点注册、Config schema、卸载清理与 settings 面分派,装配回归发版前即可拦截;
174
177
  - `npm run build`:host+client 全量打包(改任何 `src/` 后必跑);`npm run check:dsh`:dsh 版本巡检(发布前)。
@@ -0,0 +1,24 @@
1
+ <!--
2
+ 插件图标(package.json 顶层 icon 字段引用,宿主 dsh-app-boot 的 readPluginMeta
3
+ 读取后转 base64 data URL,在插件管理页的 bundle 卡片/详情页以 <img> 渲染)。
4
+
5
+ 形状=撤回按钮的 UndoIcon(src/client/recall-node.ts),画布 20×20,四处改动:
6
+ - 颜色写死 #658EFF(官方品牌渐变的蓝端),不能用 stroke="currentColor"——
7
+ 图标以 <img src="data:..."> 隔离渲染,不继承页面 currentColor,会退化成黑色;
8
+ 该色在浅色底(对比 ≈3.1:1)与深色底(≈5.5:1)都清晰,故无需主题分支。
9
+ - 箭头两条侧翼从箭尖算起缩短 30%(对角分量 4 → 2.8,端点 (7.5,3.5)/(7.5,11.5)
10
+ → (6.3,4.7)/(6.3,10.3)):只让箭尖更紧凑,箭尖 (3.5,7.5) 与线宽 1.4 保持原样。
11
+ - 尾端水平线向左延长到 x=6.3(长 1.5 → 3.2):让尾端与侧翼端点在竖直方向对齐
12
+ (上翼端、下翼端同在 x=6.3),版面收束。
13
+ - 整组上移 1.1 做视觉居中:改完的路径几何 bbox 是 x[3.5,12.5] / y[4.7,13.5],
14
+ 中心 (8, 9.1) 而画布中心是 (8, 8)——平移后上下留白对称(各 3.6),左右本就对称。
15
+ 居中用组上的 transform 而不重算路径坐标,路径仍与按钮内联版逐点对应、便于比对。
16
+ 保留 viewBox 0 0 16 16,width/height 声明 20 即命中「图标大小 20×20」;
17
+ 卡片里由官方按 36px 缩放显示。
18
+ -->
19
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="none">
20
+ <g transform="translate(0 -1.1)" stroke="#658EFF" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
21
+ <path d="M6.3 4.7 3.5 7.5l2.8 2.8" />
22
+ <path d="M4.5 7.5h5a3 3 0 0 1 0 6H6.3" />
23
+ </g>
24
+ </svg>
package/lib/client.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // 语义变量集中声明(V1-5):btn-danger 的 hover 亮度是令牌体系外补丁,
6
6
  // 集中成单一来源避免散落硬编码;前景色用官方实证配对 bg-layer-3(error
7
7
  // primary 底色上文本随主题翻转,见 plan-settings-ui V1 核验记录)。
8
- ":root{--dsh-recall-btn-danger-hover:brightness(1.08);--dsh-recall-tree-indent:24px;--dsh-recall-space-1:4px;--dsh-recall-space-2:8px;--dsh-recall-space-3:12px}",
8
+ ":root{--dsh-recall-btn-danger-hover:brightness(1.08);--dsh-recall-tree-indent:24px;--dsh-recall-space-1:4px;--dsh-recall-space-2:8px;--dsh-recall-space-3:12px;--dsh-recall-input-w:120px;--dsh-recall-switch-w:36px}",
9
9
  ".dsh-recall-row{flex-direction:column;align-items:flex-end;gap:6px;display:flex}",
10
10
  ".dsh-recall-stack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}",
11
11
  ".dsh-recall-bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:1.5;white-space:pre-wrap;word-break:break-word}",
@@ -20,7 +20,7 @@
20
20
  ".dsh-recall-filecard-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.4}",
21
21
  ".dsh-recall-actions{align-items:center;gap:10px;height:28px;display:flex}",
22
22
  ".dsh-recall-time{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:1.5}",
23
- ".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
23
+ ".dsh-recall-action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;corner-shape:round;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
24
24
  ".dsh-recall-action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
25
25
  "@media (hover:hover){[data-time-hover-root] .dsh-recall-time{opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover .dsh-recall-time,[data-time-hover-root]:focus-within .dsh-recall-time{opacity:1}}",
26
26
  ".dsh-recall-panel{width:min(480px,100%);box-sizing:border-box;background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;text-align:left;box-shadow:0 8px 28px rgba(0,0,0,.22)}",
@@ -55,6 +55,13 @@
55
55
  ".dsh-recall-btn{border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary)}",
56
56
  ".dsh-recall-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}",
57
57
  ".dsh-recall-btn:disabled,.dsh-recall-ex-chip:disabled{opacity:.4;cursor:default}",
58
+ // 按下确认感:hover 只表示「可点」,active 才表示「已触发」;位移 .5px 不引
59
+ // 发布局抖动,对 primary/danger 实心变体同样生效(它们 hover 无视觉变化,
60
+ // 此前点击全程零反馈)
61
+ ".dsh-recall-btn:active:not(:disabled){transform:translateY(.5px)}",
62
+ // 危险按钮与前一按钮的物理间隔:panel-actions 的 gap:8px 对常规按钮是分组,
63
+ // 对危险按钮不够——误点「全部删除」的代价不可逆,再加 8px 拉开
64
+ ".dsh-recall-btn-gap{margin-left:8px}",
58
65
  // 危险/主按钮的 hover 须显式重申文字与描边——基础 ghost hover 带伪类、优先级
59
66
  // 更高,会把它们的字色刷回 label-primary 并描出亮边
60
67
  ".dsh-recall-btn-danger{background:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}",
@@ -68,14 +75,22 @@
68
75
  // 焦点可见性对齐官方设置页实测写法(同上产物):按钮类 = brand-primary 2px
69
76
  // outline(卡片头 offset -2px 内收,其余 +1px);输入类 = brand-primary 描边
70
77
  // 变色、不套 ring。替换 V2-5 的 border-l3 环方案(当时核验的是旧版写法)。
71
- ".dsh-recall-btn:focus-visible,.dsh-recall-ex-chip:focus-visible,.dsh-recall-tree-toggle:focus-visible,.dsh-recall-cfg-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
78
+ ".dsh-recall-btn:focus-visible,.dsh-recall-ex-chip:focus-visible,.dsh-recall-tree-toggle:focus-visible,.dsh-recall-cfg-switch:focus-visible,.dsh-recall-icon-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
72
79
  ".dsh-recall-cardbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
73
80
  ".dsh-recall-ex-input:focus-visible,.dsh-recall-cfg-input:focus-visible,.dsh-recall-ex-area:focus-visible,.dsh-recall-cfg-area:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}",
81
+ // 输入类 hover 中间态:rest l4 → hover l3 → focus brand 三级递进,与按钮的
82
+ // 描边 hover 逻辑同语言;:not(:focus-visible) 收口是防 hover 伪类(特异性更
83
+ // 高)把 focus 的品牌色描边刷回 l3
84
+ ".dsh-recall-ex-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-ex-area:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-area:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-border-l3)}",
74
85
  ".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:1.5;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
75
86
  ".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
76
87
  ".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
77
88
  ".dsh-recall-ex-card{display:flex;flex-direction:column;gap:8px}",
78
- ".dsh-recall-ex-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:1.5}",
89
+ // 分区折叠头标题:与表单分组标题(cfg-group)同款 14px/700 + label-primary——
90
+ // 折叠头在视觉上是「下一个分组」的入口,同款排版 + 同一条左缘线,扫读时与
91
+ // 上方分组标题连成一体(用户实测反馈:13px/600 secondary 看起来是另一套层级、
92
+ // 且左缘错位)
93
+ ".dsh-recall-section-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:1.5;font-weight:700}",
79
94
  ".dsh-recall-ex-note{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;word-break:break-word}",
80
95
  // 存储路径独立行:等宽字体 + break-all,Windows 长路径整齐折行;12px tertiary
81
96
  // 降为辅助信息层级(与说明正文同色系但更轻),margin-top 收紧与上一行说明的
@@ -87,10 +102,17 @@
87
102
  ".dsh-recall-ex-area{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:8px 10px;font-size:12px;line-height:1.5;font-family:var(--dsw-font-code, ui-monospace, SFMono-Regular, Consolas, monospace);resize:vertical;min-height:120px}",
88
103
  ".dsh-recall-ex-quick{display:flex;flex-wrap:wrap;gap:8px;align-items:center}",
89
104
  ".dsh-recall-ex-input{flex:1;min-width:180px;box-sizing:border-box;height:34px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}",
105
+ // 快照管理搜索行:框高 34px × 1.2 ≈ 41px(搜索是卡片首屏的主入口,加高后
106
+ // 命中区更大);图标绝对定位在框内左侧、pointer-events:none 不挡点击,输入框
107
+ // 用 padding-left 让出图标位(覆写只 scope 到 .dsh-recall-search 内,排除配置的
108
+ // 快速添加框维持原高)
109
+ ".dsh-recall-search{position:relative;display:flex;align-items:center}",
110
+ ".dsh-recall-search .dsh-recall-ex-input{height:41px;padding-left:38px}",
111
+ ".dsh-recall-search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--dsw-alias-label-tertiary);pointer-events:none}",
90
112
  // chip 改官方 badge 丸形配方(bg-module-platform + 999px)——树内小操作与
91
113
  // 排除建议属「标签级动作」,与状态徽章同形态更一致;hover 用 :not(:disabled)
92
114
  // 收口(与按钮同法)
93
- ".dsh-recall-ex-chip{border:none;border-radius:999px;padding:1px 10px;font-size:12px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}",
115
+ ".dsh-recall-ex-chip{border:none;border-radius:999px;corner-shape:round;padding:1px 10px;font-size:12px;line-height:1.5;cursor:pointer;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}",
94
116
  ".dsh-recall-ex-chip:hover:not(:disabled){color:var(--dsw-alias-label-primary)}",
95
117
  // V9 确认按钮 danger chip:红色文字 + 官方失败状态行底,与 .btn-danger 同义
96
118
  // 但保持 chip 尺寸层级(确认条内部不出现大按钮)
@@ -98,13 +120,23 @@
98
120
  // 基础 chip hover 带伪类、优先级更高,会把危险 chip 的红字刷回普通色——
99
121
  // 显式重申保持 hover 下仍是红字(危险语义在悬停确认时最不能丢)
100
122
  ".dsh-recall-ex-chip-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary)}",
123
+ // 树行内图标按钮(删除):20px 方盒保证命中区,静息 tertiary 不抢读、hover
124
+ // 转 error 色 + 危险底色(危险语义在悬停确认时显现,安全兜底是行内确认条)。
125
+ // align-self:center 抵消 tree-label 的 baseline 对齐——SVG 的基线在底边,
126
+ // 随基线摆会浮在文字上方
127
+ ".dsh-recall-icon-btn{flex:none;align-self:center;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:0;border-radius:6px;background:0 0;cursor:pointer;color:var(--dsw-alias-label-tertiary)}",
128
+ ".dsh-recall-icon-btn-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}",
101
129
  ".dsh-recall-ex-status{margin-right:auto;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}",
102
130
  ".dsh-recall-ex-status-error{color:var(--dsw-alias-state-error-primary)}",
131
+ // 空状态(无快照/搜索无匹配):居中 + 24px 上下留白,与官方空列表形态
132
+ // 对齐;此前复用 ex-note 左对齐正文样式,空列表区显得是「缺了一行」而非
133
+ // 一个状态
134
+ ".dsh-recall-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5;text-align:center;padding:24px 0}",
103
135
  ".dsh-recall-ex-status-success{color:var(--dsw-alias-state-success-primary)}",
104
136
  // V6 健康徽章(git 可用性):pill 配色直接复用官方状态行配对——成功用
105
137
  // success-tertiary 底、失败用 interactive-bg-hover-danger 底(error 无
106
138
  // tertiary 令牌,官方失败状态行即用此搭配,主题感知)。
107
- ".dsh-recall-health-pill{display:inline-flex;align-items:center;padding:1px 10px;border-radius:999px;font-size:12px;line-height:1.5}",
139
+ ".dsh-recall-health-pill{display:inline-flex;align-items:center;padding:1px 10px;border-radius:999px;corner-shape:round;font-size:12px;line-height:1.5}",
108
140
  ".dsh-recall-health-pill-ok{background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary)}",
109
141
  ".dsh-recall-health-pill-bad{background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}",
110
142
  // V6 错误区标题:error 色 + 条数,错误不再是灰色小字(fail-loud 可见性)
@@ -113,6 +145,9 @@
113
145
  ".dsh-recall-tree-node{display:flex;flex-direction:column;gap:1px}",
114
146
  ".dsh-recall-tree-row{display:flex;gap:6px;align-items:center;min-width:0;padding:2px 4px;border-radius:6px;cursor:default}",
115
147
  ".dsh-recall-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
148
+ // 可展开的行整行可点(仅快照管理树):只命中 18px 折叠钮太难,cursor 也跟着
149
+ // 变指针,提示「这一行都是命中区」
150
+ ".dsh-recall-tree-row-toggle{cursor:pointer}",
116
151
  // V2 树折叠钮 span→button 的 UA 默认样式重置:button 自带 appearance/背景/边框/
117
152
  // 内边距,与 span 形态差异在此抹平,保证纯键盘可达不引入视觉回归。
118
153
  ".dsh-recall-tree-toggle{appearance:none;background:0 0;border:0;padding:0;font:inherit;flex:none;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:pointer;border-radius:4px;font-size:12px;line-height:1.5;user-select:none}",
@@ -121,7 +156,9 @@
121
156
  ".dsh-recall-tree-label{flex:1;min-width:0;display:flex;gap:8px;align-items:baseline;font-size:12px;line-height:1.5;overflow:hidden}",
122
157
  ".dsh-recall-tree-name{flex:none;font-weight:600;color:var(--dsw-alias-label-secondary)}",
123
158
  ".dsh-recall-tree-title{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
124
- ".dsh-recall-tree-meta{flex:none;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
159
+ // tabular-nums:树 meta 列(「3 会话 / 28 快照」「v2/3」)多行纵向扫描时
160
+ // 数字等宽,个位对齐不跳动
161
+ ".dsh-recall-tree-meta{flex:none;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-variant-numeric:tabular-nums}",
125
162
  // V4 树缩进契约化:总缩进 = --dsh-recall-tree-indent(折叠钮 18px + gap 6px),
126
163
  // children 按 2/3(16px margin)与 1/3(8px padding)拆分,恰好让子行文字
127
164
  // 与父行折叠钮右缘对齐——把「16+8 恰等于 18+6」的巧合变成单一事实源。
@@ -131,54 +168,80 @@
131
168
  // 弃用 max-height 过渡:树展开高度可变(长列表数百 px),固定上限会裁切;
132
169
  // opacity+位移给出同等显隐反馈且零裁切风险。
133
170
  "@keyframes dsh-recall-unfold{from{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}",
134
- "@media (prefers-reduced-motion:reduce){.dsh-recall-tree-children,.dsh-recall-tree-confirm{animation:none}}",
135
- // 卡片对齐同列官方插件设置卡(YyYd_a_card 逐字配方):.5px l4 发丝描边 +
136
- // 16px 圆角 + hover 升 label-dimmed 描边;打开态(layer-2 底 + dimmed 描边)
137
- // 本就与官方一致,保持不变
138
- ".dsh-recall-card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s;display:flex;flex-direction:column;text-align:left}",
139
- ".dsh-recall-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
140
- ".dsh-recall-card.dsh-recall-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
141
- ".dsh-recall-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
142
- ".dsh-recall-card-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
143
- ".dsh-recall-card-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
144
- ".dsh-recall-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:flex;flex-direction:column;gap:12px}",
171
+ "@media (prefers-reduced-motion:reduce){.dsh-recall-tree-children,.dsh-recall-tree-confirm,.dsh-recall-settings-body,.dsh-recall-section-body{animation:none}}",
172
+ // 设置块容器:插件设置项直接平铺在设置页上(用户实测反馈——先去掉外层折叠头、
173
+ // 再去掉卡片外框,两层包裹皆属冗余):无描边、无底色、无圆角,只做纵向排版
174
+ // 与 list-style 归零(li 默认圆点/缩进须清)
175
+ ".dsh-recall-settings{list-style:none;display:flex;flex-direction:column;text-align:left}",
176
+ // settings-body 挂载即播放一次 unfold 入场(opacity + 2px 上移,.16s):随设置页
177
+ // 进入即挂载,入场动画起到「内容就位」的引导作用;内部两个分区(排除/快照管理)
178
+ // 展开时另有 section-body 容器承载
179
+ ".dsh-recall-settings-body{display:flex;flex-direction:column;gap:12px;animation:dsh-recall-unfold .16s ease-out}",
180
+ // SectionToggle 仍复用卡片头按钮形态(可点整行 + 焦点环),仅在设置块内覆写
181
+ // 内边距:14/16px 会让折叠头相对表单标签右缩进 16px(用户实测与分组标题/
182
+ // 表单内容错位);纵向 8px 给标题级排版留呼吸,与 cfg-group 的 8px 节奏同档
183
+ ".dsh-recall-settings-body .dsh-recall-cardbtn{padding:8px 0}",
145
184
  ".dsh-recall-cardbtn{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
146
- // 折叠头(SectionToggle)复用卡片头 cardbtn:14/16px 内边距在卡片 body 内表现为
147
- // 16px 左缩进,与分组标题/表单内容错位(用户实测反馈)——body 内覆写为贴左,
148
- // 卡片头(.dsh-recall-card 直接子级)不受影响。
149
- ".dsh-recall-card-body .dsh-recall-cardbtn{padding:6px 0}",
150
- // 折叠头箭头贴左:tree-toggle 是 18px 定宽居中盒,▸/▾ 字形居中使其墨迹比
151
- // 上方表单标签右偏 ~5px(用户实测折叠头与内容左缘不齐);盒内改左对齐后,
152
- // 三个折叠头(高级/排除配置/快照管理)箭头与标签共享同一左缘。
153
- ".dsh-recall-card-body .dsh-recall-cardbtn .dsh-recall-tree-toggle{justify-content:flex-start}",
154
- // 分区折叠头箭头旋转化:与官方卡片 chevron 同一动效语言(transition .16s、
155
- // 开合=旋转),替代 ▸/▾ 字符瞬切;只挂 SectionToggle,树内折叠钮维持字符
156
- // 切换不受影响
157
- ".dsh-recall-section-chevron{transition:transform .16s}",
158
- ".dsh-recall-section-chevron-open{transform:rotate(90deg)}",
185
+ // 折叠分区列表与上方表单的分界线,挂在首个折叠头上(「高级:基础排除表」):
186
+ // .5px l2 + 上下各 8px,与 cfg-group 的组间线同规格,整列排版只有一种分割语言。
187
+ // 线用绝对定位的伪元素画,不用 border-top——cardbtn 带 12px 圆角,border-top
188
+ // 会沿圆角走、两端上翘(实测);伪元素横贯全宽且保持按钮圆角不被破坏(焦点环
189
+ // 仍沿圆角)。双类选择器压过 `.dsh-recall-settings-body .dsh-recall-cardbtn` 的
190
+ // padding 覆写(同为 0,2,0 时靠后取胜)
191
+ ".dsh-recall-cardbtn.dsh-recall-section-divider{position:relative;margin-top:8px;padding-top:8px}",
192
+ '.dsh-recall-cardbtn.dsh-recall-section-divider::before{content:"";position:absolute;top:0;left:0;right:0;height:.5px;background:var(--dsw-alias-border-l2)}',
193
+ // 展开分区的内容容器(排除配置/快照管理):不再自带描边/底色/圆角——外层
194
+ // 卡片框去掉后,内容区再套一层框等于把「去包裹」又加回来(用户实测反馈);
195
+ // 容器只保留纵向间距与展开入场动画
196
+ ".dsh-recall-section-body{display:flex;flex-direction:column;gap:12px;animation:dsh-recall-unfold .16s ease-out}",
197
+ // 折叠头 chevron 收在行尾(margin-left:auto 顶到右缘,官方卡片头同布局):
198
+ // 向下字形,收起态向下、展开态 rotate(180deg) 朝上,transition .16s 与官方
199
+ // 卡片 chevron 同一动效语言。树内折叠钮(snapshot-manager chevronIcon)同枚
200
+ // SVG、在 18px 盒内左侧、旋转挂内联 style,不受这两条类规则约束
201
+ ".dsh-recall-section-chevron{flex:none;margin-left:auto;color:var(--dsw-alias-label-tertiary);transition:transform .16s}",
202
+ ".dsh-recall-section-chevron-open{transform:rotate(180deg)}",
203
+ // hover 反馈只提亮 chevron:标题保持 label-primary(已是最高层级,再变无可变),
204
+ // 可点提示由尾部箭头承担
205
+ ".dsh-recall-settings-body .dsh-recall-cardbtn:hover .dsh-recall-section-chevron{color:var(--dsw-alias-label-primary)}",
159
206
  // cfg-grid:ConfigForm 全部行共享的单一 grid 容器。此前每行 cfg-row 是独立
160
207
  // grid,「第一列 max-content」各行各算,跨行对齐从未成立(checkbox/输入框
161
208
  // 列参差);cfg-row 改 display:contents 透明化后,label/控件行/hint 直接成为
162
209
  // 本容器的 item,第一列列宽由全表单最长 label 决定——跨行对齐自此成立。
163
- ".dsh-recall-cfg-grid{display:grid;grid-template-columns:max-content minmax(0,1fr);column-gap:8px;row-gap:4px;align-items:start}",
210
+ // 三列:标签 | 控件 | 说明。控件列 max-content 取全表单最宽的一行(输入框 120px
211
+ // + 该行 tag),说明列起点因此由列宽统一决定,不随各行 tag 宽度(条/小时/MB/天)
212
+ // 逐行漂移;column-gap 12px(space-3 档)与卡片内其他 12px 节奏同档
213
+ ".dsh-recall-cfg-grid{display:grid;grid-template-columns:max-content max-content minmax(0,1fr);column-gap:12px;row-gap:4px;align-items:start}",
164
214
  ".dsh-recall-cfg-row{display:contents}",
165
215
  ".dsh-recall-cfg-grid > .dsh-recall-cardbtn{grid-column:1/-1}",
166
216
  // V5 表单分组小标题:语义分组分隔符。组间用整行分隔线 + 加倍留白划界
167
217
  // (「快照行为」「自动治理」是两个配置维度,仅小标题用户实测分不清组界);
168
218
  // 首组(紧跟卡片头)不需要分隔线,三件套清零。
169
- // 组间分隔线随官方发丝线规格(.5px l2,与官方卡体/字段分隔同粗细)
170
- ".dsh-recall-cfg-group{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5;font-weight:600;grid-column:1/-1;margin-top:10px;padding-top:10px;border-top:.5px solid var(--dsw-alias-border-l2)}",
219
+ // 组间分隔线随官方发丝线规格(.5px l2,与官方卡体/字段分隔同粗细);
220
+ // 分隔线上下各 8px、总节奏 16px(4 的倍数),替代原 10px 非标准档。
221
+ // 14px/700 + label-primary:分组标题与字段标签(13px/500)必须一眼可辨,
222
+ // 13px/600 secondary 与字段标签只差字重,用户实测反馈「分不清小标题」
223
+ ".dsh-recall-cfg-group{color:var(--dsw-alias-label-primary);font-size:14px;line-height:1.5;font-weight:700;grid-column:1/-1;margin-top:8px;padding-top:8px;border-top:.5px solid var(--dsw-alias-border-l2)}",
171
224
  ".dsh-recall-cfg-group:first-child{margin-top:0;padding-top:0;border-top:none}",
172
- // V4 后 cfg-line 只装控件(label 已是 cfg-row 直接子元素,占 grid 第一列 max-content)
173
- ".dsh-recall-cfg-line{display:flex;align-items:center;gap:8px;min-width:0;grid-column:2}",
225
+ // 控件行 min-height:34px 与输入框等高——开关行(20px 滑钮)与输入行因此同高,
226
+ // 第一列 label 的垂直居中基准统一,不随控件形态跳动。占第二列(grid-column:2
227
+ // 是显式定位:每字段的 label/控件/说明落在同一行,靠它锚定)
228
+ ".dsh-recall-cfg-line{grid-column:2;display:flex;align-items:center;gap:8px;min-width:0;min-height:34px}",
229
+ // 开关行变体:滑钮右缘对齐数字行输入框的右边框(不是控件列右缘——列右缘由
230
+ // 最宽的单位 tag 决定,`条`/`天` 行的输入框右缘本来就追不上它)。滑钮左推
231
+ // (输入框宽 - 滑钮宽) 后,它占据的正是输入框那一段槽位,其后的状态 tag 也就
232
+ // 与数字行的单位 tag 同起点——两行的「控件 + tag」结构遂逐列对齐
233
+ ".dsh-recall-cfg-line-switch .dsh-recall-cfg-switch{margin-left:calc(var(--dsh-recall-input-w) - var(--dsh-recall-switch-w))}",
174
234
  // 主标签用 label-primary + 官方字段标签字重 500(At1oFq_label),与 12px
175
235
  // tertiary 的说明文字拉开层级;说明固定在控件下方第二行,不混排进主标签行。
176
- ".dsh-recall-cfg-label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;font-weight:500}",
236
+ // line-height:34px 让标签文字在行首 34px 高度内垂直居中——与同排 34px 的
237
+ // 输入框/开关行共用同一中轴(align-self:start 顶到行首,不能靠 grid 居中:
238
+ // 行高由「控件 + 多行 hint」决定,居中的话标签会跟着 hint 高度漂移)
239
+ ".dsh-recall-cfg-label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:34px;align-self:start}",
177
240
  // 数字输入框 = 官方 field input 配方(.5px l4 发丝描边 + layer-3 底 + 34px
178
241
  // 高 + 0/12px 内边距)叠加本插件既有约定:定宽 120px + 右对齐(不定宽时框宽
179
242
  // 随行内 tag 有无伸缩,实测参差);tabular-nums 让同列数字等宽、纵向扫描
180
243
  // 小数位整齐。120px 足够容纳「0.01」~「1000000」区间。
181
- ".dsh-recall-cfg-input{flex:none;width:120px;box-sizing:border-box;height:34px;font:inherit;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px}",
244
+ ".dsh-recall-cfg-input{flex:none;width:var(--dsh-recall-input-w);box-sizing:border-box;height:34px;font:inherit;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px}",
182
245
  // disabled 随官方 input:文字降 tertiary,不动透明度(整框变淡会让「框还在
183
246
  // 只是不可写」的语义变含糊)
184
247
  ".dsh-recall-cfg-input:disabled,.dsh-recall-cfg-area:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}",
@@ -192,14 +255,30 @@
192
255
  // 布尔开关:官方设置表单的布尔字段用 role=switch 滑钮而非原生 checkbox
193
256
  //(vCGm7G_switch 逐字配方:36×20 轨道 border-l3 底、开=brand-primary、
194
257
  // 16px 拇指 label-primary-foreground 滑动 16px、transition .12s)
195
- ".dsh-recall-cfg-switch{box-sizing:border-box;flex:none;width:36px;height:20px;padding:2px;border:0;border-radius:10px;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative}",
258
+ // 跨引擎一致性的加固(用户实测:同一开关在不同浏览器观感不同):button 各
259
+ // 引擎自带 UA appearance(Safari 的 -webkit-appearance:button 会覆写圆角与
260
+ // 内边距、Firefox 另有 ::-moz-focus-inner 内衬)与各自的字体度量(匿名行盒
261
+ // 会参与内容高度计算)——故显式收敛为 inline-flex + font:inherit + line-height:0。
262
+ // corner-shape:round 见拇指处的说明(DSH 主题的全局超椭圆圆角)
263
+ ".dsh-recall-cfg-switch{appearance:none;-webkit-appearance:none;box-sizing:border-box;flex:none;display:inline-flex;align-items:center;width:var(--dsh-recall-switch-w);height:20px;padding:2px;margin:0;border:0;border-radius:10px;corner-shape:round;background:var(--dsw-alias-border-l3);font:inherit;line-height:0;cursor:pointer;position:relative}",
264
+ ".dsh-recall-cfg-switch::-moz-focus-inner{border:0;padding:0}",
196
265
  '.dsh-recall-cfg-switch[aria-checked="true"]{background:var(--dsw-alias-brand-primary)}',
197
266
  ".dsh-recall-cfg-switch:disabled{opacity:.4;cursor:default}",
198
- ".dsh-recall-cfg-switch-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s}",
267
+ // switch hover 微提亮(官方 switch 无 hover 态,此处补上不伤一致性):开态
268
+ // brand 底与关态 border-l3 底同用 brightness,两态反馈强度一致
269
+ ".dsh-recall-cfg-switch:hover:not(:disabled){filter:brightness(1.05)}",
270
+ // corner-shape:round 是必须的:DSH 主题包在支持该属性的浏览器里给「所有元素」
271
+ // 下发 --dsw-corner-shape: superellipse(1.5)(`*,:before,:after{corner-shape:
272
+ // var(--dsw-corner-shape)}`),圆形/胶囊会被渲染成超椭圆(squircle,观感更方)。
273
+ // 官方同样在 104 处圆形/胶囊元素上显式写回 round(含本开关的轨道与拇指);
274
+ // 不写回则新版 Edge 与旧内核(如 IDE 内置浏览器,不认该属性)观感不一致
275
+ ".dsh-recall-cfg-switch-thumb{flex:none;display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s}",
199
276
  '.dsh-recall-cfg-switch[aria-checked="true"] .dsh-recall-cfg-switch-thumb{transform:translateX(16px)}',
200
- // hint 的 padding-bottom 即组内行距的单一事实源(row-gap 只管网格行缝)——
201
- // 行间节奏统一为 8+4=12px,避免各行松紧不一。
202
- ".dsh-recall-cfg-hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;grid-column:2;padding-bottom:8px}",
277
+ // 说明列(第三列):padding-top 8px 把首行文字的视觉中线抬到 34px 控件行的
278
+ // 中线上(12px × 1.5 = 18px 行盒,居中需上移 (34-18)/2 = 8px),与标签的
279
+ // line-height:34px 是同一个中轴的两种写法;padding-bottom 即字段间距的单一
280
+ // 事实源(网格 row-gap 只管行缝),行间节奏维持 8+4=12px
281
+ ".dsh-recall-cfg-hint{grid-column:3;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;min-width:0;padding-top:8px;padding-bottom:8px}",
203
282
  // 基础排除表通栏(baseExcludes textarea/hint):折叠展开后内容顶满卡片宽度,
204
283
  // 消除左侧 label 列竖直死区。必须定义在 480px media 之前——media 内的
205
284
  // grid-column:auto 同特异性、后出现,窄屏下仍能正确回落单列堆叠。
@@ -207,7 +286,10 @@
207
286
  // V4/V8 共用 480px 断点:cfg 表单单列堆叠(V4);exclude quick 行输入框独占
208
287
  // 一行(flex:1 1 100%,basis 100% 强制换行,添加按钮与芯片建议另起一行)、
209
288
  // panel-actions 长状态文案不再挤压按钮(wrap 已在基础规则,此处无需重复)。
210
- "@media (max-width:480px){.dsh-recall-cfg-grid{grid-template-columns:minmax(0,1fr);row-gap:2px}.dsh-recall-cfg-line,.dsh-recall-cfg-hint,.dsh-recall-cfg-area{grid-column:auto}.dsh-recall-ex-quick .dsh-recall-ex-input{flex:1 1 100%}}",
289
+ // 窄屏单列堆叠:三列回落为逐行堆叠(控件行/说明列各自独占一行)。label 的
290
+ // 34px 行高是为「与同排控件共用中轴」而设,堆叠后 label 独占一行、行高与
291
+ // 说明的上边距都要复位,否则标签与控件之间空出一大截
292
+ "@media (max-width:480px){.dsh-recall-cfg-grid{grid-template-columns:minmax(0,1fr);row-gap:2px}.dsh-recall-cfg-line,.dsh-recall-cfg-hint,.dsh-recall-cfg-area{grid-column:auto}.dsh-recall-cfg-label{align-self:auto;line-height:1.5}.dsh-recall-cfg-hint{padding-top:0}.dsh-recall-cfg-line-switch .dsh-recall-cfg-switch{margin-left:0}.dsh-recall-ex-quick .dsh-recall-ex-input{flex:1 1 100%}}",
211
293
  ".dsh-recall-cfg-tag{flex:none;font-size:12px;line-height:1.4;padding:1px 6px;border-radius:6px;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary)}",
212
294
  // 已修改(用户改了配置)用 warn 底色提示「有未保存变更」——修改不是错误,
213
295
  // warn 家族来自 V1 核验的官方配对(tertiary 底 + label 文)。
@@ -483,8 +565,12 @@
483
565
  return React.createElement(
484
566
  "svg",
485
567
  { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
486
- React.createElement("path", { d: "M7.5 3.5 3.5 7.5l4 4" }),
487
- React.createElement("path", { d: "M4.5 7.5h5a3 3 0 0 1 0 6H8" })
568
+ React.createElement(
569
+ "g",
570
+ { transform: "translate(0 -1.1)" },
571
+ React.createElement("path", { d: "M6.3 4.7 3.5 7.5l2.8 2.8" }),
572
+ React.createElement("path", { d: "M4.5 7.5h5a3 3 0 0 1 0 6H6.3" })
573
+ )
488
574
  );
489
575
  }
490
576
  function recallPanel(recall, closePanel, executeRecall, scope, onScopeChange) {
@@ -1076,15 +1162,20 @@
1076
1162
  return React.createElement(
1077
1163
  "div",
1078
1164
  { className: "dsh-recall-cfg-row", key },
1079
- // V4:label 上提为 cfg-row 直接子元素——grid 第一列(max-content)跨行
1080
- // 对齐最长标签,消灭 130px 定宽魔法数与 hint 138px 缩进耦合;
1081
- // htmlFor-id 关联不变(V2)
1082
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-" + key }, label),
1165
+ // V4:标签上提为 cfg-row 直接子元素——grid 第一列(max-content)跨行
1166
+ // 对齐最长标签,消灭 130px 定宽魔法数与 hint 138px 缩进耦合。
1167
+ // 标签用 span + aria-labelledby 而非 label[for]:label 关联会让点击左侧
1168
+ // 标签直接触发右侧控件(输入框被聚焦),用户实测反馈为误触
1169
+ React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-" + key }, label),
1170
+ // 控件行(grid 第二列)与说明文字(第三列)分别为独立 grid item:三列
1171
+ // 各自成像,说明列起点由该列列宽统一决定——说明若跟在控件后面按流排,
1172
+ // 起笔位置会随行内 tag(条/小时/MB/天)的宽度逐行漂移(实测逐行参差)
1083
1173
  React.createElement(
1084
1174
  "div",
1085
1175
  { className: "dsh-recall-cfg-line" },
1086
1176
  React.createElement("input", {
1087
1177
  id: "dsh-recall-cfg-" + key,
1178
+ "aria-labelledby": "dsh-recall-cfg-label-" + key,
1088
1179
  className: "dsh-recall-cfg-input",
1089
1180
  type: "number",
1090
1181
  value: draft ? draft[key] : "",
@@ -1107,16 +1198,20 @@
1107
1198
  return React.createElement(
1108
1199
  "div",
1109
1200
  { className: "dsh-recall-cfg-row", key },
1110
- // label 上提为 cfg-row 直接子元素(与 numRow 同法,V4 跨行对齐契约)
1111
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-" + key }, label),
1201
+ // 标签上提为 cfg-row 直接子元素(与 numRow 同法,V4 跨行对齐契约)
1202
+ React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-" + key }, label),
1203
+ // 与 numRow 同法:控件行(第二列)+ 说明(第三列)各自为 grid item。
1204
+ // -line-switch 修饰类把滑钮右缘推到数字行输入框的右边框上(滑钮只 36px
1205
+ // 宽,左对齐会在右侧留空档、与相邻数字行参差,用户实测反馈)
1112
1206
  React.createElement(
1113
1207
  "div",
1114
- { className: "dsh-recall-cfg-line" },
1208
+ { className: "dsh-recall-cfg-line dsh-recall-cfg-line-switch" },
1115
1209
  React.createElement("button", {
1116
1210
  id: "dsh-recall-cfg-" + key,
1117
1211
  type: "button",
1118
1212
  role: "switch",
1119
1213
  "aria-checked": on,
1214
+ "aria-labelledby": "dsh-recall-cfg-label-" + key,
1120
1215
  className: "dsh-recall-cfg-switch",
1121
1216
  disabled: !writable,
1122
1217
  onClick: () => edit(key, !on)
@@ -1146,25 +1241,25 @@
1146
1241
  "div",
1147
1242
  { className: "dsh-recall-ex-card" },
1148
1243
  // 全部行包进单一 cfg-grid:cfg-row 是 display:contents 透明层(css.ts),
1149
- // label/控件行/hint 直接成为同一 grid 的 item,第一列 max-content 由全表单
1150
- // 最长 label 决定——checkbox 与数字输入框的控件列自此跨行对齐(此前每行
1151
- // 是独立 grid 容器,max-content 各算各的,跨行对齐从未成立,实测参差)。
1244
+ // 每字段留下标签/控件行/说明三个 grid item,分占第一/二/三列——第一列
1245
+ // max-content 由全表单最长标签决定(跨行对齐),第二列同理(说明列起点
1246
+ // 逐行齐平)。此前每行是独立 grid 容器,max-content 各算各的,实测参差。
1152
1247
  React.createElement(
1153
1248
  "div",
1154
1249
  { className: "dsh-recall-cfg-grid" },
1155
1250
  // V5 表单分组:9 字段平铺 → 「快照行为 / 自动治理」两组语义分组小标题,
1156
1251
  // 降低认知负担;「高级:基础排除表」沿用 SectionToggle 折叠,不重复加标题。
1157
1252
  React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u5FEB\u7167\u884C\u4E3A"),
1158
- boolRow("snapshotEnabled", "\u542F\u7528\u5FEB\u7167", "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF08\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE\uFF09\uFF0C\u9002\u5408\u4E34\u65F6\u7981\u7528\u5FEB\u7167\u7684\u573A\u5408"),
1159
- boolRow("refillDraft", "\u64A4\u56DE\u540E\u56DE\u586B\u8F93\u5165\u6846", "\u64A4\u56DE\u6210\u529F\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\u6587\u672C\u56DE\u586B\u5230\u8F93\u5165\u6846\uFF0C\u65B9\u4FBF\u4FEE\u6539\u540E\u91CD\u65B0\u53D1\u9001"),
1160
- boolRow("archiveOriginal", "\u64A4\u56DE\u540E\u5F52\u6863\u539F\u4F1A\u8BDD", "\u64A4\u56DE\u540E\u539F\u4F1A\u8BDD\u4ECE\u5217\u8868\u5F52\u6863\u9690\u85CF\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\uFF1B\u5173\u95ED\u5219\u4FDD\u7559\u5728\u5217\u8868\u4E2D\uFF0C\u65B9\u4FBF\u5BF9\u7167\u56DE\u9000\u524D\u540E\u7684\u4E0A\u4E0B\u6587"),
1253
+ boolRow("snapshotEnabled", "\u542F\u7528\u5FEB\u7167", "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF1B\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE"),
1254
+ boolRow("refillDraft", "\u56DE\u586B\u8F93\u5165\u6846", "\u64A4\u56DE\u540E\u628A\u6D88\u606F\u6587\u672C\u56DE\u586B\u8F93\u5165\u6846\uFF0C\u4FBF\u4E8E\u6539\u5B8C\u91CD\u53D1"),
1255
+ boolRow("archiveOriginal", "\u5F52\u6863\u539F\u4F1A\u8BDD", "\u539F\u4F1A\u8BDD\u5F52\u6863\u9690\u85CF\uFF0C\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF1B\u5173\u95ED\u5219\u7559\u5728\u5217\u8868\u4FBF\u4E8E\u5BF9\u7167"),
1161
1256
  React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u81EA\u52A8\u6CBB\u7406"),
1162
1257
  // 单位后缀统一挂输入框右侧(与状态标签同基线),不再只藏在说明文字里
1163
- numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 git gc", { suffix: "\u6761", min: 1, step: 1 }),
1164
- numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF08\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1\uFF09", { suffix: "\u5C0F\u65F6", min: 1, step: 1 }),
1165
- numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\uFF08\u5355\u4F4D MB\uFF0C\u652F\u6301\u5C0F\u6570\uFF09", { suffix: "MB", min: 0.01, step: 0.5 }),
1166
- numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u9650\u5236", { suffix: "\u6761", min: 0, step: 1 }),
1167
- numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u6309\u5929\u6570\u4FDD\u7559\u5FEB\u7167\uFF0C\u8D85\u671F\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B\u586B 0 \u8868\u793A\u4E0D\u542F\u7528\uFF08\u4E0E\u5FEB\u7167\u603B\u6570\u4E0A\u9650\u5404\u81EA\u751F\u6548\uFF09", { suffix: "\u5929", min: 0, step: 1 }),
1258
+ numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 gc", { suffix: "\u6761", min: 1, step: 1 }),
1259
+ numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF0C\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1", { suffix: "\u5C0F\u65F6", min: 1, step: 1 }),
1260
+ numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0", { suffix: "MB", min: 0.01, step: 0.5 }),
1261
+ numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236", { suffix: "\u6761", min: 0, step: 1 }),
1262
+ numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u8D85\u671F\u5FEB\u7167\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528", { suffix: "\u5929", min: 0, step: 1 }),
1168
1263
  // 操作区在「基础排除表」折叠头之前:按钮服务整个表单(含折叠区之外的字段),
1169
1264
  // 排在折叠头之后会被误读为折叠区内容、折叠时像漏收起(用户实测反馈);
1170
1265
  // 因此也不把按钮藏进折叠分支——否则折叠基础排除表后将无法保存。
@@ -1185,12 +1280,14 @@
1185
1280
  React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: state.busy || !writable, onClick: save }, "\u4FDD\u5B58"),
1186
1281
  !writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u53EA\u8BFB\u8BBE\u7F6E\u6E90") : null
1187
1282
  ),
1188
- React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v) }),
1283
+ // divider:本条是折叠分区列表的首项,上方分界线把「字段表单」与「折叠
1284
+ // 分区」分开(否则保存按钮行紧贴折叠头,读起来像同一组字段)
1285
+ React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v), divider: true }),
1189
1286
  showAdvanced ? React.createElement(
1190
1287
  "div",
1191
1288
  { className: "dsh-recall-cfg-row", key: "baseExcludes" },
1192
- // V4:label 上提(与 numRow 同法);cfg-line 只剩 tags,textarea/hint 各自归第二列
1193
- React.createElement("label", { className: "dsh-recall-cfg-label", htmlFor: "dsh-recall-cfg-baseExcludes" }, "\u57FA\u7840\u6392\u9664\u8868"),
1289
+ // V4:标签上提(与 numRow 同法);cfg-line 只剩 tags,textarea/hint 通栏
1290
+ React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-baseExcludes" }, "\u57FA\u7840\u6392\u9664\u8868"),
1194
1291
  React.createElement(
1195
1292
  "div",
1196
1293
  { className: "dsh-recall-cfg-line" },
@@ -1198,17 +1295,19 @@
1198
1295
  overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1199
1296
  ),
1200
1297
  // textarea/hint 加 cfg-span 通栏:折叠区在共享 grid 内展开时,内容若只占
1201
- // 第二列,左侧长 label 列会成为竖直死区(实测 textarea 被挤窄);通栏后
1202
- // 与 label/tags 行左缘对齐。label 保持原位不动——htmlFor 关联是 a11y 契约。
1298
+ // 第二列,左侧标签列会成为竖直死区(实测 textarea 被挤窄);通栏后与
1299
+ // 标签/tags 行左缘对齐。名称经 aria-labelledby 给出(与 numRow 同法,
1300
+ // 不建立 label 关联)
1203
1301
  React.createElement("textarea", {
1204
1302
  id: "dsh-recall-cfg-baseExcludes",
1303
+ "aria-labelledby": "dsh-recall-cfg-label-baseExcludes",
1205
1304
  className: "dsh-recall-cfg-area dsh-recall-cfg-span",
1206
1305
  rows: 4,
1207
1306
  value: draft.baseExcludes,
1208
1307
  disabled: !writable,
1209
1308
  onChange: (e) => edit("baseExcludes", e.target.value)
1210
1309
  }),
1211
- React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, "\u5185\u7F6E\u89C4\u5219\uFF0C\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u5171\u4EAB\uFF0C\u5EFA\u8BAE\u4FDD\u6301\u9ED8\u8BA4\uFF1Bgitignore \u8BED\u6CD5\u6BCF\u884C\u4E00\u6761\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u91CC\u7684 exclude.txt\uFF08S3-2 \u6298\u53E0\uFF09")
1310
+ React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, "\u5404\u5DE5\u4F5C\u533A\u5171\u4EAB\u7684\u5185\u7F6E\u89C4\u5219\uFF1Bgitignore \u8BED\u6CD5\uFF0C\u6BCF\u884C\u4E00\u6761\uFF1B\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u7684 exclude.txt")
1212
1311
  ) : null
1213
1312
  )
1214
1313
  );
@@ -1271,7 +1370,7 @@
1271
1370
  file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\u3002"
1272
1371
  ),
1273
1372
  React.createElement("div", { className: "dsh-recall-ex-path" }, "\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path),
1274
- React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u6392\u9664\u7684\u6587\u4EF6\u4E0E\u76EE\u5F55\u4E0D\u8FDB\u5165\u5FEB\u7167\uFF0C\u4E5F\u4E0D\u4F1A\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
1373
+ React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u9879\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
1275
1374
  React.createElement("textarea", {
1276
1375
  className: "dsh-recall-ex-area",
1277
1376
  "aria-label": "\u5FEB\u7167\u6392\u9664\u6A21\u5F0F\u5217\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF09",
@@ -1404,6 +1503,42 @@
1404
1503
  }
1405
1504
  function buildSnapshotManager(React, util, sessionsSvc, workspacesSvc, uiWorkspaceSvc) {
1406
1505
  const { api, clockText: clockText2, sizeText: sizeText2, buildTree: buildTree2 } = util;
1506
+ function DeleteButton(props) {
1507
+ return React.createElement("button", {
1508
+ type: "button",
1509
+ className: "dsh-recall-icon-btn dsh-recall-icon-btn-danger",
1510
+ title: props.title,
1511
+ "aria-label": props.title,
1512
+ // 阻止冒泡:行本身可点(展开/收起),删除按钮不该顺带折叠该行
1513
+ onClick: (e) => {
1514
+ e.stopPropagation();
1515
+ props.onClick();
1516
+ }
1517
+ }, React.createElement(
1518
+ "svg",
1519
+ { width: 14, height: 14, viewBox: "0 0 48 48", fill: "none", "aria-hidden": true },
1520
+ React.createElement("path", { d: "M9 10V44H39V10H9Z", stroke: "currentColor", strokeWidth: 4, strokeLinejoin: "round" }),
1521
+ React.createElement("path", { d: "M20 20V33", stroke: "currentColor", strokeWidth: 4, strokeLinecap: "round", strokeLinejoin: "round" }),
1522
+ React.createElement("path", { d: "M28 20V33", stroke: "currentColor", strokeWidth: 4, strokeLinecap: "round", strokeLinejoin: "round" }),
1523
+ React.createElement("path", { d: "M4 10H44", stroke: "currentColor", strokeWidth: 4, strokeLinecap: "round", strokeLinejoin: "round" }),
1524
+ React.createElement("path", { d: "M16 10L19.289 4H28.7771L32 10H16Z", stroke: "currentColor", strokeWidth: 4, strokeLinejoin: "round" })
1525
+ ));
1526
+ }
1527
+ function chevronIcon(open) {
1528
+ return React.createElement("svg", {
1529
+ width: 12,
1530
+ height: 12,
1531
+ viewBox: "0 0 16 16",
1532
+ style: { transition: "transform .16s", transform: open ? "none" : "rotate(-90deg)" }
1533
+ }, React.createElement("path", {
1534
+ d: "M4 6l4 4 4-4",
1535
+ fill: "none",
1536
+ stroke: "currentColor",
1537
+ strokeWidth: 1.5,
1538
+ strokeLinecap: "round",
1539
+ strokeLinejoin: "round"
1540
+ }));
1541
+ }
1407
1542
  function ManageCard() {
1408
1543
  const [items, setItems] = React.useState(null);
1409
1544
  const [usage, setUsage] = React.useState(null);
@@ -1598,14 +1733,14 @@
1598
1733
  React.createElement(
1599
1734
  "span",
1600
1735
  { className: "dsh-recall-tree-label" },
1601
- React.createElement("span", { className: "dsh-recall-tree-title" }, label)
1602
- ),
1603
- React.createElement("button", {
1604
- type: "button",
1605
- className: "dsh-recall-ex-chip",
1606
- title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
1607
- onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1608
- }, "\u5220\u9664")
1736
+ React.createElement("span", { className: "dsh-recall-tree-title" }, label),
1737
+ // 删除紧贴本条消息摘要(而非行尾):按行扫读时动作落在「看的那一行」,
1738
+ // 不必横跳到右侧同列按钮再回读行号
1739
+ React.createElement(DeleteButton, {
1740
+ title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
1741
+ onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1742
+ })
1743
+ )
1609
1744
  ),
1610
1745
  renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1611
1746
  );
@@ -1619,9 +1754,12 @@
1619
1754
  return React.createElement(
1620
1755
  "div",
1621
1756
  { className: "dsh-recall-tree-node", key },
1757
+ // 整行可点即展开/收起(用户实测反馈:只能精确命中箭头才展开);行内的
1758
+ // 图标/芯片按钮各自 stopPropagation,避免连带折叠。折叠钮仍是键盘与
1759
+ // 读屏的可达入口(aria-expanded 播报),行点击只是给鼠标补命中区
1622
1760
  React.createElement(
1623
1761
  "div",
1624
- { className: "dsh-recall-tree-row" },
1762
+ { className: "dsh-recall-tree-row dsh-recall-tree-row-toggle", onClick: () => toggle(key) },
1625
1763
  // V2:折叠钮 span→button——Tab/Enter/Space 可达,读屏经 aria-expanded
1626
1764
  // 与 aria-label 播报展开语义与节点名;CSS 已做 button 重置防视觉回归。
1627
1765
  React.createElement("button", {
@@ -1629,33 +1767,37 @@
1629
1767
  className: "dsh-recall-tree-toggle",
1630
1768
  "aria-expanded": open,
1631
1769
  "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + label,
1632
- onClick: () => toggle(key)
1633
- }, open ? "\u25BE" : "\u25B8"),
1770
+ onClick: (e) => {
1771
+ e.stopPropagation();
1772
+ toggle(key);
1773
+ }
1774
+ }, chevronIcon(open)),
1634
1775
  React.createElement(
1635
1776
  "span",
1636
1777
  { className: "dsh-recall-tree-label", title: s.sessionId || "" },
1637
1778
  React.createElement("span", { className: "dsh-recall-tree-title" }, label),
1638
1779
  version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: "\u7248\u672C\u5BB6\u65CF\uFF1A" + version.family.join(" \u2192 ") }, "v" + version.index + "/" + version.family.length) : null,
1639
- React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761")
1780
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761"),
1781
+ s.sessionId ? React.createElement(DeleteButton, {
1782
+ title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
1783
+ onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1784
+ }) : null
1640
1785
  ),
1786
+ // 「切换」留在行尾单占右侧:它是导航动作(离开当前视图),与行内
1787
+ // 删除的危险动作分开摆放,避免两类语义在同一个位置上误点
1641
1788
  switchable ? React.createElement("button", {
1642
1789
  type: "button",
1643
1790
  className: "dsh-recall-ex-chip",
1644
1791
  title: "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
1645
- onClick: () => {
1792
+ onClick: (ev) => {
1793
+ ev.stopPropagation();
1646
1794
  try {
1647
1795
  if (uiWorkspaceSvc && typeof uiWorkspaceSvc.openSession === "function") uiWorkspaceSvc.openSession(s.sessionId);
1648
1796
  else if (typeof sessionsSvc.open === "function") sessionsSvc.open(s.sessionId);
1649
1797
  } catch (e) {
1650
1798
  }
1651
1799
  }
1652
- }, "\u5207\u6362") : null,
1653
- s.sessionId ? React.createElement("button", {
1654
- type: "button",
1655
- className: "dsh-recall-ex-chip",
1656
- title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
1657
- onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1658
- }, "\u5220\u9664") : null
1800
+ }, "\u5207\u6362") : null
1659
1801
  ),
1660
1802
  open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...s.items.map(renderLeaf)) : null,
1661
1803
  s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
@@ -1669,29 +1811,31 @@
1669
1811
  return React.createElement(
1670
1812
  "div",
1671
1813
  { className: "dsh-recall-tree-node", key },
1814
+ // 整行可点即展开/收起,语义与 renderSession 一致
1672
1815
  React.createElement(
1673
1816
  "div",
1674
- { className: "dsh-recall-tree-row" },
1817
+ { className: "dsh-recall-tree-row dsh-recall-tree-row-toggle", onClick: () => toggle(key) },
1675
1818
  // V2:工作区折叠钮同 renderSession——span→button 键盘化,aria 语义并列播报
1676
1819
  React.createElement("button", {
1677
1820
  type: "button",
1678
1821
  className: "dsh-recall-tree-toggle",
1679
1822
  "aria-expanded": open,
1680
1823
  "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + ws.name,
1681
- onClick: () => toggle(key)
1682
- }, open ? "\u25BE" : "\u25B8"),
1824
+ onClick: (e) => {
1825
+ e.stopPropagation();
1826
+ toggle(key);
1827
+ }
1828
+ }, chevronIcon(open)),
1683
1829
  React.createElement(
1684
1830
  "span",
1685
1831
  { className: "dsh-recall-tree-label", title: ws.root || "" },
1686
1832
  React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
1687
- React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167")
1688
- ),
1689
- ws.root ? React.createElement("button", {
1690
- type: "button",
1691
- className: "dsh-recall-ex-chip",
1692
- title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
1693
- onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1694
- }, "\u5220\u9664") : null
1833
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167"),
1834
+ ws.root ? React.createElement(DeleteButton, {
1835
+ title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
1836
+ onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
1837
+ }) : null
1838
+ )
1695
1839
  ),
1696
1840
  open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...ws.sessions.map(renderSession)) : null,
1697
1841
  ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
@@ -1729,14 +1873,38 @@
1729
1873
  }, health.gitAvailable ? "git \u53EF\u7528" : "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09"),
1730
1874
  " \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome " + health.homeStores + " \u4E2A\u5DE5\u4F5C\u533A" + (health.fallbackStores ? "\uFF0C\u964D\u7EA7 " + health.fallbackStores + " \u4E2A" : "")
1731
1875
  ) : null,
1732
- React.createElement("input", {
1733
- className: "dsh-recall-ex-input",
1734
- placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
1735
- "aria-label": "\u641C\u7D22\u5FEB\u7167",
1736
- value: query,
1737
- spellCheck: false,
1738
- onChange: (e) => setQuery(e.target.value)
1739
- }),
1876
+ // 搜索行:图标绝对定位在框内左侧(pointer-events:none 不挡点击),输入框
1877
+ // 靠 padding-left 让出图标位;高度由 CSS 的 .dsh-recall-search 覆写加高 20%
1878
+ React.createElement(
1879
+ "div",
1880
+ { className: "dsh-recall-search" },
1881
+ React.createElement(
1882
+ "svg",
1883
+ {
1884
+ className: "dsh-recall-search-icon",
1885
+ width: 16,
1886
+ height: 16,
1887
+ viewBox: "0 0 48 48",
1888
+ fill: "none",
1889
+ stroke: "currentColor",
1890
+ strokeWidth: 4,
1891
+ strokeLinecap: "round",
1892
+ strokeLinejoin: "round",
1893
+ "aria-hidden": true
1894
+ },
1895
+ React.createElement("path", { d: "M21 38C30.3888 38 38 30.3888 38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38Z" }),
1896
+ React.createElement("path", { d: "M26.657 14.3431C25.2093 12.8954 23.2093 12 21.0001 12C18.791 12 16.791 12.8954 15.3433 14.3431" }),
1897
+ React.createElement("path", { d: "M33.2216 33.2217L41.7069 41.707" })
1898
+ ),
1899
+ React.createElement("input", {
1900
+ className: "dsh-recall-ex-input",
1901
+ placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
1902
+ "aria-label": "\u641C\u7D22\u5FEB\u7167",
1903
+ value: query,
1904
+ spellCheck: false,
1905
+ onChange: (e) => setQuery(e.target.value)
1906
+ })
1907
+ ),
1740
1908
  // V3 加载骨架:items===null 表示首查未回——用 5 条 pulse 灰条占位替代
1741
1909
  // 打开快照管理时的一段空白;aria-hidden 纯装饰不打扰读屏
1742
1910
  items === null ? React.createElement(
@@ -1745,8 +1913,8 @@
1745
1913
  ...[1, 2, 3, 4, 5].map((n) => React.createElement("div", { key: "sk-" + n, className: "dsh-recall-tree-skeleton-row" }))
1746
1914
  ) : null,
1747
1915
  treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
1748
- items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-ex-note", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
1749
- q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-ex-note", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
1916
+ items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-empty", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
1917
+ q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-empty", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
1750
1918
  renderDeleteAllConfirm(),
1751
1919
  // V6:错误区从卡片最底上移到操作区上方(fail-loud 可见性);标题
1752
1920
  // error 色带条数徽章,时间戳格式维持原样
@@ -1781,11 +1949,12 @@
1781
1949
  onClick: () => run("gc", {}, "gc \u5B8C\u6210")
1782
1950
  }, "\u7ACB\u5373 gc"),
1783
1951
  // V5:全部删除固定为操作区最后一个按钮(排在立即 gc 之后)——即使
1784
- // 「加载更多」出现/消失也不漂移;danger 与普通按钮间由 panel-actions
1785
- // 统一 gap:8px 形成视觉分组
1952
+ // 「加载更多」出现/消失也不漂移;danger 与普通按钮间在 panel-actions
1953
+ // 统一 gap:8px 之上再加 btn-gap 的 8px 物理间隔,危险按钮与常规按钮
1954
+ // 拉开距离防误点
1786
1955
  React.createElement("button", {
1787
1956
  type: "button",
1788
- className: "dsh-recall-btn dsh-recall-btn-danger",
1957
+ className: "dsh-recall-btn dsh-recall-btn-danger dsh-recall-btn-gap",
1789
1958
  disabled: state.busy,
1790
1959
  title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
1791
1960
  onClick: () => setConfirming({ kind: "all" })
@@ -1803,60 +1972,55 @@
1803
1972
  "button",
1804
1973
  {
1805
1974
  type: "button",
1806
- className: "dsh-recall-cardbtn",
1975
+ className: "dsh-recall-cardbtn" + (props.divider ? " dsh-recall-section-divider" : ""),
1807
1976
  "aria-expanded": props.open,
1808
1977
  onClick: props.onToggle
1809
1978
  },
1810
- // 箭头恒为 ▸、开合靠旋转(CSS transition .16s)——与官方卡片 chevron 同一
1811
- // 动效语言;读屏状态仍由 aria-expanded 播报,字形不再承担状态语义
1812
- React.createElement("span", { className: "dsh-recall-tree-toggle dsh-recall-section-chevron" + (props.open ? " dsh-recall-section-chevron-open" : "") }, "\u25B8"),
1813
- React.createElement("span", { className: "dsh-recall-ex-title" }, props.title),
1814
- props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null
1979
+ // 标题在前、chevron 收尾(官方卡片头同为「左标题 + 右 chevron」):chevron
1980
+ // 在前的布局会把标题推到 18px 盒 + 12px gap 之后,实测与上方分组标题
1981
+ // (cfg-group)左缘差出约 30px、视觉上不齐;标题独占行首后与分组标题、
1982
+ // 表单标签共享同一条左缘线
1983
+ React.createElement("span", { className: "dsh-recall-section-title" }, props.title),
1984
+ props.meta ? React.createElement("span", { className: "dsh-recall-tree-meta" }, props.meta) : null,
1985
+ // 向下字形 14px chevron:收起不旋转、展开 rotate(180deg) 朝上(transition
1986
+ // .16s);读屏状态由 aria-expanded 播报,字形只承担可点提示
1987
+ React.createElement("svg", {
1988
+ width: 14,
1989
+ height: 14,
1990
+ viewBox: "0 0 16 16",
1991
+ className: "dsh-recall-section-chevron" + (props.open ? " dsh-recall-section-chevron-open" : ""),
1992
+ fill: "none",
1993
+ stroke: "currentColor",
1994
+ strokeWidth: 1.5,
1995
+ strokeLinecap: "round",
1996
+ strokeLinejoin: "round"
1997
+ }, React.createElement("path", { d: "M4 6l4 4 4-4" }))
1815
1998
  );
1816
1999
  }
1817
2000
  const { ConfigForm } = buildConfigForm(React, util, SectionToggle);
1818
2001
  const { ExcludeFilesSection } = buildExcludeCards(React, util);
1819
2002
  const { ManageCard } = buildSnapshotManager(React, util, sessionsSvc, workspacesSvc, uiWorkspaceSvc);
1820
2003
  function RecallSettingsCard() {
1821
- const [open, setOpen] = React.useState(false);
1822
2004
  const [sections, setSections] = React.useState({ exclude: false, manage: false });
1823
2005
  function toggle(key) {
1824
2006
  setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
1825
2007
  }
1826
2008
  return React.createElement(
1827
2009
  "li",
1828
- { className: "dsh-recall-card" + (open ? " dsh-recall-card-open" : "") },
2010
+ { className: "dsh-recall-settings" },
1829
2011
  React.createElement(
1830
- "button",
1831
- {
1832
- type: "button",
1833
- className: "dsh-recall-cardbtn",
1834
- "aria-expanded": open,
1835
- "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + ": \u64A4\u56DE\u63D2\u4EF6",
1836
- onClick: () => setOpen((v) => !v)
1837
- },
1838
- React.createElement(
1839
- "span",
1840
- { className: "dsh-recall-card-head" },
1841
- React.createElement("span", { className: "dsh-recall-card-name" }, "\u64A4\u56DE\u63D2\u4EF6"),
1842
- React.createElement("span", { className: "dsh-recall-card-desc" }, "\u6D88\u606F\u64A4\u56DE\uFF08\u6587\u4EF6\u5FEB\u7167 + \u5BF9\u8BDD\u56DE\u9000\uFF09\u7684\u9608\u503C\u4E0E\u6CBB\u7406")
1843
- ),
1844
- React.createElement("svg", {
1845
- width: 14,
1846
- height: 14,
1847
- viewBox: "0 0 16 16",
1848
- style: { color: "var(--dsw-alias-label-tertiary)", flex: "none", transition: "transform .16s", transform: open ? "rotate(180deg)" : "none" }
1849
- }, React.createElement("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }))
1850
- ),
1851
- open ? React.createElement(
1852
2012
  "div",
1853
- { className: "dsh-recall-card-body" },
2013
+ { className: "dsh-recall-settings-body" },
1854
2014
  React.createElement(ConfigForm),
1855
2015
  React.createElement(SectionToggle, { title: "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09", open: sections.exclude, onToggle: () => toggle("exclude") }),
1856
- sections.exclude ? React.createElement(ExcludeFilesSection) : null,
2016
+ // section-body 只做纵向间距与展开入场动画,不带描边/底色:外层卡片框
2017
+ // 去掉后内容区再套一层框等于把「去包裹」加回来(用户实测反馈)。「高级:
2018
+ // 基础排除表」不包——它在 cfg-grid 内本就走通栏,且 label 列对齐依赖
2019
+ // grid item 身份,包容器会破坏跨行对齐。
2020
+ sections.exclude ? React.createElement("div", { className: "dsh-recall-section-body" }, React.createElement(ExcludeFilesSection)) : null,
1857
2021
  React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
1858
- sections.manage ? React.createElement(ManageCard) : null
1859
- ) : null
2022
+ sections.manage ? React.createElement("div", { className: "dsh-recall-section-body" }, React.createElement(ManageCard)) : null
2023
+ )
1860
2024
  );
1861
2025
  }
1862
2026
  return { RecallSettingsCard };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "dsh-recall-plugin",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "在 DSH 中撤回自己发过的消息:工作区文件与对话历史一起回到那条消息发出之前,消息的文本与附件自动放回输入框,改完即可重发。文件走独立影子 git 快照(不碰项目自身的 git),对话走官方 sessions.fork,原会话归档可找回。",
5
+ "icon": "assets/icon.svg",
5
6
  "type": "module",
6
7
  "repository": {
7
8
  "type": "git",
@@ -18,6 +19,7 @@
18
19
  },
19
20
  "files": [
20
21
  "lib",
22
+ "assets",
21
23
  "cordis.patch.yml",
22
24
  "README.md",
23
25
  "README.en.md",
@@ -61,18 +63,20 @@
61
63
  "0.1.6-alpha.2": "compatible",
62
64
  "0.1.7-alpha.1": "compatible",
63
65
  "0.1.7-alpha.2": "compatible",
64
- "0.1.7-rc.1": "compatible"
66
+ "0.1.7-rc.1": "compatible",
67
+ "0.1.7-rc.2": "compatible",
68
+ "0.2.0-rc.1": "compatible"
65
69
  }
66
70
  }
67
71
  },
68
72
  "peerDependencies": {
69
73
  "@deepseek-ai/cordis": "^4.0.1",
70
- "@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
71
- "@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
72
- "@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
73
- "@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
74
- "@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
75
- "@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8",
74
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
75
+ "@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
76
+ "@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
77
+ "@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
78
+ "@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
79
+ "@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
76
80
  "@deepseek-ai/schemastery": "^3.18.1",
77
81
  "react": "^18.2.0"
78
82
  },