Skip to content

그룹채팅 구현 및 로그인 적용 - #16

Merged
minseokim0113 merged 9 commits into
devfrom
feat/group-chat-ui
Apr 8, 2026
Merged

그룹채팅 구현 및 로그인 적용#16
minseokim0113 merged 9 commits into
devfrom
feat/group-chat-ui

Conversation

@minseokim0113

@minseokim0113 minseokim0113 commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

📌 PR 설명

그룹 채팅 UI를 구현하고, 전체/그룹 채팅에 과거 메시지 조회 페이지네이션을 적용했습니다.
또한 로그인 기능이 추가된 이후 채팅 기능이 실제 인증 사용자 기준으로 동작하도록 REST/STOMP 연결을 모두 토큰 기반으로 정리했습니다.

기존에는 데모용 userId와 임시 헤더 흐름이 남아 있었고, 채팅 조회도 첫 페이지 로딩까지만 동작하던 상태였습니다.
이번 작업에서는 그룹 채팅 화면 구성, 답장 기능 UI, 로그인 기반 사용자 식별, 이전 메시지 불러오기, 도배 감지 배너 처리까지 함께 정리했습니다.


✅ 완료한 기능 명세

  • 그룹 채팅 UI 구현
  • 그룹 채팅 답장 기능 UI 구현
  • 전체 채팅/그룹 채팅 로그인 기반 인증 흐름 적용
  • STOMP CONNECT를 Authorization: Bearer <token> 기반으로 수정
  • 전체 채팅/그룹 채팅 초기 메시지 조회를 페이지네이션 응답 구조에 맞게 수정
  • 전체 채팅/그룹 채팅에서 위로 스크롤 시 이전 메시지 추가 조회 구현
  • 이전 메시지 prepend 시 스크롤 위치 유지 처리
  • 도배 감지/전송 실패 시 채팅창을 없애지 않고 배너만 표시하도록 UX 수정
  • 도배 감지 메시지 자동 해제 처리

💭 고민과 해결과정

이번 작업에서 가장 크게 신경 쓴 부분은 채팅 기능을 “임시 데모 흐름”에서 “로그인 사용자 기준 실제 동작 흐름”으로 바꾸는 것이었습니다.
기존에는 데모용 userId를 따로 받아오거나 X-User-Id를 직접 넣는 코드가 남아 있었는데, 로그인 기능이 붙은 이후에는 채팅도 다른 API와 동일하게 accessToken 기준으로 동작해야 했습니다. 그래서 REST는 공통 API 클라이언트의 Authorization 헤더를 그대로 사용하고, STOMP CONNECT 역시 Authorization: Bearer <token> 방식으로 맞춰 사용자 식별 기준을 통일했습니다.

페이지네이션은 백엔드에서 이미 messages, nextCursor, hasNext 구조로 내려주도록 바뀌어 있었기 때문에, 프론트도 그 구조를 그대로 받아 초기 메시지와 이전 메시지 로딩 흐름을 분리해서 처리했습니다.
처음 진입 시에는 최신 메시지 묶음을 가져오고, 스크롤이 상단에 닿으면 beforeMessageId=nextCursor 기준으로 이전 메시지를 다시 요청하도록 구현했습니다.

여기서 UX적으로 가장 중요했던 부분은 “이전 메시지를 불러올 때 화면이 갑자기 맨 아래로 내려가지 않게 하는 것”이었습니다.
메시지를 앞쪽에 붙이면 DOM 높이가 늘어나기 때문에, 별도 처리 없이 렌더링하면 사용자가 읽던 위치가 흔들리거나 다시 아래로 이동하게 됩니다. 이를 막기 위해 이전 메시지를 불러오기 전 스크롤 높이를 저장해두고, prepend 이후 증가한 높이만큼 scrollTop을 보정해 현재 읽던 위치를 유지하도록 처리했습니다.

