@almadar/ui 5.158.0 → 5.160.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.
@@ -3,7 +3,7 @@ import { EntityRow, SExpr } from '@almadar/core';
3
3
  export { ANONYMOUS_USER } from '@almadar/core';
4
4
  import { U as UIThemeDefinition, j as UserData } from '../UserContext-g_LcDiGN.js';
5
5
  export { a as CurrentPagePathContext, b as CurrentPagePathProvider, c as CurrentPagePathProviderProps, d as DesignThemeProvider, O as OrbitalThemeProvider, e as OrbitalThemeProviderProps, h as UserContext, i as UserContextValue, k as UserProvider, l as UserProviderProps, u as useCurrentPagePath, m as useDesignTheme, n as useHasPermission, o as useHasRole, q as useUser, r as useUserForEvaluation } from '../UserContext-g_LcDiGN.js';
6
- export { E as EntityBindingContext, a as EntityBindingSource, b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, S as SendEventResult, e as ServerBridgeContextValue, f as ServerBridgeProvider, g as ServerBridgeProviderProps, h as ServerBridgeTransport, i as ServerClientEffect, j as ServerResponseMeta, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, u as useEntityBindingSnapshot, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Bn3ePJQC.js';
6
+ export { o as EntityBindingContext, E as EntityBindingSource, a as EntitySchemaContextValue, b as EntitySchemaProvider, c as EntitySchemaProviderProps, p as SendEventResult, d as ServerBridgeContextValue, e as ServerBridgeProvider, q as ServerBridgeProviderProps, S as ServerBridgeTransport, f as ServerClientEffect, r as ServerResponseMeta, T as TraitContext, g as TraitContextValue, h as TraitInstance, i as TraitProvider, j as TraitProviderProps, s as useEntityBindingSnapshot, u as useEntitySchema, k as useEntitySchemaOptional, l as useServerBridge, m as useTrait, n as useTraitContext } from '../EntityBindingContext-0Evn_LcT.js';
7
7
  import { j as UseOfflineExecutorResult, U as UseOfflineExecutorOptions } from '../offline-executor-QUdKOj7f.js';
8
8
  export { c as NavigationContextValue, d as NavigationProvider, e as NavigationProviderProps, f as NavigationState, k as comparePathSpecificity, m as extractRouteParams, n as findPageByName, o as findPageByPath, p as getAllPages, q as getDefaultPage, r as matchPath, s as matchPathAmong, t as pathMatches, u as useActivePage, v as useInitPayload, w as useNavigateTo, x as useNavigation, y as useNavigationId, z as useNavigationState } from '../offline-executor-QUdKOj7f.js';
9
9
  import { E as EventBusContextType } from '../event-bus-types-Bl78kokd.js';
@@ -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();
@@ -26501,8 +26540,8 @@ function DataGrid({
26501
26540
  }
26502
26541
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26503
26542
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
26504
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26505
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
26543
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26544
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
26506
26545
  ] }, field.name);
26507
26546
  }) })
26508
26547
  ] }) })
@@ -26930,7 +26969,11 @@ function DataList({
26930
26969
  Box,
26931
26970
  {
26932
26971
  className: cn(
26933
- "group flex items-center gap-4 transition-all duration-fast",
26972
+ // items-start, not items-center: a multi-line row (title + meta +
26973
+ // progress) centred its action cluster in the vertical middle, so
26974
+ // the buttons floated between the meta fields instead of anchoring
26975
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
26976
+ "group flex items-start gap-4 transition-all duration-fast",
26934
26977
  isCompact ? "px-4 py-2" : "px-6 py-4",
26935
26978
  "hover:bg-muted/80",
26936
26979
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -26961,11 +27004,19 @@ function DataList({
26961
27004
  if (value === void 0 || value === null || value === "") return null;
26962
27005
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26963
27006
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
26964
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
26965
- field.label ?? fieldLabel3(field.name),
26966
- ":"
26967
- ] }),
26968
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
27007
+ /* @__PURE__ */ jsxs(
27008
+ Typography,
27009
+ {
27010
+ variant: "caption",
27011
+ color: "secondary",
27012
+ className: cn(field.format !== "boolean" && "sr-only"),
27013
+ children: [
27014
+ field.label ?? fieldLabel3(field.name),
27015
+ ":"
27016
+ ]
27017
+ }
27018
+ ),
27019
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26969
27020
  ] }, field.name);
