Skip to content

[feat] 약속회고 조회 및 댓글 기능 구현 #103

Merged
haruyam15 merged 9 commits intodevelopfrom
feat/meeting-retro-detail-96
Mar 6, 2026
Merged

[feat] 약속회고 조회 및 댓글 기능 구현 #103
haruyam15 merged 9 commits intodevelopfrom
feat/meeting-retro-detail-96

Conversation

@haruyam15
Copy link
Contributor

@haruyam15 haruyam15 commented Mar 2, 2026

🚀 풀 리퀘스트 제안

📋 작업 내용

  1. 약속 회고 상세 조회 기능 구현
  • 주제별 회고를 탭별로 조회 할 수 있음
  1. 약속회고 댓글 조회/삭제 기능 구현
  • 댓글 목록 조회 (무한 스크롤, 커서 기반 페이지네이션)
  • 댓글 작성
  • 댓글 삭제 (약속장 및 댓글 작성자만 가능)
  • Textarea 자동 높이 조절 기능

📸 스크린샷 (선택 사항)

image image

Summary by CodeRabbit

출시 노트

  • 새로운 기능

    • 회고 의견 섹션 추가 — 무한 스크롤로 의견 조회, 작성 및 삭제(작성자/리더 권한) 지원, 로딩 스켈레톤 포함
    • 회의 회고 상세 페이지 추가 — 주제별 탭과 키포인트 요약 제공, 회의 정보와 날짜 표시 개선
  • 컴포넌트 업데이트

    • Textarea에 comment 형식 추가(동적 높이)
    • Input에 문자 카운터 토글 추가
  • UI 개선

    • Tabs 세로 패딩 조정

@haruyam15 haruyam15 self-assigned this Mar 2, 2026
@haruyam15 haruyam15 added the feat 새로운 기능 추가 label Mar 2, 2026
@haruyam15 haruyam15 linked an issue Mar 2, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

회의 회고 모듈을 재구성하고 미팅 회고 관련 기능을 meeting 서브모듈로 집중했습니다. 미팅 회고의 댓글 CRUD(목록 페이징·무한스크롤, 생성, 삭제), 관련 훅·API·타입·모의데이터, 날짜 포맷 유틸, UI 변화(Textarea/Input 카운터 등), 페이지 경로 재배치가 추가되었습니다.

Changes

