@almadar/ui 6.51.2 → 6.53.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 (127) hide show
  1. package/dist/{EntityBindingContext-BbxrwSb9.d.ts → AwaitingSkeletonContext-Bh399p1x.d.ts} +17 -2
  2. package/dist/{Canvas3DHost-CLC_jXh_.d.ts → Canvas3DHost-B-y2TNij.d.ts} +1 -1
  3. package/dist/{FileTree-RKQqRhIM.d.ts → FileTree-DUi7e7Z8.d.ts} +5 -5
  4. package/dist/{GameAudioProvider-BgWLggIy.d.ts → GameAudioProvider-DyBvYP2c.d.ts} +1 -1
  5. package/dist/{NavStackContext-B-XLULCO.d.ts → NavStackContext-04NLdXsY.d.ts} +8 -0
  6. package/dist/Typography-JGSDH4JP.js +5 -0
  7. package/dist/{UISlotContext-DNM8iXna.d.ts → UISlotContext-CS487Uoc.d.ts} +1 -1
  8. package/dist/{UISlotRenderer-7UYHFW4S.js → UISlotRenderer-JT34I3O7.js} +9 -9
  9. package/dist/avl/index.cjs +39958 -38525
  10. package/dist/avl/index.d.ts +8 -5
  11. package/dist/avl/index.js +37 -26
  12. package/dist/{chunk-JXUYEO7U.js → chunk-2LFBQ4IS.js} +7 -1
  13. package/dist/{chunk-GIPZSCND.js → chunk-3EF5SDIJ.js} +31157 -31206
  14. package/dist/{chunk-GHXWGQTX.js → chunk-4OZDHCZV.js} +1 -1
  15. package/dist/{chunk-HQDKHLFG.js → chunk-64WACXM7.js} +15 -1
  16. package/dist/{chunk-FGHRVJCI.js → chunk-6O37FCX4.js} +13 -2
  17. package/dist/{chunk-BUEIZY54.js → chunk-7LM6I3NA.js} +8 -7
  18. package/dist/chunk-CBIJP5PL.js +284 -0
  19. package/dist/{chunk-MWPX2O4M.js → chunk-GMDMOYXD.js} +3 -16
  20. package/dist/{chunk-YKHNK5JH.js → chunk-GODZKHAL.js} +11 -4
  21. package/dist/{chunk-T7IH5ABO.js → chunk-IKULQIDX.js} +199 -88
  22. package/dist/{chunk-NTO555KF.js → chunk-JRW33U35.js} +183 -62
  23. package/dist/{chunk-OLZ3EEML.js → chunk-KQXXO42S.js} +106 -90
  24. package/dist/chunk-OKWD7LPO.js +80 -0
  25. package/dist/{chunk-YHY5K2Y7.js → chunk-PEST66PN.js} +6 -0
  26. package/dist/{chunk-L55SAFKL.js → chunk-UGHAADU5.js} +77 -1
  27. package/dist/{chunk-6CFYRP4S.js → chunk-VHTJRS2N.js} +4 -8
  28. package/dist/{chunk-R4V7IZQP.js → chunk-WPNM2KC6.js} +2 -2
  29. package/dist/{cn-CO-0rolG.d.ts → cn-ClXGlIOY.d.ts} +2 -2
  30. package/dist/components/index.cjs +46084 -44766
  31. package/dist/components/index.d.ts +645 -699
  32. package/dist/components/index.js +621 -13
  33. package/dist/context/index.cjs +20 -7
  34. package/dist/context/index.d.ts +3 -3
  35. package/dist/context/index.js +3 -3
  36. package/dist/hooks/index.cjs +126 -9
  37. package/dist/hooks/index.d.ts +32 -7
  38. package/dist/hooks/index.js +7 -7
  39. package/dist/lib/drawable/three/index.cjs +79 -2
  40. package/dist/lib/drawable/three/index.d.ts +3 -3
  41. package/dist/lib/drawable/three/index.js +7 -7
  42. package/dist/lib/index.cjs +116 -2
  43. package/dist/lib/index.d.ts +29 -6
  44. package/dist/lib/index.js +6 -5
  45. package/dist/locales/index.cjs +231 -3
  46. package/dist/locales/index.js +155 -3
  47. package/dist/{paintDispatch-CMgOyVjS.d.ts → paintDispatch-_6EhJCiL.d.ts} +6 -6
  48. package/dist/providers/index.cjs +36404 -35028
  49. package/dist/providers/index.d.ts +34 -9
  50. package/dist/providers/index.js +82 -19
  51. package/dist/renderer/index.cjs +6 -0
  52. package/dist/renderer/index.d.ts +1 -1
  53. package/dist/renderer/index.js +1 -1
  54. package/dist/runtime/index.cjs +37861 -36437
  55. package/dist/runtime/index.d.ts +6 -4
  56. package/dist/runtime/index.js +11 -11
  57. package/dist/ssr/index.cjs +584 -181
  58. package/dist/ssr/index.d.cts +36 -44
  59. package/dist/ssr/index.d.ts +36 -44
  60. package/dist/ssr/index.js +527 -124
  61. package/dist/{themeTokens-CN9WCXQc.d.ts → themeTokens-Ccu7EmGY.d.ts} +4 -2
  62. package/dist/{useUISlots-CwUWM1ra.d.ts → useUISlots-1dk2TR4j.d.ts} +1 -9
  63. package/dist/{verificationRegistry-DBHfOR9y.d.ts → verificationRegistry-CD7Xb40L.d.ts} +1 -1
  64. package/locales/ar.json +77 -1
  65. package/locales/en.json +77 -1
  66. package/locales/sl.json +77 -1
  67. package/package.json +13 -10
  68. package/tailwind-preset.cjs +37 -1
  69. package/themes/_base.css +114 -8
  70. package/themes/_contract.md +6 -0
  71. package/themes/almadar-website.css +2 -0
  72. package/themes/almadar.css +2 -0
  73. package/themes/aqua.css +5 -3
  74. package/themes/arabesque.css +9 -7
  75. package/themes/art-deco.css +5 -3
  76. package/themes/art-nouveau.css +4 -0
  77. package/themes/atelier.css +10 -8
  78. package/themes/bauhaus.css +2 -0
  79. package/themes/bloomberg-dense.css +13 -11
  80. package/themes/blueprint.css +2 -0
  81. package/themes/clay.css +2 -0
  82. package/themes/comic.css +12 -10
  83. package/themes/constructivist.css +2 -0
  84. package/themes/corporate.css +4 -2
  85. package/themes/cyberpunk.css +2 -0
  86. package/themes/frutiger-aero.css +2 -0
  87. package/themes/game-adventure.css +1 -0
  88. package/themes/game-rpg.css +1 -0
  89. package/themes/game-sci-fi.css +1 -0
  90. package/themes/game-ui-pack.css +1 -0
  91. package/themes/gazette.css +10 -8
  92. package/themes/glass.css +17 -15
  93. package/themes/gothic.css +2 -0
  94. package/themes/ink-wash.css +2 -0
  95. package/themes/kawaii.css +2 -0
  96. package/themes/kiosk.css +17 -15
  97. package/themes/linear-clean.css +12 -10
  98. package/themes/luxury.css +2 -0
  99. package/themes/mac-classic.css +2 -0
  100. package/themes/material.css +2 -0
  101. package/themes/memphis.css +2 -0
  102. package/themes/mid-century.css +2 -0
  103. package/themes/minimalist.css +2 -0
  104. package/themes/mudcloth.css +2 -0
  105. package/themes/neon.css +2 -0
  106. package/themes/neumorphic.css +2 -0
  107. package/themes/newsprint.css +2 -0
  108. package/themes/notion-editorial.css +12 -10
  109. package/themes/pixel.css +2 -0
  110. package/themes/prism.css +20 -18
  111. package/themes/retro.css +2 -0
  112. package/themes/risograph.css +2 -0
  113. package/themes/scandi.css +2 -0
  114. package/themes/sketch.css +2 -0
  115. package/themes/solarpunk.css +2 -0
  116. package/themes/steampunk.css +2 -0
  117. package/themes/swiss.css +2 -0
  118. package/themes/terminal.css +8 -6
  119. package/themes/trait-wars.css +2 -0
  120. package/themes/ukiyo-e.css +2 -0
  121. package/themes/vaporwave.css +2 -0
  122. package/themes/victorian.css +2 -0
  123. package/themes/wabi-sabi.css +2 -0
  124. package/themes/win95.css +2 -0
  125. package/themes/wireframe.css +2 -0
  126. package/themes/zine.css +2 -0
  127. package/dist/Typography-2Z3JKWHN.js +0 -5
