dsh-fonttune 0.1.0
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 +41 -0
- package/LICENSE +21 -0
- package/README.en.md +78 -0
- package/README.md +108 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +2455 -0
- package/lib/index.js +124 -0
- package/lib/shared.cjs +663 -0
- package/package.json +66 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-09-14
|
|
4
|
+
|
|
5
|
+
首个版本(M1–M3 完成)。架构取"host 半 + 客户端半"双半插件,而非设计文档最初设想的纯客户端插件——理由见 README「为什么不是纯客户端插件」。
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- **插件配置卡片**:注册在 `settings.plugin.item`、key 为 `dsh-fonttune` 的 host 设置命名空间,出现在 设置 → 插件 → 插件配置 里,保存/重置走 DSH 自己的设置文档(`settings.yaml`)。
|
|
10
|
+
- **正文 / 代码字体族**:两套独立的 CSS font-family fallback 列表,可为空(= 完全不动 DSH 的字体栈)。
|
|
11
|
+
- **选字体面板**:内置 等宽 / 中文(CJK)/ 拉丁 / 通用 四组预设;Chromium 下用 `queryLocalFonts()` 补"本机已安装"分组;搜索不到的名字给"使用 xxx"新建入口;每行用其自身字体渲染。
|
|
12
|
+
- **中西分家(简单模式)**(2026-09-14,用户点名要 Word/docx 式的中文字体/西文字体两格):卡片顶部一个开关——**简单模式**把栈拆成「西文字体」「中文字体」两个单选格(正文/代码各一对),内部自动维护同一条栈;**高级模式**就是完整的 chips 编辑器。语义(用户拍板"简单模式只动最前面"):西文格 = 栈里第一个非中文项(通常就是第 1 位),改选原地替换、无则插到最前;中文格 = 第一个中文项,改选原地替换、无则**紧跟西文槽插入**(保证 `西文, 中文, 通用兜底` 的 CSS 语义正确);两格之外的所有项与顺序**原样保留**,并在"其余回退项"一行提示;**模式切换是纯视图切换、零写入**。中文判定 = 名字启发式优先(宽松正则 + 含 CJK 字符的本地化名),canvas 测宽只对名字不像 CJK 的字体补充确认——**未安装的预设字体也能正确落入中文槽**(实测「思源黑体」本机没装也被正确分类;测宽对未安装字体必然误判,故只作次级手段)。实测:槽位派发正确、高级模式完整列表可见、来回切换零数据变化、真实面板点选只替换对应槽、控制台干净。开关行布局(用户反馈):左侧说明文字「编辑方式」+ 右侧分段式小切换(简单 | 高级),替换原两个等宽大按钮。
|
|
13
|
+
- **拖拽排序**:已选字体是 chip 列表,可拖拽调整回退顺序,同时保留前移/后移按钮(键盘与触屏可用)。
|
|
14
|
+
- **实时预览**:中英混排 + 代码两行,随当前配置实时渲染。
|
|
15
|
+
- **全局字号偏移**(-3 ~ +6 px):**等比缩放 DSH 自己的字号 token**(`--dsh-content-font-size`、`--dsw-font-*-font-size` / `-line-height`),基准值实时读自计算样式,因此与 DSH 的"字号大小"滑块叠加而非互相覆盖;token 名单运行时发现、内置兜底表。
|
|
16
|
+
- **全局字重**(300 ~ 600):作用于 `body, body *`,400 与未设置都表示"不动"。
|
|
17
|
+
- **滑块松手才落盘**(2026-09-14,用户反馈"动一点就马上调整很卡"):字号偏移/字重滑块拖动时只更新本地待定值与读数显示,`pointerup`/`touchend`(窗口捕获级监听)或失焦/键盘抬起时一次性提交,拖动过程零写入——不再每挪一格就全量重算字号 token + 写设置文档。**松手后保持待定值直到宿主确认值回来**(用户反馈"回弹再到位"):提交与设置文档回执之间的一拍里若立刻清本地值,滑块会闪回旧提交值再跳到新值——现以待提交值上屏、回执到达(或外部值变化)才清除,逐帧采样验证松手后读数序列无旧值闪现;重复 `pointerup` 以"已待确认"守卫挡住,不会重复写同一值。
|
|
18
|
+
- **字重提示精简**(用户点名):改为「覆盖全局文字粗细(含标题);默认字重为400。」,删去"任意整数都生效/可变字体线性/普通字体取最近一档"的说明。
|
|
19
|
+
- **首帧不闪字体**:host 半监听 `webserver/index-inject` 注入同款 `<style>`,客户端插件激活前首帧就是保存的字体。
|
|
20
|
+
- **中英双语文案**,缺英文回退;`queryLocalFonts` 不可用/被拒时静默回退到内置列表。
|
|
21
|
+
|
|
22
|
+
### 安全
|
|
23
|
+
|
|
24
|
+
- 字体名按**白名单**消毒(仅保留字母/数字/空格/`.` `,` `_` `-`),消毒后再整体加引号;Host schema 另加 `^[^{};<>\\]*$` 与长度上限。手工构造的 CSS 注入无法闭合声明或规则。
|
|
25
|
+
|
|
26
|
+
### 工程
|
|
27
|
+
|
|
28
|
+
- 纯 JavaScript 源码 + **零依赖构建脚本**(`build.mjs`):内联 shared、套 `window.__ModuleLoader__.load` 外壳、挂 `exports.apply/inject`,并强制校验"客户端 bundle 只能 require shell 预注入模块"。
|
|
29
|
+
- `node test/run.mjs`:**35 项离线检查**全绿,含自建 DOM、cordis 替身、设置面与 slot 派发替身、真实 `@deepseek-ai/schemastery` schema 解析、CSS 生成与注入、消毒对抗用例。
|
|
30
|
+
- **真实浏览器验证闭环**(本机可复跑,无需用户参与):受管实例(`--port 0 --no-open`,token 从 stdout 拿)→ `Invoke-WebRequest -SessionVariable` 用 token 换 cookie 后可直接 POST `/api/settings/describe`(信封 `{type:"client-request",rpcId,method:"<ns>/<method>",payload:{args:{}}}`)验证 namespace 已注册 → `test/browser-probe.mjs` / `test/ui-walk.mjs` 用**无头 Edge + CDP**(Node 内置 WebSocket)真实渲染页面:设置 → 插件 → 插件配置 → 断言卡片渲染、展开后控件齐全、控制台零报错。最终态实测:`fontCardVisible: true`,展开后 sans/mono/size/weight/preview/resetAll 全渲染、2 个滑块、控制台干净。
|
|
31
|
+
|
|
32
|
+
### 修复(开发期自查发现的真实缺陷)
|
|
33
|
+
|
|
34
|
+
- **卡片首渲染即崩溃(无头浏览器实测抓到的发布阻断 bug)**:`scope.subscribe` 被以裸方法引用传给 React 的 `useSyncExternalStore`,而宿主的 `SettingsScopeController.subscribe` 是读 `this.store` 的原型方法,脱离对象调用时 `this` 为 undefined → `slot entry crashed in 'settings.plugin.item': Cannot read properties of undefined (reading 'store')` → 卡片在插件配置页里根本不出现(其他卡片正常、插件清单里能看到本插件)。修复 = 传给 React 的 subscribe 一律包闭包保住 `this`。离线测试曾漏检:替身的 subscribe 是不依赖 `this` 的闭包,且 React 替身从不调用 subscribe——现两处替身都已改成会暴露该 bug 的形态(严格 scope + 脱离式调用)。
|
|
35
|
+
- **字号偏移三连 bug(真实浏览器实测逐个抓到)**:① 缺 `!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 跳过显式缩放,经变量链自动继承。跨两个刷新周期的稳定性实测:content-size 数值不变、样式表 0 个 `var(`、控制台干净。
|
|
36
|
+
- **字体族不铺满对话区/侧边栏**:对话 markdown 与侧栏元素的 CSS 自己声明 `font-family: var(--dsw-font-family)`,继承被截断,只写 body 的 font-family 传不进去。修复 = 按 dsh-ui-font 的验证做法在**变量源头**覆盖:`:root,body{--dsw-font-family:<sans>!important}` 与 `--dsw-font-mono`/`--ds-font-family-code`(代码 token 全链 `var(--ds-font-family-code)`);显式 body/pre,code 规则保留作第二路径。
|
|
37
|
+
- **字重吸附到 ±100 档**:原来把任意值 snap 到 300/400/500/600,拖动时看似只 ±100;现按用户所选整数原样写 CSS(可变字体全线性,普通字体内建就近取整),滑块 1 步进。
|
|
38
|
+
- 消毒过于宽松时 `Arial"; } body { background: url(evil) }` 能残留 `:` `(` `)`,改写为白名单式。
|
|
39
|
+
- host 半从 CJS 共享模块具名导入会让 ESM 加载失败(`Named export not found`),改为默认导入。
|
|
40
|
+
- 字号 token 正则漏掉 `--dsh-content-font-size`(该名不含 `-font-` 前缀)与其 `-secondary` 变体。
|
|
41
|
+
- 计时器改走 `globalThis`,不再依赖 `window` 上是否存在这两个方法。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LyaxZ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# dsh-fonttune
|
|
2
|
+
|
|
3
|
+
A font enhancement plugin for the DeepSeek Harness (DSH) Web GUI: **UI & code font families + a global font-size offset + a global font weight**, configured from **Settings → Plugins → Plugin configuration** (a native settings card). Every change applies immediately — no reload.
|
|
4
|
+
|
|
5
|
+
中文文档:[README.md](README.md)
|
|
6
|
+
|
|
7
|
+
## Why it is not a client-only plugin
|
|
8
|
+
|
|
9
|
+
The design doc originally planned a client-only plugin with localStorage. In practice it became a **dual-half (host + client)** plugin, for three verified reasons:
|
|
10
|
+
|
|
11
|
+
| Client-only | Dual-half (this plugin) |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| The card can never render — the `settings.plugin.item` slot is dispatched by the settings namespace **registered on the host**; without the host half there is no key | The card appears in the native plugin configuration page; save/reset go through DSH's own settings document |
|
|
14
|
+
| First paint flashes the default fonts (client plugins load asynchronously) | The host half listens on `webserver/index-inject` and injects the same declarations into the served index `<head>` — **the first paint is already correct** |
|
|
15
|
+
| Settings live in localStorage only (cleared with the cache) | Settings live in the Host settings document (`settings.yaml`) |
|
|
16
|
+
|
|
17
|
+
The doc's worry that a host half forces a restart does not hold: the web profile runs with `patchReload: "live"`. Only the install itself needs one restart (to place the package into the profile).
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```powershell
|
|
22
|
+
# from npm (after publishing)
|
|
23
|
+
dsh plugin --profile web add dsh-fonttune
|
|
24
|
+
|
|
25
|
+
# local development: junction into this folder and add
|
|
26
|
+
# - id: fonttune / name: dsh-fonttune to profiles\web\cordis.patch.yml
|
|
27
|
+
dsh plugin --profile web add link:F:\deepseek harness\dsh-fonttune
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Restart DSH once after installing; after that, changing settings needs no restart.
|
|
31
|
+
|
|
32
|
+
## The four axes
|
|
33
|
+
|
|
34
|
+
| Axis | Range | Default | Notes |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| Body font (`sans`) | Arbitrary fallback list | empty = untouched | Latin families first, CJK after; empty keeps DSH's own stack |
|
|
37
|
+
| Code font (`mono`) | Same | empty = untouched | Applies to `pre/code/kbd/samp/var/tt/textarea` and CodeMirror editors |
|
|
38
|
+
| Font-size offset | -3 ~ +6 px | 0 = untouched | Uniform proportional rescale, stacks with DSH's own font-size setting |
|
|
39
|
+
| Font weight | 300 ~ 600, any integer | unset = untouched | 400 is DSH's own body weight and also counts as "untouched" |
|
|
40
|
+
|
|
41
|
+
Both number sliders are **commit-on-release**: dragging only updates a local value, and the write happens once on pointer/touch release (or keyboard/blur) — no recomputation per pixel.
|
|
42
|
+
|
|
43
|
+
### West / CJK split (simple / advanced mode)
|
|
44
|
+
|
|
45
|
+
The switch row at the top of the card (a label on the left, a segmented Simple/Advanced toggle on the right):
|
|
46
|
+
|
|
47
|
+
- **Simple mode** splits the stack into a Western and a CJK single-pick slot (one pair for body, one for code). It only manages the **front** of the stack: the Western slot replaces the first non-CJK entry in place; the CJK slot replaces the first CJK entry in place, or inserts right after the Western slot when none exists. Everything beyond the two slots keeps its order.
|
|
48
|
+
- **Advanced mode** is the full chip editor with drag-to-reorder.
|
|
49
|
+
- **Switching modes writes nothing** — both views share one stack, and your tuned order survives; the preference is stored in the browser.
|
|
50
|
+
|
|
51
|
+
The font picker ships with four preset groups (monospace / CJK / Latin / generic), adds "installed on this machine" via `queryLocalFonts()` on Chromium, and lets you create any name via "use xxx". Each row renders in its own font, with a mixed-script live preview below.
|
|
52
|
+
|
|
53
|
+
## How the size offset works
|
|
54
|
+
|
|
55
|
+
DSH does not have "one font size"; it generates a set of CSS custom properties at runtime — `--dsh-content-font-size` (written **inline on `body`** by the theme package) plus one `--dsw-font-*-font-size` / `-line-height` per design-system step. Writing `font-size: calc(1em + 2px)` would compound down the DOM tree, so this plugin instead **rescales every token by one ratio** (`(16 + offset) / 16`), with the base read live — so it stacks with DSH's own slider, and each step scales its own line-height along. The token list is discovered at runtime (stylesheet declarations → computed values → built-in fallback), so future DSH token changes are followed automatically.
|
|
56
|
+
|
|
57
|
+
## Safety
|
|
58
|
+
|
|
59
|
+
User-entered family names are sanitized with an allowlist (letters incl. CJK, digits, space, `.`, `,`, `_`, `-`); everything else is dropped and the whole name is quoted, so a name cannot close a declaration, start a rule, or reach `url()`. The host schema adds a `^[^{};<>\\]*$` pattern and a length cap on top.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```powershell
|
|
64
|
+
node build.mjs # build lib/ (zero-dependency, no bundler)
|
|
65
|
+
node build.mjs --watch
|
|
66
|
+
node test/run.mjs # 42 offline checks
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The sources are plain JavaScript with **no build dependencies**: `src/shared.cjs` (pure-function core), `src/index.mjs` (host half), `src/client.js` (browser half). `build.mjs` inlines the core into the client bundle, wraps it in the `window.__ModuleLoader__.load({id, factory})` shell, copies the host half, and verifies the "only shell-held modules may be required" constraint.
|
|
70
|
+
|
|
71
|
+
## Compatibility
|
|
72
|
+
|
|
73
|
+
- DSH `0.1.5-rc.2` (declared in `dsh.compatibility.dshReleases`).
|
|
74
|
+
- Do not enable other plugins that also write the `body` font family at the same time; this plugin can fully replace them.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# dsh-fonttune
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness(DSH)Web GUI 的字体增强插件:**正文/代码字体族 + 全局字号偏移 + 全局字重**,设置入口在 **设置 → 插件 → 插件配置**(原生的插件配置卡片),改完即时生效。
|
|
4
|
+
|
|
5
|
+
English: [README.en.md](README.en.md)
|
|
6
|
+
|
|
7
|
+
## 为什么不是"纯客户端插件"
|
|
8
|
+
|
|
9
|
+
设计文档最初定的是纯客户端 + localStorage。实测后改成**双半(host + client)**,原因有三条,都是本机验证过的:
|
|
10
|
+
|
|
11
|
+
| 纯客户端 | 双半(本插件) |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| 插件配置页进不去——`settings.plugin.item` 这个槽是**按 host 注册的 settings namespace 派发**的(`settings-plugins` 的 slot-contract 与 `ConfigurablePluginsTab` 类型都写明这点),没有 host 半就没有 key,卡片永远不会被渲染 | 卡片出现在原生的插件配置页里,保存/重置走 DSH 自己的设置文档 |
|
|
14
|
+
| 首帧闪一帧默认字体(客户端插件是异步加载的) | host 半监听 `webserver/index-inject`,把同样的声明塞进 index `<head>`,**首帧就是对的字体** |
|
|
15
|
+
| 设置只存 localStorage,清缓存即丢,且不在 `settings.yaml` 里 | 存进 Host 设置文档(`settings.yaml`),与其它偏好一起 |
|
|
16
|
+
|
|
17
|
+
而文档担心的"host 半 ⇒ 必须重启"在本机不成立:web profile 是 `patchReload: "live"`,插件行增删热生效。真正需要重启的只有"安装那一刻"(要跑 `dsh plugin add` 把包放进 profile)。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```powershell
|
|
22
|
+
# 发布后:直接从 npm 装(需要重启一次 DSH 让 bundle 进 boot graph)
|
|
23
|
+
dsh plugin --profile web add dsh-fonttune
|
|
24
|
+
|
|
25
|
+
# 本机开发:junction 到源码目录,再往 profiles\web\cordis.patch.yml 的
|
|
26
|
+
# insert 列表加一行 - id: fonttune / name: dsh-fonttune
|
|
27
|
+
dsh plugin --profile web add link:F:\deepseek harness\dsh-fonttune
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
装完重启一次 `dsh web` 即可,之后改设置不再需要重启。
|
|
31
|
+
|
|
32
|
+
## 四个调节轴
|
|
33
|
+
|
|
34
|
+
| 轴 | 范围 | 默认 | 说明 |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| 正文字体(sans) | 任意长度的 fallback 列表 | 空 = 不动 | 拉丁字体在前、中文字体在后;空表示完全沿用 DSH 的字体栈 |
|
|
37
|
+
| 代码字体(mono) | 同上 | 空 = 不动 | 作用于 `pre/code/kbd/samp/var/tt/textarea` 与 CodeMirror 编辑区 |
|
|
38
|
+
| 字号偏移 | -3 ~ +6 px | 0 = 不动 | 全局等比缩放,与 DSH 自己的"字号大小"叠加 |
|
|
39
|
+
| 字重 | 300 ~ 600 任意整数 | 未设置 = 不动 | 400 即 DSH 原本的正文字重,同样视为"不动" |
|
|
40
|
+
|
|
41
|
+
两个数值滑块都是**拖动过程零写入**:拖动只更新本地显示,松手(`pointerup`/触屏松开,或键盘松键/失焦)才一次性提交并生效——拖到哪都不会卡,松手即到位。
|
|
42
|
+
|
|
43
|
+
选字体面板:内置 **等宽 / 中文(CJK)/ 拉丁 / 通用** 四组预设,Chromium 下再用 `queryLocalFonts()` 补上"本机已安装";搜索不到的名字可以直接"使用 xxx"新建。已选字体是 **chip 列表**,支持 **拖拽排序**(并保留 ‹ › 键盘/触屏按钮),每项用其自身字体渲染,下方还有中英混排 + 代码的实时预览。
|
|
44
|
+
|
|
45
|
+
### 中西分家(简单模式 / 高级模式)
|
|
46
|
+
|
|
47
|
+
卡片顶部的开关行(左"编辑方式"说明 + 右侧简单/高级分段切换):
|
|
48
|
+
|
|
49
|
+
- **简单模式**:把栈拆成「西文字体」「中文字体」两个单选格(正文/代码各一对)。语义是"只动最前面"——西文格 = 栈里第一个非中文项,改选**原地替换**;中文格 = 第一个中文项,改选原地替换、栈里还没有中文项时**紧跟西文槽插入**;两格之外的项与顺序**原样保留**("其余回退项"一行可见)。中文判定以名字启发式优先(未安装的字体也能正确归入中文槽——canvas 测宽对没装的字体必然误判,只作补充手段)。
|
|
50
|
+
- **高级模式**:完整的 chips 编辑器,拖拽排序。
|
|
51
|
+
- **切换模式零写入**:两种视图共享同一条栈,来回切换不会改动你排好的顺序;视图偏好存在浏览器本地。
|
|
52
|
+
|
|
53
|
+
## 字号偏移是怎么实现的
|
|
54
|
+
|
|
55
|
+
关键点:DSH 的字号不是"一个全局字号",而是**运行时生成的一堆 CSS 自定义属性**——
|
|
56
|
+
|
|
57
|
+
- `--dsh-content-font-size`:会话内容字号,由 `dsh-client-ui-theme` 写在 `body` 的**内联样式**上(设置里那个"字号大小"滑块),`--dsh-content-font-delta` 等由它派生;
|
|
58
|
+
- `--dsw-font-*-font-size` / `-line-height`:设计系统每一档字号(`--dsw-font-s-14-*`、`--dsw-font-markdown-h1-*` …),主题插件在启动后注入。
|
|
59
|
+
|
|
60
|
+
所以直接写 `body, body * { font-size: calc(1em + 2px) !important }` 是**错的**:嵌套元素会按各自的 `1em` 反复加偏移,层级越深越大。本插件改成:
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
body, body * {
|
|
64
|
+
--dsh-content-font-size: calc((14px) * 1.125); /* 基准值读自实时文档 */
|
|
65
|
+
--dsw-font-s-14-font-size: calc((14px) * 1.125);
|
|
66
|
+
--dsw-font-s-14-line-height: calc((24px) * 1.125);
|
|
67
|
+
/* … 每个字号/行高 token 同一个比例 … */
|
|
68
|
+
}
|
|
69
|
+
body, body * { font-weight: 500 !important; }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
即**把这些 token 按同一个比例重算**(`比例 = (16 + 偏移) / 16`)。好处:
|
|
73
|
+
|
|
74
|
+
- 每一档字号与它自己的行高同步放大,文字不会挤进没放大的行高里;
|
|
75
|
+
- 基准值来自 `getComputedStyle(document.body)`,所以偏移是**叠加**在 DSH 字号滑块之上的,两者不打架;
|
|
76
|
+
- 只改 `<body>` 元素上的自定义属性,不碰 `html`,没有 `zoom` 那种视口/滚动条副作用。
|
|
77
|
+
|
|
78
|
+
token 名单**不写死**:优先读同名样式表里的实际声明,再读实时计算值,最后才用内置兜底表(覆盖 0.1.5-rc.2 的全部字号 token)。判定规则是"名字以 `-font-size` / `-line-height` 结尾",所以 DSH 之后改名或增删档位也能跟上。
|
|
79
|
+
|
|
80
|
+
## 安全
|
|
81
|
+
|
|
82
|
+
用户输入的字体名会被**按白名单消毒**:只保留字母(任意语言,含 CJK)、数字、空格、`.`、`,`、`_`、`-`,其余(引号、`{}`、`;`、`:`、`()`、`/`、`<>`、反斜杠)全部丢弃后再整体加引号。所以手输 `Arial"; } body { background: url(x) }` 只会变成家族名 `"Arial body background urlx"`,不可能闭合声明、另起规则或触达 `url()`。Host 侧 schema 另有 `^[^{};<>\\]*$` 与长度上限兜底。
|
|
83
|
+
|
|
84
|
+
## 开发
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
node build.mjs # 构建 lib/(零依赖,无需 bundler)
|
|
88
|
+
node build.mjs --watch # 改 src 自动重建
|
|
89
|
+
node test/run.mjs # 42 项离线检查(自建 DOM / cordis / 设置面替身)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
源码是纯 JavaScript,**没有任何构建依赖**:`src/shared.cjs`(纯函数核心)、`src/index.mjs`(host 半,ESM)、`src/client.js`(客户端半)。`build.mjs` 干三件事:把 shared 内联进客户端 bundle、套上 `window.__ModuleLoader__.load({id, factory})` 外壳并挂上 `exports.apply/inject`、拷贝 host 半——同时校验"只 require shell 预注入的模块"这条硬约束。
|
|
93
|
+
|
|
94
|
+
### 客户端模块约束(踩过的坑)
|
|
95
|
+
|
|
96
|
+
- 浏览器半能 `require` 的**只有** shell 静态表里的词:`react`、`react/jsx-runtime`、`react-dom`、`react-dom/client`、`@deepseek-ai/cordis`、`@deepseek-ai/dsh-client-store`、`@deepseek-ai/dsh-client-ui-slots`、`@deepseek-ai/dsh-client-ui-primitives`、`@deepseek-ai/dsh-client-ui-dockkit`(见 `dsh-web-frontend` 里的静态表与 `dsh-client-modules` 的解析顺序:seed → 已物化记录 → 已注册 factory,**miss 直接抛错**)。
|
|
97
|
+
- 客户端插件的 `ctx` **没有** `ctx.inject` 之外的花样:`ctx.effect / on / once / provide / timer` 是白名单动词;要拿别的服务用 `ctx.get(name)`(本插件只声明 `inject: ["slots", "locale", "settingsScope"]`)。
|
|
98
|
+
- host 半里 `import shared from "./shared.cjs"` **必须是默认导入**:具名导入会走 Node 的 CJS 静态导出探测,`dsh-settings` 解析 namespace 时会直接抛 `Named export 'FALLBACK_TOKENS' not found`,整半起不来。
|
|
99
|
+
- 改 `package.json` 千万不要带 BOM(`Set-Content -Encoding UTF8` 会加),DSH 的 loader 直接 `JSON.parse` 会整树炸掉。
|
|
100
|
+
|
|
101
|
+
## 兼容性
|
|
102
|
+
|
|
103
|
+
- DSH `0.1.5-rc.2`(`dsh.compatibility.dshReleases` 已声明)。
|
|
104
|
+
- 若同时启用其它写 `body` 字体族的插件(如设计文档里提到的 `dsh-ui-font`),后加载的赢,**不要同时开**;本插件可完全替代它(多了字号/字重/中西分家/拖拽排序/中文预设)。
|
|
105
|
+
|
|
106
|
+
## 许可
|
|
107
|
+
|
|
108
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# dsh-fonttune layer.
|
|
2
|
+
#
|
|
3
|
+
# Adds one plugin row. The host half registers the `dsh-fonttune` settings
|
|
4
|
+
# namespace and injects the saved font declarations into the served index, so
|
|
5
|
+
# the first paint already uses them; the browser half owns the
|
|
6
|
+
# Settings -> Plugins -> Plugin configuration card.
|
|
7
|
+
#
|
|
8
|
+
# A profile may pin the composition defaults here; a saved value still wins,
|
|
9
|
+
# and a field left out falls back to the schema default (empty = do not touch
|
|
10
|
+
# that axis):
|
|
11
|
+
#
|
|
12
|
+
# - insert:
|
|
13
|
+
# - id: fonttune
|
|
14
|
+
# name: dsh-fonttune
|
|
15
|
+
# config:
|
|
16
|
+
# sans: '"JetBrains Mono", "Sarasa Mono SC", "Microsoft YaHei", sans-serif'
|
|
17
|
+
# mono: '"JetBrains Mono", "Cascadia Code", Consolas, monospace'
|
|
18
|
+
- insert:
|
|
19
|
+
- id: fonttune
|
|
20
|
+
name: dsh-fonttune
|