또 하나의 고민은 도배 감지 에러 UX였습니다.
처음에는 에러 메시지가 들어오면 채팅 화면 전체를 에러 화면으로 바꾸는 구조였는데, 실제 사용성 측면에서는 채팅창이 유지된 상태에서 배너만 잠깐 보여주는 편이 더 자연스럽다고 판단했습니다. 그래서 전역/그룹 채팅 모두 에러를 상단 배너로만 노출하고, remainingSeconds가 있으면 그 시간 이후, 없으면 기본 시간 이후 자동으로 사라지도록 수정했습니다.

이번 작업은 단순히 그룹 채팅 화면을 만드는 것보다,
그룹 채팅 UI 구현 + 로그인 기반 인증 정리 + 채팅 히스토리 페이지네이션 UX 정리를 함께 묶어 사용자 경험을 실제 서비스 흐름에 가깝게 다듬은 작업이었습니다.


Summary by CodeRabbit

릴리스 노트

  • New Features

    • 그룹 채팅 전용 UI 컴포넌트 및 실시간 구독/수신 추가
    • 글로벌·그룹 채팅용 메시지 페이징 API 연동 및 이전 메시지 무한 스크롤 복원
    • 답장(Reply) 기능과 답장 미리보기, 입력·전송 UX 개선
  • Refactor

    • 전역/그룹 채팅 상태 관리(store/hooks) 정리 및 세션/버전 기반 동기화 강화
    • STOMP 연결·오류 표시 로직 개선(자동 해제 타이머 등)
  • Chores

    • 기본 임시 사용자 헤더 제거 및 인증 토큰 기반 처리로 정리

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

그룹 채팅 기능 추가: 그룹 메타 조회와 페이징된 메시지 API/검증, 그룹 전용 Zustand 저장소, STOMP 소켓 훅과 GroupChatRoom UI가 추가되었고, 글로벌 채팅은 페이징·무한스크롤 리팩터링 및 JWT 기반 사용자 ID 추출로 전환되었습니다. (≤50단어)

Changes

Cohort / File(s) Summary
Group Chat API
src/features/chat/api/group/fetch-group-chat-messages.ts, src/features/chat/api/group/fetch-group-chat-meta.ts
그룹 메시지/메타용 Zod 스키마와 fetch 함수 추가. 응답 래퍼 검증(apiResponseSchema), beforeMessageId 기반 페이징, 개발시 상세 Zod 오류 로깅 포함.
Global Chat API
src/features/chat/api/global/fetch-global-chat-messages.ts
글로벌 메시지용 Zod 스키마와 fetch 함수 추가/이동, 동일한 응답 검증 및 페이징 처리.
Group Chat State
src/features/chat/model/group/group-chat-store.ts
Zustand 저장소 추가: groupMeta, 메시지 목록/중복제거, nextCursor/hasNext, sendMessage/publish, loadOlderMessages(버전 토큰으로 stale 보호) 등.
Group Chat Socket
src/features/chat/model/group/use-group-chat-socket.ts
userId 변경시 초기 로드(fetchMeta/messages), STOMP 구독(/topic/chat/group/{groupId}, /user/queue/errors), 메시지 검증·append 및 오류 자동 해제 타이머 관리.
Group Chat UI
src/features/chat/ui/group-chat-room.tsx
새로운 GroupChatRoom 컴포넌트: 고정 높이 채팅, 스크롤 보존/복원, 오래된 메시지 로드 트리거, 답장 미리보기, IME 배려한 Enter 전송 등.
Global Chat Store / Hooks / UI
src/features/chat/model/global/global-chat-store.ts, src/features/chat/model/global/use-global-chat-boot.ts, src/features/chat/model/global/use-global-chat-messages-query.ts, src/features/chat/ui/global-chat-room.tsx
글로벌 채팅을 페이징으로 리팩터링: store에 nextCursor/hasNext/loadingOlder, setInitialPage/merge/중복제거, 인증(Authorization) 헤더 사용, 에러 자동 해제 타이머 조정.
User ID / Auth / baseApi
src/features/chat/model/global/use-demo-user-id.ts, src/features/chat/model/use-demo-user-id.ts, src/shared/api/base-api.ts
useDemoUserId를 localStorage JWT 디코드 방식으로 이동(이전 sessionStorage 훅 제거). baseApi에서 임시 X-User-Id 헤더 제거; Authorization 헤더 사용 유지.
App Layout imports
src/widgets/app-layout/ui/app-layout.tsx
useDemoUserId 및 useGlobalChatBoot import 경로를 model/global로 갱신.

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant UI as GroupChatRoom
    participant Store as GroupChatStore
    participant API as API 클라이언트
    participant Socket as STOMP 클라이언트
    participant Server as 채팅 서버

    User->>UI: 컴포넌트 마운트 / 입력
    UI->>API: fetchGroupChatMeta()
    API->>Server: GET /chat/group/me
    Server-->>API: group meta
    API-->>UI: GroupChatMeta

    UI->>API: fetchGroupChatMessages()
    API->>Server: GET /chat/group/messages?size=...
    Server-->>API: messages page
    API-->>UI: GroupChatMessagesPage
    UI->>Store: setInitialPage(page)

    User->>UI: 메시지 전송
    UI->>Store: sendMessage(client, content)
    Store->>Socket: PUBLISH /app/chat/group/send
    Socket->>Server: 전송
    Server->>Socket: BROADCAST /topic/chat/group/{groupId}
    Socket-->>Store: FRAME 수신
    Store->>Store: appendMessage(validatedMessage)

    User->>UI: 최상단 스크롤(이전 메시지 로드)
    UI->>Store: loadOlderMessages()
    Store->>API: fetchGroupChatMessages(beforeMessageId=nextCursor)
    API->>Server: GET older page
    Server-->>API: older page
    API-->>Store: Older page
    Store->>Store: mergeOlderMessages(중복 제거 및 상태 갱신)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

