fix(code): re-apply theme preference on /reload (#4514)

`/reload` now picks up a theme default saved in another session and
switches to it.

---

`/reload` (in `libs/code`) rebuilt the theme *registry*
(`theme.reload_registry()` + `_register_custom_themes()`) but never
re-evaluated which theme should be *active* — it never re-ran
`_load_theme_preference()` or reassigned `self.theme`. So a per-terminal
default (`[ui.terminal_themes][TERM_PROGRAM]`) or global default saved
by another session was ignored until the app restarted. This re-resolves
the preference after the registry reload and applies it (with
terminal-background sync + CSS refresh), matching startup resolution
order (env → terminal default → `[ui].theme` → default). Re-applying the
on-disk preference is the intended `/reload` semantic, so it
deliberately overrides an unsaved in-session `/theme` choice.

Made by [Open
SWE](https://openswe.vercel.app/agents/7edbf211-076a-afd0-c0a8-76c44c422e00)

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-06 10:20:29 -04:00
committed by GitHub
parent a4431e4339
commit 5d1c3928f7
2 changed files with 111 additions and 0 deletions
+26
View File
@@ -9642,6 +9642,28 @@ class DeepAgentsApp(App):
theme_reload_ok = False
logger.warning("Failed to reload user themes", exc_info=True)
# Re-resolve and apply the theme preference so a per-terminal or
# global default saved by another session is picked up. This
# re-syncs to on-disk config using the same resolution as startup
# (env -> [ui.terminal_themes][TERM_PROGRAM] -> [ui].theme ->
# default), which intentionally overrides an unsaved in-session
# `/theme` choice. Guarded on the registry reload succeeding since
# the target theme must be registered before it can be applied.
theme_switched_to: str | None = None
if theme_reload_ok:
try:
new_theme = _load_theme_preference()
if new_theme != self.theme and new_theme in theme.get_registry():
self.theme = new_theme
self.sync_terminal_background()
self.refresh_css(animate=False)
theme_switched_to = new_theme
except Exception:
logger.warning(
"Failed to re-apply theme preference on reload",
exc_info=True,
)
# Re-discover skills so autocomplete reflects any new/removed
# skills. Run via the same exclusive-group worker used at
# startup so any in-flight startup discovery is cancelled
@@ -9667,6 +9689,10 @@ class DeepAgentsApp(App):
report += "\nModel config caches cleared."
if theme_reload_ok:
report += "\nTheme registry reloaded."
if theme_switched_to is not None:
entry = theme.get_registry().get(theme_switched_to)
label = entry.label if entry is not None else theme_switched_to
report += f"\nSwitched theme to {label}."
else:
report += (
"\nTheme registry reload failed. Check config.toml for errors."
+85
View File
@@ -828,3 +828,88 @@ class TestReloadSkillReport:
# Critical: must not claim every prior skill was removed.
assert "Removed:" not in text
assert "Skills updated" not in text
class TestReloadThemeReapply:
"""`/reload` should re-apply the resolved theme preference.
Guards the cross-session behavior: saving a per-terminal (or global)
default theme in one window should be picked up by an already-running
session's `/reload`, matching startup resolution.
"""
async def _run_reload_theme(
self,
monkeypatch: pytest.MonkeyPatch,
*,
initial_theme: str,
resolved_theme: str,
) -> tuple[str, str]:
"""Drive `/reload` once with a stubbed preference resolver.
Args:
monkeypatch: pytest fixture for restorable patching.
initial_theme: theme active before reload.
resolved_theme: value `_load_theme_preference` returns on reload.
Returns:
The active theme after reload and the mounted `AppMessage` text.
"""
from deepagents_code import app as app_module
from deepagents_code.app import DeepAgentsApp
from deepagents_code.widgets.messages import AppMessage
app = DeepAgentsApp()
async with app.run_test() as pilot:
await pilot.pause()
app.theme = initial_theme
async def _fake_discover() -> bool: # noqa: RUF029 # awaited by handler
return True
monkeypatch.setattr(app, "_discover_skills", _fake_discover)
monkeypatch.setattr(
app_module, "_load_theme_preference", lambda: resolved_theme
)
await app._handle_command("/reload")
await pilot.pause()
text = "\n".join(str(w._content) for w in app.query(AppMessage))
return app.theme, text
async def test_switches_to_new_default(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A newly resolved preference should become the active theme."""
active, text = await self._run_reload_theme(
monkeypatch,
initial_theme="langchain",
resolved_theme="langchain-light",
)
assert active == "langchain-light"
assert "Switched theme to" in text
async def test_no_switch_when_unchanged(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""When the resolved preference matches the active theme, no switch."""
active, text = await self._run_reload_theme(
monkeypatch,
initial_theme="langchain",
resolved_theme="langchain",
)
assert active == "langchain"
assert "Switched theme to" not in text
async def test_unregistered_preference_ignored(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A resolved name that isn't registered must not change the theme."""
active, text = await self._run_reload_theme(
monkeypatch,
initial_theme="langchain",
resolved_theme="not-a-real-theme",
)
assert active == "langchain"
assert "Switched theme to" not in text