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/lib/index.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * dsh-fonttune — host half.
3
+ *
4
+ * Two jobs, both small:
5
+ *
6
+ * 1. Register the `dsh-fonttune` settings namespace so the durable values live
7
+ * in the Host user-settings document next to every other preference. The
8
+ * plugin's own composition entry is the base layer, so a value the user
9
+ * clears falls back to the profile's config rather than to nothing.
10
+ * 2. Contribute a `<style>` row to the served index. The browser half applies
11
+ * the same declarations once it activates, but that is after the shell has
12
+ * already painted; injecting here is what keeps the first frame from
13
+ * flashing DSH's default typography.
14
+ *
15
+ * @module dsh-fonttune
16
+ */
17
+ import z from "@deepseek-ai/schemastery";
18
+ // Default import, not named: `shared.cjs` is a CommonJS module, and Node's
19
+ // CJS-to-ESM interop only guarantees `module.exports` as the default binding
20
+ // (staticky-detected named exports would break this half at boot).
21
+ import shared from "./shared.cjs";
22
+
23
+ const {
24
+ buildFontCss,
25
+ FALLBACK_TOKENS,
26
+ MONO_FIELD,
27
+ NAMESPACE,
28
+ normalizeConfig,
29
+ SANS_FIELD,
30
+ SIZE_FIELD,
31
+ SIZE_MAX,
32
+ SIZE_MIN,
33
+ WEIGHT_FIELD,
34
+ WEIGHT_MAX,
35
+ WEIGHT_MIN,
36
+ WEIGHT_UNSET,
37
+ } = shared;
38
+
39
+ /**
40
+ * Characters a declaration cannot survive: they would close the declaration,
41
+ * the rule, or the `<style>` element the row is rendered into.
42
+ */
43
+ const SAFE_STACK = /^[^{};<>\\]*$/;
44
+
45
+ /**
46
+ * Stack length accepted by the durable schema, in characters.
47
+ */
48
+ const MAX_STACK = 200;
49
+
50
+ /**
51
+ * The durable settings section.
52
+ *
53
+ * Every field defaults to "leave DSH alone": an empty stack injects no family
54
+ * rule, a zero offset injects no size rule, and a zero weight injects no
55
+ * weight rule. Installing the plugin therefore changes nothing until the user
56
+ * asks for something.
57
+ */
58
+ export const Config = z.object({
59
+ [SANS_FIELD]: z
60
+ .string()
61
+ .max(MAX_STACK)
62
+ .pattern(SAFE_STACK)
63
+ .default("")
64
+ .description("CSS font-family list for the UI and conversation text"),
65
+ [MONO_FIELD]: z
66
+ .string()
67
+ .max(MAX_STACK)
68
+ .pattern(SAFE_STACK)
69
+ .default("")
70
+ .description("CSS font-family list for code and monospaced text"),
71
+ [SIZE_FIELD]: z
72
+ .number()
73
+ .min(SIZE_MIN)
74
+ .max(SIZE_MAX)
75
+ .default(0)
76
+ .description(
77
+ `Global font-size offset in px (${SIZE_MIN}..${SIZE_MAX}, 0 keeps DSH's own sizes)`
78
+ ),
79
+ [WEIGHT_FIELD]: z
80
+ .number()
81
+ .min(WEIGHT_UNSET)
82
+ .max(WEIGHT_MAX)
83
+ .default(WEIGHT_UNSET)
84
+ .description(
85
+ `Global font weight (${WEIGHT_MIN}..${WEIGHT_MAX}, ${WEIGHT_UNSET} keeps DSH's own weights)`
86
+ ),
87
+ });
88
+
89
+ /**
90
+ * One row of the structured index injection table.
91
+ * @typedef {{kind: "style", text: string}} StyleRow
92
+ */
93
+
94
+ /**
95
+ * The style row carrying one configuration.
96
+ * @param {unknown} resolved - the current settings section.
97
+ * @returns {StyleRow} the row the web server renders into `<head>`.
98
+ */
99
+ function styleRow(resolved) {
100
+ const text = buildFontCss(normalizeConfig(resolved), FALLBACK_TOKENS);
101
+ return { kind: "style", text };
102
+ }
103
+
104
+ /**
105
+ * Register the settings section and keep the index in sync with it.
106
+ * @param {object} ctx - host cordis context.
107
+ * @param {unknown} config - the plugin entry's composition config (base layer).
108
+ */
109
+ export function apply(ctx, config) {
110
+ let current = () => config;
111
+ ctx.inject(["settings"], (settingsCtx) => {
112
+ settingsCtx.settings.installSection(ctx, NAMESPACE, Config, config ?? {}, {
113
+ setSource: (source) => {
114
+ current = source;
115
+ },
116
+ // The row table is rebuilt on every index render and every worker boot
117
+ // payload, so each read is already fresh; nothing to invalidate here.
118
+ onChange: () => {},
119
+ });
120
+ });
121
+ ctx.on("webserver/index-inject", (table) => {
122
+ table.push(styleRow(current()));
123
+ });
124
+ }