@prettier-ai/dsh-client-ui-renderer 0.1.2-alpha.1
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 +104 -0
- package/README.zh.md +104 -0
- package/lib/client.js +1449 -0
- package/lib/index.js +6 -0
- package/lib/invariant.js +34 -0
- package/lib/types/client/app.d.ts +18 -0
- package/lib/types/client/bind.d.ts +11 -0
- package/lib/types/client/bindings.d.ts +53 -0
- package/lib/types/client/index.d.ts +38 -0
- package/lib/types/client/registry.d.ts +222 -0
- package/lib/types/client/scoped-slots.d.ts +9 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +67 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@prettier-ai/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 _prettier_ai_dsh_client_ui_slots = require("@prettier-ai/dsh-client-ui-slots");
|
|
15
|
+
let _prettier_ai_cordis = require("@prettier-ai/cordis");
|
|
16
|
+
//#region ../../../node_modules/.pnpm/use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.min.js
|
|
17
|
+
/**
|
|
18
|
+
* @license React
|
|
19
|
+
* use-sync-external-store-shim.production.min.js
|
|
20
|
+
*
|
|
21
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
22
|
+
*
|
|
23
|
+
* This source code is licensed under the MIT license found in the
|
|
24
|
+
* LICENSE file in the root directory of this source tree.
|
|
25
|
+
*/
|
|
26
|
+
var require_use_sync_external_store_shim_production_min = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
27
|
+
var e = require("react");
|
|
28
|
+
function h(a, b) {
|
|
29
|
+
return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b;
|
|
30
|
+
}
|
|
31
|
+
var k = "function" === typeof Object.is ? Object.is : h, l = e.useState, m = e.useEffect, n = e.useLayoutEffect, p = e.useDebugValue;
|
|
32
|
+
function q(a, b) {
|
|
33
|
+
var d = b(), f = l({ inst: {
|
|
34
|
+
value: d,
|
|
35
|
+
getSnapshot: b
|
|
36
|
+
} }), c = f[0].inst, g = f[1];
|
|
37
|
+
n(function() {
|
|
38
|
+
c.value = d;
|
|
39
|
+
c.getSnapshot = b;
|
|
40
|
+
r(c) && g({ inst: c });
|
|
41
|
+
}, [
|
|
42
|
+
a,
|
|
43
|
+
d,
|
|
44
|
+
b
|
|
45
|
+
]);
|
|
46
|
+
m(function() {
|
|
47
|
+
r(c) && g({ inst: c });
|
|
48
|
+
return a(function() {
|
|
49
|
+
r(c) && g({ inst: c });
|
|
50
|
+
});
|
|
51
|
+
}, [a]);
|
|
52
|
+
p(d);
|
|
53
|
+
return d;
|
|
54
|
+
}
|
|
55
|
+
function r(a) {
|
|
56
|
+
var b = a.getSnapshot;
|
|
57
|
+
a = a.value;
|
|
58
|
+
try {
|
|
59
|
+
var d = b();
|
|
60
|
+
return !k(a, d);
|
|
61
|
+
} catch (f) {
|
|
62
|
+
return !0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function t(a, b) {
|
|
66
|
+
return b();
|
|
67
|
+
}
|
|
68
|
+
var u = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? t : q;
|
|
69
|
+
exports.useSyncExternalStore = void 0 !== e.useSyncExternalStore ? e.useSyncExternalStore : u;
|
|
70
|
+
}));
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region ../../../node_modules/.pnpm/use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store/shim/index.js
|
|
73
|
+
var require_shim = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
74
|
+
module.exports = require_use_sync_external_store_shim_production_min();
|
|
75
|
+
}));
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region ../../../node_modules/.pnpm/use-sync-external-store@1.2.0_react@18.3.1/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.production.min.js
|
|
78
|
+
/**
|
|
79
|
+
* @license React
|
|
80
|
+
* use-sync-external-store-shim/with-selector.production.min.js
|
|
81
|
+
*
|
|
82
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
83
|
+
*
|
|
84
|
+
* This source code is licensed under the MIT license found in the
|
|
85
|
+
* LICENSE file in the root directory of this source tree.
|
|
86
|
+
*/
|
|
87
|
+
var require_with_selector_production_min = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
88
|
+
var h = require("react"), n = require_shim();
|
|
89
|
+
function p(a, b) {
|
|
90
|
+
return a === b && (0 !== a || 1 / a === 1 / b) || a !== a && b !== b;
|
|
91
|
+
}
|
|
92
|
+
var q = "function" === typeof Object.is ? Object.is : p, r = n.useSyncExternalStore, t = h.useRef, u = h.useEffect, v = h.useMemo, w = h.useDebugValue;
|
|
93
|
+
exports.useSyncExternalStoreWithSelector = function(a, b, e, l, g) {
|
|
94
|
+
var c = t(null);
|
|
95
|
+
if (null === c.current) {
|
|
96
|
+
var f = {
|
|
97
|
+
hasValue: !1,
|
|
98
|
+
value: null
|
|
99
|
+
};
|
|
100
|
+
c.current = f;
|
|
101
|
+
} else f = c.current;
|
|
102
|
+
c = v(function() {
|
|
103
|
+
function a(a) {
|
|
104
|
+
if (!c) {
|
|
105
|
+
c = !0;
|
|
106
|
+
d = a;
|
|
107
|
+
a = l(a);
|
|
108
|
+
if (void 0 !== g && f.hasValue) {
|
|
109
|
+
var b = f.value;
|
|
110
|
+
if (g(b, a)) return k = b;
|
|
111
|
+
}
|
|
112
|
+
return k = a;
|
|
113
|
+
}
|
|
114
|
+
b = k;
|
|
115
|
+
if (q(d, a)) return b;
|
|
116
|
+
var e = l(a);
|
|
117
|
+
if (void 0 !== g && g(b, e)) return b;
|
|
118
|
+
d = a;
|
|
119
|
+
return k = e;
|
|
120
|
+
}
|
|
121
|
+
var c = !1, d, k, m = void 0 === e ? null : e;
|
|
122
|
+
return [function() {
|
|
123
|
+
return a(b());
|
|
124
|
+
}, null === m ? void 0 : function() {
|
|
125
|
+
return a(m());
|
|
126
|
+
}];
|
|
127
|
+
}, [
|
|
128
|
+
b,
|
|
129
|
+
e,
|
|
130
|
+
l,
|
|
131
|
+
g
|
|
132
|
+
]);
|
|
133
|
+
var d = r(a, c[0], c[1]);
|
|
134
|
+
u(function() {
|
|
135
|
+
f.hasValue = !0;
|
|
136
|
+
f.value = d;
|
|
137
|
+
}, [d]);
|
|
138
|
+
w(d);
|
|
139
|
+
return d;
|
|
140
|
+
};
|
|
141
|
+
}));
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region lib/types/client/bind.js
|
|
144
|
+
var import_with_selector = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
145
|
+
module.exports = require_with_selector_production_min();
|
|
146
|
+
})))();
|
|
147
|
+
/**
|
|
148
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
149
|
+
* subscribe/getSnapshot are captured once per source into stable closures
|
|
150
|
+
* (also re-binds `this` for method-based sources), so components never
|
|
151
|
+
* resubscribe across renders. Equality defaults to Object.is.
|
|
152
|
+
* @param w - snapshot source (engine store, Session object, store instance).
|
|
153
|
+
* @returns the selector hook.
|
|
154
|
+
*/
|
|
155
|
+
function bindSnapshotSelector(w) {
|
|
156
|
+
const subscribe = (fn) => w.subscribe(fn);
|
|
157
|
+
const getSnapshot = () => w.getSnapshot();
|
|
158
|
+
return function useSelector(sel, eq) {
|
|
159
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, void 0, sel, eq);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region lib/types/client/bindings.js
|
|
164
|
+
/** Internal React bindings for renderer hosts and standard-source scopes. */
|
|
165
|
+
/** Missing renderer assembly dependency. */
|
|
166
|
+
var SlotAssemblyError = class extends Error {};
|
|
167
|
+
/** In-package renderer host context. */
|
|
168
|
+
const HostContext = (0, react.createContext)(null);
|
|
169
|
+
/**
|
|
170
|
+
* Read the installed renderer host.
|
|
171
|
+
* @returns the host API.
|
|
172
|
+
*/
|
|
173
|
+
function useHost() {
|
|
174
|
+
const host = (0, react.useContext)(HostContext);
|
|
175
|
+
if (host === null) throw new SlotAssemblyError("slot machinery rendered outside the installed renderer tree");
|
|
176
|
+
return host;
|
|
177
|
+
}
|
|
178
|
+
const RootBindingContext = (0, react.createContext)(null);
|
|
179
|
+
const ScopeBindingContext = (0, react.createContext)(null);
|
|
180
|
+
/**
|
|
181
|
+
* Read the root standard-source binding.
|
|
182
|
+
* @returns the current root binding.
|
|
183
|
+
*/
|
|
184
|
+
function useRootBinding() {
|
|
185
|
+
const binding = (0, react.useContext)(RootBindingContext);
|
|
186
|
+
if (binding === null) throw new SlotAssemblyError("slot rendered outside the root standard-source provider");
|
|
187
|
+
return binding;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read the current-session-optional binding.
|
|
191
|
+
* @returns a binding whose key is absent when no Session is selected.
|
|
192
|
+
*/
|
|
193
|
+
function useScopeBinding() {
|
|
194
|
+
const binding = (0, react.useContext)(ScopeBindingContext);
|
|
195
|
+
if (binding === null) throw new SlotAssemblyError("scoped slot rendered outside its scope provider");
|
|
196
|
+
return binding;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Bind one observable source to an identity-stable selector Hook.
|
|
200
|
+
* @param source - observable source.
|
|
201
|
+
* @returns cached selector Hook.
|
|
202
|
+
*/
|
|
203
|
+
function observableHook(source) {
|
|
204
|
+
let hook = hookCache.get(source);
|
|
205
|
+
if (hook === void 0) {
|
|
206
|
+
hook = bindSnapshotSelector(source);
|
|
207
|
+
hookCache.set(source, hook);
|
|
208
|
+
}
|
|
209
|
+
return hook;
|
|
210
|
+
}
|
|
211
|
+
const hookCache = /* @__PURE__ */ new WeakMap();
|
|
212
|
+
const absentSource = {
|
|
213
|
+
getSnapshot: () => void 0,
|
|
214
|
+
subscribe: () => () => {}
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Bind an optional source without changing Hook call order.
|
|
218
|
+
* @param source - current source, or absence.
|
|
219
|
+
* @returns selector Hook returning `undefined` while absent.
|
|
220
|
+
*/
|
|
221
|
+
function maybeObservableHook(source) {
|
|
222
|
+
if (source !== void 0) return observableHook(source);
|
|
223
|
+
return useAbsentSnapshot;
|
|
224
|
+
}
|
|
225
|
+
function useAbsentSnapshot(_selector, _equal) {
|
|
226
|
+
observableHook(absentSource)(() => void 0);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Bind an open-key source family.
|
|
230
|
+
* @param source - keyed resolver, or absence for an optional scope.
|
|
231
|
+
* @returns cached keyed selector Hook.
|
|
232
|
+
*/
|
|
233
|
+
function keyedObservableHook(source) {
|
|
234
|
+
if (source === void 0) return absentKeyedHook;
|
|
235
|
+
let hook = keyedHookCache.get(source);
|
|
236
|
+
if (hook === void 0) {
|
|
237
|
+
hook = (key, selector, equal) => {
|
|
238
|
+
return observableHook(source(key) ?? absentSource)(selector ?? identity, equal);
|
|
239
|
+
};
|
|
240
|
+
keyedHookCache.set(source, hook);
|
|
241
|
+
}
|
|
242
|
+
return hook;
|
|
243
|
+
}
|
|
244
|
+
const keyedHookCache = /* @__PURE__ */ new WeakMap();
|
|
245
|
+
const identity = (value) => value;
|
|
246
|
+
const absentKeyedHook = (_key, selector, equal) => observableHook(absentSource)(selector ?? identity, equal);
|
|
247
|
+
/** Subscribe the tree to the atomically assembled root standard-source roster. */
|
|
248
|
+
function RootStandardProvider({ children }) {
|
|
249
|
+
const binding = observableHook(useHost().root)((value) => value);
|
|
250
|
+
return (0, react_jsx_runtime.jsx)(RootBindingContext.Provider, {
|
|
251
|
+
value: binding,
|
|
252
|
+
children
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/** Subscribe to the scope roster before resolving and binding its current adapter. */
|
|
256
|
+
function ScopeProvider({ scope, children }) {
|
|
257
|
+
const host = useHost();
|
|
258
|
+
observableHook(host.scopeRevision)((value) => value);
|
|
259
|
+
const adapter = host.scope(scope);
|
|
260
|
+
if (adapter === void 0) throw new SlotAssemblyError(`scope '${scope}' rendered without an installed adapter`);
|
|
261
|
+
const binding = observableHook(adapter.current)((value) => value);
|
|
262
|
+
return (0, react_jsx_runtime.jsx)(ScopeBindingContext.Provider, {
|
|
263
|
+
value: binding,
|
|
264
|
+
children
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region lib/types/client/scoped-slots.js
|
|
269
|
+
/**
|
|
270
|
+
* React renderer for declarative slots. Per-entry bindings enforce child
|
|
271
|
+
* authorization, and entry boundaries contain registrant failures.
|
|
272
|
+
*/
|
|
273
|
+
/**
|
|
274
|
+
* Per-entry renderSlot bindings. The binding is identity-stable per entry
|
|
275
|
+
* (memoized components must not resubscribe on unrelated re-renders) and dies
|
|
276
|
+
* with the entry: a retained closure calling after the entry's disposal hits
|
|
277
|
+
* the in-ledger check and throws.
|
|
278
|
+
*/
|
|
279
|
+
const renderSlotCache = /* @__PURE__ */ new WeakMap();
|
|
280
|
+
function boundRenderSlot(host, entry) {
|
|
281
|
+
let binding = renderSlotCache.get(entry);
|
|
282
|
+
if (!binding) {
|
|
283
|
+
binding = (key, owner, opts) => {
|
|
284
|
+
if (!host.isLive(entry)) throw new _prettier_ai_dsh_client_ui_slots.StaleAuthorizationError(`renderSlot('${key}') from a disposed registration`);
|
|
285
|
+
const declared = entry.children?.[key];
|
|
286
|
+
if (declared === void 0) throw new _prettier_ai_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is not declared by this entry's children`);
|
|
287
|
+
if (declared.kind === "chain") throw new _prettier_ai_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is declared 'chain' — use renderSlotChain`);
|
|
288
|
+
return (0, react_jsx_runtime.jsx)(SlotOutlet, {
|
|
289
|
+
slotKey: key,
|
|
290
|
+
ownerProps: owner,
|
|
291
|
+
opts
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
renderSlotCache.set(entry, binding);
|
|
295
|
+
}
|
|
296
|
+
return binding;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Per-entry renderSlotChain bindings: identity-stable per entry (same cache
|
|
300
|
+
* axis as renderSlot — a per-frame dispatch must not rebuild the binding) and
|
|
301
|
+
* dead with the entry. The chain-kind check is the plain-JS backstop twin of
|
|
302
|
+
* the declaration check; typed callers are narrowed to chain keys.
|
|
303
|
+
*/
|
|
304
|
+
const renderSlotChainCache = /* @__PURE__ */ new WeakMap();
|
|
305
|
+
function boundRenderSlotChain(host, entry) {
|
|
306
|
+
let binding = renderSlotChainCache.get(entry);
|
|
307
|
+
if (!binding) {
|
|
308
|
+
binding = (key, owner, opts) => {
|
|
309
|
+
if (!host.isLive(entry)) throw new _prettier_ai_dsh_client_ui_slots.StaleAuthorizationError(`renderSlotChain('${key}') from a disposed registration`);
|
|
310
|
+
const declared = entry.children?.[key];
|
|
311
|
+
if (declared === void 0) throw new _prettier_ai_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is not declared by this entry's children`);
|
|
312
|
+
if (declared.kind !== "chain") throw new _prettier_ai_dsh_client_ui_slots.SlotOwnershipError(`slot '${key}' is declared '${declared.kind}', not 'chain' — use renderSlot`);
|
|
313
|
+
return (0, react_jsx_runtime.jsx)(SlotOutlet, {
|
|
314
|
+
slotKey: key,
|
|
315
|
+
ownerProps: owner,
|
|
316
|
+
opts
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
renderSlotChainCache.set(entry, binding);
|
|
320
|
+
}
|
|
321
|
+
return binding;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Inject results cache: root entries per entry, session entries per
|
|
325
|
+
* (entry x scope binding). WeakMap keys are entry/binding objects (both
|
|
326
|
+
* identity-stable per registration/session scope), so cache lifetime rides
|
|
327
|
+
* the same axes as the values it memoizes.
|
|
328
|
+
*/
|
|
329
|
+
const rootInjectCache = /* @__PURE__ */ new WeakMap();
|
|
330
|
+
const sessionInjectCache = /* @__PURE__ */ new WeakMap();
|
|
331
|
+
const sessionMaybeInjectCache = /* @__PURE__ */ new WeakMap();
|
|
332
|
+
const EMPTY_INJECTED_PROPS = {};
|
|
333
|
+
function runInject(entry, binding, actions) {
|
|
334
|
+
const inject = entry.inject;
|
|
335
|
+
if (!inject) return EMPTY_INJECTED_PROPS;
|
|
336
|
+
const args = [];
|
|
337
|
+
if (binding !== void 0) args.push(binding.key);
|
|
338
|
+
if (actions !== void 0) args.push(actions);
|
|
339
|
+
return bindInjectHooks(inject(...args));
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Normalize one entry-owned inject face on its existing cache axis. Its hooks
|
|
343
|
+
* compartment remains the original Observable-only contract.
|
|
344
|
+
*/
|
|
345
|
+
function bindInjectHooks(face) {
|
|
346
|
+
const sources = face["hooks"];
|
|
347
|
+
if (sources === void 0) return face;
|
|
348
|
+
const { hooks: _hooks, ...rest } = face;
|
|
349
|
+
const bound = rest;
|
|
350
|
+
for (const [name, source] of Object.entries(sources)) {
|
|
351
|
+
const hookName = (0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name);
|
|
352
|
+
bound[hookName] = observableHook(source);
|
|
353
|
+
}
|
|
354
|
+
return bound;
|
|
355
|
+
}
|
|
356
|
+
const slotInjectCache = /* @__PURE__ */ new WeakMap();
|
|
357
|
+
const EMPTY_SLOT_INJECT = { props: EMPTY_INJECTED_PROPS };
|
|
358
|
+
/** Normalize one dispatcher-owned inject face by its stable object identity. */
|
|
359
|
+
function cachedSlotInject(face) {
|
|
360
|
+
if (face === void 0) return EMPTY_SLOT_INJECT;
|
|
361
|
+
let bound = slotInjectCache.get(face);
|
|
362
|
+
if (bound !== void 0) return bound;
|
|
363
|
+
const definitions = face["hooks"];
|
|
364
|
+
if (definitions === void 0) {
|
|
365
|
+
bound = { props: face };
|
|
366
|
+
slotInjectCache.set(face, bound);
|
|
367
|
+
return bound;
|
|
368
|
+
}
|
|
369
|
+
const { hooks: _hooks, ...rest } = face;
|
|
370
|
+
const props = rest;
|
|
371
|
+
let factories;
|
|
372
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
373
|
+
const hookName = (0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name);
|
|
374
|
+
if (typeof definition === "function") {
|
|
375
|
+
factories ??= {};
|
|
376
|
+
factories[name] = definition;
|
|
377
|
+
} else props[hookName] = observableHook(definition);
|
|
378
|
+
}
|
|
379
|
+
bound = factories === void 0 ? { props } : {
|
|
380
|
+
props,
|
|
381
|
+
slotHookFactories: factories
|
|
382
|
+
};
|
|
383
|
+
slotInjectCache.set(face, bound);
|
|
384
|
+
return bound;
|
|
385
|
+
}
|
|
386
|
+
/** Bind deferred slot-level factories for one stable renderSlot occurrence. */
|
|
387
|
+
function bindSlotHookFactories(factories, standard, hookContext) {
|
|
388
|
+
const hooks = {};
|
|
389
|
+
for (const [name, factory] of Object.entries(factories)) {
|
|
390
|
+
const hookName = (0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name);
|
|
391
|
+
hooks[hookName] = factory(standard, hookContext);
|
|
392
|
+
}
|
|
393
|
+
return hooks;
|
|
394
|
+
}
|
|
395
|
+
function cachedRootInject(entry, actions) {
|
|
396
|
+
let props = rootInjectCache.get(entry);
|
|
397
|
+
if (!props) {
|
|
398
|
+
props = runInject(entry, void 0, actions);
|
|
399
|
+
rootInjectCache.set(entry, props);
|
|
400
|
+
}
|
|
401
|
+
return props;
|
|
402
|
+
}
|
|
403
|
+
function cachedSessionInject(entry, binding, actions) {
|
|
404
|
+
let perBinding = sessionInjectCache.get(entry);
|
|
405
|
+
if (!perBinding) {
|
|
406
|
+
perBinding = /* @__PURE__ */ new WeakMap();
|
|
407
|
+
sessionInjectCache.set(entry, perBinding);
|
|
408
|
+
}
|
|
409
|
+
let props = perBinding.get(binding);
|
|
410
|
+
if (!props) {
|
|
411
|
+
props = runInject(entry, binding, actions);
|
|
412
|
+
perBinding.set(binding, props);
|
|
413
|
+
}
|
|
414
|
+
return props;
|
|
415
|
+
}
|
|
416
|
+
function cachedSessionMaybeInject(entry, binding, actions) {
|
|
417
|
+
let perBinding = sessionMaybeInjectCache.get(entry);
|
|
418
|
+
if (!perBinding) {
|
|
419
|
+
perBinding = /* @__PURE__ */ new WeakMap();
|
|
420
|
+
sessionMaybeInjectCache.set(entry, perBinding);
|
|
421
|
+
}
|
|
422
|
+
let props = perBinding.get(binding);
|
|
423
|
+
if (!props) {
|
|
424
|
+
props = runInject(entry, binding, actions);
|
|
425
|
+
perBinding.set(binding, props);
|
|
426
|
+
}
|
|
427
|
+
return props;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Locale `t` seat bindings, cached per (face, namespace, revision). The
|
|
431
|
+
* revision is part of the cache key ON PURPOSE: a locale switch mints a NEW
|
|
432
|
+
* function reference per namespace, so `React.memo` components taking `t`
|
|
433
|
+
* re-render through ordinary shallow comparison — freshness rides identity,
|
|
434
|
+
* no extra invalidation channel. Within one revision the reference is stable
|
|
435
|
+
* (memoized children do not churn on unrelated re-renders).
|
|
436
|
+
*/
|
|
437
|
+
const localeSeatCache = /* @__PURE__ */ new WeakMap();
|
|
438
|
+
function localeSeat(face, ns) {
|
|
439
|
+
let perNs = localeSeatCache.get(face);
|
|
440
|
+
if (!perNs) {
|
|
441
|
+
perNs = /* @__PURE__ */ new Map();
|
|
442
|
+
localeSeatCache.set(face, perNs);
|
|
443
|
+
}
|
|
444
|
+
const revision = face.getSnapshot().revision;
|
|
445
|
+
const cached = perNs.get(ns);
|
|
446
|
+
if (cached && cached.revision === revision) return cached.t;
|
|
447
|
+
const bound = face.bind(ns);
|
|
448
|
+
const t = (key, params) => bound(key, params);
|
|
449
|
+
perNs.set(ns, {
|
|
450
|
+
revision,
|
|
451
|
+
t
|
|
452
|
+
});
|
|
453
|
+
return t;
|
|
454
|
+
}
|
|
455
|
+
const noopSubscribe = () => () => {};
|
|
456
|
+
const zeroRevision = () => 0;
|
|
457
|
+
/**
|
|
458
|
+
* Per-face subscribe/getSnapshot closure pair. Cached by face identity: the
|
|
459
|
+
* face is one global source shared by every outlet, and uSES resubscribes
|
|
460
|
+
* whenever the subscribe reference changes — fresh closures per render would
|
|
461
|
+
* churn one unsubscribe/resubscribe pair per outlet per render.
|
|
462
|
+
*/
|
|
463
|
+
const localeSubscriptionCache = /* @__PURE__ */ new WeakMap();
|
|
464
|
+
function localeSubscription(face) {
|
|
465
|
+
let cached = localeSubscriptionCache.get(face);
|
|
466
|
+
if (!cached) {
|
|
467
|
+
cached = {
|
|
468
|
+
subscribe: (fn) => face.subscribe(fn),
|
|
469
|
+
getRevision: () => face.getSnapshot().revision
|
|
470
|
+
};
|
|
471
|
+
localeSubscriptionCache.set(face, cached);
|
|
472
|
+
}
|
|
473
|
+
return cached;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Subscribe an outlet to the installed locale face's revision (0 while none
|
|
477
|
+
* is installed — exactly one uSES call either way, keeping hook order
|
|
478
|
+
* stable). Every outlet re-renders on a locale switch; entry bodies then
|
|
479
|
+
* re-derive their `t` seat at the new revision. The face must be installed
|
|
480
|
+
* before the first render that needs it — a face appearing later has no
|
|
481
|
+
* notification channel to already-mounted outlets.
|
|
482
|
+
*/
|
|
483
|
+
function useLocaleRevision(face) {
|
|
484
|
+
const subscription = face !== void 0 ? localeSubscription(face) : void 0;
|
|
485
|
+
return (0, react.useSyncExternalStore)(subscription?.subscribe ?? noopSubscribe, subscription?.getRevision ?? zeroRevision);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Entry-identity React keys for entry boundaries. An outlet renders one
|
|
489
|
+
* winner per position (single/keyed/list cell head, chain election) through
|
|
490
|
+
* an error boundary; without a key, a boundary that failed on entry A would
|
|
491
|
+
* survive a winner change (re-election, shadowing fallback after an
|
|
492
|
+
* abdication, HMR re-registration) and keep a healthy entry B blacked out.
|
|
493
|
+
* Keying by entry identity remounts the boundary fresh whenever the winner
|
|
494
|
+
* changes (entries are identity-stable per registration, so the key is
|
|
495
|
+
* stable while the same entry stays the winner).
|
|
496
|
+
*/
|
|
497
|
+
let nextEntryKey = 0;
|
|
498
|
+
const entryKeys = /* @__PURE__ */ new WeakMap();
|
|
499
|
+
function entryKeyOf(entry) {
|
|
500
|
+
let key = entryKeys.get(entry);
|
|
501
|
+
if (key === void 0) {
|
|
502
|
+
key = nextEntryKey++;
|
|
503
|
+
entryKeys.set(entry, key);
|
|
504
|
+
}
|
|
505
|
+
return key;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Per-entry isolation: one registrant crashing (component render or inject
|
|
509
|
+
* factory) must not take down siblings. Assembly errors (missing providers)
|
|
510
|
+
* rethrow — a miswired shell must fail loud, not degrade into fallbacks.
|
|
511
|
+
* Every catch reports through `onEntryError` (the ledger's supervision
|
|
512
|
+
* seam); for shadowing kinds the report abdicates the entry, the outlet
|
|
513
|
+
* re-renders onto the cell's next survivor, and this boundary's crash face
|
|
514
|
+
* only shows until that re-render lands (permanently once the cell is dry —
|
|
515
|
+
* the outlet then owns the crash face).
|
|
516
|
+
*/
|
|
517
|
+
var SlotErrorBoundary = class extends react.Component {
|
|
518
|
+
state = { failed: false };
|
|
519
|
+
static getDerivedStateFromError(error) {
|
|
520
|
+
if (error instanceof SlotAssemblyError) throw error;
|
|
521
|
+
return { failed: true };
|
|
522
|
+
}
|
|
523
|
+
componentDidCatch(error) {
|
|
524
|
+
console.error(`slot entry crashed in '${this.props.slotKey}':`, error);
|
|
525
|
+
this.props.onEntryError(error);
|
|
526
|
+
}
|
|
527
|
+
render() {
|
|
528
|
+
if (this.state.failed) return (0, react_jsx_runtime.jsx)("div", { "data-slot-error": this.props.slotKey });
|
|
529
|
+
return this.props.children;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
const rootStandardCache = /* @__PURE__ */ new WeakMap();
|
|
533
|
+
const sessionStandardCache = /* @__PURE__ */ new WeakMap();
|
|
534
|
+
const sessionMaybeStandardCache = /* @__PURE__ */ new WeakMap();
|
|
535
|
+
/** Materialize one binding into stable framework Hook and plain-prop seats. */
|
|
536
|
+
function materializeStandardBinding(binding, optional) {
|
|
537
|
+
const standard = { ...binding.props };
|
|
538
|
+
for (const [name, source] of Object.entries(binding.hooks)) {
|
|
539
|
+
if (source === void 0 && !optional) throw new SlotAssemblyError(`strict standard hook '${name}' has no source`);
|
|
540
|
+
standard[(0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name)] = optional ? maybeObservableHook(source) : observableHook(source);
|
|
541
|
+
}
|
|
542
|
+
for (const [name, source] of Object.entries(binding.keyedHooks)) {
|
|
543
|
+
if (source === void 0 && !optional) throw new SlotAssemblyError(`strict keyed standard hook '${name}' has no source resolver`);
|
|
544
|
+
standard[(0, _prettier_ai_dsh_client_ui_slots.standardHookPropName)(name)] = keyedObservableHook(source);
|
|
545
|
+
}
|
|
546
|
+
return standard;
|
|
547
|
+
}
|
|
548
|
+
/** Stable official-props object used by contextual Hook factories. */
|
|
549
|
+
function standardProps(scope, rootBinding, scopeBinding) {
|
|
550
|
+
let root = rootStandardCache.get(rootBinding);
|
|
551
|
+
if (root === void 0) {
|
|
552
|
+
root = materializeStandardBinding(rootBinding, false);
|
|
553
|
+
rootStandardCache.set(rootBinding, root);
|
|
554
|
+
}
|
|
555
|
+
if (scope === "root") return root;
|
|
556
|
+
if (scopeBinding === void 0) throw new SlotAssemblyError(`scope '${scope}' rendered without a standard-source binding`);
|
|
557
|
+
const cache = scope === "session" ? sessionStandardCache : sessionMaybeStandardCache;
|
|
558
|
+
let perScope = cache.get(rootBinding);
|
|
559
|
+
if (perScope === void 0) {
|
|
560
|
+
perScope = /* @__PURE__ */ new WeakMap();
|
|
561
|
+
cache.set(rootBinding, perScope);
|
|
562
|
+
}
|
|
563
|
+
let standard = perScope.get(scopeBinding);
|
|
564
|
+
if (standard !== void 0) return standard;
|
|
565
|
+
standard = {
|
|
566
|
+
...root,
|
|
567
|
+
...materializeStandardBinding(scopeBinding, scope === "session-maybe")
|
|
568
|
+
};
|
|
569
|
+
perScope.set(scopeBinding, standard);
|
|
570
|
+
return standard;
|
|
571
|
+
}
|
|
572
|
+
const scopeAreaCache = /* @__PURE__ */ new WeakMap();
|
|
573
|
+
/** Bind one domain-owned scope area renderer to the current scope binding. */
|
|
574
|
+
function scopeAreaProvider(adapter) {
|
|
575
|
+
let Provider = scopeAreaCache.get(adapter);
|
|
576
|
+
if (Provider !== void 0) return Provider;
|
|
577
|
+
if (adapter.renderArea === void 0) throw new SlotAssemblyError("scope 'session' adapter does not provide its area renderer");
|
|
578
|
+
const renderArea = adapter.renderArea.bind(adapter);
|
|
579
|
+
Provider = function ScopeAreaProvider(props) {
|
|
580
|
+
return renderArea(useScopeBinding(), props);
|
|
581
|
+
};
|
|
582
|
+
scopeAreaCache.set(adapter, Provider);
|
|
583
|
+
return Provider;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Standard-kit synthesis shared by both scope branches: the global
|
|
587
|
+
* useSessions/useWorkspaces hooks, the per-session provide bundle (every
|
|
588
|
+
* `hooks` source becomes a `use<Name>` selector hook — useSession is the
|
|
589
|
+
* runtime's own 'session' contribution, no special case — and `props` spread
|
|
590
|
+
* verbatim), the store pair when declared, the renderSlot binding when
|
|
591
|
+
* children are declared, and the SessionProvider seat when the children
|
|
592
|
+
* declare a session-scope slot. Hosts hand out BARE observable sources
|
|
593
|
+
* (hooks never cross the host contract); every hook is bound HERE, cached
|
|
594
|
+
* per source (observableHook), so spreading a fresh kit object per render
|
|
595
|
+
* never churns child subscriptions.
|
|
596
|
+
*/
|
|
597
|
+
function standardKit(host, entry, scope, rootBinding, scopeBinding) {
|
|
598
|
+
const standard = standardProps(scope, rootBinding, scopeBinding);
|
|
599
|
+
const kit = { ...standard };
|
|
600
|
+
if (entry.locale !== void 0) {
|
|
601
|
+
const face = host.locale;
|
|
602
|
+
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?)`);
|
|
603
|
+
kit["t"] = localeSeat(face, entry.locale);
|
|
604
|
+
}
|
|
605
|
+
const scopedStoreBinding = scopeBinding?.key === void 0 ? void 0 : scopeBinding;
|
|
606
|
+
const store = host.storeOf(entry, scopedStoreBinding);
|
|
607
|
+
if (store !== void 0) {
|
|
608
|
+
kit["useStore"] = observableHook(store);
|
|
609
|
+
kit["actions"] = store.actions;
|
|
610
|
+
}
|
|
611
|
+
if (entry.children !== void 0) {
|
|
612
|
+
kit["renderSlot"] = boundRenderSlot(host, entry);
|
|
613
|
+
if (Object.values(entry.children).some((spec) => spec.kind === "chain")) kit["renderSlotChain"] = boundRenderSlotChain(host, entry);
|
|
614
|
+
if (Object.values(entry.children).some((spec) => spec.scope === "session")) {
|
|
615
|
+
const adapter = host.scope("session");
|
|
616
|
+
if (adapter === void 0) throw new SlotAssemblyError("entry declares a session child without an installed 'session' scope adapter");
|
|
617
|
+
kit["SessionProvider"] = scopeAreaProvider(adapter);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
kit,
|
|
622
|
+
standard,
|
|
623
|
+
actions: store?.actions
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* One rendered entry: standard kit + cached entry inject + common slot inject
|
|
628
|
+
* + owner props (owner wins). The shares are erased at this render boundary;
|
|
629
|
+
* the registration and renderSlot seams already proved their contracts.
|
|
630
|
+
*/
|
|
631
|
+
function ContextualEntry({ slotKey, Comp, kit, standard, injected, slotInjected, ownerProps, hookContext, hasHookContext }) {
|
|
632
|
+
const contextual = (0, react.useMemo)(() => {
|
|
633
|
+
if (!hasHookContext) throw new SlotAssemblyError(`slot '${slotKey}' has contextual injected Hooks but no hookContext`);
|
|
634
|
+
return bindSlotHookFactories(slotInjected.slotHookFactories, standard, hookContext);
|
|
635
|
+
}, [
|
|
636
|
+
hasHookContext,
|
|
637
|
+
hookContext,
|
|
638
|
+
slotInjected.slotHookFactories,
|
|
639
|
+
slotKey,
|
|
640
|
+
standard
|
|
641
|
+
]);
|
|
642
|
+
return (0, react_jsx_runtime.jsx)(Comp, {
|
|
643
|
+
...kit,
|
|
644
|
+
...injected,
|
|
645
|
+
...slotInjected.props,
|
|
646
|
+
...contextual,
|
|
647
|
+
...ownerProps
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function renderEntry(slotKey, Comp, kit, standard, injected, slotInjected, ownerProps, hookContext, hasHookContext) {
|
|
651
|
+
if (slotInjected.slotHookFactories === void 0) return (0, react_jsx_runtime.jsx)(Comp, {
|
|
652
|
+
...kit,
|
|
653
|
+
...injected,
|
|
654
|
+
...slotInjected.props,
|
|
655
|
+
...ownerProps
|
|
656
|
+
});
|
|
657
|
+
return (0, react_jsx_runtime.jsx)(ContextualEntry, {
|
|
658
|
+
slotKey,
|
|
659
|
+
Comp,
|
|
660
|
+
kit,
|
|
661
|
+
standard,
|
|
662
|
+
injected,
|
|
663
|
+
slotInjected,
|
|
664
|
+
ownerProps,
|
|
665
|
+
hookContext,
|
|
666
|
+
hasHookContext
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
function SessionEntry({ entry, ownerProps, binding, slotKey, slotInjected, hookContext, hasHookContext }) {
|
|
670
|
+
const host = useHost();
|
|
671
|
+
const rootBinding = useRootBinding();
|
|
672
|
+
const Comp = entry.component;
|
|
673
|
+
const { kit, standard, actions } = standardKit(host, entry, "session", rootBinding, binding);
|
|
674
|
+
return renderEntry(slotKey, Comp, kit, standard, cachedSessionInject(entry, binding, actions), slotInjected, ownerProps, hookContext, hasHookContext);
|
|
675
|
+
}
|
|
676
|
+
function SessionMaybeEntryBody({ entry, ownerProps, binding, slotKey, slotInjected, hookContext, hasHookContext }) {
|
|
677
|
+
const host = useHost();
|
|
678
|
+
const rootBinding = useRootBinding();
|
|
679
|
+
const Comp = entry.component;
|
|
680
|
+
const { kit, standard, actions } = standardKit(host, entry, "session-maybe", rootBinding, binding);
|
|
681
|
+
return renderEntry(slotKey, Comp, kit, standard, cachedSessionMaybeInject(entry, binding, actions), slotInjected, ownerProps, hookContext, hasHookContext);
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Session-maybe identity: adoption — the ONLY behavior (there is no
|
|
685
|
+
* hold-identity-forever mode). An incarnation born session-less ADOPTS the
|
|
686
|
+
* first session that arrives: identity holds across that one transition
|
|
687
|
+
* (undefined → first id), so a blank shell's DOM survives the moment a
|
|
688
|
+
* session appears. From then on the entry behaves exactly like a strict
|
|
689
|
+
* session entry: switching to a DIFFERENT session remounts (component-local
|
|
690
|
+
* state must not leak between sessions), and dropping back to no-session
|
|
691
|
+
* remounts into a fresh blank incarnation, which will adopt again.
|
|
692
|
+
* Component-local per-session state therefore clears by construction; state
|
|
693
|
+
* that must SURVIVE a switch belongs in session-bound sources (machine,
|
|
694
|
+
* store, hooks) — the existing layering rule, now load-bearing.
|
|
695
|
+
*/
|
|
696
|
+
function SessionMaybeEntry({ entry, ownerProps, slotKey, slotInjected, hookContext, hasHookContext }) {
|
|
697
|
+
const binding = useScopeBinding();
|
|
698
|
+
const [state, setState] = (0, react.useState)(FIRST_INCARNATION);
|
|
699
|
+
let { adopted, epoch } = state;
|
|
700
|
+
if (binding.key !== void 0 && adopted === void 0) {
|
|
701
|
+
adopted = binding.key;
|
|
702
|
+
setState({
|
|
703
|
+
adopted,
|
|
704
|
+
epoch
|
|
705
|
+
});
|
|
706
|
+
} else if (adopted !== void 0 && binding.key !== void 0 && binding.key !== adopted) {
|
|
707
|
+
adopted = binding.key;
|
|
708
|
+
epoch += 1;
|
|
709
|
+
setState({
|
|
710
|
+
adopted,
|
|
711
|
+
epoch
|
|
712
|
+
});
|
|
713
|
+
} else if (adopted !== void 0 && binding.key === void 0) {
|
|
714
|
+
adopted = void 0;
|
|
715
|
+
epoch += 1;
|
|
716
|
+
setState({
|
|
717
|
+
adopted,
|
|
718
|
+
epoch
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return (0, react_jsx_runtime.jsx)(SessionMaybeEntryBody, {
|
|
722
|
+
entry,
|
|
723
|
+
ownerProps,
|
|
724
|
+
binding,
|
|
725
|
+
slotKey,
|
|
726
|
+
slotInjected,
|
|
727
|
+
hookContext,
|
|
728
|
+
hasHookContext
|
|
729
|
+
}, epoch);
|
|
730
|
+
}
|
|
731
|
+
const FIRST_INCARNATION = {
|
|
732
|
+
adopted: void 0,
|
|
733
|
+
epoch: 0
|
|
734
|
+
};
|
|
735
|
+
function RootEntry({ entry, ownerProps, slotKey, slotInjected, hookContext, hasHookContext }) {
|
|
736
|
+
const host = useHost();
|
|
737
|
+
const rootBinding = useRootBinding();
|
|
738
|
+
const Comp = entry.component;
|
|
739
|
+
const { kit, standard, actions } = standardKit(host, entry, "root", rootBinding, void 0);
|
|
740
|
+
return renderEntry(slotKey, Comp, kit, standard, cachedRootInject(entry, actions), slotInjected, ownerProps, hookContext, hasHookContext);
|
|
741
|
+
}
|
|
742
|
+
function StrictSessionEntry({ slotKey, entry, ownerProps, slotInjected, hookContext, hasHookContext, onEntryError }) {
|
|
743
|
+
const binding = useScopeBinding();
|
|
744
|
+
if (binding.key === void 0) throw new SlotAssemblyError(`strict session slot '${slotKey}' rendered without a scope binding`);
|
|
745
|
+
return (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
|
|
746
|
+
slotKey,
|
|
747
|
+
onEntryError,
|
|
748
|
+
children: (0, react_jsx_runtime.jsx)(SessionEntry, {
|
|
749
|
+
entry,
|
|
750
|
+
ownerProps,
|
|
751
|
+
binding,
|
|
752
|
+
slotKey,
|
|
753
|
+
slotInjected,
|
|
754
|
+
hookContext,
|
|
755
|
+
hasHookContext
|
|
756
|
+
})
|
|
757
|
+
}, binding.key);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Anchor style shared by every outlet wrapper: `display:contents` keeps the
|
|
761
|
+
* wrapper out of layout (grid/flex parents see the slot's own children), so
|
|
762
|
+
* the anchor is purely addressable surface. Module-level constant — a stable
|
|
763
|
+
* reference so the wrapper never diffs its style prop.
|
|
764
|
+
*/
|
|
765
|
+
const ANCHOR_STYLE = { display: "contents" };
|
|
766
|
+
function SlotOutlet({ slotKey, ownerProps, opts }) {
|
|
767
|
+
const host = useHost();
|
|
768
|
+
(0, react.useSyncExternalStore)((fn) => host.subscribe(slotKey, fn), () => host.getVersion(slotKey));
|
|
769
|
+
useLocaleRevision(host.locale);
|
|
770
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
771
|
+
"data-slot": slotKey,
|
|
772
|
+
style: ANCHOR_STYLE,
|
|
773
|
+
children: renderOutletContent(host, slotKey, ownerProps, opts, useScopeBinding())
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
/** Kind dispatch behind the outlet anchor (single/keyed/list/chain, fallbacks, crash faces). */
|
|
777
|
+
function renderOutletContent(host, slotKey, ownerProps, opts, scopeBinding) {
|
|
778
|
+
const spec = host.specOf(slotKey);
|
|
779
|
+
if (!spec) return null;
|
|
780
|
+
if (spec.kind === "chain" && opts?.fallbackOnly === true) return renderChainResult(slotKey, null, opts);
|
|
781
|
+
if (spec.scope === "session" && scopeBinding.key === void 0) throw new SlotAssemblyError(`strict session slot '${slotKey}' rendered without a scope binding`);
|
|
782
|
+
const entries = host.entriesOf(slotKey);
|
|
783
|
+
const slotInjected = cachedSlotInject(spec.inject);
|
|
784
|
+
const guarded = (entry, key, owner = ownerProps) => {
|
|
785
|
+
const hasHookContext = opts !== void 0 && Object.hasOwn(opts, "hookContext");
|
|
786
|
+
const hookContext = opts?.hookContext;
|
|
787
|
+
const onEntryError = (error) => {
|
|
788
|
+
host.reportEntryError(slotKey, entry, error, { abdicate: spec.kind !== "chain" });
|
|
789
|
+
};
|
|
790
|
+
return spec.scope === "session" ? (0, react_jsx_runtime.jsx)(StrictSessionEntry, {
|
|
791
|
+
slotKey,
|
|
792
|
+
entry,
|
|
793
|
+
ownerProps: owner,
|
|
794
|
+
slotInjected,
|
|
795
|
+
hookContext,
|
|
796
|
+
hasHookContext,
|
|
797
|
+
onEntryError
|
|
798
|
+
}, key) : (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
|
|
799
|
+
slotKey,
|
|
800
|
+
onEntryError,
|
|
801
|
+
children: spec.scope === "session-maybe" ? (0, react_jsx_runtime.jsx)(SessionMaybeEntry, {
|
|
802
|
+
entry,
|
|
803
|
+
ownerProps: owner,
|
|
804
|
+
slotKey,
|
|
805
|
+
slotInjected,
|
|
806
|
+
hookContext,
|
|
807
|
+
hasHookContext
|
|
808
|
+
}) : (0, react_jsx_runtime.jsx)(RootEntry, {
|
|
809
|
+
entry,
|
|
810
|
+
ownerProps: owner,
|
|
811
|
+
slotKey,
|
|
812
|
+
slotInjected,
|
|
813
|
+
hookContext,
|
|
814
|
+
hasHookContext
|
|
815
|
+
})
|
|
816
|
+
}, key);
|
|
817
|
+
};
|
|
818
|
+
const deadCell = () => (0, react_jsx_runtime.jsx)("div", { "data-slot-error": slotKey });
|
|
819
|
+
if (spec.kind === "single") {
|
|
820
|
+
const entry = host.entriesOfSlot(slotKey)[0];
|
|
821
|
+
if (!entry) return entries.length > 0 ? deadCell() : (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
|
|
822
|
+
return guarded(entry, entryKeyOf(entry));
|
|
823
|
+
}
|
|
824
|
+
if (spec.kind === "keyed") {
|
|
825
|
+
const entry = host.entriesOfSlot(slotKey).find((e) => e.options.key === opts?.entryKey);
|
|
826
|
+
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 });
|
|
827
|
+
return guarded(entry, entryKeyOf(entry));
|
|
828
|
+
}
|
|
829
|
+
if (spec.kind === "chain") {
|
|
830
|
+
let elected = null;
|
|
831
|
+
for (const entry of entries) {
|
|
832
|
+
let matched;
|
|
833
|
+
try {
|
|
834
|
+
matched = entry.select(ownerProps);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
console.error(`chain selector crashed in '${slotKey}' (${entry.registrant ?? "unknown registrant"}), treating as declined:`, error);
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
if (matched !== null) {
|
|
840
|
+
elected = guarded(entry, entryKeyOf(entry), {
|
|
841
|
+
...ownerProps,
|
|
842
|
+
matched
|
|
843
|
+
});
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return renderChainResult(slotKey, elected, opts);
|
|
848
|
+
}
|
|
849
|
+
const rows = host.entriesOfSlot(slotKey).map((entry) => ({
|
|
850
|
+
entry,
|
|
851
|
+
id: entry.options.id,
|
|
852
|
+
order: entry.options.order ?? 0
|
|
853
|
+
}));
|
|
854
|
+
const rowIds = new Set(rows.map((row) => row.id));
|
|
855
|
+
for (const entry of entries) {
|
|
856
|
+
if (rowIds.has(entry.options.id)) continue;
|
|
857
|
+
rowIds.add(entry.options.id);
|
|
858
|
+
rows.push({
|
|
859
|
+
entry: void 0,
|
|
860
|
+
id: entry.options.id,
|
|
861
|
+
order: entry.options.order ?? 0
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
let list = [...rows].sort((a, b) => a.order - b.order);
|
|
865
|
+
if (opts?.only !== void 0) list = list.filter((item) => item.id === opts.only);
|
|
866
|
+
if (list.length === 0) return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
|
|
867
|
+
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}`)) });
|
|
868
|
+
}
|
|
869
|
+
/** Render a chain election while preserving the overlay fallback's tree position. */
|
|
870
|
+
function renderChainResult(slotKey, elected, opts) {
|
|
871
|
+
if (!opts?.overlay) return elected ?? (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: opts?.fallback ?? null });
|
|
872
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
|
|
873
|
+
"data-chain-overlay-fallback": slotKey,
|
|
874
|
+
style: { display: elected === null ? "contents" : "none" },
|
|
875
|
+
children: opts.fallback ?? null
|
|
876
|
+
}), elected] });
|
|
877
|
+
}
|
|
878
|
+
/** Root outlet: the shell's single ctx-level render entry — an unregistered 'root' is a boot-order failure, never a silent blank. */
|
|
879
|
+
function RootOutlet({ ownerProps }) {
|
|
880
|
+
const host = useHost();
|
|
881
|
+
(0, react.useSyncExternalStore)((fn) => host.subscribe("root", fn), () => host.getVersion("root"));
|
|
882
|
+
useLocaleRevision(host.locale);
|
|
883
|
+
const entry = host.entriesOfSlot("root")[0];
|
|
884
|
+
if (!entry) {
|
|
885
|
+
if (host.entriesOf("root").length > 0) return (0, react_jsx_runtime.jsx)("div", { "data-slot-error": "root" });
|
|
886
|
+
throw new SlotAssemblyError("renderSlot('root') before any 'root' registration (boot order)");
|
|
887
|
+
}
|
|
888
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
889
|
+
"data-slot": "root",
|
|
890
|
+
style: ANCHOR_STYLE,
|
|
891
|
+
children: (0, react_jsx_runtime.jsx)(SlotErrorBoundary, {
|
|
892
|
+
slotKey: "root",
|
|
893
|
+
onEntryError: (error) => {
|
|
894
|
+
host.reportEntryError("root", entry, error, { abdicate: true });
|
|
895
|
+
},
|
|
896
|
+
children: (0, react_jsx_runtime.jsx)(RootEntry, {
|
|
897
|
+
entry,
|
|
898
|
+
ownerProps,
|
|
899
|
+
slotKey: "root",
|
|
900
|
+
slotInjected: EMPTY_SLOT_INJECT,
|
|
901
|
+
hookContext: void 0,
|
|
902
|
+
hasHookContext: false
|
|
903
|
+
})
|
|
904
|
+
}, entryKeyOf(entry))
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Build the renderer installed into the `ui-renderer` SlotRegistry
|
|
909
|
+
* (ctx.slots.install(createSlotRenderer()) at boot; the service owns the
|
|
910
|
+
* install/renderSlot contract and the double-install/not-installed throws).
|
|
911
|
+
* @returns the renderer.
|
|
912
|
+
*/
|
|
913
|
+
function createSlotRenderer() {
|
|
914
|
+
return { renderRoot(host, ownerProps) {
|
|
915
|
+
return (0, react_jsx_runtime.jsx)(HostContext.Provider, {
|
|
916
|
+
value: host,
|
|
917
|
+
children: (0, react_jsx_runtime.jsx)(RootStandardProvider, { children: (0, react_jsx_runtime.jsx)(ScopeProvider, {
|
|
918
|
+
scope: "session-maybe",
|
|
919
|
+
children: (0, react_jsx_runtime.jsx)(RootOutlet, { ownerProps })
|
|
920
|
+
}) })
|
|
921
|
+
});
|
|
922
|
+
} };
|
|
923
|
+
}
|
|
924
|
+
//#endregion
|
|
925
|
+
//#region lib/types/client/app.js
|
|
926
|
+
/**
|
|
927
|
+
* Build the assembled application factory.
|
|
928
|
+
* @param deps - Active UI-renderer dependencies.
|
|
929
|
+
* @returns Factory producing the application React tree.
|
|
930
|
+
*/
|
|
931
|
+
function buildRenderApp(deps) {
|
|
932
|
+
const { ctx } = deps;
|
|
933
|
+
return () => ctx.slots.renderSlot("root", {});
|
|
934
|
+
}
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region lib/types/client/registry.js
|
|
937
|
+
/**
|
|
938
|
+
* SlotRegistry: the renderer-owned Cordis service over the pure
|
|
939
|
+
* SlotCore (ui-slots owns registration semantics, the declaration ledger,
|
|
940
|
+
* the load-time validations, and the unload cascade). This layer owns what
|
|
941
|
+
* needs a live application: the 'slots/changed' event bridge, register and
|
|
942
|
+
* declaration injection through the caller's ctx.effect (fiber unload
|
|
943
|
+
* collects both), the renderer installation contract (install()/renderSlot('root') +
|
|
944
|
+
* the SlotRendererHost face), and the store INSTANCE axis — handle x scope
|
|
945
|
+
* key -> create/cache, dropped with the last holding entry, session instances
|
|
946
|
+
* cleared (with persisted state) on scope death.
|
|
947
|
+
*/
|
|
948
|
+
/** Instance key for root-scoped store records (session records key by session id, so the literal cannot collide). */
|
|
949
|
+
const ROOT_INSTANCE_KEY = "root";
|
|
950
|
+
/** cordis Service layer of the slot system; see the module doc for the split with SlotCore. */
|
|
951
|
+
var SlotRegistry = class extends _prettier_ai_cordis.Service {
|
|
952
|
+
_core = new _prettier_ai_dsh_client_ui_slots.SlotCore();
|
|
953
|
+
/** Store-instance axis: handle -> mounted scope, refcount, resolved instances. */
|
|
954
|
+
_stores = /* @__PURE__ */ new Map();
|
|
955
|
+
/** Latest live Context generation for each scoped store key. */
|
|
956
|
+
_storeScopeOwners = /* @__PURE__ */ new Map();
|
|
957
|
+
_renderer;
|
|
958
|
+
_locale;
|
|
959
|
+
_host;
|
|
960
|
+
_rootContributions = [];
|
|
961
|
+
_rootListeners = /* @__PURE__ */ new Set();
|
|
962
|
+
_rootBinding = {
|
|
963
|
+
key: void 0,
|
|
964
|
+
hooks: {},
|
|
965
|
+
keyedHooks: {},
|
|
966
|
+
props: {}
|
|
967
|
+
};
|
|
968
|
+
_rootSource = {
|
|
969
|
+
getSnapshot: () => this._rootBinding,
|
|
970
|
+
subscribe: (listener) => {
|
|
971
|
+
this._rootListeners.add(listener);
|
|
972
|
+
return () => {
|
|
973
|
+
this._rootListeners.delete(listener);
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
_scopes = /* @__PURE__ */ new Map();
|
|
978
|
+
_scopeRevision = 0;
|
|
979
|
+
_scopeListeners = /* @__PURE__ */ new Set();
|
|
980
|
+
_scopeRevisionSource = {
|
|
981
|
+
getSnapshot: () => this._scopeRevision,
|
|
982
|
+
subscribe: (listener) => {
|
|
983
|
+
this._scopeListeners.add(listener);
|
|
984
|
+
return () => {
|
|
985
|
+
this._scopeListeners.delete(listener);
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
/**
|
|
990
|
+
* @param ctx - owning root context.
|
|
991
|
+
*/
|
|
992
|
+
constructor(ctx) {
|
|
993
|
+
super(ctx, "slots");
|
|
994
|
+
this._core.onMutate((key) => {
|
|
995
|
+
ctx.emit("slots/changed", key);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Install an effect for each declaration lifetime of a slot. The callback
|
|
1000
|
+
* runs synchronously when the declaration already exists; otherwise it runs
|
|
1001
|
+
* inside the declaring `register()` call after the declaration is committed.
|
|
1002
|
+
* Collapse disposes the effect and a later declaration runs it again.
|
|
1003
|
+
* Callback effects are synchronous disposers; iterable effects install
|
|
1004
|
+
* transactionally and dispose in reverse order. The controller belongs to
|
|
1005
|
+
* the caller's fiber, so plugin unload cancels a pending wait and removes any
|
|
1006
|
+
* active contribution.
|
|
1007
|
+
*
|
|
1008
|
+
* @param key - declared SlotMap key to depend on.
|
|
1009
|
+
* @param callback - creates one disposer or an iterable of disposers.
|
|
1010
|
+
* @returns idempotent disposer for the wait and active effect.
|
|
1011
|
+
* @throws callback setup failures synchronously when the slot is already declared.
|
|
1012
|
+
*/
|
|
1013
|
+
inject(key, callback) {
|
|
1014
|
+
const ctx = this.ctx;
|
|
1015
|
+
const disposeController = ctx.effect(() => {
|
|
1016
|
+
let active;
|
|
1017
|
+
let activeEpoch;
|
|
1018
|
+
let stopped = false;
|
|
1019
|
+
let unsubscribe = () => {};
|
|
1020
|
+
const stop = () => {
|
|
1021
|
+
if (stopped) return;
|
|
1022
|
+
stopped = true;
|
|
1023
|
+
unsubscribe();
|
|
1024
|
+
const dispose = active;
|
|
1025
|
+
active = void 0;
|
|
1026
|
+
activeEpoch = void 0;
|
|
1027
|
+
dispose?.();
|
|
1028
|
+
};
|
|
1029
|
+
const reconcile = () => {
|
|
1030
|
+
if (stopped) return;
|
|
1031
|
+
const spec = this._core.specDynamic(key);
|
|
1032
|
+
const epoch = this._core.declarationEpoch(key);
|
|
1033
|
+
if (active !== void 0 && activeEpoch === epoch) return;
|
|
1034
|
+
const dispose = active;
|
|
1035
|
+
active = void 0;
|
|
1036
|
+
activeEpoch = void 0;
|
|
1037
|
+
dispose?.();
|
|
1038
|
+
if (spec === void 0) return;
|
|
1039
|
+
const disposeEffect = ctx.effect(callback, `slots.inject(${JSON.stringify(key)}): declaration`);
|
|
1040
|
+
active = () => {
|
|
1041
|
+
disposeEffect();
|
|
1042
|
+
};
|
|
1043
|
+
activeEpoch = epoch;
|
|
1044
|
+
};
|
|
1045
|
+
const changed = () => {
|
|
1046
|
+
try {
|
|
1047
|
+
reconcile();
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
if (error?.code === "INACTIVE_EFFECT") {
|
|
1050
|
+
stop();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
stop();
|
|
1054
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
1055
|
+
queueMicrotask(() => {
|
|
1056
|
+
throw failure;
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
unsubscribe = this._core.subscribeDeclaration(key, changed);
|
|
1061
|
+
try {
|
|
1062
|
+
reconcile();
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
stop();
|
|
1065
|
+
throw error;
|
|
1066
|
+
}
|
|
1067
|
+
return stop;
|
|
1068
|
+
}, `slots.inject(${JSON.stringify(key)})`);
|
|
1069
|
+
return () => {
|
|
1070
|
+
disposeController();
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Install the shell's renderer (ui-renderer's createSlotRenderer product).
|
|
1075
|
+
* Boot-once: a second install throws. Runs through the caller's ctx.effect,
|
|
1076
|
+
* so shell fiber unload uninstalls the renderer.
|
|
1077
|
+
* @param renderer - the outlet machinery implementing SlotRenderer.
|
|
1078
|
+
*/
|
|
1079
|
+
install(renderer) {
|
|
1080
|
+
if (this._renderer !== void 0) throw new Error("slot renderer already installed (install() is boot-once)");
|
|
1081
|
+
this.ctx.effect(() => {
|
|
1082
|
+
this._renderer = renderer;
|
|
1083
|
+
return () => {
|
|
1084
|
+
if (this._renderer === renderer) this._renderer = void 0;
|
|
1085
|
+
};
|
|
1086
|
+
}, "slots.install()");
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Install the locale face backing the `t` standard seat (the locale
|
|
1090
|
+
* plugin's product; same boot-once discipline as the renderer install).
|
|
1091
|
+
* Runs through the caller's ctx.effect, so the installing fiber's unload
|
|
1092
|
+
* uninstalls the face.
|
|
1093
|
+
* @param face - namespace binder + revision observable.
|
|
1094
|
+
*/
|
|
1095
|
+
installLocale(face) {
|
|
1096
|
+
if (this._locale !== void 0) throw new Error("locale face already installed (installLocale() is boot-once)");
|
|
1097
|
+
this.ctx.effect(() => {
|
|
1098
|
+
this._locale = face;
|
|
1099
|
+
return () => {
|
|
1100
|
+
if (this._locale === face) this._locale = void 0;
|
|
1101
|
+
};
|
|
1102
|
+
}, "slots.installLocale()");
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Contribute domain-owned root data. Hook names must be globally unique;
|
|
1106
|
+
* registration and disposal republish one atomic root binding.
|
|
1107
|
+
* @param contribution - bare sources and stable props.
|
|
1108
|
+
* @returns disposer owned by the caller's Cordis fiber.
|
|
1109
|
+
*/
|
|
1110
|
+
provideRoot(contribution) {
|
|
1111
|
+
const dispose = this.ctx.effect(() => {
|
|
1112
|
+
this._rootContributions.push(contribution);
|
|
1113
|
+
try {
|
|
1114
|
+
this.rebuildRootBinding();
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
this._rootContributions.pop();
|
|
1117
|
+
throw error;
|
|
1118
|
+
}
|
|
1119
|
+
return () => {
|
|
1120
|
+
const index = this._rootContributions.indexOf(contribution);
|
|
1121
|
+
if (index === -1) return;
|
|
1122
|
+
this._rootContributions.splice(index, 1);
|
|
1123
|
+
this.rebuildRootBinding();
|
|
1124
|
+
};
|
|
1125
|
+
}, "slots.provideRoot()");
|
|
1126
|
+
return () => {
|
|
1127
|
+
dispose();
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Install the owner adapter for one strict scope. Its optional counterpart
|
|
1132
|
+
* resolves through the same adapter.
|
|
1133
|
+
* @param scope - strict scope name.
|
|
1134
|
+
* @param adapter - current/resolved binding source and release notifications.
|
|
1135
|
+
*/
|
|
1136
|
+
installScope(scope, adapter) {
|
|
1137
|
+
if (this._scopes.has(scope)) throw new Error(`slot scope '${scope}' already has an adapter`);
|
|
1138
|
+
this.ctx.effect(() => {
|
|
1139
|
+
this._scopes.set(scope, adapter);
|
|
1140
|
+
this.publishScopeRevision();
|
|
1141
|
+
return () => {
|
|
1142
|
+
if (this._scopes.get(scope) === adapter) {
|
|
1143
|
+
this._scopes.delete(scope);
|
|
1144
|
+
this.publishScopeRevision();
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}, `slots.installScope(${JSON.stringify(scope)})`);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Bind all scoped Store handles to one owner Context lifetime. The cleanup
|
|
1151
|
+
* materializes an otherwise-unused handle before clearing it, because a
|
|
1152
|
+
* previous application run may have persisted state for a Slot that this
|
|
1153
|
+
* scope never rendered. Rebinding the same key transfers cleanup ownership
|
|
1154
|
+
* to the newest Context generation.
|
|
1155
|
+
*
|
|
1156
|
+
* @param binding - materialized scope identity and its owning Context.
|
|
1157
|
+
*/
|
|
1158
|
+
bindStoreScope(binding) {
|
|
1159
|
+
if (this._storeScopeOwners.get(binding.key) === binding.ctx) return;
|
|
1160
|
+
this._storeScopeOwners.set(binding.key, binding.ctx);
|
|
1161
|
+
binding.ctx.effect(() => () => {
|
|
1162
|
+
if (this._storeScopeOwners.get(binding.key) !== binding.ctx) return;
|
|
1163
|
+
this._storeScopeOwners.delete(binding.key);
|
|
1164
|
+
this.clearStoreScope(binding.key);
|
|
1165
|
+
}, `slots: store scope ${binding.key}`);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* The single ctx-level render entry: the shell renders 'root'; every other
|
|
1169
|
+
* key renders inside components through the props renderSlot face. All
|
|
1170
|
+
* three guards are fail-loud boot-order checks, no fallback.
|
|
1171
|
+
* @param key - must be 'root' (runtime-enforced for dynamically composed callers).
|
|
1172
|
+
* @param owner - owner share for the root entry (the shell supplies {}).
|
|
1173
|
+
* @returns the rendered root tree.
|
|
1174
|
+
*/
|
|
1175
|
+
renderSlot(key, owner) {
|
|
1176
|
+
if (key !== "root") throw new Error(`ctx-level renderSlot only renders 'root' (got "${key}"); child slots render through the component props face`);
|
|
1177
|
+
if (this._renderer === void 0) throw new Error("slot renderer not installed — boot must call ctx.slots.install(createSlotRenderer()) before rendering 'root'");
|
|
1178
|
+
if (this._core.entries("root").length === 0) throw new Error("'root' has no registration — a layout entry must register into 'root' before the shell renders it");
|
|
1179
|
+
return this._renderer.renderRoot(this.hostFace(), owner);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Snapshot entries for a key (render-erased view; stable reference between mutations).
|
|
1183
|
+
* @param key - SlotMap key.
|
|
1184
|
+
* @returns registered entries.
|
|
1185
|
+
*/
|
|
1186
|
+
entries(key) {
|
|
1187
|
+
return this._core.entries(key);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Shadowing winners per cell for a key: the first live (non-abdicated)
|
|
1191
|
+
* entry of each cell in priority order — what outlets render; chain keys
|
|
1192
|
+
* pass through unchanged (election consumes every entry). The raw
|
|
1193
|
+
* {@link SlotsService.entries} view stays the inspection surface. Fresh
|
|
1194
|
+
* array per call, not a uSES getSnapshot source.
|
|
1195
|
+
* @param key - SlotMap key.
|
|
1196
|
+
* @returns the winning entry per occupied cell.
|
|
1197
|
+
*/
|
|
1198
|
+
entriesOfSlot(key) {
|
|
1199
|
+
return this._core.entriesOfSlot(key);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Export the current JSON-safe Slot declaration tree for read-only inspection.
|
|
1203
|
+
* @param root - exact live Slot root; omitted returns all roots.
|
|
1204
|
+
* @returns selected Slot trees.
|
|
1205
|
+
*/
|
|
1206
|
+
snapshot(root) {
|
|
1207
|
+
return this._core.snapshot(root);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Observe entry boundary crashes (every render-time entry failure the
|
|
1211
|
+
* boundaries contain, abdicating or not) — the supervision seam for
|
|
1212
|
+
* plugins mirroring contribution health. Fires synchronously per report,
|
|
1213
|
+
* after the registry mutated for abdicating crashes. Callers own the
|
|
1214
|
+
* disposer (wire it through ctx.effect for fiber-lifetime cleanup, as with
|
|
1215
|
+
* {@link SlotsService.subscribe}).
|
|
1216
|
+
* @param fn - called with the slot key, the crashed entry, the crash
|
|
1217
|
+
* cause, and `abdicated`: whether the crash retired the entry from its cell.
|
|
1218
|
+
* @returns unsubscribe.
|
|
1219
|
+
*/
|
|
1220
|
+
onEntryError(fn) {
|
|
1221
|
+
return this._core.onEntryError(fn);
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Look up a declared spec (register-declared or the built-in 'root').
|
|
1225
|
+
* @param key - SlotMap key.
|
|
1226
|
+
* @returns spec or undefined.
|
|
1227
|
+
*/
|
|
1228
|
+
spec(key) {
|
|
1229
|
+
return this._core.spec(key);
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Subscribe to a key's registration changes (microtask-batched).
|
|
1233
|
+
* @param key - SlotMap key.
|
|
1234
|
+
* @param fn - change callback.
|
|
1235
|
+
* @returns unsubscribe.
|
|
1236
|
+
*/
|
|
1237
|
+
subscribe(key, fn) {
|
|
1238
|
+
return this._core.subscribe(key, fn);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Version counter for uSES pairing.
|
|
1242
|
+
* @param key - SlotMap key.
|
|
1243
|
+
* @returns current version.
|
|
1244
|
+
*/
|
|
1245
|
+
getVersion(key) {
|
|
1246
|
+
return this._core.getVersion(key);
|
|
1247
|
+
}
|
|
1248
|
+
/** Delegating registration path: factory minting + registrant stamp + core write + instance-axis bookkeeping. */
|
|
1249
|
+
_register(options, component) {
|
|
1250
|
+
const store = typeof options.store === "function" ? options.store() : options.store;
|
|
1251
|
+
const registrant = options.registrant ?? this.ctx.fiber?.name;
|
|
1252
|
+
const erased = {
|
|
1253
|
+
...options,
|
|
1254
|
+
...store !== void 0 ? { store } : {},
|
|
1255
|
+
...registrant !== void 0 ? { registrant } : {}
|
|
1256
|
+
};
|
|
1257
|
+
const dispose = this._core.register(erased, component);
|
|
1258
|
+
if (store !== void 0) {
|
|
1259
|
+
const scope = this._core.specDynamic(options.name).scope;
|
|
1260
|
+
this._acquire(store, scope);
|
|
1261
|
+
}
|
|
1262
|
+
let disposed = false;
|
|
1263
|
+
return () => {
|
|
1264
|
+
if (disposed) return;
|
|
1265
|
+
disposed = true;
|
|
1266
|
+
dispose();
|
|
1267
|
+
if (store !== void 0) this._release(store);
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
/** Build the domain-neutral host face once; installed adapters remain live through getters. */
|
|
1271
|
+
hostFace() {
|
|
1272
|
+
if (this._host !== void 0) return this._host;
|
|
1273
|
+
const service = this;
|
|
1274
|
+
this._host = {
|
|
1275
|
+
subscribe: (key, fn) => this._core.subscribe(key, fn),
|
|
1276
|
+
getVersion: (key) => this._core.getVersion(key),
|
|
1277
|
+
entriesOf: (key) => this._core.entries(key),
|
|
1278
|
+
entriesOfSlot: (key) => this._core.entriesOfSlot(key),
|
|
1279
|
+
reportEntryError: (key, entry, error, info) => {
|
|
1280
|
+
this._core.reportEntryError(key, entry, error, info);
|
|
1281
|
+
},
|
|
1282
|
+
specOf: (key) => this._core.specDynamic(key),
|
|
1283
|
+
isLive: (entry) => this._core.isLive(entry),
|
|
1284
|
+
storeOf: (entry, scopeBinding) => entry.store === void 0 ? void 0 : this.resolveStore(entry.store, scopeBinding),
|
|
1285
|
+
root: this._rootSource,
|
|
1286
|
+
scopeRevision: this._scopeRevisionSource,
|
|
1287
|
+
scope: (scope) => service._scopes.get(scope === "session-maybe" ? "session" : scope),
|
|
1288
|
+
get locale() {
|
|
1289
|
+
return service._locale;
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
return this._host;
|
|
1293
|
+
}
|
|
1294
|
+
/** Validate and atomically publish the current root contribution roster. */
|
|
1295
|
+
rebuildRootBinding() {
|
|
1296
|
+
const hooks = {};
|
|
1297
|
+
const keyedHooks = {};
|
|
1298
|
+
const props = {};
|
|
1299
|
+
const finalProps = /* @__PURE__ */ new Set();
|
|
1300
|
+
for (const contribution of this._rootContributions) {
|
|
1301
|
+
copyUnique("hook", hooks, contribution.hooks, finalProps, _prettier_ai_dsh_client_ui_slots.standardHookPropName);
|
|
1302
|
+
copyUnique("keyed hook", keyedHooks, contribution.keyedHooks, finalProps, _prettier_ai_dsh_client_ui_slots.standardHookPropName);
|
|
1303
|
+
copyUnique("prop", props, contribution.props, finalProps, (name) => name);
|
|
1304
|
+
}
|
|
1305
|
+
this._rootBinding = {
|
|
1306
|
+
key: void 0,
|
|
1307
|
+
hooks,
|
|
1308
|
+
keyedHooks,
|
|
1309
|
+
props
|
|
1310
|
+
};
|
|
1311
|
+
for (const listener of [...this._rootListeners]) try {
|
|
1312
|
+
listener();
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
console.error("root standard-source subscriber failed:", error);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
/** Publish one installed-scope roster transition after the map is authoritative. */
|
|
1318
|
+
publishScopeRevision() {
|
|
1319
|
+
this._scopeRevision += 1;
|
|
1320
|
+
for (const listener of [...this._scopeListeners]) try {
|
|
1321
|
+
listener();
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
console.error("scope-adapter subscriber failed:", error);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
/** Resolve (create or reuse) the store instance for a registered handle under a scope key. */
|
|
1327
|
+
resolveStore(handle, scopeBinding) {
|
|
1328
|
+
const record = this._stores.get(handle);
|
|
1329
|
+
if (record === void 0) throw new Error("store handle is not registered (entry unloaded, or the handle never went through register)");
|
|
1330
|
+
let key;
|
|
1331
|
+
if (record.scope === "root") key = ROOT_INSTANCE_KEY;
|
|
1332
|
+
else {
|
|
1333
|
+
if (scopeBinding === void 0) throw new Error(`${record.scope} store resolution requires a session id`);
|
|
1334
|
+
key = scopeBinding.key;
|
|
1335
|
+
this.bindStoreScope(scopeBinding);
|
|
1336
|
+
}
|
|
1337
|
+
let instance = record.instances.get(key);
|
|
1338
|
+
if (instance === void 0) {
|
|
1339
|
+
instance = record.scope === "root" ? handle.create() : handle.create(key);
|
|
1340
|
+
record.instances.set(key, instance);
|
|
1341
|
+
}
|
|
1342
|
+
return instance;
|
|
1343
|
+
}
|
|
1344
|
+
/** Clear every live non-root Store handle for one dead scope key. */
|
|
1345
|
+
clearStoreScope(key) {
|
|
1346
|
+
for (const [handle, record] of this._stores) {
|
|
1347
|
+
if (record.scope === "root") continue;
|
|
1348
|
+
(record.instances.get(key) ?? handle.create(key)).clearPersisted();
|
|
1349
|
+
record.instances.delete(key);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
/** Bind (or re-reference) a handle on the axis; cross-scope conflicts already threw in the core. */
|
|
1353
|
+
_acquire(handle, scope) {
|
|
1354
|
+
const record = this._stores.get(handle);
|
|
1355
|
+
if (record === void 0) {
|
|
1356
|
+
this._stores.set(handle, {
|
|
1357
|
+
scope,
|
|
1358
|
+
refs: 1,
|
|
1359
|
+
instances: /* @__PURE__ */ new Map()
|
|
1360
|
+
});
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
record.refs += 1;
|
|
1364
|
+
}
|
|
1365
|
+
/** Drop one reference; the last holder's unload drops the record (instances go with it — engine stores need no explicit dispose). */
|
|
1366
|
+
_release(handle) {
|
|
1367
|
+
const record = this._stores.get(handle);
|
|
1368
|
+
/* v8 ignore next -- defensive: release only runs from a disposer whose
|
|
1369
|
+
* register acquired the same handle, so the record must exist; kept so a
|
|
1370
|
+
* future call site cannot underflow the axis. */
|
|
1371
|
+
if (record === void 0) return;
|
|
1372
|
+
record.refs -= 1;
|
|
1373
|
+
if (record.refs !== 0) return;
|
|
1374
|
+
this._stores.delete(handle);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
function copyUnique(kind, target, values, finalProps, propNameOf) {
|
|
1378
|
+
if (values === void 0) return;
|
|
1379
|
+
for (const [name, value] of Object.entries(values)) {
|
|
1380
|
+
const propName = propNameOf(name);
|
|
1381
|
+
if (finalProps.has(propName)) throw new Error(`duplicate root standard ${kind} '${name}' at prop '${propName}'`);
|
|
1382
|
+
finalProps.add(propName);
|
|
1383
|
+
target[name] = value;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
SlotRegistry.prototype.register = function register(rawOptions, component) {
|
|
1387
|
+
const options = rawOptions;
|
|
1388
|
+
return this.ctx.effect(() => this["_register"](options, component), "slots.register()");
|
|
1389
|
+
};
|
|
1390
|
+
//#endregion
|
|
1391
|
+
//#region lib/types/client/index.js
|
|
1392
|
+
/**
|
|
1393
|
+
* Browser UI renderer. It installs the slot renderer after its Cordis
|
|
1394
|
+
* dependencies activate and exposes the mount operation used by the web boot
|
|
1395
|
+
* kernel after the complete client roster settles.
|
|
1396
|
+
*/
|
|
1397
|
+
/** Services required before application assembly. */
|
|
1398
|
+
const inject = [];
|
|
1399
|
+
/** Hydrate the kernel-owned loading DOM before replacing it with the application. */
|
|
1400
|
+
function BootHandoff(props) {
|
|
1401
|
+
const [ready, setReady] = (0, react.useState)(false);
|
|
1402
|
+
(0, react.useLayoutEffect)(() => {
|
|
1403
|
+
setReady(true);
|
|
1404
|
+
}, []);
|
|
1405
|
+
if (ready) return props.app();
|
|
1406
|
+
return (0, react.createElement)("div", {
|
|
1407
|
+
className: props.boot.className,
|
|
1408
|
+
"data-dsh-boot": "",
|
|
1409
|
+
dangerouslySetInnerHTML: { __html: props.boot.html }
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
/** Mount React while preserving the framework-free boot DOM through hydration. */
|
|
1413
|
+
function mountApp(container, app) {
|
|
1414
|
+
const boot = container.querySelector(":scope > [data-dsh-boot]");
|
|
1415
|
+
if (boot !== null) return (0, react_dom_client.hydrateRoot)(container, (0, react.createElement)(BootHandoff, {
|
|
1416
|
+
app,
|
|
1417
|
+
boot: {
|
|
1418
|
+
className: boot.className,
|
|
1419
|
+
html: boot.innerHTML
|
|
1420
|
+
}
|
|
1421
|
+
}));
|
|
1422
|
+
const root = (0, react_dom_client.createRoot)(container);
|
|
1423
|
+
(0, react_dom.flushSync)(() => {
|
|
1424
|
+
root.render(app());
|
|
1425
|
+
});
|
|
1426
|
+
return root;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Install the slot renderer and provide the application mount face.
|
|
1430
|
+
* @param ctx - Plugin context.
|
|
1431
|
+
*/
|
|
1432
|
+
function apply(ctx) {
|
|
1433
|
+
new SlotRegistry(ctx).install(createSlotRenderer());
|
|
1434
|
+
ctx.reflect.provide("uiRenderer", { mount: (container) => {
|
|
1435
|
+
const root = mountApp(container, buildRenderApp({ ctx }));
|
|
1436
|
+
return () => {
|
|
1437
|
+
root.unmount();
|
|
1438
|
+
};
|
|
1439
|
+
} });
|
|
1440
|
+
}
|
|
1441
|
+
//#endregion
|
|
1442
|
+
exports.SlotRegistry = SlotRegistry;
|
|
1443
|
+
exports.apply = apply;
|
|
1444
|
+
exports.inject = inject;
|
|
1445
|
+
return module.exports;
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
//# sourceMappingURL=client.js.map
|