causal-inspector 0.1.6 → 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.
Files changed (37) hide show
  1. package/dist/CausalInspector.css +20 -447
  2. package/dist/CausalInspector.d.ts +8 -1
  3. package/dist/CausalInspector.js +32 -9
  4. package/dist/components/CopyablePayload.js +8 -8
  5. package/dist/components/EffectList.d.ts +4 -0
  6. package/dist/components/EffectList.js +15 -0
  7. package/dist/components/FilterBar.js +7 -10
  8. package/dist/components/GlobalScrubber.js +6 -6
  9. package/dist/components/JsonSyntax.js +8 -8
  10. package/dist/engines/query.js +131 -52
  11. package/dist/engines/scrubber.js +1 -1
  12. package/dist/engines/url.d.ts +5 -2
  13. package/dist/engines/url.js +50 -22
  14. package/dist/events.d.ts +38 -13
  15. package/dist/index.d.ts +5 -3
  16. package/dist/index.js +4 -2
  17. package/dist/panes/AggregateTimelinePane.js +4 -4
  18. package/dist/panes/CausalFlowPane.js +19 -19
  19. package/dist/panes/CausalTreePane.js +43 -34
  20. package/dist/panes/CorrelationExplorerPane.d.ts +2 -2
  21. package/dist/panes/CorrelationExplorerPane.js +10 -15
  22. package/dist/panes/LogsPane.js +8 -17
  23. package/dist/panes/SubjectChainPane.d.ts +1 -0
  24. package/dist/panes/SubjectChainPane.js +50 -0
  25. package/dist/panes/TimelinePane.js +33 -19
  26. package/dist/panes/WaterfallPane.js +5 -5
  27. package/dist/panes/WorkflowExplorerPane.d.ts +2 -0
  28. package/dist/panes/WorkflowExplorerPane.js +46 -0
  29. package/dist/queries.d.ts +16 -12
  30. package/dist/queries.js +103 -27
  31. package/dist/reducer.js +99 -38
  32. package/dist/state.d.ts +15 -5
  33. package/dist/state.js +16 -8
  34. package/dist/theme.js +4 -4
  35. package/dist/types.d.ts +52 -12
  36. package/dist/utils.js +1 -1
  37. package/package.json +1 -1
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, ReactorAttempt, 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,27 +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
34
  type AttemptsLoaded = BaseEvent<"events/attempts_loaded", {
35
- correlationId: string;
35
+ workflowId: string;
36
36
  attempts: ReactorAttempt[];
37
37
  }>;
