dsh-fonttune 0.2.4 → 0.2.6

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 CHANGED
@@ -2,179 +2,166 @@
2
2
 
3
3
  **dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
4
4
 
5
- ## [0.2.4] - 2026-09-23
5
+ ## [0.2.6] - 2026-09-24
6
+
7
+ ### 修复
8
+ - **点进「字体增强」还要再点一次才看得到设置**:官方插件页现在把「点进这个条目」本身当作展开的手势,插件卡片里却还留着一层自己的折叠头,于是要连点两次。现在在条目页与包配置页里,卡片一进来就是展开的(那枚箭头照旧可以手动收起)。
9
+ - **界面拖动不再一顿一顿的(侧栏、拖宽对话列表都能感觉到)**:插件注入的样式表让浏览器每次重算样式都要把页面上近两千个元素逐个匹配一遍(约 1.5 万次),而拖动时每一帧都会重算一次,于是侧栏和拖拽明显卡顿。现在每个轴只匹配真正需要的那几个元素(约 1 400 次):实测 50 步拖动期间的重算耗时从 **1.5 秒降到 0.5 秒**,超过 25ms 的帧从 **49 个降到 0–2 个**,帧间隔回到 **17ms**(长任务 0);字体、字号、字重、行高、代码轴(含连字与特性开关)的观感与读数完全不变。
6
10
 
7
11
  ### 新增
8
- - **一个包同时装上两条宿主线**:`0.1.5-rc.1` – `rc.3`(宿主 `settings.installSection` + 客户端 `settingsScope` 那一套)与 `0.1.7-alpha.1` – `alpha.2`(宿主 `settings.configure({auto:true}, ctx.fiber)` + 客户端 `configForms` 那一套)。插件在运行时看宿主到底给了哪一套,**两条都不在就只装样式、不注册卡片**,不会把整包 park 住。`package.json` 的 `engines.dsh` 写成语义化预发布区间 `>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`(写 `>=0.1.5-rc.1` 是匹配不到 `0.1.7-alpha.1` 的),`dsh.compatibility.dshReleases` 逐条列出五条实测过的宿主线。
9
- - **卡片在两条线的三个座位上都能出现**,由宿主自己决定渲染哪一个:rc 的 `settings.plugin.item`(按设置命名空间认领)、alpha 的 `plugins.item`(插件页的插件条目,`id: fonttune`、标签取字典里的 `card.title`、摘要用 `card.description`、详情页才渲染整张卡——**DSH 自己的 shell / agent-loop / subagent / web-search 也是这么登记的**)、以及给已装 profile 包用的 `plugins.bundle.config`(按 npm 包名 `dsh-fonttune` 认领,落在该包的详情页)。
10
- - **认领命名空间改成「先等、再按签名认」**:alpha 的 `configForms.describe()` 是异步的,以前抢在它应答前按名字猜候选,猜错就会落在别的命名空间上、每次写入都被宿主拒绝(真机表现:卡片能开、一改就报「设置没有保存成功」)。现在候选为空就返回 `null`,并挂一个「待定作用域」占位(`getSnapshot`/`subscribe`/`set`/`unset` 全套),等 describe 的视图到达后按 **schema 里同时含 `uiFollowsDialog` 与 `stackDialog`** 认出自己那一个再接管(真机日志:`served namespaces []` → `matched by served schema: fonttune`)。
11
- - **宿主侧拆掉 volatile 引用**:alpha 的 `ctx.fiber.config` 里每个字段都是 cosmokit 的 volatile 引用(`{ get(), [Symbol.for("cosmokit.volatile.write")](v) }`,且被冻结),当普通对象读只会拿到空值——真机表现是**卡片第一次渲染是一整排空控件、注入的样式表 0 字节**。现在统一走 `plainConfigValue()` 深拆,`styleRow()` 拿到的是普通对象(实测样式行 973 B、`Georgia` / `520`)。
12
- - **`inject` 收窄到两条线都有的服务**(`slots` / `locale`):声明一个某条线上不存在的服务(例如 rc 才有的 `settingsScope`)会把**整包 park 住**(`pending (waiting for service: …)`),Web 直接起不来。设置作用域改为运行时用 `ctx.get()` 免声明查找,找不到就按「没有宿主层」降级。
13
- - **写设置的结果两种形状都认**:rc 的 `mutate` 即使失败也 resolve `undefined`,alpha 的 `set`/`unset` resolve 布尔(`false` = 宿主拒绝)。`followWrite` 现在把同步 `false`、resolve 成 `false` 的 thenable、以及 rejection 三种都报到预设行右侧那句提示里。
14
- - **`test/host-line-probe.mjs`:与宿主线无关的真机探针**。只断言两条线都必须成立的事——页面无报错、插件只维护**一份**样式表且已接入文档、设置真的作用到文档(设了 `DFP_EXPECT_FAMILY` / `DFP_EXPECT_WEIGHT` 就顺带断言计算样式)、设置页里能找到并渲染出 `.dfp-card`(哪条线的座位都行)、以及从卡片上改一个值真的写进宿主。已并入 `npm run verify:browser` 的只读组。设 `DFP_PROBE=1` 时插件会往控制台打诊断行(服务了哪些命名空间、认领了哪个、座位当前状态),`DFP_DUMP=1` 时探针额外按分组打印插件页的条目。
12
+ - **支持 DSH 0.1.7-rc.1**:它的设置接口与 0.1.5-rc.x 不同,插件在运行时按宿主实际提供的那一套工作。字体、界面字重、代码轴与配置卡片都在这条线上实测正常(卡片在侧栏「插件」页的「字体增强」条目里,也能从 `dsh-fonttune` 包行进入)。
15
13
 
16
14
  ### 变更
17
- - **不再 require 宿主内部原语**:alpha 只导出 `IconPlusOutlineRegular` / `IconPlusOutlineMedium`(没有 rc 的 `IconPlusOutline16`),所以 `primitives` 的 require 整个去掉,用到的加号改成内联 SVG。
18
- - **实测清楚 rc.3 到底改了什么**(供后续判断要不要为它开分支):把 rc.2 与 rc.3 的 `lib/` + `dist/` 共 728 个 js/css 逐个哈希比对,**只有 2 个文件不同**——`@deepseek-ai/cosmokit/lib/index.js`(13336 → 17160 B,新增 volatile 原语 `createVolatile` / `isVolatile` / `volatileEntries` / `updateVolatile` 与 `Symbol.for("cosmokit.volatile.write")`)和 `dsh-client-ui-sidebar/lib/client.js`(只差构建戳 `0.1.5-rc.2-449af19` → `0.1.5-rc.3-a5380f3`);依赖上内部包抬到 `^0.1.5-rc.3`,`@deepseek-ai/schemastery` / `@deepseek-ai/cordis` 从 `^` 改成精确钉版(3.18.2 / 4.0.2)。**排版、主题、设置 API 一处没动**,面向 rc 的行为在 rc.3 上不需要新分支。rc 线自带的 schemastery 3.18.2 **没有** `.volatile()`(alpha 的 3.18.4 才有),所以根 schema 上的 volatile 标记必须**有则加、无则跳**。
19
- - 兼容表补 0.2.4(中英两份 README 都改了运行时双方言的处理说明)。
15
+ - **兼容下限明确为 `0.1.5-rc.3`**(不再声明 rc.1 / rc.2),兼容表补上 `0.1.7-rc.1`。
16
+ - **插件自带的 loader 行 id 改成 `dsh-fonttune`**(与设置命名空间、包名一致):DSH 0.1.7 起设置按这个 id 存档,并在首次启动时按**同名**把旧的 `settings.yaml` 搬过去——id 不一致,已保存的字体与字重就只能留在备份文件里,插件看起来「完全没生效」。改成同名之后,从 0.1.5-rc.x 升到 0.1.7 不需要重新选一遍。(同一个 id 也让插件可以写进 profile 的 `dsh.profile.bundles` 安装:插件管理页会把它显示为运行中,配置页照旧可达。)
17
+
18
+ ## [0.2.5] - 2026-09-23
19
+
20
+ ### 修复
21
+ - **以「链接到源码目录」的方式安装时,0.1.5-rc.x 上的配置卡片与字体一起消失**:插件会用自己目录里的 schema 库副本,而不是 DSH 自带的那一份;副本的较新版本给配置加了 rc 线宿主处理不了的标记,设置命名空间因此根本没有注册出去——设置页看不到卡片、注入的样式是空的、字体毫无变化。现在插件始终使用 DSH 自带的那一份,链接安装与正常安装在行为上完全一致。
22
+
23
+ ## [0.2.4] - 2026-09-23
24
+
25
+ ### 新增
26
+ - **同一个安装包同时支持两条宿主线**:DSH **0.1.5-rc.1 – rc.3** 与 **0.1.7-alpha.1 – alpha.2** 上都能直接安装使用,不需要挑版本。插件在运行时按宿主实际提供的那一套设置接口工作(rc 线一套、alpha 线一套),两条都不在时只注入字体、不显示卡片。
27
+ - **alpha 上的配置入口**:在 0.1.7-alpha.x 上,卡片出现在侧栏「插件」页的插件条目「字体增强」里;rc.x 上仍在 设置 → 插件 → 插件配置。
20
28
 
