diff --git a/CHANGELOG.md b/CHANGELOG.md index 711034c..0dc40a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). to remove a shared project scratchpad. ### Changed +- The tab bar now shows each visible agent tab's own summary instead + of only rendering the focused tab's summary. - Grid-mode `get_process_output` now returns whitespace-normalized text to avoid sending padded terminal rows and repeated blank lines over MCP. diff --git a/TODO.md b/TODO.md index b5dae71..e69de29 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +0,0 @@ -- [ ] The per-tab agent summary text should display below the tab always, not just when the tab is focused. diff --git a/internal/app/app.go b/internal/app/app.go index fcdaf28..a4a9339 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -514,7 +514,14 @@ func (st *uiState) dbgf(format string, args ...any) { } func (st *uiState) activeSummaryText(width int) string { - text := st.activeSummaryRaw() + st.mu.Lock() + active := st.activeAgentID + st.mu.Unlock() + return st.summaryTextFor(active, width) +} + +func (st *uiState) summaryTextFor(childID string, width int) string { + text := st.summaryRawFor(childID) if text == "" || width <= 0 { return "" } @@ -525,7 +532,14 @@ func (st *uiState) activeSummaryText(width int) string { } func (st *uiState) activeSummaryRaw() string { - if st.summaries == nil { + st.mu.Lock() + active := st.activeAgentID + st.mu.Unlock() + return st.summaryRawFor(active) +} + +func (st *uiState) summaryRawFor(childID string) string { + if st.summaries == nil || childID == "" { return "" } st.settingsMu.Lock() @@ -534,13 +548,7 @@ func (st *uiState) activeSummaryRaw() string { if !enabled { return "" } - st.mu.Lock() - active := st.activeAgentID - st.mu.Unlock() - if active == "" { - return "" - } - sum := st.summaries.Summary(active) + sum := st.summaries.Summary(childID) text := strings.TrimSpace(sum.Text) if text == "" { return "" diff --git a/internal/app/summarizer_test.go b/internal/app/summarizer_test.go index a38fc7f..bbaab62 100644 --- a/internal/app/summarizer_test.go +++ b/internal/app/summarizer_test.go @@ -52,6 +52,41 @@ func TestWrapSidebarSummaryKeepsWordBoundaries(t *testing.T) { } } +func TestSummaryTextForSelectsChildAndClips(t *testing.T) { + sess := NewSession(t.TempDir(), "test") + cfg := defaultSettings() + st := &uiState{ + sess: sess, + settings: cfg, + summaries: newSummaryManager(sess, t.TempDir(), preset.Set{}, func() autoSummarySettings { + return cfg.AutoSummary.clone() + }, nil, nil), + } + st.summaries.mu.Lock() + st.summaries.entries["a1"] = &summaryEntry{state: summaryState{Text: " alpha summary "}} + st.summaries.entries["a2"] = &summaryEntry{state: summaryState{Text: "beta summary"}} + st.summaries.entries["empty"] = &summaryEntry{state: summaryState{Text: " "}} + st.summaries.entries["long"] = &summaryEntry{state: summaryState{Text: "abcdefghijklmnopqrstuvwxyz"}} + st.summaries.mu.Unlock() + + if got := st.summaryTextFor("a2", 20); got != "beta summary" { + t.Fatalf("summaryTextFor(a2) = %q, want beta summary", got) + } + if got := st.summaryTextFor("empty", 20); got != "" { + t.Fatalf("summaryTextFor(empty) = %q, want empty", got) + } + if got := st.summaryTextFor("long", 8); got != "abcdefg…" { + t.Fatalf("summaryTextFor(long) = %q, want abcdefg…", got) + } + + st.settingsMu.Lock() + st.settings.AutoSummary.Enabled = false + st.settingsMu.Unlock() + if got := st.summaryTextFor("a1", 20); got != "" { + t.Fatalf("summaryTextFor disabled = %q, want empty", got) + } +} + func TestSummaryManagerArmsOnlyTrackedTopLevelAgents(t *testing.T) { sess := NewSession(t.TempDir(), "test") c := newChildEntry("a1", "agent", KindAgent, []string{"fake"}, nil, "", "", "") diff --git a/internal/app/tabbar.go b/internal/app/tabbar.go index 55341f4..6672187 100644 --- a/internal/app/tabbar.go +++ b/internal/app/tabbar.go @@ -59,6 +59,7 @@ func (st *uiState) drawTabBar() { newHintW := utf8.RuneCountInString(newHint) + 2 // " + new " framing type tabRect struct { + childID string startCol int width int label string @@ -66,8 +67,6 @@ func (st *uiState) drawTabBar() { glyphStyle string active bool } - activeTab := -1 - // Reserve space at the right edge for "+ new". If there are too // many tabs to fit even at minTabWidth, drop tabs from the right // until they do. The current focus stays visible. @@ -139,6 +138,7 @@ func (st *uiState) drawTabBar() { labelW = utf8.RuneCountInString(label) } tabs = append(tabs, tabRect{ + childID: c.ID, startCol: col, width: w, label: label, @@ -146,9 +146,6 @@ func (st *uiState) drawTabBar() { glyphStyle: glyphStyle, active: active, }) - if tabs[len(tabs)-1].active { - activeTab = len(tabs) - 1 - } col += w } } @@ -224,10 +221,9 @@ func (st *uiState) drawTabBar() { hintCol, styleBorder, strings.Repeat("─", newHintW), styleReset) } - if activeTab >= 0 { - tab := tabs[activeTab] + for _, tab := range tabs { summaryWidth := tab.width - 2 - if summary := st.activeSummaryText(summaryWidth); summary != "" { + if summary := st.summaryTextFor(tab.childID, summaryWidth); summary != "" { fmt.Fprintf(&b, "\x1b[2;%dH %s%s%s", tab.startCol, styleDim, summary, styleReset) } }