feat(tui): panel focus with click, scroll, and keyboard

Click any panel (Monitors, Logs, Detail) to focus it — accent border
follows focus. Mouse wheel scrolls the focused panel.

Keyboard: l toggles log panel focus. Arrow keys scroll logs when
focused, navigate monitors when not. Esc returns focus to monitors.

Log sidebar now supports scroll offset — tracks position across
renders without a viewport. Mouse wheel scrolls 3 lines, keyboard
scrolls 1.
This commit is contained in:
2026-06-20 19:44:35 -04:00
parent e5ac4a1fec
commit 7109b6fa1c
4 changed files with 110 additions and 18 deletions
+38 -6
View File
@@ -60,7 +60,7 @@ func (m Model) viewLogsSidebar(width, maxLines int) string {
sidebarStyle := lipgloss.NewStyle().Width(width).MaxWidth(width)
var lines []string
var all []string
for _, line := range logs {
if strings.TrimSpace(line) == "" {
continue
@@ -68,11 +68,43 @@ func (m Model) viewLogsSidebar(width, maxLines int) string {
if m.logFilterImportant && !isImportantLog(classifyLog(line)) {
continue
}
lines = append(lines, m.renderCompactLogLine(line, width))
if maxLines > 0 && len(lines) >= maxLines {
break
}
all = append(all, m.renderCompactLogLine(line, width))
}
return sidebarStyle.Render(strings.Join(lines, "\n"))
start := m.logScrollOffset
if start > len(all) {
start = len(all)
}
end := start + maxLines
if end > len(all) {
end = len(all)
}
visible := all[start:end]
return sidebarStyle.Render(strings.Join(visible, "\n"))
}
func (m *Model) scrollLogs(delta int) {
logs := m.engine.GetLogs()
total := 0
for _, line := range logs {
if strings.TrimSpace(line) == "" {
continue
}
if m.logFilterImportant && !isImportantLog(classifyLog(line)) {
continue
}
total++
}
m.logScrollOffset += delta
if m.logScrollOffset < 0 {
m.logScrollOffset = 0
}
if m.logScrollOffset > total-1 {
m.logScrollOffset = total - 1
}
if m.logScrollOffset < 0 {
m.logScrollOffset = 0
}
}