docs(main): 메인 탭 새로고침 계획을 기록한다
This commit is contained in:
253
docs/20260818_메인_탭_당겨서_새로고침/prd.md
Normal file
253
docs/20260818_메인_탭_당겨서_새로고침/prd.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# PRD: 메인 탭 당겨서 새로고침
|
||||
|
||||
## 문서 정보
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 문서 상태 | 구현 완료 |
|
||||
| 작성일 | `2026-08-18` |
|
||||
| 최종 수정일 | `2026-08-18` |
|
||||
| 대상 제품 | 메인 홈, 콘텐츠, 대화 탭 당겨서 새로고침 |
|
||||
| 작성자·결정권자 | 사용자, 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` | 빌드 | 대화 필터별 새로고침 |
|
||||
|
||||
## 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·검증 기록을 삭제하거나 덮어쓰지 않았다.
|
||||
Reference in New Issue
Block a user