diff --git a/ui-tui/src/__tests__/viewport.test.ts b/ui-tui/src/__tests__/viewport.test.ts new file mode 100644 index 000000000..0a949e44c --- /dev/null +++ b/ui-tui/src/__tests__/viewport.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' + +import { stickyPromptFromViewport } from '../domain/viewport.js' + +describe('stickyPromptFromViewport', () => { + it('hides the sticky prompt when a newer user message is already visible', () => { + const messages = [ + { role: 'user' as const, text: 'older prompt' }, + { role: 'assistant' as const, text: 'older answer' }, + { role: 'user' as const, text: 'current prompt' }, + { role: 'assistant' as const, text: 'current answer' } + ] + + const offsets = [0, 2, 10, 12, 20] + + expect(stickyPromptFromViewport(messages, offsets, 16, 8, false)).toBe('') + }) + + it('shows the latest user message above the viewport when no user message is visible', () => { + const messages = [ + { role: 'user' as const, text: 'older prompt' }, + { role: 'assistant' as const, text: 'older answer' }, + { role: 'user' as const, text: 'current prompt' }, + { role: 'assistant' as const, text: 'current answer' } + ] + + const offsets = [0, 2, 10, 12, 20] + + expect(stickyPromptFromViewport(messages, offsets, 20, 16, false)).toBe('current prompt') + }) +}) diff --git a/ui-tui/src/components/appChrome.tsx b/ui-tui/src/components/appChrome.tsx index 8b1f816ce..d7974d533 100644 --- a/ui-tui/src/components/appChrome.tsx +++ b/ui-tui/src/components/appChrome.tsx @@ -270,7 +270,7 @@ export function StickyPromptTracker({ messages, offsets, scrollRef, onChange }: const vp = Math.max(0, s?.getViewportHeight() ?? 0) const total = Math.max(vp, s?.getScrollHeight() ?? vp) const atBottom = (s?.isSticky() ?? true) || top + vp >= total - 2 - const text = stickyPromptFromViewport(messages, offsets, top, atBottom) + const text = stickyPromptFromViewport(messages, offsets, top + vp, top, atBottom) useEffect(() => onChange(text), [onChange, text]) diff --git a/ui-tui/src/domain/viewport.ts b/ui-tui/src/domain/viewport.ts index 788f94269..3a358eb6f 100644 --- a/ui-tui/src/domain/viewport.ts +++ b/ui-tui/src/domain/viewport.ts @@ -18,6 +18,7 @@ const upperBound = (offsets: ArrayLike, target: number) => { export const stickyPromptFromViewport = ( messages: readonly Msg[], offsets: ArrayLike, + bottom: number, top: number, sticky: boolean ) => { @@ -26,8 +27,15 @@ export const stickyPromptFromViewport = ( } const first = Math.max(0, Math.min(messages.length - 1, upperBound(offsets, top) - 1)) + const last = Math.max(first, Math.min(messages.length - 1, upperBound(offsets, bottom) - 1)) - for (let i = first; i >= 0; i--) { + for (let i = first; i <= last; i++) { + if (messages[i]?.role === 'user') { + return '' + } + } + + for (let i = first - 1; i >= 0; i--) { if (messages[i]?.role !== 'user') { continue }