21 KiB
이벤트 상세 API 연동 실행 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | 클라이언트 구현·로컬 검증 완료 |
| 작성일 | 2026-10-29 |
| 요구사항 기준 | prd.md |
| API 기준 | api-contract.md |
| 현재 Phase | 1 상세 API와 ID-only 탐색 |
| 현재 활성 Goal | 없음 |
목표
기존 진입점에서 ID로 이벤트 최신 상세를 안전하게 조회하고 참여한다.
현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 남은 조건 |
|---|---|---|---|---|
| 1 | 완료 | 3/3 | 없음 | 실화면 QA는 도구 부재로 미실행 |
범위와 기술적 제약
실제 EventApi/EventRepository/EventItem, 신규 V2 VM, 기존 상세 View 및 7개 탐색 호출만 변경한다. Swift/Combine/Moya와 기존 UI 스타일을 유지하며 dependency·테스트 타깃·공통 인증 변경은 없다. 딥링크 parser와 사용자 지시에 따른 실서버 테스트는 제외한다. 상위 실행자가 양 스킴 빌드·전체 CLI 회귀·단일 리뷰를 완료했다.
Phase 1
Phase 결과: ID만으로 상세를 조회하고 안전한 참여 링크를 제공한다. 선행조건: 사용자 확정 계약, 가이드/샘플과 기존 notice CLI seam 확인 완료.
Task 1.1 API·상태·생명주기
Goal 실행 P1-T1: 실제 production API/repository/VM/DTO가 응답·취소·링크 계약을 만족한다.
완료 증거: focused CLI RED/GREEN 및 직접 production driver 실행 결과.
Files:
-
Create:
work/tests/EventDetailCheck.swift,work/tests/check-event-detail.sh,SodaLive/Sources/V2/Settings/Event/EventDetailViewModel.swift -
Modify:
SodaLive/Sources/Settings/Event/EventApi.swift,SodaLive/Sources/Settings/Event/EventRepository.swift -
Consumes: 기존
EventItem,ApiResponse,ApiResponseWithoutData, provider plugin. -
Produces:
getEventDetail(id:),EventDetailViewModel(eventId:), loading/loaded/unavailable/error와 일회성 dismissal 소비. -
RED: 실제 production 파일을 컴파일하는 notice 방식 CLI를 먼저 작성한다.
-
RED 확인:
bash work/tests/check-event-detail.sh로 신규 인터페이스 미구현 실패를 기록한다. -
GREEN: status-first·success-first decoding, 안전 URL, 취소/요청 중복 제어를 최소 구현한다.
-
GREEN 확인: 동일 CLI에서 요청/토큰/64-bit/비양수/nullable·malformed/HTTP/transport/main/cancel/중복 검사를 통과한다.
-
REFACTOR: 신규 중복만 정리하고 focused regression을 재실행한다.
Task 1.2 View·ID-only 라우팅·타깃 등록
Goal 실행 P1-T2: 모든 기존 진입점에서 ID를 전달하고 기존 외형으로 상세를 표시한다.
시작 조건: P1-T1의 VM 계약.
완료 증거: ID-only source 보완 검사, Swift parse, pbxproj 검증.
Files:
-
Modify:
SodaLive/Sources/App/AppStep.swift,SodaLive/Sources/ContentView.swift,SodaLive/Sources/Settings/Event/EventDetailView.swift,SodaLive.xcodeproj/project.pbxproj -
Modify:
SodaLive/Sources/Settings/Event/EventListView.swift,SodaLive/Sources/Main/EventPopupDialogView.swift,SodaLive/Sources/Live/EventBanner/SectionEventBannerView.swift,SodaLive/Sources/Content/Main/Banner/ContentMainBannerView.swift,SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift,SodaLive/Sources/V2/Main/MainView.swift -
RED: CLI 보완 검사로 payload route 잔존을 검출한다.
-
RED 확인: 기존 payload route assertion 실패를 기록한다.
-
GREEN: StateObject, BaseView/toast, scoped dismiss와 optional image/참여 Button을 연결한다. 7개 호출과 양 target membership을 갱신한다.
-
GREEN 확인: CLI 보완 검사와
swiftc -frontend -parse,plutil -lint성공을 확인한다. -
REFACTOR: 기존 외형·선택·인증 분기의 불필요한 변경이 없는지 diff를 대조한다.
Task 1.3 검증 runner와 최종 기록 정리
Goal 실행 P1-T3: REV-P1-001/002의 비차단 지적을 해결하고 반복 가능한 로컬 검증 결과를 남긴다.
- Files:
work/tests/check-event-detail.sh,prd.md,plan-task.md,reviews/phase1-event-detail-api.md. - RED:
env -u TMPDIR bash work/tests/check-event-detail.sh가 unset 변수로 exit 1임을 확인했다. runner에 EXIT trap이 없음을 확인했다. - GREEN: 기존 notice runner의
mktemp -d와 EXIT trap을 적용한다. - GREEN 확인: TMPDIR 제거 실행과 격리된 TMPDIR의 성공/실패 실행 후 생성 디렉터리 부재를 확인한다.
- REFACTOR: 추가 앱 코드 변경 없이 최신 빌드·전체 회귀·리뷰 결과를 문서에 누적하고 소유 임시 자원을 정리한다.
Phase 1 Gate (최초 구현 인계 기록)
Goal 실행 P1-GATE: 집중 검증과 문서 결과를 인계한다.
- 두 Task 완료 증거와 RED/GREEN 기록.
bash work/tests/check-event-detail.shexit 0.- 영향 회귀
bash work/tests/check-my-page-notices.shexit 0. - 양 앱 타깃 등록·Swift parse·diff 공백 검사 통과.
- 빌드·시뮬레이터 QA·최종 리뷰 미실행을 명시하고 상위 실행자에 인계.
실행 순서와 의존성
P1-T1 → P1-T2 → P1-GATE. 서브에이전트/commit/push/별도 worktree는 사용하지 않는다.
Progress
계약·설계 확인 1차 실행 - 2026-10-29
- 상태: 완료.
- 무엇을: 저장소 가이드, 문서 샘플, 기존 notice CLI, EventItem/API/Repository/View, 전체 진입점, AppState.back과 AuthPlugin을 확인했다.
- 왜: signed 64-bit ID-only와 false-first/status-first 계약, scoped dismiss의 실제 경계를 확정하기 위해서다.
- 어떻게: read 및 rg로 실제 파일을 대조했다. 기존 UI/토큰 10개 이상을 확인하고 레거시 상세 외형을 그대로 보존하기로 했다.
- 남은 항목: 테스트부터 작성하고 실행한다.
RED 1차 실행 - 2026-10-29
bash work/tests/check-event-detail.sh: exit 1. 신규 VM 입력 파일이 아직 없어 컴파일 실패했다. 이는 인터페이스 준비 단계의 RED이며 동작 assertion 증거와 구분한다.swiftc -swift-version 5 -parse-as-library -module-cache-path "$TMPDIR/event-route-module-cache" -D EVENT_ROUTE_CHECK work/tests/EventDetailCheck.swift -o "$TMPDIR/check-event-routes-red" && "$TMPDIR/check-event-routes-red" "$PWD": exit 1,FAIL: event detail route still retains payload instead of Int ID.- 다음 행동: API 인터페이스와 최소 VM 선언을 추가해 실제 VM 동작 assertion의 RED를 확인한다. 이후에만 VM 상태 로직을 구현한다.
RED 2차 실행 - 2026-10-29
bash work/tests/check-event-detail.sh: exit 1,FAIL: Loading must deduplicate.- API 선언과 VM 인터페이스만 준비한 실제 production VM에 대해 native Combine driver를 실행했다. 요청 경로·헤더 검사는 PASS했고 상태 로직 미구현 assertion이 실패했다. 테스트를 완화하지 않고 VM 동작 구현으로 진행한다.
GREEN 및 P1-GATE 실행 - 2026-10-29
- 상태: 완료. 이벤트 API/Repository/VM/DTO를 native Combine으로 실제 실행하는 CLI와 직접 영향 notice 회귀를 실행했다.
- 왜: 서버 payload와 무관한 ID-only 진입, 익명 헤더, 응답 상태 분류와 이탈 안전성을 검증하고 기존 공유 envelope 동작이 유지되는지 확인했다.
- 모든 shell 검증에 다음 환경을 사용했다. 생성된 event-detail 디렉터리와 route RED 실행 파일은 이 TMPDIR 내부에 보존했고 정리는 상위 실행자 소유다.
export TMPDIR=/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/tmp.UN5uRreooU
export NODE_COMPILE_CACHE=$TMPDIR/node-compile-cache
export BUN_RUNTIME_TRANSPILER_CACHE_PATH=$TMPDIR/bun-transpiler
bash work/tests/check-event-detail.sh
bash work/tests/check-my-page-notices.sh
swiftc -frontend -parse SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/Settings/Event/EventApi.swift SodaLive/Sources/Settings/Event/EventRepository.swift SodaLive/Sources/Settings/Event/EventDetailView.swift SodaLive/Sources/Settings/Event/EventListView.swift SodaLive/Sources/Main/EventPopupDialogView.swift SodaLive/Sources/Live/EventBanner/SectionEventBannerView.swift SodaLive/Sources/Content/Main/Banner/ContentMainBannerView.swift SodaLive/Sources/Content/Main/V2/ContentMainBannerViewV2.swift SodaLive/Sources/V2/Main/MainView.swift SodaLive/Sources/V2/Settings/Event/EventDetailViewModel.swift work/tests/EventDetailCheck.swift
plutil -lint SodaLive.xcodeproj/project.pbxproj
bash -n work/tests/check-event-detail.sh
GIT_MASTER=1 git diff --check
각 명령 exit 0. 이벤트 CLI 실제 출력:
PASS: actual API/repository exact GET/requestPlain, anonymous/bearer, legacy routes/headers, >32-bit and Int.max
PASS: actual DTO/VM single object, optional omitted/null, image-only/link-only/both/neither, 64-bit IDs, main delivery, no refetch
PASS: nonpositive no request; false ignores malformed data/localized message; malformed success/HTML503/abnormal HTTP/401/transport stay error, loading ends, one dismissal
PASS: actual participation URL HTTP(S)+host boundary; cancel/queued/late/stale/duplicate responses, reload after cancellation, disappearance suppresses dismissal
PASS: Event detail regression checks (actual production API/repository/VM/DTO, native Combine; no server calls)
PASS: supplementary ID-only route/destination and 7 existing navigation call sites
notice CLI는 다음 네 줄로 완료됐다:
PASS: actual NoticeApi/Repository public v2 GET requestPlain without Authorization; legacy list auth/timezone unchanged
PASS: actual DTO/roller 0/1/2/3, omitted/null envelope, repeated order/wrap/selection, exact HTML/date, legacy no-id, shrink/reset and lifecycle predicate
PASS: actual MyPageViewModel background delivery on main, 0/1/2/3 success, empty hides, HTTP/envelope/decode/transport failure preserves data, log-only/loading semantics
PASS: My Page notices regression checks (actual production API/Repository/VM/DTO/roller, native Combine; no server calls)
plutil은 SodaLive.xcodeproj/project.pbxproj: OK, Swift parse·shell 문법·diff 공백 검사는 출력 없이 exit 0이었다. 타깃·경로 그래프는 다음 명령으로 별도 검증했다:
plutil -convert json -o - SodaLive.xcodeproj/project.pbxproj | python3 -c 'import json,sys; project=json.load(sys.stdin); objects=project["objects"]; refs=[key for key,obj in objects.items() if obj.get("isa")=="PBXFileReference" and obj.get("path")=="EventDetailViewModel.swift"]; assert len(refs)==1, refs; ref=refs[0]; paths=[]
def walk(key,path=""):
obj=objects[key]; path="/".join(part for part in [path,obj.get("path","")] if part)
if key==ref: paths.append(path)
for child in obj.get("children",[]): walk(child,path)
walk(objects[project["rootObject"]]["mainGroup"]); assert paths==["SodaLive/Sources/V2/Settings/Event/EventDetailViewModel.swift"], paths
for name in ["SodaLive","SodaLive-dev"]:
target=next(obj for obj in objects.values() if obj.get("isa")=="PBXNativeTarget" and obj.get("name")==name); files=[file for phase in target["buildPhases"] if objects[phase]["isa"]=="PBXSourcesBuildPhase" for file in objects[phase]["files"]]; count=sum(objects[file].get("fileRef")==ref for file in files); assert count==1,(name,count); print("PASS: "+name+" has exactly one EventDetailViewModel.swift source membership at "+paths[0])'
exit 0, 실제 출력:
PASS: SodaLive has exactly one EventDetailViewModel.swift source membership at SodaLive/Sources/V2/Settings/Event/EventDetailViewModel.swift
PASS: SodaLive-dev has exactly one EventDetailViewModel.swift source membership at SodaLive/Sources/V2/Settings/Event/EventDetailViewModel.swift
- 직접 사용 검증: production 모듈 driver로 object 정상 로드, link-only, 비양수 입력, transport/HTTP/파싱 오류와 취소/지연 응답을 실행했다. 실제 서버·UIApplication.open은 실행하지 않았다.
- 미실행: 전체 build/test·시뮬레이터/실기기 시각 QA·VoiceOver·최종 리뷰. 사용자 위임 범위에 따라 상위 실행자가 수행한다. 이 환경에는 lsp_diagnostics 도구가 없어서 해당 clean 판정은 하지 않았다. 대신 VM/API/repository/DTO의 swiftc typecheck/실행과 변경 Swift 전체 parse를 확인했다.
- diff 대조: 상세의 레거시 외형 값, list/live의 상세 대 외부 URL 분기와 로그인 게이트를 유지했다. AppDeepLinkHandler, AuthPlugin, AcceptLanguagePlugin, EventItem, 기존 notice 테스트는 변경하지 않았다.
- 관찰: 최종 project.pbxproj diff에는 이번 패치에 없는 MyPageNoticeRoller 참조·그룹 블록의 순서 이동도 보인다. 공유 작업 변경으로 간주하여 되돌리지 않았다. 이벤트 VM 추가 membership과 경로는 독립적으로 검증했다.
- 남은 구현 항목: 없음. 다음 행동: 상위 실행자의 앱 빌드·실화면 QA·단일 리뷰.
Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal |
|---|---|---|---|---|---|
| 2026-10-29 | DEC-001 | 확정 | ID-only, Int.max 검증, DTO 재사용 | PRD DEC-001/002 | P1-T1/P1-T2 |
| 2026-10-29 | DEC-002 | 확정 | View 환경 dismiss와 일회성 소비 | 전역 back의 live sheet 우선 처리 | P1-T1/P1-T2 |
| 2026-10-29 | DEC-003 | 확정 | 현재 브랜치 직접 작업, 실서버 연결 테스트 제외 | 사용자 후속 지시, PRD DEC-004 | 최종 검증 |
상위 실행자 검증 1차 - 2026-10-29
- 현재 브랜치에서 작업했으며 별도 worktree/commit/push는 생성하지 않았다.
bash work/tests/check-event-detail.sh: 실제 production API/repository/VM/DTO와 native Combine, ID-only 7개 호출 검사 PASS, exit 0.for script in work/tests/check-*.sh; do bash "$script"; done: 신규 이벤트 검사를 포함한 14개 스크립트 모두 PASS. 변경 전 13개 스크립트도 모두 PASS였다.xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS Simulator' -disableAutomaticPackageResolution CODE_SIGNING_ALLOWED=NO build: BUILD SUCCEEDED, exit 0.- 같은 명령의
-scheme SodaLive-dev: BUILD SUCCEEDED, exit 0. 변경한 Swift 코드가 양 타깃에서 컴파일됐다. - 기존 중복 Compile Sources, Run Script 출력 미지정, AppIntents 메타데이터 경고는 수정 범위 밖이라 유지했다.
- 초기 standard 빌드는 120초 timeout, 다음 시도는 구현 도중 VM 타깃 등록 전이라 scope 오류였다. 등록 완료 후 위 최종 빌드는 성공했다.
- Xcode 실행이 만든 MyPageNoticeRoller 블록의 순서 이동만 원위치로 정리했다. 정리 전후 plutil JSON 정렬 SHA-256은
c010f8053c481722451be769f8dbfe6532866ad3301e20054465106c020a13e2로 동일하여 프로젝트 의미와 빌드 입력은 변하지 않았다. - 실서버 연결 테스트: 사용자 지시로 구현 후 수행하지 않았다. 지시 전 조사에서 관찰한 401은 서버 개발 중의 정보이며 클라이언트 완료 차단 사유가 아니다.
- 네이티브 화면 조작/시각 QA, VoiceOver 및
lsp_diagnostics는 해당 도구가 없어 미실행이다. Swift 컴파일과 로컬 상태 드라이버 검사로 대체했으며 실제 화면 검증 통과를 주장하지 않는다. - 단일 Oracle 리뷰 진행 중. 추가 확인:
env -u TMPDIR bash work/tests/check-event-detail.sh는 5행의 unset 변수로 exit 1. 반복 실행 시 생성된 검사 임시 디렉터리도 자체 정리가 없어 리뷰와 수정 대상으로 기록한다.
발견된 문제
양 스킴 빌드·14개 CLI 회귀는 최종 PASS이며 Oracle 단일 리뷰는 APPROVE다. MyPage 블록 순서 이동은 상위 실행자의 Xcode 실행 산출물로 확인 후 제거했다. 네이티브 실화면 QA/VoiceOver/LSP는 도구 부재로 미실행이며 실서버 테스트는 사용자 지시로 제외한다.
P1-T3 및 최종 리뷰 - 2026-10-29
- Oracle 단일 리뷰: 앱 계약 위반의 확정 차단 사항 없음, APPROVE. 비차단 runner/문서 지적은 후속 수정했다.
- runner 수정 후
env -u TMPDIR bash work/tests/check-event-detail.sh: exit 0, 모든 집중 검사 PASS. bash -x work/tests/check-event-detail.sh: exit 0, 생성한 임시 디렉터리의 EXIT trap 실행과 이후test ! -ePASS를 확인했다.bash -c 'swiftc() { return 42; }; export -f swiftc; bash -x work/tests/check-event-detail.sh; result=$?; test "$result" -eq 42': 의도한 컴파일 실패 코드 42 유지, EXIT trap 실행, 이후test ! -ePASS를 확인했다.bash -n work/tests/check-event-detail.sh: exit 0. 앱 production 코드는 리뷰 승인·양 스킴 빌드 이후 변경하지 않았다. 최종 수정은 runner 정리 2줄과 문서뿐이라 해당 runner 경로만 재검증했다.- 자원 정리: xcodebuild/이벤트 swiftc 실행 프로세스 부재를 확인하고 이번 실행이 생성한 parent/구현자 임시 디렉터리와 notepad를 삭제했다. 두 run root의
test ! -ePASS. 공유 Xcode 캐시·사용자 파일·worktree는 삭제하지 않았다. 일회성 raw 빌드 로그는 함께 제거했고 명령·종료 코드·주요 결과는 이 문서에 보존했다.
Phase 2: 목록 이미지 원본 비율
Phase 결과: EVT-005에 따라 기존 너비에서 이미지 전체를 원본 비율로 표시한다. 선행조건: 사용자 요청 확정, 기존 디자인/Kingfisher 조사 완료. 현재 브랜치에서 최소 수정한다.
Task 2.1 목록 이미지 표시
Goal 실행 P2-T1: 고정 1000:300 표시 높이를 제거하고 기존 너비를 유지한다.
Files: SodaLive/Sources/Settings/Event/EventListView.swift.
완료 증거: 변경 소스 확인, Swift parse와 diff 공백 검사; 실제 화면·빌드·리뷰는 상위 실행자 담당.
- EVT-005/DEC-005 및 TDD 예외를 소스 수정 전에 기록한다.
- scaledToFill을 scaledToFit으로 바꾸고 표시 frame의 높이만 제거한다. downsampling·간격·탭·상태·API는 유지한다.
swiftc -frontend -parse SodaLive/Sources/Settings/Event/EventListView.swift와GIT_MASTER=1 git diff --check의 exit 0을 확인한다.- 상위 실행자가 로컬 렌더·빌드·테스트·리뷰를 수행하고 증거를 누적한다.
P2-T1 검증 기록
- 무엇/왜: 목록 이미지의 고정 표시 비율만 제거한다. 새 로직 seam이 없는 UI modifier 변경이므로 TDD 예외이며 소스 문자열 의사 테스트는 만들지 않는다.
- 검증 범위: 구현자는 소스 확인·parse·diff 검사만 수행한다. 로컬 렌더·빌드·리뷰 증거는 상위 실행자 검증 대기이며 서버/CDN 호출은 금지한다.
- 실행 결과: 위 Swift parse·diff 공백 검사는 모두 출력 없이 exit 0. 기존 ID-only 라우팅 변경은 보존했다. 지정 TMPDIR를 사용했으며 정리는 상위 실행자 소유다.
P2-T1 상위 실행자 검증 - 2026-10-29
- 독립 macOS SwiftUI
ImageRenderer에 실제 변경 전후 modifier를 적용했다. 원본 fixture는 로컬 CoreGraphics로 생성하여 서버/CDN 호출이 없었다. 너비 363.3/717.3pt에서 1000x300, 600x600, 400x600의 6개 조합을 측정하고 전체 상하단 색상 밴드가 남는지 assertion했다. parent도 driver를 직접 실행하여 6/6 PASS, exit 0을 확인하고 비교 PNG 두 장을 직접 읽었다. - 363.3pt 너비의 새 높이는 각각 108.99/363.30/544.95pt, 717.3pt 너비는 215.19/717.30/1075.95pt다. 오차 0.01pt 이내로 원본 비율을 유지한다. 이 검증은 격리된 네이티브 image modifier 검증이며 실제 iOS 전체 화면이나 Kingfisher 다운로드 검증이 아니다.
for script in work/tests/check-*.sh; do bash "$script"; done: 첫 12개 PASS 후 13번째 실행 중 전체 명령이 120초 timeout. 남은bash work/tests/check-received-gift-detail.sh && bash work/tests/check-user-creator-chat-date-presentation.sh를 240초 제한으로 별도 재실행하여 둘 다 PASS/exit 0. 합계 기존 14개 검사 모두 통과했다.xcodebuild -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS Simulator' -disableAutomaticPackageResolution CODE_SIGNING_ALLOWED=NO build: BUILD SUCCEEDED, exit 0. 동일 명령의 SodaLive-dev 스킴도 BUILD SUCCEEDED, exit 0.- 단일 독립 리뷰: phase2-event-list-image-aspect.md, APPROVE. 추가 수정 사항 없음.
git diff --checkexit 0. LSP 도구가 없어 Swift parse 및 양 타깃 실제 컴파일로 대체했다. 현재 브랜치 작업이며 commit/worktree/실서버 호출은 없다.- 정리: 관련 실행 프로세스 부재를 확인하고 소유한 build/probe 임시 디렉터리(스크립트·이미지·바이너리·캐시)와 notepad를 제거했다. 두 run root 부재 검사 PASS. 공유 Xcode 캐시·기존 변경은 유지했고 측정값/명령/리뷰 판정은 위 기록에 보존했다.