fix(code): keep footer branch visible and ellipsized instead of hiding when narrow (#4506)

The status-bar footer now keeps the git branch visible at any terminal
width, truncating it with a trailing ellipsis (`…`) instead of
hard-clipping it or hiding it outright.

---

Previously, in `libs/code`:

- The branch rendered in an auto-width widget that overflowed its
collapsible container and was hard-clipped with no visual cue.
- `on_resize` hid the branch entirely below 100 cols (70 with
`HIDE_CWD`), so on a narrow-ish terminal the branch name simply
disappeared.

Now:

- `.status-branch` uses a flexible width with `text-overflow: ellipsis`,
so a too-long branch truncates with a trailing `…` while short names
still render in full.
- The width-based hide is removed: the branch stays visible and
ellipsizes down as the terminal narrows. The `HIDE_GIT_BRANCH` env
override still hides it entirely, and the cwd still hides below 70 cols
to reclaim space (model > cwd priority is unchanged).

Made by [Open
SWE](https://openswe.vercel.app/agents/c0006e5f-a4dc-52aa-2fbf-f7979af72ffb)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-06 01:59:11 -04:00
committed by GitHub
parent 88d167f9ce
commit ccf30c342e
2 changed files with 250 additions and 33 deletions
+81 -21
View File
@@ -130,6 +130,54 @@ class ModelLabel(Widget):
return Content("\u2026")
class BranchLabel(Widget):
"""A label that displays the git branch with glyph-aware truncation.
Unlike CSS `text-overflow: ellipsis` (which always uses the Unicode
ellipsis character), this widget truncates manually in :meth:`render` using
:func:`get_glyphs` so ASCII mode (`DEEPAGENTS_CODE_UI_CHARSET_MODE=ascii`)
gets `"..."` instead of `""`.
"""
branch: reactive[str] = reactive("", layout=True)
def get_content_width(self, container: Size, viewport: Size) -> int: # noqa: ARG002
"""Return the intrinsic width so the widget participates in flex layout.
Args:
container: Size of the container.
viewport: Size of the viewport.
Returns:
Character length of the full branch string (icon + space + name),
or `0` when the branch is empty.
"""
if not self.branch:
return 0
icon = get_glyphs().git_branch
return len(icon) + 1 + len(self.branch)
def render(self) -> RenderResult:
"""Render the branch label, truncating with the configured glyph.
Returns:
Branch text (icon + name) truncated from the right with
:func:`get_glyphs`'s ellipsis when it overflows the available
width, or an empty string when no branch is set.
"""
width = self.content_size.width
if not self.branch or width <= 0:
return ""
icon = get_glyphs().git_branch
full = f"{icon} {self.branch}"
if len(full) <= width:
return full
ellipsis = get_glyphs().ellipsis
if width <= len(ellipsis):
return full[:width]
return full[: width - len(ellipsis)] + ellipsis
class StatusBar(Horizontal):
"""Status bar showing mode, auto-approve, cwd, git branch, tokens, and model."""
@@ -202,12 +250,16 @@ class StatusBar(Horizontal):
width: auto;
text-align: right;
color: $text-muted;
/* Right padding only, so the cwd/branch gap collapses with the cwd
when it hides (a left pad on the branch would ghost in its place). */
padding: 0 1 0 0;
}
StatusBar .status-branch {
width: auto;
color: $text-muted;
padding: 0 1;
width: 1fr;
min-width: 0;
overflow-x: hidden;
text-wrap: nowrap;
}
StatusBar .status-left-collapsible {
@@ -236,6 +288,13 @@ class StatusBar(Horizontal):
color: $text-muted;
text-align: right;
}
StatusBar BranchLabel {
color: $text-muted;
/* No left pad: the separating gap is owned by the cwd's right pad (or
the message's) so nothing lingers where the cwd was once it hides. */
padding: 0 1 0 0;
}
"""
"""Mode badges and auto-approve pills use distinct colors for at-a-glance status."""
@@ -282,31 +341,22 @@ class StatusBar(Horizontal):
yield Static("", classes="status-connection", id="connection-indicator")
yield Static("", classes="status-message", id="status-message")
yield Static("", classes="status-cwd", id="cwd-display")
yield Static("", classes="status-branch", id="branch-display")
yield BranchLabel(classes="status-branch", id="branch-display")
yield Static("", classes="status-rubric", id="rubric-display")
yield Static("", classes="status-tokens", id="tokens-display")
yield ModelLabel(id="model-display")
_BRANCH_WIDTH_THRESHOLD = 100
"""Hide git branch display below this terminal width."""
_CWD_WIDTH_THRESHOLD = 70
"""Hide cwd display below this terminal width."""
def on_resize(self, event: events.Resize) -> None:
"""Manage visibility of status items based on terminal width.
"""Hide the cwd on very narrow terminals.
Priority (highest first): model, cwd, git branch.
The git branch stays visible at any width (unless disabled via
`HIDE_GIT_BRANCH`) and ellipsizes to fit; only the cwd is dropped
outright to reclaim space when the terminal gets narrow.
"""
width = event.size.width
branch_threshold = (
self._CWD_WIDTH_THRESHOLD
if self._hide_cwd
else self._BRANCH_WIDTH_THRESHOLD
)
with suppress(NoMatches):
self.query_one("#branch-display", Static).display = (
not self._hide_git_branch and width >= branch_threshold
)
with suppress(NoMatches):
self.query_one("#cwd-display", Static).display = (
not self._hide_cwd and width >= self._CWD_WIDTH_THRESHOLD
@@ -326,7 +376,7 @@ class StatusBar(Horizontal):
self.query_one("#cwd-display", Static).display = False
if self._hide_git_branch:
with suppress(NoMatches):
self.query_one("#branch-display", Static).display = False
self.query_one("#branch-display", BranchLabel).display = False
# Set initial model display
label = self.query_one("#model-display", ModelLabel)
label.provider = settings.model_provider or ""
@@ -336,6 +386,11 @@ class StatusBar(Horizontal):
# Reactives are `init=False`, so the connection watcher never fires on
# mount; render once to hide the empty indicator (and its padding).
self._render_connection()
# Same reasoning for the message and token slots: both start empty, so
# hide them on mount so their padding doesn't reserve a blank gap.
self.watch_status_message(self.status_message)
with suppress(NoMatches):
self.query_one("#tokens-display", Static).display = False
def watch_mode(self, mode: str) -> None:
"""Update mode indicator when mode changes."""
@@ -384,11 +439,10 @@ class StatusBar(Horizontal):
def watch_branch(self, new_value: str) -> None:
"""Update branch display when it changes."""
try:
display = self.query_one("#branch-display", Static)
display = self.query_one("#branch-display", BranchLabel)
except NoMatches:
return
icon = get_glyphs().git_branch
display.update(f"{icon} {new_value}" if new_value else "")
display.branch = new_value
def watch_status_message(self, new_value: str) -> None:
"""Update status message display."""
@@ -402,6 +456,9 @@ class StatusBar(Horizontal):
return
msg_widget.remove_class("thinking")
# Hide when empty so the widget's padding doesn't reserve a blank gap
# in the footer (mirrors the connection indicator).
msg_widget.display = bool(new_value)
if new_value:
msg_widget.update(new_value)
if "thinking" in new_value.lower() or "executing" in new_value.lower():
@@ -491,6 +548,7 @@ class StatusBar(Horizontal):
except NoMatches:
return
widget.remove_class("thinking")
widget.display = True
frame = self._spinner.current_frame()
widget.update(Content.assemble(frame, " ", Content(self._busy_message)))
@@ -610,6 +668,8 @@ class StatusBar(Horizontal):
except NoMatches:
return
# Hide when empty so the widget's padding doesn't reserve a blank gap.
display.display = count > 0
if count > 0:
suffix = "+" if approximate else ""
# Format with K suffix for thousands
+169 -12
View File
@@ -9,7 +9,7 @@ from textual.geometry import Size
from textual.widgets import Static
from deepagents_code._env_vars import HIDE_CWD, HIDE_GIT_BRANCH
from deepagents_code.widgets.status import ModelLabel, StatusBar
from deepagents_code.widgets.status import BranchLabel, ModelLabel, StatusBar
class StatusBarApp(App):
@@ -106,6 +106,99 @@ class TestBranchDisplay:
display = pilot.app.query_one("#branch-display")
assert display.render() == ""
@staticmethod
def _visible_branch_text(display: BranchLabel) -> str:
"""Return the branch text as actually rendered to the terminal line."""
from rich.segment import Segment
return "".join(
seg.text for seg in display.render_line(0) if isinstance(seg, Segment)
)
async def test_long_branch_name_truncates_with_ellipsis(self) -> None:
"""A branch too long for the footer should render a trailing ellipsis.
The branch widget's width is pinned directly (rather than relying on
whole-status-bar layout arithmetic) so truncation is deterministic and
independent of the other status items' sizes.
"""
long_branch = "feature/some-really-long-descriptive-branch-name-here"
async with StatusBarApp().run_test(size=(110, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = long_branch
display = pilot.app.query_one("#branch-display", BranchLabel)
# Force a box far narrower than the branch text so overflow applies.
display.styles.width = 20
await pilot.pause()
visible = self._visible_branch_text(display)
# A *trailing* ellipsis (glyph-aware truncation), not a leading
# one, with the head of the name preserved.
assert visible.rstrip().endswith("\u2026")
assert "feature/" in visible
async def test_long_branch_truncates_with_ellipsis_when_cwd_hidden(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Truncation still applies in the cwd-hidden layout.
With `HIDE_CWD` set, the branch is shown at a lower width threshold
and fills the collapsible region alone; a too-long name must still
ellipsize rather than hard-clip.
"""
monkeypatch.setenv(HIDE_CWD, "1")
long_branch = "feature/some-really-long-descriptive-branch-name-here"
async with StatusBarApp().run_test(size=(90, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = long_branch
display = pilot.app.query_one("#branch-display", BranchLabel)
display.styles.width = 20
await pilot.pause()
assert display.display is True
visible = self._visible_branch_text(display)
assert visible.rstrip().endswith("\u2026")
assert "feature/" in visible
async def test_short_branch_name_not_truncated(self) -> None:
"""A branch that fits should render in full with no ellipsis."""
async with StatusBarApp().run_test(size=(150, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
await pilot.pause()
display = pilot.app.query_one("#branch-display", BranchLabel)
visible = self._visible_branch_text(display)
assert "\u2026" not in visible
assert "main" in visible
async def test_long_branch_truncates_with_ascii_ellipsis(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""In ASCII charset mode, truncation uses `"..."` not `""`.
CSS `text-overflow: ellipsis` always emits the Unicode ellipsis
character; `BranchLabel` truncates manually via `get_glyphs` so
the configured glyph (ASCII `"..."` in ascii mode) is used instead.
"""
from deepagents_code.config import reset_glyphs_cache
monkeypatch.setenv("UI_CHARSET_MODE", "ascii")
reset_glyphs_cache()
long_branch = "feature/some-really-long-descriptive-branch-name-here"
try:
async with StatusBarApp().run_test(size=(110, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = long_branch
display = pilot.app.query_one("#branch-display", BranchLabel)
display.styles.width = 20
await pilot.pause()
visible = self._visible_branch_text(display)
# ASCII ellipsis is three dots, not the Unicode character.
assert visible.rstrip().endswith("...")
assert "\u2026" not in visible
assert "feature/" in visible
finally:
monkeypatch.delenv("UI_CHARSET_MODE", raising=False)
reset_glyphs_cache()
async def test_branch_display_contains_git_icon(self) -> None:
"""Branch display should include the git branch glyph prefix."""
async with StatusBarApp().run_test() as pilot:
@@ -120,19 +213,28 @@ class TestBranchDisplay:
class TestResizePriority:
"""Branch hides before cwd, cwd hides before model."""
"""The cwd hides on narrow terminals; the branch truncates but never hides."""
async def test_branch_hidden_on_narrow_terminal(self) -> None:
"""Branch display should be hidden when terminal width < 100."""
async def test_branch_stays_visible_on_narrow_terminal(self) -> None:
"""The branch truncates rather than hiding on a narrow terminal."""
async with StatusBarApp().run_test(size=(80, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
await pilot.pause()
branch = pilot.app.query_one("#branch-display")
assert branch.display is False
assert branch.display is True
async def test_branch_stays_visible_below_cwd_threshold(self) -> None:
"""Even below the cwd hide threshold the branch stays visible."""
async with StatusBarApp().run_test(size=(50, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
await pilot.pause()
branch = pilot.app.query_one("#branch-display")
assert branch.display is True
async def test_branch_visible_on_wide_terminal(self) -> None:
"""Branch display should be visible when terminal width >= 100."""
"""Branch display should be visible on a wide terminal."""
async with StatusBarApp().run_test(size=(120, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
@@ -146,8 +248,8 @@ class TestResizePriority:
cwd = pilot.app.query_one("#cwd-display")
assert cwd.display is False
async def test_cwd_visible_branch_hidden_at_medium_width(self) -> None:
"""Between 70-99 cols: cwd visible, branch hidden."""
async def test_cwd_and_branch_visible_at_medium_width(self) -> None:
"""Between 70-99 cols: cwd visible and branch visible (truncating)."""
async with StatusBarApp().run_test(size=(85, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
@@ -155,19 +257,22 @@ class TestResizePriority:
cwd = pilot.app.query_one("#cwd-display")
branch = pilot.app.query_one("#branch-display")
assert cwd.display is True
assert branch.display is False
assert branch.display is True
async def test_resize_restores_branch_visibility(self) -> None:
"""Widening terminal should restore branch display."""
async def test_resize_never_hides_branch(self) -> None:
"""Resizing must never toggle the branch off; it only truncates."""
async with StatusBarApp().run_test(size=(80, 24)) as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.branch = "main"
await pilot.pause()
branch = pilot.app.query_one("#branch-display")
assert branch.display is False
assert branch.display is True
await pilot.resize_terminal(120, 24)
await pilot.pause()
assert branch.display is True
await pilot.resize_terminal(50, 24)
await pilot.pause()
assert branch.display is True
async def test_model_visible_at_narrow_width(self) -> None:
"""Model display should remain visible even at very narrow widths."""
@@ -286,6 +391,58 @@ class TestTokenDisplay:
assert "8.0K" in rendered
assert "+" not in rendered
async def test_empty_tokens_hidden_on_mount(self) -> None:
"""An empty token slot is hidden so its padding adds no gap."""
async with StatusBarApp().run_test() as pilot:
display = pilot.app.query_one("#tokens-display")
assert display.display is False
async def test_set_tokens_shows_then_zero_hides(self) -> None:
"""A positive count reveals the token slot; zeroing hides it again."""
async with StatusBarApp().run_test() as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.set_tokens(5000)
await pilot.pause()
display = pilot.app.query_one("#tokens-display")
assert display.display is True
bar.set_tokens(0)
await pilot.pause()
assert display.display is False
class TestStatusMessageVisibility:
"""The status-message slot hides when empty so its padding adds no gap."""
async def test_empty_message_hidden_on_mount(self) -> None:
"""The status-message slot starts empty and is hidden on mount."""
async with StatusBarApp().run_test() as pilot:
msg = pilot.app.query_one("#status-message")
assert msg.display is False
async def test_setting_message_shows_then_clearing_hides(self) -> None:
"""Setting a message reveals the slot; clearing it hides it again."""
async with StatusBarApp().run_test() as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.set_status_message("Thinking")
await pilot.pause()
msg = pilot.app.query_one("#status-message")
assert msg.display is True
bar.set_status_message("")
await pilot.pause()
assert msg.display is False
async def test_busy_shows_slot_and_clearing_hides(self) -> None:
"""A busy indicator reveals the slot; clearing busy (no message) hides it."""
async with StatusBarApp().run_test() as pilot:
bar = pilot.app.query_one("#status-bar", StatusBar)
bar.set_busy("Switching model")
await pilot.pause()
msg = pilot.app.query_one("#status-message")
assert msg.display is True
bar.set_busy("")
await pilot.pause()
assert msg.display is False
class TestModeIndicator:
"""Tests for the input-mode indicator in the status bar."""