causal-inspector 0.1.4 → 0.1.5

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.
@@ -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:");
@@ -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)
@@ -592,5 +596,5 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
592
596
  if (flowLoading) {
593
597
  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" })] })] })] }) })] }));
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: "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, _jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [_jsx("button", { onClick: () => setDirection(d => d === "LR" ? "TB" : "LR"), 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", title: direction === "LR" ? "Switch to vertical layout" : "Switch to horizontal layout", children: direction === "LR" ? _jsx(ArrowDown, { size: 11, className: "inline" }) : _jsx(ArrowRight, { size: 11, className: "inline" }) }), _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 })] }) })] }));
596
600
  }
@@ -61,5 +61,5 @@ export function LogsPane({ onInvestigate } = {}) {
61
61
  };
62
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
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)))] })] }));
64
+ : "text-muted-foreground/30 line-through"}`, children: level }, level))) }), _jsxs("div", { className: "relative flex items-center", 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)))] })] }));
65
65
  }
@@ -4,13 +4,13 @@ import { useSelector, useDispatch } from "../machine";
4
4
  import { FilterBar } from "../components/FilterBar";
5
5
  import { CopyablePayload } from "../components/CopyablePayload";
6
6
  import { eventTextColor, eventBg } from "../theme";
7
- import { formatTs, compactPayload, aggregateKey, inScrubberRange } from "../utils";
7
+ import { formatTs, compactPayload, inScrubberRange } from "../utils";
8
8
  import { Search, ChevronRight } from "lucide-react";
9
- function EventRow({ event, isSelected, onClick, onFilterCorrelation, onFilterStream, onInvestigate, }) {
9
+ function EventRow({ event, isSelected, onClick, onFilterCorrelation, onInvestigate, }) {
10
10
  const [payloadOpen, setPayloadOpen] = useState(false);
11
11
  return (_jsxs("div", { className: `group w-full text-left px-3 py-2 border-b border-border transition-all duration-150 ${isSelected
12
12
  ? "bg-indigo-500/15"
13
- : "hover:bg-white/[0.02]"}`, children: [_jsx("div", { onClick: onClick, role: "button", tabIndex: 0, className: "w-full text-left cursor-pointer", children: _jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [_jsx("span", { className: "text-[10px] font-mono text-muted-foreground/60 w-10 shrink-0 text-right tabular-nums", children: event.seq }), _jsx("span", { className: "text-[10px] text-muted-foreground/70 shrink-0 w-32 tabular-nums", children: formatTs(event.ts) }), event.correlationId && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onFilterCorrelation(event.correlationId); }, className: "px-1.5 py-0.5 rounded-full text-[9px] font-mono bg-purple-500/8 text-purple-400/80 hover:bg-purple-500/15 hover:text-purple-400 shrink-0 transition-all border border-purple-500/10", title: `Filter by correlation ${event.correlationId}`, children: event.correlationId.slice(0, 8) })), event.aggregateType && event.aggregateId && (_jsxs("button", { onClick: (e) => { e.stopPropagation(); onFilterStream(aggregateKey(event)); }, className: "opacity-0 group-hover:opacity-100 px-1.5 py-0.5 rounded-full text-[9px] font-mono bg-teal-500/8 text-teal-400/80 hover:bg-teal-500/15 hover:text-teal-400 shrink-0 transition-all border border-teal-500/10", title: `Filter by stream ${event.aggregateType}:${event.aggregateId}`, children: [event.aggregateType, ":", event.aggregateId.slice(0, 8)] })), _jsx("span", { className: "text-xs font-mono shrink-0 px-1.5 py-0.5 rounded", style: {
13
+ : "hover:bg-white/[0.02]"}`, children: [_jsx("div", { onClick: onClick, role: "button", tabIndex: 0, className: "w-full text-left cursor-pointer", children: _jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [_jsx("span", { className: "text-[10px] font-mono text-muted-foreground/60 w-10 shrink-0 text-right tabular-nums", children: event.seq }), _jsx("span", { className: "text-[10px] text-muted-foreground/70 shrink-0 w-32 tabular-nums", children: formatTs(event.ts) }), event.correlationId && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onFilterCorrelation(event.correlationId); }, className: "px-1.5 py-0.5 rounded-full text-[9px] font-mono bg-purple-500/8 text-purple-400/80 hover:bg-purple-500/15 hover:text-purple-400 shrink-0 transition-all border border-purple-500/10", title: `Filter by correlation ${event.correlationId}`, children: event.correlationId.slice(0, 8) })), _jsx("span", { className: "text-xs font-mono shrink-0 px-1.5 py-0.5 rounded", style: {
14
14
  color: eventTextColor(event.name),
15
15
  background: eventBg(event.name),
16
16
  }, children: event.name }), _jsxs("button", { onClick: (e) => { e.stopPropagation(); setPayloadOpen((v) => !v); }, className: "flex items-center gap-1 text-[10px] font-mono text-muted-foreground/60 hover:text-muted-foreground truncate text-left min-w-0 transition-colors", title: "Click to expand payload", children: [_jsx(ChevronRight, { size: 10, className: `shrink-0 transition-transform duration-150 ${payloadOpen ? "rotate-90" : ""}` }), _jsx("span", { className: "truncate", children: event.summary ?? compactPayload(event.payload) })] }), onInvestigate && (_jsx("button", { onClick: (e) => { e.stopPropagation(); onInvestigate(); }, className: "opacity-0 group-hover:opacity-100 transition-opacity duration-150 ml-auto p-1 rounded-md hover:bg-white/[0.05] shrink-0 text-muted-foreground", title: "Investigate", children: _jsx(Search, { size: 12 }) }))] }) }), payloadOpen && (_jsx(CopyablePayload, { payload: event.payload, className: "mt-2 ml-12 max-h-64" }))] }));
