@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,6 +3,7 @@
3
3
  var React85 = require('react');
4
4
  var providers = require('@almadar/ui/providers');
5
5
  var logger = require('@almadar/logger');
6
+ var ui = require('@almadar/runtime/ui');
6
7
  var runtime = require('@almadar/runtime');
7
8
  var core = require('@almadar/core');
8
9
  var clsx = require('clsx');
@@ -13,7 +14,6 @@ var reactDom = require('react-dom');
13
14
  var hooks = require('@almadar/ui/hooks');
14
15
  var context = require('@almadar/ui/context');
15
16
  var evaluator = require('@almadar/evaluator');
16
- var ui = require('@almadar/runtime/ui');
17
17
  var reactRouterDom = require('react-router-dom');
18
18
  var ELK = require('elkjs/lib/elk.bundled.js');
19
19
  var SyntaxHighlighter = require('react-syntax-highlighter/dist/esm/prism-light.js');
@@ -867,6 +867,22 @@ var init_useTapReveal = __esm({
867
867
  "hooks/useTapReveal.ts"() {
868
868
  }
869
869
  });
870
+ function usePerfBuffer() {
871
+ return React85.useSyncExternalStore(ui.perfStore.subscribe, ui.perfStore.getSnapshot, ui.perfStore.getSnapshot);
872
+ }
873
+ exports.profilerOnRender = void 0;
874
+ var init_perf = __esm({
875
+ "lib/perf.ts"() {
876
+ exports.profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
877
+ ui.pushPerfEntry({
878
+ name: `profiler:${id}:${phase}`,
879
+ durationMs: actualDuration,
880
+ ts: commitTime,
881
+ detail: { baseDuration }
882
+ });
883
+ };
884
+ }
885
+ });
870
886
  function resolveMarkerExpression(expression, entity, config, state) {
871
887
  const ctx = runtime.createContextFromBindings({
872
888
  entity,
@@ -884,7 +900,16 @@ function isPlainObject(value) {
884
900
  if (typeof value === "function") return false;
885
901
  return true;
886
902
  }
903
+ function isEvaluatorResolvedData(value) {
904
+ return evaluatorResolvedData.has(value);
905
+ }
906
+ function brandResolved(value) {
907
+ if (value !== null && typeof value === "object" && !React85__namespace.default.isValidElement(value) && !(value instanceof Date)) {
908
+ resolvedMarkerFree.add(value);
909
+ }
910
+ }
887
911
  function subtreeHasMarker(value) {
912
+ if (resolvedMarkerFree.has(value)) return false;
888
913
  const cached = markerPresenceCache.get(value);
889
914
  if (cached !== void 0) return cached;
890
915
  let found = false;
@@ -906,10 +931,23 @@ function subtreeHasMarker(value) {
906
931
  }
907
932
  function walkValue(value, scopeTrait, entity, config, state) {
908
933
  if (core.isRenderBindingMarker(value)) {
909
- return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
934
+ const cached = markerResolutionCache.get(value);
935
+ if (cached !== void 0 && cached.entity === entity && cached.config === config && cached.state === state) {
936
+ return { resolved: cached.resolved, changed: false };
937
+ }
938
+ const resolved = resolveMarkerExpression(value.expression, entity, config, state);
939
+ markerResolutionCache.set(value, { entity, config, state, resolved });
940
+ if (resolved !== null && typeof resolved === "object" && !React85__namespace.default.isValidElement(resolved) && !(resolved instanceof Date)) {
941
+ resolvedMarkerFree.add(resolved);
942
+ evaluatorResolvedData.add(resolved);
943
+ }
944
+ return { resolved, changed: true };
910
945
  }
911
946
  if (Array.isArray(value)) {
912
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
947
+ if (!subtreeHasMarker(value)) {
948
+ brandResolved(value);
949
+ return { resolved: value, changed: false };
950
+ }
913
951
  const out = [];
914
952
  let changed = false;
915
953
  for (const item of value) {
@@ -924,10 +962,14 @@ function walkValue(value, scopeTrait, entity, config, state) {
924
962
  out.push(resolved);
925
963
  if (itemChanged) changed = true;
926
964
  }
965
+ brandResolved(out);
927
966
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
928
967
  }
929
968
  if (isPlainObject(value)) {
930
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
969
+ if (!subtreeHasMarker(value)) {
970
+ brandResolved(value);
971
+ return { resolved: value, changed: false };
972
+ }
931
973
  const sourceTrait = value._sourceTrait;
932
974
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
933
975
  return { resolved: value, changed: false };
@@ -939,11 +981,13 @@ function walkValue(value, scopeTrait, entity, config, state) {
939
981
  out[key] = resolved;
940
982
  if (itemChanged) changed = true;
941
983
  }
984
+ brandResolved(out);
942
985
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
943
986
  }
944
987
  return { resolved: value, changed: false };
945
988
  }
946
989
  function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
990
+ if (resolvedMarkerFree.has(props)) return props;
947
991
  const out = {};
948
992
  let changed = false;
949
993
  for (const [key, value] of Object.entries(props)) {
@@ -951,13 +995,17 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
951
995
  out[key] = resolved;
952
996
  if (propChanged) changed = true;
953
997
  }
998
+ brandResolved(out);
954
999
  return changed ? out : props;
955
1000
  }
956
- var markerPresenceCache;
1001
+ var markerPresenceCache, markerResolutionCache, resolvedMarkerFree, evaluatorResolvedData;
957
1002
  var init_resolve_render_bindings = __esm({
958
1003
  "lib/resolve-render-bindings.ts"() {
959
1004
  "use client";
960
1005
  markerPresenceCache = /* @__PURE__ */ new WeakMap();
1006
+ markerResolutionCache = /* @__PURE__ */ new WeakMap();
1007
+ resolvedMarkerFree = /* @__PURE__ */ new WeakSet();
1008
+ evaluatorResolvedData = /* @__PURE__ */ new WeakSet();
961
1009
  }
962
1010
  });
963
1011
  function cn(...inputs) {
@@ -12282,6 +12330,7 @@ var init_LearningCanvas = __esm({
12282
12330
  "components/learning/atoms/LearningCanvas.tsx"() {
12283
12331
  "use client";
12284
12332
  init_cn();
12333
+ init_perf();
12285
12334
  init_useEventBus();
12286
12335
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12287
12336
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
@@ -12325,6 +12374,7 @@ var init_LearningCanvas = __esm({
12325
12374
  return [...shapes, ...traceOut, ...readoutOut];
12326
12375
  }, [shapes, traces, readouts, width, height]);
12327
12376
  const draw = React85.useCallback(() => {
12377
+ const _perfT = ui.perfStart("learningcanvas:paint");
12328
12378
  const canvas = canvasRef.current;
12329
12379
  if (!canvas) return;
12330
12380
  const ctx = canvas.getContext("2d");
@@ -12346,6 +12396,7 @@ var init_LearningCanvas = __esm({
12346
12396
  for (const shape of derivedShapes) {
12347
12397
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12348
12398
  }
12399
+ ui.perfEnd("learningcanvas:paint", _perfT);
12349
12400
  }, [width, height, backgroundColor, derivedShapes]);
12350
12401
  React85.useEffect(() => {
12351
12402
  draw();
@@ -26128,8 +26179,8 @@ function DataGrid({
26128
26179
  }
26129
26180
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
26130
26181
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
26131
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26132
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
26182
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26183
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
26133
26184
  ] }, field.name);
26134
26185
  }) })
26135
26186
  ] }) })
@@ -26557,7 +26608,11 @@ function DataList({
26557
26608
  Box,
26558
26609
  {
26559
26610
  className: cn(
26560
- "group flex items-center gap-4 transition-all duration-fast",
26611
+ // items-start, not items-center: a multi-line row (title + meta +
26612
+ // progress) centred its action cluster in the vertical middle, so
26613
+ // the buttons floated between the meta fields instead of anchoring
26614
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
26615
+ "group flex items-start gap-4 transition-all duration-fast",
26561
26616
  isCompact ? "px-4 py-2" : "px-6 py-4",
26562
26617
  "hover:bg-muted/80",
26563
26618
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -26588,11 +26643,19 @@ function DataList({
26588
26643
  if (value === void 0 || value === null || value === "") return null;
26589
26644
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
26590
26645
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
26591
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", color: "secondary", children: [
26592
- field.label ?? fieldLabel3(field.name),
26593
- ":"
26594
- ] }),
26595
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26646
+ /* @__PURE__ */ jsxRuntime.jsxs(
26647
+ Typography,
26648
+ {
26649
+ variant: "caption",
26650
+ color: "secondary",
26651
+ className: cn(field.format !== "boolean" && "sr-only"),
26652
+ children: [
26653
+ field.label ?? fieldLabel3(field.name),
26654
+ ":"
26655
+ ]
26656
+ }
26657
+ ),
26658
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26596
26659
  ] }, field.name);
26597
26660
  }) }),
