Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { type ChatMessage, ConnectionStatus } from "@/types/chat";
// --- 프로젝트 내부 의존성 ---
import useInfinityScroll from "@/utils/useInfinityScroll";

const BOTTOM_PROXIMITY_THRESHOLD = 80;

const getMessageDedupeKey = (message: ChatMessage): string => {
if (message.id > 0) {
return `id:${message.id}`;
Expand All @@ -22,6 +24,9 @@ const useChatListHandler = (chatId: number) => {
// --- 1. State 및 Ref 선언 ---
const clientRef = useRef<Client | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null); // 새 메시지 수신 시 자동 스크롤을 위한 ref
const scrollContainerRef = useRef<HTMLDivElement>(null); // 실제 스크롤 컨테이너 ref
const hasInitialAutoScrolledRef = useRef(false);
const prevMessageCountRef = useRef(0);

// --- 2. 하위 Hooks 호출 ---

Expand Down Expand Up @@ -71,13 +76,69 @@ const useChatListHandler = (chatId: number) => {
}
}, [chatHistoryPages, setSubmittedMessages]);

// 새로운 메시지가 추가되었을 때, 스크롤을 대화 목록의 맨 아래로 이동시킵니다.
// 채팅방 전환 시 자동 스크롤 상태를 초기화합니다.
useEffect(() => {
hasInitialAutoScrolledRef.current = false;
prevMessageCountRef.current = 0;
}, [chatId]);

// 초기 히스토리 로딩 완료 후, 최초 1회만 하단으로 이동합니다.
useEffect(() => {
// 이전 기록을 불러오는 중일 때는 자동 스크롤을 방지하여 사용자 경험을 해치지 않습니다.
if (!isFetchingNextPage && messagesEndRef.current) {
messagesEndRef.current.scrollIntoView();
if (isLoading || isFetchingNextPage || submittedMessages.length === 0 || hasInitialAutoScrolledRef.current) {
return;
}

const rafId = requestAnimationFrame(() => {
const container = scrollContainerRef.current;
if (!container) return;

container.scrollTop = container.scrollHeight;
hasInitialAutoScrolledRef.current = true;
prevMessageCountRef.current = submittedMessages.length;
});

return () => cancelAnimationFrame(rafId);
}, [isLoading, isFetchingNextPage, submittedMessages.length]);

// 신규 메시지 도착 시, 사용자가 하단 근처에 있을 때만 자동으로 하단을 유지합니다.
useEffect(() => {
if (isLoading || isFetchingNextPage) return;

const currentMessageCount = submittedMessages.length;
const prevMessageCount = prevMessageCountRef.current;
const container = scrollContainerRef.current;

if (!container) {
prevMessageCountRef.current = currentMessageCount;
return;
}
}, [isFetchingNextPage]);

if (currentMessageCount <= prevMessageCount) {
prevMessageCountRef.current = currentMessageCount;
return;
}

if (!hasInitialAutoScrolledRef.current) {
prevMessageCountRef.current = currentMessageCount;
return;
}

const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;

if (distanceFromBottom <= BOTTOM_PROXIMITY_THRESHOLD) {
const rafId = requestAnimationFrame(() => {
Comment on lines +126 to +129
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check bottom proximity before appending new messages

The auto-scroll gate now computes distanceFromBottom only after submittedMessages.length has already increased, so users who were exactly at the bottom can still fail the <= 80 check when a newly received message is taller than 80px (e.g., long text or image). In that case, the view stops following new messages even though the user never scrolled up, which regresses expected chat behavior for active conversations.

Useful? React with 👍 / 👎.

const target = scrollContainerRef.current;
if (!target) return;
target.scrollTop = target.scrollHeight;
});

prevMessageCountRef.current = currentMessageCount;

return () => cancelAnimationFrame(rafId);
}

prevMessageCountRef.current = currentMessageCount;
}, [isLoading, isFetchingNextPage, submittedMessages.length]);

// --- 4. Handler 함수 ---

Expand Down Expand Up @@ -197,6 +258,7 @@ const useChatListHandler = (chatId: number) => {
isFetchingNextPage, // 이전 기록 로딩 상태

// Refs
scrollContainerRef, // 실제 스크롤 컨테이너 ref
messagesEndRef, // 자동 스크롤을 위한 ref
topDetectorRef, // 무한 스크롤 감지를 위한 ref

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => {
isFetchingNextPage, // 이전 기록 로딩 상태

// Refs
scrollContainerRef, // 실제 스크롤 컨테이너 ref
messagesEndRef, // 자동 스크롤을 위한 ref
topDetectorRef, // 무한 스크롤 감지를 위한 ref

Expand Down Expand Up @@ -111,6 +112,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => {
</div>
{/* 채팅 메시지 영역 - 항상 스크롤 가능, 스크롤바 숨김 */}
<div
ref={scrollContainerRef}
className="scrollbar-hide mt-4 flex-1 overflow-y-auto p-4 pb-6"
style={{
scrollbarWidth: "none" /* Firefox */,
Expand Down
Loading