dsh-fonttune 0.2.3 → 0.2.4
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 +22 -0
- package/CHANGELOG.md +22 -0
- package/README.en.md +16 -4
- package/README.md +16 -4
- package/lib/client.js +431 -28
- package/lib/index.js +95 -11
- package/package.json +10 -6
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to **dsh-fonttune** are documented here. Chinese version: [CHANGELOG.md](CHANGELOG.md).
|
|
4
4
|
|
|
5
|
+
## [0.2.4] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **One package installs on both host lines**: `0.1.5-rc.1` – `rc.3` (the host's `settings.installSection` plus the client's `settingsScope`) and `0.1.7-alpha.1` – `alpha.2` (the host's `settings.configure({auto:true}, ctx.fiber)` plus the client's `configForms`). The plugin looks at which dialect the running host offers, and **when neither is there it only applies its styles and registers no card** rather than parking the whole package. `package.json` now declares `engines.dsh` as the semver prerelease range `>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0` (a plain `>=0.1.5-rc.1` never matches `0.1.7-alpha.1`), and `dsh.compatibility.dshReleases` lists the five host lines that were actually exercised.
|
|
9
|
+
- **The card can appear in any of the three seats the two lines declare**, and the host picks which to render: the rc line's `settings.plugin.item` (claimed by settings namespace), the alpha's `plugins.item` (an entry on the Plugins page — `id: fonttune`, label from the dictionary's `card.title`, summary from `card.description`, the whole card only on its detail page, which is **how DSH's own shell / agent-loop / subagent / web-search register themselves**), and `plugins.bundle.config` for an installed profile package (claimed by the npm name `dsh-fonttune`, and rendered on that package's detail page).
|
|
10
|
+
- **Claiming the settings namespace now waits, then matches by signature**: the alpha's `configForms.describe()` is asynchronous, and the old code guessed a candidate by name before it answered, so the guess could land on somebody else's namespace and every write was refused by the host (on a real machine: the card opens, and the first edit reports "The setting was not saved"). With no candidate the scope is now `null` and a pending stand-in (the whole `getSnapshot`/`subscribe`/`set`/`unset` surface) is adopted once the describe view arrives, recognized by a **schema that carries both `uiFollowsDialog` and `stackDialog`** (real log: `served namespaces []` → `matched by served schema: fonttune`).
|
|
11
|
+
- **The host side unwraps volatile references**: on the alpha every field of `ctx.fiber.config` is a cosmokit volatile reference (`{ get(), [Symbol.for("cosmokit.volatile.write")](v) }`, frozen), and reading it as a plain object yields nothing — on a real machine the card's **first render was a column of empty controls and the injected stylesheet was 0 bytes**. `plainConfigValue()` now unwraps it deeply, so `styleRow()` sees an ordinary object (measured: a 973 B rule set with `Georgia` / `520`).
|
|
12
|
+
- **`inject` narrowed to the services both lines have** (`slots` / `locale`): declaring one that a line does not have (say the rc-only `settingsScope`) **parks the whole package** (`pending (waiting for service: …)`) and the web never boots. The settings scope is looked up at runtime with `ctx.get()`, and a missing host layer degrades to browser-local storage.
|
|
13
|
+
- **Both shapes of a write result are understood**: the rc line's `mutate` resolves `undefined` even when it failed, while the alpha's `set`/`unset` resolve a boolean (`false` = the host refused). `followWrite` now reports a synchronous `false`, a thenable resolving `false`, and a rejection in that same note beside the preset row.
|
|
14
|
+
- **`test/host-line-probe.mjs`, a live probe that is independent of the host line.** It asserts only what must hold on every line — the page boots without errors, the plugin owns exactly **one** stylesheet element and it is connected, the settings really reach the document (with `DFP_EXPECT_FAMILY` / `DFP_EXPECT_WEIGHT` set it also asserts the computed values), the configuration card is reachable from the settings sheet and renders (`.dfp-card`, whichever seat that line declares) and a control on it really writes through to the host. It joined the read-only group of `npm run verify:browser`. `DFP_PROBE=1` makes the plugin log its diagnostics (which namespaces are served, which one was claimed, what state the seat is in); `DFP_DUMP=1` makes the probe print the Plugins page grouped as the host renders it.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **No more `require` of host internals**: the alpha exports `IconPlusOutlineRegular` / `IconPlusOutlineMedium` and not the rc's `IconPlusOutline16`, so the `primitives` require is gone and the plus icon needed here is an inline SVG.
|
|
18
|
+
- **What rc.3 actually changed, measured** (so a later release can decide whether it needs a branch): hashing all 728 js/css files under `lib/` + `dist/` across rc.2 and rc.3 leaves **exactly 2 files different** — `@deepseek-ai/cosmokit/lib/index.js` (13336 → 17160 B, gaining the volatile primitives `createVolatile` / `isVolatile` / `volatileEntries` / `updateVolatile` and `Symbol.for("cosmokit.volatile.write")`) and `dsh-client-ui-sidebar/lib/client.js` (a build stamp only, `0.1.5-rc.2-449af19` → `0.1.5-rc.3-a5380f3`). The internal `@deepseek-ai/*` dependencies moved to `^0.1.5-rc.3`, and `@deepseek-ai/schemastery` / `@deepseek-ai/cordis` went from `^` to exact pins (3.18.2 / 4.0.2). **No typography, theme or settings-API change**, so the rc behaviour needs no branch for rc.3. The rc line's own schemastery 3.18.2 has **no** `.volatile()` (the alpha's 3.18.4 does), so the volatile mark on the root schema has to be **added when supported and skipped when not**.
|
|
19
|
+
- The compatibility table gained 0.2.4, and both READMEs describe how the runtime handles the two dialects.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
- `test/run.mjs` (91 checks): the card's slot allow-list gained `plugins.item` / `plugins.bundle.config`, and the case is now "registers the card in every seat a supported host may declare", asserting the three seats plus the `plugins.item` id, label and component shape (summary from `card.description`, the page returning an object).
|
|
23
|
+
- `test/render-card.mjs` (23 checks): rendering resolves its seat as `settings.plugin.item` and falls back to `plugins.item`, so both lines' shapes are exercised.
|
|
24
|
+
- Live (managed instance + headless Edge): `host-line-probe` passes on `0.1.5-rc.2`, `0.1.5-rc.3` and `0.1.7-alpha.2` (insert-style loading), and once more on alpha.2 loaded as a bundle. On the alpha the `codeLigatures: 2` driven from the card lands in `profiles/dfp/cordis.patch.yml`; on the rc line it lands in `settings.yaml`.
|
|
25
|
+
- **CI caught a case the local dialect cannot show**: three schema cases in `test/run.mjs` read fields straight off `schema({…}).sans`. The release CI installs the newest schemastery (3.18.4, which has `.volatile()`), and a resolved field is then a **reference** rather than a value — all three were green locally (3.18.2) and red in CI. The host half now exports `plainConfigValue`, those cases read the config **the way the plugin reads it**, and each dialect's shape is asserted: with the modifier a field is a reference that has to be unwrapped, without it the field is the value. The devDependency moved to `^3.18.4` so local and CI exercise the same dialect (the rc line's unmarked shape is covered live on rc.2 / rc.3 by the probe).
|
|
26
|
+
|
|
5
27
|
## [0.2.3] - 2026-09-22
|
|
6
28
|
|
|
7
29
|
### Fixed
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
**dsh-fonttune** 的重要变更都记录在这里。英文版见 [CHANGELOG.en.md](CHANGELOG.en.md)。
|
|
4
4
|
|
|
5
|
+
## [0.2.4] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
- **一个包同时装上两条宿主线**:`0.1.5-rc.1` – `rc.3`(宿主 `settings.installSection` + 客户端 `settingsScope` 那一套)与 `0.1.7-alpha.1` – `alpha.2`(宿主 `settings.configure({auto:true}, ctx.fiber)` + 客户端 `configForms` 那一套)。插件在运行时看宿主到底给了哪一套,**两条都不在就只装样式、不注册卡片**,不会把整包 park 住。`package.json` 的 `engines.dsh` 写成语义化预发布区间 `>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`(写 `>=0.1.5-rc.1` 是匹配不到 `0.1.7-alpha.1` 的),`dsh.compatibility.dshReleases` 逐条列出五条实测过的宿主线。
|
|
9
|
+
- **卡片在两条线的三个座位上都能出现**,由宿主自己决定渲染哪一个:rc 的 `settings.plugin.item`(按设置命名空间认领)、alpha 的 `plugins.item`(插件页的插件条目,`id: fonttune`、标签取字典里的 `card.title`、摘要用 `card.description`、详情页才渲染整张卡——**DSH 自己的 shell / agent-loop / subagent / web-search 也是这么登记的**)、以及给已装 profile 包用的 `plugins.bundle.config`(按 npm 包名 `dsh-fonttune` 认领,落在该包的详情页)。
|
|
10
|
+
- **认领命名空间改成「先等、再按签名认」**:alpha 的 `configForms.describe()` 是异步的,以前抢在它应答前按名字猜候选,猜错就会落在别的命名空间上、每次写入都被宿主拒绝(真机表现:卡片能开、一改就报「设置没有保存成功」)。现在候选为空就返回 `null`,并挂一个「待定作用域」占位(`getSnapshot`/`subscribe`/`set`/`unset` 全套),等 describe 的视图到达后按 **schema 里同时含 `uiFollowsDialog` 与 `stackDialog`** 认出自己那一个再接管(真机日志:`served namespaces []` → `matched by served schema: fonttune`)。
|
|
11
|
+
- **宿主侧拆掉 volatile 引用**:alpha 的 `ctx.fiber.config` 里每个字段都是 cosmokit 的 volatile 引用(`{ get(), [Symbol.for("cosmokit.volatile.write")](v) }`,且被冻结),当普通对象读只会拿到空值——真机表现是**卡片第一次渲染是一整排空控件、注入的样式表 0 字节**。现在统一走 `plainConfigValue()` 深拆,`styleRow()` 拿到的是普通对象(实测样式行 973 B、`Georgia` / `520`)。
|
|
12
|
+
- **`inject` 收窄到两条线都有的服务**(`slots` / `locale`):声明一个某条线上不存在的服务(例如 rc 才有的 `settingsScope`)会把**整包 park 住**(`pending (waiting for service: …)`),Web 直接起不来。设置作用域改为运行时用 `ctx.get()` 免声明查找,找不到就按「没有宿主层」降级。
|
|
13
|
+
- **写设置的结果两种形状都认**:rc 的 `mutate` 即使失败也 resolve `undefined`,alpha 的 `set`/`unset` resolve 布尔(`false` = 宿主拒绝)。`followWrite` 现在把同步 `false`、resolve 成 `false` 的 thenable、以及 rejection 三种都报到预设行右侧那句提示里。
|
|
14
|
+
- **`test/host-line-probe.mjs`:与宿主线无关的真机探针**。只断言两条线都必须成立的事——页面无报错、插件只维护**一份**样式表且已接入文档、设置真的作用到文档(设了 `DFP_EXPECT_FAMILY` / `DFP_EXPECT_WEIGHT` 就顺带断言计算样式)、设置页里能找到并渲染出 `.dfp-card`(哪条线的座位都行)、以及从卡片上改一个值真的写进宿主。已并入 `npm run verify:browser` 的只读组。设 `DFP_PROBE=1` 时插件会往控制台打诊断行(服务了哪些命名空间、认领了哪个、座位当前状态),`DFP_DUMP=1` 时探针额外按分组打印插件页的条目。
|
|
15
|
+
|
|
16
|
+
### 变更
|
|
17
|
+
- **不再 require 宿主内部原语**:alpha 只导出 `IconPlusOutlineRegular` / `IconPlusOutlineMedium`(没有 rc 的 `IconPlusOutline16`),所以 `primitives` 的 require 整个去掉,用到的加号改成内联 SVG。
|
|
18
|
+
- **实测清楚 rc.3 到底改了什么**(供后续判断要不要为它开分支):把 rc.2 与 rc.3 的 `lib/` + `dist/` 共 728 个 js/css 逐个哈希比对,**只有 2 个文件不同**——`@deepseek-ai/cosmokit/lib/index.js`(13336 → 17160 B,新增 volatile 原语 `createVolatile` / `isVolatile` / `volatileEntries` / `updateVolatile` 与 `Symbol.for("cosmokit.volatile.write")`)和 `dsh-client-ui-sidebar/lib/client.js`(只差构建戳 `0.1.5-rc.2-449af19` → `0.1.5-rc.3-a5380f3`);依赖上内部包抬到 `^0.1.5-rc.3`,`@deepseek-ai/schemastery` / `@deepseek-ai/cordis` 从 `^` 改成精确钉版(3.18.2 / 4.0.2)。**排版、主题、设置 API 一处没动**,面向 rc 的行为在 rc.3 上不需要新分支。rc 线自带的 schemastery 3.18.2 **没有** `.volatile()`(alpha 的 3.18.4 才有),所以根 schema 上的 volatile 标记必须**有则加、无则跳**。
|
|
19
|
+
- 兼容表补 0.2.4(中英两份 README 都改了运行时双方言的处理说明)。
|
|
20
|
+
|
|
21
|
+
### 测试
|
|
22
|
+
- `test/run.mjs`(91 项):卡片槽位白名单加入 `plugins.item` / `plugins.bundle.config`,那条用例改成「每个受支持宿主可能派发的座位都注册」,断言三个座位与 `plugins.item` 的 id / 标签 / 组件形态(摘要取 `card.description`,页面交出一个对象)。
|
|
23
|
+
- `test/render-card.mjs`(23 项):渲染取值改为「`settings.plugin.item` 不在就取 `plugins.item`」,两条线的座位各跑一遍。
|
|
24
|
+
- 真机(受管实例 + 无头 Edge):`0.1.5-rc.2`、`0.1.5-rc.3`、`0.1.7-alpha.2` 各跑一次 `host-line-probe` 通过(插入式装载);alpha.2 另跑一次 bundle 式装载通过。alpha 上确认从卡片改的 `codeLigatures: 2` 真的写进 `profiles/dfp/cordis.patch.yml`,rc 上确认落在 `settings.yaml`。
|
|
25
|
+
- **CI 抓到一条本地测不出来的用例**:`test/run.mjs` 里三条 schema 用例直接读 `schema({…}).sans` 这类字段。发布 CI 装的是最新的 schemastery(3.18.4,带 `.volatile()`),于是解析出来的字段是**引用**而不是值,三条在本地(3.18.2)全绿、在 CI 全红。现在宿主半把 `plainConfigValue` 导出,这三条按**插件读配置的同一方式**取值,并断言两种方言各自的形态:有修饰符时字段是引用、拆开后才是值;没有修饰符时字段本身就是值。devDependency 同时抬到 `^3.18.4`,让本地与 CI 跑同一个方言(rc 线那套无修饰符的形态由真机探针在 rc.2 / rc.3 上覆盖)。
|
|
26
|
+
|
|
5
27
|
## [0.2.3] - 2026-09-22
|
|
6
28
|
|
|
7
29
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -22,14 +22,25 @@
|
|
|
22
22
|
|
|
23
23
|
## Compatibility
|
|
24
24
|
|
|
25
|
+
**One package serves both host lines, 0.1.5-rc.x and 0.1.7-alpha.x**, and the install is the same on either: both were verified for "the settings card opens, the stylesheet really reaches the page, and a change made in the card is written through".
|
|
26
|
+
|
|
25
27
|
| Plugin version | Supported DSH versions |
|
|
26
28
|
| --- | --- |
|
|
27
|
-
| **0.2.
|
|
29
|
+
| **0.2.4** (latest) | 0.1.5-rc.1 / rc.2 / rc.3, 0.1.7-alpha.1 / alpha.2 |
|
|
30
|
+
| 0.2.3 | 0.1.5-rc.2 |
|
|
28
31
|
| 0.2.2 | 0.1.5-rc.2 |
|
|
29
32
|
| 0.2.1 | 0.1.5-rc.2 |
|
|
30
33
|
| 0.2.0 | 0.1.5-rc.2 |
|
|
31
34
|
|
|
32
|
-
`engines.dsh`
|
|
35
|
+
`engines.dsh` is declared as **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`** (a prerelease needs its own branch: node-semver admits one only when some comparator in the range sits on that version's exact `major.minor.patch` tuple and carries a prerelease tag of its own, so a plain `>=0.1.5-rc.1` never matches `0.1.7-alpha.1`). Every difference between the lines is settled at runtime rather than by version number:
|
|
36
|
+
|
|
37
|
+
- **The settings service**: up to 0.1.5-rc.x it is `settingsScope.bind({namespace})` (by namespace); from 0.1.7-alpha.x it is `configForms.get(<profile entry id>)` (by entry id, which the installing profile's patch decides — so the plugin claims the form whose served schema names its own fields). Both expose the same `getSnapshot`/`subscribe`/`set`/`unset` face, so one implementation covers them.
|
|
38
|
+
- **The configuration card's seat**: the rc line has a keyed cell under Settings → Plugins → Plugin configuration (`settings.plugin.item`); the alpha line dropped that slot and instead contributes an entry to the Plugins page's official list (`plugins.item`, which is how the built-in settings pages are added there), plus a per-package page for a profile that installed this plugin as a bundle (`plugins.bundle.config`). All three are registered; the one a host never declares is inert.
|
|
39
|
+
- **The host half's config values**: once the alpha marks the schema `.volatile()`, the fields the host half receives are cosmokit volatile references (`{get(),[write]}`) rather than plain values, which left the first-frame stylesheet row empty; the references are unwrapped before use.
|
|
40
|
+
- **`inject` declares only services both lines have** (`slots`, `locale`): declaring one a host does not provide parks the whole package — on the alpha a declared `settingsScope` kept the entire Web UI from booting. The settings service is looked up with `ctx.get(name)` instead.
|
|
41
|
+
- **The alpha's form is addressed by entry id**: while the host's `describe()` view has not arrived, the plugin does not guess a name (a wrong guess means every write is refused) — it stands in with a pending scope, adopts the real form as soon as the view is served, and re-reads immediately.
|
|
42
|
+
|
|
43
|
+
The market entry also states these requirements (`engines.dsh`, `dsh.compatibility.dshReleases` and `peerDependencies` in `package.json`), so the host a version needs can be checked before installing.
|
|
33
44
|
|
|
34
45
|
## Install
|
|
35
46
|
|
|
@@ -73,8 +84,9 @@ Restart DSH once afterwards and open the Web UI; changing settings needs no rest
|
|
|
73
84
|
- `src/index.mjs` — host half: registers the `dsh-fonttune` settings namespace (schemastery schema with length and range validation) and ships a **stamped** `<style>` into the served index through `webserver/index-inject`, so the first paint already uses the saved values; the browser half then **adopts and keeps that element** (there is only ever one copy of the stylesheet, so rules can be added *and* removed)
|
|
74
85
|
- `src/client.js` — browser half: the settings card, the font picker panel, font enumeration and style injection
|
|
75
86
|
- `build.mjs` — zero-dependency build: inlines the shared core, wraps the bundle in the `window.__ModuleLoader__.load({id, factory})` shell, copies the host half, and enforces that the client bundle only requires shell-held modules
|
|
76
|
-
- `test/run.mjs` — offline checks (hand-built DOM / cordis / settings-surface doubles, the real schemastery schema, CSS generation and injection, sanitizer adversarial cases, dictionary key parity, the picker's grouping rules, the token-refresh power gate); `test/render-card.mjs` renders the card component through a mini React hooks runtime (including forced-open and forced-state renders, covering the 0.1.0 release-blocking crash class), fires **every control's callback** (an unwired handler only fails when someone actually clicks it) and **renders a second time** to cover a hook order that changes with state; `test/artifacts.mjs` proves `lib/` matches the current `src/` byte for byte (`npm run verify` runs all three)
|
|
77
|
-
- Checks that need a real page run through `npm run verify:browser -- "<url-with-token>"` (start a managed instance first: `dsh web --port 0 --no-open` prints the URL). The default group is read-only (`browser-probe` / `ui-walk` / `style-verify`); `--writers` adds the scripts that **write the settings namespace** (`slider-walk` / `split-walk` / `split-verify` / `weight-verify`, each snapshots the user layer first and restores it before exiting), and `--only a,b` picks a subset. Every script's exit code is its verdict; `test/set-user-layer.mjs <url> '<json>'` puts the layer back after an interrupted run
|
|
87
|
+
- `test/run.mjs` — offline checks (hand-built DOM / cordis / settings-surface doubles, the real schemastery schema, CSS generation and injection, sanitizer adversarial cases, dictionary key parity, the picker's grouping rules, the token-refresh power gate); `test/render-card.mjs` renders the card component through a mini React hooks runtime (including forced-open and forced-state renders, covering the 0.1.0 release-blocking crash class), fires **every control's callback** (an unwired handler only fails when someone actually clicks it) and **renders a second time** to cover a hook order that changes with state; `test/artifacts.mjs` proves `lib/` matches the current `src/` byte for byte (`npm run verify` runs all three). The devDependency is the **newest** schemastery (`^3.18.4`) — the alpha's dialect, whose `.volatile()` makes a resolved field a **reference** rather than a value — so these suites read values through the `plainConfigValue` the host half exports, the same path the plugin takes; at runtime the **host's own copy** is used (3.18.2 on rc.2 / rc.3, with no such modifier, a shape the live probe covers)
|
|
88
|
+
- Checks that need a real page run through `npm run verify:browser -- "<url-with-token>"` (start a managed instance first: `dsh web --port 0 --no-open` prints the URL). The default group is read-only (`browser-probe` / `ui-walk` / `host-line-probe` / `style-verify`); `--writers` adds the scripts that **write the settings namespace** (`slider-walk` / `split-walk` / `split-verify` / `weight-verify`, each snapshots the user layer first and restores it before exiting), and `--only a,b` picks a subset. Every script's exit code is its verdict; `test/set-user-layer.mjs <url> '<json>'` puts the layer back after an interrupted run
|
|
89
|
+
- `test/host-line-probe.mjs` is the **host-line-independent** check: it asserts only what must hold on every supported line — the page boots without errors, the plugin owns exactly ONE stylesheet element, the settings really reach the document (`DFP_EXPECT_FAMILY` / `DFP_EXPECT_WEIGHT` also assert the computed values when set), the configuration card is reachable from the settings sheet and renders (`.dfp-card`, whichever seat that line declares), and changing a control there really writes through. Run it first after switching host lines (rc ↔ alpha); set `DFP_PROBE=1` when debugging "the card's writes do not stick on the alpha" — the plugin then logs one `[dfp-probe]` line (which namespaces are served, which one was claimed, and what state the seat is in)
|
|
78
90
|
- `test/weight-verify.mjs` measures all three weight axes (interface / conversation / code) in a live page, including that there is only one stylesheet element and that switching following off drops the followed weight without a reload; `test/split-verify.mjs` measures the conversation and code size axes plus the **retired interface size axis moving nothing**; `test/slider-walk.mjs` and `test/split-walk.mjs` drive the real card (a drag writes nothing, a release does; the simple-mode slots and the whole stack round-trip); the market-entry tooling is `test/market-pr.mjs` (status / update / refresh / reopen / open / about) and the diagnostic `test/market-inspect.mjs` (PR state, comments, CI and branch diff)
|
|
79
91
|
- `docs/` — the 0.2.0 [research](docs/research-0.2.0.md) and [specification](docs/spec-0.2.0.md) records (written during design, with the trade-offs of that moment; where they differ from what shipped, README/CHANGELOG win — see the note at the top of each). **Not part of the npm `files` list, so they are not published**
|
|
80
92
|
- The client bundle may only require modules from the shell's static table (`react`, `react/jsx-runtime`, `react-dom`, `@deepseek-ai/cordis`, `@deepseek-ai/dsh-client-*`…); `dsh.client.inject` declares load order, not require permission
|
package/README.md
CHANGED
|
@@ -22,14 +22,25 @@
|
|
|
22
22
|
|
|
23
23
|
## 兼容性
|
|
24
24
|
|
|
25
|
+
**一份包同时支持 0.1.5-rc.x 与 0.1.7-alpha.x 两条宿主线**,安装方式与宿主线无关:两条线都验证过「设置卡片可用、样式真的作用到页面、卡片上的改动写得进去」。
|
|
26
|
+
|
|
25
27
|
| 插件版本 | 支持的 DSH 版本 |
|
|
26
28
|
| --- | --- |
|
|
27
|
-
| **0.2.
|
|
29
|
+
| **0.2.4**(最新) | 0.1.5-rc.1 / rc.2 / rc.3、0.1.7-alpha.1 / alpha.2 |
|
|
30
|
+
| 0.2.3 | 0.1.5-rc.2 |
|
|
28
31
|
| 0.2.2 | 0.1.5-rc.2 |
|
|
29
32
|
| 0.2.1 | 0.1.5-rc.2 |
|
|
30
33
|
| 0.2.0 | 0.1.5-rc.2 |
|
|
31
34
|
|
|
32
|
-
`engines.dsh`
|
|
35
|
+
`engines.dsh` 声明为 **`>=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0`**(预发布版本必须显式留分支:node-semver 只在一个范围的某个比较符与该版本落在同一个 `major.minor.patch` 元组、且自身带预发布标签时才放行,写成 `>=0.1.5-rc.1` 匹配不到 `0.1.7-alpha.1`)。两条线的差别**全部在运行时二选一,不看版本号**:
|
|
36
|
+
|
|
37
|
+
- **设置服务**:0.1.5-rc.x 是 `settingsScope.bind({namespace})`(按命名空间),0.1.7-alpha.x 换成了 `configForms.get(<profile 条目 id>)`(按条目 id,id 由安装方的 profile patch 决定,所以插件靠「宿主服务的那份 schema 里有没有本插件自己的字段」来认领)。两者都是 `getSnapshot/subscribe/set/unset` 同一张脸,插件只写一套。
|
|
38
|
+
- **配置卡片座位**:rc 线是 设置 → 插件 → 插件配置 里的一个 keyed 单元(`settings.plugin.item`);alpha 线删掉了这个槽位,改成在「插件」页的官方插件列表里贡献一个条目(`plugins.item`,内置那些设置页就是这么挂的),并另外提供「已安装成 bundle 时」的每包页面(`plugins.bundle.config`)。三个座位都注册,宿主不声明的那个是惰性的。
|
|
39
|
+
- **宿主半的配置来源**:alpha 把 schema 标成 `.volatile()` 之后,宿主半拿到的配置字段是 cosmokit 的 volatile 引用(`{get(),[write]}`)而不是普通值,首帧样式行会因此变空;插件先把引用解出来再用。
|
|
40
|
+
- **`inject` 只声明两条线都有的服务**(`slots`、`locale`):声明一个宿主没有的服务会把整包 park 住——alpha 上声明 `settingsScope` 会让整个 Web 界面起不来。设置服务一律用 `ctx.get(name)` 现查。
|
|
41
|
+
- **alpha 的表单是「条目 id + 描述视图」**:宿主 `describe()` 还没答话时,插件不会去猜一个条目名(猜错的话每次写入都会被告主拒绝),而是先用一个等待态座位顶住,等宿主把视图发出来再接管并立刻重读。
|
|
42
|
+
|
|
43
|
+
市场条目同时声明这些兼容信息(`package.json` 的 `engines.dsh`、`dsh.compatibility.dshReleases` 与 `peerDependencies`),安装前可据此判断这一版需要的宿主版本。
|
|
33
44
|
|
|
34
45
|
## 安装
|
|
35
46
|
|
|
@@ -73,8 +84,9 @@ dsh plugin --profile web add <插件目录路径>
|
|
|
73
84
|
- `src/index.mjs` —— 宿主半:注册 `dsh-fonttune` 设置命名空间(schemastery schema,含长度与取值范围校验),并通过 `webserver/index-inject` 把一个打过标记的 `<style>` 随首页下发,首帧即是设置里的字体;那个元素随后由浏览器半**认领并接管**(页面里只有这一份样式表,规则可增可删)
|
|
74
85
|
- `src/client.js` —— 浏览器半:设置卡片、选字体面板、字体枚举与样式注入
|
|
75
86
|
- `build.mjs` —— 零依赖构建:内联共享核心、套上 `window.__ModuleLoader__.load({id, factory})` 外壳、拷贝宿主半,并校验客户端 bundle 只 require shell 预注入的模块
|
|
76
|
-
- `test/run.mjs` —— 离线检查(自建 DOM / cordis / 设置面替身、真实 schemastery schema、CSS 生成与注入、消毒对抗用例、双语文案键一致性、选字体面板的分组规则、令牌轮询的省电闸门);`test/render-card.mjs` 用 mini React hooks 运行时真实渲染卡片组件(含强制展开态与强制状态,覆盖 0.1.0 那类发布阻断崩溃),把**每个控件的回调都点一遍**(未接线的回调只有真点下去才会暴露),并**渲染第二次**覆盖「状态变化后 hook 顺序改变」这一类崩溃;`test/artifacts.mjs` 校验 `lib/` 与当前 `src/` 逐字节一致(`npm run verify`
|
|
77
|
-
- 需要真实页面的检查走 `npm run verify:browser -- "<带 token 的地址>"`(先起一个受管实例:`dsh web --port 0 --no-open`,它会打印地址)。默认只跑**只读**的几支(`browser-probe` / `ui-walk` / `style-verify`);加 `--writers` 再跑会**写设置命名空间**的几支(`slider-walk` / `split-walk` / `split-verify` / `weight-verify`,各自先快照用户层、退出前还原),`--only a,b` 挑子集;每支的退出码就是判定。`test/set-user-layer.mjs <url> '<json>'` 是在被中断的运行之后还原用户层的工具
|
|
87
|
+
- `test/run.mjs` —— 离线检查(自建 DOM / cordis / 设置面替身、真实 schemastery schema、CSS 生成与注入、消毒对抗用例、双语文案键一致性、选字体面板的分组规则、令牌轮询的省电闸门);`test/render-card.mjs` 用 mini React hooks 运行时真实渲染卡片组件(含强制展开态与强制状态,覆盖 0.1.0 那类发布阻断崩溃),把**每个控件的回调都点一遍**(未接线的回调只有真点下去才会暴露),并**渲染第二次**覆盖「状态变化后 hook 顺序改变」这一类崩溃;`test/artifacts.mjs` 校验 `lib/` 与当前 `src/` 逐字节一致(`npm run verify` 跑全部三套)。devDependency 钉的是**最新的** schemastery(`^3.18.4`),也就是 alpha 那套方言——它带 `.volatile()`,解析出来的字段是**引用**而不是值,所以这几套检查都通过宿主半导出的 `plainConfigValue` 取值(和插件读配置同一条路);运行时用的是**宿主自带的那份**(rc.2/rc.3 是 3.18.2,没有该修饰符,那条形态由真机探针覆盖)
|
|
88
|
+
- 需要真实页面的检查走 `npm run verify:browser -- "<带 token 的地址>"`(先起一个受管实例:`dsh web --port 0 --no-open`,它会打印地址)。默认只跑**只读**的几支(`browser-probe` / `ui-walk` / `host-line-probe` / `style-verify`);加 `--writers` 再跑会**写设置命名空间**的几支(`slider-walk` / `split-walk` / `split-verify` / `weight-verify`,各自先快照用户层、退出前还原),`--only a,b` 挑子集;每支的退出码就是判定。`test/set-user-layer.mjs <url> '<json>'` 是在被中断的运行之后还原用户层的工具
|
|
89
|
+
- `test/host-line-probe.mjs` 是**与宿主线无关**的那一支:只断言两条线都必须成立的事——页面无报错、插件只维护**一份**样式表、设置真的作用到文档(`DFP_EXPECT_FAMILY` / `DFP_EXPECT_WEIGHT` 给了就顺带断言计算样式)、设置页里能找到并渲染出 `.dfp-card`(哪条线的座位都行)、以及从卡片上改一个值真的写进去。换宿主线(rc ↔ alpha)后先跑它;调试「alpha 上卡片写不进去」这类问题时设 `DFP_PROBE=1`,插件会往控制台打一行 `[dfp-probe]`(服务了哪些命名空间、认领了哪个、座位当前是什么状态)
|
|
78
90
|
- `test/weight-verify.mjs` 在真实页面里量三条字重轴(界面 / 对话 / 代码)的独立性与「样式表只有一份、不刷新关掉跟随立即回落」;`test/split-verify.mjs` 量对话/代码两条字号轴的独立性,外加**退役的界面字号轴什么都不动**;`test/slider-walk.mjs` 与 `test/split-walk.mjs` 驱动真实卡片(拖动不写、松手才写;简单模式两格与整条栈的往返);另有市场条目的维护脚本 `test/market-pr.mjs`(status / update / refresh / reopen / open / about)与诊断脚本 `test/market-inspect.mjs`(PR 状态、评论、CI 与分支差异)
|
|
79
91
|
- `docs/` —— 0.2.0 的[背景调研](docs/research-0.2.0.md)与[功能规格](docs/spec-0.2.0.md)(设计阶段的记录,含当时的取舍理由;与最终实现不同的地方以 README/CHANGELOG 为准,两份文件文首都有说明)。**不在 npm `files` 清单内,不随包发布**
|
|
80
92
|
- 客户端模块能 require 的只有 shell 静态表里的模块(`react`、`react/jsx-runtime`、`react-dom`、`@deepseek-ai/cordis`、`@deepseek-ai/dsh-client-*` 等);`dsh.client.inject` 只是加载顺序声明,不是 require 许可
|
package/lib/client.js
CHANGED
|
@@ -2266,7 +2266,6 @@ if (typeof module !== "undefined" && module.exports) module.exports = shared;
|
|
|
2266
2266
|
|
|
2267
2267
|
var React = require("react");
|
|
2268
2268
|
var createPortal = require("react-dom").createPortal;
|
|
2269
|
-
var primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
2270
2269
|
|
|
2271
2270
|
var h = React.createElement;
|
|
2272
2271
|
var useCallback = React.useCallback;
|
|
@@ -2334,8 +2333,24 @@ var setWestEntry = shared.setWestEntry;
|
|
|
2334
2333
|
var setEastEntry = shared.setEastEntry;
|
|
2335
2334
|
var removeStackEntry = shared.removeStackEntry;
|
|
2336
2335
|
|
|
2337
|
-
/**
|
|
2338
|
-
|
|
2336
|
+
/**
|
|
2337
|
+
* Services this bundle waits for before it applies.
|
|
2338
|
+
*
|
|
2339
|
+
* `settingsScope` is deliberately absent: a host that does not provide a
|
|
2340
|
+
* declared service parks the whole package until it appears, and DSH
|
|
2341
|
+
* 0.1.7-alpha.x replaced that service with `configForms` — a plugin asking for
|
|
2342
|
+
* it never activates there. Both settings dialects are looked up at apply time
|
|
2343
|
+
* with `ctx.get` instead (see `pickSettingsScope`).
|
|
2344
|
+
*/
|
|
2345
|
+
var inject = ["slots", "locale"];
|
|
2346
|
+
|
|
2347
|
+
/**
|
|
2348
|
+
* The settings scope and translator `apply` bound, for the seat that renders
|
|
2349
|
+
* the card without handing it either: the alpha line's bundle page renders the
|
|
2350
|
+
* registered component with its own props, so the card falls back to these.
|
|
2351
|
+
*/
|
|
2352
|
+
var cardScope = null;
|
|
2353
|
+
var cardT = null;
|
|
2339
2354
|
|
|
2340
2355
|
/** The weight DSH uses for body text; choosing it means "leave it alone". */
|
|
2341
2356
|
var NEUTRAL_WEIGHT = 400;
|
|
@@ -2364,6 +2379,80 @@ var CHEVRON_PATH =
|
|
|
2364
2379
|
/** Panel height cap, used to decide whether it opens up or down. */
|
|
2365
2380
|
var PANEL_HEIGHT = 380;
|
|
2366
2381
|
|
|
2382
|
+
/**
|
|
2383
|
+
* The translator the card falls back to when the host hands it no `t`: the
|
|
2384
|
+
* built-in English table says something useful where the key alone would not.
|
|
2385
|
+
* @param {string} key - dictionary key.
|
|
2386
|
+
* @param {Record<string, string>} [params] - `{name}` substitutions.
|
|
2387
|
+
* @returns {string} the text.
|
|
2388
|
+
*/
|
|
2389
|
+
function fallbackTranslate(key, params) {
|
|
2390
|
+
return translate("en", key, params);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
/**
|
|
2394
|
+
* The settings seat a host without any settings service leaves the card: every
|
|
2395
|
+
* control renders disabled over an empty document, exactly like a read-only
|
|
2396
|
+
* deployment. It keeps the card — and therefore the whole configuration page —
|
|
2397
|
+
* from failing to render on a composition this plugin does not know.
|
|
2398
|
+
*/
|
|
2399
|
+
var MEMORY_SCOPE = {
|
|
2400
|
+
getSnapshot: function () {
|
|
2401
|
+
return {
|
|
2402
|
+
status: "ready",
|
|
2403
|
+
value: {},
|
|
2404
|
+
base: {},
|
|
2405
|
+
user: {},
|
|
2406
|
+
revision: 0,
|
|
2407
|
+
writable: false,
|
|
2408
|
+
mode: "memory",
|
|
2409
|
+
};
|
|
2410
|
+
},
|
|
2411
|
+
subscribe: function () {
|
|
2412
|
+
return function () {};
|
|
2413
|
+
},
|
|
2414
|
+
set: function () {
|
|
2415
|
+
return Promise.reject(new Error("settings are not available in this deployment"));
|
|
2416
|
+
},
|
|
2417
|
+
unset: function () {
|
|
2418
|
+
return Promise.reject(new Error("settings are not available in this deployment"));
|
|
2419
|
+
},
|
|
2420
|
+
};
|
|
2421
|
+
|
|
2422
|
+
/* ------------------------------------------------------------------ *
|
|
2423
|
+
* inline icons
|
|
2424
|
+
* ------------------------------------------------------------------ */
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* The 16px "plus" glyph of the "add a font" button.
|
|
2428
|
+
*
|
|
2429
|
+
* Drawn here rather than taken from `@deepseek-ai/dsh-client-ui-primitives`
|
|
2430
|
+
* because that module's icon names are not stable across the host lines this
|
|
2431
|
+
* plugin supports: up to 0.1.5-rc.x the shell exports `IconPlusOutline16`,
|
|
2432
|
+
* while 0.1.7-alpha.x ships the same artwork as `IconPlusOutlineRegular` /
|
|
2433
|
+
* `IconPlusOutlineMedium` — and requiring a name the shell does not have turns
|
|
2434
|
+
* the whole card into a render error. The path is the shipped one, byte for
|
|
2435
|
+
* byte, so the button looks identical on both.
|
|
2436
|
+
* @returns {object} the svg element.
|
|
2437
|
+
*/
|
|
2438
|
+
function plusIcon() {
|
|
2439
|
+
return h(
|
|
2440
|
+
"svg",
|
|
2441
|
+
{
|
|
2442
|
+
width: 16,
|
|
2443
|
+
height: 16,
|
|
2444
|
+
viewBox: "0 0 16 16",
|
|
2445
|
+
fill: "none",
|
|
2446
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2447
|
+
"aria-hidden": "true",
|
|
2448
|
+
},
|
|
2449
|
+
h("path", {
|
|
2450
|
+
d: "M8.64453 1.5V7.34961H14.5V8.65039H8.64453V14.5H7.34473V8.65039H1.5V7.34961H7.34473V1.5H8.64453Z",
|
|
2451
|
+
fill: "currentColor",
|
|
2452
|
+
})
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2367
2456
|
/* ------------------------------------------------------------------ *
|
|
2368
2457
|
* copy
|
|
2369
2458
|
* ------------------------------------------------------------------ */
|
|
@@ -4013,7 +4102,7 @@ function StackPicker(props) {
|
|
|
4013
4102
|
setOpen(!open);
|
|
4014
4103
|
},
|
|
4015
4104
|
},
|
|
4016
|
-
|
|
4105
|
+
plusIcon(),
|
|
4017
4106
|
h("span", null, t("stack.add"))
|
|
4018
4107
|
)
|
|
4019
4108
|
)
|
|
@@ -4186,6 +4275,24 @@ function firstFamily(stack) {
|
|
|
4186
4275
|
* the card
|
|
4187
4276
|
* ------------------------------------------------------------------ */
|
|
4188
4277
|
|
|
4278
|
+
/**
|
|
4279
|
+
* The card as DSH 0.1.7-alpha.x's Plugins page renders it.
|
|
4280
|
+
*
|
|
4281
|
+
* That page contributes one entry per plugin (`plugins.item`) and asks that one
|
|
4282
|
+
* component for two different views: a one-line `summary` for the card in the
|
|
4283
|
+
* list, and the `page` body once the entry is opened. The summary is answered
|
|
4284
|
+
* with the card's own description, and every other view is the card itself, so
|
|
4285
|
+
* the same UI serves the rc line's configuration cell and the alpha's plugin
|
|
4286
|
+
* page without a second implementation.
|
|
4287
|
+
* @param {object} props - the view asked for plus the injected face.
|
|
4288
|
+
* @returns {object|string} the card, or the one-line summary.
|
|
4289
|
+
*/
|
|
4290
|
+
function FontCardPage(props) {
|
|
4291
|
+
var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
|
|
4292
|
+
if (props.view === "summary") return t("card.description");
|
|
4293
|
+
return h(FontCard, { scope: props.scope || cardScope || MEMORY_SCOPE, t: t });
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4189
4296
|
/**
|
|
4190
4297
|
* Render the plugin's card: the settings section dispatches this component
|
|
4191
4298
|
* under the namespace key, and the host has to serve that namespace for it to
|
|
@@ -4203,8 +4310,11 @@ function firstFamily(stack) {
|
|
|
4203
4310
|
* @returns {object} the card element.
|
|
4204
4311
|
*/
|
|
4205
4312
|
function FontCard(props) {
|
|
4206
|
-
|
|
4207
|
-
|
|
4313
|
+
// The registration hands the card its face on the line that supports
|
|
4314
|
+
// `inject`; the alpha's bundle page renders it with its own props, so the
|
|
4315
|
+
// values `apply` bound are the fallback.
|
|
4316
|
+
var t = typeof props.t === "function" ? props.t : cardT || fallbackTranslate;
|
|
4317
|
+
var scope = props.scope || cardScope || MEMORY_SCOPE;
|
|
4208
4318
|
var [open, setOpen] = useState(false);
|
|
4209
4319
|
var [view, setView] = useState(readViewMode);
|
|
4210
4320
|
var [expanded, setExpanded] = useState(null);
|
|
@@ -4306,13 +4416,48 @@ function FontCard(props) {
|
|
|
4306
4416
|
* Report a write the settings document refused. A silent failure is the
|
|
4307
4417
|
* worst outcome here: the control goes back to showing the stored value and
|
|
4308
4418
|
* the user is left believing the change was saved.
|
|
4309
|
-
* @param {unknown} error - the rejection reason.
|
|
4419
|
+
* @param {unknown} error - the rejection reason, or a boolean refusal.
|
|
4310
4420
|
*/
|
|
4311
4421
|
var writeFailed = function (error) {
|
|
4312
|
-
var message =
|
|
4422
|
+
var message =
|
|
4423
|
+
error === false || error === undefined
|
|
4424
|
+
? "the host refused the write"
|
|
4425
|
+
: error && error.message
|
|
4426
|
+
? String(error.message)
|
|
4427
|
+
: String(error);
|
|
4313
4428
|
if (message.length > 160) message = message.slice(0, 160) + "…";
|
|
4314
4429
|
showStatus(t("preset.writeFailed", { message: message }));
|
|
4315
4430
|
};
|
|
4431
|
+
/**
|
|
4432
|
+
* Follow one queued settings write.
|
|
4433
|
+
*
|
|
4434
|
+
* The two host lines answer differently: up to 0.1.5-rc.x the scope resolves
|
|
4435
|
+
* a promise that rejects on a transport failure, while 0.1.7-alpha.x resolves
|
|
4436
|
+
* `false` for a write the host refused (a stale revision, say). Both are
|
|
4437
|
+
* reported, and a refused write is never left looking saved.
|
|
4438
|
+
* @param {unknown} result - what the scope returned.
|
|
4439
|
+
*/
|
|
4440
|
+
var followWrite = function (result) {
|
|
4441
|
+
if (result === false) {
|
|
4442
|
+
writeFailed(result);
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
if (result && typeof result.then === "function") {
|
|
4446
|
+
result.then(function (accepted) {
|
|
4447
|
+
if (accepted === false) writeFailed(accepted);
|
|
4448
|
+
}, function (error) {
|
|
4449
|
+
// The scope reloads the Host state itself; the status row is what
|
|
4450
|
+
// keeps the user from thinking the change stuck.
|
|
4451
|
+
writeFailed(error);
|
|
4452
|
+
});
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
if (result && typeof result.catch === "function") {
|
|
4456
|
+
result.catch(function (error) {
|
|
4457
|
+
writeFailed(error);
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
};
|
|
4316
4461
|
var submit = function (field, value) {
|
|
4317
4462
|
var result;
|
|
4318
4463
|
try {
|
|
@@ -4321,13 +4466,7 @@ function FontCard(props) {
|
|
|
4321
4466
|
writeFailed(error);
|
|
4322
4467
|
return;
|
|
4323
4468
|
}
|
|
4324
|
-
|
|
4325
|
-
result.catch(function (error) {
|
|
4326
|
-
// The scope reloads the Host state itself; the status row is what
|
|
4327
|
-
// keeps the user from thinking the change stuck.
|
|
4328
|
-
writeFailed(error);
|
|
4329
|
-
});
|
|
4330
|
-
}
|
|
4469
|
+
followWrite(result);
|
|
4331
4470
|
};
|
|
4332
4471
|
var clear = function (field) {
|
|
4333
4472
|
var result;
|
|
@@ -4337,11 +4476,7 @@ function FontCard(props) {
|
|
|
4337
4476
|
writeFailed(error);
|
|
4338
4477
|
return;
|
|
4339
4478
|
}
|
|
4340
|
-
|
|
4341
|
-
result.catch(function (error) {
|
|
4342
|
-
writeFailed(error);
|
|
4343
|
-
});
|
|
4344
|
-
}
|
|
4479
|
+
followWrite(result);
|
|
4345
4480
|
};
|
|
4346
4481
|
var overridden = function (field) {
|
|
4347
4482
|
return Object.prototype.hasOwnProperty.call(user, field);
|
|
@@ -5282,7 +5417,231 @@ function apply(ctx) {
|
|
|
5282
5417
|
var applyCss = createStylesheet(function () {
|
|
5283
5418
|
return tokens;
|
|
5284
5419
|
});
|
|
5285
|
-
|
|
5420
|
+
|
|
5421
|
+
/**
|
|
5422
|
+
* Look a service up without declaring it.
|
|
5423
|
+
*
|
|
5424
|
+
* Declaring a name the running host does not provide parks the whole package
|
|
5425
|
+
* until it appears — on DSH 0.1.7-alpha.x a declared `settingsScope` left the
|
|
5426
|
+
* Web UI waiting on a service that no longer exists. Both routes are tried:
|
|
5427
|
+
* `ctx.get(name)` first, then the service property older lines also expose.
|
|
5428
|
+
* @param {string} name - the service name.
|
|
5429
|
+
* @returns {object|null} the service, or null when this host has none.
|
|
5430
|
+
*/
|
|
5431
|
+
var getService = function (name) {
|
|
5432
|
+
try {
|
|
5433
|
+
if (typeof ctx.get === "function") {
|
|
5434
|
+
var found = ctx.get(name);
|
|
5435
|
+
if (found) return found;
|
|
5436
|
+
}
|
|
5437
|
+
} catch (error) {
|
|
5438
|
+
// fall through to the property read
|
|
5439
|
+
}
|
|
5440
|
+
try {
|
|
5441
|
+
return ctx[name] || null;
|
|
5442
|
+
} catch (error) {
|
|
5443
|
+
return null;
|
|
5444
|
+
}
|
|
5445
|
+
};
|
|
5446
|
+
|
|
5447
|
+
/**
|
|
5448
|
+
* Whether one served namespace is THIS plugin's form.
|
|
5449
|
+
*
|
|
5450
|
+
* The alpha keys a form by profile entry id, which the installing profile's
|
|
5451
|
+
* patch decides, so the entry's own fields are what identify us: either the
|
|
5452
|
+
* served schema or the values it carries name them.
|
|
5453
|
+
* @param {object} entry - one namespace from the describe view.
|
|
5454
|
+
* @returns {boolean} true when the entry is this plugin's.
|
|
5455
|
+
*/
|
|
5456
|
+
var isOurNamespace = function (entry) {
|
|
5457
|
+
var schema = "";
|
|
5458
|
+
try {
|
|
5459
|
+
schema = JSON.stringify((entry && entry.schema) || {});
|
|
5460
|
+
} catch (error) {
|
|
5461
|
+
schema = "";
|
|
5462
|
+
}
|
|
5463
|
+
if (schema.indexOf(UI_FOLLOWS_FIELD) >= 0 && schema.indexOf(STACK_DIALOG_FIELD) >= 0) {
|
|
5464
|
+
return true;
|
|
5465
|
+
}
|
|
5466
|
+
var values = (entry && (entry.value || entry.user || entry.base)) || {};
|
|
5467
|
+
return (
|
|
5468
|
+
Object.prototype.hasOwnProperty.call(values, UI_FOLLOWS_FIELD) &&
|
|
5469
|
+
Object.prototype.hasOwnProperty.call(values, STACK_DIALOG_FIELD)
|
|
5470
|
+
);
|
|
5471
|
+
};
|
|
5472
|
+
|
|
5473
|
+
/**
|
|
5474
|
+
* The profile entry id the `configForms` dialect addresses settings by.
|
|
5475
|
+
*
|
|
5476
|
+
* Up to 0.1.5-rc.x a namespace is a name the plugin registers; from
|
|
5477
|
+
* 0.1.7-alpha.x the settings service derives a form per composed entry and
|
|
5478
|
+
* keys it by that entry's id — which the installing profile's patch decides,
|
|
5479
|
+
* so nothing here may hard-code it. The served view is the only honest
|
|
5480
|
+
* answer: while it has not arrived this returns null, and the caller waits
|
|
5481
|
+
* for it instead of guessing a name whose form could never be written.
|
|
5482
|
+
* @param {object} forms - the `configForms` service.
|
|
5483
|
+
* @returns {string|null} the entry id, or null while the host has not served it.
|
|
5484
|
+
*/
|
|
5485
|
+
var formsNamespace = function (forms) {
|
|
5486
|
+
var view = null;
|
|
5487
|
+
try {
|
|
5488
|
+
view = forms.describe ? forms.describe().getSnapshot().view : null;
|
|
5489
|
+
} catch (error) {
|
|
5490
|
+
view = null;
|
|
5491
|
+
}
|
|
5492
|
+
var served = (view && view.namespaces) || [];
|
|
5493
|
+
if (globalThis.__DFP_PROBE__ === true) {
|
|
5494
|
+
console.log(
|
|
5495
|
+
"[dfp-probe] served namespaces " +
|
|
5496
|
+
JSON.stringify(
|
|
5497
|
+
served.map(function (item) {
|
|
5498
|
+
return item && item.ns;
|
|
5499
|
+
})
|
|
5500
|
+
)
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
for (var index = 0; index < served.length; index += 1) {
|
|
5504
|
+
if (isOurNamespace(served[index])) {
|
|
5505
|
+
if (globalThis.__DFP_PROBE__ === true) {
|
|
5506
|
+
console.log("[dfp-probe] matched by served schema: " + String(served[index].ns));
|
|
5507
|
+
}
|
|
5508
|
+
return served[index].ns;
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
return null;
|
|
5512
|
+
};
|
|
5513
|
+
|
|
5514
|
+
/**
|
|
5515
|
+
* A scope that adopts the host's scope the moment the host serves it.
|
|
5516
|
+
*
|
|
5517
|
+
* The alpha's `configForms` can be asked for a form before its describe view
|
|
5518
|
+
* has arrived — `get(id)` then answers with a not-ready controller for that
|
|
5519
|
+
* id, and every write through it is refused. This stands in for the scope
|
|
5520
|
+
* until the real one is identifiable, forwards the card's subscription to it
|
|
5521
|
+
* on adoption, and lets the card re-read immediately afterwards.
|
|
5522
|
+
* @returns {object} the pending scope, with an `adopt` seat for the caller.
|
|
5523
|
+
*/
|
|
5524
|
+
var createPendingScope = function () {
|
|
5525
|
+
var target = null;
|
|
5526
|
+
var listeners = [];
|
|
5527
|
+
return {
|
|
5528
|
+
getSnapshot: function () {
|
|
5529
|
+
return target === null
|
|
5530
|
+
? {
|
|
5531
|
+
status: "loading",
|
|
5532
|
+
value: undefined,
|
|
5533
|
+
base: undefined,
|
|
5534
|
+
user: undefined,
|
|
5535
|
+
revision: undefined,
|
|
5536
|
+
writable: false,
|
|
5537
|
+
mode: "pending",
|
|
5538
|
+
}
|
|
5539
|
+
: target.getSnapshot();
|
|
5540
|
+
},
|
|
5541
|
+
subscribe: function (listener) {
|
|
5542
|
+
if (target !== null) return target.subscribe(listener);
|
|
5543
|
+
listeners.push(listener);
|
|
5544
|
+
return function () {
|
|
5545
|
+
var at = listeners.indexOf(listener);
|
|
5546
|
+
if (at >= 0) listeners.splice(at, 1);
|
|
5547
|
+
};
|
|
5548
|
+
},
|
|
5549
|
+
set: function (field, value) {
|
|
5550
|
+
if (target === null) {
|
|
5551
|
+
return Promise.reject(new Error("the settings service is still starting"));
|
|
5552
|
+
}
|
|
5553
|
+
return target.set(field, value);
|
|
5554
|
+
},
|
|
5555
|
+
unset: function (field) {
|
|
5556
|
+
if (target === null) {
|
|
5557
|
+
return Promise.reject(new Error("the settings service is still starting"));
|
|
5558
|
+
}
|
|
5559
|
+
return target.unset(field);
|
|
5560
|
+
},
|
|
5561
|
+
/** Hand over the real scope once it is identifiable; true when taken. */
|
|
5562
|
+
adopt: function (next) {
|
|
5563
|
+
if (target !== null || next === null || next === undefined) return false;
|
|
5564
|
+
target = next;
|
|
5565
|
+
var pending = listeners.slice();
|
|
5566
|
+
listeners.length = 0;
|
|
5567
|
+
for (var index = 0; index < pending.length; index += 1) {
|
|
5568
|
+
target.subscribe(pending[index]);
|
|
5569
|
+
pending[index]();
|
|
5570
|
+
}
|
|
5571
|
+
return true;
|
|
5572
|
+
},
|
|
5573
|
+
};
|
|
5574
|
+
};
|
|
5575
|
+
|
|
5576
|
+
/**
|
|
5577
|
+
* Bind the settings scope in whichever dialect the running host speaks: the
|
|
5578
|
+
* `settingsScope` service up to 0.1.5-rc.x (`bind({namespace})`), the
|
|
5579
|
+
* `configForms` service from 0.1.7-alpha.x (`get(entryId)`). Both expose the
|
|
5580
|
+
* same snapshot/`set`/`unset` face, so one card covers them.
|
|
5581
|
+
* @returns {object|null} the bound scope, or null when neither dialect answered.
|
|
5582
|
+
*/
|
|
5583
|
+
var pickSettingsScope = function () {
|
|
5584
|
+
var settingsScope = getService("settingsScope");
|
|
5585
|
+
if (settingsScope && typeof settingsScope.bind === "function") {
|
|
5586
|
+
try {
|
|
5587
|
+
var bound = settingsScope.bind({ namespace: NAMESPACE });
|
|
5588
|
+
if (bound) return bound;
|
|
5589
|
+
} catch (error) {
|
|
5590
|
+
// fall through to the newer dialect
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
var forms = getService("configForms");
|
|
5594
|
+
if (forms && typeof forms.get === "function") {
|
|
5595
|
+
var bindAlphaForm = function () {
|
|
5596
|
+
var ns = formsNamespace(forms);
|
|
5597
|
+
if (ns === null) return null;
|
|
5598
|
+
try {
|
|
5599
|
+
var form = forms.get(ns);
|
|
5600
|
+
return form && typeof form.getSnapshot === "function" ? form : null;
|
|
5601
|
+
} catch (error) {
|
|
5602
|
+
return null;
|
|
5603
|
+
}
|
|
5604
|
+
};
|
|
5605
|
+
var immediate = bindAlphaForm();
|
|
5606
|
+
if (immediate !== null) return immediate;
|
|
5607
|
+
var pending = createPendingScope();
|
|
5608
|
+
var mirror = null;
|
|
5609
|
+
try {
|
|
5610
|
+
mirror = typeof forms.describe === "function" ? forms.describe() : null;
|
|
5611
|
+
} catch (error) {
|
|
5612
|
+
mirror = null;
|
|
5613
|
+
}
|
|
5614
|
+
var offMirror = null;
|
|
5615
|
+
var adopt = function () {
|
|
5616
|
+
var form = bindAlphaForm();
|
|
5617
|
+
if (form === null) return;
|
|
5618
|
+
if (!pending.adopt(form)) return;
|
|
5619
|
+
if (typeof offMirror === "function") offMirror();
|
|
5620
|
+
};
|
|
5621
|
+
if (mirror && typeof mirror.subscribe === "function") {
|
|
5622
|
+
offMirror = mirror.subscribe(adopt);
|
|
5623
|
+
if (typeof mirror.ensure === "function") mirror.ensure();
|
|
5624
|
+
}
|
|
5625
|
+
adopt();
|
|
5626
|
+
return pending;
|
|
5627
|
+
}
|
|
5628
|
+
return null;
|
|
5629
|
+
};
|
|
5630
|
+
|
|
5631
|
+
var scope = pickSettingsScope() || MEMORY_SCOPE;
|
|
5632
|
+
if (globalThis.__DFP_PROBE__ === true) {
|
|
5633
|
+
var probeSnapshot = scope.getSnapshot ? scope.getSnapshot() : {};
|
|
5634
|
+
console.log(
|
|
5635
|
+
"[dfp-probe] scope " +
|
|
5636
|
+
JSON.stringify({
|
|
5637
|
+
mode: probeSnapshot.mode,
|
|
5638
|
+
status: probeSnapshot.status,
|
|
5639
|
+
revision: probeSnapshot.revision,
|
|
5640
|
+
writable: probeSnapshot.writable,
|
|
5641
|
+
value: probeSnapshot.value,
|
|
5642
|
+
})
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5286
5645
|
var appliedOverrides = "";
|
|
5287
5646
|
|
|
5288
5647
|
/**
|
|
@@ -5436,6 +5795,9 @@ function apply(ctx) {
|
|
|
5436
5795
|
}
|
|
5437
5796
|
return translate(locale, key, params);
|
|
5438
5797
|
};
|
|
5798
|
+
// The card reads these when a seat renders it without the injected face.
|
|
5799
|
+
cardScope = scope;
|
|
5800
|
+
cardT = t;
|
|
5439
5801
|
ctx.effect(
|
|
5440
5802
|
function () {
|
|
5441
5803
|
return ctx.locale.register(NAMESPACE, DICTS);
|
|
@@ -5443,18 +5805,59 @@ function apply(ctx) {
|
|
|
5443
5805
|
"dsh-fonttune: dictionaries"
|
|
5444
5806
|
);
|
|
5445
5807
|
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5808
|
+
/**
|
|
5809
|
+
* Offer the card in one slot seat.
|
|
5810
|
+
*
|
|
5811
|
+
* Which seat a host declares is a line difference, not a preference, and the
|
|
5812
|
+
* three are not alternatives to pick from — each is simply inert where a host
|
|
5813
|
+
* never declares it:
|
|
5814
|
+
*
|
|
5815
|
+
* * `settings.plugin.item` — up to 0.1.5-rc.x, a keyed cell under
|
|
5816
|
+
* Settings → Plugins → Plugin configuration (keyed by the namespace);
|
|
5817
|
+
* * `plugins.item` — 0.1.7-alpha.x's own entry in the Plugins page's
|
|
5818
|
+
* official list, which is how EVERY plugin page is contributed there (the
|
|
5819
|
+
* built-in settings pages do the same), and the seat that needs no
|
|
5820
|
+
* installed package behind it;
|
|
5821
|
+
* * `plugins.bundle.config` — the alpha's per-package page, keyed by the npm
|
|
5822
|
+
* package name, for a profile that installed this plugin as a bundle.
|
|
5823
|
+
*
|
|
5824
|
+
* @param {string} seat - the slot name.
|
|
5825
|
+
* @param {object} options - the registration options that seat looks up.
|
|
5826
|
+
* @param {Function} [component] - the component to register; the card itself
|
|
5827
|
+
* unless a seat hands it props the card does not read.
|
|
5828
|
+
*/
|
|
5829
|
+
var offerCard = function (seat, options, component) {
|
|
5830
|
+
ctx.slots.inject(seat, function () {
|
|
5831
|
+
var registration = {
|
|
5832
|
+
name: seat,
|
|
5833
|
+
locale: NAMESPACE,
|
|
5451
5834
|
inject: function () {
|
|
5452
5835
|
return { scope: scope, t: t };
|
|
5453
5836
|
},
|
|
5837
|
+
};
|
|
5838
|
+
for (var field in options) {
|
|
5839
|
+
if (Object.prototype.hasOwnProperty.call(options, field)) {
|
|
5840
|
+
registration[field] = options[field];
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
return ctx.slots.register(registration, component === undefined ? FontCard : component);
|
|
5844
|
+
});
|
|
5845
|
+
};
|
|
5846
|
+
if (scope !== MEMORY_SCOPE) {
|
|
5847
|
+
offerCard("settings.plugin.item", { key: NAMESPACE });
|
|
5848
|
+
offerCard("plugins.bundle.config", { key: "dsh-fonttune" });
|
|
5849
|
+
offerCard(
|
|
5850
|
+
"plugins.item",
|
|
5851
|
+
{
|
|
5852
|
+
id: "fonttune",
|
|
5853
|
+
order: 40,
|
|
5854
|
+
label: function () {
|
|
5855
|
+
return t("card.title");
|
|
5856
|
+
},
|
|
5454
5857
|
},
|
|
5455
|
-
|
|
5858
|
+
FontCardPage
|
|
5456
5859
|
);
|
|
5457
|
-
}
|
|
5860
|
+
}
|
|
5458
5861
|
}
|
|
5459
5862
|
|
|
5460
5863
|
exports.apply = apply;
|
package/lib/index.js
CHANGED
|
@@ -71,6 +71,30 @@ const MAX_STACK = 200;
|
|
|
71
71
|
*/
|
|
72
72
|
const SAFE_FEATURES = /^[a-zA-Z0-9"' ,]*$/;
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Mark a schema editable without reloading, where the running schemastery
|
|
76
|
+
* knows the modifier.
|
|
77
|
+
*
|
|
78
|
+
* DSH 0.1.7-alpha.x derives a settings page from one form per composed entry
|
|
79
|
+
* and only projects the fields under a node marked `.volatile()` — an entry
|
|
80
|
+
* whose schema has no such node is skipped entirely, so the plugin would have
|
|
81
|
+
* no configuration page there. The modifier arrived in schemastery 3.18.x for
|
|
82
|
+
* the alpha line; the 0.1.5-rc.x line ships 3.18.2, where calling it while
|
|
83
|
+
* this module is imported would throw. Feature-detecting it is therefore the
|
|
84
|
+
* whole compatibility shim: the rc line keeps the plain schema it has always
|
|
85
|
+
* used, and the alpha gets the marking it needs.
|
|
86
|
+
*
|
|
87
|
+
* Marking the ROOT object makes every field below it live-editable; schemastery
|
|
88
|
+
* refuses a volatile node nested inside another volatile one, so nothing below
|
|
89
|
+
* may carry the modifier as well.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} schema - the object schema.
|
|
92
|
+
* @returns {object} the schema, marked when the modifier exists.
|
|
93
|
+
*/
|
|
94
|
+
function volatileWhenSupported(schema) {
|
|
95
|
+
return typeof schema.volatile === "function" ? schema.volatile() : schema;
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
/**
|
|
75
99
|
* The durable settings section.
|
|
76
100
|
*
|
|
@@ -82,7 +106,7 @@ const SAFE_FEATURES = /^[a-zA-Z0-9"' ,]*$/;
|
|
|
82
106
|
* value. Installing the plugin therefore changes nothing until the user asks
|
|
83
107
|
* for something.
|
|
84
108
|
*/
|
|
85
|
-
export const Config = z.object({
|
|
109
|
+
export const Config = volatileWhenSupported(z.object({
|
|
86
110
|
[SANS_FIELD]: z
|
|
87
111
|
.string()
|
|
88
112
|
.max(MAX_STACK)
|
|
@@ -220,13 +244,46 @@ export const Config = z.object({
|
|
|
220
244
|
.max(shared.MAX_PRESET_NAME)
|
|
221
245
|
.default("")
|
|
222
246
|
.description("Name of the preset the card currently edits (edits auto-save into it)"),
|
|
223
|
-
});
|
|
247
|
+
}));
|
|
224
248
|
|
|
225
249
|
/**
|
|
226
250
|
* One row of the structured index injection table.
|
|
227
251
|
* @typedef {{kind: "style", text: string} | {kind: "html", placement: "head", html: string}} StyleRow
|
|
228
252
|
*/
|
|
229
253
|
|
|
254
|
+
/**
|
|
255
|
+
* The cosmokit volatile-reference protocol: a parsed config field may be a
|
|
256
|
+
* frozen `{ get(), [write](value) }` reference rather than a plain value.
|
|
257
|
+
*
|
|
258
|
+
* DSH 0.1.7-alpha.x marks the plugin's whole schema `.volatile()` so a settings
|
|
259
|
+
* write can land without remounting the entry, and the loader then hands this
|
|
260
|
+
* plugin a config whose fields ARE those references. Reading them as plain data
|
|
261
|
+
* (which `normalizeConfig` expects) would see objects where strings belong and
|
|
262
|
+
* emit nothing, so the references are unwrapped first. On the 0.1.5-rc.x line
|
|
263
|
+
* no field is a reference and this is a plain deep copy.
|
|
264
|
+
*/
|
|
265
|
+
const VOLATILE_WRITE = Symbol.for("cosmokit.volatile.write");
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Replace every volatile reference in a parsed config with its current value.
|
|
269
|
+
*
|
|
270
|
+
* Exported because it is half of this plugin's host-line contract: a caller that
|
|
271
|
+
* wants to read a resolved config — a test, or a later composition — has to go
|
|
272
|
+
* through it, since on the alpha a parsed field is a reference rather than data.
|
|
273
|
+
* @param {unknown} value - a parsed config value, or a reference to one.
|
|
274
|
+
* @returns {unknown} the same shape, references unwrapped.
|
|
275
|
+
*/
|
|
276
|
+
export function plainConfigValue(value) {
|
|
277
|
+
if (value === null || typeof value !== "object") return value;
|
|
278
|
+
if (VOLATILE_WRITE in value && typeof value.get === "function") {
|
|
279
|
+
return plainConfigValue(value.get());
|
|
280
|
+
}
|
|
281
|
+
if (Array.isArray(value)) return value.map(plainConfigValue);
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const [key, child] of Object.entries(value)) out[key] = plainConfigValue(child);
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
|
|
230
287
|
/**
|
|
231
288
|
* The style row carrying one configuration.
|
|
232
289
|
*
|
|
@@ -240,7 +297,7 @@ export const Config = z.object({
|
|
|
240
297
|
* @returns {StyleRow} the row the web server renders into `<head>`.
|
|
241
298
|
*/
|
|
242
299
|
function styleRow(resolved) {
|
|
243
|
-
const cfg = normalizeConfig(resolved);
|
|
300
|
+
const cfg = normalizeConfig(plainConfigValue(resolved));
|
|
244
301
|
// 0.2.0 ships the per-theme editor out; light and dark stay one value set
|
|
245
302
|
// (the dark fields remain in the schema, dormant, for a future release).
|
|
246
303
|
cfg[PER_THEME_FIELD] = false;
|
|
@@ -258,20 +315,47 @@ function styleRow(resolved) {
|
|
|
258
315
|
|
|
259
316
|
/**
|
|
260
317
|
* Register the settings section and keep the index in sync with it.
|
|
318
|
+
*
|
|
319
|
+
* TWO HOST DIALECTS. Up to DSH 0.1.5-rc.x the settings service registers a
|
|
320
|
+
* named section with a schema and reads it back by that name
|
|
321
|
+
* (`settings.installSection`), and the section name is what the browser half
|
|
322
|
+
* binds. From 0.1.7-alpha.x the service derives one form per composed entry and
|
|
323
|
+
* addresses it by the PROFILE ENTRY ID — there is no `installSection` at all,
|
|
324
|
+
* and an instance announces its page policy with `settings.configure` instead.
|
|
325
|
+
* Both are pure registrations that the browser half then reads through its own
|
|
326
|
+
* service, so which one ran does not change anything else here.
|
|
327
|
+
*
|
|
261
328
|
* @param {object} ctx - host cordis context.
|
|
262
329
|
* @param {unknown} config - the plugin entry's composition config (base layer).
|
|
263
330
|
*/
|
|
264
331
|
export function apply(ctx, config) {
|
|
265
332
|
let current = () => config;
|
|
266
333
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
267
|
-
settingsCtx.settings
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
334
|
+
const settings = settingsCtx.settings;
|
|
335
|
+
if (settings && typeof settings.installSection === "function") {
|
|
336
|
+
settings.installSection(ctx, NAMESPACE, Config, config ?? {}, {
|
|
337
|
+
setSource: (source) => {
|
|
338
|
+
current = source;
|
|
339
|
+
},
|
|
340
|
+
// The row table is rebuilt on every index render and every worker boot
|
|
341
|
+
// payload, so each read is already fresh; nothing to invalidate here.
|
|
342
|
+
onChange: () => {},
|
|
343
|
+
});
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (settings && typeof settings.configure === "function") {
|
|
347
|
+
// `auto: true` keeps the host's generated page as the fallback wherever
|
|
348
|
+
// no custom page of ours is reachable.
|
|
349
|
+
settingsCtx.effect(() => settings.configure({ auto: true }, ctx.fiber));
|
|
350
|
+
// This dialect has no registered reader: the entry's live, merged value
|
|
351
|
+
// is the fiber's own resolved config, which is what the settings service
|
|
352
|
+
// shows a form as well. The browser half adopts and rewrites the served
|
|
353
|
+
// element on activation either way, so this only decides the first frame.
|
|
354
|
+
current = () => {
|
|
355
|
+
const live = ctx.fiber && ctx.fiber.config;
|
|
356
|
+
return live === undefined || live === null ? config : live;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
275
359
|
});
|
|
276
360
|
ctx.on("webserver/index-inject", (table) => {
|
|
277
361
|
table.push(styleRow(current()));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fonttune",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"dsh": ">=0.1.5-rc.2"
|
|
22
|
+
"dsh": ">=0.1.5-rc.1 <0.1.7-0 || >=0.1.7-alpha.1 <0.2.0-0"
|
|
23
23
|
},
|
|
24
24
|
"dsh": {
|
|
25
25
|
"bundle": {
|
|
@@ -30,13 +30,17 @@
|
|
|
30
30
|
},
|
|
31
31
|
"compatibility": {
|
|
32
32
|
"dshReleases": {
|
|
33
|
-
"0.1.5-rc.
|
|
33
|
+
"0.1.5-rc.1": "compatible",
|
|
34
|
+
"0.1.5-rc.2": "compatible",
|
|
35
|
+
"0.1.5-rc.3": "compatible",
|
|
36
|
+
"0.1.7-alpha.1": "compatible",
|
|
37
|
+
"0.1.7-alpha.2": "compatible"
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"peerDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.0",
|
|
43
|
+
"react": "^18.2.0"
|
|
40
44
|
},
|
|
41
45
|
"peerDependenciesMeta": {
|
|
42
46
|
"@deepseek-ai/schemastery": {
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"devDependencies": {
|
|
47
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
51
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
48
52
|
},
|
|
49
53
|
"scripts": {
|
|
50
54
|
"build": "node build.mjs",
|