causal-inspector 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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, WorkflowSummary, ReactorDependency, AggregateLifecycleEntry, FilterState, FlowSelection, ReactorDescription, ReactorDescriptionSnapshot, AggregateTimelineEntry, ReactorLog, ReactorOutcome, ReactorAttempt, PaneLayout, SubjectChainEvent, SubjectChainMode, InspectorEffect } 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", {
@@ -16,23 +16,27 @@ type CausalTreeLoaded = BaseEvent<"events/causal_tree_loaded", {
16
16
  type FlowLoaded = BaseEvent<"events/flow_loaded", InspectorEvent[]>;
17
17
  type LogsLoaded = BaseEvent<"events/logs_loaded", ReactorLog[]>;
18
18
  type DescriptionsLoaded = BaseEvent<"events/descriptions_loaded", {
19
- correlationId: string;
19
+ workflowId: string;
20
20
  descriptions: ReactorDescription[];
21
21
  }>;
22
22
  type DescriptionSnapshotsLoaded = BaseEvent<"events/description_snapshots_loaded", {
23
- correlationId: string;
23
+ workflowId: string;
24
24
  snapshots: ReactorDescriptionSnapshot[];
25
25
  }>;
26
26
  type AggregateTimelineLoaded = BaseEvent<"events/aggregate_timeline_loaded", {
27
- correlationId: string;
27
+ workflowId: string;
28
28
  entries: AggregateTimelineEntry[];
29
29
  }>;
30
30
  type OutcomesLoaded = BaseEvent<"events/outcomes_loaded", {
31
- correlationId: string;
31
+ workflowId: string;
32
32
  outcomes: ReactorOutcome[];
33
33
  }>;
34
- type CorrelationsLoaded = BaseEvent<"events/correlations_loaded", {
35
- correlations: CorrelationSummary[];
34
+ type AttemptsLoaded = BaseEvent<"events/attempts_loaded", {
35
+ workflowId: string;
36
+ attempts: ReactorAttempt[];
37
+ }>;
38
+ type WorkflowsLoaded = BaseEvent<"events/workflows_loaded", {
39
+ workflows: WorkflowSummary[];
36
40
  hasMore: boolean;
37
41
  append: boolean;
38
42
  }>;
@@ -47,7 +51,7 @@ type EventSelected = BaseEvent<"ui/event_selected", {
47
51
  }>;
48
52
  type EventDeselected = BaseEvent<"ui/event_deselected">;
49
53
  type FlowOpened = BaseEvent<"ui/flow_opened", {
50
- correlationId: string;
54
+ workflowId: string;
51
55
  }>;
52
56
  type FlowClosed = BaseEvent<"ui/flow_closed">;
53
57
  type FlowNodeSelected = BaseEvent<"ui/flow_node_selected", FlowSelection>;
@@ -64,7 +68,7 @@ type ScrubberPlayToggled = BaseEvent<"ui/scrubber_play_toggled">;
64
68
  type ScrubberSpeedChanged = BaseEvent<"ui/scrubber_speed_changed", {
65
69
  speed: number;
66
70
  }>;
67
- type CorrelationsRequested = BaseEvent<"ui/correlations_requested", {
71
+ type WorkflowsRequested = BaseEvent<"ui/workflows_requested", {
68
72
  search?: string;
69
73
  }>;
70
74
  type HandlerSelected = BaseEvent<"ui/handler_selected", {
@@ -73,10 +77,35 @@ type HandlerSelected = BaseEvent<"ui/handler_selected", {
73
77
  type AggregateLifecycleRequested = BaseEvent<"ui/aggregate_lifecycle_requested", {
74
78
  aggregateKey: string;
75
79
  }>;
76
- type LoadMoreCorrelationsRequested = BaseEvent<"ui/load_more_correlations_requested">;
80
+ type LoadMoreWorkflowsRequested = BaseEvent<"ui/load_more_workflows_requested">;
77
81
  type LocationChanged = BaseEvent<"location/changed", {
78
- correlationId: string | null;
82
+ workflowId: string | null;
79
83
  handler: string | null;
84
+ subject: string | null;
85
+ subjectMode: SubjectChainMode | null;
86
+ }>;
87
+ type SubjectSelected = BaseEvent<"ui/subject_selected", {
88
+ aggregateType: string;
89
+ aggregateId: string;
90
+ mode?: SubjectChainMode;
91
+ }>;
92
+ type SubjectModeChanged = BaseEvent<"ui/subject_mode_changed", {
93
+ mode: SubjectChainMode;
94
+ }>;
95
+ type SubjectChainLoadMore = BaseEvent<"ui/subject_chain_load_more">;
96
+ type EventEffectsRequested = BaseEvent<"ui/event_effects_requested", {
97
+ eventId: string;
98
+ }>;
99
+ type SubjectChainLoaded = BaseEvent<"events/subject_chain_loaded", {
100
+ events: SubjectChainEvent[];
101
+ hasMore: boolean;
102
+ cursor: number | null;
103
+ depthCapped: boolean;
104
+ append: boolean;
105
+ }>;
106
+ type EventEffectsLoaded = BaseEvent<"events/event_effects_loaded", {
107
+ eventId: string;
108
+ effects: InspectorEffect[];
80
109
  }>;
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;
110
+ export type InspectorMachineEvent = EventsReceived | SubscriptionConnected | SubscriptionError | PageLoaded | CausalTreeLoaded | FlowLoaded | LogsLoaded | DescriptionsLoaded | DescriptionSnapshotsLoaded | AggregateTimelineLoaded | OutcomesLoaded | AttemptsLoaded | WorkflowsLoaded | EventSelected | EventDeselected | FlowOpened | FlowClosed | FlowNodeSelected | FilterChanged | LoadMoreRequested | LayoutChanged | ScrubberStartChanged | ScrubberEndChanged | ScrubberPlayToggled | ScrubberSpeedChanged | WorkflowsRequested | ReactorDependenciesLoaded | AggregateKeysLoaded | HandlerSelected | LocationChanged | AggregateLifecycleLoaded | AggregateLifecycleRequested | LoadMoreWorkflowsRequested | SubjectSelected | SubjectModeChanged | SubjectChainLoadMore | EventEffectsRequested | SubjectChainLoaded | EventEffectsLoaded;
82
111
  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, WorkflowSummary, ReactorDependency, AggregateLifecycleEntry, Block, ReactorLog, ReactorDescription, ReactorDescriptionSnapshot, AggregateStateEntry, AggregateTimelineEntry, ReactorOutcome, ReactorAttempt, FilterState, LogsFilter, FlowSelection, PaneLayout, SubjectChainSourceMode, SubjectChainMode, SubjectChainEvent, InspectorEffect, AggregateKeyEntry, AggregateKeysPage, } 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_WORKFLOWS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_WORKFLOW, 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, INSPECTOR_SUBJECT_CHAIN, INSPECTOR_EFFECTS_FOR_EVENT, INSPECTOR_AGGREGATE_TYPES, INSPECTOR_AGGREGATE_KEYS_BY_TYPE, } 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";
@@ -17,6 +17,7 @@ export { FilterBar } from "./components/FilterBar";
17
17
  export { CopyablePayload } from "./components/CopyablePayload";
18
18
  export { JsonSyntax } from "./components/JsonSyntax";
19
19
  export { GlobalScrubber } from "./components/GlobalScrubber";
20
+ export { EffectList } from "./components/EffectList";
20
21
  export { TimelinePane } from "./panes/TimelinePane";
21
22
  export type { TimelinePaneProps } from "./panes/TimelinePane";
22
23
  export { CausalTreePane } from "./panes/CausalTreePane";
@@ -27,4 +28,5 @@ export { CausalFlowPane } from "./panes/CausalFlowPane";
27
28
  export type { CausalFlowPaneProps } from "./panes/CausalFlowPane";
28
29
  export { AggregateTimelinePane } from "./panes/AggregateTimelinePane";
29
30
  export { WaterfallPane } from "./panes/WaterfallPane";
30
- export { CorrelationExplorerPane } from "./panes/CorrelationExplorerPane";
31
+ export { WorkflowExplorerPane } from "./panes/WorkflowExplorerPane";
32
+ export { SubjectChainPane } from "./panes/SubjectChainPane";
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_WORKFLOWS, INSPECTOR_REACTOR_LOGS, INSPECTOR_REACTOR_LOGS_BY_WORKFLOW, 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, INSPECTOR_SUBJECT_CHAIN, INSPECTOR_EFFECTS_FOR_EVENT, INSPECTOR_AGGREGATE_TYPES, INSPECTOR_AGGREGATE_KEYS_BY_TYPE, } from "./queries";
11
11
  // ── Theme ──
12
12
  export { eventHue, eventBg, eventBorder, eventTextColor, LOG_LEVEL_COLORS, } from "./theme";
13
13
  // ── Utilities ──
@@ -19,6 +19,7 @@ export { FilterBar } from "./components/FilterBar";
19
19
  export { CopyablePayload } from "./components/CopyablePayload";
20
20
  export { JsonSyntax } from "./components/JsonSyntax";
21
21
  export { GlobalScrubber } from "./components/GlobalScrubber";
22
+ export { EffectList } from "./components/EffectList";
22
23
  // ── Panes ──
23
24
  export { TimelinePane } from "./panes/TimelinePane";
24
25
  export { CausalTreePane } from "./panes/CausalTreePane";
@@ -26,4 +27,5 @@ export { LogsPane } from "./panes/LogsPane";
26
27
  export { CausalFlowPane } from "./panes/CausalFlowPane";
27
28
  export { AggregateTimelinePane } from "./panes/AggregateTimelinePane";
28
29
  export { WaterfallPane } from "./panes/WaterfallPane";
29
- export { CorrelationExplorerPane } from "./panes/CorrelationExplorerPane";
30
+ export { WorkflowExplorerPane } from "./panes/WorkflowExplorerPane";
31
+ export { SubjectChainPane } from "./panes/SubjectChainPane";
@@ -144,8 +144,8 @@ function AggregateCard({ aggregateKey, state, prevState, diffMode, }) {
144
144
  }, children: [_jsx("span", { style: { fontSize: 9, color: "#52525b", transform: collapsed ? "rotate(-90deg)" : "rotate(0deg)", transition: "transform 100ms" }, children: "\u25BC" }), _jsx("span", { style: { fontSize: 11, fontWeight: 500, color: "#e4e4e7" }, children: aggType }), aggId && (_jsx("span", { style: { fontSize: 10, color: "#71717a", fontFamily: "monospace" }, children: aggId })), showDiff && diffs.length > 0 && (_jsxs("span", { style: { fontSize: 9, color: "#fbbf24", marginLeft: "auto" }, children: [diffs.length, " change", diffs.length !== 1 ? "s" : ""] })), showDiff && diffs.length === 0 && (_jsx("span", { style: { fontSize: 9, color: "#52525b", marginLeft: "auto" }, children: "unchanged" }))] }), !collapsed && (_jsx("div", { style: { padding: "4px 8px 6px" }, children: showDiff ? _jsx(DiffView, { diffs: diffs }) : _jsx(InlineJson, { value: state }) }))] }));
