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:
@@ -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.
|
||||||
|
|||||||
1
TODO.md
1
TODO.md
@@ -1 +0,0 @@
|
|||||||
- [ ] The per-tab agent summary text should display below the tab always, not just when the tab is focused.
|
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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, "", "", "")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user