Files

47 KiB
Raw Permalink Blame History

메인 탭 당겨서 새로고침 구현 계획

문서 항목 내용
상태 Phase 4 layout 보존 motion 자동 검증 완료 / 사용자 확인 대기
작성일 2026-08-18
요구사항 기준 docs/20260818_메인_탭_당겨서_새로고침/prd.md
API 기준 기존 홈/콘텐츠/대화 API 계약 재사용, 신규 API 없음
현재 Phase Phase 1~3 완료, Phase 4 Gate 수동 검증 대기
현재 활성 Goal P4-R3

목표

메인 , 콘텐츠, 대화 탭에서 현재 선택된 내부 탭·필터 상태를 유지한 채 사용자가 당겨서 최신 데이터를 다시 조회할 수 있게 한다.

현재 상태

Phase 상태 완료 Task 활성/다음 Goal 차단 또는 남은 조건
1 완료 1/1 없음 없음
2 완료 1/1 없음 없음
3 완료 1/1 없음 없음
4 회귀 수정 대기 4/5 P4-R3 layout 보존 motion 구현·검증
  • 동시에 하나의 미완료 goal만 운용한다.
  • 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. 후속 수정은 회귀 수정 Task와 새 goal ID를 추가한다.
  • 구현 체크박스는 완료 상태를 반영하고, 사용자 수동 검증 체크박스는 실제 확인 전까지 미완료로 둔다.

범위

포함

  • 메인 홈 내부 추천, 랭킹, 팔로잉 현재 선택 상태 새로고침.
  • 메인 콘텐츠 내부 추천, 랭킹, 전체 현재 선택 상태 새로고침.
  • 콘텐츠 랭킹의 현재 AudioRankingType, 콘텐츠 전체의 현재 MainContentAllType, ContentSort, SeriesPublishedDaysOfWeek 유지.
  • 대화 탭의 현재 MainChatFilter 유지와 첫 페이지 재조회.
  • loading, empty, error, content 상태에서 모두 새로고침 가능하도록 상태 화면 구조 점검.
  • 메인 홈·콘텐츠·대화 7개 View의 20개 refresh 가능 상태에서 기본 indicator를 pull-to-refresh.json Lottie로 교체.
  • Lottie를 최대 48×48pt, 원본 비율 유지, 중앙 정렬하고 상하 8pt를 포함한 총 64pt refresh 영역으로 표시.
  • Reduce Motion 활성화 시 animation을 재생하지 않고 첫 번째 유효 frame 1을 표시.

제외

  • 마이 탭 새로고침.
  • 하단 탭 전체 일괄 새로고침.
  • 신규 API endpoint, 신규 dependency, 신규 공통 refresh abstraction.
  • 자동 갱신, polling, push 기반 갱신.
  • LiveNowAllView, ContentDetailView와 그 밖의 legacy 화면 pull-to-refresh 변경.

기술적 제약

  • 기술 스택: Swift, SwiftUI, Combine, Moya, CocoaPods 기반 iOS 앱.
  • 아키텍처: 기존 View -> ViewModel -> Repository -> Api(TargetType) 흐름을 유지한다.
  • UI: SwiftUI .refreshable 또는 동등한 iOS 기본 refresh affordance를 우선 사용한다.
  • 후속 UI: SwiftUI .refreshable은 custom indicator API를 제공하지 않으므로 Phase 4에서는 V2 공용 LottieRefreshableScrollView 한 개로만 대체한다.
  • Animation: Lottie 4.6.1, SodaLive/Resources/pull-to-refresh.json만 사용한다. asset은 150×150, 30fps, frame 0..<45, marker 없음이다.
  • 접근성: accessibilityReduceMotion == true이면 frame 1을 정지 표시하고, Lottie visual은 접근성 트리에서 숨긴다. refresh action 이름은 기존 I18n.LiveNow.refreshButton을 재사용한다.
  • 데이터·보안: 기존 인증 header, token 저장, 언어 header 흐름을 변경하지 않는다.
  • 의존성: Lottie 4.6.1 외 신규 dependency를 추가하지 않고, 기존 RefreshableScrollView package source는 수정하지 않는다.
  • 계약: 제공되지 않은 endpoint, DTO, enum, 오류 status/key를 추정하지 않는다.
  • 구현: 모든 구현 Task는 RED → GREEN → REFACTOR 순서를 체크박스에 명시한다. 현재 저장소는 테스트 번들 타깃이 명확하지 않으므로 테스트 작성이 불가능한 Task는 대체 검증 방법을 사용한다.
  • 검증: xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build와 수동 QA를 필수 Gate로 둔다.

Task TDD 작성 규칙

테스트 작성이 현실적으로 가능한 경우 아래 순서를 따른다.

  • RED: 가장 작은 실패 test를 작성한다.
  • RED 확인: focused test를 실행해 요구 동작이 없어서 발생한 의도한 assertion 실패를 확인한다.
  • GREEN: RED를 통과시키는 최소 구현을 작성한다.
  • GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
  • REFACTOR: 새 동작을 바꾸지 않는 범위에서 이번 Task가 만든 중복만 정리하고, focused test·직접 영향 회귀·lint를 다시 실행해 실제 결과를 Progress에 기록한다.

현재 docs/agent-guides/build-test-verification.md 기준으로 테스트 번들 타깃이 확인되지 않는다. 테스트 타깃이 없는 상태에서는 각 구현 Task에 TDD 예외 사유대체 검증 방법을 기록하고 빌드와 수동 QA로 Gate를 통과시킨다.

Phase 1 홈 탭 새로고침

Phase 결과: 홈 탭의 현재 내부 선택 상태에서 당겨서 새로고침을 실행할 수 있다.

선행조건: PRD REFRESH-001~004, HOME-REFRESH-001~003 확정.

Phase 완료 조건: P1-T1P1-GATE 완료, 검증 기록 누적.

구현 항목

Task 1.1 홈 내부 탭별 refresh 연결

Goal 실행 P1-T1:추천, 랭킹, 팔로잉에서 현재 선택된 내부 탭의 기존 fetch 경로를 당겨서 새로고침에 연결한다.

  • 시작 조건: PRD HOME-REFRESH-001~003, REFRESH-003 확정.
  • 완료 증거: 홈 내부 3개 탭의 새로고침 동작과 빈/오류 상태 재시도 확인.
  • 범위 밖: 콘텐츠/대화 탭 변경, 공통 refresh abstraction 생성.
  • TDD 예외 사유: 현재 테스트 번들 타깃이 확인되지 않는 SwiftUI 화면 동작 변경이다.
  • 대체 검증 방법: 구현 diff 검토, xcodebuild 빌드, 시뮬레이터 또는 기기 수동 QA.

