Skip to content

fix(tui): defer on_mount/on_screen_resume work across screens (xdist race)#122

Merged
aorumbayev merged 1 commit intomainfrom
fix/tui-xdist-query-one-races
Apr 23, 2026
Merged

fix(tui): defer on_mount/on_screen_resume work across screens (xdist race)#122
aorumbayev merged 1 commit intomainfrom
fix/tui-xdist-query-one-races

Conversation

@aorumbayev
Copy link
Copy Markdown
Member

Summary

Systematic follow-up to #121. My earlier fix only covered WelcomeScreen.on_mount but the test_doctor_modal_skip_button_dismisses_modal CI failure actually hit on_screen_resume (DoctorModal dismiss → WelcomeScreen resume → _reload_projectsquery_one('#project-list') race under pytest-xdist).

This PR audits every on_mount / on_screen_resume in src/kagan/tui/screens/ and applies call_after_refresh(…) deferral wherever a query_one can race with DOM readiness.

Files changed

  • welcome.pyon_screen_resume (the CI-blocker)
  • session_resume_modal.pyon_mount (matches CI failure on commit 32dcf1e: No nodes match '#column-backlog')
  • kanban.pyon_screen_resume
  • workspace.pyon_screen_resume

Test plan

  • test_doctor_modal_skip_button_dismisses_modal under -n auto — 5/5 pass
  • Full tests/tui/ -n auto -m "not snapshot" — 3/3 runs pass (129 tests each)
  • Sequential sanity — passes
  • CI matrix green on this PR

Notes

Spotted but left alone (no observed race, deferred via existing call_after_refresh elsewhere, or children composed by same screen so always ready at mount time):

  • kanban.on_mount — multiple query_one but guarded; _focus_default_widget already uses call_after_refresh.
  • workspace.on_mountquery_one(ChatPanel) but ChatPanel is composed by workspace.py itself.

🤖 Generated with Claude Code

…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-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

This PR systematically applies the call_after_refresh deferral pattern to on_screen_resume (and one on_mount) across four TUI screens, fixing xdist-parallel test races where query_one was called before the DOM was fully ready. The approach is consistent with the existing pattern established in WelcomeScreen.on_mount in PR #121, and the PR description correctly justifies the cases that were intentionally left alone.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix(tui): defer query_one work in on_mou..." | Re-trigger Greptile

@aorumbayev aorumbayev merged commit 3adc7c5 into main Apr 23, 2026
20 checks passed
@aorumbayev aorumbayev deleted the fix/tui-xdist-query-one-races branch April 23, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants