dsh-fonttune 0.7.7 → 0.7.8

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.en.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
4
4
 
5
+ ## [0.7.8] - 2026-11-05
6
+
7
+ ### Fixed
8
+ - **The font picker opened slowly**: the list now builds one screenful, and the names are no longer drawn in each family's own font, so opening and scrolling stay quick on a machine with many installed fonts.
9
+ - **A pause when switching presets**: a preset remembers the notches that were measured for it, so switching uses them directly instead of measuring again at that moment.
10
+
11
+ ### Changed
12
+ - **Only verified host versions are declared**: 0.2.0-rc.1 through 0.2.2-rc.1 are listed one by one; older or untested versions are no longer claimed.
13
+
5
14
  ## [0.7.7] - 2026-11-05
6
15
 
7
16
  ### Fixed
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  **dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
4
4
 
5
+ ## [0.7.8] - 2026-11-05
6
+
7
+ ### 修复
8
+ - **字体选择列表打开较慢**:列表现在只列出首屏,并且不再逐个用各自字体显示名称;机器上安装的字体很多时,打开与滚动都更快。
9
+ - **切换配置方案时的停顿**:方案会记住已测好的档位,切换时直接使用,切换的那一瞬间不再重新测量。
10
+
11
+ ### 变更
12
+ - **兼容范围只保留实测过的版本**:从 0.2.0-rc.1 到 0.2.2-rc.1 逐个列出,更早或未验证的版本不再声明支持。
13
+
5
14
  ## [0.7.7] - 2026-11-05
6
15
 
7
16
  ### 修复
package/README.en.md CHANGED
@@ -39,7 +39,7 @@ The floating tuning panel (left, with its "in force right now" readout) and the
39
39
 
40
40
  | Plugin version | Supported DSH versions |
41
41
  | --- | --- |
