dsh-anima 0.0.0-stage → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,1009 @@
1
+ # 更新日志
2
+
3
+ 本文件记录用户可见的变化。格式参照 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
4
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
5
+
6
+ 两套编号不要混:**插件版本**是 `package.json` 里的 semver(本文件记的就是它),
7
+ **存储格式戳**是 `lib/store.mjs` 里 `DOMAIN_VERSION` 的个位数。后者在 1.0.0 与 2.0.0 上都是 **1**,
8
+ 所以从 1.0.0 升级不需要迁移数据。
9
+
10
+ ---
11
+
12
+ ## [2.2.2] - 2026-11-11
13
+
14
+ 纯文档版本:把已经合进 main、却没随任何 npm 版本发出的安装说明修正带上 npm。
15
+ 不改代码、不改存储格式、没有新功能。
16
+
17
+ ### 修复
18
+
19
+ - **npm 发布包的 README 不再自相矛盾**:2.2.1 的包是从 tag b795497 打的,当时的
20
+ 安装段还写着「本插件尚未发布到 npm,从仓库直接装」——可这个包本身已经发在 npm 上。
21
+ 修正(提交 5f5049f:把安装段改成「从 npm 安装(推荐)」并给出按包名安装的命令)是在
22
+ 那个 tag 之后才合进 main 的,所以 2.2.1 包没带上它。2.2.2 把修正后的
23
+ README.md / README_EN.md 重新发布一次。
24
+ - **安装示例的版本号对齐到 2.2.2**:npm 段与 GitHub 段的 pin,以及解释 @ / # 的
25
+ 相邻文字,一起从 2.2.1 / v2.2.1 改到 2.2.2 / v2.2.2。
26
+
27
+ ### 没做
28
+
29
+ - 代码、运行时行为、存储格式(DOMAIN_VERSION 仍为 1)一律不动,2.2.1 升到 2.2.2
30
+ 不需要迁移数据。
31
+ - 讲历史区间的陈述(「从 1.0.0 到 2.2.1 的存储格式相同」等)与 2.2.1 及更早的
32
+ 更新日志原样保留,bump 时不做全量版本替换,避免误伤历史记录。
33
+
34
+ ## [2.2.1] - 2026-11-10
35
+
36
+ 一轮增量 review(按 git diff 逐段读 23 个提交)报出两条,都修掉了。这是纯修复版本,
37
+ 没有新功能,也没有改存储格式。
38
+
39
+ ### 修复
40
+
41
+ - **导入切分不再吃掉小数/版本号的前导数字**:`lib/import.mjs` 的 `LIST_ITEM` 为了修
42
+ `-没有空格的行` 把 `\s+` 放宽成 `\s*`,副作用是 `1.5 版本…`
43
+ `2024.10 的记录…` `3.14 是…` 里的 `1.`/`2024.`/`3.` 被当成列表编号,
44
+ 切出「5 版本之后默认开启暗色」这种少了前导数字的残句且毫无提示。给数字分隔符补
45
+ `(?!\d)`:分隔符后紧跟数字就判成小数/版本号,整行留作普通段落。
46
+ - **删掉无调用的 `Pre` 组件**:`client/index.mjs` 里的 `function Pre(props)`
47
+ 在 2.1.19 把两处等宽正文换成 CodeBlock 之后就零调用,当时「保留着可能别处用」是没核过的
48
+ 假设。`an-pre` 这条 CSS 类仍被 CodeBlock 的 `bodyClass` 复用,保留不动。
49
+
50
+ ### 验证
51
+
52
+ - `verify-core` PASSED all **256**(新增 1 条小数/版本号回归用例,另断正常编号列表项
53
+ 仍切成三条);`smoke-host` 113;`smoke-client` 通过 85 项;`check-client-i18n`
54
+ 305 字面 key + 5 前缀对称;`check-live` 38。
55
+ - 反向验证:用独立脚本去掉 `(?!\d)` 写回坏正则 → 恰好新用例 FAIL、
56
+ `FAILED 1 / 256`;恢复后 `PASSED all 256`。证明用例不是空转。
57
+
58
+ ### 没做
59
+
60
+ - review 里第三条「`lib/import.mjs` 注释把 normalizeCwd 的 trim 说成『死路』措辞含糊」
61
+ 是纯措辞问题、结论本身没错,本轮不改;连同其它历史注释里的版本标记一起原样保留,
62
+ 避免 bump 时全量替换又误伤历史陈述。
63
+
64
+ ---
65
+
66
+ ## [2.2.0] - 2026-11-10
67
+
68
+ 这是把 2.1.2 到 2.1.21 二十个小版本打包后的第一个 minor 版本:功能线(导入修复)
69
+ 与设置页观感线都收口了,按语义化版本升一位。发布说明与下方条目一致。
70
+
71
+ ### 功能:导入外部 AI 记忆(2.1.2,两笔合并)
72
+
73
+ - **导入的记忆终于会生效**:提交阶段补回工作区坐标 `cwd`(原来只有 parse 阶段有,
74
+ commit 丢掉,workspace 级记忆全部变成 `workspace-scope-unknown` 死条,永不注入)。
75
+ - `cwd` 只认查询参数,不认 body 里的 `item.cwd`(可伪造)。
76
+ - 没选定工作区时不再建议「本工作区」(原来会生成全部死条);提交没带坐标时逐条拒绝、
77
+ 不静默降级成全局;界面在没选工作区时把「本工作区」按钮置灰。
78
+ - 被否决过的记忆不再永久挡住重新导入(查重跳过 `discarded` / `dropped`,已采纳仍拦截)。
79
+ - 没有空格的列表项也能切成独立条目。
80
+ - 单条长度上限不再在前端硬编码 500,走 `/state` 下发的 `maxText`。
81
+ - 设置页拉 state 带上工作区坐标,注入解释面板不再把生效中的记忆标成没生效。
82
+
83
+ ### 设置页(观感与结构,2.1.3 → 2.1.21)
84
+
85
+ - **五个分区收成四个**:预设并入人设栏(2.1.5),旧 localStorage 值自动回落,无需迁移。
86
+ - **记忆卡四个清单区可折叠**,默认只展开待审(2.1.3);收起只隐藏不销毁,草稿不丢。
87
+ - **分区导航三代演进**:恢复吸顶(2.1.15,滚下去完全切不了栏)→ 深色带收回正文列
88
+ (2.1.18,`elementFromPoint` 实测推翻 2.1.17 的负 margin)→ 收成短圆角胶囊、
89
+ 四周露出面板底色(2.1.20),tab 间距 2px → 12px(2.1.21)。
90
+ - **等宽正文改成宿主代码块观感**(2.1.19):导入提示词与预览装配结果有圆角、内缩露底、
91
+ 标题栏带复制按钮,底色走 `markdown-code-block` token 亮暗自动跟随。
92
+ - **暗色主题修复**:toast 换语义 token;`label-error/label-warning`(宿主不存在)
93
+ 换成 `state-error/warn`;提示行补基础规则;`**加粗**` 真的加粗。
94
+ - **标题层级收进宿主字号档**、卡片降级成分段、去掉盒中盒、圆角收一档(2.1.6~2.1.8)。
95
+ - **对比度**:提示与示例文字提到 5.80:1(2.1.11)。
96
+ - **内置预设不再画删除按钮、下拉正确标「内置」**(2.1.17,`listPresets` 补双标志 +
97
+ `isBuiltinPreset()` 按 id 兜底);「共 N 项」走词典。
98
+ - **脏态保存栏贴住容器底**,不再悬空 48px(2.1.16)。
99
+ - **a11y**:分区导航左右键 / Home / End 键盘漫游(2.1.3)、`focus-visible` 焦点环、
100
+ 五个只有 placeholder 的输入框补上可访问名(2.1.13)、切分区后滚动位置回到栏首(2.1.14)。
101
+ - **数字框收窄**(`an-num`),「整理时刻」不再占满整行(2.1.4)。
102
+
103
+ ### 兼容性
104
+
105
+ - 存储格式无变化;旧 localStorage 的分区名(`preset`)自动回落到第一栏。
106
+ - 客户端是浏览器侧模块,升级后需刷新页面才生效。
107
+
108
+ ### 验证
109
+
110
+ - `verify-core` PASSED all 255;`smoke-host` PASSED all 113;
111
+ - `smoke-client` 通过 85 项;`check-client-i18n` zh/en 对称;
112
+ - `check-live` PASSED all 38(对运行中的宿主)。
113
+ - 每个小版本各自的反向验证记录见对应段落。
114
+
115
+ ### 没做
116
+
117
+ - 分区数量维持四栏;没动任何后端路由契约;存储格式无迁移。
118
+
119
+ ---
120
+
121
+ ## [2.1.20] - 2026-11-10
122
+
123
+ ### 改进
124
+
125
+ - **分区导航从「横贯整行的深色带」改成一条短圆角胶囊,四周露出面板底色**。用户原话(m05692):
126
+ `这里要改它改成圆角加露出底色然后剪短他的这行整个调整`。
127
+ 2.1.19 及之前 `.an-sectnav` 是整行宽的实心条,四个 tab 之间还压着一条发丝线,
128
+ 真机截图(m05690)里橙圈圈出的就是它。现在拆成两层:外层 `.an-sectnav` 仍是
129
+ 整行宽的 sticky 占位层(与正文列同宽同起点、用面板底色挡住滚上来的卡片),内层
130
+ `.an-sectnav-inner` 才是一条 `display:inline-flex` 的短胶囊 —— 只占内容宽、
131
+ `border-radius:var(--dsw-radius-md,12px)`、四周正 margin 露出父容器底色、底色取宿主的
132
+ `--dsw-alias-markdown-code-block`(亮 `#f9fafb` / 暗 `#1b1b1c`,与代码块同源)。
133
+ 四个 tab 改成 `padding:6px 16px` 的小圆角块,选中态用
134
+ `color-mix(in srgb, ...banner 82%, label-primary)` —— 因为亮色下 code-block 与它的
135
+ banner 是同一个 `#f9fafb`,直接拿 banner 当选中底会完全看不见。
136
+ - **外层整宽占位层是刻意保留的,不是又退回「铺满」**。2.1.15 实测过:滚动 1422px 后
137
+ 导航会跑到 -1304px、中段完全切不了栏(2168px / -482 / -882 / -1304 四个数)。试渲染否决过两条:
138
+ D 用 `box-shadow:0 0 0 100vmax` 铺遮罩会把下方正文一起盖成面板色;E 只靠悬浮投影则
139
+ 右侧滚来的「用职位或昵称」贴着胶囊右缘被切掉半截。G/H 的「整宽不透明占位层 + 内层短胶囊」
140
+ 是唯一同时满足剪短、露底、吸顶可用、不硬切正文的结构。
141
+ - 2.1.17 / 2.1.18 的负 margin 老路继续封死:外层 margin 不许负、padding 不许有 px 值
142
+ (否则深色带又比页头标题、卡片、变更记录各宽 24px,实测 nav 212..800 vs 正文 236..776)。
143
+ - a11y 与功能不减:内层挂 `role="presentation"`,四个 tab 的 `id` /
144
+ `aria-controls` / `aria-selected` / `tabIndex` / 左右箭头、Home/End
145
+ 键盘漫游全部原样保留;`position:sticky;top:0` 不变。
146
+
147
+ - **四个 tab 之间的间距拉开(2px → 12px)**。2.1.20 首版胶囊内 `gap:2px`,实测四个
148
+ tab 盒子之间只有 2px、词与词之间 34px,挤成一条。用户原话(m05986):
149
+ `把这四个人设记忆表现高级分开不要挨得这么紧`。在复刻页量了 6/10/12/14/16px 五档,
150
+ 定 12px:与文件既有间距阶梯对齐;460 窄屏仍折成 2+2 两行、无溢出、吸顶正常;
151
+ 胶囊从 w246 涨到 w276,仍是短条。`smoke-client` 的胶囊用例新增断言 `gap ≥ 8px`,
152
+ 反向验证:改回 `gap:2px` → 恰好这一条 FAIL(84 项通过、FAILED 1);恢复后 85 项全绿。
153
+
154
+ ### 验证
155
+
156
+ - 无头 Edge 用**当前落盘源码**重建复刻页(`ANIMA_SECTION=persona node base.mjs` →
157
+ `page-persona.html`,66175 字节)后实测(`navfinal.cjs`):
158
+ 外层 nav `x236 y118 w540 h48`、内层胶囊 `x236 y124 w246 h40`(确实剪短)、
159
+ 圆角 `12px`;亮色 navBg `rgb(255,255,255)` / 胶囊 `rgb(249,250,251)`、
160
+ 暗色 navBg `rgb(44,44,46)` / 胶囊 `rgb(27,27,28)`(四周露底可见);
161
+ 吸顶后 nav `x236 y54`、inner `x236 y60`,`hitRight` 命中 `an-sectnav`(占位层挡住滚上来的卡片)、
162
+ `insidePill` 命中选中的 tab(仍可点)。截图 `nav-final-light.png` /
163
+ `nav-final-dark.png` / `nav-final-stuck-dark.png` 肉眼确认。
164
+ - 460 窄屏(`navnarrow.cjs`):nav `x456 w200 h82`、inner `x456 w200 h74`,
165
+ 四个 tab 自动折成两行、最后一个 tab 仍在胶囊内(lastTabBottom 227 < innerBottom 231),
166
+ 吸顶后 `y60` 仍粘住;截图 `nav-real-460-dark.png`。
167
+ - `node scripts/verify-core.mjs` → PASSED all 255
168
+ - `node scripts/smoke-host.mjs` → PASSED all 113
169
+ - `node scripts/smoke-client.mjs` → 通过 85 项,PASSED(原「同宽同起点」用例改写成
170
+ 「占位层与正文同起点,内层才是短圆角胶囊」,新增 5 条 CSS 断言 + 3 条 DOM 断言)
171
+ - `node scripts/check-client-i18n.mjs` → 305 个字面 key + 5 个前缀式 key,zh/en 对称
172
+ - `node scripts/check-live.mjs http://127.0.0.1:49720` → PASSED all 38
173
+ - 反向验证:把 `.an-sectnav-inner` 退化成 `display:flex;margin:0;padding:0` 的整行带 →
174
+ 恰好「占位层与正文同起点,内层才是短圆角胶囊」这一条 FAIL、84 项通过、FAILED 1 项;
175
+ 恢复后 85 项全绿。证明新用例不是空转。
176
+
177
+ ### 没做
178
+
179
+ - 没改分区数量(仍是四栏)、没动 tab 文案与图标 —— 用户这轮只要求调整这条的观感。
180
+ - 没在宿主真窗口里刷新后截图:`curl http://127.0.0.1:49720/` 返回 401
181
+ `dsh web authentication required`,客户端 bundle 要用户手动刷新页面才生效。
182
+
183
+ ---
184
+
185
+ ## [2.1.19] - 2026-11-10
186
+
187
+ ### 改进
188
+
189
+ - **设置页里两块等宽正文改成宿主「代码块」的样子**。原先 `an-import-prompt`(导入提示词)
190
+ 和 `an-pre`(AI 实际看到的内容)都是「白底 + 1px 描边 + 8px 圆角」的光板 pre,
191
+ 底色与卡片几乎同色,圆角根本看不出来。新增纯展示组件 `CodeBlock`:
192
+ 圆角 12px、左右各内缩 12px 让父容器底色露出来、顶部一条标题栏(左标签 + 右复制按钮)、
193
+ 正文无 1px 描边、等宽字体、底色直接取宿主的 `--dsw-alias-markdown-code-block`。
194
+ 用户原话(m05335):「圆角加左右都有一部分深色渲染」。
195
+ - 底色用宿主语义 token 而不是写死颜色:亮色 `#f9fafb`、暗色 `#1b1b1c`(即参考图里实测的 rgb(27,27,28)),
196
+ 标题栏取 `--dsw-alias-markdown-code-block-banner`,亮暗自动跟随,不用自己写媒体查询。
197
+ `--dsw-radius-*` 在宿主 token 表里 0 条定义,所以用 `var(--dsw-radius-md,12px)` 带兜底。
198
+ - **功能不减**:导入块的复制按钮从第一步标题行挪进代码块标题栏,仍是 `t('imp.copy')` 文案、
199
+ 仍调 `copyPrompt`(失败提示 `imp.copyFailed` 不变);预览块新增一个「复制」按钮,
200
+ 复用页面已有的 toast 反馈(`setToast`),零新增 useState。两块正文的 `<pre>` 元素与
201
+ `an-import-prompt` / `an-pre` 类名都保留,老测试断言照旧命中。
202
+ - 新增 3 个 i18n 键(zh/en 各 3):`code.prompt`「提示词」、`code.preview`「装配结果」、
203
+ `code.copy`「复制」。
204
+
205
+ ### 验证
206
+
207
+ - 无头 Edge 实测(`_tmp/visui/cb.cjs`,亮/暗各一张):
208
+ 卡片 x248 w516,圆角 `12px`,左右各露 36px 底色;亮色底 `rgb(249,250,251)`、
209
+ 暗色底 `rgb(27,27,28)`、暗色标题栏 `rgb(44,44,46)`;正文 `border:0`、
210
+ `background:transparent`、字体 `Consolas,Menlo,monospace`;标签「装配结果」、复制按钮「复制」都在。
211
+ 截图 `cb-light.png` / `cb-dark.png` 肉眼确认圆角与左右露底都出来了。
212
+ - `node scripts/verify-core.mjs` → PASSED all 255
213
+ - `node scripts/smoke-host.mjs` → PASSED all 113
214
+ - `node scripts/smoke-client.mjs` → 通过 85 项,PASSED(新增 1 条 CSS 用例 + 2 处 DOM 断言)
215
+ - `node scripts/check-client-i18n.mjs` → zh=400 en=400,305 个字面 key + 5 个前缀式 key,对称
216
+ - `node scripts/check-live.mjs http://127.0.0.1:49720` → PASSED all 38
217
+ - 反向验证:把 `.an-codeblock` 的 `margin:10px 12px 12px` 改回 `margin:0` →
218
+ 恰好「代码块:圆角、左右内缩露底…」这一条 FAIL、其余 84 项通过;恢复后 85 项全绿。
219
+
220
+ ### 没做
221
+
222
+ - 没改 `.an-proposal-detail`(提议详情那块等宽文本),它是另一条独立规则,且不在用户指的两块里。
223
+ - 没删 `Pre` 组件:还有别的调用点可能用,删它属于额外动作。
224
+
225
+ ---
226
+
227
+ ## [2.1.18] - 2026-11-10
228
+
229
+ ### 修复
230
+
231
+ - **吸顶分区导航的深色带比正文宽 24px**。2.1.17 为了让底色盖住宿主 `.options` 的左右内边距,
232
+ 加了 `margin:0 -24px;padding:0 24px`,于是导航变成 x212 w588,而页头标题、变更记录、
233
+ 每张卡片都是 x236 w540 —— 肉眼就是「那条带的起始点和下面的内容对不上」。
234
+ - 改回 `margin:0;padding:0`,宽度与正文列一致。
235
+
236
+ ### 为什么 2.1.17 的理由不成立
237
+
238
+ - 当时写的是「左右两条 24px 是没被子盖住的空白,滚过去的卡片从那缝里露出来」。
239
+ 2.1.18 用 `elementFromPoint` 实测:撤掉负 margin 后,那两条 24px 命中 `options`(滚动容器本身),
240
+ 而卡片始终排在内容盒 x236 w540 里,物理上进不去那条缝。
241
+ - 实测数据:改前 `nav 212..800` / `正文 236..776`;改后 `nav 236..776`,与 `an-recent`、`an-card`、
242
+ `an-root`、`an-page-title` 全部同起点同宽。460 窄外壳下同样对齐(nav/card/root 均 x406 w200)。
243
+
244
+ ### 测试
245
+
246
+ - 把 2.1.17 那条「吸顶时底色铺满滚动容器整行」改成 `分区导航:吸顶栏与正文列同宽同起点(不额外铺满容器)`,
247
+ 改断「水平 margin / padding 不许为非零」,正好反过来锁住这次的回归。
248
+ - 反向验证:把负 margin 写回去 → 只这一条 FAIL、其余 83 项通过;恢复后 84 项全绿。
249
+
250
+ ### 验证
251
+
252
+ - `node scripts/verify-core.mjs` → PASSED all 255
253
+ - `node scripts/smoke-host.mjs` → PASSED all 113
254
+ - `node scripts/smoke-client.mjs` → 通过 84 项,PASSED
255
+ - `node scripts/check-client-i18n.mjs` → 302 个字面 key + 5 个前缀式 key,zh/en 对称
256
+ - `node scripts/check-live.mjs http://127.0.0.1:60860` → PASSED all 38
257
+ - 无头 Edge 实测(`_tmp/visui/align.cjs`):800 宽与 460 宽下导航、卡片、页头全部同 x;
258
+ 滚到 700px 后 `elementFromPoint` 打在原来那两条缝上,命中的是 `options` 不是正文。
259
+
260
+ ### 没做
261
+
262
+ - 没动 `.options` 的 24px 内边距(那是宿主的,改了会影响其它设置分区)。
263
+ - 没做「每栏记住自己的滚动位置」(上一轮列过,不在这次范围)。
264
+
265
+ ## [2.1.17] - 2026-11-10
266
+
267
+ ### 修复
268
+
269
+ - **六条内置预设全都画着删除按钮,下拉里还一律标成「自建」**。`lib/presets.mjs` 的 `BUILTIN_PRESETS` 数据里没有 `builtin` 字段,`listPresets` 也只是把内置与自建拼起来、不补标志;而客户端靠这个字段决定「下拉里标内置还是自建」和「要不要给这条画删除按钮」。结果是:六条内置预设全部显示成「自建」、每条右边各一个红色删除按钮,按下去被后端 `/api/anima/presets/delete` 以 400「内置预设不可删除」挡回来 —— 而同一张卡片上方正写着「内置预设不可删除」。两处一起修:`listPresets` 用 map 产出新对象并补 `builtin/custom` 双标志(不就地改常量);客户端新增 `isBuiltinPreset()`,把 `builtin === true` 与 `id.startsWith('builtin:')` 两条判据用 OR 连起来 —— 后者是后端自己拒删时用的判据,宿主侧漏补字段时至少还能按 id 认出来。
270
+
271
+ - **吸顶的分区导航左右各漏出 24px 正文**。宿主给第三方分区的内容列带左右各 24px 内边距(`.options` 宽 588、真正的内容区 540),而导航自身宽 540:吸在顶部时左边那 24px 是没有底色的空白,滚过去的卡片从缝里露出来。平时看不出来只因为导航底色恰好与面板底色同为 #fff / rgb(44,44,46) —— 属于「碰巧同色」,换主题立刻变成三段断带。改法是 `margin:0 -24px; padding:0 24px`:负 margin 吃掉容器内边距、等量 padding 把 tab 推回原位。实测导航 `navX 236→212`、`navW 540→588`,与 `.options` 严丝合缝,四个 tab 起点 236/282/328/374 一格没动;800/620/460 三种宽度下 吸顶后 `elementFromPoint` 仍全部命中 tab。
272
+
273
+ - **「共 N 项」里的「项」是硬编码中文**,英文界面会显示成「6 项」。改为走词典 `preset.count`(zh「{n} 项」/ en「{n} presets」)。
274
+
275
+ ### 验证
276
+
277
+ - `verify-core`:PASSED all 255(新增 1 条:`listPresets` 必须给内置补 `builtin`、给自建补 `custom`,且不许就地污染 `BUILTIN_PRESETS` 常量)。
278
+ - `smoke-client`:通过 84 项(新增 2 条:内置不给删除按钮 + 下拉标签正确;吸顶栏负 margin 与 padding 必须等量成对)。
279
+ - `smoke-host`:PASSED all 113。`check-client-i18n`:302 个字面 key + 5 个前缀式 key,zh/en 对称。
280
+ - `check-live`(http://127.0.0.1:52052):PASSED all 38。
281
+ - **反向验证**(每条新用例都先破坏再恢复):①去掉客户端的 id 前缀兜底 → 只「预设:内置的不给删除按钮」一条 FAIL、其余 83 绿;②去掉 `listPresets` 的补标志 → 只 verify-core 那一条 FAIL;③去掉导航的负 margin → 只「吸顶时底色铺满」一条 FAIL。
282
+ - 渲染实测(`_tmp/visui/nav3seg.cjs`):把导航底色临时换成对比色 rgb(20,40,40),改前左右留白是白的、中间才是底色(三段断带),改后 600px 宽度采样三点全是 rgb(20,40,40)。
283
+
284
+ ### 没做
285
+
286
+ - 没有给预设列表加「内置/自建」的可见分组标题:现在靠下拉与删除按钮的有无来区分,再加一层分组会把 244px 的卡片继续拉长。
287
+ - 没有把 `smoke-client` 里预设夹具的 `builtin: true` 删掉:那条夹具现在用来测「标志位存在时」的正常路径,新用例另外构造了「标志位缺失」的路径 —— 两条路径都要有覆盖。
288
+
289
+ ## [2.1.16] - 2026-11-09
290
+
291
+ ### 修复
292
+
293
+ - **脏态保存栏悬空 48px。** 2.1.15 恢复分区导航吸顶后,用户 m04712 指出「位置有问题」。
294
+ 实测:滚到底时栏底停在 752、滚动容器底 800,中间露出 48px 正文,像一条没落下去的浮条。
295
+ 根因是 sticky 的 `bottom:0` 贴的是**滚动容器的 padding box**,而栏下面压着两层内边距:
296
+ 宿主 `.options` 的 `padding-bottom:24px` 加 `.an-root` 自己的 `padding-bottom:24px`。
297
+ 改成 `bottom:-24px;margin-bottom:-48px`,两层占位一起抵消。实测 gap 0、
298
+ 最后字段底 712 vs 栏顶 748,不遮挡任何控件。两个数耦合宿主内边距,宿主改 padding 需同步调。
299
+
300
+ ### 验证
301
+
302
+ - 无头 Edge 实测(800×800 复刻外壳):`geo.cjs` 脏态 `{"barTop":748,"barBottom":800,"gapToViewport":0,"gapToOptsBottom":0}`;
303
+ `sc4.cjs` `{"barTop":748,"barBottom":800,"maxFieldBottom":712,"overlap":0,"gapToBottom":0}`;
304
+ `cover2.cjs` 亮/暗两套均 `covered:0`,栏底亮 `rgb(255,255,255)`、暗 `rgb(44,44,46)`(与面板同色,无色块)。
305
+ - 新增 smoke-client 用例「吸底保存栏:把内边距一起抵消掉,滚到底时贴着容器底(不悬空)」,
306
+ 断规则里写了负 `bottom` 与负 `margin-bottom`。
307
+ **反向验证**:把这两个负值改回 `bottom:0` → 恰好这一条 FAIL、其余 81 绿;恢复后 82 项全绿。
308
+ - 五套:verify-core 254 / smoke-host 113 / smoke-client **82 项** / check-client-i18n zh/en 对称(301 字面 key + 5 前缀)
309
+ / check-live `http://127.0.0.1:52052` 38 项。
310
+
311
+ ### 没做
312
+
313
+ - 保存栏的负值是按宿主当前 `padding-bottom:24px` 与 `.an-root` 的 24px 算出来的;
314
+ 宿主若哪天改这两处内边距,需要用 `_tmp/visui/geo.cjs` 重新量一次(没有自动探测办法)。
315
+ - 宿主真窗口仍未截图确认(`curl http://127.0.0.1:52052/` 返回 401 `dsh web authentication required`),
316
+ 全部数字来自按宿主 CSS 复刻的 800×800 外壳。
317
+
318
+ ## [2.1.15] - 2026-11-09
319
+
320
+ ### 修复
321
+
322
+ - **滚下去就切不了栏。** 2.1.5 把分区导航的吸顶去掉了,当时的理由是「只有四栏、
323
+ 切换成本低,没必要常驻」。这次量了才看清代价:人设栏内容 2168px、滚动上限
324
+ 1422;`scrollTop=600` 时导航顶边已经到 -482,`=1422` 时到 -1304,
325
+ `elementFromPoint` 打不中任何一个 tab。不是「切起来麻烦」,是中段完全切不了。
326
+ 现在恢复 `position:sticky;top:0`,但保持 2.1.5 定下的无框语言 —— 不回到白底 +
327
+ 1px 边框 + 8px 圆角的「盒中盒」,只用面板自身底色
328
+ `var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))` 把滚过去的卡片盖住
329
+ (实测亮色 #fff、暗色 rgb(44,44,46),与宿主 panel 同色,贴顶时没有色带)。
330
+ 实测四个滚动位置(0/600/1000/1422)`hitIsTab` 全为 true。
331
+
332
+ ### 验证
333
+
334
+ - `verify-core` PASSED all 254;`smoke-host` PASSED all 113;`smoke-client` 通过 81 项
335
+ (新增一条:断 `.an-sectnav` 规则里必须同时有 `position:sticky`、`top:0`,且
336
+ `background` 不能是 `transparent` —— 少了后者虽然还吸着,卡片会从下面透上来);
337
+ `check-client-i18n` zh/en 对称;`check-live` PASSED all 38。
338
+ - **反向验证**:把 `position:sticky;top:0;z-index:20` 从规则里删掉重跑,
339
+ 恰好这一条 FAIL(其余 80 项仍绿),恢复后 81 项全绿。
340
+ - 窄宽度实测(外壳 380/420/460/540/620/700/800 七档):滚到 900px 时导航仍贴
341
+ `top:54`,最后一个 tab 的 `elementFromPoint` 全部命中;外壳 ≤420 时导航换两行
342
+ (36→71px),仍吸住、仍可点。
343
+ - **脏态吸底保存栏不会压住表单**:人设栏滚到底、`scrollTop=1443`(= 上限)时,
344
+ 最后一个字段底边 664、保存栏顶边 700,重叠 0px;保存栏底边距视口底 48px。
345
+
346
+ ### 没做
347
+
348
+ - 每栏各自记住滚动位置(多一份 per-section 持久化,收益不明)。
349
+ - 13px 那档没有收敛(宿主自己也在用 13px,降下去会让 tab 与主按钮掉到正文同级)。
350
+ ## [2.1.14] - 2026-11-09
351
+
352
+ ### 修复
353
+
354
+ - 切换分区后,滚动位置不再留在上一栏的中段。宿主把第三方分区挂在同一个滚动容器里
355
+ (`overflow-y:auto` 的 `.options`),换栏只换内容、不换容器,所以 `scrollTop` 会跨栏
356
+ 残留,而且会被浏览器夹到新栏的最大值。实测:人设栏内容 2168px、滚动上限 1422;
357
+ 记忆栏 1439px、上限 693。从人设栏滚到底再切到记忆栏,`scrollTop` 落到 693,
358
+ 记忆卡顶边变成 -467 —— 一进来直接落在「记忆体检」附近,卡片标题和待审队列全在
359
+ 视野外,看起来像是跳到了一个不认识的页面。现在切栏后把滚动带回该栏开头。
360
+ - 归位的实现不写死 `.options` 这个类名:从新栏首卡往上找第一个「真的能滚」的祖先
361
+ (`scrollHeight > clientHeight + 1` 且 `overflow-y` 是 auto/scroll)。宿主改版换类名
362
+ 也不会失效;拿不到 DOM 时(渲染测试只有假 React)静默跳过,不抛。
363
+
364
+ ### 改进
365
+
366
+ - 补一条用例守接线:用假 DOM 验「切栏后有没有去找新栏首卡、有没有把 scrollTop 归零」。
367
+ 真实浏览器行为另在 `_tmp/visui/scrollsim2.cjs`(跨栏残留)与 `resetsim.cjs`(向上找
368
+ 滚动容器)里单独验过,用例本身只守接线不守布局。
369
+
370
+ ### 没做
371
+
372
+ - 没改「记住每栏各自的滚动位置」。现在是一律回到栏首 —— 少一个状态、少一处持久化;
373
+ 真要「回到上次看到那儿」,得再存一份 per-section 的 scrollTop。
374
+ - 没动首屏。人设栏 2168px、视口 746px,仍然要滚;这一轮只修「切栏后落在错地方」。
375
+
376
+ ### 验证
377
+
378
+ - 五套:verify-core 254、smoke-host 113、smoke-client 80(新增 1 条)、
379
+ check-client-i18n 字典完整 zh/en 对称、check-live 38。
380
+ - 无头 Edge 复刻宿主外壳实测:人设栏滚到 1422 后模拟换栏(只切 display,不动 scrollTop),
381
+ `scrollTop` 被夹到 693、记忆卡 top = -467;`resetScroll` 跑到真实 DOM 结构里
382
+ (`.options` 可滚 + 卡片两层嵌套),`scrollTop` 从 500 归 0。
383
+ - 新用例做过反向验证:把 `setTimeout(() => resetScroll(id), 0)` 整句删掉,用例 FAIL 并
384
+ 报「没有去找新栏首卡对应的元素(查过:[])」,其余 79 项绿;恢复后 80 项通过。
385
+ - 顺带修了这条用例自己的一个坑:断言写在 `setTimeout` 回调里且 Promise 没 return,
386
+ 失败会逃出 `test()` 的 try,套件先报「全部通过」再在进程末尾崩 —— 反向验证时亲眼看到。
387
+
388
+ ## [2.1.13] - 2026-11-09
389
+
390
+ ### 修复
391
+
392
+ - 五个输入框补上可访问名。人设预设卡的「预设名」「说明」、记忆卡「手动添加」的
393
+ 内容/备注/作用域,原来都只有 `placeholder`,没有 id、没有 label、没有 aria-label。
394
+ placeholder 一敲字就消失,读屏软件到那一刻就不知道这个框是干什么的;「手动添加」
395
+ 又是 2.1.10 起默认收起、一展开就是三个无标签输入区的块。现在用 `aria-label` 补上
396
+ (`preset.name` / `preset.descField` / `mem.add` / `mem.addNote` / `mem.addScope`,
397
+ 这些 key 本来就在词典里,只是没接到元素上)。没加可见 label:那一行要和「保存为
398
+ 预设」按钮挤在同一行,加文字会把三者挤回折行(同 2.1.9 的修法)。
399
+
400
+ ### 改进
401
+
402
+ - 补一条用例守这一层:遍历树上所有 input/textarea/select(跳过 checkbox 与 hidden),
403
+ 每个都必须在控件自己或 `label[for]` 上找到名字。用例同时断「扫到的控件数 > 20」,
404
+ 防止选择器坏掉之后「零个未命名」也算通过。
405
+
406
+ ### 没做
407
+
408
+ - 没给 checkbox 接 aria-label。它们的 `<label for>` 一直是对的,自动化扫描从未命中。
409
+ - 没做全量 axe-core / WAI-ARIA 合规审查。这一轮只查了「控件有没有名字」这一件事,
410
+ 对比度是 2.1.11 单独查的,键盘是 2.1.12 单独查的。
411
+
412
+ ### 验证
413
+
414
+ - 五套:verify-core 254、smoke-host 113、smoke-client 79(新增 1 条)、
415
+ check-client-i18n 字典完整 zh/en 对称、check-live 38。
416
+ - 无头 Edge 扫四个分区的可见与隐藏控件:修复前 persona 命中 2 个真实缺口(预设名/说明)
417
+ 加 3 个隐藏的(手动添加),其余三栏各 3 个;修复后四个分区全部 0 命中。
418
+ - 新用例做过反向验证:删掉 `aria-label: t('preset.name')` 那一行,用例 FAIL 并指名
419
+ 「1 个控件没有可访问名:input / 例如「午夜的毒舌」」,其余 78 项全绿;恢复后 79 项通过。
420
+ - 顺带核了 ARIA 连接:4 个 tab 的 `aria-controls` 全部能解析到对应卡片,4 个
421
+ `role="tabpanel"` 的 `aria-labelledby` 全部指向存在的 tab id。
422
+
423
+ ## [2.1.12] - 2026-11-09
424
+
425
+ ### 修复
426
+
427
+ - 预设卡「套用预设」那行在窄面板下不再把按钮甩到第二行。宿主面板宽度是
428
+ `max-width:calc(100vw - 48px)`,shell 缩到 540 时内容区只剩 280px;下拉框
429
+ 用的是 `flex:1 1 240px`,基准 240 让它吃满整行,48px 的按钮被挤到下一行靠左,
430
+ 实测行高 59 → 98。改用基准 0(`an-flexfill`,只参与分配剩余空间),540 / 500 /
431
+ 460 三个宽度实测行高都是 59、按钮 left 一直贴右。
432
+ - 示例词条在极窄面板下不再横向溢出发丝线外。`.an-chip` 原本 `white-space:nowrap`,
433
+ shell 420(内容区 160px)时「研究生在读,方向是自然语言处理…」这类 162px 的长示例
434
+ 溢出容器 2px。改成 `white-space:normal` + `max-width:100%` 后自己折行。
435
+ 正常宽度(540–800)每条示例本来只有几十像素,11 个 `.an-fill` 行高仍是 17px 不变。
436
+ - 键盘换栏不再依赖完整的 DOM。`focusTab` 原来只判 `typeof document === undefined`,
437
+ 但宿主可能只给了部分能力;缺 `document.getElementById` 时会抛错,把整个换栏动作
438
+ 一起带崩。现在缺这个函数就静默跳过带焦点,选中照常切换。
439
+
440
+ ### 改进
441
+
442
+ - 补上分区导航键盘操作的测试。这一层 2.1.3 就实现了(←/→ 循环、Home/End 到两端、
443
+ 漫游 tabIndex、带焦点过去),但 `grep ArrowRight/onKeyDown/Home/End scripts/`
444
+ 命中 0 —— 一年前加的功能一条用例都没看过。新用例断八件事:起始在第一栏、
445
+ → 逐格前进、到底回绕、← 反向回绕、Home/End 到两端、方向键会 preventDefault、
446
+ 未处理的键不许 preventDefault(否则 Tab 焦点出不去)、未处理的键不改选中栏。
447
+ 分区顺序在用例里写死,不跟实现一起读。
448
+
449
+ ### 没做
450
+
451
+ - 没做媒体查询。整个文件仍然只有 `prefers-reduced-motion` 两条 `@media`,
452
+ 窄宽度靠 flex 基准与换行自己折,这是从 2.1.9 起就一致的做法。
453
+ - 没有为 420px 以下再加断点。内容区 160px 时正文已经挤到一行五六个字,
454
+ 那不是排版问题,是面板本身不该那么窄。
455
+
456
+ ### 验证
457
+
458
+ - 五套本地/线上:verify-core 254、smoke-host 113、smoke-client 78(新增 1 条)、
459
+ check-client-i18n 字典完整 zh/en 对称、check-live 38。
460
+ - 无头 Edge 量窄宽度(shell 800/700/620/540/500/460/420):修复前 540 行高 98、
461
+ 420 有 1 个 `.an-chip` 溢出 2px;修复后全部 0 溢出、`.an-rowend` 行高恒为 59。
462
+ - 键盘用例做过反向验证:把 `ArrowRight` 那个分支改成恒假,新用例 FAIL、其余 77 项
463
+ 全绿(说明只有它能看见这个坏法);恢复后 78 项通过。
464
+
465
+ ## [2.1.11] - 2026-11-09
466
+
467
+ ### 修复
468
+
469
+ - 提示、示例、字符计数这一族文字在白底上的对比度不达标。实测 tertiary = `rgb(129,133,140)`,
470
+ 对比度 **3.71 : 1**;它们是 11–12px 的正文字号,WCAG AA 1.4.3 对这类文字要求 4.5 : 1,
471
+ 而同一页的 `.an-subhead`(secondary)是 5.80 : 1。现在整族统一提到 secondary,实测 **5.80 : 1**。
472
+ 涉及 14 条规则:`.an-card-desc` / `.an-hintline` / `.an-fill-label` / `.an-chip` /
473
+ `.an-item-meta` / `.an-empty` / `.an-import-summary` / `.an-kv dt` / `.an-audit-arrow` /
474
+ `.an-recent-caret` / `.an-trail-preview-label` / `.an-card-toggle` / `.an-block-toggle` / `.an-charcount`。
475
+ 字号、位置、元素数量一律未动 —— 收不收是用户的选择,不该拿可读性去换。
476
+ - 输入框占位符此前没有设色,走浏览器默认 `rgb(117,117,117)`,在 `bg-module-platform` 底上
477
+ 实测 **4.26 : 1**,13px 文字同样低于 4.5 : 1。新增
478
+ `.an-input::placeholder,.an-textarea::placeholder{color:var(--dsw-alias-label-secondary);opacity:1}`,
479
+ 实测升到 **5.36 : 1**(底色 #f5f6f7)。
480
+
481
+ ### 没做
482
+
483
+ - 13px 那档没压。宿主的字号直方图(从 `app.asar` 的 `theme.js` 数出来)是 14/12/16/13/11/24/20,
484
+ 13px 是宿主自己也在用的档位,不是自造;而 `.an-sectnav-tab` 与主按钮用 13px 是有意的层级。
485
+ - 页头占位没动。实测 `options` 起点 54 → 页头 58(高 40)→ 分区导航 118(36)→ 变更记录 174(32)
486
+ → 第一张卡 226;746px 视口里 226px 是页头 + 导航 + 变更记录。要压只能删这三者之一,
487
+ 那属于「少功能」,不是这次的范围。
488
+
489
+ ### 验证
490
+
491
+ - 亮色对比度(`_tmp/visui/contrast.cjs`,无头 Edge 读 computed):提示族 3.71 → **5.80**;
492
+ 占位符 4.26 → **5.36**;`.an-subhead` 5.80;`.an-card-title` 18.90 不变。
493
+ - 暗色对比度(`_tmp/visui/cdk.cjs`):提示族 **9.25**、字段标签 **13.34**、输入框正文 **11.57**,
494
+ 全部随 `--dsw-alias-*` 自动切换,没有额外写暗色规则。
495
+ - 四套测试:`verify-core` PASSED all 254 / `smoke-host` PASSED all 113 /
496
+ `smoke-client` 通过 77 项 / `check-client-i18n` zh=299 字面 key + 5 前缀 key 对称。
497
+ - `node scripts/check-live.mjs http://127.0.0.1:52052` → PASSED all 38。
498
+
499
+ ## [2.1.10] - 2026-11-09
500
+
501
+ ### 修复
502
+
503
+ - **卡片收起会删掉正在敲的草稿**。`Card` 的渲染尾巴原先是 `collapsed ? null : props.children`,
504
+ 收起即卸载子树。而卡内所有字段都没有自己的保存按钮 —— 身份、背景、风格是随敲随存到内存,
505
+ 要按底部保存栏才落盘 —— 所以「收一下卡看看别的」等于把刚敲的字扔了。这属于少功能,
506
+ 不是省地方。记忆卡里的子块(`MemBlock`)一直用 `display:none`,卡片这一层当时漏了。
507
+ 现在正文包进 `.an-card-body`,收起只加 `display:none`,子树仍在。
508
+
509
+ ### 改进
510
+
511
+ - 「手动添加」子块改为默认收起。它是记忆卡里一张常驻的空表单(500 字文本框 + 备注 + 作用域 + 提交),
512
+ 实测 333px,占记忆卡正文 1463px 的 23%,而绝大多数时候人是来看结果的。收起同样只是
513
+ `display:none`,点开接着敲,草稿不丢。记忆卡由此 1463px → **1153px**,整个记忆栏 1750px → **1439px**。
514
+ - 四个子块的默认态统一成一条判据:**只有「待审」展开**,因为那是唯一一件「现在就得处理」的事
515
+ (有待审条目时它才出现,出现本身即提醒)。已验证此改动不改变可见按钮总数以外的任何能力。
516
+
517
+ ### 没做
518
+
519
+ - 人设栏首屏仍是 2168px(800px 窗口只露到「助手身份」中部)。要压只能默认收起更多人设卡,
520
+ 而人设卡正好是「打开设置就是来改的」那部分,收起来违背直觉,留待用户指位置。
521
+ - 13px 那一档没压:剩 10 个叶子(4 个分区 tab、变更记录标题、两个 `.an-btn`、一个主按钮、
522
+ 一个 checkbox 文案、两个导入标题)。tab 与主按钮比正文大一号是有意的层级。
523
+
524
+ ### 验证
525
+
526
+ - `verify-core` PASSED all 254;`smoke-host` PASSED all 113;
527
+ `smoke-client` 通过 77 项 / PASSED;`check-client-i18n` 字典完整、zh/en 对称;
528
+ `check-live http://127.0.0.1:52052` PASSED all 38。
529
+ - 无头 Edge 实测四栏内容高:人设 2168 / 记忆 **1439**(改前 1750)/ 表现 1117 / 高级 746。
530
+ - 记忆卡子块实测:待审展开 223px、手动添加收起 0px(正文高度)、已记住收起 0px、卡片 1153px。
531
+ - 测试侧同步改了三处死数字(默认收起数 2 → 3、展开后剩余 1 → 2、`aria-expanded` 数组)
532
+ 与一处取按钮方式(原按「第一个 `aria-expanded=false`」取到的不再是已采纳),
533
+ 并新增一条断言:手动添加收起后它的文本框仍在渲染树里。
534
+
535
+ ## [2.1.9] - 2026-11-09
536
+
537
+ ### 修复
538
+
539
+ - **「人设预设」卡里有两行挤成了两段。** 无头浏览器量出来:`an-row an-rowend` 那一行本该是「下拉 + 套用按钮」并排,实测下拉吃掉 484px、按钮被挤到第二行 42px 之下;`an-row an-rowgap8mt10` 那一行本该是「预设名 + 说明 + 保存按钮」同行,实测按钮掉到第三行孤零零靠左。两个原因叠在一起:
540
+ 1. **外边距被算进了对齐框。** 那一行用 `align-items:flex-end`,而里面的 `.an-field` 自带 `margin-bottom:14px`(那是给上下堆叠的字段留的间距)。外边距进入 flex 的对齐框后,按钮贴着「字段底边 + 14px」对齐,于是比输入框低出一截。加一条 `.an-rowend > .an-field{margin-bottom:0}` 就好。
541
+ 2. **flex-basis 加起来超宽。** 「存为预设」那行两个字段是 `an-flex200` + `an-flex260`,两个 gap 加 87px 的按钮一共 563px,而内容区只有 540px(800px 面板 − 188px 导航 − 48px 内边距),按钮必然折行。换成 `flex:1 1 0` / `flex:2 1 0` 的 `an-flexfill1` / `an-flexfill2`:基础尺寸不再参与折行计算,只按剩余空间分配,于是能同行。
542
+
543
+ 修完实测:预设卡 297px → **244px**,两行都回到单行;人设栏整栏 2221px → **2168px**。
544
+
545
+ ### 改进
546
+
547
+ - **所有可点控件补上 120ms 过渡。** 之前 `transition` 全文件只有一条(变更记录的折叠三角),hover 从无到有是硬切一下的,鼠标扫过一排示例文字时像在闪。现在按钮、示例词、折叠按钮、分区 tab、快捷开关、输入框、取色框统一 `transition:color .12s ease, background-color .12s ease, border-color .12s ease, text-decoration-color .12s ease`,只过渡不触发布局的属性。同时给了 `prefers-reduced-motion` 的关断分支。
548
+ - **两个控件补了缺失的 hover 回执。** 遍历 `document.styleSheets` 实测:`an-quick-toggle`(输入框快捷开关)与 `an-input-color`(取色框)是这套界面里仅有的两个「鼠标移上去毫无变化」的可点元素。前者加底色 hover(1px 边框变化太小,看不出「能按」),后者把边框从 `border-l2` 提到 `border-l4`。实测 `noHover: []`。
549
+
550
+ ### 没做
551
+
552
+ - **首屏还是看不全第一张卡之后的内容。** 高级栏 746px 刚好等于滚动视口,是唯一一屏放得下的分区;人设栏 2168px,800px 窗口滚到底要三屏。要压只能砍字段或默认收起更多卡,都碰「不要少东西」这条线,留着。
553
+ - **13px 那一档仍保留。** 剩下 10 个叶子(4 个分区 tab、变更记录标题、两个次按钮、一个主按钮、一个复选项文案、两个导入面板标题)。tab 与主按钮比正文大一号是有意的层级,压平反而难扫。
554
+
555
+ ### 验证
556
+
557
+ - 无头 Chromium(系统 Edge)按宿主 CSS 复刻 800×800 外壳逐分区实测。四栏内容高:人设 **2168**、记忆 **1750**、表现 **1117**、高级 **746**。带边框/带底元素:22 / 19 / 16 / 5。可见按钮:27 / 21 / 15 / 9 —— 与 2.1.8 完全一致,本轮只动排版与交互反馈,没删任何按钮。
558
+ - 折行检测脚本遍历所有 `display:flex` 容器,按「子元素 left 回退」判真折行:改前人设栏命中两条,改后四个分区均 `[]`。
559
+ - `verify-core` PASSED all 254、`smoke-host` PASSED all 113、`smoke-client` 通过 77 项、`check-client-i18n` zh 299 字面 key + 5 前缀 key 双双对称、`check-live`(对运行中的宿主)PASSED all 38。
560
+
561
+ ## [2.1.8] - 2026-11-09
562
+
563
+ ### 改进
564
+
565
+ - **页面标题不再和卡片标题同级。** 截图实测「灵台」与「人设预设」都是 15px/600,页头看上去就是第一张卡的标题,整页少了最上面那一层。层级改成 16 > 14 > 12:页面 16px、卡片 14px、小节 12px。16 与 14 这两个值不是新造的,是从宿主自己的设置面板里数出来的档位(`general.js` 与 `theme.js` 里出现的字号是 11/12/13/14/16/20/24)。
566
+ - **页头与分区导航之间补一条发丝线。** 两者原来只隔 `.an-root` 的 20px gap,视觉上标题和导航粘成一块。试过给页头加 `border-bottom`,截图后发现它和导航自己的下划线形成两条相距 20px 的平行线,比不加更碎,于是把这条线撤了,只留 `padding-bottom:2px` 的呼吸。这条弯路写在这里,免得下次又想加。
567
+ - **圆角从三档收到一档。** 改前是 6px(输入框/按钮/toast/11 处面板)、8px(徽标、导入面板、光轨预览)、4px(折叠按钮、焦点环)。6px 不在宿主的圆角阶梯上(`--dsw-radius-sm:8px` / `md:12px` / `lg:16px` / `xl:20px`),所以全部收到 `var(--dsw-radius-sm,8px)`。带兜底是因为截图工具的 `tokens-full.css` 里圆角定义在另一段,没取到,宿主万一没注入这一层也不至于退化成直角。
568
+ - **折叠三角不再用 9px。** `.an-recent-caret` 的 ▶ 是字号表里唯一的 9px 孤例(其余是 11/12/13/14/16),9px 的三角渲染出来带毛边,并到 11px 那一档,宽度 10px→12px 保持视觉大小不变。
569
+ - **修正一条过时注释。** 页头那段写着「有未保存改动时给底部多留 72px」,但 72px 那条规则在 2.1.6 就删掉了(实测它会把吸底栏顶到离视口底 96px 悬空),注释跟着改成当前的脏态标记说明。
570
+
571
+ ### 没做
572
+
573
+ - 没把 13px 这一档也收掉。剩下的 13px 只有 10 个叶子:4 个分区 tab、变更记录标题、两个 `.an-btn`、一个 `.an-btn-primary`、一个 checkbox 文案、两个 `.an-import-title`。tab 和主要按钮比正文大一号是有意的层级,全压到 12px 会让它们比正文还弱。
574
+ - 没动首屏。人设栏 2221px,800px 高窗口只露到「助手身份」中部 —— 这是内容量决定的,要压下来只能砍字段或默认收起更多卡片,两条都动到了「不要少东西」。
575
+
576
+ ### 验证
577
+
578
+ - `scripts/verify-core.mjs` → PASSED all 254
579
+ - `scripts/smoke-host.mjs` → PASSED all 113
580
+ - `scripts/smoke-client.mjs` → 通过 77 项
581
+ - `scripts/check-client-i18n.mjs` → zh 396 / en 396 对称
582
+ - `scripts/check-live.mjs` → PASSED all 38
583
+ - 无头 Edge 实测(800×800,宿主面板复刻):四栏内容高 persona 2221 / memory 1750 / behavior 1117 / advanced 683;带边框或底色的元素 persona 22 个、可见按钮 27 个,与 2.1.7 完全一致(只动视觉,没动结构)。字号档位收敛为 11 / 12 / 13 / 14 / 16 五档,圆角只剩 8px 一档(26 个元素)。
584
+ - 暗色实测 computed color:body `rgb(21,21,23)`、面板 `rgb(44,44,46)`、输入框 `rgb(53,54,56)`、正文 `rgb(249,250,251)`、提示 `rgb(173,178,184)`、导航底线 `rgba(255,255,255,0.12)`。输入框靠底色而非描边浮起来,这一版在暗色下仍成立。
585
+
586
+ ## [2.1.7] - 2026-11-09
587
+
588
+ ### 修复
589
+
590
+ 接 2.1.6 的收尾。同样是**只动视觉与文案,没有删任何入口、字段、按钮或档位**。
591
+
592
+ - **分区导航不再是一块盒子。** 2.1.6 把卡片降级成分段之后,那条白底 + 1px 边框 +
593
+ 8px 圆角的横条成了全页最后一块「盒中盒」—— 输入框已经靠底色区分,剩这一个空框
594
+ 横贯整栏,视线会先落在它上面。改成宿主 tab 的语言:无底无框,靠一条 2px 下划线
595
+ 标出选中项,其余三项只靠字重和颜色区分。
596
+ - **下划线与导航的底线对齐。** 第一版用 `box-shadow: inset 0 -2px` 画下划线,
597
+ 截图里它悬在发丝线上方 1px,像一块没对齐的补丁。改成按钮自带 2px 底边 +
598
+ `margin-bottom:-1px` 压住那条 `.5px` 线,两者合成一条。
599
+ - **本栏第一张卡去掉顶线。** 2.1.6 写的注释说「第一张卡不画顶线」,但选择器是
600
+ `.an-card + .an-card` —— 九张卡是**同一个父节点下的兄弟**,只是别的栏被
601
+ `display:none` 藏了。所以没藏起来的任一栏首卡,前面都还有一个隐藏卡,`+`
602
+ 照样命中。实测记忆栏首卡仍有 `border-top`,与变更记录条的下线相距 20px,
603
+ 成了两条平行的发丝线。现在给本栏首卡打 `data-an-firstcard="true"` 覆盖,
604
+ 不再依赖兄弟顺序这个假设。
605
+ - **变更记录条只留下面一条线。** 上面那条紧挨着分区导航的底线,20px 内两条平行
606
+ 发丝线,看上去像忘了删掉的旧分隔。
607
+ - **示例词条常态就有下划线。** 2.1.6 把它们改成无边框文字、下划线只在 hover 出现,
608
+ 在触屏和键盘上完全没有「这里可以点」的信号。现在常态留一条极淡的
609
+ (`border-l3` 那档灰),hover 才换成品牌色。**14 个示例一个没少。**
610
+ - **五个短字段的字数计数挪到右侧。** 2.1.6 给 `Hint` 加了 `count` 属性,但
611
+ `wordingField` 里那五个字段还漏在外面,计数仍用全角空格拼进提示句 ——
612
+ 「身份、技术背景…… 4/300」会被读成一整句。
613
+ - **五个「上限 300」的字段排成一行。** 原来基准 240px,宿主内容区实测 540px,
614
+ `240×3+16×2=752` 放不下,第三个小字段被挤到下一行单独占满,五个摊成三行。
615
+ 新增 `.an-flex160an`(基准 160),`160×3+32=512` 刚好一行。
616
+ - **四条提示再削一遍。** `page.desc`、`identity.nameHint`、`quality.strictHint`、
617
+ `inj.desc` 仍在解释设计(「三件事」「那个在下一张卡里」「而不是关掉整个质检层」
618
+ 「先到哪个算哪个」)。英文词典同步改写。
619
+
620
+ ### 没做
621
+
622
+ - **仍然没有照抄宿主「外观」页的行式布局**(理由同 2.1.6:13 个多行输入框撑不住右对齐)。
623
+ - **没有删功能。** 上列九条全部是「换形状 / 换位置 / 换措辞」。
624
+
625
+ ### 验证
626
+
627
+ - `verify-core` PASSED all 254、`smoke-host` PASSED all 113、`smoke-client` 通过 77 项、
628
+ `check-client-i18n` zh=396 en=396。
629
+ - 无头 Edge 复量(2.1.6 → 2.1.7,`scrollH` 与带边框元素数):
630
+ persona 2372 → **2222**、24 → **22**;memory 1772 → **1748**、22 → **19**。
631
+ 按钮数仍是 27 / 21,**与 2.1.6 完全一致**。
632
+ - 暗色量过 computed color:tab 选中 `rgb(249,250,251)`、未选中 `rgb(97,102,107)`、
633
+ 输入框底 `rgb(53,54,56)`、下划线与导航底线 `rgba(255,255,255,0.12)`。
634
+ - 肉眼比对截图:本栏首卡顶线、变更记录条的双线、tab 下划线的对齐,三处都拍图确认过。
635
+
636
+ ## [2.1.6] - 2026-11-09
637
+
638
+ ### 修复
639
+
640
+ 这一版只动设置页的视觉分层与文案,**没有删任何入口、字段、按钮或档位**。
641
+
642
+ - **输入框不再靠灰线区分,改靠底色。** 宿主亮色下 `--dsw-alias-bg-layer-1/2/3` 全是 `#fff`,
643
+ 卡片、输入框、面板完全同色,整页只能靠一圈 1px 边框说「这里能输入」。实测人设栏有
644
+ 40 个带边框元素、高级栏 105 个,视觉上是一片方框套方框。现在输入区用
645
+ `--dsw-alias-bg-module-platform`(宿主专为「嵌在面板里的控件」准备的底色,亮 `#f5f6f7` /
646
+ 暗 `bluish-800`),边框退成透明,hover 时才回来。
647
+ - **卡片从「盒子」降级成「分段」。** 卡片原本是白底 + 1px 边框 + 8px 圆角 + 16px 内边距,
648
+ 四张叠起来就是四个同心框,外面再套输入框的边框,一共三层。改成无底色、无边框、
649
+ 无圆角,只靠一条 `.5px` 发丝线和间距分组 —— `.5px` 是宿主自己容器的口径
650
+ (`AppearanceRow` / `FontSizeRow` 都是 `border-bottom:.5px solid`),1px 在 2x 屏上会糊成 2px。
651
+ - **次要按钮、徽标、收起按钮、示例词条全部降权。** 「收起 / 展开 / 改元数据 / 记住它 / 丢弃」
652
+ 这类动作原来都是「白底 + 1px 边框 + 6px 圆角」的小方块,一屏二十几颗,分不清主次。
653
+ 现在只有主按钮(保存 / 套用 / 手动添加 / 记住它)和危险按钮(删除)保留形状,其余靠
654
+ 文字本身 + hover 底色表达可点。徽标从「描边」改成「浅底药丸」,读起来像标签而不是按钮。
655
+ 示例词条从带边框胶囊改成 11px 下划线文字,中间用「·」隔开 —— 人设栏一共 14 个、350px 高、
656
+ 占该栏 16%,压在 13 个字段之间,是「满屏可点方块」最主要的来源。**14 个示例一个没少。**
657
+ - **文案去掉写给开发看的那半句。** `identity.desc` 原为「描述 AI 自己是谁。与下面『用户背景』
658
+ 方向相反 —— 那段描述你,这段描述它。」,`user.desc` 原为「描述你本人。独立成段是为了不与
659
+ 上面的『助手身份』混读 —— 两段语义方向相反。」,`style.desc` 原为「这几项决定『怎么说』,
660
+ 与回答内容无关……」。三句都在解释「为什么这样设计」,而不是「这个框让你填什么」。
661
+ 另外把四处「。上限 N 字。」从提示行删掉 —— 同一行的末尾已经有 `0/800` 计数,
662
+ 上限说两遍。英文词典同步改写。
663
+ - **字数计数从提示行拆到右侧。** 原先提示与计数用全角空格拼成一句(「身份、技术背景……
664
+ 0/500」),一句被拉成两截,要多扫一次才知道 `0/500` 不是那句话的一部分。
665
+ 现在 `Hint` 支持 `count` 属性,计数靠右独立成列,用满时变警示色。
666
+ - **短字段的多行框降到两行高。** 上限 300 的五个字段(标题和列表 / 表情符号 / 表格 /
667
+ 回复长度 / 回复语言)实际填的是「增强」「简体中文」这种两四个字,而 88px 最小高度
668
+ 原本是给 2000 字的补充指令准备的,五个空格子加起来 440px。≤300 的走
669
+ `.an-textarea-short`(56px),2000 字的补充指令仍是 88px。
670
+ - **标签与卡片标题的层级理顺。** 字段标签从 `13px/500/次要色` 改成 `12px/600/主要色`
671
+ (同字号但更重更暗,靠字重分「标签 vs 正文」,原来它比正文大一号却更淡);
672
+ 卡片标题 14px → 15px 并加 `-.01em` 字距;多列字段排之间补 14px 纵向间距
673
+ (原本排与排之间是 0,「填入 增强」紧贴下一排的「回复长度」)。
674
+ - **保存栏在干净态不再是一个描边盒子。** 卡片都降级成分段了,页尾突然冒出一个有框的条
675
+ 会显得比正文更重。现在干净态只有一行字 + 两个按钮;有未保存改动时才加底色、顶线并吸底。
676
+ - **删掉一条有害的 `padding-bottom:72px`。** 2.1.3 为「吸底栏会压住最后一行」加过
677
+ `.an-root[data-an-dirty="true"]{padding-bottom:72px}`。实测这个理由不成立:sticky 的粘住位置
678
+ 是按滚动容器的 padding box 底边算的,这 72px 会把吸底栏整体顶上去 —— 滚到底时保存栏
679
+ 离视口底 96px(72 + 24 的 options 内边距)悬在半空,下面留一截空白。删掉后实测
680
+ 底部间距 48px(只有 options 自身的 24 + 一点余量)。
681
+
682
+ ### 没做
683
+
684
+ - **没有照抄宿主「外观」页的行式布局。** 那套「标题在左、控件在右、行间一条下划线」的前提
685
+ 是每行只有一个短控件;本页有 13 个多行输入框,右对齐会把内容区压到不足 300px。
686
+ - **没有删任何功能。** 示例词条、每张卡的收起按钮、记忆卡的四个子块、字段级提示、
687
+ 字数上限提示(换了位置)全部保留。
688
+
689
+ ### 验证
690
+
691
+ - `verify-core` PASSED all 254、`smoke-host` PASSED all 113、`smoke-client` 通过 77 项、
692
+ `check-client-i18n` zh=396 en=396、`check-live`(http://127.0.0.1:52052)PASSED all 38。
693
+ - 用无头 Edge 逐个分区量(改前 → 改后,`scrollH` 与带边框元素数):
694
+ persona 2581 → **2372**、47 → **24**;memory 1838 → **1772**、36 → **22**;
695
+ behavior 1245 → **1157**、27 → **19**;advanced 746 → **746**、13 → **8**。
696
+ 按钮数四个分区都是 27 / 21 / 15 / 9,**改前改后完全一致** —— 降的是视觉权重,不是元素。
697
+ - 干净态与脏态分别验证过:干净态 `position:static`、无底色、无阴影;
698
+ 强制脏态后 `position:sticky`、有底色与顶线、滚到底贴住底部。
699
+ - 暗色逐项量过 computed color:body `rgb(21,21,23)`、输入框 `rgb(53,54,56)`、
700
+ 正文 `rgb(249,250,251)`、提示 `rgb(173,178,184)`、危险 `rgb(242,90,90)`、徽标底 `rgb(53,54,56)`。
701
+
702
+ ## [2.1.5] - 2026-11-09
703
+
704
+ ### 改进
705
+
706
+ 这一版只动设置页的分区划分,不删任何入口、不动任何存储字段。
707
+
708
+ - **「预设」并入「人设」栏,五栏变四栏。** 预设那一栏只有一张卡,而它装的东西就是
709
+ 「把当前这套人设存成一组」—— 和人设是同一件事的两面。独立成栏的结果是:想存一套
710
+ 现成的配置,得先切到「预设」,套完再切回「人设」继续改,来回两次。
711
+ 合并后顺序是 预设 → 身份 → 背景 → 风格,读起来是「先套一套现成的,再按自己的
712
+ 情况微调」,这也是大多数人实际的动手顺序。
713
+ - **旧用户的 localStorage 不需要迁移。** 上次停留的栏名存的是裸字符串,老值
714
+ `'preset'` 在新表里不存在,由 `readActiveSection` 的脏值兜底自然回落到第一栏。
715
+ 这条专门加了一个断言(把 `'preset'` 塞进脏值列表一起测)。
716
+ - 清掉两处注释里过时的「五栏」说法,删掉变成孤儿的 `section.preset` 词典键
717
+ (zh/en 各一条,词典从 397 降到 396,仍然 zh/en 对称)。
718
+
719
+ ### 没做
720
+
721
+ - 没有把任何卡片或字段删掉或藏起来 —— 九张卡一张不少,只是分区归属变了。
722
+ - 没有加「记住每栏滚动位置」之类的增强。宿主只挂载当前分区,切栏本来就会回到顶部,
723
+ 要改得动宿主的滚动容器,不在这一版的范围里。
724
+
725
+ ### 验证
726
+
727
+ - `verify-core` PASSED all 254;`smoke-host` PASSED all 113;
728
+ `smoke-client` 通过 77 项 + PASSED;`check-client-i18n` zh=396 en=396 PASSED;
729
+ `check-live http://127.0.0.1:52052` PASSED all 38。
730
+ - 无头 Edge 实测渲染:`.an-sectnav-tab` 4 个(人设/记忆/表现/高级),
731
+ `data-an-card="preset"` 的 `data-an-section` 为 `persona`、
732
+ 且在人设栏里排在 identity/user/style 之前;人设栏内容高 2581px
733
+ (preset 328 / identity 450 / user 493 / style 981)。
734
+
735
+ ---
736
+
737
+ ## [2.1.4] - 2026-11-09
738
+
739
+ ### 修复
740
+
741
+ 这一版只改设置页的字体与排版,不动任何存储字段、不动任何判据、不删任何一个入口。
742
+
743
+ - **小节标题与按钮撞词。** 「体检」那一小节的标题和右边的按钮都用 `checkup.run`,
744
+ 渲染出来是「体检体检」;导入那一块同样是「导入外部 AI 的记忆」写两遍。新增
745
+ `checkup.title`(记忆体检)与 `imp.title`(从别的 AI 搬过来)两个键,标题与
746
+ 动作名分开。
747
+ - **卡片内小节标题两种字号并存。** 可折叠块用 `.an-block-title`(13px),其余小节用
748
+ `.an-subhead`(12px),同一张记忆卡里六个同级小节实测出了两种大小。现在两者统一
749
+ 成 12px —— 层级靠「小节 12 < 卡片标题 14」拉开,不靠同类小节互相比较。
750
+ - **单数字输入框吃满整行。** 「整理时刻(0-23 时)」那格实测宽 506px(占卡片宽的 94%),
751
+ 里面只填一个「3」。新增 `.an-num{max-width:158px}` 用在四个纯数字框上,与旁边
752
+ 三个预算框等宽对齐。
753
+ - 上一版遗留:宿主 header 里没有任何标题文字(`settings.action` 槽 + 关闭按钮而已),
754
+ 截图工具此前在壳里自己写死了一行「灵台 · Persona & Memory」,已删。
755
+
756
+ ### 验证
757
+
758
+ `verify-core` PASSED all 254 / `smoke-host` PASSED all 113 / `smoke-client` 通过 77 项 /
759
+ `check-client-i18n` zh=397 en=397 PASSED。截图(自建渲染管线,非宿主实拍)重跑后
760
+ 五分区内容高:persona 2237 / preset 609 / memory 1838 / behavior 1245 / advanced 715。
761
+
762
+ ## [2.1.3] - 2026-11-09
763
+
764
+ ### 改进
765
+
766
+ 这一版只动设置页的排版,不改任何存储字段、不改任何判据。原因:宿主把第三方分区
767
+ 塞进一个 800px 宽、最高 800px 的设置面板里,左边已固定占掉 188px 导航、右边留
768
+ 24px 内边距,**实际可用内容宽约 564px、可视滚动高约 722px**(这组数是按宿主
769
+ `dsh-client-ui-settings-general` 的 CSS 量出来的推断值,没截图量过)。而灵台这张
770
+ 设置页有九张卡、约 1200 行渲染代码,其中记忆卡一张就占四分之一。
771
+
772
+ - **两条常驻栏不再同时吃视口**。原来的分区导航 `position:sticky;top:0` 与保存栏
773
+ `position:sticky;bottom:0` 在 722px 的滚动窗口里常驻约 76px(≈10%)。现在导航
774
+ **不再吸顶**(只有五栏、切换成本本来就低,吸顶收益抵不上它吃掉的高度);
775
+ 保存栏**只在有未保存改动时才吸底**,干净时它留在页面末尾当普通一行 ——
776
+ 干净时它也没东西可保存,钉在那里只是占位。底部为吸底预留的 72px 内边距也只在
777
+ 脏态才给。
778
+ - **记忆卡里的四个清单区可折叠**:待审、手动添加、已采纳、已丢弃各有一个折叠按钮,
779
+ 默认「要动手的展开(待审、手动添加)、只核对的收起(已采纳、已丢弃)」。
780
+ 收起是 `display:none` 而**不是卸载子树** —— 子树里有未提交的草稿(手动添加的
781
+ 输入框、导入面板解析出来的预览),卸载等于把用户刚敲的字扔掉。折叠态与卡片折叠
782
+ 共用同一份 `localStorage`(键带 `block:` 前缀),刷新后记得住。
783
+ - **窄窗口下的字段折行**。三个并排的数字框原来挤在 300px 内容宽里会被压到不足
784
+ 90px,现在按 `flex-wrap` 自然折行。这条沿用本文件既有的「不用媒体查询」做法。
785
+ - **分区导航补上键盘操作**:左右方向键换栏、`Home`/`End` 跳到首末栏,焦点跟着
786
+ 一起走(只改选中不改焦点,下一次按 Tab 的落点会跳得很意外)。补了 `aria-controls`
787
+ (指到本栏第一张卡)与 `role="tabpanel"` / `aria-labelledby`,tab 用漫游
788
+ `tabIndex`(选中项 0,其余 -1)。
789
+
790
+ ### 没做
791
+
792
+ - 自建左栏导航、双列栅格、给内容加 `max-width` 居中:这三条在量完宿主面板之后都
793
+ 不成立 —— 宿主导航已占 188px,内容列本来就只有约 564px,再分栏只会更窄。
794
+ - 把五个分区合并成四个:会改变用户可见的页面结构,需要单独确认。
795
+
796
+ ### 验证
797
+
798
+ `verify-core` **254**、`smoke-host` **113**、`smoke-client` **77**(+1:记忆卡四个
799
+ 子块的默认态、收起不卸子树、折叠态落盘)、`check-client-i18n` zh **395** / en **395**。
800
+
801
+ 新加的那条 client 用例中途失败过三次,每次都是断言本身写错(默认收起态应有 4 个子块
802
+ 但夹具里 `discarded` 是空的;`block:adopted` 存的是「收起没有」而不是「展开没有」;
803
+ 卡片收起数被前一条用例翻过不能死写 1)—— 不是实现错。这三次都记在这里,因为
804
+ 「用例红了先怀疑实现」差点让我把正确的代码改坏。
805
+
806
+ ---
807
+
808
+ ## [2.1.2] - 2026-11-09
809
+
810
+ ### 修复
811
+
812
+ - **导入的记忆终于会生效了(这条是主 bug)**。导入分两步走:`parse` 把粘贴内容切成
813
+ 候选并给出「全局 / 本工作区」建议,`commit` 把用户确认过的那批落进待审缓冲区。
814
+ 旧实现的 `parse` 会把工作区坐标写进候选记录,`commit` 阶段却重新调了一次
815
+ `makeCandidate()` 而**没带 `cwd`** ⇒ 落库记录没有 `cwd` 字段 ⇒ `matchLayer()` 判成
816
+ `workspace-scope-unknown` ⇒ 用户明明采纳了,这条记忆却永远不会被注入,而界面上
817
+ 「已导入 N 条」一切正常。现在提交与解析走同一口径(`?cwd=` 查询参数),
818
+ workspace 级条目落库时带上归一化后的坐标。
819
+ - **提交阶段的 `cwd` 只认查询参数,不认 body 里的 `item.cwd`**。`cwd` 决定「这条记忆
820
+ 在哪些工作区生效」,而 body 是请求方自己填的 —— 让它自报坐标等于让它自己划范围。
821
+ - **没选定工作区时,不再建议「本工作区」**。旧实现只看措辞(「这个项目」「本项目」…)、
822
+ 不看有没有坐标,于是没选工作区的用户会得到一批 `workspace` 建议,落库全是死记录。
823
+ 现在 `suggestLayer()` 收 `hasCwd`:没坐标时降级为全局、置信度 `low`,理由里写明缺工作区;
824
+ 设置页把「本工作区」按钮置灰并给出提示;宿主对「选了 workspace 但没带坐标」的提交
825
+ **逐条拒绝并说明原因**,而不是静默降级成全局 —— 降级会改变它生效的范围,那是用户的
826
+ 决定,不是服务端的。
827
+ - **被否决过的记忆不再永久挡住重新导入**。查重指纹表原来看全量记录,一条已经
828
+ `discarded`(或 `state: 'dropped'`)的记忆会让用户永远重导不进来,而界面只显示「重复」。
829
+ 现在只拿「还活着」的记录做指纹;已采纳的仍然参与查重(那条行为没错)。
830
+ - **没有空格的列表项也能切成独立条目**。`LIST_ITEM` 原来要求列表符号后必须跟空白,
831
+ `-没有空格的补充说明` 会被并进上一行,切出一条「1、xxx -没有空格的行」的缝合怪。
832
+ - **单条长度上限不再在界面上硬编码 500**。真实上限是 `MEMORY_TEXT_MAX`,
833
+ `/state` 现在把它作为 `memoryImport.maxText` 下发,界面读它。原先这是两份独立数字,
834
+ 改了一处另一处不会跟着动。
835
+ - **设置页的注入解释面板不再拿错答案**。`loadState()` 原来打的是裸
836
+ `/api/anima/state`,而宿主用 `?cwd=` 决定「按哪个工作区算注入」——不带就是按
837
+ 「没有工作区上下文」算,于是一条**正在生效**的工作区记忆会被标成
838
+ `no-workspace-context`,面板指着它说「这条没进去」。现在拉 state 带上当前工作区,
839
+ 且 `loadWorkspace()` 改完坐标会重算一遍(空路径也重算 —— 那是「回到全局」,
840
+ decisions 同样要变)。这个错以前看不出来,因为工作区记忆根本落不了库(上面那条
841
+ 主 bug),没有工作区条目可解释。
842
+
843
+ ### 验证
844
+
845
+ `verify-core` **254**(+3:无 cwd 不给 workspace 建议 / 被丢弃的不参与查重 / 无空格列表项)、
846
+ `smoke-host` **113**(+2:提交必须带 cwd 且 global 不带 / 没 cwd 时逐条拒绝;
847
+ 原 commit 用例补了 `cwd` 断言)、`smoke-client` **76**(+2:没工作区时按钮置灰、
848
+ 预览里挂着的 workspace 显示成全局、提交不带空 cwd 参数;拉 state 必须带工作区坐标;
849
+ 原用例补了"提交必须带 cwd")、`check-client-i18n` zh **395** / en **395**。
850
+
851
+ 这四套以前全都不断言 `cwd`,所以这个 bug 一直跑绿 —— 这也是本轮每条新用例都盯
852
+ 「坐标有没有落下去」的原因。新加的那条 client 用例做过反向验证:把 `?cwd=` 去掉后
853
+ 它确实 FAIL(报「拉 state 没带工作区坐标」),装回来才 ok。
854
+
855
+ ---
856
+
857
+ ## [2.1.1] - 2026-11-08
858
+
859
+ ### 修复
860
+
861
+ - **没有新线索的轮次不再清空上一批明细**。旧实现每次写 `leads: 新的一批 or null`,
862
+ 于是用户第二次点「回扫一次」之后,界面上原本找到的线索全不见了 —— 读起来像
863
+ 「上次找到的都丢了」。它们其实还在 `leadKeys` 里、也不会重报,只是不再显示。
864
+ 现在没有新线索时保留上一批明细,并新增 `meta.retrospect.leadsAt` 记录**这批明细是
865
+ 哪一轮找到的**(与 `scannedAt` 分开:扫描时刻每轮都新,明细时刻可能很旧),
866
+ 界面据此显示「这批线索是 X 那轮找到的;这轮没有新的,所以沿用」。
867
+
868
+ ### 验证
869
+
870
+ `verify-core` **251**、`smoke-host` **111**、`smoke-client` **74**、
871
+ `check-client-i18n` zh **394** / en **394**,四套全绿(其中 smoke-client 与 i18n
872
+ 各加 1 项,守住「旧明细必须说清是哪一轮找到的」)。
873
+
874
+ ---
875
+
876
+ ## [2.1.0] - 2026-11-08
877
+
878
+ ### 新增
879
+
880
+ - **回扫式复盘**(`lib/retrospect.mjs`、`lib/sessions.mjs`):回头扫最近 N 天的会话日志,
881
+ 挑出用户当时在纠正 / 抱怨 / 明说要记 / 明说禁令的句子,产出**带出处的线索**。
882
+ 三条硬规矩:只产线索不写记忆库、无论有没有线索都写一条 `meta.retrospect`、
883
+ 默认关闭(它要读磁盘会话日志,是新开的口子)。
884
+ 入口四个:`/anima 复盘`、`POST /api/anima/retrospect`、设置页「回扫一次」按钮、
885
+ `/api/anima/state` 的 `retrospectMeta` 下发;打开开关后还会随夜间定时与启动补跑自动跑。
886
+ - 新配置项 `memory.retrospectEnabled`(默认 `false`)与 `memory.retrospectDays`(默认 14,区间 1–365)。
887
+ 默认关闭的理由与开关必须真有读取点:关着时接口明确回「已关闭」且**不写任何记录** ——
888
+ 「被关掉」和「跑了没成」混进同一条记录,以后看到「上次回扫失败」会以为坏了。
889
+ - `scripts/probe-retrospect.mjs`:只读探针,用真实语料复核判据命中率。本机 53 个会话 /
890
+ 593 条真实发言实测产出 9 条线索(complaint 2 / explicit 0 / correction 7 / constraint 0),
891
+ 全量扫描约 3 秒。
892
+
893
+ ### 变更
894
+
895
+ - `scripts/zstd-frames.mjs` 从实体实现改为对 `lib/sessions.mjs` 的纯 re-export。
896
+ 原因:`package.json` 的 `files` 数组不含 `scripts/`,实现留在那里等于「开发时能跑、装好就坏」。
897
+
898
+ ### 修复
899
+
900
+ - `lib/store.mjs` 的 `configSchema.memory` 补上 `retrospectEnabled` / `retrospectDays`。
901
+ 缺这两个字段不会报错,而是**设置页保存成功、刷新后又变回关闭** —— zod 读回时把它们静默剥掉,
902
+ 写入路径却照落盘,于是 POST 返回 200 而 GET 读不到。与 2026-10-24 那次 `metaSchema` 是同一形状。
903
+ - `metaSchema` 的 `files` / `messages` / `found` / `leadsDropped` 允许 `null`。回扫失败的轮次
904
+ 这几个值**未知**,写成 0 就变成「扫了 0 个会话、0 条发言、0 条线索」,
905
+ 与「扫过没事」长得一模一样。
906
+
907
+ ### 变更(定时)
908
+
909
+ - 回扫接进**夜间定时**与**启动补跑**,与巡夜各自判自己的开关和日期。
910
+ 曾经把它写在 `nightlyTriage` 分支里 —— 「关掉巡夜」会连带让回扫永远不跑,
911
+ 而设置页里回扫是单开的。`verify-core` 加了一条源码级断言守住「回扫有自己的开关读取点」。
912
+
913
+ ### 验证
914
+
915
+ `verify-core` 234 → **251**、`smoke-host` 103 → **111**、`smoke-client` 69 → **73**、
916
+ `check-client-i18n` zh 377 → **393** / en 377 → **393**,四套全绿。
917
+
918
+ ---
919
+
920
+ ## [2.0.0] - 2026-11-07
921
+
922
+ 首个完整发布版。1.0.0 是发布前打的标签,只有人设与记忆的骨架;2.0.0 补上了回答质检、
923
+ 记忆衰减、巡夜、记忆体检四层能力,并做了一轮完整的外部代码审查。
924
+
925
+ ### 新增
926
+
927
+ - **回答质检层**(`lib/quality.mjs`):把十二条判据固化进提示词,并区分「机器能判」与
928
+ 「只有人能干」。机器判不了的(答非所问、假装忘记、选择性呈现)照样列出来,
929
+ 但标成 `catchMode: 'human'` 并在回报里写清 —— 检查器自己先不许谎报。
930
+ 配套脚本 `scripts/audit-response.mjs` 做事后验证,模型无权调用它决定何时检查自己。
931
+ - **对账块**:回答里出现数字 / 自查类断言 / 归属类引用时,末尾必须追一个三行的
932
+ 【对账】块(查了什么、没查什么)。触发条件写在 `lib/quality.mjs`,不靠自觉。
933
+ - **记忆衰减**(`lib/decay.mjs`):按日历算一条记忆几岁了、有没有超出它那一类的时间窗。
934
+ 超窗的只做两件事:注入行尾部加字面标签、在注入排序里排到最后。**记录一个字节都不改。**
935
+ - **巡夜**(`lib/patrol.mjs`):到点自己醒,有需要汇报的才说话,没有就不说。
936
+ 堵法:**汇报可以不发,巡夜记录必须留** —— 否则「沉默」会同时代表「查过没事」和「压根没跑」。
937
+ - **记忆体检**(`lib/memory-audit.mjs`):只做机械能验的那一半 —— 记忆里写的路径还在不在、
938
+ 复核点过没过。答案来自文件系统与日历,不是来自模型的判断,所以每条结论都能拿命令复现。
939
+ - **`remember` 工具**:把值得长期保留的信息投递到待审缓冲区。**投递不等于生效**,
940
+ 要用户在设置页采纳后才进长期记忆。
941
+ - **注入消息自持 producer id**:会话格式 v4 要求注入消息自带身份,`lib/injection.mjs` 已对齐。
942
+ - **两套编号的界面口径**:格式戳一律写「存储格式 vN」,插件版本一律带小数点,
943
+ 免得用户分不清哪个数字在说他装的插件旧。
944
+
945
+ ### 变更
946
+
947
+ - **版本号 1.0.0 → 2.0.0**。存储格式未变(`DOMAIN_VERSION` 仍是 1,四张表 spec 逐字一致),
948
+ 从 1.0.0 升级不需要数据迁移。
949
+ - **发布物去除所有第三方项目名**:代码注释、README、脚本里的 71 处改为中性表述
950
+ (「某同类人设插件」「某记忆插件」等)。**这只是改称呼,不改事实** ——
951
+ 机制来自谁、依据是哪一行,在 `docs/peers/` 的调研记录里仍可逐条核对。
952
+ - **逐字引用改为复述**:`lib/store.mjs` 与 `lib/prompt.mjs` 里原先逐字摘抄了同生态项目的
953
+ 中文注释,改为用自己的话复述并指明出处位置。
954
+ - **示例昵称去个人化**:界面占位符里的具体人名改成通用示例。
955
+
956
+ ### 修复
957
+
958
+ 这些是外部代码审查(按 ocr 的 TypeScript/JavaScript 规则文档逐条判)查出来的:
959
+
960
+ - **27 处嵌套三元**(`client/index.mjs` 15、`scripts/acceptance.mjs` 5、`test/run-m0.mjs` 3、
961
+ `index.mjs` 1、`lib/injection.mjs` 1、`lib/memory.mjs` 1、`scripts/review.mjs` 1)——
962
+ 规则原文「Nested ternary expressions are not allowed」。全部改为扁平化:条件提前算进变量,
963
+ 或用 `if` 链。`lib/memory.mjs` 的优先级顺序(disabled > count > chars > error > none)现在是函数体的
964
+ 语句顺序,读起来比原表达式清楚。
965
+ - **3 处非严格相等** —— 规则原文 `==` / `!=` 一律禁止。关键是 `acc == null` 的语义是
966
+ null ∪ undefined,直接换成 `=== null` 会在中间键缺失时抛 TypeError,所以两处都写了显式判空。
967
+ - **65 处静态 inline style** —— 规则原文只豁免动态样式。全部搬进 26 个 CSS 类;
968
+ 剩下 1 处 `background: color` 是真动态,按规则保留。
969
+ - **1 处硬编码默认 URL** —— `scripts/backfill-types.mjs` 原写着 `http://127.0.0.1:54537`。
970
+ 改为无默认值并强制传 `--url`:宿主每次启动端口是随机的,猜错会连到别的进程上。
971
+ - **硬编码端口** —— `scripts/check-live.mjs` 与 `scripts/trace-sources.mjs` 同上处理。
972
+ - **用户名兜底** —— 三个脚本里 `process.env.USERPROFILE ?? 'C:\Users\...'` 写死了一个用户名,
973
+ 改为 `process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh')`。
974
+ - **zstd 分帧代码重复三份** —— 抽成 `scripts/zstd-frames.mjs`,导出 `frameOffsets` /
975
+ `decodeFrames` / `readSessionEvents`。这是真 bug 的温床:直接解整个文件只能得到第一帧,
976
+ 看起来正常实则残缺。
977
+ - **`scripts/smoke-client.mjs` 的 `byClass` 判等太严** —— 原来要求 `className` 整串相等,
978
+ 加了版式类名后断言全挂。改为按空白切 token 做子集匹配(`an-badge` 仍不会匹配 `an-badge-ok`)。
979
+ - **零引用导入与注释拼写** —— `writeFile` / `parsePatrolSignature` / `patrolSignature` 三处删除;
980
+ `lib/injection.mjs` 的 `finduings` 改为 `findings`。
981
+
982
+ ### 验证
983
+
984
+ 发布时四套验证全部通过:
985
+
986
+ - `node scripts/verify-core.mjs` → 234 项
987
+ - `node scripts/smoke-host.mjs` → 103 项
988
+ - `node scripts/smoke-client.mjs` → 69 项
989
+ - `node scripts/check-client-i18n.mjs` → 中英字典各 377 条,源码用到的 key 全部存在
990
+
991
+ 另外 `node scripts/acceptance.mjs` 会跑端到端验收(会启动宿主、需要指定端口),
992
+ `node scripts/review.mjs` 可以把外部代码审查工具接进来。
993
+
994
+ ### 已知未做
995
+
996
+ - **没发 npm**。安装走 GitHub 标签(见 README「安装」节)。
997
+ - **`docs/` 里的第三方名字保留**。它不在 `package.json` 的 `files` 里,不进发布包;
998
+ 留着是为了让「这条机制来自哪个仓库的哪一行」可核对。
999
+ - **没有 CI**。四套验证靠发布前手动跑。
1000
+
1001
+ ---
1002
+
1003
+ ## [1.0.0] - 2026-10-15
1004
+
1005
+ 发布前打的第一个标签。只有人设与记忆的骨架(`lib/config.mjs` / `lib/memory.mjs` /
1006
+ `lib/prompt.mjs` / `lib/store.mjs` / `lib/workspace.mjs` / `lib/injection.mjs` / `lib/presets.mjs`
1007
+ 与设置页),没有质检层、衰减、巡夜、记忆体检。
1008
+
1009
+ 存储格式戳当时就是 **1**,四张表(config / memories / meta / audit)与 2.0.0 一致。