docs(creator): 커뮤니티 잠금 기록을 갱신한다

This commit is contained in:
Yu Sung
2026-08-10 18:44:14 +09:00
parent 8cbac16382
commit c8636a22f1
3 changed files with 247 additions and 32 deletions

View File

@@ -4,7 +4,7 @@
| 항목 | 내용 |
|---|---|
| 문서 상태 | 공지 상세 구매 일원화·Debug build 완료·`EXT-001`/Simulator 통합 검증 대기 |
| 문서 상태 | `P2-R3` 상태별 수동 검증 완료·`P2-R4` 코드/자동 검증 완료·더보기 상태별 수동 확인 대기 |
| 작성일 | 2026-08-10 |
| 최종 수정일 | 2026-08-10 |
| 대상 제품 | SodaLive iOS 크리에이터 채널 홈·커뮤니티 |
@@ -15,7 +15,7 @@
## 1. Overview
크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다.
크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다. 커뮤니티 상세의 유료 미구매 방문자에게는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다.
## 2. Problem Statement
@@ -25,6 +25,7 @@
- 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다.
- 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다.
- 홈 공지 카드는 상세·댓글·구매 callback이 기본 no-op이고, 유료 잠금 영역에 목록 구매용 `Button`이 남아 있어 상세 전용 구매 원칙에서 제외되어 있다.
- 커뮤니티 상세는 `isCommentAvailable`만으로 댓글 목록과 입력창을 표시해, 유료 미구매 잠금 상태에서도 댓글을 조회하고 작성·수정·삭제할 수 있다.
## 3. Goals
@@ -36,6 +37,7 @@
- 홈 공지 게시글도 본체·댓글·유료 잠금 영역을 상세 진입으로 통일한다.
- 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다.
- 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다.
- 유료 미구매 방문자는 커뮤니티 상세에서 댓글 영역을 보거나 댓글 작성·수정·삭제 동작을 실행할 수 없게 한다.
### 3.2 UX 목표
@@ -43,6 +45,7 @@
- 아이템 본문·이미지·유료 잠금 영역을 터치하면 예상 가능하게 상세 화면으로 이동해야 한다.
- 더보기·좋아요 같은 독립 제어의 터치는 아이템 상세 터치와 중복 실행되지 않아야 한다.
- 표시할 reaction이 없으면 콘텐츠 아래에 빈 여백이 남지 않아야 한다.
- 유료 게시글 구매 전에는 상세의 댓글 구분선·목록·입력창이 보이지 않고, 구매 후에는 `isCommentAvailable` 정책에 따라 다시 표시되어야 한다.
## 4. Non-Goals
@@ -50,19 +53,23 @@
- 썸네일형 커뮤니티 아이템의 레이아웃은 변경하지 않는다.
- `CommunityPostCard`의 다른 사용처인 메인 홈 팔로잉·추천 섹션은 변경하지 않는다.
- 신규 endpoint, 앱 dependency, 범위 밖 커뮤니티 리팩터링은 추가하지 않는다.
- 상세 API 응답의 댓글 payload 계약과 별도 답글 상세 화면은 변경하지 않는다.
## 5. Target Users and Permissions
- 타인 채널 방문자: 더보기에서 신고 동작을 사용할 수 있다.
- 본인 채널의 크리에이터: 기존 커뮤니티 게시글 관리 권한 범위 안에서 더보기 동작을 사용한다.
- 유료 미구매 방문자: 목록에서 구매하지 않고 상세 화면에서 구매를 시작한다.
- 유료 미구매 방문자: 구매 전에는 상세의 reaction·댓글 영역을 볼 수 없고 댓글 mutation을 실행할 수 없다.
## 6. 핵심 사용자 흐름
1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 또는 공지 아이템을 확인한다.
2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다.
3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다.
4. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다.
4. 구매 전에는 상세의 reaction·댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 진입을 차단한다.
5. 구매 성공 후 상세를 다시 조회하고 `isCommentAvailable`에 따라 댓글 영역을 표시한다.
6. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다.
## 7. 기능 요구사항
@@ -78,6 +85,8 @@
| `CCCI-008` | 외부 의존 | 백엔드 홈 API의 `communities`·`notices` 아이템에 `isCommentAvailable: Bool`·`isPinned: Bool`을 커뮤니티 탭과 동일하게 제공한다. | iOS가 두 필드를 임의 기본값 없이 디코딩해 고정·댓글 상태를 표시한다. | `P1-T1`, `EXT-001` |
| `CCCI-009` | 확정 | 홈·커뮤니티 리스트와 커뮤니티 상세에서 하트·댓글을 모두 표시하지 않을 때 reaction bar를 삽입하지 않는다. | 유료 미구매 잠금 상태에서 하트·댓글과 고정 24pt 높이가 모두 사라지고, 하나라도 표시되면 기존 reaction bar가 유지된다. | `P2-R1` |
| `CCCI-010` | 확정 | 홈 공지 게시글도 목록 구매를 제거하고 본체·댓글·유료 잠금 영역을 상세 이동으로 처리한다. | 공지 카드에서 구매 다이얼로그가 직접 열리지 않고 세 영역 모두 선택한 `postId`의 상세로 이동한다. | `P2-R2` |
| `CCCI-011` | 확정 | 커뮤니티 상세의 유료 미구매 잠금 상태에서는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다. | 댓글 구분선·제목·목록·하단 입력창이 View 계층에 없고 ViewModel의 댓글 mutation 진입점이 API 요청을 실행하지 않는다. 구매 후에는 `isCommentAvailable`에 따라 기존 댓글 UI와 동작이 복원된다. | `P2-R3` |
| `CCCI-012` | 확정 | 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨기고, 무료·구매 완료 게시글과 작성자 본인 게시글에서 표시한다. | `CreatorChannelCommunityListItem`의 더보기가 유료 미구매 타인에게 없고 나머지 상태에는 표시된다. | `P2-R4` |
## 8. UI/UX Expectations
@@ -87,6 +96,8 @@
- 리스트형 유료 미구매 영역은 236pt 높이를, 썸네일형은 기존 정사각형 비율을 유지한다. 두 형태 모두 잠금 icon·가격 capsule을 유지하고 배경만 `Color.gray800`으로 통일한다.
- 더보기와 좋아요 제어는 자신의 action만 실행하고 상세 이동을 중복 실행하지 않는다.
- 유료 미구매 잠금 상태처럼 하트·댓글을 모두 숨기는 경우 reaction bar 전체를 조건부로 제외해 고정 높이와 `VStack` 간격을 남기지 않는다.
- 커뮤니티 상세의 유료 미구매 잠금 상태에서는 본문 아래 댓글 구분선·댓글 제목과 목록·하단 입력창을 모두 삽입하지 않는다.
- 홈·커뮤니티 탭 리스트 아이템의 우측 상단 더보기는 유료 미구매 타인 게시글에서 레이아웃과 hit-testing 모두 제거하고, 작성자에게는 구매 여부와 무관하게 항상 표시한다.
## 9. 기술·운영 제약
@@ -96,6 +107,8 @@
- 백엔드는 홈 API 응답에 커뮤니티 탭과 동일한 `isCommentAvailable: Bool`·`isPinned: Bool`을 제공한다. iOS는 두 필드가 없는 응답을 추정 기본값으로 보정하지 않는다.
- 홈 응답 모델과 커뮤니티 탭 응답 모델의 차이를 제거하기 위한 투기적 protocol·factory·신규 dependency를 추가하지 않는다.
- 본인 게시글 판정은 기존처럼 `creatorId == UserDefaults.int(forKey: .userId)`를 사용한다.
- 유료 미구매 잠금 판정은 기존처럼 `price > 0 && existOrdered == false && isOwnPost == false`를 사용하며, 본인 게시글에는 댓글 차단을 적용하지 않는다.
- 더보기 노출도 같은 유료 미구매 잠금 판정을 재사용하고 별도 권한 상태를 추가하지 않는다.
## 10. 성공 기준
@@ -108,6 +121,9 @@
- [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다.
- [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다.
- [ ] 홈 공지의 본체·댓글·유료 잠금 영역이 상세로 이동하고 공지 카드에서 구매 다이얼로그가 직접 열리지 않는다.
- [x] 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창이 보이지 않고 작성·수정·삭제 API 요청이 실행되지 않는다.
- [x] 무료·구매 완료·본인 게시글은 기존 `isCommentAvailable` 값에 따라 댓글 영역과 동작을 유지한다.
- [ ] 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨고, 무료·구매 완료·작성자 게시글에서 표시된다.
- [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다.
- [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다.
@@ -119,6 +135,7 @@
| `OQ-002` | 확정 | 목록 구매 제거를 홈과 커뮤니티 탭 모두에 적용한다. | A안 확정 | 사용자 | 2026-08-10 | `CCCI-004`, `CCCI-007` |
| `OQ-003` | 확정 | 백엔드 홈 API 응답에 `isCommentAvailable`·`isPinned`을 커뮤니티 탭과 동일하게 추가한다. | A안 확정 | 사용자·API 담당 | iOS 통합 검증 전 | `CCCI-001`, `CCCI-008`, `EXT-001` |
| `OQ-004` | 확정 | 홈 API의 날짜 키 `dateUtc`를 유지하고 iOS 로컬 변환에서 같은 UTC 문자열을 커뮤니티 탭 모델의 `createdAtUtc`로 연결한다. | A안 확정·날짜 값 파싱·시간대·포맷 변환 없음 | 사용자 | 2026-08-10 | `DEC-008` |
| `OQ-005` | 확정 | 유료 미구매 상태의 댓글 차단을 UI에만 적용할지 ViewModel mutation까지 방어할지 결정한다. | A안 확정·댓글 UI와 작성·수정·삭제 동작 모두 차단 | 사용자 | 2026-08-10 | `CCCI-011`, `DEC-015` |
## 12. 외부 제공 대기 계약
@@ -135,6 +152,7 @@
| `CCCI-008` | 홈 API 필드 확장 | `P1-T1`, `P3-GATE`, `EXT-001` | decoding contract·Debug build | 백엔드 반영 후 홈 응답 확인 |
| `CCCI-009` | 기존 목록·상세 모델 재사용 | `P2-R1` | reaction bar 조건부 삽입 contract check·Debug build | 홈·커뮤니티 리스트·상세 유료 잠금 여백 확인 |
| `CCCI-010` | 기존 홈 상세 route·상세 구매 API 재사용 | `P2-R2` | 공지 구매 callback 제거·상세 callback 연결 contract check·Debug build | 공지 본체·댓글·유료 잠금 상세 진입 확인 |
| `CCCI-011` | 기존 상세 응답·댓글 mutation API 재사용 | `P2-R3` | 댓글 표시 조건·mutation guard contract check·Debug build | 구매 전 댓글 UI 미노출·구매 후 복원 확인 |
## 14. Decision Log
@@ -154,6 +172,8 @@
| 2026-08-10 | `DEC-012` | 확정 | 목록에 있던 기존 구매 다이얼로그·API 호출 소유권을 상세 화면으로 옮긴다. | 구현 전 기준선에서 상세 잠금 버튼이 구매가 아닌 `showPurchaseError()`만 호출함을 확인·사용자 직접 요구 충족 | `CCCI-004`, `CCCI-007` |
| 2026-08-10 | `DEC-013` | 확정 | 하트·댓글이 모두 숨겨지는 유료 잠금 상태에서는 고정 높이를 보정하지 않고 reaction bar 전체를 조건부로 삽입하지 않는다. | 목록·상세의 공통 원인은 빈 `HStack``.frame(height: 24)` 유지이며, 상위 조건부 삽입이 가장 작은 근본 수정이다. | `CCCI-009`, `P2-R1` |
| 2026-08-10 | `DEC-014` | 확정 | 홈 공지도 별도 구매 callback을 제거하고 기존 `onTapCommunityDetail`·`showCommunityPostDetail(_:)` 흐름을 재사용한다. | 공지도 커뮤니티 게시글이며 구매는 상세에서만 가능해야 한다는 사용자 요청 | `CCCI-010`, `P2-R2` |
| 2026-08-10 | `DEC-015` | 확정 | 상세의 유료 미구매 잠금 상태에서는 기존 reaction bar 차단을 유지하고, 댓글 구분선·목록·입력창과 ViewModel의 작성·수정·삭제 mutation 진입을 모두 차단한다. | 사용자 후속 요청과 인터뷰 A안 선택 | `CCCI-009`, `CCCI-011`, `P2-R1`, `P2-R3` |
| 2026-08-10 | `DEC-016` | 확정 | 홈·커뮤니티 탭의 공용 리스트 아이템은 기존 `isPaidLocked`를 재사용해 더보기를 조건부 삽입한다. 작성자는 구매 여부와 무관하게 표시한다. | 사용자 상태별 수동 검증 후속 요청·공용 컴포넌트와 기존 잠금 판정 재사용 | `CCCI-012`, `P2-R4` |
## 15. 구현 설계
@@ -173,7 +193,10 @@
- 상세 진입 helper는 표시 모델 전체 대신 `postId` 만 받아 홈·리스트형·썸네일형이 같은 route를 사용한다.
- 더보기·좋아요 `Button`은 각자의 action만 실행하고 부모 아이템의 상세 진입을 중복 실행하지 않아야 한다.
- `CreatorChannelCommunityListItem``CreatorChannelCommunityPostDetailContentView``isPaidLocked``true`이면 `reactionBar` 자체를 View 계층에 삽입하지 않는다. 댓글만 비활성이고 좋아요가 표시되는 상태는 기존 reaction bar를 유지한다.
- `CreatorChannelCommunityPostDetailViewModel`은 상세의 `isCommentAvailable`과 기존 유료 잠금 조건을 함께 판정하는 단일 댓글 상호작용 상태를 제공한다. 상세 View는 이 상태가 `true`일 때만 댓글 구분선·목록·하단 입력창을 삽입한다.
- 같은 상태를 댓글 전송 가능 여부와 작성·수정·삭제 mutation 진입 guard에 재사용한다. 잠금 상태에서는 댓글 API 요청을 시작하지 않는다.
- `CreatorChannelNoticeSection`은 본체·댓글·유료 잠금 영역에 하나의 상세 callback을 사용하고, `CreatorChannelHomeView`의 기존 `onTapCommunityDetail`을 전달한다. 공지 카드의 외형은 변경하지 않는다.
- `CreatorChannelCommunityListItem.header`의 방문자 더보기 분기를 `!isPaidLocked`로 제한한다. 기존 작성자 제어 분기·가격 표시·callback은 유지한다.
### 15.3 더보기와 변경 후 갱신
@@ -197,6 +220,8 @@
- 정적 검증은 `rg`로 목록 `onTapPurchase`·목록 구매 dialog 호출처 제거, 상세 구매 dialog·API 연결, `Color.gray800`, 홈·목록·썸네일 상세 route 연결을 확인하고 `git diff --check`를 실행한다.
- reaction bar 회귀 검증은 두 대상 View의 body에 무조건 삽입된 `reactionBar`가 없고 `if !isPaidLocked` 내부에만 삽입되는지 focused `rg`로 확인한다.
- 공지 회귀 검증은 `CreatorChannelNoticeSection``onTapPurchase`·`onTapNotice`·`onTapComment`가 제거되고 단일 상세 callback이 `CreatorChannelHomeView.onTapCommunityDetail`에 연결되는지 focused `rg`로 확인한다.
- 상세 댓글 차단 검증은 `CreatorChannelCommunityPostDetailView`가 ViewModel의 단일 댓글 상호작용 상태로 댓글 구분선·목록·입력창을 조건부 삽입하고, `CreatorChannelCommunityPostDetailViewModel`의 전송·삭제 mutation 진입점이 같은 상태를 guard하는지 focused `rg`로 확인한다.
- 더보기 노출 검증은 공용 리스트 아이템의 기존 `else if !isOwnPost`를 RED로 확인하고 `else if !isPaidLocked`로 변경된 것을 GREEN으로 확인한다.
- 자동 검증은 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`로 컴파일·링크 성공을 확인한다.
- `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형의 상세 진입, 더보기 권한 메뉴, 목록 구매 dialog 미노출, 상세 구매, `Color.gray800`을 수동 확인한다.
@@ -216,3 +241,10 @@
- 2026-08-10: 두 body의 무조건 `reactionBar` 삽입을 `if !isPaidLocked` 조건부 삽입으로 변경했다. focused `rg`에서 무조건 삽입 0건·조건부 삽입 2건, `git diff --check` 오류 0건, 동일 iPhone 17 Pro 대상 `SodaLive-dev` Debug build exit code 0을 확인했다.
- 2026-08-10: `CreatorChannelNoticeSection`에 기본 no-op인 상세·댓글 callback과 별도 구매 callback·잠금 `Button`이 남고 `CreatorChannelHomeView`가 상세 callback을 전달하지 않는 `REV-P3-004`를 확인해 `CCCI-010`, `DEC-014`, `P2-R2`로 수정 범위를 확정했다.
- 2026-08-10: 공지 callback을 단일 `onTapDetail`로 통일하고 잠금 영역의 직접 구매 `Button`을 제거한 뒤 `CreatorChannelHomeView.onTapCommunityDetail`에 연결했다. RED 계약 검사는 직접 구매 callback과 미연결 생성부를 각각 검출했고, GREEN은 기존 callback 0건·상세 연결 match, `git diff --check` 오류 0건, `SodaLive-dev` Debug build exit code 0을 확인했다.
- 2026-08-10: 사용자 후속 요청과 인터뷰 A안 선택으로 커뮤니티 상세의 유료 미구매 상태에서 reaction bar 차단을 유지하고 댓글 UI·작성·수정·삭제 mutation을 모두 차단하기로 확정했다. 현재 상세 View와 ViewModel이 `isCommentAvailable`만으로 댓글 표시·전송을 허용하는 기준선을 확인했으며 구현·커밋은 진행하지 않았다.
- 2026-08-10: `P2-R3` RED에서 상세 View의 `isCommentAvailable` 직접 조건 2건과 ViewModel의 paid-lock 미반영 상태를 확인했다. `isCommentInteractionAvailable` 하나로 댓글 구분선·목록·입력창과 페이지네이션·전송·편집·삭제·상태 초기화를 통일했다.
- 2026-08-10: GREEN focused contract check에서 상세 View 직접 조건 0건, shared 상태 참조 10건, mutation guard match를 확인했고 `git diff --check`가 통과했다. device와 iPhone 17 Pro iOS 26.0 Simulator 대상 `SodaLive-dev` Debug build가 각각 exit code 0으로 성공했다.
- 2026-08-10: iPhone 17 Pro Simulator에 앱을 설치·실행해 홈 진입을 확인하고 `/tmp/p2-r3-launch.png`를 캡처했다. 익명 세션에 유효한 유료 커뮤니티 상세 데이터가 없어 `CCCI-011` 상태별 화면·요청 검증은 완료하지 못했으며 Simulator는 종료했다.
- 2026-08-10: 사용자가 유료 미구매·무료·구매 완료·본인 게시글의 상세 댓글 상태를 수동 확인해 `CCCI-011`의 두 성공 기준을 완료 처리했다.
- 2026-08-10: 후속 요청으로 홈·커뮤니티 탭 리스트 아이템의 더보기를 유료 미구매 타인에게 숨기고 무료·구매 완료·작성자에게 표시하는 `CCCI-012`, `DEC-016`, `P2-R4`를 확정했다.
- 2026-08-10: `P2-R4` RED에서 방문자 분기의 `else if !isOwnPost` 1건을 확인하고 `else if !isPaidLocked`로 교체했다. GREEN에서 기존 조건 0건·신규 조건 1건, production diff 한 줄, `git diff --check` 성공과 `SodaLive-dev` Debug build exit code 0을 확인했다.