@@ -1,17 +1,18 @@
1
1
  import { runTickFrame } from './chunk-DQTV6QVS.js';
2
- import { ALL_SLOTS } from './chunk-6CFYRP4S.js';
3
- import { UISlotRenderer } from './chunk-GIPZSCND.js';
4
- import { Box } from './chunk-BUEIZY54.js';
5
- import { Typography } from './chunk-MWPX2O4M.js';
2
+ import { ALL_SLOTS } from './chunk-VHTJRS2N.js';
3
+ import { UISlotRenderer } from './chunk-3EF5SDIJ.js';
4
+ import { Box } from './chunk-7LM6I3NA.js';
5
+ import { Typography } from './chunk-GMDMOYXD.js';
6
+ import { beginPending, endPending } from './chunk-JRW33U35.js';
6
7
  import { useEventBus } from './chunk-6EV3OEBQ.js';
7
- import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-OLZ3EEML.js';
8
+ import { registerTrait, unregisterTrait, perfTimeAsync } from './chunk-KQXXO42S.js';
8
9
  import { bindTraitStateGetter, registerTraitSnapshot, recordTransition, recordingTransport, recordServerResponse } from './chunk-O7OLVVBR.js';
9
- import { isNotificationSlot } from './chunk-YHY5K2Y7.js';
10
+ import { isNotificationSlot } from './chunk-PEST66PN.js';
10
11
  import React, { useState, useMemo, useEffect, useRef, useCallback, useSyncExternalStore } from 'react';