26598
26661
  progressFields.map((field) => {
@@ -26611,7 +26674,7 @@ function DataList({
26611
26674
  ]
26612
26675
  }
26613
26676
  ),
26614
- isCard && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-6 border-b border-border/40" })
26677
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
26615
26678
  ] }, id)
26616
26679
  );
26617
26680
  };
@@ -26621,7 +26684,13 @@ function DataList({
26621
26684
  {
26622
26685
  className: cn(
26623
26686
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
26624
- !isCard && gapClass,
26687
+ // `gap-*` is inert on a block container, and Box only emits a display
26688
+ // class when its `display` prop is set — so every non-card list had
26689
+ // been asking for a gap that CSS silently dropped. flex-col makes it
26690
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
26691
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
26692
+ !isCard && "flex flex-col",
26693
+ !isCard && !isCompact && gapClass,
26625
26694
  listLookStyles[look],
26626
26695
  className
26627
26696
  ),
@@ -30172,6 +30241,7 @@ var init_MathCanvas = __esm({
30172
30241
  "components/learning/molecules/MathCanvas.tsx"() {
30173
30242
  "use client";
30174
30243
  init_useEventBus();
30244
+ init_perf();
30175
30245
  init_atoms();
30176
30246
  init_Stack();
30177
30247
  init_LearningCanvas();
@@ -30237,6 +30307,7 @@ var init_MathCanvas = __esm({
30237
30307
  };
30238
30308
  }, [stableKeyMap, stableKeyUpMap, eventBus]);
30239
30309
  const derivedShapes = React85.useMemo(() => {
30310
+ const _perfT = ui.perfStart("mathcanvas:derive");
30240
30311
  const out = [];
30241
30312
  const margin = 24;
30242
30313
  const plotW = width - margin * 2;
@@ -30480,6 +30551,7 @@ var init_MathCanvas = __esm({
30480
30551
  }
30481
30552
  }
30482
30553
  out.push(...shapes);
30554
+ ui.perfEnd("mathcanvas:derive", _perfT);
30483
30555
  return out;
30484
30556
  }, [
30485
30557
  width,
@@ -36695,7 +36767,7 @@ var init_RichBlockEditor = __esm({
36695
36767
  {
36696
36768
  variant: "bordered",
36697
36769
  padding: "none",
36698
- className: cn("flex flex-col", className),
36770
+ className: cn("flex flex-col text-card-foreground", className),
36699
36771
  children: [
36700
36772
  enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
36701
36773
  Box,
@@ -41923,6 +41995,7 @@ var init_DetailPanel = __esm({
41923
41995
  "use client";
41924
41996
  init_atoms();
41925
41997
  init_Box();
41998
+ init_Input();
41926
41999
  init_Stack();
41927
42000
  init_SimpleGrid();
41928
42001
  init_Menu();
@@ -41938,6 +42011,7 @@ var init_DetailPanel = __esm({
41938
42011
  ReactMarkdown2 = React85.lazy(() => import('react-markdown'));
41939
42012
  DetailPanel = ({
41940
42013
  title: propTitle,
42014
+ onTitleCommit,
41941
42015
  subtitle,
41942
42016
  status,
41943
42017
  avatar,
@@ -41959,6 +42033,7 @@ var init_DetailPanel = __esm({
41959
42033
  }) => {
41960
42034
  const eventBus = useEventBus();
41961
42035
  const { t } = hooks.useTranslate();
42036
+ const [titleDraft, setTitleDraft] = React85__namespace.default.useState(null);
41962
42037
  const isFieldDefArray = (arr) => {
41963
42038
  if (!arr || arr.length === 0) return false;
41964
42039
  const first = arr[0];
@@ -42179,6 +42254,50 @@ var init_DetailPanel = __esm({
42179
42254
  }),
42180
42255
  status && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: status.variant ?? "default", children: status.label })
42181
42256
  ] });
42257
+ const commitTitle = () => {
42258
+ if (!onTitleCommit) return;
42259
+ const next = (titleDraft ?? "").trim();
42260
+ setTitleDraft(null);
42261
+ if (!next || next === title) return;
42262
+ onTitleCommit(next, normalizedData?.id !== void 0 ? String(normalizedData.id) : "");
42263
+ };
42264
+ const titleNode = onTitleCommit && titleDraft !== null ? /* @__PURE__ */ jsxRuntime.jsx(
42265
+ Input,
42266
+ {
42267
+ value: titleDraft,
42268
+ autoFocus: true,
42269
+ "aria-label": t("common.title"),
42270
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
42271
+ onChange: (e) => setTitleDraft(e.target.value),
42272
+ onBlur: commitTitle,
42273
+ onKeyDown: (e) => {
42274
+ if (e.key === "Enter") {
42275
+ e.preventDefault();
42276
+ commitTitle();
42277
+ } else if (e.key === "Escape") {
42278
+ e.preventDefault();
42279
+ setTitleDraft(null);
42280
+ }
42281
+ },
42282
+ "data-testid": "detail-title-input"
42283
+ }
42284
+ ) : onTitleCommit ? /* @__PURE__ */ jsxRuntime.jsx(
42285
+ Box,
42286
+ {
42287
+ role: "button",
42288
+ tabIndex: 0,
42289
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
42290
+ onClick: () => setTitleDraft(title ?? ""),
42291
+ onKeyDown: (e) => {
42292
+ if (e.key === "Enter" || e.key === " ") {
42293
+ e.preventDefault();
42294
+ setTitleDraft(title ?? "");
42295
+ }
42296
+ },
42297
+ "data-testid": "detail-title-editable",
42298
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" })
42299
+ }
42300
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" });
42182
42301
  const content = /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "md", className: "p-6", children: [
42183
42302
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
42184
42303
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
@@ -42198,7 +42317,7 @@ var init_DetailPanel = __esm({
42198
42317
  avatar,
42199
42318
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
42200
42319
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
42201
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
42320
+ titleNode,
42202
42321
  statusBadges
42203
42322
  ] }),
42204
42323
  subtitle && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
@@ -42298,16 +42417,11 @@ var init_DetailPanel = __esm({
42298
42417
  footer
42299
42418
  ] })
42300
42419
  ] }) });
42301
- return /* @__PURE__ */ jsxRuntime.jsx(
42302
- Box,
42303
- {
42304
- className: cn(
42305
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
42306
- className
42307
- ),
42308
- children: content
42309
- }
42310
- );
42420
+ if (!slideOver) {
42421
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
42422
+ }
42423
+ const panel = /* @__PURE__ */ jsxRuntime.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 });
42424
+ return typeof document === "undefined" ? panel : reactDom.createPortal(panel, document.body);
42311
42425
  };
42312
42426
  DetailPanel.displayName = "DetailPanel";
42313
42427
  }
@@ -42759,7 +42873,7 @@ var init_Form = __esm({
42759
42873
  });
42760
42874
  debug(
42761
42875
  "forms",
42762
- `Calculation triggered: ${calc.variableName} = ${value}`
42876
+ `Calculation triggered: ${calc.variableName} = ${String(value)}`
42763
42877
  );
42764
42878
  }
42765
42879
  });
@@ -49258,6 +49372,7 @@ function isPlainConfigObject(value) {
49258
49372
  return proto === Object.prototype || proto === null;
49259
49373
  }
49260
49374
  function subtreeHasTraitRef(value) {
49375
+ if (isEvaluatorResolvedData(value)) return false;
49261
49376
  const cached = traitRefPresenceCache.get(value);
49262
49377
  if (cached !== void 0) return cached;
49263
49378
  let found = false;
@@ -49330,6 +49445,10 @@ function renderPatternProps(props, onDismiss, propsSchema) {
49330
49445
  };
49331
49446
  rendered[key] = /* @__PURE__ */ jsxRuntime.jsx(SlotContentRenderer, { content: childContent, onDismiss });
49332
49447
  } else if (Array.isArray(value)) {
49448
+ if (!isEvaluatorResolvedData(value) && value.some((el) => isPatternConfig(el))) ; else if (!subtreeHasTraitRef(value)) {
49449
+ rendered[key] = value;
49450
+ continue;
49451
+ }
49333
49452
  const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
49334
49453
  rendered[key] = value.map((item, i) => {
49335
49454
  const el = item;
@@ -49808,6 +49927,9 @@ function enqueueEvent(queue, entry) {
49808
49927
  }
49809
49928
  queue.push(entry);
49810
49929
  }
49930
+
49931
+ // hooks/useTraitStateMachine.ts
49932
+ init_perf();
49811
49933
  var lambdaLog = logger.createLogger("almadar:ui:fn-form-lambda");
49812
49934
  function isOperatorCall(value) {
49813
49935
  const first = value[0];
@@ -50666,6 +50788,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50666
50788
  }, [eventBus]);
50667
50789
  React85.useEffect(() => {
50668
50790
  const scheduler = runtime.createTickScheduler();
50791
+ const timedTick = (key, fn) => () => ui.perfTimeAsync(key, fn);
50669
50792
  const pureWriterTickKeys = /* @__PURE__ */ new Set();
50670
50793
  for (const group of sharedGroups.values()) {
50671
50794
  const ticksByInterval = /* @__PURE__ */ new Map();
@@ -50682,12 +50805,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50682
50805
  }
50683
50806
  for (const entries of ticksByInterval.values()) {
50684
50807
  const interval = entries[0].tick.interval;
50685
- const onDue = () => {
50808
+ const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
50686
50809
  const writers = entries.map(
50687
50810
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
50688
50811
  );
50689
50812
  runTickFrame(group.storeKey, writers, sharedEntityStore);
50690
- };
50813
+ });
50691
50814
  if (interval === "frame") {
50692
50815
  scheduler.add(0, onDue);
50693
50816
  } else if (typeof interval === "number") {
@@ -50705,14 +50828,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50705
50828
  if (sharedKey !== void 0 && pureWriterTickKeys.has(`${binding.trait.name}::${tick.name}`)) {
50706
50829
  continue;
50707
50830
  }
50831
+ const tickKey = `tick:${binding.trait.name}::${tick.name}`;
50708
50832
  if (tick.interval === "frame") {
50709
- scheduler.add(0, () => runTickEffects(tick, binding));
50833
+ scheduler.add(0, timedTick(tickKey, () => runTickEffects(tick, binding)));
50710
50834
  } else if (typeof tick.interval === "number") {
50711
- scheduler.add(tick.interval, () => runTickEffects(tick, binding));
50835
+ scheduler.add(tick.interval, timedTick(tickKey, () => runTickEffects(tick, binding)));
50712
50836
  } else if (runtime.isValidCronExpression(tick.interval)) {
50713
- scheduler.addCron(tick.interval, () => runTickEffects(tick, binding));
50837
+ scheduler.addCron(tick.interval, timedTick(tickKey, () => runTickEffects(tick, binding)));
50714
50838
  } else {
50715
- scheduler.add(runtime.parseDurationString(tick.interval), () => runTickEffects(tick, binding));
50839
+ scheduler.add(runtime.parseDurationString(tick.interval), timedTick(tickKey, () => runTickEffects(tick, binding)));
50716
50840
  }
50717
50841
  }
50718
50842
  }
@@ -50720,6 +50844,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50720
50844
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
50721
50845
  const processEventQueued = React85.useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
50722
50846
  const normalizedEvent = normalizeEventKey(eventKey);
50847
+ const _perfT0 = ui.perfStart("processEvent:total");
50723
50848
  const bindings = traitBindingsRef.current;
50724
50849
  const currentManager = managerRef.current;
50725
50850
  crossTraitLog.debug("processEvent:enter", () => ({
@@ -50743,6 +50868,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50743
50868
  entityByTrait[name] = { ...sharedEntityStore.getSnapshot(sharedKey) };
50744
50869
  }
50745
50870
  }
50871
+ const _perfT1 = ui.perfStart("processEvent:guardMatch");
50746
50872
  const results = currentManager.sendEvent(
50747
50873
  normalizedEvent,
50748
50874
  payload,
@@ -50751,6 +50877,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50751
50877
  void 0,
50752
50878
  targetTrait
50753
50879
  );
50880
+ ui.perfEnd("processEvent:guardMatch", _perfT1);
50754
50881
  crossTraitLog.debug("processEvent:results", {
50755
50882
  event: normalizedEvent,
50756
50883
  executedCount: results.length,
@@ -50800,6 +50927,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50800
50927
  transition: `${result.previousState} -> ${result.newState}`,
50801
50928
  effects: JSON.stringify(result.effects)
50802
50929
  }));
50930
+ const _perfT2 = ui.perfStart("processEvent:executeAll");
50803
50931
  const emittedDuringExec = await executeTransitionEffects({
50804
50932
  binding,
50805
50933
  // upstream gap: /runtime TransitionResult.effects is unknown[] — they are SExpr at runtime (see Almadar_UI_Gaps.md)
@@ -50811,6 +50939,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50811
50939
  syncOnly: false,
50812
50940
  log: stateLog
50813
50941
  });
50942
+ ui.perfEnd("processEvent:executeAll", _perfT2);
50814
50943
  emittedByTrait.set(traitName, emittedDuringExec);
50815
50944
  for (const emittedKey of emittedDuringExec) {
50816
50945
  bridgeEchoPendingRef.current.set(
@@ -50907,27 +51036,31 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50907
51036
  if (orbital) dispatchedOrbitals.add(orbital);
50908
51037
  }
50909
51038
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
50910
- if (tick !== void 0) {
50911
- void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
50912
- } else {
50913
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
50914
- }
51039
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
50915
51040
  }
51041
+ ui.perfEnd("processEvent:total", _perfT0);
51042
+ ui.perfEnd(`event:${normalizedEvent}`, _perfT0);
50916
51043
  }, [entities, eventBus, sharedEntityStore]);
50917
51044
  const drainEventQueue = React85.useCallback(async () => {
50918
51045
  if (processingRef.current) return;
50919
51046
  processingRef.current = true;
51047
+ const _perfT = ui.perfStart("drain:pass");
51048
+ let _perfN = 0;
50920
51049
  try {
50921
51050
  while (eventQueueRef.current.length > 0) {
50922
51051
  const entry = eventQueueRef.current.shift();
51052
+ _perfN++;
50923
51053
  await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
50924
51054
  }
50925
51055
  } finally {
50926
51056
  processingRef.current = false;
51057
+ ui.perfEnd("drain:pass", _perfT);
51058
+ ui.perfGauge("drain:passEntries", _perfN);
50927
51059
  }
50928
51060
  }, [processEventQueued]);
50929
51061
  const enqueueAndDrain = React85.useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
50930
51062
  enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
51063
+ ui.perfGauge("queue:depthAtEnqueue", eventQueueRef.current.length);
50931
51064
  void drainEventQueue();
50932
51065
  }, [drainEventQueue]);
