@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.
@@ -821,6 +821,36 @@ function recordTransition(trace) {
821
821
  function getTransitions() {
822
822
  return [...getState().transitions];
823
823
  }
824
+ function recordServerResponse(orbitalName, event, response) {
825
+ const serverResponse = {
826
+ ...response,
827
+ orbitalName,
828
+ timestamp: Date.now()
829
+ };
830
+ const entry = {
831
+ id: `srv-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
832
+ traitName: `server:${orbitalName}`,
833
+ from: "server",
834
+ to: "server",
835
+ event,
836
+ effects: [],
837
+ serverResponse,
838
+ timestamp: Date.now()
839
+ };
840
+ getState().transitions.push(entry);
841
+ if (getState().transitions.length > MAX_TRANSITIONS) {
842
+ getState().transitions.shift();
843
+ }
844
+ if (!response.success && response.error) {
845
+ registerCheck(
846
+ `server-error-${entry.id}`,
847
+ `Server error for ${orbitalName}:${event}`,
848
+ "fail",
849
+ response.error
850
+ );
851
+ }
852
+ notifyListeners2();
853
+ }
824
854
  function getBridgeHealth() {
825
855
  const bh = getState().bridgeHealth;
826
856
  return bh ? { ...bh } : null;
@@ -27904,6 +27934,119 @@ function EventDispatcherTab({ traits: traits2, schema }) {
27904
27934
  ] });
27905
27935
  }
27906
27936
  EventDispatcherTab.displayName = "EventDispatcherTab";
27937
+ function ServerResponseRow({ sr }) {
27938
+ const entityEntries = Object.entries(sr.dataEntities);
27939
+ return /* @__PURE__ */ jsxs("div", { className: "ml-4 pl-2 border-l border-purple-700/50 py-0.5 text-[10px] font-mono", children: [
27940
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
27941
+ /* @__PURE__ */ jsxs("span", { className: sr.success ? "text-green-400" : "text-red-400", children: [
27942
+ sr.success ? "\u2713" : "\u2717",
27943
+ " server"
27944
+ ] }),
27945
+ /* @__PURE__ */ jsx("span", { className: "text-purple-300", children: sr.orbitalName }),
27946
+ sr.clientEffects > 0 && /* @__PURE__ */ jsxs("span", { className: "px-1 rounded bg-purple-900/50 text-purple-300", children: [
27947
+ sr.clientEffects,
27948
+ " clientEffect",
27949
+ sr.clientEffects !== 1 ? "s" : ""
27950
+ ] }),
27951
+ sr.emittedEvents.length > 0 && /* @__PURE__ */ jsxs("span", { className: "px-1 rounded bg-blue-900/50 text-blue-300", children: [
27952
+ "emit: ",
27953
+ sr.emittedEvents.join(", ")
27954
+ ] }),
27955
+ sr.error && /* @__PURE__ */ jsx("span", { className: "px-1 rounded bg-red-900/50 text-red-400 truncate max-w-[300px]", children: sr.error })
27956
+ ] }),
27957
+ entityEntries.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mt-0.5", children: entityEntries.map(([name, count]) => /* @__PURE__ */ jsxs("span", { className: "px-1 rounded bg-gray-800 text-gray-300", children: [
27958
+ name,
27959
+ ": ",
27960
+ count,
27961
+ " row",
27962
+ count !== 1 ? "s" : ""
27963
+ ] }, name)) })
27964
+ ] });
27965
+ }
27966
+ function TransitionRow({ trace }) {
27967
+ const isServerEntry = !!trace.serverResponse && trace.traitName.startsWith("server:");
27968
+ const hasFailedEffects = trace.effects.some((e) => e.status === "failed");
27969
+ if (isServerEntry && trace.serverResponse) {
27970
+ return /* @__PURE__ */ jsxs("div", { className: "py-0.5 border-b border-gray-800 last:border-0", children: [
27971
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs font-mono", children: [
27972
+ /* @__PURE__ */ jsx("span", { className: "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0 bg-purple-500" }),
27973
+ /* @__PURE__ */ jsx(Badge, { variant: "warning", size: "sm", className: "flex-shrink-0", children: trace.event }),
27974
+ /* @__PURE__ */ jsx("span", { className: "text-purple-400 flex-shrink-0", children: "server response" })
27975
+ ] }),
27976
+ /* @__PURE__ */ jsx(ServerResponseRow, { sr: trace.serverResponse })
27977
+ ] });
27978
+ }
27979
+ return /* @__PURE__ */ jsxs("div", { className: "py-0.5 border-b border-gray-800 last:border-0", children: [
27980
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs font-mono", children: [
27981
+ /* @__PURE__ */ jsx("span", { className: cn(
27982
+ "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0",
27983
+ hasFailedEffects ? "bg-red-500" : "bg-green-500"
27984
+ ) }),
27985
+ /* @__PURE__ */ jsx(Badge, { variant: "info", size: "sm", className: "flex-shrink-0", children: trace.event }),
27986
+ /* @__PURE__ */ jsx("span", { className: "text-gray-300 flex-shrink-0", children: trace.traitName }),
27987
+ /* @__PURE__ */ jsxs("span", { className: "text-gray-400 flex-shrink-0", children: [
27988
+ trace.from,
27989
+ " ",
27990
+ "\u2192",
27991
+ " ",
27992
+ trace.to
27993
+ ] })
27994
+ ] }),
27995
+ trace.effects.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 ml-6 mt-0.5", children: trace.effects.map((eff, i) => /* @__PURE__ */ jsxs("span", { className: cn(
27996
+ "px-1 rounded text-[10px]",
27997
+ 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"
27998
+ ), children: [
27999
+ eff.status === "executed" ? "\u2713" : eff.status === "failed" ? "\u2717" : "-",
28000
+ " ",
28001
+ eff.type,
28002
+ eff.args.length > 0 && /* @__PURE__ */ jsx("span", { className: "text-gray-500 ml-0.5", children: JSON.stringify(eff.args).slice(0, 40) })
28003
+ ] }, i)) }),
28004
+ trace.serverResponse && /* @__PURE__ */ jsx(ServerResponseRow, { sr: trace.serverResponse })
28005
+ ] });
28006
+ }
28007
+ function VerifyModePanel({
28008
+ className,
28009
+ failedChecks,
28010
+ transitions,
28011
+ traitStates,
28012
+ serverCount,
28013
+ localCount
28014
+ }) {
28015
+ const scrollRef = React117.useRef(null);
28016
+ const prevCountRef = React117.useRef(0);
28017
+ React117.useEffect(() => {
28018
+ if (transitions.length > prevCountRef.current && scrollRef.current) {
28019
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
28020
+ }
28021
+ prevCountRef.current = transitions.length;
28022
+ }, [transitions.length]);
28023
+ return /* @__PURE__ */ jsxs(
28024
+ "div",
28025
+ {
28026
+ className: cn(
28027
+ "runtime-debugger runtime-debugger--verify",
28028
+ "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",
28029
+ className
28030
+ ),
28031
+ "data-testid": "debugger-verify",
28032
+ children: [
28033
+ /* @__PURE__ */ 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: [
28034
+ /* @__PURE__ */ jsx(Badge, { variant: failedChecks > 0 ? "danger" : "success", size: "sm", children: failedChecks > 0 ? `${failedChecks} fail` : "OK" }),
28035
+ /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
28036
+ localCount,
28037
+ " local"
28038
+ ] }),
28039
+ /* @__PURE__ */ jsxs("span", { className: "text-purple-400", children: [
28040
+ serverCount,
28041
+ " server"
28042
+ ] }),
28043
+ traitStates && /* @__PURE__ */ jsx("span", { className: "text-cyan-400 truncate max-w-[400px]", children: traitStates })
28044
+ ] }),
28045
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-2 py-1", children: transitions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-gray-500 text-xs font-mono py-2 text-center", children: "Waiting for transitions..." }) : /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: transitions.map((trace) => /* @__PURE__ */ jsx(TransitionRow, { trace }, trace.id)) }) }) })
28046
+ ]
28047
+ }
28048
+ );
28049
+ }
27907
28050
  function RuntimeDebugger({
27908
28051
  position = "bottom-right",
27909
28052
  defaultCollapsed = true,
@@ -28053,51 +28196,17 @@ function RuntimeDebugger({
28053
28196
  }
28054
28197
  if (mode === "verify") {
28055
28198
  const traitStates = debugData.traits.map((t) => `${t.name}:${t.currentState}`).join(" | ");
28056
- return /* @__PURE__ */ jsxs(
28057
- "div",
28199
+ const serverEntries = verification.transitions.filter((t) => t.serverResponse);
28200
+ const localEntries = verification.transitions.filter((t) => !t.serverResponse);
28201
+ return /* @__PURE__ */ jsx(
28202
+ VerifyModePanel,
28058
28203
  {
28059
- className: cn(
28060
- "runtime-debugger runtime-debugger--verify",
28061
- "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",
28062
- className
28063
- ),
28064
- "data-testid": "debugger-verify",
28065
- children: [
28066
- /* @__PURE__ */ 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: [
28067
- /* @__PURE__ */ jsx(Badge, { variant: failedChecks > 0 ? "danger" : "success", size: "sm", children: failedChecks > 0 ? `${failedChecks} fail` : "OK" }),
28068
- /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
28069
- verification.transitions.length,
28070
- " transitions"
28071
- ] }),
28072
- traitStates && /* @__PURE__ */ jsx("span", { className: "text-cyan-400 truncate max-w-[400px]", children: traitStates })
28073
- ] }),
28074
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx("div", { className: "px-2 py-1", children: verification.transitions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-gray-500 text-xs font-mono py-2 text-center", children: "Waiting for transitions..." }) : /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: verification.transitions.map((trace) => {
28075
- const hasFailedEffects = trace.effects.some((e) => e.status === "failed");
28076
- return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs font-mono py-0.5 border-b border-gray-800 last:border-0", children: [
28077
- /* @__PURE__ */ jsx("span", { className: cn(
28078
- "mt-1.5 w-1.5 h-1.5 rounded-full flex-shrink-0",
28079
- hasFailedEffects ? "bg-red-500" : "bg-green-500"
28080
- ) }),
28081
- /* @__PURE__ */ jsx(Badge, { variant: "info", size: "sm", className: "flex-shrink-0", children: trace.event }),
28082
- /* @__PURE__ */ jsxs("span", { className: "text-gray-400 flex-shrink-0", children: [
28083
- trace.from,
28084
- " ",
28085
- "\u2192",
28086
- " ",
28087
- trace.to
28088
- ] }),
28089
- /* @__PURE__ */ jsx("span", { className: "flex flex-wrap gap-1 ml-1", children: trace.effects.map((eff, i) => /* @__PURE__ */ jsxs("span", { className: cn(
28090
- "px-1 rounded text-[10px]",
28091
- 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"
28092
- ), children: [
28093
- eff.status === "executed" ? "\u2713" : eff.status === "failed" ? "\u2717" : "-",
28094
- " ",
28095
- eff.type,
28096
- eff.args.length > 0 && /* @__PURE__ */ jsx("span", { className: "text-gray-500 ml-0.5", children: JSON.stringify(eff.args).slice(0, 30) })
28097
- ] }, i)) })
28098
- ] }, trace.id);
28099
- }) }) }) })
28100
- ]
28204
+ className,
28205
+ failedChecks,
28206
+ transitions: verification.transitions,
28207
+ traitStates,
28208
+ serverCount: serverEntries.length,
28209
+ localCount: localEntries.length
28101
28210
  }
28102
28211
  );
28103
28212
  }
@@ -32305,17 +32414,42 @@ function VerificationProvider({
32305
32414
  const pending = pendingRef.current.get(key);
32306
32415
  pendingRef.current.delete(key);
32307
32416
  const newState = payload["newState"] ?? payload["state"] ?? "unknown";
32308
- const effects = Array.isArray(payload["clientEffects"]) ? payload["clientEffects"].map((e) => ({
32417
+ const clientEffectsArr = Array.isArray(payload["clientEffects"]) ? payload["clientEffects"] : [];
32418
+ const effects = clientEffectsArr.map((e) => ({
32309
32419
  type: String(e["type"] ?? "unknown"),
32310
32420
  args: Array.isArray(e["args"]) ? e["args"] : [],
32311
32421
  status: "executed"
32312
- })) : [];
32422
+ }));
32423
+ const effectResults = Array.isArray(payload["effectResults"]) ? payload["effectResults"] : [];
32424
+ for (const er of effectResults) {
32425
+ effects.push({
32426
+ type: String(er["type"] ?? er["effect"] ?? "server-effect"),
32427
+ args: [er["entity"] ?? er["service"] ?? ""].filter(Boolean),
32428
+ status: er["error"] ? "failed" : "executed",
32429
+ error: er["error"]
32430
+ });
32431
+ }
32432
+ const dataEntities = {};
32433
+ const responseData = payload["data"];
32434
+ if (responseData && typeof responseData === "object") {
32435
+ for (const [entityName, records] of Object.entries(responseData)) {
32436
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
32437
+ }
32438
+ }
32313
32439
  recordTransition({
32314
32440
  traitName: parsed.traitName,
32315
32441
  from: pending?.from ?? payload["currentState"] ?? newState,
32316
32442
  to: newState,
32317
32443
  event: parsed.event,
32318
32444
  effects,
32445
+ serverResponse: {
32446
+ orbitalName: parsed.traitName,
32447
+ success: true,
32448
+ clientEffects: clientEffectsArr.length,
32449
+ dataEntities,
32450
+ emittedEvents: [],
32451
+ timestamp: Date.now()
32452
+ },
32319
32453
  timestamp: Date.now()
32320
32454
  });
32321
32455
  } else if (parsed.kind === "error" && parsed.event) {
@@ -32336,6 +32470,15 @@ function VerificationProvider({
32336
32470
  status: "failed",
32337
32471
  error: errorMsg
32338
32472
  }],
32473
+ serverResponse: {
32474
+ orbitalName: parsed.traitName,
32475
+ success: false,
32476
+ clientEffects: 0,
32477
+ dataEntities: {},
32478
+ emittedEvents: [],
32479
+ error: errorMsg,
32480
+ timestamp: Date.now()
32481
+ },
32339
32482
  timestamp: Date.now()
32340
32483
  });
32341
32484
  }
@@ -32456,7 +32599,8 @@ var ServerBridgeContext = createContext(null);
32456
32599
  function useServerBridge() {
32457
32600
  const ctx = useContext(ServerBridgeContext);
32458
32601
  if (!ctx) {
32459
- return { connected: false, sendEvent: async () => [] };
32602
+ const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
32603
+ return { connected: false, sendEvent: async () => ({ effects: [], meta: emptyMeta }) };
32460
32604
  }
32461
32605
  return ctx;
32462
32606
  }
@@ -32488,7 +32632,8 @@ function ServerBridgeProvider({
32488
32632
  }
32489
32633
  }, [serverUrl]);
32490
32634
  const sendEvent = useCallback(async (orbitalName, event, payload) => {
32491
- if (!connected) return [];
32635
+ const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
32636
+ if (!connected) return { effects: [], meta: emptyMeta };
32492
32637
  try {
32493
32638
  const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
32494
32639
  method: "POST",
@@ -32497,8 +32642,19 @@ function ServerBridgeProvider({
32497
32642
  });
32498
32643
  const result = await res.json();
32499
32644
  const effects = [];
32645
+ const responseData = result.data || {};
32646
+ const dataEntities = {};
32647
+ for (const [entityName, records] of Object.entries(responseData)) {
32648
+ dataEntities[entityName] = Array.isArray(records) ? records.length : 0;
32649
+ }
32650
+ const meta = {
32651
+ success: !!result.success,
32652
+ clientEffects: result.clientEffects?.length ?? 0,
32653
+ dataEntities,
32654
+ emittedEvents: result.emittedEvents?.map((e) => e.event) ?? [],
32655
+ error: result.error
32656
+ };
32500
32657
  if (result.success) {
32501
- const responseData = result.data || {};
32502
32658
  if (result.clientEffects) {
32503
32659
  for (const effect of result.clientEffects) {
32504
32660
  const arr = effect;
@@ -32524,10 +32680,10 @@ function ServerBridgeProvider({
32524
32680
  } else if (result.error) {
32525
32681
  console.error("[ServerBridge] Event error:", result.error);
32526
32682
  }
32527
- return effects;
32683
+ return { effects, meta };
32528
32684
  } catch (err) {
32529
32685
  console.error("[ServerBridge] Event send failed:", err);
32530
- return [];
32686
+ return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
32531
32687
  }
32532
32688
  }, [connected, serverUrl, eventBus]);
32533
32689
  useEffect(() => {
@@ -32588,7 +32744,8 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
32588
32744
  const onEventProcessed = useCallback(async (event, payload) => {
32589
32745
  if (!bridge.connected || !orbitalNames?.length) return;
32590
32746
  for (const name of orbitalNames) {
32591
- const effects = await bridge.sendEvent(name, event, payload);
32747
+ const { effects, meta } = await bridge.sendEvent(name, event, payload);
32748
+ recordServerResponse(name, event, meta);
32592
32749
  for (const eff of effects) {
32593
32750
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
32594
32751
  slotsActions.setSlotPatterns(
@@ -32622,7 +32779,8 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
32622
32779
  initSentRef.current = true;
32623
32780
  (async () => {
32624
32781
  for (const name of orbitalNames) {
32625
- const effects = await bridge.sendEvent(name, "INIT", {});
32782
+ const { effects, meta } = await bridge.sendEvent(name, "INIT", {});
32783
+ recordServerResponse(name, "INIT", meta);
32626
32784
  const effectTraces = [
32627
32785
  { type: "fetch", args: [], status: "executed" },
32628
32786
  ...effects.map((eff) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "2.27.5",
3
+ "version": "2.28.1",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "main": "./dist/components/index.js",