lantern

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 position field 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: 47

Playback 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 overlay

Corner 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-garden

South

slots: []

East

slots: []

West

slots: []
↑ northsprout-garden