causal-inspector 0.1.4 → 0.1.6

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.
@@ -1,4 +1,4 @@
1
- import { INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, } from "../queries";
1
+ import { INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, } from "../queries";
2
2
  /**
3
3
  * Query engine — fetches data in response to state transitions.
4
4
  *
@@ -69,11 +69,12 @@ export const createQueryEngine = (transport) => {
69
69
  };
70
70
  const fetchFlowMetadata = async (correlationId) => {
71
71
  try {
72
- const [descData, snapshotData, aggTimelineData, outcomeData] = await Promise.all([
72
+ const [descData, snapshotData, aggTimelineData, outcomeData, attemptData] = await Promise.all([
73
73
  transport.query(INSPECTOR_REACTOR_DESCRIPTIONS, { correlationId }),
74
74
  transport.query(INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, { correlationId }),
75
75
  transport.query(INSPECTOR_AGGREGATE_TIMELINE, { correlationId }),
76
76
  transport.query(INSPECTOR_REACTOR_OUTCOMES, { correlationId }),
77
+ transport.query(INSPECTOR_REACTOR_ATTEMPTS, { correlationId }),
77
78
  ]);
78
79
  if (activeFlowCorrelationId !== correlationId)
79
80
  return; // stale
@@ -105,6 +106,13 @@ export const createQueryEngine = (transport) => {
105
106
  outcomes: outcomeData.inspectorReactorOutcomes,
106
107
  },
107
108
  });
109
+ dispatch({
110
+ type: "events/attempts_loaded",
111
+ payload: {
112
+ correlationId,
113
+ attempts: attemptData.inspectorReactorAttempts,
114
+ },
115
+ });
108
116
  }
109
117
  catch (e) {
110
118
  console.error("[causal-inspector] fetch flow metadata failed:", e);
@@ -119,23 +127,18 @@ export const createQueryEngine = (transport) => {
119
127
  console.error("[causal-inspector] fetch logs failed:", e);
120
128
  }
121
129
  };
122
- let correlationCursor = null;
123
130
  const fetchCorrelations = async (opts) => {
124
- const append = opts?.append ?? false;
125
- const cursor = append ? correlationCursor : undefined;
126
131
  try {
127
132
  const data = await transport.query(INSPECTOR_CORRELATIONS, {
128
133
  search: opts?.search || undefined,
129
- limit: 50,
130
- cursor: cursor || undefined,
134
+ limit: 100,
131
135
  });
132
- correlationCursor = data.inspectorCorrelations.nextCursor;
133
136
  dispatch({
134
137
  type: "events/correlations_loaded",
135
138
  payload: {
136
- correlations: data.inspectorCorrelations.correlations,
137
- hasMore: data.inspectorCorrelations.nextCursor != null,
138
- append,
139
+ correlations: data.inspectorCorrelations,
140
+ hasMore: false,
141
+ append: false,
139
142
  },
140
143
  });
141
144
  }
@@ -229,10 +232,9 @@ export const createQueryEngine = (transport) => {
229
232
  fetchEvents();
230
233
  break;
231
234
  case "ui/load_more_correlations_requested":
232
- fetchCorrelations({ append: true });
235
+ fetchCorrelations();
233
236
  break;
234
237
  case "ui/correlations_requested":
235
- correlationCursor = null;
236
238
  fetchCorrelations({ search: event.payload.search });
237
239
  stopCorrelationPolling();
238
240
  correlationPollTimer = setInterval(() => fetchCorrelations({ search: event.payload.search }), 5000);
package/dist/events.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { BaseEvent } from "./machine";
2
- import type { InspectorEvent, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, PaneLayout } from "./types";
2
+ import type { InspectorEvent, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, ReactorAttempt, PaneLayout } from "./types";
3
3
  type EventsReceived = BaseEvent<"events/received", InspectorEvent[]>;
4
4
  type SubscriptionConnected = BaseEvent<"events/subscription_connected">;
5
5
  type SubscriptionError = BaseEvent<"events/subscription_error", {
@@ -31,6 +31,10 @@ type OutcomesLoaded = BaseEvent<"events/outcomes_loaded", {
31
31
  correlationId: string;
32
32
  outcomes: ReactorOutcome[];
33
33
  }>;
34
+ type AttemptsLoaded = BaseEvent<"events/attempts_loaded", {
35
+ correlationId: string;
36
+ attempts: ReactorAttempt[];
37
+ }>;
34
38
  type CorrelationsLoaded = BaseEvent<"events/correlations_loaded", {
35
39
  correlations: CorrelationSummary[];
36
40
  hasMore: boolean;
@@ -78,5 +82,5 @@ type LocationChanged = BaseEvent<"location/changed", {
78
82
  correlationId: string | null;
79
83
  handler: string | null;
80
84
  }>;
81
- export type InspectorMachineEvent = EventsReceived | SubscriptionConnected | SubscriptionError | PageLoaded | CausalTreeLoaded | FlowLoaded | LogsLoaded | DescriptionsLoaded | DescriptionSnapshotsLoaded | AggregateTimelineLoaded | OutcomesLoaded | CorrelationsLoaded | EventSelected | EventDeselected | FlowOpened | FlowClosed | FlowNodeSelected | FilterChanged | LoadMoreRequested | LayoutChanged | ScrubberStartChanged | ScrubberEndChanged | ScrubberPlayToggled | ScrubberSpeedChanged | CorrelationsRequested | ReactorDependenciesLoaded | AggregateKeysLoaded | HandlerSelected | LocationChanged | AggregateLifecycleLoaded | AggregateLifecycleRequested | LoadMoreCorrelationsRequested;
85
+ export type InspectorMachineEvent = EventsReceived | SubscriptionConnected | SubscriptionError | PageLoaded | CausalTreeLoaded | FlowLoaded | LogsLoaded | DescriptionsLoaded | DescriptionSnapshotsLoaded | AggregateTimelineLoaded | OutcomesLoaded | AttemptsLoaded | CorrelationsLoaded | EventSelected | EventDeselected | FlowOpened | FlowClosed | FlowNodeSelected | FilterChanged | LoadMoreRequested | LayoutChanged | ScrubberStartChanged | ScrubberEndChanged | ScrubberPlayToggled | ScrubberSpeedChanged | CorrelationsRequested | ReactorDependenciesLoaded | AggregateKeysLoaded | HandlerSelected | LocationChanged | AggregateLifecycleLoaded | AggregateLifecycleRequested | LoadMoreCorrelationsRequested;
82
86
  export {};
package/dist/index.d.ts CHANGED
@@ -5,10 +5,10 @@ export { reducer } from "./reducer";
5
5
  export { initialState } from "./state";
6
6
  export type { InspectorState } from "./state";
7
7
  export type { InspectorMachineEvent } from "./events";
8
- export type { InspectorEvent, InspectorEventsPage, InspectorCausalTree, InspectorCausalFlow, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, Block, ReactorLog, ReactorDescription, ReactorDescriptionSnapshot, AggregateStateEntry, AggregateTimelineEntry, ReactorOutcome, FilterState, LogsFilter, FlowSelection, PaneLayout, } from "./types";
8
+ export type { InspectorEvent, InspectorEventsPage, InspectorCausalTree, InspectorCausalFlow, CorrelationSummary, ReactorDependency, AggregateLifecycleEntry, Block, ReactorLog, ReactorDescription, ReactorDescriptionSnapshot, AggregateStateEntry, AggregateTimelineEntry, ReactorOutcome, ReactorAttempt, FilterState, LogsFilter, FlowSelection, PaneLayout, } from "./types";
9
9
  export { createInspectorEngine, createSubscriptionEngine, createQueryEngine, createStorageEngine, } from "./engines";
10
10
  export type { InspectorTransport, SubscriptionTransport, QueryTransport, StorageTransport, } from "./engines";
11
- export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
11
+ export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
12
12
  export { eventHue, eventBg, eventBorder, eventTextColor, LOG_LEVEL_COLORS, } from "./theme";
13
13
  export { formatTs, compactPayload, copyToClipboard } from "./utils";
14
14
  export { CausalInspector } from "./CausalInspector";
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { initialState } from "./state";
7
7
  // ── Engines ──
8
8
  export { createInspectorEngine, createSubscriptionEngine, createQueryEngine, createStorageEngine, } from "./engines";
9
9
  // ── Queries ──
10
- export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
10
+ export { EVENTS_SUBSCRIPTION, INSPECTOR_EVENTS, INSPECTOR_CAUSAL_TREE, INSPECTOR_CAUSAL_FLOW, INSPECTOR_CORRELATIONS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_CORRELATION, INSPECTOR_REACTOR_DESCRIPTIONS, INSPECTOR_REACTOR_DESCRIPTION_SNAPSHOTS, INSPECTOR_AGGREGATE_TIMELINE, INSPECTOR_REACTOR_OUTCOMES, INSPECTOR_REACTOR_ATTEMPTS, INSPECTOR_REACTOR_DEPENDENCIES, INSPECTOR_AGGREGATE_KEYS, INSPECTOR_AGGREGATE_LIFECYCLE, } from "./queries";
11
11
  // ── Theme ──
12
12
  export { eventHue, eventBg, eventBorder, eventTextColor, LOG_LEVEL_COLORS, } from "./theme";
13
13
  // ── Utilities ──
@@ -4,7 +4,7 @@ import { ReactFlow, Background, Controls, useReactFlow, Handle, MarkerType, Posi
4
4
  import dagre from "@dagrejs/dagre";
5
5
  import { useSelector, useDispatch } from "../machine";
6
6
  import { eventBg, eventBorder, eventTextColor } from "../theme";
7
- import { Filter } from "lucide-react";
7
+ import { Filter, ArrowRight, ArrowDown } from "lucide-react";
8
8
  import { inScrubberRange } from "../utils";
9
9
  /* eslint-disable-next-line @typescript-eslint/no-redeclare -- shadowing the tree-pane ReactorNode on purpose */