145
145
  }
146
146
  export function AggregateTimelinePane() {
147
- const correlationId = useSelector((s) => s.flowCorrelationId);
148
- const entries = useSelector((s) => correlationId ? s.aggregateTimeline[correlationId] ?? [] : []);
147
+ const workflowId = useSelector((s) => s.flowWorkflowId);
148
+ const entries = useSelector((s) => workflowId ? s.aggregateTimeline[workflowId] ?? [] : []);
149
149
  const scrubberStart = useSelector((s) => s.scrubberStart);
150
150
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
151
151
  const logsFilter = useSelector((s) => s.logsFilter);
@@ -185,11 +185,11 @@ export function AggregateTimelinePane() {
185
185
  const handleRowClick = useCallback((seq) => {
186
186
  dispatch({ type: "ui/scrubber_end_changed", payload: { end: seq } });
187
187
  }, [dispatch]);
188
- if (!correlationId) {
188
+ if (!workflowId) {
189
189
  return (_jsx("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#50506a", fontSize: 12, letterSpacing: "0.03em" }, children: "Open a flow to see the aggregate state timeline" }));
190
190
  }
191
191
  if (entries.length === 0) {
192
- return (_jsx("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#50506a", fontSize: 12, letterSpacing: "0.03em" }, children: "No aggregate state snapshots for this correlation" }));
192
+ return (_jsx("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "#50506a", fontSize: 12, letterSpacing: "0.03em" }, children: "No aggregate state snapshots for this workflow" }));
193
193
  }
194
194
  return (_jsxs("div", { style: { height: "100%", display: "flex", flexDirection: "column" }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4, padding: "6px 12px", borderBottom: "1px solid rgba(255,255,255,0.06)", flexShrink: 0, background: "rgba(15, 15, 20, 0.6)", backdropFilter: "blur(8px)" }, children: [_jsx("button", { onClick: () => setDiffMode(false), style: {
195
195
  fontSize: 10,
@@ -126,10 +126,10 @@ function buildFlowGraph(events, descriptions, outcomes, hiddenReactors, directio
126
126
  children.add(groupKey);
127
127
  reactorToChildTypes.set(evt.reactorId, children);
128
128
  }
129
- if (evt.parentId && evt.reactorId) {
130
- const reactors = parentToReactor.get(evt.parentId) ?? new Set();
129
+ if (evt.causationId && evt.reactorId) {
130
+ const reactors = parentToReactor.get(evt.causationId) ?? new Set();
131
131
  reactors.add(evt.reactorId);
132
- parentToReactor.set(evt.parentId, reactors);
132
+ parentToReactor.set(evt.causationId, reactors);
133
133
  }
134
134
  }
135
135
  const eventIdToGroup = new Map();
@@ -334,8 +334,8 @@ function computeVisibleIds(allEvents, start, end) {
334
334
  edgeIds.add(`hdl:${evt.reactorId}->evt:${evt.name}`);
335
335
  }
336
336
  // Parent event -> reactor edge
337
- if (evt.parentId && evt.reactorId) {
338
- const parentGroup = eventIdToGroup.get(evt.parentId);
337
+ if (evt.causationId && evt.reactorId) {
338
+ const parentGroup = eventIdToGroup.get(evt.causationId);
339
339
  if (parentGroup) {
340
340
  edgeIds.add(`evt:${parentGroup}->hdl:${evt.reactorId}`);
341
341
  }
@@ -343,7 +343,7 @@ function computeVisibleIds(allEvents, start, end) {
343
343
  // Root event -> reactor edges
344
344
  if (!evt.reactorId && evt.id) {
345
345
  for (const child of visible) {
346
- if (child.parentId === evt.id && child.reactorId) {
346
+ if (child.causationId === evt.id && child.reactorId) {
347
347
  const rootGroup = eventIdToGroup.get(evt.id);
348
348
  if (rootGroup) {
349
349
  edgeIds.add(`evt:${rootGroup}->hdl:${child.reactorId}`);
@@ -430,7 +430,7 @@ function ReactorFilter({ allReactorIds, hiddenReactors, setHiddenReactors }) {
430
430
  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" }))] })] }))] }));
431
431
  }
432
432
  export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
433
- const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
433
+ const flowWorkflowId = useSelector((s) => s.flowWorkflowId);
434
434
  const flowData = useSelector((s) => s.flowData);
435
435
  const flowSelection = useSelector((s) => s.flowSelection);
436
436
  const descriptionsMap = useSelector((s) => s.descriptions);
@@ -438,30 +438,30 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
438
438
  const scrubberStart = useSelector((s) => s.scrubberStart);
439
439
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
440
440
  const dispatch = useDispatch();
441
- const flowLoading = flowCorrelationId != null && flowData.length === 0;
441
+ const flowLoading = flowWorkflowId != null && flowData.length === 0;
442
442
  // Build typed maps from state
443
443
  const descriptions = useMemo(() => {
444
- if (!flowCorrelationId)
444
+ if (!flowWorkflowId)
445
445
  return undefined;
446
- const raw = descriptionsMap[flowCorrelationId];
446
+ const raw = descriptionsMap[flowWorkflowId];
447
447
  if (!raw)
448
448
  return undefined;
449
449
  const map = new Map();
450
450
  for (const d of raw)
451
451
  map.set(d.reactorId, d.blocks);
452
452
  return map;
453
- }, [descriptionsMap, flowCorrelationId]);
453
+ }, [descriptionsMap, flowWorkflowId]);
454
454
  const outcomes = useMemo(() => {
455
- if (!flowCorrelationId)
455
+ if (!flowWorkflowId)
456
456
  return undefined;
457
- const raw = outcomesMap[flowCorrelationId];
457
+ const raw = outcomesMap[flowWorkflowId];
458
458
  if (!raw)
459
459
  return undefined;
460
460
  const map = new Map();
461
461
  for (const o of raw)
462
462
  map.set(o.reactorId, o);
463
463
  return map;
464
- }, [outcomesMap, flowCorrelationId]);
464
+ }, [outcomesMap, flowWorkflowId]);
465
465
  const [hiddenReactors, setHiddenReactors] = useState(() => defaultHiddenReactors ?? new Set());
466
466
  const [direction, setDirection] = useState("LR");
467
467
  const allReactorIds = useMemo(() => {
@@ -590,11 +590,11 @@ export function CausalFlowPane({ defaultHiddenReactors, headerExtra } = {}) {
590
590
  dispatch({ type: "ui/flow_node_selected", payload: null });
591
591
  }, [dispatch]);
592
592
  const onNodesChange = useCallback((_changes) => { }, []);
593
- if (!flowCorrelationId) {
593
+ if (!flowWorkflowId) {
594
594
  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" }));
595
595
  }
596
596
  if (flowLoading) {
597
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" })] })] })] }) })] }));
598
598
  }
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 })] }) })] }));
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: flowWorkflowId }), _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 })] }) })] }));
600
600
  }
