diff --git a/src/hqt/tui/app.py b/src/hqt/tui/app.py index dd2031d..45e6402 100644 --- a/src/hqt/tui/app.py +++ b/src/hqt/tui/app.py @@ -73,6 +73,7 @@ class HqtApp(App): Binding("h", "focus_projects", "Projects"), Binding("l", "focus_sessions", "Sessions"), Binding("x", "archive_session", "Archive"), + Binding("A", "toggle_archived", "Show Archived"), Binding("d", "delete_session", "Delete Session"), Binding("enter", "attach_session", "Attach", priority=True), Binding("r", "rename_session", "Rename"), @@ -91,6 +92,9 @@ class HqtApp(App): self.register_theme(FRAPPE_THEME) self.theme = "catppuccin-frappe" self._selected_project_id: int | None = None + # When True, the Sessions column shows this project's archived sessions + # instead of its active ones. Persists across project switches. + self._show_archived = False # project_id -> last selected session_id, so switching projects restores # each project's previously highlighted session. self._selected_session_by_project: dict[int, int] = {} @@ -181,7 +185,7 @@ class HqtApp(App): except ServiceError as err: log.warning("Session poll failed: %s", err) return - if self._selected_project_id is not None: + if self._selected_project_id is not None and not self._show_archived: subset = [ i for i in infos if i.session.project_id == self._selected_project_id ] @@ -190,7 +194,9 @@ class HqtApp(App): async def _refresh_sessions(self, restore_selection: bool = False) -> None: if self._selected_project_id is None: return - infos = await self._sessions().list_sessions(self._selected_project_id) + infos = await self._sessions().list_sessions( + self._selected_project_id, archived=self._show_archived + ) sl = self.query_one(SessionList) if restore_selection: # Project switch: restore this project's remembered session, or let the @@ -448,6 +454,11 @@ class HqtApp(App): self._run_service_worker(_do()) + def action_toggle_archived(self) -> None: + self._show_archived = not self._show_archived + self.query_one(SessionList).set_mode(archived=self._show_archived) + self._run_service_worker(self._refresh_sessions(restore_selection=True)) + def action_schedule_prompt(self) -> None: sid = self.query_one(SessionList).get_selected_session_id() if sid is None: diff --git a/tests/test_tui.py b/tests/test_tui.py index 1afce4e..3052016 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -310,7 +310,7 @@ async def test_new_session_on_dismiss_sequential_create_then_refresh(): session=MagicMock(), spawn_ok=True, spawn_error="" ) - async def fake_list(project_id): + async def fake_list(project_id, archived=False): call_order.append("list") return [] @@ -2088,3 +2088,42 @@ async def test_archive_session_action_calls_service_and_refreshes(): mock_service.archive_session.assert_awaited_once_with(sess_id) mock_service.list_sessions.assert_awaited() + + +@pytest.mark.asyncio +async def test_toggle_archived_binding_exists(): + bindings = {b.key: b for b in HqtApp.BINDINGS} + assert bindings["A"].action == "toggle_archived" + + +@pytest.mark.asyncio +async def test_toggle_archived_switches_list_and_header(): + app = HqtApp() + async with app.run_test(size=(120, 40)) as pilot: + from hqt.tui.widgets.session_list import SessionList + from textual.widgets import Label + + proj_id = 42 + app._selected_project_id = proj_id + + mock_service = MagicMock() + mock_service.list_sessions = AsyncMock(return_value=[]) + mock_service.sync_window_labels = AsyncMock(return_value=[]) + app._session_service = mock_service + + assert app._show_archived is False + + await app.run_action("toggle_archived") + await pilot.pause() + + assert app._show_archived is True + mock_service.list_sessions.assert_awaited_with(proj_id, archived=True) + header = app.query_one(SessionList).query_one("#session-header", Label) + assert "archived" in str(header.render()).lower() + + await app.run_action("toggle_archived") + await pilot.pause() + + assert app._show_archived is False + mock_service.list_sessions.assert_awaited_with(proj_id, archived=False) + assert str(header.render()) == "Sessions"