Story 22: Implement smart auto-scroll that respects user scrolling
User Story: As a user, I want to be able to scroll up to review previous messages while the AI is streaming or adding new content, without being constantly dragged back to the bottom. Implementation: - Replaced position-based threshold detection with user-intent tracking - Detects when user scrolls UP and disables auto-scroll completely - Auto-scroll only re-enables when user manually returns to bottom (<5px) - Uses refs to track scroll position and direction for smooth operation - Works seamlessly during rapid token streaming and tool execution Technical Details: - lastScrollTopRef: Tracks previous scroll position to detect direction - userScrolledUpRef: Flag set when upward scrolling is detected - Direct scrollTop manipulation for instant, non-fighting scroll behavior - Threshold of 5px from absolute bottom to re-enable auto-scroll Spec Updates: - Added comprehensive Smart Auto-Scroll section to UI_UX.md - Documented the problem, solution, requirements, and implementation - Includes code examples and edge case handling Acceptance Criteria Met: ✅ Auto-scroll disabled when scrolling up ✅ Auto-scroll resumes when returning to bottom ✅ Works normally when already at bottom ✅ Smooth detection without flickering ✅ Works during streaming and tool execution Files Changed: - src/components/Chat.tsx: Implemented user-intent tracking - .living_spec/specs/functional/UI_UX.md: Added Smart Auto-Scroll spec - .living_spec/stories/22_smart_autoscroll.md: Marked complete
This commit is contained in:
@@ -22,6 +22,10 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
const [streamingContent, setStreamingContent] = useState("");
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const shouldAutoScrollRef = useRef(true);
|
||||
const lastScrollTopRef = useRef(0);
|
||||
const userScrolledUpRef = useRef(false);
|
||||
|
||||
// Token estimation and context window tracking
|
||||
const estimateTokens = (text: string): number => {
|
||||
@@ -118,11 +122,43 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
}, []);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
const element = scrollContainerRef.current;
|
||||
if (element) {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
lastScrollTopRef.current = element.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
const element = scrollContainerRef.current;
|
||||
if (!element) return;
|
||||
|
||||
const currentScrollTop = element.scrollTop;
|
||||
const isAtBottom =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight < 5;
|
||||
|
||||
// Detect if user scrolled UP
|
||||
if (currentScrollTop < lastScrollTopRef.current) {
|
||||
userScrolledUpRef.current = true;
|
||||
shouldAutoScrollRef.current = false;
|
||||
}
|
||||
|
||||
// If user scrolled back to bottom, re-enable auto-scroll
|
||||
if (isAtBottom) {
|
||||
userScrolledUpRef.current = false;
|
||||
shouldAutoScrollRef.current = true;
|
||||
}
|
||||
|
||||
lastScrollTopRef.current = currentScrollTop;
|
||||
};
|
||||
|
||||
// Smart auto-scroll: only scroll if user hasn't scrolled up
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We intentionally trigger on messages/streamingContent changes
|
||||
useEffect(scrollToBottom, [messages, streamingContent]);
|
||||
useEffect(() => {
|
||||
if (shouldAutoScrollRef.current && !userScrolledUpRef.current) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [messages, streamingContent]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
@@ -419,6 +455,8 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
|
||||
{/* Messages Area */}
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
onScroll={handleScroll}
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
|
||||
Reference in New Issue
Block a user