21
- ### 测试
22
- - `test/run.mjs`(91 项):卡片槽位白名单加入 `plugins.item` / `plugins.bundle.config`,那条用例改成「每个受支持宿主可能派发的座位都注册」,断言三个座位与 `plugins.item` 的 id / 标签 / 组件形态(摘要取 `card.description`,页面交出一个对象)。
23
- - `test/render-card.mjs`(23 项):渲染取值改为「`settings.plugin.item` 不在就取 `plugins.item`」,两条线的座位各跑一遍。
24
- - 真机(受管实例 + 无头 Edge):`0.1.5-rc.2`、`0.1.5-rc.3`、`0.1.7-alpha.2` 各跑一次 `host-line-probe` 通过(插入式装载);alpha.2 另跑一次 bundle 式装载通过。alpha 上确认从卡片改的 `codeLigatures: 2` 真的写进 `profiles/dfp/cordis.patch.yml`,rc 上确认落在 `settings.yaml`。
25
- - **CI 抓到一条本地测不出来的用例**:`test/run.mjs` 里三条 schema 用例直接读 `schema({…}).sans` 这类字段。发布 CI 装的是最新的 schemastery(3.18.4,带 `.volatile()`),于是解析出来的字段是**引用**而不是值,三条在本地(3.18.2)全绿、在 CI 全红。现在宿主半把 `plainConfigValue` 导出,这三条按**插件读配置的同一方式**取值,并断言两种方言各自的形态:有修饰符时字段是引用、拆开后才是值;没有修饰符时字段本身就是值。devDependency 同时抬到 `^3.18.4`,让本地与 CI 跑同一个方言(rc 线那套无修饰符的形态由真机探针在 rc.2 / rc.3 上覆盖)。
29
+ ### 修复
30
+ - **alpha 上卡片第一次打开是一排空控件**:此前读到的是宿主的内部引用而不是配置值,注入的样式因此为空;现在首帧就是正确的字体与读数。
31
+ - **alpha 上改设置提示「设置没有保存成功」**:插件有几率把改动写到不属于自己的位置;现在只在确认是自己的配置之后才写入。
32
+
33
+ ### 变更
34
+ - 兼容声明与中英 README 的兼容表更新到 0.2.4,逐条列出五条实测过的宿主线(0.1.5-rc.1 / rc.2 / rc.3、0.1.7-alpha.1 / alpha.2),并写清两条线各自的设置入口。
26
35
 
27
36
  ## [0.2.3] - 2026-09-22
28
37
 
29
38
  ### 修复
30
- - **写设置失败不再被吞掉**:以前 `scope.set` / `scope.unset` 的失败只挂一个空的 `catch`——控件弹回旧值、用户以为改成功了。现在同步抛出的错误和 Promise 拒绝都会在预设行右侧显示一句「设置没有保存成功:<原因>」(超长截断到 160 字,悬停可看全文),走的是同一个 2.6 秒自动淡出的行,不占额外版面。
31
- - **剪贴板不可用时,导出不再只给一段被截断的 JSON**:以前导出失败会把 JSON 截到 120/200 字塞进那一行提示里,用户根本拿不到完整内容。现在三级兜底:① 异步 Clipboard API;② 非安全上下文(远程 HTTP 入口)里 `navigator.clipboard` 压根不存在,退回隐藏 textarea + `document.execCommand("copy")` 这条老路(在非安全上下文里照样可用);③ 两条都失败才把**完整 JSON** 放进一个自动聚焦、已全选、只读的文本框(复用输入框样式),按一次复制快捷键即可,`onCopy` 之后自动收起。状态行只说一句「剪贴板不可用,按 Ctrl+C 复制下方已选中的内容」。
32
- - **选字体面板的三个问题**:① 内置四组(等宽 / 中文 / 拉丁 / 通用)以前在「本机字体枚举成功**并且**正在搜索」时整组消失,于是本机没装的预设字体**搜名字都搜不到**;现在四组任何情况下都在(搜索时按关键词过滤)。② 本机分组不再重复预设组已经列出的字体,同一个字体在一个面板里只会出现一次(预设标题优先,因为它才是那张短名单);实测 Windows 上拉丁预设组几乎全部已安装,若让本机分组优先,预设组会被掏空。③ 枚举被拒(`queryLocalFonts` 拒绝)以前会**永久缓存**,用户去浏览器里授予字体权限后也不会再变;现在拒绝结果只信任 20 秒,之后再次打开面板就会重试——还是被拒时浏览器是**立即**拒绝、不会重新弹窗,所以重试没有代价。
33
- - **令牌轮询的省电闸门**:界面字号轴需要在 DSH 运行时写入令牌后重新读取基准值,原来每 4 秒无条件把每个样式表的每条规则的每个属性扫一遍。现在每 tick 只比较三个廉价信号(`body` 的 `style` 属性字符串、`html` 的 class、样式表数量),三者都没动就直接返回;每第 8 次 tick 仍做一次全量扫描,接住 CSS-in-JS 样式表**就地改写**规则的情况;`theme/change` 事件照旧强制全量。离线用计数桩证明:环境不变时一次 CSS 都不读,内联令牌一变下一个廉价 tick 就立刻跟上,第 8 次 tick 照常兜底。
34
- - **逐主题的「对话框内代码字体」规则漏了主题前缀**(休眠缺陷,逐主题当前关闭):`pushScopedCodeFamily` 生成的作用域规则没带 `body[data-ds-dark-theme]`,一旦开启逐主题,深色那一套会用**浅色**的代码字体覆盖深色对话框。现在与其他规则一样带前缀,并加了「每一条逐主题规则都必须以主题属性开头」的用例。
35
- - **`test/split-verify.mjs` 测的是早已退役的轴**:脚本还在写 `sizeOffset`(0.2.0 起退役的界面字号轴)并期望对话链路跟着动,真机自然红。现在它写 `sizeOffsetDialog` + `sizeOffsetCode`,比值按**实测基准**算(对话轴加在 DSH 自己的字号上、代码轴按 16px 基准缩放,两者不同),并新增一条「退役的界面字号轴什么都不动」的真机断言。
39
+ - **改设置失败不再显得像成功了**:保存被宿主拒绝时,预设行右侧会出现一句「设置没有保存成功:原因」,不再只是把控件弹回旧值让人以为存上了。
40
+ - **没有剪贴板的环境里导出也能拿到完整内容**:优先用剪贴板;远程 HTTP 入口取不到剪贴板时退回旧的复制通道;两条都不行才把完整 JSON 放进一个已全选的只读框,按一次复制快捷键即可。
41
+ - **选字体面板**:内置的等宽 / 中文 / 拉丁 / 通用四组不再因为本机字体枚举成功而消失(本机没装的预设字体也能按名字搜到);同一个字体只列一次;拒绝了字体权限的浏览器不再被永久记住,二十秒后再打开会重试。
42
+ - 每一条按主题生成的规则都带上了主题前缀(逐主题目前还没开放,属提前修掉的隐患)。
36
43
 
37
44
  ### 变更
38
- - **样式表瘦身**:把级联的 `:not(a):not(b):not(c)…` 收成一个 `:not(a,b,c…)`,那条排除链原本会被复制进对话框规则的两个选择器与界面规则各一份,每份省约 120 字节。用户真实配置的注入样式 **2735 → 2440 B**,`:not(` 出现次数 **62 → 3**。**特异性确实被降低了**(`:not(a):not(b)` 累加参数特异性,`:not(a,b)` 只取最高的那个),所以先量了风险:DSH 0.1.5-rc.2 全部前端资产里只有一条 `!important` 排版声明(markdown 行内 `code` 的 `font-size`,而本插件从不写 `font-size`),已装插件里也没有第二条 `!important` 的字重/字体规则;本插件的规则全部带 `!important`(本来就赢得过主题那些非 important 的 class 规则),而且彼此靠这些排除列表**互不重叠**。真机复测 WEIGHT OK 17 项全过(界面移动 137 个文本元素、markdown 子树 0 个)。顺带把逐主题用的前缀函数改成只按**顶层逗号**切分选择器列表(`:not(a,b)` 里的逗号不再被切断)。
39
- - **退役的字号/行高轴退出「值表面」**:`sizeOffset` 与 `lineHeight`(界面的字号与行高,0.2.0 起不产生任何规则)仍然留在 schema 与文档字段里(老配置照常解析、`normalizeConfig` 照常带出来),但不再进预设快照、不再参与明暗对比——只在这两个字段上有差别的明暗配置不再生成第二份样式表;卡片上的「全部重置」**仍然会清掉它们**,免得一个永远不显示的值烂在文档里。
40
- - **清理死代码**:删掉 31 个不再渲染的词典键(中英各 31 个,含退役的界面字号/行高、逐主题编辑器、旧预设按钮组与固定预览的文案)、1 条死 CSS(`.dfp-presetForm`)与 7 个死导出(`MARKER`、`isShorthandToken`、`bodyTokenNames`、`uiLineTokenNames`、`deltaTokenNames`、`codeTokenNames`、`uiShorthandTokens`、`scaleFontShorthand`)。`VALUE_FIELDS` / `RETIRED_FIELDS` / `DURABLE_FIELDS` 三个清单把「值轴」「仍存储但不渲染」「全部持久字段」分开写清楚了。
41
- - **`npm run verify:browser` 统一入口**(`test/browser-verify.mjs`):默认跑只读的 `browser-probe` / `ui-walk` / `style-verify`,`--writers` 再加会写设置命名空间的四支(各自快照并在退出前还原),`--only a,b` 挑子集,逐支打印耗时与退出码并汇总。两支走查脚本按 0.2.x 的卡片重写:`slider-walk` 先展开分区再拖(卡片默认全部收起),断言「拖动不写、松手才写、每条轴落到自己的字段」;`split-walk` 先写入三档字体栈,断言简单模式两格与剩余提示、模式往返不改动栈、真实面板里选一个西文字体只替换西文槽。**找不到控件现在是失败**(以前打印一句「not found」然后退出 0,等于假绿)。
45
+ - **注入的样式更小、后台更省电**:排除列表的写法收敛后,一份真实配置的样式表从 2735 字节降到 2440 字节;界面字号轴的令牌轮询改为先比三个廉价信号,环境没变就完全不读样式表。
42
46
  - 兼容表补 0.2.3。
