@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.
package/dist/avl/index.js CHANGED
@@ -13,7 +13,7 @@ import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCirc
13
13
  import { createPortal } from 'react-dom';
14
14
  import { UISlotProvider, useUISlots, useTheme } from '@almadar/ui/context';
15
15
  import { evaluateGuard, evaluateListenPayloadExpr, evaluate, createMinimalContext, executeEffects } from '@almadar/evaluator';
16
- import { prepareSchemaForPreview, collectTraitRefsFromResolvedTrait, buildOrbitalsByTrait, collectEmbeddedTraits, wrapCallbackForEvent, pushPerfEntry, perfStart, perfEnd } from '@almadar/runtime/ui';
16
+ import { prepareSchemaForPreview, collectTraitRefsFromResolvedTrait, buildOrbitalsByTrait, collectEmbeddedTraits, perfStart, perfEnd, perfGauge, perfTimeAsync, wrapCallbackForEvent, pushPerfEntry } from '@almadar/runtime/ui';
17
17
  import { Link, Outlet, useLocation } from 'react-router-dom';
18
18
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
19
19
  import dark from 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus.js';
@@ -4588,7 +4588,16 @@ function isPlainObject(value) {
4588
4588
  if (typeof value === "function") return false;
4589
4589
  return true;
4590
4590
  }
