sprout-garden-debugger
Sprout Garden: The Debugger
Two layers, same graph. Watch your workflows like marble runs.
The Core Insight
The game view and the production dashboard are the same component. They render two things:
- Underlay (Structure): The workflow topology - plots, vines, connections
- Overlay (Execution): The sprouts flowing through - position, state, history
Both live in the graph. Both are renderable. The difference is just data source.
Two-Layer Architecture
Underlay: The Topology
The workflow/garden structure. Relatively static. Changes when you edit, not when you run.
{
type: 'garden',
grid: [8, 6],
plots: {
'A1': { type: 'spring', config: { emits: { shape: 'circle', color: 'red' } } },
'B1': { type: 'vine-right' },
'C1': { type: 'form-grower' },
'D1': { type: 'vine-right' },
'E1': { type: 'harvest' }
}
}This is the train track. It doesn't move.
Overlay: The Executions
The sprouts/messages flowing through. These are separate objects in the graph.
{
type: 'sprout',
id: 'sprout-001',
garden: 'my-workflow', // reference to underlay
position: 'C1', // current location
state: {
shape: 'square', // current form
color: 'red'
},
lineage: [
{ tick: 0, plot: 'A1', action: 'emit', state: { shape: 'circle', color: 'red' } },
{ tick: 1, plot: 'B1', action: 'pass' },
{ tick: 2, plot: 'C1', action: 'transform', before: { shape: 'circle' }, after: { shape: 'square' } }
],
status: 'transit' // transit | arrived | lost | looping
}This is the train. It moves. It has history. It's a separate entity.
Why This Matters
Rendering
Draw underlay first (static grid, plots, vines)
↓
Draw overlay on top (sprouts at their positions)The overlay is just "put emoji at coordinates." The underlay is the stable frame.
Replay
Click a sprout in the debugger:
- Grab its lineage
- Re-render overlay position frame by frame
- Underlay doesn't change
You're not "re-running" the workflow. You're just animating the recorded path.
Live View
When running in production:
- Sprout objects get created/updated in the graph
- Their
positionfield changes - If you're viewing the garden, you see them move
- No push mechanism needed - you're watching data mutate
Multiple Executions
Ten JIRA tickets in flight? Ten sprout objects. Each with their own:
- UUID
- Position
- State
- Lineage
All rendered as overlay. All independent. You can click one, highlight it, follow it.
The Debugger UI
Live Trace Panel
Bottom of screen, always visible during tend/run:
┌─────────────────────────────────────────────────────────────────────────┐
│ LIVE TRACES tick: 47 │
├─────────────────────────────────────────────────────────────────────────┤
│ ● sprout-001: 🌱 → → ⚙️ → → 🎯 ✓ (arrived: ■ red) │
│ ● sprout-002: 🌱 → → ⚙️ → → 🎯 ✓ (arrived: ■ red) │
│ ○ sprout-003: 🌱 → → ⚙️ → ↓ 🎨 ← ↑ ⚙️ → ... (looping B2-C2-C3) │
│ 💀 sprout-004: 🌱 → → 💀 (fell off at D1) │
└─────────────────────────────────────────────────────────────────────────┘Symbols:
●Active/successful○In progress💀Lost (fell off edge)🔄Looping (detected cycle)⏳Waiting (at grafter, etc.)
Path Grouping
When you have many sprouts, group by path:
┌─────────────────────────────────────────────────────────────────────────┐
│ PATH ANALYSIS │
├─────────────────────────────────────────────────────────────────────────┤
│ 🌱 → → ⚙️ → → 🎯 : 18 sprouts (90%) ✓ MAIN PATH │
│ 🌱 → → ⚙️ → ↓ 🎨 ← ... (loop) : 2 sprouts (10%) ⚠️ STUCK │
│ 🌱 → → 💀 : 0 sprouts (0%) │
└─────────────────────────────────────────────────────────────────────────┘Click a group → highlight all sprouts that took that path.
Status Bar
Real-time counters:
🌱 Active: 5 | ✓ Arrived: 18 | 🔄 Looping: 2 | 💀 Lost: 0 | Tick: 47Playback Controls
[⏮️ START] [⏪ -10] [◀️ PREV] [▶️ NEXT] [⏩ +10] [⏭️ END]
Tick: [====|=====================================] 47/200
Speed: [🐢 0.5x] [1x] [🐇 2x] [🚀 10x]
[⏸️ PAUSE] [🔁 LOOP] [📍 FOLLOW sprout-003]Replay Mechanics
Recording
Every sprout automatically records its lineage:
sprout.lineage.push({
tick: currentTick,
plot: currentPlot.id,
action: 'transform', // emit | pass | transform | block | arrive | fall
before: { ...previousState },
after: { ...newState }
});No extra work. Just append on every tick.
Playback
function replaySprout(sprout, targetTick) {
const frame = sprout.lineage.find(l => l.tick === targetTick);
if (frame) {
highlightPlot(frame.plot);
renderSproutAt(frame.plot, frame.after);
}
}Scrub the timeline → render the frame. That's it.
Diff View
Two sprouts diverged - why?
function diffLineages(sprout1, sprout2) {
for (let i = 0; i < sprout1.lineage.length; i++) {
if (sprout1.lineage[i].plot !== sprout2.lineage[i]?.plot) {
return {
divergedAt: i,
tick: sprout1.lineage[i].tick,
sprout1Path: sprout1.lineage[i],
sprout2Path: sprout2.lineage[i]
};
}
}
}Show the fork point. Highlight the decision that differed.
Production Dashboard Mode
The Same Component
<SproutGarden
underlay={workflowNode} // from Oculus
overlay={activeExecutions} // from live query
mode="dashboard" // vs "game"
/>Game mode:
- Grid is editable
- Palette visible
- Goal display
- Tend button
Dashboard mode:
- Grid is read-only
- No palette
- Status metrics
- Live updating
Connecting to Stuffy
// Subscribe to workflow executions
stuffy.subscribe(`workflow:${workflowId}:executions`, (sprouts) => {
setOverlay(sprouts);
});When a sprout's position changes in the graph, Stuffy streams it, the component re-renders, you see it move.
What You See
┌─────────────────────────────────────────────────────────────────────────┐
│ JIRA TRIAGE WORKFLOW LIVE 🟢 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ 📥 │ → │ 🧠 │ → │ ⑂ │ → │ 🔧 │ → ... │
│ │watch│ │ ctx │ │route│ │retry│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ ↑ │ │ │
│ [🎫] [🎫] [🎫] │
│ DVOPS-123 DVOPS-456 DVOPS-789 │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ Active: 3 | Processed today: 47 | Avg time: 2.3s | Errors: 0 │
└─────────────────────────────────────────────────────────────────────────┘The tickets are sprouts. The workflow is the garden. Same visualization.
Sprout Object Specification
Full Schema
{
// Identity
id: 'sprout-001', // UUID
type: 'sprout', // object type
garden: 'jira-triage-workflow', // reference to underlay
// Current state
position: 'C2', // current plot address
state: {
// Game mode
shape: 'square',
color: 'red',
// Production mode - any attributes
ticket_key: 'DVOPS-123',
priority: 'P2',
assignee: 'gfawcett',
context: { ... }
},
// Execution state
status: 'transit', // transit | arrived | lost | looping | waiting
created_at: '2025-11-29T10:00:00Z',
updated_at: '2025-11-29T10:00:05Z',
// History
lineage: [
{
tick: 0,
plot: 'A1',
action: 'emit',
timestamp: '2025-11-29T10:00:00Z',
state: { ... }
},
// ... full path history
],
// Loop detection
loop_detected: null, // or { start_index: 4, length: 3, count: 12 }
// Tags for indexing
tags: ['workflow:jira-triage', 'source:aws-event']
}Indexes
For efficient querying:
- By garden: "all sprouts in this workflow"
- By status: "all looping sprouts"
- By position: "what's at C2 right now?"
- By tag: "all JIRA-related executions"
Icon Customization
Like Monopoly - pick your piece:
| Icon | Name | Context |
|---|---|---|
| 🌱 | Sprout | Default (game) |
| 🎫 | Ticket | JIRA workflows |
| 📦 | Package | CI/CD pipelines |
| 🚂 | Train | Infrastructure |
| 🐹 | Hamster | Priscilla's theme |
| 🔮 | Orb | Generic data |
| Custom | User-defined | Any emoji |
sprout.icon = '🎫'; // renders as ticket in overlayCorner Awareness
Visual debugging heuristic:
- Top-right corner: Validation loops spinning - probably fine
- Bottom-right corner: Problem zone - things stuck or dying
- Left edge: Sources, fresh entries
- Right edge: Usually exits/harvests
"Something's stuck in the corner" = glanceable insight.
Implementation Notes
Separation of Concerns
┌─────────────────────┐
│ Garden View │ ← React/Svelte/Vue component
├─────────────────────┤
│ Underlay Renderer │ ← Draws grid, plots, vines
├─────────────────────┤
│ Overlay Renderer │ ← Draws sprouts at positions
├─────────────────────┤
│ Animation Engine │ ← Handles tick-by-tick playback
├─────────────────────┤
│ Data Layer │ ← Fetches from graph/Stuffy
└─────────────────────┘Performance
- Underlay renders once, caches
- Overlay re-renders on sprout position change
- Use CSS transforms for smooth animation
- Batch updates in production mode
Accessibility
- Screen reader announces: "Sprout 001 entered Form Grower at C2"
- Keyboard navigation through traces
- Color-blind friendly status indicators (shapes + colors)
Tags
sprout-garden, debugger, visualization, overlay-underlay, dashboard, replay, wanderland
Slots
North
slots:
- sprout-gardenSouth
slots: []East
slots: []West
slots: []