@citisen/dsh-font 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/LICENSE +21 -0
- package/README.md +241 -0
- package/README.zh.md +165 -0
- package/cordis.patch.yml +20 -0
- package/lib/client.js +847 -0
- package/lib/index.js +239 -0
- package/package.json +76 -0
- package/scripts/build-client.mjs +297 -0
- package/scripts/verify-client.mjs +410 -0
- package/scripts/verify-host.mjs +187 -0
- package/scripts/verify-profile.mjs +146 -0
- package/src/client.js +838 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 j-sen
|
|
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.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# dsh-font
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Customize the DeepSeek Harness **Web GUI fonts** from Settings: the interface
|
|
6
|
+
font family, the code font family, and three independent size axes.
|
|
7
|
+
|
|
8
|
+
This is a third-party [dsh](https://github.com/deepseek-ai/deepseek-harness)
|
|
9
|
+
profile bundle. It ships as one dual-face package: a Node half that owns a
|
|
10
|
+
durable settings namespace and a pre-paint style row, and a browser half that
|
|
11
|
+
paints the result and registers the Settings row.
|
|
12
|
+
|
|
13
|
+
Requires dsh `0.1.5-rc.1` or a later `0.1.5-rc.x`; it uses the `settings.general.item`
|
|
14
|
+
slot, the `settingsScope` service, and `ctx.theme.overrideTokens`, all of which
|
|
15
|
+
are present in the `latest` and `next` release channels.
|
|
16
|
+
|
|
17
|
+
## What it adds
|
|
18
|
+
|
|
19
|
+
A **Fonts** row in *Settings → General*, with five controls:
|
|
20
|
+
|
|
21
|
+
| Control | Effect | Range |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Interface font | `--dsw-font-family` — all non-code UI text | any CSS `font-family` list |
|
|
24
|
+
| Code font | `--ds-font-family-code` — code blocks, inline code, monospace | any CSS `font-family` list |
|
|
25
|
+
| Interface text size | Scales every hard-coded UI text size | 75% – 150%, step 5% |
|
|
26
|
+
| Conversation text size | Message bodies, headings, and tables | 12 – 20 px |
|
|
27
|
+
| Code text size | Code blocks and inline code | 10 – 20 px |
|
|
28
|
+
|
|
29
|
+
Both family fields accept free text and offer one-click presets. Every value is
|
|
30
|
+
saved through the host settings document (`$DSH_HOME/settings.yaml`,
|
|
31
|
+
namespace `ui-font`), so settings survive a restart and are shared by every
|
|
32
|
+
browser pointed at the same host.
|
|
33
|
+
|
|
34
|
+
The *Interface text size* and *Conversation text size* axes are independent on
|
|
35
|
+
purpose: bumping the interface makes the surrounding chrome easier to read
|
|
36
|
+
without changing how much message text fits on screen, and vice versa.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
dsh plugin --profile web add @citisen/dsh-font
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Straight from GitHub (identical package, no registry involved):
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
dsh plugin --profile web add github:citisen/dsh-font
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then restart the Web surface:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh --profile web
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`dsh plugin` forwards to pnpm inside the profile directory and then reconciles
|
|
57
|
+
`dsh.profile.bundles`: because this package declares `dsh.bundle`, the install
|
|
58
|
+
appends it as a profile layer automatically. Nothing has to be hand-edited in
|
|
59
|
+
`cordis.patch.yml`.
|
|
60
|
+
|
|
61
|
+
Both paths are verified end to end on a fresh profile — the row reaches the
|
|
62
|
+
composed entry list and the browser roster resolves the client bundle. Confirm
|
|
63
|
+
it yourself with `node scripts/verify-profile.mjs <profile>` after installing.
|
|
64
|
+
|
|
65
|
+
### Installing from a local checkout
|
|
66
|
+
|
|
67
|
+
`dsh plugin --profile web add <path>` is unreliable on Windows when the profile
|
|
68
|
+
and the checkout are on **different drives** — pnpm resolves the cross-drive
|
|
69
|
+
directory link to a nonexistent path, and the reconciliation then concludes the
|
|
70
|
+
package declares no `dsh.bundle` and leaves it out of `bundles`. Link it
|
|
71
|
+
yourself instead:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
cd "$DSH_HOME/profiles/web"
|
|
75
|
+
pnpm add "D:/path/to/dsh-font" # writes the dependency
|
|
76
|
+
# then repair the link pnpm created, which points at
|
|
77
|
+
# <profile>/D:/path/to/dsh-font -- a path that does not exist
|
|
78
|
+
rm -rf node_modules/dsh-font
|
|
79
|
+
cmd /c mklink /J node_modules\dsh-font D:\path\to\dsh-font # Windows
|
|
80
|
+
# and add "dsh-font" to dsh.profile.bundles in package.json by hand
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Verify the result with `node scripts/verify-profile.mjs`; it fails loudly if the
|
|
84
|
+
row never made it into the composed entry list.
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
Read this section before changing the code; the two halves solve different
|
|
89
|
+
problems and the split is deliberate.
|
|
90
|
+
|
|
91
|
+
### Why the families go through the theme service
|
|
92
|
+
|
|
93
|
+
The design system declares both families once, on `:root`:
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
:root{--dsw-font-family:…;--ds-font-family-code:…}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every typographic token in the UI resolves through `var(--dsw-font-family)`
|
|
100
|
+
(`--dsw-font-base-16`, `--dsw-font-xs-13`, the whole Markdown ladder, …), and
|
|
101
|
+
the shell's own `body` rule reads it too, so overriding those two variables
|
|
102
|
+
retargets the entire interface.
|
|
103
|
+
|
|
104
|
+
The obvious way to set them — a custom property on `documentElement` — does not
|
|
105
|
+
survive. `ui-layout`'s `ThemePresenter` owns `document.body.style` and, on every
|
|
106
|
+
theme change, **deletes every custom property it did not write** before
|
|
107
|
+
re-writing the active token set. A property set from outside disappears the
|
|
108
|
+
first time the user switches between light and dark.
|
|
109
|
+
|
|
110
|
+
So the browser half stacks the families onto the theme itself:
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
ctx.theme.overrideTokens('dsh-font', {
|
|
114
|
+
'--dsw-font-family': { light: uiFontFamily, dark: uiFontFamily },
|
|
115
|
+
'--ds-font-family-code': { light: codeFontFamily, dark: codeFontFamily },
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The override layer folds into the active snapshot, and the presenter then
|
|
120
|
+
republishes it — and keeps republishing it — on every palette change. The
|
|
121
|
+
`{ light, dark }` pair is mandatory: `overrideTokens` throws a teaching error on
|
|
122
|
+
a bare string, because a single value would go illegible on the other palette.
|
|
123
|
+
|
|
124
|
+
### Why the sizes are a stylesheet
|
|
125
|
+
|
|
126
|
+
There is **no** interface font-size variable in the design system. Each
|
|
127
|
+
component hard-codes one of 11, 12, 13, 14, 16, 20, or 24 px, and the
|
|
128
|
+
`--dsw-font-*` scale is a fixed ladder (`--dsw-font-xs-13` is 13px, full stop).
|
|
129
|
+
Code sizes are likewise baked into composite tokens.
|
|
130
|
+
|
|
131
|
+
So the plugin emits one utility class per shipped size:
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
.dsh-font-size-14{font-size:calc(14px * var(--dsh-font-ui-scale,1)) !important}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
and stamps the matching class onto each element, measuring its shipped size with
|
|
138
|
+
`getComputedStyle` while it carries no stamp. A `MutationObserver` re-stamps as
|
|
139
|
+
the interface mounts new nodes.
|
|
140
|
+
|
|
141
|
+
Stamping per element rather than using a universal rule is what keeps the two
|
|
142
|
+
size axes independent: a `html body *` override would inherit into the
|
|
143
|
+
conversation subtree and compound with the content size, and it would also
|
|
144
|
+
rewrite decorative sizes (the SVG labels inside file-type icons) that nobody
|
|
145
|
+
meant to scale. Measuring per element also means the plugin needs no knowledge
|
|
146
|
+
of any component's class names, so it survives a UI refactor.
|
|
147
|
+
|
|
148
|
+
### Where the conversation size is written
|
|
149
|
+
|
|
150
|
+
`--dsh-content-font-size` is an **inline** custom property on `body`, written by
|
|
151
|
+
`ui-layout` from the sibling `ui-theme` namespace (whose own Settings row offers
|
|
152
|
+
12–17 px). An inline declaration outranks a stylesheet regardless of
|
|
153
|
+
`!important`, so the plugin writes its value to exactly the same place:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
document.body.style.setProperty('--dsh-content-font-size', `${contentSize}px`)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The stylesheet then re-derives the whole Markdown ladder in absolute px from
|
|
160
|
+
that value. `ui-theme` still owns the variable and still writes it on every
|
|
161
|
+
theme change; the plugin re-asserts its own on every settings change. The row's
|
|
162
|
+
help text tells the user this control wins over the *Font size* row in
|
|
163
|
+
Appearance.
|
|
164
|
+
|
|
165
|
+
### Pre-paint
|
|
166
|
+
|
|
167
|
+
The Node half answers `webserver/index-inject` with an inline `<script>` that
|
|
168
|
+
installs the stylesheet and sets the two family variables before the shell
|
|
169
|
+
mounts, so the first frame is already in the user's fonts. It reads the same
|
|
170
|
+
`ui-font` settings section at render time, and falls back to the schema defaults
|
|
171
|
+
when the settings provider is absent.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
npm run build # src/client.js -> lib/client.js
|
|
177
|
+
npm run check # fail if lib/client.js is stale
|
|
178
|
+
node scripts/verify-host.mjs # host half: schema, bootstrap, injection row
|
|
179
|
+
node scripts/verify-client.mjs # client half: envelope, sheet, stamping, row
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`src/client.js` is the single source of truth for the browser half. It is
|
|
183
|
+
written as an ES module for readability, but DSH client bundles are **classic
|
|
184
|
+
scripts** that may only register a lazy CommonJS factory through
|
|
185
|
+
`window.__ModuleLoader__` — so `scripts/build-client.mjs` applies that envelope
|
|
186
|
+
and rewrites the static imports into `require` calls. It is deliberately narrow
|
|
187
|
+
and fails the build on anything it cannot rewrite, because a hand-rolled client
|
|
188
|
+
bundle has no bundler to catch a mistake.
|
|
189
|
+
|
|
190
|
+
The only modules a browser half may request are the nine the shell seeds into
|
|
191
|
+
its module table (`react`, `react/jsx-runtime`, `react-dom`,
|
|
192
|
+
`react-dom/client`, `@deepseek-ai/cordis`, `@deepseek-ai/dsh-client-store`,
|
|
193
|
+
`@deepseek-ai/dsh-client-ui-slots`, `@deepseek-ai/dsh-client-ui-primitives`,
|
|
194
|
+
`@deepseek-ai/dsh-client-ui-dockkit`); anything else must be declared in
|
|
195
|
+
`dsh.client.external` and shipped as its own graph row. The build enforces this.
|
|
196
|
+
|
|
197
|
+
To iterate on the browser half against a running host, run the watcher and let
|
|
198
|
+
`dsh-client-hmr` swap the plugin in — it stat-polls client bundles every 500 ms,
|
|
199
|
+
so a saved rebuild reaches the open page without a refresh:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
# terminal 1
|
|
203
|
+
npm run watch
|
|
204
|
+
|
|
205
|
+
# terminal 2
|
|
206
|
+
dsh --profile web
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
A page refresh always picks up a newly composed graph, so the watcher is a
|
|
210
|
+
convenience rather than a requirement.
|
|
211
|
+
|
|
212
|
+
## Package layout
|
|
213
|
+
|
|
214
|
+
| Path | Role |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `lib/index.js` | Host half: settings namespace, pre-paint injection. Loaded by the loader. |
|
|
217
|
+
| `lib/client.js` | Browser half, **generated** from `src/client.js`. Served at `/plugins/dsh-font/client.js`. |
|
|
218
|
+
| `src/client.js` | Browser-half source. |
|
|
219
|
+
| `cordis.patch.yml` | The profile layer this bundle contributes. |
|
|
220
|
+
| `scripts/` | Build and verification scripts. |
|
|
221
|
+
| `package.json` | Declares `dsh.bundle` (profile layer) and `dsh.client` (browser roster entry). |
|
|
222
|
+
|
|
223
|
+
## Known limitations
|
|
224
|
+
|
|
225
|
+
- **Hard-coded sizes only.** The interface scale only moves text that a shipped
|
|
226
|
+
stylesheet fixes in px. It deliberately leaves display-size text and decorative
|
|
227
|
+
glyph sizes alone, so a scale of 150% is not a uniform 1.5× of the whole UI.
|
|
228
|
+
- **The conversation size overrides `ui-theme`'s font-size row.** Both write
|
|
229
|
+
`--dsh-content-font-size`; last write wins, and this plugin always writes on a
|
|
230
|
+
settings change. Use one or the other.
|
|
231
|
+
- **Fonts are not installed.** A family name is only used if the browser or OS
|
|
232
|
+
can resolve it; the plugin does not bundle or download webfonts, so a typo
|
|
233
|
+
falls back silently to the generic family at the end of the list.
|
|
234
|
+
- **Per-element stamping is proportional to the DOM.** The scale pass measures
|
|
235
|
+
each element once and caches the result, and re-stamps on mutation, so it is
|
|
236
|
+
bounded — but it is not free on a very large transcript.
|
|
237
|
+
- **The settings row is English/Chinese only**, matching the shipped locale pair.
|
|
238
|
+
|
|
239
|
+
## License
|
|
240
|
+
|
|
241
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# dsh-font
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
在设置里自定义 DeepSeek Harness **Web 界面字体**:界面字体、代码字体,以及三条互相独立的字号轴。
|
|
6
|
+
|
|
7
|
+
这是一个第三方 [dsh](https://github.com/deepseek-ai/deepseek-harness) profile bundle(插件包)。它是一个「双面」包:Node 半边负责持久化的设置命名空间与首屏绘制前的样式注入,浏览器半边负责实际绘制并注册设置项。
|
|
8
|
+
|
|
9
|
+
需要 dsh `0.1.5-rc.1` 或更新的 `0.1.5-rc.x`;它用到 `settings.general.item` 插槽、`settingsScope` 服务以及 `ctx.theme.overrideTokens`,这些在 `latest` 与 `next` 两条发布通道里都已具备。
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
在 *设置 → 通用* 中新增一个 **字体** 行,包含五个控件:
|
|
14
|
+
|
|
15
|
+
| 控件 | 作用 | 范围 |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| 界面字体 | `--dsw-font-family`,所有非代码的界面文字 | 任意 CSS `font-family` 列表 |
|
|
18
|
+
| 代码字体 | `--ds-font-family-code`,代码块、行内代码、等宽文本 | 任意 CSS `font-family` 列表 |
|
|
19
|
+
| 界面字号 | 按比例缩放所有写死的界面文字尺寸 | 75% – 150%,步进 5% |
|
|
20
|
+
| 会话正文字号 | 消息正文、标题与表格 | 12 – 20 px |
|
|
21
|
+
| 代码字号 | 代码块与行内代码 | 10 – 20 px |
|
|
22
|
+
|
|
23
|
+
两个字体输入框都支持自由填写,并提供一键预设。所有取值都通过宿主设置文档持久化(`$DSH_HOME/settings.yaml`,命名空间 `ui-font`),因此重启后依然生效,并且对指向同一台宿主的所有浏览器共享。
|
|
24
|
+
|
|
25
|
+
*界面字号* 与 *会话正文字号* 刻意设计成两条独立的轴:放大界面可以让周围的控件更好读,而不改变屏幕上能显示多少正文;反过来也一样。
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
dsh plugin --profile web add @citisen/dsh-font
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
直接从 GitHub 安装(同一个包,不走 registry):
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
dsh plugin --profile web add github:citisen/dsh-font
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
然后重启 Web 界面:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
dsh --profile web
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`dsh plugin` 会在 profile 目录里转发给 pnpm,然后对 `dsh.profile.bundles` 做一次对账:由于本包声明了 `dsh.bundle`,安装时会自动把它追加为一个 profile 层,无需手工修改 `cordis.patch.yml`。
|
|
46
|
+
|
|
47
|
+
这两条路径都在全新 profile 上实测过:这一行会进入最终 entry 列表,浏览器 roster 也能解析到客户端 bundle。装完后可以用 `node scripts/verify-profile.mjs <profile>` 自己确认。
|
|
48
|
+
|
|
49
|
+
### 从本地目录安装
|
|
50
|
+
|
|
51
|
+
在 Windows 上,如果 profile 和代码目录位于**不同盘符**,`dsh plugin --profile web add <路径>` 不可靠——pnpm 会把跨盘符的目录链接解析成一个不存在的路径,随后对账会认为该包没有声明 `dsh.bundle`,于是不把它写进 `bundles`。这时请自己建立链接:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
cd "$DSH_HOME/profiles/web"
|
|
55
|
+
pnpm add "D:/path/to/dsh-font" # 写入依赖
|
|
56
|
+
# pnpm 建立的链接指向 <profile>/D:/path/to/dsh-font,该路径不存在,需要修复:
|
|
57
|
+
cmd /c rmdir node_modules\dsh-font
|
|
58
|
+
cmd /c mklink /J node_modules\dsh-font D:\path\to\dsh-font
|
|
59
|
+
# 再手工把 "dsh-font" 加进 package.json 的 dsh.profile.bundles
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
用 `node scripts/verify-profile.mjs` 校验结果;只要这一行没进最终的 entry 列表,它就会直接报错。
|
|
63
|
+
|
|
64
|
+
## 实现原理
|
|
65
|
+
|
|
66
|
+
修改代码前请先读这一节;两个半边解决的是不同的问题,这个划分是有意的。
|
|
67
|
+
|
|
68
|
+
### 为什么字体族走 theme 服务
|
|
69
|
+
|
|
70
|
+
设计系统只在 `:root` 声明了这两个字体变量:
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
:root{--dsw-font-family:…;--ds-font-family-code:…}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
界面上几乎所有排版 token 都经由 `var(--dsw-font-family)` 解析(`--dsw-font-base-16`、`--dsw-font-xs-13`、整条 Markdown 字号阶梯……),shell 自己的 `body` 规则也读取它,所以覆盖这两个变量就等于重新指向整个界面。
|
|
77
|
+
|
|
78
|
+
但最直观的写法——在 `documentElement` 上设自定义属性——活不下来。`ui-layout` 的 `ThemePresenter` 掌管 `document.body.style`,每次主题变化时它会把**所有不是自己写的自定义属性删掉**,再重新写入当前 token 集合。外部设置的属性会在用户第一次切换浅色/深色时消失。
|
|
79
|
+
|
|
80
|
+
所以浏览器半边把字体叠加到 theme 上:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
ctx.theme.overrideTokens('dsh-font', {
|
|
84
|
+
'--dsw-font-family': { light: uiFontFamily, dark: uiFontFamily },
|
|
85
|
+
'--ds-font-family-code': { light: codeFontFamily, dark: codeFontFamily },
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
这个覆盖层会被折进当前快照,presenter 随后会把它写出去,并在每次调色板变化时继续写。`{ light, dark }` 这一对是强制的:`overrideTokens` 对裸字符串会抛出带解释的错误,因为单一取值在另一套调色板下会不可读。
|
|
90
|
+
|
|
91
|
+
### 为什么字号走样式表
|
|
92
|
+
|
|
93
|
+
设计系统里**没有**界面字号变量。每个组件都写死 11、12、13、14、16、20、24 px 之一,而 `--dsw-font-*` 是一套固定阶梯(`--dsw-font-xs-13` 就是 13px,没有变量可调)。代码字号同样烘焙在复合 token 里。
|
|
94
|
+
|
|
95
|
+
因此插件为每个内置尺寸生成一个工具类:
|
|
96
|
+
|
|
97
|
+
```css
|
|
98
|
+
.dsh-font-size-14{font-size:calc(14px * var(--dsh-font-ui-scale,1)) !important}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
并把对应的类逐个盖章到元素上——在元素还没有盖章时用 `getComputedStyle` 量出它的内置尺寸。`MutationObserver` 会在界面挂载新节点时补盖。
|
|
102
|
+
|
|
103
|
+
之所以逐元素盖章而不是用通配规则,正是为了让两条字号轴互不影响:`html body *` 这种覆盖会继承进会话区域并与正文字号复合叠加,而且还会改掉那些本来不该缩放的装饰性尺寸(文件类型图标里内嵌的 SVG 文字标签)。逐元素测量还意味着插件不需要知道任何组件的类名,因此能扛住界面重构。
|
|
104
|
+
|
|
105
|
+
### 正文字号写在哪里
|
|
106
|
+
|
|
107
|
+
`--dsh-content-font-size` 是写在 `body` 上的**内联**自定义属性,由 `ui-layout` 依据兄弟命名空间 `ui-theme` 写入(它自己的设置项提供 12–17 px)。内联声明无论有没有 `!important` 都压过样式表,所以插件把取值写到完全相同的位置:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
document.body.style.setProperty('--dsh-content-font-size', `${contentSize}px`)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
样式表随后以这个值为基准,用绝对 px 重新推导整条 Markdown 字号阶梯。变量仍然归 `ui-theme` 所有,它每次主题变化仍会写自己的值;插件在每次设置变化时重新写入自己的值。设置项的说明文字会告诉用户:这里的取值会盖过「外观」里的「字号大小」。
|
|
114
|
+
|
|
115
|
+
### 首屏绘制前
|
|
116
|
+
|
|
117
|
+
Node 半边响应 `webserver/index-inject`,注入一段内联 `<script>`,在 shell 挂载之前就装好样式表并设好两个字体变量,因此第一帧就已经是用户的字体。它在渲染时读取同一个 `ui-font` 设置段;当设置服务缺失时回退到 schema 默认值。
|
|
118
|
+
|
|
119
|
+
## 开发
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
npm run build # src/client.js -> lib/client.js
|
|
123
|
+
npm run check # 检查 lib/client.js 是否过期,并跑两个校验脚本
|
|
124
|
+
npm run verify # 只跑校验脚本
|
|
125
|
+
npm run watch # 保存即重建,配合 dsh-client-hmr
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
浏览器半边的唯一真源是 `src/client.js`。它为了可读性写成 ES module,但 DSH 的客户端 bundle 是**传统脚本(classic script)**,只允许通过 `window.__ModuleLoader__` 注册一个惰性 CommonJS 工厂——因此 `scripts/build-client.mjs` 负责套上这层外壳,并把静态 import 改写成 `require` 调用。这个转换刻意做得很窄,遇到无法改写的写法会直接让构建失败,因为没有打包器能替你发现问题。
|
|
129
|
+
|
|
130
|
+
浏览器半边只允许请求 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.client.external` 并作为独立的图节点发布。构建会强制检查这一点。
|
|
131
|
+
|
|
132
|
+
要在运行中的宿主上迭代浏览器半边,开一个 watcher,让 `dsh-client-hmr` 热替换插件——它每 500 ms 轮询一次客户端 bundle,因此保存后的重建无需刷新页面即可生效:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
# 终端 1
|
|
136
|
+
npm run watch
|
|
137
|
+
|
|
138
|
+
# 终端 2
|
|
139
|
+
dsh --profile web
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
刷新页面总能拿到新组合的图,所以 watcher 只是便利,不是必需。
|
|
143
|
+
|
|
144
|
+
## 包结构
|
|
145
|
+
|
|
146
|
+
| 路径 | 作用 |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `lib/index.js` | Node 半边:设置命名空间、首屏绘制前的注入。由 loader 加载。 |
|
|
149
|
+
| `lib/client.js` | 浏览器半边,**由 `src/client.js` 生成**。由 `/plugins/dsh-font/client.js` 提供。 |
|
|
150
|
+
| `src/client.js` | 浏览器半边源码。 |
|
|
151
|
+
| `cordis.patch.yml` | 本 bundle 贡献的 profile 层。 |
|
|
152
|
+
| `scripts/` | 构建与校验脚本。 |
|
|
153
|
+
| `package.json` | 声明 `dsh.bundle`(profile 层)与 `dsh.client`(浏览器节点)。 |
|
|
154
|
+
|
|
155
|
+
## 已知限制
|
|
156
|
+
|
|
157
|
+
- **只处理写死的尺寸。** 界面缩放只作用于样式表里以 px 写死的文字,刻意不动展示级大字号和装饰性字形尺寸,因此 150% 并不等于整个界面的等比 1.5 倍。
|
|
158
|
+
- **正文字号会盖过 `ui-theme` 的字号设置项。** 两者都写 `--dsh-content-font-size`,后写者生效,而本插件在设置变化时总会写。请只用其中一个。
|
|
159
|
+
- **字体不会被安装。** 只有浏览器或操作系统能解析到的字体名才会生效;插件不打包也不下载 webfont,因此写错名字只会静默回退到列表末尾的通用字体。
|
|
160
|
+
- **逐元素盖章的开销与 DOM 规模成正比。** 缩放扫描对每个元素只测量一次并缓存结果,之后按 DOM 变化增量盖章,因此开销有界——但在超长会话里并非免费。
|
|
161
|
+
- **设置项只有中英两种文案**,与官方内置的语言对一致。
|
|
162
|
+
|
|
163
|
+
## 许可证
|
|
164
|
+
|
|
165
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# dsh-font bundle patch — the layer this package contributes to a dsh profile.
|
|
2
|
+
#
|
|
3
|
+
# A bundle layer is a top-level YAML array of loader patch entries applied over
|
|
4
|
+
# the layers before it (`dsh-base`, `dsh-web-app`, then the profile's own
|
|
5
|
+
# cordis.patch.yml). This patch only inserts one row, so it composes with any
|
|
6
|
+
# other ordering.
|
|
7
|
+
#
|
|
8
|
+
# The inserted row is the plugin's host half. It is also the row the browser
|
|
9
|
+
# roster is keyed on: `@deepseek-ai/dsh-client-modules` scans the live loader
|
|
10
|
+
# entries for packages declaring `dsh.client`, finds this one, and serves
|
|
11
|
+
# `exports["./client"]` (`lib/client.js`) into the Web GUI at
|
|
12
|
+
# `/plugins/@citisen/dsh-font/client.js`.
|
|
13
|
+
#
|
|
14
|
+
# The quote around `name` is required: a bare YAML scalar may not start with
|
|
15
|
+
# `@`. The id is deliberately short and stable — remove or disable a row by id
|
|
16
|
+
# from a later layer, and never mount this package through two loader entries.
|
|
17
|
+
|
|
18
|
+
- insert:
|
|
19
|
+
- id: font
|
|
20
|
+
name: '@citisen/dsh-font'
|