# dsh-font-manager 验证报告(verifier)
本文件记录 **独立验证** 的环境、命令、期望/实际结果、证据与**明确未验证项**。
沙箱断言与截图在 `tests/client/`,真实宿主端到端证据在 `docs/shots/`(由 Lead 执行、本节记录)。
- 验证时间:2026-10-17 17:58(+08:00),默认路径回归:12/12 PASS、退出清理自检 PASS
- 被测文件:`lib/client.js`(sha256 `c980edd50f3d3be666db4b62c427688753aa48d77d0c0565423dc510d60990ec`,97,627 B)
—— client-ui 已确认 **frozen**(2238 行,`node --check` OK)
- 结论:**A1–A12 共 12/12 PASS**(真实 `lib/client.js`,headless Chrome + CDP)
- **断言自身也不是空跑**:同一套断言对 `tests/client/fake-client.js`(独立编写的契约最小实现)
同样 **12/12 PASS** —— 两套实现都通过,说明断言真正在检验契约行为。
- **真实宿主回归**:冻结版 `c980edd5…` 的 E1–E7 已在真实 DSH 上重跑 **7/7 PASS**(Lead 执行,见 §5.1)。
- 本轮还修掉了验证脚本自身的一个真实缺陷(浏览器进程/临时 profile 泄漏),见 §6.1。
> 改动 `lib/client.js` 后本结论失效,必须重新跑 `tests/client/assert-cdp.mjs` 并把新 hash 记到这里。
> 历史:17:15 版 `71a61b67…`(当时 9 条断言)→ 17:17:50 版 `d49199de…`(11 条)→
> 17:40 版 `c980edd5…`(12 条,当前)。
---
## 1. 环境
| 项 | 值 | 取证方式 |
|---|---|---|
| OS | Windows(本机 DSH Desktop) | 文件系统 / 进程 |
| DSH Desktop 壳 | `@deepseek-ai/dsh-desktop` **0.2.1-rc.1**,`dshBuildCommit=ed75efeffee65f9fe93625bbd76a68cb219aaaf9` | 解析 `E:\DSH Desktop\resources\app.asar` 根 `package.json` |
| DSH bundled runtime | `@deepseek-ai/dsh-desktop-runtime` **0.2.1-rc.1** | 同上(`dsh/package.json`) |
| Node | **v24.21.0**,`C:\Users\Leaf_\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe`(不在 PATH) | 脚本内 `process.version` / 报告 `node` 字段 |
| 浏览器(沙箱实际使用) | **Chrome 155.0.8059.84**,`C:\Program Files\Google\Chrome\Application\chrome.exe` | `Browser.getVersion` → 报告 `browserVersion` |
| 浏览器(真实 E2E) | Chrome 155.0.8059.84(headless + CDP,页面为真实 DSH web GUI) | §5 |
| 浏览器(不可用于 CDP) | Edge 155.0.4283.45,`C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe` | 见 §6.2 |
| 沙箱页 | `http://127.0.0.1:<随机端口>/tests/client/sandbox.html`(脚本内置零依赖静态 server) | 报告 `url` |
| 主题 token 来源 | 解包的官方 `@deepseek-ai/dsh-client-ui-theme`:`C:\Users\Leaf_\AppData\Local\Temp\dshfont\ui-theme\lib\client.js` 的 `base_css_default`(`--dsh-font-family-text-default` / `--dsw-font-family` / `--dsh-font-family-code-default` / `--ds-font-family-code` 及 `body{--dsw-font-family:var(--dsh-font-family-text,…),…}`) | 手工比对,裁剪后内联进 sandbox.html |
| mock 契约 | `docs/CONTRACT.md` §3(宿主路由)/ §4(State)/ §5(client 外形与写路径) | `tests/client/sandbox.html` 的实现 |
React 18.2.0 UMD 来自 npm registry 官方 tarball(非 CDN),校验和见 `tests/client/README.md`;
项目未引入任何 npm 依赖。
## 2. 断言结果总表
执行命令(Node 绝对路径,见上):
```bash
"$NODE" tests/client/assert-cdp.mjs
```
| id | 断言 | 期望 | 实际 | 结果 | 证据 |
|---|---|---|---|---|---|
| A1 | 模块注册 | `__FM_MOD.id === 'dsh-font-manager'`,`exports.apply` 是函数 | `id=dsh-font-manager`、`apply=function`、`inject=["slots","theme"]`、factory 无异常 | **PASS** | 报告 `results[A1]` |
| A2 | `settings.section` + DOM | 注册 `id=font-manager`、`order=65`、label 含「字体管理」;三个角色卡 + 预览块 | section 元数据完全一致;`data-fm` 钩子 4/4;`settings.section` 注入 1 次 | **PASS** | 截图 `01-default-panel.png` |
| A12 | 加载期零主题写入 | 首次渲染后 `__FM_CALLS` 为空、主题 `fontFamilies` 全空、无 POST、state 不被回填 | `calls=[]`、`fontFamilies={text:'',code:'',terminal:''}`、`posts=0`、`stateText=''` | **PASS** | 报告 `results[A12]` |
| A3 | 选字体写主题 | `__fmPick('HarmonyOS Sans SC')` 后出现 `['family','text', …含该家族…]` | `["family","text","HarmonyOS Sans SC"]`(真实输入框键入 + 点击选项) | **PASS** | 截图 `02-after-pick.png` |
| A4 | 字号 | 点 `+` 后 `['size','text',N]`,`N ∈ 10..22` | `["size","text",15]`(起始 14) | **PASS** | 报告 `results[A4]` |
| A5 | `@font-face` | `#dsh-font-manager-css` 含自定义家族名与 `url(` | style 存在;含 `DSH FM Demo Serif`、`Demo WOFF2 Sans`、`url(`、`format(` | **PASS** | 报告 `results[A5]` |
| A6 | 预览 | 预览块 computed `fontFamily` 含当前家族 | `"HarmonyOS Sans SC"` | **PASS** | 截图 `02-after-pick.png` |
| A8 | token 链路 | `documentElement` 的 `--dsh-font-family-text` → `body` 的 computed `font-family` 含所选家族 | rootInline=`"HarmonyOS Sans SC"`;`body` computed = `"HarmonyOS Sans SC", -apple-system, …, sans-serif` | **PASS** | 报告 `results[A8]` |
| A10 | 拉丁覆盖(附加) | 打开覆盖并选 `Consolas`:样式含 `dsh-font-manager-latin`+`unicode-range`;组合栈 latin 在最前;状态落盘 | 样式命中;`["family","text","dsh-font-manager-latin, HarmonyOS Sans SC"]`(iLatin=0 < iHarmony=1);`state.latin={enabled:true,family:"Consolas",roles:["text"]}` | **PASS** | 截图 `03-latin-override.png` |
| A11 | 排版附加(附加) | 「等宽数字」+「抗锯齿」写入样式表并保存 | 样式含 `font-variant-numeric:tabular-nums!important` 与 `-webkit-font-smoothing:antialiased!important`;`POST /api/state` 已含 `typography.tabularNums=true, smooth="antialiased"` | **PASS** | 报告 `results[A11]` |
| A7 | 恢复默认 | 三个角色都 `setFontFamily(role,'')`,且发出 `POST /api/reset` | `freshFamilyCalls=[["family","text",""],["family","code",""],["family","terminal",""]]`;`POST /dsh-font-manager/api/reset`(body `{"removeCustomFonts":false}`) | **PASS** | 报告 `results[A7]` |
| A9 | 降级 | `fetch` 全失败时仍渲染三个角色卡 + 可见错误行 | 卡片 3/3(钩子与文本两条路径都 3/3);`[data-fm="error"]` 可见,文案「状态接口不可用:… 已降级:仍可选择主题默认字体或手输家族名…」;页面文本 1,568 字符 | **PASS** | 截图 `04-degraded.png` |
补充说明:
- A2 优先用 `[data-fm="role:text|code|terminal"]`、`[data-fm="preview"]` 定位;本实现 4 个钩子齐全,
脚本同时记录可见文本回退路径作对照(报告 `domSource: "data-fm hooks"`)。
- A3 不是“直接调用内部函数”:`__fmPick` 用 `picker` 输入框键入家族名、派发 `input/change`,
再点击 `[data-fm="option"]`(优先 `data-family`,否则文本)——走的是真实 DOM 事件。
- A8 的 mock `applySnapshot` 把变量写到 `documentElement`;真实主题写 `body`,自定义属性继承关系相同,
两者都能证明「主题服务写变量 → body 的 `font-family` 计算值变化」。
- A12 对应 17:40 版的“加载期零主题写入”行为:加载只注入样式、读 state/theme 填充 UI,不写主题,
避免用空 state 覆盖主题现值。
- **执行顺序**:A8/A10 需要观察所选字体,排在 A7(恢复默认会清空字体)之前;
stdout 与报告顺序为 A1→A2→A12→A3→A4→A5→A6→A8→A10→A11→A7→A9。
- 退出清理自检(A1–A12 完成后):`[SELFCHECK] 残留 0 / 0 / PowerShell 失败 0 次 → PASS`,见 §6.1。
## 3. 截图(沙箱)
| 文件 | 内容 | 尺寸 / 大小 |
|---|---|---|
| `tests/client/shots/01-default-panel.png` | 默认面板(三张角色卡、排版、已导入 2 个字体、预览、页脚) | 1000×1793,200 KB |
| `tests/client/shots/02-after-pick.png` | 选完 `HarmonyOS Sans SC` + 字号 15(预览同步变化) | 1000×1800,232 KB |
| `tests/client/shots/03-latin-override.png` | 拉丁覆盖打开并选中 `Consolas`(「当前生效」显示 `"dsh-font-manager-latin", "HarmonyOS Sans SC"`) | 1000×1912,227 KB |
| `tests/client/shots/04-degraded.png` | 降级态(fetch 全失败):错误行 + 卡片 + 预览仍可用 | 1000×1769,207 KB |
截图由 CDP `Page.captureScreenshot`(`captureBeyondViewport` + `clip`)生成;
`Emulation.setDeviceMetricsOverride` 固定 1000×1500 逻辑视口。
## 4. 安装 / 卸载工具
脚本默认 **dry-run**,真正改动必须显式 `--apply`;脚本不会自动运行。
### 4.1 真实 profile
Lead 已于 2026-10-17 17:30 对真实 desktop profile 执行过安装(见 §5 第 8 项),当前 profile 处于**已安装**状态:
- 备份:`package.json.pre-fontmanager-20261017-173026`
- `dependencies["dsh-font-manager"] = "link:C:\\Users\\Leaf_\\Desktop\\dsh-font-manager"`
- `node_modules\dsh-font-manager` 为 junction → 本工作目录
- `dsh.profile.bundles` 末尾含 `dsh-font-manager`
- 回滚:`"$NODE" tools/uninstall.mjs --apply`
安装前的 dry-run 输出(历史记录,用于核对脚本行为):
```
== dsh-font-manager 安装 ==
模式 : DRY-RUN(不写任何文件)
目标 profile: C:\Users\Leaf_\.dsh\profiles\desktop
工作目录 : C:\Users\Leaf_\Desktop\dsh-font-manager
junction : C:\Users\Leaf_\.dsh\profiles\desktop\node_modules\dsh-font-manager → C:\Users\Leaf_\Desktop\dsh-font-manager
link 依赖 : link:C:\Users\Leaf_\Desktop\dsh-font-manager
将要执行的步骤:
1. 备份 package.json → package.json.pre-fontmanager-20261017-171538
2. junction C:\Users\Leaf_\.dsh\profiles\desktop\node_modules\dsh-font-manager
→ 新建(cmd /c mklink /J "" "")
3. package.json
dependencies["dsh-font-manager"] = "link:C:\\Users\\Leaf_\\Desktop\\dsh-font-manager"
dsh.profile.bundles 末尾追加 "dsh-font-manager"
DRY-RUN 结束:以上步骤未执行。真正安装请加 --apply。
```
### 4.2 临时 profile 上的 APPLY 往返测试(真实文件系统)
在 `C:\Users\Leaf_\AppData\Local\Temp\dshfm-install-test\` 复制真实 profile 的 `package.json`
并造了 `node_modules/` + 一个最小 workspace,验证(该临时 fixture 已清理,可按 §8 命令重建复现):
| 步骤 | 结果 |
|---|---|
| `install.mjs --apply` | 生成备份;`mklink /J` 成功(`fs.lstat` 为 symlink,target 指向 workspace);`package.json` 写入 `link:` 依赖并把 `dsh-font-manager` 追加到 `dsh.profile.bundles` 末尾 |
| 再次 `install.mjs --apply` | 「已经安装且状态一致,无需任何改动」(幂等) |
| `uninstall.mjs --apply` | 删除 junction;从最新备份还原 `package.json`;还原前另存 `package.json.pre-fontmanager-uninstall-` |
| 还原校验 | `sha256(package.json)` = 安装前 = `be0d2dbfd38017935b7bfcf97feddf4a2077bbbf059b06b70881b2a48abe3bd9`(**逐字节一致**) |
| 再次 `uninstall.mjs --apply` | 「已经处于未安装状态,无需任何改动」(幂等) |
| 删掉备份后再卸载 | 走「就地摘除」路径,结果同样逐字节还原(hash 一致) |
| `node_modules/dsh-font-manager` 是真实目录时 | 安装/卸载都拒绝并 exit 1,不覆盖、不代删 |
`--restore-backup=` 可显式指定备份;脚本会校验文件存在、是合法 JSON、看起来像 profile
`package.json`。
### 4.3 junction 可被 Node 解析(不跑 pnpm 的依据)
在临时 profile 上安装后,从该 profile 目录用 Node 解析:
```
resolve(package.json): C:\Users\Leaf_\AppData\Local\Temp\dshfm-install-test\workspace\package.json
resolve(main): C:\Users\Leaf_\AppData\Local\Temp\dshfm-install-test\workspace\lib\host.js
name/version: dsh-font-manager 0.1.0
```
即 `node_modules/dsh-font-manager` 这个目录 junction 已足够让 Node 的 CJS/ESM 解析找到
`package.json` 与 `main`,无需 pnpm 安装步骤。
两个脚本都**不跑 pnpm**、**不碰** `cordis.patch.yml`(插件靠自身 bundle patch `insert` 生效)、
**不碰** `app.asar`。
## 5. 真实宿主 + 真实 GUI 端到端(Lead 执行,verifier 记录)
> 本节不是 verifier 沙箱跑出来的,而是 Lead 在**真实 DSH 桌面版**上执行的端到端验证;
> verifier 负责把证据固化在这里并据此更新未验证清单。截图在 `docs/shots/`。
>
> **版本**:§5 的 8 项在 `d49199de…`(17:17:50 版)上完成;冻结版 `c980edd5…`(97,627 B)的
> 7 项回归(E1–E7)于 **2026-10-17 18:02** 重跑,**7/7 PASS**,报告 `docs/e2e-report.json`、
> 截图 `docs/shots/e2e-final-*.png`(见 §5.1)。
环境与启动:
- `DSH_HOME=C:\Users\Leaf_\AppData\Local\Temp\dshfm-home`,临时 profile `fm-test`;
`bundles = @deepseek-ai/dsh-base, @deepseek-ai/dsh-web-app, dsh-font-manager`;
`node_modules/dsh-font-manager` 为 junction → 本工作目录。
- 启动命令(desktop 自带 CLI,`ELECTRON_RUN_AS_NODE=1`):
```
"E:\DSH Desktop\DeepSeek Harness.exe" --expose-internals "E:\DSH Desktop\resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" --profile fm-test --no-open --port 56891
```
(profile 名放在 `--profile` 之后;`--no-open --port` 是 web app 自己的参数;输出会打印带 token 的 URL。)
- 验证方式:headless Chrome 155 + CDP,页面为真实 DSH web GUI。
| # | 检查项 | 期望 | 实际 | 结果 |
|---|---|---|---|---|
| 5.1 | 真实宿主路由 health | `GET /dsh-font-manager/api/health` → 200 + `{ok:true,version,pid,…}` | 200,`{ok:true,version:"0.1.0",pid:…}` | **PASS** |
| 5.2 | 真实字体枚举 | `GET /api/fonts` → 契约形状 | 200 / 93,564 B;`system=220, custom=0, errors=1`(本机 `C:\WINDOWS\Fonts\mstmc.ttf` 是 0 字节坏文件);含 `HarmonyOS Sans SC [250,300,400,500,700,900]`、`Fira Code`、`Segoe UI` | **PASS** |
| 5.3 | 真实 ui-settings 槽 | 设置导航出现「字体管理」,面板渲染 3 张角色卡 | 出现;`data-fm=role:text|code|terminal` 齐全;`#dsh-font-manager-css` 存在 | **PASS**(`docs/shots/e2e-01-section.png`) |
| 5.4 | 真实 ui-theme 写主题 | 选 `HarmonyOS Sans SC` 后主题变量/计算值变化 | `document.body.style.getPropertyValue('--dsh-font-family-text')` = `"HarmonyOS Sans SC"`;`getComputedStyle(body).fontFamily` 含之;预览块同值 | **PASS**(`docs/shots/e2e-02-picked-harmony.png`) |
| 5.5 | 持久化 + reload | 设置落盘,刷新后仍生效(无 FOUC 回退) | `profiles/fm-test/cordis.patch.yml` 出现 `textFontFamily: '"HarmonyOS Sans SC"'`;`font-manager/state.json` 里 `roles.text.family` 同值;`location.reload()` 后 body 计算字体仍是 HarmonyOS | **PASS**(`docs/shots/e2e-05-after-reload.png`) |
| 5.6 | 字号 + 恢复默认 | 字号 15 生效;恢复默认后主题回内置栈 | 步进到 15 生效;点「恢复默认…」→「确认恢复」后 body 行内只剩 `--dsh-content-font-size:14px; --dsh-code-font-size:11px; --dsh-terminal-font-size:13px`,`--dsh-font-family-*` 全部移除;`cordis.patch.yml` 变 `textFontFamily: ""`、`codeFontFamily: ""` | **PASS** |
| 5.7 | 真实导入 woff2 | 上传 woff2 → 解析家族、入库、@font-face、浏览器解码、可选用、可删除 | `DOM.setFileInputFiles` 塞入 62,008 B `montserrat-regular.woff2` → 家族 **Montserrat**(`familySource=sfnt`)、按内容哈希 id `0f4a15e29a9535af`、生成 `@font-face{font-family:"Montserrat";src:url("/dsh-font-manager/font/0f4a15e29a9535af") format("woff2");…;font-display:swap}`;`document.fonts.check('12px "Montserrat"')===true`;选为 code 字体后 `--dsh-font-family-code="Montserrat"`;删除后列表项与 @font-face 消失、fonts 目录为空 | **PASS**(`docs/shots/e2e-03-imported-code-font.png`、`e2e-04-import-list.png`) |
| 5.8 | 真实 desktop profile 安装 + 激活取证 | 安装后 `last-activation.json` 出现,GUI 不崩 | `tools/install.mjs --apply` 生成备份 / junction / `link:` 依赖 / bundles 追加;`C:\Users\Leaf_\.dsh\font-manager\last-activation.json` = `{version:"0.1.0", pid:26664, dshHome:"C:\\Users\\Leaf_\\.dsh", at:"2026-10-17T09:30:28.338Z"}`(运行中的桌面版经 HMR 载入宿主半);GUI 端口 56750 仍稳定返回 401(应用未崩) | **PASS** |
E2E 截图(Lead 提供,已复制进仓库):
| 文件 | 内容 |
|---|---|
| `docs/shots/e2e-01-section.png` | 真实设置页出现「字体管理」分区 |
| `docs/shots/e2e-02-picked-harmony.png` | 选择 HarmonyOS Sans SC 后的面板与预览 |
| `docs/shots/e2e-03-imported-code-font.png` | 导入 woff2 后选为 code 字体 |
| `docs/shots/e2e-04-import-list.png` | 已导入字体列表 |
| `docs/shots/e2e-05-after-reload.png` | 刷新后字体仍生效 |
### 5.1 冻结版 `c980edd5…` 的真实宿主回归(2026-10-17 18:02,Lead 执行)
环境与 §5 一致,只换了临时 home 与端口:`DSH_HOME=…\Temp\dshfm-home3`、profile `fm-test`、
junction 指向本工作目录、端口 56893。原始报告 `docs/e2e-report.json`(`passed=7, total=7`)。
| # | 检查项 | 证据(来自 `e2e-report.json`) | 结果 |
|---|---|---|---|
| E1 | 真实 GUI 渲染 | 3 张角色卡;`#dsh-font-manager-css` 存在;无错误行;状态行 `宿主 0.1.0 · 系统字体 220 · 已导入 0 · 共 220 个家族`;**全新 home 下 state 无 family 回填**(面板显示「跟随主题默认(内置栈)」,主题无写入;此条为 Lead 运行记录补充,报告 JSON 只含 cards/css/error/sub)——与沙箱 A12 一致 | **PASS**(`e2e-final-01-panel.png`) |
| E2 | 选 `HarmonyOS Sans SC` | 行内 `--dsh-font-family-text` = `"HarmonyOS Sans SC"`;computed body 与预览块都含该家族 | **PASS**(`e2e-final-02-picked.png`) |
| E3 | `location.reload()` 后仍生效 | 刷新后行内与 computed 仍为 HarmonyOS(boot 期应用) | **PASS** |
| E4 | 导入 woff2 | `DOM.setFileInputFiles` 62,008 B;面板行 `Montserrat / montserrat-regular.woff2 · 60.6 KB · 400 · normal`;`@font-face{font-family:"Montserrat";src:url("/dsh-font-manager/font/0f4a15e29a9535af") format("woff2")…}` | **PASS**(`e2e-final-03-import.png`) |
| E5 | 设为 code 字体并真实解码 | `--dsh-font-family-code="Montserrat"`;`document.fonts.check('12px "Montserrat"') === true` | **PASS** |
| E6 | 一键恢复默认 | `--dsh-font-family-*` 全部清空、`--dsh-content-font-size` 回 `14px` | **PASS**(`e2e-final-04-reset.png`) |
| E7 | 删除导入字体 | 列表 0 行、样式表里无 `@font-face` | **PASS** |
截图:`docs/shots/e2e-final-01-panel.png`、`e2e-final-02-picked.png`、`e2e-final-03-import.png`、
`e2e-final-04-reset.png`。
结论:冻结版 `c980edd5…` 已在真实宿主上闭环(含“加载期零主题写入”行为);§7 中
“当前冻结版未在真实宿主重跑”一项据此划掉。
## 6. 环境/链路已知问题
### 6.1 验证脚本的浏览器进程泄漏(已修复并自检)
- 现象:修复前跑完断言会残留大量 `msedge.exe`(命令行带 `--user-data-dir=…\Temp\dshfm-cdp-*`)
和临时目录。
- 根因(两条叠加):
1. 清理逻辑里调用了 `spawnSync` 却**漏了 import**,每次回收查询都抛错被 `catch` 吞掉,
清理实际是空操作(自检也因此“看不见”残留);
2. 本机 Edge 的 `--headless=new` 会把手上的请求交给自己的常驻进程、父进程立刻 `exit(0)`,
被接手的浏览器进程树要 **7–24 秒**后才带着同一个 `--user-data-dir` 出现,一次查询收不到。
- 修复:
- `spawnSync` 正确导入;PowerShell 调用失败会计数(`psFailures`),>0 时自检判 FAIL;
- 每个自己创建的 user-data-dir 都登记;成功实例走 `Browser.close` → `child.kill()` → 轮询兜底 kill → 删目录;
- 启动失败(异步接手)的目录进入 `PENDING_REAP`,在进程退出前持续看护(默认窗口 35s,
`DSHFM_REAP_MS` 可调),出现即 kill,最后删目录;
- 默认浏览器顺序改为 **Chrome 优先**(`--browser=edge` 可强制先试 Edge,用于诊断该回退路径);
- 退出时打印自检:`[SELFCHECK] … 残留 N 个 / 临时 profile M 个 / PowerShell 调用失败 K 次 → PASS|FAIL`。
- 验证结果:
- 默认路径(Chrome):12/12 PASS,11.7s,`[SELFCHECK] 残留 0 / 0 / 0 → PASS`,运行后系统里 **0** 个带
`dshfm-cdp` 的 msedge 进程、**0** 个临时目录;
- `--browser=edge` 诊断路径:Edge 失败回退 Chrome,12/12 PASS,42s,Edge 接手进程被回收
(该次回收 13 个 PID),`[SELFCHECK] 残留 0 / 0 / 0 → PASS`。
### 6.2 Edge 不能用于本机 headless CDP
`msedge.exe --headless=new`(含 `--headless=old`、固定调试端口、`--disable-background-mode` 等变体)
会把请求交给已运行的 Edge 实例并立即 `exit 0`,不打印 `DevTools listening on …`。因此断言脚本
默认用 Chrome;`--browser=edge` 仅用于诊断回退路径(会自动回收它异步拉起的进程)。
### 6.3 主题变量位置
官方主题把 `--dsh-font-family-*` 写到 `body`;沙箱按任务要求写到 `documentElement` 以验证
继承链路,两者对 `getComputedStyle(document.body).fontFamily` 等价。
## 7. 明确未验证项(诚实清单)
以下内容**没有**在本轮验证中跑通或取证,交付/验收时不能当作已验证:
1. **Electron 窗口内 client 半的观感/交互**:E2E 是在真实 DSH web GUI(Chrome)里做的;Electron
窗口需要用户刷新页面或重启后才可见,Lead 无法驱动该窗口。面板在 Electron 壳里的最终渲染
(尤其字体/DPI/滚动条)未验证。
2. **暗色主题下的面板视觉**:沙箱与 E2E 都是浅色;暗色 alias token 下的对比度/观感未验证。
3. **`.ttc / .woff`(非 woff2)真实字形加载**:E2E 只验证了 woff2;ttc/woff 的真实解码与
多面(TTC collection)选择未验证。
4. **同一窗口重复热重载的边界**:反复 HMR/卸载重装后样式元素、@font-face、事件订阅是否
幂等收敛,未做压力验证。
5. **真实宿主上“导入失败/超大文件/坏字体”等错误路径**:只验证了正常 woff2 导入与删除;
413/解析失败/重名等错误分支未在真实宿主触发。
6. **导入/删除 UI 在真实 GUI 里的多选与失败提示**:E2E 用 `DOM.setFileInputFiles` 走单文件,
多选、改家族名、失败提示文案未逐项验证。
## 8. 复现命令汇总
```bash
NODE=/c/Users/Leaf_/.dsh/dsh-runtimes/dsh-primary-runtime/dependencies/node/bin/node.exe
cd /c/Users/Leaf_/Desktop/dsh-font-manager
# 断言 + 截图(真实 lib/client.js;默认 Chrome 优先,退出自检进程/目录清理)
"$NODE" tests/client/assert-cdp.mjs
# 断言链路自检(不依赖 client-ui 交付)
"$NODE" tests/client/assert-cdp.mjs --module=fake-client.js
# 诊断 Edge 回退路径(本机 Edge 会异步接手;脚本会看护并回收,约 40s)
"$NODE" tests/client/assert-cdp.mjs --browser=edge
# 看护窗口可用 DSHFM_REAP_MS 调整(默认 35000)
DSHFM_REAP_MS=20000 "$NODE" tests/client/assert-cdp.mjs --browser=edge
# 安装/卸载(默认 dry-run;--apply 仅 Lead 执行)
"$NODE" tools/install.mjs
"$NODE" tools/install.mjs --apply
"$NODE" tools/uninstall.mjs
"$NODE" tools/uninstall.mjs --apply
```
在**临时副本**上做 install/uninstall 往返(不碰真实 profile,§4.2 用的就是这套):
```bash
cd /c/Users/Leaf_/Desktop/dsh-font-manager
T=/c/Users/Leaf_/AppData/Local/Temp/dshfm-install-test
mkdir -p "$T/profile/node_modules" "$T/workspace/lib"
cp package.json cordis.patch.yml "$T/workspace/"
printf '// stub\n' > "$T/workspace/lib/host.js"
cp /c/Users/Leaf_/.dsh/profiles/desktop/package.json "$T/profile/package.json"
"$NODE" tools/install.mjs --apply --profile="$(cygpath -w "$T/profile")" --workspace="$(cygpath -w "$T/workspace")"
"$NODE" tools/uninstall.mjs --apply --profile="$(cygpath -w "$T/profile")"
```
失败排查:`tests/client/shots/assert-report.json` 里有每条断言的完整 evidence、页面 console/异常
(`pageLog`)、浏览器版本、截图路径与退出清理自检(`browserCleanup`)。