State Machine Playground
Interactive state machine explorer with preset workflows, per-state multi-conditional classes that flip together on transition, a reactive transitions loop source, and a history memo chain that filters and groups derived views.
#Preview
States
4 totalTransitions
History
0 eventsNo transitions yet. Click a transition to fire it.
1"use client"23import { createSignal, createMemo } from '@barefootjs/client'45// Each state node evaluates three independent conditional classes6// (isCurrent / isReachable / isVisited). Firing a single transition7// flips all three across multiple list items at once — the compiler8// must wire reactive className bindings inside the states loop so9// every affected node updates on the next microtask.1011function StateMachinePlayground() {12 const [machineId, setMachineId] = createSignal('traffic-light')13 const [currentState, setCurrentState] = createSignal(machine().initial)14 const [history, setHistory] = createSignal<HistoryEntry[]>([])15 const [onlyPossible, setOnlyPossible] = createSignal(false)16 const [historySearch, setHistorySearch] = createSignal('')17 const [groupBy, setGroupBy] = createSignal<'none' | 'event' | 'target'>('none')1819 const machine = createMemo(() => MACHINES.find(m => m.id === machineId())!)2021 // Drives the "reachable" highlight on state nodes.22 const possibleTransitions = createMemo(() =>23 machine().transitions.filter(t => t.from === currentState())24 )2526 // The transitions loop source itself is reactive.27 const visibleTransitions = createMemo(() =>28 onlyPossible() ? possibleTransitions() : machine().transitions29 )3031 // 3-level history memo chain: raw → filtered → grouped.32 const historyFiltered = createMemo(() => /* filter by search */)33 const historyGroups = createMemo(() => /* group by mode */)3435 return (36 <div>37 {/* Every state node has three conditional classes that flip together. */}38 {machine().states.map(s => (39 <button key={s.id}40 className={`state-node${s.id === currentState() ? ' state-current' : ''}${possibleTargetIds()[s.id] ? ' state-reachable' : ''}${visitedSet()[s.id] ? ' state-visited' : ''}`}41 onClick={() => setCurrentState(s.id)}42 >43 {s.label}44 </button>45 ))}4647 {/* Loop source may be the full list OR the filtered one. */}48 {visibleTransitions().map(t => (49 <button key={t.id}50 disabled={t.from !== currentState()}51 onClick={() => fireTransition(t)}52 >53 {t.event}: {t.from} → {t.to}54 </button>55 ))}5657 {/* Loop-of-loops over the grouped history. */}58 {historyGroups().map(g => (59 <div key={g.key}>60 <h4>{g.key}</h4>61 {g.entries.map(e => (62 <div key={String(e.id)}>{e.event}: {e.from} → {e.to}</div>63 ))}64 </div>65 ))}66 </div>67 )68}#Features
Many Conditionals Switching Simultaneously
Every state node evaluates three independent conditional classes —isCurrent,isReachableFromCurrent, andisVisited — inside the same.map() body. Firing one transition flips all three conditions for multiple list items at once, exercising reactive className binding wiring across every loop item on a single signal update.
Dynamic Transition List
The "Only possible" toggle switches the transitions loop source between the full machine transition array and thepossibleTransitions() filter. Toggling it shrinks or grows the rendered list entirely — the compiler must re-run the loop with a different source signal without stale list items left behind.
Machine Switching Reshapes Loop Structure
Selecting a different preset replaces the states, transitions, and initial state all at once via selectMachine(). Every loop on the page (states, transitions, history) re-renders against completely new source data, stressing the compiler's key-based reconciliation.
History Filter/Group Memo Chain
History rendering uses a three-level memo chain:history →historyFiltered (search text) →historyGroups (mode: none / event / target). The outer grouped loop and each group's inner entries loop both react to signal changes upstream in the chain. Group keys change as the grouping mode changes, so every list item's key identity shifts.