Files:

  • Modify: SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationViewModel.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingViewModel.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingViewModel.swift

Interfaces:

  • Consumes: 기존 fetchRecommendations(), fetchRankings(), fetchFollowing() 메서드.

  • Produces: 각 홈 내부 View의 pull-to-refresh 사용자 동작.

  • 상태 확인: 각 View의 현재 loading, empty, error, content 분기를 확인하고 새로고침 가능한 ScrollView 배치가 필요한 지점을 표시한다.

  • GREEN: 최소 구현으로 각 View에 새로고침 동작을 연결한다. 필요 시 ViewModel에 refresh()처럼 기존 fetch를 감싸는 얇은 메서드만 추가한다.

  • GREEN 확인:추천, 랭킹, 팔로잉에서 당겨서 새로고침 후 선택된 내부 탭이 유지되는지 수동 확인한다.

  • REFACTOR: 이번 Task에서 만든 중복만 정리하고 신규 공통 abstraction은 만들지 않는다.

완료 조건

  • P1-T1의 체크박스와 완료 증거가 모두 충족됐다.
  • 홈 내부 탭 3개가 PRD 요구사항과 추적된다.
  • 알려진 문서와 구현의 차이가 없다.

검증 방법

Phase 1 Gate

Goal 실행 P1-GATE: 홈 탭 새로고침 사용자 흐름과 공통 품질 기준을 최종 판정한다.

  • 시작 조건: P1-T1 완료.
  • 완료 증거: 아래 자동·수동 검증 통과와 Progress 기록.
  • 범위 밖: 콘텐츠/대화 탭 구현.
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build

Expected: exit code 0. 홈 탭 진입 후 추천, 랭킹, 팔로잉 각각에서 당겨서 새로고침이 동작하고 내부 선택 상태가 유지된다.

수동 검증:

  • 추천 콘텐츠 상태에서 새로고침한다.
  • 랭킹 콘텐츠 상태에서 새로고침한다.
  • 팔로잉 콘텐츠 또는 로그인 필요/빈 상태에서 새로고침한다.
  • 새로고침 실패 시 기존 오류 표시 정책을 유지한다.

Phase 2 콘텐츠 탭 새로고침

Phase 결과: 콘텐츠 탭의 현재 내부 선택 상태와 필터 조건에서 당겨서 새로고침을 실행할 수 있다.

선행조건: P1-GATE 완료.

Phase 완료 조건: P2-T1P2-GATE 완료, 검증 기록 누적.

구현 항목

Task 2.1 콘텐츠 내부 탭별 refresh 연결

Goal 실행 P2-T1: 콘텐츠 추천, 랭킹, 전체에서 현재 선택된 내부 탭과 필터 상태의 기존 fetch 경로를 당겨서 새로고침에 연결한다.

  • 시작 조건: PRD CONTENT-REFRESH-001~003, REFRESH-003~004 확정.
  • 완료 증거: 콘텐츠 내부 3개 탭과 전체 하위 필터 상태의 새로고침 확인.
  • 범위 밖: 홈/대화 탭 변경, 전체 데이터 일괄 refresh.
  • TDD 예외 사유: 현재 테스트 번들 타깃이 확인되지 않는 SwiftUI 화면 동작 변경이다.
  • 대체 검증 방법: 구현 diff 검토, xcodebuild 빌드, 시뮬레이터 또는 기기 수동 QA.

Files:

  • Modify: SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationViewModel.swift
  • Modify: SodaLive/Sources/V2/Main/Content/Ranking/MainContentRankingView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/Ranking/MainContentRankingViewModel.swift
  • Modify: SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/All/MainContentAllViewModel.swift

Interfaces:

  • Consumes: 기존 fetchRecommendations(), fetchRankings(), fetchFirstPage() 메서드와 선택 상태 selectedType, selectedSort, selectedDayOfWeek, selectedType.

  • Produces: 콘텐츠 내부 View의 pull-to-refresh 사용자 동작.

  • 상태 확인: 콘텐츠 추천, 랭킹, 전체의 loading, empty, error, content 분기를 확인한다.

  • GREEN: 최소 구현으로 각 View에 새로고침 동작을 연결한다. 콘텐츠 전체는 현재 type/sort/dayOfWeek를 유지하고 첫 페이지를 다시 조회한다.

  • GREEN 확인: 콘텐츠 추천, 랭킹, 전체에서 당겨서 새로고침 후 선택된 내부 탭과 필터가 유지되는지 확인한다.

  • REFACTOR: 새로 만든 중복만 정리하고 신규 공통 abstraction은 만들지 않는다.

완료 조건

  • P2-T1의 체크박스와 완료 증거가 모두 충족됐다.
  • 콘텐츠 내부 탭 3개와 전체 하위 필터 상태가 PRD 요구사항과 추적된다.
  • 알려진 문서와 구현의 차이가 없다.

검증 방법

Phase 2 Gate

Goal 실행 P2-GATE: 콘텐츠 탭 새로고침 사용자 흐름과 공통 품질 기준을 최종 판정한다.

  • 시작 조건: P2-T1 완료.
  • 완료 증거: 아래 자동·수동 검증 통과와 Progress 기록.
  • 범위 밖: 대화 탭 구현.
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build

Expected: exit code 0. 콘텐츠 탭 진입 후 추천, 랭킹, 전체 각각에서 당겨서 새로고침이 동작하고 현재 선택 상태가 유지된다.

수동 검증:

  • 콘텐츠 추천에서 새로고침한다.
  • 콘텐츠 랭킹에서 랭킹 타입을 바꾼 뒤 새로고침하고 선택 타입이 유지되는지 확인한다.
  • 콘텐츠 전체 > 오디오에서 sort를 바꾼 뒤 새로고침하고 type/sort가 유지되는지 확인한다.
  • 콘텐츠 전체 > 시리즈에서 요일과 sort를 바꾼 뒤 새로고침하고 type/dayOfWeek/sort가 유지되는지 확인한다.
  • 빈/오류 상태에서도 새로고침을 재시도할 수 있는지 확인한다.

Phase 3 대화 탭 새로고침

Phase 결과: 대화 탭의 현재 필터에서 당겨서 첫 페이지를 다시 조회할 수 있다.

선행조건: P2-GATE 완료.

Phase 완료 조건: P3-T1P3-GATE 완료, 검증 기록 누적.

구현 항목