4591
+ function isEvaluatorResolvedData(value) {
4592
+ return evaluatorResolvedData.has(value);
4593
+ }
4594
+ function brandResolved(value) {
4595
+ if (value !== null && typeof value === "object" && !React94__default.isValidElement(value) && !(value instanceof Date)) {
4596
+ resolvedMarkerFree.add(value);
4597
+ }
4598
+ }
4591
4599
  function subtreeHasMarker(value) {
4600
+ if (resolvedMarkerFree.has(value)) return false;
4592
4601
  const cached = markerPresenceCache.get(value);
4593
4602
  if (cached !== void 0) return cached;
4594
4603
  let found = false;
@@ -4610,10 +4619,23 @@ function subtreeHasMarker(value) {
4610
4619
  }
4611
4620
  function walkValue(value, scopeTrait, entity, config, state) {
4612
4621
  if (isRenderBindingMarker(value)) {
4613
- return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
4622
+ const cached = markerResolutionCache.get(value);
4623
+ if (cached !== void 0 && cached.entity === entity && cached.config === config && cached.state === state) {
4624
+ return { resolved: cached.resolved, changed: false };
4625
+ }
4626
+ const resolved = resolveMarkerExpression(value.expression, entity, config, state);
4627
+ markerResolutionCache.set(value, { entity, config, state, resolved });
4628
+ if (resolved !== null && typeof resolved === "object" && !React94__default.isValidElement(resolved) && !(resolved instanceof Date)) {
4629
+ resolvedMarkerFree.add(resolved);
4630
+ evaluatorResolvedData.add(resolved);
4631
+ }
4632
+ return { resolved, changed: true };
4614
4633
  }
4615
4634
  if (Array.isArray(value)) {
4616
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4635
+ if (!subtreeHasMarker(value)) {
4636
+ brandResolved(value);
4637
+ return { resolved: value, changed: false };
4638
+ }
4617
4639
  const out = [];
4618
4640
  let changed = false;
4619
4641
  for (const item of value) {
@@ -4628,10 +4650,14 @@ function walkValue(value, scopeTrait, entity, config, state) {
4628
4650
  out.push(resolved);
4629
4651
  if (itemChanged) changed = true;
4630
4652
  }
4653
+ brandResolved(out);
4631
4654
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4632
4655
  }
4633
4656
  if (isPlainObject(value)) {
4634
- if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4657
+ if (!subtreeHasMarker(value)) {
4658
+ brandResolved(value);
4659
+ return { resolved: value, changed: false };
4660
+ }
4635
4661
  const sourceTrait = value._sourceTrait;
4636
4662
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
4637
4663
  return { resolved: value, changed: false };
@@ -4643,11 +4669,13 @@ function walkValue(value, scopeTrait, entity, config, state) {
4643
4669
  out[key] = resolved;
4644
4670
  if (itemChanged) changed = true;
4645
4671
  }
4672
+ brandResolved(out);
4646
4673
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4647
4674
  }
4648
4675
  return { resolved: value, changed: false };
4649
4676
  }
4650
4677
  function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
4678
+ if (resolvedMarkerFree.has(props)) return props;
4651
4679
  const out = {};
4652
4680
  let changed = false;
4653
4681
  for (const [key, value] of Object.entries(props)) {
@@ -4655,13 +4683,17 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
4655
4683
  out[key] = resolved;
4656
4684
  if (propChanged) changed = true;
4657
4685
  }
4686
+ brandResolved(out);
4658
4687
  return changed ? out : props;
4659
4688
  }
4660
- var markerPresenceCache;
4689
+ var markerPresenceCache, markerResolutionCache, resolvedMarkerFree, evaluatorResolvedData;
4661
4690
  var init_resolve_render_bindings = __esm({
4662
4691
  "lib/resolve-render-bindings.ts"() {
4663
4692
  "use client";
4664
4693
  markerPresenceCache = /* @__PURE__ */ new WeakMap();
4694
+ markerResolutionCache = /* @__PURE__ */ new WeakMap();
4695
+ resolvedMarkerFree = /* @__PURE__ */ new WeakSet();
4696
+ evaluatorResolvedData = /* @__PURE__ */ new WeakSet();
4665
4697
  }
4666
4698
  });
4667
4699
  function getCurrentIconFamily() {
@@ -15266,6 +15298,19 @@ var init_molecules = __esm({
15266
15298
  init_puzzleObject();
15267
15299
  }
15268
15300
  });
15301
+ var profilerOnRender;
15302
+ var init_perf = __esm({
15303
+ "lib/perf.ts"() {
15304
+ profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
15305
+ pushPerfEntry({
15306
+ name: `profiler:${id}:${phase}`,
15307
+ durationMs: actualDuration,
15308
+ ts: commitTime,
15309
+ detail: { baseDuration }
15310
+ });
15311
+ };
15312
+ }
15313
+ });
15269
15314
  function themeBodyFont(el) {
15270
15315
  if (typeof getComputedStyle !== "function") return "system-ui, sans-serif";
15271
15316
  const v = getComputedStyle(el).getPropertyValue("--font-family-body").trim();
@@ -15614,6 +15659,7 @@ var init_LearningCanvas = __esm({
15614
15659
  "components/learning/atoms/LearningCanvas.tsx"() {
15615
15660
  "use client";
15616
15661
  init_cn();
15662
+ init_perf();
15617
15663
  init_useEventBus();
15618
15664
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
15619
15665
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
@@ -15657,6 +15703,7 @@ var init_LearningCanvas = __esm({
15657
15703
  return [...shapes, ...traceOut, ...readoutOut];
15658
15704
  }, [shapes, traces, readouts, width, height]);
15659
15705
  const draw = useCallback(() => {
15706
+ const _perfT = perfStart("learningcanvas:paint");
15660
15707
  const canvas = canvasRef.current;
15661
15708
  if (!canvas) return;
15662
15709
  const ctx = canvas.getContext("2d");
@@ -15678,6 +15725,7 @@ var init_LearningCanvas = __esm({
15678
15725
  for (const shape of derivedShapes) {
15679
15726
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
15680
15727
  }
15728
+ perfEnd("learningcanvas:paint", _perfT);
15681
15729
  }, [width, height, backgroundColor, derivedShapes]);
15682
15730
  useEffect(() => {
15683
15731
  draw();
@@ -28764,8 +28812,8 @@ function DataGrid({
28764
28812
  }
28765
28813
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
28766
28814
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
28767
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28768
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
28815
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28816
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
28769
28817
  ] }, field.name);
28770
28818
  }) })
28771
28819
  ] }) })
@@ -29193,7 +29241,11 @@ function DataList({
29193
29241
  Box,
29194
29242
  {
29195
29243
  className: cn(
29196
- "group flex items-center gap-4 transition-all duration-fast",
29244
+ // items-start, not items-center: a multi-line row (title + meta +
29245
+ // progress) centred its action cluster in the vertical middle, so
29246
+ // the buttons floated between the meta fields instead of anchoring
29247
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
29248
+ "group flex items-start gap-4 transition-all duration-fast",
29197
29249
  isCompact ? "px-4 py-2" : "px-6 py-4",
29198
29250
  "hover:bg-muted/80",
29199
29251
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -29224,11 +29276,19 @@ function DataList({
29224
29276
  if (value === void 0 || value === null || value === "") return null;
29225
29277
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
29226
29278
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
29227
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
29228
- field.label ?? fieldLabel3(field.name),
29229
- ":"
29230
- ] }),
29231
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29279
+ /* @__PURE__ */ jsxs(
29280
+ Typography,
29281
+ {
29282
+ variant: "caption",
29283
+ color: "secondary",
29284
+ className: cn(field.format !== "boolean" && "sr-only"),
29285
+ children: [
29286
+ field.label ?? fieldLabel3(field.name),
29287
+ ":"
29288
+ ]
29289
+ }
29290
+ ),
29291
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29232
29292
  ] }, field.name);