@@ -2,9 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useState, useMemo, useRef, useEffect, useCallback } from "react";
3
3
  import { useSelector, useDispatch } from "../machine";
4
4
  import { CopyablePayload } from "../components/CopyablePayload";
5
+ import { EffectList } from "../components/EffectList";
5
6
  import { eventTextColor } from "../theme";
6
7
  import { formatTs, compactPayload, copyToClipboard, inScrubberRange } from "../utils";
7
- import { Copy, Check, Search, X, ChevronRight, ChevronDown } from "lucide-react";
8
+ import { Copy, Check, Search, X, ChevronRight, ChevronDown, AlertTriangle, Zap } from "lucide-react";
8
9
  function buildTreeJson(roots, childrenMap) {
9
10
  function toNode(evt) {
10
11
  const children = evt.id ? (childrenMap.get(evt.id) ?? []) : [];
@@ -23,7 +24,7 @@ function buildTreeJson(roots, childrenMap) {
23
24
  // ---------------------------------------------------------------------------
24
25
  // ReactorNode — intermediate node grouping children by reactor_id
25
26
  // ---------------------------------------------------------------------------
26
- function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, }) {
27
+ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, isHighlighted, onClickReactor, outcome, outcomesByReactor, }) {
27
28
  const [collapsed, setCollapsed] = useState(false);
28
29
  const nodeRef = useRef(null);
29
30
  useEffect(() => {
@@ -34,16 +35,25 @@ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, i
34
35
  const handleClick = useCallback(() => {
35
36
  onClickReactor(reactorId, parentEventId);
36
37
  }, [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)))] }));