11
12
  import { schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, applyListenPayloadMapping, buildResolvedTraitConfigs, collectCallsiteCaptureChildren, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER } from '@almadar/core';
12
13
  import { createLogger } from '@almadar/logger';
13
- import { LIFECYCLE_EVENTS, InMemoryPersistence, createInProcessTransport, buildTraitIndex, createMemoryCircuitStore, createIndexStageRunner, evaluateOrbitalEvent, createClientKernel, collectListenerTargets, createTickScheduler, isValidCronExpression, parseDurationString, UNMOUNT_EVENT, createClientEffectHandlers, EffectExecutor, isSExpression, collectDeclaredConfigDefaults, createContextFromBindings, normalizeCallSiteConfigToValues } from '@almadar/runtime';
14
- import { useUser, useArbitraryClassStyles, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, useNavStack, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, ServerBridgeProvider, useServerBridge, EntityBindingContext } from '@almadar/ui/providers';
14
+ import { collectListenerTargets, LIFECYCLE_EVENTS, InMemoryPersistence, createInProcessTransport, buildTraitIndex, createMemoryCircuitStore, createIndexStageRunner, evaluateOrbitalEvent, createClientKernel, createTickScheduler, isValidCronExpression, parseDurationString, UNMOUNT_EVENT, createClientEffectHandlers, EffectExecutor, isSExpression, collectDeclaredConfigDefaults, createContextFromBindings, normalizeCallSiteConfigToValues } from '@almadar/runtime';
15
+ import { useUser, useArbitraryClassStyles, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, useNavStack, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, ServerBridgeProvider, useServerBridge, EntityBindingContext, AwaitingSkeletonContext } from '@almadar/ui/providers';
15
16
  import { evaluateListenPayloadExpr, evaluate, createMinimalContext, evaluateGuard, executeEffects } from '@almadar/evaluator';
16
17
  import { getPatternDefinition } from '@almadar/core/patterns';
17
18
  import { isKnownStdOperator } from '@almadar/std/registry';
@@ -269,6 +270,18 @@ function useCircuitKernel(traitBindings, options) {
269
270
  [baseTransport]
270
271
  );
271
272
  const orbitalName = options.orbitals[0]?.name ?? "";
