Skip to content

tui: pause animation ticks while the terminal is blurred#2668

Merged
dgageot merged 3 commits intodocker:mainfrom
dgageot:board/1b994833ccc0868c
May 6, 2026
Merged

tui: pause animation ticks while the terminal is blurred#2668
dgageot merged 3 commits intodocker:mainfrom
dgageot:board/1b994833ccc0868c

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented May 6, 2026

Why

When docker-agent runs in a background tab while the agent is streaming, the bubbletea animation tick chain (14 fps spinner + cache invalidations + view rebuilds) keeps firing into a screen nobody is looking at. That's pure CPU waste.

What

Drop `animation.TickMsg` while the terminal is blurred and let the chain die. Re-arm it on the next `FocusMsg` via `animation.StartTick()` so spinners resume immediately when the user comes back. The existing Docker Desktop release/restore cycle (commit cc2dfcf) is preserved.

Why not just `tea.WithFPS(30)`?

Static FPS reduction halves the ~60 cheap goroutine wake-ups per second everywhere — well under 1 % CPU savings. This change targets the expensive work — full `Update` + `View` + cache invalidation per spinner tick — exactly when the user can't see it (background tab), and costs nothing when they're looking at the terminal (foreground stays at full responsiveness).

Implementation note

Two flags rather than one:

  • `focused` — for the spurious-`FocusMsg` filter introduced by Docker Desktop's release/restore (must default to `false` so the first real `FocusMsg` is treated as a transition).
  • `tickPaused` — for animation gating (must default to `false` so ticks flow at startup, before any focus event arrives — some terminals never send one).

They share most transitions but their initial values differ, so keeping them separate avoids cute-but-wrong overloading.

dgageot added 3 commits May 6, 2026 15:22
The commit 33d0d5c3b changed focused field initialization from false (default)
to true. This breaks the Docker Desktop terminal restoration fix from cc2dfcf.

In Docker Desktop, the first FocusMsg after startup triggers the
ReleaseTerminal/RestoreTerminal cycle that fixes broken display after tab
switches. With focused=true at startup, this first FocusMsg is filtered out
by the spurious-FocusMsg check, preventing the restoration.

The animation tick pausing behavior is correct: dropping ticks while blurred
saves CPU, and the active counter staying positive is intentional (animations
are registered but paused). StartTick() on FocusMsg correctly restarts the
chain when focus is regained.
@dgageot dgageot requested a review from a team as a code owner May 6, 2026 13:24
Copy link
Copy Markdown
Contributor

@derekmisler derekmisler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my almost-4-year-old laptop thanks you.

Copy link
Copy Markdown

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The PR cleanly decouples the animation tick-pause concern from the focus-tracking concern by introducing two boolean flags with different initialization semantics. The logic is correct:

  • Startup safety: tickPaused starts false so ticks flow before any focus event arrives (terminals that never send FocusMsg work correctly).
  • Spurious-FocusMsg filter: focused still correctly suppresses the spurious FocusMsg emitted by Docker Desktop's RestoreTerminal cycle.
  • Tick resumption: On FocusMsg, StartTick() is only called when animation.HasActive() is true — if no animations are active at refocus time, the tick chain will be restarted by the first component that calls StartTickIfFirst(), which is the existing behavior.
  • m.program guard: The removal of the m.program == nil early-return is safe because SetProgram is called synchronously before p.Run() in cmd/root/new.go, so m.program is always set before the event loop can deliver any messages.
  • tea.Batch with empty slice: Returns a nil Cmd, which bubbletea handles correctly.

No bugs found in the changed code.

@dgageot dgageot merged commit 8467ab2 into docker:main May 6, 2026
7 checks passed
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.

2 participants