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