Files
sodalive-ios/docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md

56 KiB
Raw Blame History

크리에이터 채널 팬Talk 탭 구현 계획

기준 문서

  • PRD: docs/20260704_크리에이터_채널_팬Talk_탭/prd.md
  • 기존 크리에이터 채널 홈 PRD: docs/20260701_크리에이터_채널_홈/prd.md
  • 기존 크리에이터 채널 후원 탭 구현 계획: docs/20260704_크리에이터_채널_후원_탭/plan-task.md
  • 코드 스타일: docs/agent-guides/code-style.md
  • 빌드/검증: docs/agent-guides/build-test-verification.md
  • Figma 전체 화면: 290:9139, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9139&m=dev
  • Figma 아이템 뷰(답글 없음): 290:9145, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9145&m=dev
  • Figma 아이템 뷰(답글 있음): 290:9147, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9147&m=dev
  • Figma 더보기 anchored context popup: 666:36114, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=666-36114&m=dev
  • Figma Empty: 290:9001, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9001&m=dev
  • Figma 글 입력(입력 값 없음, 키보드 있음): 290:9235, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9235&m=dev
  • Figma 글 입력(입력 값 있음, 키보드 있음): 290:9282, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9282&m=dev
  • Figma 글 입력(입력 값 있음, 키보드 없음): 290:9290, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9290&m=dev
  • Figma 입력 종료 모달: 308:19046, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=308-19046&m=dev
  • Figma 공용 모달(버튼 1개): 567:17662, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=567-17662&m=dev
  • Figma 공용 모달(버튼 2개): 567:17666, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=567-17666&m=dev
  • Figma 공용 모달 dimmed 포함: 567:17712, https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=567-17712&m=dev

구현 원칙

  • 기존 CreatorChannelView의 공통 shell, header, sticky tab-bar, title bar 동작은 유지한다.
  • CreatorChannelTab.fanTalk 콘텐츠만 신규 CreatorChannelFanTalkTabView로 교체한다.
  • 팬Talk API 모델은 홈 탭 모델과 타입명 충돌이 나지 않도록 분리한다.
  • sort-bar는 정렬 없이 전체 {fanTalkCount}만 표시한다.
  • 신고는 기존 CheersReportDialogViewReportRepository.reportReportType.CHEERS 흐름을 재사용한다.
  • 삭제는 기존 ExplorerRepository.modifyCheers(cheersId:content:isActive:)isActive=false를 전달하는 흐름을 재사용한다.
  • 더보기 버튼은 ic_new_more를 사용한다.
  • 내가 쓴 글이면 수정하기, 삭제하기를 표시하고, 내가 쓴 글이 아니지만 내 채널이면 삭제하기만 표시한다.
  • 글쓰기와 수정하기 action은 이번에 작성하는 팬Talk 쓰기 페이지로 연결한다.
  • 작성 API는 기존 팬Talk API인 ExplorerRepository.writeCheers(parentCheersId:creatorId:content:)를 사용한다.
  • 수정 API는 기존 팬Talk API인 ExplorerRepository.modifyCheers(cheersId:content:isActive:)를 사용하고 isActivenil로 전달한다.
  • 내 채널인 경우 우측하단 글쓰기 floating button과 empty view 내부 응원남기기 버튼을 모두 표시하지 않는다.
  • 우측하단 plus button은 CreatorChannelFloatingActionMenu의 private floatingButton과 동일한 UI이므로, 팬Talk 전용으로 별도 구현하지 않고 CreatorChannel/Components 하위 공용 icon floating button으로 추출해 재사용한다.
  • 팬Talk 쓰기 화면은 크리에이터 채널 팬Talk 탭의 우측하단 plus button, empty state의 응원남기기 버튼, 내가 쓴 글의 수정하기에서 진입한다.
  • 팬Talk 쓰기 화면 진입 시 키보드가 올라온 상태를 기본으로 하며, 입력 값 유무에 따라 보내기 버튼 disabled/enabled 상태를 분리한다.
  • 입력 값이 있는 상태에서 취소 버튼을 터치하면 입력 종료 안내 모달을 표시한다. 입력 값이 없으면 별도 모달 없이 이전 화면으로 닫는다.
  • 취소 문구의 고정 width: 64, height: 56만 제거하고 내비게이션 높이 56과 보내기 capsule 높이 44는 유지한다.
  • 입력 영역은 I18n.CreatorChannelFanTalk의 ko/en/ja placeholder를 사용한다.
  • 입력은 Swift String.count 기준 500자까지 허용하고, 501번째 이후 사용자 입력은 prefix(500)으로 반영하지 않는다.
  • 예외적으로 content가 500자를 초과하면 메시지 본문과 현재 글자 수를 Color.red400으로 표시하고 보내기 버튼을 비활성화한다.
  • 공용 모달은 요청 명칭상 V2/Components 대상이지만, 현재 저장소 경로 관례에 맞춰 SodaLive/Sources/V2/Component/Modal/** 아래에 작성한다.

주요 대상 파일

생성

  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Models/CreatorChannelFanTalkTabResponse.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkApi.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkRepository.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkViewModel.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkCountBar.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkListItem.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
  • SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingIconButton.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift
  • SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift
  • SodaLive/Sources/V2/Component/Modal/SodaV2ActionModal.swift

수정

  • SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
  • SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift
  • SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift
  • SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelFanTalkSection.swift
  • SodaLive/Sources/App/AppStep.swift
  • SodaLive/Sources/ContentView.swift
  • SodaLive/Sources/Explorer/ExplorerRepository.swift
  • SodaLive/Sources/I18n/I18n.swift
  • 필요 시 SodaLive.xcodeproj/project.pbxproj

TASK 체크리스트

Phase 1: 문서와 API 모델

  • Task 1.1: PRD와 계획 문서 생성

    • 대상 파일:
      • 생성: docs/20260704_크리에이터_채널_팬Talk_탭/prd.md
      • 생성: docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
    • 검증 기준:
      • 실행 명령: rg "290:9139|290:9145|290:9147|666:36114|290:9001|CreatorChannelFanTalkTabResponse|/api/v2/creator-channels/.*/fan-talks|ic_new_more" docs/20260704_크리에이터_채널_팬Talk_탭
      • 기대 결과: Figma URL, 응답 모델, API path, 액션 요구사항이 문서에 기록된다.
  • Task 1.2: 팬Talk 탭 Response model 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Models/CreatorChannelFanTalkTabResponse.swift
      • 수정: SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift
      • 수정: SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelFanTalkSection.swift
    • 작업 내용:
      • CreatorChannelFanTalkTabResponse, CreatorChannelFanTalkItemResponse, CreatorChannelFanTalkReplyResponseDecodable로 생성한다.
      • 홈 탭 기존 CreatorChannelFanTalkResponse와 충돌하지 않도록 홈 탭 타입명을 변경하거나 신규 타입명을 다르게 둔다.
      • IdentifiablefanTalkIdid로 사용한다.
    • 검증 기준:
      • 실행 명령: rg "struct CreatorChannelFanTalkTabResponse|fanTalkCount|fanTalks|hasNext|struct CreatorChannelFanTalkItemResponse|writerId|creatorReplies|struct CreatorChannelFanTalkReplyResponse" SodaLive/Sources/V2/CreatorChannel/FanTalk/Models/CreatorChannelFanTalkTabResponse.swift