38
+ const isError = outcome?.status === "error";
39
+ const isRunning = outcome?.status === "running";
40
+ return (_jsxs("div", { className: depth > 0 ? "pl-6" : "", children: [_jsxs("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" : ""} ${isError ? "bg-red-500/8" : ""}`, 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 italic border ${isError
41
+ ? "bg-red-500/10 text-red-400/80 border-red-500/20"
42
+ : isRunning
43
+ ? "bg-yellow-500/10 text-yellow-400/80 border-yellow-500/20"
44
+ : "bg-white/[0.04] text-muted-foreground/60 border-border"}`, children: "reactor" }), _jsx("span", { className: "text-[10px] font-mono text-foreground/60 shrink-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", { className: "text-[10px] text-muted-foreground shrink-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
45
  }
39
46
  // ---------------------------------------------------------------------------
40
47
  // TreeNode (recursive)
41
48
  // ---------------------------------------------------------------------------
42
- function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, }) {
49
+ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, outcomesByReactor, }) {
43
50
  const selectedSeq = useSelector((s) => s.selectedSeq);
44
51
  const flowSelection = useSelector((s) => s.flowSelection);
52
+ const expandedEffects = useSelector((s) => s.expandedEffects);
53
+ const loadingEffectsIds = useSelector((s) => s.loadingEffects);
45
54
  const dispatch = useDispatch();
46
55
  const [payloadOpen, setPayloadOpen] = useState(false);
56
+ const [effectsOpen, setEffectsOpen] = useState(false);
47
57
  const [collapsed, setCollapsed] = useState(false);
48
58
  const [copied, setCopied] = useState(false);
49
59
  const isSelected = event.seq === selectedSeq;
@@ -74,17 +84,28 @@ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, })
74
84
  const highlightedReactorId = flowSelection?.kind === "reactor" ? flowSelection.reactorId : null;
75
85
  return (_jsxs("div", { className: depth > 0 ? "pl-6" : "", children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
76
86
  dispatch({ type: "ui/event_selected", payload: { seq: event.seq } });
77
- if (event.correlationId) {
78
- dispatch({ type: "ui/flow_opened", payload: { correlationId: event.correlationId } });
87
+ if (event.workflowId) {
88
+ dispatch({ type: "ui/flow_opened", payload: { workflowId: event.workflowId } });
79
89
  }
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) => {
90
+ }, 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, ")"] })), event.aggregateType && event.aggregateId && (_jsxs("button", { onClick: (e) => {
91
+ e.stopPropagation();
92
+ dispatch({ type: "ui/subject_selected", payload: { aggregateType: event.aggregateType, aggregateId: event.aggregateId, mode: "both" } });
93
+ }, className: "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: `View subject ${event.aggregateType}:${event.aggregateId}`, children: [event.aggregateType, ":", event.aggregateId.slice(0, 8)] })), _jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: formatTs(event.ts) }), event.id && (_jsxs("button", { onClick: (e) => {
94
+ e.stopPropagation();
95
+ if (!effectsOpen && expandedEffects[event.id] === undefined) {
96
+ dispatch({ type: "ui/event_effects_requested", payload: { eventId: event.id } });
97
+ }
98
+ setEffectsOpen((v) => !v);
99
+ }, className: `opacity-0 group-hover/tree:opacity-100 transition-all duration-150 flex items-center gap-1 px-1.5 py-0.5 rounded text-[9px] ${effectsOpen
100
+ ? "opacity-100 bg-indigo-500/10 text-indigo-400/70 border border-indigo-500/20"
101
+ : "hover:bg-white/[0.05] text-muted-foreground/50 border border-transparent"}`, title: "Show effects", children: [_jsx(Zap, { size: 9 }), expandedEffects[event.id] !== undefined && expandedEffects[event.id].length > 0 && (_jsx("span", { children: expandedEffects[event.id].length }))] })), _jsx("button", { onClick: (e) => {
81
102
  e.stopPropagation();
82
103
  const json = buildTreeJson([event], childrenMap);
83
104
  const text = JSON.stringify(json[0], null, 2);
84
105
  copyToClipboard(text);
85
106
  setCopied(true);
86
107
  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)))] }))] }));