Task 3.1 대화 필터별 refresh 연결

Goal 실행 P3-T1: 대화 전체, AI, DM 현재 필터의 기존 첫 페이지 fetch 경로를 당겨서 새로고침에 연결한다.

  • 시작 조건: PRD CHAT-REFRESH-001~002, REFRESH-003~004 확정.
  • 완료 증거: 대화 3개 필터의 새로고침과 pagination 초기화 확인.
  • 범위 밖: 채팅방 상세 라우팅 변경, 신규 DM 기능.
  • TDD 예외 사유: 현재 테스트 번들 타깃이 확인되지 않는 SwiftUI 화면 동작 변경이다.
  • 대체 검증 방법: 구현 diff 검토, xcodebuild 빌드, 시뮬레이터 또는 기기 수동 QA.

Files:

  • Modify: SodaLive/Sources/V2/Main/Chat/MainChatView.swift
  • Modify: SodaLive/Sources/V2/Main/Chat/MainChatViewModel.swift

Interfaces:

  • Consumes: 기존 selectedFilter, fetchFirstPage(filter:), fetchNextPageIfNeeded(currentItem:).

  • Produces: 대화 View의 pull-to-refresh 사용자 동작.

  • 상태 확인: 대화 탭의 loading, empty, error, content 분기와 ScrollView 배치를 확인한다.

  • GREEN: 최소 구현으로 현재 selectedFilter를 유지한 첫 페이지 새로고침을 연결한다.

  • GREEN 확인: 전체, AI, DM 각 필터에서 새로고침 후 필터가 유지되고 rooms, hasMore, nextCursor가 첫 페이지 응답 기준으로 갱신되는지 확인한다.

  • REFACTOR: 새로 만든 중복만 정리하고 신규 공통 abstraction은 만들지 않는다.

완료 조건

  • P3-T1의 체크박스와 완료 증거가 모두 충족됐다.
  • 대화 필터 3개와 pagination 초기화가 PRD 요구사항과 추적된다.
  • 알려진 문서와 구현의 차이가 없다.

검증 방법

Phase 3 Gate

Goal 실행 P3-GATE: 대화 탭 새로고침 사용자 흐름과 최종 회귀 범위를 판정한다.

  • 시작 조건: P3-T1 완료.
  • 완료 증거: 아래 자동·수동 검증 통과와 Progress 기록.
  • 범위 밖: 실패와 무관한 다음 기능 구현.
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build

Expected: exit code 0. 대화 전체, AI, DM 필터에서 당겨서 새로고침이 동작하고 필터와 pagination 상태가 올바르게 유지·초기화된다.

수동 검증:

  • 대화 전체에서 새로고침한다.
  • 대화 AI에서 새로고침하고 필터가 유지되는지 확인한다.
  • 대화 DM에서 새로고침하고 필터가 유지되는지 확인한다.
  • 빈/오류 상태에서도 새로고침을 재시도할 수 있는지 확인한다.
  • 새로고침 후 스크롤 하단에서 다음 페이지 조회가 이어지는지 확인한다.

Phase 4 Lottie 새로고침 표시

Phase 결과: 메인 홈·콘텐츠·대화 7개 View의 모든 refresh 가능 상태에서 기본 indicator 대신 지정된 Lottie가 표시되고 기존 새로고침 동작은 유지된다.

선행조건: Phase 1~3 Gate 완료, PRD REFRESH-ANIMATION-001~005DEC-004~011 확정, 사용자 코드 구현 승인.

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

구현 항목

Task 4.1 공용 Lottie refresh component

Goal 실행 P4-T1: Lottie 표시와 pull-to-refresh 상태를 한 곳에서 관리하는 V2 공용 vertical scroll component를 만든다.

  • 시작 조건: SodaLive/Resources/pull-to-refresh.json과 Lottie 4.6.1SodaLive, SodaLive-dev target에 포함되어 있다.
  • 완료 증거: component source 2개가 두 target에서 빌드되고, pull offset·단일 refresh·완료 초기화·Reduce Motion 상태가 아래 contract를 따른다.
  • 범위 밖: 메인 7개 View 연결, legacy RefreshableScrollView 변경, pull progress와 animation progress 연동.
  • TDD 예외 사유: 현재 저장소에 앱 test bundle target이 없고, ScrollView bounce와 Lottie playback은 runtime UI 동작이다.
  • 대체 검증 방법: asset metadata 검사, 두 scheme Debug 빌드, Phase 4 수동 상태 전이 확인.

Files:

  • Create: SodaLive/Sources/V2/Component/Refresh/LottieRefreshIndicator.swift
  • Create: SodaLive/Sources/V2/Component/Refresh/LottieRefreshableScrollView.swift
  • Modify: SodaLive.xcodeproj/project.pbxproj — 두 source를 SodaLive, SodaLive-dev Sources build phase에 추가
  • Verify: SodaLive/Resources/pull-to-refresh.json

Interfaces:

struct LottieRefreshIndicator: View {
    let isAnimating: Bool
}

struct LottieRefreshableScrollView<Content: View>: View {
    init(
        action: @escaping () async -> Void,
        @ViewBuilder content: @escaping () -> Content
    )
}
  • Consumes: Lottie LottieView, pull-to-refresh.json, SodaSpacing.s8, SodaSpacing.s48, EnvironmentValues.accessibilityReduceMotion, I18n.LiveNow.refreshButton.

  • Produces: indicator가 없는 vertical ScrollView, 64pt trigger/표시 영역, 중복 실행을 막는 단일 async refresh action, accessibility refresh action.

  • Asset 확인: JSON이 150×150, 30fps, frame 0..<45이고 frame 0은 비어 있으며 frame 1부터 stroke가 표시되는지 확인한다.

  • GREEN: LottieRefreshIndicator는 일반 설정에서 영역 노출 중 frame 0..<45를 반복 재생하고, Reduce Motion에서는 frame 1에 정지한다. 크기는 aspect fit 최대 48×48pt, 중앙 정렬, Lottie 자체는 accessibility tree에서 숨긴다.

  • GREEN: LottieRefreshableScrollView는 양수 pull offset에서 indicator를 보이고, 64pt 기준으로 action을 한 번만 실행하며, action이 끝날 때까지 영역을 유지한 뒤 offset·animation을 처음 상태로 되돌린다.

  • GREEN: 기준 거리 미만에서 손을 놓으면 API를 호출하지 않고 indicator를 숨겨 frame 0으로 초기화하며, refresh 중 재당김은 두 번째 action을 만들지 않는다.

  • GREEN 확인: 두 scheme을 빌드해 Lottie API, async closure, project source membership 오류가 없음을 확인한다.

  • REFACTOR: offset 측정용 PreferenceKey와 refresh 상태는 LottieRefreshableScrollView.swift 내부 private 구현으로 두고 추가 protocol·ViewModel·config type을 만들지 않는다.