29233
29293
  }) }),
29234
29294
  progressFields.map((field) => {
@@ -29247,7 +29307,7 @@ function DataList({
29247
29307
  ]
29248
29308
  }
29249
29309
  ),
29250
- isCard && !isLast && /* @__PURE__ */ jsx(Box, { className: "mx-6 border-b border-border/40" })
29310
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
29251
29311
  ] }, id)
29252
29312
  );
29253
29313
  };
@@ -29257,7 +29317,13 @@ function DataList({
29257
29317
  {
29258
29318
  className: cn(
29259
29319
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
29260
- !isCard && gapClass,
29320
+ // `gap-*` is inert on a block container, and Box only emits a display
29321
+ // class when its `display` prop is set — so every non-card list had
29322
+ // been asking for a gap that CSS silently dropped. flex-col makes it
29323
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
29324
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
29325
+ !isCard && "flex flex-col",
29326
+ !isCard && !isCompact && gapClass,
29261
29327
  listLookStyles[look],
29262
29328
  className
29263
29329
  ),
@@ -32872,6 +32938,7 @@ var init_MathCanvas = __esm({
32872
32938
  "components/learning/molecules/MathCanvas.tsx"() {
32873
32939
  "use client";
32874
32940
  init_useEventBus();
32941
+ init_perf();
32875
32942
  init_atoms();
32876
32943
  init_Stack();
32877
32944
  init_LearningCanvas();
@@ -32937,6 +33004,7 @@ var init_MathCanvas = __esm({
32937
33004
  };
32938
33005
  }, [stableKeyMap, stableKeyUpMap, eventBus]);
32939
33006
  const derivedShapes = useMemo(() => {
33007
+ const _perfT = perfStart("mathcanvas:derive");
32940
33008
  const out = [];
32941
33009
  const margin = 24;
32942
33010
  const plotW = width - margin * 2;
@@ -33180,6 +33248,7 @@ var init_MathCanvas = __esm({
33180
33248
  }
33181
33249
  }
33182
33250
  out.push(...shapes);
33251
+ perfEnd("mathcanvas:derive", _perfT);
33183
33252
  return out;
33184
33253
  }, [
33185
33254
  width,
@@ -39533,7 +39602,7 @@ var init_RichBlockEditor = __esm({
39533
39602
  {
39534
39603
  variant: "bordered",
39535
39604
  padding: "none",
39536
- className: cn("flex flex-col", className),
39605
+ className: cn("flex flex-col text-card-foreground", className),
39537
39606
  children: [
39538
39607
  enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
39539
39608
  Box,
@@ -44352,6 +44421,7 @@ var init_DetailPanel = __esm({
44352
44421
  "use client";
44353
44422
  init_atoms();
44354
44423
  init_Box();
44424
+ init_Input();
44355
44425
  init_Stack();
44356
44426
  init_SimpleGrid();
44357
44427
  init_Menu();
@@ -44367,6 +44437,7 @@ var init_DetailPanel = __esm({
44367
44437
  ReactMarkdown2 = lazy(() => import('react-markdown'));
44368
44438
  DetailPanel = ({
44369
44439
  title: propTitle,
44440
+ onTitleCommit,
44370
44441
  subtitle,
44371
44442
  status,
44372
44443
  avatar,
@@ -44388,6 +44459,7 @@ var init_DetailPanel = __esm({
44388
44459
  }) => {
44389
44460
  const eventBus = useEventBus();
44390
44461
  const { t } = useTranslate();
44462
+ const [titleDraft, setTitleDraft] = React94__default.useState(null);
44391
44463
  const isFieldDefArray = (arr) => {
44392
44464
  if (!arr || arr.length === 0) return false;
44393
44465
  const first = arr[0];
@@ -44608,6 +44680,50 @@ var init_DetailPanel = __esm({
44608
44680
  }),
44609
44681
  status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
44610
44682
  ] });
44683
+ const commitTitle = () => {
44684
+ if (!onTitleCommit) return;
44685
+ const next = (titleDraft ?? "").trim();
44686
+ setTitleDraft(null);
44687
+ if (!next || next === title) return;
44688
+ onTitleCommit(next, normalizedData?.id !== void 0 ? String(normalizedData.id) : "");
44689
+ };
44690
+ const titleNode = onTitleCommit && titleDraft !== null ? /* @__PURE__ */ jsx(
44691
+ Input,
44692
+ {
44693
+ value: titleDraft,
44694
+ autoFocus: true,
44695
+ "aria-label": t("common.title"),
44696
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
44697
+ onChange: (e) => setTitleDraft(e.target.value),
44698
+ onBlur: commitTitle,
44699
+ onKeyDown: (e) => {
44700
+ if (e.key === "Enter") {
44701
+ e.preventDefault();
44702
+ commitTitle();
44703
+ } else if (e.key === "Escape") {
44704
+ e.preventDefault();
44705
+ setTitleDraft(null);
44706
+ }
44707
+ },
44708
+ "data-testid": "detail-title-input"
44709
+ }
44710
+ ) : onTitleCommit ? /* @__PURE__ */ jsx(
44711
+ Box,
44712
+ {
44713
+ role: "button",
44714
+ tabIndex: 0,
44715
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
44716
+ onClick: () => setTitleDraft(title ?? ""),
44717
+ onKeyDown: (e) => {
44718
+ if (e.key === "Enter" || e.key === " ") {
44719
+ e.preventDefault();
44720
+ setTitleDraft(title ?? "");
44721
+ }
44722
+ },
44723
+ "data-testid": "detail-title-editable",
44724
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" })
44725
+ }
44726
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" });
44611
44727
  const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
44612
44728
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
44613
44729
  /* @__PURE__ */ jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
@@ -44627,7 +44743,7 @@ var init_DetailPanel = __esm({
44627
44743
  avatar,
44628
44744
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
44629
44745
  /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
44630
- /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
44746
+ titleNode,
44631
44747
  statusBadges
44632
44748
  ] }),
44633
44749
  subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
@@ -44727,16 +44843,11 @@ var init_DetailPanel = __esm({
44727
44843
  footer
44728
44844
  ] })
44729
44845
  ] }) });
44730
- return /* @__PURE__ */ jsx(
44731
- Box,
44732
- {
44733
- className: cn(
44734
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
44735
- className
44736
- ),
44737
- children: content
44738
- }
44739
- );
44846
+ if (!slideOver) {
44847
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
44848
+ }
44849
+ 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 });
44850
+ return typeof document === "undefined" ? panel : createPortal(panel, document.body);
44740
44851
  };
44741
44852
  DetailPanel.displayName = "DetailPanel";
44742
44853
  }
@@ -45188,7 +45299,7 @@ var init_Form = __esm({
45188
45299
  });
45189
45300
  debug(
45190
45301
  "forms",
45191
- `Calculation triggered: ${calc.variableName} = ${value}`
45302
+ `Calculation triggered: ${calc.variableName} = ${String(value)}`
45192
45303
  );
45193
45304
  }
45194
45305
  });
@@ -51687,6 +51798,7 @@ function isPlainConfigObject(value) {
51687
51798
  return proto === Object.prototype || proto === null;
51688
51799
  }
51689
51800
  function subtreeHasTraitRef(value) {
51801
+ if (isEvaluatorResolvedData(value)) return false;
51690
51802
  const cached = traitRefPresenceCache.get(value);
51691
51803
  if (cached !== void 0) return cached;
51692
51804
  let found = false;
@@ -51759,6 +51871,10 @@ function renderPatternProps(props, onDismiss, propsSchema) {
51759
51871
  };
51760
51872
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
51761
51873
  } else if (Array.isArray(value)) {
51874
+ if (!isEvaluatorResolvedData(value) && value.some((el) => isPatternConfig(el))) ; else if (!subtreeHasTraitRef(value)) {
51875
+ rendered[key] = value;
51876
+ continue;
51877
+ }
51762
51878
  const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
51763
51879
  rendered[key] = value.map((item, i) => {
51764
51880
  const el = item;
@@ -55785,6 +55901,7 @@ function enqueueEvent(queue, entry) {
55785
55901
  }
55786
55902
 
55787
55903
  // hooks/useTraitStateMachine.ts
55904
+ init_perf();
55788
55905
  init_traitRegistry();
55789
55906
  init_verificationRegistry();
55790
55907
  var crossTraitLog = createLogger("almadar:ui:cross-trait");
@@ -56471,6 +56588,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56471
56588
  }, [eventBus]);
56472
56589
  useEffect(() => {
56473
56590
  const scheduler = createTickScheduler();
56591
+ const timedTick = (key, fn) => () => perfTimeAsync(key, fn);
56474
56592
  const pureWriterTickKeys = /* @__PURE__ */ new Set();
56475
56593
  for (const group of sharedGroups.values()) {
56476
56594
  const ticksByInterval = /* @__PURE__ */ new Map();
@@ -56487,12 +56605,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56487
56605
  }
56488
56606
  for (const entries of ticksByInterval.values()) {
56489
56607
  const interval = entries[0].tick.interval;
56490
- const onDue = () => {
56608
+ const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
56491
56609
  const writers = entries.map(
56492
56610
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
56493
56611
  );
56494
56612
  runTickFrame(group.storeKey, writers, sharedEntityStore);
56495
- };
56613
+ });
56496
56614
  if (interval === "frame") {
56497
56615
  scheduler.add(0, onDue);
56498
56616
  } else if (typeof interval === "number") {
@@ -56510,14 +56628,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56510
56628
  if (sharedKey !== void 0 && pureWriterTickKeys.has(`${binding.trait.name}::${tick.name}`)) {
56511
56629
  continue;
56512
56630
  }
56631
+ const tickKey = `tick:${binding.trait.name}::${tick.name}`;
56513
56632
  if (tick.interval === "frame") {
56514
- scheduler.add(0, () => runTickEffects(tick, binding));
56633
+ scheduler.add(0, timedTick(tickKey, () => runTickEffects(tick, binding)));
56515
56634
  } else if (typeof tick.interval === "number") {
56516
- scheduler.add(tick.interval, () => runTickEffects(tick, binding));
56635
+ scheduler.add(tick.interval, timedTick(tickKey, () => runTickEffects(tick, binding)));
56517
56636
  } else if (isValidCronExpression(tick.interval)) {
56518
- scheduler.addCron(tick.interval, () => runTickEffects(tick, binding));
56637
+ scheduler.addCron(tick.interval, timedTick(tickKey, () => runTickEffects(tick, binding)));
56519
56638
  } else {
56520
- scheduler.add(parseDurationString(tick.interval), () => runTickEffects(tick, binding));
56639
+ scheduler.add(parseDurationString(tick.interval), timedTick(tickKey, () => runTickEffects(tick, binding)));
56521
56640
  }
56522
56641
  }
56523
56642
  }
@@ -56525,6 +56644,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56525
56644
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
56526
56645
  const processEventQueued = useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
56527
56646
  const normalizedEvent = normalizeEventKey(eventKey);
56647
+ const _perfT0 = perfStart("processEvent:total");
56528
56648
  const bindings = traitBindingsRef.current;
56529
56649
  const currentManager = managerRef.current;
56530
56650
  crossTraitLog.debug("processEvent:enter", () => ({
@@ -56548,6 +56668,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56548
56668
  entityByTrait[name] = { ...sharedEntityStore.getSnapshot(sharedKey) };
56549
56669
  }
56550
56670
  }
56671
+ const _perfT1 = perfStart("processEvent:guardMatch");
56551
56672
  const results = currentManager.sendEvent(
56552
56673
  normalizedEvent,
56553
56674
  payload,
@@ -56556,6 +56677,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56556
56677
  void 0,
56557
56678
  targetTrait
56558
56679
  );
56680
+ perfEnd("processEvent:guardMatch", _perfT1);
56559
56681
  crossTraitLog.debug("processEvent:results", {
56560
56682
  event: normalizedEvent,
56561
56683
  executedCount: results.length,
@@ -56605,6 +56727,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56605
56727
  transition: `${result.previousState} -> ${result.newState}`,
56606
56728
  effects: JSON.stringify(result.effects)
56607
56729
  }));
56730
+ const _perfT2 = perfStart("processEvent:executeAll");
56608
56731
  const emittedDuringExec = await executeTransitionEffects({
56609
56732
  binding,
56610
56733
  // upstream gap: /runtime TransitionResult.effects is unknown[] — they are SExpr at runtime (see Almadar_UI_Gaps.md)
@@ -56616,6 +56739,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56616
56739
  syncOnly: false,
56617
56740
  log: stateLog
56618
56741
  });
56742
+ perfEnd("processEvent:executeAll", _perfT2);
56619
56743
  emittedByTrait.set(traitName, emittedDuringExec);
56620
56744
  for (const emittedKey of emittedDuringExec) {
56621
56745
  bridgeEchoPendingRef.current.set(
@@ -56712,27 +56836,31 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56712
56836
  if (orbital) dispatchedOrbitals.add(orbital);
56713
56837
  }
56714
56838
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
56715
- if (tick !== void 0) {
56716
- void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
56717
- } else {
56718
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
56719
- }
56839
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
56720
56840
  }
56841
+ perfEnd("processEvent:total", _perfT0);
56842
+ perfEnd(`event:${normalizedEvent}`, _perfT0);
56721
56843
  }, [entities, eventBus, sharedEntityStore]);
56722
56844
  const drainEventQueue = useCallback(async () => {
56723
56845
  if (processingRef.current) return;
56724
56846
  processingRef.current = true;
56847
+ const _perfT = perfStart("drain:pass");
56848
+ let _perfN = 0;
56725
56849
  try {
56726
56850
  while (eventQueueRef.current.length > 0) {
56727
56851
  const entry = eventQueueRef.current.shift();
56852
+ _perfN++;
56728
56853
  await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
56729
56854
  }
56730
56855
  } finally {
56731
56856
  processingRef.current = false;
56857
+ perfEnd("drain:pass", _perfT);
56858
+ perfGauge("drain:passEntries", _perfN);
56732
56859
  }
56733
56860
  }, [processEventQueued]);
56734
56861
  const enqueueAndDrain = useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
56735
56862
  enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
56863
+ perfGauge("queue:depthAtEnqueue", eventQueueRef.current.length);
56736
56864
  void drainEventQueue();
56737
56865
  }, [drainEventQueue]);
