Skip to content

refactor(ui): make cycleSessionStatus non-blocking #137

@dnlopes

Description

@dnlopes

Summary

The cycleSessionStatus method performs a synchronous database operation inside the method body before returning a tea.Cmd. This blocks the UI thread while the database write completes.

Problem

Location: internal/ui/session_list.go:1160-1164

In Bubble Tea, the Update function should return quickly to keep the UI responsive. Long-running operations should be wrapped in tea.Cmd functions that execute asynchronously.

Current code:

func (sl *SessionList) cycleSessionStatus(sessionName string) tea.Cmd {
    // ... get current status ...
    
    // This blocks the UI thread!
    if err := sl.sessionService.UpdateStatus(context.Background(), sessionName, nextStatus); err != nil {
        logging.Logger.Error("Failed to cycle session status", "error", err)
        return nil
    }
    
    // Only after blocking operation completes does this return
    return func() tea.Msg {
        return checkStateMsg{}
    }
}

Execution flow:

  1. User presses the status cycle key
  2. cycleSessionStatus is called from within Update()
  3. UI freezes while UpdateStatus() hits the database
  4. After DB completes, function returns
  5. UI becomes responsive again

For SQLite on a local SSD, this might be 1-5ms (barely noticeable). But:

  • Disk could be busy → 50-100ms delay
  • Database lock contention → longer delays
  • Future change to remote DB → significant delays

Why It Matters

  • Bubble Tea's architecture assumes Update returns quickly
  • Blocking breaks the event loop model
  • Other keypresses queue up, causing input lag
  • Sets a bad precedent for future code

Proposed Solution

Move the database operation into the returned command:

func (sl *SessionList) cycleSessionStatus(sessionName string) tea.Cmd {
    var currentStatus *string
    if sessionInfo, ok := sl.sessionState.Sessions[sessionName]; ok {
        currentStatus = sessionInfo.Status
    }
    nextStatus := sl.statusConfig.GetNextStatus(currentStatus)

    // Return a Cmd that does the DB work asynchronously
    return func() tea.Msg {
        if err := sl.sessionService.UpdateStatus(context.Background(), sessionName, nextStatus); err != nil {
            logging.Logger.Error("Failed to cycle session status", "error", err)
            // Could return an error message type for UI feedback
        }
        return checkStateMsg{}
    }
}

Now the DB operation happens in Bubble Tea's command executor, not blocking the UI.

Additional Context

Similar patterns exist in moveSelectedUp and moveSelectedDown methods which also perform synchronous DB operations. Those should be reviewed as part of this or a follow-up issue.

Labels

  • enhancement
  • performance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions