docs(mypage): 공지 롤링 요구사항과 검증을 기록한다

This commit is contained in:
Yu Sung
2026-10-28 17:15:48 +09:00
parent 42bdfb8779
commit e0e59e5acf
2 changed files with 323 additions and 0 deletions
@@ -0,0 +1,197 @@
# 마이 탭 공지사항 롤링 구현 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | 사용자 계약·정상 동작 확인 / 제목 전용 롤링 보완 완료 |
| 작성일 | 2026-10-28 |
| 요구사항 / API 기준 | `prd.md` / §11 |
| 현재 Phase | 1 |
| 현재 활성 Goal | 없음 |
## 목표
마이 탭에서 최대 3개 공지를 서버 순서로 읽고 현재 공지의 상세 화면에 진입할 수 있다.
## 현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---|---|---|---|---|
| 1 | 후속 UI 보완 완료 | 4/4 | 없음 | 이번 턴 실제 터치·VoiceOver·Reduce Motion 설정 재검증은 생략 |
## 범위
포함: 신규 공개 배열 API, 기존 배너 문구 제거·제목 확장·5초 롤링, 기존 상세 이동, 0/1/2/3 목 회귀, 미사용 구형 클라이언트 API 제거.
제외: 서버 수정·구형 서버 endpoint 삭제, 홈·공지 목록 동작 변경, 페이지 표시기·스와이프, 공통 인증 변경, 신규 의존성.
## 기술적 제약
- iOS 16.6, SwiftUI/Combine/Moya 및 기존 View→ViewModel→Repository→Api 구조.
- 기존 `UpdateBannerView`를 수정한다. 테스트 가능한 상태가 필요하면 작은 모델만 `V2/MyPage` 아래 추가한다. 별도 범용 캐러셀은 만들지 않는다.
- `NoticeItem`, `ApiResponse`와 상세 본문/date 처리를 재사용한다. 기존 공지 목록이 id 없이 디코딩되는 동작을 깨지 않는다.
- 공개 요청에는 Authorization을 보내지 않는다. 기존 목록 요청과 공통 플러그인은 유지한다.
- 오류 시 기존 공지를 보존하고, 정상 빈 배열만 숨김 처리한다.
- `work/tests`의 실제 소스를 컴파일하는 self-check 패턴을 사용한다. 소스 문자열 검사만으로 동작 통과를 주장하지 않는다.
## Phase 1
**Phase 결과:** 공개 최신 공지 배열과 롤링 배너가 연결되고 회귀 검증 근거가 남는다.
**선행조건:** 사용자 계약, 조사 완료, ulw의 최소 해석 진행 지시.
### Task 1.1 배열 API와 기존 배너 연결
**Goal 실행 `P1-T1`:** 0/1/2/3 공지를 올바르게 표시·순환하고 선택 항목을 기존 상세로 전달한다.
**Files:**
- Modify: `SodaLive/Sources/Settings/Notice/NoticeApi.swift`, `NoticeRepository.swift`
- Modify: `SodaLive/Sources/MyPage/MyPageViewModel.swift`, `MyPageView.swift`
- Conditional modify: `SodaLive/Sources/Settings/Notice/GetNoticeResponse.swift` (id를 보존할 경우 기존 payload 호환 유지)
- Conditional create: `SodaLive/Sources/V2/MyPage/Models/MyPageNoticeRoller.swift` (실제 UI가 사용하는 최소 상태)
- Modify when adding the model: `SodaLive.xcodeproj/project.pbxproj` (두 앱 타깃의 명시적 소스 등록)
- Test: `work/tests/MyPageNoticesCheck.swift`, `work/tests/check-my-page-notices.sh`
- Modify test fixture: `work/tests/AuthContentSettingsExitCheck.swift`, 필요한 경우 해당 runner
**Interfaces:** `getLatestNotices()`, `latestNotices: [NoticeItem]`, 기존 `.noticeDetail(notice:)`.
- [x] **RED:** 실제 API/VM/표시 상태를 사용하는 0/1/2/3 및 nullable envelope·오류·순서·선택 대상 검사를 작성했다.
- [x] **RED 확인:** 신규 API 검사가 `FAIL: Latest notices must use public v2 array endpoint; got /notice/latest`와 exit 1로 실패했다.
- [x] **GREEN:** v2 공개 GET, 배열 상태, 기존 배너 제목 폭·화살표·5초 세로 롤링을 구현했다. 생명주기 정책의 native 발견 회귀는 아래 P1-R1로 추적한다.
- [x] **GREEN 확인:** 같은 검사와 `bash work/tests/check-auth-content-settings-exit.sh`가 exit 0이다.
- [x] **REFACTOR:** 신규 상태는 25줄로 제한하고 구형 API 호출 0건을 확인했다. 관련 없는 변경 없음.
### Task 1.2 검증·리뷰·외부 조건 기록
**Goal 실행 `P1-GATE`:** 변경·검증·미확인 사항을 근거와 함께 인계한다.
**Files:** Phase 1 변경 파일, 이 문서, `prd.md`, `reviews/phase1-my-page-notices.md`.
**TDD 예외 사유:** 검증 및 문서화 Task로 제품 동작을 추가하지 않는다.
**대체 검증 방법:** 실제 명령 결과, native QA와 리뷰 근거를 기록한다.
- [x] 모든 `work/tests/check-*.sh` 13개 실행 PASS.
- [x] 두 앱 스킴의 iOS/시뮬레이터 빌드 PASS. LSP 도구 미제공을 별도 기록.
- [x] native 개수·긴 제목·순환·생명주기 확인, 선택 payload 검사와 실제 탭 미검증의 경계를 명시.
- [x] 독립 리뷰의 두 회귀를 신규 P1-R1로 수정하고 재리뷰 APPROVE.
- [x] 소유 임시 자원 정리·부재 확인, 검증 및 남은 외부 조건 기록.
### Task 1.3 리뷰 회귀 수정
**Goal P1-R1:** 기존 배너 전체 클릭 영역을 복원하고 새 scenePhase에 맞춰 롤링을 즉시 중단·재개한다.
- Review: `reviews/phase1-my-page-notices.md`, REV-P1-001/002.
- Modify: `SodaLive/Sources/MyPage/MyPageView.swift`의 배너만.
- Evidence: 기존 `reviews/evidence/native/`는 RED 증거로 유지, 새 검증은 별도 후속 폴더에 기록.
- [x] **RED:** 기존 여백의 Button 외부 배치와 native lifecycle 실패를 확인했다.
- [x] **RED 확인:** native probe에서 inactive 전달 후 제목 변경, active 복귀 후 7초 이상 미변경을 두 실행에서 재현했다.
- [x] **GREEN:** padding을 Button label 내부로 이동하고 새 phase/접근성 값을 명시적 함수 인자로 전달했다.
- [x] **GREEN 확인:** 동일 native 하네스로 0/1/2/3·긴 제목·생명주기·OS 복귀를 재실행하여 통과했다. 실제 좌표 탭은 별도 미검증이다.
- [x] **REFACTOR:** 관련 없는 변경 없이 최종 빌드·코드 재리뷰 APPROVE와 실제 탭 검증 한계를 기록했다.
### Task 1.4 화살표 고정·제목 전용 롤링
**Goal P1-T2:** 배너와 화살표는 고정한 채 제목만 동일한 세로 롤링으로 교체한다.
- Modify: `SodaLive/Sources/MyPage/MyPageView.swift`의 `UpdateBannerView` 표시 구조만.
- 유지: API/상태 모델/5초 타이머/생명주기/전체 배너 클릭 영역/글꼴·색상·여백.
- TDD 예외: UI 전환 modifier의 적용 범위 변경이며 신규 비즈니스 로직이 없다. 소스 문자열만 검사하는 테스트를 추가하지 않는다.
- 대체 검증: 기존 native 하네스를 재사용해 전환 전·중·후의 화살표 영역 불변 및 제목 변경을 확인하고, 기존 `check-my-page-notices.sh`와 앱 빌드를 실행한다.
- [x] 제목에만 identity·transition·clipping을 적용하고 고정된 버튼·화살표의 구조를 유지했다.
- [x] native 10프레임의 화살표 변경 픽셀 0, wrap 중간 제목 이동을 확인했다.
- [x] 전체 13개 회귀 검사·dev simulator 빌드·비교 검사·리뷰 APPROVE 및 확인 한계를 기록했다.
### 검증 시나리오 (기존)
| 시나리오 | 이진 통과 기준 | 증명 표면 |
|---|---|---|
| S1 개수 | 0은 미표시, 1은 시간이 지나도 같은 항목 | 실제 상태 드라이버 + native UI |
| S2 순서/클릭 | 2/3개가 서버 순서로 마지막→처음 반복, 현재 payload가 클릭 대상 | 실제 상태/VM 목 검사 + native UI |
| S3 레이아웃 | 자세히 없음, 화살표 있음, 긴 한글 제목과 겹침 없음 | native 렌더링 |
| S4 경계/회귀 | message/errorProperty 누락/null 허용, date/HTML 원문, 실패 시 보존, 새 빈 배열 숨김, 기존 목록·홈 미변경 | 실제 디코딩·VM 목 검사 + diff/빌드 |
| S5 생명주기 | 화면 이탈·앱 비활성 시 중단, 재진입 시 중복 롤링 없음 | native 실행 + 코드 검토 |
```bash
bash work/tests/check-my-page-notices.sh
bash work/tests/check-auth-content-settings-exit.sh
for check in work/tests/check-*.sh; do bash "$check"; done
xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build
xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build
```
**Expected:** 관련 검사·빌드 exit 0, native 동작 확인. 서버 배포/401과 테스트 타깃 부재는 별도 제한으로 남기며 통과로 표시하지 않는다.
## 실행 순서와 의존성
`조사 완료 → P1-T1(단일 UI 구현자) → P1-GATE(검증/읽기 전용 리뷰) → 소유 자원 정리`
## 변경 금지 항목
테스트 삭제·완화, 구형 API 자동 fallback, 공통 인증 플러그인 수정, 서버 배포/삭제, 기존 사용자 변경 덮어쓰기.
## Progress
### 조사 — 2026-10-28
- 현재 `/notice/latest` 호출자는 마이 탭 한 곳. `ApiResponse`의 공통 nullable 필드는 이미 optional.
- `NoticeItem`은 현재 id 없이 title/content/date만 가진다. 기존 목록·상세 호환이 우선이다.
- V2 메인과 구형 메인 모두 MyPage를 선택 중일 때만 생성한다. V2 배너의 기존 간격은 5초.
- 기준 검사 `bash work/tests/check-auth-content-settings-exit.sh` exit 0. 실제 MyPageViewModel을 컴파일하므로 plural 상태 변경 시 fixture도 갱신해야 한다.
- `xcodebuild -version`: Xcode 26.0 (17A324). XCTest 번들 타깃 없음, booted simulator 없음.
- `curl --max-time 20 https://test-api.sodalive.net/api/v2/notices/latest` 무인증 요청: HTTP 401. 본문을 저장소에 보관하지 않음.
### P1-GATE 중간 검증 — 2026-10-28
- 부모가 `bash work/tests/check-my-page-notices.sh`를 직접 실행, exit 0. 실제 API/Repository/VM/DTO/롤링 상태를 컴파일하고 전송 계층만 격리했다. 0/1/2/3, 서버 순서·반복·선택 payload, HTML/date 원문, 공통 nullable 필드 누락/null, 배열 교체·축소, 오류 보존, main-thread 발행을 확인했다. 실제 물리 탭 검증은 아니다.
- `for check in work/tests/check-*.sh; do bash "$check"; done` 전체 13개 스크립트 PASS, 실패 0. 기존 인증 mock의 Debug/Release 심볼 검사 포함.
- `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -destination 'generic/platform=iOS' -disableAutomaticPackageResolution -skipPackageUpdates CODE_SIGNING_ALLOWED=NO build` exit 0.
- 위 명령의 `-scheme SodaLive` 빌드도 exit 0. 서명 없는 iOS 컴파일이며 시뮬레이터/실기기에서 전체 앱을 실행한 증거와 구분한다.
- 초기 별도 DerivedData 빌드는 SPM 해석 중 120초 도구 timeout. 기존 workspace 캐시를 재사용한 첫 시도는 구현 중 신규 상태 파일의 타깃 등록 전이라 실패했고, 등록 후 두 스킴 모두 통과했다. 공유 캐시는 삭제하지 않는다.
- `git diff --check` exit 0. LSP 진단 도구는 제공되지 않아 실행하지 못했으며 빌드 결과와 구분한다.
- 네이티브 배너 하네스 및 독립 리뷰는 진행 중이다.
- 추가 검증: `SodaLive-dev`, `-sdk iphonesimulator -destination 'generic/platform=iOS Simulator'`와 기존 캐시/패키지 고정 옵션으로 시뮬레이터 빌드 exit 0.
- `rg -n 'getLatestNotice\b|latestNotice\b|/notice/latest' SodaLive/Sources work/tests` 결과 0건. 기존 홈 소스는 변경하지 않았다.
- 빌드의 기존 중복 소스 등록·Alamofire/BootpayUI 의존성 경고는 신규 롤러와 관계없어 수정하지 않았다.
- 추가 검증: `SodaLive`의 iOS Simulator 빌드도 exit 0. 두 스킴의 iOS/Simulator 컴파일이 모두 통과했다.
- 최초 native QA: 개수·순서·wrap·긴 제목·remount·축소 PASS, inactive/active S5 FAIL. 독립 코드 리뷰는 기존 배너 padding 클릭 영역 축소를 확인했다. 완료로 판정하지 않고 P1-R1을 등록했다.
### P1-R1 후속 검증 — 2026-10-28
- 변경 원인: onChange의 새 phase를 버리고 이전 환경 값을 캡처한 함수 경로를 사용했다. phase·Reduce Motion을 명시적 함수 인자로 전달하여 새 상태로 판단한다. 좌우24·상하6 여백은 Button label 내부로 옮겨 기존 전체 배너의 contentShape를 복원했다.
- 제품 수정은 기존 `UpdateBannerView` 내부에 한정. 최종 MyPageView SHA-256: `7f8ac15573ab4c9fe01508431297e40d2e95a22ad37585859f03224475749d02`.
- 새 [native 증거](reviews/evidence/native-fix/README.md): 기존 RED 하네스를 그대로 사용한 새 36 PNG. inactive에서 둘째 유지, active 복귀 후 셋째 진행, OS 복귀 후 진행을 확인했다. 정적 긴 제목 캡처는 RED와 동일해 geometry/style이 유지됐다.
- 부모가 후속 lifecycle/긴 제목 PNG를 직접 열어 확인했다. 별도 실제 상태 helper+추출 callback 드라이버를 직접 컴파일·실행하여 title/content/date payload 동일성 10건과 빈 상태 검사를 확인했다. 물리 탭/전체 상세 이동 검증으로 간주하지 않는다.
- 수정 후 전체 `work/tests/check-*.sh` 13개 재실행 PASS, 실패 0.
- 수정 후 `SodaLive` iOS 및 iOS Simulator 빌드 exit 0. 연속 빌드 실행이 총 600초 timeout으로 dev 단계에서 중단됐으나, `SodaLive-dev` iOS와 Simulator를 각각 재실행해 모두 exit 0. 최종 두 스킴·두 플랫폼의 컴파일이 확인됐다.
- 수정 후 `git diff --check` exit 0. LSP 미제공, 실제 좌표 탭·VoiceOver·Reduce Motion 설정 변경·전체 앱/실서버 연동은 미검증으로 유지한다.
- 부모 작업 TMP `my-notice-ulw.TGNyMy`와 durable note를 명령 결과 기록 후 삭제하고 `test ! -e`로 부재를 확인했다. 공유 Xcode 캐시는 보존했다. native-fix의 별도 소유 자원 정리 영수증은 최종 검토 시 확인한다.
- 최종 Oracle 재리뷰 APPROVE, native 하네스 범위 시각 PASS. 두 회귀 해결과 실제 탭 미검증은 `reviews/phase1-my-page-notices.md`에 누적했다.
- QA worker 연결 실패로 fallback이 동일 코드로 추가 재실행했다. 정리 영수증 확인 후 부모가 잔여 첫 worker TMP를 제거했고, `pgrep` 프로세스 부재·`simctl` booted device 없음·`test ! -e` 소유 TMP 4개 부재를 확인했다.
- 최종 남은 조건: 테스트 서버 공개 GET의 401 해소 및 정상 실제 응답 검증, 실제 앱에서 공지/배너 여백 탭→해당 상세 이동. 서버 endpoint 삭제는 이전 앱·타 클라이언트 사용 여부 확인 후 서버 담당이 판단한다. 코드·로컬 검증 완료를 출시/전체 E2E 완료로 표시하지 않는다.
### P1-T2 사용자 확인 및 후속 보완 — 2026-10-28
- 사용자가 계약·정상 동작·롤링 애니메이션 정상 동작을 확인했다. 최초 401 측정은 과거 기록으로 유지하되 현재 미해결 계약으로 다시 보고하지 않는다. 환경별 응답을 에이전트가 재호출한 것은 아니다.
- 추가 요청은 화살표를 고정하고 제목만 롤링하는 것으로 한정한다. 기존 표시/클릭/타이머/데이터 계약은 유지한다.
- 변경 전 기준 `bash work/tests/check-my-page-notices.sh` exit 0. UI-only 전환 범위 변경이므로 신규 소스 문자열 테스트 대신 실제 native 전환 프레임으로 검증한다.
- 후속 구현은 기존 Button의 `.id`/`.transition`을 제목 Text로 이동하고 제목만 ZStack에서 clip하는 변경이다. 화살표·버튼·padding·타이머·API는 유지했다.
- 부모 실행: 전체 `work/tests/check-*.sh` 13개 PASS, 실패 0. `SodaLive-dev` Debug, generic iOS Simulator, no-sign 빌드 exit 0 (`-disableAutomaticPackageResolution -skipPackageUpdates`). `git diff --check` exit 0.
- 이번 변경 소스 SHA-256: `af77569942c0bbdbad0242431362b5987dc278d8f70d015ccc1ef675b2b264ed`. native 전환 프레임 검증 및 범위 한정 리뷰 진행 중.
- 최종 native 증거: [title-only](reviews/evidence/title-only/README.md). 부모가 비교판을 직접 열고 `python3 docs/20261028_마이_탭_공지사항_롤링/reviews/evidence/title-only/compare.py`를 재실행해 exit 0을 확인했다. 10장 화살표 변경 픽셀 0, wrap rest→mid 3,857px 및 mid→end 3,796px 제목 변경, 단일 공지 고정·긴 제목 비겹침·배너 밖 제품 소스 불변을 확인했다.
- 독립 범위 한정 리뷰 APPROVE. reviewer도 실제 비교판을 열어 제목만 전환됨을 확인했다. 실제 좌표 탭·VoiceOver·Reduce Motion 런타임은 이번 턴 재검증하지 않았으며 기존 코드 경로를 유지했다. LSP 도구는 미제공이다.
- native 소유 프로세스·device·TMP 부재 영수증은 `reviews/evidence/title-only/cleanup.json`에 보존했다. 부모 임시 루트·노트도 제거하고 `test ! -e`로 두 소유 TMP 루트의 부재를 확인했다. 커밋·서버 호출은 하지 않았다.
## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 |
|---|---|---|---|---|
| 2026-10-28 | PLAN-001 | 확정 | 계획 에이전트 제안에서 별도 캐러셀 UI 생성을 생략하고 기존 배너 수정으로 축소 | 요청의 기존 UI 재사용, 최소 변경 |
| 2026-10-28 | PLAN-002 | 확정 | 사용자 제공 envelope를 그대로 사용, 5초 간격·세로 전환·기존 오류 보존 | PRD DEC-001/002 및 사용자 명시 계약 |
| 2026-10-28 | PLAN-003 | 확정 | 사용자가 계약·정상 동작·롤링 정상 확인. 추가 변경은 화살표 고정과 제목 전환 범위로 한정 | PRD DEC-005, P1-T2 |
## 발견된 문제
| ID | 심각도 | 상태 | 발견 내용 | 처리 계획 |
|---|---|---|---|---|
| EXT-001 | High | 사용자 확인으로 해소 | 최초 테스트 서버 호출은 HTTP 401이었으나 후속 메시지에서 계약·정상 동작 확인 | 과거 측정 기록 보존. 에이전트가 이번 턴 실서버 재측정했다고 주장하지 않음 |