108
+ }, 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" })), effectsOpen && (_jsx("div", { className: "mt-1 ml-3", children: loadingEffectsIds.includes(event.id ?? "") ? (_jsx("div", { className: "text-[9px] text-muted-foreground/40 italic py-1", children: "Loading\u2026" })) : (_jsx(EffectList, { effects: event.id ? (expandedEffects[event.id] ?? []) : [] })) }))] }), !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
109
  }
89
110
  // ---------------------------------------------------------------------------
90
111
  // matchesFlowSelection
@@ -100,10 +121,23 @@ export function CausalTreePane({ onInvestigate } = {}) {
100
121
  const causalTree = useSelector((s) => s.causalTree);
101
122
  const selectedSeq = useSelector((s) => s.selectedSeq);
102
123
  const flowSelection = useSelector((s) => s.flowSelection);
103
- const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
124
+ const flowWorkflowId = useSelector((s) => s.flowWorkflowId);
104
125
  const scrubberStart = useSelector((s) => s.scrubberStart);
105
126
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
127
+ const outcomesMap = useSelector((s) => s.outcomes);
106
128
  const dispatch = useDispatch();
129
+ // Build reactor outcome lookup for current flow
130
+ const outcomesByReactor = useMemo(() => {
131
+ if (!flowWorkflowId)
132
+ return new Map();
133
+ const raw = outcomesMap[flowWorkflowId];
134
+ if (!raw)
135
+ return new Map();
136
+ const map = new Map();
137
+ for (const o of raw)
138
+ map.set(o.reactorId, o);
139
+ return map;
140
+ }, [outcomesMap, flowWorkflowId]);
107
141
  const treeEvents = useMemo(() => {
108
142
  const all = causalTree?.events ?? null;
109
143
  if (all == null || (scrubberStart == null && scrubberEnd == null))
@@ -112,35 +146,35 @@ export function CausalTreePane({ onInvestigate } = {}) {
112
146
  }, [causalTree?.events, scrubberStart, scrubberEnd]);
113
147
  const treeLoading = selectedSeq != null && causalTree == null;
114
148
  const onClickReactor = useCallback((reactorId, _parentEventId) => {
115
- if (flowCorrelationId) {
149
+ if (flowWorkflowId) {
116
150
  dispatch({ type: "ui/flow_node_selected", payload: { kind: "reactor", reactorId } });
117
151
  }
118
152
  dispatch({ type: "ui/handler_selected", payload: { reactorId } });
119
- }, [flowCorrelationId, dispatch]);
153
+ }, [flowWorkflowId, dispatch]);
120
154
  const { roots, childrenMap, totalCount, filteredCount } = useMemo(() => {
121
155
  if (!treeEvents || treeEvents.length === 0)
122
156
  return { roots: [], childrenMap: new Map(), totalCount: 0, filteredCount: 0 };
123
157
  const total = treeEvents.length;
124
- const events = (flowCorrelationId && flowSelection)
158
+ const events = (flowWorkflowId && flowSelection)
125
159
  ? treeEvents.filter(e => matchesFlowSelection(e, flowSelection))
126
160
  : treeEvents;
127
161
  const idSet = new Set(events.map(e => e.id).filter(Boolean));
128
162
  const cMap = new Map();
129
163
  const rootList = [];
130
164
  for (const evt of events) {
131
- if (evt.parentId == null || !idSet.has(evt.parentId)) {
165
+ if (evt.causationId == null || !idSet.has(evt.causationId)) {
132
166
  rootList.push(evt);
133
167
  }
134
168
  else {
135
- const siblings = cMap.get(evt.parentId) ?? [];
169
+ const siblings = cMap.get(evt.causationId) ?? [];
136
170
  siblings.push(evt);
137
- cMap.set(evt.parentId, siblings);
171
+ cMap.set(evt.causationId, siblings);
138
172
  }
139
173
  }
140
174
  rootList.sort((a, b) => a.seq - b.seq);
141
175
  const filtered = rootList.length + [...cMap.values()].reduce((s, a) => s + a.length, 0);
142
176
  return { roots: rootList, childrenMap: cMap, totalCount: total, filteredCount: filtered };
143
- }, [treeEvents, flowCorrelationId, flowSelection]);
177
+ }, [treeEvents, flowWorkflowId, flowSelection]);
144
178
  if (treeLoading) {
145
179
  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" })] })] })] }));
146
180
  }
