fix(tui): move blocking DB IO out of Update/View into tea.Cmds #101

Merged
lerko merged 1 commits from fix/tui-io-mvu into main 2026-06-11 01:26:06 +00:00
Owner

Final phase of the fresh-eyes first-cut backlog. Makes the TUI data flow idiomatic MVU — no database IO on the UI goroutine.

Problem

  • handleTick called refreshData every second, issuing 4 blocking SQLite queries (GetAllAlerts/GetAllUsers/GetAllNodes/GetAllMaintenanceWindows) on the UI goroutine, errors swallowed.
  • viewDetailPanel ran GetStateChanges — a DB query — inside View(), on every render (tick, keypress, mouse).

A slow disk stalled input and animation.

Fix

  • Split refreshDatarefreshLive() (in-memory engine copies only: sites + logs; safe every tick) and loadTabDataCmd(), a tea.Cmd that loads the 4 DB tables off the UI goroutine and returns a tabDataMsg.
  • handleTick refreshes live state every tick but dispatches the tab-data load only when stale (tabRefreshTTL = 5s) — tab-bar counts stay fresh without a per-second storm. Errors now log; a transient failure keeps previous data instead of blanking the view.
  • Detail panel: state-change history loads once on enter via loadDetailCmd, cached on the model; viewDetailPanel reads the cache — View no longer touches the DB.
  • Init kicks an initial load (no empty first frame); the bare time.Time tick becomes a named tickMsg; the test-alert raw goroutine becomes a tea.Cmd.

Tests

The package's first Update()-driven tests: tab-data load + apply, error-keeps-previous-data, detail cache with a store-hit counter proving View does zero IO across repeated renders, and the handleTick throttle. Full suite green under -race; golangci-lint clean locally.

Phase 4 of the fresh-eyes first-cut backlog (builds on #98, #99, #100).

Final phase of the fresh-eyes first-cut backlog. Makes the TUI data flow idiomatic MVU — no database IO on the UI goroutine. ## Problem - `handleTick` called `refreshData` **every second**, issuing 4 blocking SQLite queries (`GetAllAlerts/GetAllUsers/GetAllNodes/GetAllMaintenanceWindows`) on the UI goroutine, errors swallowed. - `viewDetailPanel` ran `GetStateChanges` — a DB query — **inside `View()`**, on every render (tick, keypress, mouse). A slow disk stalled input and animation. ## Fix - Split `refreshData` → **`refreshLive()`** (in-memory engine copies only: sites + logs; safe every tick) and **`loadTabDataCmd()`**, a `tea.Cmd` that loads the 4 DB tables off the UI goroutine and returns a `tabDataMsg`. - `handleTick` refreshes live state every tick but dispatches the tab-data load only when stale (`tabRefreshTTL` = 5s) — tab-bar counts stay fresh without a per-second storm. Errors now log; a transient failure keeps previous data instead of blanking the view. - **Detail panel:** state-change history loads once on enter via `loadDetailCmd`, cached on the model; `viewDetailPanel` reads the cache — **`View` no longer touches the DB**. - `Init` kicks an initial load (no empty first frame); the bare `time.Time` tick becomes a named `tickMsg`; the test-alert raw goroutine becomes a `tea.Cmd`. ## Tests The package's **first `Update()`-driven tests**: tab-data load + apply, error-keeps-previous-data, detail cache with a **store-hit counter proving `View` does zero IO** across repeated renders, and the `handleTick` throttle. Full suite green under `-race`; golangci-lint clean locally. Phase 4 of the fresh-eyes first-cut backlog (builds on #98, #99, #100).
lerko added 1 commit 2026-06-11 01:15:04 +00:00
fix(tui): move blocking DB IO out of Update/View into tea.Cmds
CI / test (pull_request) Successful in 2m38s
CI / lint (pull_request) Successful in 56s
CI / vulncheck (pull_request) Successful in 51s
f349d0dfd1
The TUI ran database queries on the UI goroutine: handleTick called
refreshData every second, which issued four blocking SQLite queries
(GetAllAlerts/GetAllUsers/GetAllNodes/GetAllMaintenanceWindows) and
swallowed their errors; viewDetailPanel ran GetStateChanges — a DB query
— inside View(), on every render (tick, keypress, mouse). A slow disk
stalled input and animation.

Split refreshData into refreshLive() (in-memory engine copies only —
sites + logs — safe every tick) and loadTabDataCmd(), a tea.Cmd that
loads the four DB-backed tables off the UI goroutine and returns a
tabDataMsg. handleTick now refreshes live state every tick but dispatches
the tab-data load only when older than tabRefreshTTL (5s), so tab-bar
counts stay fresh without a per-second query storm. Errors surface to the
log instead of being dropped, and a transient failure keeps the previous
data rather than blanking the view.

The detail panel's state-change history is loaded once on enter via
loadDetailCmd and cached on the model; viewDetailPanel reads the cache,
so View no longer touches the database. Init kicks an initial load so the
dashboard isn't empty on the first frame, and the bare time.Time tick
message is now a named tickMsg (no cross-message collision). The
test-alert handler's raw goroutine becomes a tea.Cmd.

Adds the package's first Update()-driven tests: tab-data load + apply,
error-keeps-previous-data, detail cache with a store-hit counter proving
View does zero IO across repeated renders, and the handleTick throttle.
Full suite green under -race; golangci-lint clean.
lerko merged commit f349d0dfd1 into main 2026-06-11 01:26:06 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lerkolabs/uptop#101