43
47
 
44
- ### 测试
45
- - `test/run.mjs`(86 项):新增面板分组规则(预设四组恒在、同字体不重复、本机分组上限与消毒、拒绝后的重试契约)与轮询闸门的计数用例(含负对照:去掉闸门后该用例红)。
46
- - `test/render-card.mjs`(21 项):新增「第二次渲染(状态变化后 hook 顺序不变)」「拒绝写入的两种形态都上报」「导出兜底交出完整 JSON 并被选中」「重置清掉退役轴」「退役轴不再是值轴」。
47
- - 真机(受管实例 + 无头 Edge):WEIGHT OK(17 项)、SPLIT OK(9 项)、SLIDER OK(9 项)、SPLIT WALK OK(14 项)、browser-probe / ui-walk 通过。
48
48
 
49
49
  ## [0.2.2] - 2026-09-16
50
50
 
51
51
  ### 修复
52
- - **简单模式下选/清「中文字体」直接报错、改动丢失**(0.2.0 引入):中文槽的两个处理函数读的是 `edit[field]`,而模块里根本没有 `edit` 这个绑定(真正的变量叫 `editing`)。西文槽正常,只有中文槽踩雷——**任一字体分区**(对话 / 界面 / 代码)在**简单模式**下点中文格子选字体、或点中文 chip 的 ×,都会抛 `ReferenceError: edit is not defined`,设置不写入、界面无变化。修法就是那两处改成 `editing[field]`。**为什么一直没被发现**:卡片测试只渲染、从不触发任何控件回调,所以这类「回调没接线」的错误离线全绿。
53
- - **补上这一类错误的护栏**:`test/render-card.mjs` 新增两条用例,把渲染出来的**每个控件的回调都按真实载荷调一遍**(滑块的 min/max、分段控件的每个选项、字体选择器的 onPick/onRemove、预设下拉的整条预设对象、文本输入的事件、以及每个 `<button>` 的 onClick),断言「一个都不许抛」;并单独断言三个字体分区的**中文槽确实写进了设置**(不只是没报错)。用 0.2.1 的写法做对照实验:注入旧代码后这两条用例红,恢复后绿。
54
- - **`lib/` 与 `src/` 不再可能悄悄不同步**:新增 `test/artifacts.mjs`,把 `build.mjs` 的产物在内存里重新渲染一遍,与仓库里的 `lib/client.js`、`lib/shared.cjs`、`lib/index.js` **逐字节比对**(`npm run verify` 跑它,`prepublishOnly` 也跑它)。此前所有测试都只读 `lib/`,只改 `src/` 不构建的话测试照样全绿、发出去的还是旧包。顺带把 `build.mjs` 拆出纯函数(`renderArtifacts` / `readSources`)并加了「被 import 时不产生副作用」的判定。
55
- - **发布 workflow 从来没跑过测试**:`npm pack` **不会**触发 `prepublishOnly`(只有 `npm publish` 会),所以 tag 触发的发布流程一直在打包未测试的产物。现在加了 `npm install` + `npm run verify` 两步(**放在打包之前、且不先构建**——`test/artifacts.mjs` 要校验的正是「仓库里的 `lib/` 就是当前 `src/` 的产物」,先构建会把这个问题掩盖掉),并补一条「tag 必须等于 `package.json` 版本」。
56
- - **CHANGELOG 提取的前缀匹配会串版本**:release 正文用 `index(line, "## " pat)` 取章节,`0.2.1` 会命中 `0.2.10`。现在按 `## <版本> ` 精确匹配,并在找不到该版本章节时**直接让 job 失败**(以前会静默退回 GitHub 自动生成的 notes)。
57
- - **CJK 判定漏了两个常见字体名**:`MS PGothic` / `MS PMincho` 被判成非中文字体(`MS Gothic` / `MS Mincho` 正常),而这会直接影响简单模式的「中文槽」推导,故按名字启发式的方向修成 `ms ?p?(?:gothic|mincho)`。
58
- - **`shiftTokens` 补上零保护**:偏移为 0 时会写出 `calc((14px) + 0px)` 这种真声明,与 `isDormant` / `sameValueSet` 的语义冲突。当前调用侧都拦着,属于给未来调用者拆雷。
59
- - **`build.mjs --watch` 盯的是文件而不是目录**:源文件被编辑器改名后,进程还守着已经消失的 inode,永远不再重建。现在监听 `src/` 目录并按文件名过滤,重建成功也会打一行日志。
52
+ - **简单模式下「中文字体」选不了也清不掉**:点中文格子选字体、或点中文 chip 的 ×,会直接报错且改动丢失(西文格正常);现在三个字体分区的中文槽都能正常写入。
53
+ - **`MS PGothic` / `MS PMincho` 也被认作中文字体**:此前会被判成西文,影响简单模式中文槽的推导。
54
+ - **补上「控件没接线」这类漏洞的护栏**:离线检查现在会把卡片上每个控件的回调都真点一遍(滑块、分段控件、字体选择器、预设下拉、文本输入、每个按钮),漏接会立刻失败。
60
55
 
61
56
  ### 变更
62
- - **文档纠错**:README 里「改 `src/client.js` 后刷新页面即可」漏了最关键的一步——浏览器加载的是 `lib/`,必须先 `node build.mjs`(中英两份都改了);「市场会依据 `engines.dsh` / `dshReleases` / `peerDependencies` 做宿主兼容预检」这句在本地 rc.2 全量包里找不到任何对应实现,改成如实描述(市场条目**声明**这些兼容信息,供安装前判断)。
63
- - `package.json` 增加 `devDependencies`(`@deepseek-ai/schemastery`,测试用)与 `verify` 脚本;兼容表补 0.2.2。
57
+ - **发布不再可能发出未测试的包**:打标签时先跑一遍完整检查再打包,同时校验打包产物与源码一致、标签与版本号一致。
58
+ - 文档纠错:改完源码必须先构建(浏览器加载的是构建产物);市场条目只是**声明**兼容信息,并不存在安装前的自动预检。
59
+
64
60
 
65
61
  ## [0.2.1] - 2026-09-16
66
62
 
67
63
  ### 修复
