@almadar/ui 6.59.0 → 6.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/BehaviorValueFrame-3ORZHPWQ.css +1252 -0
  2. package/dist/BehaviorValueFrame-QTD7BWOI.js +18 -0
  3. package/dist/TraitValueFrame-2WMYJOA5.css +1252 -0
  4. package/dist/TraitValueFrame-WOXNLLMM.js +17 -0
  5. package/dist/Typography-BY4A2NBY.js +5 -0
  6. package/dist/{UISlotRenderer-YNY7V2UJ.js → UISlotRenderer-EEOXSJCI.js} +10 -8
  7. package/dist/avl/index.cjs +31974 -27856
  8. package/dist/avl/index.d.ts +36 -13
  9. package/dist/avl/index.js +296 -6128
  10. package/dist/{avl-3d-context-BpBKZIbc.d.ts → avl-3d-context-Dxun3-ri.d.ts} +2 -0
  11. package/dist/{chunk-E6MWYQVZ.js → chunk-6PV4LASE.js} +9493 -641
  12. package/dist/{chunk-723MJUU3.js → chunk-6ZCU3BQN.js} +1 -1
  13. package/dist/{chunk-7CVXM2KS.js → chunk-7NIU5GDC.js} +2 -2
  14. package/dist/{chunk-YYYHH5BA.js → chunk-CB64IOYD.js} +0 -4
  15. package/dist/{chunk-MZIDOJMD.js → chunk-EIIQGOZW.js} +2 -1
  16. package/dist/chunk-F7NZ3KZQ.js +434 -0
  17. package/dist/{chunk-LZRW6GXG.js → chunk-FYX64CDL.js} +0 -2
  18. package/dist/{chunk-4ZGXIW3B.js → chunk-KZRAMPM6.js} +1 -1
  19. package/dist/chunk-LUREMDI3.js +49 -0
  20. package/dist/{chunk-J755QADM.js → chunk-MGN6B6OQ.js} +318 -18
  21. package/dist/{chunk-Q47ZN2DW.js → chunk-N23SVGTH.js} +83 -1
  22. package/dist/{chunk-KJGVLXEN.js → chunk-N4IJS5DD.js} +50 -116
  23. package/dist/{chunk-I5QOYW2Z.js → chunk-UTSDPJUP.js} +205 -2
  24. package/dist/{chunk-V3FGTQUE.js → chunk-ZR45FGW3.js} +7 -1
  25. package/dist/{cn-DIql5232.d.ts → cn-CDIhHCUC.d.ts} +40 -16
  26. package/dist/components/index.cjs +14423 -4100
  27. package/dist/components/index.d.ts +160 -172
  28. package/dist/components/index.js +14 -15
  29. package/dist/context/index.cjs +9 -1
  30. package/dist/context/index.js +3 -1
  31. package/dist/hooks/index.cjs +72 -6
  32. package/dist/hooks/index.d.ts +43 -2
  33. package/dist/hooks/index.js +6 -7
  34. package/dist/lib/drawable/three/index.cjs +350 -26
  35. package/dist/lib/drawable/three/index.d.ts +2 -2
  36. package/dist/lib/drawable/three/index.js +28 -7
  37. package/dist/lib/index.cjs +90 -4
  38. package/dist/lib/index.d.ts +2 -2
  39. package/dist/lib/index.js +74 -6
  40. package/dist/locales/index.cjs +0 -6
  41. package/dist/locales/index.js +2 -2
  42. package/dist/providers/index.cjs +13846 -1062
  43. package/dist/providers/index.d.ts +36 -4
  44. package/dist/providers/index.js +35 -15
  45. package/dist/runtime/index.cjs +28395 -18308
  46. package/dist/runtime/index.d.ts +65 -3
  47. package/dist/runtime/index.js +13 -379
  48. package/dist/ssr/index.cjs +506 -133
  49. package/dist/ssr/index.d.cts +56 -4
  50. package/dist/ssr/index.d.ts +56 -4
  51. package/dist/ssr/index.js +444 -71
  52. package/dist/{verificationRegistry-B3dj1Zl8.d.ts → verificationRegistry-B5aGl7WF.d.ts} +2 -1
  53. package/locales/ar.json +0 -2
  54. package/locales/en.json +0 -2
  55. package/locales/sl.json +0 -2
  56. package/package.json +7 -7
  57. package/tailwind-preset.cjs +78 -8
  58. package/themes/_contract.md +6 -0
  59. package/themes/_fonts.css +4 -4
  60. package/themes/_foundation.css +11 -0
  61. package/themes/_presets.css +2 -0
  62. package/themes/almadar-website.css +10 -0
  63. package/themes/almadar.css +10 -0
  64. package/themes/atelier.css +10 -0
  65. package/themes/bauhaus.css +10 -0
  66. package/themes/bloomberg-dense.css +10 -0
  67. package/themes/clay.css +10 -0
  68. package/themes/corporate.css +10 -0
  69. package/themes/gazette.css +10 -0
  70. package/themes/glass.css +10 -0
  71. package/themes/kflow.css +5 -0
  72. package/themes/kiosk.css +10 -0
  73. package/themes/linear-clean.css +10 -0
  74. package/themes/material.css +10 -0
  75. package/themes/minimalist.css +10 -0
  76. package/themes/nata.css +304 -0
  77. package/themes/neon.css +10 -0
  78. package/themes/notion-editorial.css +10 -0
  79. package/themes/orb.css +10 -0
  80. package/themes/prism.css +10 -0
  81. package/themes/rawi.css +304 -0
  82. package/themes/retro.css +10 -0
  83. package/themes/risograph.css +10 -0
  84. package/themes/scandi.css +10 -0
  85. package/themes/solarpunk.css +10 -0
  86. package/themes/terminal.css +138 -128
  87. package/dist/Typography-LZTP72Y4.js +0 -5
  88. package/dist/chunk-D2E44ODB.js +0 -350
  89. package/dist/chunk-N7JIG57B.js +0 -138
  90. package/dist/chunk-QJIAJ6EH.js +0 -2256