Task 4.2 메인 7개 View 연결

Goal 실행 P4-T2: 기존 7개 View의 20개 .refreshable 경로를 공용 Lottie refresh component로 교체한다.

  • 시작 조건: P4-T1 완료.
  • 완료 증거: 대상 파일의 .refreshable 0건, 기존 refresh() 호출·상태 분기·레이아웃 유지, 두 scheme 빌드와 수동 QA 통과.
  • 범위 밖: 7개 ViewModel 변경, LiveNowAllView, ContentDetailView, 최초 loading용 본문 ProgressView 디자인 변경.
  • TDD 예외 사유: 현재 앱 test bundle target이 없는 SwiftUI 화면 연결 변경이다.
  • 대체 검증 방법: 대상 modifier 정적 검색, ViewModel diff 부재 확인, 두 scheme Debug 빌드, 화면별 수동 QA.

Files:

  • Modify: SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift
  • Modify: SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/Ranking/MainContentRankingView.swift
  • Modify: SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift
  • Modify: SodaLive/Sources/V2/Main/Chat/MainChatView.swift
  • Test: 별도 test file 없음 — Phase 4 Gate의 정적 검사·빌드·수동 검증 사용

Interfaces:

  • Consumes: LottieRefreshableScrollView(action:content:)와 기존 7개 ViewModel의 refresh() async.

  • Produces: content·loading·empty/error 분기 전체에서 동일한 Lottie pull-to-refresh 사용자 동작.

  • 상태 확인: 현재 7개 View의 .refreshable 20곳과 각 content·loading·empty/error 분기를 대조한다.

  • GREEN:ScrollView.refreshable 조합을 LottieRefreshableScrollView로 교체하고 action에는 기존 await viewModel.refresh()만 연결한다.

  • GREEN: 기존 frame(minHeight:), background, padding, pagination onAppear와 최초 loading ProgressView를 그대로 유지한다.

  • GREEN 확인: 대상 7개 파일의 .refreshable이 0건이고 LottieRefreshableScrollView 적용 수가 기존 상태 분기 수와 일치하는지 정적 검사한다.

  • GREEN 확인: 홈·콘텐츠·대화의 선택 탭·필터·pagination·실패 재시도 동작이 Phase 1~3 결과와 동일한지 수동 확인한다.

  • REFACTOR: 7개 ViewModel, legacy pull-to-refresh 화면과 요청 범위 밖 UI에 diff가 없는지 확인한다.

Task 4.3 pull trigger 회귀 수정

Goal 실행 P4-R1: Phase 4 첫 수동 검증에서 Lottie 표시와 refresh가 모두 실행되지 않은 회귀를 수정한다.

  • 원인: viewport와 같은 짧은 content의 bounce 부재, 신뢰할 수 없는 DragGesture.onEnded, 현재 hierarchy에서 양수 pull offset을 만들지 못한 named coordinate 측정이 겹쳤다.

  • TDD 예외 사유: 앱 test bundle target이 없고 실제 ScrollView rubber-band와 gesture arbitration이 필요한 runtime UI 회귀다.

  • 대체 검증 방법: 사용자 실패 재현, Apple·OSS·설치 package source 비교, 정적 검사, 두 scheme Debug 빌드, 사용자 재확인.

  • Files:

    • Modify: SodaLive/Sources/V2/Component/Refresh/LottieRefreshableScrollView.swift
  • RED: 사용자가 당김 시 Lottie 표시와 refresh가 모두 실행되지 않음을 확인했다.

  • 원인 확인: 짧은 content의 bounce 부재, DragGesture.onEnded 트리거와 offset 측정을 기존 동작 package·Apple·OSS 패턴과 비교했다.

  • GREEN: vertical bounce를 항상 허용하고 global moving-minus-fixed offset이 64pt를 넘겼다가 돌아오는 전이에서 action을 한 번만 실행하도록 수정했다.

  • GREEN 확인: named coordinate·DragGesture 0건, global moving/fixed 측정과 bounce·armed 전이 존재, 두 scheme Debug build 성공을 확인했다.

  • 수동 확인: 기존 실패 화면에서 Lottie가 표시되고 refresh action이 한 번 실행되는지 사용자가 재확인한다.

Task 4.4 refresh motion 회귀 수정 (롤백)

Goal 실행 P4-R2: 기능 복구 후 refresh 시작·완료 시 발생하는 UI 덜컹임을 제거한다.

  • 원인: ScrollView bounce-back 중 refresh row 높이 0→64pt와 indicator offset -64→0이 동시에 즉시 변경되고, 완료 시 row가 64→0pt로 즉시 축소됐다.

  • TDD 예외 사유: 앱 test bundle target이 없고 실제 ScrollView rubber-band와 layout handoff가 필요한 runtime motion 회귀다.

  • 대체 검증 방법: 사용자 체감 RED, 기존 package freeze 패턴 비교, 정적 검사, 두 scheme Debug 빌드, 사용자 재확인.

  • Files:

    • Modify: SodaLive/Sources/V2/Component/Refresh/LottieRefreshableScrollView.swift
  • RED: 사용자가 Lottie 표시와 refresh는 동작하지만 진행이 덜컹거림을 확인했다.

  • 원인 확인: 시작·완료의 구조적 row 삽입/축소와 indicator 위치 전환이 bounce-back과 충돌함을 확인했다.

  • GREEN: 구조적 refresh row를 제거하고 content·indicator가 같은 frozen alignment handoff를 사용하도록 수정했다.

  • GREEN 확인: frozen alignment, Reduce Motion 분기, 완료 animation과 두 scheme Debug build 성공을 확인했다.

  • 수동 확인: motion은 부드러워졌지만 기존 UI 간격이 변경되는 회귀를 사용자가 확인했다.

  • ROLLBACK: 사용자 결정에 따라 frozen motion handoff와 후속 layout 보정을 모두 제거하고 P4-R1 기능 복구 상태로 되돌렸다.

Task 4.5 layout 보존 refresh motion 수정