68
- - **「跟随关掉后界面字重不回落,重置也没用」**(真机复现):界面样式表在页面里其实有**两份**——host 半随首页下发一份做首帧(`webserver/index-inject`),客户端半再维护一份,而**下发的那份只在下次首页渲染时才重建**。两份都在时「新增/改值」没问题(客户端那份在后面,同特异性后写的赢),但**「删掉一条规则」不可能生效**:关掉跟随、或把界面字重重置,客户端下发的新样式里已经没有那条 `font-weight` 规则,而首页那份旧规则还挂着,于是界面继续是跟随时的 480,直到刷新页面。修法两步:① host 半改发 **`html` 行**(`<style data-plugin="dsh-fonttune" data-plugin-css="dsh-fonttune">…</style>`,而不是不带属性的 `style` 行),客户端半启动时**认领这个元素**、不再另建一个(`createStylesheet` 先 `querySelector` 再兜底自建,并检查 `isConnected`)——之后页面里永远只有一个该元素的实例,规则可增可删。真机验证:`test/weight-verify.mjs` 新增「不刷新页面把跟随关掉」一步,界面 480 → 400(DSH 自己的值)立刻生效,样式表元素始终 `count=1`,且首页源码里确实带那个打标的 `<style>`。
69
- - **从跟随切到不跟随,界面不再「跳回自己的旧值」**:以前关闭跟随只是把开关改掉,界面立刻回落到它自己存过的值(多半是空 → DSH 默认),而滑块读数还是 400 与实际不符。现在**关闭的那一刻把界面正在显示的值写进它自己的两个字段**(字体取对话的字体、字重取对话的字重,都是 `resolveAxes` 展开后的有效值),所以关掉开关视觉上零变化、滑块也从当前值继续调;对话没设的那一项(空字体、未设字重)则清掉界面自己的字段,保持 DSH 默认。真机走查的期望由此变成:跟随时 480 → 关掉跟随 → 界面仍是 480 且滑块显示 480 → 手动改或「重置」才回到 400。
70
- - **界面字重回来了,而且真的碰不到对话**:0.2.0 把界面字重整条退役,理由是「字重只能写成 `body, body *`,无法不波及对话」。这次在真机页面上把这条规则量了一遍,找到了隔离点:**把对话 markdown 整棵子树(以及代码表面)从这条规则里 `:not()` 排除**。实测(活的 DSH 页面、计算样式):`body, body *` 加排除后把界面的文本元素全部改到目标字重、**markdown 子树内的元素一个都没动**;若不加排除,同一份配置会把对话也一起拉动。界面字重现在是「界面」分区里的独立一轴(跟随关闭时出现),范围 300 ~ 600、读数同样显示 400。顺带给界面字重补上代码侧的回退:界面字重一旦设置,未单独设字重的代码表面会显式写回 `normal`(DSH 原本就是这样算的),所以代码块不会被界面字重带跑。
71
- - **对话分区的“跟随界面”描述全部改掉**:跟随关系早已是单向的(界面跟随对话),但对话分区的四条小字仍在写「未设置时跟随界面偏移 / 字重 / 比例 / 留空表示跟随界面字体」。现在四条都改成如实描述(「0 表示保持 DSH 原样」「未设置时保持 DSH 原本的粗细」「100% 表示保持 DSH 原样」等),并加了回归测试:中英两份词典的这四条文案里不允许再出现「跟随界面 / follows the interface」。界面分区的跟随开关下面新增一行小字「字体与字重」,说明这个开关管的是哪两项。
72
- - **跟随开关现在管字体和字重两项**:开启(默认)时界面沿用对话的**字体与字重**,界面自己的两个控件隐藏;关闭后界面用自己的字体与字重,两边互不影响。跟随开启时若对话没设字重,界面仍回落到自己存过的值(与字体同一条兜底规则),因此老配置升级后不会丢值。
73
- - **卡片预览不再说谎**:界面字重是 `!important` 的全页规则,会连卡片自己的预览框一起刷成界面字重。给对话预览加了 `dfp-previewDialog` 标记并把它排除在界面规则之外、单独下发对话字重;代码预览沿用代码轴(已在 `CODE_SELECTOR` 里),界面预览留在界面规则内——三块预览的颜色/字重现在各自对应真实表面。
64
+ - **关掉「界面跟随对话」后界面字重不回落、重置也没用**:页面里此前同时存在两份注入样式,随首页下发的那份只在下次刷新时重建,所以「删掉一条规则」这类改动永远不生效;现在页面里始终只有一份,关掉跟随或重置都会立刻生效。
65
+ - **从跟随切到不跟随,界面不再跳回旧值**:关闭的那一刻把界面当时正在显示的值存成它自己的值,视觉上零变化、滑块也从当前位置继续调;对话没设的那一项保持 DSH 原样。
66
+ - **界面字重回来了,而且真的碰不到对话**:把对话正文与代码整棵子树从界面字重规则里排除掉,实测界面文字全部生效、对话一个字都没被带动。
67
+ - **对话分区里「未设置时跟随界面」的说明全部改成如实描述**(跟随关系一直是单向的:界面跟随对话)。
68
+ - 卡片中的预览不再被界面字重波及:对话、代码、界面三块预览各自对应真实效果。
69
+
70
+ ### 变更
71
+ - **「跟随对话」开关现在同时管字体与字重**:开启时界面沿用对话的字体与字重(界面自己的两个控件隐藏),关闭后界面用自己的两项,两边互不影响。
72
+
74
73
 
75
74
  ## [0.2.0] - 2026-09-16
76
75
 
77
76
  ### 新增
78
- - **三套独立排印**:排印从「正文/代码」两套扩成**三套**——「**对话**」(会话 markdown 正文、表格、标题)拥有**全部轴**(字体、字号、行高、字重);「**界面**」(设置页、侧栏、工作区、标题与按钮)只有**字体**一项,且**默认跟随对话**(可关);「**代码**」独立成轴。对话的实现走 markdown 容器作用域规则(`[class*="_markdown_" i]` + 排除代码表面的 `:not()` 复合选择器),不碰哈希类名。
79
- - **对话字号轴**:-3 ~ +6 px,**加在官方「对话字号」(`--dsh-content-font-size`)之上**,官方数值始终是基准。实现只在**源头加一次**:`--dsh-content-font-size` 变成「官方值 + 偏移」,次级字号、delta、delta 派生的标题字号与行高全部按派生关系自动跟随——段落、标题、行高一起动,且不会重复计入。官方字号滑块的任何改动仍由 `theme/change` 即时跟随。
80
- - **对话行高轴**:倍率 100% ~ 160%(step 5),重建 markdown 的 `font` 简写内联行高;**代码行高**是独立的加法偏移(-4 ~ +8 px),与代码字号偏移复合。
81
- - **代码连字**:三档开关(默认 / 开启 / 关闭)写 `font-variant-ligatures`(浏览器默认即开启,这是给想关掉的人的开关),高级模式另有经消毒的 `font-feature-settings` 值输入(`"ss01" on, "cv01" 1` 形态)。
82
- - **拒绝合成样式**:禁用浏览器为无斜体/无粗体字面伪造的伪斜体/伪粗体(`font-synthesis-style` / `font-synthesis-weight`);简单模式一个开关同时禁用两者,高级模式两个独立开关。
83
- - **界面跟随对话(开关)**:对话是「全轴」的那一套,界面只有字体一项,所以**默认由界面跟随对话**——对话分区没有开关、四项始终可调;界面分区顶部是「跟随对话设置」开关(默认开启),开启时界面沿用对话的字体(字体控件隐藏,界面自己的旧值留作兜底),关闭后出现界面自己的字体控件。对话未设置字体时界面回落到自己的值,因此**老配置升级后视觉零变化**。
84
- - **预设方案**:编辑模式正下方的**下拉选择框**(框内右侧是与卡片一致的下拉箭头),内置五个默认配置(默认配置 1 ~ 5),点开选择即应用;**选中方案后所有改动自动存入该方案**(自动保存,无需手动「保存当前」)。右侧「改名 / 导入 / 导出」管理方案——改名只改名字、已存数值原样保留,导出复制 JSON、导入按名合并(导入走同一条消毒管线,最多 20 个)。方案与当前选中的方案名都存宿主设置文档,跨浏览器跟随。操作结果(如「已复制到剪贴板」)显示在下行的右侧并**约 2.6 秒后自动淡出**(不顶动任何布局)。
85
- - **卡片信息架构重构**:卡片展开后自上而下是编辑模式行 + **预设条** + **对话 → 界面 → 代码**三个**手风琴分区**(对话排在最前,因为它拥有全部轴),默认全部收起,标题行显示当前值摘要、箭头固定在最右,同一时刻最多展开一个;「拒绝合成样式」只有一两个开关,**不再单独占一个手风琴分区**,直接放在三个分区下面。分区展开后各自在分区末尾出现一个**独立预览框**(只渲染该分区的部分;曾试过悬浮式,真机体验不佳后改回内联)。**行格式与 dsh-quick-toc 对齐**:每项设置是 14px 上下内边距的字段行(原为 16px),开启/关闭一律用「大框切两半」的联合分段控件(28px 高,原为 22px 的胶囊开关),标签在左、控件在同行的右侧(`.dfp-inline` 与 quick-toc 的 `.dqt-pinline` 同构),因此两张卡片的行高与控件对齐一致。
86
- - **界面的字号 / 行高 / 字重三轴退役(实测结论)**:DSH **没有界面尺寸接口**——设置页、左右侧栏与工作区的文字尺寸是上游 CSS 里的字面量(settings-plugins 22 处、workspace 18 处、sidebar 6 处 `font-size`,**0 处**使用界面阶梯 token),而界面阶梯 token(`--dsw-font-xs-13` / `--dsw-font-s-14` 等)只被会话区的 6 个组件(tool / trajectory / attachment / chat / conversation / cordis)消费;唯一的官方字号接口是「对话字号」。此前用界面阶梯去缩放,既够不到设置页与侧栏,又会顺着 `--dsh-content-font-size` 把**对话**改了;字重更棘手——它只能表达为 `body, body *` 全页规则,无法不波及对话(收窄到 markdown 之外又会连 DSH 给标题的字重一起剥掉)。结论:**字号、行高、字重全部归对话独占**,界面只保留实测生效的字体(`body` 全页 + 主题变量)。`sizeOffset` / `lineHeight` / `weight` 字段保留在 schema 里(旧文档与预设继续解析),但**不再下发任何规则**。
87
- - **分主题数值移出本版**:深浅色在本版共用同一套数值——顶栏的分主题开关与浅/深编辑切换整体移除(宿主 schema 里的深色字段保留但休眠,渲染层强制忽略),功能留到下个版本再评估。
88
-
89
- ### 修复(真机走查)
90
- - **预设提示不再常驻、也不顶动布局**:「方案已复制到剪贴板。」这类提示以前会一直挂在卡片里,且作为新的一行把下面的分区顶下去。现在它和「选中方案后,所有改动自动存入该方案。」**共用一行**:提示出现在该行**右侧**,**2.6 秒后自动渐隐**(透明度过渡 0.24s,之后清空文本)。那一行**始终挂载**且只有一行高,提示文本用 `nowrap + ellipsis` 限宽——出现与消失都不会改变行高,因此下方内容一动不动。连续两次相同操作也不会失灵(消息带自增 id,自动隐藏的定时器会重置),超长文本(如剪贴板不可用时的回落内容)可在悬停时通过 `title` 看全文。
91
- - **「改界面,动的却是对话」**:界面字号轴此前在缩放界面阶梯 token 的同时,也会缩放对话专用的官方内容字号链(`--dsh-content-font-size` / 次级字号),而 markdown 的每一级尺寸都由这条链派生——所以调界面字号看起来就是在调对话。现在界面轴被明令隔离在这条链之外(`bodyTokenNames` 排除 `--dsh-content-font-*`),并且界面字号轴本身也因「没有接口」而退役(见上)。
92
- - **对话字号偏移过去只影响标题、不影响段落**:原实现只改写 delta 与字面量字号,而段落的字号取自 `var(--dsh-content-font-size,14px)`(`var()` 在自定义属性里于**声明处**求值,深层作用域覆盖无效),所以段落纹丝不动。新实现改为在源头把 `--dsh-content-font-size` 声明为「官方值 + 偏移」,段落、标题、行高一起跟随。
93
- - **暗色主题下深色覆盖会被浅色值盖掉**:按主题解析时,深色映射此前是合并在**已展开**的取值上,跟随规则一改就会出现互相覆盖。现在先在原始值上合并深色覆盖、再做展开,深色设的字体不会再被浅色值顶掉。
94
- - **字重读数显示 400**:未设置的字重以前读数写「未设置」,而滑块其实停在 400、提示也写「400 表示保持 DSH 原样」——三者不一致。现在未设置的读数就是 **400**(对话字重与代码字重同此)。
95
- - **行高滑块拖动时 % 消失**:拖动中的待定读数没带单位,松手才出现——现在拖动读数同样显示 `%`(字号/代码行高的 `px` 前缀原本就带)。
96
- - **对话分区全是英文“变量名”**:对话轴的字段标签键(`dialog.*`、`size/line/weight.dialogLabel` 等)没有进双语词典,`translate` 回退显示键名——已补齐中英两份。
97
- - **手风琴展开后首行离标题太远**:字段行自带 16px 顶部内边距叠加在分区体上——分区体的首个字段收紧到 10px。
98
- - **展开态箭头不在最右**:展开后摘要消失导致箭头滑到标题旁——展开时渲染一个弹性占位符,箭头两种状态下都钉在最右。
77
+ - **排印分成三套**:**对话**(正文、表格、标题,字体 / 字号 / 行高 / 字重全都有)、**界面**(字体一项,默认跟随对话)、**代码**(独立一套,含连字与特性开关)。
78
+ - **对话字号**:在 DSH 自己的对话字号之上 −3 ~ +6 px,段落、标题与行高一起跟随。
79
+ - **对话行高**:100% ~ 160%;代码行高是独立的加减偏移。
80
+ - **代码连字**:默认 / 开启 / 关闭三档,高级模式还可填 `font-feature-settings`。
81
+ - **拒绝合成斜体与合成粗体**:简单模式一个开关同时禁掉,高级模式分开控制。
82
+ - **预设方案**:卡片里的下拉框,内置五个默认方案,点选即应用;选中之后所有改动自动存入该方案;右侧可改名、导入、导出(导入按名合并,最多 20 个,跨浏览器同步)。
83
+ - **卡片改版**:编辑模式 + 预设条 + 对话 / 界面 / 代码三个可折叠分区,默认全部收起、一次只展开一个,展开后末尾是该分区的独立预览;行高与控件形状与 dsh-quick-toc 对齐。
99
84
 
