fix(code): full-width chat messages, hide scrollbar, flush input bg (#4374)

Closes #3548

---

The chat surface had two nits: message blocks didn't line up with the
input box (the chat container had horizontal padding the input border
didn't), and the vertical scrollbar was always on unless the terminal
was in ASCII mode — with no way for a user to hide it on a capable
terminal.

This makes the chat area flush with the input box and adds a
user-toggleable scrollbar so people can choose whether the scrollbar is
visible.

- `#chat` drops its horizontal padding so message blocks span the same
full width as the input box border below.
- The input box and status bar switch from `$surface` to `$background`
so they blend with the app background instead of reading as a separate
band.
- New `/scrollbar` slash command toggles the vertical scrollbar on or
off for the current session and persists the choice to
`[ui].show_scrollbar` (and `DEEPAGENTS_CODE_SHOW_SCROLLBAR` for an
env-var override). It defaults to off, matching the look of other coding
CLIs; wheel and keyboard scrolling still work when the scrollbar is
hidden. ASCII-mode terminals keep the scrollbar hidden regardless of the
preference, since they can't render the glyphs.

## Screenshots
### Before
<img width="861" height="632" alt="Screenshot 2026-06-29 at 5 38 10 PM"
src="https://github.com/user-attachments/assets/4add48dd-f761-4edf-a2a1-aefa9c6e4697"
/>

### After
<img width="862" height="634" alt="Screenshot 2026-06-29 at 5 36 48 PM"
src="https://github.com/user-attachments/assets/d7ecb5b7-87b3-4122-8b28-bc61b248b670"
/>
<img width="857" height="634" alt="Screenshot 2026-06-29 at 5 37 19 PM"
src="https://github.com/user-attachments/assets/2f656c5b-5509-4326-a68a-ad2a3a59fd6a"
/>

---------

Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Johannes du Plessis
2026-06-30 13:47:02 -07:00
committed by GitHub
parent 83898ba608
commit 1f8e8dc942
9 changed files with 522 additions and 13 deletions
+2 -1
View File
@@ -8,7 +8,7 @@ Regenerate this file with `make commands-catalog` after changing command names,
aliases, descriptions, visibility, or hidden-command metadata.
## Public (30)
## Public (31)
| Command | Aliases | Description |
| --- | --- | --- |
@@ -34,6 +34,7 @@ aliases, descriptions, visibility, or hidden-command metadata.
| `/remember` | | Save useful context to memory or skills |
| `/restart` | | Restart the agent server |
| `/rubric` | `/criteria` | Set explicit acceptance criteria for rubric grading |
| `/scrollbar` | | Show or hide the chat scrollbar |
| `/skill-creator` | | Create or refine agent skills |
| `/theme` | | Change color theme |
| `/threads` | | Browse and resume past threads |
+12
View File
@@ -206,6 +206,18 @@ Defaults to enabled; set to a falsy value (`0`, `false`, `no`, `off`, or empty)
to hide replica tracing details from the splash while leaving tracing active.
"""
SHOW_SCROLLBAR = "DEEPAGENTS_CODE_SHOW_SCROLLBAR"
"""Show the vertical scrollbar in the chat area when enabled.
Off by default; use the `/scrollbar` slash command or `[ui].show_scrollbar` in
config.toml to toggle. Parsed by `classify_env_bool` (an unrecognized or empty
value falls through to the config value rather than forcing the default).
When set, this env var takes precedence over the persisted `[ui].show_scrollbar`
config value on launch, so a `/scrollbar` toggle will not appear to "stick"
across restarts while the env var remains set.
"""
THEME = "DEEPAGENTS_CODE_THEME"
"""Force the CLI to launch with this theme name when set."""
+183 -8
View File
@@ -57,12 +57,10 @@ from deepagents_code._session_stats import (
format_token_count,
)
# Only is_ascii_mode is needed before first paint (on_mount scrollbar config).
# All other config imports — settings, create_model, detect_provider, etc. — are
# deferred to local imports at their call sites since they are only accessed
# after user interaction begins.
# All config imports — settings, create_model, detect_provider, is_ascii_mode,
# etc. — are deferred to local imports at their call sites since they are only
# accessed after user interaction begins.
from deepagents_code._version import CHANGELOG_URL, DOCS_URL
from deepagents_code.config import is_ascii_mode
from deepagents_code.formatting import format_message_timestamp
from deepagents_code.iterm_cursor_guide import restore_iterm_cursor_guide
from deepagents_code.notifications import (
@@ -603,6 +601,55 @@ def _load_message_timestamps_visible() -> bool:
return False
def _load_show_scrollbar() -> bool:
"""Load the chat scrollbar visibility preference.
Reads `DEEPAGENTS_CODE_SHOW_SCROLLBAR` env var, falling back to
`[ui].show_scrollbar` from `~/.deepagents/config.toml`, and finally `False`.
Returns:
The resolved preference.
"""
from deepagents_code._env_vars import SHOW_SCROLLBAR, classify_env_bool
raw = os.environ.get(SHOW_SCROLLBAR)
if raw is not None and raw.strip():
env = classify_env_bool(raw)
if env is not None:
return env
import tomllib
from deepagents_code.model_config import DEFAULT_CONFIG_PATH
if not DEFAULT_CONFIG_PATH.exists():
return False
try:
with DEFAULT_CONFIG_PATH.open("rb") as f:
data = tomllib.load(f)
except (tomllib.TOMLDecodeError, PermissionError, OSError) as exc:
logger.warning("Could not read config for scrollbar preference: %s", exc)
return False
ui = data.get("ui", {})
if not isinstance(ui, dict):
logger.warning(
"[ui] should be a table; got %s while loading scrollbar preference",
type(ui).__name__,
)
return False
value = ui.get("show_scrollbar")
if isinstance(value, bool):
return value
if value is not None:
logger.warning(
"[ui].show_scrollbar should be a boolean; got %s",
type(value).__name__,
)
return False
def _replace_malformed_ui(
data: dict[str, object],
) -> tuple[dict[str, object], str | None]:
@@ -946,6 +993,65 @@ def _save_message_timestamps_visible_result(visible: bool) -> _ConfigWriteResult
return _ConfigWriteResult(True, repair_message)
def _save_show_scrollbar_result(visible: bool) -> _ConfigWriteResult:
"""Persist the chat scrollbar visibility preference.
Writes `[ui].show_scrollbar` atomically (temp file +
`Path.replace`). Mirrors `_save_message_timestamps_visible_result`.
Returns:
Write status and a message suitable for a toast when the user needs to
know about a repair or failure.
"""
import contextlib
import tempfile
import tomllib
try:
import tomli_w
from deepagents_code.model_config import DEFAULT_CONFIG_PATH
DEFAULT_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
with _CONFIG_WRITE_LOCK:
if DEFAULT_CONFIG_PATH.exists():
with DEFAULT_CONFIG_PATH.open("rb") as f:
data = tomllib.load(f)
else:
data = {}
ui, repair_message = _replace_malformed_ui(data)
ui["show_scrollbar"] = visible
fd, tmp_path = tempfile.mkstemp(
dir=DEFAULT_CONFIG_PATH.parent,
suffix=".tmp",
)
try:
with os.fdopen(fd, "wb") as f:
tomli_w.dump(data, f)
Path(tmp_path).replace(DEFAULT_CONFIG_PATH)
except BaseException:
with contextlib.suppress(OSError):
Path(tmp_path).unlink()
raise
except (
OSError,
tomllib.TOMLDecodeError,
ImportError,
TypeError,
ValueError,
) as exc:
logger.exception("Could not save scrollbar preference")
return _ConfigWriteResult(
False,
f"Scrollbar toggled for this session but could not be saved "
f"({type(exc).__name__}).",
"error",
)
return _ConfigWriteResult(True, repair_message)
def _extract_model_params_flag(raw_arg: str) -> tuple[str, dict[str, Any] | None]:
"""Extract `--model-params` and its JSON value from a `/model` arg string.
@@ -2143,6 +2249,13 @@ class DeepAgentsApp(App):
Restored from `[ui].show_message_timestamps` and re-persisted on toggle.
"""
self._show_scrollbar = _load_show_scrollbar()
"""Whether the vertical scrollbar is shown in the chat area.
Restored from `DEEPAGENTS_CODE_SHOW_SCROLLBAR` env var or
`[ui].show_scrollbar` and re-persisted on toggle. Off by default.
"""
# Widget refs (populated in compose/on_mount)
self._status_bar: StatusBar | None = None
"""Status bar widget; populated in `on_mount`."""
@@ -2622,8 +2735,7 @@ class DeepAgentsApp(App):
chat = self.query_one("#chat", VerticalScroll)
chat.anchor()
if is_ascii_mode():
chat.styles.scrollbar_size_vertical = 0
self._apply_scrollbar_visibility(chat)
self._status_bar = self.query_one("#status-bar", StatusBar)
self._chat_input = self.query_one("#input-area", ChatInput)
@@ -8749,7 +8861,8 @@ class DeepAgentsApp(App):
"/mcp, /model [--model-params JSON] [--default], "
"/notifications, /reload, /restart, /rubric, "
"/skill:<name>, /remember, "
"/skill-creator, /theme, /timestamps, /tokens, /threads, /trace, "
"/skill-creator, /theme, /scrollbar, /timestamps, /tokens, "
"/threads, /trace, "
"/update, /auto-update, /install, /changelog, /docs, "
"/feedback, /help\n\n"
"Interactive Features:\n"
@@ -8876,6 +8989,15 @@ class DeepAgentsApp(App):
await self._handle_auto_update_toggle()
elif cmd == "/install" or cmd.startswith("/install "):
await self._handle_install_command(command)
elif cmd == "/scrollbar":
await self._toggle_scrollbar()
label = "shown" if self._show_scrollbar else "hidden"
self.notify(
f"Chat scrollbar {label}.",
severity="information",
timeout=5,
markup=False,
)
elif cmd == "/timestamps":
await self._toggle_message_timestamp_footers()
label = "shown" if self._message_timestamps_visible else "hidden"
@@ -10245,6 +10367,59 @@ class DeepAgentsApp(App):
"Failed to persist message timestamp preference",
exc_info=True,
)
self.notify(
"Timestamps toggled for this session but could not be saved.",
severity="error",
timeout=6,
markup=False,
)
def _apply_scrollbar_visibility(self, chat: VerticalScroll | None = None) -> None:
"""Apply the current scrollbar visibility to the chat container.
Hides the scrollbar when the user preference is off or ASCII mode is
active (ASCII terminals can't render the scrollbar glyphs).
"""
from deepagents_code.config import is_ascii_mode
if chat is None:
try:
chat = self.query_one("#chat", VerticalScroll)
except NoMatches:
return
if self._show_scrollbar and not is_ascii_mode():
chat.styles.scrollbar_size_vertical = 1
else:
chat.styles.scrollbar_size_vertical = 0
async def _toggle_scrollbar(self) -> None:
"""Toggle chat scrollbar visibility and persist the preference."""
self._show_scrollbar = not self._show_scrollbar
self._apply_scrollbar_visibility()
try:
status = await asyncio.to_thread(
_save_show_scrollbar_result,
self._show_scrollbar,
)
if status.message is not None:
self.notify(
status.message,
severity=status.severity,
timeout=6,
markup=False,
)
except Exception:
logger.warning(
"Failed to persist scrollbar preference",
exc_info=True,
)
self.notify(
"Scrollbar toggled for this session but could not be saved.",
severity="error",
timeout=6,
markup=False,
)
async def _mount_message(
self,
+5 -2
View File
@@ -39,10 +39,13 @@ Screen {
display: block;
}
/* Chat area - main scrollable messages area */
/* Chat area - main scrollable messages area.
No horizontal padding so message blocks span the same full width as the
input box below (whose border is flush to both edges). The scrollbar is
toggled at runtime via the /scrollbar command (off by default). */
#chat {
height: 1fr;
padding: 1 2 0 2;
padding: 1 0 0 0;
background: $background;
}
@@ -196,6 +196,12 @@ COMMANDS: tuple[SlashCommand, ...] = (
bypass_tier=BypassTier.IMMEDIATE_UI,
hidden_keywords="dark light color appearance",
),
SlashCommand(
name="/scrollbar",
description="Show or hide the chat scrollbar",
bypass_tier=BypassTier.SIDE_EFFECT_FREE,
hidden_keywords="scroll scroller bar vertical",
),
SlashCommand(
name="/timestamps",
description="Show or hide message timestamps",
@@ -795,6 +795,15 @@ _STATIC_OPTIONS: tuple[ConfigOption, ...] = (
kind=OptionKind.BOOL,
env_var=_env_vars.KITTY_KEYBOARD,
),
ConfigOption(
key="display.show_scrollbar",
group="Display",
summary="Show the vertical scrollbar in the chat area (off by default).",
kind=OptionKind.BOOL,
default=False,
env_var=_env_vars.SHOW_SCROLLBAR,
toml_keys=("ui", "show_scrollbar"),
),
ConfigOption(
key="display.hide_cwd",
group="Display",
@@ -1431,7 +1431,7 @@ class ChatInput(Vertical):
min-height: 3;
max-height: 25;
padding: 0;
background: $surface;
background: $background;
border: solid $primary;
}
+1 -1
View File
@@ -111,7 +111,7 @@ class StatusBar(Horizontal):
StatusBar {
height: 1;
dock: bottom;
background: $surface;
background: $background;
}
StatusBar .status-mode {
+303
View File
@@ -7512,6 +7512,309 @@ class TestMessageTimestampsPersistence:
assert data["ui"]["show_message_timestamps"] is True
class TestScrollbarToggle:
"""Tests for the toggleable chat scrollbar."""
def test_load_defaults_false_when_config_missing(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A missing config yields the hidden default."""
from deepagents_code.app import _load_show_scrollbar
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
tmp_path / "config.toml",
)
assert _load_show_scrollbar() is False
def test_load_reads_saved_true(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""An explicit `true` preference is read back."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("[ui]\nshow_scrollbar = true\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
assert _load_show_scrollbar() is True
def test_load_ignores_non_boolean(
self,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""A non-boolean preference is ignored with a warning."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text('[ui]\nshow_scrollbar = "yes"\n')
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
with caplog.at_level("WARNING", logger="deepagents_code.app"):
assert _load_show_scrollbar() is False
assert any("show_scrollbar" in record.getMessage() for record in caplog.records)
def test_env_var_overrides_config(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The env var takes priority over the config.toml value."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("[ui]\nshow_scrollbar = false\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
monkeypatch.setenv("DEEPAGENTS_CODE_SHOW_SCROLLBAR", "1")
assert _load_show_scrollbar() is True
def test_invalid_env_var_falls_back_to_config(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""An unrecognized env var value does not mask the saved preference."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("[ui]\nshow_scrollbar = true\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
monkeypatch.setenv("DEEPAGENTS_CODE_SHOW_SCROLLBAR", "maybe")
assert _load_show_scrollbar() is True
def test_empty_env_var_falls_back_to_config(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""An empty env var value does not mask the saved preference."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("[ui]\nshow_scrollbar = true\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
monkeypatch.setenv("DEEPAGENTS_CODE_SHOW_SCROLLBAR", "")
assert _load_show_scrollbar() is True
def test_save_round_trips(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Saving then loading returns the saved value, both directions."""
from deepagents_code.app import (
_load_show_scrollbar,
_save_show_scrollbar_result,
)
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
tmp_path / "config.toml",
)
assert _save_show_scrollbar_result(True).ok is True
assert _load_show_scrollbar() is True
assert _save_show_scrollbar_result(False).ok is True
assert _load_show_scrollbar() is False
def test_save_preserves_other_ui_keys(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Persisting the toggle leaves unrelated `[ui]` keys intact."""
import tomllib
from deepagents_code.app import _save_show_scrollbar_result
config = tmp_path / "config.toml"
config.write_text('[ui]\ntheme = "langchain"\n')
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
assert _save_show_scrollbar_result(True).ok is True
data = tomllib.loads(config.read_text())
assert data["ui"]["theme"] == "langchain"
assert data["ui"]["show_scrollbar"] is True
async def test_toggle_flips_preference_and_applies(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The `/scrollbar` toggle flips the flag and updates the chat widget."""
from textual.containers import VerticalScroll
from deepagents_code.config import reset_glyphs_cache
# Force Unicode charset so the scrollbar can actually be shown —
# in ASCII mode _apply_scrollbar_visibility keeps it at 0.
monkeypatch.setenv("UI_CHARSET_MODE", "unicode")
reset_glyphs_cache()
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
tmp_path / "config.toml",
)
app = DeepAgentsApp()
assert app._show_scrollbar is False
try:
async with app.run_test() as pilot:
await pilot.pause()
chat = app.query_one("#chat", VerticalScroll)
assert chat.styles.scrollbar_size_vertical == 0
await app._toggle_scrollbar()
await pilot.pause()
assert app._show_scrollbar is True
assert chat.styles.scrollbar_size_vertical == 1
await app._toggle_scrollbar()
await pilot.pause()
assert app._show_scrollbar is False
assert chat.styles.scrollbar_size_vertical == 0
finally:
# The glyph cache is process-global; clear the forced charset so the
# Unicode result does not leak into later tests in this process.
monkeypatch.delenv("UI_CHARSET_MODE", raising=False)
reset_glyphs_cache()
async def test_ascii_mode_keeps_scrollbar_hidden(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""ASCII terminals never show the scrollbar, even when enabled."""
from textual.containers import VerticalScroll
from deepagents_code.config import reset_glyphs_cache
# ASCII terminals can't render the scrollbar glyphs, so the visibility
# helper must keep the width at 0 regardless of the user preference.
monkeypatch.setenv("UI_CHARSET_MODE", "ascii")
reset_glyphs_cache()
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
tmp_path / "config.toml",
)
app = DeepAgentsApp()
try:
async with app.run_test() as pilot:
await pilot.pause()
chat = app.query_one("#chat", VerticalScroll)
app._show_scrollbar = True
app._apply_scrollbar_visibility()
await pilot.pause()
assert chat.styles.scrollbar_size_vertical == 0
finally:
monkeypatch.delenv("UI_CHARSET_MODE", raising=False)
reset_glyphs_cache()
async def test_command_shows_toast_not_app_message(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""`/scrollbar` reports its new state via a toast, not a chat message."""
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
tmp_path / "config.toml",
)
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
with patch.object(app, "notify") as notify_mock:
await app._handle_command("/scrollbar")
await pilot.pause()
notify_mock.assert_called_once()
assert notify_mock.call_args.args[0] == "Chat scrollbar shown."
assert notify_mock.call_args.kwargs.get("severity") == "information"
assert notify_mock.call_args.kwargs.get("markup") is False
assert not app.query(UserMessage)
# Toggling back reports the "hidden" state via the same toast path.
with patch.object(app, "notify") as notify_mock:
await app._handle_command("/scrollbar")
await pilot.pause()
notify_mock.assert_called_once()
assert notify_mock.call_args.args[0] == "Chat scrollbar hidden."
assert notify_mock.call_args.kwargs.get("severity") == "information"
assert notify_mock.call_args.kwargs.get("markup") is False
assert not app.query(UserMessage)
def test_falsy_env_overrides_config(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A falsy env var overrides a `true` config value (precedence)."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("[ui]\nshow_scrollbar = true\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
monkeypatch.setenv("DEEPAGENTS_CODE_SHOW_SCROLLBAR", "0")
assert _load_show_scrollbar() is False
def test_load_handles_malformed_toml(
self,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Unparsable config falls back to the hidden default with a warning."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text("this is = = not valid toml [[[\n")
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
with caplog.at_level("WARNING", logger="deepagents_code.app"):
assert _load_show_scrollbar() is False
assert any("scrollbar" in record.getMessage() for record in caplog.records)
def test_load_ignores_non_table_ui(
self,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""A scalar `[ui]` value is ignored with a warning on load."""
from deepagents_code.app import _load_show_scrollbar
config = tmp_path / "config.toml"
config.write_text('ui = "oops"\n')
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
with caplog.at_level("WARNING", logger="deepagents_code.app"):
assert _load_show_scrollbar() is False
assert any("ui" in record.getMessage() for record in caplog.records)
def test_save_repairs_malformed_ui_table(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Saving over a scalar `[ui]` replaces it and reports the repair."""
import tomllib
from deepagents_code.app import _save_show_scrollbar_result
config = tmp_path / "config.toml"
config.write_text('ui = "oops"\n')
monkeypatch.setattr("deepagents_code.model_config.DEFAULT_CONFIG_PATH", config)
result = _save_show_scrollbar_result(True)
assert result.ok is True
assert result.message is not None
assert "[ui]" in result.message
data = tomllib.loads(config.read_text())
assert data["ui"]["show_scrollbar"] is True
def test_save_failure_reports_error(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""An unwritable target yields an error result instead of raising."""
from deepagents_code.app import _save_show_scrollbar_result
# Point the config at a path whose parent is a regular file, so the
# `mkdir` in the writer raises an OSError that must be converted into a
# structured failure rather than propagating.
blocker = tmp_path / "not-a-dir"
blocker.write_text("")
monkeypatch.setattr(
"deepagents_code.model_config.DEFAULT_CONFIG_PATH",
blocker / "config.toml",
)
result = _save_show_scrollbar_result(True)
assert result.ok is False
assert result.severity == "error"
assert result.message is not None
class TestAppBlurPausesCursorBlink:
"""Test `on_app_blur` pauses cursor blink without changing widget focus."""