fix(tui): stop the first keystroke after entering a panel from leaking into the chat buffer - #139
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first keystroke after entering a panel tab was delivered to the panel and the chat
editor at once. On Tasks, a single
/opened the panel's search row, the global slashpalette, and seeded
/into the prompt buffer — three surfaces from one key — and the straycharacter survived the trip back to Run, prefixing the next message.
Why it happens
Ink keeps the latest
useInputhandler current on every render, but appliesisActivethrough a passive effect. Between the commit that flips
editorFocustofalseand thateffect's flush there is a window where the editor's subscription is still live — and the
handler body only checked
disabled, neverfocus. A keypress landing in the window isprocessed by the freshly-active panel bindings and the stale editor subscription. The
palette then rides along for free: it opens whenever the buffer becomes
/…(
onEditorChange→slash_palette_opened), so the leaked/is what summons it.Fix
multi-line-editor.tsx: a ref written during render mirrorsfocus && !disabled, and theuseInputcallback early-returns when it is false. The focus flip now takes effect the sameframe; no new focus model, no new state.
Testing
The window is invisible to
rerender()(ink-testing-library flushes effects beforereturning), so the regression test drives it the way the app really hits it: a bus-originated
tab switch, two
setImmediateyields — render committed, teardown effect not yet run — thenthe keystroke. Verified stable 3/3 in both directions: on unpatched code the frame shows
the seeded
❯ /plus the palette (test fails); patched, only the tasks search opens.npm run lintcleanmulti-line-editor.test.tsx(focused/unfocused basics) + the app-level window testsrc/tuiserial: failure set identical tomain's pre-existing five (tui-app×2stale copy,
splash-banner,chat-log,persist-embedding-hybrid-recall;llm-health-poller12/12 solo, fails only under parallel load)