100
85
  ### 变更
101
- - **字体族写入接入官方主题层**:`ctx.theme.overrideTokens` 注册 `--dsw-font-family` 与 `--ds-font-family-code`/`--dsw-font-mono` 的 `{light, dark}` 对,presenter 在每次主题切换后把数值内联到 body——字体族不再依赖样式表与轮询的时序。`theme/change` 事件即时重读 token 并重涂(官方字号行的变更也在其中),4 秒轮询保留作兜底。
102
- - **兼容表**改列 0.2.0 为最新版本;README 重写为三套排印 + 预设的结构;新增 `docs/`(0.2.0 的调研与方案记录,不随包发布)。
86
+ - **界面的字号、行高、字重三项退役**:DSH 没有界面尺寸接口(设置页与侧栏的字号是上游写死的),此前这几轴既够不到界面、又会误动对话;现在字号 / 行高 / 字重归对话独占,界面只保留实测生效的字体。老配置里的这些字段继续保留、照常解析。
87
+ - 字体族改由官方主题层下发,主题切换后立刻生效。
88
+ - 深浅色本版共用同一套数值,按主题分别设置留到以后。
89
+
90
+ ### 修复
91
+ - **「改界面,动的却是对话」**:界面字号轴此前会连带缩放到对话的字号链,现已隔离。
92
+ - **对话字号偏移只影响标题、不影响段落**:改为在源头加一次偏移,段落、标题与行高一起跟随。
93
+ - **暗色主题下深色的值会被浅色的值盖掉**。
94
+ - **字重读数写「未设置」而滑块停在 400**:现在读数就是 400。
95
+ - **行高滑块拖动时 `%` 会消失**:拖动过程中同样显示单位。
96
+ - **对话分区的标签显示成英文变量名**:中英文案补齐。
97
+ - 展开后首行离标题太远、展开态箭头不在最右。
98
+
103
99
 
104
100
  ## [0.1.6] - 2026-09-15
105
101
 
106
102
  ### 修复
107
- - **配置卡片的展开/收起箭头与宿主不一致**:原为文本字符 `⌄`——按文字字重渲染,比宿主其它卡片的箭头**更细**;且旋转写在文本行盒上,字符墨迹在行盒内不居中(落在基线上),`rotate(180deg)` 绕盒心转时字符被甩开,看起来「不是以自己为中心转」。现改为宿主 PluginCard 同款 14×14 内联 SVG(path 从 DSH 前端 bundle 逐字节提取,与 dsh-quick-toc 0.6.3 的修法同构);旋转类挂在 `<svg>` 本体上,绕图标自身中心转。真机验证:与同页 11 个宿主自家箭头 path **逐字节一致**,尺寸同为 14×14。
103
+ - **配置卡片的展开箭头与宿主不一致**:原先是文本字符,比宿主卡片的箭头更细、旋转时还会偏移;现在换成与宿主同款的内联箭头图标,绕自身中心翻转。
104
+
108
105
 
109
106
  ## [0.1.5] - 2026-09-15
110
107
 
111
108
  ### 新增
112
- - **代码字重**:字重从「一个全局偏移」拆成**两个互不影响的轴**——「正文字重」覆盖正文与界面文字(含标题),「代码字重」只覆盖代码块、行内代码、工具代码块与终端输出。原来那一个滑块同时压两条链路,调正文必然带动代码;现在各调各的。字段为 `weightCode`(300~600,0 = 不动),与既有的 `weight` 并列;`weight` 语义收敛为「正文与界面文字」。
109
+ - **代码字重**:字重拆成「正文字重」与「代码字重」两根互不影响的滑块(各 300 ~ 600,400 表示不动),调正文不再带动代码。
113
110
 
114
111
  ### 变更
115
- - **卡片布局**:字重滑块同样移进对应字体那一组——正文字体 → 正文字号偏移 → 正文字重 → 代码字体 → 代码字号偏移 → 代码字重;两处文案分开(「正文字重」/「代码字重」),卡片描述改为「正文与代码字体、各自的字号与字重、中西文分栏」。
116
- - **代码轴不再被正文字重带着走**:字重没有可依附的 token 链(rc.2 实测**零处** `font-weight: var(--dsw-font-…)`——主题是在自己的类规则里写字面量),所以正文轴一直是 `body, body *` 的全量 `!important` 规则。代码轴现在在这条规则**之后**追加一条作用域规则,靠更高特异性(`[class*=…]`)或更靠后的同特异性(`pre` 与 `body *` 同分)取胜;选择器覆盖 `pre` / `code` / `kbd` / `samp` / `var` / `tt` / `textarea` / `.cm-editor` **及其后代**(全量规则命中每一个元素,所以 `<pre>` 里高亮用的 `<span>` 也必须点名),再加 `[class*="code" i]` 与 `[class*="terminal" i]` 覆盖工具代码块与终端这类没有语义标签的纯 `div`。**代码字重未设置时也会写这条规则**(值 `normal`),否则全量规则会把代码一起拽到正文字重上;旧配置升级后正文字重行为不变、代码回到 DSH 原值。
117
- - **兼容表**改列 0.1.5 为最新版本。
112
+ - 卡片里字重滑块移进对应的字体分组,两处文案分开。
113
+ - 未设置代码字重时显式写回 DSH 原值,旧配置升级后代码不会跟着正文一起变粗。
114
+
118
115
 
119
116
  ## [0.1.4] - 2026-09-15
120
117
 
121
118
  ### 新增
