Development Guide
June 9, 2025 · View on GitHub
This guide covers setting up a development environment for SimTool and explains the codebase structure.
Development Setup
Prerequisites
- Go 1.21 or later
- macOS with Xcode
- Git
- Make (optional but recommended)
Getting Started
-
Fork and Clone
git clone https://github.com/yourusername/simtool.git cd simtool go mod download -
Build and Run
make build ./simtool # Or directly: go run ./cmd/simtool -
Run Tests
make test # With coverage: go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out
Project Structure
simtool/
├── cmd/simtool/ # Application entry point
│ └── main.go # CLI flags, initialization
├── internal/ # Internal packages (not importable)
│ ├── config/ # Configuration management
│ │ ├── config.go # Config loading and structures
│ │ ├── keys.go # Keyboard shortcut mapping
│ │ ├── theme.go # Theme extraction and management
│ │ └── detect.go # Terminal theme detection
│ ├── simulator/ # iOS simulator interaction
│ │ ├── simulator.go # Core types and interfaces
│ │ ├── fetcher.go # xcrun simctl wrapper
│ │ ├── app.go # App information and operations
│ │ ├── files.go # File system operations
│ │ └── viewer.go # File content rendering
│ ├── tui/ # Terminal UI (Bubble Tea)
│ │ ├── model.go # Application state
│ │ ├── update.go # Message handling
│ │ ├── view.go # Main view orchestration
│ │ ├── viewport.go # Scrolling logic
│ │ └── components/ # Reusable UI components
│ └── ui/ # UI utilities
│ ├── styles.go # Lipgloss style definitions
│ └── format.go # Formatting helpers
├── docs/ # Documentation
├── scripts/ # Build and utility scripts
└── Makefile # Build automation
Key Concepts
Architecture
SimTool follows clean architecture principles:
- Separation of Concerns: Business logic (simulator package) is separate from UI (tui package)
- Dependency Injection: Interfaces allow for easy testing and mocking
- Component-Based UI: Reusable components for consistent rendering
Bubble Tea Framework
SimTool uses Bubble Tea for the TUI:
- Model: Contains application state
- Update: Handles messages and updates state
- View: Renders the UI based on state
Example flow:
// Model
type Model struct {
simulators []simulator.Item
cursor int
}
// Update
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
// Handle key press
}
return m, nil
}
// View
func (m Model) View() string {
// Render UI
}
Adding a New Feature
-
Plan the Feature
- Identify affected packages
- Design data structures
- Plan UI changes
-
Implement Business Logic
- Add to simulator package if iOS-related
- Create interfaces for testability
-
Add UI Components
- Create new component in components/
- Follow existing patterns
-
Wire Everything Together
- Update model.go with new state
- Add message types and handlers
- Update view.go to render
-
Add Tests
- Unit tests for business logic
- Component tests for UI
Testing
Unit Tests
func TestGetAppsForSimulator(t *testing.T) {
// Arrange
mockFetcher := &MockFetcher{...}
// Act
apps, err := GetAppsForSimulator("udid", true)
// Assert
assert.NoError(t, err)
assert.Len(t, apps, 2)
}
Mocking
Use interfaces for external dependencies:
type Fetcher interface {
Fetch() ([]Item, error)
Boot(udid string) error
}
type MockFetcher struct {
FetchFunc func() ([]Item, error)
}
Code Style
-
Go Standards
- Run
gofmtbefore committing - Follow Effective Go
- Use meaningful variable names
- Run
-
Project Conventions
- Interfaces end with "-er" (Fetcher, Viewer)
- Error messages start with lowercase
- Use table-driven tests
-
Comments
- Document all exported types and functions
- Explain "why", not "what"
Debugging
-
Enable Debug Logging
// Add debug prints (remove before committing) fmt.Fprintf(os.Stderr, "DEBUG: %v\n", variable) -
Use Delve
dlv debug ./cmd/simtool -
Bubble Tea Debug Mode
p := tea.NewProgram(model, tea.WithAltScreen())
Common Tasks
Adding a New View
- Add ViewState constant
- Create component in components/
- Add case in view.go
- Handle navigation in update.go
Adding a Config Option
- Update config.Config struct
- Add to defaultConfig
- Update config.example.toml
- Document in configuration.md
Adding a Keyboard Shortcut
- Update KeysConfig in config/keys.go
- Add default in defaultKeysConfig
- Handle in handleKeyPress
- Update documentation
Performance Considerations
- Lazy Loading: Load file content in chunks
- Caching: Cache lexers for syntax highlighting
- Efficient Rendering: Only render visible content
- Async Operations: Use tea.Cmd for blocking operations
Release Process
- Update version in Makefile
- Update CHANGELOG.md
- Create git tag
- Push tag (triggers CI release)
Troubleshooting Development Issues
Build Errors
# Clean and rebuild
make clean
go mod tidy
make build
Test Failures
# Run specific test
go test -v -run TestName ./internal/simulator
# Update golden files
go test ./... -update
Terminal Issues
- Test in different terminals (iTerm2, Terminal.app, WezTerm)
- Check TERM environment variable
- Verify terminal capabilities
Resources
Getting Help
- Check existing issues on GitHub
- Ask in discussions
- Read the test files for examples
- Explore the codebase with
go doc