Goal 실행 P4-R3: 기존 VStack layout과 UI 간격을 유지하면서 refresh 시작·완료의 목록 위치 전환을 부드럽게 한다.

  • 원인: isRefreshing 전환 때 refresh 영역 높이와 indicator offset이 animation 없이 즉시 0↔64pt로 변경되어 목록이 위아래로 순간 이동한다.

  • 선택 설계: 현재 VStackrefreshRegion 구조는 유지하고 isRefreshing 시작·완료 상태만 동일한 .easeOut(duration: 0.2) transaction으로 처리한다.

  • 대안 제외: 고정 64pt 영역은 scroll layout을 변경하고, UIRefreshControl 재작성은 현재 회귀에 비해 범위가 크므로 제외한다.

  • 접근성: Reduce Motion에서는 animation 없이 즉시 상태를 전환한다.

  • TDD 예외 사유: 앱 test bundle target이 없고 실제 ScrollView rubber-band와 layout animation이 필요한 runtime motion 회귀다.

  • 대체 검증 방법: 사용자 체감 RED, 구조·상태 전이 정적 검사, 두 scheme Debug 빌드, 사용자 재확인.

  • Files:

    • Modify: SodaLive/Sources/V2/Component/Refresh/LottieRefreshableScrollView.swift
  • RED: Lottie와 refresh는 동작하지만 시작·완료 때 목록이 위아래로 끊겨 이동함을 사용자가 확인했다.

  • 설계 승인: 기존 구조의 높이·offset 전환만 animate하는 최소안을 사용자가 선택했다.

  • GREEN: refresh 시작·완료를 같은 Reduce Motion-aware animation transaction으로 처리한다.

  • GREEN 확인: 기존 VStack, global offset 측정, bounce, armed trigger와 두 scheme build 성공을 확인한다.

  • 수동 확인: 기존 UI 간격이 유지되고 시작·완료 목록 이동이 부드러운지 사용자가 확인한다.

완료 조건

  • P4-T1, P4-T2의 체크박스와 완료 증거가 모두 충족됐다.
  • REFRESH-ANIMATION-001~005가 구현 또는 명시적 제외로 추적된다.
  • Lottie와 기본 indicator가 동시에 보이는 대상 상태가 없다.
  • Phase 1~3에서 검증한 데이터 refresh 동작에 회귀가 없다.

검증 방법

Phase 4 Gate

Goal 실행 P4-GATE: Lottie 표시, 접근성 설정, 기존 데이터 refresh와 범위 제외를 최종 판정한다.

  • 시작 조건: P4-T1, P4-T2 완료.
  • 완료 증거: 아래 자동·수동 검증 통과와 Progress 기록.
  • 범위 밖: Gate 실패와 무관한 UI 변경, legacy refresh 화면 변경, test 삭제·완화.
jq -e '.w == 150 and .h == 150 and .fr == 30 and .ip == 0 and .op == 45' SodaLive/Resources/pull-to-refresh.json
! rg -n '\.refreshable' SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift SodaLive/Sources/V2/Main/Home/Ranking/MainHomeRankingView.swift SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift SodaLive/Sources/V2/Main/Content/Recommendation/MainContentRecommendationView.swift SodaLive/Sources/V2/Main/Content/Ranking/MainContentRankingView.swift SodaLive/Sources/V2/Main/Content/All/MainContentAllView.swift SodaLive/Sources/V2/Main/Chat/MainChatView.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -sdk iphonesimulator -destination "generic/platform=iOS Simulator" build CODE_SIGNING_ALLOWED=NO
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug -sdk iphonesimulator -destination "generic/platform=iOS Simulator" build CODE_SIGNING_ALLOWED=NO
git diff --check

Expected: 모든 명령 exit code 0. 대상 7개 파일에 .refreshable이 없고 두 scheme이 빌드되며, asset metadata와 target membership이 일치한다.

수동 검증:

  • 기준 거리 미만으로 당기는 동안 Lottie가 반복 재생되고 손을 놓으면 API 호출 없이 사라진 뒤 다음 당김에서 처음부터 재생된다.
  • 64pt 기준을 넘겨 새로고침하면 Lottie가 API 완료까지 유지되고 기본 indicator는 보이지 않으며 요청은 한 번만 실행된다.
  • Reduce Motion을 켜면 frame 1이 정지 표시되고, accessibility refresh action으로도 동일한 기존 refresh()가 한 번 실행된다.
  • 홈 3개·콘텐츠 3개·대화 View의 content·loading·empty/error 상태에서 최대 48×48pt Lottie와 기존 데이터 갱신 동작을 확인한다.
  • iPhone과 iPad에서 영역이 총 64pt이고 content가 겹치거나 잘리지 않으며, LiveNowAllViewContentDetailView는 기존 표시를 유지한다.

실행 순서와 의존성

순서 Goal 선행조건 병행 가능 차단 시 다음 행동
1 P1-T1 PRD 확정 아니요 홈 탭 상태 분기 재확인
2 P1-GATE P1-T1 아니요 실패 소유 Task의 회귀 수정 goal 생성
3 P2-T1 P1-GATE 아니요 콘텐츠 탭 상태 분기 재확인
4 P2-GATE P2-T1 아니요 실패 소유 Task의 회귀 수정 goal 생성
5 P3-T1 P2-GATE 아니요 대화 탭 상태 분기 재확인
6 P3-GATE P3-T1 아니요 실패 소유 Task의 회귀 수정 goal 생성
7 P4-T1 P3-GATE, 구현 승인 아니요 asset·Lottie target membership 재확인
8 P4-T2 P4-T1 아니요 대상 View의 상태 분기와 wrapper contract 재확인
9 P4-GATE P4-T2 아니요 실패 소유 Task의 회귀 수정 goal 생성
P1-T1 -> P1-GATE -> P2-T1 -> P2-GATE -> P3-T1 -> P3-GATE -> P4-T1 -> P4-T2 -> P4-GATE

변경 금지 항목

  • 확정된 요구사항을 근거 없이 변경하지 않는다.
  • 기존 완료 체크박스와 Progress·Decision Log·검증 기록을 삭제하거나 덮어쓰지 않는다.
  • 신규 endpoint, DTO, dependency와 P4-T1의 component 2개 외 추가 refresh abstraction을 만들지 않는다.
  • 요청 범위 밖의 리팩터링과 UI 재설계를 하지 않는다.
  • test를 삭제·skip·완화하거나 타입 오류를 우회해 Gate를 통과시키지 않는다.
  • JWT, password, signed URL, 개인 메시지 본문 같은 민감정보를 log·fixture·문서에 기록하지 않는다.

