detached 9f470d3

fix(tui): omit metadata below indent width

commit
9f470d309c420d890fdb7995a840459ba5f47e06
author
brice · · 2026-08-20 13:25 UTC
parent
778aec9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +30 -4
M packages/agent/modes/resident_subagent_indicator.go +0 -4
@@ -85,10 +85,6 @@ func renderResidentSubagentActivityGroup(theme tui.Theme, color tui.TerminalColo
8585 }
8686 metadataWidthLimit := width - runewidth.StringWidth(metadataIndent)
8787 if metadataWidthLimit <= 0 {
88- metadata = truncateResidentSubagentIndicator(metadata, width)
89- if metadata != "" {
90- lines = append(lines, theme.FGColor(theme.Muted, metadata))
91- }
9288 return lines
9389 }
9490 for _, metadataLine := range tui.WrapANSILine(metadata, metadataWidthLimit) {
@@ -85,10 +85,6 @@ func renderResidentSubagentActivityGroup(theme tui.Theme, color tui.TerminalColo
85 }85 }
86 metadataWidthLimit := width - runewidth.StringWidth(metadataIndent)86 metadataWidthLimit := width - runewidth.StringWidth(metadataIndent)
87 if metadataWidthLimit <= 0 {87 if metadataWidthLimit <= 0 {
88- metadata = truncateResidentSubagentIndicator(metadata, width)
89- if metadata != "" {
90- lines = append(lines, theme.FGColor(theme.Muted, metadata))
91- }
92 return lines88 return lines
93 }89 }
94 for _, metadataLine := range tui.WrapANSILine(metadata, metadataWidthLimit) {90 for _, metadataLine := range tui.WrapANSILine(metadata, metadataWidthLimit) {
M packages/agent/modes/resident_subagent_indicator_test.go +30 -0
@@ -252,6 +252,36 @@ func TestFitResidentSubagentActivityLinesKeepsActivityWhenWrappedMetadataDoesNot
252252 }
253253 }
254254
255+func TestRenderResidentSubagentActivityLinesOmitsMetadataWhenIndentDoesNotFit(t *testing.T) {
256+ now := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
257+ snapshot := subagents.ResidentSnapshot{
258+ ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning,
259+ Usage: provider.Usage{InputTokens: 84_000, OutputTokens: 1_500, CacheReadTokens: 123_000, CacheMeasuredPromptTokens: 207_000, CacheMeasuredReadTokens: 123_000, CostUSD: 0.525},
260+ ContextUsed: 45_152, ContextMax: 272_000, Subscription: true,
261+ }
262+ for _, width := range []int{3, 4} {
263+ t.Run(fmt.Sprintf("width_%d", width), func(t *testing.T) {
264+ lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{snapshot}, width, now)
265+ plain := plainResidentIndicatorLines(lines)
266+ wantActivity := " " + strings.Repeat(".", width-runewidth.StringWidth(" "))
267+ if len(plain) != 1 || plain[0] != wantActivity {
268+ t.Fatalf("activity lines = %#v, want only activity row %#v", plain, []string{wantActivity})
269+ }
270+ if strings.Contains(strings.Join(plain, "\n"), "↑84k") {
271+ t.Fatalf("activity lines include metadata despite unavailable indent: %#v", plain)
272+ }
273+ if gotWidth := runewidth.StringWidth(plain[0]); gotWidth > width {
274+ t.Fatalf("activity line width = %d, want <= %d: %q", gotWidth, width, plain[0])
275+ }
276+
277+ limited := plainResidentIndicatorLines(limitResidentSubagentActivityLines(tui.Dark, lines, 0, 1, width))
278+ if len(limited) != 1 || limited[0] != plain[0] || strings.Contains(limited[0], "more active") {
279+ t.Fatalf("limited activity lines = %#v, want preserved activity row %#v without overflow", limited, plain)
280+ }
281+ })
282+ }
283+}
284+
255285 func TestRenderResidentSubagentActivityLinesAnimatesModelWait(t *testing.T) {
256286 started := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
257287 now := started.Add(2 * time.Second)
@@ -252,6 +252,36 @@ func TestFitResidentSubagentActivityLinesKeepsActivityWhenWrappedMetadataDoesNot
252 }252 }
253 }253 }
254 254
255+func TestRenderResidentSubagentActivityLinesOmitsMetadataWhenIndentDoesNotFit(t *testing.T) {
256+ now := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
257+ snapshot := subagents.ResidentSnapshot{
258+ ID: "running-id", Profile: "reviewer", State: subagents.ResidentRunning,
259+ Usage: provider.Usage{InputTokens: 84_000, OutputTokens: 1_500, CacheReadTokens: 123_000, CacheMeasuredPromptTokens: 207_000, CacheMeasuredReadTokens: 123_000, CostUSD: 0.525},
260+ ContextUsed: 45_152, ContextMax: 272_000, Subscription: true,
261+ }
262+ for _, width := range []int{3, 4} {
263+ t.Run(fmt.Sprintf("width_%d", width), func(t *testing.T) {
264+ lines := renderResidentSubagentActivityLines(tui.Dark, "/", []subagents.ResidentSnapshot{snapshot}, width, now)
265+ plain := plainResidentIndicatorLines(lines)
266+ wantActivity := " " + strings.Repeat(".", width-runewidth.StringWidth(" "))
267+ if len(plain) != 1 || plain[0] != wantActivity {
268+ t.Fatalf("activity lines = %#v, want only activity row %#v", plain, []string{wantActivity})
269+ }
270+ if strings.Contains(strings.Join(plain, "\n"), "↑84k") {
271+ t.Fatalf("activity lines include metadata despite unavailable indent: %#v", plain)
272+ }
273+ if gotWidth := runewidth.StringWidth(plain[0]); gotWidth > width {
274+ t.Fatalf("activity line width = %d, want <= %d: %q", gotWidth, width, plain[0])
275+ }
276+
277+ limited := plainResidentIndicatorLines(limitResidentSubagentActivityLines(tui.Dark, lines, 0, 1, width))
278+ if len(limited) != 1 || limited[0] != plain[0] || strings.Contains(limited[0], "more active") {
279+ t.Fatalf("limited activity lines = %#v, want preserved activity row %#v without overflow", limited, plain)
280+ }
281+ })
282+ }
283+}
284+
255 func TestRenderResidentSubagentActivityLinesAnimatesModelWait(t *testing.T) {285 func TestRenderResidentSubagentActivityLinesAnimatesModelWait(t *testing.T) {
256 started := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)286 started := time.Date(2026, time.August, 13, 12, 0, 0, 0, time.UTC)
257 now := started.Add(2 * time.Second)287 now := started.Add(2 * time.Second)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close