10
10
  const NODE_WIDTH = 180;
@@ -60,6 +60,7 @@ const ReactorNode = memo(({ data }) => {
60
60
  const d = data;
61
61
  const blocks = d.blocks;
62
62
  const outcome = d.outcome;
63
+ const dir = d.direction ?? "LR";
63
64
  const hasBlocks = blocks && blocks.length > 0;
64
65
  const borderColor = STATUS_BORDER[outcome?.status ?? "pending"] ?? "#2a2a35";
65
66
  const isRunning = outcome?.status === "running";
@@ -79,10 +80,11 @@ const ReactorNode = memo(({ data }) => {
79
80
  boxShadow: isRunning
80
81
  ? `0 0 12px ${borderColor}40`
81
82
  : "0 2px 8px rgba(0, 0, 0, 0.3)",
82
- }, children: [_jsx(Handle, { type: "target", position: Position.Left, style: { visibility: "hidden" } }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", direction: "rtl", textAlign: "left" }, children: d.label }), duration && _jsx("span", { style: { fontSize: 9, color: "#60608a", fontStyle: "normal", whiteSpace: "nowrap", flexShrink: 0 }, children: duration })] }), hasBlocks && blocks.map((block, i) => _jsx(BlockRenderer, { block: block }, i)), outcome?.status === "error" && outcome.error && (_jsx("div", { style: { fontSize: 9, color: "#ef4444", marginTop: 4 }, children: outcome.error })), _jsx(Handle, { type: "source", position: Position.Right, style: { visibility: "hidden" } })] }));
83
+ }, children: [_jsx(Handle, { type: "target", position: dir === "LR" ? Position.Left : Position.Top, style: { visibility: "hidden" } }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", direction: "rtl", textAlign: "left" }, children: d.label }), duration && _jsx("span", { style: { fontSize: 9, color: "#60608a", fontStyle: "normal", whiteSpace: "nowrap", flexShrink: 0 }, children: duration })] }), hasBlocks && blocks.map((block, i) => _jsx(BlockRenderer, { block: block }, i)), outcome?.status === "error" && outcome.error && (_jsx("div", { style: { fontSize: 9, color: "#ef4444", marginTop: 4 }, children: outcome.error })), _jsx(Handle, { type: "source", position: dir === "LR" ? Position.Right : Position.Bottom, style: { visibility: "hidden" } })] }));
83
84
  });
84
85
  const EventNode = memo(({ data }) => {
85
86
  const d = data;
87
+ const dir = d.direction ?? "LR";
86
88
  return (_jsxs("div", { style: {
87
89
  background: eventBg(d.eventName),
88
90
  border: `1px solid ${eventBorder(d.eventName)}`,
@@ -99,10 +101,10 @@ const EventNode = memo(({ data }) => {
99
101
  boxShadow: `0 2px 8px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.04)`,
100
102
  fontWeight: 500,
101
103
  letterSpacing: "0.01em",
102
- }, children: [_jsx(Handle, { type: "target", position: Position.Left, style: { visibility: "hidden" } }), d.label, _jsx(Handle, { type: "source", position: Position.Right, style: { visibility: "hidden" } })] }));
104
+ }, children: [_jsx(Handle, { type: "target", position: dir === "LR" ? Position.Left : Position.Top, style: { visibility: "hidden" } }), d.label, _jsx(Handle, { type: "source", position: dir === "LR" ? Position.Right : Position.Bottom, style: { visibility: "hidden" } })] }));
103
105
  });
104
106
  const nodeTypes = { reactor: ReactorNode, event: EventNode };
105
- function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
107
+ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors, direction = "LR") {
106
108
  // Group events by type name only (merge across reactors)
107
109
  const eventGroups = new Map();
108
110
  const reactorIds = new Set();
@@ -154,9 +156,10 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
154
156
  label: group.count > 1 ? `${group.name} (${group.count})` : group.name,
155
157
  nodeKind: "event-type",
156
158
  eventName: group.name,
159
+ direction,
157
160
  },
158
- sourcePosition: Position.Right,
159
- targetPosition: Position.Left,
161
+ sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
162
+ targetPosition: direction === "LR" ? Position.Left : Position.Top,
160
163
  });
161
164
  }
162
165
  // Reactor nodes
@@ -169,9 +172,9 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
169
172
  id: `hdl:${reactorId}`,
170
173
  type: "reactor",
171
174
  position: { x: 0, y: 0 },
172
- data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome },
173
- sourcePosition: Position.Right,
174
- targetPosition: Position.Left,
175
+ data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome, direction },
176
+ sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
177
+ targetPosition: direction === "LR" ? Position.Left : Position.Top,
175
178
  });
176
179
  }
177
180
  const arrowMarker = { type: MarkerType.ArrowClosed, color: "#3a3a4a", width: 14, height: 14 };
@@ -226,9 +229,9 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
226
229
  id: `hdl:${reactorId}`,
227
230
  type: "reactor",
228
231
  position: { x: 0, y: 0 },
229
- data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome },
230
- sourcePosition: Position.Bottom,
231
- targetPosition: Position.Top,
232
+ data: { label: reactorId, nodeKind: "reactor", reactorId, blocks, outcome, direction },
233
+ sourcePosition: direction === "LR" ? Position.Right : Position.Bottom,
234
+ targetPosition: direction === "LR" ? Position.Left : Position.Top,
232
235
  });
233
236
  const isPending = outcome.status === "pending" || outcome.status === "running";
234
237
  for (const eventId of outcome.triggeringEventIds ?? []) {
@@ -250,7 +253,7 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors) {
250
253
  }
251
254
  }
252
255
  }
253
- return layoutGraph(nodes, edges);
256
+ return layoutGraph(nodes, edges, direction);
254
257
  }
255
258
  function estimateReactorHeight(data) {
256
259
  if (data.nodeKind !== "reactor")
@@ -274,10 +277,10 @@ function estimateReactorHeight(data) {
274
277
  h += 14;
275
278
  return h;
276
279
  }
277
- function layoutGraph(nodes, edges) {
280
+ function layoutGraph(nodes, edges, direction = "LR") {
278
281
  const g = new dagre.graphlib.Graph();
279
282
  g.setDefaultEdgeLabel(() => ({}));
280
- g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: 80 });
283
+ g.setGraph({ rankdir: direction, nodesep: 40, ranksep: 80 });
281
284
  const heights = new Map();
282
285
  for (const node of nodes) {
283
286
  const isReactor = node.id.startsWith("hdl:");
@@ -424,7 +427,7 @@ function ReactorFilter({ allReactorIds, hiddenReactors, setHiddenReactors }) {
424
427
  ? allReactorIds.filter(id => id.toLowerCase().includes(filter.toLowerCase()))
425
428
  : allReactorIds;
426
429
  const hiddenCount = hiddenReactors.size;
427
- return (_jsxs("div", { ref: containerRef, className: "relative", children: [_jsxs("button", { onClick: () => setOpen(v => !v), className: "text-[10px] text-muted-foreground/60 hover:text-foreground px-2 py-1 rounded-md border border-border hover:border-indigo-500/30 transition-all duration-150", children: [_jsx(Filter, { size: 11, className: "inline mr-1 -mt-px" }), hiddenCount > 0 ? `${hiddenCount} hidden` : "Filter"] }), open && (_jsxs("div", { className: "absolute top-full right-0 mt-1 z-50 border border-border rounded-lg min-w-[240px]", style: { background: "rgba(17, 17, 22, 0.95)", backdropFilter: "blur(12px)", boxShadow: "0 8px 32px rgba(0, 0, 0, 0.5)" }, children: [_jsx("div", { className: "px-3 py-2 border-b border-border", children: _jsx("input", { autoFocus: true, type: "text", value: filter, onChange: e => setFilter(e.target.value), placeholder: "Search reactors...", className: "w-full text-xs bg-transparent border-none outline-none text-foreground placeholder:text-muted-foreground/50" }) }), _jsxs("div", { className: "max-h-64 overflow-y-auto py-1", children: [filtered.map(id => (_jsxs("label", { className: "flex items-center gap-2 px-3 py-1.5 hover:bg-white/[0.03] cursor-pointer transition-colors", children: [_jsx("input", { type: "checkbox", checked: !hiddenReactors.has(id), onChange: () => toggle(id), className: "rounded border-border accent-indigo-500" }), _jsx("span", { className: "text-[11px] font-mono text-foreground/80 truncate", children: id })] }, id))), filtered.length === 0 && (_jsx("div", { className: "text-xs text-muted-foreground/50 px-3 py-2", children: "No matches" }))] })] }))] }));
430
+ return (_jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [_jsxs("button", { onClick: () => setOpen(v => !v), className: "ci-toolbar-btn", children: [_jsx(Filter, { size: 11, style: { display: "inline", marginRight: 4, verticalAlign: "-1px" } }), hiddenCount > 0 ? `${hiddenCount} hidden` : "Filter"] }), open && (_jsxs("div", { className: "ci-dropdown", style: { position: "absolute", top: "100%", right: 0, marginTop: 4, zIndex: 50, minWidth: 240 }, children: [_jsx("div", { style: { padding: "8px 12px", borderBottom: "1px solid var(--ci-border)" }, children: _jsx("input", { autoFocus: true, type: "text", value: filter, onChange: e => setFilter(e.target.value), placeholder: "Search reactors...", style: { width: "100%", fontSize: 12, background: "transparent", border: "none", outline: "none", color: "var(--ci-text)" } }) }), _jsxs("div", { style: { maxHeight: 256, overflowY: "auto", padding: "4px 0" }, children: [filtered.map(id => (_jsxs("label", { className: "ci-row", style: { gap: 8, padding: "6px 12px", cursor: "pointer", transition: "background 150ms" }, children: [_jsx("input", { type: "checkbox", checked: !hiddenReactors.has(id), onChange: () => toggle(id), style: { accentColor: "var(--ci-accent)" } }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.8 }, children: id })] }, id))), filtered.length === 0 && (_jsx("div", { style: { fontSize: 12, color: "var(--ci-text-muted)", opacity: 0.5, padding: "8px 12px" }, children: "No matches" }))] })] }))] }));
428
431
  }
429
432
  export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
430
433
  const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
@@ -460,6 +463,7 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
460
463
  return map;
461
464
  }, [outcomesMap, flowCorrelationId]);
462
465
  const [hiddenReactors, setHiddenReactors] = useState(() => defaultHiddenReactors ?? new Set());
466
+ const [direction, setDirection] = useState("LR");
463
467
  const allReactorIds = useMemo(() => {
464
468
  const ids = new Set();
465
469
  for (const evt of flowData) {
@@ -475,8 +479,8 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
475
479
  const { nodes: fullNodes, edges: fullEdges } = useMemo(() => {
476
480
  if (!flowData || flowData.length === 0)
477
481
  return { nodes: [], edges: [] };
478
- return buildFlowGraph(flowData, descriptions, outcomes, hiddenReactors);
479
- }, [flowData, descriptions, outcomes, hiddenReactors]);
482
+ return buildFlowGraph(flowData, descriptions, outcomes, hiddenReactors, direction);
483
+ }, [flowData, descriptions, outcomes, hiddenReactors, direction]);
480
484
  // Compute visible IDs when scrubber range is active
481
485
  const visibleIds = useMemo(() => {
482
486
  if (scrubberStart == null && scrubberEnd == null)
@@ -587,10 +591,10 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
587
591
  }, [dispatch]);
588
592
  const onNodesChange = useCallback((_changes) => { }, []);
589
593
  if (!flowCorrelationId) {
590
- return (_jsx("div", { className: "flex items-center justify-center h-full text-xs text-muted-foreground/50 tracking-wide", children: "Select an event to visualize its causal flow" }));
594
+ return (_jsx("div", { className: "ci-empty", children: "Select an event to visualize its causal flow" }));
591
595
  }
592
596
  if (flowLoading) {
593
- return (_jsxs("div", { className: "h-full flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border shrink-0", children: [_jsx("div", { className: "h-3 w-10 bg-muted rounded animate-pulse" }), _jsx("div", { className: "h-3 w-48 bg-muted rounded animate-pulse" })] }), _jsx("div", { className: "flex-1 flex items-center justify-center", children: _jsxs("div", { className: "animate-pulse flex flex-col items-center gap-3", children: [_jsx("div", { className: "h-8 w-40 bg-muted rounded-md" }), _jsx("div", { className: "h-6 w-px bg-muted" }), _jsx("div", { className: "h-6 w-28 bg-muted rounded-full" }), _jsxs("div", { className: "flex items-start gap-8", children: [_jsxs("div", { className: "flex flex-col items-center gap-3", children: [_jsx("div", { className: "h-6 w-px bg-muted" }), _jsx("div", { className: "h-8 w-36 bg-muted rounded-md" })] }), _jsxs("div", { className: "flex flex-col items-center gap-3", children: [_jsx("div", { className: "h-6 w-px bg-muted" }), _jsx("div", { className: "h-8 w-36 bg-muted rounded-md" })] })] })] }) })] }));
597
+ return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row", style: { gap: 8, padding: "6px 12px", borderBottom: "1px solid var(--ci-border)", flexShrink: 0 }, children: [_jsx("div", { className: "ci-muted-bg ci-pulse", style: { height: 12, width: 40 } }), _jsx("div", { className: "ci-muted-bg ci-pulse", style: { height: 12, width: 192 } })] }), _jsx("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }, children: _jsxs("div", { className: "ci-pulse", style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 160, borderRadius: "var(--ci-radius-md)" } }), _jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 112, borderRadius: "var(--ci-radius-full)" } }), _jsxs("div", { style: { display: "flex", alignItems: "start", gap: 32 }, children: [_jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 144, borderRadius: "var(--ci-radius-md)" } })] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 24, width: 1 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 32, width: 144, borderRadius: "var(--ci-radius-md)" } })] })] })] }) })] }));
594
598
  }