56738
56866
  useCallback((eventKey, payload) => {
@@ -57031,7 +57159,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
57031
57159
  [serverActiveTraits]
57032
57160
  );
57033
57161
  const uiSlots = useUISlots();
57034
- const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
57162
+ const onEventProcessed = useCallback((event, payload, dispatchedOrbitals, tick, sourceTrait) => {
57035
57163
  if (!bridge.connected || !orbitalNames?.length) return;
57036
57164
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
57037
57165
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -57045,9 +57173,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
57045
57173
  void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
57046
57174
  continue;
57047
57175
  }
57048
- const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
57049
- recordServerResponse(name, event, meta);
57050
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
57176
+ void bridge.sendEvent(name, event, withActiveTraits(payload)).then(({ effects, meta }) => {
57177
+ recordServerResponse(name, event, meta);
57178
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
57179
+ });
57051
57180
  }
57052
57181
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
57053
57182
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
@@ -57365,14 +57494,15 @@ function OrbPreview({
57365
57494
  return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
57366
57495
  }, [currentPagePath, routeParams]);
57367
57496
  const navStackRef = useRef(null);
57368
- const handleNavigate = useCallback((path) => {
57497
+ const handleNavigate = useCallback((path, navState) => {
57369
57498
  const hit = matchPathAmong(pages, path, (entry) => entry.page.path);
57370
57499
  const match = hit?.candidate;
57371
- const params = hit?.params ?? {};
57500
+ const params = { ...hit?.params ?? {}, ...navState ?? {} };
57372
57501
  navLog.debug("handleNavigate", () => ({
57373
57502
  path,
57374
57503
  matched: match?.page.name ?? null,
57375
57504
  params,
57505
+ navState: navState ? JSON.stringify(navState) : void 0,
57376
57506
  availablePaths: pages.map((p) => p.page.path)
57377
57507
  }));
57378
57508
  if (match?.page.name) {
@@ -57387,9 +57517,9 @@ function OrbPreview({
57387
57517
  }
57388
57518
  }, [pages]);
57389
57519
  const handleNavigateEffect = useCallback(
57390
- (path, _params, crumb) => {
57520
+ (path, params, crumb) => {
57391
57521
  navStackRef.current?.beginNavigate(path, crumb);
57392
- handleNavigate(path);
57522
+ handleNavigate(path, params);
57393
57523
  },
57394
57524
  [handleNavigate]
57395
57525
  );
@@ -59706,14 +59836,7 @@ TraitCardNode.displayName = "TraitCardNode";
59706
59836
 
59707
59837
  // components/avl/organisms/FlowCanvas.tsx
59708
59838
  init_useEventBus();
59709
- var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
59710
- pushPerfEntry({
59711
- name: `profiler:${id}:${phase}`,
59712
- durationMs: actualDuration,
59713
- ts: commitTime,
59714
- detail: { baseDuration }
59715
- });
59716
- };
59839
+ init_perf();
59717
59840
  var flowCanvasLog = createLogger("almadar:ui:flow-canvas");
59718
59841
  var NODE_TYPES = {
59719
59842
  preview: OrbPreviewNode,