# How witr Implements Adaptive Refresh Rates and Mouse Navigation in Its Bubble Tea TUI

> Explore how the witr TUI implements adaptive refresh rates with self-tuning loops and mouse navigation by converting input into UI actions. Learn about its innovative techniques.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: internals
- Published: 2026-08-10

---

**The witr TUI uses a self-tuning tick-based loop that adjusts refresh intervals based on measured completion times, combined with a mouse dispatcher that converts clicks, wheels, and double-clicks into semantic UI actions.**

The `witr` tool is a terminal-based system monitor built on [Charm's Bubble Tea](https://github.com/charmbracelet/bubbletea) framework. This article examines how the project balances responsive updates against system load through **adaptive refresh rates**, and how it implements **mouse navigation** across multiple data views without losing keyboard accessibility.

---

## Adaptive Refresh Rate Mechanism

The adaptive refresh system lives in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) and operates through three coordinated components: a base ticker, a refresh guard, and a self-tuning interval adjuster.

### Base Ticker and Refresh Guard

The `waitTick()` function creates a 3-second heartbeat using `tea.Tick`:

```go
func waitTick() tea.Cmd {
    return tea.Tick(refreshInterval, func(t time.Time) tea.Msg {
        return tickMsg(t)               // fires every 3 s
    })
}

```