273
+ const topology = useMemo(() => {
274
+ let resolve = () => void 0;
275
+ const promise = new Promise((r) => {
276
+ resolve = r;
277
+ });
278
+ return { promise, resolve };
279
+ }, [effectiveTransport]);
280
+ const awaitTopology = options.awaitTopology === true;
281
+ const confirmedCarries = options.carriesCircuitState ?? false;
282
+ useEffect(() => {
283
+ if (!awaitTopology) topology.resolve({ carriesCircuitState: confirmedCarries });
284
+ }, [awaitTopology, confirmedCarries, topology]);
272
285
  const rawKernel = useMemo(() => createClientKernel({
273
286
  orbitalName,
274
287
  traitIndex,
@@ -281,7 +294,8 @@ function useCircuitKernel(traitBindings, options) {
281
294
  ...options.contextExtensions !== void 0 ? { contextExtensions: options.contextExtensions } : {},
282
295
  ...options.debug !== void 0 ? { debug: options.debug } : {},
283
296
  ...options.logContext !== void 0 ? { logContext: options.logContext } : {},
284
- carriesCircuitState: options.carriesCircuitState ?? false,
297
+ carriesCircuitState: false,
298
+ topology: topology.promise,
285
299
  ...effectiveTransport !== void 0 ? { transport: effectiveTransport } : {}
286
300
  }), [
287
301
  orbitalName,
@@ -295,50 +309,16 @@ function useCircuitKernel(traitBindings, options) {
295
309
  options.contextExtensions,
296
310
  options.debug,
297
311
  options.logContext,
298
- options.carriesCircuitState,
312
+ topology,
299
313
  effectiveTransport
300
314
  ]);
301
- const rawKernelRef = useRef(rawKernel);
302
- rawKernelRef.current = rawKernel;
303
- const awaitTopology = options.awaitTopology === true;
304
- const holdRef = useRef(awaitTopology);
305
- holdRef.current = awaitTopology;
306
- const heldRef = useRef([]);
307
- useEffect(() => {
308
- if (awaitTopology) return;
309
- const held = heldRef.current;
310
- heldRef.current = [];
311
- for (const release of held) release();
312
- }, [awaitTopology, rawKernel]);
313
315
  const kernel = useMemo(() => ({
314
316
  store: rawKernel.store,
315
- dispatch: (request) => {
316
- const stamped = request.clientId !== void 0 ? request : { ...request, clientId: getTabClientId() };
317
- if (!holdRef.current) return rawKernel.dispatch(stamped);
318
- return new Promise((resolve, reject) => {
319
- heldRef.current.push(() => {
320
- rawKernelRef.current.dispatch(stamped).then(resolve, reject);
321
- });
322
- });
323
- }
317
+ dispatch: (request, hooks) => rawKernel.dispatch(request.clientId !== void 0 ? request : { ...request, clientId: getTabClientId() }, hooks),
318
+ dispatchMount: (seeds, hooks, base = {}) => rawKernel.dispatchMount(seeds, hooks, base.clientId !== void 0 ? base : { ...base, clientId: getTabClientId() }),
319
+ dispatchProgress: (request) => rawKernel.dispatchProgress(request.clientId !== void 0 ? request : { ...request, clientId: getTabClientId() })
324
320
  }), [rawKernel]);
325
- const kernelRef = useRef(kernel);
326
- kernelRef.current = kernel;
327
- useEffect(() => {
328
- if (!effectiveTransport?.subscribe) return;
329
- return effectiveTransport.subscribe((emitted) => {
330
- const targets = collectListenerTargets(traitIndex, emitted.source, emitted.event, emitted.payload);
331
- for (const target of targets) {
332
- void kernelRef.current.dispatch({
333
- event: target.triggers,
334
- ...target.payload !== void 0 ? { payload: target.payload } : {},
335
- ...target.entityId !== void 0 ? { entityId: target.entityId } : {},
336
- targetTrait: target.listenerTrait
337
- });
338
- }
339
- }, { clientId: getTabClientId() });
340
- }, [effectiveTransport, traitIndex]);
341
- return { kernel, store, traitIndex };
321
+ return { kernel, store, traitIndex, transport: effectiveTransport };
342
322
  }
343
323
  var log2 = createLogger("almadar:ui:circuit:bus-ingress");
344
324
  function useBusIngress(traitBindings, traitIndex, settle, eventBus) {
@@ -363,8 +343,12 @@ function useBusIngress(traitBindings, traitIndex, settle, eventBus) {
363
343
  const dispatch = (traitName, eventKey, payload, source) => {
364
344
  if (!claim(source, traitName, eventKey)) return;
365
345
  const tick = source?.tick;
346
+ const pendingKey = source?.pendingKey;
347
+ if (pendingKey !== void 0) beginPending(pendingKey);
366
348
  void settle(traitName, eventKey, payload, tick).catch((err) => {
367
349
  log2.error("dispatch:failed", { trait: traitName, event: eventKey, error: String(err) });
350
+ }).finally(() => {
351
+ if (pendingKey !== void 0) endPending(pendingKey);
368
352
  });
369
353
  };
370
354
  for (const binding of traitBindings) {
@@ -514,7 +498,7 @@ function resolveLambdaBindings(body, params, item, index) {
514
498
  return body;
515
499
  }
516
500
  var LazySlotContentRenderer = React.lazy(
517
- () => import('./UISlotRenderer-7UYHFW4S.js').then((m) => ({ default: m.SlotContentRenderer }))
501
+ () => import('./UISlotRenderer-JT34I3O7.js').then((m) => ({ default: m.SlotContentRenderer }))
518
502
  );
519
503
  function deferLambdaEntityExprs(value) {
520
504
  if (typeof value === "string") {
@@ -638,12 +622,15 @@ function useSlotFlush(uiSlots, embeddedTraits) {
638
622
  uiSlotsRef.current = uiSlots;
639
623
  const embeddedTraitsRef = useRef(embeddedTraits);
640
624
  embeddedTraitsRef.current = embeddedTraits;
625
+ const writtenRef = useRef(/* @__PURE__ */ new Map());
626
+ const keyOf = (slot, trait) => `${slot}\0${trait}`;
641
627
  const flushSlot = useCallback((traitName, slot, patterns, source) => {
642
628
  const slots = uiSlotsRef.current;
643
629
  const embedded = embeddedTraitsRef.current;
644
630
  if (patterns.length === 0) {
645
631
  flushLog.debug("clear", { traitName, slot });
646
632
  slots.clearBySource(slot, traitName);
633
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "clear", slot, trait: traitName });
647
634
  return;
648
635
  }
649
636
  const last = patterns[patterns.length - 1];
@@ -652,19 +639,20 @@ function useSlotFlush(uiSlots, embeddedTraits) {
652
639
  const props = convertFnFormLambdasInProps(rawProps, typeof patternType === "string" ? patternType : void 0);
653
640
  const isEmbedded = (embedded?.has(traitName) ?? false) && !isNotificationSlot(slot);
654
641
  if (isEmbedded) {
655
- slots.updateTraitContent(traitName, {
642
+ const content = {
656
643
  pattern: patternType,
657
644
  props,
658
645
  slot,
659
646
  priority: 0,
660
- animation: "fade",
661
647
  transitionEvent: source?.event,
662
648
  fromState: source?.state,
663
649
  entity: source?.entity
664
- });
650
+ };
651
+ slots.updateTraitContent(traitName, content);
652
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "trait", trait: traitName, content });
665
653
  return;
666
654
  }
667
- slots.render({
655
+ const config = {
668
656
  target: slot,
669
657
  pattern: patternType,
670
658
  props,
@@ -672,7 +660,9 @@ function useSlotFlush(uiSlots, embeddedTraits) {
672
660
  transitionEvent: source?.event,
673
661
  fromState: source?.state,
674
662
  entity: source?.entity
675
- });
663
+ };
664
+ slots.render(config);
665
+ writtenRef.current.set(keyOf(slot, traitName), { kind: "render", config });
676
666
  }, []);
677
667
  const applyClientEffects = useCallback((clientEffects, clientEffectsByTrait, onNavigate, onNavigateBack, activeTraits) => {
678
668
  const slots = uiSlotsRef.current;
@@ -687,13 +677,18 @@ function useSlotFlush(uiSlots, embeddedTraits) {
687
677
  const { bareTraitRef, patternType, props: unwrapped } = unwrapPattern(pattern);
688
678
  const isEmbedded = (embedded?.has(sourceTrait) ?? false) && !isNotificationSlot(slot);
689
679
  const propsValue = bareTraitRef !== void 0 ? bareTraitRef : { ...unwrapped ?? {}, ...rawProps };
690
- const props = convertFnFormLambdasInProps(propsValue);
680
+ const props = convertFnFormLambdasInProps(propsValue, typeof patternType === "string" ? patternType : void 0);
691
681
  if (pattern === null) {
692
682
  slots.clearBySource(slot, sourceTrait);
683
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "clear", slot, trait: sourceTrait });
693
684
  } else if (isEmbedded) {
694
- slots.updateTraitContent(sourceTrait, { pattern: patternType, props, slot, priority: 0, animation: "fade", transitionEvent: event, fromState });
685
+ const content = { pattern: patternType, props, slot, priority: 0, transitionEvent: event, fromState };
686
+ slots.updateTraitContent(sourceTrait, content);
687
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "trait", trait: sourceTrait, content });
695
688
  } else {
696
- slots.render({ target: slot, pattern: patternType, props, sourceTrait, transitionEvent: event, fromState });
689
+ const config = { target: slot, pattern: patternType, props, sourceTrait, transitionEvent: event, fromState };
690
+ slots.render(config);
691
+ writtenRef.current.set(keyOf(slot, sourceTrait), { kind: "render", config });
697
692
  }
698
693
  } else if (kind === "navigate") {
699
694
  const [, route, params, options] = effect;
@@ -703,7 +698,26 @@ function useSlotFlush(uiSlots, embeddedTraits) {
703
698
  }
704
699
  }
705
700
  }, []);
706
- return useMemo(() => ({ flushSlot, applyClientEffects }), [flushSlot, applyClientEffects]);
701
+ const checkpoint = useCallback(() => new Map(writtenRef.current), []);
702
+ const rollback = useCallback((cp) => {
703
+ const slots = uiSlotsRef.current;
704
+ for (const [key, now] of writtenRef.current) {
705
+ const then = cp.get(key);
706
+ if (then === now) continue;
707
+ if (then === void 0 || then.kind === "clear") {
708
+ if (now.kind === "render") slots.clearBySource(now.config.target, now.config.sourceTrait ?? "kernel");
709
+ else if (now.kind === "trait") {
710
+ flushLog.warn("rollback:embedded-frame-kept", { trait: now.trait });
711
+ }
712
+ } else if (then.kind === "render") {
713
+ slots.render(then.config);
714
+ } else {
715
+ slots.updateTraitContent(then.trait, then.content);
716
+ }
717
+ }
718
+ writtenRef.current = new Map(cp);
719
+ }, []);
720
+ return useMemo(() => ({ flushSlot, applyClientEffects, checkpoint, rollback }), [flushSlot, applyClientEffects, checkpoint, rollback]);
707
721
  }
