Skip to content

Commit 1eabf50

Browse files
committed
Fix sidebar agent click zones mapping all lines to first agent
buildAgentClickZones used lipgloss.Width()==0 to detect blank separator lines between agent blocks, but TabStyle.Width() pads all lines with whitespace, so separator lines had non-zero width. This caused every agent line to be mapped to the first agent, making clicking on any agent other than the first one impossible. Replace the check with isVisuallyBlank() which strips ANSI codes and checks for whitespace-only content. Add regression test. Assisted-By: docker-agent
1 parent 5cdb6dd commit 1eabf50

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package sidebar
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/docker/docker-agent/pkg/runtime"
9+
"github.com/docker/docker-agent/pkg/session"
10+
"github.com/docker/docker-agent/pkg/tui/service"
11+
)
12+
13+
func TestSidebar_HandleClickType_Agent(t *testing.T) {
14+
t.Parallel()
15+
16+
sess := session.New()
17+
sessionState := service.NewSessionState(sess)
18+
sessionState.SetCurrentAgentName("agent1")
19+
sb := New(sessionState)
20+
21+
m := sb.(*model)
22+
m.sessionHasContent = true
23+
m.titleGenerated = true
24+
m.sessionTitle = "Test"
25+
m.currentAgent = "agent1"
26+
m.availableAgents = []runtime.AgentDetails{
27+
{Name: "agent1", Provider: "openai", Model: "gpt-4", Description: "First agent"},
28+
{Name: "agent2", Provider: "anthropic", Model: "claude", Description: "Second agent"},
29+
}
30+
m.width = 40
31+
m.height = 50
32+
33+
// Force a render to populate agentClickZones
34+
_ = sb.View()
35+
36+
paddingLeft := m.layoutCfg.PaddingLeft
37+
38+
// Verify clicking on agent1 lines returns ClickAgent with "agent1"
39+
foundAgent1 := false
40+
foundAgent2 := false
41+
for y := range len(m.cachedLines) {
42+
result, agentName := sb.HandleClickType(paddingLeft+2, y)
43+
if result == ClickAgent {
44+
if agentName == "agent1" {
45+
foundAgent1 = true
46+
}
47+
if agentName == "agent2" {
48+
foundAgent2 = true
49+
}
50+
}
51+
}
52+
assert.True(t, foundAgent1, "should be able to click on agent1")
53+
assert.True(t, foundAgent2, "should be able to click on agent2")
54+
}

pkg/tui/components/sidebar/sidebar.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"charm.land/bubbles/v2/textinput"
1414
tea "charm.land/bubbletea/v2"
1515
"charm.land/lipgloss/v2"
16+
"github.com/charmbracelet/x/ansi"
1617

1718
"github.com/docker/docker-agent/pkg/paths"
1819
"github.com/docker/docker-agent/pkg/runtime"
@@ -1214,6 +1215,13 @@ func (m *model) renderAgentEntry(content *strings.Builder, agent runtime.AgentDe
12141215
content.WriteString(toolcommon.TruncateText("Model: "+agent.Model, maxWidth))
12151216
}
12161217

1218+
// isVisuallyBlank returns true if a rendered line contains no visible content.
1219+
// Lines may contain ANSI escape codes and whitespace padding from lipgloss styles
1220+
// (e.g., TabStyle.Width()), so we strip ANSI sequences and check for whitespace.
1221+
func isVisuallyBlank(line string) bool {
1222+
return strings.TrimSpace(ansi.Strip(line)) == ""
1223+
}
1224+
12171225
// buildAgentClickZones populates agentClickZones by scanning the rendered lines
12181226
// to find which lines belong to which agent. It relies on the structure produced
12191227
// by renderTab + agentInfo: a 2-line tab header, then agent blocks separated by
@@ -1230,7 +1238,7 @@ func (m *model) buildAgentClickZones(agentSectionStart int, lines []string) {
12301238
inBlock := false
12311239

12321240
for i := agentSectionStart + tabHeaderLines; i < len(lines) && agentIdx < len(m.availableAgents); i++ {
1233-
if lipgloss.Width(lines[i]) == 0 {
1241+
if isVisuallyBlank(lines[i]) {
12341242
// Blank line: if we were inside a block, advance to the next agent
12351243
if inBlock {
12361244
agentIdx++

0 commit comments

Comments
 (0)