@almadar/ui 5.50.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.
- package/dist/avl/index.cjs +42 -15
- package/dist/avl/index.js +42 -15
- package/dist/runtime/createClientEffectHandlers.d.ts +12 -1
- package/dist/runtime/index.cjs +42 -15
- package/dist/runtime/index.js +42 -15
- package/package.json +1 -1
package/dist/avl/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
@@ -57426,6 +57430,7 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
|
57426
57430
|
"do",
|
|
57427
57431
|
"when"
|
|
57428
57432
|
]);
|
|
57433
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
57429
57434
|
function toTraitDefinition(binding) {
|
|
57430
57435
|
return {
|
|
57431
57436
|
name: binding.trait.name,
|
|
@@ -57458,6 +57463,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57458
57463
|
});
|
|
57459
57464
|
const eventQueueRef = React90.useRef([]);
|
|
57460
57465
|
const processingRef = React90.useRef(false);
|
|
57466
|
+
const initedTraitsRef = React90.useRef(/* @__PURE__ */ new Set());
|
|
57461
57467
|
const traitBindingsRef = React90.useRef(traitBindings);
|
|
57462
57468
|
const managerRef = React90.useRef(manager);
|
|
57463
57469
|
const uiSlotsRef = React90.useRef(uiSlots);
|
|
@@ -57627,6 +57633,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57627
57633
|
const traitName = binding.trait.name;
|
|
57628
57634
|
const linkedEntity = binding.linkedEntity || "";
|
|
57629
57635
|
const entityId = payload?.entityId;
|
|
57636
|
+
let liveEntity = traitFieldStatesRef.current.get(traitName);
|
|
57637
|
+
if (!liveEntity) {
|
|
57638
|
+
liveEntity = {};
|
|
57639
|
+
traitFieldStatesRef.current.set(traitName, liveEntity);
|
|
57640
|
+
}
|
|
57630
57641
|
const effects = syncOnly ? params.effects.filter(
|
|
57631
57642
|
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
57632
57643
|
) : params.effects;
|
|
@@ -57649,7 +57660,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57649
57660
|
},
|
|
57650
57661
|
navigate: optionsRef.current?.navigate,
|
|
57651
57662
|
notify: optionsRef.current?.notify,
|
|
57652
|
-
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
|
|
57653
57669
|
});
|
|
57654
57670
|
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
57655
57671
|
let handlers = clientHandlers;
|
|
@@ -57660,7 +57676,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57660
57676
|
// uses below. `@payload.*` resolves from `payload` separately,
|
|
57661
57677
|
// so dropping the prior `payload as EntityRow` cast loses
|
|
57662
57678
|
// nothing — it just stops mislabelling the payload as an entity.
|
|
57663
|
-
entity:
|
|
57679
|
+
entity: liveEntity,
|
|
57664
57680
|
payload: payload || {},
|
|
57665
57681
|
state: previousState
|
|
57666
57682
|
};
|
|
@@ -57700,24 +57716,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57700
57716
|
handlers = {
|
|
57701
57717
|
...handlers,
|
|
57702
57718
|
set: async (targetId, field, value) => {
|
|
57703
|
-
|
|
57704
|
-
if (!fieldState) {
|
|
57705
|
-
fieldState = {};
|
|
57706
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57707
|
-
}
|
|
57708
|
-
fieldState[field] = value;
|
|
57719
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
57709
57720
|
log15.debug("set:write", {
|
|
57710
57721
|
traitName,
|
|
57711
57722
|
field,
|
|
57712
57723
|
value: JSON.stringify(value),
|
|
57713
57724
|
transition: `${previousState}->${newState}`
|
|
57714
57725
|
});
|
|
57715
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
57716
57726
|
}
|
|
57717
57727
|
};
|
|
57718
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57719
57728
|
const bindingCtx = {
|
|
57720
|
-
entity:
|
|
57729
|
+
entity: liveEntity,
|
|
57721
57730
|
payload: payload || {},
|
|
57722
57731
|
state: previousState
|
|
57723
57732
|
};
|
|
@@ -58070,7 +58079,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
58070
58079
|
if (!orbitalName) continue;
|
|
58071
58080
|
for (const transition of binding.trait.transitions) {
|
|
58072
58081
|
const eventKey = transition.event;
|
|
58073
|
-
if (eventKey
|
|
58082
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
58074
58083
|
continue;
|
|
58075
58084
|
}
|
|
58076
58085
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -58125,6 +58134,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
58125
58134
|
crossTraitLog.debug("cleanup:done", {});
|
|
58126
58135
|
};
|
|
58127
58136
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
58137
|
+
React90.useEffect(() => {
|
|
58138
|
+
const mgr = managerRef.current;
|
|
58139
|
+
const inited = initedTraitsRef.current;
|
|
58140
|
+
for (const binding of traitBindings) {
|
|
58141
|
+
const traitName = binding.trait.name;
|
|
58142
|
+
if (inited.has(traitName)) continue;
|
|
58143
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
58144
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
58145
|
+
);
|
|
58146
|
+
if (lifecycleEvent === void 0) continue;
|
|
58147
|
+
inited.add(traitName);
|
|
58148
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
58149
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
58150
|
+
}
|
|
58151
|
+
return () => {
|
|
58152
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
58153
|
+
};
|
|
58154
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
58128
58155
|
return {
|
|
58129
58156
|
traitStates,
|
|
58130
58157
|
sendEvent,
|
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
|
-
|
|
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);
|
|
@@ -57379,6 +57383,7 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
|
57379
57383
|
"do",
|
|
57380
57384
|
"when"
|
|
57381
57385
|
]);
|
|
57386
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
57382
57387
|
function toTraitDefinition(binding) {
|
|
57383
57388
|
return {
|
|
57384
57389
|
name: binding.trait.name,
|
|
@@ -57411,6 +57416,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57411
57416
|
});
|
|
57412
57417
|
const eventQueueRef = useRef([]);
|
|
57413
57418
|
const processingRef = useRef(false);
|
|
57419
|
+
const initedTraitsRef = useRef(/* @__PURE__ */ new Set());
|
|
57414
57420
|
const traitBindingsRef = useRef(traitBindings);
|
|
57415
57421
|
const managerRef = useRef(manager);
|
|
57416
57422
|
const uiSlotsRef = useRef(uiSlots);
|
|
@@ -57580,6 +57586,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57580
57586
|
const traitName = binding.trait.name;
|
|
57581
57587
|
const linkedEntity = binding.linkedEntity || "";
|
|
57582
57588
|
const entityId = payload?.entityId;
|
|
57589
|
+
let liveEntity = traitFieldStatesRef.current.get(traitName);
|
|
57590
|
+
if (!liveEntity) {
|
|
57591
|
+
liveEntity = {};
|
|
57592
|
+
traitFieldStatesRef.current.set(traitName, liveEntity);
|
|
57593
|
+
}
|
|
57583
57594
|
const effects = syncOnly ? params.effects.filter(
|
|
57584
57595
|
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
57585
57596
|
) : params.effects;
|
|
@@ -57602,7 +57613,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57602
57613
|
},
|
|
57603
57614
|
navigate: optionsRef.current?.navigate,
|
|
57604
57615
|
notify: optionsRef.current?.notify,
|
|
57605
|
-
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
|
|
57606
57622
|
});
|
|
57607
57623
|
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
57608
57624
|
let handlers = clientHandlers;
|
|
@@ -57613,7 +57629,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57613
57629
|
// uses below. `@payload.*` resolves from `payload` separately,
|
|
57614
57630
|
// so dropping the prior `payload as EntityRow` cast loses
|
|
57615
57631
|
// nothing — it just stops mislabelling the payload as an entity.
|
|
57616
|
-
entity:
|
|
57632
|
+
entity: liveEntity,
|
|
57617
57633
|
payload: payload || {},
|
|
57618
57634
|
state: previousState
|
|
57619
57635
|
};
|
|
@@ -57653,24 +57669,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57653
57669
|
handlers = {
|
|
57654
57670
|
...handlers,
|
|
57655
57671
|
set: async (targetId, field, value) => {
|
|
57656
|
-
|
|
57657
|
-
if (!fieldState) {
|
|
57658
|
-
fieldState = {};
|
|
57659
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57660
|
-
}
|
|
57661
|
-
fieldState[field] = value;
|
|
57672
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
57662
57673
|
log15.debug("set:write", {
|
|
57663
57674
|
traitName,
|
|
57664
57675
|
field,
|
|
57665
57676
|
value: JSON.stringify(value),
|
|
57666
57677
|
transition: `${previousState}->${newState}`
|
|
57667
57678
|
});
|
|
57668
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
57669
57679
|
}
|
|
57670
57680
|
};
|
|
57671
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57672
57681
|
const bindingCtx = {
|
|
57673
|
-
entity:
|
|
57682
|
+
entity: liveEntity,
|
|
57674
57683
|
payload: payload || {},
|
|
57675
57684
|
state: previousState
|
|
57676
57685
|
};
|
|
@@ -58023,7 +58032,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
58023
58032
|
if (!orbitalName) continue;
|
|
58024
58033
|
for (const transition of binding.trait.transitions) {
|
|
58025
58034
|
const eventKey = transition.event;
|
|
58026
|
-
if (eventKey
|
|
58035
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
58027
58036
|
continue;
|
|
58028
58037
|
}
|
|
58029
58038
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -58078,6 +58087,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
58078
58087
|
crossTraitLog.debug("cleanup:done", {});
|
|
58079
58088
|
};
|
|
58080
58089
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
58090
|
+
useEffect(() => {
|
|
58091
|
+
const mgr = managerRef.current;
|
|
58092
|
+
const inited = initedTraitsRef.current;
|
|
58093
|
+
for (const binding of traitBindings) {
|
|
58094
|
+
const traitName = binding.trait.name;
|
|
58095
|
+
if (inited.has(traitName)) continue;
|
|
58096
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
58097
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
58098
|
+
);
|
|
58099
|
+
if (lifecycleEvent === void 0) continue;
|
|
58100
|
+
inited.add(traitName);
|
|
58101
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
58102
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
58103
|
+
}
|
|
58104
|
+
return () => {
|
|
58105
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
58106
|
+
};
|
|
58107
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
58081
58108
|
return {
|
|
58082
58109
|
traitStates,
|
|
58083
58110
|
sendEvent,
|
|
@@ -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
|
package/dist/runtime/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
@@ -51480,6 +51484,7 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
|
51480
51484
|
"do",
|
|
51481
51485
|
"when"
|
|
51482
51486
|
]);
|
|
51487
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
51483
51488
|
function toTraitDefinition(binding) {
|
|
51484
51489
|
return {
|
|
51485
51490
|
name: binding.trait.name,
|
|
@@ -51512,6 +51517,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51512
51517
|
});
|
|
51513
51518
|
const eventQueueRef = React81.useRef([]);
|
|
51514
51519
|
const processingRef = React81.useRef(false);
|
|
51520
|
+
const initedTraitsRef = React81.useRef(/* @__PURE__ */ new Set());
|
|
51515
51521
|
const traitBindingsRef = React81.useRef(traitBindings);
|
|
51516
51522
|
const managerRef = React81.useRef(manager);
|
|
51517
51523
|
const uiSlotsRef = React81.useRef(uiSlots);
|
|
@@ -51681,6 +51687,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51681
51687
|
const traitName = binding.trait.name;
|
|
51682
51688
|
const linkedEntity = binding.linkedEntity || "";
|
|
51683
51689
|
const entityId = payload?.entityId;
|
|
51690
|
+
let liveEntity = traitFieldStatesRef.current.get(traitName);
|
|
51691
|
+
if (!liveEntity) {
|
|
51692
|
+
liveEntity = {};
|
|
51693
|
+
traitFieldStatesRef.current.set(traitName, liveEntity);
|
|
51694
|
+
}
|
|
51684
51695
|
const effects = syncOnly ? params.effects.filter(
|
|
51685
51696
|
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
51686
51697
|
) : params.effects;
|
|
@@ -51703,7 +51714,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51703
51714
|
},
|
|
51704
51715
|
navigate: optionsRef.current?.navigate,
|
|
51705
51716
|
notify: optionsRef.current?.notify,
|
|
51706
|
-
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
|
|
51707
51723
|
});
|
|
51708
51724
|
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
51709
51725
|
let handlers = clientHandlers;
|
|
@@ -51714,7 +51730,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51714
51730
|
// uses below. `@payload.*` resolves from `payload` separately,
|
|
51715
51731
|
// so dropping the prior `payload as EntityRow` cast loses
|
|
51716
51732
|
// nothing — it just stops mislabelling the payload as an entity.
|
|
51717
|
-
entity:
|
|
51733
|
+
entity: liveEntity,
|
|
51718
51734
|
payload: payload || {},
|
|
51719
51735
|
state: previousState
|
|
51720
51736
|
};
|
|
@@ -51754,24 +51770,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51754
51770
|
handlers = {
|
|
51755
51771
|
...handlers,
|
|
51756
51772
|
set: async (targetId, field, value) => {
|
|
51757
|
-
|
|
51758
|
-
if (!fieldState) {
|
|
51759
|
-
fieldState = {};
|
|
51760
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51761
|
-
}
|
|
51762
|
-
fieldState[field] = value;
|
|
51773
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
51763
51774
|
log14.debug("set:write", {
|
|
51764
51775
|
traitName,
|
|
51765
51776
|
field,
|
|
51766
51777
|
value: JSON.stringify(value),
|
|
51767
51778
|
transition: `${previousState}->${newState}`
|
|
51768
51779
|
});
|
|
51769
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
51770
51780
|
}
|
|
51771
51781
|
};
|
|
51772
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51773
51782
|
const bindingCtx = {
|
|
51774
|
-
entity:
|
|
51783
|
+
entity: liveEntity,
|
|
51775
51784
|
payload: payload || {},
|
|
51776
51785
|
state: previousState
|
|
51777
51786
|
};
|
|
@@ -52124,7 +52133,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52124
52133
|
if (!orbitalName) continue;
|
|
52125
52134
|
for (const transition of binding.trait.transitions) {
|
|
52126
52135
|
const eventKey = transition.event;
|
|
52127
|
-
if (eventKey
|
|
52136
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
52128
52137
|
continue;
|
|
52129
52138
|
}
|
|
52130
52139
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -52179,6 +52188,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52179
52188
|
crossTraitLog.debug("cleanup:done", {});
|
|
52180
52189
|
};
|
|
52181
52190
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
52191
|
+
React81.useEffect(() => {
|
|
52192
|
+
const mgr = managerRef.current;
|
|
52193
|
+
const inited = initedTraitsRef.current;
|
|
52194
|
+
for (const binding of traitBindings) {
|
|
52195
|
+
const traitName = binding.trait.name;
|
|
52196
|
+
if (inited.has(traitName)) continue;
|
|
52197
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
52198
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
52199
|
+
);
|
|
52200
|
+
if (lifecycleEvent === void 0) continue;
|
|
52201
|
+
inited.add(traitName);
|
|
52202
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
52203
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
52204
|
+
}
|
|
52205
|
+
return () => {
|
|
52206
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
52207
|
+
};
|
|
52208
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
52182
52209
|
return {
|
|
52183
52210
|
traitStates,
|
|
52184
52211
|
sendEvent,
|
package/dist/runtime/index.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
@@ -51433,6 +51437,7 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
|
51433
51437
|
"do",
|
|
51434
51438
|
"when"
|
|
51435
51439
|
]);
|
|
51440
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
51436
51441
|
function toTraitDefinition(binding) {
|
|
51437
51442
|
return {
|
|
51438
51443
|
name: binding.trait.name,
|
|
@@ -51465,6 +51470,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51465
51470
|
});
|
|
51466
51471
|
const eventQueueRef = useRef([]);
|
|
51467
51472
|
const processingRef = useRef(false);
|
|
51473
|
+
const initedTraitsRef = useRef(/* @__PURE__ */ new Set());
|
|
51468
51474
|
const traitBindingsRef = useRef(traitBindings);
|
|
51469
51475
|
const managerRef = useRef(manager);
|
|
51470
51476
|
const uiSlotsRef = useRef(uiSlots);
|
|
@@ -51634,6 +51640,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51634
51640
|
const traitName = binding.trait.name;
|
|
51635
51641
|
const linkedEntity = binding.linkedEntity || "";
|
|
51636
51642
|
const entityId = payload?.entityId;
|
|
51643
|
+
let liveEntity = traitFieldStatesRef.current.get(traitName);
|
|
51644
|
+
if (!liveEntity) {
|
|
51645
|
+
liveEntity = {};
|
|
51646
|
+
traitFieldStatesRef.current.set(traitName, liveEntity);
|
|
51647
|
+
}
|
|
51637
51648
|
const effects = syncOnly ? params.effects.filter(
|
|
51638
51649
|
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
51639
51650
|
) : params.effects;
|
|
@@ -51656,7 +51667,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51656
51667
|
},
|
|
51657
51668
|
navigate: optionsRef.current?.navigate,
|
|
51658
51669
|
notify: optionsRef.current?.notify,
|
|
51659
|
-
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
|
|
51660
51676
|
});
|
|
51661
51677
|
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
51662
51678
|
let handlers = clientHandlers;
|
|
@@ -51667,7 +51683,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51667
51683
|
// uses below. `@payload.*` resolves from `payload` separately,
|
|
51668
51684
|
// so dropping the prior `payload as EntityRow` cast loses
|
|
51669
51685
|
// nothing — it just stops mislabelling the payload as an entity.
|
|
51670
|
-
entity:
|
|
51686
|
+
entity: liveEntity,
|
|
51671
51687
|
payload: payload || {},
|
|
51672
51688
|
state: previousState
|
|
51673
51689
|
};
|
|
@@ -51707,24 +51723,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51707
51723
|
handlers = {
|
|
51708
51724
|
...handlers,
|
|
51709
51725
|
set: async (targetId, field, value) => {
|
|
51710
|
-
|
|
51711
|
-
if (!fieldState) {
|
|
51712
|
-
fieldState = {};
|
|
51713
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51714
|
-
}
|
|
51715
|
-
fieldState[field] = value;
|
|
51726
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
51716
51727
|
log14.debug("set:write", {
|
|
51717
51728
|
traitName,
|
|
51718
51729
|
field,
|
|
51719
51730
|
value: JSON.stringify(value),
|
|
51720
51731
|
transition: `${previousState}->${newState}`
|
|
51721
51732
|
});
|
|
51722
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
51723
51733
|
}
|
|
51724
51734
|
};
|
|
51725
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51726
51735
|
const bindingCtx = {
|
|
51727
|
-
entity:
|
|
51736
|
+
entity: liveEntity,
|
|
51728
51737
|
payload: payload || {},
|
|
51729
51738
|
state: previousState
|
|
51730
51739
|
};
|
|
@@ -52077,7 +52086,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52077
52086
|
if (!orbitalName) continue;
|
|
52078
52087
|
for (const transition of binding.trait.transitions) {
|
|
52079
52088
|
const eventKey = transition.event;
|
|
52080
|
-
if (eventKey
|
|
52089
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
52081
52090
|
continue;
|
|
52082
52091
|
}
|
|
52083
52092
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -52132,6 +52141,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
52132
52141
|
crossTraitLog.debug("cleanup:done", {});
|
|
52133
52142
|
};
|
|
52134
52143
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
52144
|
+
useEffect(() => {
|
|
52145
|
+
const mgr = managerRef.current;
|
|
52146
|
+
const inited = initedTraitsRef.current;
|
|
52147
|
+
for (const binding of traitBindings) {
|
|
52148
|
+
const traitName = binding.trait.name;
|
|
52149
|
+
if (inited.has(traitName)) continue;
|
|
52150
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
52151
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
52152
|
+
);
|
|
52153
|
+
if (lifecycleEvent === void 0) continue;
|
|
52154
|
+
inited.add(traitName);
|
|
52155
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
52156
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
52157
|
+
}
|
|
52158
|
+
return () => {
|
|
52159
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
52160
|
+
};
|
|
52161
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
52135
52162
|
return {
|
|
52136
52163
|
traitStates,
|
|
52137
52164
|
sendEvent,
|