dsh-fonttune 0.4.0 → 0.4.2

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,22 @@
2
2
 
3
3
  All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
4
4
 
5
+ ## [0.4.2] - 2026-10-12
6
+
7
+ ### Changed
8
+ - **DSH 0.2.1-alpha.1 and 0.2.1-alpha.2 join the compatibility table**: both were verified live — the settings card, the floating panel's tweaks and their writes, and the plugin's page all behave as documented.
9
+
10
+ ## [0.4.1] - 2026-10-04
11
+
12
+ ### Fixed
13
+ - **Panel weight values falling back after an adjustment**: on a freshly installed machine, reaching the target weight notch snapped straight back and a second try lost it too; the first adjustment now sticks.
14
+ - **Panel size and line-height values falling back after an adjustment**: the page followed for a moment and then returned to its old value, which made the line height look ineffective; an adjustment now applies at once and stays.
15
+ - **The unit next to a panel value vanishing while the slider moved**: it blinked away mid-drag and came back on release; it now stays put.
16
+
17
+ ### Changed
18
+ - **The plugin no longer appears in the Plugins page's official list**: it is not an official plugin, so that entry is gone; the whole settings card lives on the installed side now, and the panel's settings button opens that page directly.
19
+ - **The plugin's name and blurb on the Plugins page are Chinese**: the name reads 「字体增强(dsh-fonttune)」 and the blurb matches the settings card's wording.
20
+
5
21
  ## [0.4.0] - 2026-10-01
6
22
 
7
23
  ### Added
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  **dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
4
4
 
5
+ ## [0.4.2] - 2026-10-12
6
+
7
+ ### 变更
8
+ - **兼容对照表列出 DSH 0.2.1-alpha.1 与 0.2.1-alpha.2**:两个新预发布版实测通过,设置卡片、悬浮面板调参与写入、插件页卡片均正常。
9
+
10
+ ## [0.4.1] - 2026-10-04
11
+
12
+ ### 修复
13
+ - **面板调整字重后数值跳回**:在新安装的环境里,字重调到目标档位后会立刻退回原处,再调一次也留不住;现在第一次调整就能稳定保存。
14
+ - **面板调整字号与行高后数值跳回**:页面刚跟着变化就退回原值,行高因此看着没有作用;现在调整即时生效并保存。
15
+ - **面板数值的单位在调整时短暂消失**:滑块移动过程中单位会不见又出现,现在始终保留。
16
+
17
+ ### 变更
18
+ - **插件从插件页的官方列表移出**:本插件不是官方插件,官方栏不再有它的条目;完整设置改由「已安装」栏目的插件页承载,面板上的设置按钮直达该页。
19
+ - **插件页上的插件名称与简介改为中文**:名称显示为「字体增强(dsh-fonttune)」,简介与设置卡片同一句中文说明。
20
+
5
21
  ## [0.4.0] - 2026-10-01
6
22
 
7
23
  ### 新增
package/README.en.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > [中文](README.md) | **English**
4
4
 
5
- **Font plugin for DeepSeek Harness (DSH)**: the **conversation** (markdown prose) gets full typographic control — font, size, line height, weight; the **interface** (settings sheet, sidebars, workspace, buttons) takes a font and a weight and **follows the conversation by default**; **code** stays its own axis with ligature and feature switches; and whole setups can be saved as **presets**. Everything lives in the native card under **Settings → Plugins → Plugin configuration** (collapsed sections with summaries) and applies immediately.
5
+ **Font plugin for DeepSeek Harness (DSH)**: the **conversation** (markdown prose) gets full typographic control — font, size, line height, weight; the **interface** (settings sheet, sidebars, workspace, buttons) takes a font and a weight and **follows the conversation by default**; **code** stays its own axis with ligature and feature switches; and whole setups can be saved as **presets**. Everything lives in this plugin's native card (collapsed sections with summaries) and applies immediately: on the 0.2 line it is on the Plugins page under **Installed** (open this plugin's card), on the rc line under **Settings → Plugins → Plugin configuration**.
6
6
 
7
7
  ## Features
8
8
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  | Plugin version | Supported DSH versions |
30
30
  | --- | --- |
31
- | **0.4.x** (latest, 0.4.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 |
31
+ | **0.4.x** (latest, 0.4.1) | 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 |
32
32
  | 0.3.x (0.3.6) | 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 |
33
33
  | 0.2.x (0.2.8) | 0.1.5-rc.3, 0.1.7-alpha.1, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2 |
34
34
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > [English](README.en.md) | **中文**
4
4
 
5
- **DeepSeek Harness(DSH)字体插件**:**对话**(会话 Markdown 正文)拥有完整的排印控制——字体族、字号、行高、字重;**界面**(设置页、侧栏、工作区、按钮)提供字体与字重并**默认跟随对话**;**代码**独立成轴,另有连字与特性开关;整套配置可存为**预设方案**。配置入口是 **设置 → 插件 → 插件配置** 里的原生卡片(分区收起 + 摘要),改完即时生效。
5
+ **DeepSeek Harness(DSH)字体插件**:**对话**(会话 Markdown 正文)拥有完整的排印控制——字体族、字号、行高、字重;**界面**(设置页、侧栏、工作区、按钮)提供字体与字重并**默认跟随对话**;**代码**独立成轴,另有连字与特性开关;整套配置可存为**预设方案**。配置入口是**原生卡片**(分区收起 + 摘要),改完即时生效:0.2 线在「插件」页的**已安装**栏目里(点开本插件的卡片),rc 线在 设置 → 插件 → 插件配置。
6
6
 
7
7
  ## 功能
8
8
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  | 插件版本 | 支持的 DSH 版本 |
30
30
  | --- | --- |
31
- | **0.4.x**(最新,0.4.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 |
31
+ | **0.4.x**(最新,0.4.1) | 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 |
32
32
  | 0.3.x(0.3.6) | 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 |
33
33
  | 0.2.x(0.2.8) | 0.1.5-rc.3、0.1.7-alpha.1、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2 |
34
34
 
package/lib/client.js CHANGED
@@ -743,48 +743,6 @@ function isPluginEntry(node) {
743
743
  /** The package name the host's own navigation service opens for this plugin. */
744
744
  var PLUGIN_BUNDLE_NAME = "dsh-fonttune";
745
745
 
746
- /** The main panel id the Plugins page lives in (what `layout.selectPanel` takes). */
747
- var PLUGIN_PANEL_ID = "plugins";
748
-
749
- /**
750
- * Whether a node sits inside this plugin's own floating panel.
751
- *
752
- * The panel can carry the plugin's own name, and a lookup that mistakes it for
753
- * a host entry would click ourselves.
754
- * @param {object} node - the candidate element.
755
- * @returns {boolean} true when the node is ours, not the host's.
756
- */
757
- function isOwnPanelNode(node) {
758
- if (node === null || node === undefined) return true;
759
- try {
760
- return typeof node.closest === "function" && node.closest(".dfp-floatHost") !== null;
761
- } catch (error) {
762
- return false;
763
- }
764
- }
765
-
766
- /**
767
- * THIS plugin's own card on the host's Plugins page, or null.
768
- *
769
- * The Plugins page publishes every entry as `data-plugin-item="<id>"` and this
770
- * plugin's registration id is `fonttune`: the attribute is host-published data,
771
- * so the lookup survives a language change and can never match a sentence that
772
- * merely names the plugin.
773
- * @param {object} doc - the document to search (a stub is fine).
774
- * @returns {object|null} the card element, or null.
775
- */
776
- function pluginEntryCard(doc) {
777
- if (doc === null || doc === undefined || typeof doc.querySelector !== "function") return null;
778
- var card = null;
779
- try {
780
- card = doc.querySelector("[" + PLUGIN_ITEM_ATTR + '="' + PLUGIN_ITEM_VALUE + '"]');
781
- } catch (error) {
782
- return null;
783
- }
784
- if (card === null || card === undefined) return null;
785
- return isOwnPanelNode(card) ? null : card;
786
- }
787
-
788
746
  /**
789
747
  * The element that actually opens a plugin's page from its entry card.
790
748
  *
@@ -4247,8 +4205,6 @@ var shared = {
4247
4205
  PLUGIN_ITEM_ATTR: PLUGIN_ITEM_ATTR,
4248
4206
  PLUGIN_ITEM_VALUE: PLUGIN_ITEM_VALUE,
4249
4207
  PLUGIN_BUNDLE_NAME: PLUGIN_BUNDLE_NAME,
4250
- PLUGIN_PANEL_ID: PLUGIN_PANEL_ID,
4251
- pluginEntryCard: pluginEntryCard,
4252
4208
  pluginClickTarget: pluginClickTarget,
4253
4209
  // The client's own click step needs this: the bundle keeps this module's scope
4254
4210
  // separate from the client's, so a bare name here is a ReferenceError there.
@@ -5558,18 +5514,103 @@ function writeSingleShot(scope, presetList, activeName, changes, options) {
5558
5514
  paintLocalMove(field, changes[field]);
5559
5515
  }
5560
5516
  if (fields.length === 0) return;
5517
+ // The weight fields live on an OFFSET scale; a document written before that
5518
+ // scale stores absolute weights. The card adopts the scale on its first
5519
+ // weight write (converted values first, the marker last) and every writer has
5520
+ // to do the same: without the marker a written offset reads back as one of
5521
+ // those absolute weights and is converted away — the "+2 falls straight
5522
+ // back" a fresh document showed. The marker is painted locally too, so the
5523
+ // rows and the page agree with the write from the first frame.
5524
+ var raw = {};
5525
+ try {
5526
+ var snap = scope && typeof scope.getSnapshot === "function" ? scope.getSnapshot() : null;
5527
+ raw = snap !== null && snap !== undefined && snap.value !== null && typeof snap.value === "object" ? snap.value : {};
5528
+ } catch (error) {
5529
+ raw = {};
5530
+ }
5531
+ var touchesWeight = false;
5532
+ for (var weightIndex = 0; weightIndex < shared.WEIGHT_FIELDS.length; weightIndex += 1) {
5533
+ if (fields.indexOf(shared.WEIGHT_FIELDS[weightIndex]) >= 0) {
5534
+ touchesWeight = true;
5535
+ break;
5536
+ }
5537
+ }
5538
+ var adopting = touchesWeight && raw[WEIGHT_OFFSETS_FIELD] !== true;
5539
+ // The preset snapshot is decided UP FRONT: it tells the adoption below
5540
+ // whether it still has to convert the snapshots itself, and it is the one op
5541
+ // a batch can be refused over.
5542
+ var mirrorOp = null;
5543
+ if (settings.mirror !== false) {
5544
+ var snapshot = shared.mirrorPresetSnapshot(presetList, activeName, changes);
5545
+ if (snapshot !== null) mirrorOp = { op: snapshot.op, path: snapshot.path, value: snapshot.value };
5546
+ }
5561
5547
  var ops = [];
5548
+ if (adopting) {
5549
+ for (var adoptIndex = 0; adoptIndex < shared.WEIGHT_FIELDS.length; adoptIndex += 1) {
5550
+ var adoptName = shared.WEIGHT_FIELDS[adoptIndex];
5551
+ if (fields.indexOf(adoptName) >= 0) continue;
5552
+ var storedWeight = Number(raw[adoptName]);
5553
+ if (!isFinite(storedWeight) || storedWeight < shared.WEIGHT_MIN || storedWeight > shared.WEIGHT_MAX) continue;
5554
+ var convertedWeight = storedWeight - shared.WEIGHT_BASE;
5555
+ paintLocalMove(adoptName, convertedWeight);
5556
+ ops.push({ op: shared.PENDING_SET, path: [adoptName], value: convertedWeight });
5557
+ }
5558
+ paintLocalMove(WEIGHT_OFFSETS_FIELD, true);
5559
+ // The snapshots are converted from their STORED form, like the card does:
5560
+ // the normalized list is already converted and would report no change. A
5561
+ // mirrored write carries that converted list anyway; with no mirror to do
5562
+ // it, this is the only chance to keep the snapshots on the new scale.
5563
+ if (mirrorOp === null) {
5564
+ var rawPresets = raw[PRESETS_FIELD];
5565
+ if (typeof rawPresets === "string" && rawPresets.trim() !== "") {
5566
+ var parsedPresets = null;
5567
+ try {
5568
+ parsedPresets = JSON.parse(rawPresets);
5569
+ } catch (parseError) {
5570
+ parsedPresets = null;
5571
+ }
5572
+ var migratedPresets = shared.migratePresetWeights(parsedPresets);
5573
+ if (migratedPresets.changed) {
5574
+ ops.push({ op: shared.PENDING_SET, path: [PRESETS_FIELD], value: JSON.stringify(migratedPresets.presets) });
5575
+ }
5576
+ }
5577
+ }
5578
+ }
5562
5579
  for (var index = 0; index < fields.length; index += 1) {
5563
5580
  var name = fields[index];
5564
5581
  if (changes[name] === undefined) ops.push({ op: shared.PENDING_UNSET, path: [name] });
5565
5582
  else ops.push({ op: shared.PENDING_SET, path: [name], value: changes[name] });
5566
5583
  }
5567
- if (settings.mirror !== false) {
5568
- var snapshot = shared.mirrorPresetSnapshot(presetList, activeName, changes);
5569
- if (snapshot !== null) ops.push({ op: snapshot.op, path: snapshot.path, value: snapshot.value });
5584
+ if (mirrorOp !== null) ops.push(mirrorOp);
5585
+ if (adopting) {
5586
+ // Marker last: a marked document is never left holding an unconverted
5587
+ // absolute weight, and a stylesheet rebuild between the ops still renders
5588
+ // exactly as before.
5589
+ ops.push({ op: shared.PENDING_SET, path: [WEIGHT_OFFSETS_FIELD], value: true });
5570
5590
  }
5591
+ var retried = false;
5571
5592
  var fail = function (message) {
5593
+ // The axis write is the product; the preset snapshot is a nicety. A batch
5594
+ // the host refuses wholesale gets exactly ONE more try without the
5595
+ // snapshot: landing the value beats keeping the mirror. A second refusal
5596
+ // has nothing but the value in it, so it is the value the host declined.
5597
+ if (!retried && mirrorOp !== null) {
5598
+ retried = true;
5599
+ var lean = ops.filter(function (op) {
5600
+ return op !== mirrorOp;
5601
+ });
5602
+ probeLog("write-retry-without-mirror", { fields: fields.slice() });
5603
+ send(lean);
5604
+ return;
5605
+ }
5606
+ probeLog("write-failed", { message: message, fields: fields.slice(), adopting: adopting === true });
5572
5607
  for (var at = 0; at < fields.length; at += 1) clearLocalMove(fields[at]);
5608
+ if (adopting) {
5609
+ for (var unadopt = 0; unadopt < shared.WEIGHT_FIELDS.length; unadopt += 1) {
5610
+ clearLocalMove(shared.WEIGHT_FIELDS[unadopt]);
5611
+ }
5612
+ clearLocalMove(WEIGHT_OFFSETS_FIELD);
5613
+ }
5573
5614
  if (typeof settings.onError === "function") settings.onError(message);
5574
5615
  };
5575
5616
  var verdict = function (applied, error) {
@@ -5588,13 +5629,57 @@ function writeSingleShot(scope, presetList, activeName, changes, options) {
5588
5629
  return op.path[0] + "=" + String(op.value).slice(0, 24);
5589
5630
  }),
5590
5631
  });
5591
- try {
5592
- if (scope && typeof scope.mutate === "function" && (typeof scope.canMutate !== "function" || scope.canMutate())) {
5593
- var batch = scope.mutate(ops);
5594
- if (batch && typeof batch.then === "function") {
5595
- batch.then(
5632
+ /**
5633
+ * Hand one batch to the host: the batch call where there is one, and one
5634
+ * field per call where there is not.
5635
+ *
5636
+ * @param {object[]} batch - the ops to land, in order.
5637
+ * @returns {void}
5638
+ */
5639
+ var send = function (batch) {
5640
+ try {
5641
+ if (scope && typeof scope.mutate === "function" && (typeof scope.canMutate !== "function" || scope.canMutate())) {
5642
+ var call = scope.mutate(batch);
5643
+ if (call && typeof call.then === "function") {
5644
+ call.then(
5645
+ function (accepted) {
5646
+ verdict(accepted !== false, accepted);
5647
+ },
5648
+ function (error) {
5649
+ verdict(false, error);
5650
+ }
5651
+ );
5652
+ return;
5653
+ }
5654
+ verdict(call !== false, call);
5655
+ return;
5656
+ }
5657
+ } catch (error) {
5658
+ fail(error && error.message ? String(error.message).slice(0, 160) : String(error).slice(0, 160));
5659
+ return;
5660
+ }
5661
+ // A host without the batch call takes one field per call; the last verdict
5662
+ // is the write's.
5663
+ var at = 0;
5664
+ var step = function () {
5665
+ if (at >= batch.length) return;
5666
+ var op = batch[at];
5667
+ at += 1;
5668
+ var single = null;
5669
+ try {
5670
+ single =
5671
+ op.op === shared.PENDING_UNSET
5672
+ ? scope.unset(op.path[0])
5673
+ : scope.set(op.path[0], op.value);
5674
+ } catch (error) {
5675
+ fail(error && error.message ? String(error.message).slice(0, 160) : String(error).slice(0, 160));
5676
+ return;
5677
+ }
5678
+ if (single && typeof single.then === "function") {
5679
+ single.then(
5596
5680
  function (accepted) {
5597
- verdict(accepted !== false, accepted);
5681
+ if (accepted === false) verdict(false, accepted);
5682
+ else step();
5598
5683
  },
5599
5684
  function (error) {
5600
5685
  verdict(false, error);
@@ -5602,45 +5687,11 @@ function writeSingleShot(scope, presetList, activeName, changes, options) {
5602
5687
  );
5603
5688
  return;
5604
5689
  }
5605
- verdict(batch !== false, batch);
5606
- return;
5607
- }
5608
- } catch (error) {
5609
- fail(error && error.message ? String(error.message).slice(0, 160) : String(error).slice(0, 160));
5610
- return;
5611
- }
5612
- // A host without the batch call takes one field per call; the last verdict
5613
- // is the write's.
5614
- var at = 0;
5615
- var step = function () {
5616
- if (at >= ops.length) return;
5617
- var op = ops[at];
5618
- at += 1;
5619
- var single = null;
5620
- try {
5621
- single =
5622
- op.op === shared.PENDING_UNSET
5623
- ? scope.unset(op.path[0])
5624
- : scope.set(op.path[0], op.value);
5625
- } catch (error) {
5626
- fail(error && error.message ? String(error.message).slice(0, 160) : String(error).slice(0, 160));
5627
- return;
5628
- }
5629
- if (single && typeof single.then === "function") {
5630
- single.then(
5631
- function (accepted) {
5632
- if (accepted === false) verdict(false, accepted);
5633
- else step();
5634
- },
5635
- function (error) {
5636
- verdict(false, error);
5637
- }
5638
- );
5639
- return;
5640
- }
5690
+ step();
5691
+ };
5641
5692
  step();
5642
5693
  };
5643
- step();
5694
+ send(ops);
5644
5695
  }
5645
5696
 
5646
5697
  /**
@@ -7217,32 +7268,6 @@ function SwitchField(props) {
7217
7268
  * the card
7218
7269
  * ------------------------------------------------------------------ */
7219
7270
 
7220
- /**
7221
- * The card as DSH 0.1.7-alpha.x's Plugins page renders it.
7222
- *
7223
- * That page contributes one entry per plugin (`plugins.item`) and asks that one
7224
- * component for two different views: a one-line `summary` for the card in the
7225
- * list, and the `page` body once the entry is opened. The summary is answered
7226
- * with the card's own description, and every other view is the card itself, so
7227
- * the same UI serves the rc line's configuration cell and the alpha's plugin
7228
- * page without a second implementation.
7229
- * @param {object} props - the view asked for plus the injected face.
7230
- * @returns {object|string} the card, or the one-line summary.
7231
- */
7232
- function FontCardPage(props) {
7233
- var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
7234
- if (props.view === "summary") return t("card.description");
7235
- // Every other view is the card as the content of a section the reader has
7236
- // already opened, so it starts open: the row/entry they clicked IS the
7237
- // expansion gesture, and asking for a second click on the card's own header
7238
- // was the one thing left to remove on the official plugin page.
7239
- return h(FontCard, {
7240
- scope: props.scope || cardScope || MEMORY_SCOPE,
7241
- t: t,
7242
- defaultOpen: props.view !== "row",
7243
- });
7244
- }
7245
-
7246
7271
  /**
7247
7272
  * Render the plugin's card: the settings section dispatches this component
7248
7273
  * under the namespace key, and the host has to serve that namespace for it to
@@ -7265,13 +7290,11 @@ function FontCard(props) {
7265
7290
  // values `apply` bound are the fallback.
7266
7291
  var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
7267
7292
  var scope = props.scope || cardScope || MEMORY_SCOPE;
7268
- // The official plugin page (0.1.7: `view: "page"`) and the rc line's
7269
- // configuration cell (which passes no props at all) both render this card as
7270
- // the whole body of a section the reader has ALREADY opened — the host's own
7271
- // row is the expansion gesture there, so starting collapsed asked for a
7272
- // second click. The card therefore starts open unless the seat says
7273
- // otherwise (`defaultOpen: false`), and `FontCardPage` answers a one-line
7274
- // summary request with its description instead of the card.
7293
+ // The per-package page on the Plugins list's INSTALLED side and the rc line's
7294
+ // configuration cell both render this card as the whole body of a section the
7295
+ // reader has ALREADY opened — the host's own row is the expansion gesture
7296
+ // there, so starting collapsed asked for a second click. The card therefore
7297
+ // starts open unless the seat says otherwise (`defaultOpen: false`).
7275
7298
  var [open, setOpen] = useState(props.defaultOpen !== false);
7276
7299
  var [view, setView] = useState(readViewMode);
7277
7300
  var [expanded, setExpanded] = useState(null);
@@ -9430,115 +9453,38 @@ function mountFloatPanel(scope, t, hostCtx) {
9430
9453
  }
9431
9454
  };
9432
9455
 
9433
- var ENTRY_CLICK_TRIES = 12;
9434
- var ENTRY_CLICK_DELAY_MS = 150;
9435
-
9436
- /**
9437
- * Click THIS plugin's entry on the Plugins page once the page has mounted it.
9438
- *
9439
- * The click lands on the entry card's title button, which is the host's own
9440
- * open gesture; the card itself is a list item with no handler.
9441
- * @param {string} tag - a short tag for the diagnostic line.
9442
- * @param {Function} done - settled with whether the click landed.
9443
- */
9444
- var clickPluginEntry = function (tag, done) {
9445
- var tries = 0;
9446
- var step = function () {
9447
- tries += 1;
9448
- var target = shared.pluginClickTarget(shared.pluginEntryCard(document));
9449
- if (target !== null) {
9450
- var landed = shared.dispatchSynthetic(target, "click");
9451
- probeLog("settings-open", { reason: tag, kind: "panel-entry", taken: landed === true });
9452
- done(landed === true);
9453
- return;
9454
- }
9455
- if (tries >= ENTRY_CLICK_TRIES) {
9456
- probeLog("settings-open", { reason: tag, kind: "panel-entry", taken: false });
9457
- done(false);
9458
- return;
9459
- }
9460
- globalThis.setTimeout(step, ENTRY_CLICK_DELAY_MS);
9461
- };
9462
- step();
9463
- };
9464
-
9465
9456
  /**
9466
9457
  * Reach this plugin's own configuration page through the HOST's navigation.
9467
9458
  *
9468
- * The Plugins page is a main panel the layout service selects by id, and the
9469
- * plugin manager publishes `pluginNavigation` for cross-plugin jumps straight
9470
- * to a package's own page — the same service a shipped DSH feature calls for
9471
- * exactly this gesture. Both are looked up WITHOUT being declared (see
9459
+ * The plugin manager publishes `pluginNavigation` for cross-plugin jumps
9460
+ * straight to a package's own page — the per-package page on the INSTALLED
9461
+ * side of the Plugins list, which carries this plugin's whole card. It is the
9462
+ * same service a shipped DSH feature calls for exactly this gesture, and it
9463
+ * comes before any DOM lookup: it cannot mistake text in a conversation for a
9464
+ * navigation entry. The lookup does not DEPEND on the service (see
9472
9465
  * `getService`): a required dependency would park this whole package on a
9473
- * host line that ships neither.
9474
- *
9475
- * Order: open the Plugins panel and click this plugin's entry (its own page,
9476
- * titled with its name); failing the click, the navigation service's package
9477
- * page; failing both services, the caller keeps the legacy DOM chain.
9466
+ * host line that ships none. Failing it, the caller keeps the legacy DOM
9467
+ * chain.
9478
9468
  * @param {string} tag - a short tag for the diagnostic line.
9479
- * @returns {boolean} whether a host route was taken (the entry click settles later).
9469
+ * @returns {boolean} whether a host route was taken.
9480
9470
  */
9481
9471
  var openPluginPage = function (tag) {
9482
9472
  var nav = serviceOf("pluginNavigation");
9483
- var layout = serviceOf("layout");
9484
9473
  if (globalThis.__DFP_PROBE__ === true) {
9485
- var raw = function (name) {
9486
- var out = {};
9487
- try {
9488
- var viaGet = serviceOf(name);
9489
- out.get = viaGet === null || viaGet === undefined ? String(viaGet) : typeof viaGet;
9490
- } catch (error) {
9491
- out.get = "THREW " + String(error && error.message ? error.message : error).slice(0, 80);
9492
- }
9493
- try {
9494
- var direct = hostCtx === null || hostCtx === undefined ? undefined : hostCtx[name];
9495
- out.direct = direct === null || direct === undefined ? String(direct) : typeof direct;
9496
- } catch (error) {
9497
- out.direct = "THREW " + String(error && error.message ? error.message : error).slice(0, 80);
9498
- }
9499
- return out;
9500
- };
9501
9474
  probeLog("settings-routes", {
9502
- nav: raw("pluginNavigation"),
9503
- layout: raw("layout"),
9504
- slots: raw("slots"),
9505
- canNav: nav !== null && nav !== undefined && typeof nav.openBundle === "function",
9506
- canLayout: layout !== null && layout !== undefined && typeof layout.selectPanel === "function",
9475
+ nav: nav === null || nav === undefined ? String(nav) : typeof nav,
9476
+ canBundle: nav !== null && nav !== undefined && typeof nav.openBundle === "function",
9507
9477
  });
9508
9478
  }
9509
- var canBundle = nav !== null && nav !== undefined && typeof nav.openBundle === "function";
9510
- var canPanel = layout !== null && layout !== undefined && typeof layout.selectPanel === "function";
9511
- if (canPanel) {
9512
- try {
9513
- layout.selectPanel(shared.PLUGIN_PANEL_ID);
9514
- } catch (error) {
9515
- canPanel = false;
9516
- }
9517
- }
9518
- if (canPanel) {
9519
- clickPluginEntry(tag, function (clicked) {
9520
- if (clicked || !canBundle) return;
9521
- try {
9522
- nav.openBundle(shared.PLUGIN_BUNDLE_NAME);
9523
- probeLog("settings-open", { reason: tag, kind: "plugin-navigation", taken: true });
9524
- } catch (error) {
9525
- warnSettingsMissing(tag, "the plugin navigation service refused the package");
9526
- }
9527
- });
9528
- probeLog("settings-open", { reason: tag, kind: "panel-select", taken: true });
9479
+ if (nav === null || nav === undefined || typeof nav.openBundle !== "function") return false;
9480
+ try {
9481
+ nav.openBundle(shared.PLUGIN_BUNDLE_NAME);
9482
+ probeLog("settings-open", { reason: tag, kind: "plugin-navigation", taken: true });
9529
9483
  return true;
9484
+ } catch (error) {
9485
+ warnSettingsMissing(tag, "the plugin navigation service refused the package");
9486
+ return false;
9530
9487
  }
9531
- if (canBundle) {
9532
- try {
9533
- nav.openBundle(shared.PLUGIN_BUNDLE_NAME);
9534
- probeLog("settings-open", { reason: tag, kind: "plugin-navigation", taken: true });
9535
- return true;
9536
- } catch (error) {
9537
- warnSettingsMissing(tag, "the plugin navigation service refused the package");
9538
- return false;
9539
- }
9540
- }
9541
- return false;
9542
9488
  };
9543
9489
 
9544
9490
  /**
@@ -9818,12 +9764,12 @@ function mountFloatPanel(scope, t, hostCtx) {
9818
9764
  : DEFAULT_PRESET_NAMES.map(function (name) {
9819
9765
  return { name: name, values: {}, savedAt: 0 };
9820
9766
  });
9767
+ // No selection means NO auto-save, exactly like the card: a preset is only
9768
+ // written into when the reader picked one. Falling back to the first
9769
+ // built-in here made every panel tweak write the WHOLE preset list as well
9770
+ // — a second op in the same batch that a document with no presets has no
9771
+ // business receiving, and one more thing a host can refuse the batch over.
9821
9772
  var activeName = config[ACTIVE_PRESET_FIELD];
9822
- var known = false;
9823
- for (var index = 0; index < list.length; index += 1) {
9824
- if (list[index].name === activeName) known = true;
9825
- }
9826
- if (!known) activeName = list[0].name;
9827
9773
  return { config: config, presetList: list, activeName: activeName, writable: snap.writable === true };
9828
9774
  };
9829
9775
 
@@ -10573,11 +10519,11 @@ function mountFloatPanel(scope, t, hostCtx) {
10573
10519
  render();
10574
10520
  };
10575
10521
 
10576
- var sliderRow = function (body, labelText, valueText, min, max, step, value, onRelease) {
10522
+ var sliderRow = function (body, labelText, format, min, max, step, value, onRelease) {
10577
10523
  var row = el("div", "dfp-floatRow");
10578
10524
  var label = el("div", "dfp-floatLabel");
10579
10525
  label.appendChild(el("span", null, labelText));
10580
- var shown = el("span", "dfp-floatValue", valueText);
10526
+ var shown = el("span", "dfp-floatValue", format(value));
10581
10527
  label.appendChild(shown);
10582
10528
  row.appendChild(label);
10583
10529
  var input = el("input", "dfp-floatSlider");
@@ -10588,7 +10534,9 @@ function mountFloatPanel(scope, t, hostCtx) {
10588
10534
  input.value = String(value);
10589
10535
  input.setAttribute("aria-label", labelText);
10590
10536
  input.addEventListener("input", function () {
10591
- shown.textContent = input.value;
10537
+ // The resting formatter, every time: a raw number here is what made the
10538
+ // unit blink away the moment the slider moved.
10539
+ shown.textContent = format(Number(input.value));
10592
10540
  });
10593
10541
  input.addEventListener("change", function () {
10594
10542
  onRelease(Number(input.value));
@@ -11031,7 +10979,9 @@ function mountFloatPanel(scope, t, hostCtx) {
11031
10979
  sliderRow(
11032
10980
  card,
11033
10981
  t("size.dialogLabel"),
11034
- (size > 0 ? "+" + size : String(size)) + " " + t("size.unit"),
10982
+ function (next) {
10983
+ return (next > 0 ? "+" + next : String(next)) + " " + t("size.unit");
10984
+ },
11035
10985
  SIZE_MIN,
11036
10986
  SIZE_MAX,
11037
10987
  1,
@@ -11045,7 +10995,9 @@ function mountFloatPanel(scope, t, hostCtx) {
11045
10995
  sliderRow(
11046
10996
  card,
11047
10997
  t("line.dialogLabel"),
11048
- line + " " + t("line.unit"),
10998
+ function (next) {
10999
+ return String(next) + " " + t("line.unit");
11000
+ },
11049
11001
  LINE_HEIGHT_MIN,
11050
11002
  LINE_HEIGHT_MAX,
11051
11003
  5,
@@ -11818,18 +11770,19 @@ function apply(ctx) {
11818
11770
  * Offer the card in one slot seat.
11819
11771
  *
11820
11772
  * Which seat a host declares is a line difference, not a preference, and the
11821
- * three are not alternatives to pick from — each is simply inert where a host
11773
+ * two are not alternatives to pick from — each is simply inert where a host
11822
11774
  * never declares it:
11823
11775
  *
11824
11776
  * * `settings.plugin.item` — up to 0.1.5-rc.x, a keyed cell under
11825
11777
  * Settings → Plugins → Plugin configuration (keyed by the namespace);
11826
- * * `plugins.item` — 0.1.7-alpha.x's own entry in the Plugins page's
11827
- * official list, which is how EVERY plugin page is contributed there (the
11828
- * built-in settings pages do the same), and the seat that needs no
11829
- * installed package behind it;
11830
- * * `plugins.bundle.config` — the alpha's per-package page, keyed by the npm
11778
+ * * `plugins.bundle.config` — the per-package page, keyed by the npm
11831
11779
  * package name, for a profile that installed this plugin as a bundle.
11832
11780
  *
11781
+ * The `plugins.item` entry is deliberately NOT offered: it renders in the
11782
+ * Plugins page's OFFICIAL list, and this is not an official plugin. Its page
11783
+ * belongs on the installed side, where the per-package seat above already
11784
+ * carries the whole card.
11785
+ *
11833
11786
  * @param {string} seat - the slot name.
11834
11787
  * @param {object} options - the registration options that seat looks up.
11835
11788
  * @param {Function} [component] - the component to register; the card itself
@@ -11855,17 +11808,6 @@ function apply(ctx) {
11855
11808
  if (scope !== MEMORY_SCOPE) {
11856
11809
  offerCard("settings.plugin.item", { key: NAMESPACE });
11857
11810
  offerCard("plugins.bundle.config", { key: "dsh-fonttune" });
11858
- offerCard(
11859
- "plugins.item",
11860
- {
11861
- id: "fonttune",
11862
- order: 40,
11863
- label: function () {
11864
- return t("card.title");
11865
- },
11866
- },
11867
- FontCardPage
11868
- );
11869
11811
  }
11870
11812
  }
11871
11813
 
package/lib/shared.cjs CHANGED
@@ -734,48 +734,6 @@ function isPluginEntry(node) {
734
734
  /** The package name the host's own navigation service opens for this plugin. */
735
735
  var PLUGIN_BUNDLE_NAME = "dsh-fonttune";
736
736
 
737
- /** The main panel id the Plugins page lives in (what `layout.selectPanel` takes). */
738
- var PLUGIN_PANEL_ID = "plugins";
739
-
740
- /**
741
- * Whether a node sits inside this plugin's own floating panel.
742
- *
743
- * The panel can carry the plugin's own name, and a lookup that mistakes it for
744
- * a host entry would click ourselves.
745
- * @param {object} node - the candidate element.
746
- * @returns {boolean} true when the node is ours, not the host's.
747
- */
748
- function isOwnPanelNode(node) {
749
- if (node === null || node === undefined) return true;
750
- try {
751
- return typeof node.closest === "function" && node.closest(".dfp-floatHost") !== null;
752
- } catch (error) {
753
- return false;
754
- }
755
- }
756
-
757
- /**
758
- * THIS plugin's own card on the host's Plugins page, or null.
759
- *
760
- * The Plugins page publishes every entry as `data-plugin-item="<id>"` and this
761
- * plugin's registration id is `fonttune`: the attribute is host-published data,
762
- * so the lookup survives a language change and can never match a sentence that
763
- * merely names the plugin.
764
- * @param {object} doc - the document to search (a stub is fine).
765
- * @returns {object|null} the card element, or null.
766
- */
767
- function pluginEntryCard(doc) {
768
- if (doc === null || doc === undefined || typeof doc.querySelector !== "function") return null;
769
- var card = null;
770
- try {
771
- card = doc.querySelector("[" + PLUGIN_ITEM_ATTR + '="' + PLUGIN_ITEM_VALUE + '"]');
772
- } catch (error) {
773
- return null;
774
- }
775
- if (card === null || card === undefined) return null;
776
- return isOwnPanelNode(card) ? null : card;
777
- }
778
-
779
737
  /**
780
738
  * The element that actually opens a plugin's page from its entry card.
781
739
  *
@@ -4238,8 +4196,6 @@ var shared = {
4238
4196
  PLUGIN_ITEM_ATTR: PLUGIN_ITEM_ATTR,
4239
4197
  PLUGIN_ITEM_VALUE: PLUGIN_ITEM_VALUE,
4240
4198
  PLUGIN_BUNDLE_NAME: PLUGIN_BUNDLE_NAME,
4241
- PLUGIN_PANEL_ID: PLUGIN_PANEL_ID,
4242
- pluginEntryCard: pluginEntryCard,
4243
4199
  pluginClickTarget: pluginClickTarget,
4244
4200
  // The client's own click step needs this: the bundle keeps this module's scope
4245
4201
  // separate from the client's, so a bare name here is a ReferenceError there.
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Font tune (dsh-fonttune)",
4
+ "description": "Interface and conversation fonts, a size, a weight and a line height for each, code extras and presets"
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "字体增强(dsh-fonttune)",
4
+ "description": "界面与对话字体、各自的字号/字重/行高,代码增强与预设方案"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "dsh-fonttune",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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
  "main": "lib/index.js",
7
7
  "exports": {
8
8
  ".": "./lib/index.js",
9
9
  "./client": "./lib/client.js",
10
+ "./locale/*": "./locale/*",
10
11
  "./package.json": "./package.json"
11
12
  },
12
13
  "files": [
13
14
  "lib",
15
+ "locale",
14
16
  "cordis.patch.yml",
15
17
  "README.md",
16
18
  "README.en.md",
@@ -35,7 +37,9 @@
35
37
  "0.1.7-rc.1": "compatible",
36
38
  "0.1.7-rc.2": "compatible",
37
39
  "0.2.0-rc.1": "compatible",
38
- "0.2.0-rc.2": "compatible"
40
+ "0.2.0-rc.2": "compatible",
41
+ "0.2.1-alpha.1": "compatible",
42
+ "0.2.1-alpha.2": "compatible"
39
43
  }
40
44
  }
41
45
  },