Skip to content

Reduce TUI idle CPU and improve responsiveness with event-driven UI wakeups #135

Description

@inureyes

Problem / Background

The current TUI loop in src/view/ui_loop.rs is still driven by a fixed polling cadence (event::poll(100ms) plus render interval checks), even when there is no new data and no user interaction.

This has two practical side effects:

  • Idle CPU overhead: the UI wakes up repeatedly to do render housekeeping, frame counting, scroll offset updates, and content generation checks even when the screen is effectively idle.
  • Input latency coupling: keyboard and mouse responsiveness are partially tied to the poll interval, which makes the interface feel less responsive than it should for a terminal UI.

Although DifferentialRenderer reduces the amount of terminal output, the application still pays the cost of waking up and traversing the render path too often.

Proposed Solution

Convert the UI loop from a fixed poll-driven design to an event-driven wakeup model.

The UI should wake up only for meaningful reasons:

  • terminal input events (keyboard / mouse)
  • terminal resize events
  • fresh data from background collectors
  • explicit animation ticks for views that actually need them (loading indicator, marquee scroll, clock, etc.)

A tokio::select!-based coordinator with channels / notifications should replace the current poll + throttle + retry pattern.

Scope

Event-driven UI wakeups

  • Replace the fixed event::poll() loop with an event-forwarding task or async event stream
  • Introduce explicit wakeup sources for input, resize, data refresh, and animation tick events
  • Ensure the UI sleeps fully when nothing changes on screen

Collector-to-UI notification path

  • Add a notification mechanism from data collectors to the UI loop when new state is available
  • Trigger immediate re-render on fresh data instead of waiting for the next polling window
  • Preserve existing local and remote collection behavior

Animation isolation

  • Separate animation cadence from general UI wakeups
  • Run animation ticks only when an animated element is visible
  • Allow slower cadence for non-critical animation without affecting input responsiveness

Input responsiveness

  • Make key and mouse handling independent from the previous 100ms polling interval
  • Preserve all existing navigation semantics and shortcuts
  • Ensure resize handling remains immediate and stable

Acceptance Criteria

  • Idle TUI does not continuously wake up on a fixed 100ms cadence when nothing is changing
  • New collector data triggers prompt screen refresh without a busy loop
  • Keyboard and mouse input feel immediate and are no longer gated by the old polling interval
  • Loading animation, marquee scrolling, and clock updates still work correctly under the new event model
  • Local mode and remote mode both preserve current functionality and navigation behavior
  • CPU usage in an idle TUI session is measurably lower than the current baseline

Integration and Verification Requirements

  • The implementation must be verified by actually running the affected code path and confirming the intended behavior works end-to-end
  • Verification must cover both behavior correctness and the expected performance/responsiveness improvement for this issue
  • The final result must be integrated into the existing project flow, not left behind as an isolated module, helper, or function that is not exercised by the real application path
  • Completion is not satisfied by adding code alone; the change must be wired into the real local/remote TUI execution path and confirmed to be active in practice

Technical Considerations

  • crossterm input handling may need to move to a dedicated forwarding task or EventStream-based adapter depending on feature availability.
  • Avoid holding AppState mutex guards across await points while dispatching events.
  • The wakeup model should be designed so later render optimizations can plug into it cleanly.
  • This should be treated as the first implementation step for the TUI performance/responsiveness work, because later optimizations benefit from a cleaner event model.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions