fix(tui): track selection by site ID + q means back everywhere
CI / test (pull_request) Successful in 2m5s
CI / lint (pull_request) Successful in 1m26s
CI / vulncheck (pull_request) Successful in 1m2s

Cursor tracked by site ID instead of positional index. When the
list re-sorts every tick (sites change status), the selection stays
on the same monitor instead of silently jumping to whatever now
occupies that index position.

q now means "back" in detail, history, SLA, and alert-detail views
— consistent with muscle memory from navigating deeper views.
Only the dashboard q quits the app. ctrl+c always quits from
anywhere.
This commit was merged in pull request #113.
This commit is contained in:
2026-06-11 19:10:22 -04:00
parent f7da69f25f
commit fa56f47f96
5 changed files with 23 additions and 6 deletions
+15
View File
@@ -105,9 +105,24 @@ func (m *Model) refreshLive() {
} }
m.sites = ordered m.sites = ordered
m.logViewport.SetContent(strings.Join(m.engine.GetLogs(), "\n")) m.logViewport.SetContent(strings.Join(m.engine.GetLogs(), "\n"))
if m.currentTab == 0 && m.selectedID != 0 {
for i, s := range m.sites {
if s.ID == m.selectedID {
m.cursor = i
break
}
}
}
m.clampCursor() m.clampCursor()
} }
func (m *Model) syncSelectedID() {
if m.currentTab == 0 && m.cursor < len(m.sites) {
m.selectedID = m.sites[m.cursor].ID
}
}
// clampCursor keeps the cursor and scroll offset within the current tab's list. // clampCursor keeps the cursor and scroll offset within the current tab's list.
func (m *Model) clampCursor() { func (m *Model) clampCursor() {
listLen := m.currentListLen() listLen := m.currentListLen()
+1 -1
View File
@@ -271,7 +271,7 @@ func (m Model) viewAlertDetailPanel() string {
} }
b.WriteString(m.divider() + "\n") b.WriteString(m.divider() + "\n")
b.WriteString(m.st.subtleStyle.Render(" [i/Esc] Back [e] Edit [t] Test [q] Quit")) b.WriteString(m.st.subtleStyle.Render(" [q/Esc] Back [e] Edit [t] Test"))
return lipgloss.NewStyle().Padding(1, 2).Render(b.String()) return lipgloss.NewStyle().Padding(1, 2).Render(b.String())
} }
+1
View File
@@ -103,6 +103,7 @@ type Model struct {
state sessionState state sessionState
currentTab int currentTab int
cursor int cursor int
selectedID int
tableOffset int tableOffset int
maxTableRows int maxTableRows int
termWidth int termWidth int
+5 -4
View File
@@ -286,6 +286,7 @@ func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
} }
} }
} }
m.syncSelectedID()
return m, nil return m, nil
} }
@@ -379,7 +380,7 @@ func (m *Model) handleDetailKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, m.openSLAView(m.sites[m.cursor]) return m, m.openSLAView(m.sites[m.cursor])
} }
case "q": case "q":
return m, tea.Quit m.state = stateDashboard
} }
return m, nil return m, nil
} }
@@ -499,10 +500,8 @@ func (m *Model) handleHistoryKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
func (m *Model) handleAlertDetailKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { func (m *Model) handleAlertDetailKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.String() { switch msg.String() {
case "i", "esc": case "q", "i", "esc":
m.state = stateDashboard m.state = stateDashboard
case "q":
return m, tea.Quit
} }
return m, nil return m, nil
} }
@@ -537,6 +536,7 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if m.cursor < m.tableOffset { if m.cursor < m.tableOffset {
m.tableOffset = m.cursor m.tableOffset = m.cursor
} }
m.syncSelectedID()
} }
case "down", "j": case "down", "j":
if m.state == stateLogs { if m.state == stateLogs {
@@ -548,6 +548,7 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
if m.cursor >= m.tableOffset+m.maxTableRows { if m.cursor >= m.tableOffset+m.maxTableRows {
m.tableOffset++ m.tableOffset++
} }
m.syncSelectedID()
} }
} }
case "n": case "n":
+1 -1
View File
@@ -254,7 +254,7 @@ func (m Model) viewDetailPanel() string {
b.WriteString("\n") b.WriteString("\n")
b.WriteString(m.divider() + "\n") b.WriteString(m.divider() + "\n")
b.WriteString(m.st.subtleStyle.Render(" [i/Esc] Back [e] Edit [h] History [s] SLA [click] Inspect [q] Quit")) b.WriteString(m.st.subtleStyle.Render(" [q/Esc] Back [e] Edit [h] History [s] SLA [click] Inspect"))
return lipgloss.NewStyle().Padding(1, 2).Render(b.String()) return lipgloss.NewStyle().Padding(1, 2).Render(b.String())
} }