122
- - **代码字号偏移**:字号从「一个全局偏移」拆成**两个互不影响的轴**——「正文字号偏移」作用于正文与界面文字,「代码字号偏移」只作用于代码块与行内代码。原来那一个滑块同时缩放两条链路,调正文必然带动代码;现在各调各的。字段为 `sizeOffsetCode`(-3 ~ +6 px,0 = 不动),与既有的 `sizeOffset` 并列;`sizeOffset` 语义收敛为「正文与界面文字」,旧配置升级后代码字号回到 DSH 原值、正文行为不变。
119
+ - **代码字号**:字号拆成「正文字号」与「代码字号」两根互不影响的滑块(各 −3 ~ +6 px,0 表示不动),调正文不再带动代码。
123
120
 
124
121
  ### 变更
125
- - **卡片布局**:字号滑块移到对应字体下面——正文字体 → 正文字号偏移 → 代码字体 → 代码字号偏移 → 字重;两处滑块文案分开(「正文字号偏移」/「代码字号偏移」),卡片描述改为「正文与代码字体、各自的字号偏移、字重、中西文分栏」。
126
- - **代码链路的消费点**:DSH 的 markdown 代码 token 都是 `font` 简写(`--dsw-font-markdown-code` / `-code-block` / `-code-block-small`,值是 `11px/19px <family>` 这种形式),真正被样式表消费的是简写而不是 `-font-size` 分件,所以代码轴连同简写一起重写(尺寸与行高按比例乘、字体族原样保留),分件也一并覆盖;内置兜底表按 rc.2 的真实取值修正(此前记的 13px/20px 与主题不符),并补上三个简写名。
127
- - **兼容表**改列 0.1.4 为最新版本。
122
+ - 卡片里字号滑块移到对应字体下面,两处滑块文案分开;旧配置升级后正文行为不变、代码回到 DSH 原值。
123
+
128
124
 
129
125
  ## [0.1.3] - 2026-09-14
130
126
 
131
127
  ### 变更
132
- - **开关行文案与形状**:左侧说明由「编辑方式」改为**「编辑模式」**;右侧两个选项恢复**连体分段**(一个外框、选项之间一条分隔线),选中态仍用灰底 `--dsw-specific-sidebar-nav-item-active` + 正文色文字(0.1.2 曾一度改成两个独立按钮,现改回)。
133
- - **卡片描述**:补上「中西文分栏」(中英两份),与卡片实际能力一致。
134
- - **README 功能列表精简**:去掉「松手才生效」「首帧不闪字体」「中英双语」三条——描述的是实现细节而非功能项,行为本身未变(仍记录在本文件 0.1.0 一节)。
135
- - **兼容表**改列 0.1.3 为最新版本。
128
+ - 开关行左侧说明改为「编辑模式」,右侧两个选项恢复为一个外框、中间一条分隔线的连体分段样式。
129
+ - 卡片描述补上「中西文分栏」。
130
+ - README 精简:去掉「松手才生效」「首帧不闪字体」「中英双语」三条实现细节(行为未变)。
131
+
136
132
 
137
133
  ## [0.1.2] - 2026-09-14
138
134
 
139
135
  ### 变更
140
- - **README 重写**:只讲功能、安装、使用、兼容性与开发,设计取舍、实现原理与开发期踩坑不再写进 README。中英两份同步。
141
- - **兼容性声明收紧**:`engines.dsh` 仍为 `>=0.1.5-rc.2`,而兼容表只列 **0.1.5 线的最新版本**(`0.1.5-rc.2`)——它包含这条线的全部修复,是同一大版本里最稳的一版;同一大版本内的旧版本按语义化版本约定向上兼容,不再逐个罗列。
142
- - **npm 描述精简**:去掉「配置入口」一句与 `(DSH)` 括号,改为一句功能描述。已发布版本的元数据无法修改,因此这条从 0.1.2 起生效。
143
- - **英文卡片标题**:`Font plus` → `Font tune`,与包名一致(中文标题仍是「字体增强」)。
136
+ - README 重写为「功能 / 安装 / 使用 / 兼容性 / 开发」,设计取舍与开发期踩坑不再写进 README(中英两份同步)。
137
+ - 兼容声明收紧:只列 0.1.5 线的最新补丁——同一大版本内的旧版本按语义化版本约定向上兼容,不再逐个罗列。
138
+ - npm 描述精简(已发布版本的元数据无法修改,故从 0.1.2 起生效);英文卡片标题改为「Font tune」,与包名一致。
139
+
144
140
 
145
141
  ## [0.1.1] - 2026-09-14
146
142
 
147
143
  ### 新增
148
- - **英文更新日志**:新增 `CHANGELOG.en.md`,随包发布。
149
- - **GitHub Release 工作流**:推送 `v*` 标签时自动打包并创建/更新 Release,产出**不带版本号**的 `dsh-fonttune.tgz`(供市场 `tarball:` 字段使用,不会因为下次发版而 404);Release 正文取本文件对应版本的中文段落,英文段落折叠在 `<details>` 里。
144
+ - 英文更新日志 `CHANGELOG.en.md`,随包发布。
145
+ - 打完 `v*` 标签自动打包并创建 / 更新 Release,另外产出一个不带版本号的 `dsh-fonttune.tgz`(供市场条目长期引用);Release 正文取对应版本的中文段落,英文折叠在里面。
150
146
 
151
- ### 变更
152
- - 订正 README 与更新日志里过期的离线检查数(35 → 42 项)。运行代码与 0.1.0 相同。
153
147
 
154
148
  ## [0.1.0] - 2026-09-14
155
149
 
156
- 首个版本。架构取「host 半 + 客户端半」双半插件:客户端半负责设置卡片与样式注入,宿主半注册设置命名空间并把已保存的声明随首页下发。
150
+ 首个版本:宿主半 + 客户端半的双半插件。客户端半负责设置卡片与样式注入,宿主半注册设置命名空间并把已保存的声明随首页下发,因此首帧就是保存的字体。
157
151
 
158
152
  ### 新增
159
- - **插件配置卡片**:注册在 `settings.plugin.item`、key 为 `dsh-fonttune` 的宿主设置命名空间,出现在 **设置 → 插件 → 插件配置** 里;保存/重置走 DSH 自己的设置文档(`settings.yaml`)。
160
- - **正文 / 代码字体族**:两套独立的 CSS font-family 回退列表,可为空(= 完全不动 DSH 的字体栈)。
161
- - **选字体面板**:内置 等宽 / 中文(CJK)/ 拉丁 / 通用 四组预设;Chromium 下用 `queryLocalFonts()` 补「本机已安装」分组;搜索不到的名字给「使用 xxx」新建入口;每行用其自身字体渲染。
162
- - **中西分家(简单模式)**:卡片顶部一个开关——**简单模式**把字体栈拆成「西文字体」「中文字体」两个单选格(正文/代码各一对),内部仍维护同一条栈;**高级模式**是完整的 chips 编辑器。语义为「简单模式只动最前面」:西文格 = 栈里第一个非中文项(改选原地替换、无则插到最前),中文格 = 第一个中文项(改选原地替换、无则**紧跟西文槽插入**,保证 `西文, 中文, 通用兜底` 的 CSS 语义正确);两格之外的所有项与顺序**原样保留**并在「其余回退项」一行提示;**切换模式是纯视图切换、零写入**。中文判定以名字启发式优先(宽松正则 + 含 CJK 字符的本地化名),canvas 测宽只对名字不像 CJK 的字体补充确认——未安装的预设字体也能正确落入中文槽。开关行布局:左侧「编辑模式」说明 + 右侧连体分段式小切换(选中项用灰底)。
163
- - **拖拽排序**:已选字体是 chip 列表,可拖拽调整回退顺序,同时保留前移/后移按钮(键盘与触屏可用)。
164
- - **实时预览**:中英混排 + 代码两行,随当前配置实时渲染。
165
- - **全局字号偏移**(-3 ~ +6 px):等比缩放 DSH 自己的字号 token(`--dsh-content-font-size`、`--dsw-font-*-font-size` / `-line-height`),基准值实时读,因此与 DSH 的「字号大小」滑块**叠加**而非互相覆盖;token 名单运行时发现,内置兜底表。
166
- - **全局字重**(300 ~ 600,任意整数):作用于 `body, body *`;400 与未设置都表示「不动」。
167
- - **滑块松手才落盘**:字号偏移 / 字重滑块拖动时只更新本地待定值与读数,`pointerup` / `touchend`(窗口捕获级监听)或失焦、键盘抬起时才一次性提交——拖动过程零写入,不再每挪一格就重算字号 token 并写设置文档。**松手后保持待定值直到宿主确认值回来**:提交与设置文档回执之间若立刻清本地值,滑块会闪回旧值再跳到新值(「回弹再到位」),现在以待提交值上屏、回执到达(或外部值变化)才清除;重复 `pointerup` 以「已待确认」守卫挡住,不会重复写同一个值。
168
- - **首帧不闪字体**:宿主半监听 `webserver/index-inject` 注入同款 `<style>`,客户端插件激活前首帧就是保存的字体。
169
- - **中英双语文案**,缺英文回退中文;`queryLocalFonts` 不可用或被拒时静默回退到内置列表。
170
- - **字体名消毒**:按白名单过滤后整体加引号(仅保留字母/数字/空格/`.` `,` `_` `-`),宿主 schema 另加 `^[^{};<>\\]*$` 与长度上限;手工构造的 CSS 注入无法闭合声明或另起规则。
153
+ - **插件配置卡片**:出现在 设置 → 插件 → 插件配置,读写 DSH 自己的设置文档。
154
+ - **正文与代码两套字体族**:各一串回退列表,留空即完全不动 DSH 的字体。
155
+ - **选字体面板**:内置等宽 / 中文 / 拉丁 / 通用四组,Chromium 下可列出本机已安装字体,搜不到的名字可新建,每行用自身字体渲染。
156
+ - **中西分家(简单模式)**:把字体栈拆成「西文字体」「中文字体」两个格子,其余回退项与顺序原样保留;高级模式是完整的 chip 编辑器,支持拖拽排序与实时预览。
157
+ - **全局字号偏移**(−3 ~ +6 px,与 DSH 自己的字号滑块叠加)与**全局字重**(300 ~ 600,400 表示不动)。
158
+ - **滑块松手才写入**:拖动过程零写入,松手后读数保持到宿主确认,不再回弹。
159
+ - **中英双语文案**;字体名经过消毒,无法闭合声明或另起规则。
171
160
 
