feat(code): quit shortcut on completed update modal (#4312)

Add a `q` shortcut to quit the app from the update-complete modal.

---

The "Updating Deep Agents Code" modal tells users to "Quit and relaunch"
once the self-update finishes, but only offered Esc (close modal). This
adds a `q` shortcut that exits the app, active only after the update
command completes, and surfaces it in the modal help line when done.

Made by [Open
SWE](https://openswe.vercel.app/agents/f676294d-7c1a-943c-ad84-bf77cb7ed926)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-06-26 17:16:28 -04:00
committed by GitHub
parent 8e8680bd01
commit 5e6eae9f09
2 changed files with 60 additions and 0 deletions
@@ -27,6 +27,7 @@ class UpdateProgressScreen(ModalScreen[None]):
BINDINGS: ClassVar[list[BindingType]] = [
Binding("d", "toggle_details", "Details", show=False),
Binding("c", "copy_log_path", "Copy log path", show=False),
Binding("q", "quit_app", "Quit", show=False),
Binding("escape", "cancel", "Close", show=False),
]
@@ -255,6 +256,11 @@ class UpdateProgressScreen(ModalScreen[None]):
if self._done:
self.dismiss(None)
def action_quit_app(self) -> None:
"""Quit the app once the update command has finished."""
if self._done:
self.app.exit()
def action_copy_log_path(self) -> None:
"""Copy warning action text or the persisted log path."""
copy_text = self._copy_text
@@ -311,6 +317,8 @@ class UpdateProgressScreen(ModalScreen[None]):
parts.insert(1, f"c copy {self._copy_label}")
elif self._details_visible:
parts.insert(1, "c copy log path")
if self._done:
parts.append("q quit")
return f" {glyphs.bullet} ".join(parts)
def _update_spinner(self) -> None:
@@ -2,6 +2,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from textual.app import App
from textual.widgets import Log, Static
@@ -9,6 +11,9 @@ from textual.widgets import Log, Static
from deepagents_code.config import get_glyphs
from deepagents_code.widgets.update_progress import UpdateProgressScreen
if TYPE_CHECKING:
from collections.abc import Callable
async def test_update_progress_screen_shows_tail_when_details_toggle(tmp_path) -> None:
"""The progress modal keeps a bounded tail hidden until details are toggled."""
@@ -221,3 +226,50 @@ async def test_update_progress_screen_warning_without_copy_text_copies_log_path(
await pilot.pause()
assert copied == [str(log_path)]
@pytest.mark.parametrize(
"complete",
[
pytest.param(lambda screen: screen.mark_success(), id="success"),
pytest.param(
lambda screen: screen.mark_failure("uv tool upgrade deepagents-code"),
id="failure",
),
pytest.param(lambda screen: screen.mark_warning("Heads up"), id="warning"),
],
)
async def test_update_progress_screen_quit_waits_until_done(
complete: Callable[[UpdateProgressScreen], None],
tmp_path,
) -> None:
"""Q is ignored while updating and quits the app once the modal is done.
Every terminal state (success, failure, warning) gates quit on the same
`_done` flag, so the shortcut must behave identically in each.
"""
screen = UpdateProgressScreen(
latest="2.0.0",
command="uv tool upgrade deepagents-code",
log_path=tmp_path / "update.log",
)
app = App()
async with app.run_test() as pilot:
app.push_screen(screen)
await pilot.pause()
help_text = screen.query(Static).filter(".up-help").first()
assert "q quit" not in str(help_text.render())
await pilot.press("q")
await pilot.pause()
assert app.is_running
complete(screen)
await pilot.pause()
assert "q quit" in str(help_text.render())
await pilot.press("q")
await pilot.pause()
assert not app.is_running