fix(tui-go): event-stream badge tracks session status, not just the socket

The EVENTS panel showed "● live" off m.connected alone, so a completed/failed/
paused session kept reading "live" as long as the websocket was up — stale, since
a terminal session emits no more events. Derive the badge from the selected
session's Status (the same vocabulary the status bar uses): ✓ done, ✗ failed,
‖ paused, ● live while active, ○ idle when disconnected. event_badge_test.go
covers all five.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 09:52:37 +00:00
parent a547bc4342
commit 85121cec9d
2 changed files with 58 additions and 6 deletions
@@ -0,0 +1,33 @@
package app
import (
"strings"
"testing"
)
// The event-stream badge must track the selected session's lifecycle, not just the socket:
// a terminal/paused session emits no more events, so it must not keep reading "● live".
func TestEventStreamBadgeReflectsSessionStatus(t *testing.T) {
cases := []struct{ status, want string }{
{"ACTIVE", "● live"},
{"COMPLETED", "✓ done"},
{"FAILED", "✗ failed"},
{"PAUSED awaiting approval", "‖ paused"},
}
for _, c := range cases {
m := inSessionModel(120, 40)
m.connected = true
m.sessions[0].Status = c.status
if got := m.eventStreamBadge(); !strings.Contains(got, c.want) {
t.Errorf("status %q: badge = %q, want substring %q", c.status, got, c.want)
}
}
// Disconnected always reads idle, regardless of the session's status.
m := inSessionModel(120, 40)
m.connected = false
m.sessions[0].Status = "ACTIVE"
if got := m.eventStreamBadge(); !strings.Contains(got, "○ idle") {
t.Errorf("disconnected: badge = %q, want idle", got)
}
}
+25 -6
View File
@@ -590,12 +590,7 @@ func (m Model) buildTranscriptRows(w int) ([]string, []int) {
func (m Model) eventRows(w, h int) []string {
t := m.theme
header := lipgloss.NewStyle().Foreground(t.P.Accent).Background(t.P.Bg).Bold(true).Render("event stream") +
t.span(" ", t.P.Bg)
if m.connected {
header += lipgloss.NewStyle().Foreground(t.P.OK).Background(t.P.Bg).Render("● live")
} else {
header += t.span("○ idle", t.P.Faint)
}
t.span(" ", t.P.Bg) + m.eventStreamBadge()
s := m.session(m.selectedID)
if s == nil || len(s.Events) == 0 {
@@ -617,6 +612,30 @@ func (m Model) eventRows(w, h int) []string {
return append([]string{header, ""}, evRows...)
}
// eventStreamBadge reflects whether the *selected session's* stream is still live, not
// just whether the socket is connected: a completed / failed / paused session emits no
// more events, so the old connection-only "● live" went stale the moment a session ended.
func (m Model) eventStreamBadge() string {
t := m.theme
if !m.connected {
return t.span("○ idle", t.P.Faint)
}
if s := m.session(m.selectedID); s != nil {
badge := func(fg lipgloss.Color, text string) string {
return lipgloss.NewStyle().Foreground(fg).Background(t.P.Bg).Render(text)
}
switch u := strings.ToUpper(s.Status); {
case strings.Contains(u, "FAIL"):
return badge(t.P.Bad, "✗ failed")
case strings.Contains(u, "COMPLETE"):
return badge(t.P.OK, "✓ done")
case strings.Contains(u, "PAUSE"):
return badge(t.P.Warn, "‖ paused")
}
}
return lipgloss.NewStyle().Foreground(t.P.OK).Background(t.P.Bg).Render("● live")
}
// --- width helpers ---
// shortPath abbreviates the user's home dir to ~ for a compact cwd in the status bar.