feat: A toggles the sessions column between active and archived

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 12:22:41 -04:00
parent 6724d5e128
commit 167695708b
2 changed files with 53 additions and 3 deletions
+13 -2
View File
@@ -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:
+40 -1
View File
@@ -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"