docs(recommendation): 서브 배너 요구사항과 검증 기록을 남긴다
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
# 추천 서브 배너 PRD
|
||||
|
||||
## 문서 정보
|
||||
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 문서 상태 | 구현·로컬 검증 완료 |
|
||||
| 작성일 | 2026-10-29 |
|
||||
| 최종 수정일 | 2026-10-30 |
|
||||
| 대상 제품 | 홈·콘텐츠 추천 서브 배너 |
|
||||
| 작성자·결정권자 | 작성: 구현 에이전트, 제품 결정: 사용자 |
|
||||
| 관련 API Contract | 없음, 계약은 이 PRD에 임시 기록 |
|
||||
| 관련 구현 계획 | [plan-task.md](plan-task.md) |
|
||||
| 관련 review | [통합 리뷰](reviews/phase1-sub-banner-integration.md) |
|
||||
|
||||
### 요구사항 상태
|
||||
|
||||
| 상태 | 의미 | 구현 처리 |
|
||||
|---|---|---|
|
||||
| 확정 | 사용자 요청으로 명시된 요구 | 구현 기준 |
|
||||
| 미결 | 최종 제품 선택이 필요한 UX | 결정 전 임의 구현 금지 |
|
||||
| 권고 | 검토를 위한 제안 기본값 | 승인 전 수용 기준 아님 |
|
||||
| 외부 의존 | 서버 응답·정책 제공 필요 | 제공 전 추정 구현 금지 |
|
||||
|
||||
## 1. Overview
|
||||
|
||||
기존 홈 추천과 오디오 추천 조회 응답의 `data.subBanners`를 사용해 각 추천 화면에 배너 목록을 표시한다. 별도 endpoint나 추가 조회 없이 서버 순서와 자격 필터링 결과를 유지하고, 플랫폼별로 열 수 있는 링크만 처리한다.
|
||||
|
||||
## 2. Problem Statement
|
||||
|
||||
두 추천 화면에는 배치 목적이 다른 서브 배너가 필요하다. 기존 `BannerCarousel`은 최대 20개 제한, 정사각형 이미지, 좌우 여백을 전제로 하므로 이 요구의 배너 목록과 원본 이미지 비율을 그대로 충족하지 않는다. 기존 컴포넌트를 그대로 재사용할 수 없다.
|
||||
|
||||
## 3. Goals
|
||||
|
||||
- 기존 응답의 `subBanners`를 홈과 콘텐츠 추천의 지정 위치에 표시한다.
|
||||
- 서버가 정한 순서를 보존하고 placement별로 분리해 보여 준다.
|
||||
- 안전한 플랫폼별 링크 열기를 제공하고, 이미지·링크 오류가 추천 화면 전체를 막지 않게 한다.
|
||||
|
||||
## 4. Non-Goals
|
||||
|
||||
- `/api/v2/sub-banners` 추가, 두 번째 요청, placement query 추가는 하지 않는다.
|
||||
- 기존 `data` 필드나 `banners`를 바꾸지 않는다.
|
||||
- Android 전용 배너 노출 또는 Android 링크 실행을 지원하지 않는다.
|
||||
- 클라이언트 날짜 계산, 타이머를 이용한 노출 자격 판정, 설치 가능 여부 사전 필터링을 하지 않는다.
|
||||
- 기존 메인 배너 캐러셀의 동작을 변경하지 않는다.
|
||||
|
||||
## 5. Target Users and Permissions
|
||||
|
||||
- 비로그인 사용자와 로그인 사용자는 모두 추천 조회 결과의 서브 배너를 본다.
|
||||
- 비로그인 요청에는 `Authorization`을 보내지 않는다.
|
||||
- 로그인 요청은 기존 인증 헤더를 그대로 유지한다.
|
||||
- 확인한 두 `TargetType`은 현재 `Authorization: Bearer {token}` 헤더를 항상 구성한다. 익명에서 헤더를 생략하라는 요구와 차이가 있어, 공통 plugin 동작 및 endpoint별 수정 seam은 구현 전 확인이 필요하다.
|
||||
|
||||
## 6. 핵심 사용자 흐름
|
||||
|
||||
1. 홈 또는 콘텐츠 추천 화면이 기존 추천 API를 한 번 호출한다.
|
||||
2. HTTP 200이며 envelope의 `success == true`인 응답에서 `data.subBanners`를 읽는다.
|
||||
3. 화면별 placement에 해당하며 iOS 또는 웹 링크가 하나 이상 유효한 배너만 서버 순서대로 표시한다.
|
||||
4. 사용자가 배너를 탭하면 iOS 앱 링크를 먼저 열고 실패할 때 웹 링크를 한 번 시도한다.
|
||||
5. 두 링크가 모두 없거나 열기에 실패해도 추천 화면은 계속 사용할 수 있다.
|
||||
|
||||
## 7. 정보 구조와 라우팅
|
||||
|
||||
```text
|
||||
홈 추천: 최근 활동 크리에이터 다음
|
||||
콘텐츠 추천: 기존 상단 배너 다음, 독점 시리즈 이전
|
||||
공용 신규 컴포넌트 위치: SodaLive/Sources/V2/Component/Banner
|
||||
```
|
||||
|
||||
## 8. 기능 요구사항
|
||||
|
||||
### 8.1 추천 API와 응답
|
||||
|
||||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||
|---|---|---|---|---|
|
||||
| SB-001 | 확정 | `GET /api/v2/home/recommendations`와 `GET /api/v2/audio/recommendations` 응답의 기존 envelope `success/message/data/errorProperty` 및 기존 `data` 필드·`banners`를 보존하고 `data.subBanners`를 추가한다. | 두 경로에서 기존 필드가 유지되고 서브 배너를 같은 응답에서 읽는다. | 기존 API 응답 |
|
||||
| SB-002 | 확정 | API 성공은 HTTP 200과 `success == true`를 모두 만족해야 한다. | HTTP 비 200 또는 `success == false`는 성공 데이터·빈 성공으로 취급되지 않는다. | SB-001 |
|
||||
| SB-003 | 확정 | 별도 `/api/v2/sub-banners` 요청이나 두 번째 요청, placement query를 추가하지 않는다. | 추천 화면 진입 시 서브 배너 때문에 추가 네트워크 요청이 발생하지 않는다. | SB-001 |
|
||||
| SB-004 | 확정 | 비로그인 요청에서 `Authorization`을 생략하고 로그인 요청은 현재 endpoint의 기존 헤더를 보존한다. | 양쪽 인증 상태에서 헤더가 기대값과 일치하며 다른 기존 헤더도 유지된다. | 기존 인증 정책 |
|
||||
| SB-005 | 확정 | 서버는 `id` 오름차순으로 정렬하고 날짜 자격 조건을 적용한 목록을 내려준다. 클라이언트는 순서를 보존하며 날짜나 timer로 자격을 다시 판정하지 않는다. | 제공된 계약 기반 fixture의 순서가 화면까지 유지되고 클라이언트 날짜 기반 제외·재정렬이 없다. | 사용자 제공 구현 완료 계약 |
|
||||
|
||||
### 8.2 SubBanner 데이터와 자격
|
||||
|
||||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||
|---|---|---|---|---|
|
||||
| SB-006 | 확정 | `SubBanner` 필드는 `id`(JSON integer), `placement`(정확히 `HOME_RECOMMENDATION` 또는 `CONTENT_RECOMMENDATION`), `imageUrl`(절대 http(s) URL), nullable `webUrl`, `androidDeepLink`, `iosDeepLink`다. 추가 서버 필드를 가정하지 않는다. | 제공된 필드만으로 디코딩하고 알 수 없는 필드는 요구하지 않는다. | 서버 응답 계약 |
|
||||
| SB-007 | 확정 | `id`는 정밀도 손실 없는 native `Int` decoding 전략으로 처리한다. | 큰 정수 식별자가 반올림·문자열 변환 없이 정확히 보존된다. | SB-006 |
|
||||
| SB-008 | 확정 | placement를 화면별로 분리하고 리스트의 서버 순서를 그대로 유지한다. | 홈에는 홈 placement만, 콘텐츠에는 콘텐츠 placement만 표시하며 각 내부 순서가 입력과 같다. | SB-005, SB-006 |
|
||||
| SB-009 | 확정 | iOS/web URL 유효성 중 하나 이상을 만족하는 항목만 노출한다. Android 링크만 있는 항목은 제외하며 앱 설치 가능 여부를 사전 확인하지 않는다. | Android-only 항목은 숨기고, 설치 상태 조회로 제외되는 항목이 없다. | SB-006 |
|
||||
| SB-010 | 확정 | 필터 후 목록이 비면 배너 영역과 그 영역으로 추가된 간격을 모두 숨긴다. | 빈 목록 전후에 서브 배너 전용 영역 높이나 간격이 남지 않는다. | SB-008, SB-009 |
|
||||
|
||||
### 8.3 노출과 상호작용
|
||||
|
||||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||
|---|---|---|---|---|
|
||||
| SB-011 | 확정 | 자격을 통과한 전체 목록을 캐러셀에 제공한다. 20개 제한 없이 모든 항목을 순환 대상으로 유지한다. | 21개 이상 목록의 마지막 항목까지 도달 가능하고 입력 순환 순서가 보존된다. | SB-008 |
|
||||
| SB-012 | 확정 | 홈에서는 최근 활동 크리에이터 다음, 콘텐츠에서는 기존 상단 배너 다음이자 독점 시리즈 이전에 둔다. 주변 기존 섹션과 동일한 `SodaSpacing.s24` 간격을 사용한다. | 기존 메인 배너를 이동하지 않고 새 섹션을 지정 위치에 추가하며 비어 있을 때 추가 간격이 없다. | SB-010 |
|
||||
| SB-013 | 확정 | 배너는 가로 전체 폭, 수평 padding 0으로 표시하고 이미지 원본 비율을 유지한다. | 양쪽 화면 경계까지 폭을 쓰며 이미지를 정사각형으로 강제하지 않는다. | SB-012 |
|
||||
| SB-014 | 확정 | 5초마다 자동 순환하고 현재 이미지 원본 비율에 맞춰 높이를 변경한다. 기존 패턴의 swipe를 제공하고 항목이 하나면 timer를 실행하지 않는다. | 자동 전환 전후 이미지 비율과 높이가 일치하고 빈 목록이면 컴포넌트 자체가 없다. | OQ-001 |
|
||||
| SB-015 | 확정 | 기존 메인 배너 캐러셀 동작은 바꾸지 않고, 새 공용 코드는 `V2/Component/Banner` 아래에 둔다. | 기존 메인 캐러셀에 변경이 없고 서브 배너 전용 요구가 기존 제약과 섞이지 않는다. | SB-011, SB-013 |
|
||||
|
||||
### 8.4 링크와 오류 처리
|
||||
|
||||
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||
|---|---|---|---|---|
|
||||
| SB-016 | 확정 | 웹 URL은 scheme이 http 또는 https이고 host가 있어야 한다. 판정은 대소문자를 구분하지 않는다. | host 누락, 다른 scheme, 잘못된 URL은 web 후보로 인정되지 않는다. | SB-009 |
|
||||
| SB-017 | 확정 | deep link는 절대 URI여야 하며 `javascript`, `data`, `file`, `content` scheme은 대소문자 무관하게 거부한다. | 상대 URI와 금지 scheme은 실행되지 않는다. 허용 URI는 검증용 파싱 후에도 원래 문자열 그대로 전달한다. | SB-009 |
|
||||
| SB-018 | 확정 | 사용자 추천안을 구현 기본값으로 채택한다. 유효한 iOS 링크를 먼저 한 번 열고, 없거나 열기에 실패하면 유효한 웹 링크를 한 번 연다. Android 링크는 쓰지 않는다. | iOS 성공 시 웹 시도 0회, iOS 실패 및 웹 유효 시 웹 시도 1회, 모두 실패 시 비치명 상태를 검증한다. | SB-016, SB-017 |
|
||||
| SB-019 | 확정 | 이미지 로드 실패와 개별 링크 실패는 해당 배너에만 국한한다. | 실패한 이미지나 링크가 다른 배너 및 추천 화면 로딩·사용을 막지 않는다. | SB-018 |
|
||||
| SB-020 | 확정 | 추천 조회 실패를 빈 성공이나 빈 배너 목록으로 바꾸지 않고 기존 추천 오류 의미를 유지한다. | HTTP 비 200, `success == false`, 디코딩·전송 실패가 기존 추천 오류 처리로 전달되고 정상 empty와 구분된다. | SB-002 |
|
||||
|
||||
## 9. UI/UX Expectations
|
||||
|
||||
- 홈과 콘텐츠 화면의 기존 `VStack` 섹션 간격을 기준으로 서브 배너를 배치한다. 배너가 비면 그 섹션 때문에 추가된 간격도 없어야 한다.
|
||||
- 이미지 및 링크의 오류는 국소 처리한다.
|
||||
- page indicator는 기존 우측 상단 현재/전체 표시를 따른다. 이미지 오류는 기존 회색 placeholder 계열로 국소 처리하고 별도 오류 팝업을 추가하지 않는다.
|
||||
- 구현 기본값: 이미지 치수를 알기 전 임의 원본 비율을 만들지 않는다. 최초 치수가 없으면 이미지 영역 높이를 예약하지 않고, 다음 이미지 로딩 중에는 마지막 확인된 높이를 유지할 수 있다. 로드 성공 후 현재 이미지 비율로 높이를 갱신한다.
|
||||
- 콘텐츠의 `AudioRecommendationsResponse.isEmpty`는 현재 기존 배열만 검사한다. 서브 배너만 있는 응답도 표시되도록 유효 서브 배너 목록을 빈 화면 판정에 포함해야 한다.
|
||||
- 기존 `BannerCarousel`은 최대 20개, 5초 timer, 좌우 여백이 있는 정사각형 이미지 및 우측 상단 page count 표시를 가진다. 따라서 그대로 재사용할 수 없다.
|
||||
|
||||
## 10. API 계약
|
||||
|
||||
### 10.1 공통 규칙
|
||||
|
||||
- 성공 조건: HTTP 200 및 `success == true`.
|
||||
- envelope: 기존 `success`, `message`, `data`, `errorProperty` 보존.
|
||||
- `data`의 기존 필드와 `banners` 보존, `subBanners` 추가.
|
||||
- `subBanners` 항목: `id: JSON integer`, `placement`, `imageUrl`, nullable `webUrl`, nullable `androidDeepLink`, nullable `iosDeepLink`.
|
||||
- 서버는 id 순서 및 날짜 자격 필터링을 담당한다. 이는 사용자가 구현 완료로 제공한 확정 계약이며 재확인으로 구현을 차단하지 않는다. 실제 서버 실행 검증 여부는 별도로 기록한다.
|
||||
- 공개 DTO에 `startDate`, `endDate`, `isActive`를 추가하지 않는다. 정상 빈 결과는 `subBanners: []`이며 삭제된 별도 endpoint의 오류 status는 가정하지 않는다.
|
||||
|
||||
### 10.2 Endpoint 추적
|
||||
|
||||
| 요구사항 | Method | Path | 계약 상태 |
|
||||
|---|---|---|---|
|
||||
| SB-001~020 | GET | `/api/v2/home/recommendations` | 사용자 제공 구현 완료 계약, placement HOME_RECOMMENDATION |
|
||||
| SB-001~020 | GET | `/api/v2/audio/recommendations` | 사용자 제공 구현 완료 계약, placement CONTENT_RECOMMENDATION |
|
||||
|
||||
## 11. 보안과 데이터 취급
|
||||
|
||||
- 사용자가 제공한 URL을 실행하기 전 scheme, host, 절대 URI 조건을 검증한다.
|
||||
- 금지 deep-link scheme을 대소문자 무관하게 차단한다.
|
||||
- URI를 임의 정규화하지 않으며 검증에 통과한 입력 문자열을 그대로 연다.
|
||||
- 인증 헤더는 로그인 여부에 따른 기존 정책을 유지한다.
|
||||
|
||||
## 12. 성능과 품질 요구사항
|
||||
|
||||
- 추가 요청 없이 기존 추천 응답에 포함된 목록을 쓴다.
|
||||
- 이미지 크기는 실제 이미지 치수로 계산하며 개별 로딩 실패는 추천 응답 오류로 승격하지 않는다.
|
||||
- 검증은 API 디코딩, 화면별 placement/순서, URL 판정 및 링크 fallback, 이미지·링크 실패 격리를 다룬 fixture 기반 테스트를 포함해야 한다.
|
||||
- 아래 수용 시나리오는 요구사항 검증 기준이며 아직 실행되지 않았다.
|
||||
- fixture 검증은 명시적인 테스트 모드에 한정한다. 운영 요청에서 fixture를 자동 사용하거나 통신 오류를 fixture로 대체하지 않는다.
|
||||
|
||||
## 13. 성공 기준
|
||||
|
||||
### 13.1 기능 수용 기준
|
||||
|
||||
- [x] 두 추천 endpoint의 production DTO/API/repository 검사에서 기존 필드·`banners`와 통합 `subBanners` 소비를 확인했다.
|
||||
- [x] HTTP 200과 `success == true`가 함께 만족될 때만 성공이며, false/비200 실패를 실제 VM 검사로 확인했다.
|
||||
- [x] 익명 Authorization 생략, 로그인 헤더 보존, 추가 endpoint/request/query 없음을 production API/repository 검사로 확인했다.
|
||||
- [x] `Int` 정밀도 및 23개 목록 순서 보존, native에서 21번째 항목 도달을 확인했다.
|
||||
- [x] placement 필터 분리와 빈 필터 결과를 검사하고 native composition에서 empty 추가 gap0을 확인했다.
|
||||
- [x] web http(s)+host 및 deep 절대 URI/금지 scheme을 production URL 검사로 확인했다.
|
||||
- [x] URI 원문 및 iOS/웹/Android 8조합의 opener 시도 순서·횟수를 driver로 확인했다. 실제 외부 앱 launch 검증과 구분한다.
|
||||
- [x] 이미지 실패 placeholder의 국소 처리와 링크 실패 종료를 native harness/production opener 검사로 확인했다.
|
||||
- [x] 실행하지 않은 physical input·실서비스·전체 앱 UI 검증은 별도 한계로 명시했다.
|
||||
|
||||
### 13.2 화면 수용 기준
|
||||
|
||||
- [x] 홈 위치는 최근 활동 크리에이터 다음이다.
|
||||
- [x] 콘텐츠 위치는 상단 배너 다음이자 독점 시리즈 이전이다.
|
||||
- [x] 섹션 간격은 기존 s24이며, 비어 있으면 컴포넌트와 추가 간격이 없다.
|
||||
- [x] native 양 화면에서 가로 전체 폭, 수평 padding 0, 두 원본 비율 높이를 확인했다.
|
||||
- [x] 기존 메인 배너 캐러셀의 코드·연결은 변경하지 않았다.
|
||||
|
||||
## 14. Open Questions
|
||||
|
||||
| ID | 상태 | 결정 필요 사항 | 현재 권고 | 결정 주체 | 결정 시점 |
|
||||
|---|---|---|---|---|---|
|
||||
| OQ-001 | 해소 | 전환 주기와 높이, 빈 목록 처리 | 사용자 승인: 5초 자동 전환, 현재 이미지 원본 비율 높이, 빈 목록 컴포넌트 미표시. swipe/단일 항목 timer 중지는 기존 패턴을 따른다. | 사용자 | 확인 완료 |
|
||||
| OQ-002 | 해소 | 이미지 로딩·실패 표시 | 기존 회색 placeholder 계열, 첫 치수 확인 전 임의 비율 없음, 개별 오류 격리. §9 구현 기본값 적용. | 구현 담당 | 구현 기본값 |
|
||||
| OQ-003 | 해소 | indicator의 표시 여부와 모양 | 기존 우측 상단 현재/전체 표시를 따른다. | 구현 담당 | 기존 패턴 |
|
||||
| OQ-004 | 부분 확인 | 정확한 섹션 spacing과 추천 요청의 인증·실패 처리 seam | 두 View의 VStack은 `SodaSpacing.s24`다. 두 API target의 익명 헤더 및 HTTP status 검증은 구현 단계에서 최소 수정한다. 공통 plugin 영향은 추가 코드 확인 대상이다. | 구현 담당, 사용자 질문 불필요 | 구현 전 |
|
||||
| OQ-005 | 해소 | 새 필드 fixture와 서버 정렬·날짜 필터링 계약 | 사용자 메시지에 계약과 홈 두 항목/빈 목록 및 콘텐츠 빈 목록 예시가 제공됐다. 실제 서버 검증과 계약 확정은 구분한다. | 구현 담당 | 확인 완료 |
|
||||
|
||||
## 15. Decision Log
|
||||
|
||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항 |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-10-29 | DEC-001 | 확정 | 기존 두 추천 응답에 `subBanners`를 추가하며 별도 조회는 하지 않는다. | 사용자 요구 | SB-001~004 |
|
||||
| 2026-10-29 | DEC-002 | 확정 | 링크 검증과 iOS 우선 후 웹 fallback을 요구하고 Android 링크를 사용하지 않는다. | 사용자 요구 | SB-016~018 |
|
||||
| 2026-10-29 | DEC-003 | 권고 | 5초 자동 순환 등 UX 기본값은 승인 전까지 제안으로만 둔다. | 기존 `BannerCarousel`의 5초 설정은 참고 근거이며 신규 UX 결정은 아님 | SB-014, OQ-001 |
|
||||
| 2026-10-29 | DEC-004 | 정정 대기 | 정확한 spacing/auth/error seam은 추가 코드 확인 전 확정하지 않는다. | 현재까지 조회한 추천 화면·API와 기존 carousel 코드 | OQ-004 |
|
||||
| 2026-10-29 | DEC-005 | 정정 | DEC-002의 링크 우선순위는 사용자 권고안이며 최종 설계 승인 전 확정으로 취급하지 않는다. URI 안전성과 Android 실행 금지는 확정이다. | 최초 요청의 계약/추천 구분 | SB-018 |
|
||||
| 2026-10-29 | DEC-006 | 정정 | 서버 정렬·기간 필터와 응답 예시는 이미 제공된 확정 계약이다. 기존 섹션 간격 s24 및 콘텐츠 isEmpty 수정 필요를 코드로 확인했다. | 사용자 계약, MainHomeRecommendationView:27, MainContentRecommendationView:28, AudioRecommendationsResponse:13 | SB-005, SB-012, OQ-004~005 |
|
||||
| 2026-10-29 | DEC-007 | 확정 | worktree를 만들지 않고 현재 작업 트리에서 구현. 5초 자동 전환, 현재 이미지 원본 비율 높이, 빈 목록 컴포넌트 미표시. | 사용자 후속 답변 | SB-010, SB-014, 실행 방식 |
|
||||
| 2026-10-29 | DEC-008 | 확정 | 나머지는 기존 패턴과 사용자 추천안을 기본값으로 적용: swipe/페이지 카운터, 단일 항목 timer 없음, iOS 우선 후 웹 1회 대체, 실패 팝업 추가 없음. | 사용자 진행 승인 및 기존 앱 패턴 지시 | SB-014, SB-018, OQ-002~003 |
|
||||
|
||||
## 16. 요구사항 추적표
|
||||
|
||||
| 요구사항 범위 | API 계약 | 계획 Phase | 자동 검증 | 수동 검증 |
|
||||
|---|---|---|---|---|
|
||||
| SB-001~005 | §10 | 미작성 | fixture에서 envelope, status, auth, 요청 횟수 확인 | 요청 헤더와 추가 호출 없음 확인 |
|
||||
| SB-006~010 | §10 | 미작성 | lossless 정수, placement, 순서, empty gap 검사 | 두 화면에서 분리 및 빈 상태 확인 |
|
||||
| SB-011~015 | §8.3 | 미작성 | 21개 이상 입력 및 배치 검증 | 폭·비율·섹션 위치 확인 |
|
||||
| SB-016~020 | §8.4 | 미작성 | URL 안전성, 원문 보존, fallback 횟수, 실패 격리 | 실제 대상 없이 mock open 결과로 흐름 확인 |
|
||||
|
||||
## 17. 변경 관리
|
||||
|
||||
- UX 결정이 추가되면 먼저 Open Questions와 Decision Log를 갱신한다.
|
||||
- 사용자 답변과 설계 승인을 PRD에 반영한 뒤 계획/TASK 문서를 작성한다. 이미 제공된 서버 계약을 다시 요청하지 않는다.
|
||||
- 이 문서의 수용 시나리오는 아직 실행하지 않았으며 구현 완료를 뜻하지 않는다.
|
||||
|
||||
## 탐색 검증 기록
|
||||
|
||||
- 2026-10-29: 기존 carousel, 두 추천 화면, 콘텐츠 DTO 및 관련 문서 샘플을 읽고 요구사항을 대조했다. 탐색 결과 중 기존 메인 배너 이동·정사각형 재사용 제안은 사용자 조건과 충돌하여 채택하지 않았다.
|
||||
- Xcode 26.0과 swift/xcrun은 사용 가능하다. 부팅된 시뮬레이터는 없으며 빌드·테스트·네이티브 QA는 미실행이다. 현재 도구 목록에 `lsp_diagnostics`가 없어 해당 검증을 수행했다고 주장하지 않는다.
|
||||
- 제품 코드 수정 없이 PRD만 작성했다. 5초 자동 전환과 서로 다른 원본 비율에 따른 현재 배너 높이 변경 질문에 사용자 답변 대기 중이다.
|
||||
|
||||
## 구현·검증 후속 기록
|
||||
- 2026-10-30: 위 탐색 기록 이후 사용자 답변 DEC-007과 실행 기준 DEC-008에 따라 구현했다. 데이터/API/opener 집중 checks, 전체16개 local scripts, 양 앱 Debug simulator build가 통과했다.
|
||||
- 최신 native 증거는 [native-final/README.md](reviews/evidence/native-final/README.md)다. 최종 캐러셀 hash `7e25db3076d545ffc61ea3a1c5585e7b8a617964ef406d7570286d75a75e010a`와 snapshot·실행 전후 해시가 일치한다. 이전 native 증거는 최종 gesture 변경 전 기록으로 보존한다.
|
||||
- 네이티브 검증은 실제 production 두 View composition/캐러셀과 주변 대역을 사용하는 명시 harness다. physical tap/swipe, 실제 외부 앱 열기, Reduce Motion ON, 실서비스·실기기·전체 앱 UI는 미검증이며 LSP 도구는 제공되지 않았다.
|
||||
- 독립 리뷰의 소스/증거 불일치는 최신 native 재실행으로 해소했다. 최종 판정 APPROVE, 남은 차단 항목 없음. worktree 없이 구현했고 소유 QA 자원은 정리했다.
|
||||
|
||||
## 후속 버그: 스와이프 중 배너 탭 실행 (2026-10-30)
|
||||
- 사용자 재현: 다음 이미지로 넘기려고 드래그했지만 이전 이미지의 링크가 실행된다.
|
||||
- `SB-021` 확정: 배너를 넘기는 수평 드래그와 인식된 짧은 드래그·세로 스크롤은 링크를 실행하지 않는다. 정상 탭은 현재 표시 항목을 정확히 한 번 실행하며 스와이프 직후 탭도 유실하지 않는다.
|
||||
- 원본 비율·5초 자동 전환·전체 목록·빈 영역 숨김·두 화면 배치·링크 정책은 변경하지 않는다. worktree 없이 기존 작업에 이어 수정한다.
|
||||
- 기존 native 증거는 physical tap/swipe 미검증이었다. 이번 검증은 실제 XCTest 좌표 tap/drag를 사용하며 스크립트가 state/opener를 직접 호출한 결과로 대체하지 않는다.
|
||||
- `SB-022` 확정: 마지막→첫 번째 및 첫 번째→마지막 순환도 일반 전환과 같은 방향의 연속 슬라이드로 표시한다. 경계에서 전체 목록을 역방향으로 빠르게 이동하거나 순간 교체하지 않는다. 자동 5초 전환과 수동 swipe 모두 적용한다.
|
||||
- 추가 사용자 보고의 코드 원인: `move(by:)` 경계 분기가 `disablesAnimations = true`로 실제 마지막/첫 번째 사이 이동 자체를 수행한다. 기존 main BannerCarousel처럼 복제 경계 항목으로 애니메이션 후 동일 이미지 위치로만 무애니메이션 정렬하는 방식을 참고한다.
|
||||
- 후속 완료: primitive button style의 배타적인 drag/tap 인식과 전역 drag 좌표, 복제 경계 페이지의 연속 slide/동일 이미지 정렬로 수정했다. 실제 swipe 오탭 RED 및 native11개 GREEN, 자동/수동 경계 중간 프레임 확인. 부모도 native11개·기존16개 check·양 앱 build 통과를 확인했다. [Phase3 리뷰](reviews/phase3-sub-banner-gesture.md) APPROVE.
|
||||
|
||||
## 후속 사용자 결정: 서브 배너 카운트 숨김 (2026-10-30)
|
||||
- DEC-009 확정: 홈·콘텐츠 공용 `SubBannerCarousel`의 우측 상단 현재/전체 배지만 제거한다. §9, OQ-003 및 DEC-008의 시각적 페이지 카운터 기준은 이 결정으로 대체한다.
|
||||
- 접근성 페이지/전체 라벨, 탭·스와이프·연속 순환·5초 타이머·레이아웃과 기존 메인 `BannerCarousel`은 그대로 유지한다. 순수 렌더링 삭제이므로 기존 UI typecheck와 소규모 native XCTest/PNG로 검증하며 새 테스트나 전체 앱 빌드는 추가하지 않는다.
|
||||
Reference in New Issue
Block a user