595
- return (_jsxs("div", { className: "h-full flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2 border-b border-border shrink-0", style: { background: "rgba(15, 15, 20, 0.6)", backdropFilter: "blur(8px)" }, children: [_jsx("h3", { className: "text-[10px] font-semibold text-muted-foreground/60 uppercase tracking-widest", children: "Flow" }), _jsx("span", { className: "text-[10px] font-mono text-foreground/80 truncate px-1.5 py-0.5 rounded bg-white/[0.03] border border-border", children: flowCorrelationId }), _jsxs("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums", children: [flowData.length, " events \u00B7 ", nodes.length, " nodes"] }), headerExtra, _jsx("div", { className: "ml-auto", children: _jsx(ReactorFilter, { allReactorIds: allReactorIds, hiddenReactors: hiddenReactors, setHiddenReactors: setHiddenReactors }) })] }), _jsx("div", { className: "flex-1 relative", children: _jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, defaultEdgeOptions: { type: "smoothstep" }, onNodesChange: onNodesChange, onNodeClick: onNodeClick, onPaneClick: onPaneClick, minZoom: 0.25, proOptions: { hideAttribution: true }, nodesDraggable: false, nodesConnectable: false, elevateNodesOnSelect: false, colorMode: "dark", children: [_jsx(FitOnLoad, {}), _jsx(FocusOnSelection, { nodes: nodes, flowData: flowData }), _jsx(Background, { color: "rgba(255,255,255,0.03)", gap: 24, size: 1 }), _jsx(Controls, { showInteractive: false })] }) })] }));
599
+ return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row ci-surface", style: { gap: 10, padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", flexShrink: 0 }, children: [_jsx("h3", { className: "ci-header", children: "Flow" }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 10, color: "var(--ci-text)", opacity: 0.8, padding: "2px 6px", borderRadius: "var(--ci-radius-sm)", background: "rgba(255, 255, 255, 0.03)", border: "1px solid var(--ci-border)" }, children: flowCorrelationId }), _jsxs("span", { className: "ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5 }, children: [flowData.length, " events \u00B7 ", nodes.length, " nodes"] }), headerExtra, _jsxs("div", { className: "ci-row", style: { marginLeft: "auto", gap: 6 }, children: [_jsx("button", { onClick: () => setDirection(d => d === "LR" ? "TB" : "LR"), className: "ci-toolbar-btn", title: direction === "LR" ? "Switch to vertical layout" : "Switch to horizontal layout", children: direction === "LR" ? _jsx(ArrowDown, { size: 11, style: { display: "inline", verticalAlign: "-1px" } }) : _jsx(ArrowRight, { size: 11, style: { display: "inline", verticalAlign: "-1px" } }) }), _jsx(ReactorFilter, { allReactorIds: allReactorIds, hiddenReactors: hiddenReactors, setHiddenReactors: setHiddenReactors })] })] }), _jsx("div", { style: { flex: 1, position: "relative" }, children: _jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, defaultEdgeOptions: { type: "smoothstep" }, onNodesChange: onNodesChange, onNodeClick: onNodeClick, onPaneClick: onPaneClick, minZoom: 0.25, proOptions: { hideAttribution: true }, nodesDraggable: false, nodesConnectable: false, elevateNodesOnSelect: false, colorMode: "dark", children: [_jsx(FitOnLoad, {}), _jsx(FocusOnSelection, { nodes: nodes, flowData: flowData }), _jsx(Background, { color: "rgba(255,255,255,0.03)", gap: 24, size: 1 }), _jsx(Controls, { showInteractive: false })] }) })] }));
596
600
  }
@@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "../machine";
4
4
  import { CopyablePayload } from "../components/CopyablePayload";
5
5
  import { eventTextColor } from "../theme";
6
6
  import { formatTs, compactPayload, copyToClipboard, inScrubberRange } from "../utils";
7
- import { Copy, Check, Search, X, ChevronRight, ChevronDown } from "lucide-react";
7
+ import { Copy, Check, Search, X, ChevronRight, ChevronDown, AlertTriangle } from "lucide-react";
8
8
  function buildTreeJson(roots, childrenMap) {
9
9
  function toNode(evt) {
10
10
  const children = evt.id ? (childrenMap.get(evt.id) ?? []) : [];
@@ -23,7 +23,7 @@ function buildTreeJson(roots, childrenMap) {
23
23
  // ---------------------------------------------------------------------------
24
24
  // ReactorNode — intermediate node grouping children by reactor_id
25
25
  // ---------------------------------------------------------------------------
26
- function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, }) {
26
+ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, outcome, outcomesByReactor, }) {
27
27
  const [collapsed, setCollapsed] = useState(false);
28
28
  const nodeRef = useRef(null);
29
29
  useEffect(() => {
@@ -34,12 +34,24 @@ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, i
34
34
  const handleClick = useCallback(() => {
35
35
  onClickReactor(reactorId, parentEventId);
36
36
  }, [onClickReactor, parentEventId, reactorId]);
37
- return (_jsxs("div", { className: depth > 0 ? "pl-6" : "", children: [_jsx("div", { ref: isHighlighted ? nodeRef : undefined, className: `group/tree w-full text-left px-2 py-1.5 rounded-md transition-all duration-150 hover:bg-white/[0.03] ${isHighlighted ? "bg-indigo-500/15 ring-1 ring-indigo-500/25" : ""}`, children: _jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed(v => !v); }, className: "text-[10px] text-muted-foreground hover:text-foreground shrink-0 w-3 text-center", children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) }), _jsxs("button", { onClick: handleClick, className: "flex items-center gap-1.5 min-w-0", children: [_jsx("span", { className: "px-1.5 py-0.5 rounded text-[9px] font-medium shrink-0 bg-white/[0.04] text-muted-foreground/60 italic border border-border", children: "reactor" }), _jsx("span", { className: "text-[10px] font-mono text-foreground/60 shrink-0", children: reactorId }), collapsed && (_jsxs("span", { className: "text-[10px] text-muted-foreground shrink-0", children: ["(", children.length, ")"] }))] })] }) }), !collapsed && children.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor }, child.seq)))] }));
37
+ const isError = outcome?.status === "error";
38
+ const isRunning = outcome?.status === "running";
39
+ return (_jsxs("div", { style: depth > 0 ? { paddingLeft: 24 } : undefined, children: [_jsxs("div", { ref: isHighlighted ? nodeRef : undefined, className: `ci-tree-node${isHighlighted ? " ci-selected" : ""}`, style: isError ? { background: "rgba(239, 68, 68, 0.08)" } : undefined, children: [_jsxs("div", { className: "ci-row", style: { gap: 6, minWidth: 0 }, children: [_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed(v => !v); }, style: { fontSize: 10, color: "var(--ci-text-muted)", background: "none", border: "none", cursor: "pointer", flexShrink: 0, width: 12, textAlign: "center", padding: 0, transition: "color 150ms" }, children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) }), _jsxs("button", { onClick: handleClick, className: "ci-row", style: { gap: 6, minWidth: 0, background: "none", border: "none", cursor: "pointer", padding: 0 }, children: [_jsx("span", { style: {
40
+ padding: "2px 6px",
41
+ borderRadius: "var(--ci-radius-sm)",
42
+ fontSize: 9,
43
+ fontWeight: 500,
44
+ flexShrink: 0,
45
+ fontStyle: "italic",
46
+ background: isError ? "rgba(239, 68, 68, 0.1)" : isRunning ? "rgba(234, 179, 8, 0.1)" : "rgba(255, 255, 255, 0.04)",
47
+ color: isError ? "rgba(248, 113, 113, 0.8)" : isRunning ? "rgba(234, 179, 8, 0.8)" : "var(--ci-text-dim)",
48
+ border: `1px solid ${isError ? "rgba(239, 68, 68, 0.2)" : isRunning ? "rgba(234, 179, 8, 0.2)" : "var(--ci-border)"}`,
49
+ }, children: "reactor" }), _jsx("span", { className: "ci-mono", style: { fontSize: 10, color: "var(--ci-text)", opacity: 0.6, flexShrink: 0 }, children: reactorId }), isError && (_jsxs("span", { className: "flex items-center gap-1 text-[9px] text-red-400/80 shrink-0", title: outcome.error ?? "Error", children: [_jsx(AlertTriangle, { size: 10 }), outcome.attempts > 1 && _jsxs("span", { children: ["x", outcome.attempts] })] })), collapsed && (_jsxs("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: ["(", children.length, ")"] }))] })] }), isError && outcome.error && (_jsx("div", { className: "mt-1 ml-7 text-[9px] text-red-400/70 truncate", title: outcome.error, children: outcome.error }))] }), !collapsed && children.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor, outcomesByReactor: outcomesByReactor }, child.seq)))] }));
38
50
  }
39
51
  // ---------------------------------------------------------------------------
40
52
  // TreeNode (recursive)
41
53
  // ---------------------------------------------------------------------------
42
- function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, }) {
54
+ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, outcomesByReactor, }) {
43
55
  const selectedSeq = useSelector((s) => s.selectedSeq);
44
56
  const flowSelection = useSelector((s) => s.flowSelection);
45
57
  const dispatch = useDispatch();
@@ -72,19 +84,19 @@ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, })
72
84
  return { reactorGroups: groups, directChildren: direct };
73
85
  }, [children]);
74
86
  const highlightedReactorId = flowSelection?.kind === "reactor" ? flowSelection.reactorId : null;
75
- return (_jsxs("div", { className: depth > 0 ? "pl-6" : "", children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
87
+ return (_jsxs("div", { style: depth > 0 ? { paddingLeft: 24 } : undefined, children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
76
88
  dispatch({ type: "ui/event_selected", payload: { seq: event.seq } });
77
89
  if (event.correlationId) {
78
90
  dispatch({ type: "ui/flow_opened", payload: { correlationId: event.correlationId } });
79
91
  }
80
- }, className: `group/tree w-full text-left px-2 py-1.5 rounded-md transition-all duration-150 cursor-pointer hover:bg-white/[0.03] ${isSelected ? "bg-indigo-500/15 ring-1 ring-indigo-500/25" : ""}`, children: [_jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [hasChildren ? (_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed((v) => !v); }, className: "text-[10px] text-muted-foreground hover:text-foreground shrink-0 w-3 text-center", children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) })) : (_jsx("span", { className: "w-3 shrink-0" })), _jsx("span", { className: "text-[10px] font-mono shrink-0", style: { color: eventTextColor(event.name) }, children: event.name }), collapsed && hasChildren && (_jsxs("span", { className: "text-[10px] text-muted-foreground shrink-0", children: ["(", children.length, ")"] })), _jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: formatTs(event.ts) }), _jsx("button", { onClick: (e) => {
92
+ }, className: `ci-tree-node${isSelected ? " ci-selected" : ""}`, style: { cursor: "pointer" }, children: [_jsxs("div", { className: "ci-row", style: { gap: 6, minWidth: 0 }, children: [hasChildren ? (_jsx("button", { onClick: (e) => { e.stopPropagation(); setCollapsed((v) => !v); }, style: { fontSize: 10, color: "var(--ci-text-muted)", background: "none", border: "none", cursor: "pointer", flexShrink: 0, width: 12, textAlign: "center", padding: 0, transition: "color 150ms" }, children: collapsed ? _jsx(ChevronRight, { size: 10 }) : _jsx(ChevronDown, { size: 10 }) })) : (_jsx("span", { style: { width: 12, flexShrink: 0 } })), _jsx("span", { className: "ci-mono", style: { fontSize: 10, flexShrink: 0, color: eventTextColor(event.name) }, children: event.name }), collapsed && hasChildren && (_jsxs("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: ["(", children.length, ")"] })), _jsx("span", { style: { fontSize: 10, color: "var(--ci-text-muted)", flexShrink: 0 }, children: formatTs(event.ts) }), _jsx("button", { onClick: (e) => {
81
93
  e.stopPropagation();
82
94
  const json = buildTreeJson([event], childrenMap);
83
95
  const text = JSON.stringify(json[0], null, 2);
84
96
  copyToClipboard(text);
85
97
  setCopied(true);
86
98
  setTimeout(() => setCopied(false), 1500);
87
- }, className: "opacity-0 group-hover/tree:opacity-100 transition-all duration-150 ml-auto p-1 rounded-md hover:bg-white/[0.05] shrink-0 text-[10px] text-muted-foreground/50", title: "Copy subtree as JSON", children: copied ? _jsx(Check, { size: 12 }) : _jsx(Copy, { size: 12 }) }), onInvestigate && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onInvestigate(event); }, className: "opacity-0 group-hover/tree:opacity-100 transition-all duration-150 p-1 rounded-md hover:bg-white/[0.05] shrink-0 text-muted-foreground/50", title: "Investigate", children: _jsx(Search, { size: 12 }) }))] }), _jsx("button", { onClick: (e) => { e.stopPropagation(); setPayloadOpen((v) => !v); }, className: "mt-0.5 ml-3 text-[10px] font-mono text-muted-foreground hover:text-foreground truncate text-left max-w-full block", title: "Click to expand payload", children: event.summary ?? compactPayload(event.payload) }), payloadOpen && (_jsx(CopyablePayload, { payload: event.payload, className: "mt-1 ml-3 max-h-48" }))] }), !collapsed && (_jsxs(_Fragment, { children: [directChildren.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor, onInvestigate: onInvestigate }, child.seq))), [...reactorGroups.entries()].map(([hid, group]) => (_jsx(ReactorNode, { reactorId: hid, parentEventId: event.id, children: group, childrenMap: childrenMap, depth: depth + 1, isHighlighted: hid === highlightedReactorId, onClickReactor: onClickReactor }, hid)))] }))] }));
99
+ }, className: "ci-tree-actions ci-btn", style: { marginLeft: "auto", flexShrink: 0, fontSize: 10 }, title: "Copy subtree as JSON", children: copied ? _jsx(Check, { size: 12 }) : _jsx(Copy, { size: 12 }) }), onInvestigate && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onInvestigate(event); }, className: "ci-tree-actions ci-btn", style: { flexShrink: 0 }, title: "Investigate", children: _jsx(Search, { size: 12 }) }))] }), _jsx("button", { onClick: (e) => { e.stopPropagation(); setPayloadOpen((v) => !v); }, className: "ci-mono ci-truncate", style: { marginTop: 2, marginLeft: 12, fontSize: 10, color: "var(--ci-text-muted)", textAlign: "left", maxWidth: "100%", display: "block", background: "none", border: "none", cursor: "pointer", padding: 0, transition: "color 150ms" }, title: "Click to expand payload", children: event.summary ?? compactPayload(event.payload) }), payloadOpen && (_jsx("div", { style: { marginTop: 4, marginLeft: 12, maxHeight: 192 }, children: _jsx(CopyablePayload, { payload: event.payload }) }))] }), !collapsed && (_jsxs(_Fragment, { children: [directChildren.map((child) => (_jsx(TreeNode, { event: child, childrenMap: childrenMap, depth: depth + 1, onClickReactor: onClickReactor, onInvestigate: onInvestigate, outcomesByReactor: outcomesByReactor }, child.seq))), [...reactorGroups.entries()].map(([hid, group]) => (_jsx(ReactorNode, { reactorId: hid, parentEventId: event.id, children: group, childrenMap: childrenMap, depth: depth + 1, isHighlighted: hid === highlightedReactorId, onClickReactor: onClickReactor, outcome: outcomesByReactor?.get(hid), outcomesByReactor: outcomesByReactor }, hid)))] }))] }));
88
100
  }
