Rewrite the Chop TUI application from Go (using Charmbracelet's Bubble Tea framework) to Zig (using rockorager/libvaxis). This is a complete rewrite, not a port - take advantage of Zig's strengths and libvaxis's design patterns.
Chop is an Interactive EVM Development Environment - a terminal UI for local Ethereum development that provides:
- EVM Call Execution - Execute bytecode with custom parameters (CALL, STATICCALL, CREATE, CREATE2, DELEGATECALL)
- Blockchain Simulation - In-memory blockchain with blocks, transactions, accounts
- State Inspection - Query balances, nonces, storage slots, contract code
- Bytecode Disassembly - View EVM opcodes with basic block navigation and PC jumping
- Call History & Fixtures - Track execution history, save/load test fixtures
- Dashboard - Real-time blockchain stats, recent activity
- Framework: Bubble Tea (Elm-inspired MVU architecture)
- Styling: Lipgloss (declarative terminal styling)
- Components: Bubbles (table, textinput widgets)
- CLI: urfave/cli v2
Model (state) → View (render) → Update (event handling) → Model...
Modelstruct holds all application stateView()returns string representationUpdate(Msg)handles events, returns new Model + Commands- Commands for async operations (EVM execution, file I/O)
| File | Lines | Purpose |
|---|---|---|
/app/model.go |
106 | Central Model struct - all application state |
/app/view.go |
388 | State-based rendering switch |
/app/handlers.go |
1340 | Keyboard input handlers (18 handler functions) |
/app/update.go |
157 | Event routing and message processing |
/app/init.go |
101 | Initialization and async commands |
/tui/ui.go |
984 | UI components and layout helpers |
/config/config.go |
187 | Colors, keybindings, defaults |
/types/types.go |
359 | Shared types (AppState enum, Tab, CallEntry, etc.) |
/core/blockchain/ |
- | Blockchain simulation |
/core/accounts/ |
- | Account management |
/core/evm/ |
- | EVM execution |
/core/state/ |
- | State persistence |
| Location | Purpose |
|---|---|
/libvaxis/src/vxfw/ |
High-level framework (use this!) |
/libvaxis/src/vxfw/widgets/ |
Built-in widgets (Button, TextField, Text, Table, etc.) |
/libvaxis/examples/ |
Example applications |
/libvaxis/src/Vaxis.zig |
Core terminal handling |
/libvaxis/src/Cell.zig |
Cell (character + style) |
/libvaxis/src/Window.zig |
Window abstraction |
Main Flow:
StateMainMenu → StateCallParameterList → StateCallParameterEdit → StateCallExecuting → StateCallResult
Dashboard Tabs (1-7 keys):
Tab 1: StateDashboard
Tab 2: StateCallHistory → StateCallHistoryDetail
Tab 3: StateContracts → StateContractDetail (bytecode disassembly)
Tab 4: StateAccountsList → StateAccountDetail
Tab 5: StateBlocksList → StateBlockDetail
Tab 6: StateTransactionsList → StateTransactionDetail
Tab 7: StateSettings
Other:
StateStateInspector (address query)
StateGotoPC (jump to instruction)
StateLogDetail, StateFixturesList, StateConfirmReset
- Tab Navigation: Keys 1-7 switch major views
- Stack Navigation: Enter pushes detail views, Esc pops
- Modal States: Parameter editing, confirmation dialogs
| Bubble Tea (Go) | libvaxis/vxfw (Zig) |
|---|---|
Model struct |
Custom struct implementing Widget |
Init() |
.init event in eventHandler |
Update(Msg) |
eventHandler function |
View() string |
drawFn returning Surface |
tea.Cmd |
EventContext.cmds |
tea.Msg |
vxfw.Event union |
| Bubbles (Go) | libvaxis/vxfw (Zig) |
|---|---|
table.Model |
vxfw.Table widget |
textinput.Model |
vxfw.TextField widget |
| Lipgloss styles | vaxis.Style struct |
lipgloss.JoinVertical |
vxfw.FlexColumn |
lipgloss.JoinHorizontal |
vxfw.FlexRow |
| Border rendering | vxfw.Border widget |
| Custom components | Structs with widget() method |
| Bubble Tea | libvaxis |
|---|---|
tea.KeyMsg |
.key_press / .key_release |
tea.MouseMsg |
.mouse |
tea.WindowSizeMsg |
.winsize |
| Custom messages | Custom event types or state changes |
q/Ctrl+C- Quitc- Copy (context-aware)1-7- Tab switching
↑/k,↓/j- Cursor up/down←/h,→/l- Navigate (blocks in disassembly)Enter- Select/confirmEsc- Back/cancel
e- Execute EVM callr- Reset parameterR- Reset all parametersCtrl+V- Pastef- Save fixtureg- Jump to destinationG- Open goto PCp- Reveal private key (account detail)
const colors = struct {
const primary = vaxis.Color{ .rgb = .{ 0x00, 0xD9, 0xFF } }; // Cyan - headings
const secondary = vaxis.Color{ .rgb = .{ 0x7D, 0x56, 0xF4 } }; // Purple
const amber = vaxis.Color{ .rgb = .{ 0xFF, 0xB8, 0x6C } }; // Orange - values
const success = vaxis.Color{ .rgb = .{ 0x50, 0xFA, 0x7B } }; // Green
const err = vaxis.Color{ .rgb = .{ 0xFF, 0x55, 0x55 } }; // Red
const muted = vaxis.Color{ .rgb = .{ 0x62, 0x72, 0xA4 } }; // Gray - help text
const text = vaxis.Color{ .rgb = .{ 0xF8, 0xF8, 0xF2 } }; // Light - default
};src/
├── main.zig # Entry point, app initialization
├── app/
│ ├── model.zig # Central application state
│ ├── root.zig # Root widget (routing, tab bar)
│ └── events.zig # Custom event types
├── views/
│ ├── dashboard.zig # Dashboard view
│ ├── history.zig # Call history view
│ ├── contracts.zig # Contracts/disassembly view
│ ├── accounts.zig # Accounts view
│ ├── blocks.zig # Blocks view
│ ├── transactions.zig # Transactions view
│ ├── settings.zig # Settings view
│ └── inspector.zig # State inspector view
├── widgets/
│ ├── tab_bar.zig # Tab navigation bar
│ ├── data_table.zig # Reusable data table
│ ├── disassembly.zig # Bytecode disassembly view
│ ├── param_editor.zig # Call parameter editor
│ └── help_bar.zig # Context-sensitive help
├── core/
│ ├── blockchain.zig # Blockchain simulation
│ ├── accounts.zig # Account management
│ ├── evm.zig # EVM execution
│ └── state.zig # State persistence
├── config.zig # Colors, keys, defaults
└── types.zig # Shared type definitions
const MyView = struct {
// State
table: vxfw.Table,
selected_index: usize = 0,
// Parent reference for callbacks
app: *AppModel,
pub fn widget(self: *MyView) vxfw.Widget {
return .{
.userdata = self,
.eventHandler = typeErasedEventHandler,
.drawFn = typeErasedDrawFn,
};
}
fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
const self: *MyView = @ptrCast(@alignCast(ptr));
return self.handleEvent(ctx, event);
}
fn handleEvent(self: *MyView, ctx: *vxfw.EventContext, event: vxfw.Event) !void {
switch (event) {
.key_press => |key| {
if (key.matches('q', .{})) {
ctx.quit = true;
} else if (key.matches(vaxis.Key.enter, .{})) {
// Handle selection
try ctx.consumeAndRedraw();
}
},
else => {},
}
}
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) !vxfw.Surface {
const self: *MyView = @ptrCast(@alignCast(ptr));
return self.draw(ctx);
}
fn draw(self: *MyView, ctx: vxfw.DrawContext) !vxfw.Surface {
// Use FlexColumn for vertical layout
// Return Surface with children
}
};Use vaxis.vxfw for the application - it handles:
- Event loop management
- Focus tracking
- Automatic redrawing
- Mouse handling
libvaxis provides a per-frame arena allocator (ctx.arena). Use it for temporary strings and layouts - they're automatically freed after rendering.
const label = try std.fmt.allocPrint(ctx.arena, "Block #{d}", .{block_num});Build UIs by composing Surfaces with children:
return .{
.size = max_size,
.widget = self.widget(),
.buffer = &.{}, // Empty = just composition
.children = children, // SubSurface array with offsets
};libvaxis has a built-in Table widget (vxfw.Table) that handles:
- Column layout and resizing
- Row selection and navigation
- Scrolling
- Custom cell rendering
Request focus explicitly:
try ctx.requestFocus(self.text_input.widget());For EVM execution and file I/O, consider:
- Running in separate threads
- Using libvaxis's tick system for polling
- State machine for loading states
- Basic app structure with vxfw
- Tab bar navigation (7 tabs)
- Keyboard handling framework
- Color/style configuration
- Dashboard (stats display)
- Accounts list/detail
- Blocks list/detail
- Transactions list/detail
- Call history list/detail
- Contract list with disassembly view
- Settings view
- Call parameter editor
- EVM execution integration
- State inspector
- Fixture management
- Bytecode disassembly navigation (basic blocks, PC jumping)
- Copy/paste integration
- Help system
- Error handling and feedback messages
- libvaxis examples:
/libvaxis/examples/counter.zig(simple vxfw app) - Table example:
/libvaxis/examples/table.zig - Current model:
/app/model.go(understand state structure) - Current view:
/app/view.go(understand rendering logic) - Current handlers:
/app/handlers.go(understand keyboard handling)
- Build incrementally - get each view working before moving on
- Test keyboard navigation thoroughly
- Verify color rendering in different terminals
- Test with various terminal sizes
- Ensure Esc always returns to previous state
- Should we keep the core domain logic (blockchain, accounts, EVM) in Go and call via FFI, or rewrite in Zig?
- How should we handle file persistence (JSON files for state/fixtures)?
- Should we support the existing config file format or create a new one?
- What's the minimum terminal size we should support?
- All 7 tabs navigable with keyboard
- Data tables with selection and scrolling
- Call parameter editing with validation
- EVM execution with result display
- Bytecode disassembly with navigation
- State persistence (history, fixtures)
- Responsive layout
- Consistent color theming
- Context-sensitive help text
- Copy to clipboard functionality