FEAT, ready-for-review

Suggested reviewers

  • hjh79gw
  • gaeunnlee

Poem

🐰 새벽에 깡총, 채팅방이 열렸네
토큰 속 서브를 꺼내 친구들 모였네
오래된 말도 불러오고 새소식 전하며
당근 한 입으로 버그를 쫓아버려요 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '그룹채팅 구현 및 로그인 적용'으로, 실제 변경 사항의 주요 내용을 명확하게 반영하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/group-chat-ui

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 and usage tips.

@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: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/chat/model/global-chat-store.ts`:
- Around line 120-138: loadOlderMessages can apply a late/stale response after
resetConnectionState; before calling fetchGlobalChatMessages(nextCursor) capture
a current session/version/token (the same value resetConnectionState updates in
use-global-chat-boot.ts) or create an AbortController, pass it into
fetchGlobalChatMessages, and after the await verify the session/version/token
(or that the request was not aborted) before calling set to update
messages/nextCursor/hasNext; if it doesn't match (stale) simply ignore the
response so logout/unmount won’t resurrect old session state.
- Around line 68-73: setInitialPage currently overwrites the whole messages
array and can drop messages appended by appendMessage during parallel boot;
change setInitialPage to merge page.messages into the existing state.messages
instead of replacing them: inside setInitialPage (the setter passed to set) read
the current messages from state, combine/merge page.messages with current
messages while deduplicating by message id (or timestamp if id absent) and
preserving chronological order, and then update nextCursor and hasNext as
before; ensure appendMessage and any consumers still work with the merged array.
- Around line 4-8: The import path is incorrect and should point to the module
that exports fetchGlobalChatMessages, GlobalChatMessage and
GlobalChatMessagesPage; update the import in global-chat-store.ts to remove the
stray "global" segment so it imports from the fetch-global-chat-messages module
in the chat api (i.e., reference the module that actually exports
fetchGlobalChatMessages, GlobalChatMessage, GlobalChatMessagesPage), then run
the TypeScript build to verify TS2307 is resolved.

In `@src/features/chat/model/group/use-group-chat-socket.ts`:
- Line 8: The import path for useGlobalChatStore is wrong; update the import in
use-group-chat-socket.ts to the correct module
"@/features/chat/model/global-chat-store" (so the symbol useGlobalChatStore
resolves) to restore the selector usage in useGroupChatSocket (references at the
useGlobalChatStore call around lines 20–21) and to ensure the client variable
used later in the socket handlers (client usages around lines ~87 and ~105) is
imported correctly.
- Around line 73-139: The effect that creates subscriptions should depend on
globalConnected so it re-runs after connection completes; update the guard from
checking client?.connected to checking globalConnected (e.g., use if
(!globalConnected || !client || !groupMeta?.groupId) to early-return) and add
globalConnected to the useEffect dependency array along with appendMessage,
client, groupMeta?.groupId, setErrorMessage so that subscriptions to
`/topic/chat/group/${groupMeta.groupId}` and `/user/queue/errors` are created
when client.onConnect flips globalConnected to true; keep existing unsubscribes
using messageSubscriptionRef and errorSubscriptionRef and retain errorTimeoutRef
cleanup.

In `@src/features/chat/model/use-global-chat-boot.ts`:
- Around line 37-41: The effect reads accessToken from localStorage once and
only depends on userId, causing STOMP client to keep using an expired token;
update the useEffect(s) in use-global-chat-boot.ts (the blocks that call
localStorage.getItem('accessToken') and check hasBootstrappedRef.current/userId)
to either include accessToken in the dependency array or subscribe to the
'auth-changed' event so the effect re-runs on token refresh; ensure the code
paths that create/configure the STOMP client and set the Authorization header
(referenced in the same effect and the other occurrences around lines 68-71 and
157-166) read the updated token when the effect re-triggers and clean up the
previous client via the existing teardown logic.

In `@src/features/chat/ui/global-chat-room.tsx`:
- Around line 55-70: The restore logic in useLayoutEffect relies on whether
older messages were actually prepended, but loadOlderMessages() swallows
failures/no-ops so the refs (shouldRestoreScrollRef, previousHeightRef) can
remain set and cause a jump when later messages render; update either the store
method loadOlderMessages() to return a boolean indicating success (true when
messages were prepended) and use that return value to decide whether to set
shouldRestoreScrollRef/previousHeightRef, or immediately clear
shouldRestoreScrollRef.current and previousHeightRef.current when
loadOlderMessages() fails/no-ops (ensure you update the callers of
loadOlderMessages() and the useLayoutEffect condition around
shouldRestoreScrollRef/previousHeightRef accordingly).
- Line 4: Import for useGlobalChatStore is using an extra "global/" segment;
update the import to point to the actual module by replacing
"@/features/chat/model/global/global-chat-store" with
"@/features/chat/model/global-chat-store" where useGlobalChatStore is imported
(also check and fix the analogous wrong imports in files that import the group
store and use-group-chat-socket so they reference
"@/features/chat/model/group-chat-store" or
"@/features/chat/model/use-group-chat-socket" as appropriate).

In `@src/features/chat/ui/group-chat-room.tsx`:
- Around line 232-236: The onKeyDown handler in the GroupChatRoom input sends
messages even when IME composition is active; update the handler (the input in
the GroupChatRoom component that currently calls handleSendMessage on Enter) to
first check event.nativeEvent.isComposing and return/skip if true, then only
call handleSendMessage when !isComposing and event.key === 'Enter' so Enter
during Korean/Japanese/Chinese composition does not prematurely submit.
- Around line 148-159: The list item keys use `${msg.messageId}-${index}`, which
causes key churn when older messages are prepended (see loadOlderMessages) and
forces unnecessary remounts; update the rendering in the GroupChatRoom message
map to use only the stable `msg.messageId` as the React key (remove `-
${index}`) for both the centered system message branch and the regular message
branch so keys remain stable after prepend operations.
- Line 4: The import uses a wrong module path
('@/features/chat/model/global/global-chat-store') which doesn't exist; update
the import in the three affected files to reference the actual module
('@/features/chat/model/global-chat-store') so that the useGlobalChatStore
import resolves correctly (search for useGlobalChatStore or the bad import
string in group-chat-room.tsx, global-chat-room.tsx, and
use-group-chat-socket.ts and replace the path).
- Around line 240-244: The button's disabled logic must match the actual send
condition used in group-chat-store.sendMessage (which checks client?.connected);
update the UI to disable the send button when the STOMP client isn't actually
connected by referencing the store's real client connection flag (e.g., expose
or derive clientConnected from the group chat store or read
store.client?.connected) and use that in the button's disabled prop (alongside
!message.trim()), ensuring handleSendMessage and sendMessage will only be
callable when both message.trim() and the actual clientConnected are true.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34e4a6ca-5bea-42c1-86e4-842d9bab722c

📥 Commits

Reviewing files that changed from the base of the PR and between d86b8e3 and 8e3290a.

📒 Files selected for processing (11)
  • src/features/chat/api/fetch-global-chat-messages.ts
  • src/features/chat/api/group/fetch-group-chat-messages.ts
  • src/features/chat/api/group/fetch-group-chat-meta.ts
  • src/features/chat/model/global-chat-store.ts
  • src/features/chat/model/global/use-demo-user-id.ts
  • src/features/chat/model/group/group-chat-store.ts
  • src/features/chat/model/group/use-group-chat-socket.ts
  • src/features/chat/model/use-global-chat-boot.ts
  • src/features/chat/model/use-global-chat-messages-query.ts
  • src/features/chat/ui/global-chat-room.tsx
  • src/features/chat/ui/group-chat-room.tsx

Comment thread src/features/chat/model/global/global-chat-store.ts
Comment thread src/features/chat/model/global/global-chat-store.ts Outdated
Comment thread src/features/chat/model/global/global-chat-store.ts
Comment thread src/features/chat/model/group/use-group-chat-socket.ts
Comment thread src/features/chat/model/group/use-group-chat-socket.ts Outdated
Comment thread src/features/chat/ui/global-chat-room.tsx
Comment thread src/features/chat/ui/group-chat-room.tsx
Comment thread src/features/chat/ui/group-chat-room.tsx Outdated
Comment thread src/features/chat/ui/group-chat-room.tsx Outdated
Comment thread src/features/chat/ui/group-chat-room.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

🧹 Nitpick comments (3)
src/features/chat/ui/group-chat-room.tsx (1)

137-139: 상태 표시와 실제 전송 조건 간 불일치 가능성

global-chat-room.tsx와 동일하게, 연결 상태 표시는 group store의 connected를 사용하지만 전송 버튼은 client?.connected를 사용합니다. 일관성을 위해 표시도 client?.connected를 사용하는 것을 고려해 보세요.

♻️ 수정 예시
         <div className="text-sm font-medium text-gray-500">
-          {connected ? 'Connected' : 'Connecting...'}
+          {client?.connected ? 'Connected' : 'Connecting...'}
         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/ui/group-chat-room.tsx` around lines 137 - 139, The UI
