Repository navigation
feat(native-chat): a solid, fading Jump to latest #25759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
brennanb2025
merged 1 commit into
main
from
brennanb2025/native-chat-jump-to-latest-style
Oct 6, 2026
+77
−13
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
src/renderer/src/components/native-chat/NativeChatJumpToLatest.test.tsx
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // @vitest-environment happy-dom | ||
|
|
||
| import '@testing-library/jest-dom/vitest' | ||
|
|
||
| import { cleanup, render, screen } from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
| import { afterEach, expect, it, vi } from 'vitest' | ||
| import { NativeChatJumpToLatest } from './NativeChatJumpToLatest' | ||
|
|
||
| afterEach(cleanup) | ||
|
|
||
| // The reader is usually mid-draft when they reach for this: a press must not take the caret. | ||
| it('leaves focus in the composer when pressed with the pointer', async () => { | ||
| const onJump = vi.fn() | ||
| render( | ||
| <> | ||
| <NativeChatJumpToLatest visible onJump={onJump} /> | ||
| <textarea aria-label="Message" /> | ||
| </> | ||
| ) | ||
| const composer = screen.getByRole('textbox', { name: 'Message' }) | ||
| composer.focus() | ||
|
|
||
| await userEvent.click(screen.getByRole('button', { name: 'Jump to latest' })) | ||
|
|
||
| // Anti-vacuous: the press did act. | ||
| expect(onJump).toHaveBeenCalledOnce() | ||
| expect(document.activeElement).toBe(composer) | ||
| }) | ||
|
|
||
| // Mounted while hidden so it can fade out; it must not stay reachable meanwhile. | ||
| it('stays mounted but unreachable while hidden', () => { | ||
| const { container } = render(<NativeChatJumpToLatest visible={false} onJump={vi.fn()} />) | ||
|
|
||
| expect(screen.queryByRole('button', { name: 'Jump to latest' })).toBeNull() | ||
| const button = container.querySelector('button') | ||
| expect(button).toHaveAttribute('inert') | ||
| expect(button).toHaveAttribute('data-shown', 'false') | ||
| }) |
36 changes: 36 additions & 0 deletions
36
src/renderer/src/components/native-chat/NativeChatJumpToLatest.tsx
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // The way back to the latest message once the reader has scrolled up. | ||
| // | ||
| // It stays mounted so it can fade rather than pop, and while hidden it is inert | ||
| // and hidden from assistive tech, so it can be neither clicked, tabbed to nor read. | ||
|
|
||
| import { ArrowDown } from 'lucide-react' | ||
| import { translate } from '@/i18n/i18n' | ||
|
|
||
| /** Keeps focus where the reader was typing: a pointer press never moves it here. */ | ||
| function keepFocus(event: React.MouseEvent): void { | ||
| event.preventDefault() | ||
| } | ||
|
|
||
| export function NativeChatJumpToLatest({ | ||
| visible, | ||
| onJump | ||
| }: { | ||
| visible: boolean | ||
| onJump: () => void | ||
| }): React.JSX.Element { | ||
| const label = translate('components.native-chat.jumpToLatest', 'Jump to latest') | ||
| return ( | ||
| <button | ||
| type="button" | ||
| data-shown={visible} | ||
| inert={!visible} | ||
| aria-hidden={!visible || undefined} | ||
| onMouseDown={keepFocus} | ||
| onClick={onJump} | ||
| className="absolute bottom-3 left-1/2 flex h-8 -translate-x-1/2 items-center gap-1.5 rounded-full border border-border bg-popover px-3 text-xs font-medium text-popover-foreground shadow-floating transition-[opacity,translate] duration-150 ease-out hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none data-[shown=false]:pointer-events-none data-[shown=false]:translate-y-1 data-[shown=false]:opacity-0" | ||
| > | ||
| <ArrowDown className="size-3.5" /> | ||
| <span>{label}</span> | ||
| </button> | ||
| ) | ||
| } | ||
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: stablyai/orca
Length of output: 5715
🏁 Script executed:
Repository: stablyai/orca
Length of output: 13681
🏁 Script executed:
Repository: stablyai/orca
Length of output: 37641
🤖 get_repo_knowledge executed:
get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventionsLength of output: 40334
🌐 Web query:
WHATWG HTML inert attribute focused element becomes inert focus behavior activeElement💡 Result:
🏁 Script executed:
Repository: stablyai/orca
Length of output: 26879
Move focus before making the jump button inert.
When a keyboard user activates the button,
jumpToLatestcallsscrollToBottom, which hides it. The button stays mounted and becomesinertandaria-hidden. HTML does not requireinertto move focus from an element that already has focus, so focus can remain on a control hidden from assistive technology. Move focus to the transcript scroll root only when the button has focus. Keep pointer presses from taking focus away from the composer.Suggested fix
📝 Committable suggestion