feat: 프로필 통계와 채팅 신고·차단 기능 추가#26
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
📝 WalkthroughWalkthrough프로필 통계를 실제 데이터로 표시하고 음악력 조회·추천을 추가했다. 채팅에는 사용자 차단·메시지 신고 모달과 관리 메뉴가 연결됐으며, 관련 API·캐시 처리·접근성 동작·테스트 및 CI 검증 절차가 보강됐다. Changes프로필 통계 및 음악력
사용자 차단
채팅 신고
채팅 관리 메뉴
전달 및 테스트 기반
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ChatArea
participant BlockUserModal
participant BlockUserAPI
participant ChatMessageList
User->>ChatArea: 메시지 관리 메뉴 선택
ChatArea->>BlockUserModal: 차단 대상 표시
BlockUserModal->>BlockUserAPI: 사용자 차단 요청
BlockUserAPI-->>BlockUserModal: 성공 또는 오류
BlockUserModal-->>ChatArea: 완료 상태와 닫힘 전달
ChatArea->>ChatMessageList: 차단 sender slug 필터 적용
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request configures a frontend testing environment using Vitest, jsdom, and Testing Library, and establishes a Korean Conventional Commit policy. It replaces dummy profile statistics with actual music power and queuing count data, removing the usage time field. Additionally, it implements APIs, React Query hooks, and accessible modal dialogs for retrieving/recommending music power, blocking users, and reporting chat messages, which are integrated into the chat area's message management menu. Comprehensive unit and integration tests have been added for these features. There are no review comments, so I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
@codex review |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (4)
src/features/user/profile/api/fetchMusicPower.ts (1)
6-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDTO → Domain 매핑 분리를 권장
MusicPowerResponse의targetUserSlug필드는 API 응답 메타데이터로, UI에서 직접 소비할 필요가 없습니다.useRecommendMusicPower.onSuccess에서 캐시 키로 사용하기 위해 포함된 필드이지만, API 반환 타입을 도메인 타입(예:MusicPower—musicPower,recommendedByMe만 포함)으로 정규화하고targetUserSlug는 별도 DTO 타입에 두면 계층 책임이 더 명확해집니다.As per coding guidelines
**/api/**/*.ts*: "반환 타입은 Domain(User 등)으로 정규화" — 현재MusicPowerResponse가 DTO와 도메인 타입을 겸하고 있습니다.♻️ 제안: DTO와 도메인 타입 분리
// model/types.ts export type MusicPowerResponse = { musicPower: number; recommendedByMe: boolean; targetUserSlug: string; }; + +// UI가 소비하는 도메인 타입 +export type MusicPower = { + musicPower: number; + recommendedByMe: boolean; +}; // api/fetchMusicPower.ts +import type { MusicPower } from "../model/types"; + export async function fetchMusicPower( userSlug: string, -): Promise<MusicPowerResponse> { +): Promise<MusicPower> { const { data } = await axiosInstance.get<ApiResponse<MusicPowerResponse>>( `/api/v1/user-profiles/${encodeURIComponent(userSlug)}/music-power`, ); - return unwrapApiResponse(data); + const { targetUserSlug: _, ...domain } = unwrapApiResponse(data); + return domain; }🤖 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/user/profile/api/fetchMusicPower.ts` around lines 6 - 14, Separate the DTO and domain representations used by fetchMusicPower: keep targetUserSlug in a dedicated API response DTO, but return the domain type MusicPower containing only musicPower and recommendedByMe. Update fetchMusicPower to unwrap the DTO and explicitly map it to MusicPower, and adjust useRecommendMusicPower.onSuccess to obtain the target slug from its existing request context rather than the normalized API result.Source: Path instructions
src/features/room/profile/ui/RoomProfilePanel.tsx (1)
21-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
formatStat헬퍼가ProfileStats.tsx와 완전히 동일하게 중복 정의되어 있습니다.두 feature(
room/profile,settings)에 동일한 로직이 각각 존재합니다. 공유 UI 헬퍼는src/shared에 두어야 합니다."Shared API/UI helpers live in `src/shared`" 가이드라인에 따라 제안합니다.♻️ 제안: 공유 헬퍼로 추출
// src/shared/lib/formatStat.ts export function formatStat(value: number | undefined) { return typeof value === "number" ? value.toLocaleString("ko-KR") : "-"; }-function formatStat(value: number | undefined) { - return typeof value === "number" ? value.toLocaleString("ko-KR") : "-"; -} +import { formatStat } from "`@/src/shared/lib/formatStat`";🤖 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/room/profile/ui/RoomProfilePanel.tsx` around lines 21 - 23, 중복 정의된 formatStat 헬퍼를 src/shared의 공유 유틸리티로 추출하고, RoomProfilePanel과 ProfileStats에서 해당 공유 함수를 import해 사용하도록 변경하세요. 기존 숫자 포맷팅 및 undefined 처리 동작은 그대로 유지하세요.Source: Coding guidelines
src/features/room/profile/ui/RoomProfilePanel.test.tsx (1)
42-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
as unknown as이중 캐스팅으로 목 타입 검사가 무력화됩니다.
recommendMutation이UseMutationResult의 일부 필드만 가지고 있어unknown을 거쳐 강제 캐스팅했습니다.Partial<ReturnType<typeof useRecommendMusicPower>>로 선언하면unknown없이 단일 캐스팅으로 타입 안전성을 유지할 수 있습니다."any/과도한 as 캐스팅을 지양" 지침에 따라 제안합니다.♻️ 제안 수정
-const recommendMutation = { +const recommendMutation: Partial<ReturnType<typeof useRecommendMusicPower>> = { error: null, isPending: false, mutate: vi.fn(), };- vi.mocked(useRecommendMusicPower).mockReturnValue( - recommendMutation as unknown as ReturnType<typeof useRecommendMusicPower>, - ); + vi.mocked(useRecommendMusicPower).mockReturnValue( + recommendMutation as ReturnType<typeof useRecommendMusicPower>, + );Also applies to: 85-87
🤖 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/room/profile/ui/RoomProfilePanel.test.tsx` around lines 42 - 46, Update the recommendMutation mock used by the RoomProfilePanel tests to be declared as Partial<ReturnType<typeof useRecommendMusicPower>>, then replace the existing as unknown as double casts at both referenced usages with a single appropriate cast. Preserve the mock’s existing fields and behavior while avoiding unknown or excessive type assertions.Source: Path instructions
src/features/follow/blocked/ui/BlockUserModal.tsx (1)
20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
isComplete는blockUser.isSuccess의 파생 복제 상태입니다.
useBlockUser()(useMutation)가 이미isSuccess를 제공하고,resetBlockUser()호출 시isSuccess도 함께 초기화되므로 별도의isCompletestate와onSuccess: () => setIsComplete(true)콜백은 중복입니다. 파생 가능한 값을 별도 state로 유지하면 두 상태가 어긋날 여지(예:mutate재호출 순서)를 만듭니다.♻️ 제안하는 리팩터
export default function BlockUserModal({ onClose, target }: Props) { - const [isComplete, setIsComplete] = useState(false); const closeButtonRef = useRef<HTMLButtonElement>(null); const confirmButtonRef = useRef<HTMLButtonElement>(null); const previousFocusRef = useRef<HTMLElement | null>(null); const blockUser = useBlockUser(); const resetBlockUser = blockUser.reset; const open = Boolean(target); const handleClose = useCallback(() => { if (!blockUser.isPending) { - setIsComplete(false); resetBlockUser(); onClose(); } }, [blockUser.isPending, onClose, resetBlockUser]); @@ useEffect(() => { - if (isComplete) { + if (blockUser.isSuccess) { closeButtonRef.current?.focus(); } - }, [isComplete]); + }, [blockUser.isSuccess]); @@ const handleConfirm = () => { if (blockUser.isPending) { return; } - blockUser.mutate(target.slug, { - onSuccess: () => setIsComplete(true), - }); + blockUser.mutate(target.slug); }; @@ - {isComplete ? ( + {blockUser.isSuccess ? (As per path instructions, "파생 값은 state로 두지 말고 계산(useMemo는 필요할 때만)."
Also applies to: 27-33, 52-56, 62-70, 90-106
🤖 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/follow/blocked/ui/BlockUserModal.tsx` at line 20, Remove the redundant isComplete state and its setIsComplete updates in BlockUserModal. Use blockUser.isSuccess from useBlockUser() directly for completion checks and preserve resetBlockUser() as the mutation reset path, without introducing separate derived state.Source: Path instructions
🤖 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/follow/blocked/ui/BlockUserModal.test.tsx`:
- Around line 58-78: Update the test case “요청 중 중복 제출과 닫기를 막는다” to await the
resolution of the mocked blockUser promise and flush its resulting React state
updates inside act. Ensure the completion handler and pending-state cleanup
finish before the test exits and before RTL cleanup runs, while preserving the
existing assertions and interaction flow.
In `@src/features/follow/blocked/ui/BlockUserModal.tsx`:
- Around line 52-56: Update the focus management effect around isComplete in
BlockUserModal to focus confirmButtonRef.current when the blocking operation
fails, while retaining closeButtonRef.current?.focus() for successful
completion. Use the existing blockUser.isPending and failure state to
distinguish retryable failures, ensuring keyboard and screen-reader users can
immediately retry.
In `@src/features/room/chat/model/chatMessages.ts`:
- Around line 179-196: Update getChatMessageManagementActions to return no
management actions when currentUser is null, before evaluating the message
ownership or messageKey/senderSlug checks. Ensure ChatArea’s handleBlock and
handleReport also guard against a missing currentUser, and add coverage in
chatMessages.test.ts for unauthenticated users receiving neither action.
In `@src/features/room/chat/ui/ChatArea.module.css`:
- Around line 72-84: Update the .menu positioning behavior so management menus
near the bottom of the .list scroll container are not clipped by .list or .root
overflow; either render the menu through a portal outside those containers or
implement placement that opens upward when insufficient space remains below.
In `@src/features/room/chat/ui/ChatArea.tsx`:
- Around line 93-105: Update the management menu button aria-label in ChatArea
so it includes a stable message-specific identifier, such as a concise
message-content fragment or timestamp, alongside message.senderNickname. Ensure
each rendered message menu has a distinguishable accessible name while
preserving the existing Korean label context.
In `@src/features/room/chat/ui/ReportChatMessageModal.module.css`:
- Around line 66-76: Replace the deprecated clip declaration in .visuallyHidden
with clip-path: inset(50%), preserving the existing visually hidden behavior and
all other accessibility-related styles.
In `@src/features/room/profile/ui/RoomProfilePanel.tsx`:
- Around line 91-117: Update recommendationLabel in RoomProfilePanel so it
explicitly handles !targetSlug before the musicPowerQuery.data fallback, using
the same “준비 중” wording as the follow button for the unavailable slug state;
preserve the existing labels and ordering for login, loading, query failure,
already-recommended, and pending states.
In `@src/features/settings/ui/ProfileSettingsTab.module.css`:
- Around line 258-261: Update the mobile `profileStats` styles under `@media
(max-width: 760px)` to use `grid-template-columns: 1fr` for a single-column
layout, and remove the obsolete `flex: 0 0 auto` declaration from the grid-based
`profileStats` rule.
In `@src/features/user/profile/api/musicPower.test.ts`:
- Around line 35-44: Update the recommendMusicPower test to also assert that the
function returns the mocked response payload with musicPower set to 13 and
recommendedByMe set to true, while preserving the existing encoded-URL request
assertion.
---
Nitpick comments:
In `@src/features/follow/blocked/ui/BlockUserModal.tsx`:
- Line 20: Remove the redundant isComplete state and its setIsComplete updates
in BlockUserModal. Use blockUser.isSuccess from useBlockUser() directly for
completion checks and preserve resetBlockUser() as the mutation reset path,
without introducing separate derived state.
In `@src/features/room/profile/ui/RoomProfilePanel.test.tsx`:
- Around line 42-46: Update the recommendMutation mock used by the
RoomProfilePanel tests to be declared as Partial<ReturnType<typeof
useRecommendMusicPower>>, then replace the existing as unknown as double casts
at both referenced usages with a single appropriate cast. Preserve the mock’s
existing fields and behavior while avoiding unknown or excessive type
assertions.
In `@src/features/room/profile/ui/RoomProfilePanel.tsx`:
- Around line 21-23: 중복 정의된 formatStat 헬퍼를 src/shared의 공유 유틸리티로 추출하고,
RoomProfilePanel과 ProfileStats에서 해당 공유 함수를 import해 사용하도록 변경하세요. 기존 숫자 포맷팅 및
undefined 처리 동작은 그대로 유지하세요.
In `@src/features/user/profile/api/fetchMusicPower.ts`:
- Around line 6-14: Separate the DTO and domain representations used by
fetchMusicPower: keep targetUserSlug in a dedicated API response DTO, but return
the domain type MusicPower containing only musicPower and recommendedByMe.
Update fetchMusicPower to unwrap the DTO and explicitly map it to MusicPower,
and adjust useRecommendMusicPower.onSuccess to obtain the target slug from its
existing request context rather than the normalized API result.
🪄 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: CHILL
Plan: Pro
Run ID: 0f9f2775-f985-4b9c-bb4b-e348373d196d
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (55)
.agents/skills/queuing-feature-delivery/SKILL.md.agents/skills/queuing-feature-delivery/references/delivery-policy.md.github/pull_request_template.md.github/workflows/ci.ymldocs/exec-plans/active/2026-07-12-profile-stats-moderation/api-contract.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/change-summary.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/delivery-state.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/handoff.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/implementation-notes.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/plan.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/qa-report.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/request-summary.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/ui-flow.mddocs/exec-plans/active/README.mdpackage.jsonsrc/features/follow/blocked/api/blockUser.test.tssrc/features/follow/blocked/api/blockUser.tssrc/features/follow/blocked/hooks/useBlockUser.test.tsxsrc/features/follow/blocked/hooks/useBlockUser.tssrc/features/follow/blocked/ui/BlockUserModal.module.csssrc/features/follow/blocked/ui/BlockUserModal.test.tsxsrc/features/follow/blocked/ui/BlockUserModal.tsxsrc/features/follow/model/queryKeys.tssrc/features/room/chat/api/reportChatMessage.test.tssrc/features/room/chat/api/reportChatMessage.tssrc/features/room/chat/constants/reportReasons.tssrc/features/room/chat/hooks/useReportChatMessage.tssrc/features/room/chat/model/chatMessages.test.tssrc/features/room/chat/model/chatMessages.tssrc/features/room/chat/ui/ChatArea.module.csssrc/features/room/chat/ui/ChatArea.test.tsxsrc/features/room/chat/ui/ChatArea.tsxsrc/features/room/chat/ui/ReportChatMessageModal.module.csssrc/features/room/chat/ui/ReportChatMessageModal.test.tsxsrc/features/room/chat/ui/ReportChatMessageModal.tsxsrc/features/room/page/ui/RoomPlaybackScreen.tsxsrc/features/room/profile/ui/RoomProfilePanel.module.csssrc/features/room/profile/ui/RoomProfilePanel.test.tsxsrc/features/room/profile/ui/RoomProfilePanel.tsxsrc/features/settings/ui/ProfileSettingsTab.module.csssrc/features/settings/ui/ProfileSettingsTab.tsxsrc/features/settings/ui/components/ProfileStats.test.tsxsrc/features/settings/ui/components/ProfileStats.tsxsrc/features/user/model/queryKeys.tssrc/features/user/model/types.tssrc/features/user/profile/api/fetchMusicPower.tssrc/features/user/profile/api/musicPower.test.tssrc/features/user/profile/api/recommendMusicPower.tssrc/features/user/profile/hooks/useMusicPower.tssrc/features/user/profile/hooks/useRecommendMusicPower.test.tsxsrc/features/user/profile/hooks/useRecommendMusicPower.tssrc/features/user/profile/model/types.tssrc/shared/test/testingLibrarySetup.test.tsxvitest.config.tsvitest.setup.ts
| display: grid; | ||
| flex: 0 0 auto; | ||
| align-items: flex-start; | ||
| justify-content: space-between; | ||
| grid-template-columns: repeat(2, minmax(0, 1fr)); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== CSS outline ==\n'
ast-grep outline src/features/settings/ui/ProfileSettingsTab.module.css --view expanded || true
printf '\n== CSS relevant lines ==\n'
sed -n '240,380p' src/features/settings/ui/ProfileSettingsTab.module.css
printf '\n== ProfileStats usages ==\n'
rg -n "profileStats|statItem|ProfileStats" src/features/settings -SRepository: Queuing-org/frontend
Length of output: 3967
모바일 레이아웃을 grid 기준으로 같이 수정하세요. profileStats를 grid로 바꿨는데 @media (max-width: 760px)는 아직 flex-direction: column만 바꾸고 있어서 모바일에서 1열 스택이 되지 않습니다. grid-template-columns: 1fr를 추가하고, 더 이상 의미 없는 flex: 0 0 auto는 제거하세요.
🤖 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/settings/ui/ProfileSettingsTab.module.css` around lines 258 -
261, Update the mobile `profileStats` styles under `@media (max-width: 760px)`
to use `grid-template-columns: 1fr` for a single-column layout, and remove the
obsolete `flex: 0 0 auto` declaration from the grid-based `profileStats` rule.
There was a problem hiding this comment.
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/room/profile/ui/RoomProfilePanel.tsx`:
- Around line 172-175: RoomProfilePanel의 이용 시간 placeholder를 ProfileStats.tsx와
동일하게 “개발중입니다.”로 통일하세요. 해당 컴포넌트의 cardValue 텍스트만 수정하고 주변 구조는 유지하세요.
🪄 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: CHILL
Plan: Pro
Run ID: 6ac0fab2-ca53-4f66-a391-a858e893753d
📒 Files selected for processing (23)
docs/exec-plans/active/2026-07-12-profile-stats-moderation/change-summary.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/delivery-state.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/implementation-notes.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/plan.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/qa-report.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/request-summary.mddocs/exec-plans/active/2026-07-12-profile-stats-moderation/review-findings.mddocs/exec-plans/active/README.mdsrc/features/follow/blocked/ui/BlockUserModal.test.tsxsrc/features/follow/blocked/ui/BlockUserModal.tsxsrc/features/room/chat/model/chatMessages.test.tssrc/features/room/chat/model/chatMessages.tssrc/features/room/chat/ui/ChatArea.module.csssrc/features/room/chat/ui/ChatArea.test.tsxsrc/features/room/chat/ui/ChatArea.tsxsrc/features/room/chat/ui/ReportChatMessageModal.module.csssrc/features/room/profile/ui/RoomProfilePanel.module.csssrc/features/room/profile/ui/RoomProfilePanel.test.tsxsrc/features/room/profile/ui/RoomProfilePanel.tsxsrc/features/settings/ui/components/ProfileStats.test.tsxsrc/features/settings/ui/components/ProfileStats.tsxsrc/features/user/profile/api/musicPower.test.tssrc/shared/lib/formatOptionalStat.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- docs/exec-plans/active/2026-07-12-profile-stats-moderation/change-summary.md
- src/features/user/profile/api/musicPower.test.ts
- docs/exec-plans/active/2026-07-12-profile-stats-moderation/request-summary.md
- src/features/room/chat/ui/ReportChatMessageModal.module.css
- src/features/room/profile/ui/RoomProfilePanel.test.tsx
- src/features/follow/blocked/ui/BlockUserModal.tsx
- src/features/follow/blocked/ui/BlockUserModal.test.tsx
- docs/exec-plans/active/2026-07-12-profile-stats-moderation/plan.md
- src/features/room/chat/ui/ChatArea.tsx
변경 내용
ChatArea에 회원·비회원·구형 메시지 조건별 신고/차단 메뉴와 접근 가능한 모달을 연결했습니다.npm ci → lint → test → build순서로 확장했습니다.변경 이유
프로필의 더미 통계를 실제 서버 데이터로 교체하고, 채팅에서 부적절한 사용자와 메시지를 직접 신고·차단할 수 있는 관리 흐름이 필요했습니다. API payload, React Query cache, 모달 pending/error, 메시지 식별자 호환이 한 흐름에서 어긋나지 않도록 자동 테스트와 독립 QA를 함께 추가했습니다.
영향 범위
검증
npm run lintnpm run test— 12 files, 36 tests passnpm run build검증한 주요 경계:
기능별 커밋
chore: 기능 단위 한국어 커밋 정책 추가— 배포 하네스와 실행 계획test: 프론트 컴포넌트 테스트 환경 추가— Vitest/Testing Library/CIfeat: 프로필 음악력과 큐잉 통계 표시— 프로필 통계, 음악력 API·캐시·조건 테스트feat: 사용자 차단 기능 추가— 차단 API·캐시·모달 테스트feat: 채팅 메시지 신고 기능 추가— 신고 API·payload·모달 테스트feat: 채팅 메시지 관리 메뉴 연결— 메시지 조건별 메뉴·닫힘·포커스 테스트fix: 차단 채팅과 프로필 UI 후속 수정— CodeRabbit 리뷰, 차단 메시지 즉시 제거, 이용 시간/음악력 UI 후속 변경리뷰 포인트
recommendedByMe === false가 확인된 경우에만 추천 버튼을 활성화하는 fail-closed 조건reasonpayload와 비공개 방 헤더ChatArea가 소유하는 단일 메뉴/모달 대상 상태와 식별자별 액션 판정차단된 사용자의 채팅입니다센티널을 기준으로 숨기는 방어 로직위험 및 후속 작업
추적 정보
docs/exec-plans/active/2026-07-12-profile-stats-moderation/queuing-feature-delivery,queuing-orchestrator,queuing-api-boundary,queuing-ui-flow,frontend-architecture-guardrails,queuing-qa-reviewer,github:yeetpass화면 변경
프로필 통계와 채팅 관리 메뉴/모달 UI가 변경되었습니다. 로컬 자동 테스트와 production build로 검증했으며 스크린샷은 첨부하지 않았습니다.