# 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`)。