@monotykamary/dsh-client-ui-renderer 0.1.0-rc.10

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/ui-renderer/README.md
5
+ README.md: 604b563b5b0e5c4e8ac99ed27dda1b16ada1443b
6
+ README.zh.md: d711def4ed6c75085a77a82928e2773c2c4d8e78
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @monotykamary/dsh-client-ui-renderer
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ The browser Cordis plugin that owns the React rendering layer. [`dsh-client-web`](../web/README.md) renders a framework-free boot page and loads the complete client plugin roster; after every entry activates, it calls `ctx.uiRenderer.mount(container)`. This package provides that service, installs the slot renderer, hydrates the existing boot DOM, switches to the assembled application before the next paint, and returns the React root's unmount disposer.
6
+
7
+ The client entry also owns the React implementation of slot outlets, session providers, and observable-to-uSES binding. Business plugins pass bare observable sources through typed slot `hooks`; the renderer binds them at the outlet. The plugin activates after `slots`, `sessions`, and `layout`, projects the selected session title, and performs the sole context-level `renderSlot('root')` call. React, React DOM, Cordis, ui-slots, and ui-primitives retain one browser identity through the web shell's static module table; this package arrives as a dynamic client bundle.
8
+
9
+ ## Model Experience
10
+
11
+ None, as the UI renderer only assembles browser UI and contributes no model-visible input.
12
+
13
+ #### KV Cache effect
14
+
15
+ None; this package neither assembles nor sends provider requests.
16
+
17
+ ## Known Limitations and Deferred Work
18
+
19
+ - **The first application frame waits for every client entry** — the boot kernel hands over the mount point only after the loader roster settles. Per-region readiness remains deferred.
20
+ - **Slot rendering has no Suspense integration or per-entry lazy loading** — the complete plugin roster settles before the renderer mounts the root.
package/README.zh.md ADDED
@@ -0,0 +1,20 @@
1
+ # @monotykamary/dsh-client-ui-renderer
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 负责 React 渲染层的浏览器 Cordis 插件。[`dsh-client-web`](../web/README.md) 渲染不依赖框架的启动页并加载完整的客户端插件名册;所有 entry 激活后,它调用 `ctx.uiRenderer.mount(container)`。本包提供该服务、安装 slot 渲染器、hydrate 现有启动 DOM、在下一次绘制前切换到组装完成的应用,并返回 React 根的卸载 disposer。
6
+
7
+ client entry 还持有 slot outlet、会话 provider 以及 observable 到 uSES 绑定的 React 实现。业务插件通过带类型的 slot `hooks` 传递裸 observable source;渲染器在 outlet 处完成绑定。插件在 `slots`、`sessions` 和 `layout` 就绪后激活,投影当前会话标题,并执行全程序唯一一次上下文级 `renderSlot('root')` 调用。React、React DOM、Cordis、ui-slots 和 ui-primitives 通过 web 外壳的静态模块表保持同一浏览器身份;本包则以动态客户端 bundle 到达。
8
+
9
+ ## 模型体验
10
+
11
+ 无,因为 UI 渲染器只组装浏览器 UI,不贡献模型可见输入。
12
+
13
+ #### KV Cache 影响
14
+
15
+ 无;本包既不组装也不发送提供方请求。
16
+
17
+ ## 已知限制与暂缓事项
18
+
19
+ - **应用首帧会等待全部客户端 entry**——启动内核只在 loader 名册稳定后交出挂载点。按区域就绪仍属暂缓事项。
20
+ - **slot 渲染没有 Suspense 集成或逐 entry 惰性加载**——完整插件名册稳定后,渲染器才挂载根节点。
package/lib/client.js ADDED
@@ -0,0 +1,989 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@monotykamary/dsh-client-ui-renderer",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region \0rolldown/runtime.js
8
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
+ //#endregion
10
+ let react = require("react");
11
+ let react_dom = require("react-dom");
12
+ let react_dom_client = require("react-dom/client");
13
+ let react_jsx_runtime = require("react/jsx-runtime");
14
+ let _monotykamary_dsh_client_ui_slots = require("@monotykamary/dsh-client-ui-slots");
15
+ //#region ../../../../../../../Library/pnpm/store/v11/links/@/use-sync-external-store/1.2.0/a9a76b595d3a7829a4b24b4d9eaaf6f4abecceea73ded26120b3386303ce5091/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.min.js
16
+ /**
17
+ * @license React
18
+ * use-sync-external-store-shim.production.min.js
19
+ *
20
+ * Copyright (c) Facebook, Inc. and its affiliates.
21
+ *
22
+ * This source code is licensed under the MIT license found in the
23
+ * LICENSE file in the root directory of this source tree.
24
+ */
25
+ var require_use_sync_external_store_shim_production_min = /* @__PURE__ */ __commonJSMin(((exports) => {
26
+ var e = require("react");
27
+ function h(a, b) {
28
+ return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b;
29
+ }
30
+ var k = "function" === typeof Object.is ? Object.is : h, l = e.useState, m = e.useEffect, n = e.useLayoutEffect, p = e.useDebugValue;
31
+ function q(a, b) {
32
+ var d = b(), f = l({ inst: {
33
+ value: d,
34
+ getSnapshot: b
35
+ } }), c = f[0].inst, g = f[1];
36
+ n(function() {
37
+ c.value = d;
38
+ c.getSnapshot = b;
39
+ r(c) && g({ inst: c });
40
+ }, [
41
+ a,
42
+ d,
43
+ b
44
+ ]);
45
+ m(function() {
46
+ r(c) && g({ inst: c });
47
+ return a(function() {
48
+ r(c) && g({ inst: c });
49
+ });
50
+ }, [a]);
51
+ p(d);
52
+ return d;
53
+ }
54
+ function r(a) {
55
+ var b = a.getSnapshot;
56
+ a = a.value;
57
+ try {
58
+ var d = b();
59
+ return !k(a, d);
60
+ } catch (f) {
61
+ return !0;
62
+ }
63
+ }
64
+ function t(a, b) {
65
+ return b();
66
+ }
67
+ var u = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? t : q;
68
+ exports.useSyncExternalStore = void 0 !== e.useSyncExternalStore ? e.useSyncExternalStore : u;
69
+ }));
70
+ //#endregion
71
+ //#region ../../../../../../../Library/pnpm/store/v11/links/@/use-sync-external-store/1.2.0/a9a76b595d3a7829a4b24b4d9eaaf6f4abecceea73ded26120b3386303ce5091/node_modules/use-sync-external-store/shim/index.js
72
+ var require_shim = /* @__PURE__ */ __commonJSMin(((exports, module) => {
73
+ module.exports = require_use_sync_external_store_shim_production_min();
74
+ }));
75
+ //#endregion
76
+ //#region ../../../../../../../Library/pnpm/store/v11/links/@/use-sync-external-store/1.2.0/a9a76b595d3a7829a4b24b4d9eaaf6f4abecceea73ded26120b3386303ce5091/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.production.min.js
77
+ /**
78
+ * @license React
79
+ * use-sync-external-store-shim/with-selector.production.min.js
80
+ *
81
+ * Copyright (c) Facebook, Inc. and its affiliates.
82
+ *
83
+ * This source code is licensed under the MIT license found in the
84
+ * LICENSE file in the root directory of this source tree.
85
+ */
86
+ var require_with_selector_production_min = /* @__PURE__ */ __commonJSMin(((exports) => {
87
+ var h = require("react"), n = require_shim();
88
+ function p(a, b) {
89
+ return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b;
90
+ }
91
+ var q = "function" === typeof Object.is ? Object.is : p, r = n.useSyncExternalStore, t = h.useRef, u = h.useEffect, v = h.useMemo, w = h.useDebugValue;
92
+ exports.useSyncExternalStoreWithSelector = function(a, b, e, l, g) {
93
+ var c = t(null);
94
+ if (null === c.current) {
95
+ var f = {
96
+ hasValue: !1,
97
+ value: null
98
+ };
99
+ c.current = f;
100
+ } else f = c.current;
101
+ c = v(function() {
102
+ function a(a) {
103
+ if (!c) {
104
+ c = !0;
105
+ d = a;
106
+ a = l(a);
107
+ if (void 0 !== g && f.hasValue) {
108
+ var b = f.value;
109
+ if (g(b, a)) return k = b;
110
+ }
111
+ return k = a;
112
+ }
113
+ b = k;
114
+ if (q(d, a)) return b;
115
+ var e = l(a);
116
+ if (void 0 !== g && g(b, e)) return b;
117
+ d = a;
118
+ return k = e;
119
+ }
120
+ var c = !1, d, k, m = void 0 === e ? null : e;
121
+ return [function() {
122
+ return a(b());
123
+ }, null === m ? void 0 : function() {
124
+ return a(m());
125
+ }];
126
+ }, [
127
+ b,
128
+ e,
129
+ l,
130
+ g
131
+ ]);
132
+ var d = r(a, c[0], c[1]);
133
+ u(function() {
134
+ f.hasValue = !0;
135
+ f.value = d;
136
+ }, [d]);
137
+ w(d);
138
+ return d;
139
+ };
140
+ }));
141
+ //#endregion
142
+ //#region lib/types/client/bind.js
143
+ var import_with_selector = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
144
+ module.exports = require_with_selector_production_min();
145
+ })))();
146
+ /**
147
+ * Bind a bare observable source to a typed uSES selector hook.
148
+ * subscribe/getSnapshot are captured once per source into stable closures
149
+ * (also re-binds `this` for method-based sources), so components never
150
+ * resubscribe across renders. Equality defaults to Object.is.
151
+ * @param w - snapshot source (engine store, Session object, store instance).
152
+ * @returns the selector hook.
153
+ */
154
+ function bindSnapshotSelector(w) {
155
+ const subscribe = (fn) => w.subscribe(fn);
156
+ const getSnapshot = () => w.getSnapshot();
157
+ return function useSelector(sel, eq) {
158
+ return (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, void 0, sel, eq);
159
+ };
160
+ }
161
+ //#endregion
162
+ //#region lib/types/client/session-provider.js
163
+ /** Internal React bindings for the renderer host and active session provide bundle. */
164
+ /**
165
+ * A missing-provider assembly error: the shell wired the tree wrong. The slot
166
+ * error boundary rethrows this class so misassembly stays fail-loud while
167
+ * registrant errors (inject factories, entry components) are contained
168
+ * per entry.
169
+ */
170
+ var SlotAssemblyError = class extends Error {};
171
+ /** In-package renderer host context. */
172
+ const HostContext = (0, react.createContext)(null);
173
+ /**
174
+ * Read the installed renderer host; throws outside the rendered root tree
175
+ * (framework components must not render detached from the renderer).
176
+ * @returns the host API.
177
+ */
178
+ function useHost() {
179
+ const host = (0, react.useContext)(HostContext);
180
+ if (!host) throw new SlotAssemblyError("slot machinery rendered outside the installed renderer tree");
181
+ return host;
182
+ }
183
+ const BindingContext = (0, react.createContext)(null);
184
+ /** Read the current-session-optional bundle supplied at the root. */
185
+ function useSessionMaybeProvideInfo() {
186
+ const info = (0, react.useContext)(BindingContext);
187
+ if (!info) throw new SlotAssemblyError("session-aware slot rendered outside the root binding provider");
188
+ return info;
189
+ }
190
+ /**
191
+ * Identity-stable selector hook per host observable. uSES resubscribes when
192
+ * the subscribe reference changes, so the bound hook must be created once per
193
+ * source — cached here by source identity (sources are host-owned singletons).
194
+ * @param source - host-provided observable.
195
+ * @returns the cached selector hook.
196
+ */
197
+ function observableHook(source) {
198
+ let hook = hookCache.get(source);
199
+ if (hook === void 0) {
200
+ hook = bindSnapshotSelector(source);
201
+ hookCache.set(source, hook);
202
+ }
203
+ return hook;
204
+ }
205
+ const hookCache = /* @__PURE__ */ new WeakMap();
206
+ const absentSource = {
207
+ getSnapshot: () => void 0,
208
+ subscribe: () => () => {}
209
+ };
210
+ /** Bind a source that disappears with the current session to an optional selector hook. */
211
+ function maybeObservableHook(source) {
212
+ if (source !== void 0) return observableHook(source);
213
+ return useAbsentSnapshot;
214
+ }
215
+ function useAbsentSnapshot(_selector, _equal) {
216
+ observableHook(absentSource)(() => void 0);
217
+ }
218
+ /**
219
+ * The useProjection framework seat (docs/subsystems/session-projection.md), one bound
220
+ * function per provide bundle (cached by info identity — components may hold
221
+ * it across renders). Key-addressed: the key resolves a per-session value
222
+ * face off the projection store; the bound selector hook comes from the same
223
+ * per-source cache as every other kit hook, so exactly one uSES subscription
224
+ * runs per call and the subscribe reference stays stable per key. A key no
225
+ * baseline or frame has carried (or a no-session bundle) reads `undefined` —
226
+ * capability absence — keeping the hook order constant.
227
+ */
228
+ function projectionHook(info) {
229
+ let hook = projectionHookCache.get(info);
230
+ if (hook === void 0) {
231
+ hook = (key, selector, eq) => {
232
+ return observableHook(info.projections?.faceOf(key) ?? absentSource)(selector ?? ((value) => value), eq);
233
+ };
234
+ projectionHookCache.set(info, hook);
235
+ }
236
+ return hook;
237
+ }
238
+ const projectionHookCache = /* @__PURE__ */ new WeakMap();
239
+ /**
240
+ * Root-level binding provider. It follows current selection without a key;
241
+ * per-entry identity is the outlet's adoption bookkeeping (SessionMaybeEntry):
242
+ * a blank-born incarnation adopts the first session without remounting, and
243
+ * every later transition (switch or loss) remounts like a strict entry.
244
+ */
245
+ function SessionMaybeProvider({ children }) {
246
+ const info = observableHook(useHost().sessions.provideInfo)((s) => s);
247
+ return (0, react_jsx_runtime.jsx)(BindingContext.Provider, {
248
+ value: info,
249
+ children
250
+ });
251
+ }
252
+ /**
253
+ * Framework-wired session area: subscribes to the host's current provide
254
+ * source and remounts the body under `key={sessionId}` so a session switch
255
+ * rebuilds the session subtree. This dependency-inverted layer uses plain
256
+ * string ids; `PropsRuntime` applies the branded type at the component
257
+ * boundary.
258
+ */
259
+ function SessionProvider({ empty, children }) {
260
+ const info = observableHook(useHost().sessions.provideInfo)((s) => s);
261
+ const id = info.sessionId;
262
+ if (id === void 0) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: empty?.() ?? null });
263
+ return (0, react_jsx_runtime.jsx)(BindingContext.Provider, {
264
+ value: info,
265
+ children: children(id)
266
+ }, id);
267
+ }
268
+ //#endregion
269
+ //#region lib/types/client/scoped-slots.js
270
+ /**
271
+ * React renderer for declarative slots. Per-entry bindings enforce child
272
+ * authorization, and entry boundaries contain registrant failures.
273
+ */
274
+ /**
275
+ * Per-entry renderSlot bindings. The binding is identity-stable per entry
276
+ * (memoized components must not resubscribe on unrelated re-renders) and dies
277
+ * with the entry: a retained closure calling after the entry's disposal hits
278
+ * the in-ledger check and throws.
279
+ */
280
+ const renderSlotCache = /* @__PURE__ */ new WeakMap();
281
+ function boundRenderSlot(host, entry) {
282
+ let binding = renderSlotCache.get(entry);
283
+ if (!binding) {
284
+ binding = (key, owner, opts) => {
285
+ if (!host.isLive(entry)) throw new _monotykamary_dsh_client_ui_slots.StaleAuthorizationError(`renderSlot('${key}') from a disposed registration`);
286
+ const declared = entry.children?.[key];
287
+ if (declared === void 0) throw new _monotykamary_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is not declared by this entry's children`);
288
+ if (declared.kind === "chain") throw new _monotykamary_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is declared 'chain' — use renderSlotChain`);
289
+ return (0, react_jsx_runtime.jsx)(SlotOutlet, {
290
+ slotKey: key,
291
+ ownerProps: owner,
292
+ opts
293
+ });
294
+ };
295
+ renderSlotCache.set(entry, binding);
296
+ }
297
+ return binding;
298
+ }
299
+ /**
300
+ * Per-entry renderSlotChain bindings: identity-stable per entry (same cache
301
+ * axis as renderSlot — a per-frame dispatch must not rebuild the binding) and
302
+ * dead with the entry. The chain-kind check is the plain-JS backstop twin of
303
+ * the declaration check; typed callers are narrowed to chain keys.
304
+ */
305
+ const renderSlotChainCache = /* @__PURE__ */ new WeakMap();
306
+ function boundRenderSlotChain(host, entry) {
307
+ let binding = renderSlotChainCache.get(entry);
308
+ if (!binding) {
309
+ binding = (key, owner, opts) => {
310
+ if (!host.isLive(entry)) throw new _monotykamary_dsh_client_ui_slots.StaleAuthorizationError(`renderSlotChain('${key}') from a disposed registration`);
311
+ const declared = entry.children?.[key];
312
+ if (declared === void 0) throw new _monotykamary_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is not declared by this entry's children`);
313
+ if (declared.kind !== "chain") throw new _monotykamary_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is declared '${declared.kind}', not 'chain' — use renderSlot`);
314
+ return (0, react_jsx_runtime.jsx)(SlotOutlet, {
315
+ slotKey: key,
316
+ ownerProps: owner,
317
+ opts
318
+ });
319
+ };
320
+ renderSlotChainCache.set(entry, binding);
321
+ }
322
+ return binding;
323
+ }
324
+ /**
325
+ * Inject results cache: root entries per entry, session entries per
326
+ * (entry x provide bundle). WeakMap keys are entry/info objects (both
327
+ * identity-stable per registration/session scope), so cache lifetime rides
328
+ * the same axes as the values it memoizes.
329
+ */
330
+ const rootInjectCache = /* @__PURE__ */ new WeakMap();
331
+ const sessionInjectCache = /* @__PURE__ */ new WeakMap();
332
+ const sessionMaybeInjectCache = /* @__PURE__ */ new WeakMap();
333
+ const EMPTY_INJECTED_PROPS = {};
334
+ function runInject(entry, info, actions) {
335
+ const inject = entry.inject;
336
+ if (!inject) return EMPTY_INJECTED_PROPS;
337
+ const args = [];
338
+ if (info !== void 0) args.push(info.sessionId);
339
+ if (actions !== void 0) args.push(actions);
340
+ return bindInjectHooks(inject(...args));
341
+ }
342
+ /**
343
+ * Normalize one entry-owned inject face on its existing cache axis. Its hooks
344
+ * compartment remains the original Observable-only contract.
345
+ */
346
+ function bindInjectHooks(face) {
347
+ const sources = face["hooks"];
348
+ if (sources === void 0) return face;
349
+ const { hooks: _hooks, ...rest } = face;
350
+ const bound = rest;
351
+ for (const [name, source] of Object.entries(sources)) {
352
+ const hookName = `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
353
+ bound[hookName] = observableHook(source);
354
+ }
355
+ return bound;
356
+ }
357
+ const slotInjectCache = /* @__PURE__ */ new WeakMap();
358
+ const EMPTY_SLOT_INJECT = { props: EMPTY_INJECTED_PROPS };
359
+ /** Normalize one dispatcher-owned inject face by its stable object identity. */
360
+ function cachedSlotInject(face) {
361
+ if (face === void 0) return EMPTY_SLOT_INJECT;
362
+ let bound = slotInjectCache.get(face);
363
+ if (bound !== void 0) return bound;
364
+ const definitions = face["hooks"];
365
+ if (definitions === void 0) {
366
+ bound = { props: face };
367
+ slotInjectCache.set(face, bound);
368
+ return bound;
369
+ }
370
+ const { hooks: _hooks, ...rest } = face;
371
+ const props = rest;
372
+ let factories;
373
+ for (const [name, definition] of Object.entries(definitions)) {
374
+ const hookName = `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
375
+ if (typeof definition === "function") {
376
+ factories ??= {};
377
+ factories[name] = definition;
378
+ } else props[hookName] = observableHook(definition);
379
+ }
380
+ bound = factories === void 0 ? { props } : {
381
+ props,
382
+ slotHookFactories: factories
383
+ };
384
+ slotInjectCache.set(face, bound);
385
+ return bound;
386
+ }
387
+ /** Bind deferred slot-level factories for one stable renderSlot occurrence. */
388
+ function bindSlotHookFactories(factories, standard, hookContext) {
389
+ const hooks = {};
390
+ for (const [name, factory] of Object.entries(factories)) {
391
+ const hookName = `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
392
+ hooks[hookName] = factory(standard, hookContext);
393
+ }
394
+ return hooks;
395
+ }
396
+ function cachedRootInject(entry, actions) {
397
+ let props = rootInjectCache.get(entry);
398
+ if (!props) {
399
+ props = runInject(entry, void 0, actions);
400
+ rootInjectCache.set(entry, props);
401
+ }
402
+ return props;
403
+ }
404
+ function cachedSessionInject(entry, info, actions) {
405
+ let perInfo = sessionInjectCache.get(entry);
406
+ if (!perInfo) {
407
+ perInfo = /* @__PURE__ */ new WeakMap();
408
+ sessionInjectCache.set(entry, perInfo);
409
+ }
410
+ let props = perInfo.get(info);
411
+ if (!props) {
412
+ props = runInject(entry, info, actions);
413
+ perInfo.set(info, props);
414
+ }
415
+ return props;
416
+ }
417
+ function cachedSessionMaybeInject(entry, info, actions) {
418
+ let perInfo = sessionMaybeInjectCache.get(entry);
419
+ if (!perInfo) {
420
+ perInfo = /* @__PURE__ */ new WeakMap();
421
+ sessionMaybeInjectCache.set(entry, perInfo);
422
+ }
423
+ let props = perInfo.get(info);
424
+ if (!props) {
425
+ props = runInject(entry, info, actions);
426
+ perInfo.set(info, props);
427
+ }
428
+ return props;
429
+ }
430
+ /**
431
+ * Locale `t` seat bindings, cached per (face, namespace, revision). The
432
+ * revision is part of the cache key ON PURPOSE: a locale switch mints a NEW
433
+ * function reference per namespace, so `React.memo` components taking `t`
434
+ * re-render through ordinary shallow comparison — freshness rides identity,
435
+ * no extra invalidation channel. Within one revision the reference is stable
436
+ * (memoized children do not churn on unrelated re-renders).
437
+ */
438
+ const localeSeatCache = /* @__PURE__ */ new WeakMap();
439
+ function localeSeat(face, ns) {
440
+ let perNs = localeSeatCache.get(face);
441
+ if (!perNs) {
442
+ perNs = /* @__PURE__ */ new Map();
443
+ localeSeatCache.set(face, perNs);
444
+ }
445
+ const revision = face.getSnapshot().revision;
446
+ const cached = perNs.get(ns);
447
+ if (cached && cached.revision === revision) return cached.t;
448
+ const bound = face.bind(ns);
449
+ const t = (key, params) => bound(key, params);
450
+ perNs.set(ns, {
451
+ revision,
452
+ t
453
+ });
454
+ return t;
455
+ }
456
+ const noopSubscribe = () => () => {};
457
+ const zeroRevision = () => 0;
458
+ /**
459
+ * Per-face subscribe/getSnapshot closure pair. Cached by face identity: the
460
+ * face is one global source shared by every outlet, and uSES resubscribes
461
+ * whenever the subscribe reference changes — fresh closures per render would
462
+ * churn one unsubscribe/resubscribe pair per outlet per render.
463
+ */
464
+ const localeSubscriptionCache = /* @__PURE__ */ new WeakMap();
465
+ function localeSubscription(face) {
466
+ let cached = localeSubscriptionCache.get(face);
467
+ if (!cached) {
468
+ cached = {
469
+ subscribe: (fn) => face.subscribe(fn),
470
+ getRevision: () => face.getSnapshot().revision
471
+ };
472
+ localeSubscriptionCache.set(face, cached);
473
+ }
474
+ return cached;
475
+ }
476
+ /**
477
+ * Subscribe an outlet to the installed locale face's revision (0 while none
478
+ * is installed — exactly one uSES call either way, keeping hook order
479
+ * stable). Every outlet re-renders on a locale switch; entry bodies then
480
+ * re-derive their `t` seat at the new revision. The face must be installed
481
+ * before the first render that needs it — a face appearing later has no
482
+ * notification channel to already-mounted outlets.
483
+ */
484
+ function useLocaleRevision(face) {
485
+ const subscription = face !== void 0 ? localeSubscription(face) : void 0;
486
+ return (0, react.useSyncExternalStore)(subscription?.subscribe ?? noopSubscribe, subscription?.getRevision ?? zeroRevision);
487
+ }
488
+ /**
489
+ * Entry-identity React keys for entry boundaries. An outlet renders one
490
+ * winner per position (single/keyed/list cell head, chain election) through
491
+ * an error boundary; without a key, a boundary that failed on entry A would
492
+ * survive a winner change (re-election, shadowing fallback after an
493
+ * abdication, HMR re-registration) and keep a healthy entry B blacked out.
494
+ * Keying by entry identity remounts the boundary fresh whenever the winner
495
+ * changes (entries are identity-stable per registration, so the key is
496
+ * stable while the same entry stays the winner).
497
+ */
498
+ let nextEntryKey = 0;
499
+ const entryKeys = /* @__PURE__ */ new WeakMap();
500
+ function entryKeyOf(entry) {
501
+ let key = entryKeys.get(entry);
502
+ if (key === void 0) {
503
+ key = nextEntryKey++;
504
+ entryKeys.set(entry, key);
505
+ }
506
+ return key;
507
+ }
508
+ /**
509
+ * Per-entry isolation: one registrant crashing (component render or inject
510
+ * factory) must not take down siblings. Assembly errors (missing providers)
511
+ * rethrow — a miswired shell must fail loud, not degrade into fallbacks.
512
+ * Every catch reports through `onEntryError` (the ledger's supervision
513
+ * seam); for shadowing kinds the report abdicates the entry, the outlet
514
+ * re-renders onto the cell's next survivor, and this boundary's crash face
515
+ * only shows until that re-render lands (permanently once the cell is dry —
516
+ * the outlet then owns the crash face).
517
+ */
518
+ var SlotErrorBoundary = class extends react.Component {
519
+ state = { failed: false };
520
+ static getDerivedStateFromError(error) {
521
+ if (error instanceof SlotAssemblyError) throw error;
522
+ return { failed: true };
523
+ }
524
+ componentDidCatch(error) {
525
+ console.error(`slot entry crashed in '${this.props.slotKey}':`, error);
526
+ this.props.onEntryError(error);
527
+ }
528
+ render() {
529
+ if (this.state.failed) return (0, react_jsx_runtime.jsx)("div", { "data-slot-error": this.props.slotKey });
530
+ return this.props.children;
531
+ }
532
+ };
533
+ const standardPropsCache = /* @__PURE__ */ new WeakMap();
534
+ /** Stable official-props object used by contextual Hook factories. */
535
+ function standardProps(host, scope, info) {
536
+ let cache = standardPropsCache.get(host);
537
+ if (cache === void 0) {
538
+ cache = {
539
+ root: {
540
+ useSessions: observableHook(host.sessions.list),
541
+ useWorkspaces: observableHook(host.workspaces.list)
542
+ },
543
+ session: /* @__PURE__ */ new WeakMap(),
544
+ sessionMaybe: /* @__PURE__ */ new WeakMap()
545
+ };
546
+ standardPropsCache.set(host, cache);
547
+ }
548
+ if (scope === "root") return cache.root;
549
+ if (info === void 0) throw new SlotAssemblyError(`scope '${scope}' rendered without session provide info`);
550
+ const byInfo = scope === "session" ? cache.session : cache.sessionMaybe;
551
+ let standard = byInfo.get(info);
552
+ if (standard !== void 0) return standard;
553
+ standard = { ...cache.root };
554
+ for (const [name, source] of Object.entries(info.hooks)) {
555
+ const hookName = `use${name[0]?.toUpperCase() ?? ""}${name.slice(1)}`;
556
+ if (scope === "session-maybe") standard[hookName] = maybeObservableHook(source);
557
+ else {
558
+ if (source === void 0) throw new SlotAssemblyError(`strict session hook '${name}' has no source`);
559
+ standard[hookName] = observableHook(source);
560
+ }
561
+ }
562
+ Object.assign(standard, info.props);
563
+ standard["sessionId"] = info.sessionId;
564
+ standard["useProjection"] = projectionHook(info);
565
+ byInfo.set(info, standard);
566
+ return standard;
567
+ }
568
+ /**
569
+ * Standard-kit synthesis shared by both scope branches: the global
570
+ * useSessions/useWorkspaces hooks, the per-session provide bundle (every
571
+ * `hooks` source becomes a `use<Name>` selector hook — useSession is the
572
+ * runtime's own 'session' contribution, no special case — and `props` spread
573
+ * verbatim), the store pair when declared, the renderSlot binding when
574
+ * children are declared, and the SessionProvider seat when the children
575
+ * declare a session-scope slot. Hosts hand out BARE observable sources
576
+ * (hooks never cross the host contract); every hook is bound HERE, cached
577
+ * per source (observableHook), so spreading a fresh kit object per render
578
+ * never churns child subscriptions.
579
+ */
580
+ function standardKit(host, entry, scope, info) {
581
+ const standard = standardProps(host, scope, info);
582
+ const kit = { ...standard };
583
+ if (entry.locale !== void 0) {
584
+ const face = host.locale;
585
+ if (face === void 0) throw new SlotAssemblyError(`entry declares locale namespace '${entry.locale}' but no locale face is installed (locale plugin missing from the composition?)`);
586
+ kit["t"] = localeSeat(face, entry.locale);
587
+ }
588
+ const store = scope === "session-maybe" && info?.sessionId === void 0 ? void 0 : host.storeOf(entry, info?.sessionId);
589
+ if (store !== void 0) {
590
+ kit["useStore"] = observableHook(store);
591
+ kit["actions"] = store.actions;
592
+ }
593
+ if (entry.children !== void 0) {
594
+ kit["renderSlot"] = boundRenderSlot(host, entry);
595
+ if (Object.values(entry.children).some((spec) => spec.kind === "chain")) kit["renderSlotChain"] = boundRenderSlotChain(host, entry);
596
+ if (Object.values(entry.children).some((spec) => spec.scope === "session")) kit["SessionProvider"] = SessionProvider;
597
+ }
598
+ return {
599
+ kit,
600
+ standard,
601
+ actions: store?.actions
602
+ };
603
+ }
604
+ /**
605
+ * One rendered entry: standard kit + cached entry inject + common slot inject
606
+ * + owner props (owner wins). The shares are erased at this render boundary;
607
+ * the registration and renderSlot seams already proved their contracts.
608
+ */
609
+ function ContextualEntry({ slotKey, Comp, kit, standard, injected, slotInjected, ownerProps, hookContext, hasHookContext }) {
610
+ const contextual = (0, react.useMemo)(() => {
611
+ if (!hasHookContext) throw new SlotAssemblyError(`slot '${slotKey}' has contextual injected Hooks but no hookContext`);
612
+ return bindSlotHookFactories(slotInjected.slotHookFactories, standard, hookContext);
613
+ }, [
614
+ hasHookContext,
615
+ hookContext,
616
+ slotInjected.slotHookFactories,
617
+ slotKey,
618
+ standard
619
+ ]);
620
+ return (0, react_jsx_runtime.jsx)(Comp, {
621
+ ...kit,
622
+ ...injected,
623
+ ...slotInjected.props,
624
+ ...contextual,
625
+ ...ownerProps
626
+ });
627
+ }
628
+ function renderEntry(slotKey, Comp, kit, standard, injected, slotInjected, ownerProps, hookContext, hasHookContext) {
629
+ if (slotInjected.slotHookFactories === void 0) return (0, react_jsx_runtime.jsx)(Comp, {
630
+ ...kit,
631
+ ...injected,
632
+ ...slotInjected.props,
633
+ ...ownerProps
634
+ });
635
+ return (0, react_jsx_runtime.jsx)(ContextualEntry, {
636
+ slotKey,
637
+ Comp,
638
+ kit,
639
+ standard,
640
+ injected,
641
+ slotInjected,
642
+ ownerProps,
643
+ hookContext,
644
+ hasHookContext
645
+ });
646
+ }
647
+ function SessionEntry({ entry, ownerProps, info, slotKey, slotInjected, hookContext, hasHookContext }) {
648
+ const host = useHost();
649
+ const Comp = entry.component;
650
+ const { kit, standard, actions } = standardKit(host, entry, "session", info);
651
+ return renderEntry(slotKey, Comp, kit, standard, cachedSessionInject(entry, info, actions), slotInjected, ownerProps, hookContext, hasHookContext);
652
+ }
653
+ function SessionMaybeEntryBody({ entry, ownerProps, info, slotKey, slotInjected, hookContext, hasHookContext }) {
654
+ const host = useHost();
655
+ const Comp = entry.component;
656
+ const { kit, standard, actions } = standardKit(host, entry, "session-maybe", info);
657
+ return renderEntry(slotKey, Comp, kit, standard, cachedSessionMaybeInject(entry, info, actions), slotInjected, ownerProps, hookContext, hasHookContext);
658
+ }
659
+ /**
660
+ * Session-maybe identity: adoption — the ONLY behavior (there is no
661
+ * hold-identity-forever mode). An incarnation born session-less ADOPTS the
662
+ * first session that arrives: identity holds across that one transition
663
+ * (undefined → first id), so a blank shell's DOM survives the moment a
664
+ * session appears. From then on the entry behaves exactly like a strict
665
+ * session entry: switching to a DIFFERENT session remounts (component-local
666
+ * state must not leak between sessions), and dropping back to no-session
667
+ * remounts into a fresh blank incarnation, which will adopt again.
668
+ * Component-local per-session state therefore clears by construction; state
669
+ * that must SURVIVE a switch belongs in session-bound sources (machine,
670
+ * store, hooks) — the existing layering rule, now load-bearing.
671
+ */
672
+ function SessionMaybeEntry({ entry, ownerProps, slotKey, slotInjected, hookContext, hasHookContext }) {
673
+ const info = useSessionMaybeProvideInfo();
674
+ const [state, setState] = (0, react.useState)(FIRST_INCARNATION);
675
+ let { adopted, epoch } = state;
676
+ if (info.sessionId !== void 0 && adopted === void 0) {
677
+ adopted = info.sessionId;
678
+ setState({
679
+ adopted,
680
+ epoch
681
+ });
682
+ } else if (adopted !== void 0 && info.sessionId !== void 0 && info.sessionId !== adopted) {
683
+ adopted = info.sessionId;
684
+ epoch += 1;
685
+ setState({
686
+ adopted,
687
+ epoch
688
+ });
689
+ } else if (adopted !== void 0 && info.sessionId === void 0) {
690
+ adopted = void 0;
691
+ epoch += 1;
692
+ setState({
693
+ adopted,
694
+ epoch
695
+ });
696
+ }
697
+ return (0, react_jsx_runtime.jsx)(SessionMaybeEntryBody, {
698
+ entry,
699
+ ownerProps,
700
+ info,
701
+ slotKey,
702
+ slotInjected,
703
+ hookContext,
704
+ hasHookContext
705
+ }, epoch);
706
+ }
707
+ const FIRST_INCARNATION = {
708
+ adopted: void 0,
709
+ epoch: 0
710
+ };
711
+ function RootEntry({ entry, ownerProps, slotKey, slotInjected, hookContext, hasHookContext }) {
712
+ const host = useHost();
713
+ const Comp = entry.component;
714
+ const { kit, standard, actions } = standardKit(host, entry, "root", void 0);
715
+ return renderEntry(slotKey, Comp, kit, standard, cachedRootInject(entry, actions), slotInjected, ownerProps, hookContext, hasHookContext);
716
+ }
717
+ function StrictSessionEntry({ slotKey, entry, ownerProps, slotInjected, hookContext, hasHookContext, onEntryError }) {
718
+ const info = useSessionMaybeProvideInfo();
719
+ if (info.sessionId === void 0) return null;
720
+ return (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
721
+ slotKey,
722
+ onEntryError,
723
+ children: (0, react_jsx_runtime.jsx)(SessionEntry, {
724
+ entry,
725
+ ownerProps,
726
+ info,
727
+ slotKey,
728
+ slotInjected,
729
+ hookContext,
730
+ hasHookContext
731
+ })
732
+ }, info.sessionId);
733
+ }
734
+ /**
735
+ * Anchor style shared by every outlet wrapper: `display:contents` keeps the
736
+ * wrapper out of layout (grid/flex parents see the slot's own children), so
737
+ * the anchor is purely addressable surface. Module-level constant — a stable
738
+ * reference so the wrapper never diffs its style prop.
739
+ */
740
+ const ANCHOR_STYLE = { display: "contents" };
741
+ function SlotOutlet({ slotKey, ownerProps, opts }) {
742
+ const host = useHost();
743
+ (0, react.useSyncExternalStore)((fn) => host.subscribe(slotKey, fn), () => host.getVersion(slotKey));
744
+ useLocaleRevision(host.locale);
745
+ return (0, react_jsx_runtime.jsx)("div", {
746
+ "data-slot": slotKey,
747
+ style: ANCHOR_STYLE,
748
+ children: renderOutletContent(host, slotKey, ownerProps, opts, useSessionMaybeProvideInfo())
749
+ });
750
+ }
751
+ /** Kind dispatch behind the outlet anchor (single/keyed/list/chain, fallbacks, crash faces). */
752
+ function renderOutletContent(host, slotKey, ownerProps, opts, sessionInfo) {
753
+ const spec = host.specOf(slotKey);
754
+ if (!spec) return null;
755
+ const strictSessionAbsent = spec.scope === "session" && sessionInfo.sessionId === void 0;
756
+ if (strictSessionAbsent && (spec.kind !== "chain" || !opts?.overlay)) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
757
+ const entries = strictSessionAbsent ? [] : host.entriesOf(slotKey);
758
+ const slotInjected = cachedSlotInject(spec.inject);
759
+ const guarded = (entry, key, owner = ownerProps) => {
760
+ const hasHookContext = opts !== void 0 && Object.hasOwn(opts, "hookContext");
761
+ const hookContext = opts?.hookContext;
762
+ const onEntryError = (error) => {
763
+ host.reportEntryError(slotKey, entry, error, { abdicate: spec.kind !== "chain" });
764
+ };
765
+ return spec.scope === "session" ? (0, react_jsx_runtime.jsx)(StrictSessionEntry, {
766
+ slotKey,
767
+ entry,
768
+ ownerProps: owner,
769
+ slotInjected,
770
+ hookContext,
771
+ hasHookContext,
772
+ onEntryError
773
+ }, key) : (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
774
+ slotKey,
775
+ onEntryError,
776
+ children: spec.scope === "session-maybe" ? (0, react_jsx_runtime.jsx)(SessionMaybeEntry, {
777
+ entry,
778
+ ownerProps: owner,
779
+ slotKey,
780
+ slotInjected,
781
+ hookContext,
782
+ hasHookContext
783
+ }) : (0, react_jsx_runtime.jsx)(RootEntry, {
784
+ entry,
785
+ ownerProps: owner,
786
+ slotKey,
787
+ slotInjected,
788
+ hookContext,
789
+ hasHookContext
790
+ })
791
+ }, key);
792
+ };
793
+ const deadCell = () => (0, react_jsx_runtime.jsx)("div", { "data-slot-error": slotKey });
794
+ if (spec.kind === "single") {
795
+ const entry = host.entriesOfSlot(slotKey)[0];
796
+ if (!entry) return entries.length > 0 ? deadCell() : (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
797
+ return guarded(entry, entryKeyOf(entry));
798
+ }
799
+ if (spec.kind === "keyed") {
800
+ const entry = host.entriesOfSlot(slotKey).find((e) => e.options.key === opts?.entryKey);
801
+ if (!entry) return entries.some((e) => e.options.key === opts?.entryKey) ? deadCell() : (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
802
+ return guarded(entry, entryKeyOf(entry));
803
+ }
804
+ if (spec.kind === "chain") {
805
+ let elected = null;
806
+ for (const entry of entries) {
807
+ let matched;
808
+ try {
809
+ matched = entry.select(ownerProps);
810
+ } catch (error) {
811
+ console.error(`chain selector crashed in '${slotKey}' (${entry.registrant ?? "unknown registrant"}), treating as declined:`, error);
812
+ continue;
813
+ }
814
+ if (matched !== null) {
815
+ elected = guarded(entry, entryKeyOf(entry), {
816
+ ...ownerProps,
817
+ matched
818
+ });
819
+ break;
820
+ }
821
+ }
822
+ if (opts?.overlay) return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
823
+ "data-chain-overlay-fallback": slotKey,
824
+ style: { display: elected === null ? "contents" : "none" },
825
+ children: opts.fallback ?? null
826
+ }), elected] });
827
+ return elected ?? (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
828
+ }
829
+ const rows = host.entriesOfSlot(slotKey).map((entry) => ({
830
+ entry,
831
+ id: entry.options.id,
832
+ order: entry.options.order ?? 0
833
+ }));
834
+ const rowIds = new Set(rows.map((row) => row.id));
835
+ for (const entry of entries) {
836
+ if (rowIds.has(entry.options.id)) continue;
837
+ rowIds.add(entry.options.id);
838
+ rows.push({
839
+ entry: void 0,
840
+ id: entry.options.id,
841
+ order: entry.options.order ?? 0
842
+ });
843
+ }
844
+ let list = [...rows].sort((a, b) => a.order - b.order);
845
+ if (opts?.only !== void 0) list = list.filter((item) => item.id === opts.only);
846
+ if (list.length === 0) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
847
+ return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: list.map((item, i) => item.entry !== void 0 ? guarded(item.entry, `e${entryKeyOf(item.entry)}`) : (0, react_jsx_runtime.jsx)("div", { "data-slot-error": slotKey }, `x${item.id ?? i}`)) });
848
+ }
849
+ /** Root outlet: the shell's single ctx-level render entry — an unregistered 'root' is a boot-order failure, never a silent blank. */
850
+ function RootOutlet({ ownerProps }) {
851
+ const host = useHost();
852
+ (0, react.useSyncExternalStore)((fn) => host.subscribe("root", fn), () => host.getVersion("root"));
853
+ useLocaleRevision(host.locale);
854
+ const entry = host.entriesOfSlot("root")[0];
855
+ if (!entry) {
856
+ if (host.entriesOf("root").length > 0) return (0, react_jsx_runtime.jsx)("div", { "data-slot-error": "root" });
857
+ throw new SlotAssemblyError("renderSlot('root') before any 'root' registration (boot order)");
858
+ }
859
+ return (0, react_jsx_runtime.jsx)("div", {
860
+ "data-slot": "root",
861
+ style: ANCHOR_STYLE,
862
+ children: (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
863
+ slotKey: "root",
864
+ onEntryError: (error) => {
865
+ host.reportEntryError("root", entry, error, { abdicate: true });
866
+ },
867
+ children: (0, react_jsx_runtime.jsx)(RootEntry, {
868
+ entry,
869
+ ownerProps,
870
+ slotKey: "root",
871
+ slotInjected: EMPTY_SLOT_INJECT,
872
+ hookContext: void 0,
873
+ hasHookContext: false
874
+ })
875
+ }, entryKeyOf(entry))
876
+ });
877
+ }
878
+ /**
879
+ * Build the renderer the shell installs into the runtime SlotRegistry
880
+ * (ctx.slots.install(createSlotRenderer()) at boot; the service owns the
881
+ * install/renderSlot contract and the double-install/not-installed throws).
882
+ * @returns the renderer.
883
+ */
884
+ function createSlotRenderer() {
885
+ return { renderRoot(host, ownerProps) {
886
+ return (0, react_jsx_runtime.jsx)(HostContext.Provider, {
887
+ value: host,
888
+ children: (0, react_jsx_runtime.jsx)(SessionMaybeProvider, { children: (0, react_jsx_runtime.jsx)(RootOutlet, { ownerProps }) })
889
+ });
890
+ } };
891
+ }
892
+ //#endregion
893
+ //#region lib/types/client/DocumentTitle.js
894
+ /**
895
+ * Project the selected durable session title into the browser title and
896
+ * restore the build-selected product title when unmounted.
897
+ * @param props - Selected session title projection.
898
+ * @returns No rendered content.
899
+ */
900
+ function DocumentTitle({ title }) {
901
+ const productTitle = "DeepSeek Harness";
902
+ (0, react.useEffect)(() => {
903
+ document.title = title === void 0 ? productTitle : `${title} — ${productTitle}`;
904
+ return () => {
905
+ document.title = productTitle;
906
+ };
907
+ }, [productTitle, title]);
908
+ return null;
909
+ }
910
+ //#endregion
911
+ //#region lib/types/client/app.js
912
+ /**
913
+ * Build the assembled application factory.
914
+ * @param deps - Active UI-renderer dependencies.
915
+ * @returns Factory producing the application React tree.
916
+ */
917
+ function buildRenderApp(deps) {
918
+ const { ctx } = deps;
919
+ const sessions = ctx.get("sessions");
920
+ if (sessions === void 0) throw new Error("ui renderer: sessions service unavailable");
921
+ const useSessions = bindSnapshotSelector(sessions.list);
922
+ const SessionDocumentTitle = () => {
923
+ const title = useSessions((state) => {
924
+ const id = state.current;
925
+ return id === void 0 ? void 0 : state.byId[id]?.title;
926
+ });
927
+ return (0, react_jsx_runtime.jsx)(DocumentTitle, { ...title === void 0 ? {} : { title } });
928
+ };
929
+ return () => (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(SessionDocumentTitle, {}), ctx.slots.renderSlot("root", {})] });
930
+ }
931
+ //#endregion
932
+ //#region lib/types/client/index.js
933
+ /**
934
+ * Browser UI renderer. It installs the slot renderer after its Cordis
935
+ * dependencies activate and exposes the mount operation used by the web boot
936
+ * kernel after the complete client roster settles.
937
+ */
938
+ /** Services required before application assembly. */
939
+ const inject = ["slots", "sessions"];
940
+ /** Hydrate the kernel-owned loading DOM before replacing it with the application. */
941
+ function BootHandoff(props) {
942
+ const [ready, setReady] = (0, react.useState)(false);
943
+ (0, react.useLayoutEffect)(() => {
944
+ setReady(true);
945
+ }, []);
946
+ if (ready) return props.app();
947
+ return (0, react.createElement)("div", {
948
+ className: props.boot.className,
949
+ "data-dsh-boot": "",
950
+ dangerouslySetInnerHTML: { __html: props.boot.html }
951
+ });
952
+ }
953
+ /** Mount React while preserving the framework-free boot DOM through hydration. */
954
+ function mountApp(container, app) {
955
+ const boot = container.querySelector(":scope > [data-dsh-boot]");
956
+ if (boot !== null) return (0, react_dom_client.hydrateRoot)(container, (0, react.createElement)(BootHandoff, {
957
+ app,
958
+ boot: {
959
+ className: boot.className,
960
+ html: boot.innerHTML
961
+ }
962
+ }));
963
+ const root = (0, react_dom_client.createRoot)(container);
964
+ (0, react_dom.flushSync)(() => {
965
+ root.render(app());
966
+ });
967
+ return root;
968
+ }
969
+ /**
970
+ * Install the slot renderer and provide the application mount face.
971
+ * @param ctx - Plugin context.
972
+ */
973
+ function apply(ctx) {
974
+ ctx.slots.install(createSlotRenderer());
975
+ ctx.reflect.provide("uiRenderer", { mount: (container) => {
976
+ const root = mountApp(container, buildRenderApp({ ctx }));
977
+ return () => {
978
+ root.unmount();
979
+ };
980
+ } });
981
+ }
982
+ //#endregion
983
+ exports.apply = apply;
984
+ exports.inject = inject;
985
+ return module.exports;
986
+ }
987
+ });
988
+
989
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Host loader entry for the browser-only UI renderer. */
3
+ /** Provides no host-side behavior. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@monotykamary/dsh-client-ui-renderer`.
4
+ * @module @monotykamary/dsh-client-ui-renderer/invariant
5
+ */
6
+ const PACKAGE_NAME = "@monotykamary/dsh-client-ui-renderer";
7
+ /** Cordis companion plugin name. */
8
+ const name = "client-ui-renderer-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the package installs the render adapter and provides a
13
+ * mount callback but owns no event stream or mutable cross-plugin data relation.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,13 @@
1
+ /** Props for the browser title projection. */
2
+ export interface DocumentTitleProps {
3
+ /** Durable title of the selected session, or undefined for the product title. */
4
+ title?: string;
5
+ }
6
+ /**
7
+ * Project the selected durable session title into the browser title and
8
+ * restore the build-selected product title when unmounted.
9
+ * @param props - Selected session title projection.
10
+ * @returns No rendered content.
11
+ */
12
+ export declare function DocumentTitle({ title }: DocumentTitleProps): null;
13
+ //# sourceMappingURL=DocumentTitle.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Real-UI assembly closure. The whole layout tree hangs from the built-in
3
+ * `root` slot, which is the only ctx-level slot render in the application.
4
+ */
5
+ import type { ReactNode } from 'react';
6
+ import type { Context } from '@monotykamary/cordis';
7
+ /** Inputs available after the UI renderer's inject set activates. */
8
+ export interface AssemblyDeps {
9
+ /** Client context carrying the slots and sessions services. */
10
+ ctx: Context;
11
+ }
12
+ /**
13
+ * Build the assembled application factory.
14
+ * @param deps - Active UI-renderer dependencies.
15
+ * @returns Factory producing the application React tree.
16
+ */
17
+ export declare function buildRenderApp(deps: AssemblyDeps): () => ReactNode;
18
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1,11 @@
1
+ import type { HostObservable, SnapshotSelectorHook } from '@monotykamary/dsh-client-ui-slots';
2
+ /**
3
+ * Bind a bare observable source to a typed uSES selector hook.
4
+ * subscribe/getSnapshot are captured once per source into stable closures
5
+ * (also re-binds `this` for method-based sources), so components never
6
+ * resubscribe across renders. Equality defaults to Object.is.
7
+ * @param w - snapshot source (engine store, Session object, store instance).
8
+ * @returns the selector hook.
9
+ */
10
+ export declare function bindSnapshotSelector<T>(w: HostObservable<T>): SnapshotSelectorHook<T>;
11
+ //# sourceMappingURL=bind.d.ts.map
@@ -0,0 +1,29 @@
1
+ import type { Context } from '@monotykamary/cordis';
2
+ import type { SnapshotSelectorHook } from '@monotykamary/dsh-client-ui-slots';
3
+ /** Selector hook over a session's conversation snapshot. */
4
+ export type UseSession<Snap extends object = object> = SnapshotSelectorHook<Snap>;
5
+ export type { ChainRenderOpts, HostObservable, RenderOpts, SessionProvideInfo, SnapshotSelectorHook, SlotRenderer, SlotRendererHost, StoreInstanceLike, } from '@monotykamary/dsh-client-ui-slots';
6
+ export type { SessionProviderProps } from './session-provider.tsx';
7
+ /** Mount operation exposed to the framework-free boot kernel. */
8
+ export interface UiRendererService {
9
+ /**
10
+ * Mount the assembled application into the supplied element.
11
+ * @param container - Application mount point.
12
+ * @returns Disposer that unmounts the React root.
13
+ */
14
+ mount: (container: HTMLElement) => () => void;
15
+ }
16
+ declare module '@monotykamary/cordis' {
17
+ interface Context {
18
+ /** Mount face provided after the UI renderer activates. */
19
+ uiRenderer: UiRendererService;
20
+ }
21
+ }
22
+ /** Services required before application assembly. */
23
+ export declare const inject: string[];
24
+ /**
25
+ * Install the slot renderer and provide the application mount face.
26
+ * @param ctx - Plugin context.
27
+ */
28
+ export declare function apply(ctx: Context): void;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { type SlotRenderer } from '@monotykamary/dsh-client-ui-slots';
2
+ /**
3
+ * Build the renderer the shell installs into the runtime SlotRegistry
4
+ * (ctx.slots.install(createSlotRenderer()) at boot; the service owns the
5
+ * install/renderSlot contract and the double-install/not-installed throws).
6
+ * @returns the renderer.
7
+ */
8
+ export declare function createSlotRenderer(): SlotRenderer;
9
+ //# sourceMappingURL=scoped-slots.d.ts.map
@@ -0,0 +1,73 @@
1
+ /** Internal React bindings for the renderer host and active session provide bundle. */
2
+ import { type ReactNode } from 'react';
3
+ import type { HostObservable, MaybeSnapshotSelectorHook, SessionMaybeProvideInfo, SessionProvideInfo, SlotRendererHost, SnapshotSelectorHook } from '@monotykamary/dsh-client-ui-slots';
4
+ /**
5
+ * A missing-provider assembly error: the shell wired the tree wrong. The slot
6
+ * error boundary rethrows this class so misassembly stays fail-loud while
7
+ * registrant errors (inject factories, entry components) are contained
8
+ * per entry.
9
+ */
10
+ export declare class SlotAssemblyError extends Error {
11
+ }
12
+ /** In-package renderer host context. */
13
+ export declare const HostContext: import("react").Context<SlotRendererHost | null>;
14
+ /**
15
+ * Read the installed renderer host; throws outside the rendered root tree
16
+ * (framework components must not render detached from the renderer).
17
+ * @returns the host API.
18
+ */
19
+ export declare function useHost(): SlotRendererHost;
20
+ /** Read the current-session-optional bundle supplied at the root. */
21
+ export declare function useSessionMaybeProvideInfo(): SessionMaybeProvideInfo;
22
+ /**
23
+ * Read the enclosing session provide bundle; throws outside a SessionProvider
24
+ * subtree (session slots must not render without a session).
25
+ * @returns the enclosing bundle.
26
+ */
27
+ export declare function useSessionProvideInfo(): SessionProvideInfo;
28
+ /**
29
+ * Identity-stable selector hook per host observable. uSES resubscribes when
30
+ * the subscribe reference changes, so the bound hook must be created once per
31
+ * source — cached here by source identity (sources are host-owned singletons).
32
+ * @param source - host-provided observable.
33
+ * @returns the cached selector hook.
34
+ */
35
+ export declare function observableHook<T>(source: HostObservable<T>): SnapshotSelectorHook<T>;
36
+ /** Bind a source that disappears with the current session to an optional selector hook. */
37
+ export declare function maybeObservableHook<T>(source: HostObservable<T> | undefined): MaybeSnapshotSelectorHook<T>;
38
+ /**
39
+ * The useProjection framework seat (docs/subsystems/session-projection.md), one bound
40
+ * function per provide bundle (cached by info identity — components may hold
41
+ * it across renders). Key-addressed: the key resolves a per-session value
42
+ * face off the projection store; the bound selector hook comes from the same
43
+ * per-source cache as every other kit hook, so exactly one uSES subscription
44
+ * runs per call and the subscribe reference stays stable per key. A key no
45
+ * baseline or frame has carried (or a no-session bundle) reads `undefined` —
46
+ * capability absence — keeping the hook order constant.
47
+ */
48
+ export declare function projectionHook(info: SessionMaybeProvideInfo): (key: string, selector?: (value: unknown) => unknown, eq?: (a: unknown, b: unknown) => boolean) => unknown;
49
+ /**
50
+ * Root-level binding provider. It follows current selection without a key;
51
+ * per-entry identity is the outlet's adoption bookkeeping (SessionMaybeEntry):
52
+ * a blank-born incarnation adopts the first session without remounting, and
53
+ * every later transition (switch or loss) remounts like a strict entry.
54
+ */
55
+ export declare function SessionMaybeProvider({ children }: {
56
+ children: ReactNode;
57
+ }): import("react").JSX.Element;
58
+ /** SessionProvider API: render-prop body plus the no-session branch. */
59
+ export interface SessionProviderProps {
60
+ /** No-session body (also covers a current id whose session cannot be resolved). */
61
+ empty?: (() => ReactNode) | undefined;
62
+ /** Session body; remounted per session via key={sessionId}. */
63
+ children: (sessionId: string) => ReactNode;
64
+ }
65
+ /**
66
+ * Framework-wired session area: subscribes to the host's current provide
67
+ * source and remounts the body under `key={sessionId}` so a session switch
68
+ * rebuilds the session subtree. This dependency-inverted layer uses plain
69
+ * string ids; `PropsRuntime` applies the branded type at the component
70
+ * boundary.
71
+ */
72
+ export declare function SessionProvider({ empty, children }: SessionProviderProps): import("react").JSX.Element;
73
+ //# sourceMappingURL=session-provider.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Host loader entry for the browser-only UI renderer. */
2
+ /** Provides no host-side behavior. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@monotykamary/dsh-client-ui-renderer`.
3
+ * @module @monotykamary/dsh-client-ui-renderer/invariant
4
+ */
5
+ import type { Context } from '@monotykamary/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "client-ui-renderer-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@monotykamary/dsh-client-ui-renderer",
3
+ "description": "Browser UI renderer: React slot bindings, ctx.uiRenderer, and the assembled application root",
4
+ "version": "0.1.0-rc.10",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/ui-renderer"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dsh": {
33
+ "client": {
34
+ "inject": [
35
+ "@monotykamary/dsh-client-runtime"
36
+ ],
37
+ "platform": "web",
38
+ "immediately": true
39
+ }
40
+ },
41
+ "license": "MIT",
42
+ "dependencies": {
43
+ "use-sync-external-store": "1.2.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@monotykamary/dsh-client-runtime": "^0.1.0-rc.10",
47
+ "@monotykamary/dsh-invariants": "^0.1.0-rc.10",
48
+ "@monotykamary/cordis": "^4.0.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "~18.3.1",
52
+ "@types/react-dom": "~18.3.0",
53
+ "react": "^18.2.0",
54
+ "react-dom": "^18.2.0",
55
+ "@monotykamary/dsh-client-ui-slots": "^0.1.0-rc.10",
56
+ "@monotykamary/dsh-client-test-runtime": "^0.1.0-rc.10",
57
+ "@monotykamary/dsh-client-runtime": "^0.1.0-rc.10",
58
+ "@monotykamary/cordis": "^4.0.1",
59
+ "@monotykamary/dsh-invariants": "^0.1.0-rc.10"
60
+ },
61
+ "files": [
62
+ "lib/index.js",
63
+ "lib/invariant.js",
64
+ "lib/client.js",
65
+ "lib/types/**/*.d.ts"
66
+ ],
67
+ "scripts": {
68
+ "bundle": "tsdown",
69
+ "watch": "tsdown --watch"
70
+ }
71
+ }