Skip to content

Commit 6e8fb90

Browse files
committed
fix(tui): ignore stale overview refreshes
1 parent 5473e08 commit 6e8fb90

2 files changed

Lines changed: 17 additions & 3 deletions

File tree

airlock/tui/screens/overview.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from textual.app import ComposeResult
2020
from textual.binding import Binding
2121
from textual.containers import Horizontal, Vertical, VerticalScroll
22+
from textual.css.query import NoMatches
2223
from textual.strip import Strip
2324
from textual.widgets import Button, Collapsible, DataTable, RichLog, Static
2425

@@ -1002,8 +1003,21 @@ def _refresh_state(self) -> None:
10021003

10031004
# --- push to UI ---
10041005
def _update_ui() -> None:
1006+
# This worker can finish after its pane has been replaced while
1007+
# navigating the TUI. In that case the scheduled UI callback is
1008+
# stale: querying the former pane's children raises NoMatches and
1009+
# turns a harmless teardown race into a failed Textual worker.
1010+
if not self.is_mounted:
1011+
return
1012+
10051013
# Providers table
1006-
ptable = self.query_one("#ov-providers", _SafeDataTable)
1014+
try:
1015+
ptable = self.query_one("#ov-providers", _SafeDataTable)
1016+
except NoMatches:
1017+
# The overview pane may remain mounted for a moment after its
1018+
# children are removed during a screen switch or app teardown.
1019+
# This callback came from the background refresh and is stale.
1020+
return
10071021
ptable.clear()
10081022
if provider_rows:
10091023
for row, key in zip(provider_rows, provider_keys):

tests/test_tui.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@ def test_guards_render_pipeline_escapes_request_metadata() -> None:
12531253
assert r"\[bold red]ACT\[/]" in out
12541254

12551255

1256-
async def test_overview_no_data_for_provider_escapes_name() -> None:
1256+
async def test_overview_no_data_for_provider_escapes_name(fresh_state_store) -> None:
12571257
"""Early-return branch in _show_provider_detail must escape markup in
12581258
the provider name so injected tags aren't interpreted as Rich markup."""
12591259
from textual.widgets import Static
@@ -1275,7 +1275,7 @@ async def test_overview_no_data_for_provider_escapes_name() -> None:
12751275
assert r"\[bold red]INJECT\[/]" in raw
12761276

12771277

1278-
async def test_overview_no_data_for_model_escapes_name() -> None:
1278+
async def test_overview_no_data_for_model_escapes_name(fresh_state_store) -> None:
12791279
"""Early-return branch in _show_model_detail must escape markup in
12801280
the model name."""
12811281
from textual.widgets import Static

0 commit comments

Comments
 (0)