Files

29 KiB

메인 홈 랭킹 탭 구현 계획

기준 문서

  • PRD: docs/20260630_메인_홈_랭킹_탭/prd.md
  • 코드 스타일: docs/agent-guides/code-style.md
  • 빌드/검증: docs/agent-guides/build-test-verification.md
  • Figma 전체 화면: 24:5654
  • Figma 1위 카드: 24:5659
  • Figma 2~7위 카드: 24:5661
  • Figma 8~10위 카드: 24:5667
  • Figma 11위 이상 row: 24:5670
  • Figma 상승 badge: 24:5674
  • Figma 하락 badge: 24:5675
  • Figma 변동 없음 badge 참고: 24:5678

구현 원칙

  • MainHomeView는 홈 상단 title bar, 추천/랭킹/팔로잉 Text tab shell만 담당한다.
  • 랭킹 탭의 API, 모델, ViewModel, 세부 UI는 SodaLive/Sources/V2/Main/Home/Ranking/** 아래에 둔다.
  • 기존 추천 탭 API/ViewModel/Moya 패턴을 따르고, 기존 구 홈 API/랭킹 구현은 수정하지 않는다.
  • Capsule Tab bar와 랭킹 필터는 구현하지 않는다.
  • 서버가 내려준 items[].rank를 화면 순위로 사용하고 클라이언트에서 index 기반 순위를 재계산하지 않는다.
  • item은 최대 20개로 취급한다.
  • 1~10위 카드는 모양이 동일하므로 MainHomeRankingCard 하나를 재사용하고, 1열/2열/3열 차이는 부모 grid/layout에서 처리한다.
  • 11위 이상은 row 방식으로 모양이 다르므로 MainHomeRankingRow로 분리한다.
  • 카드/row 컴포넌트의 루트 레이아웃은 고정 숫자 width/height로 제한하지 않고, grid column, parent width, aspectRatio, relative sizing 기반으로 화면 크기에 대응한다.
  • 랭킹 페이지 노출 자체는 로그인 guard 없이 허용하고, item 탭으로 상세 진입할 때만 기존 creator 상세 guard를 재사용한다.
  • 외부 라이브러리는 추가하지 않는다.

주요 대상 파일

생성

  • SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swift
  • SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift

수정

  • SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
  • SodaLive/Sources/V2/Main/Home/MainHomeView.swift
  • SodaLive/Sources/I18n/I18n.swift
  • 필요 시 SodaLive.xcodeproj/project.pbxproj

확인

  • SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift
  • SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift
  • SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift
  • SodaLive/Sources/V2/Main/MainView.swift
  • SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_decrease.imageset
  • SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_increase.imageset
  • SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_stay.imageset
  • SodaLive/Resources/Assets.xcassets/v2/ic_rank_new.imageset

TASK 체크리스트

Phase 1: 기준점 점검

  • Task 1.1: PRD와 기존 랭킹 placeholder 확인

    • 대상 파일:
      • 확인: docs/20260630_메인_홈_랭킹_탭/prd.md
      • 확인: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
      • 확인: SodaLive/Sources/V2/Main/Home/MainHomeView.swift
    • 작업 내용:
      • PRD 성공 기준과 제외 범위를 다시 읽고 구현 범위를 확정한다.
      • MainHomeRankingView가 placeholder인지 확인한다.
      • MainHomeView가 랭킹 탭에 onTapCreator를 아직 전달하지 않는지 확인한다.
    • 검증 기준:
      • 실행 명령: sed -n '1,140p' SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift && sed -n '1,120p' SodaLive/Sources/V2/Main/Home/MainHomeView.swift
      • 기대 결과: 랭킹 탭은 placeholder이고, MainHomeView는 탭 shell 역할만 담당한다.
  • Task 1.2: 순위 변화 아이콘 asset 확인

    • 대상 파일:
      • 확인: SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_decrease.imageset
      • 확인: SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_increase.imageset
      • 확인: SodaLive/Resources/Assets.xcassets/v2/ic_rank_caret_stay.imageset
      • 확인: SodaLive/Resources/Assets.xcassets/v2/ic_rank_new.imageset
    • 작업 내용:
      • PRD에서 지정한 순위 변화 아이콘 asset이 존재하는지 확인한다.
      • asset이 없으면 구현을 진행하지 말고 PRD/사용자 확인을 먼저 받는다.
    • 검증 기준:
      • 실행 명령: find SodaLive/Resources/Assets.xcassets/v2 -maxdepth 2 -name 'ic_rank_*' | sort
      • 기대 결과: ic_rank_caret_decrease, ic_rank_caret_increase, ic_rank_caret_stay, ic_rank_new imageset이 모두 확인된다.

Phase 2: API와 응답 모델

  • Task 2.1: 랭킹 응답 모델 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swift
    • 작업 내용:
      • MainHomeCreatorRankingResponseMainHomeCreatorRankingItem을 PRD 필드명 그대로 만든다.
      • profileImageUrl은 optional로 둔다.
      • MainHomeCreatorRankingItemIdentifiable을 채택하고 idcreatorId를 사용한다.
    • 검증 기준:
      • 실행 명령: rg "struct MainHomeCreatorRankingResponse|struct MainHomeCreatorRankingItem|showRankChange|rankChange|profileImageUrl|var id" SodaLive/Sources/V2/Main/Home/Ranking/Models/MainHomeCreatorRankingResponse.swift
      • 기대 결과: PRD의 응답 필드와 Identifiable id 기준이 확인된다.
  • Task 2.2: 랭킹 API endpoint 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift
      • 확인: SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationApi.swift
    • 작업 내용:
      • MainHomeRankingApiTargetType으로 만든다.
      • case는 getCreatorRankings 하나만 둔다.
      • path는 /api/v2/home/rankings/creators, method는 .get, task는 .requestPlain로 둔다.
      • headers는 기존 V2 홈 추천 API와 동일하게 Authorization: Bearer {token} 패턴을 사용한다.
    • 검증 기준:
      • 실행 명령: rg "/api/v2/home/rankings/creators|getCreatorRankings|Authorization|requestPlain" SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingApi.swift
      • 기대 결과: 신규 endpoint와 인증 헤더가 랭킹 전용 API에만 존재한다.
  • Task 2.3: 랭킹 Repository 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift
      • 확인: SodaLive/Sources/V2/Main/Home/Recommendation/Repository/MainHomeRecommendationRepository.swift
    • 작업 내용:
      • MainHomeRankingRepository를 만들고 MoyaProvider<MainHomeRankingApi>를 사용한다.
      • getCreatorRankings() -> AnyPublisher<Response, MoyaError> 메서드를 제공한다.
    • 검증 기준:
      • 실행 명령: rg "final class MainHomeRankingRepository|MoyaProvider<MainHomeRankingApi>|getCreatorRankings\\(\\)|AnyPublisher<Response, MoyaError>" SodaLive/Sources/V2/Main/Home/Ranking/Repository/MainHomeRankingRepository.swift
      • 기대 결과: 추천 탭과 같은 Repository 패턴이 확인된다.

Phase 3: ViewModel 상태와 데이터 로딩

  • Task 3.1: 랭킹 ViewModel 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift
      • 확인: SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift
    • 작업 내용:
      • MainHomeRankingViewModelObservableObject로 만든다.
      • isLoading, items, showRankChange, emptyMessage, hasLoaded 상태를 둔다.
      • fetchRankings()에서 ApiResponse<MainHomeCreatorRankingResponse>를 디코딩한다.
      • 성공 시 items는 서버 순서를 유지하되 최대 20개만 보관한다.
      • 빈 배열 또는 API 실패/디코딩 실패 시 emptyMessageI18n.HomeRanking.emptyStateMessage를 설정한다.
      • 실패 시 toast 대신 empty state를 표시한다.
    • 검증 기준:
      • 실행 명령: rg "final class MainHomeRankingViewModel|fetchRankings|ApiResponse<MainHomeCreatorRankingResponse>|prefix\\(20\\)|emptyStateMessage|hasLoaded" SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift
      • 기대 결과: 최대 20개 처리, empty state 처리, API 디코딩 로직이 확인된다.
  • Task 3.2: 랭킹 empty state I18n 추가

    • 대상 파일:
      • 수정: SodaLive/Sources/I18n/I18n.swift
    • 작업 내용:
      • I18n.HomeRanking.emptyStateMessage를 추가한다.
      • ko는 순위 집계 중입니다.\n잠시 후 다시 시도해 주세요.로 둔다.
      • en/ja는 기능 검증을 막지 않는 자연스러운 대응 문구로 추가한다.
    • 검증 기준:
      • 실행 명령: rg "enum HomeRanking|emptyStateMessage|순위 집계 중입니다|잠시 후 다시 시도해 주세요" SodaLive/Sources/I18n/I18n.swift
      • 기대 결과: 랭킹 empty state 문구가 I18n에 존재한다.

Phase 4: 순위 변화 UI 컴포넌트

  • Task 4.1: 순위 변화 badge 컴포넌트 생성
    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swift
    • 작업 내용:
      • 입력값은 showRankChange: Bool, rankChange: Int?, isNew: Bool로 둔다.
      • showRankChange == false이면 아무것도 표시하지 않는다.
      • isNew == true이면 Image("ic_rank_new")만 표시한다.
      • rankChange > 0이면 절댓값 숫자와 Image("ic_rank_caret_increase")를 표시한다.
      • rankChange < 0이면 절댓값 숫자와 Image("ic_rank_caret_decrease")를 표시한다.
      • rankChange == 0이면 숫자 없이 Image("ic_rank_caret_stay")만 표시한다.
      • rankChange == nil && isNew == false이면 아무것도 표시하지 않는다.
      • Figma 기준 rank 변화 badge는 컴포넌트 내부에서 Color.gray900(#202020) 배경, horizontal padding 4, corner radius 4를 적용한다.
      • Preview에는 상승, 하락, 변동 없음, 신규 진입, showRankChange == false, rankChange == nil && isNew == false 상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
    • 검증 기준:
      • 실행 명령: rg "struct MainHomeRankChangeBadge|ic_rank_new|ic_rank_caret_increase|ic_rank_caret_decrease|ic_rank_caret_stay|abs\\(|Color.gray900|cornerRadius\\(4\\)|padding\\(\\.horizontal, 4\\)|PreviewProvider|showRankChange: false|rankChange: nil|isNew: true|rankChange: 0" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankChangeBadge.swift
      • 기대 결과: PRD의 순위 변화 표시 규칙, Figma badge 배경/라운드/패딩, 아이콘명, 조건별 Preview가 모두 확인된다.

Phase 5: 순위별 랭킹 UI 컴포넌트

  • Task 5.1: 1~10위 공용 카드 컴포넌트 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swift
      • 확인: SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.swift
      • 확인: SodaLive/Sources/Home/HomeCreatorRankingItemView.swift
    • 작업 내용:
      • rank >= 1 && rank <= 10 카드형 item을 모두 담당하는 공용 컴포넌트로 만든다.
      • Figma 기준으로 1위, 27위, 810위 카드는 크기만 다르고 모양은 동일하므로 별도 카드 파일을 만들지 않는다.
      • 1열/2열/3열 배치와 카드 크기는 부모 MainHomeRankingView의 grid/layout이 결정한다.
      • DownsampledKFImageprofileImageUrl을 표시한다.
      • 카드 하단에 dim gradient를 적용하고 nickname을 한 줄 말줄임으로 표시한다.
      • rank 숫자는 item.rank를 사용한다.
      • 순위 변화 표시는 MainHomeRankChangeBadge를 사용한다.
      • 카드 tap 시 onTapCreator(item.creatorId)를 호출한다.
      • 루트 container에 고정 숫자 frame(width:), frame(height:)를 사용하지 않는다.
      • 전체 카드 비율은 부모 width 기반 aspectRatio 또는 container-relative 제약으로 잡는다.
      • Preview에는 1위 크기, 2열 크기, 3열 크기 예시를 함께 배치한다.
      • Preview에는 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
    • 검증 기준:
      • 실행 명령: rg "struct MainHomeRankingCard|DownsampledKFImage|LinearGradient|item.rank|MainHomeRankChangeBadge|onTapCreator\\(item.creatorId\\)|aspectRatio|PreviewProvider|rankChange: 5|rankChange: -|rankChange: 0|isNew: true|showRankChange: false" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingCard.swift
      • 기대 결과: 1~10위 카드가 하나의 공용 컴포넌트로 구현되고, 서버 rank와 공용 badge를 사용하며, 전체 크기는 부모 layout에 의해 결정되고 조건별 Preview가 확인된다.
  • Task 5.2: 11위 이상 row 컴포넌트 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swift
    • 작업 내용:
      • rank >= 11 item을 row 방식으로 표시한다.
      • rank 숫자, profile image, nickname, 순위 변화 badge를 Figma row 구조에 맞춰 배치한다.
      • rank 숫자는 item.rank를 사용한다.
      • 순위 변화 표시는 MainHomeRankChangeBadge를 사용한다.
      • row tap 시 onTapCreator(item.creatorId)를 호출한다.
      • 루트 container에 고정 숫자 frame(width:), frame(height:)를 사용하지 않는다.
      • row의 전체 높이는 고정 숫자로 박지 않고 내부 padding, image aspect ratio, parent width 기반 제약으로 구성한다.
      • Preview에는 11위 이상 row의 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 배치해 한 화면에서 UI 차이를 확인할 수 있게 한다.
    • 검증 기준:
      • 실행 명령: rg "struct MainHomeRankingRow|DownsampledKFImage|item.rank|MainHomeRankChangeBadge|onTapCreator\\(item.creatorId\\)|aspectRatio|PreviewProvider|rankChange: 5|rankChange: -|rankChange: 0|isNew: true|showRankChange: false" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingRow.swift
      • 기대 결과: row형 순위 UI가 서버 rank와 공용 badge를 사용하고, 전체 크기는 반응형 제약으로 구성되며 조건별 Preview가 확인된다.
  • Task 5.3: empty state 컴포넌트 생성

    • 대상 파일:
      • 생성: SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift
    • 작업 내용:
      • I18n.HomeRanking.emptyStateMessage를 중앙 또는 랭킹 콘텐츠 영역 안에 표시한다.
      • 배경은 검정, 텍스트는 기존 V2 empty state 관례와 맞춘다.
    • 검증 기준:
      • 실행 명령: rg "struct MainHomeRankingEmptyStateView|I18n.HomeRanking.emptyStateMessage|Color.black" SodaLive/Sources/V2/Main/Home/Ranking/Components/MainHomeRankingEmptyStateView.swift
      • 기대 결과: empty/API 실패 공통 문구 표시 컴포넌트가 확인된다.

Phase 6: 랭킹 탭 화면 조립과 상세 진입 연결

  • Task 6.1: MainHomeRankingView 구현

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
    • 작업 내용:
      • placeholder를 제거한다.
      • @StateObject private var viewModel = MainHomeRankingViewModel()를 사용한다.
      • 최초 onAppear에서 hasLoaded == false일 때만 fetchRankings()를 호출한다.
      • loading, empty, content 상태를 분기한다.
      • content 상태에서는 1위 단독, 27위 2열, 810위 3열, 11위 이상 row로 조립한다.
      • 1~10위는 공용 MainHomeRankingCard, 11위 이상은 MainHomeRankingRow를 사용한다.
      • 2열/3열 grid는 고정 숫자 card width가 아니라 GridItem(.flexible()) 기반 column으로 구성한다.
      • MainHomeRankingView에서 카드/row 컴포넌트의 루트 크기를 고정 숫자로 강제하지 않는다.
      • Capsule Tab bar는 추가하지 않는다.
      • 생성한 카드/row 컴포넌트에 showRankChangeonTapCreator를 전달한다.
    • 검증 기준:
      • 실행 명령: rg "MainHomeRankingViewModel|fetchRankings|MainHomeRankingCard|MainHomeRankingRow|MainHomeRankingEmptyStateView|GridItem\\(\\.flexible\\(\\)\\)|onTapCreator|MainPlaceholderTabView|CapsuleTabBar" SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
      • 기대 결과: MainPlaceholderTabViewCapsuleTabBar는 검색되지 않고, 랭킹 ViewModel/공용 카드/row 조립과 flexible grid가 확인된다.
  • Task 6.2: MainHomeView에서 creator tap callback 전달

    • 대상 파일:
      • 수정: SodaLive/Sources/V2/Main/Home/MainHomeView.swift
      • 확인: SodaLive/Sources/V2/Main/MainView.swift
    • 작업 내용:
      • 랭킹 탭에서 MainHomeRankingView(onTapCreator: onTapCreator)를 호출하도록 변경한다.
      • MainView의 기존 handleRecommendationCreatorTap 로그인 guard 흐름을 그대로 재사용한다.
      • 랭킹 페이지 노출 자체에는 별도 guard를 추가하지 않는다.
    • 검증 기준:
      • 실행 명령: rg "MainHomeRankingView\\(onTapCreator: onTapCreator\\)|handleRecommendationCreatorTap|setAppStep\\(step: \\.login\\)|creatorDetail" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift
      • 기대 결과: 랭킹 item tap이 기존 creator 상세 guard callback으로 연결된다.

Phase 7: 프로젝트 연결과 전체 검증

  • Task 7.1: Xcode 프로젝트 포함 여부 확인

    • 대상 파일:
      • 확인/필요 시 수정: SodaLive.xcodeproj/project.pbxproj
      • 확인: SodaLive/Sources/V2/Main/Home/Ranking/**
    • 작업 내용:
      • 신규 Swift 파일이 빌드 대상에 포함되는지 확인한다.
      • 현재 프로젝트 구조에서 파일 시스템 동기화가 자동이 아니면 project.pbxproj에 필요한 file reference/build phase를 추가한다.
    • 검증 기준:
      • 실행 명령: rg "MainHomeCreatorRankingResponse|MainHomeRankingApi|MainHomeRankingRepository|MainHomeRankingViewModel|MainHomeRankChangeBadge|MainHomeRankingCard|MainHomeRankingRow|MainHomeRankingEmptyStateView" SodaLive.xcodeproj/project.pbxproj SodaLive/Sources/V2/Main/Home/Ranking
      • 기대 결과: 신규 Swift 파일이 소스 경로와 필요 시 Xcode 프로젝트에서 확인된다.
  • Task 7.2: 정적 검색 검증

    • 대상 파일:
      • 확인: SodaLive/Sources/V2/Main/Home/Ranking/**
      • 확인: SodaLive/Sources/V2/Main/Home/MainHomeView.swift
      • 확인: SodaLive/Sources/I18n/I18n.swift
    • 작업 내용:
      • endpoint, 아이콘명, empty state, 공용 카드/row 컴포넌트 분리, Capsule Tab bar 제외, rank index 미사용 여부를 검색으로 확인한다.
      • 카드/row 컴포넌트의 루트 container가 고정 숫자 frame(width:)/frame(height:)로 제한되지 않는지는 코드 리뷰로 확인한다. 단, 아이콘/내부 이미지의 작은 고정 크기는 이 금지 대상이 아니다.
    • 검증 기준:
      • 실행 명령: rg "/api/v2/home/rankings/creators|ic_rank_caret_decrease|ic_rank_caret_increase|ic_rank_caret_stay|ic_rank_new|순위 집계 중입니다|MainHomeRankingCard|MainHomeRankingRow|CapsuleTabBar|enumerated\\(\\)" SodaLive/Sources/V2/Main/Home/Ranking SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/I18n/I18n.swift
      • 기대 결과: endpoint/아이콘/문구/공용 카드/row 컴포넌트는 확인되고, 랭킹 UI에서 CapsuleTabBar와 rank 계산용 enumerated() 사용은 없어야 한다.
  • Task 7.3: 빌드 검증

    • 대상 파일:
      • 확인: SodaLive.xcworkspace
      • 확인: SodaLive.xcodeproj
    • 작업 내용:
      • 공식 빌드 명령으로 컴파일 오류를 확인한다.
      • 테스트 번들 타깃이 확인되지 않는 저장소 상태이므로, 우선 Debug build를 필수 검증으로 둔다.
    • 검증 기준:
      • 실행 명령: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
      • 기대 결과: build succeeded
  • Task 7.4: 수동 QA 체크

    • 대상 파일:
      • 확인: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
      • 확인: SodaLive/Sources/V2/Main/Home/Ranking/Components/**
    • 작업 내용:
      • 랭킹 탭 진입 시 API가 1회 호출되는지 확인한다.
      • 1위, 27위, 810위, 11위 이상 배치가 Figma 기준과 맞는지 확인한다.
      • 1~10위가 MainHomeRankingCard 하나를 재사용하고, 11위 이상만 MainHomeRankingRow를 사용하는지 확인한다.
      • 카드/row 컴포넌트의 루트 레이아웃이 화면 폭에 따라 자연스럽게 대응하는지 확인한다.
      • Phase 4/5 컴포넌트 Preview에서 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태가 한 화면에서 모두 보이는지 확인한다.
      • showRankChange == false일 때 순위 변화 UI가 숨겨지는지 확인한다.
      • isNew == true일 때 ic_rank_new가 표시되는지 확인한다.
      • rankChange > 0, < 0, == 0 각각 increase/decrease/stay 아이콘 규칙을 확인한다.
      • 빈 배열 또는 API 실패 시 empty state 문구가 표시되는지 확인한다.
      • 랭킹 item tap 시 로그인 guard 이후 creator 상세로 이동하는지 확인한다.
    • 검증 기준:
      • 수동 확인: PRD Success Criteria 전 항목을 체크한다.

검증 기록

  • 2026-06-30 문서 생성:

    • 목적: prd.md를 기반으로 메인 홈 랭킹 탭 구현 전 plan-task 문서 생성
    • 확인한 기준 문서:
      • docs/20260630_메인_홈_랭킹_탭/prd.md
      • docs/agent-guides/documentation-policy.md
      • docs/agent-guides/build-test-verification.md
      • docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md
    • 확인한 코드:
      • SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift가 placeholder 상태임
      • SodaLive/Sources/V2/Main/Home/MainHomeView.swift가 랭킹 탭에 MainHomeRankingView()를 조합 중임
      • SodaLive/Sources/V2/Main/Home/Recommendation/**의 API/Repository/ViewModel 패턴
      • SodaLive/Sources/V2/Main/MainView.swift의 기존 creator 상세 guard callback
      • SodaLive/Resources/Assets.xcassets/v2/ic_rank_* 아이콘 asset 존재
  • 2026-06-30 문서 보완:

    • 목적: 순위별 UI 검증 Task를 명확히 하고, 카드/row 루트 레이아웃의 고정 숫자 width/height 금지 제약을 반영
    • 변경 내용:
      • PRD에 1~10위 공용 카드와 11위 이상 row 분리 기준 추가
      • PRD에 카드/row 컴포넌트 루트 레이아웃의 반응형 제약 기준 추가
      • plan-task Phase 5를 MainHomeRankingCard, MainHomeRankingRow Task로 정리
      • Phase 4/5 Preview에서 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태를 모두 확인하도록 검증 기준 추가
  • 2026-06-30 문서 보완:

    • 목적: Figma상 1~10위 카드는 크기만 다르고 모양이 동일하므로 카드 컴포넌트를 하나로 통합
    • 변경 내용:
      • 순위 구간별로 나뉘어 있던 카드 컴포넌트 계획을 제거
      • MainHomeRankingCard 단일 카드 컴포넌트와 부모 grid 기반 1열/2열/3열 배치로 계획 수정
  • 2026-06-30 Phase 1~3 진행:

    • 목적: 랭킹 탭 API 연동을 위한 기준점 확인, 응답 모델/API/Repository/ViewModel/I18n 추가
    • 확인한 기준:
      • MainHomeRankingView.swift는 placeholder 상태 유지
      • MainHomeView.swift는 랭킹 탭에 onTapCreator를 아직 전달하지 않는 Phase 1 기준 상태 유지
      • ic_rank_caret_decrease, ic_rank_caret_increase, ic_rank_caret_stay, ic_rank_new asset 존재 확인
    • 변경 내용:
      • MainHomeCreatorRankingResponse, MainHomeCreatorRankingItem 추가
      • MainHomeRankingApi, MainHomeRankingRepository 추가
      • MainHomeRankingViewModel 추가
      • I18n.HomeRanking.emptyStateMessage 추가
    • 검증 메모:
      • 현재 저장소 가이드상 테스트 번들 타깃이 확인되지 않아 신규 단위 테스트는 추가하지 않고 정적 검색 및 빌드 검증으로 확인
  • 2026-06-30 Phase 4 진행:

    • 목적: 랭킹 카드/row에서 재사용할 순위 변화 badge 컴포넌트 추가
    • 변경 내용:
      • MainHomeRankChangeBadge 추가
      • showRankChange, isNew, rankChange 조건에 따라 신규/상승/하락/변동 없음/미표시 상태 분기
      • Phase 4 Preview에 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김, rankChange == nil && isNew == false 상태 추가
    • 검증 메모:
      • 문서의 Phase 4 정적 검색 기준으로 아이콘명, 조건별 Preview, abs() 사용을 확인
      • Figma badge 배경 기준 확인 후 Color.gray900(#202020) 배경, horizontal padding 4, corner radius 4를 MainHomeRankChangeBadge에 반영
  • 2026-06-30 Phase 5 진행:

    • 목적: 랭킹 순위별 UI 조립에 사용할 카드/row/empty state 컴포넌트 추가
    • 확인한 기준:
      • Figma 24:5659, 24:5670 기준 카드/row 구조 확인
      • 기존 V2 추천 탭의 DownsampledKFImage, LinearGradient, SodaSpacing, Color.gray*, .buttonStyle(.plain) 사용 관례 확인
    • 변경 내용:
      • MainHomeRankingCard 추가: 1~10위 공용 카드, 서버 item.rank, 하단 dim gradient, MainHomeRankChangeBadge, creator tap callback 사용
      • MainHomeRankingRow 추가: 11위 이상 row, 서버 item.rank, 프로필 이미지, nickname, MainHomeRankChangeBadge, creator tap callback 사용
      • MainHomeRankingEmptyStateView 추가: I18n.HomeRanking.emptyStateMessage를 검정 배경 위에 표시
      • Phase 5 Preview에 상승, 하락, 변동 없음, 신규 진입, 순위 변화 숨김 상태 추가
    • 검증 메모:
      • 문서의 Phase 5 정적 검색 기준으로 카드/row/empty state 필수 심볼과 Preview 상태를 확인
      • 카드/row 루트 container에 고정 숫자 frame(width:), frame(height:)를 두지 않고, 카드 전체 비율은 aspectRatio 기반으로 구성
  • 2026-06-30 Phase 6 진행:

    • 목적: 랭킹 탭 화면을 ViewModel 상태 기반으로 조립하고, 랭킹 item tap을 기존 creator 상세 진입 callback에 연결
    • 변경 내용:
      • MainHomeRankingView placeholder 제거
      • MainHomeRankingViewModel@StateObject로 연결하고 최초 onAppear에서 hasLoaded == false일 때만 fetchRankings() 호출
      • loading, empty, content 상태 분기 추가
      • content 상태에서 1위 단독, 27위 2열, 810위 3열, 11위 이상 row로 조립
      • 1~10위는 MainHomeRankingCard, 11위 이상은 MainHomeRankingRow를 사용하고 showRankChange, onTapCreator 전달
      • MainHomeView의 랭킹 탭에서 MainHomeRankingView(onTapCreator: onTapCreator)를 호출하도록 변경
    • 검증 메모:
      • Phase 6 정적 검색 기준으로 ViewModel, fetch, 공용 카드/row, empty state, flexible grid, creator tap callback 연결을 확인
      • MainHomeRankingView에서 MainPlaceholderTabView, CapsuleTabBar가 사용되지 않음을 확인
  • 2026-06-30 Phase 7 진행:

    • 목적: 랭킹 탭 신규 파일의 Xcode 프로젝트 포함 여부, 정적 검색 기준, 빌드 가능 여부 확인
    • 확인한 기준:
      • SodaLive.xcodeproj/project.pbxproj에서 MainHomeCreatorRankingResponse, MainHomeRankingApi, MainHomeRankingRepository, MainHomeRankingViewModel, MainHomeRankChangeBadge, MainHomeRankingCard, MainHomeRankingRow, MainHomeRankingEmptyStateViewPBXFileReferencePBXBuildFile 등록 확인
      • 정적 검색으로 endpoint, 순위 변화 아이콘명, empty state 문구, 공용 카드/row 컴포넌트 사용 확인
      • 정적 검색 결과 랭킹 UI 범위에서 CapsuleTabBar, rank 계산용 enumerated() 사용 없음 확인
      • MainHomeRankingCard, MainHomeRankingRow, MainHomeRankingView 코드 리뷰로 루트 레이아웃이 aspectRatio, flexible grid, parent width 기반임을 확인
    • 검증 메모:
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build 실행 결과 ** BUILD SUCCEEDED **
      • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -destination "id=C012DC2D-6F85-4C9F-AF22-A10621385D53" build 실행 결과 ** BUILD SUCCEEDED **
      • 빌드 로그 검색 결과 duplicate build file warning은 없었고, AppIntents metadata 및 Alamofire/BootpayUI dependency scan warning만 확인
      • booted simulator에 SodaLive-dev.app 설치 후 xcrun simctl launch booted kr.co.vividnext.sodalive.debug2 실행 결과 PID 21238로 launch 확인
      • Task 7.4의 Figma 육안 대조, 랭킹 API 1회 호출, item tap 후 상세 진입은 CLI 검증만으로 확정할 수 없어 별도 수동 QA 대상으로 남김