dsh-theme-endfield 1.2.2 → 1.2.3
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/README.md +77 -32
- package/client.js +42 -8
- package/docs/engineering-notes.md +10 -19
- package/docs/features.md +8 -5
- package/docs/glass.md +2 -2
- package/docs/testing.md +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
# dsh-theme-endfield
|
|
2
2
|
|
|
3
|
-
参考《明日方舟:终末地》官网风格的 DSH Web
|
|
3
|
+
参考《明日方舟:终末地》官网风格的 DSH Web 主题插件:奶油纸底、墨黑文字、信号黄/武陵青强调色、全直角工业编辑风。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **Client 侧**(`client.js`):通过主题令牌与样式覆盖界面。
|
|
6
|
+
- **Host 侧**(`index.js`):设置持久化与可选的音频通知(派生系统播放器子进程)。
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| DSH | 设置持久化 | 备注 |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| **0.2.x**(在 **0.2.0-rc.2** 上实测) | Host 导出 `Config`,浏览器 `ctx.configForms` | 当前支持目标 |
|
|
12
|
-
| 0.1.7 – 0.1.x | 同一套 Config/configForms 接缝 | 无需改动 |
|
|
13
|
-
| ≤ 0.1.5 | `ctx.settings.register` + `ctx.settingsScope` | 自动回落,无需配置 |
|
|
14
|
-
|
|
15
|
-
0.2 改动了三处本插件依赖的应用侧细节,本次已跟进(都验证过「旧写法在 0.2 上静默失效」):
|
|
16
|
-
|
|
17
|
-
- **回合状态标签**(“Deep diving…”)从 `@deepseek-ai/dsh-client-ui-conversation` 的**渐变文字**搬到 `@deepseek-ai/dsh-client-ui-chat` 的**遮罩扫光文字**,不再有 `turnStatus` 类名,着色改由 `--dsw-alias-label-deep-diving` / `-shimmer` 两个令牌承担,所以旧规则已删除、改在 `theme.overrideTokens` 层换色(实测:留着旧规则时该令牌仍是应用自带值,标签完全没有主题色);
|
|
18
|
-
- **右侧栏**列名由 `_detailsCol` 改为 `_rightbarCol`,且不透明底色移到列元素本身——等高线图层在右侧栏打开时不会再被整块盖住;
|
|
19
|
-
- **插件卡片展示文案**改由 `locale/<语言>.json` 的 `meta.title` / `meta.description` 提供;`package.json` 的 `dsh.client.inject` 也不再列 0.2 已删除的 `@deepseek-ai/dsh-client-runtime` 与 `@deepseek-ai/dsh-client-ui-slots`(改为列出 0.2 真正的提供方)。
|
|
8
|
+
两侧都不修改应用自身代码。
|
|
20
9
|
|
|
21
10
|
## 安装
|
|
22
11
|
|
|
@@ -24,30 +13,66 @@
|
|
|
24
13
|
dsh plugin --profile web add github:ymh0000123/dsh-theme-endfield
|
|
25
14
|
```
|
|
26
15
|
|
|
27
|
-
重启或重新加载 `web` profile
|
|
16
|
+
重启或重新加载 `web` profile 后生效。卸载:
|
|
28
17
|
|
|
29
18
|
```bash
|
|
30
19
|
dsh plugin --profile web rm dsh-theme-endfield
|
|
31
20
|
```
|
|
32
21
|
|
|
22
|
+
### 更新插件文件时的加载时机
|
|
23
|
+
|
|
24
|
+
| 改动 | 生效方式 |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| Client 半 `client.js` | Host 按请求从磁盘读取,浏览器刷新即可 |
|
|
27
|
+
| Host 半 `index.js` | 只在 profile 启动时 import 一次,**必须整进程重启 DSH** |
|
|
28
|
+
|
|
29
|
+
`dsh-hmr` 的 watch 默认忽略 `**/node_modules`,软链安装的仓库文件不在其观察范围内;只刷新页面时,运行的仍是启动时那份 Host 代码。
|
|
30
|
+
|
|
33
31
|
## 功能
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
开关集中在 **设置 › 终末地主题设置**:
|
|
34
|
+
|
|
35
|
+
- **主题**:总开关、谷地黄/武陵青配色、直角/圆角模式。
|
|
36
|
+
- **背景**:等高线背景(默认关闭)及动态开关、`24 / 60 / 120 FPS`、速度 `1x / 2x / 4x`;可选鼠标轨迹(鼠标附近的等高线局部变形并逐渐恢复,默认关闭);背景水印及持续显示。
|
|
37
|
+
- **动画**:输出滚动动画(默认开启,强度分 `柔和 / 标准 / 轻快`)——把模型流式输出的「整段跳一大格」摊成逐帧滑动,不改动应用自己的滚动位置,因此跟随不会中断,向上滚动、读者手势与系统「减少动态效果」下都保持瞬时;启动加载动画。
|
|
38
|
+
- **娱乐**:雷霆大字及入场动画;顶部余额胶囊(默认关闭)。
|
|
39
|
+
- **音频**:启动音、任务开始/结束音、需要回应时提示,音量与自定义音效目录可调(默认关闭)。
|
|
40
|
+
|
|
41
|
+
几处需要单独说明的行为:
|
|
42
|
+
|
|
43
|
+
- **顶部余额胶囊**:悬浮显示 DeepSeek API 余额与峰谷时段(高峰为工作日 `9-12` 点、`14-18` 点;周末、法定节假日与落在周末的调休日都按低谷半价)。会话使用 [dsh-codearts-auth](https://www.npmjs.com/package/dsh-codearts-auth) 渠道模型时自动切换为该渠道的剩余额度与积分消耗进度,随渠道切换实时跟随,主读数可在设置中选择右侧百分比显示剩余或已用。
|
|
44
|
+
- **输出滚动动画**与**渠道额度模式**的完整行为、默认值与边界情况见 [docs/features.md](docs/features.md)。
|
|
45
|
+
- **音频通知**的防噪规则与自定义音效见 [docs/audio-notifications.md](docs/audio-notifications.md)。
|
|
46
|
+
|
|
47
|
+
所有设置由 DSH 自己的设置服务持久化,与页面 origin/端口无关,DSH web 与 DSH Desktop 都能正确保存并在重启/换端口后恢复(不再使用会被 Desktop 随机端口清空的 `localStorage`)。设置文案支持中英文;动态等高线尊重系统「减少动态效果」,动画帧率与速度可独立调整。
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
- 等高线背景、动态开关、`24 / 60 / 120 FPS`;
|
|
39
|
-
- 等高线速度 `1x / 2x / 4x`;
|
|
40
|
-
- 可选鼠标轨迹:鼠标附近的等高线局部变形并逐渐恢复,默认关闭;
|
|
41
|
-
- 背景水印及持续显示;
|
|
42
|
-
- 输出滚动动画:模型流式输出时把「整段跳一大格」摊成逐帧滑动(默认开启,强度分 `柔和 / 标准 / 轻快` 三档);不改动应用自己的滚动位置,因此跟随不会中断,向上滚动、读者手势与系统「减少动态效果」下都保持瞬时(详见 [docs/features.md](docs/features.md)「输出滚动动画」);
|
|
43
|
-
- 启动加载动画;
|
|
44
|
-
- 雷霆大字及入场动画;
|
|
45
|
-
- 顶部余额胶囊:悬浮显示 DeepSeek API 余额与峰谷时段(高峰为工作日 `9-12` 点、`14-18` 点;周末、法定节假日与落在周末的调休日都按低谷半价),默认关闭;会话使用 [dsh-codearts-auth](https://www.npmjs.com/package/dsh-codearts-auth) 渠道模型时自动切换为该渠道的剩余额度与积分消耗进度,随渠道切换实时跟随,主读数可在设置中选择右侧百分比显示剩余或已用(详见 [docs/features.md](docs/features.md)「渠道额度模式」);
|
|
46
|
-
- 可选音频通知:启动音、任务开始/结束音、需要回应时提示,音量与自定义音效目录可调(默认关闭,详见 [docs/audio-notifications.md](docs/audio-notifications.md))。
|
|
49
|
+
## DSH 版本支持与设置持久化
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
| DSH | 设置持久化 | 备注 |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| **0.2.x**(在 **0.2.0-rc.2** 与 **0.2.1-rc.2** 上实测) | Host 导出 `Config`,浏览器 `ctx.configForms` | 当前支持目标 |
|
|
54
|
+
| 0.1.7 – 0.1.x | 同一套 Config/configForms 接缝 | 无需改动 |
|
|
55
|
+
| ≤ 0.1.5 | `ctx.settings.register` + `ctx.settingsScope` | 自动回落,无需配置 |
|
|
56
|
+
|
|
57
|
+
- **0.2.x / 0.1.7+**:Host `index.js` 导出一份 34 个字段全部 `.volatile()` 的 schemastery `Config`(命名空间 = 本插件 profile entry id `theme-endfield`),浏览器 `client.js` 通过 `ctx.configForms` 读写并订阅,值随 `<profile>/cordis.patch.yml` 落盘。
|
|
58
|
+
- **≤ 0.1.5**:回落到 `ctx.settings.register('dsh-theme-endfield', schema)` + `ctx.settingsScope`(`<dshHome>/settings.yaml`)。
|
|
59
|
+
- 0.1.7 升级后旧设置需要在设置页重设一次(`settings.yaml` 已被 DSH 废弃,见 [engineering-notes.md § DSH 0.1.7-rc.1 换掉了整套 settings API](docs/engineering-notes.md#dsh-017-rc1-换掉了整套-settings-api-v110-已跟进))。
|
|
60
|
+
|
|
61
|
+
### 0.2 改动(v1.1.6 已跟进)
|
|
62
|
+
|
|
63
|
+
0.2 改动了三处本插件依赖的应用侧细节,旧写法在 0.2 上会**静默失效**:
|
|
64
|
+
|
|
65
|
+
- **回合状态标签**(“Deep diving…”)从 `@deepseek-ai/dsh-client-ui-conversation` 的渐变文字搬到 `@deepseek-ai/dsh-client-ui-chat` 的遮罩扫光文字,不再有 `turnStatus` 类名;着色改由 `--dsw-alias-label-deep-diving` / `-shimmer` 两个令牌承担,因此旧规则已删除,改在 `theme.overrideTokens` 层换色(留着旧规则时该令牌仍是应用自带值,标签完全没有主题色)。
|
|
66
|
+
- **右侧栏**列名由 `_detailsCol` 改为 `_rightbarCol`,且不透明底色移到列元素本身——等高线图层在右侧栏打开时不会再被整块盖住。
|
|
67
|
+
- **插件卡片展示文案**改由 `locale/<语言>.json` 的 `meta.title` / `meta.description` 提供;`package.json` 的 `dsh.client.inject` 也不再列 0.2 已删除的 `@deepseek-ai/dsh-client-runtime` 与 `@deepseek-ai/dsh-client-ui-slots`,改为列出 0.2 真正的提供方。
|
|
49
68
|
|
|
50
|
-
|
|
69
|
+
细节见 [docs/engineering-notes.md](docs/engineering-notes.md)。
|
|
70
|
+
|
|
71
|
+
### 开关总是「刷新后复位」
|
|
72
|
+
|
|
73
|
+
先看 Host 侧有没有这份 `Config`(`Config.listConfigs` 对该 entry 报 `absent` 就是没有)。没有 Config 时 DSH 不投影任何表单,Host `apply()` 会打一行 warn 并在 profile 目录留下报告文件 `theme-endfield-diagnostic.json`(`Config` 构建成功时会自动删除它;报告里的 `schemaMode` / `loaded` / `loadError` 会写明走了哪条解析路径、以及某个副本是否「解析得到却加载失败」)。排查与判据见 [docs/testing.md#设置页](docs/testing.md#设置页)。
|
|
74
|
+
|
|
75
|
+
另外注意:**改 Host 半(`index.js`)必须整进程重启 DSH**,刷新页面只重载 `client.js`。
|
|
51
76
|
|
|
52
77
|
## 文档
|
|
53
78
|
|
|
@@ -58,6 +83,11 @@ dsh plugin --profile web rm dsh-theme-endfield
|
|
|
58
83
|
| [docs/engineering-notes.md](docs/engineering-notes.md) | 算法、层叠、动画和性能实现说明 |
|
|
59
84
|
| [docs/testing.md](docs/testing.md) | 校验脚本与测试套件说明 |
|
|
60
85
|
| [docs/contour-trail.md](docs/contour-trail.md) | 鼠标轨迹的采样、衰减与验证 |
|
|
86
|
+
| [docs/contour-worker.md](docs/contour-worker.md) | 可选 Worker / WebGL 等高线渲染与回退 |
|
|
87
|
+
| [docs/contour-scan-bounds.md](docs/contour-scan-bounds.md) | 等高线提取的分块边界算法 |
|
|
88
|
+
| [docs/audio-notifications.md](docs/audio-notifications.md) | 音频通知的实现、防噪规则与自定义音效 |
|
|
89
|
+
| [docs/glass.md](docs/glass.md) | 可选磨砂玻璃:模糊档位与刻意排除的部件 |
|
|
90
|
+
| [docs/PR-description.md](docs/PR-description.md) | 音频通知那次改动的 PR 描述草稿 |
|
|
61
91
|
|
|
62
92
|
## 开发与验证
|
|
63
93
|
|
|
@@ -67,7 +97,18 @@ node selftest.js
|
|
|
67
97
|
npm test
|
|
68
98
|
```
|
|
69
99
|
|
|
70
|
-
`npm test`
|
|
100
|
+
`npm test` 覆盖样式不变量、配色、设置页、真实浏览器渲染、等高线平滑/尖点、动画可访问性、覆盖率和 24/60/120 FPS 性能预算。部分浏览器测试需要本机安装 Chrome 或 Edge。
|
|
101
|
+
|
|
102
|
+
**版本与 CI 口径**(三者不是同一个集合):
|
|
103
|
+
|
|
104
|
+
| | Node | 测试范围 |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| 插件运行(`package.json` 的 `engines`) | `>=18` | — |
|
|
107
|
+
| 本地 `npm test` | Node 22+ | 全量 |
|
|
108
|
+
| CI 阻塞门禁(`test:ci`) | Node 22 | 全量减去下面三项 |
|
|
109
|
+
| CI 非阻塞 job | Node 22 | `test:perf` + `test:coverage`,`continue-on-error`,结果只写进摘要 |
|
|
110
|
+
|
|
111
|
+
跑测试请用 Node 22+:浏览器 fixture 依赖全局 `WebSocket`/`fetch`,版本不足会在测试处明确报错(插件本身的运行没有这个要求)。`npm test` 比 CI 的 `test:ci` 多出 `contour-coverage`、`contour-perf`、`contour-density` 三项:前两项由 CI 的非阻塞 job 单独跑,`contour-density` 目前不在 CI 中运行。
|
|
71
112
|
|
|
72
113
|
## 项目结构
|
|
73
114
|
|
|
@@ -75,9 +116,10 @@ npm test
|
|
|
75
116
|
src/client/ Client 侧源码片段(拼接顺序见 src/client/manifest.js)
|
|
76
117
|
src/host/ Host 侧源码片段(拼接顺序见 src/host/manifest.js)
|
|
77
118
|
src/styles/ theme.css:主题样式表(那一整个模板字面量的内容)
|
|
78
|
-
src/contour
|
|
119
|
+
src/contour-worker.js 等高线 worker 源码(npm run build:worker 嵌入产物)
|
|
120
|
+
src/contour-webgl.js 等高线 WebGL 源码(同上)
|
|
79
121
|
client.js Client 侧产物,由 src/client/ + src/styles/ 拼接而来
|
|
80
|
-
index.js Host 侧产物,由 src/host/
|
|
122
|
+
index.js Host 侧产物,由 src/host/ 拼接而来;package.json 的 main 与 exports["."] 都指向它
|
|
81
123
|
scripts/ 构建与门禁:lib/bundle-build.js 拼接器 + build-*.js 入口
|
|
82
124
|
lib/ 音频通知:槽位定义、WAV 合成与播放运行时
|
|
83
125
|
locale/ 插件卡片的展示文案(meta.title / meta.description)
|
|
@@ -87,6 +129,9 @@ check.js 样式表静态校验
|
|
|
87
129
|
selftest.js 校验器自检
|
|
88
130
|
test/ 渲染、设置、配色与性能测试
|
|
89
131
|
docs/ 设计、功能、工程与测试文档
|
|
132
|
+
SECURITY.md 安全边界:发布产物清单与运行时权限(由 CI 的 package-check.js 与 files 白名单逐项核对)
|
|
133
|
+
screenshots.json 界面截图清单(对应 assets/ 下的 webp)
|
|
134
|
+
.github/ CI 工作流与检查脚本(不进发布产物)
|
|
90
135
|
```
|
|
91
136
|
|
|
92
137
|
`client.js` 与 `index.js` 是**构建产物,不要直接编辑**:改 `src/` 下的片段,再跑
|
package/client.js
CHANGED
|
@@ -133,7 +133,10 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
133
133
|
|
|
134
134
|
Both are loopback pages, so DSH resolves the connection to 'host' mode
|
|
135
135
|
and the values land on disk; a change of port does not move them because
|
|
136
|
-
nothing lives in browser storage any more.
|
|
136
|
+
nothing lives in browser storage any more. (0.2.1-rc.2 removed the `mode`
|
|
137
|
+
field from the form snapshot entirely — writes then always ride
|
|
138
|
+
`remote.settings.mutate()` to the Host document, so "is this page
|
|
139
|
+
loopback?" is no longer a question that generation can answer or needs.)
|
|
137
140
|
|
|
138
141
|
Value model. Namespace fields are the tails of the old localStorage keys
|
|
139
142
|
and are stored as the same strings, so the semantics (and any older
|
|
@@ -554,6 +557,12 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
554
557
|
writable, mode }. set() returns Promise<boolean>: false
|
|
555
558
|
means the Host refused or SKIPPED the write (the classic
|
|
556
559
|
case being memory mode on a non-loopback page).
|
|
560
|
+
0.2.1-rc.2 keeps this seam but DELETED `mode`: the
|
|
561
|
+
snapshot is { status, value, base, user, revision,
|
|
562
|
+
writable } and every accepted set() is a
|
|
563
|
+
remote.settings.mutate() against the Host document.
|
|
564
|
+
Treat a missing `mode` as durable; only an explicit
|
|
565
|
+
mode:'memory' (0.1.7 on a non-loopback page) refuses.
|
|
557
566
|
<=0.1.5-rc.2 binder = ctx.get('settingsScope') / ctx.settingsScope
|
|
558
567
|
binder.bind({ namespace, decode }) -> scope with the same
|
|
559
568
|
snapshot fields, and a fire-and-forget set().
|
|
@@ -732,10 +741,9 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
732
741
|
const dbg = (...a) => { try { if (typeof console !== 'undefined' && console.warn) console.warn('[dsh-theme-endfield:prefs]', ...a) } catch (e) { /* noop */ } }
|
|
733
742
|
|
|
734
743
|
/* --- Durable write gate -----------------------------------------------
|
|
735
|
-
A scope snapshot from @deepseek-ai/dsh-client-ui-settings carries
|
|
736
|
-
flags that must ALL hold before a scope.set can durably
|
|
737
|
-
|
|
738
|
-
'memory' (non-loopback) never persists.
|
|
744
|
+
A scope snapshot from @deepseek-ai/dsh-client-ui-settings carries the
|
|
745
|
+
flags below; the ones that must ALL hold before a scope.set can durably
|
|
746
|
+
land are:
|
|
739
747
|
status === 'ready' the Host's describe view actually SERVES this
|
|
740
748
|
namespace and a decoded value stands. It stays
|
|
741
749
|
'loading' before the first accepted section and
|
|
@@ -746,6 +754,23 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
746
754
|
a 'loading'/'unavailable' host scope reaches no
|
|
747
755
|
durable store.
|
|
748
756
|
writable=== true the Host document accepts writes.
|
|
757
|
+
mode === 'host' OPTIONAL. Loopback page syncing the Host
|
|
758
|
+
document; 'memory' (a non-loopback page) never
|
|
759
|
+
persists. @deepseek-ai/dsh-client-ui-settings
|
|
760
|
+
0.2.1-rc.2 DELETED this field (and the whole
|
|
761
|
+
`persistence` concept) along with the CSS
|
|
762
|
+
'memory' mode, so on that generation the
|
|
763
|
+
snapshot is only { status, value, base, user,
|
|
764
|
+
revision, writable } and a missing `mode` means
|
|
765
|
+
"no persistence policy to consult": every
|
|
766
|
+
write rides ctx.remote.settings.mutate() to the
|
|
767
|
+
Host document. Only a snapshot that explicitly
|
|
768
|
+
says mode:'memory' (0.1.7-rc.*, a non-loopback
|
|
769
|
+
page) may refuse the write; `undefined` must be
|
|
770
|
+
treated as durable, or a 0.2.1 page would hold
|
|
771
|
+
every edit forever and log exactly the
|
|
772
|
+
`mode= undefined` boot report this gate is
|
|
773
|
+
meant to explain.
|
|
749
774
|
Gating on snap.writable ALONE is the old bug: a host-mode describe view
|
|
750
775
|
answers writable=true even while this namespace is still unserved, so the
|
|
751
776
|
old code fired scope.set into a namespace the host had not registered,
|
|
@@ -759,7 +784,13 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
759
784
|
if (!scope) return null
|
|
760
785
|
try { return scope.getSnapshot() } catch (e) { return null }
|
|
761
786
|
}
|
|
762
|
-
|
|
787
|
+
/* `mode` is a 0.1.7-only snapshot field (see the gate note above): it is
|
|
788
|
+
absent on 0.2.1-rc.2+, where absence means "always durable", and only an
|
|
789
|
+
explicit mode:'memory' still vetoes a write. */
|
|
790
|
+
const prefsDurablyServed = (snap) => !!snap
|
|
791
|
+
&& (snap.mode === undefined || snap.mode === 'host')
|
|
792
|
+
&& snap.status === 'ready'
|
|
793
|
+
&& !!snap.writable
|
|
763
794
|
/* One field write through the bound transport, with one settlement contract
|
|
764
795
|
for both generations. The legacy scope.set() is fire-and-forget; a
|
|
765
796
|
ConfigForm's set() returns Promise<boolean>, where false means the Host
|
|
@@ -1168,8 +1199,11 @@ function apply(ctx) { // Idempotency: the installed bundle can be applied mor
|
|
|
1168
1199
|
it to guesswork. */
|
|
1169
1200
|
const prefsReportBoot = () => {
|
|
1170
1201
|
const snap = prefsSnapshotOf(prefsScope)
|
|
1171
|
-
|
|
1172
|
-
|
|
1202
|
+
// Health is the same predicate the write gate uses — `mode` is absent on
|
|
1203
|
+
// 0.2.1-rc.2+, so requiring mode:'host' here would print a boot report on
|
|
1204
|
+
// a perfectly working page (that is exactly the `mode= undefined` line
|
|
1205
|
+
// this report itself used to emit).
|
|
1206
|
+
const healthy = prefsDurablyServed(snap) && prefsDirty.size === 0
|
|
1173
1207
|
if (healthy) return
|
|
1174
1208
|
dbg('boot report (preferences did NOT reach a durable section):',
|
|
1175
1209
|
'boundNs=', prefsScopeNs, 'kind=', prefsScopeKind,
|
|
@@ -53,17 +53,18 @@
|
|
|
53
53
|
|
|
54
54
|
关键差异,逐条都有对应的防护:
|
|
55
55
|
|
|
56
|
-
- **只有 `.volatile()` 字段可编辑。** `ctx.settings` 只把 volatile 路径投影成表单,写非 volatile 路径会直接报 `Config field "x" is not volatile`;整份 schema 一个 volatile 字段都没有时该条目**根本不出现**在设置里。所以 `index.js` 的 `Config` **
|
|
56
|
+
- **只有 `.volatile()` 字段可编辑。** `ctx.settings` 只把 volatile 路径投影成表单,写非 volatile 路径会直接报 `Config field "x" is not volatile`;整份 schema 一个 volatile 字段都没有时该条目**根本不出现**在设置里。所以 `index.js` 的 `Config` **34 个字段全部 `.volatile()`**,`test/settings-config-forms.test.js` 会逐字段断言这一点(漏一个就是「设置保存不了」的现代写法)。
|
|
57
57
|
- **两个 schemastery 必须区分。** DSH 同时装了带 `.volatile()` 的 `@deepseek-ai/schemastery`(3.18.4)和不带它的旧 `schemastery`(3.18.0)。`index.js` 的 `loadSchemastery(true)` 会逐个候选检查 `.volatile` 是否真的存在,找不到就返回 `undefined`(本插件退化成无需配置,而不是挂一个假表单)。
|
|
58
58
|
- **命名空间是 entry id,不是包名。** `theme-endfield` 这个串同时出现在 `cordis.patch.yml` 的 `id:`、`index.js` 的 `SETTINGS_ENTRY` 和 client 的 `PREFS_ENTRY`;三者由新测试交叉校验。client 另外会依次尝试 `include:` 前缀等几种安装别名,优先选真正被 Host served(`status:'ready'`)的那个拼写;一个都没 served 时先绑定首选拼写(表单只是共享镜像的懒视图,早绑定才能等到迟到的 section)。此后每次镜像重载都会让每个表单重新派生,`unavailable`→`ready` 的转变会把「另一个拼写被 served」通知过来,此时自动切过去;万一镜像只更新却不通知(表单快照存储丢弃等价快照),还有**有界 settle watch**(20 × 500ms)自己轮询兜底。两种情况下切换期间 held 的编辑都会补写到新拼写上。
|
|
59
59
|
- **`settings.yaml` 已废弃。** DSH 启动时把已有的 `settings.yaml` 改名为 `settings.yaml.imported`,并只迁移 `LEGACY_SECTION_ENTRIES` 里那几段。旧的主题段落名 `dsh-theme-endfield` 不等于 entry id `theme-endfield`,因此**不在迁移之列**,需要用户在设置页重设一次;这是 DSH 侧的行为,不是本插件丢的。旧值仍留在 `settings.yaml.imported` 里可手工对照。
|
|
60
|
-
- **旧宿主仍然可用。** client 在找不到 `configForms` 时回落到 `ctx.settingsScope`(`binder.bind({ namespace: 'dsh-theme-endfield', decode })`),Host 在 `settings.register` 存在时才注册旧命名空间。两代 transport
|
|
60
|
+
- **旧宿主仍然可用。** client 在找不到 `configForms` 时回落到 `ctx.settingsScope`(`binder.bind({ namespace: 'dsh-theme-endfield', decode })`),Host 在 `settings.register` 存在时才注册旧命名空间。两代 transport 的快照字段基本同名(`status` / `value` / `writable`,0.1.7 另有可选 `mode`),所以除了「取句柄」和「写返回」之外整条存储逻辑是同一份。
|
|
61
|
+
- **三代设置快照的 `mode` 差异(0.2.1 兼容修复)。** ≤0.1.5-rc.2 的 `settingsScope` 与 0.1.7-rc.\* 的 `ConfigForm` 都带 `mode: 'host' | 'memory'`;`0.2.1-rc.2` 的 `ConfigFormController` 把它连同 `persistence` 一起删除(`status` 也从 `'unavailable'` 改由 `derive()` 在缺 ns 时设置,语义不变)。client 的闸门因此只看 `status:'ready' && writable`,并对 `mode` 做「只有显式 `'memory'` 才拒写」的判定;`boot report` 的 healthy 判定复用同一个谓词,避免在健康页面上误报。
|
|
61
62
|
|
|
62
63
|
### Host ↔ Client 数据流
|
|
63
64
|
|
|
64
65
|
1. **声明**(Host `index.js`):0.1.7-rc.1 导出 `Config`(`z.object({ 每个字段: z.string().default(...).volatile() })`);同时用 `ctx.inject(['settings'], sctx => sctx.effect(() => sctx.settings.configure({ auto: false }, ctx.fiber)))` 关掉自动生成的设置页(本插件自带四组页面)。旧宿主则在 `settings.register` 存在时执行 `register('dsh-theme-endfield', schema, { applies:'live' })`。schema 每个字段都是**字符串**字段并带 `.default(...)`:default-ON 存 `'1'`(读作 `!== '0'`),default-OFF 存 `'0'`(读作 `=== '1'`);palette/radius/fps/speed 各存一个文档里写明的字面量。字符串化让三代存盘点位的值模型完全一致,client 的键表、面板与测试都不必随 transport 改动。
|
|
65
|
-
2. **写**(浏览器 `client.js`):设置面板每个 toggle 调用内部 `prefsSet(field, value)` → 只有当快照是 **durably served**(`
|
|
66
|
-
3. **读 / 生效**:transport 的 `getSnapshot().value` 就是已由 schema 校验并合并默认值的整段(client 再经 `prefsResolveSection()` 归一化到
|
|
66
|
+
2. **写**(浏览器 `client.js`):设置面板每个 toggle 调用内部 `prefsSet(field, value)` → 只有当快照是 **durably served**(`status:'ready' && writable`,且 `mode` 不是显式的 `'memory'`)时才调用 transport 的 `set(field, value)`,Host 收到后原子写盘。`mode` 只是 **0.1.7-rc.\* 才有的可选字段**:`@deepseek-ai/dsh-client-ui-settings` 0.2.1-rc.2 连同 `persistence` 概念一起删掉了它,快照只剩 `{ status, value, base, user, revision, writable }`,写一律经 `remote.settings.mutate()` 落到 Host 文档,所以**缺省 `mode` 必须按可持久化处理**,只有显式的 `mode:'memory'`(0.1.7 非 loopback 页面)才截断写入。这正是 v1.2.2 在新版桌面「改了不保存」的根因:闸门要求 `mode === 'host'`,而新版恒为 `undefined`,于是每次 toggle 都只 held + 有界重试,约 10s 后丢弃,刷新即丢(启动报告即 `boot report … status= ready mode= undefined writable= true`)。只凭 `writable` 判写是另一个坑:host 模式的快照即便本命名空间**尚未被 served** 也会返回 `writable:true` 与 `status:'unavailable'`(`commit radius = round … status= unavailable` 就是这么打出来的)——旧代码照写不误、清了脏标记但什么都没落盘,刷新即丢。现在这种写被**拦下并标脏**(页面内仍生效),等快照在后续 ready 回相(Host 文档提交、镜像重载)时由 subscription 自动补写;`configForms.set()` 明确返回 `false`(Host 拒绝/跳过)时同样重新标脏等待下次回相,而不是假装写成功。
|
|
67
|
+
3. **读 / 生效**:transport 的 `getSnapshot().value` 就是已由 schema 校验并合并默认值的整段(client 再经 `prefsResolveSection()` 归一化到 34 个声明字段);主题层的 `isEnabled()` 与其它 getter 每次调用都 `prefsGet(field)` 现读本段,天然随值变化。
|
|
67
68
|
4. **订阅同步**:`form.subscribe(...)` / `scope.subscribe(...)` 在每次落盘/镜像变化时唤醒,client 再跑一遍 `reconcileFromPrefs()`,把主题开关(enabled → mount/unmount token+样式表)、圆角/配色 class、水印、等高线、雷霆大字重新对齐。这样同 profile 里**另一个窗口/设备**编辑落盘文件(或本轮写入被 Host 回相确认)都不需要刷新即可热生效。订阅返回的 disposer 现在会被保存并在 run 拆除时调用:`ConfigForm` 是 provider 拥有、跨插件共享的实例,漏掉它会把这一个监听器泄漏给同页面的下一次 run。
|
|
68
69
|
5. **启动恢复**:`apply()` 早于 transport 就绪时,读 schema 默认值(内存镜像),一旦 `status:'ready'` 的第一个真值镜像到达就切换到持久值——即使 Desktop 在随机端口上启动,也能立刻恢复到上次的设置。
|
|
69
70
|
|
|
@@ -101,7 +102,7 @@ v1.1.4 之前的判据是「**必须**找到带 `.volatile()` 的 schemastery,
|
|
|
101
102
|
- **判据写进自检报告**:新增 `schemaMode` 记录走了哪条路;`resolution` 每一行现在除 `resolved` / `error` 外还带 `loaded` / `loadError` / `volatile` / `marker`。「解析得到但加载失败」以前在报告里读起来像自相矛盾,现在是一行结论。
|
|
102
103
|
- **不再有「默认跳过」的断言。** `settings-config-forms.test.js` 的 Host 侧断言在拿不到 schemastery 时**整段跳过**,而拿不到 schemastery 恰恰是它要守的那个场景——于是在唯一要紧的环境里它什么都没验。新的 `settings-config-fallback.test.js` 不依赖本机 schemastery:它把**显式 builder**(含「只有 `.extra()`」这一形状)交给 `buildSchemaWith()`,逐字段断言默认值与 `meta.volatile`,并断言选择顺序(native 优先、`.extra()` 兜底、两者皆无则不导出)。
|
|
103
104
|
|
|
104
|
-
###
|
|
105
|
+
### 求值期 `require(esm)` 的启动竞态与负缓存
|
|
105
106
|
|
|
106
107
|
v1.1.5 解决的是「副本没有 `.volatile()`」,这一段解决的是**同一句 `require` 本身会失败**——两者症状一模一样(`Config === undefined`、无表单、刷新复位),但成因完全不同,且后者在**全新启动、只装本插件**的 profile 上同样复现。
|
|
107
108
|
|
|
@@ -119,23 +120,13 @@ because it is not yet fully loaded.
|
|
|
119
120
|
- **与第三方插件数量无关**:`dsh-base` / `dsh-web-app` 自己就 import cosmokit,所以把 profile 精简到只剩本插件也照样中招。缩减插件不是解法。
|
|
120
121
|
- 与「dev-link 解析不到」也无关:那条路径下 `require.resolve` 会失败并留下 `error: MODULE_NOT_FOUND`;这里是**解析得到、require 抛错**。
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
**为什么没有留在 CJS 里彻底修掉。** `require()` 没有等待能力——必须在模块求值期同步拿到 builder(见上文 `Entry._init` 那条),而 cosmokit 的 job 还在飞就无法等待;ESM 加载器**会排队**(同一句 `import` 会等 cosmokit 结束),所以 v1.1.x 曾给包加一个手写的 ESM 入口 `index.mjs`:`warmSchemastery()` 先经 ESM 加载器预热 schemastery(`@deepseek-ai/schemastery`,失败退到无 scope 的 `schemastery`,再失败就静默放行),再 `await import('./index.js')` 并原样转出,`index.js` 一行逻辑都不用改。那个入口后来被移除了:`d5efc4c` 起包回到单一 CJS 入口,`main` 与 `exports["."]` 都直接指向 `index.js`,`index.mjs` 也从 `files` 白名单里拿掉(那次提交只解除引用,文件本体仍留在工作树里,之后才被单独删除)。
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
| 条件 | 文件 | 谁走这条路 |
|
|
127
|
-
| --- | --- | --- |
|
|
128
|
-
| `import` | `index.mjs` | DSH 的 loader(Node 内部 ESM loader)、`--dump-config-schema`、dshmarket 的热挂载 |
|
|
129
|
-
| `require` | `index.js` | 测试、以及任何直接 `require` 本包的消费者 |
|
|
130
|
-
|
|
131
|
-
`index.mjs` 只做两件事:先经 ESM 加载器预热 schemastery(`@deepseek-ai/schemastery`,失败退到无 scope 的 `schemastery`,再失败就静默放行),再 `await import('./index.js')` 并原样转出。**`index.js` 一行逻辑都不用改**:它的扫描此时必然成功,自检报告也不会再写。两个约束写死在实现里:
|
|
132
|
-
|
|
133
|
-
- **必须带 `default` 导出**:cordis 的 `unwrapExports()` 是 `exports.default ?? exports`,DSH 挂载的是 `default`,`Config` 必须挂在它上面。
|
|
134
|
-
- **预热必须 best-effort**:宿主上一份 schemastery 都没有时要照旧降级成文档里的 no-op(由 `index.js` 报告),不能连 entry 都加载不了。
|
|
125
|
+
因此**求值期这道竞态并没有被消除,只是被接受**:极少数启动里 `Config` 仍是 `undefined`(无表单、刷新复位),靠下面的负缓存修复把损失限制在单次求值内,靠整进程重启恢复。只有当将来又必须在求值期拿到 builder、而 DSH 的挂载顺序对这道竞态零容忍时,才需要重新评估加回 ESM 入口。
|
|
135
126
|
|
|
136
127
|
**同一分支顺带修掉的第二个缺陷:负缓存。** `SCHEMA_SCAN_CACHE` 是进程级、以 registry Symbol 为键、**永不失效**,而且连失败结果也缓存。于是一次竞态失败被永久记成「本进程没有 schemastery」:插件重新挂载(模块重新求值)也救不回来——这正是「重启后的热挂载同样 `absent`」的原因。现在只有**可用**的扫描结果才入缓存(`cacheableScan`),`take()` 也拒收建不出 schema 的半求值模块(`exports` 还是 `{}` 时是 truthy,否则会被当成候选记下来)。实测:竞态后同进程重新求值仍失败 → 改为「可用才缓存」后,重新求值即可自愈。
|
|
137
128
|
|
|
138
|
-
**验证(真实启动内的对照)。** 同一次 `dsh web`
|
|
129
|
+
**验证(真实启动内的对照)。** 同一次 `dsh web` 启动里同时观察三种取用方式,这是记录当时现象的证据,不是当前入口形态的描述(当前包只有 `index.js` 一个入口):
|
|
139
130
|
|
|
140
131
|
| 探针动作 | 结果 |
|
|
141
132
|
| --- | --- |
|
|
@@ -143,7 +134,7 @@ because it is not yet fully loaded.
|
|
|
143
134
|
| `import('@deepseek-ai/schemastery')`(ESM) | ✅ 成功,`.volatile()` 可用 |
|
|
144
135
|
| 预热**之后**再 `require` 本插件 `index.js` | ✅ `Config` 建出(`nativeMarker: true`) |
|
|
145
136
|
|
|
146
|
-
`test/host-esm-entry.test.js`
|
|
137
|
+
那段对照只能在真实宿主启动里观察到,所以留在这里作为判据;当时用它固化入口行为的 `test/host-esm-entry.test.js` 已随 `index.mjs` 一起删除。
|
|
147
138
|
|
|
148
139
|
### DSH 0.2.0-rc.2 上失效的三处应用侧钩子(v1.1.6 已跟进)
|
|
149
140
|
|
package/docs/features.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
历史内存风格:这些开关最初存浏览器 `localStorage`。由于浏览器存储按「协议 + 主机 + 端口」的 origin 隔离,而 DSH Desktop 每次启动都在 127.0.0.1 绑定一个**随机临时端口**,端口一变 origin 就变,上次保存的设置永远读不到,表现为「重启后恢复默认」。已改用 DSH 官方的用户设置命名空间:
|
|
10
10
|
|
|
11
|
-
- **DSH 0.2.0-rc.2 / 0.1.7-rc.1 及以后(当前)**:**Host 端(index.js)** 导出 schemastery `Config`,
|
|
11
|
+
- **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-已跟进)。
|
|
12
12
|
- **≤ 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` 时自动回落到这条路径。
|
|
13
13
|
|
|
14
14
|
两代的落盘位置都由 DSH 决定(`$DSH_HOME` 或 `~/.dsh/...`),与浏览器 origin/端口无关,因此在 **dsh web(浏览器、固定/默认端口)** 和 **DSH Desktop(随机临时端口)** 两种运行方式下设置都能正确持久化——它们跑的都是 127.0.0.1 loopback 页面,DSH 会把连接解析为 `host` 持久化模式。
|
|
@@ -42,8 +42,10 @@
|
|
|
42
42
|
| 04 娱乐 | 雷霆大字 | 关 | `thunder` |
|
|
43
43
|
| | 大字入场动画 | 关 | `thunderAnim` |
|
|
44
44
|
| | 顶部余额胶囊 | 关 | `balanceCapsule` |
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
| | 渠道额度读数 | 剩余 | `creditDisplay` |
|
|
46
|
+
| | 胶囊位置 | 顶部居中 | `balanceCapsulePosition` |
|
|
47
|
+
| | 侧栏自动隐藏 | 关 | `balanceCapsuleSidebarHide` |
|
|
48
|
+
| | 靠近自动隐藏 | 开 | `balanceCapsuleHoverHide` |
|
|
47
49
|
| 05 音频 | 音频通知 | **关** | `audioEnabled` |
|
|
48
50
|
| | 启动加载动画音 | 开 | `audioBoot` |
|
|
49
51
|
| | 任务开始音 | 开 | `audioTurnStart` |
|
|
@@ -56,6 +58,8 @@
|
|
|
56
58
|
| | 自定义音效目录 | (空) | `audioSoundDir` |
|
|
57
59
|
| | 诊断日志 | 关 | `audioDiag` |
|
|
58
60
|
|
|
61
|
+
开启后默认显示 DeepSeek API 钱包余额与峰谷时段;会话使用 dsh-codearts-auth 渠道模型时自动切换为该渠道的剩余额度与积分消耗(详见「04 娱乐 › 顶部余额胶囊 › 渠道额度模式」)。
|
|
62
|
+
|
|
59
63
|
取值约定(与 localStorage 时代的极性完全一致,现在由 schema 默认值保证):**默认开启**的开关在该 schema 里默认存 `'1'`,客户端读作 `!== '0'`;**默认关闭**的存 `'0'`,读作 `=== '1'`。回滚/清除字段时客户端回到 schema 默认值;异常存储值被 schema 校验拒绝,不会悄悄改变出厂观感。
|
|
60
64
|
|
|
61
65
|
---
|
|
@@ -240,8 +244,7 @@
|
|
|
240
244
|
当当前会话使用的模型来自 [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)时,胶囊自动切换为**渠道额度**显示,随切换渠道实时跟随:
|
|
241
245
|
|
|
242
246
|
- **显示内容**:`渠道名 剩余额度 单位`——渠道名(如 `TRAE`)小字在前,剩余量沿用插件自己的格式(credit 非整数保留两位小数;token 量纲显示 `12.35K` / `12.35M`),单位尾注 `积分` 或 `Token`。多账号取第一个可用账号的 active 额度包求和。
|
|
243
|
-
- **右侧读数可选已用或剩余**:设置面板「04 娱乐 ›
|
|
244
|
-
- **右侧进度条显示积分消耗**:圆环时钟与百分比槽位改为 `已用xx%`——`Σ已用 / Σ总额度`,与左侧数字同源同账号。没有总额度信息时百分比留空、圆环归零,不编造读数。
|
|
247
|
+
- **右侧读数可选已用或剩余**:设置面板「04 娱乐 › 渠道额度读数」可切换右侧百分比的含义——**已用**(`已用xx%` = 已用/总额度,与插件徽章一致)或**剩余**(默认,`剩余xx%` = 剩余/总额度)。右侧圆环与百分比**始终同步**:选「已用」圆环走已用份额,选「剩余」圆环走剩余份额。两种读法都需渠道报出总额度,否则右侧留空、圆环归零——没有总额度的份额无从判断,不编造读数。左侧主数字始终为剩余额度,不受此设置影响。切换在下次取数时生效(不强制额外 RPC,5 分钟取数下限保持诚实)。
|
|
245
248
|
- **不显示峰谷时段**:高峰/低谷是 DeepSeek API 的计费概念,渠道模式下整段时段读数隐藏。
|
|
246
249
|
- **取数与缓存**:走插件自己的管理 RPC(`usage.badge`),复用宿主侧 120s TTL 缓存(失败缓存 15s);主题侧另有**每渠道 5 分钟的取数下限**——余额是慢变量,胶囊是一眼可读的快照,不值得更密的请求。渠道**切换**绕过下限立即跟随;取数失败 30 秒后即允许重试。同一 `--endfield-balance-sweep` 变量在两种模式间切换含义,模式切换零重排。
|
|
247
250
|
- **失败语义**:渠道未登录、插件缺失或上游失败显示 `--`,绝不变灰成 0;切回 DeepSeek 模型则恢复钱包余额显示(DeepSeek 钱包轮询始终在跑,切回即刻有数)。
|
package/docs/glass.md
CHANGED
|
@@ -12,8 +12,8 @@ The composer card deliberately keeps no `backdrop-filter`: a backdrop filter
|
|
|
12
12
|
descendants. The send-button tooltip is fixed-positioned from viewport
|
|
13
13
|
coordinates, so blurring the card resolved the tooltip's coords against the
|
|
14
14
|
shrunk card box — clipping it past the card's right edge and making its
|
|
15
|
-
ResizeObserver re-measure in a loop (visible layout jitter).
|
|
16
|
-
|
|
15
|
+
ResizeObserver re-measure in a loop (visible layout jitter). Keeping the opaque
|
|
16
|
+
tint preserves the glass look at none of the cost.
|
|
17
17
|
|
|
18
18
|
Unsupported backdrop filters use a .96 opaque fill; reduced-transparency uses
|
|
19
19
|
an opaque fill and removes blur. Disabling the theme removes the material
|
package/docs/testing.md
CHANGED
|
@@ -251,23 +251,23 @@ node test/settings-config-fallback.test.js # Host Config 字段契约与选择
|
|
|
251
251
|
node test/settings-namespace.test.js # 存储字段名对齐 schema + 旧拼写迁移 + 三条读/写边界
|
|
252
252
|
node test/settings-off.test.js # 关闭主题后设置页仍可读
|
|
253
253
|
node test/settings-locale.test.js # 跟随语言设置(zh/en 词典对齐 + 切换生效)
|
|
254
|
-
node test/host-esm-entry.test.js # ESM 宿主入口:default 上的转出面 + 无 schemastery 时仍能加载
|
|
255
254
|
```
|
|
256
255
|
|
|
257
256
|
**`settings-rows.test.js`** 不用浏览器也不用 React:以**记录型 `React` / `slots` + 假的设置 transport**(`test/fixtures/settings-scope.js`)在进程内跑一次真实 `apply()`,抓下设置面板真正的元素树。设置页是用户唯一能碰到这些开关的入口,而那里的错误(抛异常、漏 key、开关写错了 DSH 设置的字段)check.js 与画布测试都看不见。
|
|
258
257
|
|
|
259
|
-
> 说明:这个插件从 **`localStorage` 迁移到了 DSH 的持久化设置服务**(见 features.md / engineering-notes.md)。因此设置类测试不再往浏览器存储里塞值,而是驱动假的 transport:除 `settings-config-forms.test.js` 之外的用例走旧世代 `ctx.settingsScope`(fixture 的 `settingsScopeStub`,在内存里扮演 `<settings.yaml>` 的命名字段节),新世代由 `configFormsStub` 扮演 `ctx.configForms`(命名空间 = profile entry id)。断言
|
|
258
|
+
> 说明:这个插件从 **`localStorage` 迁移到了 DSH 的持久化设置服务**(见 features.md / engineering-notes.md)。因此设置类测试不再往浏览器存储里塞值,而是驱动假的 transport:除 `settings-config-forms.test.js` 之外的用例走旧世代 `ctx.settingsScope`(fixture 的 `settingsScopeStub`,在内存里扮演 `<settings.yaml>` 的命名字段节),新世代由 `configFormsStub` 扮演 `ctx.configForms`(命名空间 = profile entry id)。断言 34 行齐全且归入 5 个分组容器、key 唯一、分组标题(01 主题 / 02 背景 / 03 动画 / 04 娱乐 / 05 音频)与配色样式规则都在、配色行默认显示谷地黄且按钮提供「切换武陵青」、点击把 `palette` 写成 `wuling`、存了 `wuling` 时反向提供「切换谷地黄」并标注 `#14d0d0`、图层关闭时子开关为 disabled、开启后恢复可用,雷霆大字与大字入场动画均默认为关、说明文字包含「任务开始」/「任务完成」与 3 秒、**子开关只写自己的字段而不误写主开关的**,以及点击确实写入文档里那个 DSH 设置字段。
|
|
260
259
|
|
|
261
260
|
**`settings-durable-hold.test.js`**(旧世代 `settingsScope` 路径;0.1.7 上同一份写入 gate / 补写契约由 `settings-config-forms.test.js` 覆盖)用**两阶段假 `ctx.settingsScope`** 复现那条历史告警:宿主半部 `ctx.settings.register(...)` 尚未跑、命名空间还没进 Host 的 served 列表前,scope 快照是 `{ status:'unavailable', writable:true, mode:'host' }`——单看 `writable` 会照写不误却落不到盘。它先在未就绪态切「圆角 / 武陵青」,断言**没有任何 `scope.set` 出线**(旧 bug 会打 `commit … status= unavailable` 并静默丢脏);随后模拟文档 committed、命名空间进入 served 列表、快照翻为 `status:'ready'`,断言订阅路径把两份 held 编辑**自动补写**进文档,且不会重复写两遍(replay 有 re-entrancy 护栏)。
|
|
262
261
|
|
|
263
262
|
**`settings-config-forms.test.js`** 守的是 0.1.7-rc.1 换掉整套 settings API 之后最容易「看起来正常、其实没保存」的几处:它用假 `configForms` 服务(fixture 的 `configFormsStub`,只有被 `serve()` 过的命名空间才报 `status:'ready'`)驱动真实 client,断言
|
|
264
263
|
|
|
265
264
|
- **两半的 entry id 一致**:`client.js` 的 `PREFS_ENTRY` == `index.js` 的 `SETTINGS_ENTRY` == `cordis.patch.yml` 里那一行的 `id`(命名空间是 entry id,任一处对不上就全程读不到);
|
|
266
|
-
- **Host `Config` 真的可编辑**:
|
|
265
|
+
- **Host `Config` 真的可编辑**:34 个字段齐全、无多余字段、**每个字段都带 `.volatile()`** 且默认值等于 `FIELD_DEFAULTS`——漏一个 volatile 就是 0.1.7 版的「设置保存不了」(该断言在拿不到 schemastery 的环境里自动跳过,CI 无 DSH 时不会误报——**也正因为会跳过,它没能在唯一要紧的环境里发现问题**:本机 web profile 恰好就是「拿不到可用的 schemastery」这台机器,于是这一整段断言空跑,`Config` 缺失一路绿灯。字段契约与选择顺序现由 `settings-config-fallback.test.js` 无条件断言);
|
|
267
266
|
- **被 served 的表单会被绑定并采纳**(存档里的 `palette: wuling` 直接落到 `<body>` 的 class);
|
|
268
267
|
- **entry id 是探测出来的**:只 served `include:theme-endfield` 时写入也落在那个拼写上;
|
|
269
268
|
- **拒写与未就绪都算 held**:`set()` 解析为 `false`、或命名空间尚未 served 时,编辑不改变文档、也不假装保存;一旦拒写解除(下一次快照)或命名空间进入 served,held 编辑被自动补写;
|
|
270
269
|
- **memory 模式(非 loopback 页面)永不下盘**:一次写都不发生;
|
|
270
|
+
- **0.2.1 快照(没有 `mode` 字段)必须照写**:fixture 的 `mode:'none'` 让快照里**不存在** `mode` 键(`'mode' in snap === false`,与 `@deepseek-ai/dsh-client-ui-settings` 0.2.1-rc.2 删除该字段后的真实形状一致),断言 ready + writable 的编辑直接写到文档。这是 v1.2.2 在 0.2.1 桌面「改了不保存」的回归护栏——旧闸门要求 `mode:'host'`,新版恒为 `undefined`,编辑只会 held 到有界重试超时;
|
|
271
271
|
- **静默 ready**:命名空间开始被服务但镜像**完全不通知**任何表单时,有界 settle watch(20 × 500ms)会自己重新读取、采纳、切到被 served 的拼写并补写——这是旧世代 250 ms binder 轮询的等价安全网;
|
|
272
272
|
- **退订**:run 拆除时调用 `form.subscribe()` 返回的 disposer(`ConfigForm` 是 provider 拥有、跨插件共享的实例,漏掉就是给下一次 run 泄漏监听器)。
|
|
273
273
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-theme-endfield",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "DeepSeek Harness web theme: the industrial editorial look of the Arknights: Endfield official site — cream paper, ink text, signal-yellow accents, zero radius. Settings persist through the DSH volatile Config + configForms transport (0.2.0-rc.2 and 0.1.7+), with a ctx.settingsScope fallback for older hosts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|