@almadar/ui 2.27.5 → 2.28.1

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.
@@ -851,6 +851,36 @@ function recordTransition(trace) {
851
851
  function getTransitions() {
852
852
  return [...getState().transitions];
853
853
  }
854
+ function recordServerResponse(orbitalName, event, response) {
855
+ const serverResponse = {
856
+ ...response,
857
+ orbitalName,
858
+ timestamp: Date.now()
859
+ };
860
+ const entry = {
861
+ id: `srv-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
862
+ traitName: `server:${orbitalName}`,
863
+ from: "server",
864
+ to: "server",
865
+ event,
866
+ effects: [],
867
+ serverResponse,
868
+ timestamp: Date.now()
869
+ };
870
+ getState().transitions.push(entry);
871
+ if (getState().transitions.length > MAX_TRANSITIONS) {
872
+ getState().transitions.shift();
873
+ }
874
+ if (!response.success && response.error) {
875
+ registerCheck(
876
+ `server-error-${entry.id}`,
877
+ `Server error for ${orbitalName}:${event}`,
878
+ "fail",
879
+ response.error
880
+ );
881
+ }
882
+ notifyListeners2();
883
+ }
854
884
  function getBridgeHealth() {
855
885
  const bh = getState().bridgeHealth;
856
886
  return bh ? { ...bh } : null;
@@ -27934,6 +27964,119 @@ function EventDispatcherTab({ traits: traits2, schema }) {
27934
27964
  ] });
27935
27965
  }
27936
27966
  EventDispatcherTab.displayName = "EventDispatcherTab";
27967
+ function ServerResponseRow({ sr }) {
27968
+ const entityEntries = Object.entries(sr.dataEntities);
27969
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-4 pl-2 border-l border-purple-700/50 py-0.5 text-[10px] font-mono", children: [
27970
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
27971
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: sr.success ? "text-green-400" : "text-red-400", children: [
27972
+ sr.success ? "\u2713" : "\u2717",
27973
+ " server"
27974
+ ] }),
27975
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-300", children: sr.orbitalName }),
27976
+ sr.clientEffects > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 rounded bg-purple-900/50 text-purple-300", children: [
27977
+ sr.clientEffects,
27978
+ " clientEffect",
27979
+ sr.clientEffects !== 1 ? "s" : ""
27980
+ ] }),
27981
+ sr.emittedEvents.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 rounded bg-blue-900/50 text-blue-300", children: [
27982
+ "emit: ",
27983
+ sr.emittedEvents.join(", ")
27984
+ ] }),
27985
+ sr.error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1 rounded bg-red-900/50 text-red-400 truncate max-w-[300px]", children: sr.error })
27986
+ ] }),
27987
+ entityEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-0.5", children: entityEntries.map(([name, count]) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 rounded bg-gray-800 text-gray-300", children: [
27988
+ name,
27989
+ ": ",
27990
+ count,
27991
+ " row",
27992
+ count !== 1 ? "s" : ""
27993
+ ] }, name)) })
27994
+ ] });
27995
+ }
27996
+ function TransitionRow({ trace }) {
27997
+ const isServerEntry = !!trace.serverResponse && trace.traitName.startsWith("server:");
27998
+ const hasFailedEffects = trace.effects.some((e) => e.status === "failed");
27999
+ if (isServerEntry && trace.serverResponse) {
28000
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-0.5 border-b border-gray-800 last:border-0", children: [
28001
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 text-xs font-mono", children: [
28002
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0 bg-purple-500" }),
28003
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "warning", size: "sm", className: "flex-shrink-0", children: trace.event }),
28004
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-purple-400 flex-shrink-0", children: "server response" })
28005
+ ] }),
28006
+ /* @__PURE__ */ jsxRuntime.jsx(ServerResponseRow, { sr: trace.serverResponse })
28007
+ ] });
28008
+ }
28009
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-0.5 border-b border-gray-800 last:border-0", children: [
28010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 text-xs font-mono", children: [
28011
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
28012
+ "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0",
28013
+ hasFailedEffects ? "bg-red-500" : "bg-green-500"
28014
+ ) }),
28015
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "info", size: "sm", className: "flex-shrink-0", children: trace.event }),
28016
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-300 flex-shrink-0", children: trace.traitName }),
28017
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400 flex-shrink-0", children: [
28018
+ trace.from,
28019
+ " ",
28020
+ "\u2192",
28021
+ " ",
28022
+ trace.to
28023
+ ] })
28024
+ ] }),
28025
+ trace.effects.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 ml-6 mt-0.5", children: trace.effects.map((eff, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(
28026
+ "px-1 rounded text-[10px]",
28027
+ eff.status === "executed" ? "bg-green-900/50 text-green-400" : eff.status === "failed" ? "bg-red-900/50 text-red-400" : "bg-yellow-900/50 text-yellow-400"
28028
+ ), children: [
28029
+ eff.status === "executed" ? "\u2713" : eff.status === "failed" ? "\u2717" : "-",
28030
+ " ",
28031
+ eff.type,
28032
+ eff.args.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 ml-0.5", children: JSON.stringify(eff.args).slice(0, 40) })
28033
+ ] }, i)) }),
28034
+ trace.serverResponse && /* @__PURE__ */ jsxRuntime.jsx(ServerResponseRow, { sr: trace.serverResponse })
28035
+ ] });
28036
+ }
28037
+ function VerifyModePanel({
28038
+ className,
28039
+ failedChecks,
28040
+ transitions,
28041
+ traitStates,
28042
+ serverCount,
28043
+ localCount
28044
+ }) {
28045
+ const scrollRef = React117__namespace.useRef(null);
28046
+ const prevCountRef = React117__namespace.useRef(0);
28047
+ React117__namespace.useEffect(() => {
28048
+ if (transitions.length > prevCountRef.current && scrollRef.current) {
28049
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
28050
+ }
28051
+ prevCountRef.current = transitions.length;
28052
+ }, [transitions.length]);
28053
+ return /* @__PURE__ */ jsxRuntime.jsxs(
28054
+ "div",
28055
+ {
28056
+ className: cn(
28057
+ "runtime-debugger runtime-debugger--verify",
28058
+ "fixed bottom-0 left-0 right-0 z-[9999] h-[35vh] flex flex-col bg-gray-900 text-white border-t-2 border-cyan-500",
28059
+ className
28060
+ ),
28061
+ "data-testid": "debugger-verify",
28062
+ children: [
28063
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-1.5 flex items-center gap-3 text-xs font-mono border-b border-gray-700 flex-shrink-0", children: [
28064
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: failedChecks > 0 ? "danger" : "success", size: "sm", children: failedChecks > 0 ? `${failedChecks} fail` : "OK" }),
28065
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400", children: [
28066
+ localCount,
28067
+ " local"
28068
+ ] }),
28069
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-purple-400", children: [
28070
+ serverCount,
28071
+ " server"
28072
+ ] }),
28073
+ traitStates && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-cyan-400 truncate max-w-[400px]", children: traitStates })
28074
+ ] }),
28075
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1", children: transitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 text-xs font-mono py-2 text-center", children: "Waiting for transitions..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: transitions.map((trace) => /* @__PURE__ */ jsxRuntime.jsx(TransitionRow, { trace }, trace.id)) }) }) })
28076
+ ]
28077
+ }
28078
+ );
28079
+ }
27937
28080
  function RuntimeDebugger({
27938
28081
  position = "bottom-right",
27939
28082
  defaultCollapsed = true,
@@ -28083,51 +28226,17 @@ function RuntimeDebugger({
28083
28226
  }
28084
28227
  if (mode === "verify") {
28085
28228
  const traitStates = debugData.traits.map((t) => `${t.name}:${t.currentState}`).join(" | ");
28086
- return /* @__PURE__ */ jsxRuntime.jsxs(
28087
- "div",
28229
+ const serverEntries = verification.transitions.filter((t) => t.serverResponse);
28230
+ const localEntries = verification.transitions.filter((t) => !t.serverResponse);
28231
+ return /* @__PURE__ */ jsxRuntime.jsx(
28232
+ VerifyModePanel,
28088
28233
  {
28089
- className: cn(
28090
- "runtime-debugger runtime-debugger--verify",
28091
- "fixed bottom-0 left-0 right-0 z-[9999] h-[35vh] flex flex-col bg-gray-900 text-white border-t-2 border-cyan-500",
28092
- className
28093
- ),
28094
- "data-testid": "debugger-verify",
28095
- children: [
28096
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-1.5 flex items-center gap-3 text-xs font-mono border-b border-gray-700 flex-shrink-0", children: [
28097
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: failedChecks > 0 ? "danger" : "success", size: "sm", children: failedChecks > 0 ? `${failedChecks} fail` : "OK" }),
28098
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400", children: [
28099
- verification.transitions.length,
28100
- " transitions"
28101
- ] }),
28102
- traitStates && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-cyan-400 truncate max-w-[400px]", children: traitStates })
28103
- ] }),
28104
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1", children: verification.transitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500 text-xs font-mono py-2 text-center", children: "Waiting for transitions..." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: verification.transitions.map((trace) => {
28105
- const hasFailedEffects = trace.effects.some((e) => e.status === "failed");
28106
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 text-xs font-mono py-0.5 border-b border-gray-800 last:border-0", children: [
28107
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
28108
- "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0",
28109
- hasFailedEffects ? "bg-red-500" : "bg-green-500"
28110
- ) }),
28111
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "info", size: "sm", className: "flex-shrink-0", children: trace.event }),
28112
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400 flex-shrink-0", children: [
28113
- trace.from,
28114
- " ",
28115
- "\u2192",
28116
- " ",
28117
- trace.to
28118
- ] }),
28119
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex flex-wrap gap-1 ml-1", children: trace.effects.map((eff, i) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(
28120
- "px-1 rounded text-[10px]",
28121
- eff.status === "executed" ? "bg-green-900/50 text-green-400" : eff.status === "failed" ? "bg-red-900/50 text-red-400" : "bg-yellow-900/50 text-yellow-400"
28122
- ), children: [
28123
- eff.status === "executed" ? "\u2713" : eff.status === "failed" ? "\u2717" : "-",
28124
- " ",
28125
- eff.type,
28126
- eff.args.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 ml-0.5", children: JSON.stringify(eff.args).slice(0, 30) })
28127
- ] }, i)) })
28128
- ] }, trace.id);
28129
- }) }) }) })
28130
- ]
28234
+ className,
28235
+ failedChecks,
28236
+ transitions: verification.transitions,
28237
+ traitStates,
28238
+ serverCount: serverEntries.length,
28239
+ localCount: localEntries.length
28131
28240
  }
28132
28241
  );
28133
28242
  }
@@ -32335,17 +32444,42 @@ function VerificationProvider({
32335
32444
  const pending = pendingRef.current.get(key);
32336
32445
  pendingRef.current.delete(key);
32337
32446
  const newState = payload["newState"] ?? payload["state"] ?? "unknown";
32338
- const effects = Array.isArray(payload["clientEffects"]) ? payload["clientEffects"].map((e) => ({
32447
+ const clientEffectsArr = Array.isArray(payload["clientEffects"]) ? payload["clientEffects"] : [];
32448
+ const effects = clientEffectsArr.map((e) => ({
32339
32449
  type: String(e["type"] ?? "unknown"),
32340
32450
  args: Array.isArray(e["args"]) ? e["args"] : [],
32341
32451
  status: "executed"
32342
- })) : [];
32452
+ }));
32453
+ const effectResults = Array.isArray(payload["effectResults"]) ? payload["effectResults"] : [];
32454
+ for (const er of effectResults) {
32455
+ effects.push({
32456
+ type: String(er["type"] ?? er["effect"] ?? "server-effect"),
32457
+ args: [er["entity"] ?? er["service"] ?? ""].filter(Boolean),
32458
+ status: er["error"] ? "failed" : "executed",
32459
+ error: er["error"]
32460
+ });
32461
+ }
32462
+ const dataEntities = {};
32463
+ const responseData = payload["data"];
32464
+ if (responseData && typeof responseData === "object") {
32465
+ for (const [entityName, records] of Object.entries(responseData)) {
32466
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
32467
+ }
32468
+ }
32343
32469
  recordTransition({
32344
32470
  traitName: parsed.traitName,
32345
32471
  from: pending?.from ?? payload["currentState"] ?? newState,
32346
32472
  to: newState,
32347
32473
  event: parsed.event,
32348
32474
  effects,
32475
+ serverResponse: {
32476
+ orbitalName: parsed.traitName,
32477
+ success: true,
32478
+ clientEffects: clientEffectsArr.length,
32479
+ dataEntities,
32480
+ emittedEvents: [],
32481
+ timestamp: Date.now()
32482
+ },
32349
32483
  timestamp: Date.now()
32350
32484
  });
32351
32485
  } else if (parsed.kind === "error" && parsed.event) {
@@ -32366,6 +32500,15 @@ function VerificationProvider({
32366
32500
  status: "failed",
32367
32501
  error: errorMsg
32368
32502
  }],
32503
+ serverResponse: {
32504
+ orbitalName: parsed.traitName,
32505
+ success: false,
32506
+ clientEffects: 0,
32507
+ dataEntities: {},
32508
+ emittedEvents: [],
32509
+ error: errorMsg,
32510
+ timestamp: Date.now()
32511
+ },
32369
32512
  timestamp: Date.now()
32370
32513
  });
32371
32514
  }
@@ -32486,7 +32629,8 @@ var ServerBridgeContext = React117.createContext(null);
32486
32629
  function useServerBridge() {
32487
32630
  const ctx = React117.useContext(ServerBridgeContext);
32488
32631
  if (!ctx) {
32489
- return { connected: false, sendEvent: async () => [] };
32632
+ const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
32633
+ return { connected: false, sendEvent: async () => ({ effects: [], meta: emptyMeta }) };
32490
32634
  }
32491
32635
  return ctx;
32492
32636
  }
@@ -32518,7 +32662,8 @@ function ServerBridgeProvider({
32518
32662
  }
32519
32663
  }, [serverUrl]);
32520
32664
  const sendEvent = React117.useCallback(async (orbitalName, event, payload) => {
32521
- if (!connected) return [];
32665
+ const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
32666
+ if (!connected) return { effects: [], meta: emptyMeta };
32522
32667
  try {
32523
32668
  const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
32524
32669
  method: "POST",
@@ -32527,8 +32672,19 @@ function ServerBridgeProvider({
32527
32672
  });
32528
32673
  const result = await res.json();
32529
32674
  const effects = [];
32675
+ const responseData = result.data || {};
32676
+ const dataEntities = {};
32677
+ for (const [entityName, records] of Object.entries(responseData)) {
32678
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
32679
+ }
32680
+ const meta = {
32681
+ success: !!result.success,
32682
+ clientEffects: result.clientEffects?.length ?? 0,
32683
+ dataEntities,
32684
+ emittedEvents: result.emittedEvents?.map((e) => e.event) ?? [],
32685
+ error: result.error
32686
+ };
32530
32687
  if (result.success) {
32531
- const responseData = result.data || {};
32532
32688
  if (result.clientEffects) {
32533
32689
  for (const effect of result.clientEffects) {
32534
32690
  const arr = effect;
@@ -32554,10 +32710,10 @@ function ServerBridgeProvider({
32554
32710
  } else if (result.error) {
32555
32711
  console.error("[ServerBridge] Event error:", result.error);
32556
32712
  }
32557
- return effects;
32713
+ return { effects, meta };
32558
32714
  } catch (err) {
32559
32715
  console.error("[ServerBridge] Event send failed:", err);
32560
- return [];
32716
+ return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
32561
32717
  }
32562
32718
  }, [connected, serverUrl, eventBus]);
32563
32719
  React117.useEffect(() => {
@@ -32618,7 +32774,8 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
32618
32774
  const onEventProcessed = React117.useCallback(async (event, payload) => {
32619
32775
  if (!bridge.connected || !orbitalNames?.length) return;
32620
32776
  for (const name of orbitalNames) {
32621
- const effects = await bridge.sendEvent(name, event, payload);
32777
+ const { effects, meta } = await bridge.sendEvent(name, event, payload);
32778
+ recordServerResponse(name, event, meta);
32622
32779
  for (const eff of effects) {
32623
32780
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
32624
32781
  slotsActions.setSlotPatterns(
@@ -32652,7 +32809,8 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
32652
32809
  initSentRef.current = true;
32653
32810
  (async () => {
32654
32811
  for (const name of orbitalNames) {
32655
- const effects = await bridge.sendEvent(name, "INIT", {});
32812
+ const { effects, meta } = await bridge.sendEvent(name, "INIT", {});
32813
+ recordServerResponse(name, "INIT", meta);
32656
32814
  const effectTraces = [
32657
32815
  { type: "fetch", args: [], status: "executed" },
32658
32816
  ...effects.map((eff) => ({