Files

332 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD: 메인 탭 당겨서 새로고침
## 문서 정보
| 항목 | 내용 |
|---|---|
| 문서 상태 | 후속 변경 구현 완료 / 사용자 수동 검증 대기 |
| 작성일 | `2026-08-18` |
| 최종 수정일 | `2026-08-25` |
| 대상 제품 | 메인 홈, 콘텐츠, 대화 탭 당겨서 새로고침 및 Lottie 표시 영역 |
| 작성자·결정권자 | 사용자, Sisyphus |
| 관련 API Contract | 기존 각 탭 API 계약 재사용, 신규 endpoint 없음 |
| 관련 구현 계획 | `docs/20260818_메인_탭_당겨서_새로고침/plan-task.md` |
| 관련 review | 없음 |
### 요구사항 상태
| 상태 | 의미 | 구현 처리 |
|---|---|---|
| 확정 | 제품·기술 결정이 완료되어 구현 기준으로 사용 | `plan-task.md`의 Task와 완료 증거로 추적 |
| 미결 | 제품·UX·운영 결정이 더 필요함 | 권고안과 결정 주체를 기록하고 임의 구현 금지 |
| 제외 | 현재 릴리스에서 구현하지 않기로 결정 | 제외 이유와 후속 조건을 Decision Log에 기록 |
## 1. Overview
메인 하단 `홈`, `콘텐츠`, `대화` 탭에 iOS 기본 당겨서 새로고침 동작을 추가한다. 사용자가 현재 보고 있는 내부 탭과 필터 상태를 유지한 채 해당 화면의 첫 페이지 또는 단일 조회 API를 다시 호출해 최신 데이터를 표시한다.
구현 결과와 사용자 수동 검증 대기 상태는 관련 구현 계획에서 추적한다.
## 2. Problem Statement
현재 V2 메인 화면의 주요 조회 탭은 최초 진입 또는 내부 필터 변경 시에만 데이터를 불러온다. 사용자가 이미 보고 있는 화면에서 최신 상태를 직접 확인하려면 탭을 이동하거나 앱 흐름을 다시 진입해야 한다.
문제를 해결했다는 판단은 `홈`, `콘텐츠`, `대화` 탭에서 현재 선택 상태 그대로 당겨서 새로고침을 실행하고, 성공·빈 목록·오류 상태에서도 재시도 가능한 것으로 한다.
## 3. Goals
### 3.1 제품 목표
- `홈`, `콘텐츠`, `대화` 하단 탭에서 사용자가 수동으로 최신 데이터를 다시 조회할 수 있다.
- 새로고침은 현재 선택된 내부 탭·필터·정렬 상태만 대상으로 한다.
- 새로고침 실패 시 기존 오류 노출 패턴을 유지하고, 사용자는 같은 화면에서 다시 당겨서 재시도할 수 있다.
### 3.2 UX 목표
- iOS 기본 pull-to-refresh 제스처와 표시 방식을 사용한다.
- loading, empty, error, content 상태 어디서든 당겨서 새로고침을 시도할 수 있다.
- 새로고침은 현재 선택된 탭을 바꾸지 않고 스크롤 가능한 화면 문맥 안에서 동작한다.
## 4. Non-Goals
- `마이` 탭에는 당겨서 새로고침을 추가하지 않는다.
- 하단 탭 전체 데이터를 한 번에 새로고침하지 않는다.
- background 자동 갱신, 주기적 polling, push 기반 갱신은 추가하지 않는다.
- 신규 API endpoint, DTO, Repository를 만들지 않는다.
- 새 공통 추상화나 refresh protocol을 만들지 않는다. 기존 `ViewModel` fetch 경로를 화면별로 연결한다.
## 5. Target Users and Permissions
### 5.1 사용자
| 사용자 | 목표 | 주요 작업 | 사용 환경 |
|---|---|---|---|
| 로그인 사용자 | 메인 화면의 최신 홈·콘텐츠·대화 데이터를 직접 갱신 | 당겨서 새로고침 | iOS 앱 |
| 비로그인 또는 로그인 필요 상태 사용자 | 실패/제한 상태에서 재시도 | 당겨서 새로고침 | iOS 앱 |
### 5.2 권한
- 인증 주체: 기존 앱 인증 상태와 각 API의 현재 인증 정책을 그대로 사용한다.
- 허용 역할: 기존 각 탭 접근 정책을 따른다.
- 거부 조건: 기존 API 실패·로그인 필요 상태를 그대로 표시한다.
- 리소스 소유권: 기존 API와 화면 모델의 소유권 검증을 변경하지 않는다.
## 6. 핵심 사용자 흐름
1. 사용자가 메인 하단 `홈`, `콘텐츠`, `대화` 중 하나에 진입한다.
2. 사용자는 내부 탭 또는 필터를 선택해 특정 상태를 보고 있다.
3. 사용자가 화면을 아래로 당긴다.
4. 앱은 현재 선택 상태를 유지하고 해당 상태의 첫 페이지 또는 단일 조회 API를 다시 호출한다.
5. 성공하면 최신 데이터로 교체하고, 실패하면 기존 오류 메시지/토스트 정책을 유지한다.
6. 빈 목록이나 오류 상태에서도 사용자는 다시 당겨서 새로고침할 수 있다.
## 7. 정보 구조와 라우팅
```text
MainView
MainTab.home
MainHomeTab.recommendation
MainHomeTab.ranking
MainHomeTab.following
MainTab.content
MainContentTab.recommendation
MainContentTab.ranking
AudioRankingType
MainContentTab.all
MainContentAllType
ContentSort
SeriesPublishedDaysOfWeek
MainTab.chat
MainChatFilter.all
MainChatFilter.ai
MainChatFilter.dm
```
- 새로고침은 라우팅을 변경하지 않는다.
- 현재 하단 탭과 내부 선택 상태는 유지한다.
- 목록 아이템 상세 진입, 검색, 충전, 보관함 라우팅은 변경하지 않는다.
## 8. 기능 요구사항
### 8.1 공통 새로고침 동작
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|---|---|---|---|---|
| `REFRESH-001` | 확정 | `홈`, `콘텐츠`, `대화` 하단 탭에 당겨서 새로고침을 제공한다. | 각 탭에서 아래로 당기면 현재 화면의 조회 API가 다시 호출된다. | `P1-T1`, `P2-T1`, `P3-T1` |
| `REFRESH-002` | 확정 | 현재 선택된 내부 탭·필터·정렬·요일 상태만 새로고침한다. | 선택 상태가 변경되지 않고 해당 조건의 데이터만 첫 페이지부터 다시 조회된다. | `P1-T1`, `P2-T1`, `P3-T1` |
| `REFRESH-003` | 확정 | loading, empty, error, content 상태 모두에서 새로고침을 시도할 수 있다. | 빈/오류 화면에서도 pull-to-refresh 제스처 또는 동등한 재시도 흐름이 가능하다. | `P1-GATE`, `P2-GATE`, `P3-GATE` |
| `REFRESH-004` | 확정 | 새로고침은 기존 loading guard와 요청 경합 방지 정책을 깨지 않는다. | 중복 요청이 화면 상태를 역전시키지 않고, 기존 최신 요청 우선 패턴을 유지한다. | `P2-T1`, `P3-T1` |
### 8.2 홈 탭
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|---|---|---|---|---|
| `HOME-REFRESH-001` | 확정 | 홈 `추천` 선택 시 `MainHomeRecommendationViewModel.fetchRecommendations()` 경로로 다시 조회한다. | 기존 추천 섹션 데이터가 최신 응답으로 교체된다. | `P1-T1` |
| `HOME-REFRESH-002` | 확정 | 홈 `랭킹` 선택 시 `MainHomeRankingViewModel.fetchRankings()` 경로로 다시 조회한다. | 기존 랭킹 데이터와 rank change 표시가 최신 응답으로 교체된다. | `P1-T1` |
| `HOME-REFRESH-003` | 확정 | 홈 `팔로잉` 선택 시 `MainHomeFollowingViewModel.fetchFollowing()` 경로로 다시 조회한다. | 로그인 필요, 빈 상태, 콘텐츠 상태가 최신 응답 기준으로 갱신된다. | `P1-T1` |
### 8.3 콘텐츠 탭
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|---|---|---|---|---|
| `CONTENT-REFRESH-001` | 확정 | 콘텐츠 `추천` 선택 시 `MainContentRecommendationViewModel.fetchRecommendations()` 경로로 다시 조회한다. | 추천 콘텐츠 섹션 데이터가 최신 응답으로 교체된다. | `P2-T1` |
| `CONTENT-REFRESH-002` | 확정 | 콘텐츠 `랭킹` 선택 시 현재 `AudioRankingType`을 유지하고 `MainContentRankingViewModel.fetchRankings()` 경로로 다시 조회한다. | 선택된 랭킹 타입이 유지되고 목록이 최신 응답으로 교체된다. | `P2-T1` |
| `CONTENT-REFRESH-003` | 확정 | 콘텐츠 `전체` 선택 시 현재 `MainContentAllType`, `ContentSort`, `SeriesPublishedDaysOfWeek`를 유지하고 첫 페이지를 다시 조회한다. | 기존 목록이 첫 페이지 결과로 교체되고 다음 페이지 상태가 초기화된다. | `P2-T1` |
### 8.4 대화 탭
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|---|---|---|---|---|
| `CHAT-REFRESH-001` | 확정 | 대화 탭에서 현재 `MainChatFilter`를 유지하고 첫 페이지를 다시 조회한다. | `ALL`, `AI`, `DM` 중 선택된 필터가 유지되고 `rooms`가 첫 페이지 결과로 교체된다. | `P3-T1` |
| `CHAT-REFRESH-002` | 확정 | 새로고침 후 pagination 상태를 첫 페이지 기준으로 초기화한다. | `hasMore`, `nextCursor`가 최신 첫 페이지 응답 기준으로 갱신된다. | `P3-T1` |
## 9. 반응형 기능 범위
| 기능 | iPhone | iPad | 비고 |
|---|---:|---:|---|
| 당겨서 새로고침 | 전체 | 전체 | SwiftUI 기본 동작 기준 |
| 빈/오류 상태 재시도 | 전체 | 전체 | 상태 화면도 스크롤 가능한 컨테이너 안에서 표시 |
## 10. UI/UX Expectations
### 10.1 디자인과 component 원칙
- SwiftUI `.refreshable` 또는 동등한 iOS 기본 refresh affordance를 사용한다.
- 별도 커스텀 spinner, 신규 refresh button, 신규 공통 컴포넌트는 만들지 않는다.
- 기존 `Color.black`, `ProgressView`, `sodaToast`, empty state 스타일을 유지한다.
### 10.2 화면 상태
- 첫 로딩 상태와 수동 새로고침 상태를 구분하되, 기존 화면 레이아웃을 크게 바꾸지 않는다.
- 빈/오류 상태에서도 새로고침 가능해야 하므로 상태 화면을 refresh 가능한 스크롤 컨테이너로 감싸는 방식을 계획한다.
- 새로고침 실패 시 현재 탭 선택 상태는 유지한다.
### 10.3 접근성
- 기본 iOS refresh control 접근성 동작을 해치지 않는다.
- 기존 버튼, 탭, 리스트 아이템의 focus/touch target을 변경하지 않는다.
## 11. API 계약
### 11.1 공통 규칙
- 신규 endpoint 없음.
- 기존 Repository와 API 계약을 그대로 사용한다.
- 콘텐츠 `전체`와 대화 탭은 첫 페이지 재조회 시 기존 pagination cursor/page 상태를 초기화한다.
### 11.2 Endpoint 추적
| 요구사항 | Method | Path | 계약 상태 | API Contract | 소유 Goal |
|---|---|---|---|---|---|
| `HOME-REFRESH-001` | GET | 기존 홈 추천 API | 제공됨 | 기존 `MainHomeRecommendationRepository` | `P1-T1` |
| `HOME-REFRESH-002` | GET | 기존 홈 랭킹 API | 제공됨 | 기존 `MainHomeRankingRepository` | `P1-T1` |
| `HOME-REFRESH-003` | GET | 기존 홈 팔로잉 API | 제공됨 | 기존 `MainHomeFollowingRepository` | `P1-T1` |
| `CONTENT-REFRESH-001` | GET | 기존 콘텐츠 추천 API | 제공됨 | 기존 `MainContentRecommendationRepository` | `P2-T1` |
| `CONTENT-REFRESH-002` | GET | 기존 콘텐츠 랭킹 API | 제공됨 | 기존 `MainContentRankingRepository` | `P2-T1` |
| `CONTENT-REFRESH-003` | GET | 기존 콘텐츠 전체 API | 제공됨 | 기존 `MainContentAllRepository` | `P2-T1` |
| `CHAT-REFRESH-001` | GET | `/api/v2/chat/rooms` | 제공됨 | `docs/20260708_홈_채팅_탭/prd.md` | `P3-T1` |
## 12. 보안과 데이터 취급
- 인증 header, token 저장, 언어 header 흐름은 변경하지 않는다.
- 새로고침 실패 로그에 token, 개인 메시지 본문, signed URL 등 민감정보를 기록하지 않는다.
- 기존 `AuthPlugin`, `UserDefaultsKey.token` 처리 로직은 수정 대상이 아니다.
## 13. 성능과 품질 요구사항
- 이미 로딩 중인 동일 화면에서 중복 새로고침이 과도하게 발생하지 않도록 기존 loading guard를 유지하거나 같은 수준의 guard를 둔다.
- 콘텐츠 `전체`와 대화 탭은 첫 페이지 새로고침 시 기존 다음 페이지 로딩 상태를 초기화한다.
- 새 공통 abstraction을 만들지 않고 화면별 최소 변경으로 구현한다.
- 현재 테스트 번들 타깃이 명확하지 않으므로 구현 검증은 focused 코드 리뷰, `xcodebuild` 빌드, 수동 QA를 필수로 한다.
## 14. 성공 기준
### 14.1 기능 수용 기준
- [x]`추천`, `랭킹`, `팔로잉`에서 각각 현재 선택된 내부 탭만 새로고침된다. (`HOME-REFRESH-001~003`)
- [x] 콘텐츠 `추천`, `랭킹`, `전체`에서 각각 현재 선택된 내부 탭과 필터 상태만 새로고침된다. (`CONTENT-REFRESH-001~003`)
- [x] 대화 `전체`, `AI`, `DM`에서 현재 필터만 유지해 첫 페이지를 새로고침한다. (`CHAT-REFRESH-001~002`)
- [x] 빈/오류 상태에서도 사용자가 당겨서 재시도할 수 있다. (`REFRESH-003`)
### 14.2 UI/UX 수용 기준
- [x] iOS 기본 refresh affordance가 보인다.
- [x] 새로고침 후 선택된 하단 탭과 내부 탭/필터가 변경되지 않는다.
- [x] 새로고침 실패 시 기존 토스트/오류 상태 정책을 유지한다.
### 14.3 추적성 완료 기준
- [x] 모든 `확정` 요구사항이 `plan-task.md`의 Task/Goal 완료 증거로 연결된다.
- [x] 신규 API 또는 신규 dependency가 없다는 결정이 Decision Log에 남아 있다.
- [x] 코드 구현 전 이 문서와 계획 문서가 먼저 작성되어 있다.
## 15. Open Questions
| ID | 상태 | 결정 필요 사항 | 현재 권고 | 결정 주체 | 결정 기한/시점 | 영향 Goal |
|---|---|---|---|---|---|---|
| 없음 | 확정 | 남은 제품 결정 없음 | 없음 | 사용자 | 2026-08-18 | 전체 |
## 16. 요구사항 추적표
| 요구사항 범위 | API Contract | 계획 Phase | Goal | 자동 검증 | 수동 검증 |
|---|---|---:|---|---|---|
| `REFRESH-001~004` | 기존 API 재사용 | 1~3 | `P1-T1`, `P2-T1`, `P3-T1` | 빌드 | 홈/콘텐츠/대화 새로고침 |
| `HOME-REFRESH-001~003` | 홈 기존 Repository | 1 | `P1-T1`, `P1-GATE` | 빌드 | 홈 내부 탭별 새로고침 |
| `CONTENT-REFRESH-001~003` | 콘텐츠 기존 Repository | 2 | `P2-T1`, `P2-GATE` | 빌드 | 콘텐츠 내부 탭/필터별 새로고침 |
| `CHAT-REFRESH-001~002` | `/api/v2/chat/rooms` | 3 | `P3-T1`, `P3-GATE` | 빌드 | 대화 필터별 새로고침 |
| `REFRESH-ANIMATION-001~005` | local Lottie asset, 신규 API 없음 | 4 | `P4-T1`, `P4-T2`, `P4-GATE` | asset 검사, 두 scheme 빌드 | Lottie 재생·크기·Reduce Motion·범위 제외 |
## 17. Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·계약·Goal |
|---|---|---|---|---|---|
| 2026-08-18 | `DEC-001` | 확정 | 당겨서 새로고침은 현재 선택된 내부 탭·필터·정렬 상태만 대상으로 한다. | 사용자 인터뷰 A안 선택 | `REFRESH-002`, 전체 Goal |
| 2026-08-18 | `DEC-002` | 확정 | 빈/오류 상태에서도 당겨서 새로고침을 허용한다. | 사용자 인터뷰 A안 선택 | `REFRESH-003`, 전체 Gate |
| 2026-08-18 | `DEC-003` | 확정 | 신규 API, 신규 dependency, 신규 공통 refresh abstraction은 만들지 않는다. | 기존 화면별 fetch 경로 존재, 최소 변경 원칙 | `REFRESH-004`, 전체 Goal |
## 18. 변경 관리
요구사항 변경 시 다음을 확인한다.
- [x] Decision Log에 변경 이유와 날짜를 기록했다.
- [x] 관련 요구사항 상태·본문·수용 기준을 갱신했다.
- [x] `plan-task.md`의 범위·Files·체크박스·완료 증거를 코드 변경 전에 갱신했다.
- [x] 기존 Progress·review·검증 기록을 삭제하거나 덮어쓰지 않았다.
## 19. 후속 변경 확정: Lottie 새로고침 표시
### 19.1 확인된 현재 상태
- `2026-08-25` 사용자 요청은 기존 pull-to-refresh 동작의 표시 영역을 `Resources/pull-to-refresh.json` 기반 Lottie animation으로 변경하는 것이다.
- 구현 전 pull-to-refresh는 메인 홈·콘텐츠·대화의 7개 SwiftUI View에 있고, loading·empty·error·content 분기를 합쳐 `.refreshable` modifier가 20곳에 적용되어 있었다.
- Lottie `4.6.1` Swift Package가 `SodaLive`, `SodaLive-dev` 두 target에 연결되어 있다.
- 공식 Lottie iOS API는 SwiftUI용 `LottieView`와 local JSON loading, playback mode 및 progress 제어를 제공한다.
- `SodaLive/Resources/pull-to-refresh.json``150×150`, `30fps`, frame `0..<45`, marker 없음인 local Lottie asset이며 `SodaLive`, `SodaLive-dev` 두 target의 Resources build phase에 포함되어 있다.
- asset의 frame `0`은 trim path 시작·끝 값이 모두 `3`이라 비어 있고, frame `1`부터 stroke가 표시된다.
- Phase 4 요구사항 인터뷰 요청에서는 문서 작성만 수행했고, 이후 사용자 승인으로 코드 구현과 자동 검증을 완료했다.
### 19.2 후속 요구사항
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|---|---|---|---|---|
| `REFRESH-ANIMATION-001` | 확정 | 기존 메인 홈·콘텐츠·대화의 pull-to-refresh 표시 영역에 지정된 Lottie animation을 사용한다. | 기존 7개 View의 모든 refresh 가능 상태에서 동일한 Lottie asset이 표시되고 기존 데이터 새로고침 동작은 유지된다. | `P4-T1`, `P4-T2` |
| `REFRESH-ANIMATION-002` | 확정 | 당김 영역이 보이면 Lottie animation을 반복 재생하고, 새로고침 완료 또는 당김 취소로 영역이 사라질 때 정지·처음 frame으로 초기화한다. | 당기는 거리에 따른 progress 제어 없이 영역 노출 중 반복 재생하며, 영역이 사라졌다가 다시 나타나면 처음부터 재생한다. | `DEC-006`, `P4-T1` |
| `REFRESH-ANIMATION-003` | 확정 | Lottie animation을 원본 비율을 유지한 최대 `48×48pt`로 중앙 정렬하고 상하 `8pt` 여백을 둔다. | 총 `64pt` 높이의 refresh 영역에서 animation이 늘어나거나 잘리지 않고 iPhone·iPad에 동일하게 표시된다. | `DEC-007`, `P4-T1`, `P4-GATE` |
| `REFRESH-ANIMATION-004` | 확정 | Reduce Motion 활성화 시 반복 재생하지 않고 asset의 첫 번째 유효 frame인 frame `1`을 표시한다. | 동작 줄이기가 켜지면 animation이 재생되지 않으며, frame `1`의 stroke가 영역 노출 중 정지 상태로 표시된다. | `DEC-008`, `DEC-009`, `P4-T1`, `P4-GATE` |
| `REFRESH-ANIMATION-005` | 확정 | Lottie 표시를 기존 PRD 범위인 메인 홈·콘텐츠·대화 7개 View에만 적용한다. | 7개 View의 20개 refresh 가능 상태에서 Lottie만 표시하고 기존 indicator를 함께 노출하지 않으며, `LiveNowAllView``ContentDetailView`는 변경되지 않는다. | `DEC-010`, `P4-T2`, `P4-GATE` |
### 19.3 후속 Open Questions
| ID | 상태 | 결정 필요 사항 | 현재 권고 | 결정 주체 | 결정 기한/시점 | 영향 Goal |
|---|---|---|---|---|---|---|
| `OQ-002` | 확정 | 당기는 중과 refresh 실행 중 Lottie playback을 각각 어떻게 제어할지 | 영역 노출 중 반복 재생, 영역 종료 시 정지·초기화 | 사용자 | 2026-08-25 | 후속 Phase 전체 |
| `OQ-003` | 확정 | Lottie 표시 크기, refresh 영역 높이와 수평·수직 정렬 | 최대 `48×48pt`, 원본 비율 유지, 중앙 정렬, 상하 `8pt` 여백 | 사용자 | 2026-08-25 | 후속 UI Gate |
| `OQ-004` | 확정 | iOS Reduce Motion 활성화 시 표시할 정지 frame | 비어 있는 frame `0` 대신 첫 번째 유효 frame `1` 표시 | 사용자 | 2026-08-25 | 후속 접근성 Gate |
| `OQ-005` | 확정 | 메인 7개 View만 적용할지, 다른 기존 pull-to-refresh 화면도 포함할지 | 기존 PRD 범위인 메인 7개 View만 적용 | 사용자 | 2026-08-25 | 후속 Phase 전체 |
### 19.4 후속 변경 제약
- 기존 `refresh()` 호출, 선택 탭·필터 유지, pagination 초기화와 오류 처리 동작은 변경하지 않는다.
- Lottie `4.6.1` 외에 새 dependency를 추가하지 않는다.
- 20개 modifier마다 구현을 복제하지 않고, 기존 7개 View의 공통 요구를 충족하는 최소 재사용 단위만 구현한다.
- SwiftUI `.refreshable`의 기본 indicator는 custom Lottie로 교체할 공개 API가 없으므로, V2 공용 refresh component 한 개에서 당김 offset·비동기 refresh 상태·Lottie 표시를 관리한다.
- 기존 `RefreshableScrollView` `1.1.1`은 indicator가 내부에 고정되어 있으므로 후속 V2 component의 기반으로 사용하지 않고, package source도 수정하지 않는다.
- Lottie visual은 접근성 트리에서 숨기고, 공용 refresh component는 기존 `I18n.LiveNow.refreshButton` 문구를 재사용한 명시적 accessibility refresh action을 제공한다.
- `SodaLive.xcodeproj/project.pbxproj`, `SodaLive.xcworkspace/xcshareddata/swiftpm/Package.resolved`의 현재 Lottie 관련 변경은 사용자 작업으로 보존한다.
- `SodaLive/Sources/Live/Now/All/LiveNowAllView.swift`, `SodaLive/Sources/Content/Detail/ContentDetailView.swift`의 기존 pull-to-refresh는 이번 범위에서 제외한다.
### 19.5 후속 변경 Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·계약·Goal |
|---|---|---|---|---|---|
| 2026-08-25 | `DEC-004` | 확정 | 기존 iOS 기본 refresh 표시를 지정된 Lottie animation으로 변경하고, 이미 추가된 Lottie `4.6.1`을 사용한다. | 사용자 직접 요청과 현재 Swift Package 설정 | `REFRESH-ANIMATION-001~004` |
| 2026-08-25 | `DEC-005` | 확정 | Phase 4 요구사항 인터뷰 실행에서는 문서 작성만 수행하고 코드 구현은 하지 않는다. | 사용자 직접 요청 | 후속 Phase 전체 |
| 2026-08-25 | `DEC-006` | 확정 | Lottie는 당김 영역이 보이는 동안 반복 재생하고, 영역이 사라질 때 정지 후 처음 frame으로 초기화한다. | 사용자 인터뷰 A안 선택 | `REFRESH-ANIMATION-002`, `OQ-002` |
| 2026-08-25 | `DEC-007` | 확정 | Lottie는 원본 비율을 유지한 최대 `48×48pt`로 중앙 정렬하고, 상하 `8pt` 여백을 포함한 총 `64pt` refresh 영역에 표시한다. | 사용자 인터뷰 A안 선택과 기존 `SodaSpacing` 값 | `REFRESH-ANIMATION-003`, `OQ-003` |
| 2026-08-25 | `DEC-008` | 확정 | iOS Reduce Motion이 활성화되면 Lottie를 반복 재생하지 않고 정지 frame을 표시한다. | 사용자 인터뷰 A안 선택 | `REFRESH-ANIMATION-004`, `OQ-004` |
| 2026-08-25 | `DEC-009` | 확정 | Reduce Motion 정지 화면은 비어 있는 frame `0`을 건너뛰고 asset의 첫 번째 유효 frame인 frame `1`을 사용한다. | 사용자 인터뷰 A안 선택과 asset frame 확인 | `REFRESH-ANIMATION-004`, `OQ-004` |
| 2026-08-25 | `DEC-010` | 확정 | Lottie 적용 범위는 기존 PRD의 메인 홈·콘텐츠·대화 7개 View로 제한하고 legacy pull-to-refresh 2개 화면은 제외한다. | 사용자 인터뷰 A안 선택 | `REFRESH-ANIMATION-005`, `OQ-005` |
| 2026-08-25 | `DEC-011` | 정정 | `DEC-003`의 공통 refresh abstraction 제외는 Phase 1~3에 유지하되, Phase 4에서는 7개 View·20개 상태의 표시 중복을 막기 위한 V2 공용 component 한 개만 허용한다. | 동일한 Lottie 상태·접근성·비동기 guard를 한 곳에서 유지하기 위한 최소 재사용 단위 | `P4-T1`, `P4-T2` |
### 19.6 인터뷰 종료 결과
| 차원 | 명확성 |
|---|---:|
| Goal | `1.00` |
| Scope | `1.00` |
| Constraints | `0.90` |
| Success | `1.00` |
| Context | `0.90` |
- 최종 모호성: `0.03`
- 결정사항: `DEC-004~011`에 Lottie asset·재생·크기·Reduce Motion·적용 범위·최소 공용 component를 기록했다.
- 열린 질문: 없음.
### 19.7 후속 성공 기준
- [x] V2 공용 Lottie refresh component 2개가 `SodaLive`, `SodaLive-dev` 두 target에서 빌드된다. (`REFRESH-ANIMATION-001~004`)
- [x] 메인 7개 View의 기존 `.refreshable` 20곳이 공용 component로 교체되고 기본 indicator 경로가 남아 있지 않다. (`REFRESH-ANIMATION-001`, `REFRESH-ANIMATION-005`)
- [x] 기존 7개 ViewModel과 `LiveNowAllView`, `ContentDetailView`에 구현 diff가 없다. (`REFRESH-ANIMATION-005`)
- [ ] 실제 당김·취소·새로고침 완료에서 Lottie 반복·초기화와 단일 요청 동작을 확인한다. (`REFRESH-ANIMATION-002`)
- [ ] Reduce Motion frame `1`, 최대 `48×48pt`, 총 `64pt` 영역과 iPhone·iPad 레이아웃을 확인한다. (`REFRESH-ANIMATION-003~004`)