@citisen/dsh-font 0.1.0 → 0.2.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/README.md +274 -14
- package/README.zh.md +120 -9
- package/lib/client.js +1911 -145
- package/lib/index.js +66 -4
- package/package.json +9 -3
- package/scripts/build-client.mjs +24 -1
- package/scripts/release.mjs +91 -0
- package/scripts/verify-client.mjs +1177 -12
- package/scripts/verify-host.mjs +59 -5
- package/scripts/verify-profile.mjs +64 -16
- package/src/client.js +1893 -145
package/lib/index.js
CHANGED
|
@@ -27,6 +27,18 @@ export const FONT_SETTINGS_NAMESPACE = 'ui-font'
|
|
|
27
27
|
export const UI_FONT_FAMILY_FIELD = 'uiFontFamily'
|
|
28
28
|
/** Field: CSS font-family list for code, terminal, and monospace text. */
|
|
29
29
|
export const CODE_FONT_FAMILY_FIELD = 'codeFontFamily'
|
|
30
|
+
/** Field: numeric `font-weight` for code, terminal, and monospace text. */
|
|
31
|
+
export const CODE_FONT_WEIGHT_FIELD = 'codeFontWeight'
|
|
32
|
+
/**
|
|
33
|
+
* Field: numeric `font-weight` for interface text.
|
|
34
|
+
*
|
|
35
|
+
* Unlike the code weight this one is opted into: `400` — the value every
|
|
36
|
+
* untouched install resolves to — is the design system's own base, so the
|
|
37
|
+
* plugin emits no weight rule at all for it. Anything else is written as an
|
|
38
|
+
* explicit override, which is why "not set" and "set to 400" are the same
|
|
39
|
+
* state rather than two.
|
|
40
|
+
*/
|
|
41
|
+
export const UI_FONT_WEIGHT_FIELD = 'uiFontWeight'
|
|
30
42
|
/** Field: scale applied to every interface font size, as a unitless number. */
|
|
31
43
|
export const UI_FONT_SCALE_FIELD = 'uiFontScale'
|
|
32
44
|
/** Field: conversation content font size in px (owns `--dsh-content-font-size`). */
|
|
@@ -41,6 +53,36 @@ export const DEFAULT_UI_FONT_FAMILY =
|
|
|
41
53
|
export const DEFAULT_CODE_FONT_FAMILY =
|
|
42
54
|
'"SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"'
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Weights the code font may be set to.
|
|
58
|
+
*
|
|
59
|
+
* The design system ships **no** weight token — every `font: …` declaration in
|
|
60
|
+
* the interface is a literal, and the code ladder is literally `400`. So the
|
|
61
|
+
* weight is delivered as a `font-weight` override the plugin owns, and it is a
|
|
62
|
+
* closed list rather than a free number: a `font-weight` a family does not have
|
|
63
|
+
* is synthesized by the browser (faux-bold), and offering that silently would
|
|
64
|
+
* be a poor default. The list is the CSS Fonts keyword scale, which is also the
|
|
65
|
+
* scale the Local Font Access API reports individual faces on.
|
|
66
|
+
*
|
|
67
|
+
* Kept in sync by hand with the browser half's table in `src/client.js`; the
|
|
68
|
+
* two halves are separate bundles and share no module.
|
|
69
|
+
*/
|
|
70
|
+
export const FONT_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900]
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The shipped code weight. Matches the design system's own literal (`400` in
|
|
74
|
+
* every `--dsw-font-markdown-code*` token), so "no customization" is the
|
|
75
|
+
* default and an untouched install paints exactly what it painted before.
|
|
76
|
+
*/
|
|
77
|
+
export const DEFAULT_CODE_FONT_WEIGHT = 400
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The shipped interface weight. Identical to {@link DEFAULT_CODE_FONT_WEIGHT}
|
|
81
|
+
* for the same reason: it is what the shipped interface already computes, so an
|
|
82
|
+
* untouched install paints exactly what it painted before this field existed.
|
|
83
|
+
*/
|
|
84
|
+
export const DEFAULT_UI_FONT_WEIGHT = 400
|
|
85
|
+
|
|
44
86
|
/** Accepted interface scale range (1 = the shipped sizes). */
|
|
45
87
|
export const UI_FONT_SCALE_MIN = 0.75
|
|
46
88
|
/** Accepted interface scale range upper bound. */
|
|
@@ -67,6 +109,8 @@ export const FONT_STYLE_ID = 'dsh-font/variables'
|
|
|
67
109
|
export const FontSettingsSchema = z.object({
|
|
68
110
|
[UI_FONT_FAMILY_FIELD]: z.string().default(DEFAULT_UI_FONT_FAMILY),
|
|
69
111
|
[CODE_FONT_FAMILY_FIELD]: z.string().default(DEFAULT_CODE_FONT_FAMILY),
|
|
112
|
+
[CODE_FONT_WEIGHT_FIELD]: z.union(FONT_WEIGHTS).default(DEFAULT_CODE_FONT_WEIGHT),
|
|
113
|
+
[UI_FONT_WEIGHT_FIELD]: z.union(FONT_WEIGHTS).default(DEFAULT_UI_FONT_WEIGHT),
|
|
70
114
|
[UI_FONT_SCALE_FIELD]: z
|
|
71
115
|
.number()
|
|
72
116
|
.min(UI_FONT_SCALE_MIN)
|
|
@@ -108,10 +152,9 @@ function uiScaleRules(scale) {
|
|
|
108
152
|
* Build the pre-paint stylesheet for one resolved settings section.
|
|
109
153
|
*
|
|
110
154
|
* This is the subset of the browser half's sheet that must exist before any
|
|
111
|
-
* plugin runs: the interface scale and the conversation text size. Families
|
|
112
|
-
* handled separately (see {@link fontBootstrapScript})
|
|
113
|
-
* system resolves them through
|
|
114
|
-
* already reads.
|
|
155
|
+
* plugin runs: the interface scale and the conversation text size. Families and
|
|
156
|
+
* the code weight are handled separately (see {@link fontBootstrapScript})
|
|
157
|
+
* because the design system resolves them through the token chain.
|
|
115
158
|
*
|
|
116
159
|
* Written as a `<style>` element rather than inline custom properties because
|
|
117
160
|
* `ui-layout`'s theme presenter owns `document.body.style` and rewrites the
|
|
@@ -126,6 +169,19 @@ export function fontStyleSheet(section) {
|
|
|
126
169
|
const uiScale = section[UI_FONT_SCALE_FIELD]
|
|
127
170
|
const contentSize = section[CONTENT_FONT_SIZE_FIELD]
|
|
128
171
|
const codeSize = section[CODE_FONT_SIZE_FIELD]
|
|
172
|
+
const codeWeight = section[CODE_FONT_WEIGHT_FIELD]
|
|
173
|
+
const uiWeight = section[UI_FONT_WEIGHT_FIELD]
|
|
174
|
+
|
|
175
|
+
// The interface weight is the one field that is *not* always emitted: at the
|
|
176
|
+
// shipped 400 the rule would be a no-op (`normal` inherits as 400 anyway), and
|
|
177
|
+
// leaving it out keeps a default install's first frame byte-identical to the
|
|
178
|
+
// shipped one. The browser half repeats this rule in its own — larger — sheet,
|
|
179
|
+
// which replaces this one the moment the plugin activates; both exist so the
|
|
180
|
+
// weight cannot flash between the two.
|
|
181
|
+
const uiWeightRules =
|
|
182
|
+
uiWeight === undefined || uiWeight === DEFAULT_UI_FONT_WEIGHT
|
|
183
|
+
? []
|
|
184
|
+
: [`html body{font-weight:${String(uiWeight)}}`]
|
|
129
185
|
|
|
130
186
|
return [
|
|
131
187
|
'/* dsh-font: pre-paint variables and interface scale */',
|
|
@@ -133,7 +189,9 @@ export function fontStyleSheet(section) {
|
|
|
133
189
|
`--dsh-font-ui-scale:${String(uiScale)};`,
|
|
134
190
|
`--dsh-font-content-size:${String(contentSize)}px;`,
|
|
135
191
|
`--dsh-font-code-size:${String(codeSize)}px;`,
|
|
192
|
+
`--dsh-font-code-weight:${String(codeWeight)};`,
|
|
136
193
|
'}',
|
|
194
|
+
...uiWeightRules,
|
|
137
195
|
'html body,html body *{',
|
|
138
196
|
uiScaleRules(uiScale),
|
|
139
197
|
'}',
|
|
@@ -161,6 +219,7 @@ function escapeForScript(text) {
|
|
|
161
219
|
export function fontBootstrapScript(section) {
|
|
162
220
|
const uiFamily = JSON.stringify(section[UI_FONT_FAMILY_FIELD])
|
|
163
221
|
const codeFamily = JSON.stringify(section[CODE_FONT_FAMILY_FIELD])
|
|
222
|
+
const codeWeight = JSON.stringify(String(section[CODE_FONT_WEIGHT_FIELD]))
|
|
164
223
|
// Keep the stylesheet after the user-controlled family strings so the
|
|
165
224
|
// `</`-escaping cannot be defeated by a family value that itself contains a
|
|
166
225
|
// script-closing sequence.
|
|
@@ -181,6 +240,7 @@ export function fontBootstrapScript(section) {
|
|
|
181
240
|
const root = document.documentElement.style
|
|
182
241
|
root.setProperty('--dsw-font-family', ${uiFamily})
|
|
183
242
|
root.setProperty('--ds-font-family-code', ${codeFamily})
|
|
243
|
+
root.setProperty('--dsh-font-code-weight', ${codeWeight})
|
|
184
244
|
} catch (error) {
|
|
185
245
|
console.warn('dsh-font: pre-paint bootstrap failed', error)
|
|
186
246
|
}
|
|
@@ -210,6 +270,8 @@ function readSection(ctx) {
|
|
|
210
270
|
const fallback = {
|
|
211
271
|
[UI_FONT_FAMILY_FIELD]: DEFAULT_UI_FONT_FAMILY,
|
|
212
272
|
[CODE_FONT_FAMILY_FIELD]: DEFAULT_CODE_FONT_FAMILY,
|
|
273
|
+
[CODE_FONT_WEIGHT_FIELD]: DEFAULT_CODE_FONT_WEIGHT,
|
|
274
|
+
[UI_FONT_WEIGHT_FIELD]: DEFAULT_UI_FONT_WEIGHT,
|
|
213
275
|
[UI_FONT_SCALE_FIELD]: 1,
|
|
214
276
|
[CONTENT_FONT_SIZE_FIELD]: 14,
|
|
215
277
|
[CODE_FONT_SIZE_FIELD]: 12,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citisen/dsh-font",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DeepSeek Harness plugin: customize the Web GUI fonts (interface family, code family, and three font-size axes) from Settings",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,10 @@
|
|
|
49
49
|
"build": "node scripts/build-client.mjs",
|
|
50
50
|
"watch": "node scripts/build-client.mjs --watch",
|
|
51
51
|
"verify": "node scripts/verify-host.mjs && node scripts/verify-client.mjs",
|
|
52
|
-
"check": "node scripts/build-client.mjs --check && npm run verify"
|
|
52
|
+
"check": "node scripts/build-client.mjs --check && npm run verify",
|
|
53
|
+
"check:all": "npm run check && node scripts/verify-profile.mjs",
|
|
54
|
+
"release": "node scripts/release.mjs",
|
|
55
|
+
"prepublishOnly": "npm run check"
|
|
53
56
|
},
|
|
54
57
|
"peerDependencies": {
|
|
55
58
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
@@ -72,5 +75,8 @@
|
|
|
72
75
|
"theme",
|
|
73
76
|
"web-ui",
|
|
74
77
|
"web-gui"
|
|
75
|
-
]
|
|
78
|
+
],
|
|
79
|
+
"dependencies": {
|
|
80
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
81
|
+
}
|
|
76
82
|
}
|
package/scripts/build-client.mjs
CHANGED
|
@@ -38,7 +38,30 @@ const PACKAGE_NAME = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')
|
|
|
38
38
|
* what the loader consumes; the rest exist so the verifier can unit-test the
|
|
39
39
|
* pure builders without re-parsing the source.
|
|
40
40
|
*/
|
|
41
|
-
const ENVELOPE_EXPORTS = [
|
|
41
|
+
const ENVELOPE_EXPORTS = [
|
|
42
|
+
'apply',
|
|
43
|
+
'inject',
|
|
44
|
+
'fontStyleSheet',
|
|
45
|
+
'applyFonts',
|
|
46
|
+
'parseFamilyList',
|
|
47
|
+
'serializeFamilyList',
|
|
48
|
+
'normalizeWeight',
|
|
49
|
+
'weightWord',
|
|
50
|
+
'faceWeights',
|
|
51
|
+
'emphasisWeight',
|
|
52
|
+
'parseFontQuery',
|
|
53
|
+
'fontQueryTokens',
|
|
54
|
+
'serializeFontQuery',
|
|
55
|
+
'queryContextAt',
|
|
56
|
+
'querySuggestions',
|
|
57
|
+
'applySuggestion',
|
|
58
|
+
'moveFontQueryEntry',
|
|
59
|
+
'queryTokenClass',
|
|
60
|
+
'clampHighlight',
|
|
61
|
+
'describeDiagnostic',
|
|
62
|
+
'rankFamilyMatches',
|
|
63
|
+
'FontQueryEditor',
|
|
64
|
+
]
|
|
42
65
|
|
|
43
66
|
/**
|
|
44
67
|
* `import [default][, { named }] from 'spec'`, with no nested braces.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maintainer-side release helper.
|
|
3
|
+
*
|
|
4
|
+
* Publishing is split in two on purpose: CI stages a tarball (see
|
|
5
|
+
* `.github/workflows/stage.yml`), and a human approves it here, where npm
|
|
6
|
+
* demands that human's 2FA. A compromised runner or a malicious commit can
|
|
7
|
+
* therefore stage but never publish.
|
|
8
|
+
*
|
|
9
|
+
* `npm stage` needs npm >= 11.6 (12 for the current subcommands) and Node 22.22
|
|
10
|
+
* / 24.15 / 26+, which the local Node may not satisfy. So rather than requiring
|
|
11
|
+
* a global npm upgrade, this delegates to `npx npm@12`, and borrows the system
|
|
12
|
+
* `git` for the OTP flow npm uses for a 2FA-gated web approval.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node scripts/release.mjs list [package]
|
|
16
|
+
* node scripts/release.mjs view <stage-id>
|
|
17
|
+
* node scripts/release.mjs approve <stage-id>
|
|
18
|
+
* node scripts/release.mjs reject <stage-id>
|
|
19
|
+
* node scripts/release.mjs download <stage-id> [--dry-run]
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'node:child_process'
|
|
23
|
+
import { readFileSync } from 'node:fs'
|
|
24
|
+
import { dirname, join } from 'node:path'
|
|
25
|
+
import { fileURLToPath } from 'node:url'
|
|
26
|
+
|
|
27
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
28
|
+
const [, , subcommand, ...rest] = process.argv
|
|
29
|
+
|
|
30
|
+
const PACKAGE_NAME = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name
|
|
31
|
+
const VERSION = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version
|
|
32
|
+
|
|
33
|
+
/** npm CLI major to run; `npm stage` needs 12 for `list`/`approve`. */
|
|
34
|
+
const NPM_SPEC = 'npm@12'
|
|
35
|
+
|
|
36
|
+
const USAGE = `usage: node scripts/release.mjs <command> [args]
|
|
37
|
+
|
|
38
|
+
list [package] staged versions (default: ${PACKAGE_NAME})
|
|
39
|
+
view <stage-id> details of one staged version
|
|
40
|
+
approve <stage-id> publish it — REQUIRES your 2FA
|
|
41
|
+
reject <stage-id> discard it
|
|
42
|
+
download <stage-id> fetch the staged tarball for inspection
|
|
43
|
+
|
|
44
|
+
Nothing here publishes without an approval, which is the point: CI can only
|
|
45
|
+
stage.`
|
|
46
|
+
|
|
47
|
+
const KNOWN = new Set(['list', 'view', 'approve', 'reject', 'download'])
|
|
48
|
+
|
|
49
|
+
if (subcommand === undefined || subcommand === '--help' || subcommand === '-h') {
|
|
50
|
+
console.log(USAGE)
|
|
51
|
+
process.exit(0)
|
|
52
|
+
}
|
|
53
|
+
if (!KNOWN.has(subcommand)) {
|
|
54
|
+
console.error(`release: unknown command "${subcommand}"\n\n${USAGE}`)
|
|
55
|
+
process.exit(2)
|
|
56
|
+
}
|
|
57
|
+
if (subcommand !== 'list' && rest.length === 0) {
|
|
58
|
+
console.error(`release: "${subcommand}" needs a stage-id\n\n${USAGE}`)
|
|
59
|
+
process.exit(2)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const args = [NPM_SPEC, 'stage', subcommand]
|
|
63
|
+
if (subcommand === 'list') args.push(rest[0] ?? PACKAGE_NAME)
|
|
64
|
+
else args.push(...rest)
|
|
65
|
+
|
|
66
|
+
console.log(`release: npx ${args.join(' ')}`)
|
|
67
|
+
if (subcommand === 'approve') {
|
|
68
|
+
console.log('release: approving publishes to the registry and requires your 2FA')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = spawnSync('npx', ['--yes', ...args], {
|
|
72
|
+
cwd: root,
|
|
73
|
+
stdio: 'inherit',
|
|
74
|
+
shell: process.platform === 'win32',
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (result.error !== undefined) {
|
|
78
|
+
if (result.error.code === 'ENOENT') {
|
|
79
|
+
console.error('release: npx not found on PATH — install Node.js')
|
|
80
|
+
process.exit(127)
|
|
81
|
+
}
|
|
82
|
+
throw result.error
|
|
83
|
+
}
|
|
84
|
+
const exitCode = result.status ?? 1
|
|
85
|
+
|
|
86
|
+
if (exitCode === 0 && subcommand === 'list') {
|
|
87
|
+
console.log(`\nrelease: nothing is installable until a stage is approved.`)
|
|
88
|
+
console.log(`release: a local release runs \`npm run release -- approve <stage-id>\` from v${VERSION}.`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
process.exit(exitCode)
|