@@ -1,2256 +0,0 @@
1
- import { useTranslate, useRenderI18n, runTickFrame } from './chunk-N7JIG57B.js';
2
- import { ALL_SLOTS } from './chunk-L3MTZ3RH.js';
3
- import { LoadingState, ErrorState, UISlotRenderer } from './chunk-E6MWYQVZ.js';
4
- import { Box } from './chunk-J755QADM.js';
5
- import { Typography } from './chunk-4ZGXIW3B.js';
6
- import { beginPending, endPending } from './chunk-I5QOYW2Z.js';
7
- import { useEventBus } from './chunk-CHNC4UYE.js';
8
- import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-KJGVLXEN.js';
9
- import { bindTraitStateGetter, registerTraitSnapshot, recordTransition, recordingTransport, recordServerResponse } from './chunk-Q47ZN2DW.js';
10
- import { isNotificationSlot } from './chunk-PEST66PN.js';
11
- import React, { createContext, useState, useMemo, useEffect, useRef, useCallback, useSyncExternalStore, useContext } from 'react';
12
- import { schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, traitsEmbeddedByEvent, traitReferencesCallsitePayload, configReferencesCallsitePayload, TRAIT_MOUNT_ERROR_TESTID, traitsEmbeddedByEffects, applyListenPayloadMapping, buildResolvedTraitConfigs, collectCallsiteCaptureChildren, orbitalInlineEntities, storesRowsInBrowser, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
13
- import { createLogger } from '@almadar/logger';
14
- import { collectListenerTargets, LIFECYCLE_EVENTS, TraitMountError, loadLazyPage, createInProcessTransport, buildTraitIndex, createMemoryCircuitStore, createLocalStoreTransport, createResidenceTransport, createClientKernel, createTickScheduler, isValidCronExpression, parseDurationString, UNMOUNT_EVENT, createClientEffectHandlers, EffectExecutor, isSExpression, collectDeclaredConfigDefaults, createContextFromBindings, openBrowserStore, browserStoreName, normalizeCallSiteConfigToValues } from '@almadar/runtime';
15
- import { useUser, useArbitraryClassStyles, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, ServerBridgeProvider, useNavStack, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext, AwaitingSkeletonContext } from '@almadar/ui/providers';
16
- import { evaluateListenPayloadExpr, evaluate, createMinimalContext, evaluateGuard, executeEffects } from '@almadar/evaluator';
17
- import { getPatternDefinition } from '@almadar/core/patterns';
18
- import { isKnownStdOperator } from '@almadar/std/registry';
19
- import { prepareSchemaForPreview, collectTraitRefsFromResolvedTrait, buildOrbitalsByTrait, buildTraitLinkedEntities, collectEmbeddedTraits } from '@almadar/runtime/ui';
20
- export { adjustSchemaForMockData, buildMockData, collectEmbeddedTraits, prepareSchemaForPreview } from '@almadar/runtime/ui';
21
- import { useInRouterContext, useHref } from 'react-router-dom';
22
- import { UISlotProvider, useUISlots } from '@almadar/ui/context';
23
- import { useTranslate as useTranslate$1, createTranslate, I18nProvider } from '@almadar/ui/hooks';
24
- import { InMemoryPersistence } from '@almadar/db/mock';
25
- import { jsx, jsxs } from 'react/jsx-runtime';
26
- import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
27
- import { localeDirection } from '@almadar/core/i18n';
28
-
29
- var resolvedSchemaLog = createLogger("almadar:ui:resolved-schema");
30
- function useResolvedSchema(schema, pageName) {
31
- const [loading, setLoading] = useState(true);
32
- const [error, setError] = useState(null);
33
- const ir = useMemo(() => {
34
- if (!schema) return null;
35
- try {
36
- const resolved = schemaToIR(schema);
37
- const callSiteListensByTrait = /* @__PURE__ */ new Map();
38
- for (const orb of schema.orbitals) {
39
- for (const traitRef of orb.traits ?? []) {
40
- if (typeof traitRef === "string") continue;
41
- const t = traitRef;
42
- const inlineListens = t.listens?.length ? t.listens : void 0;
43
- const resolvedListens = t._resolved?.listens?.length ? t._resolved.listens : void 0;
44
- const listens = inlineListens ?? resolvedListens;
45
- const traitName = t.name ?? t._resolved?.name;
46
- if (typeof traitName !== "string" || !listens) continue;
47
- callSiteListensByTrait.set(traitName, listens);
48
- }
49
- }
50
- for (const [traitName, trait] of resolved.traits) {
51
- const callSiteListens = callSiteListensByTrait.get(traitName);
52
- if (!callSiteListens) continue;
53
- const beforeCount = trait.listens.length;
54
- trait.listens = callSiteListens.map((l) => ({
55
- event: l.event,
56
- triggers: l.triggers,
57
- source: l.source,
58
- guard: l.guard,
59
- payloadMapping: l.payloadMapping
60
- }));
61
- resolvedSchemaLog.info("listens:restored", {
62
- trait: traitName,
63
- beforeCount,
64
- afterCount: trait.listens.length,
65
- listens: callSiteListens.map((l) => {
66
- const src = l.source;
67
- const label = !src ? "(no-source)" : src.kind === "any" ? "*" : src.trait;
68
- return `${label}.${l.event}->${l.triggers}`;
69
- }).join(",")
70
- });
71
- }
72
- return resolved;
73
- } catch (err) {
74
- setError(err instanceof Error ? err.message : "Schema resolution failed");
75
- return null;
76
- }
77
- }, [schema]);
78
- useEffect(() => {
79
- setLoading(false);
80
- }, [ir]);
81
- const result = useMemo(() => {
82
- if (!ir) {
83
- return {
84
- page: void 0,
85
- traits: [],
86
- entities: /* @__PURE__ */ new Map(),
87
- allEntities: /* @__PURE__ */ new Map(),
88
- allTraits: /* @__PURE__ */ new Map(),
89
- loading,
90
- error: error || (schema ? null : "No schema provided"),
91
- ir: null
92
- };
93
- }
94
- const page = getPage(ir, pageName);
95
- resolvedSchemaLog.debug("Resolved page", () => ({
96
- name: page?.name,
97
- path: page?.path,
98
- traits: page?.traits.length
99
- }));
100
- const traits = page?.traits || [];
101
- const entities = /* @__PURE__ */ new Map();
102
- if (page) {
103
- for (const binding of page.entityBindings) {
104
- entities.set(binding.entity.name, binding.entity);
105
- }
106
- }
107
- return {
108
- page,
109
- traits,
110
- entities,
111
- allEntities: ir.entities,
112
- allTraits: ir.traits,
113
- loading,
114
- error,
115
- ir
116
- };
117
- }, [ir, pageName, loading, error, schema]);
118
- return result;
119
- }
120
- function clearSchemaCache() {
121
- clearSchemaCache$1();
122
- }
123
-
124
- // lib/circuitVerificationObserver.ts
125
- function createCircuitVerificationObserver() {
126
- return {
127
- onTransition(trace) {
128
- const effects = trace.effects.map((effect) => ({
129
- type: effect.type,
130
- args: effect.args,
131
- status: effect.status,
132
- ...effect.error !== void 0 ? { error: effect.error } : {},
133
- ...effect.durationMs !== void 0 ? { durationMs: effect.durationMs } : {}
134
- }));
135
- recordTransition({
136
- traitName: trace.traitName,
137
- from: trace.from,
138
- to: trace.to,
139
- event: trace.event,
140
- effects,
141
- timestamp: Date.now(),
142
- ...trace.guardResult !== void 0 ? { guardResult: trace.guardResult } : {}
143
- });
144
- }
145
- };
146
- }
147
- function recordDispatchVerdict(orbitalName, event, verdict) {
148
- if ("error" in verdict) {
149
- recordServerResponse(orbitalName, event, {
150
- success: false,
151
- clientEffects: 0,
152
- dataEntities: {},
153
- emittedEvents: [],
154
- error: verdict.error instanceof Error ? `${verdict.error.name}: ${verdict.error.message}` : verdict.error
155
- });
156
- return;
157
- }
158
- const { response } = verdict;
159
- const dataEntities = {};
160
- for (const [entity, rows] of Object.entries(response.data ?? {})) dataEntities[entity] = rows.length;
161
- recordServerResponse(orbitalName, event, {
162
- success: response.success,
163
- transitioned: response.transitioned,
164
- clientEffects: response.clientEffects?.length ?? 0,
165
- dataEntities,
166
- emittedEvents: response.emittedEvents.map((e) => e.event),
167
- emitted: response.emittedEvents.map((e) => e.payload !== void 0 ? { event: e.event, payload: e.payload } : { event: e.event }),
168
- ...response.error !== void 0 ? { error: response.error } : {}
169
- });
170
- }
171
- var tabClientId;
172
- function getTabClientId() {
173
- if (tabClientId === void 0) tabClientId = crypto.randomUUID();
174
- return tabClientId;
175
- }
176
- var log = createLogger("almadar:ui:circuit-kernel");
177
- function postUnmounts(transport, traits, locale) {
178
- for (const [traitName, orbitalName] of traits) {
179
- transport.send(orbitalName, { event: UNMOUNT_EVENT, targetTrait: traitName, clientId: getTabClientId(), locale }).catch((err) => log.warn("unmount-post-failed", { trait: traitName, orbital: orbitalName, error: String(err) }));
180
- }
181
- }
182
- function restrictTraitIndex(full, activeNames) {
183
- const byName = /* @__PURE__ */ new Map();
184
- for (const [name, entry] of full.byName) {
185
- if (activeNames.has(name)) byName.set(name, entry);
186
- }
187
- return { byName, allEntities: full.allEntities, orbitals: full.orbitals };
188
- }
189
- function useCircuitKernel(traitBindings, options) {
190
- const hostRunsProgram = options.transport?.hostsBrowserStore === true;
191
- const fullTraitIndex = useMemo(
192
- () => buildTraitIndex(options.orbitals, options.traitConfigsByName, { hostRunsProgram }),
193
- [options.orbitals, options.traitConfigsByName, hostRunsProgram]
194
- );
195
- const appTraitNames = useMemo(() => new Set(fullTraitIndex.byName.keys()), [fullTraitIndex]);
196
- const activeTraitKey = traitBindings.map((b) => b.trait.name).filter((n) => !!n).sort().join("\0");
197
- const traitIndex = useMemo(
198
- () => restrictTraitIndex(fullTraitIndex, new Set(activeTraitKey === "" ? [] : activeTraitKey.split("\0"))),
199
- [activeTraitKey, fullTraitIndex]
200
- );
201
- const mountedRef = useRef(/* @__PURE__ */ new Map());
202
- const { locale } = useTranslate();
203
- const i18n = useRenderI18n();
204
- const localeRef = useRef(locale);
205
- localeRef.current = locale;
206
- const transport = options.transport;
207
- const topologyPending = options.awaitTopology === true;
208
- useEffect(() => {
209
- if (transport === void 0 || topologyPending) return;
210
- const mounted = mountedRef.current;
211
- const dropped = [...mounted].filter(([name]) => !traitIndex.byName.has(name));
212
- mounted.clear();
213
- for (const [name, entry] of traitIndex.byName) mounted.set(name, entry.orbitalName);
214
- postUnmounts(transport, dropped, localeRef.current);
215
- }, [traitIndex, transport, topologyPending]);
216
- useEffect(() => {
217
- if (transport === void 0) return;
218
- const mounted = mountedRef.current;
219
- return () => postUnmounts(transport, [...mounted], localeRef.current);
220
- }, [transport]);
221
- const store = useMemo(() => {
222
- const traitDefs = Array.from(traitIndex.byName.values(), (entry) => entry.traitDef);
223
- const config = {
224
- ...options.guardMode !== void 0 ? { guardMode: options.guardMode } : {},
225
- ...options.strictBindings !== void 0 ? { strictBindings: options.strictBindings } : {},
226
- ...options.contextExtensions !== void 0 ? { contextExtensions: options.contextExtensions } : {}
227
- };
228
- return createMemoryCircuitStore(traitDefs, config, options.observer);
229
- }, [traitIndex, options.guardMode, options.strictBindings, options.contextExtensions, options.observer]);
230
- const localTransportOptions = {
231
- traitIndex,
232
- store,
233
- ...options.callService !== void 0 ? { callService: options.callService } : {},
234
- ...options.user !== void 0 ? { user: options.user } : {},
235
- ...options.guardMode !== void 0 ? { guardMode: options.guardMode } : {},
236
- ...options.strictBindings !== void 0 ? { strictBindings: options.strictBindings } : {},
237
- ...options.contextExtensions !== void 0 ? { contextExtensions: options.contextExtensions } : {},
238
- ...options.debug !== void 0 ? { debug: options.debug } : {},
239
- ...options.logContext !== void 0 ? { logContext: options.logContext } : {}
240
- };
241
- const offlineTransport = useMemo(
242
- () => options.transport !== void 0 || options.persistence === void 0 ? void 0 : createLocalStoreTransport({ ...localTransportOptions, persistence: options.persistence }),
243
- [
244
- options.transport,
245
- options.persistence,
246
- traitIndex,
247
- store,
248
- options.callService,
249
- options.user,
250
- options.guardMode,
251
- options.strictBindings,
252
- options.contextExtensions,
253
- options.debug,
254
- options.logContext
255
- ]
256
- );
257
- const routedTransport = useMemo(() => {
258
- if (options.browserStore === void 0) return void 0;
259
- const local = createLocalStoreTransport({ ...localTransportOptions, traitIndex: fullTraitIndex, persistence: options.browserStore, clientRelays: true });
260
- const remote = options.transport ?? offlineTransport;
261
- return createResidenceTransport({ local, ...remote !== void 0 ? { remote } : {}, traitIndex: fullTraitIndex });
262
- }, [
263
- options.browserStore,
264
- options.transport,
265
- offlineTransport,
266
- fullTraitIndex,
267
- store,
268
- options.callService,
269
- options.user,
270
- options.guardMode,
271
- options.strictBindings,
272
- options.contextExtensions,
273
- options.debug,
274
- options.logContext
275
- ]);
276
- const baseTransport = routedTransport ?? options.transport ?? offlineTransport;
277
- const effectiveTransport = useMemo(
278
- () => baseTransport === void 0 ? void 0 : recordingTransport(baseTransport),
279
- [baseTransport]
280
- );
281
- const orbitalName = options.orbitals[0]?.name ?? "";
282
- const topology = useMemo(() => {
283
- let resolve = () => void 0;
284
- const promise = new Promise((r) => {
285
- resolve = r;
286
- });
287
- return { promise, resolve };
288
- }, [effectiveTransport]);
289
- const awaitTopology = options.awaitTopology === true;
290
- const confirmedCarries = options.carriesCircuitState ?? false;
291
- useEffect(() => {
292
- if (!awaitTopology) topology.resolve({ carriesCircuitState: confirmedCarries });
293
- }, [awaitTopology, confirmedCarries, topology]);
294
- const rawKernel = useMemo(() => createClientKernel({
295
- orbitalName,
296
- traitIndex,
297
- fullTraitIndex,
298
- store,
299
- ...options.persistence !== void 0 ? { persistence: options.persistence } : {},
300
- ...options.user !== void 0 ? { user: options.user } : {},
301
- locale,
302
- ...i18n !== void 0 ? { messages: { [i18n.locale]: i18n.messages } } : {},
303
- ...options.guardMode !== void 0 ? { guardMode: options.guardMode } : {},
304
- ...options.strictBindings !== void 0 ? { strictBindings: options.strictBindings } : {},
305
- ...options.contextExtensions !== void 0 ? { contextExtensions: options.contextExtensions } : {},
306
- ...options.debug !== void 0 ? { debug: options.debug } : {},
307
- ...options.logContext !== void 0 ? { logContext: options.logContext } : {},
308
- carriesCircuitState: false,
309
- topology: topology.promise,
310
- ...effectiveTransport !== void 0 ? { transport: effectiveTransport } : {}
311
- }), [
312
- orbitalName,
313
- traitIndex,
314
- fullTraitIndex,
315
- store,
316
- options.persistence,
317
- options.user,
318
- locale,
319
- i18n,
320
- options.guardMode,
321
- options.strictBindings,
322
- options.contextExtensions,
323
- options.debug,
324
- options.logContext,
325
- topology,
326
- effectiveTransport
327
- ]);
328
- const kernel = useMemo(() => {
329
- const stamp = (request) => ({
330
- ...request,
331
- clientId: request.clientId ?? getTabClientId(),
332
- locale: request.locale ?? locale
333
- });
334
- return {
335
- store: rawKernel.store,
336
- dispatch: (request, hooks) => rawKernel.dispatch(stamp(request), hooks),
337
- dispatchMount: (seeds, hooks, base = {}) => rawKernel.dispatchMount(seeds, hooks, stamp(base)),
338
- dispatchProgress: (request) => rawKernel.dispatchProgress(stamp(request)),
339
- foldHostDispatch: (request, response) => rawKernel.foldHostDispatch(request, response)
340
- };
341
- }, [rawKernel, locale]);
342
- return { kernel, store, traitIndex, appTraitNames, transport: effectiveTransport };
343
- }
344
- var log2 = createLogger("almadar:ui:circuit:bus-ingress");
345
- function useBusIngress(traitBindings, traitIndex, settle, eventBus, appTraitNames) {
346
- useEffect(() => {
347
- const unsubscribes = [];
348
- const subscribedQualified = /* @__PURE__ */ new Set();
349
- const subscribedBare = /* @__PURE__ */ new Set();
350
- const deliveredHere = (source) => source?.dispatched === true && source.trait !== void 0 && (traitIndex.byName.has(source.trait) || appTraitNames.has(source.trait));
351
- const claims = /* @__PURE__ */ new WeakMap();
352
- const claim = (source, traitName, eventKey) => {
353
- if (source === void 0) return true;
354
- const key = `${traitName}\0${eventKey}`;
355
- const seen = claims.get(source);
356
- if (seen === void 0) {
357
- claims.set(source, /* @__PURE__ */ new Set([key]));
358
- return true;
359
- }
360
- if (seen.has(key)) return false;
361
- seen.add(key);
362
- return true;
363
- };
364
- const dispatch = (traitName, eventKey, payload, source) => {
365
- if (!claim(source, traitName, eventKey)) return;
366
- const tick = source?.tick;
367
- const pendingKey = source?.pendingKey;
368
- if (pendingKey !== void 0) beginPending(pendingKey);
369
- void settle(traitName, eventKey, payload, tick).catch((err) => {
370
- log2.error("dispatch:failed", { trait: traitName, event: eventKey, error: String(err) });
371
- }).finally(() => {
372
- if (pendingKey !== void 0) endPending(pendingKey);
373
- });
374
- };
375
- for (const binding of traitBindings) {
376
- const traitName = binding.trait.name;
377
- const orbitalName = traitIndex.byName.get(traitName)?.orbitalName;
378
- if (orbitalName === void 0) continue;
379
- for (const transition of binding.trait.transitions) {
380
- const eventKey = transition.event;
381
- if (LIFECYCLE_EVENTS.includes(eventKey)) continue;
382
- const qualifiedKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
383
- if (!subscribedQualified.has(qualifiedKey)) {
384
- subscribedQualified.add(qualifiedKey);
385
- const unsub = eventBus.on(qualifiedKey, (event) => {
386
- if (deliveredHere(event.source)) return;
387
- log2.debug("qualified:fire", { key: qualifiedKey });
388
- dispatch(traitName, eventKey, event.payload, event.source);
389
- });
390
- unsubscribes.push(unsub);
391
- }
392
- const bareKey = `UI:${eventKey}`;
393
- if (!subscribedBare.has(bareKey)) {
394
- subscribedBare.add(bareKey);
395
- const unsub = eventBus.on(bareKey, (event) => {
396
- if (deliveredHere(event.source)) return;
397
- const routed = event.source?.dispatched === true ? new Set(collectListenerTargets(traitIndex, event.source, eventKey, event.payload).map((t) => t.listenerTrait)) : void 0;
398
- for (const [name, entry] of traitIndex.byName) {
399
- if (!entry.traitDef.transitions.some((t) => t.event === eventKey)) continue;
400
- if (routed?.has(name) === true) continue;
401
- log2.debug("bare:fire", { key: bareKey, trait: name });
402
- dispatch(name, eventKey, event.payload, event.source);
403
- }
404
- });
405
- unsubscribes.push(unsub);
406
- }
407
- }
408
- }
409
- const listenRoutesByKey = /* @__PURE__ */ new Map();
410
- for (const binding of traitBindings) {
411
- const listenerName = binding.trait.name;
412
- const ownOrbital = traitIndex.byName.get(listenerName)?.orbitalName;
413
- if (ownOrbital === void 0) continue;
414
- for (const listen of binding.trait.listens ?? []) {
415
- const src = listen.source;
416
- if (src === void 0 || src.kind === "any") continue;
417
- const sourceOrbital = (src.kind === "orbital" ? src.orbital : void 0) ?? ownOrbital;
418
- const sourceKey = `UI:${sourceOrbital}.${src.trait}.${listen.event}`;
419
- const route = {
420
- listenerTrait: listenerName,
421
- triggers: listen.triggers,
422
- payloadMapping: listen.payloadMapping
423
- };
424
- const routes = listenRoutesByKey.get(sourceKey);
425
- if (routes) routes.push(route);
426
- else listenRoutesByKey.set(sourceKey, [route]);
427
- }
428
- }
429
- for (const [sourceKey, routes] of listenRoutesByKey) {
430
- const unsub = eventBus.on(sourceKey, (event) => {
431
- if (deliveredHere(event.source)) return;
432
- for (const route of routes) {
433
- log2.debug("listen-source:fire", { key: sourceKey, listener: route.listenerTrait, triggers: route.triggers });
434
- dispatch(
435
- route.listenerTrait,
436
- route.triggers,
437
- applyListenPayloadMapping(route.payloadMapping, event.payload, evaluateListenPayloadExpr),
438
- event.source
439
- );
440
- }
441
- });
442
- unsubscribes.push(unsub);
443
- }
444
- const routedBareEvents = /* @__PURE__ */ new Set();
445
- for (const binding of traitBindings) {
446
- for (const listen of binding.trait.listens ?? []) {
447
- if (listen.source !== void 0 && listen.source.kind !== "any") routedBareEvents.add(listen.event);
448
- }
449
- }
450
- for (const eventKey of routedBareEvents) {
451
- const unsub = eventBus.on(`UI:${eventKey}`, (event) => {
452
- if (deliveredHere(event.source) || event.source?.trait === void 0) return;
453
- for (const target of collectListenerTargets(traitIndex, event.source, eventKey, event.payload)) {
454
- log2.debug("listen-bare:fire", { event: eventKey, source: event.source.trait, listener: target.listenerTrait, triggers: target.triggers });
455
- dispatch(target.listenerTrait, target.triggers, target.payload, event.source);
456
- }
457
- });
458
- unsubscribes.push(unsub);
459
- }
460
- return () => {
461
- for (const unsub of unsubscribes) unsub();
462
- };
463
- }, [traitBindings, traitIndex, settle, eventBus, appTraitNames]);
464
- }
465
- var lambdaLog = createLogger("almadar:ui:fn-form-lambda");
466
- function patternPropsSchema(patternType) {
467
- return typeof patternType === "string" ? getPatternDefinition(patternType)?.propsSchema : void 0;
468
- }
469
- function isDeclaredSExpr(def) {
470
- return def?.types?.includes("sexpr") ?? false;
471
- }
472
- function isOperatorCall(value) {
473
- const first = value[0];
474
- if (typeof first !== "string") return false;
475
- return isKnownStdOperator(first) || first.includes("/") || first === "lambda" || first === "let";
476
- }
477
- function isFnFormLambda(value) {
478
- if (!Array.isArray(value)) return false;
479
- const arr = value;
480
- if (arr.length !== 3 || arr[0] !== "fn" || arr[2] === null || typeof arr[2] !== "object") {
481
- return false;
482
- }
483
- const params = arr[1];
484
- return typeof params === "string" || Array.isArray(params) && params.length > 0 && params.every((p) => typeof p === "string");
485
- }
486
- function fnFormParams(value) {
487
- const p = value[1];
488
- if (typeof p === "string") return [p];
489
- if (Array.isArray(p)) return p.filter((x) => typeof x === "string");
490
- return [];
491
- }
492
- function resolveLambdaBindings(body, params, item, index) {
493
- const itemName = params[0];
494
- const indexName = params[1];
495
- const itemPrefix = itemName ? `@${itemName}.` : null;
496
- const lookup = (path) => {
497
- let cur = item;
498
- for (const seg of path.split(".")) {
499
- if (cur === null || cur === void 0) return void 0;
500
- if (typeof cur !== "object" || Array.isArray(cur)) return void 0;
501
- cur = cur[seg];
502
- }
503
- return cur;
504
- };
505
- const recur = (b) => resolveLambdaBindings(b, params, item, index);
506
- if (typeof body === "string") {
507
- if (indexName && body === `@${indexName}`) return index;
508
- if (itemName && body === `@${itemName}`) return item;
509
- if (itemPrefix && body.startsWith(itemPrefix)) {
510
- const v = lookup(body.slice(itemPrefix.length));
511
- return v === void 0 || v === null ? "" : v;
512
- }
513
- return body;
514
- }
515
- if (Array.isArray(body)) {
516
- if (isFnFormLambda(body)) return body;
517
- const arr = body;
518
- const substituted = arr.map((b) => recur(b));
519
- if (isOperatorCall(substituted) && isSExpr(substituted)) {
520
- if (containsEntityBinding(substituted)) {
521
- return substituted;
522
- }
523
- const evaluated = evaluate(substituted, createMinimalContext());
524
- return isEventPayloadValue(evaluated) ? evaluated : String(evaluated);
525
- }
526
- return substituted;
527
- }
528
- if (body !== null && typeof body === "object" && !React.isValidElement(body) && !(body instanceof Date) && typeof body !== "function") {
529
- const out = {};
530
- for (const [k, v] of Object.entries(body)) {
531
- out[k] = recur(v);
532
- }
533
- return out;
534
- }
535
- return body;
536
- }
537
- var LazySlotContentRenderer = React.lazy(
538
- () => import('./UISlotRenderer-YNY7V2UJ.js').then((m) => ({ default: m.SlotContentRenderer }))
539
- );
540
- function deferLambdaEntityExprs(value) {
541
- if (typeof value === "string") {
542
- if (containsEntityBinding(value)) {
543
- const marker = { [RENDER_BINDING_MARKER]: true, expression: value };
544
- return marker;
545
- }
546
- return value;
547
- }
548
- if (Array.isArray(value)) {
549
- if (isFnFormLambda(value)) return value;
550
- const arr = value;
551
- if (isOperatorCall(arr) && isSExpr(arr) && containsEntityBinding(arr)) {
552
- const marker = { [RENDER_BINDING_MARKER]: true, expression: arr };
553
- return marker;
554
- }
555
- return arr.map((v) => deferLambdaEntityExprs(v));
556
- }
557
- if (value !== null && typeof value === "object" && !React.isValidElement(value) && !(value instanceof Date) && typeof value !== "function" && !isRenderBindingMarker(value)) {
558
- const out = {};
559
- for (const [k, v] of Object.entries(value)) {
560
- out[k] = deferLambdaEntityExprs(v);
561
- }
562
- return out;
563
- }
564
- return value;
565
- }
566
- function makeLambdaFn(params, lambdaBody, callerKey) {
567
- return (item, index) => {
568
- const resolvedBody = deferLambdaEntityExprs(
569
- resolveLambdaBindings(lambdaBody, params, item, index)
570
- );
571
- if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
572
- return null;
573
- }
574
- const record = resolvedBody;
575
- if (typeof record.type !== "string") {
576
- return null;
577
- }
578
- const rawChildProps = {};
579
- for (const [k, v] of Object.entries(record)) {
580
- if (k !== "type") rawChildProps[k] = v;
581
- }
582
- const childProps = convertObjectProps(rawChildProps, patternPropsSchema(record.type));
583
- const childContent = {
584
- id: `lambda-${callerKey}-${index}`,
585
- pattern: record.type,
586
- props: childProps,
587
- priority: 0
588
- };
589
- return React.createElement(
590
- React.Suspense,
591
- { fallback: null },
592
- React.createElement(LazySlotContentRenderer, { content: childContent, onDismiss: () => void 0 })
593
- );
594
- };
595
- }
596
- function convertNode(node, callerKey, def) {
597
- if (node === null || node === void 0) return node;
598
- if (isRenderBindingMarker(node)) return node;
599
- if (Array.isArray(node)) {
600
- if (isFnFormLambda(node)) {
601
- const arr2 = node;
602
- return makeLambdaFn(fnFormParams(arr2), arr2[2], callerKey);
603
- }
604
- const arr = node;
605
- let anyChanged = false;
606
- const mapped = arr.map((item, i) => {
607
- const next = convertNode(item, `${callerKey}[${i}]`, def?.items);
608
- if (next !== item) anyChanged = true;
609
- return next;
610
- });
611
- return anyChanged ? mapped : node;
612
- }
613
- if (typeof node === "object" && !React.isValidElement(node) && !(node instanceof Date)) {
614
- return convertObjectProps(node, def?.properties);
615
- }
616
- return node;
617
- }
618
- function convertObjectProps(props, inherited) {
619
- let convertedAny = false;
620
- const out = {};
621
- const schema = patternPropsSchema(props.type) ?? inherited;
622
- for (const [key, value] of Object.entries(props)) {
623
- const def = schema?.[key];
624
- if (isDeclaredSExpr(def)) {
625
- out[key] = value;
626
- continue;
627
- }
628
- if (isFnFormLambda(value)) {
629
- convertedAny = true;
630
- const arr = value;
631
- const compiled = makeLambdaFn(fnFormParams(arr), arr[2], key);
632
- out[key] = compiled;
633
- if (key === "renderItem") out.children = compiled;
634
- lambdaLog.debug(`convert key=${key}${key === "renderItem" ? " (+children)" : ""}`);
635
- continue;
636
- }
637
- const next = convertNode(value, key, def);
638
- if (next !== value) convertedAny = true;
639
- out[key] = next;
640
- }
641
- return convertedAny ? out : props;
642
- }
643
- function convertFnFormLambdasInProps(props, patternType) {
644
- if (props === null || typeof props !== "object") return props;
645
- return convertObjectProps(props, patternType !== void 0 ? patternPropsSchema(patternType) : void 0);
646
- }
647
-
648
- // hooks/circuit/useSlotFlush.ts
649
- var flushLog = createLogger("almadar:ui:circuit:slot-flush");
650
- function unwrapPattern(record) {
651
- if (typeof record === "string") return { bareTraitRef: record };
652
- if (record === null) return {};
653
- const { type, children, ...inlineProps } = record;
654
- const props = { ...inlineProps, ...children !== void 0 ? { children } : {} };
655
- return { patternType: type, props };
656
- }
657
- function useSlotFlush(uiSlots, embeddedTraits) {
658
- const uiSlotsRef = useRef(uiSlots);
659
- uiSlotsRef.current = uiSlots;
660
- const embeddedTraitsRef = useRef(embeddedTraits);
661
- embeddedTraitsRef.current = embeddedTraits;
662
- const writtenRef = useRef(/* @__PURE__ */ new Map());
663
- const keyOf = (slot, trait) => `${slot}\0${trait}`;
664
- const flushSlot = useCallback((traitName, slot, patterns, source) => {
665
- const slots = uiSlotsRef.current;
666
- const embedded = embeddedTraitsRef.current;
667
- if (patterns.length === 0) {
668
- flushLog.debug("clear", { traitName, slot });
669
- slots.clearBySource(slot, traitName);
670
- writtenRef.current.set(keyOf(slot, traitName), { kind: "clear", slot, trait: traitName });
671
- return;
672
- }
673
- const last = patterns[patterns.length - 1];
674
- const { bareTraitRef, patternType, props: unwrapped } = unwrapPattern(last.pattern);
675
- const rawProps = bareTraitRef !== void 0 ? bareTraitRef : { ...unwrapped ?? {}, ...last.props };
676
- const props = convertFnFormLambdasInProps(rawProps, typeof patternType === "string" ? patternType : void 0);
677
- const isEmbedded = (embedded?.has(traitName) ?? false) && !isNotificationSlot(slot);
678
- if (isEmbedded) {
679
- const content = {
680
- pattern: patternType,
681
- props,
682
- slot,
683
- priority: 0,
684
- transitionEvent: source?.event,
685
- fromState: source?.state,
686
- entity: source?.entity
687
- };
688
- slots.updateTraitContent(traitName, content);
689
- writtenRef.current.set(keyOf(slot, traitName), { kind: "trait", trait: traitName, content });
690
- return;
691
- }
692
- const config = {
693
- target: slot,
694
- pattern: patternType,
695
- props,
696
- sourceTrait: traitName,
697
- transitionEvent: source?.event,
698
- fromState: source?.state,
699
- entity: source?.entity
700
- };
701
- slots.render(config);
702
- writtenRef.current.set(keyOf(slot, traitName), { kind: "render", config });
703
- }, []);
704
- const applyClientEffects = useCallback((clientEffects, clientEffectsByTrait, onNavigate, onNavigateBack, activeTraits) => {
705
- const slots = uiSlotsRef.current;
706
- const embedded = embeddedTraitsRef.current;
707
- const tuples = clientEffectsByTrait ? [...clientEffectsByTrait] : clientEffects.map((effect) => ({ effect }));
708
- for (const { effect, traitName, event, fromState } of tuples) {
709
- if (traitName !== void 0 && activeTraits !== void 0 && !activeTraits.has(traitName)) continue;
710
- const kind = effect[0];
711
- if (kind === "render-ui") {
712
- const [, slot, pattern, rawProps] = effect;
713
- const sourceTrait = traitName ?? "kernel";
714
- const { bareTraitRef, patternType, props: unwrapped } = unwrapPattern(pattern);
715
- const isEmbedded = (embedded?.has(sourceTrait) ?? false) && !isNotificationSlot(slot);
716
- const propsValue = bareTraitRef !== void 0 ? bareTraitRef : { ...unwrapped ?? {}, ...rawProps };
717
- const props = convertFnFormLambdasInProps(propsValue, typeof patternType === "string" ? patternType : void 0);
718
- if (pattern === null) {
719
- slots.clearBySource(slot, sourceTrait);
720
- writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "clear", slot, trait: sourceTrait });
721
- } else if (isEmbedded) {
722
- const content = { pattern: patternType, props, slot, priority: 0, transitionEvent: event, fromState };
723
- slots.updateTraitContent(sourceTrait, content);
724
- writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "trait", trait: sourceTrait, content });
725
- } else {
726
- const config = { target: slot, pattern: patternType, props, sourceTrait, transitionEvent: event, fromState };
727
- slots.render(config);
728
- writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "render", config });
729
- }
730
- } else if (kind === "navigate") {
731
- const [, route, params, options] = effect;
732
- onNavigate?.(route, params, options?.crumb);
733
- } else if (kind === "navigate-back") {
734
- onNavigateBack?.();
735
- }
736
- }
737
- }, []);
738
- const checkpoint = useCallback(() => new Map(writtenRef.current), []);
739
- const rollback = useCallback((cp) => {
740
- const slots = uiSlotsRef.current;
741
- for (const [key, now] of writtenRef.current) {
742
- const then = cp.get(key);
743
- if (then === now) continue;
744
- if (then === void 0 || then.kind === "clear") {
745
- if (now.kind === "render") slots.clearBySource(now.config.target, now.config.sourceTrait ?? "kernel");
746
- else if (now.kind === "trait") {
747
- flushLog.warn("rollback:embedded-frame-kept", { trait: now.trait });
748
- }
749
- } else if (then.kind === "render") {
750
- slots.render(then.config);
751
- } else {
752
- slots.updateTraitContent(then.trait, then.content);
753
- }
754
- }
755
- writtenRef.current = new Map(cp);
756
- }, []);
757
- return useMemo(() => ({ flushSlot, applyClientEffects, checkpoint, rollback }), [flushSlot, applyClientEffects, checkpoint, rollback]);
758
- }
759
- var log3 = createLogger("almadar:ui:circuit:effect-runner");
760
- async function runCircuitEffects(params) {
761
- const { store, eventBus, traitName, frameKey, effects, payload, callsitePayload, config, user, tickName } = params;
762
- if (effects.length === 0) return { emitted: [], didWrite: false };
763
- let frame = store.frames.get(frameKey);
764
- if (frame === void 0) {
765
- frame = {};
766
- store.frames.set(frameKey, frame);
767
- }
768
- const emitted = [];
769
- let didWrite = false;
770
- const handlers = createClientEffectHandlers({
771
- eventBus: {
772
- emit: (type, emitPayload, source) => {
773
- const event = type.startsWith("UI:") ? type.slice(3) : type;
774
- emitted.push(event);
775
- eventBus.emit(type, emitPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
776
- }
777
- },
778
- slotSetter: {
779
- addPattern: (slot, pattern, props, priority) => params.onPattern(slot, pattern, props, priority),
780
- clearSlot: (slot) => params.onPattern(slot, null)
781
- },
782
- navigate: params.navigate,
783
- navigateBack: params.navigateBack,
784
- liveEntity: frame,
785
- // Ticks and callsite re-runs never carry a persistence adapter — the
786
- // same bridge-mode placeholder the composition's client env delegates
787
- // to a leg; here there is no leg to delegate to, so the write is a
788
- // documented no-op (persist/call-service were never reachable from a
789
- // tick's SYNC_TICK_OPERATORS filter or a lifecycle re-run's effects).
790
- persistDelegated: true,
791
- callServiceDelegated: true,
792
- orbitalName: params.orbitalName
793
- });
794
- const baseSet = handlers.set;
795
- const tracked = {
796
- ...handlers,
797
- set: async (targetId, field, value) => {
798
- if (baseSet) await baseSet(targetId, field, value);
799
- didWrite = true;
800
- }
801
- };
802
- const bindingCtx = {
803
- entity: frame,
804
- payload: payload ?? {},
805
- state: params.state,
806
- ...user !== void 0 ? { user } : {},
807
- ...config !== void 0 ? { config } : {},
808
- ...callsitePayload !== void 0 ? { callsitePayload } : {}
809
- };
810
- const effectContext = {
811
- traitName,
812
- ...params.orbitalName !== void 0 ? { orbitalName: params.orbitalName } : {},
813
- ...params.traitId !== void 0 ? { traitId: params.traitId } : {},
814
- ...params.orbitalId !== void 0 ? { orbitalId: params.orbitalId } : {},
815
- ...params.emits !== void 0 ? { emits: params.emits } : {},
816
- state: params.state,
817
- transition: params.transitionLabel
818
- };
819
- const executor = new EffectExecutor({ handlers: tracked, bindings: bindingCtx, context: effectContext, deferRenderBindings: true });
820
- try {
821
- await executor.executeAll(effects);
822
- } catch (error) {
823
- log3.error("effects:error", {
824
- traitName,
825
- transition: params.transitionLabel,
826
- error: error instanceof Error ? error.message : String(error)
827
- });
828
- }
829
- return { emitted, didWrite };
830
- }
831
-
832
- // hooks/circuit/useCallsiteCapture.ts
833
- var log4 = createLogger("almadar:ui:circuit:callsite-capture");
834
- function useCallsiteCapture(traitBindings, store, traitIndex, slotFlush, options) {
835
- return useCallback(async function reRender(traitName, callsitePayload, entityByTrait, visited = /* @__PURE__ */ new Set(), serverComposed = /* @__PURE__ */ new Set(), keepComposed = false, composedBy) {
836
- const children = options.callsiteCaptureChildrenByTrait?.get(traitName);
837
- if (!children || children.size === 0) return;
838
- const bindingMap = new Map(traitBindings.map((b) => [b.trait.name, b]));
839
- for (const childName of children) {
840
- if (composedBy !== void 0 && !composedBy.has(childName)) continue;
841
- if (visited.has(childName)) continue;
842
- visited.add(childName);
843
- const childBinding = bindingMap.get(childName);
844
- if (!childBinding) continue;
845
- const composedWith = serverComposed.has(childName) || keepComposed ? store.callsitePayloads.get(childName) ?? callsitePayload : callsitePayload;
846
- const lifecycleEvent = store.manager.repaintLifecycleEvent(childName);
847
- const indexed = traitIndex.byName.get(childName);
848
- const [entry] = lifecycleEvent === void 0 || indexed === void 0 ? [] : store.manager.sendEvent(lifecycleEvent, {}, void 0, entityByTrait, void 0, childName);
849
- if (lifecycleEvent === void 0 || indexed === void 0 || !entry || !entry.result.executed) {
850
- await reRender(childName, composedWith, entityByTrait, visited, serverComposed, true);
851
- continue;
852
- }
853
- const frameKey = indexed.frameKey;
854
- log4.debug("rerender", { referrer: traitName, child: childName, lifecycleEvent });
855
- store.callsitePayloads.set(childName, composedWith);
856
- const pendingSlots = /* @__PURE__ */ new Map();
857
- await runCircuitEffects({
858
- store,
859
- eventBus: options.eventBus,
860
- traitName: childName,
861
- frameKey,
862
- effects: entry.result.effects,
863
- state: entry.result.previousState,
864
- transitionLabel: `${entry.result.previousState}->${entry.result.newState}`,
865
- payload: {},
866
- callsitePayload: composedWith,
867
- config: indexed.config,
868
- user: options.user,
869
- orbitalName: indexed.orbitalName,
870
- orbitalId: indexed.orbitalId,
871
- traitId: indexed.irTrait.id,
872
- emits: indexed.irTrait.emits,
873
- navigate: options.navigate,
874
- navigateBack: options.navigateBack,
875
- onPattern: (slot, pattern, props) => {
876
- if (pattern === null) {
877
- pendingSlots.set(slot, []);
878
- return;
879
- }
880
- const existing = pendingSlots.get(slot) ?? [];
881
- existing.push({ pattern, props });
882
- pendingSlots.set(slot, existing);
883
- }
884
- });
885
- store.notify();
886
- for (const [slot, patterns] of pendingSlots) {
887
- slotFlush.flushSlot(
888
- childName,
889
- slot,
890
- patterns,
891
- { event: lifecycleEvent, state: entry.result.previousState, entity: childBinding.linkedEntity }
892
- );
893
- }
894
- await reRender(childName, composedWith, entityByTrait, visited, serverComposed, keepComposed, traitsEmbeddedByEffects(indexed.irTrait, entry.result.effects));
895
- }
896
- }, [traitBindings, store, traitIndex, slotFlush, options.callsiteCaptureChildrenByTrait, options.eventBus, options.navigate, options.navigateBack, options.user]);
897
- }
898
- var tickLog = createLogger("almadar:ui:circuit:tick-effects");
899
- var SHARED_ENTITY_WRITE_OPS = /* @__PURE__ */ new Set(["set"]);
900
- var SHARED_ENTITY_RENDER_OPS = /* @__PURE__ */ new Set(["render-ui", "render"]);
901
- function effectsCallOp(effects, ops) {
902
- for (const effect of effects) {
903
- let found = false;
904
- walkSExpr(effect, (node) => {
905
- if (!found && Array.isArray(node) && typeof node[0] === "string" && ops.has(node[0])) found = true;
906
- });
907
- if (found) return true;
908
- }
909
- return false;
910
- }
911
- function classifySharedTick(tick) {
912
- const writes = effectsCallOp(tick.effects, SHARED_ENTITY_WRITE_OPS);
913
- const renders = effectsCallOp(tick.effects, SHARED_ENTITY_RENDER_OPS);
914
- if (writes && renders) return "both";
915
- if (writes) return "writer";
916
- if (renders) return "renderer";
917
- return "neither";
918
- }
919
- function containsConfigForward(value) {
920
- if (typeof value === "string") return value.startsWith("@config.");
921
- if (Array.isArray(value)) return value.some(containsConfigForward);
922
- if (value !== null && typeof value === "object") return Object.values(value).some(containsConfigForward);
923
- return false;
924
- }
925
- function getBindingConfig(binding) {
926
- return normalizeCallSiteConfigToValues(binding.config);
927
- }
928
- function evalFieldDefault(value) {
929
- if (!Array.isArray(value) || !isSExpression(value)) return value;
930
- return evaluate(value, createMinimalContext({}, {}, ""));
931
- }
932
- function createSharedEntityWriter(binding, tick, traitStatesRef, emit, traitConfigsByName, orbitalsByTrait, orbitalIdsByTrait) {
933
- return (scratch) => {
934
- const traitName = binding.trait.name;
935
- const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
936
- if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return [];
937
- const scratchEntity = { ...scratch };
938
- const writes = [];
939
- const ctx = createMinimalContext(scratchEntity, {}, currentState);
940
- const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
941
- const resolvedByName = traitConfigsByName?.[traitName];
942
- const callSiteRaw = getBindingConfig(binding);
943
- const callSiteConfig = callSiteRaw ? Object.fromEntries(Object.entries(callSiteRaw).filter(([, v]) => !containsConfigForward(v))) : void 0;
944
- if (declaredDefaults || resolvedByName || callSiteConfig) {
945
- ctx.config = { ...declaredDefaults ?? {}, ...resolvedByName ?? {}, ...callSiteConfig ?? {} };
946
- }
947
- ctx.mutateEntity = (changes) => {
948
- for (const [field, value] of Object.entries(changes)) {
949
- const fieldValue = value;
950
- scratchEntity[field] = fieldValue;
951
- writes.push({ field, value: fieldValue });
952
- }
953
- };
954
- ctx.emit = (event, payload) => {
955
- emit(event, payload, {
956
- orbital: orbitalsByTrait?.[traitName],
957
- trait: traitName,
958
- tick: tick.name,
959
- orbitalId: orbitalIdsByTrait?.[traitName],
960
- traitId: binding.trait.id
961
- });
962
- };
963
- if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
964
- tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
965
- return [];
966
- }
967
- executeEffects(tick.effects, ctx);
968
- return writes;
969
- };
970
- }
971
- function useClientTicks(traitBindings, store, traitIndex, options) {
972
- useEffect(() => {
973
- if (options.hostRunsProgram === true) return;
974
- const scheduler = createTickScheduler();
975
- const traitStatesRef = { current: store.manager.getAllStates() };
976
- const refreshStates = () => {
977
- traitStatesRef.current = store.manager.getAllStates();
978
- };
979
- const orbitalsByTrait = {};
980
- const orbitalIdsByTrait = {};
981
- for (const [name, entry] of traitIndex.byName) {
982
- orbitalsByTrait[name] = entry.orbitalName;
983
- if (entry.orbitalId !== void 0) orbitalIdsByTrait[name] = entry.orbitalId;
984
- }
985
- const emitFromWriter = (event, payload, source) => {
986
- const prefixed = event.startsWith("UI:") ? event : `UI:${event}`;
987
- options.eventBus.emit(prefixed, payload, source);
988
- };
989
- const frameAdapter = {
990
- getSnapshot: (frameKey) => store.frames.get(frameKey) ?? {},
991
- subscribe: (_frameKey, callback) => store.subscribe(callback),
992
- commit: (frameKey, next) => {
993
- store.frames.set(frameKey, next);
994
- store.notify();
995
- },
996
- seed: (frameKey, initial) => {
997
- if (!store.frames.has(frameKey)) store.frames.set(frameKey, initial);
998
- }
999
- };
1000
- const writerTicksByFrame = /* @__PURE__ */ new Map();
1001
- const pureWriterTickKeys = /* @__PURE__ */ new Set();
1002
- const seededFrames = /* @__PURE__ */ new Set();
1003
- for (const binding of traitBindings) {
1004
- const entry = traitIndex.byName.get(binding.trait.name);
1005
- if (!entry?.isSharedEntity) continue;
1006
- if (!seededFrames.has(entry.frameKey)) {
1007
- seededFrames.add(entry.frameKey);
1008
- let defaults;
1009
- for (const field of entry.entity.fields) {
1010
- if (field.name !== void 0 && field.default !== void 0 && field.default !== null) {
1011
- (defaults ?? (defaults = {}))[field.name] = evalFieldDefault(field.default);
1012
- }
1013
- }
1014
- if (defaults) frameAdapter.seed(entry.frameKey, defaults);
1015
- }
1016
- for (const tick of binding.trait.ticks ?? []) {
1017
- if (classifySharedTick(tick) !== "writer") continue;
1018
- pureWriterTickKeys.add(`${binding.trait.name}::${tick.name}`);
1019
- const list = writerTicksByFrame.get(entry.frameKey) ?? [];
1020
- list.push({ binding, tick });
1021
- writerTicksByFrame.set(entry.frameKey, list);
1022
- }
1023
- }
1024
- for (const [frameKey, entries] of writerTicksByFrame) {
1025
- const byInterval = /* @__PURE__ */ new Map();
1026
- for (const item of entries) {
1027
- const key = String(item.tick.interval);
1028
- const list = byInterval.get(key) ?? [];
1029
- list.push(item);
1030
- byInterval.set(key, list);
1031
- }
1032
- for (const group of byInterval.values()) {
1033
- const interval = group[0].tick.interval;
1034
- const onDue = () => perfTimeAsync(`tick:shared:${frameKey}@${String(interval)}`, () => {
1035
- const writers = group.map(
1036
- ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromWriter, options.traitConfigsByName, orbitalsByTrait, orbitalIdsByTrait)
1037
- );
1038
- runTickFrame(frameKey, writers, frameAdapter);
1039
- });
1040
- if (interval === "frame") scheduler.add(0, onDue);
1041
- else if (typeof interval === "number") scheduler.add(interval, onDue);
1042
- else if (isValidCronExpression(interval)) scheduler.addCron(interval, onDue);
1043
- else scheduler.add(parseDurationString(interval), onDue);
1044
- }
1045
- }
1046
- const runNonWriterTick = (binding, tick) => {
1047
- refreshStates();
1048
- const traitName = binding.trait.name;
1049
- const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
1050
- if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
1051
- const entry = traitIndex.byName.get(traitName);
1052
- const frameKey = entry?.frameKey ?? traitName;
1053
- if (tick.guard !== void 0) {
1054
- const entity = store.frames.get(frameKey) ?? {};
1055
- const guardCtx = {
1056
- entity,
1057
- payload: {},
1058
- state: currentState,
1059
- ...options.user ? { user: options.user } : {},
1060
- ...entry?.config !== void 0 ? { config: entry.config } : {}
1061
- };
1062
- if (!evaluateGuard(tick.guard, createContextFromBindings(guardCtx))) {
1063
- tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
1064
- return;
1065
- }
1066
- }
1067
- const pendingSlots = /* @__PURE__ */ new Map();
1068
- void runCircuitEffects({
1069
- store,
1070
- eventBus: options.eventBus,
1071
- traitName,
1072
- frameKey,
1073
- effects: tick.effects,
1074
- state: currentState,
1075
- transitionLabel: `${currentState}->${currentState}`,
1076
- config: entry?.config,
1077
- user: options.user,
1078
- orbitalName: entry?.orbitalName,
1079
- orbitalId: entry?.orbitalId,
1080
- traitId: entry?.irTrait.id,
1081
- emits: entry?.irTrait.emits,
1082
- navigate: options.navigate,
1083
- navigateBack: options.navigateBack,
1084
- tickName: tick.name,
1085
- onPattern: (slot, pattern, props) => {
1086
- if (pattern === null) {
1087
- pendingSlots.set(slot, []);
1088
- return;
1089
- }
1090
- const existing = pendingSlots.get(slot) ?? [];
1091
- existing.push({ pattern, props });
1092
- pendingSlots.set(slot, existing);
1093
- }
1094
- }).then(() => {
1095
- store.notify();
1096
- for (const [slot, patterns] of pendingSlots) {
1097
- options.slotFlush.flushSlot(
1098
- traitName,
1099
- slot,
1100
- patterns,
1101
- { event: `tick:${tick.name}`, state: currentState, entity: binding.linkedEntity }
1102
- );
1103
- }
1104
- });
1105
- };
1106
- for (const binding of traitBindings) {
1107
- const entry = traitIndex.byName.get(binding.trait.name);
1108
- for (const tick of binding.trait.ticks ?? []) {
1109
- if (entry?.isSharedEntity && pureWriterTickKeys.has(`${binding.trait.name}::${tick.name}`)) continue;
1110
- const key = `tick:${binding.trait.name}::${tick.name}`;
1111
- const onDue = () => perfTimeAsync(key, () => runNonWriterTick(binding, tick));
1112
- if (tick.interval === "frame") scheduler.add(0, onDue);
1113
- else if (typeof tick.interval === "number") scheduler.add(tick.interval, onDue);
1114
- else if (isValidCronExpression(tick.interval)) scheduler.addCron(tick.interval, onDue);
1115
- else scheduler.add(parseDurationString(tick.interval), onDue);
1116
- }
1117
- }
1118
- return () => scheduler.stopAll();
1119
- }, [traitBindings, store, traitIndex, options.eventBus, options.slotFlush, options.traitConfigsByName, options.navigate, options.navigateBack, options.user, options.hostRunsProgram]);
1120
- }
1121
- var EMPTY_ENTITY = {};
1122
- function useEntityBindingSource(store, traitIndex) {
1123
- useSyncExternalStore(store.subscribe, store.getVersion);
1124
- return useMemo(() => {
1125
- const snapshots = /* @__PURE__ */ new Map();
1126
- return {
1127
- getEntitySnapshot: (traitName) => {
1128
- const entry = traitIndex.byName.get(traitName);
1129
- const frameKey = entry?.frameKey ?? traitName;
1130
- const frame = store.frames.get(frameKey);
1131
- if (frame === void 0) return EMPTY_ENTITY;
1132
- const previous = snapshots.get(frameKey);
1133
- if (previous !== void 0 && sameFields(previous, frame)) return previous;
1134
- const next = { ...frame };
1135
- snapshots.set(frameKey, next);
1136
- return next;
1137
- },
1138
- getConfig: (traitName) => traitIndex.byName.get(traitName)?.config,
1139
- getState: (traitName) => store.manager.getState(traitName)?.currentState ?? "",
1140
- subscribe: (_traitName, callback) => store.subscribe(callback)
1141
- };
1142
- }, [store, traitIndex]);
1143
- }
1144
- function sameFields(a, b) {
1145
- const keys = Object.keys(b);
1146
- if (Object.keys(a).length !== keys.length) return false;
1147
- for (const key of keys) {
1148
- if (!Object.is(a[key], b[key])) return false;
1149
- }
1150
- return true;
1151
- }
1152
- function useAwaitingSkeletonSource(store, traitIndex, embeddedTraits) {
1153
- return useMemo(() => {
1154
- const activeRenders = () => {
1155
- const renders = [];
1156
- for (const awaiting of store.awaiting.list()) {
1157
- const transitions = traitIndex.byName.get(awaiting.trait)?.irTrait.stateMachine?.transitions ?? [];
1158
- const taken = transitions.find((t) => t.event === awaiting.event && t.from === awaiting.from);
1159
- if (taken?.awaitRender) renders.push(...taken.awaitRender);
1160
- }
1161
- return renders;
1162
- };
1163
- return {
1164
- forSlot: (slot) => activeRenders().find((r) => r.slot === slot && !(embeddedTraits?.has(r.trait) ?? false))?.skeleton,
1165
- forTrait: (traitName) => activeRenders().find((r) => r.trait === traitName)?.skeleton,
1166
- subscribe: (callback) => store.awaiting.subscribeAll(callback),
1167
- getVersion: () => store.awaiting.getVersion()
1168
- };
1169
- }, [store, traitIndex, embeddedTraits]);
1170
- }
1171
-
1172
- // hooks/useTraitStateMachine.ts
1173
- var MOUNT_ERROR_SOURCE = "$mount";
1174
- var stateLog = createLogger("almadar:ui:state-transitions");
1175
- function diffDroppedTraitNames(prev, next) {
1176
- const dropped = [];
1177
- for (const name of prev) {
1178
- if (!next.has(name)) dropped.push(name);
1179
- }
1180
- return dropped;
1181
- }
1182
- function normalizeEventKey(eventKey) {
1183
- return eventKey.startsWith("UI:") ? eventKey.slice(3) : eventKey;
1184
- }
1185
- function useTraitStateMachine(traitBindings, uiSlots, options) {
1186
- const eventBus = useEventBus();
1187
- const { user: contextViewer } = useUser();
1188
- const { t } = useTranslate();
1189
- const userContext = options.user ?? contextViewer ?? void 0;
1190
- const observer = useMemo(() => createCircuitVerificationObserver(), []);
1191
- const { kernel, store, traitIndex, appTraitNames, transport } = useCircuitKernel(traitBindings, {
1192
- orbitals: options.orbitals,
1193
- ...options.traitConfigsByName !== void 0 ? { traitConfigsByName: options.traitConfigsByName } : {},
1194
- ...options.transport !== void 0 ? { transport: options.transport } : {},
1195
- ...options.carriesCircuitState !== void 0 ? { carriesCircuitState: options.carriesCircuitState } : {},
1196
- ...options.awaitTopology !== void 0 ? { awaitTopology: options.awaitTopology } : {},
1197
- ...options.persistence !== void 0 ? { persistence: options.persistence } : {},
1198
- ...options.browserStore !== void 0 ? { browserStore: options.browserStore } : {},
1199
- ...options.callService !== void 0 ? { callService: options.callService } : {},
1200
- ...userContext !== void 0 ? { user: userContext } : {},
1201
- ...options.debug !== void 0 ? { debug: options.debug } : {},
1202
- ...options.logContext !== void 0 ? { logContext: options.logContext } : {},
1203
- observer
1204
- });
1205
- const slotFlush = useSlotFlush(uiSlots, options.embeddedTraits);
1206
- const reRenderCallsiteCaptureChildren = useCallsiteCapture(traitBindings, store, traitIndex, slotFlush, {
1207
- ...options.callsiteCaptureChildrenByTrait !== void 0 ? { callsiteCaptureChildrenByTrait: options.callsiteCaptureChildrenByTrait } : {},
1208
- eventBus,
1209
- ...options.navigate !== void 0 ? { navigate: options.navigate } : {},
1210
- ...options.navigateBack !== void 0 ? { navigateBack: options.navigateBack } : {},
1211
- ...userContext !== void 0 ? { user: userContext } : {}
1212
- });
1213
- useClientTicks(traitBindings, store, traitIndex, {
1214
- eventBus,
1215
- slotFlush,
1216
- hostRunsProgram: transport?.hostsBrowserStore === true,
1217
- ...options.traitConfigsByName !== void 0 ? { traitConfigsByName: options.traitConfigsByName } : {},
1218
- ...options.navigate !== void 0 ? { navigate: options.navigate } : {},
1219
- ...options.navigateBack !== void 0 ? { navigateBack: options.navigateBack } : {},
1220
- ...userContext !== void 0 ? { user: userContext } : {}
1221
- });
1222
- const entityBindingSource = useEntityBindingSource(store, traitIndex);
1223
- const awaitingSkeletonSource = useAwaitingSkeletonSource(store, traitIndex, options.embeddedTraits);
1224
- const activeTraitNamesRef = useRef(/* @__PURE__ */ new Set());
1225
- useEffect(() => {
1226
- activeTraitNamesRef.current = new Set(traitIndex.byName.keys());
1227
- }, [traitIndex]);
1228
- useEffect(() => {
1229
- const ids = [];
1230
- for (const binding of traitBindings) {
1231
- const trait = binding.trait;
1232
- const state = store.manager.getState(trait.name);
1233
- const info = {
1234
- id: trait.name,
1235
- name: trait.name,
1236
- currentState: state?.currentState ?? trait.states[0]?.name ?? "unknown",
1237
- states: trait.states.map((s) => s.name),
1238
- transitions: trait.transitions.flatMap((t2) => {
1239
- const froms = Array.isArray(t2.from) ? t2.from : [t2.from];
1240
- return froms.map((f) => ({ from: f, to: t2.to, event: t2.event, guard: t2.guard ? String(t2.guard) : void 0 }));
1241
- }),
1242
- guards: trait.transitions.filter((t2) => t2.guard).map((t2) => ({ name: String(t2.guard) })),
1243
- transitionCount: 0
1244
- };
1245
- registerTrait(info);
1246
- ids.push(trait.name);
1247
- }
1248
- bindTraitStateGetter((traitName) => store.manager.getState(traitName)?.currentState);
1249
- const snapshotData = (traitName) => {
1250
- const entry = traitIndex.byName.get(traitName);
1251
- const frame = entry !== void 0 ? store.frames.get(entry.frameKey) : void 0;
1252
- const entityName = entry?.entity.name;
1253
- return frame !== void 0 && entityName !== void 0 ? { [entityName]: [{ ...frame }] } : {};
1254
- };
1255
- const snapshotUnregs = traitBindings.map((binding) => registerTraitSnapshot(binding.trait.name, () => {
1256
- const currentState = store.manager.getState(binding.trait.name)?.currentState ?? binding.trait.states[0]?.name ?? "unknown";
1257
- return {
1258
- traitName: binding.trait.name,
1259
- currentState,
1260
- states: binding.trait.states.map((s) => s.name),
1261
- events: binding.trait.events.map((e) => e.key),
1262
- data: snapshotData(binding.trait.name),
1263
- cascadeReceived: []
1264
- };
1265
- }));
1266
- return () => {
1267
- for (const id of ids) unregisterTrait(id);
1268
- for (const unreg of snapshotUnregs) unreg();
1269
- };
1270
- }, [traitBindings, store, traitIndex]);
1271
- const afterSettle = useCallback(async (traitName, eventKey, payload, transitioned, repaints, composedBefore) => {
1272
- if (transitioned) {
1273
- const entityByTrait = {};
1274
- for (const [name, entry] of traitIndex.byName) {
1275
- const row = store.frames.get(entry.frameKey);
1276
- if (row !== void 0) entityByTrait[name] = row;
1277
- }
1278
- const repainted = new Set(repaints.map((e) => e.traitName));
1279
- const serverComposed = new Set([...store.callsitePayloads].filter(([child, composed]) => composedBefore.get(child) !== composed).map(([child]) => child));
1280
- const irTrait = traitIndex.byName.get(traitName)?.irTrait;
1281
- const composedBy = irTrait !== void 0 ? traitsEmbeddedByEvent(irTrait, eventKey) : void 0;
1282
- await reRenderCallsiteCaptureChildren(traitName, payload ?? {}, entityByTrait, repainted, serverComposed, false, composedBy);
1283
- }
1284
- options.onEventProcessed?.(eventKey, payload);
1285
- }, [traitIndex, store, reRenderCallsiteCaptureChildren, options.onEventProcessed]);
1286
- const settleOutcome = useCallback(async (traitName, eventKey, payload, outcome, composedBefore) => {
1287
- recordDispatchVerdict(traitIndex.byName.get(traitName)?.orbitalName ?? traitName, eventKey, { response: outcome.response });
1288
- const settledEffects = outcome.localPainted && outcome.serverEffects !== void 0 ? outcome.serverEffects : outcome.response;
1289
- slotFlush.applyClientEffects(
1290
- settledEffects.clientEffects ?? [],
1291
- settledEffects.clientEffectsByTrait,
1292
- options.navigate,
1293
- options.navigateBack,
1294
- activeTraitNamesRef.current
1295
- );
1296
- for (const emitted of outcome.response.emittedEvents) {
1297
- eventBus.emit(`UI:${emitted.event}`, emitted.payload, { ...emitted.source ?? {}, dispatched: true });
1298
- }
1299
- stateLog.debug("dispatch:settled", { traitName, eventKey, mode: outcome.mode, transitioned: outcome.response.transitioned });
1300
- await afterSettle(traitName, eventKey, payload, outcome.response.transitioned, outcome.response.clientEffectsByTrait ?? [], composedBefore);
1301
- }, [slotFlush, traitIndex, eventBus, options.navigate, options.navigateBack, afterSettle]);
1302
- const dispatchAndSettle = useCallback(async (traitName, eventKey, payload, tick) => {
1303
- const entityId = typeof payload?.entityId === "string" ? payload.entityId : void 0;
1304
- const orbitalName = traitIndex.byName.get(traitName)?.orbitalName ?? traitName;
1305
- let outcome;
1306
- const composedBefore = new Map(store.callsitePayloads);
1307
- let beforeLocalPaint;
1308
- try {
1309
- outcome = await kernel.dispatch({
1310
- event: eventKey,
1311
- ...payload !== void 0 ? { payload } : {},
1312
- ...entityId !== void 0 ? { entityId } : {},
1313
- ...tick !== void 0 ? { tick } : {},
1314
- targetTrait: traitName
1315
- }, {
1316
- onLocal: (local) => {
1317
- beforeLocalPaint = slotFlush.checkpoint();
1318
- slotFlush.applyClientEffects(
1319
- local.clientEffects ?? [],
1320
- local.clientEffectsByTrait,
1321
- options.navigate,
1322
- options.navigateBack,
1323
- activeTraitNamesRef.current
1324
- );
1325
- }
1326
- });
1327
- } catch (err) {
1328
- if (beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1329
- recordDispatchVerdict(orbitalName, eventKey, { error: err instanceof Error ? err : String(err) });
1330
- throw err;
1331
- }
1332
- if (outcome.localPainted && !outcome.response.success && beforeLocalPaint !== void 0) {
1333
- slotFlush.rollback(beforeLocalPaint);
1334
- }
1335
- await settleOutcome(traitName, eventKey, payload, outcome, composedBefore);
1336
- }, [kernel, slotFlush, traitIndex, store, options.navigate, options.navigateBack, settleOutcome]);
1337
- const progressAndSettle = useCallback(async (traitName, eventKey, payload) => {
1338
- const entityId = typeof payload?.entityId === "string" ? payload.entityId : void 0;
1339
- const composedBefore = new Map(store.callsitePayloads);
1340
- const outcome = await kernel.dispatchProgress({
1341
- event: eventKey,
1342
- ...payload !== void 0 ? { payload } : {},
1343
- ...entityId !== void 0 ? { entityId } : {},
1344
- targetTrait: traitName
1345
- });
1346
- await settleOutcome(traitName, eventKey, payload, outcome, composedBefore);
1347
- }, [kernel, store, settleOutcome]);
1348
- useEffect(() => {
1349
- if (!transport?.subscribe) return;
1350
- return transport.subscribe((emitted, target) => {
1351
- const listeners = collectListenerTargets(traitIndex, emitted.source, emitted.event, emitted.payload);
1352
- if (target !== "origin") {
1353
- for (const t2 of listeners) void dispatchAndSettle(t2.listenerTrait, t2.triggers, t2.payload);
1354
- return;
1355
- }
1356
- const caller = emitted.source?.trait;
1357
- if (caller !== void 0 && traitIndex.byName.has(caller) && !listeners.some((t2) => t2.listenerTrait === caller && t2.triggers === emitted.event)) {
1358
- void progressAndSettle(caller, emitted.event, emitted.payload);
1359
- }
1360
- for (const t2 of listeners) void progressAndSettle(t2.listenerTrait, t2.triggers, t2.payload);
1361
- }, { clientId: getTabClientId() });
1362
- }, [transport, traitIndex, progressAndSettle, dispatchAndSettle]);
1363
- useEffect(() => {
1364
- if (!transport?.subscribeHostDispatches) return;
1365
- return transport.subscribeHostDispatches((_orbitalName, request, response) => {
1366
- const traitName = request.targetTrait;
1367
- if (traitName === void 0 || !traitIndex.byName.has(traitName)) return;
1368
- const composedBefore = new Map(store.callsitePayloads);
1369
- void kernel.foldHostDispatch(request, response).then((outcome) => settleOutcome(traitName, request.event, request.payload, outcome, composedBefore));
1370
- }, { clientId: getTabClientId() });
1371
- }, [transport, traitIndex, kernel, store, settleOutcome]);
1372
- const mountAndSettle = useCallback(async (seeds, payload) => {
1373
- const composedBefore = new Map(store.callsitePayloads);
1374
- let beforeLocalPaint;
1375
- let outcome;
1376
- try {
1377
- outcome = await kernel.dispatchMount(seeds, {
1378
- onLocal: (_trait, local) => {
1379
- if (beforeLocalPaint === void 0) beforeLocalPaint = slotFlush.checkpoint();
1380
- slotFlush.applyClientEffects(local.clientEffects ?? [], local.clientEffectsByTrait, options.navigate, options.navigateBack, activeTraitNamesRef.current);
1381
- }
1382
- }, { payload });
1383
- } catch (err) {
1384
- if (beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1385
- for (const seed of seeds) {
1386
- recordDispatchVerdict(traitIndex.byName.get(seed.trait)?.orbitalName ?? seed.trait, seed.event, { error: err instanceof Error ? err : String(err) });
1387
- }
1388
- throw err;
1389
- }
1390
- if (!outcome.success && beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1391
- slotFlush.applyClientEffects(outcome.serverEffects.clientEffects ?? [], outcome.serverEffects.clientEffectsByTrait, options.navigate, options.navigateBack, activeTraitNamesRef.current);
1392
- const emitted = [...outcome.seeds.flatMap((seed) => seed.local.emittedEvents), ...outcome.emittedEvents];
1393
- for (const e of emitted) {
1394
- eventBus.emit(`UI:${e.event}`, e.payload, { ...e.source ?? {}, dispatched: true });
1395
- }
1396
- const serverRepaints = outcome.serverEffects.clientEffectsByTrait ?? [];
1397
- for (const seed of outcome.seeds) {
1398
- recordDispatchVerdict(traitIndex.byName.get(seed.trait)?.orbitalName ?? seed.trait, seed.event, {
1399
- response: { ...seed.local, success: outcome.success, ...outcome.error !== void 0 ? { error: outcome.error } : {} }
1400
- });
1401
- stateLog.debug("mount:settled", { traitName: seed.trait, eventKey: seed.event, mode: seed.mode, transitioned: seed.local.transitioned });
1402
- await afterSettle(seed.trait, seed.event, payload, seed.local.transitioned, [...seed.local.clientEffectsByTrait ?? [], ...serverRepaints], composedBefore);
1403
- }
1404
- }, [kernel, slotFlush, traitIndex, store, eventBus, options.navigate, options.navigateBack, afterSettle]);
1405
- useBusIngress(traitBindings, traitIndex, dispatchAndSettle, eventBus, appTraitNames);
1406
- const sendEvent = useCallback((eventKey, payload) => {
1407
- const normalized = normalizeEventKey(eventKey);
1408
- for (const [traitName] of traitIndex.byName) {
1409
- if (!store.manager.canHandleEvent(traitName, normalized)) continue;
1410
- void dispatchAndSettle(traitName, normalized, payload);
1411
- }
1412
- }, [traitIndex, store, dispatchAndSettle]);
1413
- const getTraitState = useCallback((traitName) => store.manager.getState(traitName), [store]);
1414
- const canHandleEvent = useCallback((traitName, eventKey) => store.manager.canHandleEvent(traitName, normalizeEventKey(eventKey)), [store]);
1415
- const prevActiveTraitNamesRef = useRef(/* @__PURE__ */ new Set());
1416
- useEffect(() => {
1417
- const nextActiveTraitNames = new Set(
1418
- traitBindings.map((b) => b.trait.name).filter((n) => !!n)
1419
- );
1420
- const dropped = diffDroppedTraitNames(prevActiveTraitNamesRef.current, nextActiveTraitNames);
1421
- if (dropped.length > 0) {
1422
- for (const traitName of dropped) {
1423
- for (const slot of ALL_SLOTS) {
1424
- uiSlots.clearBySource(slot, traitName);
1425
- }
1426
- }
1427
- }
1428
- prevActiveTraitNamesRef.current = nextActiveTraitNames;
1429
- }, [traitBindings, uiSlots]);
1430
- const mountedRef = useRef(null);
1431
- const aliveRef = useRef(true);
1432
- useEffect(() => {
1433
- aliveRef.current = true;
1434
- return () => {
1435
- aliveRef.current = false;
1436
- };
1437
- }, []);
1438
- const mountKey = `${options.mountKey ?? ""}\0${JSON.stringify(Object.entries(options.initPayload ?? {}).sort(([a], [b]) => a.localeCompare(b)))}`;
1439
- useEffect(() => {
1440
- const prev = mountedRef.current;
1441
- const mount = prev !== null && prev.key === mountKey && prev.store === store ? prev : { key: mountKey, store, initialized: /* @__PURE__ */ new Set() };
1442
- const active = new Set(traitBindings.map((b) => b.trait.name));
1443
- const left = [...mount.initialized].filter((traitName) => !active.has(traitName));
1444
- for (const traitName of left) mount.initialized.delete(traitName);
1445
- store.mount.unmounted(left);
1446
- const entering = [];
1447
- const embeddedChildren = new Set([...options.callsiteCaptureChildrenByTrait?.values() ?? []].flatMap((c) => [...c]));
1448
- for (const binding of traitBindings) {
1449
- const traitName = binding.trait.name;
1450
- if (mount.initialized.has(traitName)) continue;
1451
- mount.initialized.add(traitName);
1452
- const indexed = traitIndex.byName.get(traitName);
1453
- if (embeddedChildren.has(traitName) && indexed !== void 0 && (traitReferencesCallsitePayload(indexed.irTrait) || configReferencesCallsitePayload(indexed.config)) && store.manager.repaintLifecycleEvent(traitName) !== void 0 && !store.callsitePayloads.has(traitName)) {
1454
- continue;
1455
- }
1456
- const lifecycleEvent = LIFECYCLE_EVENTS.find((evt) => store.manager.canHandleEvent(traitName, evt));
1457
- if (lifecycleEvent !== void 0) entering.push({ trait: traitName, event: lifecycleEvent });
1458
- }
1459
- store.mount.mounting(entering.map((e) => e.trait));
1460
- if (entering.length > 0) {
1461
- uiSlots.clearBySource("main", MOUNT_ERROR_SOURCE);
1462
- void mountAndSettle(entering, { ...options.initPayload ?? {} }).catch((err) => {
1463
- const traits = entering.map((e) => e.trait);
1464
- const message = err instanceof Error ? err.message : String(err);
1465
- const failed = err instanceof TraitMountError ? err.trait : traits.join(", ");
1466
- const cause = err instanceof TraitMountError ? err.original : err;
1467
- stateLog.warn("mount:failed", { traits, failed, error: message, stack: cause instanceof Error ? cause.stack : void 0 });
1468
- if (!aliveRef.current) return;
1469
- uiSlots.render({
1470
- target: "main",
1471
- pattern: "error-state",
1472
- props: { title: t("error.traitMountFailed", { trait: failed }), message, "data-testid": TRAIT_MOUNT_ERROR_TESTID },
1473
- sourceTrait: MOUNT_ERROR_SOURCE
1474
- });
1475
- });
1476
- }
1477
- mountedRef.current = mount;
1478
- }, [traitBindings, store, mountAndSettle, mountKey, uiSlots, traitIndex, options.callsiteCaptureChildrenByTrait, t]);
1479
- useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
1480
- const traitStates = useMemo(() => store.manager.getAllStates(), [store, store.getVersion()]);
1481
- return { traitStates, sendEvent, getTraitState, canHandleEvent, entityBindingSource, awaitingSkeletonSource };
1482
- }
1483
- function useBrowserStore(appName, orbitals, locale) {
1484
- const entities = useMemo(() => orbitals.flatMap(orbitalInlineEntities).filter(storesRowsInBrowser), [orbitals]);
1485
- const [state, setState] = useState(() => entities.length === 0 ? { status: "none" } : { status: "opening" });
1486
- useEffect(() => {
1487
- if (entities.length === 0) {
1488
- setState({ status: "none" });
1489
- return;
1490
- }
1491
- let cancelled = false;
1492
- let opened;
1493
- setState({ status: "opening" });
1494
- const open = async () => {
1495
- try {
1496
- const store = await openBrowserStore(browserStoreName(appName, locale), orbitals, void 0, locale);
1497
- if (store === null) return;
1498
- opened = store;
1499
- if (!cancelled) setState({ status: "ready", store });
1500
- } catch (err) {
1501
- if (!cancelled) setState({ status: "failed", error: err instanceof Error ? err : new Error(String(err)) });
1502
- }
1503
- };
1504
- void open();
1505
- return () => {
1506
- cancelled = true;
1507
- opened?.close();
1508
- };
1509
- }, [appName, locale, entities, orbitals]);
1510
- return state;
1511
- }
1512
-
1513
- // runtime/fitContent.ts
1514
- var IDENTITY = { scale: 1, x: 0, y: 0 };
1515
- function fitContentTransform(box, content) {
1516
- if (box.width <= 0 || box.height <= 0 || content.width <= 0 || content.height <= 0) return IDENTITY;
1517
- const scale = Math.min(1, box.width / content.width, box.height / content.height);
1518
- return {
1519
- scale,
1520
- x: box.width / 2 - (content.x + content.width / 2) * scale,
1521
- y: box.height / 2 - (content.y + content.height / 2) * scale
1522
- };
1523
- }
1524
- function slotContentRect(inner, scale) {
1525
- const origin = inner.getBoundingClientRect();
1526
- let left = Infinity;
1527
- let top = Infinity;
1528
- let right = -Infinity;
1529
- let bottom = -Infinity;
1530
- for (const slot of Array.from(inner.querySelectorAll("[data-orb-slot]"))) {
1531
- for (const child of Array.from(slot.querySelectorAll("*"))) {
1532
- const r = child.getBoundingClientRect();
1533
- if (r.width === 0 && r.height === 0) continue;
1534
- left = Math.min(left, r.left);
1535
- top = Math.min(top, r.top);
1536
- right = Math.max(right, r.right);
1537
- bottom = Math.max(bottom, r.bottom);
1538
- }
1539
- }
1540
- if (left === Infinity) return null;
1541
- return {
1542
- x: (left - origin.left) / scale,
1543
- y: (top - origin.top) / scale,
1544
- width: (right - left) / scale,
1545
- height: (bottom - top) / scale
1546
- };
1547
- }
1548
- var xOrbitalLog = createLogger("almadar:runtime:cross-orbital");
1549
- var navLog = createLogger("almadar:runtime:navigation");
1550
- function NavStackRefBridge({ apiRef }) {
1551
- const api = useNavStack();
1552
- useEffect(() => {
1553
- apiRef.current = api;
1554
- return () => {
1555
- apiRef.current = null;
1556
- };
1557
- }, [api, apiRef]);
1558
- return null;
1559
- }
1560
- function HostHrefBaseProbe({ onBase }) {
1561
- const base = useHref("/");
1562
- useEffect(() => onBase(base), [base, onBase]);
1563
- return null;
1564
- }
1565
- function appPathFromHref(href, hostHrefBase) {
1566
- const root = hostHrefBase.endsWith("/") ? hostHrefBase.slice(0, -1) : hostHrefBase;
1567
- if (href === root) return "/";
1568
- if (!href.startsWith(`${root}/`)) return null;
1569
- return href.slice(root.length);
1570
- }
1571
- function TraitInitializer({ traits, routeParams, mountKey, orbitals, onNavigate, onNavigateBack, onLocalFallback, localFallbackTimeoutMs, persistence, browserStore, traitConfigsByName, embeddedTraits, callsiteCaptureChildrenByTrait, hasBridge, children }) {
1572
- const bridge = useServerBridge();
1573
- const uiSlots = useUISlots();
1574
- useEffect(() => {
1575
- if (!hasBridge) return;
1576
- const timer = setTimeout(() => {
1577
- if (!bridge.connected) onLocalFallback?.();
1578
- }, localFallbackTimeoutMs ?? 5e3);
1579
- return () => clearTimeout(timer);
1580
- }, [hasBridge, bridge.connected, onLocalFallback, localFallbackTimeoutMs]);
1581
- const { entityBindingSource, awaitingSkeletonSource } = useTraitStateMachine(traits, uiSlots, {
1582
- orbitals,
1583
- ...onNavigate !== void 0 ? { navigate: onNavigate } : {},
1584
- ...onNavigateBack !== void 0 ? { navigateBack: onNavigateBack } : {},
1585
- ...traitConfigsByName !== void 0 ? { traitConfigsByName } : {},
1586
- ...embeddedTraits !== void 0 ? { embeddedTraits } : {},
1587
- ...callsiteCaptureChildrenByTrait !== void 0 ? { callsiteCaptureChildrenByTrait } : {},
1588
- initPayload: routeParams,
1589
- ...mountKey !== void 0 ? { mountKey } : {},
1590
- ...hasBridge ? { transport: bridge.transport, carriesCircuitState: bridge.carriesCircuitState, awaitTopology: !bridge.topologyKnown } : { persistence },
1591
- ...browserStore !== void 0 ? { browserStore } : {}
1592
- });
1593
- return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children: /* @__PURE__ */ jsx(AwaitingSkeletonContext.Provider, { value: awaitingSkeletonSource, children }) });
1594
- }
1595
- var CONTENT_STAGE_WIDTH = 480;
1596
- var NO_ORBITALS = [];
1597
- var LazyPageOfRegisteredProgram = createContext(false);
1598
- function FitToBox({ children, mode = "box" }) {
1599
- const outerRef = useRef(null);
1600
- const innerRef = useRef(null);
1601
- const [fitted, setFitted] = useState({ scale: 1, x: 0, y: 0 });
1602
- const scaleRef = useRef(1);
1603
- useEffect(() => {
1604
- const outer = outerRef.current;
1605
- const inner = innerRef.current;
1606
- if (!outer || !inner) return;
1607
- const update = () => {
1608
- const cw = outer.clientWidth;
1609
- const ch = outer.clientHeight;
1610
- if (mode === "content") {
1611
- const rect = slotContentRect(inner, scaleRef.current);
1612
- if (!rect) return;
1613
- const next = fitContentTransform({ width: cw, height: ch }, rect);
1614
- scaleRef.current = next.scale;
1615
- setFitted(next);
1616
- return;
1617
- }
1618
- const sw = inner.scrollWidth;
1619
- const sh = inner.scrollHeight;
1620
- if (!sw || !sh || !cw || !ch) return;
1621
- setFitted({ scale: Math.min(1, cw / sw, ch / sh), x: 0, y: 0 });
1622
- };
1623
- update();
1624
- const cleanups = [];
1625
- if (typeof ResizeObserver !== "undefined") {
1626
- const ro = new ResizeObserver(update);
1627
- ro.observe(outer);
1628
- ro.observe(inner);
1629
- cleanups.push(() => ro.disconnect());
1630
- }
1631
- if (mode === "content" && typeof MutationObserver !== "undefined") {
1632
- let frame = 0;
1633
- const schedule = () => {
1634
- cancelAnimationFrame(frame);
1635
- frame = requestAnimationFrame(update);
1636
- };
1637
- const mo = new MutationObserver(schedule);
1638
- mo.observe(inner, { childList: true, subtree: true, attributes: true, characterData: true });
1639
- schedule();
1640
- cleanups.push(() => {
1641
- mo.disconnect();
1642
- cancelAnimationFrame(frame);
1643
- });
1644
- }
1645
- return () => cleanups.forEach((c) => c());
1646
- }, [mode]);
1647
- return /* @__PURE__ */ jsx("div", { ref: outerRef, className: "relative h-full w-full overflow-hidden", "data-fit-mode": mode, children: /* @__PURE__ */ jsx(
1648
- "div",
1649
- {
1650
- ref: innerRef,
1651
- style: {
1652
- transform: `translate(${fitted.x}px, ${fitted.y}px) scale(${fitted.scale})`,
1653
- transformOrigin: "top left",
1654
- width: mode === "content" ? CONTENT_STAGE_WIDTH : "fit-content"
1655
- },
1656
- children
1657
- }
1658
- ) });
1659
- }
1660
- function SchemaRunner({ schema, serverUrl, transport, getAccessToken, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, localFallbackTimeoutMs, persistence, themeOverride }) {
1661
- const { traits, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
1662
- const allPageTraits = useMemo(() => {
1663
- let base;
1664
- if (pageName && traits.length > 0) {
1665
- base = traits;
1666
- } else if (!ir?.pages || ir.pages.size <= 1) {
1667
- base = traits;
1668
- } else {
1669
- const firstPage = ir.pages.values().next().value;
1670
- if (!firstPage) {
1671
- base = traits;
1672
- } else {
1673
- const firstPageTraits = [];
1674
- const seen = /* @__PURE__ */ new Set();
1675
- for (const binding of firstPage.traits) {
1676
- const name = binding.trait.name;
1677
- if (name && !seen.has(name)) {
1678
- seen.add(name);
1679
- firstPageTraits.push(binding);
1680
- }
1681
- }
1682
- base = firstPageTraits.length > 0 ? firstPageTraits : traits;
1683
- }
1684
- }
1685
- const byName = new Set(base.map((b) => b.trait.name));
1686
- const extra = [];
1687
- const queue = [...base];
1688
- while (queue.length > 0) {
1689
- const binding = queue.shift();
1690
- if (!binding) continue;
1691
- for (const refName of collectTraitRefsFromResolvedTrait(binding.trait)) {
1692
- if (byName.has(refName)) continue;
1693
- const rt = allTraits.get(refName);
1694
- if (!rt) continue;
1695
- byName.add(refName);
1696
- const sibling = { trait: rt, linkedEntity: rt.linkedEntity };
1697
- extra.push(sibling);
1698
- queue.push(sibling);
1699
- }
1700
- }
1701
- return extra.length > 0 ? [...base, ...extra] : base;
1702
- }, [ir, traits, pageName, allTraits]);
1703
- const orbitalsByTrait = useMemo(
1704
- () => buildOrbitalsByTrait(
1705
- schema,
1706
- ir ? Array.from(ir.pages.values()).map((p) => ({
1707
- path: p.path,
1708
- traitNames: p.traits.map((b) => b.trait.name)
1709
- })) : []
1710
- ),
1711
- [schema, ir]
1712
- );
1713
- const traitLinkedEntitiesMap = useMemo(
1714
- () => ir ? buildTraitLinkedEntities(ir) : /* @__PURE__ */ new Map(),
1715
- [ir]
1716
- );
1717
- const orbitalsByTraitMap = useMemo(
1718
- () => new Map(Object.entries(orbitalsByTrait)),
1719
- [orbitalsByTrait]
1720
- );
1721
- const entitiesArray = useMemo(() => Array.from(allEntities.values()), [allEntities]);
1722
- const pageOrbitalNames = useMemo(() => {
1723
- const set = /* @__PURE__ */ new Set();
1724
- for (const binding of allPageTraits) {
1725
- const orb = orbitalsByTrait[binding.trait.name];
1726
- if (orb) set.add(orb);
1727
- }
1728
- return Array.from(set);
1729
- }, [allPageTraits, orbitalsByTrait]);
1730
- useEffect(() => {
1731
- const traitNames = allPageTraits.map((b) => b.trait.name);
1732
- const orbitalsByTraitForPage = {};
1733
- for (const name of traitNames) {
1734
- const orb = orbitalsByTrait[name];
1735
- if (orb) orbitalsByTraitForPage[name] = orb;
1736
- }
1737
- xOrbitalLog.info("SchemaRunner:mount", {
1738
- pageName,
1739
- traitNames: traitNames.join(","),
1740
- orbitalsByTraitForPage,
1741
- pageOrbitalNames: pageOrbitalNames.join(",")
1742
- });
1743
- }, [pageName, allPageTraits, orbitalsByTrait, pageOrbitalNames]);
1744
- const traitConfigsByName = useMemo(
1745
- () => buildResolvedTraitConfigs(schema),
1746
- [schema]
1747
- );
1748
- const callsiteCaptureChildrenByTrait = useMemo(() => {
1749
- const merged = /* @__PURE__ */ new Map();
1750
- const orbitals = schema?.orbitals;
1751
- if (orbitals) {
1752
- for (const orbital of orbitals) {
1753
- for (const [referrer, children] of collectCallsiteCaptureChildren(orbital)) {
1754
- merged.set(referrer, children);
1755
- }
1756
- }
1757
- }
1758
- return merged;
1759
- }, [schema]);
1760
- const embeddedTraits = useMemo(() => {
1761
- const set = collectEmbeddedTraits(schema);
1762
- xOrbitalLog.info("SchemaRunner:embeddedTraits", {
1763
- pageName,
1764
- embedded: Array.from(set),
1765
- embeddedCount: set.size
1766
- });
1767
- return set;
1768
- }, [schema, pageName]);
1769
- const activeOrbitalTheme = useMemo(() => resolvePreviewTheme(schema, pageName), [schema, pageName]);
1770
- const { locale } = useTranslate$1();
1771
- const browserStore = useBrowserStore(schema.name ?? "app", transport?.hostsBrowserStore === true ? NO_ORBITALS : schema.orbitals, locale);
1772
- const inner = /* @__PURE__ */ jsx(VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsx(
1773
- EntitySchemaProvider,
1774
- {
1775
- entities: entitiesArray,
1776
- traitLinkedEntities: traitLinkedEntitiesMap,
1777
- orbitalsByTrait: orbitalsByTraitMap,
1778
- children: /* @__PURE__ */ jsx(
1779
- TraitInitializer,
1780
- {
1781
- traits: allPageTraits,
1782
- routeParams,
1783
- mountKey: pageName,
1784
- orbitals: schema.orbitals,
1785
- hasBridge: Boolean(serverUrl || transport),
1786
- traitConfigsByName,
1787
- embeddedTraits,
1788
- callsiteCaptureChildrenByTrait,
1789
- onNavigate,
1790
- onNavigateBack,
1791
- onLocalFallback,
1792
- localFallbackTimeoutMs,
1793
- persistence,
1794
- ...browserStore.status === "ready" ? { browserStore: browserStore.store } : {},
1795
- children: /* @__PURE__ */ jsx(OrbitalThemeProvider, { theme: activeOrbitalTheme, override: themeOverride, children: /* @__PURE__ */ jsx(Box, { className: "h-full min-h-full overflow-auto p-4", children: /* @__PURE__ */ jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true, pageKey: pageName }) }) })
1796
- }
1797
- )
1798
- }
1799
- ) });
1800
- if (browserStore.status === "opening") return /* @__PURE__ */ jsx(LoadingState, {});
1801
- if (browserStore.status === "failed") {
1802
- return /* @__PURE__ */ jsx(ErrorState, { "data-testid": "browser-store-error", message: browserStore.error.message });
1803
- }
1804
- return inner;
1805
- }
1806
- function resolvePreviewTheme(schema, pageName) {
1807
- if (!schema.orbitals?.length) return schema.theme;
1808
- if (pageName) {
1809
- for (const orb of schema.orbitals) {
1810
- for (const pageRef of orb.pages ?? []) {
1811
- const name = typeof pageRef === "object" && pageRef !== null && "name" in pageRef ? pageRef.name : void 0;
1812
- if (name === pageName) return orb.theme ?? schema.theme;
1813
- }
1814
- }
1815
- }
1816
- return schema.orbitals[0]?.theme ?? schema.theme;
1817
- }
1818
- function NavigateListener({ onNavigate }) {
1819
- const bus = useEventBus();
1820
- useEffect(
1821
- () => bus.on("UI:NAVIGATE", (event) => {
1822
- const url = event.payload?.url;
1823
- if (typeof url === "string" && url.length > 0) onNavigate(url);
1824
- }),
1825
- [bus, onNavigate]
1826
- );
1827
- return null;
1828
- }
1829
- function OrbPreview({
1830
- schema,
1831
- mockData,
1832
- autoMock = false,
1833
- height = "400px",
1834
- className,
1835
- serverUrl,
1836
- transport,
1837
- getAccessToken,
1838
- initialPagePath,
1839
- isolated = false,
1840
- fit = false,
1841
- onPageChange,
1842
- user = null,
1843
- localFallbackTimeoutMs,
1844
- themeOverride,
1845
- lazyLoader,
1846
- schemaPath,
1847
- onUnmatchedNavigate
1848
- }) {
1849
- if (serverUrl && transport) {
1850
- throw new Error("OrbPreview accepts serverUrl OR transport, not both");
1851
- }
1852
- const { t } = useTranslate$1();
1853
- const [localFallback, setLocalFallback] = useState(false);
1854
- const inHostRouter = useInRouterContext();
1855
- const [hostHrefBase, setHostHrefBase] = useState("/");
1856
- const handleLocalFallback = useCallback(() => {
1857
- if (localFallback) return;
1858
- setLocalFallback(true);
1859
- }, [localFallback]);
1860
- const isLazyPageOfRegisteredProgram = useContext(LazyPageOfRegisteredProgram);
1861
- const parseResult = useMemo(() => {
1862
- let parsed;
1863
- if (typeof schema === "string") {
1864
- try {
1865
- parsed = JSON.parse(schema);
1866
- } catch (e) {
1867
- return { ok: false, error: String(e) };
1868
- }
1869
- } else {
1870
- parsed = schema;
1871
- }
1872
- if (autoMock && !serverUrl && !transport) {
1873
- const prepared = prepareSchemaForPreview(parsed);
1874
- return { ok: true, schema: prepared.schema, mockData: prepared.mockData };
1875
- }
1876
- return { ok: true, schema: parsed, mockData: mockData ?? {} };
1877
- }, [schema, autoMock, serverUrl, transport, mockData]);
1878
- const parsedSchema = parseResult.ok ? parseResult.schema : null;
1879
- useArbitraryClassStyles(parsedSchema);
1880
- const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
1881
- const persistence = useMemo(() => {
1882
- if (!parsedSchema || serverUrl || transport) return void 0;
1883
- if (!autoMock) return void 0;
1884
- const adapter = new InMemoryPersistence();
1885
- adapter.seed(effectiveMockData);
1886
- return adapter;
1887
- }, [parsedSchema, serverUrl, transport, autoMock, effectiveMockData]);
1888
- const pages = useMemo(() => {
1889
- if (!parsedSchema) return [];
1890
- try {
1891
- return getAllPages(parsedSchema);
1892
- } catch {
1893
- return [];
1894
- }
1895
- }, [parsedSchema]);
1896
- const lazyPages = useMemo(() => parsedSchema?.lazyPages ?? [], [parsedSchema]);
1897
- const matchRoute = useCallback((path) => {
1898
- const candidates = [
1899
- ...pages.map((entry) => ({ kind: "page", entry, path: entry.page.path })),
1900
- ...lazyPages.map((page) => ({ kind: "lazy", page, path: page.path }))
1901
- ];
1902
- const hit = matchPathAmong(candidates, path, (c) => c.path);
1903
- if (!hit) return null;
1904
- return hit.candidate.kind === "page" ? { kind: "page", entry: hit.candidate.entry, params: hit.params } : { kind: "lazy", page: hit.candidate.page, params: hit.params };
1905
- }, [pages, lazyPages]);
1906
- const initialRoute = useMemo(
1907
- () => initialPagePath ? matchRoute(initialPagePath) : null,
1908
- [matchRoute, initialPagePath]
1909
- );
1910
- const initialPageMatch = useMemo(() => {
1911
- if (initialRoute?.kind !== "page" || !initialRoute.entry.page.name) return void 0;
1912
- return { name: initialRoute.entry.page.name, params: initialRoute.params };
1913
- }, [initialRoute]);
1914
- const [lazyRoute, setLazyRoute] = useState(
1915
- initialRoute?.kind === "lazy" && initialPagePath ? { page: initialRoute.page, path: initialPagePath } : null
1916
- );
1917
- const [lazyLoad, setLazyLoad] = useState({ status: "loading" });
1918
- useEffect(() => {
1919
- if (!lazyRoute) return;
1920
- if (!lazyLoader) {
1921
- setLazyLoad({ status: "error", error: `No loader for lazy page ${lazyRoute.page.path} (${lazyRoute.page.orbRef})` });
1922
- return;
1923
- }
1924
- let cancelled = false;
1925
- setLazyLoad({ status: "loading" });
1926
- void loadLazyPage(lazyLoader, lazyRoute.page, schemaPath).then((result) => {
1927
- if (cancelled) return;
1928
- setLazyLoad(result.success ? { status: "ready", schema: result.data } : { status: "error", error: result.error });
1929
- });
1930
- return () => {
1931
- cancelled = true;
1932
- };
1933
- }, [lazyRoute?.page, lazyLoader, schemaPath]);
1934
- const initialPageName = initialPageMatch?.name;
1935
- const [currentPage, setCurrentPage] = useState(initialPageName);
1936
- const [routeParams, setRouteParams] = useState(initialPageMatch?.params ?? {});
1937
- const prevInitialPagePathRef = useRef(initialPagePath);
1938
- useEffect(() => {
1939
- if (prevInitialPagePathRef.current !== initialPagePath) {
1940
- prevInitialPagePathRef.current = initialPagePath;
1941
- setCurrentPage(initialPageName);
1942
- setRouteParams(initialPageMatch?.params ?? {});
1943
- setLazyRoute(initialRoute?.kind === "lazy" && initialPagePath ? { page: initialRoute.page, path: initialPagePath } : null);
1944
- }
1945
- }, [initialPagePath, initialPageName, initialPageMatch, initialRoute]);
1946
- const currentPagePath = useMemo(() => {
1947
- const activePageName = currentPage ?? pages[0]?.page.name;
1948
- const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
1949
- return resolved ?? initialPagePath;
1950
- }, [pages, currentPage, initialPagePath]);
1951
- const navPages = useMemo(
1952
- () => pages.filter((p) => typeof p.page.path === "string" && p.page.path.length > 0).map((p) => ({ path: p.page.path, name: p.page.name, orbital: p.orbitalName, label: p.page.label })),
1953
- [pages]
1954
- );
1955
- const concreteCurrentPath = useMemo(() => {
1956
- const pattern = currentPagePath ?? "/";
1957
- return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
1958
- }, [currentPagePath, routeParams]);
1959
- const navStackRef = useRef(null);
1960
- const announcePage = useCallback((path) => {
1961
- if (onPageChange) {
1962
- onPageChange(path);
1963
- } else if (!isolated && typeof window !== "undefined") {
1964
- const url = new URL(window.location.href);
1965
- url.searchParams.set("page", path);
1966
- window.history.pushState({}, "", url.toString());
1967
- window.dispatchEvent(new PopStateEvent("popstate"));
1968
- }
1969
- }, [isolated, onPageChange]);
1970
- const handleNavigate = useCallback((path, navState) => {
1971
- const route = matchRoute(path);
1972
- if (!route) {
1973
- onUnmatchedNavigate?.(path);
1974
- return;
1975
- }
1976
- if (route.kind === "lazy") {
1977
- setLazyRoute({ page: route.page, path });
1978
- announcePage(path);
1979
- return;
1980
- }
1981
- setLazyRoute(null);
1982
- const match = route.entry;
1983
- const params = { ...route.params, ...navState ?? {} };
1984
- navLog.debug("handleNavigate", () => ({
1985
- path,
1986
- matched: match?.page.name ?? null,
1987
- params,
1988
- navState: navState ? JSON.stringify(navState) : void 0,
1989
- availablePaths: pages.map((p) => p.page.path)
1990
- }));
1991
- if (match.page.name) {
1992
- setRouteParams(params);
1993
- setCurrentPage(match.page.name);
1994
- announcePage(path);
1995
- }
1996
- }, [pages, matchRoute, announcePage, onUnmatchedNavigate]);
1997
- const handleNavigateEffect = useCallback(
1998
- (path, params, crumb) => {
1999
- navStackRef.current?.beginNavigate(path, crumb);
2000
- handleNavigate(path, params);
2001
- },
2002
- [handleNavigate]
2003
- );
2004
- const handleNavigateBack = useCallback(() => {
2005
- navStackRef.current?.back();
2006
- }, []);
2007
- if (!parseResult.ok) {
2008
- return /* @__PURE__ */ jsx(Box, { className, style: { height }, children: /* @__PURE__ */ jsxs(Typography, { as: "pre", color: "error", variant: "small", className: "font-mono whitespace-pre-wrap break-all m-0 p-4", children: [
2009
- "Parse error: ",
2010
- parseResult.error
2011
- ] }) });
2012
- }
2013
- const containerRef = useRef(null);
2014
- useEffect(() => {
2015
- const el = containerRef.current;
2016
- if (!el) return;
2017
- if (pages.length <= 1 && !isolated) {
2018
- navLog.debug("interceptor:skipped", { reason: "single-page schema", pageCount: pages.length });
2019
- return;
2020
- }
2021
- const handler = (e) => {
2022
- const anchor = e.target.closest("a");
2023
- if (!anchor) return;
2024
- const href = anchor.getAttribute("href") ?? anchor.getAttribute("to") ?? "";
2025
- navLog.debug("click:intercepted", {
2026
- href,
2027
- anchorText: anchor.textContent?.trim().slice(0, 40)
2028
- });
2029
- const external = href.startsWith("http") || href.startsWith("mailto:");
2030
- const appPath = external ? null : appPathFromHref(href, hostHrefBase);
2031
- if (isolated && appPath === null) {
2032
- e.preventDefault();
2033
- e.stopPropagation();
2034
- e.stopImmediatePropagation();
2035
- if (external) window.open(href, "_blank", "noopener,noreferrer");
2036
- navLog.debug("click:sandboxed", { href, external });
2037
- return;
2038
- }
2039
- if (appPath === null) {
2040
- navLog.debug("click:skipped", { href, reason: "not an in-app href", hostHrefBase });
2041
- return;
2042
- }
2043
- e.preventDefault();
2044
- e.stopPropagation();
2045
- e.stopImmediatePropagation();
2046
- handleNavigate(appPath);
2047
- };
2048
- el.addEventListener("click", handler, true);
2049
- navLog.info("interceptor:installed", { pageCount: pages.length, paths: pages.map((p) => p.page.path) });
2050
- return () => el.removeEventListener("click", handler, true);
2051
- }, [pages, handleNavigate, isolated, hostHrefBase]);
2052
- const withBridge = (node) => (serverUrl || transport) && !isLazyPageOfRegisteredProgram ? /* @__PURE__ */ jsx(ServerBridgeProvider, { schema: parseResult.schema, serverUrl, transport, getAccessToken, children: node }) : node;
2053
- if (lazyRoute) {
2054
- return withBridge(
2055
- /* @__PURE__ */ jsxs(Box, { ref: containerRef, className: `overflow-auto ${className ?? ""}`, style: { height }, children: [
2056
- lazyLoad.status === "loading" && /* @__PURE__ */ jsx(LoadingState, {}),
2057
- lazyLoad.status === "error" && /* @__PURE__ */ jsx(ErrorState, { "data-testid": "lazy-page-error", message: lazyLoad.error }),
2058
- lazyLoad.status === "ready" && /* @__PURE__ */ jsx(LazyPageOfRegisteredProgram.Provider, { value: true, children: /* @__PURE__ */ jsx(
2059
- OrbPreview,
2060
- {
2061
- schema: lazyLoad.schema,
2062
- autoMock,
2063
- height: "100%",
2064
- className: "border-0",
2065
- serverUrl,
2066
- transport,
2067
- getAccessToken,
2068
- initialPagePath: lazyRoute.path,
2069
- user,
2070
- isolated,
2071
- fit,
2072
- onPageChange: announcePage,
2073
- themeOverride,
2074
- lazyLoader,
2075
- onUnmatchedNavigate: handleNavigate
2076
- },
2077
- lazyRoute.page.orbRef
2078
- ) })
2079
- ] })
2080
- );
2081
- }
2082
- return withBridge(
2083
- /* @__PURE__ */ jsxs(
2084
- Box,
2085
- {
2086
- ref: containerRef,
2087
- className: `${fit ? "overflow-hidden" : "overflow-auto"} border border-[var(--color-border)] rounded-[var(--radius-md)] ${className ?? ""}`,
2088
- style: { height },
2089
- children: [
2090
- localFallback && /* @__PURE__ */ jsx(Box, { className: "px-3 py-2 bg-[var(--color-warning)] bg-opacity-10 border-b border-[var(--color-warning)] flex items-center gap-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-warning-foreground)] flex-1", children: t("orbPreview.serverUnreachable") }) }),
2091
- inHostRouter ? /* @__PURE__ */ jsx(HostHrefBaseProbe, { onBase: setHostHrefBase }) : null,
2092
- /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxs(
2093
- NavStackProvider,
2094
- {
2095
- pages: navPages,
2096
- currentPath: concreteCurrentPath,
2097
- navigate: handleNavigate,
2098
- storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
2099
- children: [
2100
- /* @__PURE__ */ jsx(NavStackRefBridge, { apiRef: navStackRef }),
2101
- /* @__PURE__ */ jsxs(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, user, children: [
2102
- /* @__PURE__ */ jsx(NavigateListener, { onNavigate: handleNavigate }),
2103
- /* @__PURE__ */ jsx(UISlotProvider, { children: fit ? /* @__PURE__ */ jsx(FitToBox, { mode: fit === "content" ? "content" : "box", children: /* @__PURE__ */ jsx(
2104
- SchemaRunner,
2105
- {
2106
- schema: parseResult.schema,
2107
- serverUrl,
2108
- transport,
2109
- getAccessToken,
2110
- mockData: effectiveMockData,
2111
- pageName: currentPage,
2112
- routeParams,
2113
- onNavigate: handleNavigateEffect,
2114
- onNavigateBack: handleNavigateBack,
2115
- onLocalFallback: handleLocalFallback,
2116
- localFallbackTimeoutMs,
2117
- persistence,
2118
- themeOverride
2119
- }
2120
- ) }) : /* @__PURE__ */ jsx(
2121
- SchemaRunner,
2122
- {
2123
- schema: parseResult.schema,
2124
- serverUrl,
2125
- transport,
2126
- getAccessToken,
2127
- mockData: effectiveMockData,
2128
- pageName: currentPage,
2129
- routeParams,
2130
- onNavigate: handleNavigateEffect,
2131
- onNavigateBack: handleNavigateBack,
2132
- onLocalFallback: handleLocalFallback,
2133
- localFallbackTimeoutMs,
2134
- persistence,
2135
- themeOverride
2136
- }
2137
- ) })
2138
- ] })
2139
- ]
2140
- }
2141
- ) })
2142
- ]
2143
- }
2144
- )
2145
- );
2146
- }
2147
- OrbPreview.displayName = "OrbPreview";
2148
- var playgroundLog = createLogger("almadar:ui:browser-playground");
2149
- function BrowserPlayground({
2150
- schema,
2151
- mode = "mock",
2152
- initialPagePath,
2153
- height,
2154
- className,
2155
- paused,
2156
- fit,
2157
- onPageChange,
2158
- viewer,
2159
- messages,
2160
- locale,
2161
- lazyLoader,
2162
- schemaPath
2163
- }) {
2164
- const [runtime] = useState(
2165
- () => new OrbitalServerRuntime({ mode, debug: false, ...viewer !== void 0 ? { defaultUser: viewer } : {} })
2166
- );
2167
- useEffect(() => {
2168
- if (paused === void 0) return;
2169
- if (paused) {
2170
- runtime.pauseTicks();
2171
- } else {
2172
- runtime.resumeTicks();
2173
- }
2174
- }, [runtime, paused]);
2175
- const lazySchemas = useMemo(() => {
2176
- const lazyPages = schema.lazyPages ?? [];
2177
- if (lazyPages.length === 0 || lazyLoader === void 0) return Promise.resolve([]);
2178
- return Promise.all(lazyPages.map(async (page) => {
2179
- const loaded = await loadLazyPage(lazyLoader, page, schemaPath);
2180
- if (!loaded.success) throw new Error(loaded.error);
2181
- return loaded.data;
2182
- }));
2183
- }, [schema.lazyPages, lazyLoader, schemaPath]);
2184
- const registerProgram = useCallback((program) => {
2185
- const orbitalNames = program.orbitals.map((o) => o.name);
2186
- playgroundLog.debug("register:start", { schema: program.name, orbitalNames });
2187
- return lazySchemas.then((lazy) => runtime.register(program, { ...messages !== void 0 ? { messages } : {}, lazy })).then(() => {
2188
- playgroundLog.debug("register:done", { schema: program.name, orbitalNames });
2189
- });
2190
- }, [runtime, lazySchemas, messages]);
2191
- const latestRegistration = useRef(Promise.resolve());
2192
- const registrationReady = useMemo(() => {
2193
- const ready = registerProgram(schema);
2194
- latestRegistration.current = ready;
2195
- return ready;
2196
- }, [registerProgram, schema]);
2197
- const teardownTimerRef = useRef(null);
2198
- useEffect(() => {
2199
- if (teardownTimerRef.current !== null) {
2200
- clearTimeout(teardownTimerRef.current);
2201
- teardownTimerRef.current = null;
2202
- }
2203
- return () => {
2204
- teardownTimerRef.current = setTimeout(() => {
2205
- teardownTimerRef.current = null;
2206
- playgroundLog.debug("unregisterAll:unmount");
2207
- runtime.unregisterAll();
2208
- }, 0);
2209
- };
2210
- }, [runtime]);
2211
- const transport = useMemo(
2212
- () => createInProcessTransport(
2213
- async (orbitalName, request) => {
2214
- await latestRegistration.current;
2215
- return runtime.processOrbitalEvent(orbitalName, request);
2216
- },
2217
- {
2218
- // Registers what the bridge hands over (lazy behaviors included): a bridge that unregistered first gets it back.
2219
- onRegister: (program) => {
2220
- const ready = registerProgram(program);
2221
- latestRegistration.current = ready;
2222
- return ready;
2223
- },
2224
- onUnregister: () => {
2225
- runtime.unregisterAll();
2226
- }
2227
- }
2228
- ),
2229
- [runtime, registerProgram, registrationReady]
2230
- );
2231
- const viewerLocale = locale ?? schema.locales?.[0];
2232
- const i18n = useMemo(() => {
2233
- if (viewerLocale === void 0 || messages === void 0) return void 0;
2234
- const catalog = messages[viewerLocale] ?? {};
2235
- return { locale: viewerLocale, direction: localeDirection(viewerLocale), t: createTranslate(catalog, viewerLocale), messages: catalog };
2236
- }, [viewerLocale, messages]);
2237
- const preview = /* @__PURE__ */ jsx(
2238
- OrbPreview,
2239
- {
2240
- schema,
2241
- transport,
2242
- initialPagePath,
2243
- height,
2244
- className,
2245
- fit,
2246
- onPageChange,
2247
- user: viewer ?? null,
2248
- lazyLoader,
2249
- schemaPath,
2250
- isolated: true
2251
- }
2252
- );
2253
- return i18n !== void 0 ? /* @__PURE__ */ jsx(I18nProvider, { value: i18n, children: preview }) : preview;
2254
- }
2255
-
2256
- export { BrowserPlayground, OrbPreview, clearSchemaCache, useResolvedSchema, useTraitStateMachine };