glaip-sdk 0.7.27__py3-none-any.whl → 0.7.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,8 +10,8 @@ from glaip_sdk.cli.slash.tui.keybind_registry import (
10
10
  parse_key_sequence,
11
11
  )
12
12
  from glaip_sdk.cli.slash.tui.remote_runs_app import (
13
- RemoteRunsTextualApp,
14
13
  RemoteRunsTUICallbacks,
14
+ RunsHarlequinScreen,
15
15
  run_remote_runs_textual,
16
16
  )
17
17
  from glaip_sdk.cli.slash.tui.terminal import TerminalCapabilities, detect_terminal_background
@@ -23,7 +23,7 @@ __all__ = [
23
23
  "ToastVariant",
24
24
  "TerminalCapabilities",
25
25
  "detect_terminal_background",
26
- "RemoteRunsTextualApp",
26
+ "RunsHarlequinScreen",
27
27
  "RemoteRunsTUICallbacks",
28
28
  "run_remote_runs_textual",
29
29
  "KeybindRegistry",
@@ -52,7 +52,7 @@ from textual.app import App, ComposeResult
52
52
  from textual.binding import Binding
53
53
  from textual.containers import Horizontal, Vertical
54
54
  from textual.coordinate import Coordinate
55
- from textual.screen import ModalScreen
55
+ from textual.screen import ModalScreen, Screen
56
56
  from textual.suggester import SuggestFromList
57
57
  from textual.theme import Theme
58
58
  from textual.widgets import Button, Checkbox, DataTable, Footer, Input, Static
@@ -107,6 +107,37 @@ class AccountsTUICallbacks:
107
107
  switch_account: Callable[[str], tuple[bool, str]]
108
108
 
109
109
 
110
+ def _ensure_context_services(
111
+ ctx: TUIContext,
112
+ notify: Callable[[ToastBus.Changed], None],
113
+ ) -> tuple[KeybindRegistry | None, ToastBus | None, ClipboardAdapter]:
114
+ """Ensure the context has keybinds, toasts, and clipboard services."""
115
+ if ctx.keybinds is None:
116
+ ctx.keybinds = KeybindRegistry()
117
+ if ctx.toasts is None:
118
+ ctx.toasts = ToastBus(on_change=notify)
119
+ if ctx.clipboard is None:
120
+ ctx.clipboard = ClipboardAdapter(terminal=ctx.terminal)
121
+ return ctx.keybinds, ctx.toasts, ctx.clipboard
122
+
123
+
124
+ def _initialize_fallback_services(
125
+ notify: Callable[[ToastBus.Changed], None],
126
+ ) -> tuple[KeybindRegistry | None, ToastBus | None, ClipboardAdapter]:
127
+ """Create fallback clipboard and toast services without a context."""
128
+ terminal = TerminalCapabilities(tty=True, ansi=True, osc52=False, osc11_bg=None, mouse=False, truecolor=False)
129
+ clipboard = ClipboardAdapter(terminal=terminal)
130
+ toast_bus = ToastBus(on_change=notify)
131
+ return None, toast_bus, clipboard
132
+
133
+
134
+ def _wire_toast_notifications(toast_bus: ToastBus | None, notify: Callable[[ToastBus.Changed], None]) -> None:
135
+ """Ensure toast bus forwards notifications to the provided callback."""
136
+ if toast_bus is None:
137
+ return
138
+ toast_bus.subscribe(notify)
139
+
140
+
110
141
  def _build_account_rows_from_store(
111
142
  store: AccountStore,
112
143
  env_lock: bool,
@@ -232,7 +263,12 @@ def run_accounts_textual(
232
263
  if not TEXTUAL_SUPPORTED:
233
264
  return
234
265
  app = AccountsTextualApp(rows, active_account, env_lock, callbacks, ctx=ctx)
235
- app.run()
266
+ mouse_enabled = True
267
+ if ctx is not None:
268
+ terminal = getattr(ctx, "terminal", None)
269
+ if terminal is not None:
270
+ mouse_enabled = bool(getattr(terminal, "mouse", True))
271
+ app.run(mouse=mouse_enabled)
236
272
 
237
273
 
238
274
  class AccountFormModal(_AccountFormBase): # pragma: no cover - interactive
@@ -543,8 +579,7 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
543
579
  )
544
580
 
545
581
  # Toast container for notifications
546
- if Toast is not None and ToastContainer is not None:
547
- yield ToastContainer(Toast(), id="toast-container")
582
+ yield ToastContainer(Toast(), id="toast-container")
548
583
 
549
584
  def on_mount(self) -> None:
550
585
  """Configure the screen after mount."""
@@ -567,29 +602,31 @@ class AccountsHarlequinScreen( # pragma: no cover - interactive
567
602
  """Initialize TUI context services."""
568
603
 
569
604
  def _notify(message: ToastBus.Changed) -> None:
605
+ # Post to self (Screen) so ToastHandlerMixin.on_toast_bus_changed can handle it.
570
606
  self.post_message(message)
571
607
 
572
608
  ctx = self.ctx if hasattr(self, "ctx") else self._ctx
573
- if ctx:
574
- if ctx.keybinds is None:
575
- ctx.keybinds = KeybindRegistry()
576
- if ctx.toasts is None and ToastBus is not None:
577
- ctx.toasts = ToastBus(on_change=_notify)
578
- if ctx.clipboard is None:
579
- ctx.clipboard = ClipboardAdapter(terminal=ctx.terminal)
580
- self._keybinds = ctx.keybinds
581
- self._toast_bus = ctx.toasts
582
- self._clip_cache = ctx.clipboard
583
- else:
584
- terminal = TerminalCapabilities(
585
- tty=True, ansi=True, osc52=False, osc11_bg=None, mouse=False, truecolor=False
586
- )
587
- self._clip_cache = ClipboardAdapter(terminal=terminal)
588
- if ToastBus is not None:
589
- self._toast_bus = ToastBus(on_change=_notify)
609
+ if ctx is None:
610
+ self._keybinds, self._toast_bus, self._clip_cache = _initialize_fallback_services(_notify)
611
+ return
612
+
613
+ self._keybinds, self._toast_bus, self._clip_cache = _ensure_context_services(ctx, _notify)
614
+ _wire_toast_notifications(self._toast_bus, _notify)
615
+
616
+ def _ensure_toast_bus(self) -> None:
617
+ """Ensure toast bus is initialized with proper message routing."""
618
+ if self._toast_bus is not None:
619
+ return
620
+
621
+ def _notify(message: ToastBus.Changed) -> None:
622
+ # Post to self (Screen) so ToastHandlerMixin.on_toast_bus_changed can handle it.
623
+ self.post_message(message)
624
+
625
+ self._toast_bus = ToastBus(on_change=_notify)
590
626
 
591
627
  def _prepare_toasts(self) -> None:
592
628
  """Prepare toast system."""
629
+ self._ensure_toast_bus()
593
630
  if self._toast_bus:
594
631
  self._toast_bus.clear()
595
632
 
@@ -1230,26 +1267,33 @@ class AccountsTextualApp( # pragma: no cover - interactive
1230
1267
 
1231
1268
  def _initialize_context_services(self) -> None:
1232
1269
  def _notify(message: ToastBus.Changed) -> None:
1233
- self.post_message(message)
1270
+ """Route messages to the active screen or app."""
1271
+ screen = self._safe_screen()
1272
+ if screen is not None:
1273
+ self._safe_post_message(screen, message)
1274
+ return
1275
+ self._safe_post_message(self, message)
1234
1276
 
1235
- if self._ctx:
1236
- if self._ctx.keybinds is None:
1237
- self._ctx.keybinds = KeybindRegistry()
1238
- if self._ctx.toasts is None and ToastBus is not None:
1239
- self._ctx.toasts = ToastBus(on_change=_notify)
1240
- if self._ctx.clipboard is None:
1241
- self._ctx.clipboard = ClipboardAdapter(terminal=self._ctx.terminal)
1242
- self._keybinds = self._ctx.keybinds
1243
- self._toast_bus = self._ctx.toasts
1244
- self._clip_cache = self._ctx.clipboard
1245
- else:
1246
- # Fallback: create services independently when ctx is None
1247
- terminal = TerminalCapabilities(
1248
- tty=True, ansi=True, osc52=False, osc11_bg=None, mouse=False, truecolor=False
1249
- )
1250
- self._clip_cache = ClipboardAdapter(terminal=terminal)
1251
- if ToastBus is not None:
1252
- self._toast_bus = ToastBus(on_change=_notify)
1277
+ if self._ctx is None:
1278
+ self._keybinds, self._toast_bus, self._clip_cache = _initialize_fallback_services(_notify)
1279
+ return
1280
+
1281
+ self._keybinds, self._toast_bus, self._clip_cache = _ensure_context_services(self._ctx, _notify)
1282
+ _wire_toast_notifications(self._toast_bus, _notify)
1283
+
1284
+ def _safe_screen(self) -> Screen | None:
1285
+ """Return the active screen when available."""
1286
+ try:
1287
+ return self.screen
1288
+ except Exception:
1289
+ return None
1290
+
1291
+ def _safe_post_message(self, target: Screen | App[Any], message: ToastBus.Changed) -> None:
1292
+ """Post a toast message while guarding against failures."""
1293
+ try:
1294
+ target.post_message(message)
1295
+ except Exception:
1296
+ return
1253
1297
 
1254
1298
  def _prepare_toasts(self) -> None:
1255
1299
  """Prepare toast system by clearing any existing toasts."""
@@ -1517,7 +1561,11 @@ class AccountsTextualApp( # pragma: no cover - interactive
1517
1561
  def _clear_filter(self) -> None:
1518
1562
  """Clear the filter input and reset filter state."""
1519
1563
  # Skip if Harlequin screen is active (it handles its own filtering)
1520
- if isinstance(self.screen, AccountsHarlequinScreen):
1564
+ try:
1565
+ screen = self.screen
1566
+ except Exception:
1567
+ screen = None
1568
+ if isinstance(screen, AccountsHarlequinScreen):
1521
1569
  return
1522
1570
  try:
1523
1571
  filter_input = self.query_one(FILTER_INPUT_ID, Input)
@@ -1567,8 +1615,12 @@ class AccountsTextualApp( # pragma: no cover - interactive
1567
1615
  UX note: helps users reset the list without leaving the TUI.
1568
1616
  """
1569
1617
  # Skip if Harlequin screen is active (it handles its own exit)
1570
- if isinstance(self.screen, AccountsHarlequinScreen):
1571
- self.exit()
1618
+ try:
1619
+ screen = self.screen
1620
+ except Exception:
1621
+ screen = None
1622
+ if isinstance(screen, AccountsHarlequinScreen):
1623
+ self._safe_exit()
1572
1624
  return
1573
1625
  try:
1574
1626
  filter_input = self.query_one(FILTER_INPUT_ID, Input)
@@ -1578,7 +1630,7 @@ class AccountsTextualApp( # pragma: no cover - interactive
1578
1630
  self._clear_filter()
1579
1631
  self._reload_rows()
1580
1632
  try:
1581
- table = self.query_one(ACCOUNTS_TABLE_ID, DataTable)
1633
+ table = self.query_one(ACCOUNTS_TABLE_ID)
1582
1634
  table.focus()
1583
1635
  except Exception:
1584
1636
  pass
@@ -1586,11 +1638,17 @@ class AccountsTextualApp( # pragma: no cover - interactive
1586
1638
  except Exception:
1587
1639
  # Filter input doesn't exist, just exit
1588
1640
  pass
1589
- self.exit()
1641
+ self._safe_exit()
1590
1642
 
1591
1643
  def action_app_exit(self) -> None:
1592
1644
  """Exit the application regardless of focus state."""
1593
- self.exit()
1645
+ self._safe_exit()
1646
+
1647
+ def _safe_exit(self) -> None:
1648
+ try:
1649
+ self.exit()
1650
+ except AttributeError:
1651
+ return
1594
1652
 
1595
1653
  def on_button_pressed(self, event: Button.Pressed) -> None:
1596
1654
  """Handle filter bar buttons."""
@@ -1836,7 +1894,11 @@ class AccountsTextualApp( # pragma: no cover - interactive
1836
1894
  def _update_filter_button_visibility(self) -> None:
1837
1895
  """Show clear button only when filter has content."""
1838
1896
  # Skip if Harlequin screen is active (it doesn't have this button)
1839
- if isinstance(self.screen, AccountsHarlequinScreen):
1897
+ try:
1898
+ screen = self.screen
1899
+ except Exception:
1900
+ screen = None
1901
+ if isinstance(screen, AccountsHarlequinScreen):
1840
1902
  return
1841
1903
  try:
1842
1904
  filter_input = self.query_one(FILTER_INPUT_ID, Input)