This document describes how to interact with the effect-dev-tui application using tmux for automated testing and development.
CRITICAL: When implementing new features or debugging crashes, ALWAYS use incremental testing:
- Start with the absolute minimum - A single static
<text>element - Add ONE feature at a time - Dynamic prop, then loop, then style, then logic
- Test after EACH addition - Don't add multiple things before testing
- When something breaks, you know exactly what caused it
// Step 1: Static placeholder
<Match when={store.ui.activeTab === "metrics"}>
<box><text>Test</text></box>
</Match>
// ✓ Works? Continue to step 2
// Step 2: Add dynamic data
<box><text>Count: {metricCount()}</text></box>
// ✓ Works? Continue to step 3
// Step 3: Add structure
<box flexDirection="row">
<box width="60%"><text>List</text></box>
<box width="40%"><text>Details</text></box>
</box>
// ✓ Works? Continue to step 4
// Step 4: Add component
<box width="60%">
<MetricsView metrics={store.metrics} />
</box>
// ✓ Works? Continue to step 5
// Step 5: Add borders/styling one property at a time
borderStyle="rounded" // Test
borderColor="#7aa2f7" // Test
overflow="scroll" // TestThis approach saved hours of debugging by quickly isolating that nested <Show> components caused the segfault, not the component logic itself.
ALWAYS kill existing bun processes before starting new sessions to prevent multiple runtimes from causing reactivity issues:
killall -9 bun 2>/dev/null
sleep 2Verify no stray processes remain:
ps aux | grep "bun.*effect-dev-tui" | grep -v grepMultiple bun processes will cause:
- Store updates going to the wrong instance (old runtime captures actions)
- UI showing "Clients: 0 | Spans: 0" despite spans being received
- Metrics not appearing in the UI
- Segmentation faults when switching tabs or rendering components
When encountering Bun segmentation faults (panic with address 0x10):
- The issue is almost NEVER in your component logic - Bun has edge cases with certain JSX patterns
- Use incremental testing to narrow down the exact line causing the crash:
- Start with static text only
- Add dynamic props one at a time
- Add loops/conditionals one at a time
- Add style objects last
- Common causes:
- Nested
<Show>components with complex conditions - Certain combinations of borders + overflow + nested boxes
- NOT the
<For>loops themselves (they work fine) - NOT reactive functions
() =>(they work fine)
- Nested
- Quick test pattern:
// Replace complex component with placeholder <Match when={store.ui.activeTab === "metrics"}> <box> <text>Test</text> </box>{" "} {/* Works? */} </Match>
- If placeholder works, add back features incrementally until you find the problematic pattern
- Solution is usually simplifying the JSX structure, not changing the logic
The recommended setup uses two tmux windows: one for the TUI server, one for the test client.
IMPORTANT: Never kill tmux session 0 - that's where OpenCode runs!
CRITICAL: NEVER use tmux kill-server - this kills ALL tmux sessions including OpenCode!
# Clean up any existing processes first
killall -9 bun 2>/dev/null
tmux kill-session -t effect-tui 2>/dev/null
sleep 2
# Create session with TUI in window 1
tmux new-session -d -s effect-tui -n tui
tmux send-keys -t effect-tui:1 "cd /home/dan/build/my-opentui-project/packages/effect-dev-tui && bun run --conditions=browser src/index.tsx" Enter
# Wait for server to start
sleep 3
# Create test client in window 2
tmux new-window -t effect-tui:2 -n client
tmux send-keys -t effect-tui:2 "cd /home/dan/build/my-opentui-project/packages/effect-dev-tui && bun run test-client.ts" Enter
# Wait for client to connect
sleep 5
# Check status
tmux capture-pane -t effect-tui:1 -p | tail -1Expected output: Listening | Port: 34437 | Clients: 1 | Spans: X | Metrics: 9 | Tick: X
Send a command to the tmux session:
tmux send-keys -t dev-tui "cd /home/dan/build/my-opentui-project/packages/effect-dev-tui && bun run start 2>&1" EnterParameters:
-t dev-tui: Target the "dev-tui" sessionEnter: Send an Enter key press to execute the command
View the current state of the terminal:
tmux capture-pane -t effect-tui:1 -pView just the status bar (bottom line):
tmux capture-pane -t effect-tui:1 -p | tail -1View top portion:
tmux capture-pane -t effect-tui:1 -p | head -20Check for errors (crashes will show panic messages):
tmux capture-pane -t effect-tui:1 -p -S -100 | grep -E "panic|Error|Segmentation"Parameters:
-t effect-tui:1: Target window 1 (the TUI)-p: Print to stdout-S -100: Include scrollback (last 100 lines)
tmux send-keys -t effect-tui:1 "1" # Switch to Clients tab
tmux send-keys -t effect-tui:1 "2" # Switch to Tracer tab
tmux send-keys -t effect-tui:1 "3" # Switch to Metrics tabtmux send-keys -t effect-tui:1 "j" # Navigate down
tmux send-keys -t effect-tui:1 "k" # Navigate uptmux send-keys -t effect-tui:1 "Enter" # Toggle expand/collapse on selected spantmux send-keys -t effect-tui:1 "?" # Show help overlayFor special keys, use:
tmux send-keys -t effect-tui:1 "C-c" # Ctrl+C (force quit)
tmux send-keys -t effect-tui:1 "q" # Quit gracefully
tmux send-keys -t effect-tui:1 "Tab" # Switch focus between panestmux send-keys -t dev-tui "?" && sleep 1 && tmux capture-pane -t dev-tui -ptmux send-keys -t dev-tui "space"tmux send-keys -t dev-tui "q"tmux send-keys -t dev-tui "C-c"The following features can be tested interactively:
- Server starts and listens on port 34437
- Terminal UI renders properly
- Keyboard shortcuts work (q, h, ?)
- Help panel toggles on (h) or (?)
- Help panel closes on any key
- Main view shows "Waiting for Effect applications to connect..."
- Status bar displays "Listening" and client count
- Clean exit on [Q]
- Demo Effect spans are logged and visible in console output
View console logs from the application:
tail -f /tmp/effect-tui.logThis shows timestamped UI events logged during execution.
Kill the tmux session:
tmux kill-session -t dev-tuiThe effect-dev-tui uses:
- Framework: OpenTUI with @opentui/solid renderer for terminal UI components
- State Management: Solid.js createStore for reactive state (NOT atoms!)
- Runtime: Bun for TypeScript execution with
--conditions=browserflag - WebSocket: WebSocket server on port 34437 for Effect DevTools clients
- Effect Runtime: Runs separately from Solid.js, communicates via global store actions
Store Pattern (Critical for Reactivity):
- Store created inside
<StoreProvider>context (src/store.tsx) - Runtime accesses store via
getGlobalActions()which is updated when provider mounts - This enables hot-reload: new store instance → updates globalStoreActions → runtime picks up new reference
- Runtime MUST start AFTER StoreProvider mounts (see store.tsx lines 558-568)
Multi-Client Pattern:
- Runtime subscribes to ALL clients concurrently (not just active client)
- Uses
Effect.forEachwithconcurrency: "unbounded"(runtime.ts lines 128-138) - Pattern from vscode-extension TracerProvider.ts line 118
Component Rendering Patterns (CRITICAL - Prevents Bun Segfaults):
-
Always use
createMemofor derived lists:const visibleNodes = createMemo(() => buildVisibleTree(props.spans, props.expandedSpanIds), );
-
For loops DON'T need
keyprop (TypeScript error if you add it):<For each={metrics()}> {" "} {/* No key needed */} {(metric) => <text>{metric.name}</text>} </For>
-
Use reactive functions for dynamic styles:
{ (metric) => { const color = () => getMetricColor(metric.type); // Reactive return <text style={{ fg: color() }}>{metric.name}</text>; }; }
-
Avoid unnecessary Show wrappers - they can cause segfaults when nested
Correct Pattern (Works):
<Match when={store.ui.activeTab === "metrics"}>
<box flexDirection="row">
<MetricsView metrics={store.metrics} />
</box>
</Match>Problematic Pattern (Segfaults):
<Match when={store.ui.activeTab === "metrics"}>
<Show when={metricCount() > 0}>
{" "}
{/* Nested Show can crash Bun */}
<box>...</box>
</Show>
</Match>src/index.tsx- Main UI with tabs, keyboard handlers, layoutsrc/store.tsx- Solid.js store, actions, StoreProvider contextsrc/runtime.ts- Effect runtime, server startup, client/span/metrics subscriptionssrc/server.ts- WebSocket DevTools server (based on vscode-extension)src/spanTree.tsx- Span tree component with expand/collapsesrc/metricsView.tsx- Metrics list and details componentssrc/clientsView.tsx- Client list componenttest-client.ts- Test Effect app that generates spans and metrics
Runtime (runtime.ts):
- Subscribes to
client.metricsqueue for each client - Polls via
client.requestMetricsevery 500ms (Schedule.spaced) - Calls
actions.updateMetrics(snapshot)to update store
Store (store.tsx):
updateMetricsaction convertsDomain.Metric[]toSimpleMetric[]- Uses
simplifyMetric()helper to extract type, value, tags, details - Supports: Counter, Gauge, Histogram, Frequency, Summary
UI (metricsView.tsx):
- MetricsView: For loop rendering metrics with icons and colors
- MetricDetailsPanel: Shows selected metric's type, value, tags, details
- Uses
createMemofor selected metric lookup
- Multiple bun processes - ALWAYS
killall -9 bunbefore starting - Runtime starts before Solid mounts - Use onMount in StoreProvider
- Nested Show components - Can cause Bun segfaults, simplify JSX
- Not using createMemo - Can cause performance issues with For loops
- Trying to use
keyprop on For - TypeScript error, not needed in Solid.js - Accessing store.metrics directly - Wrap in createMemo for reactivity