89
101
  // ---------------------------------------------------------------------------
90
102
  // matchesFlowSelection
@@ -103,7 +115,20 @@ export function CausalTreePane({ onInvestigate } = {}) {
103
115
  const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
104
116
  const scrubberStart = useSelector((s) => s.scrubberStart);
105
117
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
118
+ const outcomesMap = useSelector((s) => s.outcomes);
106
119
  const dispatch = useDispatch();
120
+ // Build reactor outcome lookup for current flow
121
+ const outcomesByReactor = useMemo(() => {
122
+ if (!flowCorrelationId)
123
+ return new Map();
124
+ const raw = outcomesMap[flowCorrelationId];
125
+ if (!raw)
126
+ return new Map();
127
+ const map = new Map();
128
+ for (const o of raw)
129
+ map.set(o.reactorId, o);
130
+ return map;
131
+ }, [outcomesMap, flowCorrelationId]);
107
132
  const treeEvents = useMemo(() => {
108
133
  const all = causalTree?.events ?? null;
109
134
  if (all == null || (scrubberStart == null && scrubberEnd == null))
@@ -142,17 +167,17 @@ export function CausalTreePane({ onInvestigate } = {}) {
142
167
  return { roots: rootList, childrenMap: cMap, totalCount: total, filteredCount: filtered };
143
168
  }, [treeEvents, flowCorrelationId, flowSelection]);
144
169
  if (treeLoading) {
145
- return (_jsxs("div", { className: "p-3 space-y-1.5 animate-pulse", children: [_jsx("div", { className: "h-3 w-32 bg-muted rounded mb-3" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-4 w-12 bg-muted rounded" }), _jsx("div", { className: "h-4 w-36 bg-muted rounded" }), _jsx("div", { className: "h-3 w-24 bg-muted rounded" })] }), _jsxs("div", { className: "pl-6 space-y-1.5", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-4 w-14 bg-muted rounded" }), _jsx("div", { className: "h-4 w-44 bg-muted rounded" }), _jsx("div", { className: "h-3 w-24 bg-muted rounded" })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-4 w-10 bg-muted rounded" }), _jsx("div", { className: "h-4 w-32 bg-muted rounded" }), _jsx("div", { className: "h-3 w-24 bg-muted rounded" })] })] })] }));
170
+ return (_jsxs("div", { className: "ci-pulse", style: { padding: 12, display: "flex", flexDirection: "column", gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 128, marginBottom: 12 } }), _jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 48 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 144 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] }), _jsxs("div", { style: { paddingLeft: 24, display: "flex", flexDirection: "column", gap: 6 }, children: [_jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 56 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 176 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] }), _jsxs("div", { className: "ci-row", style: { gap: 6 }, children: [_jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 40 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 16, width: 128 } }), _jsx("div", { className: "ci-muted-bg", style: { height: 12, width: 96 } })] })] })] }));
146
171
  }
