@almadar/ui 5.158.0 → 5.159.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.
@@ -15,7 +15,7 @@ import { useTranslate } from '@almadar/ui/hooks';
15
15
  import { useUISlots, ThemeProvider, useTheme } from '@almadar/ui/context';
16
16
  export { DesignThemeProvider, useDesignTheme } from '@almadar/ui/context';
17
17
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
18
- import { wrapCallbackForEvent } from '@almadar/runtime/ui';
18
+ import { wrapCallbackForEvent, perfStart, perfEnd } from '@almadar/runtime/ui';
19
19
  import { useLocation, useNavigate, Link, Outlet } from 'react-router-dom';
20
20
  import ELK from 'elkjs/lib/elk.bundled.js';
21
21
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
@@ -286,7 +286,16 @@ function isPlainObject(value) {
286
286
  if (typeof value === "function") return false;
287
287
  return true;
288
288
  }
289
+ function isEvaluatorResolvedData(value) {
290
+ return evaluatorResolvedData.has(value);
291
+ }
292
+ function brandResolved(value) {
293
+ if (value !== null && typeof value === "object" && !React87__default.isValidElement(value) && !(value instanceof Date)) {
294
+ resolvedMarkerFree.add(value);
295
+ }
296
+ }
289
297
  function subtreeHasMarker(value) {
298
+ if (resolvedMarkerFree.has(value)) return false;
290
299
  const cached = markerPresenceCache.get(value);
291
300
  if (cached !== void 0) return cached;
292
301
  let found = false;
@@ -308,10 +317,23 @@ function subtreeHasMarker(value) {
308
317
  }
309
318
  function walkValue(value, scopeTrait, entity, config, state) {
310
319
  if (isRenderBindingMarker(value)) {
311
- return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
320
+ const cached = markerResolutionCache.get(value);
321
+ if (cached !== void 0 && cached.entity === entity && cached.config === config && cached.state === state) {
322
+ return { resolved: cached.resolved, changed: false };
323
+ }
324
+ const resolved = resolveMarkerExpression(value.expression, entity, config, state);
325
+ markerResolutionCache.set(value, { entity, config, state, resolved });
326
+ if (resolved !== null && typeof resolved === "object" && !React87__default.isValidElement(resolved) && !(resolved instanceof Date)) {
327
+ resolvedMarkerFree.add(resolved);
328
+ evaluatorResolvedData.add(resolved);
329
+ }
330
+ return { resolved, changed: true };
312
331
  }
313
332
  if (Array.isArray(value)) {
314
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
333
+ if (!subtreeHasMarker(value)) {
334
+ brandResolved(value);
335
+ return { resolved: value, changed: false };
336
+ }
315
337
  const out = [];
316
338
  let changed = false;
317
339
  for (const item of value) {
@@ -326,10 +348,14 @@ function walkValue(value, scopeTrait, entity, config, state) {
326
348
  out.push(resolved);
327
349
  if (itemChanged) changed = true;
328
350
  }
351
+ brandResolved(out);
329
352
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
330
353
  }
331
354
  if (isPlainObject(value)) {
332
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
355
+ if (!subtreeHasMarker(value)) {
356
+ brandResolved(value);
357
+ return { resolved: value, changed: false };
358
+ }
333
359
  const sourceTrait = value._sourceTrait;
334
360
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
335
361
  return { resolved: value, changed: false };
@@ -341,11 +367,13 @@ function walkValue(value, scopeTrait, entity, config, state) {
341
367
  out[key] = resolved;
342
368
  if (itemChanged) changed = true;
343
369
  }
370
+ brandResolved(out);
344
371
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
345
372
  }
346
373
  return { resolved: value, changed: false };
347
374
  }
348
375
  function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
376
+ if (resolvedMarkerFree.has(props)) return props;
349
377
  const out = {};
350
378
  let changed = false;
351
379
  for (const [key, value] of Object.entries(props)) {
@@ -353,13 +381,17 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
353
381
  out[key] = resolved;
354
382
  if (propChanged) changed = true;
355
383
  }
384
+ brandResolved(out);
356
385
  return changed ? out : props;
357
386
  }
358
- var markerPresenceCache;
387
+ var markerPresenceCache, markerResolutionCache, resolvedMarkerFree, evaluatorResolvedData;
359
388
  var init_resolve_render_bindings = __esm({
360
389
  "lib/resolve-render-bindings.ts"() {
361
390
  "use client";
362
391
  markerPresenceCache = /* @__PURE__ */ new WeakMap();
392
+ markerResolutionCache = /* @__PURE__ */ new WeakMap();
393
+ resolvedMarkerFree = /* @__PURE__ */ new WeakSet();
394
+ evaluatorResolvedData = /* @__PURE__ */ new WeakSet();
363
395
  }
364
396
  });
365
397
  function cn(...inputs) {
@@ -11793,6 +11825,10 @@ var init_molecules = __esm({
11793
11825
  init_puzzleObject();
11794
11826
  }
11795
11827
  });
11828
+ var init_perf = __esm({
11829
+ "lib/perf.ts"() {
11830
+ }
11831
+ });
11796
11832
  function themeBodyFont(el) {
11797
11833
  if (typeof getComputedStyle !== "function") return "system-ui, sans-serif";
11798
11834
  const v = getComputedStyle(el).getPropertyValue("--font-family-body").trim();
@@ -12141,6 +12177,7 @@ var init_LearningCanvas = __esm({
12141
12177
  "components/learning/atoms/LearningCanvas.tsx"() {
12142
12178
  "use client";
12143
12179
  init_cn();
12180
+ init_perf();
12144
12181
  init_useEventBus();
12145
12182
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12146
12183
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
@@ -12184,6 +12221,7 @@ var init_LearningCanvas = __esm({
12184
12221
  return [...shapes, ...traceOut, ...readoutOut];
12185
12222
  }, [shapes, traces, readouts, width, height]);
12186
12223
  const draw = useCallback(() => {
12224
+ const _perfT = perfStart("learningcanvas:paint");
12187
12225
  const canvas = canvasRef.current;
12188
12226
  if (!canvas) return;
12189
12227
  const ctx = canvas.getContext("2d");
@@ -12205,6 +12243,7 @@ var init_LearningCanvas = __esm({
12205
12243
  for (const shape of derivedShapes) {
12206
12244
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12207
12245
  }
12246
+ perfEnd("learningcanvas:paint", _perfT);
12208
12247
  }, [width, height, backgroundColor, derivedShapes]);
12209
12248
  useEffect(() => {
12210
12249
  draw();
@@ -30609,6 +30648,7 @@ var init_MathCanvas = __esm({
30609
30648
  "components/learning/molecules/MathCanvas.tsx"() {
30610
30649
  "use client";
30611
30650
  init_useEventBus();
30651
+ init_perf();
30612
30652
  init_atoms();
30613
30653
  init_Stack();
30614
30654
  init_LearningCanvas();
@@ -30674,6 +30714,7 @@ var init_MathCanvas = __esm({
30674
30714
  };
30675
30715
  }, [stableKeyMap, stableKeyUpMap, eventBus]);
30676
30716
  const derivedShapes = useMemo(() => {
30717
+ const _perfT = perfStart("mathcanvas:derive");
30677
30718
  const out = [];
30678
30719
  const margin = 24;
30679
30720
  const plotW = width - margin * 2;
@@ -30917,6 +30958,7 @@ var init_MathCanvas = __esm({
30917
30958
  }
30918
30959
  }
30919
30960
  out.push(...shapes);
30961
+ perfEnd("mathcanvas:derive", _perfT);
30920
30962
  return out;
30921
30963
  }, [
30922
30964
  width,
@@ -32180,12 +32222,12 @@ var init_MapView = __esm({
32180
32222
  shadowSize: [41, 41]
32181
32223
  });
32182
32224
  L.Marker.prototype.options.icon = defaultIcon;
32183
- const { useEffect: useEffect67, useRef: useRef64, useCallback: useCallback97, useState: useState95 } = React87__default;
32225
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState95 } = React87__default;
32184
32226
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32185
32227
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32186
32228
  function MapUpdater({ centerLat, centerLng, zoom }) {
32187
32229
  const map = useMap();
32188
- const prevRef = useRef64({ centerLat, centerLng, zoom });
32230
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
32189
32231
  useEffect67(() => {
32190
32232
  const prev = prevRef.current;
32191
32233
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -37270,7 +37312,7 @@ var init_RichBlockEditor = __esm({
37270
37312
  {
37271
37313
  variant: "bordered",
37272
37314
  padding: "none",
37273
- className: cn("flex flex-col", className),
37315
+ className: cn("flex flex-col text-card-foreground", className),
37274
37316
  children: [
37275
37317
  enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
37276
37318
  Box,
@@ -42498,6 +42540,7 @@ var init_DetailPanel = __esm({
42498
42540
  "use client";
42499
42541
  init_atoms();
42500
42542
  init_Box();
42543
+ init_Input();
42501
42544
  init_Stack();
42502
42545
  init_SimpleGrid();
42503
42546
  init_Menu();
@@ -42513,6 +42556,7 @@ var init_DetailPanel = __esm({
42513
42556
  ReactMarkdown2 = lazy(() => import('react-markdown'));
42514
42557
  DetailPanel = ({
42515
42558
  title: propTitle,
42559
+ onTitleCommit,
42516
42560
  subtitle,
42517
42561
  status,
42518
42562
  avatar,
@@ -42534,6 +42578,7 @@ var init_DetailPanel = __esm({
42534
42578
  }) => {
42535
42579
  const eventBus = useEventBus();
42536
42580
  const { t } = useTranslate();
42581
+ const [titleDraft, setTitleDraft] = React87__default.useState(null);
42537
42582
  const isFieldDefArray = (arr) => {
42538
42583
  if (!arr || arr.length === 0) return false;
42539
42584
  const first = arr[0];
@@ -42754,6 +42799,50 @@ var init_DetailPanel = __esm({
42754
42799
  }),
42755
42800
  status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
42756
42801
  ] });
42802
+ const commitTitle = () => {
42803
+ if (!onTitleCommit) return;
42804
+ const next = (titleDraft ?? "").trim();
42805
+ setTitleDraft(null);
42806
+ if (!next || next === title) return;
42807
+ onTitleCommit(next, normalizedData?.id !== void 0 ? String(normalizedData.id) : "");
42808
+ };
42809
+ const titleNode = onTitleCommit && titleDraft !== null ? /* @__PURE__ */ jsx(
42810
+ Input,
42811
+ {
42812
+ value: titleDraft,
42813
+ autoFocus: true,
42814
+ "aria-label": t("common.title"),
42815
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
42816
+ onChange: (e) => setTitleDraft(e.target.value),
42817
+ onBlur: commitTitle,
42818
+ onKeyDown: (e) => {
42819
+ if (e.key === "Enter") {
42820
+ e.preventDefault();
42821
+ commitTitle();
42822
+ } else if (e.key === "Escape") {
42823
+ e.preventDefault();
42824
+ setTitleDraft(null);
42825
+ }
42826
+ },
42827
+ "data-testid": "detail-title-input"
42828
+ }
42829
+ ) : onTitleCommit ? /* @__PURE__ */ jsx(
42830
+ Box,
42831
+ {
42832
+ role: "button",
42833
+ tabIndex: 0,
42834
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
42835
+ onClick: () => setTitleDraft(title ?? ""),
42836
+ onKeyDown: (e) => {
42837
+ if (e.key === "Enter" || e.key === " ") {
42838
+ e.preventDefault();
42839
+ setTitleDraft(title ?? "");
42840
+ }
42841
+ },
42842
+ "data-testid": "detail-title-editable",
42843
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" })
42844
+ }
42845
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" });
42757
42846
  const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
42758
42847
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
42759
42848
  /* @__PURE__ */ jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
@@ -42773,7 +42862,7 @@ var init_DetailPanel = __esm({
42773
42862
  avatar,
42774
42863
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
42775
42864
  /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
42776
- /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
42865
+ titleNode,
42777
42866
  statusBadges
42778
42867
  ] }),
42779
42868
  subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
@@ -43334,7 +43423,7 @@ var init_Form = __esm({
43334
43423
  });
43335
43424
  debug(
43336
43425
  "forms",
43337
- `Calculation triggered: ${calc.variableName} = ${value}`
43426
+ `Calculation triggered: ${calc.variableName} = ${String(value)}`
43338
43427
  );
43339
43428
  }
43340
43429
  });
@@ -49814,6 +49903,7 @@ function isPlainConfigObject(value) {
49814
49903
  return proto === Object.prototype || proto === null;
49815
49904
  }
49816
49905
  function subtreeHasTraitRef(value) {
49906
+ if (isEvaluatorResolvedData(value)) return false;
49817
49907
  const cached = traitRefPresenceCache.get(value);
49818
49908
  if (cached !== void 0) return cached;
49819
49909
  let found = false;
@@ -49886,6 +49976,10 @@ function renderPatternProps(props, onDismiss, propsSchema) {
49886
49976
  };
49887
49977
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
49888
49978
  } else if (Array.isArray(value)) {
49979
+ if (!isEvaluatorResolvedData(value) && value.some((el) => isPatternConfig(el))) ; else if (!subtreeHasTraitRef(value)) {
49980
+ rendered[key] = value;
49981
+ continue;
49982
+ }
49889
49983
  const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
49890
49984
  rendered[key] = value.map((item, i) => {
49891
49985
  const el = item;
@@ -51327,6 +51421,53 @@ function useNavigationId2() {
51327
51421
 
51328
51422
  // providers/ServerBridge.tsx
51329
51423
  init_useEventBus();
51424
+
51425
+ // lib/tick-send-relay.ts
51426
+ function createTickSendRelay(send) {
51427
+ const lanes = /* @__PURE__ */ new Map();
51428
+ const flush = (key, lane) => {
51429
+ const next = lane.pending;
51430
+ lane.pending = void 0;
51431
+ if (next === void 0) {
51432
+ lane.inFlight = false;
51433
+ return;
51434
+ }
51435
+ lane.inFlight = true;
51436
+ void send(key, next).catch(() => void 0).then(() => flush(key, lane));
51437
+ };
51438
+ return {
51439
+ send(key, value) {
51440
+ const lane = lanes.get(key) ?? { inFlight: false };
51441
+ lanes.set(key, lane);
51442
+ if (lane.inFlight) {
51443
+ lane.pending = value;
51444
+ return;
51445
+ }
51446
+ lane.pending = value;
51447
+ flush(key, lane);
51448
+ },
51449
+ clear() {
51450
+ for (const lane of lanes.values()) {
51451
+ lane.pending = void 0;
51452
+ }
51453
+ }
51454
+ };
51455
+ }
51456
+
51457
+ // lib/command-send-pump.ts
51458
+ function createCommandSendPump() {
51459
+ let tail = Promise.resolve();
51460
+ return {
51461
+ enqueue(job) {
51462
+ const result = tail.then(job);
51463
+ tail = result.then(
51464
+ () => void 0,
51465
+ () => void 0
51466
+ );
51467
+ return result;
51468
+ }
51469
+ };
51470
+ }
51330
51471
  var xOrbitalLog = createLogger("almadar:runtime:cross-orbital");
51331
51472
  var serverBridgeLog = createLogger("almadar:ui:server-bridge");
51332
51473
  function reEmitServerEvent(eventBus, emitted, origin) {
@@ -51461,99 +51602,118 @@ function ServerBridgeProvider({
51461
51602
  async () => transport.unregister(),
51462
51603
  [transport]
51463
51604
  );
51605
+ const tickRelay = useMemo(
51606
+ () => createTickSendRelay(async (_key, snap) => {
51607
+ await transport.sendEvent(snap.orbitalName, snap.event, snap.payload, getTabClientId(), snap.tick, snap.sourceTrait);
51608
+ }),
51609
+ [transport]
51610
+ );
51611
+ useEffect(() => () => tickRelay.clear(), [tickRelay]);
51612
+ const commandPump = useMemo(createCommandSendPump, []);
51613
+ const disposedRef = useRef(false);
51614
+ useEffect(() => {
51615
+ disposedRef.current = false;
51616
+ return () => {
51617
+ disposedRef.current = true;
51618
+ };
51619
+ }, []);
51464
51620
  const sendEvent = useCallback(async (orbitalName, event, payload, tick, sourceTrait) => {
51465
51621
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
51466
51622
  if (!connected) return { effects: [], meta: emptyMeta };
51467
- try {
51468
- const result = await transport.sendEvent(orbitalName, event, payload, getTabClientId(), tick, sourceTrait);
51469
- const effects = [];
51470
- if (tick !== void 0) {
51471
- return { effects, meta: { ...emptyMeta, success: !!result.success, error: result.error } };
51472
- }
51473
- const responseData = result.data || {};
51474
- const dataEntities = {};
51475
- for (const [entityName, records] of Object.entries(responseData)) {
51476
- dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
51477
- }
51478
- const meta = {
51479
- success: !!result.success,
51480
- clientEffects: result.clientEffects?.length ?? 0,
51481
- dataEntities,
51482
- data: responseData,
51483
- emittedEvents: result.emittedEvents?.map((e) => e.event) ?? [],
51484
- error: result.error
51485
- };
51486
- if (result.success) {
51487
- const tagged = result.clientEffectsByTrait;
51488
- const tuples = tagged ? tagged.map((entry) => ({ effect: entry.effect, traitName: entry.traitName })) : (result.clientEffects ?? []).map((eff) => ({ effect: eff }));
51489
- for (const { effect, traitName } of tuples) {
51490
- const effectType = effect[0];
51491
- if (effectType === "render-ui") {
51492
- const slot = effect[1];
51493
- const pattern = effect[2];
51494
- effects.push({
51495
- type: "render-ui",
51496
- slot,
51497
- pattern: pattern !== null && typeof pattern === "object" ? pattern : void 0,
51498
- traitName
51499
- });
51500
- } else if (effectType === "navigate") {
51501
- const route = effect[1];
51502
- const rawParams = effect[2];
51503
- const rawOptions = effect[3];
51504
- const optionsObj = rawOptions !== null && rawOptions !== void 0 && typeof rawOptions === "object" && !Array.isArray(rawOptions) ? rawOptions : void 0;
51505
- const crumb = typeof optionsObj?.crumb === "string" ? optionsObj.crumb : void 0;
51506
- effects.push({
51507
- type: "navigate",
51508
- route,
51509
- params: rawParams !== null && rawParams !== void 0 && typeof rawParams === "object" && !Array.isArray(rawParams) ? rawParams : void 0,
51510
- crumb,
51511
- traitName
51512
- });
51513
- } else if (effectType === "navigate-back") {
51514
- effects.push({ type: "navigate-back", traitName });
51515
- } else if (effectType === "notify") {
51516
- const message = effect[1];
51517
- effects.push({ type: "notify", message: typeof message === "string" ? message : void 0, traitName });
51518
- }
51519
- }
51520
- if (result.emittedEvents) {
51521
- for (const emitted of result.emittedEvents) {
51522
- if (emitted.event === event) continue;
51523
- reEmitServerEvent(
51524
- eventBus,
51525
- { ...emitted, source: { ...emitted.source, dispatched: true } },
51526
- orbitalName
51527
- );
51623
+ if (tick !== void 0) {
51624
+ tickRelay.send(`${orbitalName}:${event}`, { orbitalName, event, payload, tick, sourceTrait });
51625
+ return { effects: [], meta: { ...emptyMeta, success: true } };
51626
+ }
51627
+ return commandPump.enqueue(async () => {
51628
+ if (disposedRef.current) return { effects: [], meta: emptyMeta };
51629
+ try {
51630
+ const result = await transport.sendEvent(orbitalName, event, payload, getTabClientId(), tick, sourceTrait);
51631
+ const effects = [];
51632
+ const responseData = result.data || {};
51633
+ const dataEntities = {};
51634
+ for (const [entityName, records] of Object.entries(responseData)) {
51635
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
51636
+ }
51637
+ const meta = {
51638
+ success: !!result.success,
51639
+ clientEffects: result.clientEffects?.length ?? 0,
51640
+ dataEntities,
51641
+ data: responseData,
51642
+ emittedEvents: result.emittedEvents?.map((e) => e.event) ?? [],
51643
+ error: result.error
51644
+ };
51645
+ if (result.success) {
51646
+ const tagged = result.clientEffectsByTrait;
51647
+ const tuples = tagged ? tagged.map((entry) => ({ effect: entry.effect, traitName: entry.traitName })) : (result.clientEffects ?? []).map((eff) => ({ effect: eff }));
51648
+ for (const { effect, traitName } of tuples) {
51649
+ const effectType = effect[0];
51650
+ if (effectType === "render-ui") {
51651
+ const slot = effect[1];
51652
+ const pattern = effect[2];
51653
+ effects.push({
51654
+ type: "render-ui",
51655
+ slot,
51656
+ pattern: pattern !== null && typeof pattern === "object" ? pattern : void 0,
51657
+ traitName
51658
+ });
51659
+ } else if (effectType === "navigate") {
51660
+ const route = effect[1];
51661
+ const rawParams = effect[2];
51662
+ const rawOptions = effect[3];
51663
+ const optionsObj = rawOptions !== null && rawOptions !== void 0 && typeof rawOptions === "object" && !Array.isArray(rawOptions) ? rawOptions : void 0;
51664
+ const crumb = typeof optionsObj?.crumb === "string" ? optionsObj.crumb : void 0;
51665
+ effects.push({
51666
+ type: "navigate",
51667
+ route,
51668
+ params: rawParams !== null && rawParams !== void 0 && typeof rawParams === "object" && !Array.isArray(rawParams) ? rawParams : void 0,
51669
+ crumb,
51670
+ traitName
51671
+ });
51672
+ } else if (effectType === "navigate-back") {
51673
+ effects.push({ type: "navigate-back", traitName });
51674
+ } else if (effectType === "notify") {
51675
+ const message = effect[1];
51676
+ effects.push({ type: "notify", message: typeof message === "string" ? message : void 0, traitName });
51677
+ }
51678
+ }
51679
+ if (result.emittedEvents) {
51680
+ for (const emitted of result.emittedEvents) {
51681
+ if (emitted.event === event) continue;
51682
+ reEmitServerEvent(
51683
+ eventBus,
51684
+ { ...emitted, source: { ...emitted.source, dispatched: true } },
51685
+ orbitalName
51686
+ );
51687
+ }
51528
51688
  }
51689
+ } else if (result.error) {
51690
+ xOrbitalLog.warn("response:fail", {
51691
+ orbital: orbitalName,
51692
+ event,
51693
+ error: result.error
51694
+ });
51529
51695
  }
51530
- } else if (result.error) {
51531
- xOrbitalLog.warn("response:fail", {
51532
- orbital: orbitalName,
51533
- event,
51534
- error: result.error
51535
- });
51536
- }
51537
- return { effects, meta };
51538
- } catch (err) {
51539
- const msg = err instanceof Error ? err.message : String(err);
51540
- if (err instanceof TypeError) {
51541
- xOrbitalLog.warn("response:network", {
51542
- orbital: orbitalName,
51543
- event,
51544
- error: msg,
51545
- reason: "peer endpoint unreachable (expected in standalone single-orbital mode)"
51546
- });
51547
- } else {
51548
- xOrbitalLog.error("response:network", {
51549
- orbital: orbitalName,
51550
- event,
51551
- error: msg
51552
- });
51696
+ return { effects, meta };
51697
+ } catch (err) {
51698
+ const msg = err instanceof Error ? err.message : String(err);
51699
+ if (err instanceof TypeError) {
51700
+ xOrbitalLog.warn("response:network", {
51701
+ orbital: orbitalName,
51702
+ event,
51703
+ error: msg,
51704
+ reason: "peer endpoint unreachable (expected in standalone single-orbital mode)"
51705
+ });
51706
+ } else {
51707
+ xOrbitalLog.error("response:network", {
51708
+ orbital: orbitalName,
51709
+ event,
51710
+ error: msg
51711
+ });
51712
+ }
51713
+ return { effects: [], meta: { ...emptyMeta, error: msg } };
51553
51714
  }
51554
- return { effects: [], meta: { ...emptyMeta, error: msg } };
51555
- }
51556
- }, [connected, transport, eventBus]);
51715
+ });
51716
+ }, [connected, transport, eventBus, tickRelay, commandPump]);
51557
51717
  useEffect(() => {
51558
51718
  if (!schema) return;
51559
51719
  let cancelled = false;