footprint-explainable-ui 0.3.0 → 0.3.1
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 +259 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# footprint-explainable-ui
|
|
2
2
|
|
|
3
|
-
Themeable React components for visualizing [FootPrint](https://github.com/
|
|
3
|
+
Themeable React components for visualizing [FootPrint](https://github.com/footprintjs/footPrint) pipeline execution — time-travel debugging, flowchart overlays, subflow drill-down, narrative traces, and scope diffs.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,16 +8,44 @@ Themeable React components for visualizing [FootPrint](https://github.com/sanjay
|
|
|
8
8
|
npm install footprint-explainable-ui
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer dependencies
|
|
11
|
+
**Peer dependencies:** `react >= 18`, `react-dom >= 18`
|
|
12
|
+
|
|
13
|
+
For flowchart components, also install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @xyflow/react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Entry Points
|
|
20
|
+
|
|
21
|
+
| Import path | What it provides |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `footprint-explainable-ui` | Core components, themes, adapters |
|
|
24
|
+
| `footprint-explainable-ui/flowchart` | Flowchart visualization, subflow navigation (requires `@xyflow/react`) |
|
|
12
25
|
|
|
13
26
|
## Quick Start
|
|
14
27
|
|
|
15
|
-
###
|
|
28
|
+
### 1. Convert FootPrint execution data to snapshots
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
```typescript
|
|
31
|
+
import { FlowChartExecutor } from "footprint";
|
|
32
|
+
import { toVisualizationSnapshots } from "footprint-explainable-ui";
|
|
33
|
+
|
|
34
|
+
const executor = new FlowChartExecutor(chart);
|
|
35
|
+
await executor.run();
|
|
36
|
+
|
|
37
|
+
// Convert runtime snapshot → visualization snapshots
|
|
38
|
+
const snapshots = toVisualizationSnapshots(executor.getSnapshot());
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Render with the all-in-one shell
|
|
18
42
|
|
|
19
43
|
```tsx
|
|
20
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
ExplainableShell,
|
|
46
|
+
FootprintTheme,
|
|
47
|
+
warmDark,
|
|
48
|
+
} from "footprint-explainable-ui";
|
|
21
49
|
|
|
22
50
|
function App({ snapshots, narrative, result }) {
|
|
23
51
|
return (
|
|
@@ -33,22 +61,21 @@ function App({ snapshots, narrative, result }) {
|
|
|
33
61
|
}
|
|
34
62
|
```
|
|
35
63
|
|
|
36
|
-
###
|
|
37
|
-
|
|
38
|
-
Every component works standalone. Mix and match:
|
|
64
|
+
### 3. Or compose individual components
|
|
39
65
|
|
|
40
66
|
```tsx
|
|
41
67
|
import {
|
|
42
|
-
|
|
68
|
+
TimeTravelControls,
|
|
69
|
+
MemoryInspector,
|
|
43
70
|
ScopeDiff,
|
|
44
71
|
GanttTimeline,
|
|
45
|
-
|
|
46
|
-
TimeTravelControls,
|
|
47
|
-
ResultPanel,
|
|
72
|
+
NarrativeTrace,
|
|
48
73
|
} from "footprint-explainable-ui";
|
|
49
74
|
|
|
50
75
|
function MyDebugger({ snapshots }) {
|
|
51
76
|
const [idx, setIdx] = useState(0);
|
|
77
|
+
const current = snapshots[idx];
|
|
78
|
+
const previous = idx > 0 ? snapshots[idx - 1] : null;
|
|
52
79
|
|
|
53
80
|
return (
|
|
54
81
|
<>
|
|
@@ -59,46 +86,200 @@ function MyDebugger({ snapshots }) {
|
|
|
59
86
|
/>
|
|
60
87
|
<MemoryInspector snapshots={snapshots} selectedIndex={idx} />
|
|
61
88
|
<ScopeDiff
|
|
62
|
-
previous={
|
|
63
|
-
current={
|
|
89
|
+
previous={previous?.memory ?? null}
|
|
90
|
+
current={current.memory}
|
|
64
91
|
hideUnchanged
|
|
65
92
|
/>
|
|
93
|
+
<NarrativeTrace narrative={snapshots.map(s => s.narrative)} />
|
|
66
94
|
<GanttTimeline snapshots={snapshots} selectedIndex={idx} onSelect={setIdx} />
|
|
67
95
|
</>
|
|
68
96
|
);
|
|
69
97
|
}
|
|
70
98
|
```
|
|
71
99
|
|
|
72
|
-
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Flowchart Visualization
|
|
103
|
+
|
|
104
|
+
Import from `footprint-explainable-ui/flowchart`:
|
|
73
105
|
|
|
74
|
-
|
|
106
|
+
```tsx
|
|
107
|
+
import {
|
|
108
|
+
StageNode,
|
|
109
|
+
specToReactFlow,
|
|
110
|
+
useSubflowNavigation,
|
|
111
|
+
SubflowBreadcrumb,
|
|
112
|
+
type SpecNode,
|
|
113
|
+
type ExecutionOverlay,
|
|
114
|
+
} from "footprint-explainable-ui/flowchart";
|
|
115
|
+
```
|
|
75
116
|
|
|
76
|
-
|
|
117
|
+
### Static flowchart from pipeline spec
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { ReactFlow } from "@xyflow/react";
|
|
121
|
+
import "@xyflow/react/dist/style.css";
|
|
122
|
+
import { specToReactFlow, StageNode } from "footprint-explainable-ui/flowchart";
|
|
123
|
+
|
|
124
|
+
const nodeTypes = { stage: StageNode };
|
|
125
|
+
|
|
126
|
+
function PipelineChart({ spec }) {
|
|
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
|
+
### With execution overlay (time-travel)
|
|
141
|
+
|
|
142
|
+
The overlay highlights which stages have executed, which is active, and the execution path — like a Google Maps route overlay.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { specToReactFlow, type ExecutionOverlay } from "footprint-explainable-ui/flowchart";
|
|
146
|
+
|
|
147
|
+
// Build overlay from your current time-travel position
|
|
148
|
+
const overlay: ExecutionOverlay = {
|
|
149
|
+
doneStages: new Set(["LoadOrder", "ProcessPayment"]),
|
|
150
|
+
activeStage: "ShipOrder",
|
|
151
|
+
executedStages: new Set(["LoadOrder", "ProcessPayment", "ShipOrder"]),
|
|
152
|
+
executionOrder: ["LoadOrder", "ProcessPayment", "ShipOrder"],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const { nodes, edges } = specToReactFlow(spec, overlay);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Tip:** Compute the overlay from your snapshots array and current index:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
function buildOverlay(snapshots, idx): ExecutionOverlay {
|
|
162
|
+
const executionOrder = snapshots.slice(0, idx + 1).map(s => s.stageLabel);
|
|
163
|
+
const doneStages = new Set(snapshots.slice(0, idx).map(s => s.stageLabel));
|
|
164
|
+
const activeStage = snapshots[idx]?.stageLabel ?? null;
|
|
165
|
+
const executedStages = new Set([...doneStages]);
|
|
166
|
+
if (activeStage) executedStages.add(activeStage);
|
|
167
|
+
return { doneStages, activeStage, executedStages, executionOrder };
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Custom edge colors
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
const { nodes, edges } = specToReactFlow(spec, overlay, {
|
|
175
|
+
edgeExecuted: "#00ff88", // Completed path
|
|
176
|
+
edgeActive: "#ff6b6b", // Currently executing
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Subflow drill-down navigation
|
|
181
|
+
|
|
182
|
+
For pipelines with nested subflows, `useSubflowNavigation` manages a breadcrumb stack. Clicking a subflow node drills into its internal flowchart.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import {
|
|
186
|
+
useSubflowNavigation,
|
|
187
|
+
SubflowBreadcrumb,
|
|
188
|
+
specToReactFlow,
|
|
189
|
+
StageNode,
|
|
190
|
+
} from "footprint-explainable-ui/flowchart";
|
|
191
|
+
import { ReactFlow } from "@xyflow/react";
|
|
192
|
+
|
|
193
|
+
const nodeTypes = { stage: StageNode };
|
|
194
|
+
|
|
195
|
+
function DrillDownChart({ spec, overlay }) {
|
|
196
|
+
const subflowNav = useSubflowNavigation(spec);
|
|
197
|
+
|
|
198
|
+
// Get the current level's spec from breadcrumbs
|
|
199
|
+
const currentSpec = subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1].spec;
|
|
200
|
+
const { nodes, edges } = specToReactFlow(currentSpec, overlay);
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div>
|
|
204
|
+
{/* Breadcrumb bar: Pipeline > PaymentSubflow */}
|
|
205
|
+
{subflowNav.isInSubflow && (
|
|
206
|
+
<SubflowBreadcrumb
|
|
207
|
+
breadcrumbs={subflowNav.breadcrumbs}
|
|
208
|
+
onNavigate={subflowNav.navigateTo}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
<ReactFlow
|
|
213
|
+
nodes={nodes}
|
|
214
|
+
edges={edges}
|
|
215
|
+
nodeTypes={nodeTypes}
|
|
216
|
+
onNodeClick={(_, node) => subflowNav.handleNodeClick(node.id)}
|
|
217
|
+
fitView
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**`useSubflowNavigation` returns:**
|
|
225
|
+
|
|
226
|
+
| Property | Type | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `breadcrumbs` | `BreadcrumbEntry[]` | Stack from root to current level |
|
|
229
|
+
| `nodes` | `Node[]` | ReactFlow nodes for current level |
|
|
230
|
+
| `edges` | `Edge[]` | ReactFlow edges for current level |
|
|
231
|
+
| `handleNodeClick` | `(nodeId) => boolean` | Drills into subflow if applicable |
|
|
232
|
+
| `navigateTo` | `(level) => void` | Jump to breadcrumb level (0 = root) |
|
|
233
|
+
| `isInSubflow` | `boolean` | Whether we're inside a subflow |
|
|
234
|
+
| `currentSubflowNodeName` | `string \| null` | Name of the subflow node drilled into |
|
|
235
|
+
|
|
236
|
+
### Extracting subflow execution data
|
|
237
|
+
|
|
238
|
+
When drilled into a subflow, extract its execution snapshots from the parent's memory:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import { toVisualizationSnapshots } from "footprint-explainable-ui";
|
|
242
|
+
|
|
243
|
+
// Find the parent stage that contains the subflow result
|
|
244
|
+
const parentSnap = parentSnapshots.find(s => s.stageLabel === subflowNav.currentSubflowNodeName);
|
|
245
|
+
const sfResult = parentSnap?.memory?.subflowResult;
|
|
246
|
+
const tc = sfResult?.treeContext;
|
|
247
|
+
|
|
248
|
+
if (tc?.stageContexts) {
|
|
249
|
+
const subflowSnapshots = toVisualizationSnapshots({
|
|
250
|
+
sharedState: tc.globalContext,
|
|
251
|
+
executionTree: tc.stageContexts,
|
|
252
|
+
commitLog: tc.history ?? [],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Strip builder's "subflowId/" prefix from stage names
|
|
256
|
+
const prefix = sfResult.subflowId ? `${sfResult.subflowId}/` : null;
|
|
257
|
+
if (prefix) {
|
|
258
|
+
for (const snap of subflowSnapshots) {
|
|
259
|
+
if (snap.stageLabel.startsWith(prefix))
|
|
260
|
+
snap.stageLabel = snap.stageLabel.slice(prefix.length);
|
|
261
|
+
if (snap.stageName.startsWith(prefix))
|
|
262
|
+
snap.stageName = snap.stageName.slice(prefix.length);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Theming
|
|
271
|
+
|
|
272
|
+
### ThemeProvider
|
|
77
273
|
|
|
78
274
|
```tsx
|
|
79
275
|
import { FootprintTheme, warmDark, warmLight, coolDark } from "footprint-explainable-ui";
|
|
80
276
|
|
|
81
|
-
// Use a built-in preset
|
|
82
277
|
<FootprintTheme tokens={warmDark}>
|
|
83
278
|
<MyApp />
|
|
84
279
|
</FootprintTheme>
|
|
85
|
-
|
|
86
|
-
// Or customize
|
|
87
|
-
<FootprintTheme tokens={{
|
|
88
|
-
colors: {
|
|
89
|
-
primary: "#e91e63",
|
|
90
|
-
bgPrimary: "#121212",
|
|
91
|
-
textPrimary: "#ffffff",
|
|
92
|
-
},
|
|
93
|
-
radius: "12px",
|
|
94
|
-
}}>
|
|
95
|
-
<MyApp />
|
|
96
|
-
</FootprintTheme>
|
|
97
280
|
```
|
|
98
281
|
|
|
99
|
-
###
|
|
100
|
-
|
|
101
|
-
Set `--fp-*` CSS variables directly — no provider needed:
|
|
282
|
+
### CSS Variables (no provider needed)
|
|
102
283
|
|
|
103
284
|
```css
|
|
104
285
|
:root {
|
|
@@ -115,7 +296,7 @@ Set `--fp-*` CSS variables directly — no provider needed:
|
|
|
115
296
|
### Built-in Presets
|
|
116
297
|
|
|
117
298
|
| Preset | Description |
|
|
118
|
-
|
|
299
|
+
|---|---|
|
|
119
300
|
| `coolDark` | Default — indigo/slate dark theme |
|
|
120
301
|
| `warmDark` | Charcoal-purple with warm text |
|
|
121
302
|
| `warmLight` | Cream/peach light theme |
|
|
@@ -125,55 +306,36 @@ Set `--fp-*` CSS variables directly — no provider needed:
|
|
|
125
306
|
```typescript
|
|
126
307
|
interface ThemeTokens {
|
|
127
308
|
colors?: {
|
|
128
|
-
primary?: string;
|
|
129
|
-
success?: string;
|
|
130
|
-
error?: string;
|
|
131
|
-
warning?: string;
|
|
132
|
-
bgPrimary?: string;
|
|
133
|
-
bgSecondary?: string
|
|
134
|
-
bgTertiary?: string;
|
|
135
|
-
textPrimary?: string
|
|
136
|
-
textSecondary?: string
|
|
137
|
-
textMuted?: string;
|
|
138
|
-
border?: string;
|
|
309
|
+
primary?: string; // Accent (buttons, highlights)
|
|
310
|
+
success?: string; // Completed stages
|
|
311
|
+
error?: string; // Error states
|
|
312
|
+
warning?: string; // Warnings
|
|
313
|
+
bgPrimary?: string; // Main background
|
|
314
|
+
bgSecondary?: string; // Panel/card background
|
|
315
|
+
bgTertiary?: string; // Hover/active background
|
|
316
|
+
textPrimary?: string; // Main text
|
|
317
|
+
textSecondary?: string; // Secondary text
|
|
318
|
+
textMuted?: string; // Dimmed text
|
|
319
|
+
border?: string; // Borders
|
|
139
320
|
};
|
|
140
|
-
radius?: string;
|
|
321
|
+
radius?: string;
|
|
141
322
|
fontFamily?: {
|
|
142
|
-
sans?: string;
|
|
143
|
-
mono?: string;
|
|
323
|
+
sans?: string; // UI text font
|
|
324
|
+
mono?: string; // Code/data font
|
|
144
325
|
};
|
|
145
326
|
}
|
|
146
327
|
```
|
|
147
328
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
All components accept a `size` prop: `"compact"`, `"default"`, or `"detailed"`.
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
<GanttTimeline snapshots={snapshots} size="compact" />
|
|
154
|
-
<MemoryInspector snapshots={snapshots} size="detailed" />
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Unstyled Mode
|
|
158
|
-
|
|
159
|
-
Strip all built-in styles and bring your own. Components render semantic `data-fp` attributes for CSS targeting:
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
<NarrativeTrace narrative={lines} unstyled className="my-narrative" />
|
|
163
|
-
```
|
|
329
|
+
---
|
|
164
330
|
|
|
165
|
-
|
|
166
|
-
[data-fp="narrative-header"] { font-weight: bold; }
|
|
167
|
-
[data-fp="narrative-step"] { padding-left: 2rem; }
|
|
168
|
-
[data-fp="narrative-group"][data-latest="true"] { background: highlight; }
|
|
169
|
-
```
|
|
331
|
+
## Components Reference
|
|
170
332
|
|
|
171
|
-
|
|
333
|
+
### Core Components
|
|
172
334
|
|
|
173
335
|
| Component | Description |
|
|
174
|
-
|
|
336
|
+
|---|---|
|
|
175
337
|
| `ExplainableShell` | Tabbed container: Result / Explainable / AI-Compatible |
|
|
176
|
-
| `TimeTravelControls` | Play/pause, prev/next,
|
|
338
|
+
| `TimeTravelControls` | Play/pause, prev/next, scrubber timeline |
|
|
177
339
|
| `NarrativeTrace` | Collapsible stage groups with progressive reveal |
|
|
178
340
|
| `NarrativeLog` | Simple timeline-style execution log |
|
|
179
341
|
| `ScopeDiff` | Side-by-side scope changes (added/changed/removed) |
|
|
@@ -182,107 +344,51 @@ Strip all built-in styles and bring your own. Components render semantic `data-f
|
|
|
182
344
|
| `GanttTimeline` | Horizontal duration timeline |
|
|
183
345
|
| `SnapshotPanel` | All-in-one inspector (scrubber + memory + narrative + Gantt) |
|
|
184
346
|
|
|
185
|
-
### Flowchart Components (
|
|
186
|
-
|
|
187
|
-
Requires `@xyflow/react` as a peer dependency. Import from `footprint-explainable-ui/flowchart`:
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
npm install @xyflow/react
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
```tsx
|
|
194
|
-
import {
|
|
195
|
-
FlowchartView,
|
|
196
|
-
StageNode,
|
|
197
|
-
specToReactFlow,
|
|
198
|
-
TimeTravelDebugger,
|
|
199
|
-
} from "footprint-explainable-ui/flowchart";
|
|
200
|
-
```
|
|
347
|
+
### Flowchart Components (`footprint-explainable-ui/flowchart`)
|
|
201
348
|
|
|
202
349
|
| Export | Description |
|
|
203
|
-
|
|
350
|
+
|---|---|
|
|
204
351
|
| `FlowchartView` | ReactFlow pipeline visualization with execution overlay |
|
|
205
352
|
| `StageNode` | Custom node with state-aware coloring, step badges, pulse rings |
|
|
206
353
|
| `specToReactFlow` | Convert pipeline spec → ReactFlow nodes/edges with path overlay |
|
|
207
354
|
| `TimeTravelDebugger` | Full debugger with flowchart + all panels |
|
|
355
|
+
| `SubflowBreadcrumb` | Breadcrumb bar for subflow drill-down |
|
|
356
|
+
| `useSubflowNavigation` | Hook managing subflow drill-down navigation stack |
|
|
208
357
|
|
|
209
|
-
###
|
|
358
|
+
### Adapters
|
|
210
359
|
|
|
211
|
-
|
|
360
|
+
| Export | Description |
|
|
361
|
+
|---|---|
|
|
362
|
+
| `toVisualizationSnapshots` | Convert `FlowChartExecutor.getSnapshot()` → `StageSnapshot[]` |
|
|
363
|
+
| `createSnapshots` | Build `StageSnapshot[]` from simple arrays (testing/custom data) |
|
|
212
364
|
|
|
213
|
-
|
|
214
|
-
import { specToReactFlow } from "footprint-explainable-ui/flowchart";
|
|
215
|
-
import type { ExecutionOverlay } from "footprint-explainable-ui/flowchart";
|
|
365
|
+
---
|
|
216
366
|
|
|
217
|
-
|
|
218
|
-
const { nodes, edges } = specToReactFlow(spec);
|
|
367
|
+
## Size Variants
|
|
219
368
|
|
|
220
|
-
|
|
221
|
-
const overlay: ExecutionOverlay = {
|
|
222
|
-
doneStages: new Set(["ReceiveApp", "PullCredit"]),
|
|
223
|
-
activeStage: "CalculateDTI",
|
|
224
|
-
executedStages: new Set(["ReceiveApp", "PullCredit", "CalculateDTI"]),
|
|
225
|
-
executionOrder: ["ReceiveApp", "PullCredit", "CalculateDTI"],
|
|
226
|
-
};
|
|
227
|
-
const { nodes, edges } = specToReactFlow(spec, overlay);
|
|
369
|
+
All components accept a `size` prop: `"compact"`, `"default"`, or `"detailed"`.
|
|
228
370
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
edgeActive: "#ff6b6b",
|
|
233
|
-
});
|
|
371
|
+
```tsx
|
|
372
|
+
<GanttTimeline snapshots={snapshots} size="compact" />
|
|
373
|
+
<MemoryInspector snapshots={snapshots} size="detailed" />
|
|
234
374
|
```
|
|
235
375
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
### `StageNode` — Theme-Aware Node
|
|
239
|
-
|
|
240
|
-
`StageNode` reads all colors from `--fp-*` CSS variables:
|
|
241
|
-
- **Default state**: uses `--fp-bg-secondary` background, `--fp-text-primary` text
|
|
242
|
-
- **Active/done/error**: uses `--fp-color-primary` / `success` / `error` background
|
|
243
|
-
- **Step badge**: shows execution order number on executed nodes
|
|
244
|
-
- **Font**: uses `--fp-font-sans` for label text
|
|
376
|
+
## Unstyled Mode
|
|
245
377
|
|
|
246
|
-
|
|
378
|
+
Strip all built-in styles for full CSS control. Components render semantic `data-fp` attributes:
|
|
247
379
|
|
|
248
380
|
```tsx
|
|
249
|
-
<
|
|
250
|
-
<ReactFlow nodes={nodes} edges={edges} nodeTypes={{ stage: StageNode }} />
|
|
251
|
-
</FootprintTheme>
|
|
381
|
+
<NarrativeTrace narrative={lines} unstyled className="my-narrative" />
|
|
252
382
|
```
|
|
253
383
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
import { toVisualizationSnapshots } from "footprint-explainable-ui";
|
|
260
|
-
|
|
261
|
-
const executor = new FlowChartExecutor(pipeline);
|
|
262
|
-
await executor.run(scope);
|
|
263
|
-
|
|
264
|
-
const snapshots = toVisualizationSnapshots(executor.getSnapshot());
|
|
384
|
+
```css
|
|
385
|
+
[data-fp="narrative-header"] { font-weight: bold; }
|
|
386
|
+
[data-fp="narrative-step"] { padding-left: 2rem; }
|
|
387
|
+
[data-fp="narrative-group"][data-latest="true"] { background: highlight; }
|
|
265
388
|
```
|
|
266
389
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
The library is render-target agnostic. Use components in:
|
|
390
|
+
---
|
|
270
391
|
|
|
271
|
-
|
|
272
|
-
- **Modal/dialog** — wrap in your modal component
|
|
273
|
-
- **Sidebar panel** — use `size="compact"` for narrow panels
|
|
274
|
-
- **Full-page dashboard** — use `ExplainableShell` with all tabs
|
|
275
|
-
- **Embedded widget** — use `unstyled` mode + custom CSS
|
|
392
|
+
## License
|
|
276
393
|
|
|
277
|
-
|
|
278
|
-
// In a modal
|
|
279
|
-
<Dialog>
|
|
280
|
-
<ExplainableShell snapshots={snapshots} narrative={narrative} size="compact" />
|
|
281
|
-
</Dialog>
|
|
282
|
-
|
|
283
|
-
// In a sidebar
|
|
284
|
-
<aside style={{ width: 320 }}>
|
|
285
|
-
<NarrativeTrace narrative={narrative} size="compact" />
|
|
286
|
-
<GanttTimeline snapshots={snapshots} size="compact" />
|
|
287
|
-
</aside>
|
|
288
|
-
```
|
|
394
|
+
MIT
|