Files
sodalive-ios/docs/20261006_선물_UI_추가/plan-task.md
T

1211 lines
92 KiB
Markdown

# 크리에이터 선물하기 사용자 기능 구현 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | Phase 11 코드·정적 Gate 승인, runtime 검증 대기 |
| 작성일 | `2026-10-06` |
| 요구사항 기준 | `docs/20261006_선물_UI_추가/prd.md` |
| API 기준 | `docs/20261006_선물_UI_추가/prd.md` §11 |
| 현재 Phase | Phase 11 선물 딥링크·정책 링크·크리에이터 표시 보정 |
| 현재 활성 Goal | `P11-GATE` 인증 runtime 검증 대기 |
## 목표
팬과 크리에이터가 iOS 앱에서 선물 신청, 선물함 조회, 상세 확인, 운송장 등록, 배송지 입력, 푸시/딥링크 상세 진입을 기존 앱 패턴으로 완료할 수 있게 한다.
## 현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---:|---|---:|---|---|
| 1 | 완료 | `5/5` | 없음 | 진입점 UI만 완료 |
| 2 | 완료 | `4/4` | 없음 | 없음 |
| 3 | 완료 | `4/4` | 없음 | 실서버 신청 QA는 최종 검증에서 수행 |
| 4 | 완료 | `4/4` | 없음 | 없음 |
| 5 | 완료 | `5/5` | 없음 | 실서버 취소·런타임 visual QA는 최종 검증에서 수행 |
| 6 | 진행 중 | `1/3` | `P6-T2` | 없음 |
| 7 | 대기 | `0/4` | `P7-T1` | Phase 5 필요 |
| 8 | 코드·정적 gate 승인, runtime 미완료 | `2/2` | `P8-GATE` 인증 runtime 검증 대기 | 인증 실행 환경과 Figma 접근 부재 |
| 9 | 구현·focused 검사 완료 | `2/2` | `P9-GATE` | 최종 build 및 Kakao/화면 runtime 미검증 |
| 10 | 코드·정적 gate 승인, runtime 미완료 | `6/6` | `P10-GATE` runtime 검증 대기 | 인증 실행 환경 부재 |
| 11 | 코드·정적 gate 승인, runtime 미완료 | `3/3` | `P11-GATE` 인증 runtime 검증 대기 | 인증 실행 환경과 화면 캡처 부재 |
완료된 Task와 검증 기록은 삭제하지 않는다. 후속 수정은 새 Task 또는 정정 기록으로 누적한다.
## 범위
### 포함
- 선물 보내기 신청 화면과 신청 API 연동.
- 선물함 리스트 화면과 `type=ALL` pagination.
- 공통 선물 상세 화면과 `direction` 기반 보낸/받은 UI 분기.
- 보낸 선물 운송장 등록, 신청 취소 확인, 취소 완료 UI.
- 받은 선물 배송지 입력, 배송지 입력 완료, 전달 완료, 전달 불가 UI.
- `deepLinkValue=GIFT_DETAIL`, `deepLinkId=applicationNo` 딥링크 진입.
- `gift-detail/{applicationNo}` 경로형 딥링크 진입.
- 선물 보내기 화면의 크리에이터 프로필 이미지, 파손 정책 링크, 채널 버튼 UI 보정.
### 제외
- 관리자 화면.
- 새 디자인 시스템 또는 새 dependency.
- 외부 택배 조회/운송장 실시간 검증.
- 약관 본문 API.
- 선물 이미지, 첨부파일, 메시지 카드.
- 임의 외부 URL 연결.
- Gift 모듈 브랜드 문구 변경. 현재 source가 이미 `소다라이브`이므로 추가 수정하지 않는다.
## 기술적 제약
- 기술 스택: SwiftUI, Combine, Moya, 기존 `ApiResponse<T>`, 기존 `AppState` navigation.
- 신규 기능 코드는 `SodaLive/Sources/V2/Gift/**` 아래에 작성한다.
- API는 `GiftApi: TargetType`, 저장소는 `final class GiftRepository`로 작성한다.
- 화면 상태는 `final class ...ViewModel: ObservableObject`에서 관리한다.
- 기존 `TitleBar`, `DefaultTitleBar`, `SodaV2ActionModal`, `sodaToast`, `LottieRefreshableScrollView`, 기존 picker/dialog/address search 패턴을 우선 재사용한다.
- 개인정보 필드는 display model 단계에서 숨긴다. View가 금지 필드를 우연히 그릴 수 없도록 `SENT`/`RECEIVED` display model을 분리한다.
- 현재 테스트 번들 타깃이 확인되지 않는다. ViewModel 순수 로직은 가능한 한 test 대상 후보로 계획하되, 실행 불가 시 Debug build와 수동 검증을 Phase Gate 증거로 남긴다.
## Task TDD 작성 규칙
구현 Task는 가능한 경우 아래 순서를 따른다.
- [ ] **RED:** 가장 작은 실패 test를 작성한다.
- [ ] **RED 확인:** focused test를 실행해 요구 동작 미구현 때문에 실패함을 확인한다.
- [ ] **GREEN:** RED를 통과시키는 최소 구현을 작성한다.
- [ ] **GREEN 확인:** 같은 focused test를 다시 실행해 성공을 확인한다.
- [ ] **REFACTOR:** 이번 Task가 만든 중복만 정리하고 focused test·직접 영향 회귀·build 결과를 Progress에 기록한다.
SwiftUI 화면 조립처럼 자동 테스트가 현실적으로 불가능한 Task는 TDD 예외 사유와 대체 검증 방법을 Task에 명시한다.
## Phase 1 선물 진입점 UI 추가
**Phase 결과:** 크리에이터 상세와 마이 탭에 선물 진입점 UI가 추가되고, 후속 연결 전까지 tap action은 inert 상태로 유지됐다.
**상태:** 완료.
### 완료된 구현 항목
- `P1-T1`: PRD와 plan-task 생성.
- `P1-T2`: `I18n.CreatorChannelHome.sendGift`, `I18n.MyPage.Category.giftBox` 추가.
- `P1-T3`: 크리에이터 상세 조건부 `선물 보내기` 버튼 추가.
- `P1-T4`: 마이 탭 `선물함` 버튼 추가.
- `P1-T5`: 크리에이터 상세 `선물 보내기` 버튼에서 아이콘 제거, 타인 채널 조건 추가.
### Phase 1 검증 기록
- `xcodebuild -workspace "SodaLive.xcworkspace" -list` 성공.
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 성공.
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공.
- `git diff --check` 성공.
- iOS 실기기/시뮬레이터 화면 캡처 QA는 미수행. 코드 확인과 빌드로 대체.
## Phase 2 선물 기능 기반 연결
**Phase 결과:** 선물 API, DTO, route, 공통 formatter/display policy가 준비되고 기존 진입점이 실제 화면 route로 연결된다.
**상태:** 완료.
**선행조건:** PRD 구현 기준 확정.
**Phase 완료 조건:** `P2-T1`~`P2-T4`와 `P2-GATE` 완료.
### 구현 항목
#### Task 2.1 API/DTO/Repository 추가
**Goal 실행 `P2-T1`:** 선물 기능 API 호출과 decoding에 필요한 기반 타입을 추가한다.
- **시작 조건:** PRD §11 확인.
- **완료 증거:** `GiftApi`, `GiftRepository`, request/response DTO가 compile 된다.
- **범위 밖:** 화면 UI 구현.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Repository/GiftApi.swift`
- Create: `SodaLive/Sources/V2/Gift/Repository/GiftRepository.swift`
- Create: `SodaLive/Sources/V2/Gift/Models/GiftModels.swift`
- Modify: `SodaLive.xcodeproj/project.pbxproj` if the project requires explicit file registration.
**Interfaces:**
- Produces: `GiftApi`, `GiftRepository`, `GiftFormOptionsResponse`, `CreateGiftRequest`, `GiftListResponse`, `GiftDetailResponse`, `RegisterGiftTrackingRequest`, `SubmitGiftRecipientAddressRequest`.
- [x] **RED:** DTO decoding sample을 사용해 `ApiResponse<GiftDetailResponse>` decode가 실패하는 최소 test를 작성한다. 테스트 타깃이 없으면 `GiftModels.swift` Preview/helper가 아닌 문서상 compile 검증으로 대체한다.
- [x] **RED 확인:** focused test가 있으면 실행해 type 미정의 실패를 확인한다. 없으면 `xcodebuild` 전 compile 실패 예상 지점을 기록한다.
- [x] **GREEN:** 아래 endpoint를 모두 포함하는 `GiftApi`와 repository method를 작성한다.
- `GET /api/v2/gifts/form-options`
- `POST /api/v2/gifts`
- `GET /api/v2/gifts?type=ALL&page=0&size=20`
- `GET /api/v2/gifts/{applicationNo}`
- `POST /api/v2/gifts/{applicationNo}/cancel`
- `POST /api/v2/gifts/{applicationNo}/tracking`
- `POST /api/v2/gifts/{applicationNo}/recipient-address`
- [x] **GREEN 확인:** `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`를 실행해 API/DTO compile 성공을 확인한다.
- [x] **REFACTOR:** DTO 이름과 optional 필드를 PRD §11과 대조하고 Progress에 차이를 기록한다.
#### Task 2.2 공통 표시 정책 추가
**Goal 실행 `P2-T2`:** 날짜, status, size description, 개인정보 display policy를 한 곳에서 재사용한다.
- **시작 조건:** `P2-T1` 완료.
- **완료 증거:** 화면 ViewModel이 사용할 pure helper가 compile 되고 status/date mapping이 PRD와 일치한다.
- **범위 밖:** SwiftUI 화면 배치.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Models/GiftDisplayModels.swift`
- Create: `SodaLive/Sources/V2/Gift/Models/GiftDisplayPolicy.swift`
**Interfaces:**
- Produces: `GiftStatus`, `GiftDirection`, `GiftProgressStep`, `GiftDisplayPolicy.sentDetail(from:)`, `GiftDisplayPolicy.receivedDetail(from:)`, `GiftDateFormatter.yyyyMMdd(fromUTC:)`, `GiftDateFormatter.mmDD(fromUTC:)`.
- [x] **RED:** `statusTimeline` 5단계와 `UNDELIVERABLE/CANCELED` 제외를 검증하는 가장 작은 test를 작성한다. 테스트 타깃이 없으면 helper 사용 지점과 expected table을 문서에 기록한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** status mapping, card text mapping, size description mapping, UTC ISO date formatting helper를 작성한다.
- [x] **GREEN 확인:** `SodaLive-dev` Debug build를 실행한다.
- [x] **REFACTOR:** 개인정보 금지 필드가 `RECEIVED` display model에 포함되지 않는지 코드로 대조한다.
#### Task 2.3 AppStep/ContentView route 연결
**Goal 실행 `P2-T3`:** 선물 보내기, 선물함, 선물 상세, 운송장 등록 route를 기존 navigation에 등록한다.
- **시작 조건:** `P2-T1` 완료.
- **완료 증거:** `AppStep` case와 `ContentView` switch가 compile 되고 기존 진입점 tap이 실제 route를 호출한다.
- **범위 밖:** 각 화면 내부 구현.
- **TDD 예외 사유:** navigation enum/switch 연결은 현재 UI integration test 기반이 없다.
- **대체 검증 방법:** 코드 대조와 Debug build.
**Files:**
- Modify: `SodaLive/Sources/App/AppStep.swift`
- Modify: `SodaLive/Sources/ContentView.swift`
- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- Modify: `SodaLive/Sources/MyPage/MyPageView.swift`
- Create: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- Create: `SodaLive/Sources/V2/Gift/List/GiftBoxListView.swift`
- Create: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- Create: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`
**Interfaces:**
- Produces: `.giftSend(recipientMemberId: Int, recipientCreatorNickname: String)`, `.giftBoxList`, `.giftDetail(applicationNo: String)`, `.giftTrackingRegistration(applicationNo: String)` or names matching nearby `AppStep` style.
- [x] `CreatorChannelTalkActionSection`의 inert action을 `AppState.shared.setAppStep(step: .giftSend(...))`로 교체한다.
- [x] `CategoryButtonsView`의 inert action을 `.giftBoxList` 이동으로 교체한다.
- [x] 신청 성공/취소 성공에서 사용할 replace 정책은 `AppState.replaceCurrentAppStep` 사용으로 문서화한다.
- [x] `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`를 실행한다.
#### Task 2.4 약관/주소/공통 action 정책 확정
**Goal 실행 `P2-T4`:** 약관 URL, 주소 검색 재사용 지점, 문의/정책 action fallback을 구현 기준으로 고정한다.
- **시작 조건:** `P2-T2` 완료.
- **완료 증거:** 구현자가 추정 없이 사용할 수 있는 constant/action map이 compile 된다.
- **범위 밖:** 약관 본문 API 또는 새 외부 URL.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Constants/GiftConstants.swift`
- [x] `giftTermsURLString = "https://app.notion.com/p/3eb27b359673802b937ae741ab7b4ad3"`를 추가한다.
- [x] `giftCourierCompanyNames`는 기존 공통 상수가 있으면 alias, 없으면 PRD §11.4 목록 그대로 local constant로 추가한다.
- [x] 저장소에 production 주소 검색/Kakao 우편번호 wrapper가 없으므로 `addressSearchAvailable = false`로 고정하고 이름/휴대폰/우편번호/주소/상세주소 수동 입력을 사용한다.
- [x] 문의와 피드백은 기존 `AppStep.serviceCenter`를 각각 `inquiryStep`, `feedbackStep`으로 재사용하고, 별도 정책 URL이 필요한 action은 `unavailableActionMessage = "준비 중입니다."` fallback을 사용한다.
#### Task 2.5 API 응답 계약 회귀 수정
**Goal 실행 `P2-R1`:** 실제 선물 API response key와 DTO를 일치시켜 폼 옵션과 상세 응답을 안정적으로 decode한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftModels.swift`
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- Modify: `work/tests/ReceivedGiftDetailCheck.swift`
- Create: `work/tests/GiftDecodingContractCheck.swift`
- Create: `work/tests/check-gift-decoding-contract.sh`
- [x] **RED:** 사용자 제공 response fixture로 `ApiResponse<GiftFormOptionsResponse>` decode 검사를 추가한다.
- [x] **RED 확인:** `data.categories[0].categoryName`의 `keyNotFound`를 확인한다.
- [x] **GREEN:** size/category의 `name`과 timeline의 `statusName`을 DTO에 반영하고 category UI 참조를 갱신한다.
- [x] **GREEN 확인:** 계약 fixture 21개 assertion과 기존 gift 회귀 검사, 양 스킴 generic iOS Debug build를 통과한다.
- [x] **REFACTOR:** 원인 확인용 DEBUG 로그, 진단 스크립트, debug journal을 제거하고 기존 오류 처리는 유지한다.
#### Task 2.6 상세 중첩 신청번호 null 회귀 수정
**Goal 실행 `P2-R2`:** 상세 응답의 `giftInfo.applicationNo=null`을 허용하고 top-level 신청번호를 권위 값으로 표시한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftModels.swift`
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftDisplayPolicy.swift`
- Modify: `work/tests/GiftDecodingContractCheck.swift`
- Modify: `work/tests/ReceivedGiftDetailCheck.swift`
- [x] **RED:** nested 신청번호가 null인 실제 응답 fixture와 top-level/nested 값이 다른 sent-detail mapping 검사를 추가한다.
- [x] **RED 확인:** decode의 `valueNotFound`와 nested 신청번호를 사용하는 mapping assertion 실패를 각각 확인한다.
- [x] **GREEN:** `GiftInfoResponse.applicationNo`를 optional로 변경하고 sent detail에는 `GiftDetailResponse.applicationNo`를 사용한다.
- [x] **GREEN 확인:** focused gift 검사, Swift parse, `git diff --check`, 양 스킴 generic iOS Debug build를 통과한다.
- [x] **REFACTOR:** custom decoder나 empty-string fallback 없이 두 production line 변경만 유지한다.
#### Task 2.7 전달 불가 정책 URL 연결
**Goal 실행 `P2-R3`:** 선물 상세의 전달 불가 `선물 정책 확인` 버튼을 약관 `(필수)` 링크와 같은 `GiftConstants.termsURL`에 연결한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- Modify: `work/tests/check-received-gift-detail.sh`
- [x] **RED:** 발신·수신 정책 action의 `openURL` 연결과 기존 toast fallback 제거를 요구하는 source 검사를 추가한다.
- [x] **RED 확인:** `received policy action opens gift terms` 실패를 확인한다.
- [x] **GREEN:** 두 action bar의 `onPolicy`를 공통 `openGiftTerms` 함수로 연결하고 `GiftConstants.termsURL`을 SwiftUI `openURL`로 연다.
- [x] **GREEN 확인:** focused gift 검사, Swift parse, `git diff --check`, 양 스킴 generic iOS Debug build를 통과한다.
- [x] **REFACTOR:** 정책 전용 toast state와 `준비 중입니다.` fallback만 제거하고 문의 action은 유지한다.
### 검증 방법
#### Phase 2 Gate
**Goal 실행 `P2-GATE`:** API/route/common policy 기반이 후속 화면 구현을 막지 않는지 확인한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
```
**Expected:** 두 명령 exit code 0. 신규 route case, API DTO, common policy compile 성공.
수동 검증:
- [x] 크리에이터 상세 `선물 보내기` tap이 `GiftSendView` route를 호출한다. (코드 경로 대조)
- [x] 마이 탭 `선물함` tap이 `GiftBoxListView` route를 호출한다. (코드 경로 대조)
- [x] 약관 URL과 courier constant가 PRD와 일치한다.
## Phase 3 선물 보내기 신청
**Phase 결과:** 팬이 선물 보내기 폼을 작성하고 신청 성공 후 상세로 replace 이동한다.
**상태:** 완료.
**선행조건:** `P2-GATE` 완료.
### 구현 항목
#### Task 3.1 폼 옵션 로딩 ViewModel
**Goal 실행 `P3-T1`:** 선물 보내기 화면 진입 시 recipient context와 form options를 로딩한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Send/GiftSendViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- [x] **RED:** `memberId` 또는 nickname 누락 시 submit 불가 상태가 되는 validation test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** `fetchFormOptions()`, 기본 size 첫 항목 선택, category list state, loading/error state를 구현한다.
- [x] **GREEN 확인:** focused test 또는 Debug build를 실행한다.
- [x] **REFACTOR:** `salePriceCan` CTA 문구와 `basePriceCan != salePriceCan` 표시 조건을 PRD와 대조한다.
#### Task 3.2 선물 보내기 폼 UI
**Goal 실행 `P3-T2`:** Figma 구조의 안내 배너, 크리에이터 row, size/category, sender input, 약관 동의를 구현한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- Create: `SodaLive/Sources/V2/Gift/Send/Components/GiftSendBannerView.swift`
- Create: `SodaLive/Sources/V2/Gift/Send/Components/GiftSenderFormView.swift`
- Create: `SodaLive/Sources/V2/Gift/Components/GiftAgreementRow.swift`
- [x] **TDD 예외 사유:** SwiftUI layout 구현이며 현재 UI test 타깃이 없다.
- [x] Figma node `2481:18904`, `2481:18957` 기준으로 navigation 고정, 본문 scroll, bottom CTA 구조를 구현한다.
- [x] category 선택 시 `requiresDamageWaiver`에 따라 파손 면책 row를 표시/숨김 처리한다.
- [x] `(필수)` tap은 확정 Notion URL을 연다.
- [x] `GiftConstants.addressSearchAvailable == false`이므로 이름/휴대폰/우편번호/주소/상세주소를 수동 입력한다.
- [x] Debug build를 실행한다.
#### Task 3.3 신청 validation과 CTA 상태
**Goal 실행 `P3-T3`:** 필수 입력/동의 조건이 충족될 때만 submit 가능하게 한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- [x] **RED:** 필수값 누락, 약관 미동의, 파손 면책 필요/미동의 case의 `canSubmit == false` test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** `canSubmit`, submit 중 중복 탭 방지, request payload mapping을 구현한다.
- [x] **GREEN 확인:** focused test 또는 Debug build를 실행한다.
- [x] **REFACTOR:** `senderAddressDetail`은 request에는 포함하되 CTA 필수 조건에는 포함하지 않는지 확인한다.
#### Task 3.4 신청 API와 성공 navigation
**Goal 실행 `P3-T4`:** 신청 API 성공 시 `applicationNo` 상세로 replace 이동한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- [x] **RED:** 성공 응답의 `applicationNo`가 navigation callback으로 전달되는 test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** `POST /api/v2/gifts` 호출, 실패 message 표시, 성공 toast/dialog 없이 상세 replace 이동을 구현한다.
- [x] **GREEN 확인:** focused test 또는 Debug build를 실행한다.
- [x] **REFACTOR:** 실패 시 입력값 유지와 submit state reset을 확인한다.
### 검증 방법
#### Phase 3 Gate
**Goal 실행 `P3-GATE`:** 선물 신청 사용자 흐름을 판정한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
수동 검증:
- [x] `GET /api/v2/gifts/form-options`가 화면 진입 시 호출된다. (코드 경로 대조)
- [x] 필수 입력/동의 누락 시 `POST /api/v2/gifts`가 호출되지 않는다. (focused 로직 실행·코드 대조)
- [x] 신청 성공 후 상세로 replace 이동하고 back으로 입력 폼에 돌아가지 않는다. (guarded replace 코드 대조)
## Phase 4 선물함 리스트
**Phase 결과:** 사용자가 선물함에서 보낸/받은 선물 목록, empty, 카드별 상태, pagination을 확인하고 상세로 이동한다.
**선행조건:** `P2-GATE` 완료.
### 구현 항목
#### Task 4.1 목록 ViewModel과 pagination
**Goal 실행 `P4-T1`:** `type=ALL` 목록과 `hasNext` 기반 pagination을 구현한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/List/GiftBoxListViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/List/GiftBoxListView.swift`
- [x] **RED:** 첫 page가 `type=ALL&page=0&size=20`으로 요청되는 repository interaction test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** first page, next page, refresh, duplicate load guard를 구현한다.
- [x] **GREEN 확인:** focused test 또는 Debug build를 실행한다.
- [x] **REFACTOR:** 서버 응답 순서를 변경하는 sort가 없는지 확인한다.
#### Task 4.2 리스트 카드 display model
**Goal 실행 `P4-T2`:** `direction`과 `status`에 따른 보낸/받은 카드 문구를 구현한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftDisplayModels.swift`
- Create: `SodaLive/Sources/V2/Gift/List/Components/GiftBoxCardView.swift`
- [x] **RED:** PRD의 보낸/받은 status mapping table test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** 카드 display model과 card view를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
#### Task 4.3 리스트 화면 UI 상태
**Goal 실행 `P4-T3`:** 안내 배너, empty, 카드별 배송지 필요 상태, 문의/의견 버튼을 구현한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/List/GiftBoxListView.swift`
- Create: `SodaLive/Sources/V2/Gift/List/Components/GiftBoxInfoBannerView.swift`
- Create: `SodaLive/Sources/V2/Gift/List/Components/GiftBoxEmptyView.swift`
- [x] **TDD 예외 사유:** SwiftUI 상태별 layout이며 UI test 타깃이 없다.
- [x] Figma section title `신청 내역`, `선물 내역`을 표시하지 않는다.
- [x] 보낸 선물 목록은 상단 안내 배너 `사서함을 통해 안전하게 전달됩니다`를 표시한다.
- [x] 받은 선물 목록에 item이 있으면 상단 안내 배너 `배송지 입력 기간 안내`를 표시한다.
- [x] 보낸 empty는 안내 배너만 표시한다.
- [x] 받은 empty는 제목/본문/`의견 남기기` 버튼을 표시한다.
- [x] 별도 파란 상단 배너를 만들지 않는다.
- [x] `direction=RECEIVED && status=TRACKING_REGISTERED` item은 해당 카드 안에 배송지 입력 필요 상태를 표시한다.
- [x] 배송지 입력 필요 카드를 tap하면 해당 item의 상세로 이동한다.
- [x] Debug build를 실행한다.
#### Task 4.4 상세 navigation 연결
**Goal 실행 `P4-T4`:** 리스트 item tap이 같은 상세 화면으로 진입한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/List/GiftBoxListView.swift`
- [x] 카드 tap 시 `.giftDetail(applicationNo:)` route를 호출한다.
- [x] pagination loading footer와 tap 영역이 충돌하지 않는지 확인한다.
- [x] Debug build를 실행한다.
### 검증 방법
#### Phase 4 Gate
**Goal 실행 `P4-GATE`:** 선물함 목록 사용자 흐름을 판정한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
수동 검증:
- [x] 최초 목록 요청은 `type=ALL&page=0&size=20`이다.
- [x] `direction`으로 보낸/받은 카드가 분기된다.
- [x] 리스트 item tap이 상세 API 기반 화면으로 이동한다.
## Phase 5 공통 상세와 보낸 선물 상세
**Phase 결과:** 상세 API 결과가 공통 상세 화면에서 `direction=SENT` 기준으로 안전하게 표시되고 취소 flow가 동작한다.
**선행조건:** `P2-GATE` 완료.
### 구현 항목
#### Task 5.1 상세 ViewModel
**Goal 실행 `P5-T1`:** `applicationNo`로 상세 API를 조회하고 direction/status 기반 render state를 만든다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Detail/GiftDetailViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] **RED:** `direction=SENT/RECEIVED/CANCELED` render state mapping test를 작성한다.
- [x] **RED 확인:** focused test 또는 compile 실패를 확인한다.
- [x] **GREEN:** load, retry, loading/error state, render state mapping을 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
#### Task 5.2 progress와 공통 상세 UI
**Goal 실행 `P5-T2`:** `statusTimeline` 기반 5단계 progress와 공통 card section을 구현한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/GiftProgressView.swift`
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/GiftInfoSectionView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] **RED:** `UNDELIVERABLE`, `CANCELED`가 progress step에 추가되지 않는 mapping test를 작성한다.
- [x] **GREEN:** progress, loading, retry UI를 구현한다. 카드 폭은 고정하지 않고 기기의 가용 폭을 채운다.
- [x] **GREEN 확인:** Debug build를 실행한다.
#### Task 5.3 보낸 상세 UI와 개인정보 정책
**Goal 실행 `P5-T3`:** 보낸 팬 관점 상세 hero, 선물 정보, 보내는 사람, 사서함, 사유 섹션을 구현한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/SentGiftDetailContentView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] **RED:** `recipientAddress`가 sent display model에 포함되지 않는 test를 작성한다.
- [x] **GREEN:** `status`별 hero 문구와 sections를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** `senderInfo`와 `mailbox`는 nil일 때 섹션이 숨겨지는지 확인한다.
#### Task 5.4 보낸 상세 action bar
**Goal 실행 `P5-T4`:** 운송장 등록, 신청 취소, 문의/정책/리뷰 action 조건을 구현한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/GiftDetailActionBar.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] `trackingRequired=true`일 때만 `운송장 작성하기`를 표시한다.
- [x] `direction=SENT && status=RECEIVED`일 때만 신청 취소 action을 표시한다.
- [x] 기존 리뷰 플로우가 있으면 `DELIVERED` 팬 리뷰 action을 연결하고, 없으면 숨긴다.
- [x] 기존 문의/정책 경로가 없으면 숨기거나 `준비 중입니다.` toast를 사용한다.
- [x] Debug build를 실행한다.
#### Task 5.5 신청 취소 dialog와 취소 완료 UI
**Goal 실행 `P5-T5`:** 확인 dialog 이후 취소 API를 호출하고 성공 시 취소 완료 UI로 replace 한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/GiftCancelCompleteView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] **RED:** dialog confirm 전에는 cancel API가 호출되지 않는 test를 작성한다.
- [x] **GREEN:** `SodaV2ActionModal` 기반 확인 dialog와 cancel mutation을 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** cancel 실패 시 dialog를 닫지 않고 오류를 표시하는지 확인한다.
### 검증 방법
#### Phase 5 Gate
**Goal 실행 `P5-GATE`:** 공통 상세와 보낸 상세 flow를 판정한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
수동 검증:
- [x] `direction=SENT`에서 `recipientAddress`가 표시되지 않는다.
- [x] `trackingRequired=true`일 때만 운송장 CTA가 보인다.
- [x] 신청 취소는 확인 dialog confirm 이후에만 API를 호출한다.
- [x] `status=CANCELED`는 취소 완료 UI를 표시한다.
## Phase 6 운송장 등록
**Phase 결과:** 보낸 팬이 운송장 등록 조건에서 택배사와 운송장 번호를 제출하고 상세 최신 상태로 돌아간다.
**선행조건:** `P5-GATE` 완료.
### 구현 항목
#### Task 6.1 운송장 등록 화면 진입 검증
**Goal 실행 `P6-T1`:** 화면 진입 시 상세를 재조회하고 등록 가능 상태를 검증한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`
- [x] **RED:** `trackingRequired=false` 또는 `mailbox=nil`이면 submit 불가 test를 작성한다.
- [x] **GREEN:** 상세 재조회, mailbox 표시, 등록 불가 오류 표시를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
#### Task 6.1-R1 진입 화면 리뷰 보완
**Goal 실행 `P6-R1`:** 비활성 중 토스트 종료와 긴 오류 문구에서도 상세 복귀·재시도 접근성을 유지한다.
- **시작 조건:** `REV-P6-001`, `REV-P6-002`
- **완료 증거:** lifecycle 보완, 오류 영역 scroll 적용, 양 스킴 Debug build, 재리뷰 승인.
- **범위 밖:** 택배사 선택, 운송장 입력, 등록 mutation.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`
- Review: `docs/20261006_선물_UI_추가/reviews/phase6-tracking-entry-validation.md`
- [x] 재등장 시 pending 상세 복귀를 기존 exactly-once callback으로 처리한다.
- [x] 긴 오류 메시지와 큰 글자에서도 재시도 버튼까지 scroll 접근 가능하게 한다.
- [x] focused 검증과 양 스킴 Debug build를 실행한다.
#### Task 6.2 운송장 입력 UI
**Goal 실행 `P6-T2`:** 신청 요약, 보내실 주소, 택배사 select, 운송장 input, 안내, bottom action bar와 신청 취소 확인 UI를 구현한다. 이 Task는 표시·입력 상태만 소유하며 mutation은 `P6-T3`에서 연결한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationViewModel.swift`
- Create: `SodaLive/Sources/V2/Gift/Tracking/Components/GiftCourierPickerView.swift`
- Modify: `SodaLive.xcodeproj/project.pbxproj`
- [x] **TDD 예외 사유:** SwiftUI layout 구현이며 UI test 타깃이 없다.
- [x] Figma node `2481:19066`, `2481:19109` 기준으로 구현한다.
- [x] 왼쪽 `신청 취소`, 오른쪽 `등록 완료` bottom action bar를 구현한다.
- [x] Debug build를 실행한다.
#### Task 6.3 운송장 등록과 신청 취소 mutation
**Goal 실행 `P6-T3`:** 택배사와 운송장 번호 validation 후 등록 API를 호출하고, 확인된 신청 취소는 기존 취소 API로 처리한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`
- [x] **RED:** 택배사 미선택 또는 공백 운송장 번호면 `canSubmit == false` test를 작성한다.
- [x] **GREEN:** `POST /tracking`, submit 중 중복 탭 방지, 성공 toast, 상세 replace/복귀를 구현한다.
- [x] **GREEN:** 신청 취소 확인 이후에만 `POST /cancel`을 호출하고 등록/취소 mutation 상호 중복을 막는다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** 성공 후 상세 API가 재조회되는지 확인한다.
### 검증 방법
#### Phase 6 Gate
**Goal 실행 `P6-GATE`:** 운송장 등록 flow를 판정한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
수동 검증:
- [ ] `trackingRequired=false` 상세에서는 등록 화면에 머물지 않는다.
- [ ] `mailbox=null`이면 등록을 막고 오류를 표시한다.
- [ ] 등록 성공 후 상세 재조회 결과로 `TRACKING_REGISTERED`가 표시된다.
## Phase 7 받은 상세와 배송지 입력
**Phase 결과:** 받은 크리에이터가 배송지 입력 필요 상태에서 배송지를 제출하고, 이후 상태별 받은 상세를 안전하게 확인한다.
**선행조건:** `P5-GATE` 완료.
### 구현 항목
#### Task 7.1 받은 상세 display policy
**Goal 실행 `P7-T1`:** 받은 상세에서 표시 금지 필드가 UI 모델에 포함되지 않게 한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftDisplayPolicy.swift`
- Create: `SodaLive/Sources/V2/Gift/Detail/Components/ReceivedGiftDetailContentView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- Modify: `SodaLive.xcodeproj/project.pbxproj`
- Create: `work/tests/ReceivedGiftDetailCheck.swift`
- Create: `work/tests/check-received-gift-detail.sh`
- [x] **RED:** `senderInfo`, `mailbox`, `applicationNo`, `paidCan`, `tracking`이 received display model에 포함되지 않는 compile-fail probe를 작성하고, 받은 상세 component 부재 RED를 확인한다.
- [x] **GREEN:** 기존 privacy-safe display model을 유지하고 받은 상세 hero와 선물 정보 component를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
#### Task 7.2 배송지 입력 UI
**Goal 실행 `P7-T2`:** `recipientAddressRequired=true`일 때 배송지 입력 UI를 표시한다.
**Files:**
- Create: `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressFormView.swift`
- Create: `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- [x] **TDD 예외 사유:** SwiftUI form layout 구현이며 UI test 타깃이 없다. 구조 RED/GREEN과 build로 대체한다.
- [ ] Figma node `2531:36290` 기준으로 개인정보 안내 배너, 전달 예정 선물, 배송지 입력, 약관 동의, bottom CTA를 구현한다. (node 접근 불가로 문서·기존 패턴 기반 구현만 완료, fresh 원본 대조 미수행)
- [x] `(필수)` tap은 확정 Notion URL을 연다.
- [x] `GiftConstants.addressSearchAvailable == false`이므로 이름/휴대폰/우편번호/주소/상세주소를 수동 입력한다.
- [x] Debug build를 실행한다.
#### Task 7.3 배송지 입력 validation과 submit
**Goal 실행 `P7-T3`:** 필수값과 약관 동의 후 배송지 입력 API를 호출하고 상세를 재조회한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressViewModel.swift`
- Modify: `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressFormView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- Create: `work/tests/GiftRecipientAddressCheck.swift`
- Create: `work/tests/check-gift-recipient-address.sh`
- Modify: `work/tests/check-received-gift-detail.sh`
- [x] **RED:** 이름/휴대폰/우편번호/주소/약관 누락 case의 `canSubmit == false` test를 작성한다.
- [x] **GREEN:** `POST /recipient-address`, submit 중 중복 탭 방지, 성공 toast, 상세 reload를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** 실패 시 입력값 유지와 error 표시를 확인한다.
#### Task 7.4 받은 상세 상태별 UI
**Goal 실행 `P7-T4`:** 배송지 입력 완료, 전달 완료, 전달 불가 받은 상세 UI를 구현한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftDisplayModels.swift`
- Modify: `SodaLive/Sources/V2/Gift/Models/GiftDisplayPolicy.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/Components/ReceivedGiftDetailContentView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/GiftDetailView.swift`
- Modify: `work/tests/ReceivedGiftDetailCheck.swift`
- Modify: `work/tests/check-received-gift-detail.sh`
- [x] **RED:** `recipientAddress=null`이면 주소 섹션 hidden, `UNDELIVERABLE`이면 주소/입력 UI hidden test를 작성한다.
- [x] **GREEN:** `TRACKING_REGISTERED`, `ARRIVED_AT_MAILBOX`, `INSPECTION_COMPLETED`, `DELIVERED`, `UNDELIVERABLE` UI를 구현한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** 전달 불가 정책 action은 임의 URL을 열지 않는지 확인한다.
#### Task 7.R1 전달 불가 안내 문구 보정
**Goal 실행 `P7-R1`:** 발신·수신 전달 불가 상세의 사유 아래 붉은 안내 문구를 Figma 문구로 교체한다.
**Files:**
- Modify: `SodaLive/Sources/V2/Gift/Detail/Components/SentGiftDetailContentView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Detail/Components/ReceivedGiftDetailContentView.swift`
- Modify: `work/tests/check-received-gift-detail.sh`
- Modify: `docs/20261006_선물_UI_추가/prd.md`
- Modify: `docs/20261006_선물_UI_추가/plan-task.md`
- [x] **RED:** `work/tests/check-received-gift-detail.sh`의 전달 불가 안내 문구 기대값을 Figma 문구로 바꾼다.
- [x] **RED 확인:** `bash work/tests/check-received-gift-detail.sh received-states`가 기존 화면 문구 때문에 `FAIL received-states: disposal warning`으로 실패하는지 확인한다.
- [x] **GREEN:** 발신·수신 상세의 기존 `해당 선물은 정책에 따라 폐기되며 반송되지 않습니다.` 문구를 `해당 선물은 보관기간(입고일 포함 최대 7일) 내에 반송 등 필요한 조치가 완료되지 않을 경우, 사전에 동의한 약관 및 폐기 등의 내용에 따라 폐기될 수 있습니다. 자세한 내용은 고객센터로 문의해 주세요.`로 교체한다.
- [x] **GREEN 확인:** `bash work/tests/check-received-gift-detail.sh received-states`, `bash -n work/tests/check-received-gift-detail.sh`, `git diff --check`를 실행해 성공을 확인한다.
- [x] **REFACTOR:** 추가 추상화 없이 기존 폰트·색상·조건부 표시를 유지했는지 diff로 확인하고 Progress에 검증 결과를 남긴다.
### 검증 방법
#### Phase 7 Gate
**Goal 실행 `P7-GATE`:** 받은 상세와 배송지 입력 flow를 판정한다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
수동 검증:
- [ ] 받은 상세에서 팬의 이름/휴대폰/주소가 표시되지 않는다.
- [ ] `recipientAddressRequired=true`일 때 배송지 입력 UI가 표시된다.
- [ ] 배송지 입력 성공 후 상세 재조회로 입력 완료 UI가 표시된다.
- [ ] `UNDELIVERABLE`이면 배송지 입력 UI와 주소 섹션이 숨겨진다.
## Phase 8 푸시/딥링크와 최종 회귀
**Phase 결과:** 선물 푸시/딥링크가 상세 화면으로 진입하고 전체 사용자 흐름이 build와 수동 검증으로 확인된다.
**선행조건:** `P5-GATE`, `P7-GATE` 완료.
**현재 판정:** 코드·정적 gate 승인. 인증 환경의 전체 사용자 흐름 gate는 미완료이며, 아래 수동 검증 항목은 모두 미수행이다.
### 구현 항목
#### Task 8.1 딥링크 action 추가
**Goal 실행 `P8-T1`:** `deepLinkValue=GIFT_DETAIL`, `deepLinkId=applicationNo`를 `.giftDetail` route로 연결한다.
**Files:**
- Modify: `SodaLive/Sources/App/AppDeepLinkHandler.swift`
- Create: `work/tests/AppDeepLinkGiftDetailCheck.swift`
- Create: `work/tests/check-app-deep-link-gift-detail.sh`
- [x] **RED:** URL/query parser가 `GIFT_DETAIL`과 `applicationNo`를 action으로 만드는 test를 작성한다. 테스트 타깃이 없으면 parser case table을 Progress에 기록한다.
- [x] **GREEN:** `AppDeepLinkAction.giftDetail(applicationNo:)`와 parser/apply 분기를 추가한다.
- [x] **GREEN 확인:** Debug build를 실행한다.
- [x] **REFACTOR:** 딥링크 진입이 보낸/받은 화면을 미리 결정하지 않고 상세 API 결과를 기다리는지 확인한다.
기존 `AppStep.giftDetail(applicationNo:)`를 재사용하므로 `AppStep.swift` 수정은 없다. 테스트 번들 대신 production-source Swift harness로 parser와 적용 경로를 검증한다.
#### Task 8.2 최종 회귀와 문서 갱신
**Goal 실행 `P8-T2`:** 전체 선물 기능의 검증 결과와 남은 항목을 문서에 누적한다.
**Files:**
- Modify: `docs/20261006_선물_UI_추가/plan-task.md`
- Create: `docs/20261006_선물_UI_추가/reviews/phase8-gift-user-flow.md`
- [x] `SodaLive` Debug build를 실행한다.
- [x] `SodaLive-dev` Debug build를 실행한다.
- [x] PRD §14 체크리스트를 기준으로 수동 검증 결과를 Progress에 기록한다. 인증 환경 부재로 미수행임을 기록하며, 수용 기준 통과를 뜻하지 않는다.
- [x] 코드 리뷰 리포트를 `docs/sample/sample-review.md` 구조로 작성한다.
### 검증 방법
#### Phase 8 Gate
**Goal 실행 `P8-GATE`:** 전체 선물 사용자 기능을 최종 판정한다.
- [x] 코드·정적 gate: focused checks, 양 스킴 generic iOS Debug build, Swift/shell parse, `git diff --check`, Oracle 승인 근거를 기록한다.
전체 인증 runtime 사용자 흐름 gate는 미완료다. 실제 푸시 수신, 인증 딥링크·navigation·replace 이동과 아래 수동 journey는 검증하지 않았다.
```bash
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build
git diff --check
```
수동 검증:
- [ ] 선물 보내기 신청 성공 후 상세 replace 이동.
- [ ] 선물함 리스트 item 상세 이동.
- [ ] 푸시/딥링크 상세 이동.
- [ ] 보낸 상세 개인정보 경계.
- [ ] 받은 상세 개인정보 경계.
- [ ] 운송장 등록 성공 후 상세 갱신.
- [ ] 배송지 입력 성공 후 상세 갱신.
- [ ] 취소 confirm 이후 API 호출과 취소 완료 replace.
## Phase 9 공용 주소 검색과 제한적 Figma 정렬
**Goal 실행 `P9-T1`:** 현재 size source를 고정하고 typed result/source 계약의 feature-missing RED를 확인한다.
**Files:** `work/tests/check-gift-address-search.sh`, `work/tests/KakaoPostcodeResultCheck.swift`.
- [x] 정상 문자열, 선행 0, address/roadAddress 차이, 누락/타입/공백 거부와 source 계약 검사를 실행한다.
- [x] `GiftSendView.sizeSection`, `sizeTitle` 현재 source SHA256 보존 검사를 고정한다.
**Goal 실행 `P9-T2`:** 공유 주소 컴포넌트와 Kakao embed sheet를 연결하고 제공된 Figma geometry/copy를 적용한다.
**Files:** `SodaLive/Sources/V2/Component/Address/{KakaoPostcodeSearchView,SodaAddressInputView}.swift`, `SodaLive/Sources/V2/Gift/Send/Components/GiftSenderFormView.swift`, `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressFormView.swift`, `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`, 필요 시 `SodaLive.xcodeproj/project.pbxproj`.
- [x] HTTPS CDN/HTML/JS bridge와 경계 검증, 닫기/loading/error, handler/delegate 해제를 구현한다.
- [x] readonly zip/base 및 editable detail을 양 폼에 연결하고 category만 compact화한다.
**Goal 실행 `P9-T3`:** 독립 리뷰에서 확인한 주소 재선택 및 Figma copy 결함을 수정한다.
**Files:** `SodaLive/Sources/V2/Component/Address/SodaAddressInputView.swift`, `work/tests/check-gift-address-search.sh`.
- [x] 다른 기본 주소 선택 시 이전 `addressDetail`을 초기화한다.
- [x] `우편번호`, `주소`, `상세주소` 가시 라벨과 `주소 검색` 버튼 문구를 적용한다.
- [x] 위 동작의 focused RED/GREEN을 기록한다.
**Goal 실행 `P9-GATE`:** focused GREEN, 기존 recipient 회귀, Swift parse 및 project lint 결과를 기록한다.
- [x] `bash work/tests/check-gift-address-search.sh` exit 0.
- [x] `bash work/tests/check-gift-recipient-address.sh`, `bash work/tests/check-received-gift-detail.sh recipient-form` exit 0.
- [x] 변경 Swift parse, project lint, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했다.
- [ ] 실제 Kakao 검색/선택/취소/실패 및 screenshot 기반 fidelity는 runtime 증거 확보 후 판정한다.
### Phase 9 검증 기록
- `P9-T3` 상세주소 재선택 수정: production 편집 전 focused check는 `FAIL gift-address-search: clear detail only when old postcode/base pair differs, before assigning selection`로 RED(exit 1). 기존 zip/base 쌍과 선택 결과가 다를 때만 상세주소를 비우고 이후 zip/base를 갱신하도록 수정한 뒤 같은 검사 GREEN(exit 0). 동일 쌍은 상세주소를 유지하며, 변경은 선택 callback에만 있어 취소 경로는 기존 값을 변경하지 않는다.
- `P9-T3` Figma 라벨/버튼 수정: 별도 source assertion 추가 후 `FAIL gift-address-search: visible address label 우편번호`로 RED(exit 1). `우편번호`, `주소`, `상세주소` visible label과 `주소 검색` 버튼/접근성 문구를 적용한 뒤 같은 검사 GREEN(exit 0). 기존 placeholder·토큰·간격·binding·sheet와 필드 접근성은 유지했다.
- `P9-T3` 두 GREEN 실행의 출력: `PASS gift-address-search: sizeSection and sizeTitle preserved`, `PASS gift-address-search: bridge, lifecycle, readonly shared forms and compact category source contracts`, `PASS KakaoPostcodeResult: 15 assertions`. 실행 명령은 모두 `bash work/tests/check-gift-address-search.sh`이며 runtime UI 검증 결과가 아닌 source/typed-result 검사 결과다.
- 구현 전: 기존 source와 공용 spacing/radius/font를 확인했다. Xcode 프로젝트는 앱 source에 명시적 PBXGroup/PBXBuildFile을 사용하므로 두 새 파일의 양 타깃 등록이 필요하다.
- RED (production 편집 전): `bash work/tests/check-gift-address-search.sh` exit 1, `FAIL gift-address-search: missing KakaoPostcodeSearchView.swift`; 기존 `sizeSection and sizeTitle preserved`는 PASS. `xcrun swiftc -typecheck work/tests/KakaoPostcodeResultCheck.swift -parse-as-library`는 `cannot find 'KakaoPostcodeResult' in scope`로 exit 1.
- GREEN: 같은 focused script exit 0. 출력은 `PASS gift-address-search: sizeSection and sizeTitle preserved`, `PASS gift-address-search: bridge, lifecycle, readonly shared forms and compact category source contracts`, `PASS KakaoPostcodeResult: 15 assertions`.
- 회귀: `bash work/tests/check-gift-recipient-address.sh`는 `PASS: 266 assertions`; `bash work/tests/check-received-gift-detail.sh recipient-form`은 `PASS recipient-form: injection, submission, toast lifecycle, GET-only retry and fallback`.
- `xcrun swiftc -parse`로 신규 2개와 변경 기존 3개 Swift 파일을 검사해 exit 0. `plutil -lint SodaLive.xcodeproj/project.pbxproj` 결과 `OK`.
- 최종 source 재검증: focused script, 5개 Swift parse, `bash -n work/tests/check-gift-address-search.sh`, project lint 모두 exit 0. 신규 공용 2개와 sender 폼은 임시 theme stub을 사용한 `xcrun swiftc -typecheck -sdk "$(xcrun --sdk iphoneos --show-sdk-path)" -target arm64-apple-ios16.0`에서도 exit 0이며 임시 stub은 삭제했다. 이는 실제 앱 전체 빌드를 대체하지 않는다.
- 공용 두 source는 250 LOC 미만이며 새 dependency/route/API/ViewModel 변경이 없다. 크기 선택 source SHA256은 production 편집 전 작업본과 동일하다.
- 최종 gate: focused/recipient/received/decoding/deep-link 회귀, 5개 Swift parse, shell parse, project lint, `git diff --check`가 성공했다. `SodaLive-dev`, `SodaLive` generic iOS Debug build는 각각 `BUILD SUCCEEDED`이며 독립 재리뷰는 기능 `APPROVE`, Figma 구조·CJK 정적 검토 `APPROVE_STATIC`이다.
- 한계: LSP diagnostics 도구가 제공되지 않아 수행하지 않았다. 실제 WKWebView의 Kakao 네트워크 검색/선택/취소/실패와 인증 화면 screenshot 기반 픽셀·keyboard·Dynamic Type·VoiceOver QA는 미수행이다. 정적 승인과 build 성공은 runtime 또는 pixel fidelity PASS를 의미하지 않는다.
## Phase 10 선물 연락처 입력 제한
**Phase 결과:** 보내는 사람과 받는 크리에이터 배송지 폼이 한글 IME 조합을 보존하면서 이름·휴대폰 입력 규칙을 동일하게 적용한다.
### Task 10.1 입력 규칙 RED
**Files:** `work/tests/GiftContactInputCheck.swift`, `work/tests/check-gift-contact-input.sh`.
- [x] 이름의 한글·영문 1~12자 허용, 공백·숫자·기호·emoji 거부와 13번째 문자 거부를 RED로 확인한다.
- [x] 휴대폰의 ASCII 숫자 1~11자 허용, 비숫자와 12번째 문자 거부를 RED로 확인한다.
- [x] 양 폼과 양 ViewModel이 같은 규칙을 사용하는 source contract RED를 확인한다.
### Task 10.2 IME-safe 공용 입력 필드
**Files:** `SodaLive/Sources/V2/Gift/Components/GiftContactTextField.swift`, `SodaLive.xcodeproj/project.pbxproj`.
- [x] `UIViewRepresentable`과 `UITextFieldDelegate`를 사용하고 `markedTextRange != nil` 동안 binding을 재할당하지 않는다.
- [x] 조합이 끝난 prospective text만 `NSString`/`NSRange`로 계산해 이름 12자·휴대폰 11자 경계를 적용한다.
- [x] number pad는 휴대폰에만 표시하고 붙여넣기도 동일 규칙으로 거부한다.
### Task 10.3 양 폼·제출 방어 연결
**Files:** `SodaLive/Sources/V2/Gift/Send/Components/GiftSenderFormView.swift`, `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressFormView.swift`, `SodaLive/Sources/V2/Gift/Send/GiftSendViewModel.swift`, `SodaLive/Sources/V2/Gift/RecipientAddress/GiftRecipientAddressViewModel.swift`.
- [x] 이름·휴대폰 SwiftUI `TextField`만 공용 입력 필드로 교체하고 기존 layout·copy·주소 입력을 보존한다.
- [x] 두 `canSubmit`이 이름 1~12자와 휴대폰 숫자 1~11자를 동일 규칙으로 검증한다.
- [x] 기존 request trim과 제출 lifecycle을 변경하지 않는다.
### Task 10.4 휴대폰 제출 형식 강화
**Goal 실행 `P10-T4`:** 편집 중 삭제와 11자리 입력은 유지하면서 `010`으로 시작하는 정확히 11자리 휴대폰만 제출한다. 이 Task가 Task 10.3의 휴대폰 숫자 1~11자리 제출 계약을 대체한다.
- **시작 조건:** `DEC-009`, `SEND-007`, `RECV-007` 확정.
- **완료 증거:** contact focused check와 실제 recipient ViewModel 회귀 GREEN.
- **범위 밖:** 자동 하이픈, SMS 인증, 이름 입력 구현 변경.
**Files:** `SodaLive/Sources/V2/Gift/Components/GiftContactInputRule.swift`, `work/tests/GiftContactInputCheck.swift`, `work/tests/GiftRecipientAddressCheck.swift`.
- [x] **RED:** 짧은 번호와 `011` 11자리가 제출되는 기존 동작을 실패 test로 고정한다.
- [x] **RED 확인:** `bash work/tests/check-gift-contact-input.sh`, `bash work/tests/check-gift-recipient-address.sh`에서 요구 동작 미구현 assertion 실패를 확인한다.
- [x] **GREEN:** `phoneNumber.acceptsSubmission`이 ASCII 숫자 `010` 11자리만 허용하도록 최소 수정한다.
- [x] **GREEN 확인:** 같은 focused test 두 개가 성공하고 이름 편집·제출 회귀가 유지된다.
- [x] **REFACTOR:** 새 추상화 없이 중복만 확인하고 focused test 결과를 Progress에 기록한다.
### Task 10.5 운송장 번호 입력·제출 제한
**Goal 실행 `P10-T5`:** 운송장 입력은 ASCII 숫자 최대 14자만 보존하고 10~14자리일 때만 등록한다.
- **시작 조건:** `DEC-009`, `TRACK-007` 확정.
- **완료 증거:** 실제 `GiftTrackingRegistrationViewModel` harness에서 경계·POST guard·기존 mutation 회귀 GREEN.
- **범위 밖:** request DTO, repository/API, 택배사 선택, 성공/취소 navigation 변경.
**Files:** `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationView.swift`, `SodaLive/Sources/V2/Gift/Tracking/GiftTrackingRegistrationViewModel.swift`, `work/tests/GiftTrackingRegistrationCheck.swift`.
- [x] **RED:** 9·15자리와 비ASCII/비숫자 거부, 10~14자리 허용 및 잘못된 값의 POST 차단 test를 작성한다.
- [x] **RED 확인:** tracking focused harness에서 기존 trim/nonempty 검증 때문에 발생하는 assertion 실패를 확인한다.
- [x] **GREEN:** ViewModel의 순수 편집·제출 predicate를 guarded `Binding`과 `canSubmit`에서 함께 사용하고 number pad를 적용한다.
- [x] **GREEN 확인:** 같은 harness가 기존 등록·실패·취소·navigation 회귀를 포함해 성공한다.
- [x] **REFACTOR:** request trim을 포함한 기존 lifecycle은 유지하고 focused test 결과를 Progress에 기록한다.
### Task 10.6 연락처 입력의 언어별 폰트 정책 회귀 수정
**Goal 실행 `P10-R1`:** UIKit 연락처 입력 필드가 기존 SwiftUI `.appFont(.body3)`와 같은 한국어·일본어·기타 언어별 폰트 정책을 사용한다.
- **시작 조건:** 리뷰 `REV-P10-001` 확정.
- **완료 증거:** 폰트 정책 source contract의 의도한 RED 후 GREEN, contact focused check, Swift parse, 양 스킴 build와 정적 재리뷰.
- **범위 밖:** 타이포그래피 token 변경, 입력 layout·색상·크기 변경, 다른 UIKit 입력 필드 리팩터링.
- **Review:** `docs/20261006_선물_UI_추가/reviews/phase10-contact-tracking-validation.md`.
**Files:** `SodaLive/Sources/Extensions/FontModifier.swift`, `SodaLive/Sources/V2/Gift/Components/GiftContactTextField.swift`, `work/tests/check-gift-contact-input.sh`.
- [x] **RED:** 연락처 래퍼의 Pretendard 직접 지정 금지와 공용 localized font resolver 사용을 검사한다.
- [x] **RED 확인:** `bash work/tests/check-gift-contact-input.sh`에서 언어별 폰트 정책 미연결 assertion 실패를 확인한다.
- [x] **GREEN:** 기존 `FontModifier`의 언어 해석을 재사용하는 최소 UIKit bridge를 추가하고 연락처 필드에 적용한다.
- [x] **GREEN 확인:** 같은 focused check가 성공하고 기존 IME·이름·휴대폰 source contract를 유지한다.
- [x] **REFACTOR:** 이번 수정의 중복만 확인하고 Swift parse·양 스킴 build·정적 재리뷰 결과를 Progress와 리뷰 문서에 기록한다.
### Phase 10 Gate
- [x] `bash work/tests/check-gift-contact-input.sh`가 GREEN이다.
- [x] 기존 gift address/recipient/detail 회귀, Swift/shell parse, project lint, `git diff --check`가 성공한다.
- [x] `SodaLive-dev`, `SodaLive` generic iOS Debug build가 성공한다.
- [ ] 실제 한글 조합·12/13자·숫자 11/12자 입력을 실행 화면에서 확인한다.
- [ ] 휴대폰 `010` 11자리와 운송장 숫자 10~14자리 경계, 15번째 문자 거부를 실행 화면에서 확인한다.
## Phase 11 선물 딥링크·정책 링크·크리에이터 표시 보정
**Phase 결과:** 경로형 선물 상세 딥링크가 기존 상세 화면으로 이동하고, 선물 보내기 화면에 대상 크리에이터 이미지와 올바른 파손 정책 링크가 표시되며, 채널의 선물 버튼이 승인된 UI를 사용한다.
**선행조건:** 승인된 PRD `COMMON-006`, `COMMON-007`, `SEND-008`, `GIFT-004`와 `DEC-010`~`DEC-013`.
**Phase 완료 조건:** `P11-T1`~`P11-T3`, `P11-GATE` 완료와 검증 기록 누적.
### Task 11.1 경로형 선물 상세 딥링크
**Goal 실행 `P11-T1`:** `voiceon://gift-detail/{applicationNo}`와 `voiceon-test://gift-detail/{applicationNo}`를 기존 `.giftDetail` route로 연결한다.
- **시작 조건:** `COMMON-006`, `DEC-011` 확정.
- **완료 증거:** production parser harness가 양 scheme의 경로형 URL, splash pending, 기존 query형 회귀를 검증한다.
- **범위 밖:** scheme 설정 변경, 신청번호 형식 추정, 상세 API/UI 변경.
**Files:**
- Modify: `SodaLive/Sources/App/AppDeepLinkHandler.swift`
- Modify: `work/tests/AppDeepLinkGiftDetailCheck.swift`
- Verify: `work/tests/check-app-deep-link-gift-detail.sh`
- [x] **RED:** 양 scheme의 `gift-detail/A-1002609300001`, 누락 신청번호 거부, splash pending, 기존 query형 유지 case를 harness에 추가한다.
- [x] **RED 확인:** `bash work/tests/check-app-deep-link-gift-detail.sh`를 실행해 `gift-detail` route 미지원 assertion 실패를 확인한다.
- [x] **GREEN:** `makeAction`에 `gift-detail` 분기만 추가하고 공백을 제거한 비어 있지 않은 identifier를 `.giftDetail(applicationNo:)`로 반환한다.
- [x] **GREEN 확인:** 같은 focused harness가 기존 case와 신규 case를 모두 통과한다.
- [x] **REFACTOR:** parser 구조와 scheme/query 분기는 유지하고 focused 결과를 Progress에 기록한다.
### Task 11.2 크리에이터 프로필 이미지와 파손 정책 링크
**Goal 실행 `P11-T2`:** 크리에이터 채널의 프로필 URL을 선물 보내기 화면까지 전달하고 파손 면책 `(필수)`에만 별도 정책 URL을 사용한다.
- **시작 조건:** `SEND-008`, `COMMON-007`, `DEC-012` 확정.
- **완료 증거:** profile/policy source contract, 직접 영향 회귀, 양 스킴 build가 성공한다.
- **범위 밖:** 크리에이터 재조회 API, request DTO/ViewModel 변경, 기존 sender/recipient 약관 URL 변경.
**Files:**
- Modify: `SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelHeaderSection.swift`
- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- Modify: `SodaLive/Sources/App/AppStep.swift`
- Modify: `SodaLive/Sources/ContentView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Send/GiftSendView.swift`
- Modify: `SodaLive/Sources/V2/Gift/Components/GiftAgreementRow.swift`
- Modify: `SodaLive/Sources/V2/Gift/Constants/GiftConstants.swift`
- Create: `work/tests/check-gift-phase11.sh`
- [x] **RED:** `creator.profileImageUrl`이 header → action → `.giftSend` → `ContentView` → `GiftSendView`로 전달되고, 파손 행만 새 URL을 지정하며 기존 약관/상세 정책 URL은 유지되는 source contract를 작성한다.
- [x] **RED 확인:** `bash work/tests/check-gift-phase11.sh profile`과 `bash work/tests/check-gift-phase11.sh policy`가 각각 누락된 전달값과 정책 URL 때문에 실패하는지 확인한다.
- [x] **GREEN:** `.giftSend`에 `recipientCreatorProfileImageUrl: String`을 추가하고 모든 생성·분해 지점을 갱신한다. `GiftSendView`는 `DownsampledKFImage`로 38pt 원형 이미지를 표시하며 ViewModel/API payload는 변경하지 않는다.
- [x] **GREEN:** `GiftConstants.damagePolicyURL`과 `GiftAgreementRow`의 기본값이 기존 `termsURL`인 선택적 URL 입력을 추가하고, 파손 면책 행에만 새 URL을 전달한다.
- [x] **GREEN 확인:** profile/policy mode와 전체 `bash work/tests/check-gift-phase11.sh`가 성공한다.
- [x] **REFACTOR:** 신청 성공의 guarded replace는 memberId/nickname 비교를 유지하고 아래 직접 영향 회귀를 실행한다.
```bash
bash work/tests/check-gift-address-search.sh
bash work/tests/check-gift-contact-input.sh
bash work/tests/check-gift-recipient-address.sh
bash work/tests/check-received-gift-detail.sh recipient-form
bash work/tests/check-received-gift-detail.sh received-states
```
### Task 11.3 크리에이터 채널 선물 버튼 UI
**Goal 실행 `P11-T3`:** 기존 선물 버튼의 노출 조건과 action을 유지하면서 `ic_send_gift`, 흰색 배경, 검은색 문구를 적용한다.
- **시작 조건:** `GIFT-004`, `DEC-010`, 사용자가 추가한 `ic_send_gift.imageset` 존재.
- **완료 증거:** button source contract, asset catalog lint, 양 스킴 build와 가능한 runtime 화면 확인.
- **범위 밖:** Talk/DM 버튼, gift 노출 predicate, icon asset byte 수정.
**Files:**
- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelTalkActionSection.swift`
- Modify: `work/tests/check-gift-phase11.sh`
- Verify unchanged: `SodaLive/Resources/Assets.xcassets/v2/ic_send_gift.imageset/Contents.json`
- Verify unchanged: `SodaLive/Resources/Assets.xcassets/v2/ic_send_gift.imageset/ic_send_gift.png`
- [x] **RED:** 아이콘이 문구 왼쪽에 있고 20pt, 흰색 capsule 배경, 검은색 문구이며 기존 gift predicate/action이 보존되는 button source contract를 추가한다.
- [x] **RED 확인:** `bash work/tests/check-gift-phase11.sh button`에서 기존 text-only outline UI 때문에 실패함을 확인한다.
- [x] **GREEN:** gift label만 `HStack(spacing: SodaSpacing.s6)`로 바꾸고 `ic_send_gift`와 기존 localized 문구를 넣은 뒤 흰색 capsule 배경과 검은색 문구를 적용한다.
- [x] **GREEN 확인:** button mode와 전체 Phase 11 source contract가 성공한다.
- [x] **REFACTOR:** Talk/DM UI와 `shouldShowGiftButton`이 변경되지 않았는지 확인하고 결과를 Progress에 기록한다.
### Phase 11 Gate
**Goal 실행 `P11-GATE`:** Phase 11의 네 요구사항과 기존 선물 흐름 회귀를 자동 검증과 runtime 결과로 구분해 판정한다.
- [x] 아래 focused/회귀 검사와 shell parse가 모두 exit 0이다.
- [x] `SodaLive-dev`, `SodaLive` generic iOS Debug build가 `BUILD SUCCEEDED`다.
- [x] 코드 리뷰 결과를 `docs/20261006_선물_UI_추가/reviews/phase11-gift-links-creator-display.md`에 기록한다.
- [ ] runtime 가능 시 양 scheme 딥링크, 이미지, 정책 URL, 버튼 UI를 확인하고 불가능하면 미검증 항목과 사유를 기록한다.
```bash
bash work/tests/check-app-deep-link-gift-detail.sh
bash work/tests/check-gift-phase11.sh
bash work/tests/check-gift-address-search.sh
bash work/tests/check-gift-contact-input.sh
bash work/tests/check-gift-recipient-address.sh
bash work/tests/check-received-gift-detail.sh recipient-form
bash work/tests/check-received-gift-detail.sh received-states
bash work/tests/check-gift-decoding-contract.sh
bash -n work/tests/check-gift-phase11.sh
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "generic/platform=iOS" CODE_SIGNING_ALLOWED=NO build
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -destination "generic/platform=iOS" CODE_SIGNING_ALLOWED=NO build
git diff --check
```
**Expected:** 모든 자동 검증 exit 0, 양 스킴 `BUILD SUCCEEDED`. 인증 실행 환경이 없으면 runtime Gate만 미완료로 남긴다.
## 실행 순서와 의존성
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|---:|---|---|---|---|
| 1 | `P2-T1` | PRD 확정 | 아니요 | API 계약 대조 |
| 2 | `P2-T2` | `P2-T1` | `P2-T3`와 일부 가능 | display policy 축소 |
| 3 | `P2-T3` | `P2-T1` | `P2-T2`와 일부 가능 | route shell만 연결 |
| 4 | `P2-T4` | `P2-T2` | 예 | 기존 wrapper 재확인 |
| 5 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 Task 수정 |
| 6 | `P3-*` | `P2-GATE` | `P4-*`와 가능 | 신청 flow만 독립 진행 |
| 7 | `P4-*` | `P2-GATE` | `P3-*`와 가능 | 목록 flow만 독립 진행 |
| 8 | `P5-*` | `P2-GATE` | 일부 가능 | 상세 기반 먼저 완료 |
| 9 | `P6-*` | `P5-GATE` | `P7-*`와 가능 | 운송장 독립 진행 |
| 10 | `P7-*` | `P5-GATE` | `P6-*`와 가능 | 받은 상세 독립 진행 |
| 11 | `P8-*` | `P5-GATE`, `P7-GATE` | 아니요 | 딥링크만 분리 검증 |
| 12 | `P9-*` | `P3-GATE`, `P7-GATE` | 아니요 | 주소 검색만 분리 검증 |
| 13 | `P10-*` | `P3-GATE`, `P6-GATE`, `P7-GATE` | 일부 가능 | 입력 경계별 분리 검증 |
| 14 | `P11-T1` | PRD Phase 11 승인 | `P11-T2`와 가능 | 딥링크만 독립 검증 |
| 15 | `P11-T2` | PRD Phase 11 승인 | `P11-T1`과 가능 | profile/policy mode 분리 검증 |
| 16 | `P11-T3` | `P11-T2` | 아니요 | 공유 action file 순차 수정 |
| 17 | `P11-GATE` | `P11-T1`~`P11-T3` | 아니요 | 실패 소유 Task에 회귀 Task 추가 |
```text
P2 -> P3/P4/P5 -> P6/P7 -> P8/P9/P10 -> P11
```
## 변경 금지 항목
- 새 디자인 시스템과 새 dependency를 추가하지 않는다.
- `Pods/**`, `generated/**`, `build/**`를 수정하지 않는다.
- API에 없는 필드, 오류 status/key, 취소일, 남은 일수를 추정하지 않는다.
- 개인정보 표시 금지 필드를 UI 편의상 노출하지 않는다.
- test를 삭제·skip·완화하거나 타입 오류를 우회하지 않는다.
- 임의 외부 URL을 연결하지 않는다. 확정 URL은 기존 약관 Notion URL과 Phase 11 파손 정책 Notion URL뿐이다.
## 의사결정 및 중단 규칙
- PRD와 API 응답이 충돌하면 구현을 멈추고 PRD Decision Log에 정정 후보를 남긴다.
- 개인정보 표시 여부가 애매하면 표시하지 않는다.
- 기존 앱에 문의/리뷰/정책 이동 경로가 없으면 새 외부 URL을 만들지 않는다.
- backend가 계약과 다른 payload를 내려주면 해당 Phase를 blocked로 표시하고 DTO를 추정 확장하지 않는다.
## Progress
기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 누적한다.
| 일시 | 항목 | 기록 |
|---|---|---|
| 2026-10-06 | `P1-T1` | PRD와 plan-task 생성. |
| 2026-10-06 | `P1-T2` | `I18n.CreatorChannelHome.sendGift`, `I18n.MyPage.Category.giftBox` 추가. |
| 2026-10-06 | `P1-T3` | `CreatorChannelTalkActionSection`에 `isDmAvailable`, 한국 접속, 한국어 설정 조건의 `선물 보내기` 버튼 추가. |
| 2026-10-06 | `P1-T4` | `CategoryButtonsView` 첫 항목으로 `ic_my_present` 기반 `선물함` 버튼 추가. |
| 2026-10-06 | `P1-T5` | 크리에이터 상세 `선물 보내기` 버튼에서 아이콘 제거, 타인 채널 조건 추가. |
| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -list` 성공. 스킴 `SodaLive`, `SodaLive-dev` 확인. |
| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 성공. 기존 경고는 유지. |
| 2026-10-06 | `P1-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공. 일부 dependency scan 경고는 유지. |
| 2026-10-06 | `P1-GATE` | `git diff --check` 성공. |
| 2026-10-06 | `P1-GATE` | iOS 실기기/시뮬레이터 화면 캡처 기반 visual QA는 미수행. 변경 파일 직접 확인과 빌드 검증으로 대체. |
| 2026-10-06 | `P1-T5` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공. |
| 2026-10-06 | `P1-T5` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 최초 병렬 실행은 DerivedData `build.db` 잠금으로 실패했고, 단독 재실행은 성공. |
| 2026-10-06 | 문서 보강 | 약관 Notion URL과 API 계약 확정 답변을 반영해 PRD와 plan-task를 선물 사용자 기능 전체 기준으로 보강. |
| 2026-10-06 | `P2-T1` | 7개 endpoint의 DTO, `GiftApi`, `GiftRepository`를 추가하고 계약·품질 리뷰 승인 및 `SodaLive-dev` Debug build, `git diff --check` 성공. 테스트 번들 부재로 compile 검증을 적용. |
| 2026-10-06 | `P2-T2` | 5단계 progress, SENT/RECEIVED 카드 문구, UTC 날짜, 사이즈 설명, 개인정보 display model 경계를 추가. `UNDELIVERABLE`은 배송지 입력/주소를 숨기도록 보정하고 계약·품질 리뷰 승인 및 Debug build 성공. |
| 2026-10-06 | `P2-T3` | 네 Gift route와 최소 route shell을 등록하고 크리에이터 `선물 보내기`, 마이 탭 `선물함` action을 연결. `SodaLive`, `SodaLive-dev` Debug simulator build와 `git diff --check` 성공, 계약·품질 리뷰 승인. |
| 2026-10-06 | `P2-T4` | 확정 약관 URL과 14개 택배사 상수를 양 타깃에 등록하고, 주소 검색 wrapper 부재(`addressSearchAvailable = false`), 문의/피드백 `AppStep.serviceCenter`, 외부 정책 URL 부재 시 `준비 중입니다.` fallback을 상수로 고정. `SodaLive-dev` Debug build와 `git diff --check` 성공. |
| 2026-10-06 | `P2-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공(`BUILD SUCCEEDED`). |
| 2026-10-06 | `P2-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build` 성공(`BUILD SUCCEEDED`). 기존 중복 Sources 및 script output 경고는 유지. |
| 2026-10-06 | `P2-GATE` | `git diff --check` 성공. 크리에이터 `giftSend`, 마이 탭 `giftBoxList`, 약관 URL·14개 courier 상수를 코드 경로로 대조해 Phase 2 완료. |
| 2026-10-06 | `P3-T1` | `GiftSendViewModel`에 recipient context 검증, form-options 로딩, 첫 size 기본 선택, category 미선택, loading/error 상태를 구현하고 `GiftSendView.onAppear`에 연결. 사양·품질 리뷰 승인, `SodaLive-dev` Debug build와 `git diff --check` 성공. 테스트 번들 부재로 compile 검증 적용. |
| 2026-10-06 | `P3-T2` | Figma `2481:18904`, `2481:18957` 기준 선물 보내기 폼, 고정 navigation, scroll 본문, bottom CTA, 조건부 면책 동의, 확정 약관 링크, 수동 주소 입력을 구현. 사양·품질 리뷰 승인, 양 스킴 Debug simulator build와 `git diff --check` 성공. 런타임 픽셀 QA는 미수행. |
| 2026-10-06 | `P3-T3` | 필수값·약관·조건부 면책 기반 `canSubmit`, 상세주소 비필수/요청 포함, 11개 request mapping, submit 중 중복 차단을 구현. 사양·품질 리뷰 승인 및 focused 임시 실행·Debug build 성공. |
| 2026-10-06 | `P3-T4` | `POST /api/v2/gifts` 성공 시 `applicationNo` 상세로 guarded replace 이동하고 실패 시 입력 유지·submit 상태 복구·서버 메시지 표시를 구현. 제출 중 back 차단과 현재 신청 route 검증으로 지연 응답 navigation race를 방지. 사양·품질 리뷰 승인. |
| 2026-10-06 | `P3-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 성공(`BUILD SUCCEEDED`), `git diff --check` 성공. form-options, validation guard, createGift, guarded replace 경로를 대조. 실서버 신청·기기 UI 조작은 미수행. |
| 2026-10-06 | `P4-T1` | `GiftBoxListViewModel`에 `type=ALL`, page 0/size 20 최초 조회, `hasNext` pagination, refresh, 중복 요청 차단, 실패 시 기존 목록 보존을 구현. 사양·품질 리뷰 승인, `SodaLive-dev` Debug build와 `git diff --check` 성공. 테스트 타깃 부재로 compile 검증 적용. |
| 2026-10-06 | `P4-T2` | 기존 `GiftDisplayPolicy`의 SENT/RECEIVED 상태 매핑을 재사용하는 `GiftBoxCardView`를 구현. 프로필 fallback, 날짜, 상태·메타 문구, 전체 카드 tap과 접근성 label을 적용하고 양 타깃 Debug build, `git diff --check`, 사양·품질 리뷰 승인. 테스트 타깃 부재로 compile·정적 검증 적용. 공용 `appFont`의 시스템 폰트 Dynamic Type 제한은 기존 잔존 위험으로 분류. |
| 2026-10-06 | `P4-T3` | Figma 보낸/받은/받은-empty 상태를 기준으로 안내 배너, creator empty와 의견 버튼, 카드 목록, refresh, pagination footer를 구현. section title과 별도 파란 배너는 제외하고 카드 내부 배송지 필요 상태를 유지. invalid raw tail에서도 다음 page를 조회하도록 end sentinel을 분리하고 native refresh가 실제 요청 완료를 기다리도록 보정. 양 타깃 Debug build와 `git diff --check` 성공, 사양·품질 리뷰 승인. 실기기 visual QA는 미수행. |
| 2026-10-06 | `P4-T4` | 모든 카드가 자신의 `applicationNo`로 공통 `.giftDetail` route를 호출하도록 연결됨을 확인. pagination footer는 카드 Button 밖에 있고 end sentinel은 hit testing·접근성에서 제외돼 카드 tap과 충돌하지 않음을 정적 검증. 사양 리뷰 승인 및 양 타깃 Debug build 성공. |
| 2026-10-06 | `P4-GATE` | `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "generic/platform=iOS" CODE_SIGNING_ALLOWED=NO build` 성공(`BUILD SUCCEEDED`), `git diff --check` 성공. `type=ALL&page=0&size=20`, direction display mapping, 카드별 상세 route를 코드 경로로 대조. 로그인·실서버·시뮬레이터 screenshot 기반 visual QA는 미수행. |
| 2026-10-06 | `P5-T1` | `GiftDetailViewModel`이 신청번호로 상세 API를 조회하고 승인된 개인정보 display policy를 통해 SENT, RECEIVED, SENT+CANCELED render state를 생성하도록 구현. 최초 loading/error/retry와 후속 재조회 실패 시 기존 성공 상태 유지·toast를 연결. focused 임시 driver 검증, 사양·품질 리뷰 승인, `SodaLive-dev` Debug build와 `git diff --check` 성공. |
| 2026-10-06 | `P5-T2` | `statusTimeline` 기반 5단계 progress와 direction별 개인정보-safe 공통 정보 section을 구현. `UNDELIVERABLE`, `CANCELED`에서는 progress를 숨기고, 카드에는 고정 화면 폭 대신 `maxWidth: .infinity`와 기존 좌우 padding을 적용해 기기 가용 폭을 채우도록 처리. Figma metadata geometry 대조와 사양·품질 리뷰 승인, `SodaLive-dev` generic iOS Debug build(`BUILD SUCCEEDED`) 및 `git diff --check` 성공. 실기기·시뮬레이터 screenshot 기반 visual QA는 미수행. |
| 2026-10-06 | `P5-T3` | `SentGiftDetailContentView`를 추가해 보낸 선물의 상태별 hero, 5단계 progress, 선물 정보, 선택적 보내는 사람·사서함, 전달 불가 사유/폐기 안내를 구현. `recipientAddress`는 sent display model에 없고 `senderInfo`·`mailbox`는 각각 nil이면 섹션 전체를 숨긴다. 전달 불가 사유 focused RED/GREEN과 `recipientAddress` compile-fail probe, Figma 원본 문구 대조, 사양·개인정보·정적 시각 리뷰 승인, `SodaLive-dev`·`SodaLive` generic iOS Debug build 및 `plutil -lint`, `git diff --check` 성공. 로그인·실서버 기반 현재 빌드 screenshot/VoiceOver visual QA는 미수행. |
| 2026-10-06 | `P5-T4` | `GiftDetailActionBar`를 추가해 `trackingRequired` 운송장 CTA, SENT+RECEIVED 취소 action, 전달 불가 문의/정책 fallback을 조건부 표시. 리뷰 경로 부재로 리뷰 action은 숨기고, 취소는 P5-T5 연결 전 nil/disabled seam으로 유지. 정책 toast를 화면 루트에 배치하고 이동 버튼 공유 잠금으로 연속 탭 중복 push를 방지. 사양·라우팅·품질 재리뷰 승인, `SodaLive-dev`·`SodaLive` generic iOS Debug build 및 `plutil -lint`, `git diff --check` 성공. 운송장 대상 화면 구현은 Phase 6이며 런타임 screenshot/VoiceOver 검증은 미수행. |
| 2026-10-06 | `P5-T5` | `SodaV2ActionModal` 확인 이후에만 취소 API를 호출하고, 중복 제출·GET/POST 중첩을 차단하며 실패 시 dialog와 기존 상세를 유지해 오류를 표시하도록 구현. 성공은 동일 신청번호 상세 route를 guarded replace하고, 거부 시 잠금을 풀고 권위 GET으로 복구한다. 취소 완료 화면은 크리에이터·사이즈·카테고리·신청번호·신청일·취소일·환불 캔만 allowlist로 표시. 실제 production ViewModel 기반 임시 harness 75개 assertion, Figma 원본 대조, 기능·상태 전이·정적 시각 리뷰 승인, 양 스킴 generic iOS Debug build와 `plutil -lint`, `git diff --check` 성공. 실서버 read-after-write·screenshot·VoiceOver QA는 미수행. |
| 2026-10-06 | `P5-GATE` | SENT 개인정보 경계, `trackingRequired` CTA, confirm 이후 cancel POST, CANCELED 완료 UI를 코드·focused harness로 대조. `SodaLive-dev`·`SodaLive` generic iOS Debug build 성공. 서버 취소 성공 직후 GET 일관성과 로그인 기반 실제 취소 flow는 최종 검증 잔여 항목으로 유지. |
| 2026-10-06 | `P6-T1` | 진입 시 상세를 재조회하고 SENT·비취소·`trackingRequired=true`·`mailbox != nil`일 때만 등록 가능 상태를 공개하도록 구현. `trackingRequired=false` 등 stale route는 toast 종료 후 동일 신청번호 상세로 guarded replace하고, mailbox 누락은 `받을 주소가 등록되어 있지 않습니다.`와 재시도를 유지. 실제 production ViewModel 기반 RED/GREEN harness 56개 assertion, 양 스킴 generic iOS Debug build, `plutil -lint`, Swift parse, `git diff --check` 성공. 기능 재리뷰 `APPROVE`, 정적 UI 재리뷰 `APPROVED_STATIC`. 로그인·실서버·screenshot·VoiceOver QA는 미수행. |
| 2026-10-06 | `P6-R1` | 비활성 중 toast dismissal 뒤 복귀가 유실되지 않도록 재등장 시 pending callback을 소비하고, 긴 오류·Dynamic Type에서도 재시도에 접근하도록 오류 영역을 scroll 처리. 수정 후 양 스킴 generic iOS Debug build와 독립 재리뷰 승인. |
| 2026-10-07 | `P6-T2` | 신청 요약, 보내실 주소, 14개 택배사 선택, 운송장 번호 입력, 5개 안내 문구와 반응형 bottom action bar를 구현. 실제 production ViewModel harness 71개 assertion, 양 스킴 generic iOS Debug build, `plutil -lint`, Swift parse, `git diff --check` 성공 및 기능 리뷰 승인. 현재 Figma desktop 활성 문서에서 node `2481:19066`, `2481:19109` 재조회가 실패해 fresh 원본 대조와 로그인 기반 screenshot·VoiceOver QA는 미수행 증거 공백으로 유지. |
| 2026-10-07 | `P6-T3` | 택배사·공백 운송장 validation, 운송장 등록과 확인된 신청 취소 POST, 공통 중복 제출 잠금, 등록 성공 toast 후 상세 이동, 취소 성공 즉시 상세 이동과 guarded replace 거부 시 권위 GET 복구를 구현. production ViewModel focused check에서 의도한 RED 후 62개 assertion 성공, Swift parse, `plutil -lint`, `git diff --check`, 양 스킴 generic iOS Debug build 성공 및 독립 기능 리뷰 `APPROVE`. 인증 실서버의 POST 직후 상세 일관성과 실제 toast·alert lifecycle은 최종 runtime 검증 잔여 항목으로 유지. |
| 2026-10-07 | `P7-T1` | 기존 `ReceivedGiftDetailDisplayModel`의 allowlist 개인정보 경계를 production-source check로 고정하고, 안전 모델만 받는 받은 상세 상태명·progress·선물 정보 component를 양 타깃에 연결. component 부재 RED 후 privacy 16 assertions, 허용 멤버 probe, `senderInfo`·`mailbox`·`applicationNo`·`paidCan`·`tracking` 5개 compile-fail probe와 foundation GREEN 성공. runner fail-open 리뷰 항목은 `set -euo pipefail`과 실패 exit 42 주입으로 수정·재승인. 양 스킴 generic iOS Debug build, Swift parse, `plutil -lint`, `git diff --check` 성공, 기능 리뷰 `APPROVE`, 정적 UI 리뷰 `APPROVED_STATIC`. 로그인 기반 screenshot·큰 글자·VoiceOver runtime QA는 미수행. |
| 2026-10-07 | `P7-T2` | `recipientAddressRequired=true`에서 개인정보 안내, privacy-safe 전달 예정 선물, 이름·휴대폰·우편번호·주소·상세주소 수동 입력, 독립 약관 2개와 비활성 bottom CTA를 표시하고 false에서는 기존 받은 상세를 유지. 확정 Notion URL은 기존 `GiftAgreementRow`를 재사용. privacy/foundation/recipient-form focused 검사, Swift parse, `plutil -lint`, `git diff --check`, 양 스킴 generic iOS Debug build 성공 및 기능 리뷰 `APPROVE`, 정적 UI 리뷰 `APPROVED_STATIC`. Figma node `2531:36290` 재조회 실패로 exact copy·pixel 대조와 로그인 기반 keyboard·VoiceOver QA는 미수행. |
| 2026-10-07 | `P7-T3` | 필수 4개 입력과 약관 2개 기반 validation, 선택 상세주소, 요청 시 양끝 공백 제거, 중복 POST 차단, 실패 입력 유지·오류 toast, 성공 toast 종료 후 동일 상세 ViewModel의 권위 GET을 구현. GET 실패 시 POST를 반복하지 않는 상세 재조회만 제공한다. production-source RED/GREEN harness 266 assertions, recipient-form 구조 검사, Swift parse, 양 스킴 generic iOS Debug build 성공. 독립 기능 리뷰 `APPROVE`, 정적 UI 리뷰 `APPROVED_STATIC`. 인증 실서버 read-after-write와 screenshot·keyboard·VoiceOver runtime QA는 미수행. |
| 2026-10-07 | `P7-T4` | 받은 상세에 배송지 입력 완료·사서함 도착·검수 완료·전달 완료·전달 불가 상태 hero를 구현하고, 정상 상태는 5단계 progress·privacy-safe 선물 정보·optional 받는 주소를 표시한다. `UNDELIVERABLE`은 응답 주소를 강제로 제거하고 hero·optional 사유·폐기 안내·문의/정책 fallback만 표시한다. production-source privacy 19 assertions와 금지 필드 5개 compiler probe, received-states 구조 검사, Swift parse, `git diff --check`, 양 스킴 generic iOS Debug build 성공. 독립 기능 리뷰 `APPROVE`, 정적 UI 리뷰 `APPROVED_STATIC`. Figma·인증 runtime screenshot·큰 글자·VoiceOver QA는 미수행. |
| 2026-10-07 | `P7-GATE` | received display allowlist, 배송지 입력 분기·validation·POST·권위 GET, null 주소 섹션 제거, `UNDELIVERABLE` form/address/progress/gift 숨김과 문의/정책 fallback을 코드·focused checks로 대조. privacy 19 assertions, 제출 266 assertions, foundation/recipient-form/received-states 검사, 양 스킴 generic iOS Debug build와 `git diff --check` 성공. 아래 수동 검증은 인증 실서버와 현재 화면 캡처 부재로 미완료 상태를 유지한다. |
| 2026-10-07 | `P8-T1` | `deepLinkValue=GIFT_DETAIL`과 양끝 공백 제거 후 비어 있지 않은 문자열 `deepLinkId`를 `applicationNo`로 파싱한다. splash에서는 pending action으로 보관하고 일반 진입에서는 기존 공통 `.giftDetail(applicationNo:)` route를 사용한다. SENT/RECEIVED를 미리 선택하지 않고 상세 API 결과로 분기한다. `AppStep.swift` 수정 없이 기존 route를 재사용. production-source Swift harness 결과 `PASS AppDeepLinkGiftDetailCheck assertions=14`. |
| 2026-10-07 | `P8-T2` | fresh 회귀 검증: received privacy 19 assertions와 금지 멤버 5개 compiler probe, recipient address 266 assertions, foundation·recipient-form·received-states 검사 성공. `SodaLive`·`SodaLive-dev` generic iOS Debug build 각각 `BUILD SUCCEEDED`, Swift parse·shell parse·`git diff --check` 성공. PRD §14의 기능·개인정보 항목은 자동/정적 근거와 runtime 공백을 `reviews/phase8-gift-user-flow.md`에 구분해 기록했고, 추적성은 기존 Phase/Task와 Progress에 연결했다. 인증 환경 부재로 §14의 실제 신청·목록·딥링크·상세·mutation·취소 journey는 모두 미수행이며 Figma·screenshot·VoiceOver·keyboard·Dynamic Type 검증도 미수행이다. PRD 체크박스는 변경하지 않았다. |
| 2026-10-07 | `P8-GATE` | Oracle `APPROVE`, Critical/Important 발견 사항 없음. focused checks와 양 스킴 build 및 정적 검사 근거로 Phase 8 코드·정적 gate 승인. 실제 푸시 수신, 인증 서버 응답과 POST 이후 상세 일관성, navigation/replace runtime은 검증하지 않았으므로 전체 인증 runtime 사용자 흐름 gate는 미완료다. Phase 8 수동 체크박스 8개를 모두 미완료로 유지하며, 인증 실행 환경 확보 후 재개한다. |
| 2026-10-07 | `P2-R1` | 사용자 제공 response fixture에서 `categories[].name`을 DTO가 `categoryName`으로 요구해 발생한 `keyNotFound`를 RED로 재현. `sizes[].name`, `categories[].name`, `statusTimeline[].statusName`을 계약에 맞추고 UI 참조를 갱신했다. 계약 harness 21 assertions, received privacy 19 assertions와 금지 멤버 5개 probe, recipient address 266 assertions, foundation·recipient-form·received-states 검사, Swift parse, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했다. 원인 확인용 진단 로그·스크립트·journal은 제거했다. 인증 실서버 재현은 미수행이다. |
| 2026-10-07 | `P2-R2` | 인증 runtime에서 HTTP 200 응답의 `data.giftInfo.applicationNo=null` 때문에 `valueNotFound(String)`이 발생함을 확인하고, null/string toggle로 원인을 재현했다. null contract fixture와 top-level/nested 불일치 mapping RED를 각각 확인한 뒤 nested DTO를 optional로 변경하고 sent detail은 top-level 신청번호를 사용하도록 수정했다. 계약 harness 22 assertions, received privacy/display 21 assertions와 금지 멤버 5개 probe, recipient address 266 assertions, deep link 14 assertions, foundation·recipient-form·received-states 검사, Swift parse, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했다. 수정 빌드의 인증 실서버 card tap 재확인은 잔여 항목이다. |
| 2026-10-07 | `P2-R3` | 전달 불가 상태의 발신·수신 `선물 정책 확인` action을 주소 입력 약관 `(필수)`과 같은 `GiftConstants.termsURL` 시스템 URL 열기로 연결하고 기존 `준비 중입니다.` toast fallback을 제거했다. source 검사 RED 후 received-states, privacy 21 assertions, recipient address 266 assertions, 계약 22 assertions, deep link 14 assertions, Swift/shell parse, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했다. 인증 runtime에서 실제 Safari 전환 확인은 미수행이다. |
| 2026-10-07 | `P9-GATE` | 내부 Kakao postcode bridge와 공용 readonly 주소 입력, compact category를 구현하고 size source를 보존했다. 리뷰에서 확인한 주소 재선택 상세주소 잔존과 Figma 라벨·버튼 문구를 별도 RED/GREEN으로 수정했다. focused 검사와 15 payload assertions, recipient 266 assertions, received/decoding/deep-link 회귀, Swift/shell parse, project lint, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했다. 독립 재리뷰는 `APPROVE`, `APPROVE_STATIC`. 실제 Kakao·인증 screenshot·접근성 runtime QA는 미수행이다. |
| 2026-10-08 | `P10-T4/P10-T5 계획` | 사용자 선택과 번호체계 조사에 따라 휴대폰 제출을 `010` ASCII 숫자 11자리, 운송장 입력/제출을 ASCII 숫자 최대 14자/10~14자리로 확정했다. 기존 request·API·mutation/navigation과 선물 사이즈 선택은 변경하지 않는다. |
| 2026-10-08 | `P10-T4` | contact test에서 짧은 번호가 제출되는 기존 동작과 recipient ViewModel의 `0` 허용을 각각 RED로 확인했다. 공용 `phoneNumber.acceptsSubmission`을 `010` ASCII 숫자 11자리로 제한한 뒤 contact 80 assertions와 recipient 280 assertions가 GREEN이다. 편집 중 빈 값과 최대 11자리 정책은 유지했다. |
| 2026-10-08 | `P10-T5` | tracking harness에서 9자리 `123456789`가 제출되는 기존 동작을 RED로 확인했다. 편집은 ASCII 숫자 최대 14자, 제출은 10~14자리인 순수 predicate를 guarded `Binding`과 `canSubmit`에서 재사용했다. 142 assertions로 경계, 잘못된 값의 POST 차단, leading zero, 등록·실패·취소·navigation 회귀가 GREEN이다. |
| 2026-10-08 | `P10-R1` | 정적 리뷰 `REV-P10-001`에서 연락처 UIKit 필드의 Pretendard 고정이 언어별 폰트 정책을 우회함을 확정했다. source contract RED 후 기존 `FontModifier` resolver를 재사용하는 `.body3.localizedFontName()` bridge로 수정했다. contact 80 assertions와 localized typography contract, recipient 280 assertions, tracking 142 assertions, Swift/shell parse, project lint, `git diff --check`, 양 스킴 generic iOS Debug build가 성공했고 재리뷰는 `APPROVE_STATIC`이다. |
| 2026-10-08 | `P10-GATE` | 기능 독립 리뷰 `APPROVE`, 폰트 회귀 수정 후 정적 UI 재리뷰 `APPROVE_STATIC`. 실제 한글 조합, 12/13자, 휴대폰 11/12자, 운송장 9/10/14/15자 입력·붙여넣기, keyboard·Dynamic Type·VoiceOver·screenshot QA는 부팅된 인증 실행 환경 부재로 미수행이며 runtime 체크박스를 미완료로 유지한다. |
| 2026-10-08 | `P11-T1` | 경로형 딥링크 RED가 `path gift detail accepted`에서 실패함을 확인하고 `makeAction`에 `gift-detail` 최소 분기를 추가했다. 기존 query/splash/content 우선순위를 포함한 production parser harness가 `PASS AppDeepLinkGiftDetailCheck assertions=18`로 GREEN이다. |
| 2026-10-08 | `P11-T2` | profile/policy source contract가 각각 route 값과 파손 정책 URL 부재로 RED임을 확인했다. 프로필 URL을 표시 전용 route 값으로 전달해 38pt 원형 `DownsampledKFImage`를 표시하고, 파손 행만 새 정책 URL을 사용하도록 보정했다. Phase 11 profile/policy, address, contact 80 assertions, recipient 280 assertions, recipient-form, received-states 회귀와 shell parse가 GREEN이다. |
| 2026-10-08 | `P11-T3` | button source contract가 기존 text-only outline UI에서 RED임을 확인했다. 기존 predicate와 세 recipient route 값을 유지하면서 20pt `ic_send_gift`, 검은 문구, 흰색 capsule을 적용했고 button mode와 전체 Phase 11 source contract, shell parse가 GREEN이다. Talk/DM UI는 변경하지 않았다. |
| 2026-10-08 | `P11-GATE` | 계획된 경로형 누락 identifier 거부와 path splash pending coverage를 보강해 deep-link harness 22 assertions가 GREEN이다. Phase 11 profile/policy/button, address, contact 80, recipient 280, recipient-form, received-states, decoding 22 assertions와 shell parse, `git diff --check`가 성공했다. `SodaLive-dev`, `SodaLive` generic iOS Debug build는 각각 `BUILD SUCCEEDED`다. 독립 기능 리뷰는 `APPROVE`, Critical/Important/Minor 없음이며 정적 시각 구현도 제품 결함 없이 승인됐다. 인증 화면 screenshot·링크 탭·VoiceOver·좁은 화면/Dynamic Type은 실행 환경 부재로 미완료다. |
| 2026-10-08 | `P7-R1` | source check 기대값을 Figma 전달 불가 안내 문구로 먼저 바꿔 `bash work/tests/check-received-gift-detail.sh received-states`가 `FAIL received-states: disposal warning`으로 실패하는 RED를 확인했다. 이후 발신·수신 상세의 사유 아래 붉은 안내 문구만 교체했고 기존 폰트·색상·조건부 렌더링은 유지했다. received-states 구조 검사, `bash -n work/tests/check-received-gift-detail.sh`, Swift parse, `git diff --check`, `SodaLive-dev` generic iOS Debug build가 성공했다. 인증 runtime screenshot, 좁은 화면 실제 줄바꿈, Dynamic Type·VoiceOver QA는 실행 환경 부재로 미수행이다. |
## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|---|---|---|---|---|---|
| 2026-10-06 | `DEC-001` | 확정 | 기존 선물 진입점 문서를 이어서 보강한다. | 문서 정책의 연속 작업 규칙 | PRD, 전체 plan |
| 2026-10-06 | `DEC-002` | 확정 | 약관 링크는 `https://app.notion.com/p/3eb27b359673802b937ae741ab7b4ad3`를 사용한다. | 사용자 제공 | `P2-T4`, `P3-T2`, `P7-T2` |
| 2026-10-06 | `DEC-003` | 확정 | API 계약은 사용자 프롬프트의 endpoint/request/response를 기준으로 한다. | 사용자 답변 A | `P2-T1` 이후 전체 |
| 2026-10-06 | `DEC-004` | 확정 | 구현은 신규 V2 선물 feature 폴더에 두고 기존 component/navigation/API 패턴을 재사용한다. | AGENTS.md, code-style.md, codegraph 확인 | 전체 |
| 2026-10-06 | `DEC-005` | 확정 | 선물함 배송지 미입력 상태는 별도 파란 배너 없이 각 item 카드 안에서 표시한다. | 사용자 정정 요청 | `P4-T3` |
| 2026-10-07 | `DEC-006` | 확정 | 폼 옵션의 size/category 표시명은 `name`, 상세 timeline 표시명은 `statusName`으로 decode한다. | 사용자 제공 endpoint response 계약과 fixture RED | `P2-R1`, `P3-T1`, `P5-T1` |
| 2026-10-07 | `DEC-007` | 확정 | 상세의 nested `giftInfo.applicationNo`는 nullable로 decode하고, 화면 신청번호는 top-level `data.applicationNo`를 사용한다. | 인증 runtime `valueNotFound`와 null/string toggle | `P2-R2`, `P5-T1` |
| 2026-10-07 | `DEC-008` | 확정 | 전달 불가 상세의 정책 버튼은 약관 `(필수)` 링크와 동일하게 `GiftConstants.termsURL`을 시스템 브라우저로 연다. | 사용자 확인 | `P2-R3`, `P5-T4`, `P7-T4` |
| 2026-10-08 | `DEC-009` | 확정 | 휴대폰 제출은 `010`으로 시작하는 ASCII 숫자 11자리만, 운송장 제출은 ASCII 숫자 10~14자리만 허용한다. | 사용자 선택과 국내 번호체계 조사 | `P10-T4`, `P10-T5`, PRD `SEND-007`, `TRACK-007`, `RECV-007` |
| 2026-10-08 | `DEC-010` | 정정 | `ic_send_gift`, 흰색 배경, 검은색 문구로 기존 아이콘 없는 선물 버튼 결정을 정정한다. | 사용자 요청 및 설계 승인 | `P11-T3`, PRD `GIFT-004` |
| 2026-10-08 | `DEC-011` | 확정 | `gift-detail/{applicationNo}` 경로형 딥링크는 기존 scheme/parser/navigation을 최소 확장한다. | 사용자 제공 예시 및 설계 승인 | `P11-T1`, PRD `COMMON-006` |
| 2026-10-08 | `DEC-012` | 확정 | 파손 면책 행만 새 정책 URL을 사용하고 기존 약관 URL은 유지한다. | 사용자 제공 URL 및 설계 승인 | `P11-T2`, PRD `COMMON-007` |
| 2026-10-08 | `DEC-013` | 확정 | Gift source의 브랜드 문구는 이미 `소다라이브`이므로 변경하지 않는다. | source 검색과 사용자 선택 A | Phase 11, PRD `DEC-013` |
| 2026-10-08 | `DEC-014` | 확정 | 전달 불가 상세의 붉은 안내 문구는 서버 필드 추가 없이 앱 내 고정 문구로 교체한다. | 사용자 확인: “동일하게 앱 내에 심으면 된다.” | `P7-R1`, PRD `RECV-008` |
## 발견된 문제
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|---|---|---|---|---|---|
| `ISSUE-001` | Low | 후보 | 현재 저장소는 테스트 번들 타깃이 확인되지 않아 계획의 RED/GREEN 자동 테스트 일부가 실행 불가할 수 있다. | 전체 | 가능한 pure logic test를 우선하고, 불가 시 Debug build와 수동 검증을 Progress에 기록한다. |
## 최종 보고 형식
```markdown
구현 결과: 완료한 Phase 번호와 사용자가 실제로 완료할 수 있는 선물 흐름을 한 문장으로 기록한다.
- 변경: 주요 파일과 동작을 요약한다.
- 결정: 관련 Decision Log ID와 내용을 기록한다.
- 검증:
- `실행한 명령` — 성공/실패와 핵심 수치를 기록한다.
- `수동 검증 항목` — 성공/실패/불가 사유를 기록한다.
- 남은 항목: 외부 의존, 후속 범위 또는 없음을 기록한다.
- 문서: 갱신한 PRD/API Contract/plan/review 경로를 기록한다.
```