# 设置项与功能

设置项的行为、默认值、字段名与边界情况。设计取值见 [design-language.md](design-language.md)，实现细节见 [engineering-notes.md](engineering-notes.md)。

设置入口：**设置 › 终末地主题设置**。全部文案跟随 DSH 的语言设置（中/英）即时切换，无需刷新。

## 持久化方式（不再用 localStorage）

历史内存风格：这些开关最初存浏览器 `localStorage`。由于浏览器存储按「协议 + 主机 + 端口」的 origin 隔离，而 DSH Desktop 每次启动都在 127.0.0.1 绑定一个**随机临时端口**，端口一变 origin 就变，上次保存的设置永远读不到，表现为「重启后恢复默认」。已改用 DSH 官方的用户设置命名空间：

- **DSH 0.2.0-rc.2 / 0.1.7-rc.1 及以后（当前）**：**Host 端（index.js）** 导出 schemastery `Config`，34 个字段全部 `.volatile()`——这两代都只把 volatile 字段投影成可编辑表单；命名空间就是本插件在 `cordis.patch.yml` 里那一行的 profile entry id（`theme-endfield`）。**浏览器端（client.js）** 用 `ctx.configForms.get('theme-endfield')` 读/写/订阅，值由 DSH 的设置服务写进 profile patch `<profile>/cordis.patch.yml`。另外 Host 会调用 `ctx.settings.configure({ auto: false }, ctx.fiber)` 告诉 DSH 本插件自带设置页，不要再自动生成一份。0.2 的这套接缝与 0.1.7 完全相同，本插件侧不需要改动；0.2 真正改掉的是三个**应用侧**细节（回合状态标签的类名与着色机制、右侧栏列名、插件卡片文案来源），见 [engineering-notes.md § DSH 0.2.0-rc.2](engineering-notes.md#dsh-020-rc2-上失效的三处应用侧钩子v116-已跟进)。
- **≤ 0.1.5-rc.2（旧宿主，仍兼容）**：Host 通过 `ctx.settings.register('dsh-theme-endfield', schema)` 声明命名空间，由 `@deepseek-ai/dsh-settings-file` 落到 `<dshHome>/settings.yaml`；浏览器端用 `ctx.settingsScope` 的 `bind({ namespace, decode })` 读写。client 在找不到 `configForms` 时自动回落到这条路径。

两代的落盘位置都由 DSH 决定（`$DSH_HOME` 或 `~/.dsh/...`），与浏览器 origin/端口无关，因此在 **dsh web（浏览器、固定/默认端口）** 和 **DSH Desktop（随机临时端口）** 两种运行方式下设置都能正确持久化——它们跑的都是 127.0.0.1 loopback 页面，DSH 会把连接解析为 `host` 持久化模式。

> **升级提示**：0.1.7-rc.1 废弃了 `settings.yaml`（启动时改名为 `settings.yaml.imported`），且其内置迁移只认少数几个段落名，旧的 `dsh-theme-endfield` 段落不在其中——**升级后请在「设置 › 终末地主题设置」里重设一次**。旧值仍可在 `settings.yaml.imported` 中手工对照。详见 [engineering-notes.md § DSH 0.1.7-rc.1 换掉了整套 settings API](engineering-notes.md#dsh-017-rc1-换掉了整套-settings-api-v110-已跟进)。

下表「字段」（缩写）就是存储里的字段名：0.1.7-rc.1 起是 profile patch 里 Config 的 volatile 路径，旧世代是 `settings.yaml` 段落里的键，两者同名（都等价于旧 localStorage 键名的尾部）。

> **字段名是 camelCase，且必须与 Host schema 逐字一致**（复合字段尤其：`contourAnim` 而非 `contour-anim`）。字段名对不上时，值会**静默**落在没人读的地方（旧世代是 `settings.yaml` 里多一行未声明键；0.1.7 起是对非 volatile/未声明路径的写入被 schema 拒绝），声明字段仍是默认——表现为「开关刷新后复位」。成因、影响面与旧存档的迁移见 [engineering-notes.md § 存储字段名](engineering-notes.md#存储字段名必须来自-schema不能用去掉前缀推出来issue-15)，回归测试见 [testing.md](testing.md#设置页)。

## 总览

| 组 | 开关 | 默认 | 命名空间字段 |
| --- | --- | --- | --- |
| 01 主题 | 终末地主题 | 开 | `enabled`（旧键 `dsh-theme-endfield-enabled`）|
| | 主题配色 | 谷地黄 | `palette` |
| | 主题圆角 | 直角 | `radius` |
| | 磨砂玻璃 | 关 | `glass` |
| 02 背景 | 等高线背景 | 关 | `contour` |
| | 动态等高线 | 开 | `contourAnim` |
| | 鼠标轨迹 | 关 | `contourTrail` |
| | 等高线绘制 | Canvas | `contourRenderer` |
| | 动态帧率 | 24 FPS | `contourFps` |
| | 动态速度 | 标准 | `contourSpeed` |
| | 滚动暂停 | 开 | `contourScrollPause` |
| | 背景水印 | 开 | `watermark` |
| | 水印保持显示 | 关 | `watermarkPersist` |
| 03 动画 | 输出滚动动画 | 开 | `scrollAnim` |
| | 动画强度 | 标准 | `scrollAnimLevel` |
| | 启动加载动画 | 关 | `loader` |
| 04 娱乐 | 雷霆大字 | 关 | `thunder` |
| | 大字入场动画 | 关 | `thunderAnim` |
| | 顶部余额胶囊 | 关 | `balanceCapsule` |
| | 渠道额度读数 | 剩余 | `creditDisplay` |
| | 胶囊位置 | 顶部居中 | `balanceCapsulePosition` |
| | 侧栏自动隐藏 | 关 | `balanceCapsuleSidebarHide` |
| | 靠近自动隐藏 | 开 | `balanceCapsuleHoverHide` |
| 05 音频 | 音频通知 | **关** | `audioEnabled` |
| | 启动加载动画音 | 开 | `audioBoot` |
| | 任务开始音 | 开 | `audioTurnStart` |
| | 任务结束音 | 开 | `audioTurnDone` |
| | 需要你回应 | 开 | `audioAttention` |
| | 出错提示音 | 开 | `audioTurnFail` |
| | 音量 | 100 | `audioVolume` |
| | 同槽位最小间隔 | 2500 ms | `audioDebounceMs` |
| | 开始音仅认会话框 | 开 | `audioHumanOnly` |
| | 自定义音效目录 | （空） | `audioSoundDir` |
| | 诊断日志 | 关 | `audioDiag` |

开启后默认显示 DeepSeek API 钱包余额与峰谷时段；会话使用 dsh-codearts-auth 渠道模型时自动切换为该渠道的剩余额度与积分消耗（详见「04 娱乐 › 顶部余额胶囊 › 渠道额度模式」）。

取值约定（与 localStorage 时代的极性完全一致，现在由 schema 默认值保证）：**默认开启**的开关在该 schema 里默认存 `'1'`，客户端读作 `!== '0'`；**默认关闭**的存 `'0'`，读作 `=== '1'`。回滚/清除字段时客户端回到 schema 默认值；异常存储值被 schema 校验拒绝，不会悄悄改变出厂观感。

---

## 01 主题

### 终末地主题（总开关）

关闭后移除令牌层与样式表，界面回到应用原生外观，无需刷新。它同时是**所有其他功能的总闸**：关闭会一并拆掉水印、等高线、大字与订阅。

**关闭后设置页仍保持可读。** 这一点需要专门处理：开关按钮的强调色底来自主题自己的样式表，样式表随主题关闭而移除，所以关闭时按钮回退到应用原生令牌（选中态实心底、常态透明描边），而不是留下「黑字落在深色面板上」的不可读状态。

### 主题配色

在**谷地黄**（默认，`#fff500`）与**武陵青**（`#14d0d0`）之间切换。设置行带一个色块预览，而不只是写颜色名。

切换只是 `<body>` 上加减一个 class，不重新注册令牌层、无 JS 重绘。唯一的例外是等高线画布（canvas 描边不能是 CSS 变量），由 `MutationObserver` 监听 class 变化触发重绘——顺带让「别的标签页改了配色」也能跟着变。

### 主题圆角

直角（默认）/ 圆角。同样是一个 class：`body.theme-endfield-round` 让整组 `border-radius: 0` 规则失效，恢复应用原生圆角。

---

## 02 背景

### 等高线背景（默认关闭）

参考等高线地形图：整屏铺满强调色细线，闭合环线相互嵌套成大小不一的「岛屿」。

线不是贴图也不是手绘路径，而是**标量场的真等值线**——因为动画的连贯性来自场本身：场变形后重新提取，环线会像真实地形一样合并、分裂，平移一张贴图做不到这件事。

- **场** = 若干正负相间的高斯凸起之和（既有山峰也有洼地）+ 三道极缓的长波正弦
- **线** = marching squares 在 21 个等间距高度上取等值线
- **连线** = 线段按边 ID 缝合成连续折线，再以二次曲线绘制

**密度随视口自适应**：上面的 21 级 / 22 岛是按桌面窗口调的，按「每面积」施加在窄窗口就会读作一堆乱线。短边低于 640px 时，层级数向 10、岛屿数向 12 随宽度线性递减（320px 触底），线距随之拉开；**短边 ≥640px 逐字节回到出厂密度**，桌面输出不变。覆盖率验证器（accept-or-reroll）按「该视口实际将画的层级数」评分，小窗的无空白区保证不变。见 `contourLevelsFor` / `contourBumpsFor`。
算法细节与实测数据见 [engineering-notes.md § 等高线](engineering-notes.md#等高线背景).

**图层挂在应用外框内部**，`inset:0; z-index:0`，落在「外框底色之上、所有定位子元素之下」。正文永远在图层之上，前景对比度不受影响。挂载期间会把外框、对话列、右侧栏列（0.1.x 叫详情列）与侧栏的不透明底色置为透明（`:has()` 守卫使功能关闭时全部规则失效）。

**关闭时零逐帧开销**——不是在 rAF 里空转再提前返回，而是整个循环停掉。

### 动态等高线（默认开启，需先开启等高线背景）

等高线流动变形。动态帧率提供 `24 FPS`、`60 FPS`、`120 FPS` 三档，默认 `24 FPS`；动态速度提供 `1x`、`2x`、`4x` 三档，分别对应慢速、标准、快速，默认 `2x`。两者写入 schema 字段 `contourFps` / `contourSpeed`（UI/存储键分别是 `dsh-theme-endfield-contour-fps` / `dsh-theme-endfield-contour-speed`，由 `PREFS_KEY_TO_FIELD` 映射；旧世代落盘时用的是 UI 键的尾部拼写），非法值自动回退到默认值。关闭则为静态图案，不做任何逐帧计算。

**尊重系统「减少动态效果」**：`prefers-reduced-motion` 下图案照常渲染（静态纹理不算动效），但场变形不启动——即使开关是开的。此时设置行会说明是系统偏好在生效，而不是让开关看起来失灵。

### 背景水印（默认开启）

`ENDFIELD` 大字水印，默认只在**新建会话页**（hero）显示，居中跟随标题。

- `pointer-events: none`，不拦截点击与文本选择；
- 强度按配色分别声明为 CSS 变量 `--edge-wm-alpha`（亮 `0.13` / 暗 `0.085`），配色切换时自动重解析；
- 深色模式取更低的 alpha：在近黑底上**叠加**亮度，比在纸底上**减去**亮度显得响得多。

**防浏览器误翻译**：字形由 CSS `content`（`::before`）绘制，元素本身没有任何 DOM 文本节点，逐文本节点遍历的翻译器看不到它；另加 `translate="no"`、`class="notranslate"`、`lang="en"`、`aria-hidden="true"`。

### 水印保持显示（默认关闭，需先开启背景水印）

开启后，对话页等非新建会话页面也显示水印。

此时水印挂载在**会话列内部**，以 `z-index: -1` 位于正文**之下**（该列在水印挂载期间获得 `isolation: isolate` 与 `position: relative`，卸载后自动还原）。之所以不挂 `<body>`：一个 fixed 的 body 子节点会画在消息文字**之上**，把用户正在读的内容洗白。

---

## 03 动画

### 输出滚动动画（默认开启）

模型流式输出时，应用会把对话栏**整段钉到底部**——一次一大格，中间没有过渡帧。这个开关把那一步摊成逐帧滑动：流式输出看起来是「滑」上去的，而不是「跳」上去的。

**它不改动应用自己的滚动位置。** 补偿只施加在对话栏的 `translateY` 上，随后逐帧衰减归零，因此应用读回的滚动位置分毫未变、它对「读者是否还在跟随」的判断不受影响，跟随不会中断。这也是它默认开启的原因：升级到带本功能的版本不会破坏任何既有行为，只是输出变顺滑。

选型、实测数字与那条 `scroll-behavior: smooth` 否决见 [engineering-notes.md § 输出滚动动画](engineering-notes.md#输出滚动动画为什么不能改-scrolltop-的写后回读语义)。

**动画强度**（`soft` / `standard` / `snappy`，默认 `standard`）是同一个衰减曲线上的三个速度，选的是**顺滑与滞后的取舍**，不是三个不同的效果：

| 档位 | 观感 | 单步上限 |
| --- | --- | --- |
| 柔和 | 最顺滑，最新一行离底部最远 | 420px |
| 标准（默认） | 出厂档位：平滑与跟随之间的折中 | 300px |
| 轻快 | 最跟手，平滑幅度最小 | 200px |

「单步上限」是**安全检查而非观感旋钮**：一次位移超过它的步进（恢复会话、跳到一个回合这类）按原样瞬时呈现。真实代价是**一次落一大块**时可能落空——一个几百 px 的代码块或工具结果在一步里落地，在 `标准` 下会退回硬跳（实测 400px：`标准` 瞬时、`柔和` 仍平滑）。把这一步摊成动画意味着内容要从 300px 以下滑上来三分之一秒，那比它要替代的硬跳更显眼，所以上限就设在这里；想让它也平滑就选 `柔和`，三个档位因此不是三份观感而是三个位置。

滞后是这套做法的固有代价，不是可调掉的缺陷，而且**它取决于流速**：实测在真实流式速率（每 250ms 一行 24px，约 `96px/s`）下最新一行只落后视口底约 `3px`（不足一行行高）；只有在刻意夸张的 `1600px/s` 合成速率下才会到约 `110–160px`。关闭该功能时两个速率都是 `0px`。三个档位只是在同一根「顺滑 ↔ 滞后」旋钮上取三个点。

**这些情形一律保持瞬时**，不做任何补偿：向上滚动、从阅读位置跳转（打开历史会话、跳到一个回合）、单步位移超过该档上限、用户自己的滚轮 / 触摸 / 指针 / 键盘手势，以及页面处于隐藏状态。

**展开思考 / 工具详情这类布局重排也一律瞬时**：折叠区展开会让整列在数百 px 量级上变高，应用的 `ResizeObserver` 随之重钉底——这一步在几何上和流式增长几乎一样（同样从底部出发、同样向下、同样可能小于上限），但**屏幕上既有内容其实没有被移动**，所以补偿它只会造出一次多余的下坠再滑回来。运行时因此不假设「滚动量就是视觉位移」，而是实测布局自己贡献了多少再相减（详见 [engineering-notes.md § 布局重排](engineering-notes.md#布局重排展开思考块为什么抽搐)）。

**尊重系统「减少动态效果」**：`prefers-reduced-motion` 下完全不补偿，输出回到原生硬滚动——即使开关是开的。此时设置行会说明是系统偏好在生效，而不是让开关看起来失灵。系统偏好在运行中翻转也会即时生效并清掉残留位移。

**关闭即零开销**：关掉时不留监听、不留逐帧循环，位移立即释放。

回归测试见 [testing.md § 输出滚动动画](testing.md#输出滚动动画)。

### 启动加载动画（默认关闭）

参考终末地官方主视觉：整屏黑底，左边缘 10px 强调色进度轨自上而下填充。**刻度 + 百分比 + 状态行跟随进度轨的填充端一起下移**，并做了下边界钳制，接近 100% 时不会滑出屏幕。

播放时序（开启后每次刷新页面播放一次）：

| 阶段 | 时长 | 表现 |
| --- | --- | --- |
| 进度 | 约 1750ms（`easeOutCubic`） | 强调色进度轨自上而下填充，读数随之下移 |
| 横向铺满 | 520ms | 到 100% 后进度轨从 10px 横向展开成整屏 |
| 整屏淡出 | 620ms | 连同整块遮罩淡至透明，节点随即从 DOM 移除 |

设置行提供「预览」按钮，可不刷新页面直接重播一次。

**海报式排版**：品牌块**内部左对齐**（各行共用同一条左侧节奏线）但整体**摆在画面右侧**——这个组合才是参考图的观感。`END` / `FIELD` 上下两行紧排堆叠，不含中文字标。字标尺寸 `clamp(26px, min(5.2vh, 4.8vw), 64px)`，取较小轴，因此无论窗口「矮」还是「窄」品牌块都不会挤进左侧进度轨。已在 520×900 到 2560×1440 共 13 种视口逐一验证无溢出。

工程要点：

- **进度由墙钟时间推导**（非逐帧累加），不会漂移；
- **双时钟驱动**：`requestAnimationFrame` 负责平滑更新，`setInterval` 兜底——rAF 在后台标签页与无头渲染器中会在首帧后停摆，仅靠它会让全屏遮罩永久留在屏幕上；
- **收尾动画也由 JS 驱动，不用 CSS transition**（原因见工程笔记）；
- **硬性保险丝**：一个独立 `setTimeout` 终点，即使所有时钟停摆也必定移除遮罩；
- `prefers-reduced-motion` 下直接跳过铺满与淡出，立即移除遮罩；
- `pointer-events: none`：动画进行中也不拦截点击；
- 全部字形由 CSS `content` 绘制 + `translate="no"`，防浏览器翻译改写品牌名。

`z-index: 2147483000` —— 加载屏是唯一有资格独占整屏的表面。

---

## 04 娱乐

### 雷霆大字（默认关闭）

任务开始 / 结束时，在屏幕正中用白色粗体大字打出「任务开始」/「任务完成」，**3 秒后自动隐藏**，**期间点击屏幕任意处可立即关闭**。设置行提供「预览」按钮。

**状态信号取自唯一权威位**：`@deepseek-ai/dsh-client-runtime` 的 `ConversationSnapshot.running`——应用自己的回合状态标签与停止按钮切换的就是这个字段。不去监听转圈图标或类名：那些类名带构建哈希、并非契约，而且「出现了一个 spinner」和「一个回合开始了」并不是同一件事。

**只认边沿，不认电平。** 一个回合进行中，快照存储会以每秒几十次的频率推送；只有 `false → true` / `true → false` 的跳变才播报。每个会话**第一个可读到的值记为基线并保持静默**，因此「切进一个已经在跑的会话」不会误报「任务开始」——但该会话结束时仍会正常播报「任务完成」。

**点击关闭的监听挂在 `document` 的捕获阶段**，遮罩本身保持 `pointer-events: none`：

- 一次按下**同时**完成两件事——关掉大字，并正常抵达用户真正瞄准的元素；
- 用 `pointerdown` 而非 `click`：一个事件覆盖鼠标 / 触摸 / 触控笔，且按下即消失；
- 用**捕获阶段**：菜单、下拉、拖拽手柄常在自己的处理器里调 `stopPropagation`，冒泡阶段听不到那些按下，大字就会关不掉；
- 提前关闭会同时取消 3 秒定时器并摘掉监听器。

如果改成给遮罩加 `pointer-events: auto` 再挂 `onclick`，它会变成**一块持续 3 秒的全屏点击黑洞**：用户为了关掉它而点下去的那次点击会被吃掉。

层级 `z-index: 2147482000`，比加载遮罩低一档。整块 `aria-hidden`——纯装饰不该被读屏软件念出来。

**关闭即零开销**：关掉时不订阅、不留定时器、不建节点。

### 大字入场动画（默认关闭，需先开启雷霆大字）

| 状态 | 表现 |
| --- | --- |
| **关闭（默认）** | 大字**直接出现**在屏幕正中，保持 3 秒后消失，无缩放、无淡入淡出 |
| 开启 | 大字由大缩小「砸」入（`scale(2.4) → 0.94 → 1`）并在末尾淡出 |

与「等高线背景 → 动态等高线」是同一种结构：**图层是一个决定，让它动是另一个决定**。

三个刻意的约束：

- **子开关只改入场表现，不改时长。** 3 秒由 JS 定时器持有，不挂在动画结束事件上——否则开关动画会顺带改掉停留时间。
- **未开启动画时，主开关关闭时该行为 disabled。** 一个对当前状态无意义的控件应该看起来就是不可用的。
- **系统「减少动态效果」仍然压过这个开关。** 两个独立原因（开关关闭、系统偏好）汇合到同一条静态分支；此时设置行会说明是系统偏好在生效。

---

### 顶部余额胶囊（默认关闭）

开启后屏幕顶部悬浮一枚药丸：左边是 DeepSeek API 余额（¥ / $），右边是当前**峰谷时段**——`高峰` / `低谷`、时段剩余与已过百分比、一个圆环时钟，以及开场那帧「品牌 pose」（`/// DEEPSEEK API` + `DeepSeek 当前低谷`）。设置行提供「预览」按钮，按真实开场动画重播一次；没有配置 API Key 时按钮禁用并说明原因。

**峰谷规则在本地推算**（Host 不提供排班表）：北京时间**周一至周五 9:00-12:00、14:00-18:00 是高峰**，价格为空闲时段的 2 倍。按 DeepSeek 2026-09-19 的「API 峰谷时间补充说明」，**中国法定节假日全天**以及**调休上班的周末**都按空闲计费——后一条天然落在「周末即低谷」上，所以胶囊只要保管一份法定节假日日历（按年抄录国务院通知；未收录的年份退回普通工作日规则，宁可显示高峰也不猜一个假的半价）。

倒计时读的是**同价连续段**，而不是「今天还剩多少」：周五 18:00 到周一 09:00 是一整段 63 小时的低谷，七天国庆是一整段 183 小时，所以读数可能是 `剩余47:20:00`。节假日名只出现在开场 pose 的标题里（`DeepSeek 国庆节低谷`），正式行仍只写两三个字——胶囊宽 300px，多一个字就会撑破它。

**窄屏逐级让步**：视口 ≤480px（手机）时不显示「时段剩余hh:mm:ss」，只留 `高峰` / `低谷` 与已过百分比、圆环，并且胶囊同时放下 300px 的宽度地板、按剩余读数自然收紧；再窄到 ≤316px 才把整段灰色读数一并去掉。秒级倒计时是手机上最没人看、也是最占宽度的一串字符，所以它第一个让位。

#### 渠道额度模式（dsh-codearts-auth）

当当前会话使用的模型来自 [dsh-codearts-auth](https://www.npmjs.com/package/dsh-codearts-auth)（Jet Hub）的某个渠道（CodeArts、CodeBuddy、WorkBuddy、LobsterAI、Qoder、Qoder CN、TRAE、Cline、Loomy、Raccoon、MiniMax、ZCode、OpenCode）时，胶囊自动切换为**渠道额度**显示，随切换渠道实时跟随：

- **显示内容**：`渠道名 剩余额度 单位`——渠道名（如 `TRAE`）小字在前，剩余量沿用插件自己的格式（credit 非整数保留两位小数；token 量纲显示 `12.35K` / `12.35M`），单位尾注 `积分` 或 `Token`。多账号取第一个可用账号的 active 额度包求和。
- **右侧读数可选已用或剩余**：设置面板「04 娱乐 › 渠道额度读数」可切换右侧百分比的含义——**已用**（`已用xx%` = 已用/总额度，与插件徽章一致）或**剩余**（默认，`剩余xx%` = 剩余/总额度）。右侧圆环与百分比**始终同步**：选「已用」圆环走已用份额，选「剩余」圆环走剩余份额。两种读法都需渠道报出总额度，否则右侧留空、圆环归零——没有总额度的份额无从判断，不编造读数。左侧主数字始终为剩余额度，不受此设置影响。切换在下次取数时生效（不强制额外 RPC，5 分钟取数下限保持诚实）。
- **不显示峰谷时段**：高峰/低谷是 DeepSeek API 的计费概念，渠道模式下整段时段读数隐藏。
- **取数与缓存**：走插件自己的管理 RPC（`usage.badge`），复用宿主侧 120s TTL 缓存（失败缓存 15s）；主题侧另有**每渠道 5 分钟的取数下限**——余额是慢变量，胶囊是一眼可读的快照，不值得更密的请求。渠道**切换**绕过下限立即跟随；取数失败 30 秒后即允许重试。同一 `--endfield-balance-sweep` 变量在两种模式间切换含义，模式切换零重排。
- **失败语义**：渠道未登录、插件缺失或上游失败显示 `--`，绝不变灰成 0；切回 DeepSeek 模型则恢复钱包余额显示（DeepSeek 钱包轮询始终在跑，切回即刻有数）。

---

## 05 音频（默认关闭）

四种提示音，**由宿主进程播放**而不是浏览器：页面最小化、切到别的应用时同样能听到——那恰好是最需要提示的场合。
完整文档见 [audio-notifications.md](audio-notifications.md)。

| 槽位 | 触发时机 |
| --- | --- |
| `audioBoot` 启动加载动画音 | 播放 ENDFIELD 加载板时响一次（只认真正的页面加载，点「预览」重播不响） |
| `audioTurnStart` 任务开始音 | 从会话框提交指令后 |
| `audioTurnDone` 任务结束音 | 我产出最终结果后（中途报错、等待审批不响） |
| `audioAttention` 需要你回应 | 审批请求 / 我的提问 / 计划求批；锚点为面板自己的 `data-*` 属性 |
| `audioTurnFail` 出错提示音 | **不接事件**：不需要人工干预的错误保持静音，音效与开关只是预留 |

**总开关 `audioEnabled` 默认关闭**：音效是选择加入（opt-in）的功能，升级到带本功能的版本不会自己开始出声；
打开总开关后，各槽位开关（默认开启）决定具体哪几种情形响，音量只缩放提示音本身、不改系统音量。

---

## 其他自动生效的修正

不需要开关、随主题一起生效的可读性修正：

| 位置 | 问题 | 结果 |
| --- | --- | --- |
| 设置 › 模型 行操作（`编辑`） | 白字压在强调底上，1.05:1 | 16.50:1 |
| Cordis / 技能 / 工具 检查面板按钮 | 同上 | 16.50:1 |
| 附件轮播箭头 | 同上 | 16.50:1 |
| 亮色模式「移除」按钮 | `#ff3b30` 红字在面板底上 3.16:1 | 5.16:1 |
| 回合状态标签「Deep diving…」 | 渐变文字仍是品牌蓝 | 两配色四个色标全部达 AA（0.2 起标签改用遮罩扫光，换色走 `--dsw-alias-label-deep-diving*` 两个令牌，结论不变） |
| 提问卡片「推荐」徽标 | 前景与背景令牌被映射成同一值，完全不可见 | 16.50:1 |
| 提问卡片选项编号 | 暗色选中行上黑底黑字，1.25:1 | 11.69:1 |
| 新建会话页背景光晕 | 写死的 `#6187D8` | 改强调色，按亮度对齐原强度（`*_heroGlow` 模块自 0.1.2-rc.1 起已不存在、0.2 仍无，规则保留为自愈钩子） |
| 会话头部预设徽章（agent preset，如「创造模式」） | 上游把徽章移进 `_headerActions` 插槽的包裹层里，主题的 `>` 选择器失配，退回默认灰胶囊 | 恢复强调色填充 + 黑字，**尺寸沿用上游**（不撑宽） |
| 审批弹窗（「等待审批」） | 上游是塞在输入区里的一张圆角描边小卡 | 重绘为**贯穿屏幕左右白条 + 上下遮罩**的居中确认框，按钮改为胶囊（拒绝=深色+✕，允许一次=信号黄+◎），见下 |
| 完全权限确认框（「确认启用完全权限？」） | 共享 Modal 的居中圆角小卡 | 同款白条 + 上下遮罩，取消/启用胶囊按钮，见下 |

### 审批弹窗：全屏白条 + 上下遮罩

工具越权执行时弹出的 `[data-approval-key]` 面板被重绘成参考图那种形态：**不透明纸色横条从屏幕左缘贯到右缘、垂直居中，页面其余部分只有上下被压暗**——实现上没有加任何 DOM：面板自己的 root 变成 fixed 全屏 flex 层，它的半透明黑底就是遮罩，中间的白条不透明地盖住横带，遮罩自然只剩上下两截。文字居中，「等待审批」保留状态点但去掉警示底色。两个按钮改成终末地胶囊（左取消深灰配白 ✕、右确认信号黄配 ◎，墨字对比度与主题其它实心芯片同一条 AA 约束）。**圆角模式同样保持胶囊**（与余额药丸一致），且所有选择器都钉在语义锚点上（`data-approval-key` + `[class*='_card']` 等 CSS-module 后缀），上游重哈希不会失效——`test/selector-guard.test.js` 钉住这三个钩子。

### 完全权限确认框：同一形态

「确认启用完全权限？」（权限下拉切到完全权限时的风险确认框）是**另一个组件**——ui-primitives 的共享 Modal，portal 到 `<body>`，由 RiskConfirmation 包装。它收到同款重绘：贯穿左右的纸色白条、上下遮罩、标题居中、✕ 关闭移到白条右缘、取消/启用两个胶囊按钮（未勾选风险确认前，启用键保持暗化禁用态）。作用域钉在 `[role='dialog'][class*='_confirmation']`（RiskConfirmation 加在 dialog 上唯一的语义标记）之下，其它普通弹窗不受影响；外层共享 root 的 24px 侧 padding 用 `:has()` 精确归零。

这些的成因分析见 [engineering-notes.md § 已修问题归档](engineering-notes.md#已修问题归档)。
