fix(tui): defer on_mount/on_screen_resume work across screens (xdist race)#122
fix(tui): defer on_mount/on_screen_resume work across screens (xdist race)#122aorumbayev merged 1 commit intomainfrom
Conversation
…reens (xdist race) Wrap every on_screen_resume (and SessionResumeModal.on_mount) that transitively calls query_one in a call_after_refresh deferred helper, matching the pattern already applied to WelcomeScreen.on_mount. Eliminates NoMatches races under pytest-xdist parallel workers. - welcome.py: on_screen_resume → _on_screen_resume_deferred - session_resume_modal.py: on_mount → call_after_refresh(_reload_sessions) - kanban.py: on_screen_resume → _on_screen_resume_deferred - workspace.py: on_screen_resume → _on_screen_resume_deferred Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Greptile SummaryThis PR systematically applies the Confidence Score: 5/5Safe to merge — all changes follow a well-established, correct Textual deferral pattern with no new logic introduced. All four changed files apply the same mechanical, low-risk refactor (wrap body in call_after_refresh). No new logic, no schema changes, no data mutations. The only behavioral delta is DOM-query timing, which is exactly what the fix targets. All remaining observations are at most P2. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/kagan/tui/screens/welcome.py | Adds on_screen_resume deferral pattern (matching the existing on_mount deferral), fixing the CI xdist race on _reload_projects / query_one('#project-list'). |
| src/kagan/tui/screens/kanban.py | Wraps on_screen_resume body in call_after_refresh; removes a nested call_after_refresh around _auto_focus_board since the outer deferral already guarantees DOM readiness. |
| src/kagan/tui/screens/workspace.py | Wraps on_screen_resume body in call_after_refresh and removes the nested call_after_refresh around _focus_sidebar; WorkspaceScreen owns its children so DOM queries inside the deferred callback are safe. |
| src/kagan/tui/screens/session_resume_modal.py | Changes on_mount from directly awaiting _reload_sessions to scheduling it via call_after_refresh; _reload_sessions is async, which Textual's call_after_refresh handles correctly. |
Sequence Diagram
sequenceDiagram
participant Textual
participant Screen
participant DOM
Note over Textual,DOM: Before this PR (race condition)
Textual->>Screen: on_screen_resume / on_mount
Screen->>DOM: query_one('#widget') — DOM may not be ready yet
DOM-->>Screen: NoMatches (race under xdist)
Note over Textual,DOM: After this PR (deferred, safe)
Textual->>Screen: on_screen_resume / on_mount
Screen->>Textual: call_after_refresh(_deferred)
Textual->>Textual: process pending refresh cycle
Textual->>Screen: _deferred() [DOM fully ready]
Screen->>DOM: query_one('#widget') — always succeeds
DOM-->>Screen: widget reference
Reviews (1): Last reviewed commit: "fix(tui): defer query_one work in on_mou..." | Re-trigger Greptile
Summary
Systematic follow-up to #121. My earlier fix only covered
WelcomeScreen.on_mountbut thetest_doctor_modal_skip_button_dismisses_modalCI failure actually hiton_screen_resume(DoctorModal dismiss → WelcomeScreen resume →_reload_projects→query_one('#project-list')race underpytest-xdist).This PR audits every
on_mount/on_screen_resumeinsrc/kagan/tui/screens/and appliescall_after_refresh(…)deferral wherever aquery_onecan race with DOM readiness.Files changed
welcome.py—on_screen_resume(the CI-blocker)session_resume_modal.py—on_mount(matches CI failure on commit32dcf1e:No nodes match '#column-backlog')kanban.py—on_screen_resumeworkspace.py—on_screen_resumeTest plan
test_doctor_modal_skip_button_dismisses_modalunder-n auto— 5/5 passtests/tui/ -n auto -m "not snapshot"— 3/3 runs pass (129 tests each)Notes
Spotted but left alone (no observed race, deferred via existing
call_after_refreshelsewhere, or children composed by same screen so always ready at mount time):kanban.on_mount— multiplequery_onebut guarded;_focus_default_widgetalready usescall_after_refresh.workspace.on_mount—query_one(ChatPanel)butChatPanelis composed byworkspace.pyitself.🤖 Generated with Claude Code