26970
27021
  }) }),
26971
27022
  progressFields.map((field) => {
@@ -26984,7 +27035,7 @@ function DataList({
26984
27035
  ]
26985
27036
  }
26986
27037
  ),
26987
- isCard && !isLast && /* @__PURE__ */ jsx(Box, { className: "mx-6 border-b border-border/40" })
27038
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
26988
27039
  ] }, id)
26989
27040
  );
26990
27041
  };
@@ -26994,7 +27045,13 @@ function DataList({
26994
27045
  {
26995
27046
  className: cn(
26996
27047
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
26997
- !isCard && gapClass,
27048
+ // `gap-*` is inert on a block container, and Box only emits a display
27049
+ // class when its `display` prop is set — so every non-card list had
27050
+ // been asking for a gap that CSS silently dropped. flex-col makes it
27051
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
27052
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
27053
+ !isCard && "flex flex-col",
27054
+ !isCard && !isCompact && gapClass,
26998
27055
  listLookStyles[look],
26999
27056
  className
27000
27057
  ),
@@ -30609,6 +30666,7 @@ var init_MathCanvas = __esm({
30609
30666
  "components/learning/molecules/MathCanvas.tsx"() {
30610
30667
  "use client";
30611
30668
  init_useEventBus();
30669
+ init_perf();
30612
30670
  init_atoms();
30613
30671
  init_Stack();
30614
30672
  init_LearningCanvas();
@@ -30674,6 +30732,7 @@ var init_MathCanvas = __esm({
30674
30732
  };
30675
30733
  }, [stableKeyMap, stableKeyUpMap, eventBus]);
30676
30734
  const derivedShapes = useMemo(() => {
30735
+ const _perfT = perfStart("mathcanvas:derive");
30677
30736
  const out = [];
30678
30737
  const margin = 24;
30679
30738
  const plotW = width - margin * 2;
@@ -30917,6 +30976,7 @@ var init_MathCanvas = __esm({
30917
30976
  }
30918
30977
  }
30919
30978
  out.push(...shapes);
30979
+ perfEnd("mathcanvas:derive", _perfT);
30920
30980
  return out;
30921
30981
  }, [
30922
30982
  width,
@@ -32180,12 +32240,12 @@ var init_MapView = __esm({
32180
32240
  shadowSize: [41, 41]
32181
32241
  });
32182
32242
  L.Marker.prototype.options.icon = defaultIcon;
32183
- const { useEffect: useEffect67, useRef: useRef64, useCallback: useCallback97, useState: useState95 } = React87__default;
32243
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState95 } = React87__default;
32184
32244
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32185
32245
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32186
32246
  function MapUpdater({ centerLat, centerLng, zoom }) {
32187
32247
  const map = useMap();
32188
- const prevRef = useRef64({ centerLat, centerLng, zoom });
32248
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
32189
32249
  useEffect67(() => {
32190
32250
  const prev = prevRef.current;
32191
32251
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -37270,7 +37330,7 @@ var init_RichBlockEditor = __esm({
37270
37330
  {
37271
37331
  variant: "bordered",
37272
37332
  padding: "none",
37273
- className: cn("flex flex-col", className),
37333
+ className: cn("flex flex-col text-card-foreground", className),
37274
37334
  children: [
37275
37335
  enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
37276
37336
  Box,
@@ -42498,6 +42558,7 @@ var init_DetailPanel = __esm({
42498
42558
  "use client";
42499
42559
  init_atoms();
42500
42560
  init_Box();
42561
+ init_Input();
42501
42562
  init_Stack();
42502
42563
  init_SimpleGrid();
42503
42564
  init_Menu();
@@ -42513,6 +42574,7 @@ var init_DetailPanel = __esm({
42513
42574
  ReactMarkdown2 = lazy(() => import('react-markdown'));
42514
42575
  DetailPanel = ({
42515
42576
  title: propTitle,
42577
+ onTitleCommit,
42516
42578
  subtitle,
42517
42579
  status,
42518
42580
  avatar,
@@ -42534,6 +42596,7 @@ var init_DetailPanel = __esm({
42534
42596
  }) => {
42535
42597
  const eventBus = useEventBus();
42536
42598
  const { t } = useTranslate();
42599
+ const [titleDraft, setTitleDraft] = React87__default.useState(null);
42537
42600
  const isFieldDefArray = (arr) => {
42538
42601
  if (!arr || arr.length === 0) return false;
42539
42602
  const first = arr[0];
@@ -42754,6 +42817,50 @@ var init_DetailPanel = __esm({
42754
42817
  }),
42755
42818
  status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
42756
42819
  ] });
42820
+ const commitTitle = () => {
42821
+ if (!onTitleCommit) return;
42822
+ const next = (titleDraft ?? "").trim();
42823
+ setTitleDraft(null);
42824
+ if (!next || next === title) return;
42825
+ onTitleCommit(next, normalizedData?.id !== void 0 ? String(normalizedData.id) : "");
42826
+ };
42827
+ const titleNode = onTitleCommit && titleDraft !== null ? /* @__PURE__ */ jsx(
42828
+ Input,
42829
+ {
42830
+ value: titleDraft,
42831
+ autoFocus: true,
42832
+ "aria-label": t("common.title"),
42833
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
42834
+ onChange: (e) => setTitleDraft(e.target.value),
42835
+ onBlur: commitTitle,
42836
+ onKeyDown: (e) => {
42837
+ if (e.key === "Enter") {
42838
+ e.preventDefault();
42839
+ commitTitle();
42840
+ } else if (e.key === "Escape") {
42841
+ e.preventDefault();
42842
+ setTitleDraft(null);
42843
+ }
42844
+ },
42845
+ "data-testid": "detail-title-input"
42846
+ }
42847
+ ) : onTitleCommit ? /* @__PURE__ */ jsx(
42848
+ Box,
42849
+ {
42850
+ role: "button",
42851
+ tabIndex: 0,
42852
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
42853
+ onClick: () => setTitleDraft(title ?? ""),
42854
+ onKeyDown: (e) => {
42855
+ if (e.key === "Enter" || e.key === " ") {
42856
+ e.preventDefault();
42857
+ setTitleDraft(title ?? "");
42858
+ }
42859
+ },
42860
+ "data-testid": "detail-title-editable",
42861
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" })
42862
+ }
42863
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" });
42757
42864
  const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
42758
42865
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
42759
42866
  /* @__PURE__ */ jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
@@ -42773,7 +42880,7 @@ var init_DetailPanel = __esm({
42773
42880
  avatar,
42774
42881
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
42775
42882
  /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
42776
- /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
42883
+ titleNode,
42777
42884
  statusBadges
42778
42885
  ] }),
42779
42886
  subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
@@ -42873,16 +42980,11 @@ var init_DetailPanel = __esm({
42873
42980
  footer
42874
42981
  ] })
42875
42982
  ] }) });
42876
- return /* @__PURE__ */ jsx(
42877
- Box,
42878
- {
42879
- className: cn(
42880
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
42881
- className
42882
- ),
42883
- children: content
42884
- }
42885
- );
42983
+ if (!slideOver) {
42984
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
42985
+ }
42986
+ const panel = /* @__PURE__ */ jsx(Box, { className: cn("fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6", className), children: content });
42987
+ return typeof document === "undefined" ? panel : createPortal(panel, document.body);
42886
42988
  };
42887
42989
  DetailPanel.displayName = "DetailPanel";
42888
42990
  }
@@ -43334,7 +43436,7 @@ var init_Form = __esm({
43334
43436
  });
43335
43437
  debug(
43336
43438
  "forms",
43337
- `Calculation triggered: ${calc.variableName} = ${value}`
43439
+ `Calculation triggered: ${calc.variableName} = ${String(value)}`
43338
43440
  );
43339
43441
  }
43340
43442
  });
@@ -49814,6 +49916,7 @@ function isPlainConfigObject(value) {
49814
49916
  return proto === Object.prototype || proto === null;
49815
49917
  }
49816
49918
  function subtreeHasTraitRef(value) {
49919
+ if (isEvaluatorResolvedData(value)) return false;
49817
49920
  const cached = traitRefPresenceCache.get(value);
49818
49921
  if (cached !== void 0) return cached;
49819
49922
  let found = false;
@@ -49886,6 +49989,10 @@ function renderPatternProps(props, onDismiss, propsSchema) {
49886
49989
  };
49887
49990
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
49888
49991
  } else if (Array.isArray(value)) {
49992
+ if (!isEvaluatorResolvedData(value) && value.some((el) => isPatternConfig(el))) ; else if (!subtreeHasTraitRef(value)) {
49993
+ rendered[key] = value;
49994
+ continue;
49995
+ }
49889
49996
  const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
49890
49997
  rendered[key] = value.map((item, i) => {
49891
49998
  const el = item;
@@ -51327,6 +51434,53 @@ function useNavigationId2() {
51327
51434
 
51328
51435
  // providers/ServerBridge.tsx
51329
51436
  init_useEventBus();
51437
+
51438
+ // lib/tick-send-relay.ts
51439
+ function createTickSendRelay(send) {
51440
+ const lanes = /* @__PURE__ */ new Map();
51441
+ const flush = (key, lane) => {
51442
+ const next = lane.pending;
51443
+ lane.pending = void 0;
51444
+ if (next === void 0) {
51445
+ lane.inFlight = false;
51446
+ return;
51447
+ }
51448
+ lane.inFlight = true;
51449
+ void send(key, next).catch(() => void 0).then(() => flush(key, lane));
51450
+ };
51451
+ return {
51452
+ send(key, value) {
51453
+ const lane = lanes.get(key) ?? { inFlight: false };
51454
+ lanes.set(key, lane);
51455
+ if (lane.inFlight) {
51456
+ lane.pending = value;
51457
+ return;
51458
+ }
51459
+ lane.pending = value;
51460
+ flush(key, lane);
51461
+ },
51462
+ clear() {
51463
+ for (const lane of lanes.values()) {
51464
+ lane.pending = void 0;
51465
+ }
51466
+ }
51467
+ };
51468
+ }
51469
+
51470
+ // lib/command-send-pump.ts
51471
+ function createCommandSendPump() {
51472
+ let tail = Promise.resolve();
51473
+ return {
51474
+ enqueue(job) {
51475
+ const result = tail.then(job);
51476
+ tail = result.then(
51477
+ () => void 0,
51478
+ () => void 0
51479
+ );
51480
+ return result;
51481
+ }
51482
+ };
51483
+ }
51330
51484
  var xOrbitalLog = createLogger("almadar:runtime:cross-orbital");
51331
51485
  var serverBridgeLog = createLogger("almadar:ui:server-bridge");
51332
51486
  function reEmitServerEvent(eventBus, emitted, origin) {
@@ -51461,99 +51615,118 @@ function ServerBridgeProvider({
51461
51615
  async () => transport.unregister(),
51462
51616
  [transport]
51463
51617
  );
51618
+ const tickRelay = useMemo(
51619
+ () => createTickSendRelay(async (_key, snap) => {
51620
+ await transport.sendEvent(snap.orbitalName, snap.event, snap.payload, getTabClientId(), snap.tick, snap.sourceTrait);
51621
+ }),
51622
+ [transport]
51623
+ );
51624
+ useEffect(() => () => tickRelay.clear(), [tickRelay]);
51625
+ const commandPump = useMemo(createCommandSendPump, []);
51626
+ const disposedRef = useRef(false);
51627
+ useEffect(() => {
51628
+ disposedRef.current = false;
51629
+ return () => {
51630
+ disposedRef.current = true;
51631
+ };
51632
+ }, []);
51464
51633
  const sendEvent = useCallback(async (orbitalName, event, payload, tick, sourceTrait) => {
51465
51634
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
51466
51635
  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
- );
51636
+ if (tick !== void 0) {
51637
+ tickRelay.send(`${orbitalName}:${event}`, { orbitalName, event, payload, tick, sourceTrait });
51638
+ return { effects: [], meta: { ...emptyMeta, success: true } };
51639
+ }
51640
+ return commandPump.enqueue(async () => {
51641
+ if (disposedRef.current) return { effects: [], meta: emptyMeta };
51642
+ try {
51643
+ const result = await transport.sendEvent(orbitalName, event, payload, getTabClientId(), tick, sourceTrait);
51644
+ const effects = [];
51645
+ const responseData = result.data || {};
51646
+ const dataEntities = {};
51647
+ for (const [entityName, records] of Object.entries(responseData)) {
51648
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
51649
+ }
51650
+ const meta = {
51651
+ success: !!result.success,
51652
+ clientEffects: result.clientEffects?.length ?? 0,
51653
+ dataEntities,
51654
+ data: responseData,
51655
+ emittedEvents: result.emittedEvents?.map((e) => e.event) ?? [],
51656
+ error: result.error
51657
+ };
51658
+ if (result.success) {
51659
+ const tagged = result.clientEffectsByTrait;
51660
+ const tuples = tagged ? tagged.map((entry) => ({ effect: entry.effect, traitName: entry.traitName })) : (result.clientEffects ?? []).map((eff) => ({ effect: eff }));
51661
+ for (const { effect, traitName } of tuples) {
51662
+ const effectType = effect[0];
51663
+ if (effectType === "render-ui") {
51664
+ const slot = effect[1];
51665
+ const pattern = effect[2];
51666
+ effects.push({
51667
+ type: "render-ui",
51668
+ slot,
51669
+ pattern: pattern !== null && typeof pattern === "object" ? pattern : void 0,
51670
+ traitName
51671
+ });
51672
+ } else if (effectType === "navigate") {
51673
+ const route = effect[1];
51674
+ const rawParams = effect[2];
51675
+ const rawOptions = effect[3];
51676
+ const optionsObj = rawOptions !== null && rawOptions !== void 0 && typeof rawOptions === "object" && !Array.isArray(rawOptions) ? rawOptions : void 0;
51677
+ const crumb = typeof optionsObj?.crumb === "string" ? optionsObj.crumb : void 0;
51678
+ effects.push({
51679
+ type: "navigate",
51680
+ route,
51681
+ params: rawParams !== null && rawParams !== void 0 && typeof rawParams === "object" && !Array.isArray(rawParams) ? rawParams : void 0,
51682
+ crumb,
51683
+ traitName
51684
+ });
51685
+ } else if (effectType === "navigate-back") {
51686
+ effects.push({ type: "navigate-back", traitName });
51687
+ } else if (effectType === "notify") {
51688
+ const message = effect[1];
51689
+ effects.push({ type: "notify", message: typeof message === "string" ? message : void 0, traitName });
51690
+ }
51691
+ }
51692
+ if (result.emittedEvents) {
51693
+ for (const emitted of result.emittedEvents) {
51694
+ if (emitted.event === event) continue;
51695
+ reEmitServerEvent(
51696
+ eventBus,
51697
+ { ...emitted, source: { ...emitted.source, dispatched: true } },
51698
+ orbitalName
51699
+ );
51700
+ }
51528
51701
  }
51702
+ } else if (result.error) {
51703
+ xOrbitalLog.warn("response:fail", {
51704
+ orbital: orbitalName,
51705
+ event,
51706
+ error: result.error
51707
+ });
51529
51708
  }
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
- });
51709
+ return { effects, meta };
51710
+ } catch (err) {
51711
+ const msg = err instanceof Error ? err.message : String(err);
51712
+ if (err instanceof TypeError) {
51713
+ xOrbitalLog.warn("response:network", {
51714
+ orbital: orbitalName,
51715
+ event,
51716
+ error: msg,
51717
+ reason: "peer endpoint unreachable (expected in standalone single-orbital mode)"
51718
+ });
51719
+ } else {
51720
+ xOrbitalLog.error("response:network", {
51721
+ orbital: orbitalName,
51722
+ event,
51723
+ error: msg
51724
+ });
51725
+ }
51726
+ return { effects: [], meta: { ...emptyMeta, error: msg } };
51553
51727
  }
51554
- return { effects: [], meta: { ...emptyMeta, error: msg } };
51555
- }
51556
- }, [connected, transport, eventBus]);
51728
+ });
51729
+ }, [connected, transport, eventBus, tickRelay, commandPump]);
51557
51730
  useEffect(() => {
51558
51731
  if (!schema) return;
51559
51732
  let cancelled = false;