172
161
  ### 修复
173
- - **卡片首渲染即崩溃(发布阻断 bug)**:`scope.subscribe` 被以裸方法引用传给 React 的 `useSyncExternalStore`,而宿主的 `SettingsScopeController.subscribe` 是读 `this.store` 的原型方法,脱离对象调用时 `this` 为 undefined → slot entry 静默崩溃、卡片在插件配置页里根本不出现。修复 = 一律包闭包保住 `this`。离线测试曾漏检(替身的 subscribe 不依赖 `this`、且 React 替身从不调用 subscribe),现两处替身都已改成会暴露该 bug 的形态。
174
- - **字号偏移的三个缺陷**:① 缺 `!important`——主题把 `--dsh-content-font-size` 写在 body **内联**样式上,内联声明压过普通样式表规则,导致 body 本身不缩放、后代缩放,页面字号劈成两半;② **自我污染复利循环**——token 刷新每 4 秒重读基准值时把本插件样式表里的声明当成基准扫回去,每轮再乘一次比例(表现为「字体一直变大」),修复 = 扫描时跳过自己的标签、`--dsh-content-font-*` 直接读 body 内联原值、计算样式不再作为基准来源;③ **var 链双重缩放**——DSH 的派生 token(delta/secondary/markdown 等)都从 `var(--dsh-content-font-size)` 派生,把它们也缩放会乘两次比例,修复 = 基准值含 `var(` 的 token 跳过显式缩放,经变量链自动继承。
175
- - **字体族不铺满对话区/侧边栏**:对话 markdown 与侧栏元素的 CSS 自己声明 `font-family: var(--dsw-font-family)`,继承被截断,只写 body 的 `font-family` 传不进去。修复 = 在变量源头覆盖(`:root,body{--dsw-font-family:…!important}` 与 `--dsw-font-mono` / `--ds-font-family-code`),显式 `body` / `pre,code` 规则保留作第二路径。
176
- - **字重被吸附到 ±100 档**:原来把任意值 snap 到 300/400/500/600,拖动时看似只按 ±100 跳;现按所选整数原样写 CSS(可变字体全线性,普通字体内建就近取整),滑块步进为 1。
177
- - **消毒过于宽松**:`Arial"; } body { background: url(evil) }` 能残留 `:` `(` `)`,改写为白名单式。
178
- - **宿主半从 CJS 共享模块具名导入会让 ESM 加载失败**(`Named export not found`),改为默认导入。
179
- - **字号 token 正则漏掉 `--dsh-content-font-size`**(该名不含 `-font-` 前缀)及其 `-secondary` 变体。
180
- - **计时器改走 `globalThis`**,不再依赖 `window` 上是否存在这两个方法。
162
+ - **卡片首次渲染就崩溃**(发布阻断):订阅方法被裸传导致 `this` 丢失,卡片在插件配置页里根本不出现。
163
+ - **字号偏移的三个问题**:body 自身没被缩放、刷新时把插件自己的声明当成基准越乘越大、派生字号被重复缩放。
164
+ - **字体族铺不进对话区与侧栏**(那些区域自己声明了字体变量)。
165
+ - **字重被吸附到整百档**:现在按所选整数原样生效。
166
+ - **字体名消毒过于宽松**。
167
+
package/README.en.md CHANGED
@@ -22,23 +22,26 @@
22
22
 
23
23
  ## Compatibility
24
24
 
25
- **One package serves both host lines, 0.1.5-rc.x and 0.1.7-alpha.x**, and the install is the same on either: both were verified for "the settings card opens, the stylesheet really reaches the page, and a change made in the card is written through".
25
+ **One package serves three host lines** — 0.1.5-rc.3 (the floor), 0.1.7-alpha.1 / alpha.2 and 0.1.7-rc.1 — and the install is the same on any of them: all three were verified for "the settings card opens, the stylesheet really reaches the page, and a change made in the card is written through".
26
26
 
27
27
  | Plugin version | Supported DSH versions |
28
28
  | --- | --- |
29
- | **0.2.4** (latest) | 0.1.5-rc.1 / rc.2 / rc.3, 0.1.7-alpha.1 / alpha.2 |
29
+ | **0.2.6** (latest) | 0.1.5-rc.3, 0.1.7-alpha.1 / alpha.2, 0.1.7-rc.1 |
30
+ | 0.2.5 | 0.1.5-rc.1 / rc.2 / rc.3, 0.1.7-alpha.1 / alpha.2 |
31
+ | 0.2.4 | 0.1.5-rc.1 / rc.2 / rc.3, 0.1.7-alpha.1 / alpha.2 |
30
32
  | 0.2.3 | 0.1.5-rc.2 |
31
33
  | 0.2.2 | 0.1.5-rc.2 |
32
34
  | 0.2.1 | 0.1.5-rc.2 |
33
35
  | 0.2.0 | 0.1.5-rc.2 |
34
36
 