708
722
  var log3 = createLogger("almadar:ui:circuit:effect-runner");
709
723
  async function runCircuitEffects(params) {
@@ -1096,6 +1110,25 @@ function sameFields(a, b) {
1096
1110
  }
1097
1111
  return true;
1098
1112
  }
1113
+ function useAwaitingSkeletonSource(store, traitIndex, embeddedTraits) {
1114
+ return useMemo(() => {
1115
+ const activeRenders = () => {
1116
+ const renders = [];
1117
+ for (const awaiting of store.awaiting.list()) {
1118
+ const transitions = traitIndex.byName.get(awaiting.trait)?.irTrait.stateMachine?.transitions ?? [];
1119
+ const taken = transitions.find((t) => t.event === awaiting.event && t.from === awaiting.from);
1120
+ if (taken?.awaitRender) renders.push(...taken.awaitRender);
1121
+ }
1122
+ return renders;
1123
+ };
1124
+ return {
1125
+ forSlot: (slot) => activeRenders().find((r) => r.slot === slot && !(embeddedTraits?.has(r.trait) ?? false))?.skeleton,
1126
+ forTrait: (traitName) => activeRenders().find((r) => r.trait === traitName)?.skeleton,
1127
+ subscribe: (callback) => store.awaiting.subscribeAll(callback),
1128
+ getVersion: () => store.awaiting.getVersion()
1129
+ };
1130
+ }, [store, traitIndex, embeddedTraits]);
1131
+ }
1099
1132
 