shows connection status using the group store's connected variable but the send
button checks client?.connected, creating a potential mismatch; update the
status display in group-chat-room.tsx to use client?.connected (the same signal
used by the send action) or vice versa so both the div with {connected ?
'Connected' : 'Connecting...'} and the send button reference the same source
(client?.connected or connected) to keep display and send eligibility
consistent.
src/features/chat/ui/global-chat-room.tsx (1)

128-131: 상태 표시와 실제 전송 조건 간 불일치 가능성

Line 129의 연결 상태 표시는 store의 connected 상태를 사용하지만, Line 87과 217의 실제 전송 로직은 client?.connected를 사용합니다. 동기화 타이밍 차이로 인해 "Connected"가 표시되지만 전송 버튼은 비활성화된 상태가 잠시 발생할 수 있습니다.

일관성을 위해 표시도 client?.connected를 사용하는 것을 고려해 보세요.

♻️ 수정 예시
         <div className="text-sm font-medium text-gray-500">
-          {connected ? 'Connected' : 'Connecting...'}
+          {client?.connected ? 'Connected' : 'Connecting...'}
         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/ui/global-chat-room.tsx` around lines 128 - 131, The
displayed connection status uses the store value connected while send logic
checks client?.connected, causing a transient mismatch; update the UI render to
use client?.connected (or a unified getter that returns client?.connected)
instead of the store connected so the status text ("Connected"/"Connecting...")
matches the actual send-enabled condition used in sendMessage/send handlers
(look for client?.connected checks near the send logic).
src/features/chat/model/group/use-group-chat-socket.ts (1)

103-124: 에러 메시지 파싱에 스키마 검증 추가를 고려하세요.

메시지 구독(Line 88)에서는 groupChatMessageSchema.safeParse()를 사용하지만, 에러 구독(Line 105)에서는 타입 단언(as ChatErrorMessage)만 사용합니다. 서버가 예상치 못한 형식을 보내면 런타임에서 undefined 접근 오류가 발생할 수 있습니다.

♻️ 스키마 검증 적용 예시
import { z } from 'zod'

const chatErrorMessageSchema = z.object({
  code: z.string(),
  message: z.string(),
  remainingSeconds: z.number().optional()
})

// Line 103-105 변경
const parsed = chatErrorMessageSchema.safeParse(JSON.parse(frame.body))
if (!parsed.success) {
  console.error('채팅 에러 메시지 파싱 실패')
  return
}
const error = parsed.data
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/model/group/use-group-chat-socket.ts` around lines 103 -
124, The error subscription currently type-asserts the parsed frame body to
ChatErrorMessage (errorSubscriptionRef handler) which can crash if the server
sends unexpected shape; add a zod schema (e.g., chatErrorMessageSchema) and
replace the direct cast with
chatErrorMessageSchema.safeParse(JSON.parse(frame.body)), check parsed.success
and bail with a console.error if it fails, then use parsed.data for
code/message/remainingSeconds and keep the existing timeout/clear logic in the
errorSubscriptionRef callback inside useGroupChatSocket.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/chat/model/group/group-chat-store.ts`:
- Around line 110-136: The loadOlderMessages function can overwrite newer
session state because it lacks a sessionVersion guard; before calling
fetchGroupChatMessages capture const { sessionVersion: sv } = get(), then after
the await verify get().sessionVersion === sv and if not, abort (return false)
without calling set; apply the same check in the catch path to avoid clearing
loadingOlder for a different session; reference loadOlderMessages,
fetchGroupChatMessages, get, set, sessionVersion, and mergeMessages when making
this change.

---

Nitpick comments:
In `@src/features/chat/model/group/use-group-chat-socket.ts`:
- Around line 103-124: The error subscription currently type-asserts the parsed
frame body to ChatErrorMessage (errorSubscriptionRef handler) which can crash if
the server sends unexpected shape; add a zod schema (e.g.,
chatErrorMessageSchema) and replace the direct cast with
chatErrorMessageSchema.safeParse(JSON.parse(frame.body)), check parsed.success
and bail with a console.error if it fails, then use parsed.data for
code/message/remainingSeconds and keep the existing timeout/clear logic in the
errorSubscriptionRef callback inside useGroupChatSocket.

In `@src/features/chat/ui/global-chat-room.tsx`:
- Around line 128-131: The displayed connection status uses the store value
connected while send logic checks client?.connected, causing a transient
mismatch; update the UI render to use client?.connected (or a unified getter
that returns client?.connected) instead of the store connected so the status
text ("Connected"/"Connecting...") matches the actual send-enabled condition
used in sendMessage/send handlers (look for client?.connected checks near the
send logic).

In `@src/features/chat/ui/group-chat-room.tsx`:
- Around line 137-139: The UI shows connection status using the group store's
connected variable but the send button checks client?.connected, creating a
potential mismatch; update the status display in group-chat-room.tsx to use
client?.connected (the same signal used by the send action) or vice versa so
both the div with {connected ? 'Connected' : 'Connecting...'} and the send
button reference the same source (client?.connected or connected) to keep
display and send eligibility consistent.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c9eb95c-fc2d-41a4-a08a-d0881b24d946

📥 Commits

Reviewing files that changed from the base of the PR and between 844c03c and 0edd45e.

📒 Files selected for processing (6)
  • src/features/chat/model/global/global-chat-store.ts
  • src/features/chat/model/global/use-global-chat-boot.ts
  • src/features/chat/model/group/group-chat-store.ts
  • src/features/chat/model/group/use-group-chat-socket.ts
  • src/features/chat/ui/global-chat-room.tsx
  • src/features/chat/ui/group-chat-room.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/features/chat/model/global/use-global-chat-boot.ts

Comment thread src/features/chat/model/group/group-chat-store.ts Outdated

@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.

🧹 Nitpick comments (1)
src/features/chat/model/group/use-group-chat-socket.ts (1)

105-126: 에러 메시지 파싱에 타입 검증이 누락되었습니다.

메시지 구독(Line 90)에서는 groupChatMessageSchema.safeParse로 검증하지만, 에러 구독에서는 as ChatErrorMessage 타입 단언을 사용합니다. 서버에서 예상치 못한 형태의 응답이 올 경우 런타임 오류가 발생할 수 있습니다.

zod 스키마를 사용한 검증 권장
+import { z } from 'zod'

+const chatErrorMessageSchema = z.object({
+  code: z.string(),
+  message: z.string(),
+  remainingSeconds: z.number().optional()
+})

 errorSubscriptionRef.current = client.subscribe('/user/queue/errors', (frame) => {
   try {
-    const error = JSON.parse(frame.body) as ChatErrorMessage
+    const parsed = chatErrorMessageSchema.safeParse(JSON.parse(frame.body))
+    if (!parsed.success) {
+      console.error('에러 메시지 스키마 검증 실패')
+      return
+    }
+    const error = parsed.data
     const timeoutMs = (error.remainingSeconds ?? 3) * 1000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/chat/model/group/use-group-chat-socket.ts` around lines 105 -
