# 수정 요청 변경 필드 전송 구현 계획 | 문서 항목 | 내용 | |---|---| | 상태 | 구현·검증 완료 | | 작성일 | 2026-08-06 | | 요구사항 기준 | [prd.md](./prd.md) | | API 기준 | [api-contract.md](./api-contract.md) | | 현재 Phase | Phase 1 변경 필드 전송 통합 | | 현재 활성 Goal | 없음 | ## 목표 AI 캐릭터, 오디오 콘텐츠, 커뮤니티 게시글, 시리즈, FanTalk 답글 수정 시 최종 변경 필드와 교체 파일만 전송하고, 변경이 없으면 저장과 API 요청을 차단한다. ## 현재 상태 | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| | 1 | 완료 | `7/7` | 없음 | 없음 | - 동시에 하나의 미완료 goal만 운용한다. - 완료된 Task와 검증 기록은 삭제하거나 되돌리지 않는다. 후속 수정은 회귀 수정 Task와 새 goal ID를 추가한다. - 사용자가 token budget을 지정하지 않았으므로 goal에 token budget을 설정하지 않는다. ## 범위 ### 포함 - AI 캐릭터, 오디오 콘텐츠, 커뮤니티 게시글, 시리즈, FanTalk 답글의 일반 수정 payload - 현재 form 값과 조회 기준값의 field별 비교, nullable 삭제와 미변경 생략 구분 - 이미지·cover·post image만 변경한 multipart의 빈 `request: {}` - 변경 없음·변경 후 원복 상태의 native disabled 저장 control과 mutation 0건 - 기능별 contract·component test와 mock Chromium E2E 회귀, typecheck·lint·build ### 제외 - 생성 payload, backend DTO와 HTTP method 변경 - 캐릭터·오디오 콘텐츠·시리즈 비활성화 payload - 커뮤니티 게시글 고정 전환·soft delete, 시리즈 순서 변경, FanTalk 원글 삭제 - 수정 화면 신규 field, image 삭제, 원작 연결 해제 기능 - 새 dependency, 전역 form store, 범용 deep-diff abstraction ## 기술적 제약 - 기술 스택: React 19, TypeScript 6, Zod 4, Vitest 4, Testing Library, Playwright 1.61 - 코드 스타일: TypeScript `strict`를 유지하고 `as any`, `@ts-ignore`, `@ts-expect-error`로 오류를 숨기지 않는다. - 아키텍처: API helper의 transport·schema 책임은 유지하고, 변경 판정은 각 feature의 form serializer 또는 component에 둔다. - 데이터: 현재 form과 기준 DTO에 같은 trim·nullable·array serialization을 적용하고 `false`, `0`, `null`을 유효한 변경값으로 보존한다. - 파일: multipart `request` part는 항상 유지한다. 파일만 변경하면 `{}`, 파일 미변경이면 file part를 생략한다. - UX: payload와 저장 disabled가 서로 다른 판정을 사용하지 않도록 같은 request 결과에서 `hasChanges`를 계산한다. - 보안: 인증·권한·resource ownership·민감정보 비기록 정책을 변경하지 않는다. - 호환성: 기존 기능별 desktop·tablet·mobile capability와 지원 browser를 유지한다. - 의존성: 새 package를 추가하지 않고 언어·플랫폼 기능과 기존 Zod schema를 사용한다. - 계약: 제공되지 않은 field, endpoint, response, 오류 status/key를 만들지 않는다. - mock: 기존 explicit mock mode만 사용하고 production 자동 fallback을 추가하지 않는다. - 구현: 각 Task는 RED → GREEN → REFACTOR 순서로 진행하고 실제 결과를 Progress에 누적한다. ## Phase 1. 변경 필드 전송 통합 **Phase 결과:** 다섯 수정 기능에서 단일 field 변경, nullable 삭제, file-only 변경과 무변경 상태가 동일한 계약으로 동작한다. **선행조건:** [prd.md](./prd.md)의 `DIFF-001~005`와 [api-contract.md](./api-contract.md) 확정. **Phase 완료 조건:** `P1-T1`~`P1-T5`와 `P1-GATE` 완료, 검증 결과와 실제 request 증거를 Progress에 누적. ### 구현 항목 #### Task 1.1 AI 캐릭터 변경 field 직렬화 **Goal 실행 `P1-T1`:** 캐릭터 수정 request가 변경된 profile·optional·repeated field만 포함하고 무변경 저장을 차단한다. - **시작 조건:** `CHAR-001`, `DIFF-001~005`, `DATA-001~002`, API Contract §1·§2.1 확정. - **완료 증거:** direct field·nullable·repeated·image-only·무변경 test, focused 회귀, Progress 기록. - **범위 밖:** create form, region 수정, `isActive`, 원작 연결 해제 backend 의미 변경. **Files:** - Modify: `src/features/characters/components/character-optional-field-serialization.ts` - Modify: `src/features/characters/pages/CharacterEditPage.tsx` - Test: `src/features/characters/tests/CharacterEditPage.test.tsx` - Test: `src/features/characters/tests/character-api.test.ts` **Interfaces:** - Consumes: `CharacterDetail`, `CharacterOptionalFieldsValue`, `UpdateCharacterParams["request"]`, 기존 `serializeCharacterRequest`. - Produces: 현재 optional field와 초기 optional field를 비교해 변경 key만 반환하는 `toUpdateCharacterOptionalRequest` 계약, direct field와 합쳐진 변경 전용 request, 동일 request 기반 `hasChanges`. - 보존: 원작 미선택의 `originalWorkId` key 생략, file part 이름 `image`, `region`·일반 수정 `isActive` 제외. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — `CharacterEditPage.test.tsx`에 name 단일 변경, optional field `null` 삭제, 반복 field 변경, 최초·원복 disabled, image-only `{}` 시나리오를 추가하고 focused 명령에서 현재 전체 field payload 또는 enabled 저장 때문에 실패하는지 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — optional serializer가 초기값과 달라진 key만 만들고 page가 direct field와 file을 같은 방식으로 판정하도록 최소 수정해 focused 명령을 통과시킨다. - [x] **REFACTOR: 정리/회귀 확인** — payload 계산 중복만 제거하고 create serializer·API multipart helper는 유지한 채 Character 전체 회귀를 실행한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts`; `npm run test:run -- src/features/characters`; `npm run typecheck`; `npm run lint`. - **기대 결과:** 모든 명령 `exit 0`, 신규 5개 변경 감지 test를 포함한 실행 test 전부 통과, name 단일 변경 key 1개, 무변경 `PUT` 0건, type·lint 오류 0건. - **수동 확인:** 1280px에서 캐릭터 이름만 수정한 request part가 `{name}`이고, 원복 시 저장 disabled, image만 교체하면 `image`와 `{}`만 전송된다. - [x] TDD 단계와 검증 기준의 실제 결과를 Progress에 기록한다. #### Task 1.2 오디오 콘텐츠 변경 field 직렬화 **Goal 실행 `P1-T2`:** 오디오 콘텐츠 수정 request가 변경된 title·detail·tags·price와 새 cover image만 포함한다. - **시작 조건:** `P1-T1` 완료, `AUDIO-001`, `DIFF-001~005`, API Contract §1·§2.2 확정. - **완료 증거:** 단일 field·price 0·cover-only·무변경 test, 기존 비활성화 회귀, Progress 기록. - **범위 밖:** create options, audio 원본 교체, theme·release date 수정, 비활성화 payload. **Files:** - Modify: `src/features/audio-contents/components/audio-content-form-helpers.ts` - Modify: `src/features/audio-contents/components/AudioContentForm.tsx` - Test: `src/features/audio-contents/tests/audio-form-update.test.tsx` - Test: `src/features/audio-contents/tests/audio-contract.test.ts` **Interfaces:** - Consumes: `AudioContentDetail`, `AudioContentUpdateRequest`, 기존 `toUpdateRequest`와 `createAudioContentUpdateBody`. - Produces: 직렬화된 현재 값과 `audio` 기준값을 비교해 변경 key만 반환하는 `toUpdateRequest`, 같은 request와 `coverImage` 기반 `hasChanges`. - 보존: file part `coverImage`, `request` part, 일반 update `isActive` 금지, price `0` 허용. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — detail 단일 변경, price `0`, cover-only `{}`, 최초·원복 disabled와 화면에 없는 boolean 미전송 test를 작성하고 현재 전체 payload 때문에 실패하는지 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — 기존 `toUpdateRequest`가 실제 수정 화면 field만 비교해 반환하고 edit button이 같은 결과로 disabled되도록 수정한다. - [x] **REFACTOR: 정리/회귀 확인** — create path와 비활성화 schema를 건드리지 않고 update serializer의 중복만 정리한 뒤 Audio 전체 회귀를 실행한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/audio-contents/tests/audio-form-update.test.tsx src/features/audio-contents/tests/audio-contract.test.ts`; `npm run test:run -- src/features/audio-contents`; `npm run typecheck`; `npm run lint`. - **기대 결과:** 모든 명령 `exit 0`, 신규 5개 변경 감지 test를 포함한 실행 test 전부 통과, detail 단일 변경 key 1개, cover-only request `{}`, 무변경 `PUT` 0건. - **수동 확인:** 1280px에서 detail만 수정하고 multipart `request`에 `detail`만 있는지, 기존 cover 미선택 시 `coverImage`가 없는지 확인한다. - [x] TDD 단계와 검증 기준의 실제 결과를 Progress에 기록한다. #### Task 1.3 커뮤니티 게시글 변경 field 직렬화 **Goal 실행 `P1-T3`:** 커뮤니티 게시글 수정 저장이 변경된 content·comment permission·adult flag와 새 image만 포함한다. - **시작 조건:** `P1-T2` 완료, `COMM-001`, `DIFF-001~005`, API Contract §1·§2.3 확정. - **완료 증거:** 단일 field·boolean false·image-only·무변경 test, 고정·soft delete 회귀, Progress 기록. - **범위 밖:** create form, audio file, `isFixed` 전환, soft delete 계약. **Files:** - Modify: `src/features/community-posts/components/CommunityPostSheet.tsx` - Test: `src/features/community-posts/tests/community-sheet.test.tsx` - Test: `src/features/community-posts/tests/community-contract.test.ts` **Interfaces:** - Consumes: `CommunityPostListItem`, `CommunityPostUpdateRequest`, `updateCommunityPost`. - Produces: Sheet의 현재 수정 field와 `post` 기준값을 비교한 request, 같은 request와 `postImage` 기반 `hasChanges`. - 보존: `toggleFixed`의 `{isFixed}`, `softDeleteCommunityPost`의 기존 request, file part `postImage`. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — content 단일 변경, boolean true→false, image-only `{}`, 최초·원복 disabled test를 작성하고 현재 `isFixed`와 미변경 field가 함께 전송되는 실패를 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — Sheet의 일반 수정 저장 request만 field별 비교하고 저장 button이 같은 `hasChanges`를 사용하도록 수정한다. - [x] **REFACTOR: 정리/회귀 확인** — 고정·비활성화 action의 전용 함수와 pending guard를 유지하고 일반 수정 계산만 읽기 쉽게 정리한 뒤 Community 전체 회귀를 실행한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts`; `npm run test:run -- src/features/community-posts`; `npm run typecheck`; `npm run lint`. - **기대 결과:** 모든 명령 `exit 0`, 신규 4개 변경 감지 test를 포함한 실행 test 전부 통과, content 단일 변경 key 1개, 일반 수정 `isFixed` 0건, 무변경 `PUT` 0건. - **수동 확인:** 지원 viewport에서 Sheet를 열어 content만 수정했을 때 request에 content만 있고 고정 button과 비활성화 button 동작이 유지되는지 확인한다. - [x] TDD 단계와 검증 기준의 실제 결과를 Progress에 기록한다. #### Task 1.4 시리즈 변경 field 직렬화 **Goal 실행 `P1-T4`:** 시리즈 수정 request가 변경된 기본·enum·nullable field와 새 image만 포함한다. - **시작 조건:** `P1-T3` 완료, `SERIES-001`, `DIFF-001~005`, API Contract §1·§2.4 확정. - **완료 증거:** title 단일 변경, nullable 삭제, 배열·enum 변경, image-only·무변경 test, 비활성화 회귀, Progress 기록. - **범위 밖:** create-only keyword, 연결 콘텐츠, 순서 변경, 비활성화 payload. **Files:** - Modify: `src/features/series/components/SeriesForm.tsx` - Test: `src/features/series/tests/series-form.test.tsx` - Test: `src/features/series/tests/series-contract.test.ts` - Test: `src/features/series/tests/series-update-invariants.test.ts` **Interfaces:** - Consumes: `SeriesListItem`, `SeriesUpdateRequest`, 기존 `textOrNull`, `editedState`, `updateSeries`. - Produces: 모든 edit field에 `editedState`와 같은 원본 비교를 적용한 update request, 같은 request와 `image` 기반 `hasChanges`. - 보존: array field 변경 시 전체 새 배열, writer·studio 삭제 시 `null`, 일반 update의 `isActive` 금지. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — title 단일 변경, writer 삭제 `null`, published days 변경, image-only `{}`, 최초·원복 disabled test를 작성하고 state 외 미변경 field가 전송되는 실패를 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — 기존 `editedState` 패턴을 edit field 전체에 적용해 request를 만들고 form disabled·dirty 판정이 같은 결과를 사용하게 한다. - [x] **REFACTOR: 정리/회귀 확인** — field 의미를 감추는 범용 abstraction 없이 local serializer 하나로 중복만 줄이고 Series 전체 회귀를 실행한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/series/tests/series-form.test.tsx src/features/series/tests/series-contract.test.ts src/features/series/tests/series-update-invariants.test.ts`; `npm run test:run -- src/features/series`; `npm run typecheck`; `npm run lint`. - **기대 결과:** 모든 명령 `exit 0`, 신규 5개 변경 감지 test를 포함한 실행 test 전부 통과, title 단일 변경 key 1개, nullable 삭제 `null` 보존, 무변경 `PUT` 0건. - **수동 확인:** 1280px에서 title만 수정, writer 삭제, image-only 교체를 각각 실행해 request key와 file part를 확인한다. - [x] TDD 단계와 검증 기준의 실제 결과를 Progress에 기록한다. #### Task 1.5 FanTalk 답글 무변경 수정 차단 **Goal 실행 `P1-T5`:** FanTalk 답글은 기존 답글과 다른 content가 있을 때만 수정 request를 전송한다. - **시작 조건:** `P1-T4` 완료, `FANTALK-001`, `DIFF-001`, `DIFF-004`, API Contract §1.4·§2.5 확정. - **완료 증거:** 최초 disabled, 변경 enabled·`{content}`, 원복 disabled·`PUT` 0건 test, FanTalk 회귀, Progress 기록. - **범위 밖:** 답글 생성, FanTalk 원글 삭제, `isActive`, 답글 trim 정책 변경. **Files:** - Modify: `src/features/fan-talks/components/FanTalkReplySheet.tsx` - Modify: `src/features/fan-talks/components/FanTalkReplyForm.tsx` - Test: `src/features/fan-talks/tests/fan-talk-reply.test.tsx` - Test: `src/features/fan-talks/tests/fan-talk-contract.test.ts` **Interfaces:** - Consumes: `FanTalkCreatorReply.content`, `FanTalkReplyForm`, `updateFanTalkReply`. - Produces: edit mode에서 `content !== existingReply.content`를 나타내는 submit disabled prop, 변경 시 기존 `{content}` request. - 보존: create mode의 빈값 validation, pending single-flight, update response와 원글 DELETE. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — 기존 답글 open 직후 수정 button disabled, content 변경 후 enabled·PUT 1건, 원복 후 disabled·PUT 0건 test를 작성하고 현재 항상 enabled인 실패를 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — Sheet가 edit 변경 여부를 계산해 Form submit button에 전달하고 기존 update body는 유지한다. - [x] **REFACTOR: 정리/회귀 확인** — create와 edit의 disabled 이유를 명확히 유지하고 새 serializer나 API helper 없이 FanTalk 전체 회귀를 실행한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/fan-talks/tests/fan-talk-reply.test.tsx src/features/fan-talks/tests/fan-talk-contract.test.ts`; `npm run test:run -- src/features/fan-talks`; `npm run typecheck`; `npm run lint`. - **기대 결과:** 모든 명령 `exit 0`, 신규 3개 변경 감지 test를 포함한 실행 test 전부 통과, 변경 update body `{content}` key 1개, 무변경·원복 `PUT` 0건. - **수동 확인:** FanTalk 답글 Sheet를 열면 수정 button이 disabled이고, 내용 변경 시 enabled, 원복 시 다시 disabled인지 확인한다. - [x] TDD 단계와 검증 기준의 실제 결과를 Progress에 기록한다. #### Task R1.1 커뮤니티 content 직렬화 비교 일치 **Goal 실행 `P1-R1`:** `REV-P1-001`을 수정해 커뮤니티 게시글의 기존 원문 직렬화와 변경 판정을 일치시키고 공백 변경 누락을 방지한다. - **시작 조건:** `REV-P1-001` 확정, `P1-T3`·`P1-GATE` 완료, `DIFF-001~004`, API Contract §1.2·§2.3 확정. - **완료 증거:** 공백 변경 실패 재현 test, 최소 수정 후 focused·Community 전체·Phase Gate 통과, Progress와 review 수정 검증 기록. - **범위 밖:** 커뮤니티 생성 payload, content trim 정책 신설, 고정 전환, soft delete, 다른 기능 serializer 변경. **Files:** - Modify: `src/features/community-posts/components/CommunityPostSheet.tsx` - Test: `src/features/community-posts/tests/community-sheet.test.tsx` - Modify: `docs/20260806_수정요청변경필드만전송/plan-task.md` - Add: `docs/20260806_수정요청변경필드만전송/reviews/phase1-changed-field-requests.md` **Interfaces:** - Consumes: 기존 Community update의 raw `content`, `CommunityPostListItem.content`, `CommunityPostUpdateRequest`. - Produces: 현재 `content`와 기준 `post.content`에 동일한 원문 직렬화 규칙을 적용한 변경 판정. - 보존: 전송하는 `content` 원문, file-only `{}`, `isFixed`·soft delete 전용 request. **TDD 절차:** - [x] **RED: 실패 테스트 작성/실패 확인** — 기존 content 앞뒤에 공백을 추가하면 저장이 활성화되고 원문 `{content}`가 전송돼야 하는 test가 현재 trim 비교로 실패하는지 확인한다. - [x] **GREEN: 최소 구현/통과 확인** — `content` 비교의 `trim()`만 제거해 기존 update 직렬화와 일치시키고 focused test를 통과시킨다. - [x] **REFACTOR: 회귀 확인** — 별도 abstraction 없이 Community 전체와 Phase Gate를 실행한다. - [x] review 상태와 Progress에 실제 명령·결과를 누적한다. **검증 기준:** - **실행 명령:** `npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts`; `npm run test:run -- src/features/community-posts`; Phase 1 Gate 전체 명령. - **기대 결과:** 모든 명령 `exit 0`, 공백 변경 request에 raw `content`만 존재, 기존 227개 회귀와 정적 Gate 오류 0건. - **수동 확인:** 자동 component test로 동일 payload와 disabled 상태를 검증하며 별도 Network 확인은 대체 사유를 review에 기록한다. #### Task R1.2 PRD 성공 기준 상태 동기화 **Goal 실행 `P1-R2`:** `REV-P1-002`를 수정해 검증 완료된 PRD 성공 기준과 Phase 1 완료 기록을 일치시킨다. - **시작 조건:** `REV-P1-002` 확정, `P1-R1`과 Phase Gate 완료. - **완료 증거:** PRD §14의 검증 완료 항목 체크, plan·review 기록, 문서 정적 검사. - **범위 밖:** 요구사항·API Contract 의미 변경, 검증하지 않은 항목 완료 처리, 코드 변경. **Files:** - Modify: `docs/20260806_수정요청변경필드만전송/prd.md` - Modify: `docs/20260806_수정요청변경필드만전송/plan-task.md` - Modify: `docs/20260806_수정요청변경필드만전송/reviews/phase1-changed-field-requests.md` **Interfaces:** - Consumes: `P1-T1`~`P1-R1` Progress와 Phase Gate 실제 검증 결과. - Produces: PRD §14 기능·UI/UX·추적성 성공 기준의 현재 완료 상태. - 보존: PRD 요구사항 본문, API Contract, §18 요구사항 변경 체크리스트. **검증 절차:** - [x] 기존 Phase Gate 증거와 PRD §14 각 항목을 대조한다. - [x] 증거가 있는 §14 성공 기준만 완료 표시한다. - [x] `rg`와 `git diff --check`로 미완료·공백 오류를 확인하고 Progress·review에 결과를 기록한다. **TDD 예외 사유:** 실행 코드가 아닌 완료 상태 문서 동기화이며, 근거는 이미 통과한 component·E2E·정적 Gate다. ### 완료 조건 - [x] `P1-T1`~`P1-T5`의 체크박스와 완료 증거가 모두 충족됐다. - [x] `DIFF-001~005`, 기능별 요구사항과 파일·데이터 요구사항이 구현 또는 명시적 제외로 추적된다. - [x] 다섯 기능의 payload와 저장 disabled가 같은 변경 판정을 사용한다. - [x] 기존 생성·비활성화·고정·순서·삭제 contract 회귀가 없다. - [x] 문서, 구현, test와 실제 검증 기록의 차이가 없다. ### 검증 방법 #### Phase 1 Gate **Goal 실행 `P1-GATE`:** 다섯 수정 기능의 변경 field payload, 무변경 차단과 기존 mutation 회귀를 통합 판정한다. - **시작 조건:** `P1-T1`~`P1-T5` 완료. - **완료 증거:** focused·도메인 전체·mock Chromium E2E·정적 Gate와 수동 Network 검증 통과, Progress 기록. - **범위 밖:** Gate 통과를 위한 test 삭제·skip·완화와 관련 없는 refactor. **실행 명령:** ```bash npm run test:run -- src/features/characters src/features/audio-contents src/features/community-posts src/features/series src/features/fan-talks npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/community.spec.ts tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium npm run typecheck npm run lint npm run build git diff --check ``` **기대 결과:** 모든 명령 `exit 0`; 신규 변경 감지 test 최소 22개를 포함한 실행 test 전부 통과; 단일 field request key 1개; 무변경 mutation 0건; 기존 create·deactivate·fixed·order·delete E2E 실패 0건; type·lint·build 오류 0건. **Payload·회귀 확인:** - [x] focused/component test에서 캐릭터·오디오 콘텐츠·시리즈 단일 field와 file-only 수정 request를 확인한다. - [x] focused/component test와 mock Chromium E2E에서 커뮤니티 게시글 단일 field와 image-only 수정 request, 지원 viewport 회귀를 확인한다. - [x] focused/component test와 mock Chromium E2E에서 FanTalk 답글의 최초·변경·원복 button 상태와 update body를 확인한다. - [x] focused/component test에서 nullable 삭제의 `null`, boolean `false`, price `0`이 존재하고 미변경 key가 없는지 확인한다. - [x] focused/component test와 mock Chromium E2E에서 기존 비활성화·고정·삭제 action payload/API 회귀가 없는지 확인한다. ## 실행 순서와 의존성 | 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | |---:|---|---|---|---| | 1 | `P1-T1` | PRD·API Contract 확정 | 아니요 | Character 기준값·nullable 계약 보정 | | 2 | `P1-T2` | `P1-T1` | 아니요 | Audio 상세 DTO와 화면 field 대조 | | 3 | `P1-T3` | `P1-T2` | 아니요 | 일반 수정과 전용 action request 분리 확인 | | 4 | `P1-T4` | `P1-T3` | 아니요 | Series nullable·array·enum 계약 대조 | | 5 | `P1-T5` | `P1-T4` | 아니요 | create/edit mode disabled 조건 분리 확인 | | 6 | `P1-GATE` | Phase 1 Task 전체 | 아니요 | 실패 소유 Task의 회귀 수정 goal 생성 | | 7 | `P1-R1` | `REV-P1-001`, `P1-GATE` | 아니요 | Community 원문 직렬화 계약 재확인 | | 8 | `P1-R2` | `REV-P1-002`, `P1-R1` | 아니요 | PRD 성공 기준과 Gate 증거 재대조 | ```text P1-T1 → P1-T2 → P1-T3 → P1-T4 → P1-T5 → P1-GATE → P1-R1 → P1-R2 ``` ## 변경 금지 항목 - 확정된 PRD·API Contract의 endpoint, method, field type과 nullable 의미를 근거 없이 바꾸지 않는다. - 기존 완료 체크박스와 Progress·Decision Log·검증 기록을 삭제하거나 덮어쓰지 않는다. - 생성, 비활성화, 고정, 순서, 삭제 payload를 이번 최적화에 합치지 않는다. - 수정 화면에 없는 server field를 기준값 보존 명목으로 request에 복사하지 않는다. - 범용 deep-equality dependency, form framework 또는 전역 diff store를 추가하지 않는다. - test를 삭제·skip·완화하거나 type assertion으로 오류를 우회하지 않는다. - token, 파일 본문, signed URL과 관리자 입력 전문을 log·fixture·문서에 기록하지 않는다. ## 의사결정 및 중단 규칙 - PRD와 API Contract가 충돌하면 field type·nullable·transport는 정식 OpenAPI, payload 선택은 이 기능 API Contract를 따른다. - 현재 form이 기존 상세 DTO에 없는 수정 field를 노출하면 추정 비교하지 않고 해당 field의 근거를 문서화한 뒤 진행한다. - request schema가 빈 object를 거부하거나 backend가 optional field 생략을 현재 값 유지로 처리하지 않으면 구현을 중단하고 외부 의존을 PRD → API Contract → plan 순서로 기록한다. - 범위가 바뀌면 `plan-task.md` 체크박스와 Files·Interfaces를 먼저 갱신한 뒤 코드를 수정한다. - 같은 차단 사유가 최초 시도와 자동 후속을 포함해 3회 연속 반복되고 독립 작업도 불가능할 때만 goal을 `blocked`로 갱신한다. - 코드와 일부 test만 끝난 상태에서는 goal을 완료하지 않는다. TDD·검증·Progress 증거까지 충족한 뒤 `complete`로 갱신한다. - 완료된 Task의 후속 결함은 기존 Task를 다시 열지 않고 `P1-R1`부터 회귀 수정 goal을 추가한다. ## Progress 기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다. ### 문서 준비 — 2026-08-06 - 상태: 완료 - 무엇을: PRD, API Contract, 5개 구현 Task와 Phase Gate를 작성했다. - 왜: 다섯 수정 기능의 변경 field 전송과 무변경 A안의 실행 기준을 고정하기 위해서다. - TDD 예외 사유: 이 단계는 실행 코드를 변경하지 않는 요구사항·계약·계획 문서 작성이다. - 어떻게: - 코드 조사: 다섯 form·API·schema·test와 정식 OpenAPI의 update schema를 대조했다. - 인터뷰: 사용자가 무변경 상태의 저장 button disabled·request 0건인 A안을 확정했다. - 세 문서의 미정 표현·placeholder 정적 검사 — 출력 없음, `exit 0`. - 세 문서와 정식 OpenAPI 대상 `test -f` — 누락 없음, `exit 0`. - PRD 필수 18개 section과 plan 필수 12개 section `rg` 검사 — 모두 확인, `exit 0`. - `git diff --check -- docs/20260806_수정요청변경필드만전송` — 출력 없음, `exit 0`. - 남은 항목: `P1-T1`~`P1-T5`, `P1-GATE` 구현·검증. - 다음 행동: `P1-T1`의 Character RED test 작성. ### 구현 Goal 기록 형식 각 Goal 실행 후 아래 항목을 복제하고 실제 값으로 채운다. - 상태: 진행 중 / 완료 / 차단 감사 중 / 차단 - 무엇을: 완료한 체크박스와 산출물 - 왜: Task objective와 요구사항 ID - TDD: - RED: 실패 test 명령, exit code와 의도한 assertion - GREEN: 같은 focused 명령, exit code와 통과 test 수 - REFACTOR: focused·도메인 회귀 명령, exit code와 통과 test 수 - 어떻게: typecheck·lint·build·수동 검증의 성공·실패·불가 사유 - 남은 항목: 미완료 체크박스 또는 없음 - 다음 행동: 같은 goal의 가장 작은 미완료 단계 또는 다음 Goal ID ### P1-T1 Character 변경 field 직렬화 — 2026-08-06 - 상태: 완료 - 무엇을: 캐릭터 수정의 direct field, optional field, nullable 삭제, image-only `{}`, 무변경·원복 disabled를 변경 field 전송 계약으로 바꿨다. - 왜: `CHAR-001`, `DIFF-001~005`, `FILE-001`, `DATA-001~002`를 충족하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts` — exit 1. 의도한 실패 7개: name 단일 변경에 미변경 key 포함, 최초·원복 저장 button enabled, optional 변경에 미변경 key 포함, image-only request가 `{}`가 아님. - GREEN: 같은 focused 명령 — exit 0, 2 files / 21 tests 통과. - REFACTOR: `npm run test:run -- src/features/characters` — exit 0, 6 files / 46 tests 통과. - 어떻게: - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - API multipart helper와 create serializer는 변경하지 않았다. - 수동 Network 확인은 `P1-GATE`에서 다섯 기능과 함께 진행한다. - 남은 항목: `P1-T2`~`P1-T5`, `P1-GATE`. - 다음 행동: `P1-T2`의 Audio RED test 작성. ### P1-T1 Review Blocker 수정 — 2026-08-06 - 상태: 완료 - 무엇을: direct field 기준값에도 동일한 `trim()` 직렬화 규칙을 적용했다. - 왜: 리뷰에서 조회값이 공백을 포함하면 최초 진입부터 변경으로 판정되는 `DIFF-002`, `DIFF-004` 위반 가능성이 확인됐다. - TDD: - RED: `npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts` — exit 1. `CharacterEditPage compares direct fields after applying the same trim rule` 1개 실패, 최초 저장 button enabled. - GREEN: 같은 focused 명령 — exit 0, 2 files / 22 tests 통과. - REFACTOR: `npm run test:run -- src/features/characters` — exit 0, 6 files / 47 tests 통과. - 어떻게: - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - direct field 변경 판정과 submit request 모두 같은 trim 기준값을 사용한다. - 남은 항목: `P1-T2`~`P1-T5`, `P1-GATE`. - 다음 행동: `P1-T1` 리뷰 재확인 후 `P1-T2`의 Audio RED test 작성. ### P1-T2 Audio 변경 field 직렬화 — 2026-08-06 - 상태: 완료 - 무엇을: 오디오 콘텐츠 수정의 `title`, `detail`, `tags`, `price` 변경 field와 cover-only `{}` 전송, 무변경·cover 취소 disabled를 구현했다. - 왜: `AUDIO-001`, `DIFF-001~005`, `FILE-001`, `DATA-001`를 충족하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/audio-contents/tests/audio-form-update.test.tsx src/features/audio-contents/tests/audio-contract.test.ts` — exit 1. 의도한 실패 5개: 무변경 저장 button enabled, 화면 밖 boolean과 미변경 tags 포함, cover-only request가 `{}`가 아님, cover 취소·오류 후 저장 enabled. - GREEN: 같은 focused 명령 — exit 0, 2 files / 24 tests 통과. - REFACTOR: `npm run test:run -- src/features/audio-contents` — exit 0, 9 files / 71 tests 통과. - 어떻게: - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - create upload path와 deactivate `{isActive:false}` 계약은 변경하지 않았다. - 수동 Network 확인은 `P1-GATE`에서 다섯 기능과 함께 진행한다. - 남은 항목: `P1-T3`~`P1-T5`, `P1-GATE`. - 다음 행동: `P1-T2` 리뷰 확인 후 `P1-T3`의 Community RED test 작성. ### P1-T2 Review Blocker 수정 — 2026-08-06 - 상태: 완료 - 무엇을: edit 저장 가능 여부를 가격 문자열 비교가 아니라 정규화된 `toUpdateRequest` 결과와 cover 변경에서 계산하도록 보정했다. - 왜: 리뷰에서 `01000`처럼 파싱 결과가 기존 가격과 같은 입력이 빈 `PUT`을 만들 수 있는 `DIFF-002`, `DIFF-004` 위반 가능성이 확인됐다. - TDD: - RED: `npm run test:run -- src/features/audio-contents/tests/audio-form-update.test.tsx src/features/audio-contents/tests/audio-contract.test.ts` — exit 1. `AudioContentFormPage disables edit save when price formatting normalizes to the original value` 1개 실패, 저장 button enabled. - GREEN: 같은 focused 명령 — exit 0, 2 files / 25 tests 통과. - REFACTOR: `npm run test:run -- src/features/audio-contents` — exit 0, 9 files / 72 tests 통과. - 어떻게: - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - invalid price는 기존처럼 저장 button을 눌러 validation 오류를 표시할 수 있게 유지했다. - 남은 항목: `P1-T3`~`P1-T5`, `P1-GATE`. - 다음 행동: `P1-T2` 리뷰 재확인 후 `P1-T3`의 Community RED test 작성. ### P1-T3 Community 변경 field 직렬화 — 2026-08-06 - 상태: 완료 - 무엇을: 커뮤니티 게시글 일반 수정의 `content`, `isAdult`, `isCommentAvailable` 변경 field와 image-only `{}` 전송, 무변경 저장 차단을 구현했다. - 왜: `COMM-001`, `DIFF-001~005`, `FILE-001`, `DATA-001`를 충족하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts` — exit 1. 의도한 실패 3개: 일반 수정 request에 `isFixed` 포함, 최초 저장 button enabled, image-only request가 `{}`가 아님. - GREEN: 같은 focused 명령 — exit 0, 2 files / 26 tests 통과. - REFACTOR: `npm run test:run -- src/features/community-posts` — exit 0, 7 files / 51 tests 통과. - 어떻게: - `CommunityPostSheet`의 일반 저장 request와 저장 disabled가 같은 변경 판정을 사용한다. - 고정 전환과 soft delete 전용 request는 기존 계약대로 분리해 유지했다. - 수동 Network 확인은 `P1-GATE`에서 다섯 기능과 함께 진행한다. - 남은 항목: `P1-T4`, `P1-T5`, `P1-GATE`. - 다음 행동: `P1-T4`의 Series RED test 작성. ### P1-T4 Series 변경 field 직렬화 — 2026-08-06 - 상태: 완료 - 무엇을: 시리즈 수정의 기본 field, nullable writer/studio, enum·요일 변경 field와 image-only `{}` 전송, 무변경 저장 차단을 구현했다. - 왜: `SERIES-001`, `DIFF-001~005`, `FILE-001`, `DATA-001~002`를 충족하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/series/tests/series-form.test.tsx src/features/series/tests/series-contract.test.ts src/features/series/tests/series-update-invariants.test.ts` — exit 1. 의도한 실패 5개: enum 변경에 미변경 field 포함, title 단일 변경에 미변경 field 포함, 최초 저장 button enabled, writer 삭제 외 field 포함, image-only request가 `{}`가 아님. - GREEN: 같은 focused 명령 — exit 0, 3 files / 21 tests 통과. - REFACTOR: `npm run test:run -- src/features/series` — exit 0, 9 files / 42 tests 통과. - 어떻게: - `SeriesForm`의 edit request와 저장 disabled·dirty 판정이 같은 변경 결과를 사용한다. - create path, deactivate `{isActive:false}`, order/content APIs는 변경하지 않았다. - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - 수동 Network 확인은 `P1-GATE`에서 다섯 기능과 함께 진행한다. - 남은 항목: `P1-T5`, `P1-GATE`. - 다음 행동: `P1-T5`의 FanTalk RED test 작성. ### P1-T5 FanTalk 답글 무변경 수정 차단 — 2026-08-06 - 상태: 완료 - 무엇을: FanTalk 답글 수정에서 기존 content와 동일하거나 원복된 상태의 저장 button을 disabled 처리하고, 변경된 content만 `PUT`으로 전송하게 했다. - 왜: `FANTALK-001`, `DIFF-001`, `DIFF-004`를 충족하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/fan-talks/tests/fan-talk-reply.test.tsx src/features/fan-talks/tests/fan-talk-contract.test.ts` — exit 1. 의도한 실패 1개: 기존 답글 open 직후 `답변 수정` button enabled. - GREEN: 같은 focused 명령 — exit 0, 2 files / 11 tests 통과. - REFACTOR: `npm run test:run -- src/features/fan-talks` — exit 0, 4 files / 15 tests 통과. - 어떻게: - `FanTalkReplySheet`가 edit 변경 여부를 계산하고 `FanTalkReplyForm`에 submit disabled prop으로 전달한다. - create mode의 빈값 validation, pending single-flight, `{content}` update body와 원글 삭제 계약은 유지했다. - `npx tsc --noEmit --project tsconfig.test.json --pretty false` — 출력 없음, exit 0. - 남은 항목: `P1-GATE`. - 다음 행동: Phase 1 Gate 검증 실행. ### P1-GATE Phase 1 통합 검증 — 2026-08-06 - 상태: 완료 - 무엇을: 다섯 수정 기능의 변경 field payload, 무변경 저장 차단, 기존 mutation 회귀를 통합 검증했다. - 왜: Phase 1 완료 조건과 `DIFF-001~005`, `FILE-001`, `DATA-001~002` 적용 결과를 확인하기 위해서다. - TDD: - RED: `P1-T1`~`P1-T5` 각 Goal에서 focused 실패를 확인했다. - GREEN: 각 Goal의 focused 명령이 모두 exit 0으로 통과했다. - REFACTOR: 각 domain 회귀와 Phase Gate 통합 회귀를 실행했다. - 어떻게: - `npm run test:run -- src/features/characters src/features/audio-contents src/features/community-posts src/features/series src/features/fan-talks` — exit 0, 35 files / 227 tests 통과. - `npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/community.spec.ts tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium` — exit 0, 39 tests 통과. - `npm run typecheck` — exit 0. - `npm run lint` — exit 0. - `npm run build` — exit 0, production build 완료. - `git diff --check` — 출력 없음, exit 0. - 수동 Network 항목은 동일 payload 계약을 검증하는 focused/component test와 mock Chromium E2E로 대체 확인했다. - 남은 항목: 없음. - 다음 행동: 최종 변경 요약과 검증 결과 보고. ### P1-R1 Community content 직렬화 비교 일치 — 2026-08-06 - 상태: 완료 - 무엇을: Community update가 전송하는 raw `content`와 변경 판정을 일치시키고 공백 변경 회귀 test를 추가했다. - 왜: [`REV-P1-001`](./reviews/phase1-changed-field-requests.md)의 `DIFF-001`, `DIFF-002`, `DIFF-004`, `COMM-001` 위반을 수정하기 위해서다. - TDD: - RED: `npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts` — exit 1, 1 failed / 26 passed. `Community Sheet preserves whitespace-only content changes`에서 저장 button이 disabled인 의도한 실패를 확인했다. - GREEN: 같은 focused 명령 — exit 0, 2 files / 27 tests 통과. - REFACTOR: `npm run test:run -- src/features/community-posts` — exit 0, 7 files / 52 tests 통과. 새 abstraction 없이 비교식 1줄만 수정했다. - 어떻게: - `npm run test:run -- src/features/characters src/features/audio-contents src/features/community-posts src/features/series src/features/fan-talks` — exit 0, 35 files / 228 tests 통과. - `npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/community.spec.ts tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium` — sandbox 실행은 `127.0.0.1:8889` bind `EPERM`으로 불가했고, 승인된 동일 명령 재실행은 exit 0, 39 tests 통과. - `npm run typecheck` — exit 0. - `npm run lint` — exit 0. - `npm run build` — exit 0, production build 완료. - `git diff --check` — 출력 없음, exit 0. - 실서버 Network 확인은 인증 환경이 없어 실행하지 않았고 component multipart assertion과 mock Chromium E2E로 대체했다. - 남은 항목: 없음. - 다음 행동: 최종 리뷰 결과 보고. ### P1-R2 PRD 성공 기준 상태 동기화 — 2026-08-06 - 상태: 완료 - 무엇을: Phase Gate와 회귀 검증 증거에 따라 PRD §14 성공 기준 13개를 완료 상태로 동기화했다. - 왜: [`REV-P1-002`](./reviews/phase1-changed-field-requests.md)의 PRD·plan 완료 상태 불일치를 수정하기 위해서다. - TDD 예외 사유: 실행 코드가 아닌 완료 상태 문서 동기화다. - 어떻게: - `rg -n "^- \\[x\\]" docs/20260806_수정요청변경필드만전송/prd.md` — §14 완료 항목 13개 확인, exit 0. - `rg -n "^- \\[ \\]" docs/20260806_수정요청변경필드만전송/prd.md` — §18 요구사항 변경 체크리스트 5개만 유지, exit 0. - 대상 PRD·API Contract·plan·review `test -f` — 누락 없음, exit 0. - `git diff --check` — 출력 없음, exit 0. - 남은 항목: 없음. - 다음 행동: 최종 리뷰 결과 보고. ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | |---|---|---|---|---|---| | 2026-08-06 | `DEC-001` | 확정 | 기존 `PUT`·multipart·JSON transport를 유지하고 최종 변경 field만 보낸다. | 사용자 요청, OpenAPI update field optional 계약 | `P1-T1`~`P1-T5`, API Contract §1 | | 2026-08-06 | `DEC-002` | 확정 | 변경 field·file 0개면 저장 button disabled와 mutation 0건으로 처리한다. | 사용자 인터뷰 A안 | `P1-T1`~`P1-T5` | | 2026-08-06 | `DEC-003` | 확정 | file-only multipart는 required `request` part를 `{}`로 보낸다. | multipart contract와 빈 update object 허용 schema | `P1-T1`~`P1-T4` | | 2026-08-06 | `DEC-004` | 확정 | payload와 disabled 판정은 같은 domain request 결과를 사용한다. | 원복·trim·nullable 판정 불일치 방지 | `P1-T1`~`P1-T5` | | 2026-08-06 | `DEC-005` | 확정 | domain-local serializer를 사용하고 새 공통 diff abstraction을 만들지 않는다. | field별 nullable·array·file 의미 차이와 최소 변경 원칙 | `P1-T1`~`P1-T5` | | 2026-08-06 | `DEC-006` | 확정 | Community update의 `content`는 기존 원문 전송을 유지하고 현재 값과 기준값도 원문으로 비교한다. | `DIFF-002`의 같은 기존 직렬화 적용, 기존 update가 trim 없이 전송한 코드 | `P1-R1`, `REV-P1-001` | ## 발견된 문제 | ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 | |---|---|---|---|---|---| | `ISSUE-001` | High | 확정 | Character edit가 direct·optional·repeated 현재값 전체를 request에 넣는다. | `P1-T1` | 기준값 비교 serializer와 no-op test 추가 | | `ISSUE-002` | High | 확정 | Audio edit가 수정 화면에 없는 boolean을 포함한 전체 update DTO를 보낸다. | `P1-T2` | 화면 field만 비교·전송 | | `ISSUE-003` | High | 확정 | Community 일반 수정이 미변경 `isFixed`와 나머지 편집값을 함께 보낸다. | `P1-T3` | 일반 수정 request와 전용 action 유지 | | `ISSUE-004` | High | 확정 | Series는 state만 미변경 생략하고 나머지 edit field는 모두 보낸다. | `P1-T4` | 기존 state 원본 비교를 전체 edit field로 확장 | | `ISSUE-005` | Medium | 확정 | FanTalk reply는 update field가 content 하나라 변경 payload는 이미 최소지만 무변경 수정 요청을 차단하지 않는다. | `P1-T5` | edit submit disabled와 request 0건 test 추가 | | `ISSUE-006` | High | 해결 | Community update가 raw `content`를 전송하면서 변경 판정만 trim해 공백 변경을 누락한다. | `P1-R1` | [`REV-P1-001`](./reviews/phase1-changed-field-requests.md) 수정·검증 완료 | | `ISSUE-007` | Low | 해결 | Phase 1 구현·Gate 완료 후에도 PRD §14 성공 기준이 미완료로 남아 있다. | `P1-R2` | [`REV-P1-002`](./reviews/phase1-changed-field-requests.md) 수정·검증 완료 | ## 최종 보고 형식 ```markdown 구현 결과: 다섯 수정 기능의 변경 field 전송과 무변경 저장 차단 - 변경: domain별 serializer·form·test와 실제 payload - 결정: DEC-001~005 적용 결과 - 검증: - focused·도메인 회귀·mock Chromium E2E — 성공/실패와 test 수 - typecheck·lint·build·git diff --check — exit code와 핵심 결과 - Network 수동 검증 — 단일 field, nullable, false·0, file-only, 무변경 결과 - 남은 항목: 외부 의존, 후속 회귀 또는 없음 - 문서: PRD, API Contract, plan, review 링크 ``` 최종 보고는 성공을 추정하지 않고 실제 최신 검증 결과와 완료되지 않은 범위를 함께 기록한다.