1100
1133
  // hooks/useTraitStateMachine.ts
1101
1134
  var stateLog = createLogger("almadar:ui:state-transitions");
@@ -1114,7 +1147,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1114
1147
  const { user: contextViewer } = useUser();
1115
1148
  const userContext = options.user ?? contextViewer ?? void 0;
1116
1149
  const observer = useMemo(() => createCircuitVerificationObserver(), []);
1117
- const { kernel, store, traitIndex } = useCircuitKernel(traitBindings, {
1150
+ const { kernel, store, traitIndex, transport } = useCircuitKernel(traitBindings, {
1118
1151
  orbitals: options.orbitals,
1119
1152
  ...options.traitConfigsByName !== void 0 ? { traitConfigsByName: options.traitConfigsByName } : {},
1120
1153
  ...options.transport !== void 0 ? { transport: options.transport } : {},
@@ -1144,6 +1177,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1144
1177
  ...userContext !== void 0 ? { user: userContext } : {}
1145
1178
  });
1146
1179
  const entityBindingSource = useEntityBindingSource(store, traitIndex);
1180
+ const awaitingSkeletonSource = useAwaitingSkeletonSource(store, traitIndex, options.embeddedTraits);
1147
1181
  const activeTraitNamesRef = useRef(/* @__PURE__ */ new Set());
1148
1182
  useEffect(() => {
1149
1183
  activeTraitNamesRef.current = new Set(traitIndex.byName.keys());
@@ -1191,11 +1225,41 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1191
1225
  for (const unreg of snapshotUnregs) unreg();
1192
1226
  };
1193
1227
  }, [traitBindings, store, traitIndex]);
1228
+ const afterSettle = useCallback(async (traitName, eventKey, payload, transitioned, repaints, composedBefore) => {
1229
+ if (transitioned) {
1230
+ const entityByTrait = {};
1231
+ for (const [name, entry] of traitIndex.byName) {
1232
+ const row = store.frames.get(entry.frameKey);
1233
+ if (row !== void 0) entityByTrait[name] = row;
1234
+ }
1235
+ const repainted = new Set(repaints.map((e) => e.traitName));
1236
+ const serverComposed = new Set([...store.callsitePayloads].filter(([child, composed]) => composedBefore.get(child) !== composed).map(([child]) => child));
1237
+ await reRenderCallsiteCaptureChildren(traitName, payload ?? {}, entityByTrait, repainted, serverComposed);
1238
+ }
1239
+ options.onEventProcessed?.(eventKey, payload);
1240
+ }, [traitIndex, store, reRenderCallsiteCaptureChildren, options.onEventProcessed]);
1241
+ const settleOutcome = useCallback(async (traitName, eventKey, payload, outcome, composedBefore) => {
1242
+ recordDispatchVerdict(traitIndex.byName.get(traitName)?.orbitalName ?? traitName, eventKey, { response: outcome.response });
1243
+ const settledEffects = outcome.localPainted && outcome.serverEffects !== void 0 ? outcome.serverEffects : outcome.response;
1244
+ slotFlush.applyClientEffects(
1245
+ settledEffects.clientEffects ?? [],
1246
+ settledEffects.clientEffectsByTrait,
1247
+ options.navigate,
1248
+ options.navigateBack,
1249
+ activeTraitNamesRef.current
1250
+ );
1251
+ for (const emitted of outcome.response.emittedEvents) {
1252
+ eventBus.emit(`UI:${emitted.event}`, emitted.payload, { ...emitted.source ?? {}, dispatched: true });
1253
+ }
1254
+ stateLog.debug("dispatch:settled", { traitName, eventKey, mode: outcome.mode, transitioned: outcome.response.transitioned });
1255
+ await afterSettle(traitName, eventKey, payload, outcome.response.transitioned, outcome.response.clientEffectsByTrait ?? [], composedBefore);
1256
+ }, [slotFlush, traitIndex, eventBus, options.navigate, options.navigateBack, afterSettle]);
1194
1257
  const dispatchAndSettle = useCallback(async (traitName, eventKey, payload, tick) => {
1195
1258
  const entityId = typeof payload?.entityId === "string" ? payload.entityId : void 0;
1196
1259
  const orbitalName = traitIndex.byName.get(traitName)?.orbitalName ?? traitName;
1197
1260
  let outcome;
1198
1261
  const composedBefore = new Map(store.callsitePayloads);
1262
+ let beforeLocalPaint;
1199
1263
  try {
1200
1264
  outcome = await kernel.dispatch({
1201
1265
  event: eventKey,
@@ -1203,35 +1267,80 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1203
1267
  ...entityId !== void 0 ? { entityId } : {},
1204
1268
  ...tick !== void 0 ? { tick } : {},
1205
1269
  targetTrait: traitName
1270
+ }, {
1271
+ onLocal: (local) => {
1272
+ beforeLocalPaint = slotFlush.checkpoint();
1273
+ slotFlush.applyClientEffects(
1274
+ local.clientEffects ?? [],
1275
+ local.clientEffectsByTrait,
1276
+ options.navigate,
1277
+ options.navigateBack,
1278
+ activeTraitNamesRef.current
1279
+ );
1280
+ }
1206
1281
  });
1207
1282
  } catch (err) {
1283
+ if (beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1208
1284
  recordDispatchVerdict(orbitalName, eventKey, { error: err instanceof Error ? err : String(err) });
1209
1285
  throw err;
1210
1286
  }
1211
- recordDispatchVerdict(orbitalName, eventKey, { response: outcome.response });
1212
- slotFlush.applyClientEffects(
1213
- outcome.response.clientEffects ?? [],
1214
- outcome.response.clientEffectsByTrait,
1215
- options.navigate,
1216
- options.navigateBack,
1217
- activeTraitNamesRef.current
1218
- );
1219
- for (const emitted of outcome.response.emittedEvents) {
1220
- eventBus.emit(`UI:${emitted.event}`, emitted.payload, { ...emitted.source ?? {}, dispatched: true });
1287
+ if (outcome.localPainted && !outcome.response.success && beforeLocalPaint !== void 0) {
1288
+ slotFlush.rollback(beforeLocalPaint);
1221
1289
  }
1222
- stateLog.debug("dispatch:settled", { traitName, eventKey, mode: outcome.mode, transitioned: outcome.response.transitioned });
1223
- if (outcome.response.transitioned) {
1224
- const entityByTrait = {};
1225
- for (const [name, entry] of traitIndex.byName) {
1226
- const row = store.frames.get(entry.frameKey);
1227
- if (row !== void 0) entityByTrait[name] = row;
1290
+ await settleOutcome(traitName, eventKey, payload, outcome, composedBefore);
1291
+ }, [kernel, slotFlush, traitIndex, store, options.navigate, options.navigateBack, settleOutcome]);
1292
+ const progressAndSettle = useCallback(async (traitName, eventKey, payload) => {
1293
+ const entityId = typeof payload?.entityId === "string" ? payload.entityId : void 0;
1294
+ const composedBefore = new Map(store.callsitePayloads);
1295
+ const outcome = await kernel.dispatchProgress({
1296
+ event: eventKey,
1297
+ ...payload !== void 0 ? { payload } : {},
1298
+ ...entityId !== void 0 ? { entityId } : {},
1299
+ targetTrait: traitName
1300
+ });
1301
+ await settleOutcome(traitName, eventKey, payload, outcome, composedBefore);
1302
+ }, [kernel, store, settleOutcome]);
1303
+ useEffect(() => {
1304
+ if (!transport?.subscribe) return;
1305
+ return transport.subscribe((emitted, target) => {
1306
+ for (const t of collectListenerTargets(traitIndex, emitted.source, emitted.event, emitted.payload)) {
1307
+ void (target === "origin" ? progressAndSettle(t.listenerTrait, t.triggers, t.payload) : dispatchAndSettle(t.listenerTrait, t.triggers, t.payload));
1228
1308
  }
1229
- const repainted = new Set((outcome.response.clientEffectsByTrait ?? []).map((e) => e.traitName));
1230
- const serverComposed = new Set([...store.callsitePayloads].filter(([child, composed]) => composedBefore.get(child) !== composed).map(([child]) => child));
1231
- await reRenderCallsiteCaptureChildren(traitName, payload ?? {}, entityByTrait, repainted, serverComposed);
1309
+ }, { clientId: getTabClientId() });
1310
+ }, [transport, traitIndex, progressAndSettle, dispatchAndSettle]);
1311
+ const mountAndSettle = useCallback(async (seeds, payload) => {
1312
+ const composedBefore = new Map(store.callsitePayloads);
1313
+ let beforeLocalPaint;
1314
+ let outcome;
1315
+ try {
1316
+ outcome = await kernel.dispatchMount(seeds, {
1317
+ onLocal: (_trait, local) => {
1318
+ if (beforeLocalPaint === void 0) beforeLocalPaint = slotFlush.checkpoint();
1319
+ slotFlush.applyClientEffects(local.clientEffects ?? [], local.clientEffectsByTrait, options.navigate, options.navigateBack, activeTraitNamesRef.current);
1320
+ }
1321
+ }, { payload });
1322
+ } catch (err) {
1323
+ if (beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1324
+ for (const seed of seeds) {
1325
+ recordDispatchVerdict(traitIndex.byName.get(seed.trait)?.orbitalName ?? seed.trait, seed.event, { error: err instanceof Error ? err : String(err) });
1326
+ }
1327
+ throw err;
1232
1328
  }
1233
- options.onEventProcessed?.(eventKey, payload);
1234
- }, [kernel, slotFlush, traitIndex, store, eventBus, reRenderCallsiteCaptureChildren, options.navigate, options.navigateBack, options.onEventProcessed]);
1329
+ if (!outcome.success && beforeLocalPaint !== void 0) slotFlush.rollback(beforeLocalPaint);
1330
+ slotFlush.applyClientEffects(outcome.serverEffects.clientEffects ?? [], outcome.serverEffects.clientEffectsByTrait, options.navigate, options.navigateBack, activeTraitNamesRef.current);
1331
+ const emitted = [...outcome.seeds.flatMap((seed) => seed.local.emittedEvents), ...outcome.emittedEvents];
1332
+ for (const e of emitted) {
1333
+ eventBus.emit(`UI:${e.event}`, e.payload, { ...e.source ?? {}, dispatched: true });
1334
+ }
1335
+ const serverRepaints = outcome.serverEffects.clientEffectsByTrait ?? [];
1336
+ for (const seed of outcome.seeds) {
1337
+ recordDispatchVerdict(traitIndex.byName.get(seed.trait)?.orbitalName ?? seed.trait, seed.event, {
1338
+ response: { ...seed.local, success: outcome.success, ...outcome.error !== void 0 ? { error: outcome.error } : {} }
1339
+ });
1340
+ stateLog.debug("mount:settled", { traitName: seed.trait, eventKey: seed.event, mode: seed.mode, transitioned: seed.local.transitioned });
1341
+ await afterSettle(seed.trait, seed.event, payload, seed.local.transitioned, [...seed.local.clientEffectsByTrait ?? [], ...serverRepaints], composedBefore);
1342
+ }
1343
+ }, [kernel, slotFlush, traitIndex, store, eventBus, options.navigate, options.navigateBack, afterSettle]);
1235
1344
  useBusIngress(traitBindings, traitIndex, dispatchAndSettle, eventBus);
1236
1345
  const sendEvent = useCallback((eventKey, payload) => {
1237
1346
  const normalized = normalizeEventKey(eventKey);
@@ -1272,17 +1381,19 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
1272
1381
  if (mount.initialized.has(traitName)) continue;
1273
1382
  mount.initialized.add(traitName);
1274
1383
  const lifecycleEvent = LIFECYCLE_EVENTS.find((evt) => store.manager.canHandleEvent(traitName, evt));
1275
- if (lifecycleEvent !== void 0) entering.push({ traitName, lifecycleEvent });
1384
+ if (lifecycleEvent !== void 0) entering.push({ trait: traitName, event: lifecycleEvent });
1276
1385
  }
1277
- store.mount.mounting(entering.map((e) => e.traitName));
1278
- for (const { traitName, lifecycleEvent } of entering) {
1279
- void dispatchAndSettle(traitName, lifecycleEvent, { ...options.initPayload ?? {} });
1386
+ store.mount.mounting(entering.map((e) => e.trait));
1387
+ if (entering.length > 0) {
1388
+ void mountAndSettle(entering, { ...options.initPayload ?? {} }).catch((err) => {
1389
+ stateLog.warn("mount:failed", { traits: entering.map((e) => e.trait), error: String(err) });
1390
+ });
1280
1391
  }
1281
1392
  mountedRef.current = mount;
1282
- }, [traitBindings, store, dispatchAndSettle, mountKey]);
1393
+ }, [traitBindings, store, mountAndSettle, mountKey]);
1283
1394
  useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
1284
1395
  const traitStates = useMemo(() => store.manager.getAllStates(), [store, store.getVersion()]);
1285
- return { traitStates, sendEvent, getTraitState, canHandleEvent, entityBindingSource };
1396
+ return { traitStates, sendEvent, getTraitState, canHandleEvent, entityBindingSource, awaitingSkeletonSource };
1286
1397
  }
1287
1398
 
1288
1399
  // runtime/fitContent.ts
@@ -1353,7 +1464,7 @@ function TraitInitializer({ traits, routeParams, mountKey, orbitals, onNavigate,
1353
1464
  }, localFallbackTimeoutMs ?? 5e3);
1354
1465
  return () => clearTimeout(timer);
1355
1466
  }, [hasBridge, bridge.connected, onLocalFallback, localFallbackTimeoutMs]);
1356
- const { entityBindingSource } = useTraitStateMachine(traits, uiSlots, {
1467
+ const { entityBindingSource, awaitingSkeletonSource } = useTraitStateMachine(traits, uiSlots, {
1357
1468
  orbitals,
1358
1469
  ...onNavigate !== void 0 ? { navigate: onNavigate } : {},
1359
1470
  ...onNavigateBack !== void 0 ? { navigateBack: onNavigateBack } : {},
@@ -1364,7 +1475,7 @@ function TraitInitializer({ traits, routeParams, mountKey, orbitals, onNavigate,
1364
1475
  ...mountKey !== void 0 ? { mountKey } : {},
1365
1476
  ...hasBridge ? { transport: bridge.transport, carriesCircuitState: bridge.carriesCircuitState, awaitTopology: !bridge.topologyKnown } : { persistence }
1366
1477
  });
1367
- return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children });
1478
+ return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children: /* @__PURE__ */ jsx(AwaitingSkeletonContext.Provider, { value: awaitingSkeletonSource, children }) });
1368
1479
  }
1369
1480
  var CONTENT_STAGE_WIDTH = 480;
1370
1481
  function FitToBox({ children, mode = "box" }) {