@@ -154,5 +188,5 @@ export function CausalTreePane({ onInvestigate } = {}) {
154
188
  }
155
189
  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"
156
190
  ? 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)))] }));
191
+ : `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, outcomesByReactor: outcomesByReactor }, root.seq)))] }));
158
192
  }
@@ -1,2 +1,2 @@
1
- export type CorrelationExplorerPaneProps = Record<string, never>;
2
- export declare function CorrelationExplorerPane(): import("react/jsx-runtime").JSX.Element;
1
+ export type WorkflowExplorerPaneProps = Record<string, never>;
2
+ export declare function WorkflowExplorerPane(): import("react/jsx-runtime").JSX.Element;
@@ -15,32 +15,32 @@ function RelativeDuration({ firstTs, lastTs }) {
15
15
  return _jsxs("span", { children: [(diffMs / 60_000).toFixed(1), "m"] });
16
16
  return _jsxs("span", { children: [(diffMs / 3_600_000).toFixed(1), "h"] });
17
17
  }
18
- export function CorrelationExplorerPane() {
19
- const correlations = useSelector((s) => s.correlations);
20
- const loading = useSelector((s) => s.correlationsLoading);
18
+ export function WorkflowExplorerPane() {
19
+ const workflows = useSelector((s) => s.workflows);
20
+ const loading = useSelector((s) => s.workflowsLoading);
21
21
  const dispatch = useDispatch();
22
22
  const [search, setSearch] = useState("");
23
23
  const searchTimerRef = useRef(null);
24
- // Request correlations on mount
24
+ // Request workflows on mount
25
25
  useEffect(() => {
26
- dispatch({ type: "ui/correlations_requested", payload: {} });
26
+ dispatch({ type: "ui/workflows_requested", payload: {} });
27
27
  }, [dispatch]);
28
28
  const handleSearchChange = useCallback((value) => {
29
29
  setSearch(value);
30
30
  if (searchTimerRef.current)
31
31
  clearTimeout(searchTimerRef.current);
32
32
  searchTimerRef.current = setTimeout(() => {
33
- dispatch({ type: "ui/correlations_requested", payload: { search: value || undefined } });
33
+ dispatch({ type: "ui/workflows_requested", payload: { search: value || undefined } });
34
34
  }, 300);
35
35
  }, [dispatch]);
36
- const handleRowClick = useCallback((correlationId) => {
37
- dispatch({ type: "ui/flow_opened", payload: { correlationId } });
36
+ const handleRowClick = useCallback((workflowId) => {
37
+ dispatch({ type: "ui/flow_opened", payload: { workflowId } });
38
38
  }, [dispatch]);
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: "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 workflow 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: "Workflow" }), _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 && workflows.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))) })) : workflows.length === 0 ? (_jsx("div", { className: "flex items-center justify-center h-32 text-xs text-muted-foreground/50 tracking-wide", children: "No workflows found" })) : (_jsx("div", { className: "flex-1 overflow-y-auto", children: workflows.map((corr) => (_jsxs("button", { onClick: () => handleRowClick(corr.workflowId), 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: {
43
43
  color: eventTextColor(corr.rootEventType),
44
44
  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))) }))] }));
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.workflowId}`, onClick: (e) => { e.stopPropagation(); handleCopy(corr.workflowId); }, children: corr.workflowId.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: "flex items-center gap-1 px-1.5 py-0.5 rounded text-[9px] font-semibold shrink-0 bg-red-500/10 text-red-400/80 border border-red-500/20", style: { boxShadow: "0 0 6px rgba(239, 68, 68, 0.15)" }, title: "This workflow has errors", children: "error" }))] }, corr.workflowId))) }))] }));
46
46
  }