Each tick reaches `refreshDue()` (lines 80-104 in [`update.go`](https://github.com/pranshuparmar/witr/blob/main/update.go)) before triggering any data fetch. This guard checks `refreshStartedAt` against `maxRefreshInterval` to suppress overlapping refreshes when a previous background operation hasn't completed.

### Self-Tuning Interval Adjustment

After every refresh, `adjustRefreshInterval` recalibrates the target cadence:

| Condition | Action | Threshold |
|-----------|--------|-----------|
| Slow refresh | Increase interval | Duration > `slowFraction * currentInterval` for `backoffStreak` consecutive runs |
| Fast refresh | Decrease interval | Duration < `fastFraction * currentInterval` for `backoffStreak` consecutive runs |

The interval changes by `refreshStep` (defined in [`internal/tui/constants.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/constants.go)) and clamps between base and `maxRefreshInterval` values. This creates **load-proportional refresh behavior**: heavy system activity automatically throttles updates to prevent UI lag, while idle periods receive more frequent data.

```go
func (m MainModel) handleTick(msg tickMsg) (tea.Model, tea.Cmd) {
    if m.state == stateList && m.refreshDue() {
        m.lastRefresh = time.Now()
        cmd = m.refreshProcesses()
        // refresh other tabs as needed …
    }
    return m, tea.Batch(cmd, waitTick())
}

```

---

## Mouse Navigation Architecture

witr's mouse support spans four distinct content areas: processes, ports, containers, and locks. The implementation separates event routing from area-specific handling to maintain clean separation of concerns.

### Event Dispatch Flow

The `Update` method in [`update.go`](https://github.com/pranshuparmar/witr/blob/main/update.go) (lines 30-71) routes `tea.MouseMsg` to `handleMouse`, which classifies clicks into three categories:

- **Title bar clicks** — return to process list view
- **Detail view clicks** — delegate to `handleDetailMouse`
- **Content area clicks** — route to view-specific handlers: `handleProcessAreaMouse`, `handlePortAreaMouse`, `handleContainerAreaMouse`, `handleLockAreaMouse`

### Wheel Scrolling Without Cursor Dependency

Rather than mapping wheel events to absolute Y positions, witr converts them into directional key messages. This preserves Bubble Tea's table selection model while adding scroll convenience:

```go
if isWheel {
    var keyMsg tea.KeyMsg
    if msg.Button == tea.MouseButtonWheelUp {
        keyMsg = tea.KeyMsg{Type: tea.KeyUp}
    } else {
        keyMsg = tea.KeyMsg{Type: tea.KeyDown}
    }
    m.table, cmd = m.table.Update(keyMsg)     // scroll one row
}

```

### Double-Click Detection

Double-clicks trigger detail expansion or panel focus. The detection uses timestamp (`lastClickTime`) and distance thresholds to distinguish intentional doubles from accidental repeats:

```go
if isDoubleClick {
    m.state = stateDetail
    m.viewport.GotoTop()
    m.envViewport.GotoTop()
    return m, m.fetchProcessDetail(pid)        // open the selected PID
}

```

### Column-Based Sorting from Header Clicks

Table headers accept clicks for sort operation. The `getColumnAtX` helper in [`internal/tui/mouse.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/mouse.go) (lines 5-16) translates X-pixel coordinates to column indices:

```go
func (m *MainModel) handleProcessHeaderClick(x int) {
    cols := m.table.Columns()
    colIdx := m.getColumnAtX(x, cols)          // map X → column
    // map column index → sort key, toggle direction, re-sort
    // …
}

```

---

## Key Implementation Files

| File | Responsibility |
|------|--------------|
| [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) | Core `Update` loop, tick handling, adaptive refresh logic, mouse event dispatch |
| [`internal/tui/mouse.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/mouse.go) | Coordinate-to-column mapping (`getColumnAtX`) and header click helpers |
| [`internal/tui/constants.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/constants.go) | Timing parameters: `refreshInterval`, `maxRefreshInterval`, `refreshStep`, `slowFraction`, `fastFraction`, `backoffStreak` |
| [`internal/tui/view.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/view.go) | Layout definitions for tables, viewports, and interactive regions |

---

## Summary

- **Adaptive refresh rates** in witr use a 3-second base ticker with `refreshDue()` guards and `adjustRefreshInterval` tuning to self-throttle under load
- The system measures refresh duration against `slowFraction` and `fastFraction` thresholds, adjusting the interval by `refreshStep` after `backoffStreak` consecutive measurements
- **Mouse navigation** routes `tea.MouseMsg` through `handleMouse` to area-specific handlers, supporting clicks, double-clicks, and wheel scrolling
- Wheel events convert to `KeyUp`/`KeyDown` messages for consistent table behavior, while header clicks map X-coordinates to sortable columns via `getColumnAtX`
- All implementations reside in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) and [`internal/tui/mouse.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/mouse.go) per the pranshuparmar/witr source code

---

## Frequently Asked Questions

### How does witr prevent refresh storms when the system is overloaded?

The `refreshDue()` guard in [`internal/tui/update.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/update.go) checks `refreshStartedAt` against `maxRefreshInterval` before allowing a new refresh. Additionally, the `adjustRefreshInterval` function increases the refresh interval by `refreshStep` after `backoffStreak` consecutive slow refreshes, automatically throttling the update rate when measured durations exceed `slowFraction * currentInterval`.

### Why does witr convert mouse wheel events to keyboard messages instead of using absolute positioning?

Converting `tea.MouseButtonWheelUp` and `tea.MouseButtonWheelDown` to `tea.KeyMsg{Type: tea.KeyUp}` and `tea.KeyMsg{Type: tea.KeyDown}` preserves Bubble Tea's built-in table selection semantics. This approach scrolls by logical rows without requiring coordinate-to-row mapping or cursor tracking, maintaining compatibility with the existing keyboard-driven interface.

### How does witr determine which table column was clicked for sorting?

The `getColumnAtX` function in [`internal/tui/mouse.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/mouse.go) translates the X-coordinate of a mouse click into a column index by accumulating column widths until the coordinate falls within a column's bounds. The header click handler then maps this index to the corresponding sort key, toggles sort direction, and re-sorts the table data.

### What constants control the adaptive refresh behavior in witr?

The timing parameters are defined in [`internal/tui/constants.go`](https://github.com/pranshuparmar/witr/blob/main/internal/tui/constants.go): `refreshInterval` (base 3s tick), `maxRefreshInterval` (upper bound), `refreshStep` (adjustment increment), `slowFraction` and `fastFraction` (performance thresholds), and `backoffStreak` (consecutive measurements required before adjustment). These values tune how aggressively the UI responds to system load changes.