의사결정 및 중단 규칙

  • PRD와 구현 계획이 충돌하면 PRD를 우선하고 Decision Log에 정정 기록을 남긴다.
  • 구현 범위가 바뀌면 prd.md를 먼저 보강하고 plan-task.md 체크리스트를 업데이트한 뒤 코드를 수정한다.
  • 안전한 최소 기본값이 문서에 있으면 그 값만 구현한다. 안전하게 구현할 수 없으면 추정하지 않는다.
  • 체크박스 일부, 빌드 일부 또는 코드 작성만 끝난 상태에서는 goal을 complete로 갱신하지 않는다.
  • 완료 증거와 Progress 기록까지 충족한 뒤에만 goal을 complete로 갱신한다.

Progress

기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다.

문서 작성 1차 실행 — 2026-08-18

  • 상태: 완료
  • 무엇을: prd.mdplan-task.md 신규 작성.
  • 왜: 코드 구현 전 요구사항과 구현 계획을 확정하기 위해 작성.
  • 어떻게:
    • docs/sample/sample-prd.md 확인 — 샘플 구조 확인.
    • docs/sample/sample-plan-task.md 확인 — 계획 문서 구조 확인.
    • docs/agent-guides/documentation-policy.md 확인 — 신규 문서 경로 규칙 확인.
    • 관련 V2 메인 홈/콘텐츠/대화 View와 ViewModel 확인 — 기존 fetch 경로와 선택 상태 확인.
  • 남은 항목: 코드 구현 전체.
  • 다음 행동: 사용자가 구현을 승인하면 P1-T1부터 진행.

P1-T1~P3-T1 1차 실행 — 2026-08-18

  • 상태: 구현 완료 / 사용자 수동 검증 대기
  • 무엇을: 홈, 콘텐츠, 대화 탭의 현재 선택 상태별 당겨서 새로고침과 Combine 완료 대기 연결.
  • 왜: 사용자가 현재 화면을 벗어나지 않고 최신 첫 페이지 또는 단일 조회 결과를 받을 수 있게 하기 위해 구현.
  • 어떻게:
    • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -sdk iphonesimulator -destination "generic/platform=iOS Simulator" build CODE_SIGNING_ALLOWED=NO — 성공, exit code 0, BUILD SUCCEEDED.
    • git diff --check — 성공, 오류 없음.
    • 코드 검토 — 홈/콘텐츠/대화 현재 선택 상태 유지, 빈/오류 상태 ScrollView 제공, 대화/콘텐츠 전체 첫 페이지 응답 기준 pagination 교체 확인.
    • visual-qa — 사용자 요청에 따라 미실행.
  • 남은 항목: 아래 사용자 수동 테스트 목록과 각 Phase Gate 체크박스 확인.
  • 다음 행동: 사용자가 수동 테스트를 수행하고 성공/실패 결과를 전달한다.

P1-T1~P3-T1 리뷰 보정 — 2026-08-18

  • 상태: 구현 완료 / 사용자 수동 검증 대기
  • 무엇을: Combine 취소 시 refresh 대기 종료, 최초 loading 상태 새로고침, 콘텐츠 전체 최초 요청 중복 방지 보정.
  • 왜: checked continuation은 Combine 구독 취소 시 completion을 받지 못해 대기할 수 있고, 기존 최초 loading 화면에는 refresh 가능한 ScrollView가 없었기 때문이다.
  • 어떻게:
    • 7개 ViewModel — checked continuation 대신 $isLoading.values를 기다리도록 변경하고 이미 로딩 중이면 중복 요청 없이 기존 요청 완료를 기다림.
    • 7개 View — 최초 loading을 full-height ScrollView로 구성해 기본 .refreshable 동작 제공.
    • MainContentAllViewModel.fetchFirstPageIfNeeded()isLoading == false guard를 추가해 탭 재진입 중 page 0 중복 요청 차단.
    • xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -sdk iphonesimulator -destination "generic/platform=iOS Simulator" build CODE_SIGNING_ALLOWED=NO — 성공, exit code 0, BUILD SUCCEEDED.
    • git diff --check — 성공, 오류 없음.
    • Oracle 최종 코드 리뷰 — criterion-blocking finding 없음, 무조건 승인.
  • 남은 항목: 아래 사용자 수동 테스트 목록과 각 Phase Gate 체크박스 확인.
  • 다음 행동: 사용자가 수동 테스트를 수행하고 성공/실패 결과를 전달한다.

P1-GATE~P3-GATE 사용자 수동 검증 — 2026-08-18

  • 상태: 완료
  • 무엇을: 홈, 콘텐츠, 대화 탭의 당겨서 새로고침과 선택 상태·pagination·오류 재시도 회귀를 사용자 수동 확인.
  • 왜: 실제 로그인 데이터와 네트워크 환경에서 사용자 동작 기준을 최종 판정하기 위해 수행.
  • 어떻게:
    • 사용자 수동 테스트 목록 전체 — 성공 확인.
    • PRD 기능·UI/UX 수용 기준 — 충족 확인.
  • 남은 항목: 없음.
  • 다음 행동: 없음.

Phase 4 요구사항 인터뷰와 계획 작성 — 2026-08-25

  • 상태: 계획 완료 / 구현 미착수
  • 무엇을: Lottie playback·크기·Reduce Motion·적용 범위를 확정하고 P4-T1, P4-T2, P4-GATE를 작성했다.
  • 왜: 기존 7개 View·20개 상태에 같은 표시 규칙을 적용하되 legacy 화면과 데이터 refresh 로직을 변경하지 않기 위해 작성했다.
  • 어떻게:
    • rg.refreshableRefreshableScrollView 실제 사용 화면 확인 — 메인 7개 View와 legacy 2개 View 확인.
    • jqpull-to-refresh.json 확인 — 150×150, 30fps, frame 0..<45, frame 0 비어 있음 확인.
    • Xcode project와 Package.resolved 확인 — Lottie 4.6.1 및 asset이 SodaLive, SodaLive-dev 두 target에 포함됨 확인.
    • Lottie·Apple 공식 문서와 현재 RefreshableScrollView 1.1.1 source 확인 — 공용 V2 component 계획 근거 확인.
    • git diff --check -- docs/20260818_메인_탭_당겨서_새로고침/prd.md docs/20260818_메인_탭_당겨서_새로고침/plan-task.md — 성공, exit code 0.
    • placeholder·미결정 검색 — 남은 placeholder와 미결 요구사항 없음.
    • git diff --name-only -- SodaLive/Sources — 출력 없음, 앱 source 코드 미수정 확인.
  • 남은 항목: P4-T1, P4-T2, P4-GATE 전체. 이번 요청에서는 코드 구현하지 않음.
  • 다음 행동: 사용자가 별도로 코드 구현을 요청하면 P4-T1부터 시작.