35
- `engines.dsh` is declared as **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`** (a prerelease needs its own branch: node-semver admits one only when some comparator in the range sits on that version's exact `major.minor.patch` tuple and carries a prerelease tag of its own, so a plain `>=0.1.5-rc.1` never matches `0.1.7-alpha.1`). Every difference between the lines is settled at runtime rather than by version number:
37
+ `engines.dsh` is declared as **`>=0.1.5-rc.3 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`** (a prerelease needs its own branch: node-semver admits one only when some comparator in the range sits on that version's exact `major.minor.patch` tuple and carries a prerelease tag of its own, so a plain `>=0.1.5-rc.3` never matches `0.1.7-alpha.1`). Every difference between the lines is settled at runtime rather than by version number:
36
38
 
37
- - **The settings service**: up to 0.1.5-rc.x it is `settingsScope.bind({namespace})` (by namespace); from 0.1.7-alpha.x it is `configForms.get(<profile entry id>)` (by entry id, which the installing profile's patch decides — so the plugin claims the form whose served schema names its own fields). Both expose the same `getSnapshot`/`subscribe`/`set`/`unset` face, so one implementation covers them.
38
- - **The configuration card's seat**: the rc line has a keyed cell under Settings → Plugins → Plugin configuration (`settings.plugin.item`); the alpha line dropped that slot and instead contributes an entry to the Plugins page's official list (`plugins.item`, which is how the built-in settings pages are added there), plus a per-package page for a profile that installed this plugin as a bundle (`plugins.bundle.config`). All three are registered; the one a host never declares is inert.
39
+ - **The settings service**: up to 0.1.5-rc.x it is `settingsScope.bind({namespace})` (by namespace); 0.1.7-alpha.x and 0.1.7-rc.x use `configForms.get(<profile entry id>)` (by entry id, which the installing profile decides — so the plugin claims the form whose served schema names its own fields). Both expose the same `getSnapshot`/`subscribe`/`set`/`unset` face, so one implementation covers them.
40
+ - **The loader row id IS the settings key**: from 0.1.7-rc.1 on, settings are stored per entry id, and the first start imports the removed `settings.yaml` by matching section names against it — so the row id this package ships equals its settings namespace (`dsh-fonttune`). That same id also lets the package be installed through the profile's `dsh.profile.bundles`: the plugin manager then shows it as running while the configuration page stays reachable.
41
+ - **The configuration card's seat**: 0.1.5-rc.x has a keyed cell under Settings → Plugins → Plugin configuration (`settings.plugin.item`); the 0.1.7 lines dropped that slot and instead contribute an entry to the Plugins page's official list (`plugins.item`, which is how the built-in settings pages are added there), plus a per-package page for a profile that installed this plugin as a bundle (`plugins.bundle.config`). All three are registered; the one a host never declares is inert. On those pages the card **starts open** — opening the entry is the expansion gesture, and asking for a second click is exactly what this release removed.
39
42
  - **The host half's config values**: once the alpha marks the schema `.volatile()`, the fields the host half receives are cosmokit volatile references (`{get(),[write]}`) rather than plain values, which left the first-frame stylesheet row empty; the references are unwrapped before use.
40
- - **`inject` declares only services both lines have** (`slots`, `locale`): declaring one a host does not provide parks the whole package — on the alpha a declared `settingsScope` kept the entire Web UI from booting. The settings service is looked up with `ctx.get(name)` instead.
41
- - **The alpha's form is addressed by entry id**: while the host's `describe()` view has not arrived, the plugin does not guess a name (a wrong guess means every write is refused) — it stands in with a pending scope, adopts the real form as soon as the view is served, and re-reads immediately.
43
+ - **`inject` declares only services all three lines have** (`slots`, `locale`): declaring one a host does not provide parks the whole package — on the alpha a declared `settingsScope` kept the entire Web UI from booting. The settings service is looked up with `ctx.get(name)` instead.
44
+ - **The 0.1.7 form is addressed by entry id**: while the host's `describe()` view has not arrived, the plugin does not guess a name (a wrong guess means every write is refused) — it stands in with a pending scope, adopts the real form as soon as the view is served, and re-reads immediately.
42
45
 
43
46
  The market entry also states these requirements (`engines.dsh`, `dsh.compatibility.dshReleases` and `peerDependencies` in `package.json`), so the host a version needs can be checked before installing.
44
47
 
package/README.md CHANGED
@@ -22,23 +22,26 @@
22
22
 
23
23
  ## 兼容性
24
24
 
25
- **一份包同时支持 0.1.5-rc.x 与 0.1.7-alpha.x 两条宿主线**,安装方式与宿主线无关:两条线都验证过「设置卡片可用、样式真的作用到页面、卡片上的改动写得进去」。
25
+ **一份包同时支持三条宿主线**——0.1.5-rc.3(下限)、0.1.7-alpha.1 / alpha.2、0.1.7-rc.1:安装方式与宿主线无关,三条线都验证过「设置卡片可用、样式真的作用到页面、卡片上的改动写得进去」。
26
26
 
27
27
  | 插件版本 | 支持的 DSH 版本 |
28
28
  | --- | --- |
29
- | **0.2.4**(最新) | 0.1.5-rc.1 / rc.2 / rc.3、0.1.7-alpha.1 / alpha.2 |
29
+ | **0.2.6**(最新) | 0.1.5-rc.3、0.1.7-alpha.1 / alpha.2、0.1.7-rc.1 |
30
+ | 0.2.5 | 0.1.5-rc.1 / rc.2 / rc.3、0.1.7-alpha.1 / alpha.2 |
31
+ | 0.2.4 | 0.1.5-rc.1 / rc.2 / rc.3、0.1.7-alpha.1 / alpha.2 |
30
32
  | 0.2.3 | 0.1.5-rc.2 |
31
33
  | 0.2.2 | 0.1.5-rc.2 |
32
34
  | 0.2.1 | 0.1.5-rc.2 |
33
35
  | 0.2.0 | 0.1.5-rc.2 |
34
36
 
35
- `engines.dsh` 声明为 **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**(预发布版本必须显式留分支:node-semver 只在一个范围的某个比较符与该版本落在同一个 `major.minor.patch` 元组、且自身带预发布标签时才放行,写成 `>=0.1.5-rc.1` 匹配不到 `0.1.7-alpha.1`)。两条线的差别**全部在运行时二选一,不看版本号**:
37
+ `engines.dsh` 声明为 **`>=0.1.5-rc.3 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**(预发布版本必须显式留分支:node-semver 只在一个范围的某个比较符与该版本落在同一个 `major.minor.patch` 元组、且自身带预发布标签时才放行,写成 `>=0.1.5-rc.3` 匹配不到 `0.1.7-alpha.1`)。三条线的差别**全部在运行时二选一,不看版本号**:
36
38
 
37
- - **设置服务**:0.1.5-rc.x 是 `settingsScope.bind({namespace})`(按命名空间),0.1.7-alpha.x 换成了 `configForms.get(<profile 条目 id>)`(按条目 id,id 由安装方的 profile patch 决定,所以插件靠「宿主服务的那份 schema 里有没有本插件自己的字段」来认领)。两者都是 `getSnapshot/subscribe/set/unset` 同一张脸,插件只写一套。
38
- - **配置卡片座位**:rc 线是 设置 → 插件 → 插件配置 里的一个 keyed 单元(`settings.plugin.item`);alpha 线删掉了这个槽位,改成在「插件」页的官方插件列表里贡献一个条目(`plugins.item`,内置那些设置页就是这么挂的),并另外提供「已安装成 bundle 时」的每包页面(`plugins.bundle.config`)。三个座位都注册,宿主不声明的那个是惰性的。
39
+ - **设置服务**:0.1.5-rc.x 是 `settingsScope.bind({namespace})`(按命名空间);0.1.7-alpha.x 与 0.1.7-rc.x 换成了 `configForms.get(<profile 条目 id>)`(按条目 id,id 由安装方的 profile 决定,所以插件靠「宿主服务的那份 schema 里有没有本插件自己的字段」来认领)。两者都是 `getSnapshot/subscribe/set/unset` 同一张脸,插件只写一套。
40
+ - **loader 行 id 就是设置键**:0.1.7-rc.1 起设置按入口 id 存档,首次启动还会按**同名**把旧的 `settings.yaml` 搬过去——本包自带的 loader 行 id 因此与设置命名空间一致(`dsh-fonttune`)。同名的行 id 也让这个包可以写进 profile 的 `dsh.profile.bundles` 安装:插件管理页会把它显示为运行中,配置页照旧可达。
41
+ - **配置卡片座位**:0.1.5-rc.x 是 设置 → 插件 → 插件配置 里的一个 keyed 单元(`settings.plugin.item`);0.1.7 两条线删掉了这个槽位,改成在「插件」页的官方插件列表里贡献一个条目(`plugins.item`,内置那些设置页就是这么挂的),并另外提供「已安装成 bundle 时」的每包页面(`plugins.bundle.config`)。三个座位都注册,宿主不声明的那个是惰性的;卡片在这些页面里**默认展开**——点进那个条目本身就是展开手势,不该再让人多点一次。
39
42
  - **宿主半的配置来源**:alpha 把 schema 标成 `.volatile()` 之后,宿主半拿到的配置字段是 cosmokit 的 volatile 引用(`{get(),[write]}`)而不是普通值,首帧样式行会因此变空;插件先把引用解出来再用。
40
- - **`inject` 只声明两条线都有的服务**(`slots`、`locale`):声明一个宿主没有的服务会把整包 park 住——alpha 上声明 `settingsScope` 会让整个 Web 界面起不来。设置服务一律用 `ctx.get(name)` 现查。
41
- - **alpha 的表单是「条目 id + 描述视图」**:宿主 `describe()` 还没答话时,插件不会去猜一个条目名(猜错的话每次写入都会被告主拒绝),而是先用一个等待态座位顶住,等宿主把视图发出来再接管并立刻重读。
43
+ - **`inject` 只声明三条线都有的服务**(`slots`、`locale`):声明一个宿主没有的服务会把整包 park 住——alpha 上声明 `settingsScope` 会让整个 Web 界面起不来。设置服务一律用 `ctx.get(name)` 现查。
44
+ - **0.1.7 的表单是「条目 id + 描述视图」**:宿主 `describe()` 还没答话时,插件不会去猜一个条目名(猜错的话每次写入都会被宿主拒绝),而是先用一个等待态座位顶住,等宿主把视图发出来再接管并立刻重读。
42
45
 
43
46
  市场条目同时声明这些兼容信息(`package.json` 的 `engines.dsh`、`dsh.compatibility.dshReleases` 与 `peerDependencies`),安装前可据此判断这一版需要的宿主版本。
44
47
 
package/cordis.patch.yml CHANGED
@@ -5,16 +5,26 @@
5
5
  # the first paint already uses them; the browser half owns the
6
6
  # Settings -> Plugins -> Plugin configuration card.
7
7
  #
8
+ # The row id is the settings key from DSH 0.1.7-rc.1 on: that line imports the
9
+ # sections of the removed settings.yaml by matching each section against the
10
+ # entry of the SAME name, and this plugin's host half registered the namespace
11
+ # `dsh-fonttune` (its package name). Keeping the id equal to that namespace is
12
+ # what carries an existing installation's saved fonts and weights over; naming
13
+ # it `fonttune` leaves them behind in settings.yaml.imported, which shows up as
14
+ # "the plugin does nothing" (measured on 0.1.7-rc.1: an empty runtime
15
+ # stylesheet, no font applied). On the 0.1.5-rc lines the id is only a loader
16
+ # key, so the same value is harmless there.
17
+ #
8
18
  # A profile may pin the composition defaults here; a saved value still wins,
9
19
  # and a field left out falls back to the schema default (empty = do not touch
10
20
  # that axis):
11
21
  #
12
22
  # - insert:
13
- # - id: fonttune
23
+ # - id: dsh-fonttune
14
24
  # name: dsh-fonttune
15
25
  # config:
16
26
  # sans: '"JetBrains Mono", "Sarasa Mono SC", "Microsoft YaHei", sans-serif'
17
27
  # mono: '"JetBrains Mono", "Cascadia Code", Consolas, monospace'
18
28
  - insert:
19
- - id: fonttune
29
+ - id: dsh-fonttune
20
30
  name: dsh-fonttune