50933
51066
  React85.useCallback((eventKey, payload) => {
@@ -51329,7 +51462,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51329
51462
  [serverActiveTraits]
51330
51463
  );
51331
51464
  const uiSlots = context.useUISlots();
51332
- const onEventProcessed = React85.useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51465
+ const onEventProcessed = React85.useCallback((event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51333
51466
  if (!bridge.connected || !orbitalNames?.length) return;
51334
51467
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
51335
51468
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -51343,9 +51476,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51343
51476
  void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
51344
51477
  continue;
51345
51478
  }
51346
- const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
51347
- recordServerResponse(name, event, meta);
51348
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
51479
+ void bridge.sendEvent(name, event, withActiveTraits(payload)).then(({ effects, meta }) => {
51480
+ recordServerResponse(name, event, meta);
51481
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
51482
+ });
51349
51483
  }
51350
51484
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
51351
51485
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
@@ -51663,14 +51797,15 @@ function OrbPreview({
51663
51797
  return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
51664
51798
  }, [currentPagePath, routeParams]);
51665
51799
  const navStackRef = React85.useRef(null);
51666
- const handleNavigate = React85.useCallback((path) => {
51800
+ const handleNavigate = React85.useCallback((path, navState) => {
51667
51801
  const hit = providers.matchPathAmong(pages, path, (entry) => entry.page.path);
51668
51802
  const match = hit?.candidate;
51669
- const params = hit?.params ?? {};
51803
+ const params = { ...hit?.params ?? {}, ...navState ?? {} };
51670
51804
  navLog.debug("handleNavigate", () => ({
51671
51805
  path,
51672
51806
  matched: match?.page.name ?? null,
51673
51807
  params,
51808
+ navState: navState ? JSON.stringify(navState) : void 0,
51674
51809
  availablePaths: pages.map((p) => p.page.path)
51675
51810
  }));
51676
51811
  if (match?.page.name) {
@@ -51685,9 +51820,9 @@ function OrbPreview({
51685
51820
  }
51686
51821
  }, [pages]);
51687
51822
  const handleNavigateEffect = React85.useCallback(
51688
- (path, _params, crumb) => {
51823
+ (path, params, crumb) => {
51689
51824
  navStackRef.current?.beginNavigate(path, crumb);
51690
- handleNavigate(path);
51825
+ handleNavigate(path, params);
51691
51826
  },
51692
51827
  [handleNavigate]
51693
51828
  );
@@ -51842,17 +51977,9 @@ function BrowserPlayground({
51842
51977
  }
51843
51978
  );
51844
51979
  }
51845
- function usePerfBuffer() {
51846
- return React85.useSyncExternalStore(ui.perfStore.subscribe, ui.perfStore.getSnapshot, ui.perfStore.getSnapshot);
51847
- }
51848
- var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
51849
- ui.pushPerfEntry({
51850
- name: `profiler:${id}:${phase}`,
51851
- durationMs: actualDuration,
51852
- ts: commitTime,
51853
- detail: { baseDuration }
51854
- });
51855
- };
51980
+
51981
+ // runtime/index.ts
51982
+ init_perf();
51856
51983
 
51857
51984
  Object.defineProperty(exports, "EntitySchemaProvider", {
51858
51985
  enumerable: true,
@@ -51930,7 +52057,6 @@ exports.BrowserPlayground = BrowserPlayground;
51930
52057
  exports.OrbPreview = OrbPreview;
51931
52058
  exports.clearSchemaCache = clearSchemaCache;
51932
52059
  exports.createClientEffectHandlers = createClientEffectHandlers;
51933
- exports.profilerOnRender = profilerOnRender;
51934
52060
  exports.usePerfBuffer = usePerfBuffer;
51935
52061
  exports.useResolvedSchema = useResolvedSchema;
51936
52062
  exports.useTraitStateMachine = useTraitStateMachine;
@@ -5,11 +5,11 @@ import * as _almadar_runtime from '@almadar/runtime';
5
5
  import { TraitState, EffectHandlers } from '@almadar/runtime';
6
6
  import '../useEventBus-Ckr4wqW3.cjs';
7
7
  import { c as useUISlots } from '../UISlotContext-BlRDbHDy.cjs';
8
- import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityBindingContext-Bn3ePJQC.cjs';
9
- export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Bn3ePJQC.cjs';
10
- import { PerfEntry } from '@almadar/runtime/ui';
8
+ import { E as EntityBindingSource, S as ServerBridgeTransport } from '../EntityBindingContext-0Evn_LcT.cjs';
9
+ export { a as EntitySchemaContextValue, b as EntitySchemaProvider, c as EntitySchemaProviderProps, d as ServerBridgeContextValue, e as ServerBridgeProvider, f as ServerClientEffect, T as TraitContext, g as TraitContextValue, h as TraitInstance, i as TraitProvider, j as TraitProviderProps, u as useEntitySchema, k as useEntitySchemaOptional, l as useServerBridge, m as useTrait, n as useTraitContext } from '../EntityBindingContext-0Evn_LcT.cjs';
11
10
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
12
- import React__default, { ProfilerOnRenderCallback } from 'react';
11
+ import React__default from 'react';
12
+ export { p as profilerOnRender, u as usePerfBuffer } from '../perf-DaVdsbU0.cjs';
13
13
  import '../event-bus-types-Bl78kokd.cjs';
14
14
  import '../useUISlots-GNwGLlW2.cjs';
15
15
  import '@almadar/core/patterns';
@@ -312,21 +312,4 @@ interface BrowserPlaygroundProps {
312
312
  }
313
313
  declare function BrowserPlayground({ schema, mode, initialPagePath, height, className, }: BrowserPlaygroundProps): React__default.ReactElement;
314
314
 
315
- /**
316
- * @almadar/ui/runtime — perf instrumentation
317
- *
318
- * React-specific consumption layer on top of the renderer-agnostic perf ring
319
- * now living in `@almadar/runtime/ui/perf`. Re-exports the framework-free
320
- * timing primitives and adds `useSyncExternalStore` / `React.Profiler` hooks.
321
- */
322
-
323
- /**
324
- * React hook: returns the current perf ring snapshot, re-renders on push.
325
- */
326
- declare function usePerfBuffer(): readonly PerfEntry[];
327
- /**
328
- * React.Profiler `onRender` callback. Records `actualDuration` per commit.
329
- */
330
- declare const profilerOnRender: ProfilerOnRenderCallback;
331
-
332
- export { BrowserPlayground, type BrowserPlaygroundProps, type ClientEventBus, type CreateClientEffectHandlersOptions, OrbPreview, type OrbPreviewProps, type ResolvedSchemaResult, ServerBridgeTransport, type SlotPatternEntry, type SlotSetter, type SlotSource, type TraitStateMachineResult, type UseTraitStateMachineOptions, clearSchemaCache, createClientEffectHandlers, profilerOnRender, usePerfBuffer, useResolvedSchema, useTraitStateMachine };
315
+ export { BrowserPlayground, type BrowserPlaygroundProps, type ClientEventBus, type CreateClientEffectHandlersOptions, OrbPreview, type OrbPreviewProps, type ResolvedSchemaResult, ServerBridgeTransport, type SlotPatternEntry, type SlotSetter, type SlotSource, type TraitStateMachineResult, type UseTraitStateMachineOptions, clearSchemaCache, createClientEffectHandlers, useResolvedSchema, useTraitStateMachine };