-
fix(core): prevent TUI hint popup from permanently stealing focus (#36256)
发布于
2026-07-07 20:40:42 +00:00 Current Behavior
The fullscreen TUI can wedge into a state where
q(quit), task-list
navigation, and all mouse clicks/selection are unresponsive, while?
(help), F10, and F11 still work. Switching to inline mode and back with
F11 restores normal behavior.Deterministic reproduction: focus an output pane, press any unhandled
key (e.g.x) to trigger the "press 'i' to enter interactive mode"
toast, then press F10 within 2 seconds. When the mouse-capture toast
fades, the app is wedged.Root cause: focus was tracked in a one-slot register (
focus+
previous_focus), andupdate_focusrecorded the current focus as
"previous" even for no-op transitions. F10 dispatches its ownShowHint
while the first hint is still focused, soprevious_focusbecame
Focus::HintPopupitself. Every focus-restore path (auto-dismiss, Esc,
click-away) then self-looped, parking focus permanently on an invisible
modal: the hint key branch consumed every key except those handled
earlier in dispatch (Ctrl+C, F10-F12,?), andactive_modal_kind()
absorbed every mouse event. Only F11 escaped, because switching modes
constructs a freshApp.Expected Behavior
Focus is tracked as a layer stack and popups can never wedge the UI:
focus_stack[0]is always a base layer (task list or output pane)
that lateral navigation (Tab/Esc/click) replaces in place; popup layers
(help, run report, hints) push above itpush_focusis a no-op for the already-focused layer and moves a
buried layer instead of duplicating it — the stack can never hold the
same layer twice, so dismissal can never self-loop (the original bug is
structurally impossible)close_popupremoves a layer wherever it sits (popups can die while
buried, e.g. a hint auto-expiring under the run report) and prunes
revealed layers that are no longer active, so focus always lands on
something visible- layer classification and liveness live on
Focusitself (is_popup,
is_active) - defense in depth retained: a hidden hint popup is never treated as a
modal for keys or mouse, and a key arriving while focus points at one
repairs the focus and falls through to its normal handler
Regression tests drive the focus-stack API directly: the poisoning
sequence via repeatedpush_focus, buried-popup pruning, hidden-hint
key fall-through, modal hit-testing, and the hidden-popup geometry
contract (a hidden popup reports no hit-test areas even before the next
draw clears them). The full key-event → action-queue →ShowHint
pipeline is not unit-tested —handle_actionrequires a real terminal
backend — so the end-to-end F10-during-toast sequence was validated with
the manual reproduction above. The fullnxcrate Rust suite passes.Related Issue(s)
No linked issue — diagnosed from a user report of the TUI becoming
unresponsive after toggling mouse capture (F10) while a hint toast was
visible.
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
下载附件