Show every agent tab's summary, not just the focused one

The tab bar's row-2 summary was painted only for the active tab. Add a
per-child summaryTextFor/summaryRawFor helper (active variants now
delegate to it), carry each tab's childID on its tabRect, and loop over
all visible tabs so each renders its own summary under its column.
Layout is unchanged (still 3 rows); narrow tabs clip as before.

Resolves the per-tab summary TODO item.
This commit is contained in:
2026-05-25 13:06:53 +01:00
parent 178b4437b1
commit d2342f99cf
5 changed files with 58 additions and 18 deletions

View File

@@ -11,6 +11,8 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
to remove a shared project scratchpad. to remove a shared project scratchpad.
### Changed ### 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 - Grid-mode `get_process_output` now returns whitespace-normalized
text to avoid sending padded terminal rows and repeated blank lines text to avoid sending padded terminal rows and repeated blank lines
over MCP. over MCP.

View File

@@ -1 +0,0 @@
- [ ] The per-tab agent summary text should display below the tab always, not just when the tab is focused.

View File

@@ -514,7 +514,14 @@ func (st *uiState) dbgf(format string, args ...any) {
} }
func (st *uiState) activeSummaryText(width int) string { 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 { if text == "" || width <= 0 {
return "" return ""
} }
@@ -525,7 +532,14 @@ func (st *uiState) activeSummaryText(width int) string {
} }
func (st *uiState) activeSummaryRaw() 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 "" return ""
} }
st.settingsMu.Lock() st.settingsMu.Lock()
@@ -534,13 +548,7 @@ func (st *uiState) activeSummaryRaw() string {
if !enabled { if !enabled {
return "" return ""
} }
st.mu.Lock() sum := st.summaries.Summary(childID)
active := st.activeAgentID
st.mu.Unlock()
if active == "" {
return ""
}
sum := st.summaries.Summary(active)
text := strings.TrimSpace(sum.Text) text := strings.TrimSpace(sum.Text)
if text == "" { if text == "" {
return "" return ""

View File

@@ -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) { func TestSummaryManagerArmsOnlyTrackedTopLevelAgents(t *testing.T) {
sess := NewSession(t.TempDir(), "test") sess := NewSession(t.TempDir(), "test")
c := newChildEntry("a1", "agent", KindAgent, []string{"fake"}, nil, "", "", "") c := newChildEntry("a1", "agent", KindAgent, []string{"fake"}, nil, "", "", "")

View File

@@ -59,6 +59,7 @@ func (st *uiState) drawTabBar() {
newHintW := utf8.RuneCountInString(newHint) + 2 // " + new " framing newHintW := utf8.RuneCountInString(newHint) + 2 // " + new " framing
type tabRect struct { type tabRect struct {
childID string
startCol int startCol int
width int width int
label string label string
@@ -66,8 +67,6 @@ func (st *uiState) drawTabBar() {
glyphStyle string glyphStyle string
active bool active bool
} }
activeTab := -1
// Reserve space at the right edge for "+ new". If there are too // Reserve space at the right edge for "+ new". If there are too
// many tabs to fit even at minTabWidth, drop tabs from the right // many tabs to fit even at minTabWidth, drop tabs from the right
// until they do. The current focus stays visible. // until they do. The current focus stays visible.
@@ -139,6 +138,7 @@ func (st *uiState) drawTabBar() {
labelW = utf8.RuneCountInString(label) labelW = utf8.RuneCountInString(label)
} }
tabs = append(tabs, tabRect{ tabs = append(tabs, tabRect{
childID: c.ID,
startCol: col, startCol: col,
width: w, width: w,
label: label, label: label,
@@ -146,9 +146,6 @@ func (st *uiState) drawTabBar() {
glyphStyle: glyphStyle, glyphStyle: glyphStyle,
active: active, active: active,
}) })
if tabs[len(tabs)-1].active {
activeTab = len(tabs) - 1
}
col += w col += w
} }
} }
@@ -224,10 +221,9 @@ func (st *uiState) drawTabBar() {
hintCol, styleBorder, strings.Repeat("─", newHintW), styleReset) hintCol, styleBorder, strings.Repeat("─", newHintW), styleReset)
} }
if activeTab >= 0 { for _, tab := range tabs {
tab := tabs[activeTab]
summaryWidth := tab.width - 2 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) fmt.Fprintf(&b, "\x1b[2;%dH %s%s%s", tab.startCol, styleDim, summary, styleReset)
} }
} }