@@ -10,7 +10,7 @@ import { Search, ChevronRight, ChevronDown } from "lucide-react";
10
10
  function LogRow({ log, showReactor }) {
11
11
  const [expanded, setExpanded] = useState(false);
12
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) }))] }));
13
+ return (_jsxs("div", { onClick: () => setExpanded((v) => !v), className: "px-2 py-1.5 hover:bg-white/[0.02] rounded-md transition-colors duration-100 cursor-pointer", children: [_jsxs("div", { className: "flex items-start 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 leading-[1.4]", children: formatTs(log.loggedAt) }), showReactor && (_jsx("span", { className: "text-[10px] font-mono text-muted-foreground/40 shrink-0 leading-[1.4]", children: log.reactorId })), _jsx("span", { className: `text-[11px] text-foreground/80 leading-[1.4] ${expanded ? "break-words whitespace-pre-wrap" : "truncate"}`, children: log.message }), _jsx("span", { className: "ml-auto shrink-0 text-muted-foreground/50", 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) }))] }));
14
14
  }
15
15
  export function LogsPane({ onInvestigate } = {}) {
16
16
  const logs = useSelector((s) => s.logs);
@@ -18,10 +18,10 @@ 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
- const isCorrelationScope = logsFilter.scope === "correlation" && logsFilter.correlationId != null;
24
- const hasFilter = logsFilter.reactorId != null || isCorrelationScope;
23
+ const isWorkflowScope = logsFilter.scope === "workflow" && logsFilter.workflowId != null;
24
+ const hasFilter = logsFilter.reactorId != null || isWorkflowScope;
25
25
  // Set of event IDs visible within scrubber range
26
26
  const visibleEventIds = useMemo(() => {
27
27
  if (scrubberStart == null && scrubberEnd == null)
@@ -59,7 +59,7 @@ 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)
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", "error"].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", 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 }), isWorkflowScope && _jsx("span", { className: "ml-1", children: "(all reactors in workflow)" })] })) : (_jsx("span", { children: "All reactors in workflow" })), _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: isWorkflowScope }, i)))] })] }));
65
65
  }
@@ -0,0 +1 @@
1
+ export declare function SubjectChainPane(): import("react/jsx-runtime").JSX.Element;