38
- type CorrelationsLoaded = BaseEvent<"events/correlations_loaded", {
39
- correlations: CorrelationSummary[];
38
+ type WorkflowsLoaded = BaseEvent<"events/workflows_loaded", {
39
+ workflows: WorkflowSummary[];
40
40
  hasMore: boolean;
41
41
  append: boolean;
42
42
  }>;
@@ -51,7 +51,7 @@ type EventSelected = BaseEvent<"ui/event_selected", {
51
51
  }>;
52
52
  type EventDeselected = BaseEvent<"ui/event_deselected">;
53
53
  type FlowOpened = BaseEvent<"ui/flow_opened", {
54
- correlationId: string;
54
+ workflowId: string;
55
55
  }>;
56
56
  type FlowClosed = BaseEvent<"ui/flow_closed">;
57
57
  type FlowNodeSelected = BaseEvent<"ui/flow_node_selected", FlowSelection>;
@@ -68,7 +68,7 @@ type ScrubberPlayToggled = BaseEvent<"ui/scrubber_play_toggled">;
68
68
  type ScrubberSpeedChanged = BaseEvent<"ui/scrubber_speed_changed", {
69
69
  speed: number;
70
70
  }>;
71
- type CorrelationsRequested = BaseEvent<"ui/correlations_requested", {
71
+ type WorkflowsRequested = BaseEvent<"ui/workflows_requested", {
72
72
  search?: string;
73
73
  }>;
74
74
  type HandlerSelected = BaseEvent<"ui/handler_selected", {
@@ -77,10 +77,35 @@ type HandlerSelected = BaseEvent<"ui/handler_selected", {
77
77
  type AggregateLifecycleRequested = BaseEvent<"ui/aggregate_lifecycle_requested", {
78
78
  aggregateKey: string;
79
79
  }>;
80
- type LoadMoreCorrelationsRequested = BaseEvent<"ui/load_more_correlations_requested">;
80
+ type LoadMoreWorkflowsRequested = BaseEvent<"ui/load_more_workflows_requested">;
81
81
  type LocationChanged = BaseEvent<"location/changed", {
82
- correlationId: string | null;
82
+ workflowId: string | null;
83
83
  handler: string | null;
84
+ subject: string | null;
85
+ subjectMode: SubjectChainMode | null;
84
86
  }>;
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;
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[];
109
+ }>;
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;
86
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, ReactorAttempt, 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_ATTEMPTS, 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_ATTEMPTS, 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}`);
@@ -427,10 +427,10 @@ function ReactorFilter({ allReactorIds, hiddenReactors, setHiddenReactors }) {
427
427
  ? allReactorIds.filter(id => id.toLowerCase().includes(filter.toLowerCase()))
428
428
  : allReactorIds;
429
429
  const hiddenCount = hiddenReactors.size;
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" }))] })] }))] }));
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) {
594
- return (_jsx("div", { className: "ci-empty", children: "Select an event to visualize its causal flow" }));
593
+ if (!flowWorkflowId) {
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
- 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)" } })] })] })] }) })] }));
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: "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 })] }) })] }));
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, AlertTriangle } 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) ?? []) : [];
@@ -36,17 +37,11 @@ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, i
36
37
  }, [onClickReactor, parentEventId, reactorId]);
37
38
  const isError = outcome?.status === "error";
38
39
  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)))] }));
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)))] }));
50
45
  }
51
46
  // ---------------------------------------------------------------------------
52
47
  // TreeNode (recursive)
@@ -54,8 +49,11 @@ function ReactorNode({ reactorId, parentEventId, children, childrenMap, depth, i
54
49
  function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, outcomesByReactor, }) {
55
50
  const selectedSeq = useSelector((s) => s.selectedSeq);
56
51
  const flowSelection = useSelector((s) => s.flowSelection);
52
+ const expandedEffects = useSelector((s) => s.expandedEffects);
53
+ const loadingEffectsIds = useSelector((s) => s.loadingEffects);
57
54
  const dispatch = useDispatch();
58
55
  const [payloadOpen, setPayloadOpen] = useState(false);
56
+ const [effectsOpen, setEffectsOpen] = useState(false);
59
57
  const [collapsed, setCollapsed] = useState(false);
60
58
  const [copied, setCopied] = useState(false);
61
59
  const isSelected = event.seq === selectedSeq;
@@ -84,19 +82,30 @@ function TreeNode({ event, childrenMap, depth, onClickReactor, onInvestigate, ou
84
82
  return { reactorGroups: groups, directChildren: direct };
85
83
  }, [children]);
86
84
  const highlightedReactorId = flowSelection?.kind === "reactor" ? flowSelection.reactorId : null;
87
- return (_jsxs("div", { style: depth > 0 ? { paddingLeft: 24 } : undefined, children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
85
+ return (_jsxs("div", { className: depth > 0 ? "pl-6" : "", children: [_jsxs("div", { ref: isSelected ? nodeRef : undefined, onClick: () => {
88
86
  dispatch({ type: "ui/event_selected", payload: { seq: event.seq } });
89
- if (event.correlationId) {
90
- dispatch({ type: "ui/flow_opened", payload: { correlationId: event.correlationId } });
87
+ if (event.workflowId) {
88
+ dispatch({ type: "ui/flow_opened", payload: { workflowId: event.workflowId } });
91
89
  }
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) => {
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) => {
93
102
  e.stopPropagation();
94
103
  const json = buildTreeJson([event], childrenMap);
95
104
  const text = JSON.stringify(json[0], null, 2);
96
105
  copyToClipboard(text);
97
106
  setCopied(true);
98
107
  setTimeout(() => setCopied(false), 1500);
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)))] }))] }));
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)))] }))] }));
100
109
  }
101
110
  // ---------------------------------------------------------------------------
102
111
  // matchesFlowSelection
@@ -112,23 +121,23 @@ export function CausalTreePane({ onInvestigate } = {}) {
112
121
  const causalTree = useSelector((s) => s.causalTree);
113
122
  const selectedSeq = useSelector((s) => s.selectedSeq);
114
123
  const flowSelection = useSelector((s) => s.flowSelection);
115
- const flowCorrelationId = useSelector((s) => s.flowCorrelationId);
124
+ const flowWorkflowId = useSelector((s) => s.flowWorkflowId);
116
125
  const scrubberStart = useSelector((s) => s.scrubberStart);
117
126
  const scrubberEnd = useSelector((s) => s.scrubberEnd);
118
127
  const outcomesMap = useSelector((s) => s.outcomes);
119
128
  const dispatch = useDispatch();
120
129
  // Build reactor outcome lookup for current flow
121
130
  const outcomesByReactor = useMemo(() => {
122
- if (!flowCorrelationId)
131
+ if (!flowWorkflowId)
123
132
  return new Map();
124
- const raw = outcomesMap[flowCorrelationId];
133
+ const raw = outcomesMap[flowWorkflowId];
125
134
  if (!raw)
126
135
  return new Map();
127
136
  const map = new Map();
128
137
  for (const o of raw)
129
138
  map.set(o.reactorId, o);
130
139
  return map;
131
- }, [outcomesMap, flowCorrelationId]);
140
+ }, [outcomesMap, flowWorkflowId]);
132
141
  const treeEvents = useMemo(() => {
133
142
  const all = causalTree?.events ?? null;
134
143
  if (all == null || (scrubberStart == null && scrubberEnd == null))
@@ -137,47 +146,47 @@ export function CausalTreePane({ onInvestigate } = {}) {
137
146
  }, [causalTree?.events, scrubberStart, scrubberEnd]);
138
147
  const treeLoading = selectedSeq != null && causalTree == null;
139
148
  const onClickReactor = useCallback((reactorId, _parentEventId) => {
140
- if (flowCorrelationId) {
149
+ if (flowWorkflowId) {
141
150
  dispatch({ type: "ui/flow_node_selected", payload: { kind: "reactor", reactorId } });
142
151
  }
143
152
  dispatch({ type: "ui/handler_selected", payload: { reactorId } });
144
- }, [flowCorrelationId, dispatch]);
153
+ }, [flowWorkflowId, dispatch]);
145
154
  const { roots, childrenMap, totalCount, filteredCount } = useMemo(() => {
146
155
  if (!treeEvents || treeEvents.length === 0)
147
156
  return { roots: [], childrenMap: new Map(), totalCount: 0, filteredCount: 0 };
148
157
  const total = treeEvents.length;
149
- const events = (flowCorrelationId && flowSelection)
158
+ const events = (flowWorkflowId && flowSelection)
150
159
  ? treeEvents.filter(e => matchesFlowSelection(e, flowSelection))
151
160
  : treeEvents;
152
161
  const idSet = new Set(events.map(e => e.id).filter(Boolean));
153
162
  const cMap = new Map();
154
163
  const rootList = [];
155
164
  for (const evt of events) {
156
- if (evt.parentId == null || !idSet.has(evt.parentId)) {
165
+ if (evt.causationId == null || !idSet.has(evt.causationId)) {
157
166
  rootList.push(evt);
158
167
  }
159
168
  else {
160
- const siblings = cMap.get(evt.parentId) ?? [];
169
+ const siblings = cMap.get(evt.causationId) ?? [];
161
170
  siblings.push(evt);
162
- cMap.set(evt.parentId, siblings);
171
+ cMap.set(evt.causationId, siblings);
163
172
  }
164
173
  }
165
174
  rootList.sort((a, b) => a.seq - b.seq);
166
175
  const filtered = rootList.length + [...cMap.values()].reduce((s, a) => s + a.length, 0);
167
176
  return { roots: rootList, childrenMap: cMap, totalCount: total, filteredCount: filtered };
168
- }, [treeEvents, flowCorrelationId, flowSelection]);
177
+ }, [treeEvents, flowWorkflowId, flowSelection]);
169
178
  if (treeLoading) {
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 } })] })] })] }));
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" })] })] })] }));
171
180
  }
172
181
  if (!treeEvents) {
173
- return (_jsx("div", { className: "ci-empty", children: "Select an event to view its causal tree" }));
182
+ 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" }));
174
183
  }
175
184
  if (roots.length === 0 && flowSelection) {
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"
185
+ 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"
177
186
  ? flowSelection.name
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" })] }));
187
+ : `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" })] }));
179
188
  }
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"
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"
181
190
  ? flowSelection.name
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)))] }));
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)))] }));
183
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;