dsh-web-icon-indicator 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +11 -6
- package/README.zh.md +7 -5
- package/lib/client.js +68 -13
- package/lib/types/client/index.d.ts +10 -4
- package/lib/types/index.d.ts +2 -1
- package/package.json +1 -1
- package/test/verify.js +113 -5
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.2...v0.6.0) (2026-09-24)
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
* show the settings form on the plugin's own page ([a85a8b0](https://github.com/waknow/dsh-web-icon-indicator/commit/a85a8b0ce3821743c45bd8cca6ea0d969e8a419f))
|
|
15
|
+
|
|
10
16
|
## [0.5.2](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.1...v0.5.2) (2026-09-24)
|
|
11
17
|
|
|
12
18
|
### Added
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH version support** — requires **DSH ≥ 0.1.2-rc.1**. One bundle serves both settings generations: **modern** (≥ 0.1.7-alpha.1: exported `Config` schema + `configForms` + `plugins.
|
|
10
|
+
> **⚠️ DSH version support** — requires **DSH ≥ 0.1.2-rc.1**. One bundle serves both settings generations: **modern** (≥ 0.1.7-alpha.1: exported `Config` schema + `configForms` + `plugins.bundle.config`) and **legacy** (≤ 0.1.6-alpha.1: `settings.installSection` + `settingsScope` + `settings.plugin.item`). Verified on **DSH 0.1.5-rc.3** and **DSH 0.1.7-alpha.1**; on any older host the favicon still works even if the settings page is not reachable.
|
|
11
11
|
|
|
12
12
|
Browser tab favicon reflects the current DSH session state — `idle` / `running` / `asking` / `done` — so you can see at a glance whether a session needs your attention, even when the tab is in the background.
|
|
13
13
|
|
|
@@ -227,9 +227,12 @@ patch declares); **≤ 0.1.6-alpha.1** uses the plugin-chosen
|
|
|
227
227
|
`web-icon-indicator`, the same string 0.5.x used, so an existing section keeps
|
|
228
228
|
resolving:
|
|
229
229
|
|
|
230
|
-
- **Web GUI:** open **设置 →
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
- **Web GUI:** open **设置 → 插件** and click the **dsh-web-icon-indicator**
|
|
231
|
+
card — the settings form opens directly on the plugin's page (the same place
|
|
232
|
+
the official plugins put theirs) — including on **0.1.6-alpha.2**, which still
|
|
233
|
+
binds through the legacy `settingsScope` but already renders the bundle slot.
|
|
234
|
+
The
|
|
235
|
+
*Favicon indicator* page edits the same keys: asking/done hold, the **default icon color** (with a
|
|
233
236
|
live palette preview and the similarity warning), and the per-state effect /
|
|
234
237
|
colors / cycle for `running` / `asking` / `done`. Each of those states is a
|
|
235
238
|
collapsible row whose header shows one **color chip** per state — split in two
|
|
@@ -274,8 +277,10 @@ resolving:
|
|
|
274
277
|
omitted from the resolved form until a user sets it.
|
|
275
278
|
- The browser half is a hand-written `lib/client.js` (ModuleLoader factory
|
|
276
279
|
format — no build step, no runtime deps beyond the shell's `react`). It
|
|
277
|
-
registers on
|
|
278
|
-
|
|
280
|
+
registers on the slot the running host declares — `plugins.bundle.config`
|
|
281
|
+
on ≥ 0.1.7 (so the Plugins list's card opens straight onto the form), else
|
|
282
|
+
`plugins.row.config`; both gated on the served namespace — plus the legacy
|
|
283
|
+
`settings.plugin.item` — and resolves whichever
|
|
279
284
|
settings provider the host exposes (`configForms` or `settingsScope`) at
|
|
280
285
|
render time, so neither is a hard dependency of the module. The DSH client
|
|
281
286
|
scanner picks a new `dsh.client` declaration up on the next profile start.
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2-rc.1**。同一份插件同时兼容两代 settings 契约:**新一代**(≥ 0.1.7-alpha.1:导出 `Config` schema + `configForms` + `plugins.
|
|
10
|
+
> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2-rc.1**。同一份插件同时兼容两代 settings 契约:**新一代**(≥ 0.1.7-alpha.1:导出 `Config` schema + `configForms` + `plugins.bundle.config`)与**旧一代**(≤ 0.1.6-alpha.1:`settings.installSection` + `settingsScope` + `settings.plugin.item`)。已在 **DSH 0.1.5-rc.3** 与 **DSH 0.1.7-alpha.1** 上验证;更早的宿主上 favicon 仍可用,只是配置页可能不可达。
|
|
11
11
|
|
|
12
12
|
浏览器标签页 favicon 实时反映 DSH 会话状态——`待机` / `运行中` / `提问` / `完成`——让你在标签页置于后台时也能一眼看出是否有会话需要处理。
|
|
13
13
|
|
|
@@ -205,8 +205,9 @@ schemastery schema),**并在宿主仍提供旧版服务时**用同一份 sch
|
|
|
205
205
|
表单,即 `dsh-web-icon-indicator`(bundle patch 声明的行 id);**≤ 0.1.6-alpha.1**
|
|
206
206
|
使用插件自定的 `web-icon-indicator`,与 0.5.x 保持一致,老用户的 section 继续生效:
|
|
207
207
|
|
|
208
|
-
- **Web GUI:** 打开 **设置 →
|
|
209
|
-
|
|
208
|
+
- **Web GUI:** 打开 **设置 → 插件**,点开 **dsh-web-icon-indicator** 卡片,
|
|
209
|
+
设置表单就直接出现在插件页上(与官方插件的位置一致);**0.1.6-alpha.2** 虽然还在用
|
|
210
|
+
旧的 `settingsScope`,但它同样渲染 bundle 插槽,因此表单位置一致。可编辑:提问/完成驻留、
|
|
210
211
|
**默认图标颜色**(带配色一览与相似度告警),以及
|
|
211
212
|
`running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
|
|
212
213
|
各占一行可折叠条目,行首是一个**色块**——多色状态(asking)会左右分格同时显示
|
|
@@ -239,8 +240,9 @@ schemastery schema),**并在宿主仍提供旧版服务时**用同一份 sch
|
|
|
239
240
|
- 因此实时设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir`、`defaultColor`
|
|
240
241
|
与 `states`。`iconsDir` 没有 schema 默认值,用户未设置时不会出现在解析后的表单中。
|
|
241
242
|
- 浏览器半区是手写的 `lib/client.js`(ModuleLoader factory 格式——无构建步骤、
|
|
242
|
-
无额外运行期依赖,仅用 shell 自带的 `react
|
|
243
|
-
|
|
243
|
+
无额外运行期依赖,仅用 shell 自带的 `react`)。它按宿主实际声明的插槽注册
|
|
244
|
+
(≥ 0.1.7 用 `plugins.bundle.config`,这样插件列表的卡片点开即见表单;否则退回
|
|
245
|
+
`plugins.row.config`——两者都按「Host 是否提供该命名空间」门控;另有旧的
|
|
244
246
|
`settings.plugin.item`),并在渲染时按宿主实际提供的服务解析表单
|
|
245
247
|
(`configForms` 或 `settingsScope`),因此两者都不是模块的硬依赖。DSH 客户端
|
|
246
248
|
扫描器会在下次启动 profile 时识别新的 `dsh.client` 声明。
|
package/lib/client.js
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* Registers one configuration page and serves BOTH host generations from one
|
|
6
6
|
* bundle:
|
|
7
7
|
*
|
|
8
|
-
* - DSH ≥ 0.1.7 (modern): page
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* - DSH ≥ 0.1.7 (modern): the official Plugins page, bound to the
|
|
9
|
+
* profile-entry settings namespace through `configForms`
|
|
10
|
+
* (`ctx.configForms.get(entryId)`). The form registers into
|
|
11
|
+
* `plugins.bundle.config` keyed by the bundle's package name, which the
|
|
12
|
+
* bundle's page renders under its description — the page a card in the
|
|
13
|
+
* Plugins list opens, so the settings are one click from that list (the
|
|
14
|
+
* shape the official plugins use). A host that declares only the row slot —
|
|
15
|
+
* no released host does (see AGENTS.md) — gets the row's configure control
|
|
16
|
+
* instead; the two are mutually exclusive, never both.
|
|
11
17
|
* - DSH ≤ 0.1.6-alpha.1 (legacy): page slot `settings.plugin.item` keyed by the
|
|
12
18
|
* legacy `web-icon-indicator` namespace, bound through `settingsScope`.
|
|
13
19
|
*
|
|
@@ -57,6 +63,14 @@ window.__ModuleLoader__.load({
|
|
|
57
63
|
* settings tab dispatches in `settings.plugin.item`.
|
|
58
64
|
*/
|
|
59
65
|
var LEGACY_NS = "web-icon-indicator";
|
|
66
|
+
/**
|
|
67
|
+
* `plugins.bundle.config` key: the bundle's own package name. The bundle
|
|
68
|
+
* page renders this entry between its description and its rows, and that
|
|
69
|
+
* page is exactly what a card in the Plugins list opens — so this is the
|
|
70
|
+
* slot that keeps the settings one click from the list. Must match the
|
|
71
|
+
* installed package name in `package.json`.
|
|
72
|
+
*/
|
|
73
|
+
var BUNDLE_KEY = "dsh-web-icon-indicator";
|
|
60
74
|
/** `plugins.row.config` key: `<bundle package name>#<row id as the patch declares it>`. */
|
|
61
75
|
var ROW_KEY = "dsh-web-icon-indicator#dsh-web-icon-indicator";
|
|
62
76
|
var LOCALE = "dsh-web-icon-indicator";
|
|
@@ -1355,26 +1369,67 @@ window.__ModuleLoader__.load({
|
|
|
1355
1369
|
// card follows whichever provider is mounted and renders nothing while
|
|
1356
1370
|
// neither is.
|
|
1357
1371
|
var pageFace = function () { return { scope: resolveScope(ctx), t: t }; };
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
// - `
|
|
1372
|
+
// One settings card, three page slots across the host generations.
|
|
1373
|
+
// `ctx.slots.inject(key, …)` is a no-op for a slot this host never
|
|
1374
|
+
// declares, so `start` runs exactly the branches the running host
|
|
1375
|
+
// understands. A DSH ≥ 0.1.7 host declares BOTH modern slots, so those
|
|
1376
|
+
// two branches are mutually exclusive:
|
|
1377
|
+
// - `plugins.bundle.config` (≥ 0.1.7): keyed by the bundle's own package
|
|
1378
|
+
// name. The bundle page renders it under the description, and that
|
|
1379
|
+
// page is exactly what a card in the Plugins list opens — so the
|
|
1380
|
+
// settings sit one click from the list. This is the shape the
|
|
1381
|
+
// official plugins use (the official voice-input bundle registers
|
|
1382
|
+
// this slot).
|
|
1383
|
+
// - `plugins.row.config` (≥ 0.1.6-alpha.2): keyed `<package>#<row id>`,
|
|
1384
|
+
// it only adds the configure control on the bundle page's row, which
|
|
1385
|
+
// buries the form one level deeper (bundle page -> row -> configure).
|
|
1386
|
+
// Registered only where the bundle slot is absent — a guard no
|
|
1387
|
+
// released host reaches, since every host with a Plugins page
|
|
1388
|
+
// declares the bundle slot too (see AGENTS.md).
|
|
1389
|
+
// - `settings.plugin.item` (≤ 0.1.6-alpha.1): keyed by the legacy
|
|
1364
1390
|
// settings namespace, dispatched by the settings tab per served
|
|
1365
1391
|
// namespace.
|
|
1366
1392
|
var start = function () {
|
|
1367
|
-
var
|
|
1393
|
+
var rowInject = null;
|
|
1394
|
+
var bundleLive = false;
|
|
1395
|
+
var dropRow = function () {
|
|
1396
|
+
var cancelRow = rowInject;
|
|
1397
|
+
rowInject = null;
|
|
1398
|
+
if (cancelRow !== null) cancelRow();
|
|
1399
|
+
};
|
|
1400
|
+
var disposeBundle = ctx.slots.inject("plugins.bundle.config", function () {
|
|
1401
|
+
bundleLive = true;
|
|
1402
|
+
// The bundle slot owns the card wherever it exists: cancel the row
|
|
1403
|
+
// wait, or dispose a row registration the host declared first, so the
|
|
1404
|
+
// same form is never drawn twice on one page.
|
|
1405
|
+
dropRow();
|
|
1368
1406
|
return ctx.slots.register(
|
|
1369
1407
|
{
|
|
1370
|
-
name: "plugins.
|
|
1371
|
-
key:
|
|
1408
|
+
name: "plugins.bundle.config",
|
|
1409
|
+
key: BUNDLE_KEY,
|
|
1372
1410
|
locale: LOCALE,
|
|
1373
1411
|
inject: pageFace,
|
|
1374
1412
|
},
|
|
1375
1413
|
IconConfigCard
|
|
1376
1414
|
);
|
|
1377
1415
|
});
|
|
1416
|
+
if (!bundleLive) {
|
|
1417
|
+
rowInject = ctx.slots.inject("plugins.row.config", function () {
|
|
1418
|
+
// The bundle slot may still be declared after this one (both slots
|
|
1419
|
+
// arrive with the Plugins page's own registration): stay out of its
|
|
1420
|
+
// way rather than registering a second copy of the same card.
|
|
1421
|
+
if (bundleLive) return function () {};
|
|
1422
|
+
return ctx.slots.register(
|
|
1423
|
+
{
|
|
1424
|
+
name: "plugins.row.config",
|
|
1425
|
+
key: ROW_KEY,
|
|
1426
|
+
locale: LOCALE,
|
|
1427
|
+
inject: pageFace,
|
|
1428
|
+
},
|
|
1429
|
+
IconConfigCard
|
|
1430
|
+
);
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1378
1433
|
var disposeItem = ctx.slots.inject("settings.plugin.item", function () {
|
|
1379
1434
|
return ctx.slots.register(
|
|
1380
1435
|
{
|
|
@@ -1386,7 +1441,7 @@ window.__ModuleLoader__.load({
|
|
|
1386
1441
|
IconConfigCard
|
|
1387
1442
|
);
|
|
1388
1443
|
});
|
|
1389
|
-
return function () {
|
|
1444
|
+
return function () { disposeBundle(); dropRow(); disposeItem(); };
|
|
1390
1445
|
};
|
|
1391
1446
|
// On the modern line, gate registration on the served namespace so an
|
|
1392
1447
|
// uncomposed plugin shows no page; `whileServed` owns the returned
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* Browser half of dsh-web-icon-indicator (dsh.client bundle).
|
|
3
3
|
*
|
|
4
4
|
* Registers one configuration page and serves both host generations from one
|
|
5
|
-
* bundle: `plugins.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* bundle. Modern (DSH ≥ 0.1.7): `plugins.bundle.config` keyed by the bundle
|
|
6
|
+
* package name `dsh-web-icon-indicator`, so the bundle page a Plugins-list card
|
|
7
|
+
* opens renders the form directly; a host that declares only
|
|
8
|
+
* `plugins.row.config` (no released host does) gets that slot instead, keyed
|
|
9
|
+
* `dsh-web-icon-indicator#dsh-web-icon-indicator` — never both. Either binds
|
|
10
|
+
* through `configForms` on ≥ 0.1.7 or `settingsScope` on 0.1.6-alpha.2 (which
|
|
11
|
+
* already renders the bundle slot). Legacy (DSH ≤ 0.1.6-alpha.1):
|
|
12
|
+
* `settings.plugin.item`, keyed by the legacy `web-icon-indicator` namespace,
|
|
13
|
+
* bound through `settingsScope`.
|
|
14
|
+
*
|
|
9
15
|
* Neither settings provider appears in `inject`: both are read weakly at render
|
|
10
16
|
* time, so the fiber activates on either line.
|
|
11
17
|
*/
|
package/lib/types/index.d.ts
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* The same surface is registered with the DSH settings service under the
|
|
22
22
|
* `web-icon-indicator` namespace (schemastery schema in `lib/index.js`): it is
|
|
23
23
|
* validated, persisted into the profile's `settings.yaml`, and editable from
|
|
24
|
-
* 设置 → 插件
|
|
24
|
+
* the Plugins page (设置 → 插件) through the browser half (`./client`) — the
|
|
25
|
+
* settings form is registered on the plugin's own page. While no
|
|
25
26
|
* settings service is composed, the plugin reads the composition entry only.
|
|
26
27
|
*/
|
|
27
28
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-web-icon-indicator",
|
|
3
3
|
"description": "Browser tab favicon reflects the DSH session state: idle / running / asking / done, recolored and animated from one base SVG in the browser (fully configurable colors + effects). · 浏览器标签页 favicon 实时反映 DSH 会话状态:待机 / 运行中 / 提问 / 完成,由单个 base.svg 在浏览器中动态上色与动画(颜色与特效完全可配置)。",
|
|
4
4
|
"icon": "icons/plugin.svg",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"dsh": ">=0.1.2-rc.1"
|
package/test/verify.js
CHANGED
|
@@ -1575,6 +1575,7 @@ console.log("\n=== Part 6: browser half (lib/client.js) ===");
|
|
|
1575
1575
|
const makeClientCtx = (opts) => {
|
|
1576
1576
|
const declared = new Set(opts.declared || []);
|
|
1577
1577
|
const services = opts.services || {};
|
|
1578
|
+
const sink = opts.sink || registered;
|
|
1578
1579
|
return {
|
|
1579
1580
|
get(name) { return services[name]; },
|
|
1580
1581
|
locale: {
|
|
@@ -1583,13 +1584,23 @@ console.log("\n=== Part 6: browser half (lib/client.js) ===");
|
|
|
1583
1584
|
},
|
|
1584
1585
|
slots: {
|
|
1585
1586
|
inject(name, cb) { if (declared.has(name)) cb(); return () => {}; },
|
|
1586
|
-
register(slotSpec, component) {
|
|
1587
|
+
register(slotSpec, component) {
|
|
1588
|
+
const entry = { slotSpec, component, disposed: false };
|
|
1589
|
+
sink.push(entry);
|
|
1590
|
+
return () => { entry.disposed = true; };
|
|
1591
|
+
},
|
|
1587
1592
|
},
|
|
1588
1593
|
effect(fn) { return fn(); },
|
|
1589
1594
|
};
|
|
1590
1595
|
};
|
|
1596
|
+
// A DSH >= 0.1.7 host declares BOTH configuration slots when the Plugins
|
|
1597
|
+
// page's own registration commits, and the card must land in exactly one of
|
|
1598
|
+
// them: `plugins.bundle.config` renders on the bundle's page — the page a
|
|
1599
|
+
// Plugins-list card opens, so one click from the list — while
|
|
1600
|
+
// `plugins.row.config` only puts a configure control on that page's row,
|
|
1601
|
+
// one level deeper. Registering into both would draw the same form twice.
|
|
1591
1602
|
const clientCtx = makeClientCtx({
|
|
1592
|
-
declared: ["plugins.row.config"],
|
|
1603
|
+
declared: ["plugins.bundle.config", "plugins.row.config"],
|
|
1593
1604
|
services: {
|
|
1594
1605
|
configForms: {
|
|
1595
1606
|
get(entryId) { boundEntryId = entryId; return scope; },
|
|
@@ -1602,15 +1613,18 @@ console.log("\n=== Part 6: browser half (lib/client.js) ===");
|
|
|
1602
1613
|
ok("F6 locale dictionaries registered (en + zh)",
|
|
1603
1614
|
dictionaries.length === 1 && !!dictionaries[0].dicts.en && !!dictionaries[0].dicts.zh,
|
|
1604
1615
|
"ns=" + (dictionaries[0] && dictionaries[0].ns));
|
|
1605
|
-
ok("F7 modern line: one card registered into plugins.
|
|
1606
|
-
registered.length === 1 && registered[0].slotSpec.name === "plugins.
|
|
1616
|
+
ok("F7 modern line: one card registered into plugins.bundle.config",
|
|
1617
|
+
registered.length === 1 && registered[0].slotSpec.name === "plugins.bundle.config",
|
|
1607
1618
|
"count=" + registered.length + " " + JSON.stringify(registered.map((r) => r.slotSpec.name)));
|
|
1608
1619
|
ok("F7b registration is gated on whileServed(entry id)",
|
|
1609
1620
|
Array.isArray(servedNamespaces) && servedNamespaces.length === 1 &&
|
|
1610
1621
|
servedNamespaces[0] === "dsh-web-icon-indicator",
|
|
1611
1622
|
JSON.stringify(servedNamespaces));
|
|
1612
1623
|
const slotSpec = registered[0] && registered[0].slotSpec;
|
|
1613
|
-
eq("F8 slot key is
|
|
1624
|
+
eq("F8 bundle slot key is the bundle package name", slotSpec && slotSpec.key, "dsh-web-icon-indicator");
|
|
1625
|
+
ok("F8b the row slot stays untouched where the bundle slot exists",
|
|
1626
|
+
registered.every((r) => r.slotSpec.name !== "plugins.row.config"),
|
|
1627
|
+
JSON.stringify(registered.map((r) => r.slotSpec.name)));
|
|
1614
1628
|
eq("F9 slot locale namespace", slotSpec && slotSpec.locale, "dsh-web-icon-indicator");
|
|
1615
1629
|
// The scope is resolved lazily inside the inject face (the two settings
|
|
1616
1630
|
// providers are mutually exclusive), so evaluate it before asserting.
|
|
@@ -1663,6 +1677,99 @@ console.log("\n=== Part 6: browser half (lib/client.js) ===");
|
|
|
1663
1677
|
ok("F61f legacy component renders the card", !!legacyCard && legacyCard.type === "div");
|
|
1664
1678
|
}
|
|
1665
1679
|
|
|
1680
|
+
// ---- the row-only Plugins page (DSH 0.1.6-alpha.2) ------------------------
|
|
1681
|
+
// The bundle slot arrived with the 0.1.7 Plugins page. A host that declares
|
|
1682
|
+
// only `plugins.row.config` keeps that older entry point — the configure
|
|
1683
|
+
// control on the row the bundle declares — and must still get exactly one
|
|
1684
|
+
// card, keyed by the row slot's `<package>#<row id>`.
|
|
1685
|
+
{
|
|
1686
|
+
const rowSink = [];
|
|
1687
|
+
const rowCtx = makeClientCtx({
|
|
1688
|
+
declared: ["plugins.row.config"],
|
|
1689
|
+
sink: rowSink,
|
|
1690
|
+
services: {
|
|
1691
|
+
configForms: {
|
|
1692
|
+
get: () => scope,
|
|
1693
|
+
whileServed: (namespaces, register) => register(new Set(namespaces)),
|
|
1694
|
+
},
|
|
1695
|
+
},
|
|
1696
|
+
});
|
|
1697
|
+
client.apply(rowCtx);
|
|
1698
|
+
eq("F66 row-only page: exactly one registration", rowSink.length, 1);
|
|
1699
|
+
eq("F66b row-only page: registered into plugins.row.config",
|
|
1700
|
+
rowSink[0] && rowSink[0].slotSpec.name, "plugins.row.config");
|
|
1701
|
+
eq("F66c row-only page: key is <package>#<row id>",
|
|
1702
|
+
rowSink[0] && rowSink[0].slotSpec.key, "dsh-web-icon-indicator#dsh-web-icon-indicator");
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// ---- both modern slots, declared in either order --------------------------
|
|
1706
|
+
// The real 0.1.7 host declares `plugins.bundle.config` and
|
|
1707
|
+
// `plugins.row.config` from the Plugins page's own registration, so both
|
|
1708
|
+
// waits this bundle installs resolve AFTER apply(). Whichever declaration
|
|
1709
|
+
// lands first, exactly one entry must survive — and it must be the bundle
|
|
1710
|
+
// one, or the settings would sit a level deeper than the official plugins'.
|
|
1711
|
+
{
|
|
1712
|
+
const makeLazyCtx = (sink) => {
|
|
1713
|
+
const pending = new Map();
|
|
1714
|
+
const ctx = {
|
|
1715
|
+
get: (name) => (name === "configForms"
|
|
1716
|
+
? { get: () => scope, whileServed: (_ns, register) => register(new Set()) }
|
|
1717
|
+
: undefined),
|
|
1718
|
+
locale: { register: () => () => {}, bind: () => (k) => k },
|
|
1719
|
+
slots: {
|
|
1720
|
+
inject(name, cb) {
|
|
1721
|
+
const controller = { cb, active: true, disposers: [] };
|
|
1722
|
+
if (!pending.has(name)) pending.set(name, []);
|
|
1723
|
+
pending.get(name).push(controller);
|
|
1724
|
+
return () => {
|
|
1725
|
+
if (!controller.active) return;
|
|
1726
|
+
controller.active = false;
|
|
1727
|
+
for (const dispose of controller.disposers.splice(0)) dispose();
|
|
1728
|
+
};
|
|
1729
|
+
},
|
|
1730
|
+
register(slotSpec, component) {
|
|
1731
|
+
const entry = { slotSpec, component, disposed: false };
|
|
1732
|
+
sink.push(entry);
|
|
1733
|
+
return () => { entry.disposed = true; };
|
|
1734
|
+
},
|
|
1735
|
+
},
|
|
1736
|
+
effect: (fn) => fn(),
|
|
1737
|
+
};
|
|
1738
|
+
return {
|
|
1739
|
+
ctx,
|
|
1740
|
+
declare(name) {
|
|
1741
|
+
for (const controller of pending.get(name) || []) {
|
|
1742
|
+
if (!controller.active) continue;
|
|
1743
|
+
const dispose = controller.cb();
|
|
1744
|
+
if (typeof dispose === "function") controller.disposers.push(dispose);
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
};
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
const rowFirstSink = [];
|
|
1751
|
+
const rowFirst = makeLazyCtx(rowFirstSink);
|
|
1752
|
+
client.apply(rowFirst.ctx);
|
|
1753
|
+
rowFirst.declare("plugins.row.config");
|
|
1754
|
+
eq("F67 row declaration first: the row entry is registered", rowFirstSink.length, 1);
|
|
1755
|
+
eq("F67b ... into plugins.row.config", rowFirstSink[0].slotSpec.name, "plugins.row.config");
|
|
1756
|
+
rowFirst.declare("plugins.bundle.config");
|
|
1757
|
+
const survivors = rowFirstSink.filter((entry) => !entry.disposed);
|
|
1758
|
+
eq("F67c the bundle declaration takes the card over — one live entry", survivors.length, 1);
|
|
1759
|
+
eq("F67d ... and it is the bundle entry",
|
|
1760
|
+
survivors[0] && survivors[0].slotSpec.name, "plugins.bundle.config");
|
|
1761
|
+
eq("F67e ... keyed by the bundle package name",
|
|
1762
|
+
survivors[0] && survivors[0].slotSpec.key, "dsh-web-icon-indicator");
|
|
1763
|
+
|
|
1764
|
+
const bundleFirstSink = [];
|
|
1765
|
+
const bundleFirst = makeLazyCtx(bundleFirstSink);
|
|
1766
|
+
client.apply(bundleFirst.ctx);
|
|
1767
|
+
bundleFirst.declare("plugins.bundle.config");
|
|
1768
|
+
bundleFirst.declare("plugins.row.config");
|
|
1769
|
+
eq("F68 bundle declaration first: exactly one entry", bundleFirstSink.length, 1);
|
|
1770
|
+
eq("F68b ... the bundle one", bundleFirstSink[0].slotSpec.name, "plugins.bundle.config");
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1666
1773
|
// ---- card render ---------------------------------------------------------
|
|
1667
1774
|
// React is stubbed, so a re-render is driven explicitly: the card's own
|
|
1668
1775
|
// setState calls `rerender()`, which re-invokes the component with the same
|
|
@@ -2391,6 +2498,7 @@ console.log("\n=== Part 6: browser half (lib/client.js) ===");
|
|
|
2391
2498
|
CLIENT_SRC.indexOf("settingsScope") !== -1 &&
|
|
2392
2499
|
CLIENT_SRC.indexOf("settings.plugin.item") !== -1 &&
|
|
2393
2500
|
CLIENT_SRC.indexOf("configForms") !== -1 &&
|
|
2501
|
+
CLIENT_SRC.indexOf("plugins.bundle.config") !== -1 &&
|
|
2394
2502
|
CLIENT_SRC.indexOf("plugins.row.config") !== -1 &&
|
|
2395
2503
|
CLIENT_SRC.indexOf("whileServed") !== -1);
|
|
2396
2504
|
|