Conversation
- AI 요약 결과 조회, 수정, 발행 기능 구현 - TopicSummaryCard: 구조화된 개별 input으로 주요포인트 편집 - FormPageHeader: children, onBack prop 추가 - 발행 성공 시 DetailPage로 이동 - AiLoadingOverlay SVG 직접 import 방식으로 변경 - summary 관련 목데이터 추가
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
WalkthroughAI 요약 워크플로우를 추가합니다. STT 작업 훅(useCreateSttJob)과 모의 API/데이터를 도입하고, 요약 관련 UI(요약 배너 SummaryInfoBanner, 주제별 편집 카드 TopicSummaryCard)와 결과 페이지를 추가·교체하며 인라인 SVG 아이콘을 외부 이미지로 교체하고 FormPageHeader의 props 타입을 엄격화합니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CreatePage as MeetingRetrospectiveCreatePage
participant API as retrospectives.api
participant ResultPage as MeetingRetrospectiveResultPage
participant Backend as Backend
User->>CreatePage: AI 요약 시작(파일/IDs)
CreatePage->>API: createSttJob(req, signal)
alt USE_MOCK
API->>API: 2초 지연(AbortSignal 처리)
API-->>CreatePage: SttJobResponse(status: DONE)
else 실제 API
API->>Backend: STT 작업 생성 요청
Backend-->>API: 작업 응답
API-->>CreatePage: SttJobResponse
end
CreatePage->>ResultPage: navigate(..., { fromAiSummary: true })
ResultPage->>API: getSummary(meetingId)
API-->>ResultPage: RetrospectiveSummaryResponse
User->>ResultPage: 편집/추가/삭제
ResultPage->>API: updateSummary(meetingId, data)
API-->>ResultPage: 업데이트된 요약
User->>ResultPage: 발행
ResultPage->>API: publishSummary(meetingId)
API-->>ResultPage: 발행 완료
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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.
Actionable comments posted: 5
🧹 Nitpick comments (1)
src/shared/components/FormPageHeader.tsx (1)
12-15: JSDoc과 타입 정의가 불일치합니다.주석에 "children 미사용 시 필수"라고 명시되어 있지만, 타입은
optional입니다. → 실제로 둘 다 누락해도 컴파일 에러가 발생하지 않아 액션 영역이 비어버릴 수 있습니다. → 런타임 경고를 추가하거나, 타입 레벨에서childrenvsactionLabel+onAction중 하나를 강제하는 discriminated union을 고려해보세요.🔧 타입 레벨 강제 예시 (discriminated union)
-export interface FormPageHeaderProps { - /** 페이지 제목 */ - title: string - /** 액션 버튼 텍스트 (children 미사용 시 필수) */ - actionLabel?: string - /** 액션 버튼 클릭 핸들러 (children 미사용 시 필수) */ - onAction?: () => void +interface FormPageHeaderBaseProps { + title: string isActionDisabled?: boolean to?: string onBack?: () => void className?: string - children?: React.ReactNode } + +interface FormPageHeaderWithAction extends FormPageHeaderBaseProps { + actionLabel: string + onAction: () => void + children?: never +} + +interface FormPageHeaderWithChildren extends FormPageHeaderBaseProps { + actionLabel?: never + onAction?: never + children: React.ReactNode +} + +export type FormPageHeaderProps = FormPageHeaderWithAction | FormPageHeaderWithChildren🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/components/FormPageHeader.tsx` around lines 12 - 15, The JSDoc claims "actionLabel and onAction required when children is not used" but the types for FormPageHeader currently make actionLabel?: string and onAction?: () => void optional, allowing an empty action area; update the props for the FormPageHeader component to enforce at the type level by creating a discriminated union between (a) props with children: ReactNode (no actionLabel/onAction required) and (b) props without children that require actionLabel: string and onAction: () => void, or alternatively keep current types but add a runtime check in FormPageHeader (checking children vs actionLabel/onAction) that throws or logs a clear warning if both are missing; reference the prop names actionLabel, onAction, children and the component FormPageHeader when making the change so code consumers and the component implementation are updated accordingly.
🤖 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/components/TopicSummaryCard.tsx`:
- Around line 100-101: The map over displayKeyPoints uses the array index
(kpIndex) as the React key which causes input/focus mismatches when items are
added/removed; update the local state shape for key points to include a stable
temporary id (e.g., id or uuid) when items are created/initialized, change the
mapping to use that stable id as the key (replace key={kpIndex} with
key={kp.id}), and refactor any add/remove/update handlers in TopicSummaryCard
(and any functions that push new key points) to preserve and operate on the id;
when preparing payloads for the API, strip out the temporary id and send only
the required fields ({ title, details }) as the comment suggests.
In `@src/features/retrospectives/retrospectives.api.ts`:
- Around line 81-88: The mock abort path uses reject(new DOMException(...))
which yields error.message != 'canceled' so the UI treats it as a real error;
modify the USE_MOCK branch to reject with an Error whose message is 'canceled'
(or otherwise normalize to match axios abort behavior) when
signal?.addEventListener('abort'...) fires (the block around signal and the
timeout in retrospectives.api.ts), ensuring the rejection mirrors axios's
canceled error so the UI suppression logic works.
In `@src/features/retrospectives/retrospectives.mock.ts`:
- Around line 181-280: Current mock uses a single global mockSummaryData shared
across meetings; update it to a meeting-scoped map (Record<number,
RetrospectiveSummaryResponse>) and change getMockSummary, mockUpdateSummary, and
mockPublishSummary to accept a meetingId parameter and operate on the entry for
that meeting (create a cloned default entry if missing). Specifically, replace
the global mockSummaryData with something like mockSummaryByMeeting:
Record<number, RetrospectiveSummaryResponse>, then in getMockSummary(meetingId)
return structuredClone(mockSummaryByMeeting[meetingId] ||
defaultCloneFor(meetingId)); in mockUpdateSummary(meetingId, data) find and
update only mockSummaryByMeeting[meetingId].topics (preserve other meetings),
and in mockPublishSummary(meetingId) set isPublished and publishedAt on
mockSummaryByMeeting[meetingId]; ensure all returns still use structuredClone to
avoid shared references.
In `@src/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx`:
- Around line 33-34: The component currently calls useSummary(mId) but only
destructures data and isLoading; update the hook call to also extract isError
(and error if available) from useSummary, add a render branch that shows an
error message and a retry action/component (e.g., using the existing Error/Retry
UI or a simple button that refetches) when isError is true, and change the
empty-state rendering so it only shows when summaryData is present and
summaryData.topics.length === 0; reference useSummary, summaryData, isLoading,
isError and the empty-state render path to locate where to add the new error
branch and the retry handler.
- Around line 46-51: Replace the direct history mutation in the useEffect that
checks fromAiSummary with a React Router navigation call: instead of
window.history.replaceState({}, ''), call the router navigate API (use the
navigate function from useNavigate) with the current location and options {
replace: true, state: null } so Router-managed location/state is cleared; keep
the existing showToast('독서 모임 내용 요약이 완료됐어요') and ensure useEffect depends on
fromAiSummary and that navigate is imported/available in the component.
---
Nitpick comments:
In `@src/shared/components/FormPageHeader.tsx`:
- Around line 12-15: The JSDoc claims "actionLabel and onAction required when
children is not used" but the types for FormPageHeader currently make
actionLabel?: string and onAction?: () => void optional, allowing an empty
action area; update the props for the FormPageHeader component to enforce at the
type level by creating a discriminated union between (a) props with children:
ReactNode (no actionLabel/onAction required) and (b) props without children that
require actionLabel: string and onAction: () => void, or alternatively keep
current types but add a runtime check in FormPageHeader (checking children vs
actionLabel/onAction) that throws or logs a clear warning if both are missing;
reference the prop names actionLabel, onAction, children and the component
FormPageHeader when making the change so code consumers and the component
implementation are updated accordingly.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/shared/assets/icon/ai-gradient.svgis excluded by!**/*.svg,!**/*.svg
📒 Files selected for processing (13)
src/features/retrospectives/components/AiGradientIcon.tsxsrc/features/retrospectives/components/AiLoadingOverlay.tsxsrc/features/retrospectives/components/SummaryInfoBanner.tsxsrc/features/retrospectives/components/TopicSummaryCard.tsxsrc/features/retrospectives/components/index.tssrc/features/retrospectives/retrospectives.api.tssrc/features/retrospectives/retrospectives.mock.tssrc/pages/Retrospectives/MeetingRetrospectiveCreatePage.tsxsrc/pages/Retrospectives/MeetingRetrospectivePage.tsxsrc/pages/Retrospectives/MeetingRetrospectiveResultPage.tsxsrc/pages/Retrospectives/index.tssrc/routes/index.tsxsrc/shared/components/FormPageHeader.tsx
💤 Files with no reviewable changes (2)
- src/pages/Retrospectives/MeetingRetrospectivePage.tsx
- src/features/retrospectives/components/AiGradientIcon.tsx
- 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으로 타입 강화
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/features/retrospectives/components/TopicSummaryCard.tsx (1)
125-128:⚠️ Potential issue | 🟠 Major상세 항목에 index key를 쓰면 편집 중 입력값이 섞일 수 있습니다.
문제: Line 127에서
key={detailIndex}를 사용하고 있습니다.
영향: 상세 추가/삭제 시 DOM 재사용으로 포커스나 입력값이 다른 항목으로 이동할 수 있습니다.
대안: 상세 항목도 로컬에서id를 부여한 구조({ id, value })로 관리하고, key/수정/삭제를 id 기준으로 처리한 뒤 저장 시string[]로 직렬화하세요.#!/bin/bash # Verify index-key usage on editable detail rows in TopicSummaryCard. rg -n -C2 "map\\(\\(detail, detailIndex\\)|key=\\{detailIndex\\}|handleAddDetail|handleRemoveDetail" src/features/retrospectives/components/TopicSummaryCard.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/retrospectives/components/TopicSummaryCard.tsx` around lines 125 - 128, The map over kp.details using key={detailIndex} in TopicSummaryCard (the block mapping kp.details) can cause input/ focus mixups when rows are added/removed; change the details data shape to objects like {id, value} and update TopicSummaryCard's handlers (e.g., handleAddDetail, handleRemoveDetail, and the edit/update logic that references detailIndex) to operate by id, use detail.id as the React key in the JSX map, and on save/serialize convert the array back to string[] so storage shape remains unchanged.
🤖 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/components/index.ts`:
- Line 6: The export line uses "export { type EditableKeyPoint,default as
TopicSummaryCard } from './TopicSummaryCard'" which triggers Prettier due to the
missing space after the comma; update the export to include a space after the
comma so it reads "export { type EditableKeyPoint, default as TopicSummaryCard }
from './TopicSummaryCard'" (adjusting the export statement that references
EditableKeyPoint and TopicSummaryCard).
In `@src/features/retrospectives/retrospectives.api.ts`:
- Around line 82-88: In the mock STT wait Promise, first check signal?.aborted
and immediately reject if true, attach the abort listener with { once: true }
when calling signal.addEventListener to avoid accumulating listeners, and ensure
you clean up the listener when the timer completes by storing the listener
function and removing it (or using the once option) before resolving; keep
clearing the timeout on abort as-is and preserve rejecting with new
Error('canceled') to maintain existing behavior.
In `@src/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx`:
- Around line 30-33: Validate meetingId after converting to number: replace the
naive Number(meetingId) usage that sets mId (and the similar usages around the
other occurrences) with an explicit check using Number.isInteger(mId) && mId > 0
before calling useSummary or rendering the summary UI; if validation fails,
short-circuit to an error state or perform a safe redirect/render an error
component instead of attempting to fetch/assuming empty data. Locate the
conversion and fetch call sites (the mId assignment and subsequent
useSummary(mId) invocation in MeetingRetrospectiveResultPage and the analogous
blocks at the other mentioned occurrences) and add the integer/positive guard,
branching to the error/redirect flow when the guard fails.
---
Duplicate comments:
In `@src/features/retrospectives/components/TopicSummaryCard.tsx`:
- Around line 125-128: The map over kp.details using key={detailIndex} in
TopicSummaryCard (the block mapping kp.details) can cause input/ focus mixups
when rows are added/removed; change the details data shape to objects like {id,
value} and update TopicSummaryCard's handlers (e.g., handleAddDetail,
handleRemoveDetail, and the edit/update logic that references detailIndex) to
operate by id, use detail.id as the React key in the JSX map, and on
save/serialize convert the array back to string[] so storage shape remains
unchanged.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/features/retrospectives/components/TopicSummaryCard.tsxsrc/features/retrospectives/components/index.tssrc/features/retrospectives/retrospectives.api.tssrc/features/retrospectives/retrospectives.mock.tssrc/pages/Retrospectives/MeetingRetrospectiveResultPage.tsxsrc/shared/components/FormPageHeader.tsx
- 상세 항목(details)에도 EditableDetail 타입으로 stable id 적용 (추가/삭제 시 input 포커스 혼동 방지) - handleSaveEdit에서 EditableDetail[] → string[]으로 직렬화 처리 - mock STT abort: aborted 즉시 감지, once 옵션으로 리스너 중복 방지, resolve 시 리스너 정리 - meetingId 유효성 검사 강화 (Number.isInteger && > 0)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx (1)
103-103: Non-null assertion 사용 검증 필요문제:
gatheringId!와meetingId!에서 non-null assertion을 사용하고 있습니다.영향: Line 128에서 조기 반환하므로 실제로는 안전하지만, 코드 가독성과 타입 안전성 측면에서 개선 여지가 있습니다.
대안: 조기 반환 후에는 타입이 좁혀지지 않으므로, validated 변수를 별도로 선언하거나 현재 방식을 유지해도 무방합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx` at line 103, The call to navigate(ROUTES.MEETING_RETROSPECTIVE_DETAIL(gatheringId!, meetingId!)) uses non-null assertions on gatheringId and meetingId; replace them by explicitly narrowing/validating the values before use: after the existing early-return check that ensures both exist, assign validatedGatheringId = gatheringId and validatedMeetingId = meetingId (or otherwise guard with if (!gatheringId || !meetingId) return) and then call navigate(ROUTES.MEETING_RETROSPECTIVE_DETAIL(validatedGatheringId, validatedMeetingId)) to avoid the `!` assertions while preserving the same control flow and types.
🤖 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/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx`:
- Around line 186-197: The build fails because topics is a union type
(SummaryTopic[] | EditableSummaryTopic[]) but TopicSummaryCard.topic expects
SummaryTopic; add a type guard or conditional mapping so you only pass
SummaryTopic to TopicSummaryCard: when rendering topics.map, check whether each
topic is a SummaryTopic (or whether isEditing is false) and only pass topic to
TopicSummaryCard if it satisfies SummaryTopic, otherwise either cast after a
runtime check or render a different component for EditableSummaryTopic; update
the map logic around topics, TopicSummaryCard, isEditing, editedTopics,
handleSummaryChange, and handleKeyPointsChange so the prop types align and
TS2322 is resolved.
---
Nitpick comments:
In `@src/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx`:
- Line 103: The call to
navigate(ROUTES.MEETING_RETROSPECTIVE_DETAIL(gatheringId!, meetingId!)) uses
non-null assertions on gatheringId and meetingId; replace them by explicitly
narrowing/validating the values before use: after the existing early-return
check that ensures both exist, assign validatedGatheringId = gatheringId and
validatedMeetingId = meetingId (or otherwise guard with if (!gatheringId ||
!meetingId) return) and then call
navigate(ROUTES.MEETING_RETROSPECTIVE_DETAIL(validatedGatheringId,
validatedMeetingId)) to avoid the `!` assertions while preserving the same
control flow and types.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/features/retrospectives/components/TopicSummaryCard.tsxsrc/features/retrospectives/components/index.tssrc/features/retrospectives/retrospectives.api.tssrc/pages/Retrospectives/MeetingRetrospectiveResultPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/retrospectives/components/index.ts
- guard를 핸들러 정의 이전으로 이동해 gatheringId/meetingId non-null assertion 제거 - topic prop을 항상 SummaryTopic[]으로 유지해 union 타입 불일치 해결 - EditableDetail/EditableKeyPoint를 components/index.ts에서 함께 re-export
* [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>
🚀 풀 리퀘스트 제안
📋 작업 내용
AI 요약 결과를 조회·수정·발행하는 약속 회고 결과 페이지를 구현했습니다.
🔧 변경 사항
childrenprop으로 커스텀 액션 영역 지원,onBackprop 추가📸 스크린샷 (선택 사항)
📄 기타
관련 이슈: #93
PR #100 병합 후 rebase하여 CreatePage UI와 STT 로직을 통합했습니다.
수정 모드 구현 참고
수정 API의 요청 형식이
{ title: string, details: string[] }[]구조로 고정되어 있어, 디자인의 단일 Textarea 방식(자유 텍스트 입력)으로는 파싱 신뢰성 문제가 발생합니다. 이에 따라 주요포인트 수정 영역을 구조화된 개별 input으로 구현하되, 하나의 border 박스 안에 통합하여 시각적으로 단일 입력 영역처럼 보이도록 처리했습니다. 포인트 및 상세 항목의 추가·삭제도 지원합니다.Summary by CodeRabbit
New Features
Refactor
Chores