Skip to content

fix: refocus chat input after cancelling a stream - #416

Merged
gadenbuie merged 5 commits into
posit-dev:mainfrom
taekop:fix/refocus-after-stop
Sep 24, 2026
Merged

gadenbuie merged 5 commits into
posit-dev:mainfrom
taekop:fix/refocus-after-stop

Conversation

@taekop

@taekop taekop commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Focus the chat input after cancelStream runs, so clicking Stop generating returns focus to the textarea instead of leaving it on <body>.
  • Scoping: cancelStream is also wired to the Escape keydown handler on the container, which can fire while focus is inside the history drawer (role="dialog", restores focus to its own trigger on close). Skip the input refocus whenever the active element is inside a dialog, so Escape-to-close-drawer isn't fought by this fix. The drawer's own focus handling is left untouched.

Testing

  • Added a regression test in js/tests/chat/ChatApp.test.tsx ("does not steal focus from the open history drawer when Escape cancels a stream"): opens the history drawer while a stream is running, fires Escape with focus inside the drawer, and asserts the chat input's focus() is not called.
  • Verified the new test fails against the prior version of cancelStream (unconditional chatInputRef.current?.focus()) and passes with the dialog check added.
  • Also added a test that clicking Stop generating focuses the chat input.

Verification

  • make js-test — 1362 passed, 23 skipped, 0 failed
  • make js-lint — clean (tsc -noEmit && eslint .)
  • make update-dist — rebuilt js/dist and synced pkg-py/pkg-r web assets

Fixes #242

taekop and others added 5 commits September 24, 2026 12:37
cancelStream never focused the input, so after clicking Stop
generating focus fell back to <body>. Focus the chat input, same as
the similar setInputValue(..., { focus: true }) handler already does.

cancelStream also runs on the container's Escape handler, which can
fire while focus is inside the history drawer (a role="dialog" that
restores focus to its own trigger when it closes). Skip the refocus
when the active element is inside a dialog so Escape-to-close-drawer
doesn't fight the drawer's own focus restoration.

Fixes posit-dev#242
@gadenbuie
gadenbuie self-requested a review September 24, 2026 19:54

@gadenbuie gadenbuie left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching and fixing the focus loss, @taekop! While testing the change, I found that Escape could also cancel a stream while closing the history drawer, or if the chat was embedded in a Shiny modal, it would close the modal containing the chat instead of cancelling. I pushed two follow-up commits to your branch to give those actions the right priority and added deterministic browser tests for the modal and the input's slash-command menu cases.

Thanks again for getting this started!

@gadenbuie
gadenbuie merged commit 05e0bbd into posit-dev:main Sep 24, 2026
20 checks passed
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.

Focus is dropped after cancelling a streaming response

2 participants