P4-T1~P4-T2 구현과 자동 Gate — 2026-08-25

  • 상태: 구현·자동 검증 완료 / 사용자 수동 검증 대기
  • 무엇을: V2 공용 Lottie refresh component 2개를 만들고 메인 7개 View의 .refreshable 20곳을 교체했다.
  • 왜: 기존 데이터 새로고침 동작을 유지하면서 지정된 Lottie 표시·접근성·중복 요청 방지 규칙을 한 곳에서 적용하기 위해 구현했다.
  • 어떻게:
    • jq asset metadata 검사 — 150×150, 30fps, frame 0..<45 확인, exit code 0.
    • 정적 검사 — 대상 7개 View의 .refreshable 0건, LottieRefreshableScrollView 20건(2/3/3/3/3/3/3) 확인.
    • 범위 검사 — 7개 ViewModel, LiveNowAllView, ContentDetailView diff 없음 확인.
    • xcodebuild SodaLive Debug simulator build — 성공, exit code 0, BUILD SUCCEEDED.
    • xcodebuild SodaLive-dev Debug simulator build — 성공, exit code 0, BUILD SUCCEEDED.
    • git diff --check — 성공, 오류 없음.
    • P4-T1·P4-T2 spec review와 code-quality review — 차단 finding 없이 승인.
    • visual-qa — 사용자 요청에 따라 미실행.
  • 남은 항목: Phase 4 수동 검증 5개와 P4-GATE 최종 완료 처리.
  • 다음 행동: 사용자가 아래 Phase 4 Lottie 후속 검증 5개를 확인하고 결과를 전달한다.

P4-R1 pull trigger 회귀 수정 — 2026-08-25

  • 상태: 수정·자동 검증 완료 / 사용자 재확인 대기
  • 무엇을: LottieRefreshableScrollView의 짧은 content bounce와 refresh release 판정을 수정했다.
  • 왜: Phase 4 첫 수동 검증에서 Lottie 표시와 데이터 refresh가 모두 실행되지 않았기 때문이다.
  • 어떻게:
    • 사용자 RED — 당김 시 Lottie 표시 없음, refresh 실행 없음 확인.
    • 원인 조사 — ScrollView content-size 기반 bounce와 custom DragGesture.onEnded 취소 가능성을 Apple·OSS·기존 package source로 교차 확인.
    • 1차 수정 — .scrollBounceBehavior(.always, axes: .vertical) 추가, DragGesture 제거, preference armed→release 전이 적용. 사용자 재확인에서 동일 증상 확인.
    • 2차 수정 — 기존 동작 package와 같은 global moving-minus-fixed frame 차이로 pull offset 측정 교체.
    • 정적 검사 — named coordinate·DragGesture 0건, global moving/fixed 측정과 bounce·armed 상태 전이 확인.
    • xcodebuild SodaLive, SodaLive-dev Debug simulator build — 성공, BUILD SUCCEEDED.
  • 남은 항목: 기존 실패 화면 사용자 재확인과 Phase 4 수동 검증 5개.
  • 다음 행동: 사용자가 기존 실패 화면에서 다시 당겨 Lottie 표시와 refresh 실행 여부를 확인한다.

P4-R2 motion 보정과 롤백 — 2026-08-25

  • 상태: 사용자 결정으로 롤백
  • 무엇을: refresh 시작·완료 덜컹임을 줄이기 위해 frozen alignment handoff를 적용했으나 기존 UI 간격이 변경되어 해당 보정과 후속 layout 수정을 모두 제거했다.
  • 왜: 사용자 우선순위에 따라 기존 UI layout을 motion 개선보다 우선 보존하기 위해 롤백했다.
  • 어떻게:
    • motion 보정 후 새로고침 진행이 부드러워짐을 사용자 확인.
    • 기존 UI 간격 변경 회귀를 사용자 확인.
    • LottieRefreshableScrollView를 global moving-minus-fixed offset 기반 P4-R1 기능 복구 상태로 복원.
  • 남은 항목: Phase 4 수동 검증 잔여 항목.
  • 다음 행동: 기존 UI layout과 Lottie refresh 기능 유지 여부를 확인한다.

P4-R3 layout 보존 motion 수정 — 2026-08-26

  • 상태: 구현·자동 검증 완료 / 사용자 확인 대기
  • 무엇을: 기존 VStack과 refresh hierarchy를 유지한 채 시작·완료의 isRefreshing 상태 전환만 .easeOut(duration: 0.2)로 처리했다.
  • 왜: Lottie와 refresh 기능은 정상이지만 refresh 영역 높이가 즉시 0↔64pt로 바뀌어 목록이 위아래로 순간 이동했기 때문이다.
  • 어떻게:
    • 사용자 RED — Lottie와 refresh는 동작하지만 목록 위치가 시작·완료 때 끊겨 이동함을 확인.
    • 정적 검사 — VStack(spacing: 0), global moving-minus-fixed 측정, bounce, armed trigger, refresh 영역 높이·offset 식 보존 확인.
    • 정적 검사 — Reduce Motion-aware withAnimation 2건, ZStack, alignmentGuide, UIRefreshControl, frozen 0건 확인.
    • git diff --check — 성공, 오류 없음.
    • xcodebuild SodaLive, SodaLive-dev Debug simulator build — 각각 성공, BUILD SUCCEEDED 2건 확인.
    • spec compliance review와 code-quality review — 승인, runtime motion과 layout은 사용자 확인 대상으로 유지.
  • TDD 예외: 앱 test bundle target이 없고 ScrollView rubber-band와 layout animation의 runtime 상호작용이 필요한 UI 회귀이므로 사용자 RED와 정적 검사·두 scheme build를 대체 증거로 사용했다.
  • 남은 항목: 기존 UI 간격 유지, 시작·완료 목록 이동, Reduce Motion, 단일 refresh 실행 사용자 확인.
  • 다음 행동: 사용자가 기존 화면에서 refresh를 다시 실행해 layout과 motion을 확인한다.

사용자 수동 테스트 목록

사전 조건:

  • Debug 앱을 실제 기기 또는 iOS Simulator에서 실행한다.
  • 로그인 후 메인 화면에 진입한다.
  • 각 테스트에서 화면 상단 목록을 아래로 충분히 당겨 Lottie 새로고침 표시가 보이는지 확인한다.

