Skip to content

fix(tui): unify keyboard input into a single reader thread - #817

Open
judyks wants to merge 1 commit into
mainfrom
tui-unified-input
Open

fix(tui): unify keyboard input into a single reader thread#817
judyks wants to merge 1 commit into
mainfrom
tui-unified-input

Conversation

@judyks

@judyks judyks commented Aug 6, 2026

Copy link
Copy Markdown

Two threads previously read the same crossterm event queue: a nav-only
poller thread (alive through the confirm prompt) and the prompts' own
blocking readers. Whichever reader won the race got the key, so at the
project-confirm prompt 'y' could scroll the log instead of accepting and
Enter could be silently dropped. The language/framework re-pick prompts
and the file picker (when tailoring was skipped) raced the same way.

Replace both poller variants with one input hub thread that owns
event::read() for the whole run and routes by a shared flag:

  • Execution route: nav keys become NavCmds, quit keys (q/Q/Esc/Ctrl+C)
    set a watch-channel cancel signal, resizes request a redraw.
  • Prompt route: every key and resize is forwarded verbatim to the active
    prompt over a channel; the renderer flips the route flag on prompt
    entry/exit and drains stale events on entry.

This also fixes two adjacent defects:

  • Cancellation now works outside tailoring: quit keys abort the 503
    backoff and API fetch immediately (the cancel future now wraps
    attempts and backoff sleeps alike), and take effect at stage
    boundaries after the synchronous analysis/signals phases. Previously
    the nav-only poller deliberately ignored quit keys and raw mode ate
    Ctrl+C, so nothing before tailoring could be cancelled.
  • Resize events are no longer discarded: prompt loops and review mode
    redraw immediately on resize, and the 503 backoff now sleeps in 1s
    slices with a live countdown so the frame stays current during waits.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01V9Rz9BcK1xb1jiGNER1FeU

Two threads previously read the same crossterm event queue: a nav-only
poller thread (alive through the confirm prompt) and the prompts' own
blocking readers. Whichever reader won the race got the key, so at the
project-confirm prompt 'y' could scroll the log instead of accepting and
Enter could be silently dropped. The language/framework re-pick prompts
and the file picker (when tailoring was skipped) raced the same way.

Replace both poller variants with one input hub thread that owns
event::read() for the whole run and routes by a shared flag:

- Execution route: nav keys become NavCmds, quit keys (q/Q/Esc/Ctrl+C)
  set a watch-channel cancel signal, resizes request a redraw.
- Prompt route: every key and resize is forwarded verbatim to the active
  prompt over a channel; the renderer flips the route flag on prompt
  entry/exit and drains stale events on entry.

This also fixes two adjacent defects:

- Cancellation now works outside tailoring: quit keys abort the 503
  backoff and API fetch immediately (the cancel future now wraps
  attempts and backoff sleeps alike), and take effect at stage
  boundaries after the synchronous analysis/signals phases. Previously
  the nav-only poller deliberately ignored quit keys and raw mode ate
  Ctrl+C, so nothing before tailoring could be cancelled.
- Resize events are no longer discarded: prompt loops and review mode
  redraw immediately on resize, and the 503 backoff now sleeps in 1s
  slices with a live countdown so the frame stays current during waits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9Rz9BcK1xb1jiGNER1FeU
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