Proposal: Migrate from tview to Bubbletea
## Executive Summary
This proposal outlines a plan to replace the current TUI implementation in the GitLab CLI, which uses `rivo/tview` and `gdamore/tcell/v2`, with Charm's `charmbracelet/bubbletea` framework. This migration will modernize the codebase, improve maintainability, and align with contemporary Go TUI development practices.
## Current State Analysis
### Existing TUI Framework Stack
The GitLab CLI currently uses:
- **`github.com/rivo/tview v0.0.0-20230621164836-6cc0565babaf`** - Main TUI framework
- **`github.com/gdamore/tcell/v2 v2.9.0`** - Low-level terminal handling
- **`github.com/AlecAivazis/survey/v2 v2.3.7`** - Interactive prompts
### Current TUI Usage
Analysis reveals TUI usage in two primary areas:
1. **CI Pipeline Viewer** ([internal/commands/ci/view/view.go](internal/commands/ci/view/view.go))
- Complex full-screen application with ~994 lines
- Real-time pipeline job monitoring
- Interactive navigation (vim-style keybindings)
- Features:
- Job status visualization with color-coded boxes
- Stage-based layout with connecting lines
- Log/trace viewing in split view
- Job control (run, retry, cancel)
- Modal dialogs for confirmations
- Nested pipeline navigation (bridges)
- Uses: `tview.Application`, `tview.Pages`, `tview.TextView`, `tview.Modal`
2. **Issue Board Viewer** ([internal/commands/issue/board/view/issue_board_view.go](internal/commands/issue/board/view/issue_board_view.go))
- Kanban-style board view (~473 lines)
- Displays issues grouped by labels
- Features:
- Multi-column layout (Flex-based)
- Color-coded labels
- Dynamic issue filtering
- Group and project board support
- Uses: `tview.Flex`, `tview.TextView`
3. **Interactive Prompts** (23 files)
- Uses `AlecAivazis/survey/v2` for user input
- Select menus, text inputs, confirmations
- Used throughout commands for interactive workflows
### Charm Ecosystem Already Present
Interestingly, the project already includes several Charm libraries:
- `github.com/charmbracelet/bubbletea v1.3.4` (indirect dependency)
- `github.com/charmbracelet/bubbles v0.21.0` (indirect)
- `github.com/charmbracelet/lipgloss v1.1.1` (indirect)
- `github.com/charmbracelet/huh v0.7.0` (direct dependency)
- `github.com/charmbracelet/glamour v0.10.0` (direct dependency)
- `github.com/charmbracelet/fang v0.4.3` (direct dependency)
The presence of `huh` (forms/prompts) and other Charm libraries indicates partial adoption of the Charm ecosystem.
## Why Migrate to Bubbletea?
### 1. Modern Architecture
**Elm Architecture Pattern**: Bubbletea follows The Elm Architecture (TEA), providing:
- **Predictable state management**: All state changes flow through a single `Update()` function
- **Immutable patterns**: Reduces bugs from shared mutable state
- **Testability**: Pure functions make testing straightforward
- **Clear separation of concerns**: Model (state), Update (logic), View (rendering)
**Current tview approach**: Imperative, mutation-based, making state management complex and error-prone in larger applications.
### 2. Better Developer Experience
- **Comprehensive ecosystem**:
- `bubbles`: Pre-built, composable components (lists, tables, spinners, viewports)
- `lipgloss`: Advanced styling and layout engine
- `harmonica`: Animation support
- Active community and regular updates
- **Composition over inheritance**: Components are easily composable
- **Type-safe message passing**: Commands and messages are strongly typed
- **Hot-reloading support**: Better development iteration
### 3. Improved Maintainability
- **Clearer code structure**: TEA pattern enforces organization
- **Easier debugging**: Centralized update logic
- **Better error handling**: Errors flow through messages
- **Reduced complexity**: No need to manage goroutines for UI updates
### 4. Active Development & Community
| Framework | Last Update | Stars | Community |
|-----------|-------------|-------|-----------|
| tview | June 2023 (v0.0.0) | 10.5k | Moderate |
| Bubbletea | Active (v1.3.4) | 29k+ | Very Active |
Bubbletea is actively maintained by Charm, with frequent releases, extensive documentation, and a thriving ecosystem.
### 5. Consistency with Existing Dependencies
The CLI already uses:
- `charmbracelet/huh` for forms (could replace survey/v2)
- `charmbracelet/glamour` for markdown rendering
- `charmbracelet/fang` for advanced rendering
Migrating to Bubbletea would consolidate the TUI stack around Charm's ecosystem, reducing dependency complexity.
### 6. Enhanced Features
Bubbletea provides out-of-the-box:
- Better mouse support
- Window resize handling
- Alternate screen buffer management
- Framerate control
- Suspension/resumption support
- Better terminal capability detection
## Proposed Migration Plan
### Phase 1: Foundation (Week 1-2)
**Goal**: Set up Bubbletea infrastructure without disrupting existing functionality.
1. **Add Bubbletea as direct dependency** (already indirect)
```bash
go get github.com/charmbracelet/bubbletea@latest
go get github.com/charmbracelet/bubbles@latest
```
2. **Create shared TUI utilities**
- Create `internal/tui/` package
- Common styles using lipgloss
- Reusable message types
- Helper functions for common patterns
3. **Set up testing infrastructure**
- Create test helpers for Bubbletea models
- Set up golden file testing for views
- Mock message handling
### Phase 2: Issue Board Migration (Week 3-4)
**Rationale**: Start with simpler, read-only view to learn patterns.
1. **Migrate Issue Board Viewer**
- Convert `tview.Flex` layout to lipgloss layout
- Implement board as Bubbletea model
- Add viewport for scrolling
- Preserve existing keyboard shortcuts
- Add tests
2. **Benefits of starting here**:
- Simpler use case (mostly display)
- No complex state management
- Proves lipgloss layout capabilities
- Quick win to validate approach
### Phase 3: CI View Migration (Week 5-8)
**Rationale**: More complex, requires careful state management.
1. **Design the model structure**
```go
type ciViewModel struct {
// State
pipeline gitlab.PipelineInfo
jobs []*ViewJob
currentJob *ViewJob
navigator navigator
// UI State
logsVisible bool
modalState modalState
viewport viewport.Model
// Data channels (async updates)
jobUpdates chan []*ViewJob
}
```
2. **Implement core functionality**
- Job list view with stage grouping
- Navigation (vim-style + arrows)
- Log viewer with viewport
- Modal confirmations
- Real-time job updates
3. **Preserve existing features**:
- All keyboard shortcuts
- Visual pipeline representation with connecting lines
- Color-coded job statuses
- Job control (run/retry/cancel)
- Trace suspension (Ctrl+Space)
- Nested pipeline navigation
4. **Improvements over current**:
- Cleaner separation of concerns
- Better error handling
- Easier to add new features
- More responsive UI
- Better testing
### Phase 4: Prompt Migration (Week 9-10)
**Goal**: Consolidate all interactive prompts under Charm ecosystem.
1. **Replace survey/v2 with huh**
- Already have `charmbracelet/huh` as dependency
- Migrate Select, Input, Confirm prompts
- Update 23 files using survey
2. **Benefits**:
- Consistent look and feel
- Better integration with Bubbletea apps
- More customizable
- Better accessibility
### Phase 5: Cleanup & Optimization (Week 11-12)
1. **Remove old dependencies**
```bash
go mod tidy # Remove tview, survey if no longer used
```
2. **Update documentation**
- Update contributing guide
- Add TUI development guide
- Document common patterns
3. **Performance optimization**
- Profile rendering performance
- Optimize update cycles
- Reduce allocations
4. **Polish**
- Consistent styling across all TUI views
- Improved help text
- Better error messages
## Technical Considerations
### State Management
**Current (tview)**: Global variables and goroutines manage state
```go
var (
logsVisible, modalVisible bool
curJob *ViewJob
jobs []*ViewJob
pipelines []gitlab.PipelineInfo
boxes map[string]*tview.TextView
)
```
**Proposed (Bubbletea)**: Encapsulated in model
```go
type Model struct {
state ViewState
pipeline PipelineData
navigation NavigationState
ui UIState
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)
```
### Concurrent Updates
**Current**: Background goroutines push to channels
```go
go updateJobs(app, jobsCh, forceUpdateCh, client, commit)
```
**Proposed**: Commands return messages
```go
func pollJobs(client *gitlab.Client, pipeline PipelineInfo) tea.Cmd {
return func() tea.Msg {
jobs, err := fetchJobs(client, pipeline)
return JobsUpdatedMsg{jobs: jobs, err: err}
}
}
```
### Rendering Complex Layouts
**Current**: Manual coordinate calculation and box drawing
```go
boxX := px + (maxX / stages * stageIdx)
x, y, w, h := boxX, maxY/6+(rowIdx*5), maxTitle+2, 4
```
**Proposed**: lipgloss declarative layout
```go
lipgloss.NewStyle().
Width(width).
Height(height).
Border(lipgloss.RoundedBorder()).
Render(content)
```
### Testing Strategy
**Current**: Limited TUI testing (mostly unit tests for business logic)
**Proposed**:
- Model unit tests (Update function)
- View snapshot tests (golden files)
- Integration tests with tea.TestProgram
- Business logic remains separately testable
## Migration Risks & Mitigation
### Risk 1: Breaking Existing Workflows
**Impact**: High
**Likelihood**: Medium
**Mitigation**:
- Maintain feature parity during migration
- Extensive testing with existing scripts
- Beta testing period with flag to use old version
- Clear migration guide for users
### Risk 2: Performance Regression
**Impact**: Medium
**Likelihood**: Low
**Mitigation**:
- Benchmark before and after
- Profile rendering performance
- Optimize hot paths
- Use bubbles' built-in optimizations
### Risk 3: Learning Curve
**Impact**: Medium
**Likelihood**: Medium
**Mitigation**:
- Internal training sessions
- Comprehensive documentation
- Code review guidelines
- Pair programming for first implementations
### Risk 4: Incomplete Feature Migration
**Impact**: High
**Likelihood**: Low
**Mitigation**:
- Detailed feature inventory
- Test cases for all features
- User acceptance testing
- Gradual rollout
## Success Metrics
1. **Feature Parity**: 100% of existing TUI features work in Bubbletea
2. **Performance**: No regression in render time (target: <16ms/frame)
3. **Code Quality**:
- Reduced complexity (cyclomatic complexity <15)
- Test coverage >80% for TUI code
- Reduced global state usage
4. **Maintainability**:
- Easier to add new features (measure by PR size/complexity)
- Fewer bugs reported in TUI components
5. **Developer Satisfaction**: Positive feedback from team
## Alternative Approaches Considered
### Option 1: Stay with tview
**Pros**: No migration cost, works today
**Cons**: Less active development, harder to maintain, imperative style
### Option 2: Build Custom TUI Framework
**Pros**: Full control
**Cons**: Massive effort, ongoing maintenance burden, reinventing wheel
### Option 3: Migrate to Other Frameworks (termui, gocui)
**Pros**: Alternatives exist
**Cons**: Similar issues to tview, less active than Bubbletea
## Recommendation
### Option A: Huh-First Incremental Migration (RECOMMENDED)
**Start by migrating prompts to huh, then reassess full TUI migration.**
This is a more conservative, lower-risk approach that delivers value quickly:
1. **Phase 0: Migrate Interactive Prompts to huh (2-3 weeks)**
- Replace all 23 files using `survey/v2` with `huh`
- Already have the dependency
- Immediate benefits: Consistent UX, better accessibility
- Low risk: Prompts are isolated, easy to test
- Deliverable: Remove survey/v2 dependency
2. **Phase 1: Evaluate TUI Migration Need**
- After prompt migration, reassess whether full TUI migration is necessary
- Consider: Are there user complaints about current TUI? Performance issues?
- Decision point: If tview works well enough, maybe keep it
3. **If proceeding with full migration:**
- Use the phased Bubbletea approach (Phases 1-5 above)
- Already have team familiar with Charm ecosystem from huh migration
- Better understanding of Charm patterns
**Why this is better:**
- **Immediate value**: Prompt migration has clear benefits now
- **Lower risk**: Smaller change surface, easier rollback
- **Validates Charm adoption**: Tests team's comfort with Charm libraries
- **Flexibility**: Can stop after prompts if full migration isn't justified
- **Reduced dependency now**: One less dependency (survey) vs. maybe removing tview later
### Option B: Full Bubbletea Migration (ORIGINAL PROPOSAL)
**Proceed with complete Bubbletea migration** using the phased approach outlined above.
This makes sense if:
- Current TUI has significant issues (bugs, performance, maintainability)
- Team has capacity for 12-week migration
- Strong commitment to Charm ecosystem standardization
### Justification for Huh-First
1. **Strategic alignment**: Charm ecosystem already in use (huh, glamour, fang)
2. **Risk management**: Smaller initial commitment, can validate approach
3. **Quick wins**: Prompt migration delivers value in weeks, not months
4. **Flexibility**: Can reassess full TUI migration with more information
5. **Minimal disruption**: Can be done incrementally without breaking changes
6. **Learning opportunity**: Team learns Charm patterns on simpler problems first
## Timeline Summary
### Option A: Huh-First (Recommended)
| Phase | Duration | Deliverable |
|-------|----------|-------------|
| 0. Prompts to huh | 2-3 weeks | Remove survey/v2 dependency |
| 1. Evaluate & Decide | 1 week | Decision on full TUI migration |
| **Total** | **3-4 weeks** | **Modernized prompts + informed decision** |
### Option B: Full Migration
| Phase | Duration | Deliverable |
|-------|----------|-------------|
| 1. Foundation | 2 weeks | TUI infrastructure |
| 2. Issue Board | 2 weeks | Migrated issue board viewer |
| 3. CI View | 4 weeks | Migrated CI pipeline viewer |
| 4. Prompts | 2 weeks | Migrated all prompts to huh |
| 5. Cleanup | 2 weeks | Removed old deps, docs |
| **Total** | **12 weeks** | **Complete migration** |
## Next Steps
1. **Approval**: Get stakeholder buy-in on proposal
2. **Spike**: 1-week proof-of-concept for CI viewer
3. **Planning**: Detailed task breakdown and sprint planning
4. **Execution**: Begin Phase 1
## Appendix A: Code Comparison
### Current tview Approach
```go
func NewCmdView(f cmdutils.Factory) *cobra.Command {
// ... setup ...
root := tview.NewPages()
root.SetBackgroundColor(tcell.ColorDefault)
app := tview.NewApplication()
app.SetInputCapture(inputCapture(...))
go updateJobs(app, jobsCh, ...)
go func() {
for {
app.SetFocus(root)
jobsView(app, jobsCh, ...)
app.Draw()
}
}()
return app.SetScreen(screen).SetRoot(root, true).Run()
}
```
### Proposed Bubbletea Approach
```go
type ciViewModel struct {
pipeline gitlab.PipelineInfo
jobs []*ViewJob
viewport viewport.Model
// ... state ...
}
func (m ciViewModel) Init() tea.Cmd {
return tea.Batch(
pollJobs(m.client, m.pipeline),
viewport.Init(),
)
}
func (m ciViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m.handleKeypress(msg)
case JobsUpdatedMsg:
m.jobs = msg.jobs
return m, pollJobs(m.client, m.pipeline)
// ... handle messages ...
}
return m, nil
}
func (m ciViewModel) View() string {
return lipgloss.JoinVertical(
lipgloss.Left,
m.renderHeader(),
m.renderJobsList(),
m.renderFooter(),
)
}
func NewCmdView(f cmdutils.Factory) *cobra.Command {
return &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
model := newCIViewModel(...)
p := tea.NewProgram(model)
_, err := p.Run()
return err
},
}
}
```
## Appendix B: Dependencies Before and After
### Before
```
github.com/rivo/tview v0.0.0-20230621164836-6cc0565babaf
github.com/gdamore/tcell/v2 v2.9.0
github.com/AlecAivazis/survey/v2 v2.3.7
```
### After
```
github.com/charmbracelet/bubbletea v1.3.4
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3
github.com/charmbracelet/huh v0.7.0
```
**Dependency reduction**: 3 frameworks → 1 cohesive ecosystem
## Appendix C: Community Resources
- **Bubbletea Documentation**: https://github.com/charmbracelet/bubbletea
- **Bubbles Components**: https://github.com/charmbracelet/bubbles
- **Lipgloss Styling**: https://github.com/charmbracelet/lipgloss
- **Example Applications**: https://github.com/charmbracelet/bubbletea/tree/master/examples
- **Tutorial Series**: https://github.com/charmbracelet/bubbletea/tree/master/tutorials
- **Community Showcase**: https://charm.sh/
---
**Document Version**: 1.0
**Date**: 2025-10-22
**Author**: GitLab CLI Team
**Status**: Proposed
epic