홈 탭

  • 홈 > 추천: 아래로 당기면 indicator가 API 응답까지 유지되고 최신 데이터가 표시된다. 선택 탭은 추천으로 유지된다.
  • 홈 > 랭킹: 아래로 당기면 랭킹이 다시 조회되고 선택 탭은 랭킹으로 유지된다.
  • 홈 > 팔로잉: 아래로 당기면 팔로잉 데이터가 다시 조회되고 선택 탭은 팔로잉으로 유지된다.
  • 홈 > 팔로잉 로그인 필요/빈/오류 상태: 상태 화면에서도 아래로 당겨 재시도할 수 있다.

콘텐츠 탭

  • 콘텐츠 > 추천: 아래로 당기면 추천 데이터가 다시 조회되고 선택 탭은 추천으로 유지된다.
  • 콘텐츠 > 랭킹: 주간/월간 등 기본값이 아닌 랭킹 타입을 선택한 뒤 새로고침해도 같은 타입이 유지된다.
  • 콘텐츠 > 전체 > 오디오: 타입과 정렬을 변경한 뒤 새로고침해도 선택한 타입/정렬이 유지되고 목록이 첫 페이지 결과로 교체된다.
  • 콘텐츠 > 전체 > 시리즈: 요일과 정렬을 변경한 뒤 새로고침해도 타입/요일/정렬이 유지되고 목록이 첫 페이지 결과로 교체된다.
  • 콘텐츠의 빈/오류 상태: 상태 화면에서도 아래로 당겨 재시도할 수 있다.

대화 탭

  • 대화 > 전체: 아래로 당기면 ALL 첫 페이지가 다시 조회되고 선택 필터는 유지된다.
  • 대화 > AI: 아래로 당기면 AI 첫 페이지가 다시 조회되고 선택 필터는 유지된다.
  • 대화 > DM: 아래로 당기면 DM 첫 페이지가 다시 조회되고 선택 필터는 유지된다.
  • 대화 빈/오류 상태: 상태 화면에서도 아래로 당겨 재시도할 수 있다.
  • 대화 목록 새로고침 후 하단까지 스크롤하면 다음 페이지가 중복 없이 이어서 추가된다.

공통 회귀

  • 새로고침 중 다른 내부 탭/필터로 강제 전환되지 않는다.
  • 새로고침 실패 시 기존 토스트 또는 오류/빈 상태가 표시되고 다시 당겨 재시도할 수 있다.
  • 하단 마이 탭에는 이번 변경으로 새로고침 동작이 추가되지 않았다.

Phase 4 Lottie 후속 검증

  • 당김 영역 노출 중 반복 재생, 기준 미만 취소 시 숨김·초기화를 확인한다.
  • refresh 시작 후 API 완료까지 Lottie가 유지되고 요청이 한 번만 실행되는지 확인한다.
  • Reduce Motion에서 frame 1 정지 표시와 accessibility refresh action을 확인한다.
  • 홈·콘텐츠·대화 7개 View의 content·loading·empty/error 상태와 iPhone·iPad 레이아웃을 확인한다.
  • legacy LiveNowAllView, ContentDetailView가 변경되지 않았는지 확인한다.

Decision Log

날짜 ID 상태 결정 근거 영향 Goal/문서
2026-08-18 DEC-001 확정 당겨서 새로고침은 현재 선택된 내부 탭·필터·정렬 상태만 대상으로 한다. 사용자 A안 선택 P1-T1, P2-T1, P3-T1, prd.md
2026-08-18 DEC-002 확정 빈/오류 상태에서도 당겨서 새로고침을 허용한다. 사용자 A안 선택 P1-GATE, P2-GATE, P3-GATE, prd.md
2026-08-18 DEC-003 확정 신규 API, dependency, 공통 abstraction 없이 기존 fetch 경로를 연결한다. 최소 변경 원칙과 기존 ViewModel 구조 전체 Goal
2026-08-25 DEC-004~010 확정 Lottie asset·재생·크기·Reduce Motion·적용 화면을 PRD 인터뷰 결과대로 적용한다. 사용자 인터뷰와 asset 확인 P4-T1, P4-T2, P4-GATE, prd.md
2026-08-25 DEC-011 정정 DEC-003의 공통 abstraction 제외는 Phase 1~3에 유지하고, Phase 4에서는 V2 공용 component 한 개만 허용한다. 7개 View·20개 상태의 중복 방지 P4-T1, P4-T2, prd.md
2026-08-26 DEC-012 확정 기존 VStack layout을 유지하고 refresh 상태의 0↔64pt 높이·offset 전환만 0.2초 easeOut으로 보간한다. 기존 UI 간격 보존과 최소 변경 우선 P4-R3, P4-GATE

발견된 문제

ID 심각도 상태 발견 내용 영향 Goal 처리 계획
ISSUE-001 Low 해결 현재 테스트 번들 타깃이 명확하지 않아 SwiftUI 새로고침 동작을 자동 테스트로 고정하기 어렵다. 전체 Gate Debug 빌드와 사용자 수동 검증을 완료 증거로 기록했다. 테스트 타깃이 추가되면 focused test를 후속 보강한다.
ISSUE-002 High 해결 custom ScrollView의 bounce·gesture 종료·offset 측정 경로 때문에 Lottie와 refresh가 실행되지 않았다. P4-R1, P4-GATE bounce 강제, preference armed→release 전이, global moving-minus-fixed offset 측정으로 수정하고 사용자 동작 확인을 통과했다.
ISSUE-003 Medium 롤백 frozen motion handoff는 덜컹임을 줄였지만 기존 UI 간격을 변경했다. P4-R2 사용자 결정에 따라 motion 보정과 후속 layout 수정을 모두 롤백했다.

최종 보고 형식

구현 결과: 메인 홈, 콘텐츠, 대화 탭의 Lottie 당겨서 새로고침 구현 완료 여부

- 변경: 기존 데이터 새로고침을 유지한 채 메인 7개 View의 기본 indicator를 공용 Lottie refresh component로 교체
- 결정: DEC-006 영역 노출 중 반복, DEC-007 최대 48×48pt·64pt 영역, DEC-008~009 Reduce Motion frame 1, DEC-010 메인 7개 View 한정
- 검증:
  - `xcodebuild` `SodaLive`, `SodaLive-dev` Debug simulator build — 성공/실패와 exit code
  - 수동 검증 — Lottie 재생·크기·Reduce Motion·기존 refresh 회귀·legacy 제외 결과
- 남은 항목: 미완료 `P4-*` 또는 없음
- 문서: `docs/20260818_메인_탭_당겨서_새로고침/prd.md`, `docs/20260818_메인_탭_당겨서_새로고침/plan-task.md`