Files

298 lines
21 KiB
Markdown

# 커뮤니티 댓글 직접 답글 구현 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | 구현 완료 |
| 작성일 | 2026-08-06 |
| 요구사항 기준 | [prd.md](./prd.md) |
| API 기준 | [api-contract.md](./api-contract.md) |
| 현재 Phase | Phase 1 완료 |
| 현재 활성 Goal | 없음 |
## 목표
활성 커뮤니티 게시글의 답글 0개 원댓글에서도 기존 답글 form을 열어 첫 답글과
여러 직접 답글을 작성할 수 있게 한다.
## 현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---:|---|---:|---|---|
| 1 | 완료 | `4/4` | 없음 | 완료 |
- Community 답글 GET·POST, form, 여러 직접 답글 조회·작성·재조회 흐름은 이미 구현돼 있다.
- 답글이 하나 이상인 Community root에는 `답글 보기`와 추가 작성 form이 제공된다.
- `replyCount === 0`인 활성 Community root에도 `답글 작성` 진입과 기존 답글 form이 제공된다.
- `P1-T1` 구현과 test는 완료됐고, `P1-R1`에서 E2E fixture 검증 결함 후보를 실제 mock 실행 경로와 대조해 오탐으로 판정했다.
- 최종 커밋 감사에서 위 문장의 기존 표현이 실제 완료 상태와 충돌해 `CCR-REV-P1-002`로 확정됐고 `P1-R2`에서 정정했다.
## 범위의 포함·제외
### 포함
- 활성 Community root의 `replyCount === 0`일 때 `답글 작성` 버튼 표시
- 기존 답글 영역, form, GET·POST와 mutation 상태 재사용
- 같은 원댓글에 첫 답글과 여러 직접 답글 작성
- Community 첫 답글과 Audio·비활성·reply row 경계 회귀 test
- 기존 Comments Chromium mock E2E와 정적 검증
### 제외
- 새 endpoint, DTO, component, state library 또는 dependency
- form 상시 노출, reply-of-reply, payload 정책 변경
- 기존 답글 수정·삭제·pagination 리팩터링
- Audio 전용 `languageCode`의 Community payload 추가
- optimistic update와 답글 전체 선조회
## 기술적 제약
- React·TypeScript strict, Vitest·React Testing Library와 기존 Playwright 구성을 사용한다.
- [api-contract.md](./api-contract.md)의 기존 GET·POST만 사용한다.
- `CommentThread`, `CommentItem`, `CommentForm`의 현재 책임 경계를 유지한다.
- `CommentItem`의 기존 `replyActionLabel`, `CommentThread.toggleReplies()`와 reply state를 재사용한다.
- 공통 조건 한 곳에서 Audio와 Community의 첫 답글 진입을 일치시키며 target별 분기를 추가하지 않는다.
- RED → GREEN → REFACTOR 순서와 최소 변경을 지킨다.
## Phase 1. 커뮤니티 직접 답글 진입 구현·검증
**Phase 결과:** 관리자가 활성 Community의 답글 0개 원댓글에서 첫 답글을
작성하고 같은 원댓글에 여러 직접 답글을 추가하며, 기존 Audio·읽기 전용·2단계
경계가 유지된다.
**선행조건:** `CCR-001~006`과 기존 Community 댓글 GET·POST 계약 확정.
**Phase 완료 조건:** `P1-T1`, `P1-R1`, `P1-R2`, `P1-GATE` 완료와 Progress 기록.
### Task 1.1 커뮤니티 첫 답글 진입
**Goal 실행 `P1-T1`:** Community의 답글 0개 원댓글에 기존 답글 영역을 여는
`답글 작성` action을 추가하고 직접 답글 작성 흐름을 검증한다.
- **시작 조건:** [prd.md](./prd.md)의 `CCR-001~006`, [api-contract.md](./api-contract.md).
- **완료 증거:** TDD 체크박스, focused·회귀·E2E·정적 검증과 Progress 기록.
- **범위 밖:** API·mock·schema 변경, 새 UI 구조, 관련 없는 Comments 리팩터링.
**Files:**
- Modify: `src/features/comments/components/CommentThread.tsx`
- Modify: `src/features/comments/tests/comment-thread.test.tsx`
- Modify: `tests/e2e/comments.spec.ts`
- Test: `src/features/comments/tests/comment-thread.test.tsx`, `tests/e2e/comments.spec.ts`
**Interfaces:**
- Consumes: `CommentRecord.replyCount`, `canMutate`, `expandedRootIds`, `toggleReplies()`, `CommentForm`, Community `createComment()` overload.
- Produces: 활성 Audio·Community 원댓글에 공통 적용되는 첫 답글 action 노출 조건.
**TDD 절차:**
- [x] **RED: 실패 테스트 작성/실패 확인**`comment-thread.test.tsx`에 Community `replyCount=0` root의 `답글 작성` 노출, 클릭 후 form, `parentId` POST와 `languageCode` 미전송을 검증하고 `npm run test:run -- src/features/comments/tests/comment-thread.test.tsx`가 버튼 부재로 실패하는지 확인한다.
- [x] **GREEN: 최소 구현/통과 확인**`CommentThread.tsx`의 기존 optional action label 조건에서 Audio 전용 제한만 제거하고 같은 명령이 exit 0인지 확인한다.
- [x] **REFACTOR: 정리/회귀 확인** — 추가 helper·component 없이 조건을 읽기 쉬운 최소 표현으로 유지하고 focused test와 `npm run test:run -- src/features/comments`가 모두 exit 0인지 확인한다.
- [x] 기존 Community mock E2E에 답글 0개 root의 첫 답글 작성과 같은 root에 추가 직접 답글 작성 journey를 검증한다.
- [x] 검증 결과를 Progress에 기록한다.
**검증 기준:**
- **실행 명령:** `npm run test:run -- src/features/comments/tests/comment-thread.test.tsx`; `npm run test:run -- src/features/comments`; `npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium`; `npm run typecheck`; `npm run lint`.
- **기대 결과:** 모든 명령 exit 0, Community 첫 답글 POST 1회 이상, `parentId`는 원댓글 ID, Community body의 `languageCode` 0건, reply row의 답글 action 0건, 기존 Audio·Comments 회귀 실패 0건.
- **수동 확인:** 활성 Community Sheet에서 답글 0개 root의 `답글 작성` → form 노출 → 첫 답글 등록 → 같은 root 추가 답글 등록을 확인한다. 비활성 workspace와 reply row에는 작성 진입이 없어야 한다.
### 완료 조건
- [x] `P1-T1`의 모든 TDD·검증 체크박스가 완료됐다.
- [x] `CCR-001~006`이 구현 또는 검증 증거에 연결됐다.
- [x] API·mock·schema와 범위 밖 파일 변경이 없다.
### Task 1.R1 Community E2E fixture 검증
**Goal 실행 `P1-R1`:** `CCR-REV-P1-001`의 E2E fixture 분류 오류 후보가 실제
mock E2E 실행 경로에 영향을 주는지 검증하고 판정한다.
- **시작 조건:** `P1-T1` 완료, `CCR-REV-P1-001` 확정.
- **완료 증거:** 실제 mock 요청 소유권 확인, 후보를 구분하는 E2E assertion, Chromium·Comments 회귀·정적 검증과 Progress 기록.
- **범위 밖:** 애플리케이션 mock handler·store, API·schema, production 댓글 동작 변경.
**Files:**
- Modify: `tests/e2e/comments.spec.ts`
- Test: `tests/e2e/comments.spec.ts`
**TDD 예외 사유:** 리뷰 후보를 구분하는 assertion이 기존 mock E2E에서도 통과해
production 또는 fixture 수정이 필요하지 않은 오탐으로 판정됐다. 실패하는 구현 변경이
없으므로 RED → GREEN 대신 실제 요청 소유권과 기존 동작을 대체 검증했다.
- [x] root `2102`의 초기 reply region에 root 댓글이 없고, 첫·두 번째 답글이 region에 표시되며 중첩 action이 없는 assertion을 추가했다.
- [x] 기존 `comments-test-support.ts`를 유지한 상태에서 Chromium E2E `3/3` 통과를 두 번 확인했다.
- [x] `VITE_API_MODE=mock`의 Browser MSW Service Worker가 mock 요청을 처리하며 `page.route` fixture 후보가 실제 실행 경로를 소유하지 않음을 확인했다.
- [x] fixture 변경을 폐기하고 Comments 회귀·typecheck·lint·`git diff --check`를 통과했다.
- [x] 검증 결과와 `CCR-REV-P1-001` 오탐 판정을 Progress에 기록했다.
**검증 기준:**
- **실행 명령:** `npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium`; `npm run test:run -- src/features/comments`; `npm run typecheck`; `npm run lint`; `git diff --check`.
- **기대 결과:** 기존 fixture를 변경하지 않고 모든 명령 exit 0, Chromium `3/3`, 첫·추가 답글이 root `2102` region에만 표시된다.
- **수동 확인:** 기존 `P1-GATE`의 Community 첫·추가 답글 browser QA 결과와 mock E2E의 동일 동작을 대조한다.
### Task 1.R2 완료 문서 현재 상태 정합성 복구
**Goal 실행 `P1-R2`:** `CCR-REV-P1-002`의 미구현 문장을 실제 완료 상태로
정정하고 기존 Progress와 결정 기록을 보존한다.
- **연결 리뷰:** [최종 커밋 감사](./reviews/phase1-final-commit-audit.md) — `CCR-REV-P1-002`
- **시작 조건:** `CCR-REV-P1-002` 확정, 완료된 `P1-T1`, `P1-R1`, `P1-GATE`.
- **완료 증거:** 현재 상태 문장 정정, 아래 체크박스·문서 검증 통과, review 수정 완료 기록과 Progress 누적.
- **범위 밖:** 애플리케이션 코드·test·API Contract, 기존 Progress·Decision Log 삭제 또는 덮어쓰기.
**Files:**
- Modify: `docs/20260806_커뮤니티댓글답글/plan-task.md`
- Modify: `docs/20260806_커뮤니티댓글답글/reviews/phase1-final-commit-audit.md`
- Test: 없음 — 애플리케이션 동작을 변경하지 않는 문서 정합성 수정이다.
**Interfaces:**
- Consumes: `CCR-REV-P1-002`, `CCR-001`, 완료된 `P1-T1`·`P1-GATE` 검증 증거.
- Produces: 실제 구현과 일치하는 plan 현재 상태와 수정 완료 review 기록.
**TDD 예외 사유:** 애플리케이션 코드·동작을 바꾸지 않는 문서 정정이므로 실패
unit test를 추가하지 않는다.
**대체 검증 방법:** stale 미구현 marker 부재, 완료 상태 문장·review 상태·상호
링크 존재와 Markdown diff를 명령으로 확인한다.
- [x] `replyCount === 0`인 Community root의 현재 상태를 실제 구현 완료 내용으로 정정한다.
- [x] `CCR-REV-P1-002`의 상태와 리뷰 종료 판정을 `수정 완료`로 갱신한다.
- [x] 기존 Progress와 Decision Log를 보존하고 `P1-R2` 기록을 누적한다.
- [x] 문서 marker·link·diff 검증 결과를 Progress와 review에 기록한다.
**검증 기준:**
- **실행 명령:** `! sed -n '17,28p' docs/20260806_커뮤니티댓글답글/plan-task.md | rg -n '첫 답글 작성 진입만 없다'`; `sed -n '17,28p' docs/20260806_커뮤니티댓글답글/plan-task.md | rg -n 'replyCount === 0.*답글 작성.*제공'`; `rg -n 'CCR-REV-P1-002.*수정 완료' docs/20260806_커뮤니티댓글답글/reviews/phase1-final-commit-audit.md`; `test -f docs/20260806_커뮤니티댓글답글/reviews/phase1-final-commit-audit.md`; `git diff --check`.
- **기대 결과:** 모든 명령 exit 0, stale 미구현 marker 0건, 완료 상태·review 수정 완료 marker와 링크 각 1건 이상, whitespace 오류 0건.
- **수동 확인:** 없음 — 제품 동작을 바꾸지 않으며 문서의 정확한 marker와 link를 명령으로 판정한다.
### 검증 방법
#### Phase 1 Gate
**Goal 실행 `P1-GATE`:** 커뮤니티 첫·추가 직접 답글 journey와 Comments 공통
경계를 최종 판정한다.
- **시작 조건:** `P1-T1` 완료.
- **완료 증거:** 아래 명령·수동 확인 통과와 Progress 기록.
- **범위 밖:** test 완화, timeout 상향과 관련 없는 수정.
**실행 명령:**
```bash
npm run test:run -- src/features/comments
npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium
npm run typecheck
npm run lint
git diff --check
```
**기대 결과:** 모든 명령 exit 0, `CCR-001~006` 위반 0건.
**수동 확인:** 활성·비활성 Community와 활성 Audio에서 action 노출 경계를
대조한다. Community Sheet를 1280px·320px와 200% zoom에서 열어 수평 overflow
없이 첫·추가 답글을 작성하고 keyboard-only로 form에 진입한다.
## 실행 순서와 의존성
1. `P1-T1` RED
2. `P1-T1` GREEN
3. `P1-T1` REFACTOR·회귀
4. `P1-GATE`
5. 최종 커밋 감사에서 확정된 `CCR-REV-P1-002``P1-R2`로 전환
6. `P1-R2` 문서 정정·검증과 review 수정 완료 처리
- 동시에 하나의 미완료 goal만 운용한다.
- 사용자가 goal 실행을 요청하기 전에는 goal을 생성하지 않는다.
## 변경 금지 항목
- 기존 OpenAPI, API client, request schema, mock handler·store 변경
- 새 dependency, state library, component 또는 speculative abstraction
- 답글의 답글, optimistic update와 form 상시 노출
- Audio payload와 기존 수정·삭제·pagination 동작 변경
- 실패 test 삭제·skip, timeout 상향으로 Gate 통과
- 기존 Progress와 결정 기록 삭제·덮어쓰기
## 의사결정 및 중단 규칙
- `replyCount === 0`, `canMutate === true`인 Audio·Community 원댓글에만 `답글 작성`을 표시한다.
- `replyCount > 0` 또는 펼친 원댓글은 기존 `답글 보기` label을 유지한다.
- reply row에는 `onShowReplies`를 전달하지 않으며 3단계 작성 경로를 만들지 않는다.
- API 응답이나 오류가 [api-contract.md](./api-contract.md)와 다르면 추정 수정하지 않고 외부 의존으로 기록한다.
- 범위가 바뀌면 코드보다 PRD Decision Log와 이 계획을 먼저 갱신한다.
## Progress
### 2026-08-06 요구사항·설계
- **무엇을:** 활성 Community 원댓글의 첫 답글 진입, 여러 직접 답글과 2단계 제한을 요구사항·API 재사용 계약·단일 구현 Task로 정리했다.
- **왜:** Community 답글 조회·작성 흐름은 이미 있으나 `replyCount === 0`이면 진입 action이 없어 첫 답글만 작성할 수 없다.
- **어떻게:** 선행 Audio 답글 문서, 프로젝트 OpenAPI, `CommentThread`, request schema, mock handler·store, unit·E2E를 대조했다. 기존 공통 흐름을 재사용할 수 있어 새 API·컴포넌트·mock을 계획에서 제외했다. 애플리케이션 코드와 test는 변경하지 않았다.
### 2026-08-06 `P1-T1` 커뮤니티 첫 답글 진입
- **무엇을:** 활성 Community의 `replyCount=0` 원댓글에도 기존 `답글 작성` action을 노출하고, 같은 원댓글에 첫 번째와 두 번째 직접 답글을 작성하는 단위·Chromium E2E를 추가했다. reply row의 중첩 답글 action 부재와 Community payload의 `languageCode` 미전송도 검증했다.
- **왜:** 기존 공통 GET·POST·form·재조회 흐름은 완성돼 있었지만 action label 조건이 Audio target만 허용해 Community 첫 답글 진입이 막혀 있었다.
- **어떻게:** RED에서 `npm run test:run -- src/features/comments/tests/comment-thread.test.tsx`를 실행해 `AI 루트 댓글 답글 작성` 버튼 부재로 `1 failed, 7 passed`를 확인했다. GREEN에서 `CommentThread.tsx`의 Audio 전용 조건만 제거한 뒤 focused test `8/8`을 통과했다. REFACTOR·회귀로 `npm run test:run -- src/features/comments``15/15`, `npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium``3/3`, `npm run typecheck``npm run lint`는 exit 0이었다. API·schema·mock·dependency는 변경하지 않았다.
### 2026-08-06 `P1-R1` E2E fixture 후보 판정
- **무엇을:** `CCR-REV-P1-001`이 지적한 단일 `replyRootId` fixture가 mock E2E의 root `2102` 답글을 오분류하는지 검증했다.
- **왜:** 코드만 보면 `comments-test-support.ts`가 root `2101`만 replies로 처리하지만, 실제 mock E2E가 이 fixture를 사용하는지 확인하지 않으면 오탐 수정으로 범위를 확장할 수 있다.
- **어떻게:** 기존 fixture를 유지한 상태에서 `2102` 초기 reply region에 root 댓글 0건, 첫·두 번째 답글 표시, dialog 내 각 1건, 중첩 action 0건을 추가하고 Chromium E2E `3/3` 통과를 두 번 확인했다. `playwright.config.ts``VITE_API_MODE=mock``src/shared/mocks/browser.ts``setupWorker(...)`를 대조해 Browser MSW가 Service Worker에서 요청을 처리하며 `page.route`가 해당 요청을 소유하지 않음을 확인했다. fixture 변경은 폐기했고 `CCR-REV-P1-001`을 오탐으로 판정했다.
### 2026-08-06 `P1-GATE` Phase 1 최종 검증
- **무엇을:** Community 첫·추가 직접 답글, 2단계·권한 경계, Comments 회귀와 반응형·keyboard·CJK 품질을 최종 판정했다.
- **왜:** 코드와 자동 test 통과만으로는 실제 Sheet의 keyboard 진입, 320px·200% zoom, 한국어 줄바꿈과 reviewer 차단 해소를 증명할 수 없다.
- **어떻게:** `npm run test:run -- src/features/comments``15/15`, `npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium``3/3`, `npm run typecheck`, `npm run lint`, `npm run build:dev`, `git diff --check`는 exit 0이었다. 실제 Chromium에서 첫·두 번째 답글, input 초기화, 중첩 action 0건, keyboard-only 진입과 1280px·320px·200% zoom의 수평 overflow 0건을 확인했다. 독립 goal·코드 품질·보안·컨텍스트·기능·visual/CJK 리뷰는 최종 PASS였고 [Phase 1 리뷰](./reviews/phase1-community-comment-replies.md)에 근거를 기록했다.
### 2026-08-06 `P1-R2` 완료 문서 현재 상태 정합성 복구
- **무엇을:** `CCR-REV-P1-002``P1-R2`로 전환한 뒤 `replyCount === 0`인 활성 Community root의 현재 상태를 실제 구현 완료 내용으로 정정하고 최종 커밋 감사 상태를 수정 완료로 갱신했다.
- **왜:** plan의 완료 상태·코드·test와 반대인 구현 전 문장 때문에 후속 작업자가 첫 답글 진입을 미구현으로 오인할 수 있었다.
- **어떻게:** stale 현재 상태 marker 부재, 완료 상태 문장 존재, review 파일과 수정 완료 marker 존재를 `rg`·`test -f`로 확인하고 trailing whitespace 검사와 `git diff --check`를 실행해 모두 exit 0을 확인했다. 애플리케이션 코드·test·API Contract는 변경하지 않았다.
### 2026-08-06 `P1-R2` 후 기능 회귀 감사
- **무엇을:** 문서 정정 뒤 Community 첫·추가 직접 답글과 Comments 공통 회귀, 정적 품질과 development build를 다시 확인했다.
- **왜:** 문서 전용 변경임을 diff로 확인하고 최종 완료 상태가 기존 기능 검증 증거와 계속 일치하는지 판정하기 위해서다.
- **어떻게:** `npm run test:run -- src/features/comments``15/15`, 샌드박스 밖에서 실행한 `npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium``3/3`, `npm run typecheck`, `npm run lint`, `npm run build:dev`, `git diff --check`는 모두 exit 0이었다. build의 기존 500kB chunk warning 외 실패는 없었다.
## Decision Log
| 날짜 | 결정 | 근거 | 영향 |
|---|---|---|---|
| 2026-08-06 | Audio와 동일한 `답글 작성` 진입을 활성 Community 원댓글에도 적용한다. | 사용자 요청 | `CCR-001~003`, `P1-T1` |
| 2026-08-06 | 한 원댓글에 여러 직접 답글을 허용하고 reply-of-reply는 제외한다. | 사용자 요청 | `CCR-004~005`, `P1-T1`, `P1-GATE` |
| 2026-08-06 | 기존 공통 UI와 Community GET·POST를 재사용하고 API·mock·schema는 변경하지 않는다. | OpenAPI와 코드 확인 | `CCR-002~006`, `P1-T1` |
| 2026-08-06 | 구현은 공통 action 조건의 Audio 전용 제한 제거와 기존 test 보강으로 제한한다. | `CommentThread` 흐름 확인과 최소 변경 원칙 | `P1-T1` Files·Interfaces |
| 2026-08-06 | E2E 전용 route fixture가 특정 root만 replies로 처리하는 결함을 `P1-R1`에서 수정한다. | 최종 코드 품질·컨텍스트 리뷰에서 `2102` 답글이 roots에 저장돼 E2E가 오탐 통과함을 확인 | `CCR-REV-P1-001`, `P1-R1`, `P1-GATE` |
| 2026-08-06 | 정정: `CCR-REV-P1-001`은 mock mode에서 Browser MSW가 요청을 소유해 E2E route fixture 분기가 실행되지 않으므로 오탐이다. fixture를 변경하지 않는다. | 기존 fixture 상태에서 2102 빈 reply·첫·추가 답글 assertion과 Chromium `3/3` 통과, `VITE_API_MODE=mock`·`setupWorker(...)` 확인 | `CCR-REV-P1-001`, `P1-R1`, `P1-GATE` |
| 2026-08-06 | 최종 커밋 감사에서 확정된 stale 현재 상태 문장을 문서 전용 회귀 Task로 수정한다. | `CCR-REV-P1-002`의 plan·코드·test 불일치 | `P1-R2` |
| 2026-08-06 | `P1-R2`에서 현재 상태 문장을 실제 구현과 일치시키고 review를 수정 완료 처리한다. | 문서 marker·link·diff 검증 통과 | `CCR-REV-P1-002`, `P1-R2` |
## 발견된 문제
- 수정 완료: 답글 0개 Community root의 첫 답글 작성 진입을 `P1-T1`에서 구현하고 `P1-GATE`에서 검증했다.
- 확정: E2E 전용 fixture가 `replyRootId` 하나만 replies로 분류해 다른 root의 직접 답글을 roots에 저장한다. (`CCR-REV-P1-001`, `P1-R1`에서 수정 예정)
- 오탐: `CCR-REV-P1-001` — mock mode에서는 Browser MSW가 요청을 처리해 해당 E2E route fixture 분기가 실행되지 않으며, 기존 fixture 상태에서 root `2102`의 빈 reply·첫·추가 답글 journey가 통과한다.
- 확정: 완료된 현재 상태에 첫 답글 진입이 없다는 구현 전 문장이 남아 있다. (`CCR-REV-P1-002`, `P1-R2` 진행 중)
- 수정 완료: `CCR-REV-P1-002`의 stale 현재 상태 문장을 실제 구현 완료 내용으로 정정하고 문서 검증을 통과했다. (`P1-R2`)
- 외부 차단: 없음.
## 최종 보고 형식
- 완료 Goal ID
- 변경한 파일과 최소 구현 내용
- RED·GREEN·REFACTOR 및 Gate 명령과 실제 결과
- 실행하지 못한 수동·server 검증과 이유
- 남은 위험 또는 열린 질문