@@ -34,8 +34,17 @@ function InfiniteScrollSentinel({ onVisible, loading }) {
34
34
  }
35
35
  export function TimelinePane({ onInvestigate } = {}) {
36
36
  const events = useSelector((s) => {
37
+ let result = s.events;
37
38
  const cid = s.filters.correlationId;
38
- return cid ? s.events.filter((e) => e.correlationId === cid) : s.events;
39
+ if (cid)
40
+ result = result.filter((e) => e.correlationId === cid);
41
+ const search = s.filters.search?.toLowerCase();
42
+ if (search) {
43
+ result = result.filter((e) => e.name.toLowerCase().includes(search) ||
44
+ e.payload.toLowerCase().includes(search) ||
45
+ (e.correlationId ?? "").toLowerCase().includes(search));
46
+ }
47
+ return result;
39
48
  });
40
49
  const loading = useSelector((s) => s.loading);
41
50
  const hasMore = useSelector((s) => s.hasMore);
@@ -57,11 +66,8 @@ export function TimelinePane({ onInvestigate } = {}) {
57
66
  const handleFilterCorrelation = useCallback((correlationId) => {
58
67
  dispatch({ type: "ui/filter_changed", payload: { correlationId } });
59
68
  }, [dispatch]);
60
- const handleFilterStream = useCallback((aggregateKey) => {
61
- dispatch({ type: "ui/filter_changed", payload: { aggregateKey } });
62
- }, [dispatch]);
63
69
  const handleLoadMore = useCallback(() => {
64
70
  dispatch({ type: "ui/load_more_requested" });
65
71
  }, [dispatch]);
66
- return (_jsxs("div", { className: "flex flex-col h-full", children: [_jsx(FilterBar, {}), loading && events.length === 0 ? (_jsx("div", { className: "animate-pulse p-1", children: Array.from({ length: 12 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5 border-b border-border", children: [_jsx("div", { className: "h-3 w-10 bg-white/[0.03] rounded shrink-0" }), _jsx("div", { className: "h-3 w-32 bg-white/[0.03] rounded shrink-0" }), _jsx("div", { className: "h-3 bg-white/[0.03] rounded flex-1", style: { maxWidth: `${150 + (i * 37) % 200}px` } })] }, i))) })) : events.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32 text-sm text-muted-foreground/60", children: "No events found" })) : (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [displayedEvents.map((event) => (_jsx(EventRow, { event: event, isSelected: event.seq === selectedSeq, onClick: () => handleSelect(event), onFilterCorrelation: handleFilterCorrelation, onFilterStream: handleFilterStream, onInvestigate: onInvestigate ? () => onInvestigate(event) : undefined }, event.seq))), hasMore && (_jsx(InfiniteScrollSentinel, { onVisible: handleLoadMore, loading: loading }))] }))] }));
72
+ return (_jsxs("div", { className: "flex flex-col h-full", children: [_jsx(FilterBar, {}), loading && events.length === 0 ? (_jsx("div", { className: "animate-pulse p-1", children: Array.from({ length: 12 }).map((_, i) => (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5 border-b border-border", children: [_jsx("div", { className: "h-3 w-10 bg-white/[0.03] rounded shrink-0" }), _jsx("div", { className: "h-3 w-32 bg-white/[0.03] rounded shrink-0" }), _jsx("div", { className: "h-3 bg-white/[0.03] rounded flex-1", style: { maxWidth: `${150 + (i * 37) % 200}px` } })] }, i))) })) : events.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32 text-sm text-muted-foreground/60", children: "No events found" })) : (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [displayedEvents.map((event) => (_jsx(EventRow, { event: event, isSelected: event.seq === selectedSeq, onClick: () => handleSelect(event), onFilterCorrelation: handleFilterCorrelation, onInvestigate: onInvestigate ? () => onInvestigate(event) : undefined }, event.seq))), hasMore && (_jsx(InfiniteScrollSentinel, { onVisible: handleLoadMore, loading: loading }))] }))] }));
67
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "causal-inspector",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",