Cohort / File(s) Summary
루트 배럴 변경
src/features/retrospectives/index.ts
기존의 광범위한 재내보내기(components, hooks, .api, .types 등)를 축소하고 export * from './meeting'export * from './personal'로 재구성.
미팅 회고 엔트리포인트
src/features/retrospectives/meeting/index.ts
meeting 하위의 컴포넌트, 훅, endpoints, lib 및 API/타입을 통합 재내보내기하여 meeting 전용 공개면 생성.
댓글 UI 컴포넌트
src/features/retrospectives/meeting/components/RetrospectiveComments.tsx, ...RetrospectiveCommentsSkeleton.tsx, ...components/index.ts
무한 스크롤 댓글 목록, 작성 폼, 삭제(권한: 리더 또는 작성자), 로딩 스켈레톤 컴포넌트 추가 및 재내보내기.
회고 훅
src/features/retrospectives/meeting/hooks/*
React Query 기반 훅 추가: useRetrospectiveComments(무한쿼리, 커서페이징), useCreateRetrospectiveComment, useDeleteRetrospectiveComment, useMeetingRetrospectiveDetail 및 queryKeys 확장.
API / 엔드포인트 / 목 데이터 / 타입
src/features/retrospectives/meeting/retrospectives.api.ts, ...retrospectives.endpoints.ts, ...retrospectives.mock.ts, ...retrospectives.types.ts
COMMENTS/DETAIL/COMMENT_DELETE 엔드포인트 추가, getComments/createComment/deleteComment/getMeetingRetrospectiveDetail 구현(모의 동작 포함), 관련 타입과 대량의 목 데이터/유틸 추가.
날짜 유틸 및 lib 재내보내기
src/features/retrospectives/meeting/lib/dateFormatters.ts, ...lib/index.ts
한글 로케일 형식의 날짜 포맷터 추가 및 lib 재내보내기.
공유 UI 변경
src/shared/ui/Textarea.tsx, src/shared/ui/Input.tsx, src/shared/ui/Tabs.tsx
Textarea: counter?: boolean, `format?: 'default'
페이지 경로 재구성
src/pages/Retrospectives/index.ts, src/pages/Retrospectives/meeting/*, src/pages/Meetings/MeetingDetailPage.tsx
회고 페이지들을 meeting 서브디렉토리로 집결. MeetingDetailPage의 RetrospectiveCardButtons import 경로 업데이트. MeetingRetrospectiveDetailPage가 새로 추가되어 댓글 UI 포함.
상수 추가
src/shared/constants/pagination.ts
RETROSPECTIVE_COMMENTS = 10 추가.

Sequence Diagram

sequenceDiagram
    participant User as 사용자
    participant UI as RetrospectiveComments
    participant Query as useRetrospectiveComments
    participant API as retrospectives.api
    participant Server as 서버

    User->>UI: 페이지 진입 (meetingId)
    UI->>Query: useRetrospectiveComments(meetingId)
    Query->>API: getComments({meetingId, pageSize, cursor})
    API->>Server: GET /meetings/:id/retrospectives/comments
    Server-->>API: {items, hasNext, nextCursor, totalCount}
    API-->>Query: 응답 반환(캐시)
    Query-->>UI: 댓글 데이터 렌더링

    User->>UI: 새 댓글 입력 후 제출
    UI->>API: createComment({meetingId, content})
    API->>Server: POST /meetings/:id/retrospectives/comments
    Server-->>API: createdComment
    API->>Query: invalidate commentsList(meetingId)
    Query->>API: (재요청) getComments
    API-->>Query: 갱신된 댓글 목록
    Query-->>UI: UI 갱신

    User->>UI: 댓글 삭제 요청 (권한 확인)
    UI->>API: deleteComment({meetingId, commentId})
    API->>Server: DELETE /meetings/:id/retrospectives/:commentId
    Server-->>API: OK
    API->>Query: invalidate commentsList(meetingId)
    Query->>API: (재요청) getComments
    API-->>Query: 갱신된 댓글 목록
    Query-->>UI: UI 갱신
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • mgYang53
  • choiyoungae

주요 검토 포인트

문제 → 영향 → 대안

  • 문제: 루트 배럴(@/features/retrospectives)의 공개 API가 축소되어 기존에 직접 참조하던 API/타입/훅/컴포넌트들이 더이상 재내보내기되지 않음
    영향: 기존 import 문(@/features/retrospectives/...)들이 깨질 수 있으며 소비자 코드 전반에서 경로 수정 필요.
    대안: 기존 배럴을 임시로 유지하거나 마이그레이션 가이드/리팩토링 스크립트 제공.

  • 문제: 댓글 무한스크롤은 커서 기반 페이징(생성/삭제 시 커서 무효화/겹침 가능성)과 관련된 동기화 로직 다수 존재
    영향: 중복/누락 또는 페이지 경계에서의 시각적 불일치(특히 생성 직후와 삭제 직후) 발생 가능.
    대안: 통합된 캐시 무효화/갱신 전략(예: optimistic update 또는 getNextPageParam/transform 일관화)과 유닛/통합 테스트 추가.

  • 문제: Textarea에 format="comment"로 인한 자동 높이 조절 및 Input/Textarea의 counter 기본 변경(기존 폼 영향 가능)
    영향: 기존 폼 레이아웃/스타일이 예기치 않게 변할 가능성.
    대안: 기본 동작을 기존과 동일하게 유지(명시적 opt-in으로 comment 모드 사용)하거나 변경 내용에 대한 전역 승인 및 스타일 검증 수행.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.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의 주요 변경 사항을 정확히 요약합니다. 약속회고 상세 조회와 댓글 기능 구현이 PR의 핵심 목표와 일치합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/meeting-retro-detail-96

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (1)
src/features/retrospectives/meeting/retrospectives.api.ts (1)

210-236: USE_MOCK 환경에서 댓글 작성/삭제만 실제 API를 호출합니다

문제: Line 210-236은 USE_MOCK 분기가 없어 목환경에서도 네트워크 POST/DELETE를 바로 호출합니다.
영향: 로컬/스토리북/목테스트 환경에서 댓글 기능이 실패하거나 테스트가 불안정해집니다.
대안: retrospectives.mock.ts에 생성/삭제 헬퍼를 추가하고 읽기 API와 동일하게 mock 분기를 맞춰주세요.

수정 예시
 export const createComment = async (
   params: CreateCommentParams
 ): Promise<CreateCommentResponse> => {
   const { meetingId, comment } = params
+  if (USE_MOCK) {
+    await new Promise((resolve) => setTimeout(resolve, 200))
+    return createMockComment(meetingId, comment)
+  }

   return api.post<CreateCommentResponse>(RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId), {
     comment,
   })
 }

 export const deleteComment = async (params: DeleteCommentParams): Promise<void> => {
   const { meetingId, commentId } = params
+  if (USE_MOCK) {
+    await new Promise((resolve) => setTimeout(resolve, 200))
+    deleteMockComment(meetingId, commentId)
+    return
+  }

   return api.delete<void>(RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/retrospectives/meeting/retrospectives.api.ts` around lines 210 -
236, The createComment and deleteComment functions currently always call the
real API; add the same USE_MOCK branching used by the read APIs so mocks are
used in mock environments: inside createComment (function name: createComment)
and deleteComment (function name: deleteComment) check the USE_MOCK flag and,
when true, delegate to the corresponding mock helpers you should add to
retrospectives.mock.ts (e.g., createCommentMock/deleteCommentMock or similar),
otherwise call api.post to RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId) and
api.delete to RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId) as
before; ensure signatures and returned Promise types match CreateCommentResponse
and void so callers are unaffected.
🤖 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/retrospectives/meeting/components/RetrospectiveComments.tsx`:
- Around line 49-55: The current render uses "isLoading || !commentsData" so
when fetching fails the skeleton keeps showing; update the RetrospectiveComments
component to branch on "isError" from useRetrospectiveComments (in addition to
isLoading, data, etc.) and render an error state that shows an error message and
a retry control that calls "refetch"; specifically, locate the
useRetrospectiveComments call and the rendering blocks that reference
commentsData/isLoading and replace the single loading-or-empty branch with three
branches: isLoading -> skeleton, isError -> error UI with a button wired to
refetch, otherwise -> render commentsData pages; apply the same change to the
similar block referenced around the 154-156 area.
- Around line 115-117: The canDelete function treats currentUserId falsy values
(e.g., 0) as not logged in; change the guard so only null/undefined are
rejected: in the canDelete function replace the falsy check with an explicit
null/undefined check (e.g., currentUserId == null or currentUserId === null ||
currentUserId === undefined) so users with ID 0 can still be recognized, keeping
the rest of the logic comparing currentUserId to meetingLeaderId and
commentUserId unchanged.

In
`@src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx`:
- Around line 21-22: RetrospectiveCommentsSkeleton에서 사용 중인
[...Array(count).keys()]는 count가 음수거나 소수일 때 런타임 예외를 발생시킬 수 있으니, 렌더링 전에 count 변수를
0 이상의 정수로 보정하세요 (예: undefined/null을 0으로, 음수는 0으로, 소수는 내림). 즉
RetrospectiveCommentsSkeleton 컴포넌트 내에서 count를 정규화한 로컬 변수(예: normalizedCount)로
치환한 뒤 [...Array(normalizedCount).keys()].map(...)를 사용하거나 Prop 기본값/타입 체크를 추가해
안전하게 배열을 생성하도록 수정하세요.

In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts`:
- Around line 26-31: The isValid check currently uses !Number.isNaN(meetingId)
which allows Infinity; change the validation used for the enabled flag to use
Number.isFinite so Infinity is rejected. Update the isValid definition
(referencing meetingId and isValid) used by useQuery
(retrospectiveQueryKeys.detail / getMeetingRetrospectiveDetail / enabled) to use
Number.isFinite(meetingId) && meetingId > 0 so the hook doesn't enable queries
for Infinity or other non-finite values.

In `@src/features/retrospectives/meeting/lib/dateFormatters.ts`:
- Line 39: The example in the doc comment for formatToDateWithDay is showing the
wrong weekday; update the example string from '2026.01.15(월)' to '2026.01.15(목)'
so the sample matches the actual date for '2026-01-15T09:30:00Z'—locate the
comment near the formatToDateWithDay function in dateFormatters.ts and change
only the example text.

In `@src/features/retrospectives/meeting/retrospectives.endpoints.ts`:
- Around line 26-27: The COMMENT_DELETE endpoint currently builds
`${API_PATHS.MEETINGS}/${meetingId}/retrospectives/${commentId}` which
mismatches the COMMENTS resource namespace; update COMMENT_DELETE to use the
same namespace as COMMENTS by changing the path to
`${API_PATHS.MEETINGS}/${meetingId}/retrospectives/comments/${commentId}` so
deletes target retrospectives/comments/{commentId} and align with the COMMENTS
route.

In `@src/features/retrospectives/meeting/retrospectives.mock.ts`:
- Around line 269-271: 토픽 제목 '선과 악, 현실과 연결고리'에 대한 topicDescription 값이 문장이 잘려있고
맥락과 맞지 않으므로 해당 객체의 topicDescription 문자열을 토픽 의미에 맞는 완결된 문장으로 교체하세요; 대상은
retrospectives.mock.ts에서 topicTitle이 '선과 악, 현실과 연결고리'인 항목의 topicDescription 필드이며
예를 들어 “선과 악의 경계가 현실에서 어떻게 연결되고 충돌하는지를 성찰하는 내용”처럼 토픽의 주제를 요약하는 자연스러운 한 문장으로 바꿔 주면
됩니다.

In `@src/pages/Retrospectives/meeting/MeetingRetrospectiveDetailPage.tsx`:
- Line 66: The Tabs component is given a defaultValue built from
data.topics[0]?.topicId which yields "topic-undefined" when topics is empty;
change the render flow in MeetingRetrospectiveDetailPage to check data.topics
(e.g., if (!data.topics || data.topics.length === 0)) and render a dedicated
empty-state UI instead of rendering <Tabs
defaultValue={`topic-${data.topics[0]?.topicId}`}>; ensure any code referencing
topicId (data.topics[0].topicId) is only executed after confirming topics has at
least one element so defaultValue is always valid.
- Around line 24-25: The current parsing uses Number.isFinite and a separate ===
0 guard which allows negative/decimal IDs through; update the validation for
parsedGatheringId and parsedMeetingId so gatheringId and meetingId are set only
if parsed values are positive integers (e.g. Number.isInteger(parsedX) &&
parsedX > 0), otherwise treat them as invalid (null/undefined or trigger an
early return/redirect to a 404 or error state) and ensure the subsequent code
path that checks for missing data (the !data spinner branch) is not left waiting
for requests with invalid IDs; adjust any checks that currently rely on === 0
accordingly.

In `@src/shared/ui/Textarea.tsx`:
- Around line 112-115: In the Textarea component (Textarea.tsx) the class list
currently always includes 'custom-scroll' causing comment-only scroll styling to
leak to other formats; update the className array (the expression that builds
the classes for the textarea element) so that 'custom-scroll' is only added when
format === 'comment' (i.e., change the unconditional 'custom-scroll' entry to a
conditional one alongside the existing format === 'comment' check), ensuring
disabled and other class conditions remain unchanged.
- Around line 60-97: The comment-mode auto-resize is broken because the
component mixes the generic prop height (height=180) with a fixed maxHeight
(maxHeight: '128px') and still compares scrollHeight against height; fix this by
introducing a baseHeight used when format === 'comment' (e.g., const baseHeight
= format === 'comment' ? 48 : height), update handleInput to reset and compare
against baseHeight (reset target.style.height = `${baseHeight}px` and compare
scrollHeight > baseHeight), and use baseHeight in computedStyle for the comment
branch (height: `${baseHeight}px`, keep maxHeight: '128px'); update references
in handleInput, computedStyle, and any places reading height so comment mode
relies on baseHeight not the generic height prop.

---

Nitpick comments:
In `@src/features/retrospectives/meeting/retrospectives.api.ts`:
- Around line 210-236: The createComment and deleteComment functions currently
always call the real API; add the same USE_MOCK branching used by the read APIs
so mocks are used in mock environments: inside createComment (function name:
createComment) and deleteComment (function name: deleteComment) check the
USE_MOCK flag and, when true, delegate to the corresponding mock helpers you
should add to retrospectives.mock.ts (e.g., createCommentMock/deleteCommentMock
or similar), otherwise call api.post to
RETROSPECTIVES_ENDPOINTS.COMMENTS(meetingId) and api.delete to
RETROSPECTIVES_ENDPOINTS.COMMENT_DELETE(meetingId, commentId) as before; ensure
signatures and returned Promise types match CreateCommentResponse and void so
callers are unaffected.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a773eb2 and 2560b43.

📒 Files selected for processing (39)
  • src/features/retrospectives/index.ts
  • src/features/retrospectives/meeting/components/AiGradientIcon.tsx
  • src/features/retrospectives/meeting/components/AiLoadingOverlay.tsx
  • src/features/retrospectives/meeting/components/AiSummaryToast.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveCardButtons.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveSummarySkeleton.tsx
  • src/features/retrospectives/meeting/components/index.ts
  • src/features/retrospectives/meeting/hooks/index.ts
  • src/features/retrospectives/meeting/hooks/retrospectiveQueryKeys.ts
  • src/features/retrospectives/meeting/hooks/useCollectedAnswers.ts
  • src/features/retrospectives/meeting/hooks/useCreateRetrospectiveComment.ts
  • src/features/retrospectives/meeting/hooks/useCreateSttJob.ts
  • src/features/retrospectives/meeting/hooks/useDeleteRetrospectiveComment.ts
  • src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts
  • src/features/retrospectives/meeting/hooks/usePublishSummary.ts
  • src/features/retrospectives/meeting/hooks/useRetrospectiveComments.ts
  • src/features/retrospectives/meeting/hooks/useSummary.ts
  • src/features/retrospectives/meeting/hooks/useUpdateSummary.ts
  • src/features/retrospectives/meeting/index.ts
  • src/features/retrospectives/meeting/lib/dateFormatters.ts
  • src/features/retrospectives/meeting/lib/index.ts
  • src/features/retrospectives/meeting/retrospectives.api.ts
  • src/features/retrospectives/meeting/retrospectives.endpoints.ts
  • src/features/retrospectives/meeting/retrospectives.mock.ts
  • src/features/retrospectives/meeting/retrospectives.types.ts
  • src/features/retrospectives/retrospectives.mock.ts
  • src/pages/Meetings/MeetingDetailPage.tsx
  • src/pages/Retrospectives/MeetingRetrospectiveDetailPage.tsx
  • src/pages/Retrospectives/index.ts
  • src/pages/Retrospectives/meeting/MeetingRetrospectiveCreatePage.tsx
  • src/pages/Retrospectives/meeting/MeetingRetrospectiveDetailPage.tsx
  • src/pages/Retrospectives/meeting/MeetingRetrospectivePage.tsx
  • src/pages/Retrospectives/meeting/index.ts
  • src/shared/constants/pagination.ts
  • src/shared/ui/Input.tsx
  • src/shared/ui/Tabs.tsx
  • src/shared/ui/Textarea.tsx
💤 Files with no reviewable changes (3)
  • src/features/retrospectives/retrospectives.mock.ts
  • src/pages/Retrospectives/MeetingRetrospectiveDetailPage.tsx
  • src/features/retrospectives/index.ts

- 댓글 조회 실패 시 에러 토스트 및 안내 메시지 표시.
- currentUserId null 체크로 삭제 권한 확인 로직 개선.
- 스켈레톤 count 음수값 방어 처리.
- Textarea format별 height 기본값 적용.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts (1)

29-29: ⚠️ Potential issue | 🟡 Minor

enabled 조건이 비정상 숫자(Infinity)를 통과시킵니다.

문제: Line 29에서 meetingId > 0만 검사해 Infinity(및 정수가 아닌 양수)를 허용합니다.
영향: 잘못된 ID로 상세 조회 요청이 나가 4xx/불필요 재시도 등 UX 저하를 유발할 수 있습니다.
대안: Number.isFinite(+ 필요 시 Number.isInteger)를 함께 사용해 enabled를 제한하세요.

🔧 제안 수정
-    enabled: meetingId > 0,
+    enabled: Number.isFinite(meetingId) && Number.isInteger(meetingId) && meetingId > 0,
#!/bin/bash
# enabled 조건이 유한/정수 검증을 포함하는지 확인
rg -n -C2 "enabled:\s*" src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts
rg -n "Number\.isFinite\(meetingId\)|Number\.isInteger\(meetingId\)" src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts

As per coding guidelines, "queryKey 안정성, enabled 조건 ... 중점적으로 봐줘."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts`
at line 29, The enabled check currently only uses "meetingId > 0" and allows
non-finite values like Infinity; update the enabled predicate in
useMeetingRetrospectiveDetail to ensure meetingId is a finite (and optionally
integer) positive value by combining Number.isFinite(meetingId) (and
Number.isInteger(meetingId) if you require integers) with the > 0 check so the
hook only fires for valid IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts`:
- Line 29: The enabled check currently only uses "meetingId > 0" and allows
non-finite values like Infinity; update the enabled predicate in
useMeetingRetrospectiveDetail to ensure meetingId is a finite (and optionally
integer) positive value by combining Number.isFinite(meetingId) (and
Number.isInteger(meetingId) if you require integers) with the > 0 check so the
hook only fires for valid IDs.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2560b43 and 0dbdf73.

📒 Files selected for processing (4)
  • src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx
  • src/features/retrospectives/meeting/hooks/useMeetingRetrospectiveDetail.ts
  • src/shared/ui/Textarea.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/retrospectives/meeting/components/RetrospectiveComments.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveCommentsSkeleton.tsx
  • src/shared/ui/Textarea.tsx

약속 회고 파일 구조를 meeting 폴더로 재구성하면서 develop 브랜치의 변경사항 병합:

- 개인 회고 기능 추가 (personal 폴더)
- Summary 관련 컴포넌트 및 함수 추가 (SummaryInfoBanner, TopicSummaryCard)
- 파일 구조 재정리: retrospectives를 meeting/personal로 분리
- MeetingRetrospectiveResultPage를 meeting 폴더로 이동
- AiGradientIcon 삭제 (develop에서 제거됨)
- 모든 import 경로를 meeting 구조에 맞게 수정

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/retrospectives/meeting/retrospectives.api.ts`:
- Around line 256-264: createComment and deleteComment currently always call the
real API; add the same USE_MOCK branch used by getComments so mock flows are
used in tests. Specifically, update createComment and deleteComment to check
USE_MOCK and, when true, invoke the corresponding mock handlers (e.g.,
mockCreateComment / mockDeleteComment or existing mock logic used by
getComments) and return the same CreateCommentResponse/DeleteCommentResponse
shape instead of calling api.post/api.delete against
RETROSPECTIVES_ENDPOINTS.COMMENTS (and the delete endpoint). Ensure the mock
path mirrors getComments' behavior and only the real api.* calls run when
USE_MOCK is false.

In `@src/features/retrospectives/meeting/retrospectives.mock.ts`:
- Around line 421-423: The function getMockMeetingRetrospectiveDetail currently
ignores meetingId and returns the same object reference; change its signature to
accept a meetingId parameter, create a deep copy via
structuredClone(mockMeetingRetrospectiveDetail), set the copied object's
meetingId to the provided meetingId, and return the cloned object so callers
receive distinct instances; reference getMockMeetingRetrospectiveDetail and the
mockMeetingRetrospectiveDetail constant when making the change.

ℹ️ Review info
Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 779ea60b-7810-4dae-9d4a-47865113a9fb

📥 Commits

Reviewing files that changed from the base of the PR and between 0dbdf73 and 388e795.

📒 Files selected for processing (12)
  • src/features/retrospectives/index.ts
  • src/features/retrospectives/meeting/components/AiLoadingOverlay.tsx
  • src/features/retrospectives/meeting/components/RetrospectiveCardButtons.tsx
  • src/features/retrospectives/meeting/components/SummaryInfoBanner.tsx
  • src/features/retrospectives/meeting/components/TopicSummaryCard.tsx
  • src/features/retrospectives/meeting/components/index.ts
  • src/features/retrospectives/meeting/retrospectives.api.ts
  • src/features/retrospectives/meeting/retrospectives.mock.ts
  • src/pages/Retrospectives/index.ts
  • src/pages/Retrospectives/meeting/MeetingRetrospectiveCreatePage.tsx
  • src/pages/Retrospectives/meeting/MeetingRetrospectiveResultPage.tsx
  • src/pages/Retrospectives/meeting/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/Retrospectives/index.ts
  • src/pages/Retrospectives/meeting/index.ts

Copy link

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/features/retrospectives/meeting/retrospectives.api.ts`:
- Around line 232-235: The mock branch is dropping meetingId when calling mock
helpers, causing different meetings to share comment state; update the calls in
retrospectives.api.ts to pass the meetingId into getMockComments,
mockCreateComment, and mockDeleteComment, and update the mock helpers'
signatures accordingly (e.g., getMockComments(meetingId, pageSize?,
cursorCreatedAt?, cursorCommentId?), mockCreateComment(meetingId, comment),
mockDeleteComment(meetingId, commentId)) so mock state is keyed by meetingId and
comment operations affect only the intended meeting.

ℹ️ Review info
Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e6d27df-be8a-49ff-aada-c70ee55d9d9f

📥 Commits

Reviewing files that changed from the base of the PR and between 388e795 and faafeff.

📒 Files selected for processing (2)
  • src/features/retrospectives/meeting/retrospectives.api.ts
  • src/features/retrospectives/meeting/retrospectives.mock.ts

- window.confirm을 글로벌 모달로 변경
- Mock 함수에서 불필요한 meetingId 파라미터 제거
- Textarea 컴포넌트에서 사용하지 않는 ref 제거.
Copy link
Contributor

@mgYang53 mgYang53 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다! 👍

@haruyam15 haruyam15 merged commit 6c9d135 into develop Mar 6, 2026
1 check passed
@haruyam15 haruyam15 deleted the feat/meeting-retro-detail-96 branch March 6, 2026 09:18
mgYang53 added a commit that referenced this pull request Mar 7, 2026
* [refactor] 사전의견 폴더 병합 (#92)

* refactor: 사전의견 관련 폴더 병합 (#91)

* fix: SubPageHeader 그림자 prop 추가 (#91)

* refactor: 코드 포매팅 (#91)

* fix: 빌드 에러 수정 (#91)

* fix: 개인 회고 nullable 필드 처리 및 목 모드 조기 반환 추가 (#91)

* chore: 개인회고 관련 파일 삭제 (#91)

* feat: 약속 회고 API 레이어 구축 (#94) (#95)

* feat: 약속 회고 API 레이어 구축 (#94)

STT Job 생성, 요약 조회/수정/발행 API 함수 및 React Query 훅 구현.
AbortController 기반 STT 요청 취소 지원, setQueryData 캐시 전략 적용.

* style: prettier 포맷 수정 (#94)

* refactor: 코드 리뷰 반영 (#94)

- api 함수 api.* 헬퍼로 통일
- useCreateSttJob race condition 수정
- useCreateSttJob 언마운트 시 자동 취소
- KeyPointUpdateRequest 타입 alias로 변경

* feat: 에러 코드 추가 및 사전 의견 버튼 disabled 처리 (#97) (#98)

신규 에러 코드 5개(E000, M016, M017, R106, R108) 추가.
MyMeetingListItem에 preOpinionTemplateConfirmed 필드 추가 후,
메인페이지 예정 약속의 사전 의견 작성 버튼 disabled 처리 반영.

* [feat] 약속회고 생성 전 플로우 UI 및 기능 개발 (#100)

* feat: 약속 완료 시 약속 상세 페이지 UI 변경(#87)

* feat: 약속 회고 상세 페이지 추가 (#87)

* feat:아코디언 컴포넌트 추가(#87)

* design: 아코디언 컴포넌트css 수정(#87)

* feat: 수집된 사전의견 조회 api 개발(#87)

* feat: Dropzone 공통컴포넌트 생성 및 파일 업로드 기능구현(#87)

* design: AI요약 버튼 디자인 수정(#87)

* style: 프리티어(#87)

* refactor: 파일 업로드 검증 강화 및 의존성 정리 (#87)

Dropzone 파일 타입 검증 기능 추가 및 radix-ui 의존성 정리로 코드 품질 개선.

- Dropzone 파일 타입 검증 로직 추가
- 파일 타입 불일치 시 onTypeRejected 콜백 호출
- alert를 toast로 변경하여 일관된 에러 처리
- radix-ui 제거 후 @radix-ui/react-accordion 추가
- Accordion, AlertIcon 컴포넌트 코드 정리
- Import/export 경로 정리 및 불필요한 mock export 제거

* refactor: 에러처리 개선 (#87)

* design: 아코디언css 수정 (#87)

* refactor: GetCollectedAnswersParams 배럴 추가(#87)

* style: 주석제거(#87)

* refactor: AlertIcon 색 커스텀 기능 추가(#87)

* refactor: 회고 아이콘을 SVG 파일로 전환 (#87)

- MeetingRetroIcon, PersonalRetroIcon 컴포넌트를 SVG 파일로 대체
- useCollectedAnswers 훅의 유효성 검사 강화 (정수 체크, pageSize 검증)
- MeetingDetailPage 에러 처리 중복 실행 방지

* refactor: AlertIcon적용 (#87)

* refactor: AlertIcon size prop추가(#87)

* refactor: 주제 목록 에러 처리 개선 (#87)

* fix: 버튼 프로퍼티수정(#87)

* feat: 회고 상태 기반 라우트 분기 처리 추가 (#87)

* feat: 목데이터에 회고상태  추가(#87)

* refactor: params검증로직 페이지에서 처리(#87)

* [feat] 개인회고 작성 및 조회 화면 구현 (#101)

* feat: 개인 회고 작성 화면 구현 (#90)

* feat: 개인 회고 전송 기능 구현 (#90)

* feat: 개인회고 조회 화면 구현 (#90)

* feat: 개인회고 삭제, 수정 구현 (#90)

* chore 미사용 유틸리티 제거 (#90)

* refactor: 코드 포매팅 (#90)

* fix: 코드리뷰 반영 (#90)

* fix: 코드 리뷰 반영 (#90)

* refactor: 코드 포매팅 (#90)

* refactor: 개인회고 폴더 정리 (#90)

* fix: 코드 리뷰 반영 (#90)

* feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93) (#102)

* feat: 약속 회고 결과 페이지 UI 및 기능 개발 (#93)

- AI 요약 결과 조회, 수정, 발행 기능 구현
- TopicSummaryCard: 구조화된 개별 input으로 주요포인트 편집
- FormPageHeader: children, onBack prop 추가
- 발행 성공 시 DetailPage로 이동
- AiLoadingOverlay SVG 직접 import 방식으로 변경
- summary 관련 목데이터 추가

* fix: 코드 리뷰 반영 (#93)

- TopicSummaryCard 편집 모드 keyPoints map key를 인덱스에서 stable id로 교체
  (EditableKeyPoint 타입 추가, crypto.randomUUID()로 id 생성)
- mock abort 에러를 DOMException에서 Error('canceled')로 변경해 axios 취소 동작 일치
- 회고 요약 목데이터를 전역 단일 객체에서 meetingId별 Record로 리팩터링
- MeetingRetrospectiveResultPage에 isError 처리 및 다시 시도 버튼 추가
- window.history.replaceState를 navigate(location.pathname, replace)로 교체
- FormPageHeader props를 discriminated union으로 타입 강화

* style: prettier 포맷 수정 (#93)

* fix: 코드 리뷰 반영 2차 (#93)

- 상세 항목(details)에도 EditableDetail 타입으로 stable id 적용
  (추가/삭제 시 input 포커스 혼동 방지)
- handleSaveEdit에서 EditableDetail[] → string[]으로 직렬화 처리
- mock STT abort: aborted 즉시 감지, once 옵션으로 리스너 중복 방지, resolve 시 리스너 정리
- meetingId 유효성 검사 강화 (Number.isInteger && > 0)

* fix: 코드 리뷰 반영 3차 (#93)

- guard를 핸들러 정의 이전으로 이동해 gatheringId/meetingId non-null assertion 제거
- topic prop을 항상 SummaryTopic[]으로 유지해 union 타입 불일치 해결
- EditableDetail/EditableKeyPoint를 components/index.ts에서 함께 re-export

* [fix] 책 상세 페이지, 내 책장 페이지 API 스펙 반영 (#104)

* refactor: 감상 기록 액션 메뉴 분리 (#59)

* refactor: Book/Keywords API 목데이터와 엔드포인트 분리 (#59)

* design: 감상 기록 액션 메뉴 관련 z-index 조정 (#59)

* feat: 별점 필터링에 0점 추가 (#59)

* refactor: 내 책장 무한스크롤 시 useInfiniteScroll 활용 (#59)

* feat: 내 책장에 등록된 책이 없을 경우 툴팁 추가 (#59)

* feat: 기록 타임라인의 사전의견에 답변이 없는 주제 처리 추가 (#59)

* refactor: sticky한 헤더에 대해 drop-shadow-bottom 및 useScrollCollapse 활용 (#59)

* fix: 책 타임라인 api 반영 (#59)

* fix: 책 리스트 조회 api 반영 (#59)

* fix: 책 리스트 삭제 api 반영 및 디자인 수정 (#59)

* fix: 도서 API 스펙 변경에 따른 타입 및 컴포넌트 업데이트 (#59)

* fix: 약속 회고 아이템에서 미연동 액션 메뉴 제거 (#59)

* feat: 도서 읽기 상태 토글에 낙관적 업데이트 적용 (#59)

* refactor: 코드 포매팅 (#59)

* fix: 메인페이지 ReadingBooksSection API 파라미터 및 응답 필드 타입 오류 수정 (#59)

* fix: 내책장 편집 모드 진입 시 filteredBookIds 초기화 (#59)

* fix: mock API 파라미터 계약 및 라우트 상수 관련 수정 (#59)

* fix: PRE_OPINION 아이템 React key 중복 가능성 수정 (#59)

* refactor: RootLayout에서 불필요한 overflow-x-clip 제거 (#59)

* refactor: BookLogList 삭제 로직을 useBookLogDeleteActions 훅으로 분리 (#59)

* [feat] 약속회고 조회 및 댓글 기능 구현  (#103)

* feat: 약속 회고상세 UI개발(#96)

* refactor: 회고 관련 파일 구조 meeting 폴더로 재구성 (#96)

* feat: 댓글 Textarea 높이조절 기능구현(#96)

* feat: 약속회고 댓글 기능 구현 (#96)

- 약속회고 상세 페이지에 댓글 작성/삭제 기능 추가.
- 무한 스크롤로 댓글 목록 조회하며, 약속장 및 댓글 작성자만 삭제 가능.

* fix: 약속회고 댓글 에러 처리 및 안정성 개선 (#96)

- 댓글 조회 실패 시 에러 토스트 및 안내 메시지 표시.
- currentUserId null 체크로 삭제 권한 확인 로직 개선.
- 스켈레톤 count 음수값 방어 처리.
- Textarea format별 height 기본값 적용.

* fix: 약속회고 댓글/상세 조회 mock 처리 개선 (#96)

* refactor: 약속회고 댓글 삭제 확인 모달 개선 및 코드 정리 (#96)

- window.confirm을 글로벌 모달로 변경
- Mock 함수에서 불필요한 meetingId 파라미터 제거
- Textarea 컴포넌트에서 사용하지 않는 ref 제거.

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>

* [feat] 페이지 접근 권한 전역 관리 구현 (#105)

* feat: 페이지 접근 권한 전역 관리 구현 (#99)

인터셉터에서 권한 에러 감지 시 커스텀 DOM 이벤트를 dispatch하고,
RootLayout의 usePermissionRedirect 훅에서 토스트와 홈 리다이렉트를
처리하여 한 곳에서 통합 관리.

- errors.ts: PAGE_ACCESS_ERROR_CODES Set 추가 (6개 에러 코드)
- interceptors.ts: permission-denied 커스텀 이벤트 dispatch
- usePermissionRedirect: 이벤트 → 토스트 + navigate(HOME)
- RootLayout: usePermissionRedirect 훅 연결
- MeetingRetrospectiveCreatePage: 이중 내비게이션 방지

* fix: 권한 에러 중복 이벤트 억제 및 핸들러 방어 코드 추가 (#99)

* fix: 권한 에러 시 중복 토스트/모달 방지 (#99)

* fix: 약속상세 페이지의 사전의견 버튼에 링크 연동(#106) (#107)

---------

Co-authored-by: choiyoungae <109134495+choiyoungae@users.noreply.github.com>
Co-authored-by: haeun <110523397+haruyam15@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 약속회고 조회 및 댓글 기능 구현

2 participants