@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 +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +20 -0
- package/README.zh.md +20 -0
- package/lib/client.js +989 -0
- package/lib/index.js +6 -0
- package/lib/invariant.js +23 -0
- package/lib/types/client/DocumentTitle.d.ts +13 -0
- package/lib/types/client/app.d.ts +18 -0
- package/lib/types/client/bind.d.ts +11 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/scoped-slots.d.ts +9 -0
- package/lib/types/client/session-provider.d.ts +73 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +71 -0
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.
|
package/README.i18n.yaml
ADDED
|
@@ -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
package/lib/invariant.js
ADDED
|
@@ -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,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
|
+
}
|