@almadar/ui 5.51.0 → 5.52.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.
@@ -57351,7 +57351,7 @@ function convertFnFormLambdasInProps(props) {
57351
57351
  }
57352
57352
  var log10 = logger.createLogger("almadar:ui:effects:client-handlers");
57353
57353
  function createClientEffectHandlers(options) {
57354
- const { eventBus, slotSetter, navigate, notify, callService } = options;
57354
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
57355
57355
  return {
57356
57356
  emit: (event, payload) => {
57357
57357
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -57360,8 +57360,12 @@ function createClientEffectHandlers(options) {
57360
57360
  persist: async () => {
57361
57361
  log10.warn("persist is server-side only, ignored on client");
57362
57362
  },
57363
- set: () => {
57364
- log10.warn("set is server-side only, ignored on client");
57363
+ set: (_entityId, field, value) => {
57364
+ if (!liveEntity) {
57365
+ log10.warn("set is server-side only, ignored on client (no live entity)");
57366
+ return;
57367
+ }
57368
+ liveEntity[field] = value;
57365
57369
  },
57366
57370
  callService: async (service, action, params) => {
57367
57371
  if (callService) return callService(service, action, params);
@@ -57629,6 +57633,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57629
57633
  const traitName = binding.trait.name;
57630
57634
  const linkedEntity = binding.linkedEntity || "";
57631
57635
  const entityId = payload?.entityId;
57636
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
57637
+ if (!liveEntity) {
57638
+ liveEntity = {};
57639
+ traitFieldStatesRef.current.set(traitName, liveEntity);
57640
+ }
57632
57641
  const effects = syncOnly ? params.effects.filter(
57633
57642
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
57634
57643
  ) : params.effects;
@@ -57651,7 +57660,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57651
57660
  },
57652
57661
  navigate: optionsRef.current?.navigate,
57653
57662
  notify: optionsRef.current?.notify,
57654
- callService: optionsRef.current?.callService
57663
+ callService: optionsRef.current?.callService,
57664
+ // The canonical client `set` writes `(set @entity.X)` straight into
57665
+ // the trait's one live store — the same object bound below. This is
57666
+ // what makes a [runtime] entity's set (from ticks AND events) reach
57667
+ // the render-ui + next tick + guards.
57668
+ liveEntity
57655
57669
  });
57656
57670
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
57657
57671
  let handlers = clientHandlers;
@@ -57662,7 +57676,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57662
57676
  // uses below. `@payload.*` resolves from `payload` separately,
57663
57677
  // so dropping the prior `payload as EntityRow` cast loses
57664
57678
  // nothing — it just stops mislabelling the payload as an entity.
57665
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
57679
+ entity: liveEntity,
57666
57680
  payload: payload || {},
57667
57681
  state: previousState
57668
57682
  };
@@ -57702,24 +57716,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57702
57716
  handlers = {
57703
57717
  ...handlers,
57704
57718
  set: async (targetId, field, value) => {
57705
- let fieldState = traitFieldStatesRef.current.get(traitName);
57706
- if (!fieldState) {
57707
- fieldState = {};
57708
- traitFieldStatesRef.current.set(traitName, fieldState);
57709
- }
57710
- fieldState[field] = value;
57719
+ if (baseSet) await baseSet(targetId, field, value);
57711
57720
  log15.debug("set:write", {
57712
57721
  traitName,
57713
57722
  field,
57714
57723
  value: JSON.stringify(value),
57715
57724
  transition: `${previousState}->${newState}`
57716
57725
  });
57717
- if (baseSet) await baseSet(targetId, field, value);
57718
57726
  }
57719
57727
  };
57720
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
57721
57728
  const bindingCtx = {
57722
- entity: entityForBinding,
57729
+ entity: liveEntity,
57723
57730
  payload: payload || {},
57724
57731
  state: previousState
57725
57732
  };
package/dist/avl/index.js CHANGED
@@ -57304,7 +57304,7 @@ function convertFnFormLambdasInProps(props) {
57304
57304
  }
57305
57305
  var log10 = createLogger("almadar:ui:effects:client-handlers");
57306
57306
  function createClientEffectHandlers(options) {
57307
- const { eventBus, slotSetter, navigate, notify, callService } = options;
57307
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
57308
57308
  return {
57309
57309
  emit: (event, payload) => {
57310
57310
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -57313,8 +57313,12 @@ function createClientEffectHandlers(options) {
57313
57313
  persist: async () => {
57314
57314
  log10.warn("persist is server-side only, ignored on client");
57315
57315
  },
57316
- set: () => {
57317
- log10.warn("set is server-side only, ignored on client");
57316
+ set: (_entityId, field, value) => {
57317
+ if (!liveEntity) {
57318
+ log10.warn("set is server-side only, ignored on client (no live entity)");
57319
+ return;
57320
+ }
57321
+ liveEntity[field] = value;
57318
57322
  },
57319
57323
  callService: async (service, action, params) => {
57320
57324
  if (callService) return callService(service, action, params);
@@ -57582,6 +57586,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57582
57586
  const traitName = binding.trait.name;
57583
57587
  const linkedEntity = binding.linkedEntity || "";
57584
57588
  const entityId = payload?.entityId;
57589
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
57590
+ if (!liveEntity) {
57591
+ liveEntity = {};
57592
+ traitFieldStatesRef.current.set(traitName, liveEntity);
57593
+ }
57585
57594
  const effects = syncOnly ? params.effects.filter(
57586
57595
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
57587
57596
  ) : params.effects;
@@ -57604,7 +57613,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57604
57613
  },
57605
57614
  navigate: optionsRef.current?.navigate,
57606
57615
  notify: optionsRef.current?.notify,
57607
- callService: optionsRef.current?.callService
57616
+ callService: optionsRef.current?.callService,
57617
+ // The canonical client `set` writes `(set @entity.X)` straight into
57618
+ // the trait's one live store — the same object bound below. This is
57619
+ // what makes a [runtime] entity's set (from ticks AND events) reach
57620
+ // the render-ui + next tick + guards.
57621
+ liveEntity
57608
57622
  });
57609
57623
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
57610
57624
  let handlers = clientHandlers;
@@ -57615,7 +57629,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57615
57629
  // uses below. `@payload.*` resolves from `payload` separately,
57616
57630
  // so dropping the prior `payload as EntityRow` cast loses
57617
57631
  // nothing — it just stops mislabelling the payload as an entity.
57618
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
57632
+ entity: liveEntity,
57619
57633
  payload: payload || {},
57620
57634
  state: previousState
57621
57635
  };
@@ -57655,24 +57669,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57655
57669
  handlers = {
57656
57670
  ...handlers,
57657
57671
  set: async (targetId, field, value) => {
57658
- let fieldState = traitFieldStatesRef.current.get(traitName);
57659
- if (!fieldState) {
57660
- fieldState = {};
57661
- traitFieldStatesRef.current.set(traitName, fieldState);
57662
- }
57663
- fieldState[field] = value;
57672
+ if (baseSet) await baseSet(targetId, field, value);
57664
57673
  log15.debug("set:write", {
57665
57674
  traitName,
57666
57675
  field,
57667
57676
  value: JSON.stringify(value),
57668
57677
  transition: `${previousState}->${newState}`
57669
57678
  });
57670
- if (baseSet) await baseSet(targetId, field, value);
57671
57679
  }
57672
57680
  };
57673
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
57674
57681
  const bindingCtx = {
57675
- entity: entityForBinding,
57682
+ entity: liveEntity,
57676
57683
  payload: payload || {},
57677
57684
  state: previousState
57678
57685
  };
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @packageDocumentation
7
7
  */
8
- import type { EventPayload, ServiceParams } from '@almadar/core';
8
+ import type { EventPayload, EntityRow, ServiceParams } from '@almadar/core';
9
9
  import type { EffectHandlers } from '@almadar/runtime';
10
10
  export interface ClientEventBus {
11
11
  emit: (type: string, payload?: EventPayload) => void;
@@ -19,6 +19,17 @@ export interface CreateClientEffectHandlersOptions {
19
19
  slotSetter: SlotSetter;
20
20
  navigate?: (path: string, params?: Record<string, unknown>) => void;
21
21
  notify?: (message: string, type: 'success' | 'error' | 'warning' | 'info') => void;
22
+ /**
23
+ * Live client-entity write target for `[runtime]` entities. `(set
24
+ * @entity.<field> value)` runs entirely in the browser for in-memory
25
+ * entities (game boards, wizards): there is no server row to persist to,
26
+ * so the canonical client `set` must mutate THIS object — the same object
27
+ * `EffectExecutor` reads `@entity.*` from for the current `executeAll` and
28
+ * the next tick seeds from. When omitted, `set` is a no-op (bridge mode:
29
+ * the server owns persistence). One store, read live by render-ui, guards,
30
+ * and ticks — no guard-vs-render split.
31
+ */
32
+ liveEntity?: EntityRow;
22
33
  /**
23
34
  * Optional consumer-supplied call-service handler. When set, it runs
24
35
  * instead of the default mock fallback — use to wire the playground
@@ -51279,7 +51279,7 @@ var init_UISlotRenderer = __esm({
51279
51279
  });
51280
51280
  var log = logger.createLogger("almadar:ui:effects:client-handlers");
51281
51281
  function createClientEffectHandlers(options) {
51282
- const { eventBus, slotSetter, navigate, notify, callService } = options;
51282
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
51283
51283
  return {
51284
51284
  emit: (event, payload) => {
51285
51285
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -51288,8 +51288,12 @@ function createClientEffectHandlers(options) {
51288
51288
  persist: async () => {
51289
51289
  log.warn("persist is server-side only, ignored on client");
51290
51290
  },
51291
- set: () => {
51292
- log.warn("set is server-side only, ignored on client");
51291
+ set: (_entityId, field, value) => {
51292
+ if (!liveEntity) {
51293
+ log.warn("set is server-side only, ignored on client (no live entity)");
51294
+ return;
51295
+ }
51296
+ liveEntity[field] = value;
51293
51297
  },
51294
51298
  callService: async (service, action, params) => {
51295
51299
  if (callService) return callService(service, action, params);
@@ -51683,6 +51687,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51683
51687
  const traitName = binding.trait.name;
51684
51688
  const linkedEntity = binding.linkedEntity || "";
51685
51689
  const entityId = payload?.entityId;
51690
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
51691
+ if (!liveEntity) {
51692
+ liveEntity = {};
51693
+ traitFieldStatesRef.current.set(traitName, liveEntity);
51694
+ }
51686
51695
  const effects = syncOnly ? params.effects.filter(
51687
51696
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
51688
51697
  ) : params.effects;
@@ -51705,7 +51714,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51705
51714
  },
51706
51715
  navigate: optionsRef.current?.navigate,
51707
51716
  notify: optionsRef.current?.notify,
51708
- callService: optionsRef.current?.callService
51717
+ callService: optionsRef.current?.callService,
51718
+ // The canonical client `set` writes `(set @entity.X)` straight into
51719
+ // the trait's one live store — the same object bound below. This is
51720
+ // what makes a [runtime] entity's set (from ticks AND events) reach
51721
+ // the render-ui + next tick + guards.
51722
+ liveEntity
51709
51723
  });
51710
51724
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
51711
51725
  let handlers = clientHandlers;
@@ -51716,7 +51730,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51716
51730
  // uses below. `@payload.*` resolves from `payload` separately,
51717
51731
  // so dropping the prior `payload as EntityRow` cast loses
51718
51732
  // nothing — it just stops mislabelling the payload as an entity.
51719
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
51733
+ entity: liveEntity,
51720
51734
  payload: payload || {},
51721
51735
  state: previousState
51722
51736
  };
@@ -51756,24 +51770,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51756
51770
  handlers = {
51757
51771
  ...handlers,
51758
51772
  set: async (targetId, field, value) => {
51759
- let fieldState = traitFieldStatesRef.current.get(traitName);
51760
- if (!fieldState) {
51761
- fieldState = {};
51762
- traitFieldStatesRef.current.set(traitName, fieldState);
51763
- }
51764
- fieldState[field] = value;
51773
+ if (baseSet) await baseSet(targetId, field, value);
51765
51774
  log14.debug("set:write", {
51766
51775
  traitName,
51767
51776
  field,
51768
51777
  value: JSON.stringify(value),
51769
51778
  transition: `${previousState}->${newState}`
51770
51779
  });
51771
- if (baseSet) await baseSet(targetId, field, value);
51772
51780
  }
51773
51781
  };
51774
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
51775
51782
  const bindingCtx = {
51776
- entity: entityForBinding,
51783
+ entity: liveEntity,
51777
51784
  payload: payload || {},
51778
51785
  state: previousState
51779
51786
  };
@@ -51232,7 +51232,7 @@ var init_UISlotRenderer = __esm({
51232
51232
  });
51233
51233
  var log = createLogger("almadar:ui:effects:client-handlers");
51234
51234
  function createClientEffectHandlers(options) {
51235
- const { eventBus, slotSetter, navigate, notify, callService } = options;
51235
+ const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
51236
51236
  return {
51237
51237
  emit: (event, payload) => {
51238
51238
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -51241,8 +51241,12 @@ function createClientEffectHandlers(options) {
51241
51241
  persist: async () => {
51242
51242
  log.warn("persist is server-side only, ignored on client");
51243
51243
  },
51244
- set: () => {
51245
- log.warn("set is server-side only, ignored on client");
51244
+ set: (_entityId, field, value) => {
51245
+ if (!liveEntity) {
51246
+ log.warn("set is server-side only, ignored on client (no live entity)");
51247
+ return;
51248
+ }
51249
+ liveEntity[field] = value;
51246
51250
  },
51247
51251
  callService: async (service, action, params) => {
51248
51252
  if (callService) return callService(service, action, params);
@@ -51636,6 +51640,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51636
51640
  const traitName = binding.trait.name;
51637
51641
  const linkedEntity = binding.linkedEntity || "";
51638
51642
  const entityId = payload?.entityId;
51643
+ let liveEntity = traitFieldStatesRef.current.get(traitName);
51644
+ if (!liveEntity) {
51645
+ liveEntity = {};
51646
+ traitFieldStatesRef.current.set(traitName, liveEntity);
51647
+ }
51639
51648
  const effects = syncOnly ? params.effects.filter(
51640
51649
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
51641
51650
  ) : params.effects;
@@ -51658,7 +51667,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51658
51667
  },
51659
51668
  navigate: optionsRef.current?.navigate,
51660
51669
  notify: optionsRef.current?.notify,
51661
- callService: optionsRef.current?.callService
51670
+ callService: optionsRef.current?.callService,
51671
+ // The canonical client `set` writes `(set @entity.X)` straight into
51672
+ // the trait's one live store — the same object bound below. This is
51673
+ // what makes a [runtime] entity's set (from ticks AND events) reach
51674
+ // the render-ui + next tick + guards.
51675
+ liveEntity
51662
51676
  });
51663
51677
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
51664
51678
  let handlers = clientHandlers;
@@ -51669,7 +51683,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51669
51683
  // uses below. `@payload.*` resolves from `payload` separately,
51670
51684
  // so dropping the prior `payload as EntityRow` cast loses
51671
51685
  // nothing — it just stops mislabelling the payload as an entity.
51672
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
51686
+ entity: liveEntity,
51673
51687
  payload: payload || {},
51674
51688
  state: previousState
51675
51689
  };
@@ -51709,24 +51723,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51709
51723
  handlers = {
51710
51724
  ...handlers,
51711
51725
  set: async (targetId, field, value) => {
51712
- let fieldState = traitFieldStatesRef.current.get(traitName);
51713
- if (!fieldState) {
51714
- fieldState = {};
51715
- traitFieldStatesRef.current.set(traitName, fieldState);
51716
- }
51717
- fieldState[field] = value;
51726
+ if (baseSet) await baseSet(targetId, field, value);
51718
51727
  log14.debug("set:write", {
51719
51728
  traitName,
51720
51729
  field,
51721
51730
  value: JSON.stringify(value),
51722
51731
  transition: `${previousState}->${newState}`
51723
51732
  });
51724
- if (baseSet) await baseSet(targetId, field, value);
51725
51733
  }
51726
51734
  };
51727
- const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
51728
51735
  const bindingCtx = {
51729
- entity: entityForBinding,
51736
+ entity: liveEntity,
51730
51737
  payload: payload || {},
51731
51738
  state: previousState
51732
51739
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.51.0",
3
+ "version": "5.52.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [