Skip to content

Refactor spinner to use idiomatic Bubble Tea patterns#9196

Merged
pelikhan merged 3 commits intomainfrom
copilot/refactor-spinner-idiomatic-pattern
Jan 7, 2026
Merged

Refactor spinner to use idiomatic Bubble Tea patterns#9196
pelikhan merged 3 commits intomainfrom
copilot/refactor-spinner-idiomatic-pattern

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 7, 2026

The spinner implementation used manual goroutine and mutex management, bypassing Bubble Tea's rendering pipeline. This creates race condition risks, prevents framerate optimization, and diverges from patterns used in other console components.

Changes

Architecture:

  • Implemented spinnerModel with standard Init/Update/View pattern
  • Replaced manual goroutine with tea.NewProgram() using WithoutRenderer() for inline mode
  • Removed sync.Mutex and all manual locking (5+ Lock/Unlock call sites)
  • Removed manual channel management (stopCh chan struct{})
  • Added updateMessageMsg custom message for thread-safe updates via message passing

API:

  • Maintained full backward compatibility
  • All 9 existing usage sites unchanged

Testing:

  • Added 5 new tests covering Bubble Tea model behavior, disabled operations, rapid cycles, and edge cases
  • All 11 tests passing

Before/After

Before:

type SpinnerWrapper struct {
    model   spinner.Model
    stopCh  chan struct{}
    mu      sync.Mutex  // Manual concurrency
}

func (s *SpinnerWrapper) Start() {
    s.mu.Lock()
    defer s.mu.Unlock()
    go s.animate()  // Manual goroutine
}

func (s *SpinnerWrapper) animate() {
    for {
        s.mu.Lock()
        s.model, cmd = s.model.Update(msg)
        s.mu.Unlock()
        // Manual cmd execution and cancellation logic
    }
}

After:

type spinnerModel struct {
    spinner spinner.Model
    message string
}

func (m spinnerModel) Init() tea.Cmd { return m.spinner.Tick }
func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { /* ... */ }
func (m spinnerModel) View() string { /* ... */ }

type SpinnerWrapper struct {
    program *tea.Program  // Bubble Tea manages concurrency
}

func (s *SpinnerWrapper) UpdateMessage(msg string) {
    s.program.Send(updateMessageMsg(msg))  // Message passing
}

Architecture now consistent with list.go and confirm.go components.

Original prompt

This section details on the original issue you should resolve

<issue_title>[plan] Refactor spinner to use idiomatic Bubble Tea pattern</issue_title>
<issue_description>## Overview

This tracking issue covers refactoring the spinner implementation in pkg/console/spinner.go to follow idiomatic Bubble Tea patterns instead of the current anti-pattern of manual goroutine management.

Source: Discussion #9188 (Go Fan Bubble Tea Report)

Problem

The current spinner implementation uses a custom goroutine with a manual update loop, bypassing Bubble Tea's rendering pipeline. This leads to:

  • Manual mutex management requirements
  • Potential race conditions
  • Inability to leverage framerate optimization
  • Difficulty in testing
  • Non-standard architecture

Planned Tasks

This work is broken down into focused, sequential tasks:

  1. Research and design - Evaluate idiomatic Bubble Tea patterns for inline spinners
  2. Implement new spinner - Create new implementation using proper Bubble Tea patterns
  3. Add comprehensive tests - Ensure spinner behavior is correct in all scenarios
  4. Update documentation - Document TTY detection and usage patterns
  5. Migrate and remove old code - Switch all usage to new implementation and clean up

Success Criteria

  • Spinner uses tea.NewProgram() or direct component usage (no custom goroutines)
  • No manual mutex management required
  • TTY detection still works correctly
  • Accessibility support maintained
  • All existing spinner usage continues to work
  • Comprehensive test coverage added
  • ~30% reduction in code complexity

Related

AI generated by Plan Command for discussion #9188

Comments on the Issue (you are @copilot in this section)

@pelikhan @copilot implement issue and list sub issues

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits January 7, 2026 07:34
- Replace manual goroutine management with tea.NewProgram()
- Remove sync.Mutex and manual locking (use Bubble Tea message passing)
- Implement proper Init/Update/View pattern with spinnerModel
- Use tea.WithoutRenderer() for inline mode output to stderr
- Add updateMessageMsg custom message for message updates
- Maintain backward compatibility with existing API
- All tests pass without modifications
- Update documentation to reflect Bubble Tea implementation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Test spinnerModel Init/Update/View directly
- Test disabled operations (when ACCESSIBLE is set)
- Test rapid start/stop cycles
- Test update message before start
- Test stop without start
- All tests pass successfully
- Coverage for edge cases and Bubble Tea-specific behavior

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor spinner for idiomatic Bubble Tea implementation Refactor spinner to use idiomatic Bubble Tea patterns Jan 7, 2026
Copilot AI requested a review from pelikhan January 7, 2026 07:42
@pelikhan pelikhan marked this pull request as ready for review January 7, 2026 12:06
@pelikhan pelikhan merged commit 7e8d44e into main Jan 7, 2026
168 checks passed
@pelikhan pelikhan deleted the copilot/refactor-spinner-idiomatic-pattern branch January 7, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plan] Refactor spinner to use idiomatic Bubble Tea pattern

2 participants