Phase 2: API, Repository, ViewModel

  • Task 2.1: 팬Talk 탭 API endpoint 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkApi.swift
    • 작업 내용:
      • CreatorChannelFanTalkApi.getFanTalks(creatorId:page:size:)를 만든다.
      • path는 /api/v2/creator-channels/{creatorId}/fan-talks로 구성한다.
      • query parameter는 page, size만 보낸다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkApi|getFanTalks|/api/v2/creator-channels/|/fan-talks|page|size|URLEncoding.queryString" SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkApi.swift
  • Task 2.2: 팬Talk 탭 Repository 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkRepository.swift
    • 작업 내용:
      • MoyaProvider<CreatorChannelFanTalkApi>를 사용하는 getFanTalks(...) publisher를 제공한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkRepository|MoyaProvider<CreatorChannelFanTalkApi>|AnyPublisher<Response, MoyaError>|getFanTalks" SodaLive/Sources/V2/CreatorChannel/FanTalk/Repository/CreatorChannelFanTalkRepository.swift
  • Task 2.3: 팬Talk 탭 ViewModel 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkViewModel.swift
    • 작업 내용:
      • 상태는 loading, loadingNextPage, response, fanTalks, page, size, hasNext, hasLoaded, error toast, 신고/삭제 dialog 상태를 둔다.
      • fetchFirstPage(creatorId:)page=0, size=20으로 조회한다.
      • fetchNextPageIfNeeded(creatorId:currentItem:)는 마지막 item 기준으로 다음 페이지를 조회한다.
      • 첫 페이지는 목록 교체, 다음 페이지는 append한다.
      • report(reason:)ReportRequest(type: .CHEERS, cheersId: selectedFanTalkId, reason: reason)를 호출한다.
      • deleteSelectedFanTalk(creatorId:)ExplorerRepository.modifyCheers(..., isActive: false)를 호출하고 성공 후 첫 페이지를 다시 조회한다.
      • 최신 요청만 반영하도록 latestRequestId 패턴을 사용한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkViewModel|fetchFirstPage|fetchNextPageIfNeeded|isLoadingNextPage|latestRequestId|report\\(|deleteSelectedFanTalk|ReportType\\.CHEERS|modifyCheers|hasNext" SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkViewModel.swift

Phase 3: Figma 섹션별 UI

  • Task 3.1: 팬Talk count bar 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkCountBar.swift
    • 작업 내용:
      • Figma sort-bar 기준으로 높이 52pt, 좌측 전체 {fanTalkCount}만 표시한다.
      • 정렬 버튼/아이콘/action은 포함하지 않는다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkCountBar|fanTalkCount|전체|frame\\(height: 52\\)|ic_new_sort|selectedSort" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkCountBar.swift
      • 기대 결과: count bar 구현이 확인되고 정렬 관련 문자열은 없어야 한다.
  • Task 3.2: 팬Talk item 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkListItem.swift
    • 작업 내용:
      • 작성자 프로필 42pt, 닉네임/상대 시간, 본문 50pt inset, 하단 separator를 구현한다.
      • 답글이 있으면 좌측 라인과 답글 카드를 표시한다.
      • isMine || isOwnCreatorChannel이면 ic_new_more 버튼을 표시한다.
      • 그 외에는 신고 텍스트 버튼을 표시한다.
      • 프로필 이미지와 답글 프로필 이미지는 Kingfisher 기반 기존 컴포넌트를 사용한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkListItem|writerProfileImageUrl|writerNickname|createdAtUtc|creatorReplies|ic_new_more|신고|Color\\.gray800|Color\\.gray900|42|20" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkListItem.swift
  • Task 3.3: 더보기 anchored context popup 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift
    • 작업 내용:
      • Figma 666:36114 기준으로 border Color.gray700, background Color.gray900, radius 14pt를 사용한다.
      • 내가 쓴 글이면 수정하기, 삭제하기를 표시한다.
      • 내가 쓴 글이 아니지만 내 채널이면 삭제하기만 표시한다.
      • tap action은 부모에서 주입한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkActionPopup|수정하기|삭제하기|Color\\.gray700|Color\\.gray900|cornerRadius\\(14\\)|onTapEdit|onTapDelete" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift
  • Task 3.4: Empty view 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
    • 작업 내용:
      • Figma 290:9001 기준으로 아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요! 문구를 표시한다.
      • plus 아이콘과 응원 남기기 텍스트가 있는 capsule button을 표시한다.
      • 내 채널이면 button을 숨긴다.
      • tap action은 부모에서 주입한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkEmptyView|아직 응원이 없습니다|처음으로 크리에이터를 응원해 보세요|응원 남기기|showsWriteButton|onTapWrite" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
  • Task 3.5: 공용 icon floating button 추출

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingIconButton.swift
      • 수정: SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift
    • 작업 내용:
      • CreatorChannelFloatingActionMenu 내부 private floatingButton과 같은 66pt 원형 버튼, 38pt 아이콘, background color, plain button style을 공용 컴포넌트로 분리한다.
      • 공용 컴포넌트는 imageName, backgroundColor, accessibilityLabel, action을 받는다.
      • 기존 CreatorChannelFloatingActionMenu는 private floatingButton 구현 대신 공용 컴포넌트를 사용하도록 변경한다.
      • 팬Talk 우측하단 글쓰기 plus button은 이 공용 컴포넌트에 imageName: "ic_plus_no_bg", backgroundColor: Color.soda400를 전달해 사용한다.
      • 글쓰기 plus button은 텍스트를 표시하지 않는다.
    • 검증 기준:
      • 실행 명령: rg "struct CreatorChannelFloatingIconButton|imageName|backgroundColor|accessibilityLabel|frame\\(width: 38, height: 38\\)|frame\\(width: 66, height: 66\\)|clipShape\\(Circle\\(\\)\\)" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingIconButton.swift
      • 실행 명령: rg "CreatorChannelFloatingIconButton|ic_plus_no_bg|ic_new_upload_community_post|ic_new_upload_audio|ic_new_create_live|ic_new_x_black" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingActionMenu.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
      • 실행 명령: rg "Text\\(" SodaLive/Sources/V2/CreatorChannel/Components/CreatorChannelFloatingIconButton.swift
      • 기대 결과: 공용 icon-only floating button과 기존 floating action menu 재사용이 확인되고, 공용 컴포넌트에는 텍스트가 없어야 한다.
  • Task 3.6: 팬Talk 탭 본문 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
    • 작업 내용:
      • count bar, empty view, fanTalk list를 배치한다.
      • 첫 진입 시 fetchFirstPage(creatorId:)를 호출한다.
      • 마지막 fanTalk item 노출 시 fetchNextPageIfNeeded를 호출한다.
      • fanTalkCount == 0이면 CreatorChannelFanTalkEmptyView를 표시한다.
      • 내 채널인 경우 empty view 내부 글쓰기 버튼을 숨긴다.
      • CheersReportDialogView와 삭제 confirm dialog를 표시한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkTabView|CreatorChannelFanTalkCountBar|CreatorChannelFanTalkEmptyView|CreatorChannelFanTalkListItem|fetchFirstPage|fetchNextPageIfNeeded|CheersReportDialogView|SodaDialog|onTapWrite" SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift

Phase 4: CreatorChannelView 연결

  • Task 4.1: 팬Talk 탭 콘텐츠 연결
    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
    • 작업 내용:
      • @StateObject로 팬Talk 탭 ViewModel을 보유한다.
      • selectedTabContent에서 .fanTalk일 때 CreatorChannelFanTalkTabView를 표시한다.
      • 우측하단 글쓰기 floating button은 selectedTab == .fanTalk, 내 채널이 아니고 fanTalkCount > 0일 때만 CreatorChannelFloatingIconButton으로 표시한다.
      • 글쓰기 action은 Phase 8에서 팬Talk 쓰기 페이지 작성 모드로 연결한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkViewModel|CreatorChannelFanTalkTabView|viewModel\\.selectedTab == \\.fanTalk|CreatorChannelFloatingIconButton|showFanTalkWrite|fanTalkCount|ic_plus_no_bg" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift

Phase 5: 프로젝트 등록과 검증

  • Task 5.1: 프로젝트 파일 등록과 정적 검증

    • 대상 파일:
      • 확인/수정: SodaLive.xcodeproj/project.pbxproj
    • 검증 기준:
      • 실행 명령: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj
      • 기대 결과: 공백 오류 없이 성공한다.
      • 실행 명령: plutil -lint SodaLive.xcodeproj/project.pbxproj
      • 기대 결과: OK 출력.
  • Task 5.2: 빌드 검증

    • 대상 파일:
      • 확인: SodaLive.xcworkspace
      • 확인: SodaLive.xcodeproj
    • 검증 기준:
      • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • 기대 결과: BUILD SUCCEEDED.
      • workspace 인식 실패 등 환경 문제가 있으면 xcodebuild -project "SodaLive.xcodeproj" -scheme "SodaLive-dev" -configuration Debug build를 대체 검증으로 실행하고 실패 원인을 검증 기록에 남긴다.

Phase 6: 팬Talk 글 입력 문서와 공용 모달

  • Task 6.1: 팬Talk 쓰기 Figma URL과 후속 구현 범위 기록

    • 대상 파일:
      • 수정: docs/20260704_크리에이터_채널_팬Talk_탭/prd.md
      • 수정: docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
    • 작업 내용:
      • 팬Talk 쓰기 화면 Figma URL 290:9235, 290:9282, 290:9290을 기록한다.
      • 입력 종료 모달 Figma URL 308:19046을 기록한다.
      • 공용 모달 Figma URL 567:17662, 567:17666, 567:17712을 기록한다.
      • PRD에 팬Talk 작성/수정 진입, 입력 상태별 보내기 버튼, 입력 종료 안내 모달, 공용 모달 요구사항을 기록한다.
      • 이번 작업은 문서만 작성하고 Swift 구현은 후속 task로 남긴다.
    • 검증 기준:
      • 실행 명령: rg "290:9235|290:9282|290:9290|308:19046|567:17662|567:17666|567:17712|SodaV2ActionModal|ExplorerRepository.writeCheers|ExplorerRepository.modifyCheers|수정 모드" docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
      • 기대 결과: 팬Talk 쓰기와 공용 모달 Figma node, 공용 모달, 기존 작성/수정 API 사용 방침이 PRD와 plan-task에 기록된다.
  • Task 6.2: V2 공용 액션 모달 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Component/Modal/SodaV2ActionModal.swift
    • 작업 내용:
      • Figma 567:17662, 567:17666, 567:17712 기준의 공용 모달을 만든다.
      • title, message, button1, button2, onDimmedTap을 외부에서 주입받는다.
      • button1은 오른쪽, button2는 왼쪽에 배치한다.
      • 각 버튼은 label과 action이 모두 있을 때만 표시한다.
      • 버튼 1개만 표시될 때는 Figma 567:17662 기준으로 단일 버튼 레이아웃을 사용한다.
      • 버튼 2개가 표시될 때는 Figma 567:17666 기준으로 왼쪽 button2, 오른쪽 button1 순서로 배치한다.
      • dimmed는 Color.black.opacity(0.6)로 화면 전체를 덮고, dimmed tap 시 별도 액션 없이 모달만 닫히도록 onDimmedTap을 호출한다.
      • 기존 SodaDialog는 구 UI와 opacity 50% 규칙을 사용하므로 이번 V2 Figma 모달 구현에는 직접 수정하지 않는다.
    • 검증 기준:
      • 실행 명령: rg "struct SodaV2ActionModal|button1|button2|onDimmedTap|Color\\.black\\.opacity\\(0\\.6\\)" SodaLive/Sources/V2/Component/Modal/SodaV2ActionModal.swift
      • 기대 결과: 공용 모달의 주입값, 버튼 표시 조건, dimmed 규칙이 코드에서 확인된다.

Phase 7: 팬Talk 쓰기 화면

  • Task 7.1: 팬Talk 쓰기 ViewModel 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift
      • 확인/필요 시 수정: SodaLive/Sources/Explorer/ExplorerRepository.swift
    • 작업 내용:
      • 작성/수정 mode를 구분하는 enum을 둔다.
      • @Published var content를 둔다.
      • 수정 모드는 route에서 받은 fanTalkId와 기존 content를 필수 입력값으로 사용한다.
      • 수정 모드의 content 초기값은 route에서 받은 기존 content와 같아야 한다.
      • 수정 모드의 fanTalkId는 0보다 커야 한다.
      • canSendcontent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false일 때만 true가 되게 한다.
      • shouldShowExitModal은 입력 값이 있을 때 취소 버튼을 누르면 true로 전환한다.
      • 작성 mode의 보내기 action은 기존 ExplorerRepository.writeCheers(parentCheersId:nil, creatorId:content:)를 호출한다.
      • 수정 mode의 보내기 action은 기존 ExplorerRepository.modifyCheers(cheersId:fanTalkId, content:content, isActive:nil)를 호출한다.
      • 작성/수정 성공 시 onSuccess를 호출하고 화면을 닫을 수 있도록 성공 callback을 분리한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkWriteViewModel|enum .*Mode|fanTalkId|initialContent|@Published var content|canSend|trimmingCharacters|shouldShowExitModal|writeCheers|modifyCheers|isActive: nil|onSuccess" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift SodaLive/Sources/Explorer/ExplorerRepository.swift
      • 기대 결과: 입력 값 기반 send 활성화 조건, 수정 모드의 fanTalkId/기존 content 필수 전달, 기존 작성/수정 API 사용 지점이 확인된다.
  • Task 7.2: 팬Talk 쓰기 View 구현

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift
      • 수정: SodaLive/Sources/I18n/I18n.swift
    • 작업 내용:
      • Figma 290:9235 기준으로 입력 값이 없을 때 보내기 버튼을 비활성화한다.
      • Figma 290:9282 기준으로 입력 값이 있을 때 보내기 버튼을 활성화한다.
      • Figma 290:9290 기준으로 입력 값이 있고 키보드가 내려간 상태의 레이아웃을 지원한다.
      • 화면 진입 시 FocusState로 입력 영역에 focus를 주어 키보드가 올라오게 한다.
      • 수정 모드 진입 시 route에서 받은 기존 content가 입력 영역에 표시되도록 ViewModel 초기값을 사용한다.
      • 배경 tap 또는 기존 keyboard helper로 키보드를 내릴 수 있게 하되, 입력 값은 유지한다.
      • 취소 버튼 tap 시 입력 값이 있으면 SodaV2ActionModal로 입력 종료 안내 모달을 표시하고, 입력 값이 없으면 이전 화면으로 닫는다.
      • 입력 종료 모달은 Figma 308:19046 기준으로 표시한다.
      • 입력 종료 모달의 dimmed tap은 모달만 닫고 입력 화면은 유지한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelFanTalkWriteView|FocusState|focused\\(|canSend|SodaV2ActionModal|shouldShowExitModal|initialContent|mode" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift
      • 기대 결과: 입력 상태별 UI, keyboard focus, 수정 모드 초기값, 취소 시 모달 표시가 코드에서 확인된다.

Phase 8: 팬Talk 쓰기 진입 연결

  • Task 8.1: AppStep route 추가

    • 대상 파일:
      • 수정: SodaLive/Sources/App/AppStep.swift
      • 수정: SodaLive/Sources/ContentView.swift
    • 작업 내용:
      • AppStep에 팬Talk 쓰기 route를 추가한다.
      • route parameter는 최소 creatorId, 작성/수정 mode, 수정 대상 fanTalkId, 초기 content, onSuccess를 포함한다.
      • 작성 모드는 fanTalkId와 초기 content를 전달하지 않거나 비어 있는 값으로 전달한다.
      • 수정 모드는 수정 대상 fanTalkId와 기존 글 content를 반드시 함께 전달한다.
      • ContentViewAppStepLayerView에서 route를 CreatorChannelFanTalkWriteView로 연결한다.
      • 작성/수정 성공 시 onSuccess를 호출해 팬Talk 첫 페이지를 재조회할 수 있게 한다.
    • 검증 기준:
      • 실행 명령: rg "creatorChannelFanTalkWrite|CreatorChannelFanTalkWriteView|onSuccess|fanTalkId|initialContent|mode" SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift
      • 기대 결과: route와 화면 연결이 확인된다.
  • Task 8.2: CreatorChannel 팬Talk 탭 진입 action 연결

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
    • 작업 내용:
      • 크리에이터 채널 팬Talk 탭 우측하단 plus button tap 시 팬Talk 쓰기 route를 작성 모드로 이동한다.
      • empty state의 응원 남기기 버튼 tap 시 같은 팬Talk 쓰기 route를 작성 모드로 이동한다.
      • 내가 쓴 글의 수정하기 tap 시 같은 팬Talk 쓰기 route를 수정 모드로 이동하고 수정 대상 fanTalkId, 기존 글 content를 반드시 함께 전달한다.
      • 작성/수정 전송 성공 후 fanTalkViewModel.fetchFirstPage(creatorId:)를 호출하도록 onSuccess를 전달한다.
      • 내 채널에서는 기존 규칙대로 plus button과 empty state 응원 남기기 버튼을 표시하지 않는다.
    • 검증 기준:
      • 실행 명령: rg "showFanTalkWriteView|showFanTalkModifyView|creatorChannelFanTalkWrite|fetchFirstPage\\(creatorId: creatorId\\)|onTapWrite|onTapEdit|응원 남기기|fanTalkId|content|initialContent" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
      • 기대 결과: plus button, empty state button, 수정하기가 같은 팬Talk 쓰기 route로 연결되고 수정 모드는 fanTalkId와 기존 글 content를 함께 전달한다.

Phase 9: 팬Talk 쓰기 검증

  • Task 9.1: 정적 검증

    • 대상 파일:
      • 확인: docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
      • 확인: SodaLive/Sources/V2/Component/Modal
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/Write
      • 확인: SodaLive/Sources/App/AppStep.swift
      • 확인: SodaLive/Sources/ContentView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
    • 검증 기준:
      • 실행 명령: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/Component/Modal SodaLive/Sources/V2/CreatorChannel/FanTalk/Write SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj
      • 기대 결과: 공백 오류 없이 성공한다.
      • 실행 명령: plutil -lint SodaLive.xcodeproj/project.pbxproj
      • 기대 결과: OK 출력.
  • Task 9.2: 빌드 검증

    • 대상 파일:
      • 확인: SodaLive.xcworkspace
      • 확인: SodaLive.xcodeproj
    • 검증 기준:
      • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • 기대 결과: BUILD SUCCEEDED.
      • workspace 인식 실패 등 환경 문제가 있으면 xcodebuild -project "SodaLive.xcodeproj" -scheme "SodaLive-dev" -configuration Debug build를 대체 검증으로 실행하고 실패 원인을 검증 기록에 남긴다.

Phase 10: 팬Talk 팝업 위치와 문구 통일

  • Task 10.1: 삭제 확인 모달 표시 위치 상위 이동

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
      • 수정: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
    • 작업 내용:
      • 팬Talk row 더보기의 삭제 선택 시 기존 선택 상태와 삭제 API 흐름은 유지한다.
      • 삭제 확인 모달 렌더링은 팬Talk 탭 내부 overlay에서 제거하고 CreatorChannelView의 상위 ZStack에서 표시한다.
      • 삭제 확인 모달은 기존 SodaDialog 대신 SodaV2ActionModal을 사용한다.
      • 신고 사유 선택 UI인 CheersReportDialogView는 기존 신고 플로우 재사용 범위로 유지하고 이번 확인형 V2 popup dialog 전환 대상에서 제외한다.
    • 검증 기준:
      • 실행 명령: rg "fanTalkDeleteDialog|SodaV2ActionModal|isShowDeleteDialog|deleteSelectedFanTalk|overlay\(deleteDialog\)|private var deleteDialog" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
      • 기대 결과: CreatorChannelViewfanTalkDeleteDialogSodaV2ActionModal이 있고, CreatorChannelFanTalkTabView에는 삭제 dialog overlay가 남지 않는다.
  • Task 10.2: 팬Talk 리스트/상세 anchored context popup 공용화

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift
      • 수정: SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailFeedView.swift
    • 작업 내용:
      • 공용 CreatorChannelActionPopup을 사용해 리스트와 상세의 anchored context popup UI와 문구를 통일한다.
      • 팬Talk 목록 row의 사용자 노출 문구는 I18n.Explorer.editAction, I18n.Explorer.deleteAction을 사용해 수정하기, 삭제하기로 표시한다.
    • 검증 기준:
      • 실행 명령: rg "CreatorChannelActionPopup|I18n\.Explorer\.editAction|I18n\.Explorer\.deleteAction|수정하기|삭제하기" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/I18n/I18n.swift
      • 기대 결과: 팬Talk 목록 row가 공용 popup에 action 문구를 주입하고, 수정하기/삭제하기 문구는 I18n 키에서 확인된다.
  • Task 10.3: 팬Talk 상세 삭제 dialog V2 모달 적용

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift
    • 작업 내용:
      • 팬Talk 상세/답글 화면의 삭제 확인 dialog도 SodaV2ActionModal로 교체한다.
      • 확인, 취소, dimmed tap에서 기존 삭제/닫기 상태 흐름을 유지한다.
    • 검증 기준:
      • 실행 명령: rg "SodaV2ActionModal|dismissDeleteDialog|confirmDelete|SodaDialog" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift
      • 기대 결과: 상세 삭제 확인 dialog가 SodaV2ActionModal을 사용하고 SodaDialog는 남지 않는다.
  • Task 10.4: 정적/빌드 검증

    • 대상 파일:
      • 확인: docs/20260704_크리에이터_채널_팬Talk_탭/prd.md
      • 확인: docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
      • 확인: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift
    • 검증 기준:
      • 실행 명령: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift
      • 기대 결과: 공백 오류 없이 성공한다.
      • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • 기대 결과: BUILD SUCCEEDED.

검증 기록

  • 2026-07-04: Figma get_design_contextget_screenshot으로 290:9139, 290:9145, 290:9147, 666:36114, 290:9001을 확인했다.

  • 2026-07-04: rgsed로 기존 CreatorChannelView, 후원 탭 구현, UserProfileFanTalkViewModel, UserProfileFanTalkCheersItemView, UserProfileView 신고/삭제 흐름을 확인했다.

  • 2026-07-04: 사용자 피드백에 따라 우측하단 plus button은 CreatorChannelFloatingActionMenu의 private floatingButton과 동일 UI로 판단했다. 중복 컴포넌트를 만들지 않고 CreatorChannelFloatingIconButton 공용 컴포넌트로 추출해 CreatorChannelFloatingActionMenu와 팬Talk 글쓰기 floating button에서 재사용하는 방향으로 계획을 보완했다.

  • 2026-07-04: xcodebuild -workspace "SodaLive.xcworkspace" -listSodaLive, SodaLive-dev 앱 스킴은 확인했지만 테스트 action 구성은 확인되지 않았다.

  • 2026-07-04: rg 검증으로 팬Talk 탭 response model, API endpoint, Repository, ViewModel, count bar, list item, action popup, empty view, tab view, CreatorChannelView 연결 문자열을 확인했다.

  • 2026-07-04: plutil -lint SodaLive.xcodeproj/project.pbxproj 결과 OK를 확인했다.

  • 2026-07-04: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/CreatorChannel SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj를 실행해 공백 오류가 없음을 확인했다.

  • 2026-07-04: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build 결과 BUILD SUCCEEDED를 확인했다.

  • 2026-07-04: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" testScheme SodaLive-dev is not currently configured for the test action.으로 실패했다. 현재 범위에서는 새 테스트 타깃을 추가하지 않고 빌드/정적 검증으로 대체했다.

  • 2026-07-06: 사용자 요청에 따라 Swift 구현 없이 prd.mdplan-task.md에 팬Talk 글 입력 화면 Figma URL(290:9235, 290:9282, 290:9290), 입력 종료 모달(308:19046), 공용 모달(567:17662, 567:17666, 567:17712)과 후속 구현 task를 추가했다.

  • 2026-07-06: rg "290:9235|290:9282|290:9290|308:19046|567:17662|567:17666|567:17712|SodaV2ActionModal|ExplorerRepository.writeCheers" docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md로 Figma node, 공용 모달, 기존 작성 API 재사용 방침이 문서에 포함된 것을 확인했다.

  • 2026-07-06: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md 실행 결과 출력 없이 종료되어 공백 오류가 없음을 확인했다.

  • 2026-07-06: 사용자 요청에 따라 팬Talk 작성/수정 모두 같은 쓰기 페이지에서 수행하고, 작성/수정 API는 기존 팬Talk API(ExplorerRepository.writeCheers, ExplorerRepository.modifyCheers)를 사용하도록 계획을 보완했다.

  • 2026-07-06: rg "작성/수정|쓰기 페이지|수정 모드|작성 모드|ExplorerRepository.writeCheers|ExplorerRepository.modifyCheers|modifyCheers\\(cheersId:fanTalkId, content:content, isActive:nil\\)|creatorChannelFanTalkWrite|showFanTalkModifyView" docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md로 작성/수정 공용 쓰기 페이지와 기존 팬Talk API 사용 방침을 확인했다.

  • 2026-07-06: rg -n "no-op|수정 페이지는|수정 mutation|작성용 새 API|재사용 가능 여부|불가능한 것이 확인|미결정 사항|수정하기 tap은 수정 페이지" docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md 실행 결과 매칭이 없어 이전 충돌 문구가 남지 않았음을 확인했다.

  • 2026-07-06: 사용자 확인에 따라 수정 모드 진입 시 수정 대상 fanTalkId와 기존 글 content를 반드시 함께 전달하도록 계획을 보완했다.

  • 2026-07-06: rg '수정 모드.*fanTalkId|fanTalkId.*기존 글 content|fanTalkId.*초기 content|fanTalkId.*initialContent|기존 content.*content 초기값|수정 대상.*fanTalkId.*기존 글 content|fanTalkId.*0보다 커야' docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md로 수정 모드 route에 fanTalkId와 기존 글 content를 함께 전달한다는 조건을 확인했다.

  • 2026-07-06: Phase 6.2~8.2 구현을 완료했다. SodaV2ActionModal, CreatorChannelFanTalkWriteViewModel, CreatorChannelFanTalkWriteView를 추가하고 AppStep.creatorChannelFanTalkWrite, ContentView route, CreatorChannelView의 plus/empty/edit 진입 action을 연결했다.

  • 2026-07-06: rg "struct SodaV2ActionModal|button1|button2|onDimmedTap|Color\.black\.opacity\(0\.6\)" SodaLive/Sources/V2/Component/Modal/SodaV2ActionModal.swift로 V2 공용 액션 모달의 주입값, 버튼 표시 조건, dimmed 규칙을 확인했다. Figma node id는 코드 주석으로 남기지 않고 문서 기준으로만 유지했다.

  • 2026-07-06: rg "CreatorChannelFanTalkWriteViewModel|enum .*Mode|fanTalkId|initialContent|@Published var content|canSend|trimmingCharacters|shouldShowExitModal|writeCheers|modifyCheers|isActive: nil|onSuccess" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift SodaLive/Sources/Explorer/ExplorerRepository.swift로 작성/수정 mode, 입력값 검증, 기존 작성/수정 API 재사용을 확인했다.

  • 2026-07-06: rg "CreatorChannelFanTalkWriteView|FocusState|focused\(|canSend|SodaV2ActionModal|shouldShowExitModal|initialContent|mode" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift로 쓰기 화면의 focus, send 활성화, 수정 모드 초기값, 취소 모달 연결을 확인했다.

  • 2026-07-06: rg "creatorChannelFanTalkWrite|CreatorChannelFanTalkWriteView|onSuccess|fanTalkId|initialContent|mode" SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swiftrg "showFanTalkWriteView|showFanTalkModifyView|creatorChannelFanTalkWrite|fetchFirstPage\(creatorId: creatorId\)|onTapWrite|onTapEdit|응원 남기기|fanTalkId|content|initialContent" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift로 작성/수정 route와 진입 action 연결을 확인했다.

  • 2026-07-06: plutil -lint SodaLive.xcodeproj/project.pbxproj 결과 OK를 확인했다.

  • 2026-07-06: git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/Component/Modal SodaLive/Sources/V2/CreatorChannel/FanTalk/Write SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj 실행 결과 출력 없이 종료되어 공백 오류가 없음을 확인했다.

  • 2026-07-06: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build 결과 BUILD SUCCEEDED를 확인했다. 빌드 중 Alamofire dependency scan warning이 출력되었으나 빌드는 성공했다.

  • 2026-07-07: rg "fanTalkDeleteDialog|SodaV2ActionModal|isShowDeleteDialog|deleteSelectedFanTalk|overlay\(deleteDialog\)|private var deleteDialog" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift로 팬Talk 삭제 모달이 상위 CreatorChannelView에서 SodaV2ActionModal로 표시되고, 팬Talk 탭 내부 삭제 dialog overlay가 제거된 것을 확인했다.

  • 2026-07-07: rg "CreatorChannelActionPopup|I18n\.Explorer\.edit|I18n\.Common\.delete|수정하기|삭제하기" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailFeedView.swift로 리스트와 상세 anchored context popup이 공용 컴포넌트와 동일 I18n 문구를 사용하는 것을 확인했다.

  • 2026-07-07: rg "SodaV2ActionModal|dismissDeleteDialog|confirmDelete|SodaDialog" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift로 상세 삭제 확인 dialog가 SodaV2ActionModal을 사용하는 것을 확인했다.

  • 2026-07-07: Reviewer gate 지적에 따라 PRD 본문 요구사항/성공 기준/제약 조건에 삭제 확인 모달의 상위 CreatorChannelView 표시, SodaV2ActionModal 사용, list/detail anchored context popup 공용화, CheersReportDialogView 제외 범위를 명시했다.

  • 2026-07-07: 사용자 후속 요청에 따라 팬Talk 목록 row anchored context popup 문구를 수정/삭제에서 수정하기/삭제하기로 변경했다. 상세 popup 기본 문구는 기존 공용 기본값을 유지하고, 팬Talk row 호출부에서 I18n.Explorer.editAction, I18n.Explorer.deleteAction을 주입한다.

Phase 11: 팬레터 empty state 다국어 보강

  • Task 11.1: 팬레터 empty state 문구 정의

    • 대상 파일:
      • 수정: SodaLive/Sources/I18n/I18n.swift
    • 작업 내용:
      • Empty 문구를 한국어 아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요!, 영어 No fan letters yet.\nBe the first to send one!, 일본어 まだファンレターがありません。\n最初のファンレターを送ってみましょう로 정의한다.
      • 작성 버튼을 한국어 응원남기기, 영어 Send a fan letter, 일본어 ファンレターを送る로 정의한다.
      • 지정된 \n 줄바꿈을 모든 언어에서 유지한다.
    • 검증 기준:
      • 실행 명령: rg -n "아직 응원이 없습니다|No fan letters yet|まだファンレターがありません|응원남기기|Send a fan letter|ファンレターを送る" SodaLive/Sources/I18n/I18n.swift
      • 기대 결과: empty 문구와 작성 버튼의 ko/en/ja 값이 pick(ko:en:ja:)에서 확인된다.
  • Task 11.2: 팬레터 empty View에 I18n 적용

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
    • 작업 내용:
      • 하드코딩된 empty 문구와 응원 남기기 버튼을 Task 11.1의 I18n 값으로 교체한다.
      • 기존 팬레터 작성 route 연결과 내 채널의 empty 버튼·floating button 숨김 규칙을 유지한다.
      • empty 작성 버튼은 Color.gray800 배경과 white 아이콘·텍스트를 사용한다.
      • 신규 route나 API를 추가하지 않는다.
    • 검증 기준:
      • 실행 명령: rg -n "CreatorChannelFanTalkEmptyView|I18n\.|showsWriteButton|onTapWrite|creatorChannelFanTalkWrite|Color\.gray800|foregroundColor\(\.white\)|응원 남기기" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
      • 기대 결과: View는 I18n 값을 사용하고 기존 작성 route와 내 채널 숨김 조건 및 gray800/white 버튼 색상을 유지하며 하드코딩된 응원 남기기가 없다.
  • Task 11.3: 정적 검사와 Debug 빌드 검증

    • 대상 파일:
      • 확인: SodaLive/Sources/I18n/I18n.swift
      • 확인: SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkEmptyView.swift
    • 검증 기준:
      • 실행 명령: git diff --check
      • 기대 결과: 출력 없이 exit code 0이다.
      • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • 기대 결과: ** BUILD SUCCEEDED **.
  • 2026-08-14: 사용자 인터뷰로 팬레터 탭 empty 문구와 작성 버튼의 ko/en/ja 값을 확정하고 Phase 11에 미완료 후속 Task로 추가했다. 기존 작성 route와 내 채널 버튼 숨김 규칙은 유지하며 이번 작업은 문서만 보강했다.

  • 2026-08-14: Phase 11 구현을 완료했다. 팬레터 탭 empty 문구와 작성 버튼을 ko/en/ja I18n으로 교체하고 기존 작성 route 및 내 채널 버튼 숨김 규칙을 유지했다. 사용자 후속 요청에 따라 작성 버튼은 Color.gray800 배경과 white 아이콘·텍스트로 변경했다. 23개 정적 계약, git diff --check, iPhone 16 Pro 시뮬레이터 Debug 빌드가 통과했고, 세 언어 재캡처와 두 독립 색상·CJK 리뷰에서 차단 항목 없이 PASS를 확인했다.

Phase 12: 팬Talk 쓰기 입력 UX 보강

Phase 결과: 팬Talk 쓰기 화면에서 취소 문구가 잘리지 않고, 다국어 placeholder와 500자 입력 제한 및 초과 예외 상태가 일관되게 동작한다.

선행조건: PRD §5.10, DEC-FANTALK-WRITE-001~003 확정.

Phase 완료 조건: P12-T1, P12-T2, P12-GATE 완료와 검증 기록 누적.

Task 12.1 다국어 placeholder와 입력 상태 계약

Goal 실행 P12-T1: placeholder 문구와 500자 제한에 필요한 ViewModel 상태를 기존 타입에 최소 추가한다.

  • 시작 조건: Phase 12 선행조건 충족.
  • 완료 증거: ko/en/ja 문구, 500자 허용 경계, 초과 상태 및 전송 비활성화 조건이 코드와 정적 검사에서 확인된다.
  • 범위 밖: 신규 입력 컴포넌트, dependency, route, API 변경.
  • TDD 예외 사유: 현재 프로젝트에 unit test bundle target이 없으며 이번 범위에서 신규 test target을 추가하지 않는다.
  • 대체 검증 방법: rg 정적 계약 검사, Swift 표준 문자열 경계 확인, Phase Gate의 앱 빌드와 수동 입력 검증을 사용한다.

Files:

  • Modify: SodaLive/Sources/I18n/I18n.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift

Interfaces:

  • Consumes: 기존 I18n.pick(ko:en:ja:), content, maxContentCount, canSend.

  • Produces: I18n.CreatorChannelFanTalk.writePlaceholder, content.count > maxContentCount 초과 상태, 초과 상태를 포함한 canSend.

  • I18n.CreatorChannelFanTalk.writePlaceholder에 한국어 응원 메시지를 입력해 주세요., 영어 Enter a message of support., 일본어 応援メッセージを入力してください。를 추가한다.

  • maxContentCount500을 유지하고 500자는 유효, 500자 초과만 예외인 상태를 제공한다.

  • canSend는 기존 공백·줄바꿈 검증에 더해 content가 500자를 초과하면 false가 되게 한다.

  • 수정 모드의 초기 content는 임의로 자르지 않아 500자 초과 예외 상태를 화면에서 판정할 수 있게 한다.

  • rg -n "writePlaceholder|maxContentCount|content.count|canSend|whitespacesAndNewlines" SodaLive/Sources/I18n/I18n.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift로 상태 계약을 확인한다.

Task 12.2 내비게이션·placeholder·500자 UI 적용

Goal 실행 P12-T2: 기존 쓰기 View 안에서 취소 문구 잘림을 제거하고 입력 제한과 초과 표시를 연결한다.

  • 시작 조건: P12-T1 완료.
  • 완료 증거: View 정적 검사와 ko/en/ja 및 499·500·501자 수동 검증 통과.
  • 범위 밖: 내비게이션 전체 높이, 보내기 capsule 높이, /500 색상, 작성·수정 API 흐름 변경.
  • TDD 예외 사유: 현재 프로젝트에 UI test bundle target이 없으며 이번 범위에서 신규 test target을 추가하지 않는다.
  • 대체 검증 방법: View 정적 계약 검사와 Phase Gate의 시뮬레이터 수동 검증을 사용한다.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift

Interfaces:

  • Consumes: P12-T1writePlaceholder, maxContentCount, 초과 상태, canSend.

  • Produces: 500자로 제한된 TextEditor binding과 정상·초과 입력 UI 상태.

  • 취소 Text.frame(width: 64, height: 56, alignment: .leading)만 제거한다.

  • 내비게이션 .frame(height: 56)과 보내기 capsule .frame(height: 44)는 유지한다.

  • content가 비어 있을 때 writePlaceholder를 표시하고 입력이 시작되면 숨긴다.

  • TextEditor가 전달한 새 content는 String(newValue.prefix(viewModel.maxContentCount))까지만 반영해 타이핑과 붙여넣기 모두 500자를 넘지 않게 한다.

  • ViewModel에 이미 500자를 초과한 content가 있으면 메시지 본문과 현재 글자 수를 Color.red400으로 표시한다.

  • 500자 초과 상태에서는 canSend == false에 따라 보내기 버튼을 비활성화하고 /500은 기존 Color.gray500을 유지한다.

  • 기존 focus, 키보드, 취소 모달, 작성·수정 submit 흐름은 변경하지 않는다.

  • rg -n "writePlaceholder|prefix\(viewModel.maxContentCount\)|Color.red400|frame\(width: 64, height: 56|frame\(height: 56\)|frame\(height: 44\)" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift로 추가·유지·제거 계약을 확인한다.

Phase 12 Gate

Goal 실행 P12-GATE: Phase 12 요구사항과 기존 작성·수정 흐름의 회귀 여부를 최종 판정한다.

  • 시작 조건: P12-T1, P12-T2 완료.
  • 완료 증거: 아래 자동·수동 검증 통과와 실제 결과를 검증 기록에 누적.
  • 범위 밖: Gate 통과를 위한 기존 검증 완화나 관련 없는 소스 수정.
rg -n "응원 메시지를 입력해 주세요|Enter a message of support|応援メッセージを入力してください|writePlaceholder" SodaLive/Sources/I18n/I18n.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift
rg -n "maxContentCount|content.count|prefix\(viewModel.maxContentCount\)|Color.red400|canSend" SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift
git diff --check -- SodaLive/Sources/I18n/I18n.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Write/CreatorChannelFanTalkWriteViewModel.swift docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 다국어 문구와 500자 경계 및 초과 상태 계약이 확인되고, 공백 오류 없이 ** BUILD SUCCEEDED **가 출력된다.

수동 검증:

  • ko/en/ja에서 취소 문구가 잘리지 않고 placeholder가 정확히 표시된다.

  • 499자와 500자는 입력되며 유효 content일 때 보내기 버튼이 활성화된다.

  • 501번째 타이핑 문자와 500자를 초과하는 붙여넣기 문자는 반영되지 않고 글자 수가 500을 넘지 않는다.

  • 수정 초기값 등으로 500자를 초과한 예외 상태를 주입하면 메시지 본문과 현재 글자 수가 Color.red400으로 표시되고 보내기 버튼이 비활성화된다.

  • 공백·줄바꿈만 입력한 상태, 취소 모달, 작성·수정 submit 흐름이 기존과 동일하다.

  • 2026-08-25: 사용자 인터뷰에서 500자 허용·501번째 이후 입력 차단, 500자 초과 예외 상태의 본문·현재 글자 수 붉은 표시와 보내기 비활성화, 취소 문구의 고정 크기만 제거하는 범위를 확정했다. PRD와 Phase 12 계획만 추가했으며 Swift 구현과 빌드 검증은 수행하지 않았다.

  • 2026-08-25: Phase 12 구현을 완료했다. writePlaceholder, 500자 제한 binding, 500자 초과 상태와 전송 비활성화, 취소 문구의 고정 frame 제거를 적용했으며 기존 내비게이션·보내기 높이와 작성·수정 흐름은 유지했다.

  • 2026-08-25: Phase 12의 두 rg 정적 계약 검사, Swift String.count/prefix(_:) 499·500·501자 경계 검사, git diff --check, xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build를 실행했고 ** BUILD SUCCEEDED **를 확인했다.

  • 2026-08-25: 사용자 요청에 따라 visual QA와 시뮬레이터 수동 검증은 수행하지 않았다. ko/en/ja 표시와 실제 입력 동작에 관한 수동 검증 체크박스 5개는 사용자 확인 전까지 미완료로 유지한다.