126, Replace the unchecked type assertion in the '/user/queue/errors'
subscription by validating the parsed payload with the same zod schema approach
used for group messages: parse JSON from frame.body, run
ChatErrorMessageSchema.safeParse (or a properly named zod schema for
ChatErrorMessage) and only use error.remainingSeconds, error.message,
errorTimeoutRef, setErrorMessage and FALLBACK_ERROR_TIMEOUT_MS when validation
succeeds; on validation failure log a clear parsing/validation error and bail
out without touching errorTimeoutRef or setErrorMessage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/features/chat/model/group/use-group-chat-socket.ts`:
- Around line 105-126: Replace the unchecked type assertion in the
'/user/queue/errors' subscription by validating the parsed payload with the same
zod schema approach used for group messages: parse JSON from frame.body, run
ChatErrorMessageSchema.safeParse (or a properly named zod schema for
ChatErrorMessage) and only use error.remainingSeconds, error.message,
errorTimeoutRef, setErrorMessage and FALLBACK_ERROR_TIMEOUT_MS when validation
succeeds; on validation failure log a clear parsing/validation error and bail
out without touching errorTimeoutRef or setErrorMessage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c83a8a40-74df-4426-a41b-f3e2209c2345

📥 Commits

Reviewing files that changed from the base of the PR and between 0edd45e and 9a556bb.

📒 Files selected for processing (8)
  • src/features/chat/api/global/fetch-global-chat-messages.ts
  • src/features/chat/api/group/fetch-group-chat-messages.ts
  • src/features/chat/model/global/global-chat-store.ts
  • src/features/chat/model/global/use-global-chat-boot.ts
  • src/features/chat/model/group/group-chat-store.ts
  • src/features/chat/model/group/use-group-chat-socket.ts
  • src/features/chat/ui/global-chat-room.tsx
  • src/features/chat/ui/group-chat-room.tsx
✅ Files skipped from review due to trivial changes (1)
  • src/features/chat/model/global/use-global-chat-boot.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/chat/api/global/fetch-global-chat-messages.ts
  • src/features/chat/api/group/fetch-group-chat-messages.ts
  • src/features/chat/model/global/global-chat-store.ts

@minseokim0113 minseokim0113 added FEAT 새로운 기능 추가 또는 기존 기능 확장 ready-for-review PR 리뷰 요청 labels Apr 8, 2026

@kkhhmm3103 kkhhmm3103 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.

수고하셨습니다~!~!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEAT 새로운 기능 추가 또는 기존 기능 확장 ready-for-review PR 리뷰 요청

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants