@deepseek-ai/dsh-client-ui-slots 0.0.1-rc.2 → 0.0.1-rc.3
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/README.i18n.yaml +1 -1
- package/README.zh.md +6 -6
- package/lib/index.js +140 -9
- package/lib/invariant.js +1 -1
- package/lib/types/index.d.ts +118 -6
- package/lib/types/renderer.d.ts +25 -2
- package/package.json +3 -3
package/README.i18n.yaml
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-slots/README.md
|
|
5
5
|
README.md: 7ee2a3d22c2a41b0e5c356c52d383a047cf99029
|
|
6
|
-
README.zh.md:
|
|
6
|
+
README.zh.md: 0a61ec43d575429616ad5658228d1e9c93ef8462
|
package/README.zh.md
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
Slot 注册表纯核心、slot 终端设计:SlotMap 声明合并、SlotCore 上唯一的 `register` 组合 API、四 share 组件 props 类型家族、store seat 类型家族,以及 renderer 安装约定。只使用 React 类型;该包不依赖 React,也不依赖 Cordis。
|
|
6
6
|
|
|
7
|
-
一次 `register({ name, children?, store?, inject?, ...kind }, Component)` 调用会向已声明 slot 贡献一个组件,同时声明子 slot(声明 = 渲染授权 = 运行时规范,三者共用一张表)、store seat 以及注册方的业务表层。组件会在调用点依据 `ComposedProps`
|
|
7
|
+
一次 `register({ name, children?, store?, inject?, ...kind }, Component)` 调用会向已声明 slot 贡献一个组件,同时声明子 slot(声明 = 渲染授权 = 运行时规范,三者共用一张表)、store seat 以及注册方的业务表层。组件会在调用点依据 `ComposedProps` 接受类型检查;该类型是四个 share 的交集,每个 share 都从各自的唯一真源派生:
|
|
8
8
|
|
|
9
9
|
| share | 类型 | 来源 |
|
|
10
10
|
|---|---|---|
|
|
11
|
-
|
|
|
11
|
+
| 运行时 | `PropsRuntime<K>` | SlotMap 条目:`owner`(父级 renderSlot 调用点)+ 会话标准工具包 + 全局 seat |
|
|
12
12
|
| child render | `PropsRenderSlots<S>` | register 调用的 `children` key 集合(静态缩窄的 `renderSlot`) |
|
|
13
|
-
| store | `PropsStore<H>` | 已声明 handle:`useStore` selector
|
|
13
|
+
| store | `PropsStore<H>` | 已声明 handle:`useStore` selector 钩子 + 移除 draft 的 `actions` |
|
|
14
14
|
| business | `I` | 从 `inject` factory 返回值推断 |
|
|
15
15
|
|
|
16
16
|
chain-kind slot 会反转键控路由:条目自行提名,而不是由分发点选择 `entryKey`。每次注册都携带一个纯 `ChainSelect` selector(另有可选的升序 `priority`,相同值按注册顺序处理);第一个非 null 返回值选中其条目,并成为组件的 `matched` prop;全部返回 null 时则使用 owner 的 `renderSlotChain` fallback(`ChainRenderOpts`)。
|
|
17
17
|
|
|
18
|
-
标准工具包接口(`SessionStandardProps`、`GlobalStandardProps
|
|
18
|
+
标准工具包接口(`SessionStandardProps`、`GlobalStandardProps`)在这里声明为空,由运行时包合并(与 SlotMap key 相同的 declare-merge 模式)。renderer 会把运行时会话和 Workspace observable source 绑定为 selector 钩子。Inject factory 参数从声明派生(`InjectParams`):会话 slot 获得 `sessionId`;声明 store 时追加 baked `actions`;没有其他参数,数据访问位于 apply 闭包的 ctx 中。
|
|
19
19
|
|
|
20
|
-
store 家族(输入 `defineStore` 规范/输出 `StoreHandle<T, A>`)为 store seat 建模:`init` 推断状态 schema;`actions` 是完整的 draft-transform 写入集合;`BakedActions` 移除 draft 参数,成为组件和 inject factory 收到的回调。`defineStore`
|
|
20
|
+
store 家族(输入 `defineStore` 规范/输出 `StoreHandle<T, A>`)为 store seat 建模:`init` 推断状态 schema;`actions` 是完整的 draft-transform 写入集合;`BakedActions` 移除 draft 参数,成为组件和 inject factory 收到的回调。`defineStore` 值实现位于运行时包(引擎所属位置),并满足这里导出的 `DefineStore` 约定。引擎产物与 renderer host 约定携带裸快照 source(`getSnapshot`/`subscribe`),绝不携带 React 钩子;钩子绑定属于渲染机制,只有 props 约定钩子类型(`SnapshotSelectorHook`)位于这里。
|
|
21
21
|
|
|
22
|
-
`SlotCore` 在构造时预置 `'root'` slot,并强制执行加载时验证(注册未声明 slot、重复声明子项、在两个 scope 下使用同一个共享 handle、chain 注册缺少 `select`,这些情况都在 register 时抛出)。条目的 disposer 会递归移除其声明的子 slot:账本行、贡献和 store 挂载都会随同一生命周期结束而移除。每个 key 还携带一个 declaration epoch
|
|
22
|
+
`SlotCore` 在构造时预置 `'root'` slot,并强制执行加载时验证(注册未声明 slot、重复声明子项、在两个 scope 下使用同一个共享 handle、chain 注册缺少 `select`,这些情况都在 register 时抛出)。条目的 disposer 会递归移除其声明的子 slot:账本行、贡献和 store 挂载都会随同一生命周期结束而移除。每个 key 还携带一个 declaration epoch(声明代次),它只在声明与移除时递增;运行时将其用于 [`ctx.slots.inject`](../runtime/README.md#slot-declaration-injection),且与普通条目版本相互独立。`renderer.ts` 携带安装约定(`SlotRenderer`、`SlotRendererHost`)以及 `StaleAuthorizationError`/`SlotOwnershipError`;实现在 web-react 中,安装则在外壳启动中完成。
|
|
23
23
|
|
|
24
24
|
## 模型体验
|
|
25
25
|
|
package/lib/index.js
CHANGED
|
@@ -32,7 +32,9 @@ const NO_ENTRIES = Object.freeze([]);
|
|
|
32
32
|
* fire); {@link SlotCore.subscribeDeclaration} fires synchronously for each
|
|
33
33
|
* declaration lifetime boundary; {@link SlotCore.subscribe} notifications
|
|
34
34
|
* batch per microtask, so N same-tick mutations produce one notification per
|
|
35
|
-
* touched key.
|
|
35
|
+
* touched key. Entry crash reports ({@link SlotCore.reportEntryError}) ride
|
|
36
|
+
* the same mutation channel when they abdicate, then notify
|
|
37
|
+
* {@link SlotCore.onEntryError} synchronously.
|
|
36
38
|
*/
|
|
37
39
|
var SlotCore = class {
|
|
38
40
|
records = /* @__PURE__ */ new Map();
|
|
@@ -41,6 +43,15 @@ var SlotCore = class {
|
|
|
41
43
|
handleScopes = /* @__PURE__ */ new Map();
|
|
42
44
|
dirty = /* @__PURE__ */ new Set();
|
|
43
45
|
flushScheduled = false;
|
|
46
|
+
/**
|
|
47
|
+
* Entries retired by an abdicating crash report
|
|
48
|
+
* ({@link SlotCore.reportEntryError}): excluded from
|
|
49
|
+
* {@link SlotCore.entriesOfSlot} projections for the rest of their
|
|
50
|
+
* registration's life, while the registration itself stays on the ledger
|
|
51
|
+
* (disposal authority remains with the registrant).
|
|
52
|
+
*/
|
|
53
|
+
abdicated = /* @__PURE__ */ new WeakSet();
|
|
54
|
+
entryErrorListeners = /* @__PURE__ */ new Set();
|
|
44
55
|
constructor() {
|
|
45
56
|
const root = this.record("root");
|
|
46
57
|
root.spec = {
|
|
@@ -54,18 +65,26 @@ var SlotCore = class {
|
|
|
54
65
|
const rec = this.records.get(options.name);
|
|
55
66
|
if (!rec?.spec) throw new Error(`slot "${options.name}" is not declared (a parent entry's children table must declare it)`);
|
|
56
67
|
const spec = rec.spec;
|
|
68
|
+
const priority = options.priority ?? 0;
|
|
69
|
+
const occupantHint = (occupant) => `at priority ${priority}${occupant.registrant !== void 0 ? ` (registered by ${occupant.registrant})` : ""} — register at a different priority to shadow it (lowest renders)`;
|
|
57
70
|
switch (spec.kind) {
|
|
58
|
-
case "single":
|
|
59
|
-
|
|
71
|
+
case "single": {
|
|
72
|
+
const occupant = rec.entries.find((e) => (e.options.priority ?? 0) === priority);
|
|
73
|
+
if (occupant) throw new Error(`single slot "${options.name}" already has a registration ${occupantHint(occupant)}`);
|
|
60
74
|
break;
|
|
61
|
-
|
|
75
|
+
}
|
|
76
|
+
case "keyed": {
|
|
62
77
|
if (options.key === void 0) throw new Error(`keyed slot "${options.name}" requires options.key`);
|
|
63
|
-
|
|
78
|
+
const occupant = rec.entries.find((e) => e.options.key === options.key && (e.options.priority ?? 0) === priority);
|
|
79
|
+
if (occupant) throw new Error(`keyed slot "${options.name}" already has an entry for key "${options.key}" ${occupantHint(occupant)}`);
|
|
64
80
|
break;
|
|
65
|
-
|
|
81
|
+
}
|
|
82
|
+
case "list": {
|
|
66
83
|
if (options.id === void 0) throw new Error(`list slot "${options.name}" requires options.id`);
|
|
67
|
-
|
|
84
|
+
const occupant = rec.entries.find((e) => e.options.id === options.id && (e.options.priority ?? 0) === priority);
|
|
85
|
+
if (occupant) throw new Error(`list slot "${options.name}" already has an entry with id "${options.id}" ${occupantHint(occupant)}`);
|
|
68
86
|
break;
|
|
87
|
+
}
|
|
69
88
|
case "chain":
|
|
70
89
|
if (options.select === void 0) throw new Error(`chain slot "${options.name}" requires options.select`);
|
|
71
90
|
break;
|
|
@@ -100,8 +119,7 @@ var SlotCore = class {
|
|
|
100
119
|
...options.registrant !== void 0 ? { registrant: options.registrant } : {}
|
|
101
120
|
};
|
|
102
121
|
const next = [...rec.entries, entry];
|
|
103
|
-
|
|
104
|
-
if (spec.kind === "chain") next.sort((a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0));
|
|
122
|
+
next.sort(spec.kind === "list" ? (a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0) || (a.options.order ?? 0) - (b.options.order ?? 0) : (a, b) => (a.options.priority ?? 0) - (b.options.priority ?? 0));
|
|
105
123
|
rec.entries = next;
|
|
106
124
|
this.markDirty(options.name, rec);
|
|
107
125
|
if (options.children) {
|
|
@@ -110,6 +128,7 @@ var SlotCore = class {
|
|
|
110
128
|
const childRec = this.record(childKey);
|
|
111
129
|
childRec.spec = childSpec;
|
|
112
130
|
childRec.declaredBy = `an entry in "${options.name}"${options.registrant ? ` (${options.registrant})` : ""}`;
|
|
131
|
+
childRec.parent = options.name;
|
|
113
132
|
childRec.declarationEpoch += 1;
|
|
114
133
|
declarations.push([childKey, childRec]);
|
|
115
134
|
}
|
|
@@ -146,6 +165,34 @@ var SlotCore = class {
|
|
|
146
165
|
return this.records.get(key)?.entries ?? NO_ENTRIES;
|
|
147
166
|
}
|
|
148
167
|
/**
|
|
168
|
+
* Project a key's entries to its shadowing winners: the first live
|
|
169
|
+
* (non-abdicated) entry of each cell in priority order — single: the slot
|
|
170
|
+
* is one cell; keyed: one cell per `key`; list: one cell per `id` (winners
|
|
171
|
+
* keep ledger sequence; list renderers still refine display by `order`).
|
|
172
|
+
* Chain keys return the raw entries unchanged: election consumes every
|
|
173
|
+
* entry, shadowing does not apply. The raw {@link SlotCore.entries} view
|
|
174
|
+
* stays the inspection surface. Builds a fresh array per call — a render
|
|
175
|
+
* body read, not a uSES getSnapshot source.
|
|
176
|
+
* @param key - slot key (dynamic: the render machinery holds keys as strings).
|
|
177
|
+
* @returns the winning entry per occupied cell (empty while undeclared).
|
|
178
|
+
*/
|
|
179
|
+
entriesOfSlot(key) {
|
|
180
|
+
const rec = this.records.get(key);
|
|
181
|
+
if (!rec?.spec) return NO_ENTRIES;
|
|
182
|
+
const kind = rec.spec.kind;
|
|
183
|
+
if (kind === "chain") return rec.entries;
|
|
184
|
+
const heads = [];
|
|
185
|
+
const seenCells = /* @__PURE__ */ new Set();
|
|
186
|
+
for (const entry of rec.entries) {
|
|
187
|
+
if (this.abdicated.has(entry)) continue;
|
|
188
|
+
const cell = kind === "keyed" ? entry.options.key : kind === "list" ? entry.options.id : void 0;
|
|
189
|
+
if (seenCells.has(cell)) continue;
|
|
190
|
+
seenCells.add(cell);
|
|
191
|
+
heads.push(entry);
|
|
192
|
+
}
|
|
193
|
+
return heads;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
149
196
|
* Look up a slot's declared spec, narrowed by the SlotMap key.
|
|
150
197
|
* @param key - SlotMap key.
|
|
151
198
|
* @returns the spec, or undefined while undeclared.
|
|
@@ -164,6 +211,47 @@ var SlotCore = class {
|
|
|
164
211
|
return this.records.get(key)?.spec;
|
|
165
212
|
}
|
|
166
213
|
/**
|
|
214
|
+
* Export the current declaration topology without components or executable hooks.
|
|
215
|
+
* @param root - exact Slot key to select; omitted returns every live root.
|
|
216
|
+
* @returns selected live Slot trees, or an empty array when `root` is unavailable.
|
|
217
|
+
*/
|
|
218
|
+
snapshot(root) {
|
|
219
|
+
const build = (name, seen) => {
|
|
220
|
+
const record = this.records.get(name);
|
|
221
|
+
if (record?.spec === void 0 || seen.has(name)) return void 0;
|
|
222
|
+
const branch = new Set(seen);
|
|
223
|
+
branch.add(name);
|
|
224
|
+
const active = new Set(this.entriesOfSlot(name));
|
|
225
|
+
const children = [...this.records.entries()].filter(([, candidate]) => candidate.spec !== void 0 && candidate.parent === name).flatMap(([child]) => {
|
|
226
|
+
const node = build(child, branch);
|
|
227
|
+
return node === void 0 ? [] : [node];
|
|
228
|
+
});
|
|
229
|
+
return {
|
|
230
|
+
name,
|
|
231
|
+
kind: record.spec.kind,
|
|
232
|
+
scope: record.spec.scope,
|
|
233
|
+
...record.declaredBy === void 0 ? {} : { declaredBy: record.declaredBy },
|
|
234
|
+
occupants: record.entries.map((entry) => ({
|
|
235
|
+
...entry.registrant === void 0 ? {} : { registrant: entry.registrant },
|
|
236
|
+
...entry.options.key === void 0 ? {} : { key: entry.options.key },
|
|
237
|
+
...entry.options.id === void 0 ? {} : { id: entry.options.id },
|
|
238
|
+
...entry.options.order === void 0 ? {} : { order: entry.options.order },
|
|
239
|
+
priority: entry.options.priority ?? 0,
|
|
240
|
+
active: active.has(entry)
|
|
241
|
+
})),
|
|
242
|
+
children
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
if (root !== void 0) {
|
|
246
|
+
const node = build(root, /* @__PURE__ */ new Set());
|
|
247
|
+
return node === void 0 ? [] : [node];
|
|
248
|
+
}
|
|
249
|
+
return [...this.records.entries()].filter(([, record]) => record.spec !== void 0 && (record.parent === void 0 || this.records.get(record.parent)?.spec === void 0)).flatMap(([name]) => {
|
|
250
|
+
const node = build(name, /* @__PURE__ */ new Set());
|
|
251
|
+
return node === void 0 ? [] : [node];
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
167
255
|
* Read the declaration lifetime of a key. Entry additions and removals do
|
|
168
256
|
* not change it; declaration creation and collapse each advance it.
|
|
169
257
|
* @param key - slot key.
|
|
@@ -226,6 +314,47 @@ var SlotCore = class {
|
|
|
226
314
|
};
|
|
227
315
|
}
|
|
228
316
|
/**
|
|
317
|
+
* Renderer crash report from an entry boundary. Always notifies
|
|
318
|
+
* {@link SlotCore.onEntryError} listeners; with `info.abdicate` set (the
|
|
319
|
+
* shadowing kinds — single/keyed/list) it first retires the entry from its
|
|
320
|
+
* cell, one-shot: the record's version bumps through the ordinary mutation
|
|
321
|
+
* channel so outlets re-project onto the cell's next survivor, and a
|
|
322
|
+
* repeat abdicating report no-ops entirely. Chain crashes report with
|
|
323
|
+
* `abdicate: false` — election alternatives resolve at select time, so the
|
|
324
|
+
* entry keeps its cell and only the notification fires. The registration
|
|
325
|
+
* itself stays on the ledger either way — raw {@link SlotCore.entries}
|
|
326
|
+
* still lists the entry and its disposer keeps working.
|
|
327
|
+
* @param key - slot key the entry rendered under.
|
|
328
|
+
* @param entry - the crashed entry.
|
|
329
|
+
* @param error - the crash cause, forwarded to listeners verbatim.
|
|
330
|
+
* @param info - `abdicate`: whether the crash retires the entry from its cell.
|
|
331
|
+
*/
|
|
332
|
+
reportEntryError(key, entry, error, info) {
|
|
333
|
+
if (info.abdicate) {
|
|
334
|
+
if (this.abdicated.has(entry)) return;
|
|
335
|
+
this.abdicated.add(entry);
|
|
336
|
+
const rec = this.records.get(key);
|
|
337
|
+
if (rec !== void 0) this.markDirty(key, rec);
|
|
338
|
+
}
|
|
339
|
+
for (const fn of [...this.entryErrorListeners]) fn(key, entry, error, { abdicated: info.abdicate });
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Observe entry boundary crashes (every render-time entry failure the
|
|
343
|
+
* boundaries contain, abdicating or not) — the supervision seam for hosts
|
|
344
|
+
* mirroring contribution health. Fires synchronously per report, after the
|
|
345
|
+
* registry mutated for abdicating crashes (same listener discipline as
|
|
346
|
+
* {@link SlotCore.onMutate}).
|
|
347
|
+
* @param fn - called with the slot key, the crashed entry, the crash
|
|
348
|
+
* cause, and `abdicated`: whether the crash retired the entry from its cell.
|
|
349
|
+
* @returns unsubscribe.
|
|
350
|
+
*/
|
|
351
|
+
onEntryError(fn) {
|
|
352
|
+
this.entryErrorListeners.add(fn);
|
|
353
|
+
return () => {
|
|
354
|
+
this.entryErrorListeners.delete(fn);
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
229
358
|
* Cascade for a removed entry: release its store mount and collapse every
|
|
230
359
|
* child slot it declared — specs clear, contributions empty (their stale
|
|
231
360
|
* disposers no-op), recursively down the declaration tree. One lifecycle
|
|
@@ -244,6 +373,7 @@ var SlotCore = class {
|
|
|
244
373
|
const doomed = childRec.entries;
|
|
245
374
|
childRec.spec = void 0;
|
|
246
375
|
childRec.declaredBy = void 0;
|
|
376
|
+
childRec.parent = void 0;
|
|
247
377
|
childRec.declarationEpoch += 1;
|
|
248
378
|
childRec.entries = NO_ENTRIES;
|
|
249
379
|
this.markDirty(childKey, childRec);
|
|
@@ -257,6 +387,7 @@ var SlotCore = class {
|
|
|
257
387
|
rec = {
|
|
258
388
|
spec: void 0,
|
|
259
389
|
declaredBy: void 0,
|
|
390
|
+
parent: void 0,
|
|
260
391
|
declarationEpoch: 0,
|
|
261
392
|
entries: NO_ENTRIES,
|
|
262
393
|
version: 0,
|
package/lib/invariant.js
CHANGED
|
@@ -10,7 +10,7 @@ const name = "client-ui-slots-invariant";
|
|
|
10
10
|
const inject = ["invariants"];
|
|
11
11
|
/**
|
|
12
12
|
* No runtime invariant: a zero-dependency pure registry core — it emits no
|
|
13
|
-
* cordis events itself (the runtime
|
|
13
|
+
* cordis events itself (the runtime SlotRegistry wrapper owns the event
|
|
14
14
|
* bridge and its invariants); define/register/dispose sequencing is asserted
|
|
15
15
|
* directly by this package's behavior specs.
|
|
16
16
|
*/
|
package/lib/types/index.d.ts
CHANGED
|
@@ -371,13 +371,20 @@ export type InjectParams<K extends keyof SlotMap & string, H> = ScopeOf<K> exten
|
|
|
371
371
|
* without re-registration. Owners resolve through {@link resolveSlotLabel}.
|
|
372
372
|
*/
|
|
373
373
|
export type SlotLabel = string | (() => string);
|
|
374
|
-
/**
|
|
374
|
+
/**
|
|
375
|
+
* Kind shape fields carried in register options (keyed dispatch key; list
|
|
376
|
+
* id/order/label; chain select/priority; non-chain priority = cell shadowing rank).
|
|
377
|
+
*/
|
|
375
378
|
export type KindOptions<K extends keyof SlotMap & string, EntryKey extends EntryKeyOf<K>, M = never> = SlotMap[K]['kind'] extends 'keyed' ? {
|
|
376
379
|
key: EntryKey;
|
|
380
|
+
/** Cell shadowing rank (ascending, default 0, lowest renders; same key + same priority throws — see {@link SlotCore.register}). */
|
|
381
|
+
priority?: number;
|
|
377
382
|
} : SlotMap[K]['kind'] extends 'list' ? {
|
|
378
383
|
id: string;
|
|
379
384
|
order?: number;
|
|
380
385
|
label?: SlotLabel;
|
|
386
|
+
/** Cell shadowing rank (ascending, default 0, lowest renders; same id + same priority throws — see {@link SlotCore.register}). */
|
|
387
|
+
priority?: number;
|
|
381
388
|
} : SlotMap[K]['kind'] extends 'chain' ? {
|
|
382
389
|
/** Routing selector, mandatory on chain entries; `M` (the component's `matched` prop) infers from its return. */
|
|
383
390
|
select: ChainSelect<SlotMap[K] extends {
|
|
@@ -385,7 +392,13 @@ export type KindOptions<K extends keyof SlotMap & string, EntryKey extends Entry
|
|
|
385
392
|
} ? O : object, M>;
|
|
386
393
|
/** Explicit chain position (ascending, default 0, lower tries first); ties keep registration = assembly order. */
|
|
387
394
|
priority?: number;
|
|
388
|
-
} :
|
|
395
|
+
} : {
|
|
396
|
+
/**
|
|
397
|
+
* Cell shadowing rank (ascending, default 0, lowest renders; a
|
|
398
|
+
* same-priority second registration throws — see {@link SlotCore.register}).
|
|
399
|
+
*/
|
|
400
|
+
priority?: number;
|
|
401
|
+
};
|
|
389
402
|
/**
|
|
390
403
|
* Compile-time presence check: an entry declaring children MUST consume
|
|
391
404
|
* `renderSlot` (or `renderSlotChain` when its only children are chain slots)
|
|
@@ -451,6 +464,36 @@ export interface StoredEntry {
|
|
|
451
464
|
* @returns the display string, or undefined when the entry declared none.
|
|
452
465
|
*/
|
|
453
466
|
export declare function resolveSlotLabel(label: SlotLabel | undefined): string | undefined;
|
|
467
|
+
/** JSON-safe live occupant returned by slot inspection. */
|
|
468
|
+
export interface LiveSlotOccupant {
|
|
469
|
+
/** Plugin or package that registered the entry, when known. */
|
|
470
|
+
registrant?: string;
|
|
471
|
+
/** Keyed-slot cell. */
|
|
472
|
+
key?: string;
|
|
473
|
+
/** List-slot cell. */
|
|
474
|
+
id?: string;
|
|
475
|
+
/** List display order. */
|
|
476
|
+
order?: number;
|
|
477
|
+
/** Shadowing or chain priority. */
|
|
478
|
+
priority: number;
|
|
479
|
+
/** Whether the renderer currently selects this entry. */
|
|
480
|
+
active: boolean;
|
|
481
|
+
}
|
|
482
|
+
/** JSON-safe live slot declaration tree. */
|
|
483
|
+
export interface LiveSlotNode {
|
|
484
|
+
/** Exact SlotMap key. */
|
|
485
|
+
name: string;
|
|
486
|
+
/** Slot cardinality. */
|
|
487
|
+
kind: SlotKind;
|
|
488
|
+
/** Runtime data scope. */
|
|
489
|
+
scope: SlotScope;
|
|
490
|
+
/** Diagnostic owner of this declaration. */
|
|
491
|
+
declaredBy?: string;
|
|
492
|
+
/** Current registrations in ledger order. */
|
|
493
|
+
occupants: LiveSlotOccupant[];
|
|
494
|
+
/** Slots declared by entries mounted in this slot. */
|
|
495
|
+
children: LiveSlotNode[];
|
|
496
|
+
}
|
|
454
497
|
/**
|
|
455
498
|
* Pure slot registry (no cordis; event emission and the renderer installation contract
|
|
456
499
|
* live in the runtime Service wrapper).
|
|
@@ -463,7 +506,9 @@ export declare function resolveSlotLabel(label: SlotLabel | undefined): string |
|
|
|
463
506
|
* fire); {@link SlotCore.subscribeDeclaration} fires synchronously for each
|
|
464
507
|
* declaration lifetime boundary; {@link SlotCore.subscribe} notifications
|
|
465
508
|
* batch per microtask, so N same-tick mutations produce one notification per
|
|
466
|
-
* touched key.
|
|
509
|
+
* touched key. Entry crash reports ({@link SlotCore.reportEntryError}) ride
|
|
510
|
+
* the same mutation channel when they abdicate, then notify
|
|
511
|
+
* {@link SlotCore.onEntryError} synchronously.
|
|
467
512
|
*/
|
|
468
513
|
export declare class SlotCore {
|
|
469
514
|
private records;
|
|
@@ -472,6 +517,15 @@ export declare class SlotCore {
|
|
|
472
517
|
private handleScopes;
|
|
473
518
|
private dirty;
|
|
474
519
|
private flushScheduled;
|
|
520
|
+
/**
|
|
521
|
+
* Entries retired by an abdicating crash report
|
|
522
|
+
* ({@link SlotCore.reportEntryError}): excluded from
|
|
523
|
+
* {@link SlotCore.entriesOfSlot} projections for the rest of their
|
|
524
|
+
* registration's life, while the registration itself stays on the ledger
|
|
525
|
+
* (disposal authority remains with the registrant).
|
|
526
|
+
*/
|
|
527
|
+
private abdicated;
|
|
528
|
+
private entryErrorListeners;
|
|
475
529
|
constructor();
|
|
476
530
|
/**
|
|
477
531
|
* Contribute a component to a declared slot and (optionally) declare child
|
|
@@ -481,11 +535,18 @@ export declare class SlotCore {
|
|
|
481
535
|
* re-checks nothing): registering into an undeclared slot throws; declaring
|
|
482
536
|
* an already-declared child key throws (one declarer per slot — the message
|
|
483
537
|
* names the first declarer); mounting one shared store handle under slots
|
|
484
|
-
* of different scopes throws. Kind constraints:
|
|
485
|
-
*
|
|
486
|
-
* missing/duplicate `id` throws; chain — missing `select` throws (the
|
|
538
|
+
* of different scopes throws. Kind constraints: keyed — missing `key`
|
|
539
|
+
* throws; list — missing `id` throws; chain — missing `select` throws (the
|
|
487
540
|
* selector is the entry's routing seat, see {@link ChainSelect}).
|
|
488
541
|
*
|
|
542
|
+
* Shadowing (single/keyed/list): entries sharing one cell (single — the
|
|
543
|
+
* slot itself; keyed — same `key`; list — same `id`) coexist at distinct
|
|
544
|
+
* priorities, sorted ascending with ties keeping registration order; the
|
|
545
|
+
* cell's lowest live entry renders ({@link SlotCore.entriesOfSlot}). A
|
|
546
|
+
* second registration at an occupied cell's exact priority (default 0)
|
|
547
|
+
* throws naming the occupant, so priority-less composition keeps the
|
|
548
|
+
* historical one-occupant-per-cell fail-loud.
|
|
549
|
+
*
|
|
489
550
|
* Lifecycle: the disposer removes the contribution AND collapses every
|
|
490
551
|
* declared child slot (child entries clear recursively; their stale
|
|
491
552
|
* disposers become no-ops) — one lifecycle axis, no dangling state.
|
|
@@ -531,6 +592,19 @@ export declare class SlotCore {
|
|
|
531
592
|
* @returns entries in registration (list: order) sequence.
|
|
532
593
|
*/
|
|
533
594
|
entries(key: string): readonly StoredEntry[];
|
|
595
|
+
/**
|
|
596
|
+
* Project a key's entries to its shadowing winners: the first live
|
|
597
|
+
* (non-abdicated) entry of each cell in priority order — single: the slot
|
|
598
|
+
* is one cell; keyed: one cell per `key`; list: one cell per `id` (winners
|
|
599
|
+
* keep ledger sequence; list renderers still refine display by `order`).
|
|
600
|
+
* Chain keys return the raw entries unchanged: election consumes every
|
|
601
|
+
* entry, shadowing does not apply. The raw {@link SlotCore.entries} view
|
|
602
|
+
* stays the inspection surface. Builds a fresh array per call — a render
|
|
603
|
+
* body read, not a uSES getSnapshot source.
|
|
604
|
+
* @param key - slot key (dynamic: the render machinery holds keys as strings).
|
|
605
|
+
* @returns the winning entry per occupied cell (empty while undeclared).
|
|
606
|
+
*/
|
|
607
|
+
entriesOfSlot(key: string): readonly StoredEntry[];
|
|
534
608
|
/**
|
|
535
609
|
* Look up a slot's declared spec, narrowed by the SlotMap key.
|
|
536
610
|
* @param key - SlotMap key.
|
|
@@ -545,6 +619,12 @@ export declare class SlotCore {
|
|
|
545
619
|
* @returns the wide-typed spec, or undefined while undeclared.
|
|
546
620
|
*/
|
|
547
621
|
specDynamic(key: string): SlotSpec<SlotEntryDef> | undefined;
|
|
622
|
+
/**
|
|
623
|
+
* Export the current declaration topology without components or executable hooks.
|
|
624
|
+
* @param root - exact Slot key to select; omitted returns every live root.
|
|
625
|
+
* @returns selected live Slot trees, or an empty array when `root` is unavailable.
|
|
626
|
+
*/
|
|
627
|
+
snapshot(root?: string): LiveSlotNode[];
|
|
548
628
|
/**
|
|
549
629
|
* Read the declaration lifetime of a key. Entry additions and removals do
|
|
550
630
|
* not change it; declaration creation and collapse each advance it.
|
|
@@ -586,6 +666,38 @@ export declare class SlotCore {
|
|
|
586
666
|
* @returns unsubscribe.
|
|
587
667
|
*/
|
|
588
668
|
onMutate(fn: (key: string) => void): () => void;
|
|
669
|
+
/**
|
|
670
|
+
* Renderer crash report from an entry boundary. Always notifies
|
|
671
|
+
* {@link SlotCore.onEntryError} listeners; with `info.abdicate` set (the
|
|
672
|
+
* shadowing kinds — single/keyed/list) it first retires the entry from its
|
|
673
|
+
* cell, one-shot: the record's version bumps through the ordinary mutation
|
|
674
|
+
* channel so outlets re-project onto the cell's next survivor, and a
|
|
675
|
+
* repeat abdicating report no-ops entirely. Chain crashes report with
|
|
676
|
+
* `abdicate: false` — election alternatives resolve at select time, so the
|
|
677
|
+
* entry keeps its cell and only the notification fires. The registration
|
|
678
|
+
* itself stays on the ledger either way — raw {@link SlotCore.entries}
|
|
679
|
+
* still lists the entry and its disposer keeps working.
|
|
680
|
+
* @param key - slot key the entry rendered under.
|
|
681
|
+
* @param entry - the crashed entry.
|
|
682
|
+
* @param error - the crash cause, forwarded to listeners verbatim.
|
|
683
|
+
* @param info - `abdicate`: whether the crash retires the entry from its cell.
|
|
684
|
+
*/
|
|
685
|
+
reportEntryError(key: string, entry: StoredEntry, error: unknown, info: {
|
|
686
|
+
abdicate: boolean;
|
|
687
|
+
}): void;
|
|
688
|
+
/**
|
|
689
|
+
* Observe entry boundary crashes (every render-time entry failure the
|
|
690
|
+
* boundaries contain, abdicating or not) — the supervision seam for hosts
|
|
691
|
+
* mirroring contribution health. Fires synchronously per report, after the
|
|
692
|
+
* registry mutated for abdicating crashes (same listener discipline as
|
|
693
|
+
* {@link SlotCore.onMutate}).
|
|
694
|
+
* @param fn - called with the slot key, the crashed entry, the crash
|
|
695
|
+
* cause, and `abdicated`: whether the crash retired the entry from its cell.
|
|
696
|
+
* @returns unsubscribe.
|
|
697
|
+
*/
|
|
698
|
+
onEntryError(fn: (key: string, entry: StoredEntry, error: unknown, info: {
|
|
699
|
+
abdicated: boolean;
|
|
700
|
+
}) => void): () => void;
|
|
589
701
|
/**
|
|
590
702
|
* Cascade for a removed entry: release its store mount and collapse every
|
|
591
703
|
* child slot it declared — specs clear, contributions empty (their stale
|
package/lib/types/renderer.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { SlotEntryDef, SlotSpec, StoredEntry, Translate } from './index.ts'
|
|
|
8
8
|
* active-locale or registry change; the renderer re-derives each entry's `t`
|
|
9
9
|
* from (namespace, revision), so a locale switch hands out NEW function
|
|
10
10
|
* references and memoized components re-render naturally. Implemented by the
|
|
11
|
-
* locale plugin, installed through the runtime
|
|
11
|
+
* locale plugin, installed through the runtime SlotRegistry (installLocale).
|
|
12
12
|
* Install before the first render that needs the seat: outlets bind their
|
|
13
13
|
* revision subscription at mount, and a face appearing later has no channel
|
|
14
14
|
* to notify already-mounted outlets (the locale plugin is immediately-tier
|
|
@@ -94,7 +94,7 @@ export interface RenderOpts {
|
|
|
94
94
|
/** Opaque occurrence context consumed only by function-valued injected Hooks. */
|
|
95
95
|
hookContext?: unknown;
|
|
96
96
|
}
|
|
97
|
-
/** Host API the runtime
|
|
97
|
+
/** Host API the runtime SlotRegistry presents to the installed renderer. */
|
|
98
98
|
export interface SlotRendererHost {
|
|
99
99
|
/**
|
|
100
100
|
* Subscribe to a key's registration changes (microtask-batched).
|
|
@@ -115,6 +115,29 @@ export interface SlotRendererHost {
|
|
|
115
115
|
* @returns entries in registration (list: order) sequence.
|
|
116
116
|
*/
|
|
117
117
|
entriesOf(key: string): readonly StoredEntry[];
|
|
118
|
+
/**
|
|
119
|
+
* Shadowing winners per cell for a key — the render read for single/keyed/
|
|
120
|
+
* list dispatch: the first live (non-abdicated) entry of each cell in
|
|
121
|
+
* priority order; chain keys pass through unchanged (election consumes
|
|
122
|
+
* every entry). Fresh array per call — a render-body read, not a uSES
|
|
123
|
+
* getSnapshot source.
|
|
124
|
+
* @param key - slot key.
|
|
125
|
+
* @returns the winning entry per occupied cell.
|
|
126
|
+
*/
|
|
127
|
+
entriesOfSlot(key: string): readonly StoredEntry[];
|
|
128
|
+
/**
|
|
129
|
+
* Report an entry boundary crash. With `info.abdicate` (shadowing kinds)
|
|
130
|
+
* the entry retires from its cell, one-shot, so the next survivor renders;
|
|
131
|
+
* chain crashes report without abdicating. The registration stays on the
|
|
132
|
+
* ledger either way.
|
|
133
|
+
* @param key - slot key the entry rendered under.
|
|
134
|
+
* @param entry - the crashed entry.
|
|
135
|
+
* @param error - the crash cause.
|
|
136
|
+
* @param info - `abdicate`: whether the crash retires the entry from its cell.
|
|
137
|
+
*/
|
|
138
|
+
reportEntryError(key: string, entry: StoredEntry, error: unknown, info: {
|
|
139
|
+
abdicate: boolean;
|
|
140
|
+
}): void;
|
|
118
141
|
/**
|
|
119
142
|
* Declared runtime spec from the declarations ledger.
|
|
120
143
|
* @param key - slot key.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-slots",
|
|
3
3
|
"description": "Slot registry pure core: SlotMap declaration merging, single register composition API, four-share props types, store-seat types, renderer install seam",
|
|
4
|
-
"version": "0.0.1-rc.
|
|
4
|
+
"version": "0.0.1-rc.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "restricted"
|
|
7
7
|
},
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"license": "BSD-3-Clause",
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/react": "~18.3.1",
|
|
31
|
-
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.
|
|
31
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
|
|
32
32
|
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"lib/types/**/*.d.ts"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.
|
|
40
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
|
|
41
41
|
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
42
42
|
}
|
|
43
43
|
}
|