From 85121cec9de86a24ca0fcac2d68f01a5eddb099f Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 22 Jun 2026 09:52:37 +0000 Subject: [PATCH] fix(tui-go): event-stream badge tracks session status, not just the socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/tui-go/internal/app/event_badge_test.go | 33 ++++++++++++++++++++ apps/tui-go/internal/app/view.go | 31 ++++++++++++++---- 2 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 apps/tui-go/internal/app/event_badge_test.go diff --git a/apps/tui-go/internal/app/event_badge_test.go b/apps/tui-go/internal/app/event_badge_test.go new file mode 100644 index 00000000..081f2cd1 --- /dev/null +++ b/apps/tui-go/internal/app/event_badge_test.go @@ -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) + } +} diff --git a/apps/tui-go/internal/app/view.go b/apps/tui-go/internal/app/view.go index 0aae1cd8..3f3d37e5 100644 --- a/apps/tui-go/internal/app/view.go +++ b/apps/tui-go/internal/app/view.go @@ -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.