footprint-explainable-ui 0.6.0 → 0.7.3
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/README.md +158 -401
- package/dist/flowchart.cjs +721 -485
- package/dist/flowchart.cjs.map +1 -1
- package/dist/flowchart.d.cts +68 -15
- package/dist/flowchart.d.ts +68 -15
- package/dist/flowchart.js +723 -488
- package/dist/flowchart.js.map +1 -1
- package/dist/index.cjs +1158 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -31
- package/dist/index.d.ts +115 -31
- package/dist/index.js +1159 -442
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# footprint-explainable-ui
|
|
2
2
|
|
|
3
|
-
Themeable React components for visualizing [
|
|
3
|
+
Themeable React components for visualizing [footprintjs](https://github.com/footprintjs/footPrint) pipeline execution — time-travel debugging, flowchart overlays, subflow drill-down, and collapsible detail panels.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -21,46 +21,62 @@ npm install @xyflow/react
|
|
|
21
21
|
| Import path | What it provides |
|
|
22
22
|
|---|---|
|
|
23
23
|
| `footprint-explainable-ui` | Core components, themes, adapters |
|
|
24
|
-
| `footprint-explainable-ui/flowchart` | Flowchart visualization
|
|
24
|
+
| `footprint-explainable-ui/flowchart` | Flowchart visualization (requires `@xyflow/react`) |
|
|
25
25
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
|
-
### 1. Convert
|
|
28
|
+
### 1. Convert execution data to snapshots
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
import { FlowChartExecutor } from "
|
|
31
|
+
import { FlowChartExecutor } from "footprintjs";
|
|
32
32
|
import { toVisualizationSnapshots } from "footprint-explainable-ui";
|
|
33
33
|
|
|
34
34
|
const executor = new FlowChartExecutor(chart);
|
|
35
|
-
await executor.run();
|
|
35
|
+
await executor.run({ input: data });
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const snapshots = toVisualizationSnapshots(
|
|
38
|
+
executor.getSnapshot(),
|
|
39
|
+
executor.getNarrativeEntries(), // optional — enables rich narrative
|
|
40
|
+
);
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
### 2. Render with the all-in-one shell
|
|
42
44
|
|
|
43
45
|
```tsx
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
FootprintTheme,
|
|
47
|
-
warmDark,
|
|
48
|
-
} from "footprint-explainable-ui";
|
|
46
|
+
import { ExplainableShell } from "footprint-explainable-ui";
|
|
47
|
+
import { TracedFlowchartView } from "footprint-explainable-ui/flowchart";
|
|
49
48
|
|
|
50
|
-
function
|
|
49
|
+
function DebugView({ snapshots, spec, narrative, narrativeEntries }) {
|
|
51
50
|
return (
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
<ExplainableShell
|
|
52
|
+
snapshots={snapshots}
|
|
53
|
+
spec={spec}
|
|
54
|
+
narrative={narrative}
|
|
55
|
+
narrativeEntries={narrativeEntries}
|
|
56
|
+
title="My Pipeline"
|
|
57
|
+
panelLabels={{ topology: "What Ran", details: "What Happened", timeline: "How Long" }}
|
|
58
|
+
renderFlowchart={({ spec, snapshots, selectedIndex, onNodeClick }) => (
|
|
59
|
+
<TracedFlowchartView
|
|
60
|
+
spec={spec}
|
|
61
|
+
snapshots={snapshots}
|
|
62
|
+
snapshotIndex={selectedIndex}
|
|
63
|
+
onNodeClick={onNodeClick}
|
|
64
|
+
/>
|
|
65
|
+
)}
|
|
66
|
+
/>
|
|
60
67
|
);
|
|
61
68
|
}
|
|
62
69
|
```
|
|
63
70
|
|
|
71
|
+
This gives you:
|
|
72
|
+
- **Flowchart** (center) — execution path overlay, click subflow nodes to drill-down
|
|
73
|
+
- **Topology panel** (left) — subflow tree navigator, collapsible via VLinePill handle
|
|
74
|
+
- **Details panel** (right) — Memory state + Narrative tabs, collapsible
|
|
75
|
+
- **Timeline** (bottom) — Gantt-style stage durations, collapsible
|
|
76
|
+
- **Time-travel slider** — scrub through execution steps
|
|
77
|
+
- **Breadcrumbs** — navigate back from subflow drill-down
|
|
78
|
+
- **Mobile responsive** — auto-stacks vertically below 640px
|
|
79
|
+
|
|
64
80
|
### 3. Or compose individual components
|
|
65
81
|
|
|
66
82
|
```tsx
|
|
@@ -99,225 +115,161 @@ function MyDebugger({ snapshots }) {
|
|
|
99
115
|
|
|
100
116
|
---
|
|
101
117
|
|
|
102
|
-
##
|
|
118
|
+
## ExplainableShell
|
|
103
119
|
|
|
104
|
-
|
|
120
|
+
The all-in-one orchestrator. Handles time-travel, subflow drill-down, memory/narrative panels, and responsive layout.
|
|
105
121
|
|
|
106
|
-
|
|
107
|
-
import {
|
|
108
|
-
StageNode,
|
|
109
|
-
specToReactFlow,
|
|
110
|
-
useSubflowNavigation,
|
|
111
|
-
SubflowBreadcrumb,
|
|
112
|
-
type SpecNode,
|
|
113
|
-
type ExecutionOverlay,
|
|
114
|
-
} from "footprint-explainable-ui/flowchart";
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Static flowchart from pipeline spec
|
|
122
|
+
### Props
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
| Prop | Type | Default | Description |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `snapshots` | `StageSnapshot[]` | required | Visualization snapshots |
|
|
127
|
+
| `spec` | `SpecNode \| null` | — | Pipeline spec (enables flowchart + subflow tree) |
|
|
128
|
+
| `title` | `string` | `"Flowchart"` | Breadcrumb root label |
|
|
129
|
+
| `narrative` | `string[]` | — | Flat narrative lines |
|
|
130
|
+
| `narrativeEntries` | `NarrativeEntry[]` | — | Structured narrative (rich rendering) |
|
|
131
|
+
| `panelLabels` | `PanelLabels` | `{ topology: "Topology", details: "Details", timeline: "Timeline" }` | Customize collapsible pill labels |
|
|
132
|
+
| `defaultExpanded` | `DefaultExpanded` | `{ details: true }` | Which panels start open |
|
|
133
|
+
| `tabs` | `ShellTab[]` | `["result", "explainable"]` | Visible tabs |
|
|
134
|
+
| `renderFlowchart` | `(props) => ReactNode` | — | Flowchart renderer (pass `TracedFlowchartView`) |
|
|
135
|
+
| `resultData` | `Record<string, unknown>` | — | Final output data for Result tab |
|
|
136
|
+
| `size` | `"compact" \| "default" \| "detailed"` | `"default"` | Size variant |
|
|
137
|
+
| `unstyled` | `boolean` | `false` | Strip styles, render `data-fp` attributes |
|
|
123
138
|
|
|
124
|
-
|
|
139
|
+
### Panel Labels
|
|
125
140
|
|
|
126
|
-
|
|
127
|
-
const { nodes, edges } = specToReactFlow(spec);
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<ReactFlow
|
|
131
|
-
nodes={nodes}
|
|
132
|
-
edges={edges}
|
|
133
|
-
nodeTypes={nodeTypes}
|
|
134
|
-
fitView
|
|
135
|
-
/>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Self-contained traced flowchart (recommended)
|
|
141
|
-
|
|
142
|
-
`TracedFlowchartView` handles everything — overlay computation, subflow drill-down, breadcrumbs. Just pass `spec` + `snapshots`:
|
|
141
|
+
Customize the text on collapsible pill buttons. Semantic keys — not tied to position:
|
|
143
142
|
|
|
144
143
|
```tsx
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<TracedFlowchartView
|
|
153
|
-
spec={spec}
|
|
154
|
-
snapshots={snapshots}
|
|
155
|
-
snapshotIndex={idx}
|
|
156
|
-
onNodeClick={(i) => setIdx(i as number)}
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
}
|
|
144
|
+
<ExplainableShell
|
|
145
|
+
panelLabels={{
|
|
146
|
+
topology: "What Ran", // left panel (subflow tree)
|
|
147
|
+
details: "What Happened", // right panel (memory/narrative)
|
|
148
|
+
timeline: "How Long", // bottom panel (Gantt)
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
161
151
|
```
|
|
162
152
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
### With execution overlay (manual control)
|
|
153
|
+
### Default Expanded
|
|
166
154
|
|
|
167
|
-
|
|
155
|
+
Control which panels start open. Desktop default: details panel open (flowchart + memory = the library's unique value). For mobile, pass all `false`:
|
|
168
156
|
|
|
169
157
|
```tsx
|
|
170
|
-
|
|
158
|
+
// Desktop (default) — memory panel open
|
|
159
|
+
<ExplainableShell snapshots={...} spec={...} />
|
|
171
160
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
executionOrder: ["LoadOrder", "ProcessPayment", "ShipOrder"],
|
|
178
|
-
};
|
|
161
|
+
// Mobile — all collapsed, flowchart fills screen
|
|
162
|
+
<ExplainableShell
|
|
163
|
+
snapshots={...}
|
|
164
|
+
defaultExpanded={{ details: false }}
|
|
165
|
+
/>
|
|
179
166
|
|
|
180
|
-
|
|
167
|
+
// Everything open
|
|
168
|
+
<ExplainableShell
|
|
169
|
+
snapshots={...}
|
|
170
|
+
defaultExpanded={{ topology: true, details: true, timeline: true }}
|
|
171
|
+
/>
|
|
181
172
|
```
|
|
182
173
|
|
|
183
|
-
|
|
174
|
+
### Responsive Layout
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
function buildOverlay(snapshots, idx): ExecutionOverlay {
|
|
187
|
-
const executionOrder = snapshots.slice(0, idx + 1).map(s => s.stageLabel);
|
|
188
|
-
const doneStages = new Set(snapshots.slice(0, idx).map(s => s.stageLabel));
|
|
189
|
-
const activeStage = snapshots[idx]?.stageLabel ?? null;
|
|
190
|
-
const executedStages = new Set([...doneStages]);
|
|
191
|
-
if (activeStage) executedStages.add(activeStage);
|
|
192
|
-
return { doneStages, activeStage, executedStages, executionOrder };
|
|
193
|
-
}
|
|
194
|
-
```
|
|
176
|
+
The shell auto-detects container width via `ResizeObserver`:
|
|
195
177
|
|
|
196
|
-
|
|
178
|
+
- **Desktop (≥640px):** 3-column layout — SubflowTree | Flowchart | Memory/Narrative. Side panels collapse to VLinePill handles.
|
|
179
|
+
- **Mobile (<640px):** Stacked vertical — Flowchart (350px) → collapsible HLinePill sections. All panels auto-collapse on narrow.
|
|
197
180
|
|
|
198
|
-
|
|
199
|
-
const { nodes, edges } = specToReactFlow(spec, overlay, {
|
|
200
|
-
edgeExecuted: "#00ff88", // Completed path
|
|
201
|
-
edgeActive: "#ff6b6b", // Currently executing
|
|
202
|
-
});
|
|
203
|
-
```
|
|
181
|
+
### Collapsible Panel UX
|
|
204
182
|
|
|
205
|
-
|
|
183
|
+
All panels use the **line + pill** pattern:
|
|
184
|
+
- **Collapsed:** Thin divider line with a centered pill button (label + arrow)
|
|
185
|
+
- **Expanded:** Full content with a pill handle on the closing edge
|
|
186
|
+
- **VLinePill** (left/right panels): Vertical line with centered vertical pill. `side` prop controls arrow direction.
|
|
187
|
+
- **HLinePill** (bottom timeline): Horizontal line with centered pill.
|
|
206
188
|
|
|
207
|
-
|
|
189
|
+
---
|
|
208
190
|
|
|
209
|
-
|
|
210
|
-
import {
|
|
211
|
-
useSubflowNavigation,
|
|
212
|
-
SubflowBreadcrumb,
|
|
213
|
-
specToReactFlow,
|
|
214
|
-
StageNode,
|
|
215
|
-
} from "footprint-explainable-ui/flowchart";
|
|
216
|
-
import { ReactFlow } from "@xyflow/react";
|
|
191
|
+
## Flowchart Visualization
|
|
217
192
|
|
|
218
|
-
|
|
193
|
+
Import from `footprint-explainable-ui/flowchart`:
|
|
219
194
|
|
|
220
|
-
|
|
221
|
-
const subflowNav = useSubflowNavigation(spec);
|
|
195
|
+
### TracedFlowchartView (recommended)
|
|
222
196
|
|
|
223
|
-
|
|
224
|
-
const currentSpec = subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1].spec;
|
|
225
|
-
const { nodes, edges } = specToReactFlow(currentSpec, overlay);
|
|
197
|
+
Self-contained flowchart renderer. Handles overlay computation, auto-fitView on resize.
|
|
226
198
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{/* Breadcrumb bar: Pipeline > PaymentSubflow */}
|
|
230
|
-
{subflowNav.isInSubflow && (
|
|
231
|
-
<SubflowBreadcrumb
|
|
232
|
-
breadcrumbs={subflowNav.breadcrumbs}
|
|
233
|
-
onNavigate={subflowNav.navigateTo}
|
|
234
|
-
/>
|
|
235
|
-
)}
|
|
199
|
+
```tsx
|
|
200
|
+
import { TracedFlowchartView } from "footprint-explainable-ui/flowchart";
|
|
236
201
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
246
|
-
}
|
|
202
|
+
<div style={{ height: 400 }}>
|
|
203
|
+
<TracedFlowchartView
|
|
204
|
+
spec={spec}
|
|
205
|
+
snapshots={snapshots}
|
|
206
|
+
snapshotIndex={idx}
|
|
207
|
+
onNodeClick={(nodeId) => handleClick(nodeId)}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
247
210
|
```
|
|
248
211
|
|
|
249
|
-
|
|
212
|
+
Without `snapshots`, renders a plain static flowchart. With `snapshots`, shows the execution trace path with Google Maps-style glow.
|
|
250
213
|
|
|
251
|
-
|
|
252
|
-
|---|---|---|
|
|
253
|
-
| `breadcrumbs` | `BreadcrumbEntry[]` | Stack from root to current level |
|
|
254
|
-
| `nodes` | `Node[]` | ReactFlow nodes for current level |
|
|
255
|
-
| `edges` | `Edge[]` | ReactFlow edges for current level |
|
|
256
|
-
| `handleNodeClick` | `(nodeId) => boolean` | Drills into subflow if applicable |
|
|
257
|
-
| `navigateTo` | `(level) => void` | Jump to breadcrumb level (0 = root) |
|
|
258
|
-
| `isInSubflow` | `boolean` | Whether we're inside a subflow |
|
|
259
|
-
| `currentSubflowNodeName` | `string \| null` | Name of the subflow node drilled into |
|
|
214
|
+
**Auto-fitView:** The flowchart automatically calls `fitView()` when its container resizes (e.g. panel expand/collapse).
|
|
260
215
|
|
|
261
|
-
###
|
|
262
|
-
|
|
263
|
-
When drilled into a subflow, extract its execution snapshots from the parent's memory:
|
|
216
|
+
### Manual control with specToReactFlow
|
|
264
217
|
|
|
265
218
|
```tsx
|
|
266
|
-
import {
|
|
219
|
+
import { specToReactFlow, StageNode, type ExecutionOverlay } from "footprint-explainable-ui/flowchart";
|
|
220
|
+
import { ReactFlow } from "@xyflow/react";
|
|
267
221
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
for (const snap of subflowSnapshots) {
|
|
284
|
-
if (snap.stageLabel.startsWith(prefix))
|
|
285
|
-
snap.stageLabel = snap.stageLabel.slice(prefix.length);
|
|
286
|
-
if (snap.stageName.startsWith(prefix))
|
|
287
|
-
snap.stageName = snap.stageName.slice(prefix.length);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
222
|
+
const overlay: ExecutionOverlay = {
|
|
223
|
+
doneStages: new Set(["LoadOrder", "ProcessPayment"]),
|
|
224
|
+
activeStage: "ShipOrder",
|
|
225
|
+
executedStages: new Set(["LoadOrder", "ProcessPayment", "ShipOrder"]),
|
|
226
|
+
executionOrder: ["LoadOrder", "ProcessPayment", "ShipOrder"],
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const { nodes, edges } = specToReactFlow(spec, overlay);
|
|
230
|
+
|
|
231
|
+
<ReactFlow
|
|
232
|
+
nodes={nodes}
|
|
233
|
+
edges={edges}
|
|
234
|
+
nodeTypes={{ stage: StageNode }}
|
|
235
|
+
fitView
|
|
236
|
+
/>
|
|
291
237
|
```
|
|
292
238
|
|
|
293
239
|
---
|
|
294
240
|
|
|
295
241
|
## Theming
|
|
296
242
|
|
|
297
|
-
###
|
|
298
|
-
|
|
299
|
-
```tsx
|
|
300
|
-
import { FootprintTheme, warmDark, warmLight, coolDark } from "footprint-explainable-ui";
|
|
301
|
-
|
|
302
|
-
<FootprintTheme tokens={warmDark}>
|
|
303
|
-
<MyApp />
|
|
304
|
-
</FootprintTheme>
|
|
305
|
-
```
|
|
243
|
+
### CSS Variables (recommended)
|
|
306
244
|
|
|
307
|
-
|
|
245
|
+
Consumer controls theme via `--fp-*` CSS custom properties. Components use `var(--fp-*, fallback)`:
|
|
308
246
|
|
|
309
247
|
```css
|
|
310
248
|
:root {
|
|
311
249
|
--fp-color-primary: #7c6cf0;
|
|
312
250
|
--fp-bg-primary: #1e1a2e;
|
|
313
251
|
--fp-bg-secondary: #2a2540;
|
|
252
|
+
--fp-bg-tertiary: #3a3455;
|
|
314
253
|
--fp-text-primary: #f0e6d6;
|
|
254
|
+
--fp-text-secondary: #b0a898;
|
|
255
|
+
--fp-text-muted: #6b6b80;
|
|
315
256
|
--fp-border: #3a3455;
|
|
316
257
|
--fp-radius: 8px;
|
|
258
|
+
--fp-font-sans: 'Inter', system-ui, sans-serif;
|
|
317
259
|
--fp-font-mono: 'JetBrains Mono', monospace;
|
|
318
260
|
}
|
|
319
261
|
```
|
|
320
262
|
|
|
263
|
+
### ThemeProvider
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { FootprintTheme, warmDark } from "footprint-explainable-ui";
|
|
267
|
+
|
|
268
|
+
<FootprintTheme tokens={warmDark}>
|
|
269
|
+
<MyApp />
|
|
270
|
+
</FootprintTheme>
|
|
271
|
+
```
|
|
272
|
+
|
|
321
273
|
### Built-in Presets
|
|
322
274
|
|
|
323
275
|
| Preset | Description |
|
|
@@ -325,31 +277,7 @@ import { FootprintTheme, warmDark, warmLight, coolDark } from "footprint-explain
|
|
|
325
277
|
| `coolDark` | Default — indigo/slate dark theme |
|
|
326
278
|
| `warmDark` | Charcoal-purple with warm text |
|
|
327
279
|
| `warmLight` | Cream/peach light theme |
|
|
328
|
-
|
|
329
|
-
### Token Reference
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
interface ThemeTokens {
|
|
333
|
-
colors?: {
|
|
334
|
-
primary?: string; // Accent (buttons, highlights)
|
|
335
|
-
success?: string; // Completed stages
|
|
336
|
-
error?: string; // Error states
|
|
337
|
-
warning?: string; // Warnings
|
|
338
|
-
bgPrimary?: string; // Main background
|
|
339
|
-
bgSecondary?: string; // Panel/card background
|
|
340
|
-
bgTertiary?: string; // Hover/active background
|
|
341
|
-
textPrimary?: string; // Main text
|
|
342
|
-
textSecondary?: string; // Secondary text
|
|
343
|
-
textMuted?: string; // Dimmed text
|
|
344
|
-
border?: string; // Borders
|
|
345
|
-
};
|
|
346
|
-
radius?: string;
|
|
347
|
-
fontFamily?: {
|
|
348
|
-
sans?: string; // UI text font
|
|
349
|
-
mono?: string; // Code/data font
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
```
|
|
280
|
+
| `coolLight` | Light indigo theme |
|
|
353
281
|
|
|
354
282
|
---
|
|
355
283
|
|
|
@@ -359,35 +287,47 @@ interface ThemeTokens {
|
|
|
359
287
|
|
|
360
288
|
| Component | Description |
|
|
361
289
|
|---|---|
|
|
362
|
-
| `ExplainableShell` |
|
|
290
|
+
| `ExplainableShell` | All-in-one orchestrator with collapsible panels and responsive layout |
|
|
363
291
|
| `TimeTravelControls` | Play/pause, prev/next, scrubber timeline |
|
|
292
|
+
| `MemoryPanel` | Memory state + scope diff (composite right-panel view) |
|
|
293
|
+
| `NarrativePanel` | Narrative trace with progressive reveal |
|
|
294
|
+
| `StoryNarrative` | Rich rendering of structured `NarrativeEntry[]` |
|
|
364
295
|
| `NarrativeTrace` | Collapsible stage groups with progressive reveal |
|
|
365
296
|
| `NarrativeLog` | Simple timeline-style execution log |
|
|
366
297
|
| `ScopeDiff` | Side-by-side scope changes (added/changed/removed) |
|
|
367
298
|
| `ResultPanel` | Final pipeline output + console logs |
|
|
368
299
|
| `MemoryInspector` | Accumulated memory state viewer |
|
|
369
|
-
| `GanttTimeline` | Horizontal duration timeline |
|
|
300
|
+
| `GanttTimeline` | Horizontal duration timeline (collapsible) |
|
|
370
301
|
| `SnapshotPanel` | All-in-one inspector (scrubber + memory + narrative + Gantt) |
|
|
371
302
|
|
|
372
303
|
### Flowchart Components (`footprint-explainable-ui/flowchart`)
|
|
373
304
|
|
|
374
305
|
| Export | Description |
|
|
375
306
|
|---|---|
|
|
376
|
-
| `TracedFlowchartView` | Self-contained flowchart with trace overlay
|
|
377
|
-
| `FlowchartView` | Lower-level ReactFlow wrapper
|
|
307
|
+
| `TracedFlowchartView` | Self-contained flowchart with trace overlay and auto-fitView |
|
|
308
|
+
| `FlowchartView` | Lower-level ReactFlow wrapper |
|
|
378
309
|
| `StageNode` | Custom node with state-aware coloring, step badges, pulse rings |
|
|
379
|
-
| `specToReactFlow` | Convert pipeline spec → ReactFlow nodes/edges with
|
|
380
|
-
| `TimeTravelDebugger` | Full debugger with flowchart + all panels |
|
|
310
|
+
| `specToReactFlow` | Convert pipeline spec → ReactFlow nodes/edges with overlay |
|
|
381
311
|
| `SubflowBreadcrumb` | Breadcrumb bar for subflow drill-down |
|
|
382
|
-
| `
|
|
312
|
+
| `SubflowTree` | Tree view of all subflows (used in shell's left panel) |
|
|
383
313
|
|
|
384
314
|
### Adapters
|
|
385
315
|
|
|
386
316
|
| Export | Description |
|
|
387
317
|
|---|---|
|
|
388
318
|
| `toVisualizationSnapshots` | Convert `FlowChartExecutor.getSnapshot()` → `StageSnapshot[]` |
|
|
319
|
+
| `subflowResultToSnapshots` | Convert subflow result → `StageSnapshot[]` |
|
|
389
320
|
| `createSnapshots` | Build `StageSnapshot[]` from simple arrays (testing/custom data) |
|
|
390
321
|
|
|
322
|
+
### Types
|
|
323
|
+
|
|
324
|
+
| Export | Description |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `PanelLabels` | `{ topology?, details?, timeline? }` — pill label customization |
|
|
327
|
+
| `DefaultExpanded` | `{ topology?, details?, timeline? }` — initial panel state |
|
|
328
|
+
| `StageSnapshot` | Core snapshot type for all components |
|
|
329
|
+
| `NarrativeEntry` | Structured narrative entry with type/depth/stageName |
|
|
330
|
+
|
|
391
331
|
---
|
|
392
332
|
|
|
393
333
|
## Size Variants
|
|
@@ -399,19 +339,6 @@ All components accept a `size` prop: `"compact"`, `"default"`, or `"detailed"`.
|
|
|
399
339
|
<MemoryInspector snapshots={snapshots} size="detailed" />
|
|
400
340
|
```
|
|
401
341
|
|
|
402
|
-
### Collapsible GanttTimeline
|
|
403
|
-
|
|
404
|
-
By default, the Gantt timeline collapses to 5 rows with an expand toggle. Auto-scrolls to keep the active stage visible:
|
|
405
|
-
|
|
406
|
-
```tsx
|
|
407
|
-
<GanttTimeline
|
|
408
|
-
snapshots={snapshots}
|
|
409
|
-
selectedIndex={idx}
|
|
410
|
-
onSelect={setIdx}
|
|
411
|
-
maxVisibleRows={5} // default — set 0 to disable collapse
|
|
412
|
-
/>
|
|
413
|
-
```
|
|
414
|
-
|
|
415
342
|
## Unstyled Mode
|
|
416
343
|
|
|
417
344
|
Strip all built-in styles for full CSS control. Components render semantic `data-fp` attributes:
|
|
@@ -423,178 +350,8 @@ Strip all built-in styles for full CSS control. Components render semantic `data
|
|
|
423
350
|
```css
|
|
424
351
|
[data-fp="narrative-header"] { font-weight: bold; }
|
|
425
352
|
[data-fp="narrative-step"] { padding-left: 2rem; }
|
|
426
|
-
[data-fp="narrative-group"][data-latest="true"] { background: highlight; }
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
---
|
|
430
|
-
|
|
431
|
-
## Example: Build a Pipeline Playground
|
|
432
|
-
|
|
433
|
-
A complete example combining flowchart, time-travel controls, detail panel, and Gantt timeline — the same pattern used by the [FootPrint Playground](https://footprintjs.github.io/footprint-playground/).
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
import { useState, useMemo } from "react";
|
|
437
|
-
import { ReactFlow } from "@xyflow/react";
|
|
438
|
-
import "@xyflow/react/dist/style.css";
|
|
439
|
-
import {
|
|
440
|
-
toVisualizationSnapshots,
|
|
441
|
-
GanttTimeline,
|
|
442
|
-
ScopeDiff,
|
|
443
|
-
NarrativeTrace,
|
|
444
|
-
MemoryInspector,
|
|
445
|
-
FootprintTheme,
|
|
446
|
-
warmDark,
|
|
447
|
-
} from "footprint-explainable-ui";
|
|
448
|
-
import {
|
|
449
|
-
StageNode,
|
|
450
|
-
specToReactFlow,
|
|
451
|
-
useSubflowNavigation,
|
|
452
|
-
SubflowBreadcrumb,
|
|
453
|
-
type ExecutionOverlay,
|
|
454
|
-
type SpecNode,
|
|
455
|
-
} from "footprint-explainable-ui/flowchart";
|
|
456
|
-
import { FlowChartExecutor } from "footprint";
|
|
457
|
-
|
|
458
|
-
const nodeTypes = { stage: StageNode };
|
|
459
|
-
|
|
460
|
-
// ─── Hook: time-travel + overlay + subflow drill-down ────────────────
|
|
461
|
-
function useFlowchartData(spec: SpecNode | null, vizSnapshots: any[] | null) {
|
|
462
|
-
const [snapshotIdx, setSnapshotIdx] = useState(0);
|
|
463
|
-
const subflowNav = useSubflowNavigation(spec);
|
|
464
|
-
|
|
465
|
-
const activeSnapshots = vizSnapshots; // extend with subflow logic as needed
|
|
466
|
-
|
|
467
|
-
// Compute execution overlay from current scrubber position
|
|
468
|
-
const overlay = useMemo<ExecutionOverlay | undefined>(() => {
|
|
469
|
-
if (!activeSnapshots) return undefined;
|
|
470
|
-
const executionOrder = activeSnapshots
|
|
471
|
-
.slice(0, snapshotIdx + 1)
|
|
472
|
-
.map((s) => s.stageLabel);
|
|
473
|
-
const doneStages = new Set(
|
|
474
|
-
activeSnapshots.slice(0, snapshotIdx).map((s) => s.stageLabel)
|
|
475
|
-
);
|
|
476
|
-
const activeStage = activeSnapshots[snapshotIdx]?.stageLabel ?? null;
|
|
477
|
-
const executedStages = new Set([...doneStages]);
|
|
478
|
-
if (activeStage) executedStages.add(activeStage);
|
|
479
|
-
return { doneStages, activeStage, executedStages, executionOrder };
|
|
480
|
-
}, [activeSnapshots, snapshotIdx]);
|
|
481
|
-
|
|
482
|
-
// Derive ReactFlow nodes/edges with overlay applied
|
|
483
|
-
const currentSpec =
|
|
484
|
-
subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1]?.spec ?? null;
|
|
485
|
-
const flowData = useMemo(() => {
|
|
486
|
-
if (!currentSpec || !activeSnapshots) return null;
|
|
487
|
-
return specToReactFlow(currentSpec, overlay);
|
|
488
|
-
}, [currentSpec, activeSnapshots, overlay]);
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
subflowNav,
|
|
492
|
-
activeSnapshots,
|
|
493
|
-
snapshotIdx,
|
|
494
|
-
setSnapshotIdx,
|
|
495
|
-
currentSnap: activeSnapshots?.[snapshotIdx] ?? null,
|
|
496
|
-
flowData,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// ─── Main component ──────────────────────────────────────────────────
|
|
501
|
-
function PipelinePlayground({ chart, spec }: { chart: any; spec: SpecNode }) {
|
|
502
|
-
const [snapshots, setSnapshots] = useState<any[] | null>(null);
|
|
503
|
-
|
|
504
|
-
async function run() {
|
|
505
|
-
const executor = new FlowChartExecutor(chart);
|
|
506
|
-
await executor.run();
|
|
507
|
-
setSnapshots(toVisualizationSnapshots(executor.getSnapshot()));
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const { subflowNav, activeSnapshots, snapshotIdx, setSnapshotIdx, currentSnap, flowData } =
|
|
511
|
-
useFlowchartData(spec, snapshots);
|
|
512
|
-
|
|
513
|
-
return (
|
|
514
|
-
<FootprintTheme tokens={warmDark}>
|
|
515
|
-
<button onClick={run}>Run Pipeline</button>
|
|
516
|
-
|
|
517
|
-
{/* Flowchart with execution overlay */}
|
|
518
|
-
<div style={{ height: 400 }}>
|
|
519
|
-
{subflowNav.isInSubflow && (
|
|
520
|
-
<SubflowBreadcrumb
|
|
521
|
-
breadcrumbs={subflowNav.breadcrumbs}
|
|
522
|
-
onNavigate={subflowNav.navigateTo}
|
|
523
|
-
/>
|
|
524
|
-
)}
|
|
525
|
-
{flowData && (
|
|
526
|
-
<ReactFlow
|
|
527
|
-
nodes={flowData.nodes}
|
|
528
|
-
edges={flowData.edges}
|
|
529
|
-
nodeTypes={nodeTypes}
|
|
530
|
-
onNodeClick={(_, node) => subflowNav.handleNodeClick(node.id)}
|
|
531
|
-
fitView
|
|
532
|
-
/>
|
|
533
|
-
)}
|
|
534
|
-
</div>
|
|
535
|
-
|
|
536
|
-
{activeSnapshots && (
|
|
537
|
-
<>
|
|
538
|
-
{/* Time-travel scrubber */}
|
|
539
|
-
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
|
540
|
-
<button
|
|
541
|
-
disabled={snapshotIdx <= 0}
|
|
542
|
-
onClick={() => setSnapshotIdx((i) => i - 1)}
|
|
543
|
-
>
|
|
544
|
-
Prev
|
|
545
|
-
</button>
|
|
546
|
-
<input
|
|
547
|
-
type="range"
|
|
548
|
-
min={0}
|
|
549
|
-
max={activeSnapshots.length - 1}
|
|
550
|
-
value={snapshotIdx}
|
|
551
|
-
onChange={(e) => setSnapshotIdx(Number(e.target.value))}
|
|
552
|
-
/>
|
|
553
|
-
<button
|
|
554
|
-
disabled={snapshotIdx >= activeSnapshots.length - 1}
|
|
555
|
-
onClick={() => setSnapshotIdx((i) => i + 1)}
|
|
556
|
-
>
|
|
557
|
-
Next
|
|
558
|
-
</button>
|
|
559
|
-
<span>
|
|
560
|
-
{currentSnap?.stageLabel} ({snapshotIdx + 1}/{activeSnapshots.length})
|
|
561
|
-
</span>
|
|
562
|
-
</div>
|
|
563
|
-
|
|
564
|
-
{/* Detail panels */}
|
|
565
|
-
<MemoryInspector
|
|
566
|
-
snapshots={activeSnapshots}
|
|
567
|
-
selectedIndex={snapshotIdx}
|
|
568
|
-
/>
|
|
569
|
-
<ScopeDiff
|
|
570
|
-
previous={snapshotIdx > 0 ? activeSnapshots[snapshotIdx - 1].memory : null}
|
|
571
|
-
current={currentSnap?.memory ?? {}}
|
|
572
|
-
hideUnchanged
|
|
573
|
-
/>
|
|
574
|
-
<NarrativeTrace
|
|
575
|
-
narrative={activeSnapshots.map((s) => s.narrative)}
|
|
576
|
-
/>
|
|
577
|
-
<GanttTimeline
|
|
578
|
-
snapshots={activeSnapshots}
|
|
579
|
-
selectedIndex={snapshotIdx}
|
|
580
|
-
onSelect={setSnapshotIdx}
|
|
581
|
-
/>
|
|
582
|
-
</>
|
|
583
|
-
)}
|
|
584
|
-
</FootprintTheme>
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
353
|
```
|
|
588
354
|
|
|
589
|
-
This gives you:
|
|
590
|
-
- Flowchart with Google Maps-style execution path overlay
|
|
591
|
-
- Click subflow nodes to drill down (breadcrumb navigation back)
|
|
592
|
-
- Prev/Next scrubber synced with flowchart highlighting
|
|
593
|
-
- Memory inspector, scope diffs, narrative trace, and Gantt timeline
|
|
594
|
-
- All themed via `FootprintTheme`
|
|
595
|
-
|
|
596
|
-
See the full implementation in the [footprint-playground](https://github.com/footprintjs/footprint-playground) repo.
|
|
597
|
-
|
|
598
355
|
---
|
|
599
356
|
|
|
600
357
|
## License
|