feat: add TUI auto-refresh with configurable interval and manual refresh

Introduce background data refresh so the dashboard stays current without
restarting. This touches four layers:

Config (config.go):
- New TUIConfig struct with AutoRefresh (bool) and RefreshIntervalSec
  (int) fields, defaulting to enabled at 30-second intervals.
- Minimum interval floor of 10 seconds enforced at load time.

App core (app.go):
- RefreshDataMsg type for background refresh completion signaling.
- Auto-refresh state: interval timer, refreshing flag, lastRefresh
  timestamp. Checked on every tick; fires refreshDataCmd when elapsed.
- refreshDataCmd: background goroutine that loads session data via cache
  (with uncached fallback) and posts RefreshDataMsg on completion.
- Manual refresh keybind: 'r' triggers immediate refresh.
- Auto-refresh toggle keybind: 'R' toggles auto-refresh and persists
  the preference to config.toml.
- Help text updated with r/R keybind documentation.

Status bar (statusbar.go):
- Shows spinning refresh indicator during active refresh.
- Shows auto-refresh icon when auto-refresh is enabled.

Settings tab (tab_settings.go):
- Two new editable fields: Auto Refresh (bool) and Refresh Interval
  (seconds with 10s minimum).
- Settings display reads live App state to stay consistent with the
  R toggle keybind (avoids stale config-file reads).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-23 09:36:38 -05:00
parent 5b9edc7702
commit 93e343f657
4 changed files with 153 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
"strings"
"time"
"cburn/internal/cli"
"cburn/internal/config"
@@ -21,6 +22,8 @@ const (
settingsFieldTheme
settingsFieldDays
settingsFieldBudget
settingsFieldAutoRefresh
settingsFieldRefreshInterval
settingsFieldCount // sentinel
)
@@ -80,6 +83,19 @@ func (a App) settingsStartEdit() (tea.Model, tea.Cmd) {
ti.SetValue(fmt.Sprintf("%.0f", *cfg.Budget.MonthlyUSD))
}
ti.EchoMode = textinput.EchoNormal
case settingsFieldAutoRefresh:
ti.Placeholder = "true or false"
ti.SetValue(strconv.FormatBool(a.autoRefresh))
ti.EchoMode = textinput.EchoNormal
case settingsFieldRefreshInterval:
ti.Placeholder = "30 (seconds, minimum 10)"
// Use effective value from App state to match display
intervalSec := int(a.refreshInterval.Seconds())
if intervalSec < 10 {
intervalSec = 30
}
ti.SetValue(strconv.Itoa(intervalSec))
ti.EchoMode = textinput.EchoNormal
}
ti.Focus()
@@ -144,6 +160,15 @@ func (a *App) settingsSave() {
cfg.Budget.MonthlyUSD = &b
}
}
case settingsFieldAutoRefresh:
cfg.TUI.AutoRefresh = val == "true" || val == "1" || val == "yes"
a.autoRefresh = cfg.TUI.AutoRefresh
case settingsFieldRefreshInterval:
var interval int
if _, err := fmt.Sscanf(val, "%d", &interval); err == nil && interval >= 10 {
cfg.TUI.RefreshIntervalSec = interval
a.refreshInterval = time.Duration(interval) * time.Second
}
}
a.settings.saveErr = config.Save(cfg)
@@ -184,6 +209,13 @@ func (a App) renderSettingsTab(cw int) string {
}
}
// Use live App state for TUI-specific settings (auto-refresh, interval)
// to ensure display matches actual behavior after R toggle
refreshIntervalSec := int(a.refreshInterval.Seconds())
if refreshIntervalSec < 10 {
refreshIntervalSec = 30 // match the effective default
}
fields := []field{
{"Admin API Key", apiKeyDisplay},
{"Session Key", sessionKeyDisplay},
@@ -195,6 +227,8 @@ func (a App) renderSettingsTab(cw int) string {
}
return "(not set)"
}()},
{"Auto Refresh", strconv.FormatBool(a.autoRefresh)},
{"Refresh Interval", fmt.Sprintf("%ds", refreshIntervalSec)},
}
var formBody strings.Builder