147
172
  if (!treeEvents) {
148
- return (_jsx("div", { className: "flex items-center justify-center h-full text-xs text-muted-foreground/50 tracking-wide", children: "Select an event to view its causal tree" }));
173
+ return (_jsx("div", { className: "ci-empty", children: "Select an event to view its causal tree" }));
149
174
  }
150
175
  if (roots.length === 0 && flowSelection) {
151
- return (_jsxs("div", { className: "h-full overflow-y-auto p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2 px-2.5 py-1.5 rounded-md bg-indigo-500/8 border border-indigo-500/15 text-xs text-indigo-400", children: [_jsx("span", { children: flowSelection.kind === "event-type"
176
+ return (_jsxs("div", { style: { height: "100%", overflow: "auto", padding: 12 }, children: [_jsxs("div", { className: "ci-flow-filter ci-row", style: { gap: 8, marginBottom: 8 }, children: [_jsx("span", { children: flowSelection.kind === "event-type"
152
177
  ? flowSelection.name
153
- : `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), className: "ml-auto hover:text-foreground", children: _jsx(X, { size: 12 }) })] }), _jsx("div", { className: "flex items-center justify-center h-32 text-sm text-muted-foreground", children: "No events match the current filter" })] }));
178
+ : `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", color: "inherit", padding: 0, transition: "color 150ms" }, children: _jsx(X, { size: 12 }) })] }), _jsx("div", { className: "ci-empty", style: { height: 128, fontSize: 14 }, children: "No events match the current filter" })] }));
154
179
  }
155
- return (_jsxs("div", { className: "h-full overflow-y-auto p-3", children: [flowSelection && (_jsxs("div", { className: "flex items-center gap-2 mb-2 px-2.5 py-1.5 rounded-md bg-indigo-500/8 border border-indigo-500/15 text-xs text-indigo-400", children: [_jsx("span", { children: flowSelection.kind === "event-type"
180
+ return (_jsxs("div", { style: { height: "100%", overflow: "auto", padding: 12 }, children: [flowSelection && (_jsxs("div", { className: "ci-flow-filter ci-row", style: { gap: 8, marginBottom: 8 }, children: [_jsx("span", { children: flowSelection.kind === "event-type"
156
181
  ? flowSelection.name
157
- : `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), className: "ml-auto hover:text-foreground", children: _jsx(X, { size: 12 }) })] })), _jsxs("h3", { className: "text-[10px] font-semibold text-muted-foreground/50 mb-2 uppercase tracking-widest", children: ["Causal Tree (", flowSelection ? `${filteredCount} of ${totalCount}` : totalCount, " events)"] }), roots.map(root => (_jsx(TreeNode, { event: root, childrenMap: childrenMap, depth: 0, onClickReactor: onClickReactor, onInvestigate: onInvestigate }, root.seq)))] }));
182
+ : `outputs of ${flowSelection.reactorId}` }), _jsx("button", { onClick: () => dispatch({ type: "ui/flow_node_selected", payload: null }), style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", color: "inherit", padding: 0, transition: "color 150ms" }, children: _jsx(X, { size: 12 }) })] })), _jsxs("h3", { className: "ci-header", style: { marginBottom: 8 }, children: ["Causal Tree (", flowSelection ? `${filteredCount} of ${totalCount}` : totalCount, " events)"] }), roots.map(root => (_jsx(TreeNode, { event: root, childrenMap: childrenMap, depth: 0, onClickReactor: onClickReactor, onInvestigate: onInvestigate, outcomesByReactor: outcomesByReactor }, root.seq)))] }));
158
183
  }
@@ -39,8 +39,13 @@ export function CorrelationExplorerPane() {
39
39
  const handleCopy = useCallback((text) => {
40
40
  navigator.clipboard.writeText(text).catch(() => { });
41
41
  }, []);
42
- return (_jsxs("div", { className: "flex flex-col h-full", children: [_jsx("div", { className: "px-3 py-2.5 border-b border-border", style: { background: "rgba(15, 15, 20, 0.6)", backdropFilter: "blur(8px)" }, children: _jsx("input", { type: "text", placeholder: "Search by correlation ID or event type...", value: search, onChange: (e) => handleSearchChange(e.target.value), className: "w-full px-3 py-1.5 text-xs bg-background/50 border border-border rounded-md text-foreground placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-indigo-500/40 focus:border-indigo-500/30 transition-all" }) }), _jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border text-[9px] font-semibold text-muted-foreground/40 uppercase tracking-widest", children: [_jsx("span", { className: "w-28 shrink-0", children: "Root Event" }), _jsx("span", { className: "w-24 shrink-0", children: "Correlation" }), _jsx("span", { className: "w-12 shrink-0 text-right", children: "Events" }), _jsx("span", { className: "w-20 shrink-0 text-right", children: "Duration" }), _jsx("span", { className: "flex-1", children: "Last Activity" })] }), loading && correlations.length === 0 ? (_jsx("div", { className: "animate-pulse p-3", children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2 py-2.5", children: [_jsx("div", { className: "h-3 w-28 bg-white/[0.03] rounded" }), _jsx("div", { className: "h-3 w-24 bg-white/[0.03] rounded" }), _jsx("div", { className: "h-3 w-12 bg-white/[0.03] rounded" })] }, i))) })) : correlations.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32 text-xs text-muted-foreground/50 tracking-wide", children: "No correlations found" })) : (_jsx("div", { className: "flex-1 overflow-y-auto", children: correlations.map((corr) => (_jsxs("button", { onClick: () => handleRowClick(corr.correlationId), className: "group w-full text-left flex items-center gap-2 px-3 py-2.5 border-b border-border hover:bg-indigo-500/8 transition-all duration-150", children: [_jsx("span", { className: "text-[10px] font-mono shrink-0 w-28 truncate px-1.5 py-0.5 rounded", style: {
42
+ return (_jsxs("div", { className: "ci-col", children: [_jsx("div", { className: "ci-surface", style: { padding: "10px 12px", borderBottom: "1px solid var(--ci-border)" }, children: _jsx("input", { type: "text", placeholder: "Search by correlation ID or event type...", value: search, onChange: (e) => handleSearchChange(e.target.value), className: "ci-input", style: { width: "100%", padding: "6px 12px", fontSize: 12 } }) }), _jsxs("div", { className: "ci-row ci-header", style: { gap: 8, padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", letterSpacing: "0.08em", fontSize: 9 }, children: [_jsx("span", { style: { width: 112, flexShrink: 0 }, children: "Root Event" }), _jsx("span", { style: { width: 96, flexShrink: 0 }, children: "Correlation" }), _jsx("span", { style: { width: 48, flexShrink: 0, textAlign: "right" }, children: "Events" }), _jsx("span", { style: { width: 80, flexShrink: 0, textAlign: "right" }, children: "Duration" }), _jsx("span", { style: { flex: 1 }, children: "Last Activity" })] }), loading && correlations.length === 0 ? (_jsx("div", { className: "ci-pulse", style: { padding: 12 }, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "ci-row", style: { gap: 8, padding: "10px 0" }, children: [_jsx("div", { className: "ci-skeleton", style: { height: 12, width: 112 } }), _jsx("div", { className: "ci-skeleton", style: { height: 12, width: 96 } }), _jsx("div", { className: "ci-skeleton", style: { height: 12, width: 48 } })] }, i))) })) : correlations.length === 0 ? (_jsx("div", { className: "ci-empty", style: { height: 128 }, children: "No correlations found" })) : (_jsx("div", { className: "ci-scroll", children: correlations.map((corr) => (_jsxs("button", { onClick: () => handleRowClick(corr.correlationId), className: "ci-list-row ci-row", style: { width: "100%", textAlign: "left", gap: 8, padding: "10px 12px", borderBottom: "1px solid var(--ci-border)", background: "none", border: "none", borderBottomStyle: "solid", cursor: "pointer" }, children: [_jsx("span", { className: "ci-mono ci-truncate", style: {
43
+ fontSize: 10,
44
+ flexShrink: 0,
45
+ width: 112,
46
+ padding: "2px 6px",
47
+ borderRadius: "var(--ci-radius-sm)",
43
48
  color: eventTextColor(corr.rootEventType),
44
49
  background: eventBg(corr.rootEventType),
45
- }, title: corr.rootEventType, children: corr.rootEventType }), _jsx("span", { className: "text-[10px] font-mono text-purple-400/70 w-24 shrink-0 truncate cursor-pointer hover:text-purple-400 transition-colors", title: `Click to copy: ${corr.correlationId}`, onClick: (e) => { e.stopPropagation(); handleCopy(corr.correlationId); }, children: corr.correlationId.slice(0, 8) }), _jsx("span", { className: "text-[11px] font-mono text-foreground/70 w-12 shrink-0 text-right tabular-nums", children: corr.eventCount }), _jsx("span", { className: "text-[10px] text-muted-foreground/50 w-20 shrink-0 text-right font-mono tabular-nums", children: _jsx(RelativeDuration, { firstTs: corr.firstTs, lastTs: corr.lastTs }) }), _jsx("span", { className: "text-[10px] text-muted-foreground/40 flex-1 truncate tabular-nums", children: formatTs(corr.lastTs) }), corr.hasErrors && (_jsx("span", { className: "w-2 h-2 rounded-full bg-red-500/80 shrink-0", style: { boxShadow: "0 0 6px rgba(239, 68, 68, 0.3)" }, title: "This correlation has errors" }))] }, corr.correlationId))) }))] }));
50
+ }, title: corr.rootEventType, children: corr.rootEventType }), _jsx("span", { className: "ci-mono ci-truncate", style: { fontSize: 10, color: "var(--ci-purple-dim)", width: 96, flexShrink: 0, cursor: "pointer", transition: "color 150ms" }, title: `Click to copy: ${corr.correlationId}`, onClick: (e) => { e.stopPropagation(); handleCopy(corr.correlationId); }, children: corr.correlationId.slice(0, 8) }), _jsx("span", { className: "ci-mono ci-tabular", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.7, width: 48, flexShrink: 0, textAlign: "right" }, children: corr.eventCount }), _jsx("span", { className: "ci-mono ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5, width: 80, flexShrink: 0, textAlign: "right" }, children: _jsx(RelativeDuration, { firstTs: corr.firstTs, lastTs: corr.lastTs }) }), _jsx("span", { className: "ci-tabular ci-truncate", style: { fontSize: 10, color: "var(--ci-text-dimmer)", flex: 1 }, children: formatTs(corr.lastTs) }), corr.hasErrors && (_jsx("span", { className: "ci-error-dot", title: "This correlation has errors" }))] }, corr.correlationId))) }))] }));
46
51
  }
@@ -9,8 +9,8 @@ import { Search, ChevronRight, ChevronDown } from "lucide-react";
9
9
  // ---------------------------------------------------------------------------
10
10
  function LogRow({ log, showReactor }) {
11
11
  const [expanded, setExpanded] = useState(false);
12
- const levelColor = LOG_LEVEL_COLORS[log.level] ?? "bg-zinc-600/20 text-zinc-400";
13
- return (_jsxs("div", { className: "px-2 py-1.5 hover:bg-white/[0.02] rounded-md transition-colors duration-100", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: `px-1.5 py-0.5 rounded text-[9px] font-semibold uppercase shrink-0 ${levelColor}`, children: log.level }), _jsx("span", { className: "text-[10px] text-muted-foreground/50 shrink-0 tabular-nums", children: formatTs(log.loggedAt) }), showReactor && (_jsx("span", { className: "text-[10px] font-mono text-muted-foreground/40 shrink-0", children: log.reactorId })), _jsx("span", { className: "text-[11px] text-foreground/80 truncate", children: log.message }), log.data != null && (_jsx("button", { onClick: () => setExpanded((v) => !v), className: "ml-auto text-[10px] p-1 rounded-md hover:bg-white/[0.05] shrink-0 text-muted-foreground/50 hover:text-foreground transition-colors", children: expanded ? _jsx(ChevronDown, { size: 10 }) : _jsx(ChevronRight, { size: 10 }) }))] }), expanded && log.data != null && (_jsx("pre", { className: "mt-1.5 ml-4 text-[10px] font-mono text-muted-foreground/70 bg-white/[0.02] rounded-md p-2.5 max-h-32 overflow-auto whitespace-pre-wrap border border-border", children: typeof log.data === "string" ? log.data : JSON.stringify(log.data, null, 2) }))] }));
12
+ const levelColor = LOG_LEVEL_COLORS[log.level] ?? "ci-log-debug";
13
+ return (_jsxs("div", { style: { padding: "6px 8px", borderRadius: "var(--ci-radius-md)", transition: "background 100ms" }, className: "ci-list-row", children: [_jsxs("div", { className: "ci-row", style: { gap: 8, minWidth: 0 }, children: [_jsx("span", { className: levelColor, style: { padding: "2px 6px", borderRadius: "var(--ci-radius-sm)", fontSize: 9, fontWeight: 600, textTransform: "uppercase", flexShrink: 0 }, children: log.level }), _jsx("span", { className: "ci-tabular", style: { fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5, flexShrink: 0 }, children: formatTs(log.loggedAt) }), showReactor && (_jsx("span", { className: "ci-mono", style: { fontSize: 10, color: "var(--ci-text-dimmer)", flexShrink: 0 }, children: log.reactorId })), _jsx("span", { className: "ci-truncate", style: { fontSize: 11, color: "var(--ci-text)", opacity: 0.8 }, children: log.message }), log.data != null && (_jsx("button", { onClick: () => setExpanded((v) => !v), className: "ci-btn", style: { marginLeft: "auto", flexShrink: 0, fontSize: 10 }, children: expanded ? _jsx(ChevronDown, { size: 10 }) : _jsx(ChevronRight, { size: 10 }) }))] }), expanded && log.data != null && (_jsx("pre", { className: "ci-mono", style: { marginTop: 6, marginLeft: 16, fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.7, background: "rgba(255, 255, 255, 0.02)", borderRadius: "var(--ci-radius-md)", padding: 10, maxHeight: 128, overflow: "auto", whiteSpace: "pre-wrap", border: "1px solid var(--ci-border)" }, children: typeof log.data === "string" ? log.data : JSON.stringify(log.data, null, 2) }))] }));
14
14
  }
15
15
  export function LogsPane({ onInvestigate } = {}) {
16
16
  const logs = useSelector((s) => s.logs);
@@ -18,7 +18,7 @@ export function LogsPane({ onInvestigate } = {}) {
18
18
  const flowData = useSelector((s) => s.flowData);
19
19
  const scrubberStart = useSelector((s) => s.scrubberStart);
20
20
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
21
- const [levelFilter, setLevelFilter] = useState(new Set(["debug", "info", "warn"]));
21
+ const [levelFilter, setLevelFilter] = useState(new Set(["debug", "info", "warn", "error"]));
22
22
  const [searchText, setSearchText] = useState("");
23
23
  const isCorrelationScope = logsFilter.scope === "correlation" && logsFilter.correlationId != null;
24
24
  const hasFilter = logsFilter.reactorId != null || isCorrelationScope;
@@ -47,7 +47,7 @@ export function LogsPane({ onInvestigate } = {}) {
47
47
  return filtered;
48
48
  }, [logs, logsFilter, levelFilter, searchText, visibleEventIds]);
49
49
  if (!hasFilter) {
50
- return (_jsx("div", { className: "flex items-center justify-center h-full text-xs text-muted-foreground/50 tracking-wide", children: "Click a reactor node in the causal tree to view logs" }));
50
+ return (_jsx("div", { className: "ci-empty", children: "Click a reactor node in the causal tree to view logs" }));
51
51
  }
52
52
  const toggleLevel = (level) => {
53
53
  setLevelFilter((prev) => {
@@ -59,7 +59,16 @@ export function LogsPane({ onInvestigate } = {}) {
59
59
  return next;
60
60
  });
61
61
  };
62
- return (_jsxs("div", { className: "h-full flex flex-col", children: [_jsxs("div", { className: "px-3 py-2 border-b border-border flex items-center gap-3 flex-wrap", style: { background: "rgba(15, 15, 20, 0.6)", backdropFilter: "blur(8px)" }, children: [_jsx("div", { className: "flex items-center gap-1 text-[10px]", children: ["debug", "info", "warn"].map((level) => (_jsx("button", { onClick: () => toggleLevel(level), className: `px-2 py-0.5 rounded-md uppercase font-semibold transition-all duration-150 ${levelFilter.has(level)
63
- ? LOG_LEVEL_COLORS[level]
64
- : "text-muted-foreground/30 line-through"}`, children: level }, level))) }), _jsxs("div", { className: "relative flex items-center ml-auto", children: [_jsx(Search, { size: 10, className: "absolute left-2.5 text-muted-foreground/40 pointer-events-none" }), _jsx("input", { type: "text", placeholder: "Search logs...", value: searchText, onChange: (e) => setSearchText(e.target.value), className: "pl-7 pr-2 text-[11px] bg-background/50 border border-border rounded-md py-1 w-44 focus:outline-none focus:ring-1 focus:ring-indigo-500/40 focus:border-indigo-500/30 text-foreground placeholder:text-muted-foreground/40 transition-all" })] }), onInvestigate && (_jsx("button", { onClick: () => onInvestigate(logsFilter), className: "flex items-center gap-1 px-2 py-1 rounded-md text-[11px] text-muted-foreground/50 hover:text-foreground hover:bg-white/[0.04] transition-all duration-150", title: "Investigate logs", children: _jsx(Search, { size: 12 }) }))] }), _jsxs("div", { className: "px-3 py-1.5 text-[10px] text-muted-foreground/50", children: [logsFilter.reactorId ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-mono text-foreground/60", children: logsFilter.reactorId }), isCorrelationScope && _jsx("span", { className: "ml-1", children: "(all reactors in correlation)" })] })) : (_jsx("span", { children: "All reactors in correlation" })), _jsxs("span", { className: "ml-2 tabular-nums", children: [filteredLogs.length, " logs"] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-1", children: [filteredLogs.length === 0 && (_jsx("div", { className: "p-3 text-[11px] text-muted-foreground/40", children: "No logs match filters" })), filteredLogs.map((log, i) => (_jsx(LogRow, { log: log, showReactor: isCorrelationScope }, i)))] })] }));
62
+ return (_jsxs("div", { className: "ci-col", children: [_jsxs("div", { className: "ci-row ci-surface", style: { padding: "8px 12px", borderBottom: "1px solid var(--ci-border)", gap: 12, flexWrap: "wrap" }, children: [_jsx("div", { className: "ci-row", style: { gap: 4, fontSize: 10 }, children: ["debug", "info", "warn", "error"].map((level) => (_jsx("button", { onClick: () => toggleLevel(level), className: levelFilter.has(level) ? LOG_LEVEL_COLORS[level] : "", style: {
63
+ padding: "2px 8px",
64
+ borderRadius: "var(--ci-radius-md)",
65
+ textTransform: "uppercase",
66
+ fontWeight: 600,
67
+ border: "none",
68
+ cursor: "pointer",
69
+ background: levelFilter.has(level) ? undefined : "transparent",
70
+ color: levelFilter.has(level) ? undefined : "var(--ci-text-dimmer)",
71
+ textDecoration: levelFilter.has(level) ? undefined : "line-through",
72
+ transition: "all 150ms",
73
+ }, children: level }, level))) }), _jsxs("div", { className: "ci-row", style: { position: "relative", marginLeft: "auto" }, children: [_jsx(Search, { size: 10, style: { position: "absolute", left: 10, color: "var(--ci-text-dimmer)", pointerEvents: "none" } }), _jsx("input", { type: "text", placeholder: "Search logs...", value: searchText, onChange: (e) => setSearchText(e.target.value), className: "ci-input", style: { paddingLeft: 28, paddingRight: 8, fontSize: 11, paddingTop: 4, paddingBottom: 4, width: 176 } })] }), onInvestigate && (_jsx("button", { onClick: () => onInvestigate(logsFilter), className: "ci-btn ci-row", style: { gap: 4, fontSize: 11 }, title: "Investigate logs", children: _jsx(Search, { size: 12 }) }))] }), _jsxs("div", { style: { padding: "6px 12px", fontSize: 10, color: "var(--ci-text-muted)", opacity: 0.5 }, children: [logsFilter.reactorId ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "ci-mono", style: { color: "var(--ci-text)", opacity: 0.6 }, children: logsFilter.reactorId }), isCorrelationScope && _jsx("span", { style: { marginLeft: 4 }, children: "(all reactors in correlation)" })] })) : (_jsx("span", { children: "All reactors in correlation" })), _jsxs("span", { className: "ci-tabular", style: { marginLeft: 8 }, children: [filteredLogs.length, " logs"] })] }), _jsxs("div", { className: "ci-scroll", style: { padding: "0 4px" }, children: [filteredLogs.length === 0 && (_jsx("div", { style: { padding: 12, fontSize: 11, color: "var(--ci-text-dimmer)" }, children: "No logs match filters" })), filteredLogs.map((log, i) => (_jsx(LogRow, { log: log, showReactor: isCorrelationScope }, i)))] })] }));
65
74
  }