Skip to content

feat:실시간 채팅 기능 구현(#60) - #60

Merged
seongjinss555 merged 2 commits into
developfrom
feat/realtime-chat
Jul 15, 2026
Merged

feat:실시간 채팅 기능 구현(#60)#60
seongjinss555 merged 2 commits into
developfrom
feat/realtime-chat

Conversation

@seongjinss555

@seongjinss555 seongjinss555 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Pull Request

작업 내용

  • 워크스페이스 멤버가 텍스트 메시지를 조회·전송하고 실시간으로 수신할 수 있는 채팅 기능을 구현했습니다.
  • 서버 초기 조회(RSC)와 TanStack Query 캐시, Supabase Realtime INSERT 구독을 조합했습니다.

작업 결과

  • 내 메시지는 DB 응답 전 낙관적 업데이트로 즉시 보이며, 저장 결과와 Realtime 이벤트는 메시지 ID로 중복 없이 합쳐집니다.
  • 메시지가 많아져도 채팅 카드 내부 목록만 스크롤되며, 사이드바 폭 변화에 맞춰 채팅 영역이 조정됩니다.
  • 채팅 메시지 조회·전송 RLS 정책과 chat_messages Realtime publication 등록 migration을 포함합니다.

변경 사항

Added

  • entities/chat, features/manage-chat, views/chat 기반의 채팅 도메인·UI·라우트
  • 최근 50개 메시지 및 워크스페이스 멤버 조회 API
  • Supabase Realtime 구독과 TanStack Query 캐시 동기화
  • chat_messages 멤버 조회·본인 발송 권한 및 Realtime publication migration

Changed

  • 한글 IME 조합 중 Enter 전송 방지와 일관된 한국 시간 표기 적용

Fixed

  • Realtime 이벤트와 서버 응답이 경합해도 같은 메시지가 중복 렌더링되지 않도록 보정
  • 메시지 증가 시 페이지 전체가 길어지던 레이아웃을 채팅 목록 내부 스크롤로 변경

실행화면

스크린샷 2026-07-15 오후 12 44 28
  • DB 연동 확인
스크린샷 2026-07-15 오후 12 43 41

테스트

  • 로컬 실행 확인
  • npm run typecheck
  • npm run lint
  • npm run build
  • Supabase 프로젝트에 migration 적용 후 두 세션 간 Realtime 수신 확인

리뷰 체크리스트

  • PR base branch가 올바릅니다. (feature/* -> develop, 배포 시 develop 또는 release/* -> main)
  • 브랜치명이 Type/#issue-number/description 형식을 따릅니다.
  • 커밋 메시지가 컨벤션을 따릅니다.
  • 불필요한 console.log, 주석, 임시 코드를 제거했습니다.
  • 타입 에러와 린트 에러를 확인했습니다.
  • CodeRabbit 1차 리뷰를 확인했습니다.
  • CodeRabbit 리뷰 반영 후 Discord에 공유했습니다.
  • 최소 1명 이상의 approve 후 merge합니다.

리뷰 요청사항

  • Realtime publication/RLS migration이 기존 Supabase 권한 구조와 충돌하지 않는지 확인 부탁드립니다.
  • 채팅 전송을 Server Action으로 유지한 현재 구조와 향후 직접 클라이언트 insert 전환 필요성을 검토 부탁드립니다.
  • 향후 읽음 표시 및 파일 업로드 구상 중입니다. 해당 사항에 대해서 의견 남겨주시면 감사하겠습니다

관련 이슈

Closes #60
Ref #2

Summary by CodeRabbit

  • 새로운 기능

    • 워크스페이스별 채팅 페이지를 추가했습니다.
    • 실시간 메시지 수신과 채팅 기록 조회를 지원합니다.
    • 메시지 작성, 전송, 전송 중 상태 및 최대 2,000자 입력 제한을 제공합니다.
    • 참여자 목록과 현재 사용자 표시를 추가했습니다.
    • 새 메시지에 맞춰 자동 스크롤되며, 과거 메시지 확인 중에는 위치를 유지합니다.
  • 개선 사항

    • 채팅 연결 상태와 오류를 화면에서 확인할 수 있습니다.
    • 워크스페이스 멤버만 채팅을 조회하고 메시지를 전송할 수 있도록 권한을 적용했습니다.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

워크스페이스 멤버가 채팅 메시지를 조회·전송하고 Supabase Realtime으로 새 메시지를 수신하는 기능을 추가했습니다. 서버 초기 조회, TanStack Query 캐시, 낙관적 전송, RLS 정책, 채팅 UI와 라우트를 구현합니다.

Changes

워크스페이스 채팅

Layer / File(s) Summary
RLS 및 Realtime 접근 설정
supabase/migrations/20260715100000_enable_workspace_chat_realtime.sql
chat_messages 조회·삽입 권한을 워크스페이스 멤버와 현재 발신자로 제한하고, Realtime publication에 테이블을 조건부 등록합니다.
채팅 계약 및 서버 API
src/entities/chat/model/*, src/entities/chat/api/*, src/entities/chat/index.ts
채팅 타입과 Query Key를 정의하고, 채팅방 조회 및 입력 검증·멤버 확인을 포함한 메시지 전송 API를 추가합니다.
캐시 및 Realtime 동기화
src/features/manage-chat/model/use-chat-room.ts, src/features/manage-chat/index.ts
초기 채팅 데이터를 캐시에 공급하고 Realtime INSERT를 반영하며, 낙관적 메시지 추가·교체·삭제와 오류 알림을 처리합니다.
채팅 라우트 및 화면
src/app/workspaces/[workspaceId]/chat/page.tsx, src/views/chat/*, src/features/manage-chat/ui/*
서버 라우트에서 초기 데이터를 조회하고, 연결 상태·참여자·메시지 목록·입력 및 전송 UI를 렌더링합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatView
  participant useChatRoom
  participant getChatRoom
  participant sendChatMessage
  participant SupabaseRealtime
  ChatView->>useChatRoom: 채팅방 초기화 및 메시지 전송
  useChatRoom->>getChatRoom: 초기 채팅방 조회
  useChatRoom->>sendChatMessage: 메시지 저장 요청
  SupabaseRealtime->>useChatRoom: chat_messages INSERT 이벤트
  useChatRoom->>ChatView: 캐시된 메시지와 연결 상태 반환
Loading

Possibly related PRs

  • TeampleRun/syncly#32: private.is_workspace_member 헬퍼와 Supabase RLS 구성이 이번 마이그레이션의 멤버십 정책과 직접 연결됩니다.

Suggested reviewers: 0011810, jiwoonge, wjswlgh96

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 채팅 조회·전송·실시간 수신, 캐시/Realtime 연동, RLS·publication migration, IME/시간 표기까지 이슈 목표를 충족합니다.
Out of Scope Changes check ✅ Passed 변경 사항은 채팅 도메인, 관련 UI/라우트, 쿼리/타입, 마이그레이션 범위 안에 있으며 별도 이탈 항목은 보이지 않습니다.
Title check ✅ Passed 실시간 채팅 기능 구현이라는 핵심 변경을 간결하게 잘 요약하고 있습니다.
Description check ✅ Passed 템플릿의 주요 섹션이 대부분 채워져 있고 작업 내용, 결과, 변경 사항, 테스트, 리뷰 요청사항이 구체적입니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/realtime-chat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/entities/chat/index.ts`:
- Around line 1-4: Update the chat domain entry point alongside getChatRoom to
export sendChatMessage from its API module, preserving the existing query and
type exports so upper layers can use the message-saving action without deep
imports.

In `@src/features/manage-chat/model/use-chat-room.ts`:
- Around line 147-177: The send failure handling in the mutation flow clears the
temporary message without restoring the submitted text. Update both the
!result.ok branch and catch block in the chat send handler to restore content as
the draft when the user has not entered a newer draft, or otherwise preserve the
failed message with retry support, while keeping newer user input intact.
- Around line 16-38: Update appendMessage and replaceMessage so every merge
sorts messages by createdAt in chronological order and retains only the latest
50 messages. Apply the same ordering and limit after duplicate handling, while
preserving temporary-message replacement and existing undefined-data behavior.
- Around line 107-114: Update the realtime message handlers around
chatRoomQueryKey and appendMessage so incoming DB rows are matched to the
optimistic message using the shared clientMessageId. Replace the matching
temporary message with the converted toRealtimeMessage result instead of
appending a separate message, while preserving append behavior for messages with
no matching optimistic entry; apply the same logic to the related handler at the
referenced range.

In `@src/features/manage-chat/ui/ChatMessageList.tsx`:
- Around line 33-35: Update the useEffect in ChatMessageList so new messages
only trigger scrollIntoView when the user is already near the bottom, while
preserving automatic scrolling on initial entry. Track the current scroll
position against the scroll container’s scrollHeight and clientHeight, and avoid
moving users who are reading older messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 68f6c04f-cb23-4b94-9970-fe7253b8a2b3

📥 Commits

Reviewing files that changed from the base of the PR and between 7e0a3b9 and c0bf782.

📒 Files selected for processing (13)
  • src/app/workspaces/[workspaceId]/chat/page.tsx
  • src/entities/chat/api/chat-actions.ts
  • src/entities/chat/api/get-chat-room.ts
  • src/entities/chat/index.ts
  • src/entities/chat/model/chat-query.ts
  • src/entities/chat/model/chat.types.ts
  • src/features/manage-chat/index.ts
  • src/features/manage-chat/model/use-chat-room.ts
  • src/features/manage-chat/ui/ChatComposer.tsx
  • src/features/manage-chat/ui/ChatMessageList.tsx
  • src/views/chat/index.ts
  • src/views/chat/ui/ChatView.tsx
  • supabase/migrations/20260715100000_enable_workspace_chat_realtime.sql

Comment thread src/entities/chat/index.ts
Comment thread src/features/manage-chat/model/use-chat-room.ts Outdated
Comment thread src/features/manage-chat/model/use-chat-room.ts
Comment thread src/features/manage-chat/model/use-chat-room.ts Outdated
Comment thread src/features/manage-chat/ui/ChatMessageList.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/manage-chat/ui/ChatMessageList.tsx`:
- Around line 30-51: Replace useEffect with useLayoutEffect for the
messages.length-driven scroll effect in ChatMessageList, updating the
corresponding React import. Preserve the existing shouldScrollToBottomRef guard,
bottomRef scrolling, 48px threshold, and handleScroll logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f24c3f3f-b69e-4619-82c2-0bae80bd7747

📥 Commits

Reviewing files that changed from the base of the PR and between c0bf782 and 8aac818.

📒 Files selected for processing (4)
  • src/entities/chat/api/chat-actions.ts
  • src/entities/chat/index.ts
  • src/features/manage-chat/model/use-chat-room.ts
  • src/features/manage-chat/ui/ChatMessageList.tsx

Comment on lines +30 to +51
// 메시지 영역 자체의 스크롤 위치를 판단해 과거 메시지 열람 중 자동 이동을 막습니다.
const scrollContainerRef = useRef<HTMLDivElement>(null);
// 새 메시지 전송·수신 뒤 최근 대화를 보이게 하는 스크롤 기준 요소입니다.
const bottomRef = useRef<HTMLDivElement>(null);
// 최초 진입 또는 하단 근처일 때만 자동 스크롤하도록 기억하는 상태입니다.
const shouldScrollToBottomRef = useRef(true);

useEffect(() => {
if (!shouldScrollToBottomRef.current) return;

bottomRef.current?.scrollIntoView({ block: 'end' });
shouldScrollToBottomRef.current = true;
}, [messages.length]);

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

// 48px 이내면 사용자가 대화 하단을 보고 있다고 판단합니다.
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
shouldScrollToBottomRef.current = distanceFromBottom < 48;
};

Copy link
Copy Markdown

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

최초 마운트 시 스크롤 깜빡임 방지: useLayoutEffect 사용 권장

useEffect는 브라우저 페인트 이후 실행되므로, 메시지가 많은 상태로 최초 마운트될 때 대화 상단이 잠깐 보였다가 하단으로 스크롤되는 깜빡임이 발생할 수 있습니다. 페인트 전에 스크롤 위치를 확정하려면 useLayoutEffect로 교체하는 것이 좋습니다. 나머지 스크롤 판단 로직(48px 임계값, handleScroll)은 과거 지적된 강제 스크롤 문제를 잘 해결했습니다.

💡 제안 diff
-  useEffect(() => {
+  useLayoutEffect(() => {
     if (!shouldScrollToBottomRef.current) return;

     bottomRef.current?.scrollIntoView({ block: 'end' });
     shouldScrollToBottomRef.current = true;
   }, [messages.length]);

(파일 상단의 react import에서 useEffectuseLayoutEffect로 교체해야 합니다.)

Also applies to: 71-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/manage-chat/ui/ChatMessageList.tsx` around lines 30 - 51,
Replace useEffect with useLayoutEffect for the messages.length-driven scroll
effect in ChatMessageList, updating the corresponding React import. Preserve the
existing shouldScrollToBottomRef guard, bottomRef scrolling, 48px threshold, and
handleScroll logic unchanged.

@JiWoongE
JiWoongE self-requested a review July 15, 2026 07:51

@JiWoongE JiWoongE left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

확인했습니다. 고생하셨습니다 !

@seongjinss555
seongjinss555 merged commit aa580e1 into develop Jul 15, 2026
1 check 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.

2 participants