42
- | **0.7.x** (latest, 0.7.7) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2, 0.2.1-rc.4, 0.2.2-alpha.2, 0.2.1-rc.3, 0.2.1-rc.5, 0.2.2-rc.1 |
42
+ | **0.7.x** (latest, 0.7.7) | 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-rc.1, 0.2.1-rc.2, 0.2.1-rc.3, 0.2.1-rc.4, 0.2.1-rc.5, 0.2.2-rc.1|
43
43
  | 0.6.x (0.6.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2, 0.2.1-alpha.3, 0.2.1-alpha.4, 0.2.1-alpha.5, 0.2.1-rc.1, 0.2.1-rc.2 |
44
44
  | 0.5.x (0.5.0) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
45
45
  | 0.4.x (0.4.2) | 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-alpha.1, 0.2.1-alpha.2 |
package/README.md CHANGED
@@ -39,7 +39,7 @@
39
39
 
40
40
  | 插件版本 | 支持的 DSH 版本 |
41
41
  | --- | --- |
42
- | **0.7.x**(最新,0.7.7) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2、0.2.1-rc.4、0.2.2-alpha.2、0.2.1-rc.3、0.2.1-rc.5、0.2.2-rc.1 |
42
+ | **0.7.x**(最新,0.7.7) | 0.2.0-rc.1、0.2.0-rc.2、0.2.1-rc.1、0.2.1-rc.2、0.2.1-rc.3、0.2.1-rc.4、0.2.1-rc.5、0.2.2-rc.1|
43
43
  | 0.6.x(0.6.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2、0.2.1-alpha.3、0.2.1-alpha.4、0.2.1-alpha.5、0.2.1-rc.1、0.2.1-rc.2 |
44
44
  | 0.5.x(0.5.0) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
45
45
  | 0.4.x(0.4.2) | 0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2、0.2.0-rc.1、0.2.0-rc.2、0.2.1-alpha.1、0.2.1-alpha.2 |
package/lib/client.js CHANGED
@@ -5000,6 +5000,40 @@ function catalogRefreshDue(cache, now, retryMs) {
5000
5000
  return now - (cache.at || 0) >= retryMs;
5001
5001
  }
5002
5002
 
5003
+ /**
5004
+ * The rows one picker group actually renders.
5005
+ *
5006
+ * A machine with a huge font collection — or a Super TTC, which exposes every
5007
+ * weight and variant as its own family — can enumerate hundreds of names, and
5008
+ * building a row for each one is a visible stall when the list opens (issue #3:
5009
+ * the reporter's second symptom). The list therefore renders a screenful unless
5010
+ * the reader is searching, and a family that is already selected is always
5011
+ * rendered so its check mark never falls outside the window.
5012
+ * @param {string[]} families - the group's families, in display order.
5013
+ * @param {string} query - the search text (empty means "browse the whole list").
5014
+ * @param {Object<string, boolean>} selectedLower - lower-cased names already picked.
5015
+ * @param {number} [limit] - how many rows a browse renders.
5016
+ * @returns {string[]} the families to render.
5017
+ */
5018
+ function pickerRows(families, query, selectedLower, limit) {
5019
+ var list = Array.isArray(families) ? families : [];
5020
+ var searching = typeof query === "string" && query.trim() !== "";
5021
+ if (searching) return list.slice();
5022
+ var cap = typeof limit === "number" && limit > 0 ? Math.floor(limit) : 60;
5023
+ var picked = selectedLower !== null && typeof selectedLower === "object" ? selectedLower : {};
5024
+ var rows = [];
5025
+ var shown = 0;
5026
+ for (var index = 0; index < list.length; index += 1) {
5027
+ var name = list[index];
5028
+ var isSelected = picked[String(name).toLowerCase()] === true;
5029
+ if (isSelected || shown < cap) {
5030
+ rows.push(name);
5031
+ shown += 1;
5032
+ }
5033
+ }
5034
+ return rows;
5035
+ }
5036
+
5003
5037
  var shared = {
5004
5038
  NAMESPACE: NAMESPACE,
5005
5039
  SANS_FIELD: SANS_FIELD,
@@ -5147,6 +5181,7 @@ var shared = {
5147
5181
  PRESETS: PRESETS,
5148
5182
  PRESET_GROUPS: PRESET_GROUPS,
5149
5183
  MAX_LOCAL_FONTS: MAX_LOCAL_FONTS,
5184
+ pickerRows: pickerRows,
5150
5185
  pickerGroups: pickerGroups,
5151
5186
  catalogRefreshDue: catalogRefreshDue,
5152
5187
  MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
@@ -7354,9 +7389,21 @@ function deferWeightWarm(job) {
7354
7389
  */
7355
7390
  var warmSignature = null;
7356
7391
 
7392
+ /** When a preset was applied: its warm-up waits so the switch itself feels instant. */
7393
+ var presetSwitchAt = 0;
7394
+
7357
7395
  /** The code stack the deferred slice was last scheduled for (scheduled once). */
7358
7396
  var codeWarmSignature = null;
7359
7397
  var scheduleWeightWarm = function (editing) {
7398
+ // A preset switch changes several stacks at once. Hydrated shapes make the rows
7399
+ // correct immediately, so the sweep those stacks would trigger can wait: measuring
7400
+ // inside the switch is what made switching stutter on a slow font.
7401
+ if (Date.now() - presetSwitchAt < 1200) {
7402
+ globalThis.setTimeout(function () {
7403
+ scheduleWeightWarm(editing);
7404
+ }, 1200);
7405
+ return;
7406
+ }
7360
7407
  var stacks = weightAxisStacks(editing);
7361
7408
  var signature = stacks.dialog + "\u0000" + stacks.ui + "\u0000" + stacks.code;
7362
7409
  if (signature === warmSignature) return;
@@ -8547,11 +8594,18 @@ function StackPicker(props) {
8547
8594
  var panel = null;
8548
8595
  if (open) {
8549
8596
  var rows = [];
8597
+ // A machine with a huge font collection (or a Super TTC, which exposes every
8598
+ // weight and variant as its own family) can enumerate hundreds of names. Only
8599
+ // the first screenful is built unless the reader is searching, and a selected
8600
+ // family is always among them — drawing every row was a second of layout on
8601
+ // such a machine. The subsetting itself is pure and covered offline.
8602
+ var RENDER_LIMIT = 60;
8550
8603
  for (var groupRender = 0; groupRender < options.length; groupRender += 1) {
8551
8604
  var group = options[groupRender];
8552
8605
  rows.push(h("div", { className: "dfp-group", key: "g:" + group.label }, t(group.label)));
8553
- for (var rowIndex = 0; rowIndex < group.families.length; rowIndex += 1) {
8554
- var name = group.families[rowIndex];
8606
+ var visible = shared.pickerRows(group.families, typed, selectedLower, RENDER_LIMIT);
8607
+ for (var rowIndex = 0; rowIndex < visible.length; rowIndex += 1) {
8608
+ var name = visible[rowIndex];
8555
8609
  var isSelected = selectedLower[name.toLowerCase()] === true;
8556
8610
  rows.push(
8557
8611
  h(
@@ -8560,12 +8614,13 @@ function StackPicker(props) {
8560
8614
  type: "button",
8561
8615
  className: "dfp-option",
8562
8616
  key: "o:" + group.label + ":" + name,
8563
- style: { fontFamily: quoteFamily(name) },
8564
8617
  "data-family": name,
8565
8618
  onClick: function (event) {
8566
8619
  toggle(event.currentTarget.dataset.family);
8567
8620
  },
8568
8621
  },
8622
+ // The rows are NOT drawn in their own family: on a large collection that
8623
+ // loaded one font per row, which is exactly the stall this list is about.
8569
8624
  h("span", { className: "dfp-optionLabel" }, name),
8570
8625
  isSelected ? h("span", { className: "dfp-optionCheck" }, "✓") : null
8571
8626
  )
@@ -9345,7 +9400,9 @@ function FontCard(props) {
9345
9400
  var next = presetList.slice();
9346
9401
  for (var index = 0; index < next.length; index += 1) {
9347
9402
  if (next[index].name === entry.name) {
9403
+ var shapes = shapeSnapshotFor(values);
9348
9404
  next[index] = { name: entry.name, values: values, savedAt: Date.now() };
9405
+ if (Object.keys(shapes).length > 0) next[index].shapes = shapes;
9349
9406
  }
9350
9407
  }
9351
9408
  return { op: shared.PENDING_SET, path: [PRESETS_FIELD], value: JSON.stringify(next) };
@@ -10189,10 +10246,63 @@ function FontCard(props) {
10189
10246
  * is exact.
10190
10247
  * @param {{name: string, values: object}} entry - the picked preset.
10191
10248
  */
10249
+ /**
10250
+ * The measured shapes a preset can carry.
10251
+ *
10252
+ * Switching presets changes the font stacks, which invalidates the slider shapes:
10253
+ * the new stacks would be measured again (a second of canvas work on a huge font
10254
+ * collection) the moment they are applied. A preset therefore remembers the
10255
+ * shapes it was saved with — three small records — and applying it restores them
10256
+ * instead of measuring (issue #3 follow-up: "switching presets stutters").
10257
+ * @param {object} values - the preset's value set.
10258
+ * @returns {object} cache key -> shape, empty when nothing is known yet.
10259
+ */
10260
+ var shapeSnapshotFor = function (values) {
10261
+ var stacks = weightAxisStacks(values);
10262
+ var pairs = [
10263
+ [stacks.dialog, shared.WEIGHT_LADDER_STRONG],
10264
+ [stacks.ui, shared.WEIGHT_LADDER_STRONG],
10265
+ [stacks.code, shared.WEIGHT_BASE],
10266
+ ];
10267
+ var snapshot = {};
10268
+ for (var index = 0; index < pairs.length; index += 1) {
10269
+ var stack = pairs[index][0];
10270
+ var base = pairs[index][1];
10271
+ var key = String(stack) + "\u0000" + base + "\u0000stepped";
10272
+ if (Object.prototype.hasOwnProperty.call(weightProfileCache, key)) {
10273
+ snapshot[key] = weightProfileCache[key];
10274
+ } else {
10275
+ var stored = storedWeightShape(key);
10276
+ if (stored !== null) snapshot[key] = stored;
10277
+ }
10278
+ }
10279
+ return snapshot;
10280
+ };
10281
+
10282
+ /**
10283
+ * Put a preset's remembered shapes back into the cache.
10284
+ * @param {object} shapes - what `shapeSnapshotFor` recorded.
10285
+ */
10286
+ var hydrateShapes = function (shapes) {
10287
+ if (shapes === null || typeof shapes !== "object") return;
10288
+ for (var key in shapes) {
10289
+ if (!Object.prototype.hasOwnProperty.call(shapes, key)) continue;
10290
+ var shape = shapes[key];
10291
+ if (shape === null || typeof shape !== "object") continue;
10292
+ if (!isFinite(shape.min) || !isFinite(shape.max) || !isFinite(shape.step)) continue;
10293
+ if (Object.prototype.hasOwnProperty.call(weightProfileCache, key)) continue;
10294
+ weightProfileCache[key] = { min: shape.min, max: shape.max, step: shape.step };
10295
+ }
10296
+ };
10297
+
10192
10298
  var switchPreset = function (entry) {
10193
10299
  if (!writable || entry.name === activeName) return;
10300
+ // Stamp the switch BEFORE anything is written: the sync that follows it would
10301
+ // otherwise start measuring the new stacks in the same task.
10302
+ presetSwitchAt = Date.now();
10194
10303
  applyingRef.current = true;
10195
10304
  try {
10305
+ hydrateShapes(entry.shapes);
10196
10306
  for (var index = 0; index < VALUE_FIELDS.length; index += 1) {
10197
10307
  var field = VALUE_FIELDS[index];
10198
10308
  var value = Object.prototype.hasOwnProperty.call(entry.values, field)
package/lib/shared.cjs CHANGED
@@ -4991,6 +4991,40 @@ function catalogRefreshDue(cache, now, retryMs) {
4991
4991
  return now - (cache.at || 0) >= retryMs;
4992
4992
  }
4993
4993
 
4994
+ /**
4995
+ * The rows one picker group actually renders.
4996
+ *
4997
+ * A machine with a huge font collection — or a Super TTC, which exposes every
4998
+ * weight and variant as its own family — can enumerate hundreds of names, and
4999
+ * building a row for each one is a visible stall when the list opens (issue #3:
5000
+ * the reporter's second symptom). The list therefore renders a screenful unless
5001
+ * the reader is searching, and a family that is already selected is always
5002
+ * rendered so its check mark never falls outside the window.
5003
+ * @param {string[]} families - the group's families, in display order.
5004
+ * @param {string} query - the search text (empty means "browse the whole list").
5005
+ * @param {Object<string, boolean>} selectedLower - lower-cased names already picked.
5006
+ * @param {number} [limit] - how many rows a browse renders.
5007
+ * @returns {string[]} the families to render.
5008
+ */
5009
+ function pickerRows(families, query, selectedLower, limit) {
5010
+ var list = Array.isArray(families) ? families : [];
5011
+ var searching = typeof query === "string" && query.trim() !== "";
5012
+ if (searching) return list.slice();
5013
+ var cap = typeof limit === "number" && limit > 0 ? Math.floor(limit) : 60;
5014
+ var picked = selectedLower !== null && typeof selectedLower === "object" ? selectedLower : {};
5015
+ var rows = [];
5016
+ var shown = 0;
5017
+ for (var index = 0; index < list.length; index += 1) {
5018
+ var name = list[index];
5019
+ var isSelected = picked[String(name).toLowerCase()] === true;
5020
+ if (isSelected || shown < cap) {
5021
+ rows.push(name);
5022
+ shown += 1;
5023
+ }
5024
+ }
5025
+ return rows;
5026
+ }
5027
+
4994
5028
  var shared = {
4995
5029
  NAMESPACE: NAMESPACE,
4996
5030
  SANS_FIELD: SANS_FIELD,
@@ -5138,6 +5172,7 @@ var shared = {
5138
5172
  PRESETS: PRESETS,
5139
5173
  PRESET_GROUPS: PRESET_GROUPS,
5140
5174
  MAX_LOCAL_FONTS: MAX_LOCAL_FONTS,
5175
+ pickerRows: pickerRows,
5141
5176
  pickerGroups: pickerGroups,
5142
5177
  catalogRefreshDue: catalogRefreshDue,
5143
5178
  MARKDOWN_SELECTOR: MARKDOWN_SELECTOR,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-fonttune",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "Font plugin for the DeepSeek Harness Web GUI: the conversation gets its own font, size, line height and weight, the interface font and weight follow it, code keeps its own axis with ligature control, and whole setups save as presets.",
5
5
  "type": "module",
6
6
  "icon": "icon.svg",
@@ -25,7 +25,7 @@
25
25
  "docs/card-shaping.jpg"
26
26
  ],
27
27
  "engines": {
28
- "dsh": ">=0.1.7-alpha.1 <0.3.0-0"
28
+ "dsh": ">=0.2.0-rc.1 <0.2.2-rc.2"
29
29
  },
30
30
  "dsh": {
31
31
  "bundle": {
@@ -36,24 +36,14 @@
36
36
  },
37
37
  "compatibility": {
38
38
  "dshReleases": {
39
- "0.1.7-alpha.1": "compatible",
40
- "0.1.7-alpha.2": "compatible",
41
- "0.1.7-rc.1": "compatible",
42
- "0.1.7-rc.2": "compatible",
43
39
  "0.2.0-rc.1": "compatible",
44
40
  "0.2.0-rc.2": "compatible",
45
- "0.2.1-alpha.1": "compatible",
46
- "0.2.1-alpha.2": "compatible",
47
- "0.2.1-alpha.3": "compatible",
48
- "0.2.1-alpha.4": "compatible",
49
- "0.2.1-alpha.5": "compatible",
50
41
  "0.2.1-rc.1": "compatible",
51
42
  "0.2.1-rc.2": "compatible",
52
- "0.2.1-rc.4": "compatible",
53
43
  "0.2.1-rc.3": "compatible",
54
- "0.2.1-rc.5": "compatible",
55
- "0.2.2-alpha.2": "compatible",
56
- "0.2.2-rc.1": "compatible"
44
+ "0.2.1-rc.4": "compatible",
45
+ "0.2.1-rc.5": "compatible",
46
+ "0.2.2-rc.1": "compatible"
57
47
  }
58
48
  }
59
49
  },