Coverage for src/lilbee/cli/tui/widgets/sessions_drawer.py: 100%

35 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-14 11:46 +0000

1"""Sessions side drawer: the session list docked left so chat stays live. 

2 

3Non-modal, mirroring the Fleet drawer. Docked left so the screen underneath 

4reflows to the right and the chat prompt keeps working while the drawer is open. 

5Resume, new-chat, and close come from the embedded panel as messages. 

6""" 

7 

8from __future__ import annotations 

9 

10from pathlib import Path 

11from typing import TYPE_CHECKING, ClassVar 

12 

13from textual import on 

14from textual.app import ComposeResult 

15from textual.screen import Screen 

16 

17from lilbee.cli.tui.widgets.drawer import Drawer 

18from lilbee.cli.tui.widgets.session_list import SessionListPanel 

19 

20if TYPE_CHECKING: 

21 from lilbee.cli.tui.app import LilbeeApp 

22 

23_DRAWER_CSS = (Path(__file__).parent / "sessions_drawer.tcss").read_text(encoding="utf-8") 

24 

25# CSS class set on the host screen so app.tcss insets the docked bars to the 

26# space right of the drawer. 

27_HOST_OPEN_CLASS = "sessions-open" 

28 

29 

30class SessionsDrawer(Drawer): 

31 """Session switcher as a non-modal left-side drawer; closed with esc or ctrl+o.""" 

32 

33 DEFAULT_CSS: ClassVar[str] = _DRAWER_CSS 

34 

35 app: LilbeeApp # type: ignore[assignment] 

36 

37 def __init__(self) -> None: 

38 super().__init__(id="sessions-drawer") 

39 self._host: Screen[object] | None = None 

40 

41 def compose(self) -> ComposeResult: 

42 yield SessionListPanel() 

43 

44 def on_mount(self) -> None: 

45 """Inset the host screen's docked bars so the drawer sits beside them.""" 

46 self._host = self.screen 

47 self._host.add_class(_HOST_OPEN_CLASS) 

48 

49 def on_unmount(self) -> None: 

50 """Restore the bars to full width when the drawer closes by any path.""" 

51 if self._host is not None: 

52 self._host.remove_class(_HOST_OPEN_CLASS) 

53 

54 @on(SessionListPanel.Resumed) 

55 def _on_resumed(self, event: SessionListPanel.Resumed) -> None: 

56 self.app.resume_session(event.session_id) 

57 self.remove() 

58 

59 @on(SessionListPanel.NewChat) 

60 def _on_new_chat(self, _event: SessionListPanel.NewChat) -> None: 

61 self.app.new_chat() 

62 self.remove() 

63 

64 @on(SessionListPanel.CloseRequested) 

65 def _on_close(self, _event: SessionListPanel.CloseRequested) -> None: 

66 self.remove()