# PRD: 본인인증 후 콘텐츠 보기 설정 ## 문서 정보 | 항목 | 내용 | |---|---| | 문서 상태 | Phase 7·R1 구현·로컬 기능/시각 검증 완료 / 실제 인증 통합 QA는 기존 대기 유지 | | 작성일 | 2026-10-12 | | 최종 수정일 | 2026-10-14 | | 대상 제품 | SodaLive iOS 본인인증 완료 흐름 | | 작성자·결정권자 | 사용자 승인 완료 (2026-10-12) | | API Contract | 본 문서 §11 | | 구현 계획 | `docs/20261012_본인인증_후_콘텐츠_보기_설정/plan-task.md` | | 구현 승인 | **2026-10-12 사용자 “승인”으로 구현 허가. 현재 main 브랜치, worktree 미사용.** | ### 요구사항 상태 | 상태 | 의미 | 구현 처리 | |---|---|---| | 사용자 확정 | 사용자에게 직접 확인된 제품 흐름 | 별도 문서 승인 후 계획 Task의 기준으로 사용 | | 채택 관례 | 기존 코드 동작을 기본값으로 채택 | 명시된 한정 내 적용, 사용자 결정으로 표기하지 않음 | | 구현 근거 | 저장소 소스에서 확인한 현재 동작 | 설계 근거이며 런타임 성공 증거는 아님 | | 위험/검증 | 추가 제품 결정을 요구하지 않는 미검증 런타임 조건 | 구현 후 실제 앱 QA로 판정 | ## 1. Overview Bootpay 본인인증 완료 직후 인증 요청을 보내는 현재 흐름을, 사용자가 콘텐츠 보기 설정을 확인한 뒤 제출하는 흐름으로 바꾼다. 모든 인증 진입점에서 동일한 확인 sheet와 인증·설정 제출·회원정보 동기화 순서를 사용하며, 사용자의 선호가 인증 성별 값에 의해 덮어써지지 않게 한다. ## 2. Problem Statement - 현재 `MyPageViewModel.authVerify`는 인증 완료 직후 `POST /auth`를 보내고, 응답 gender로 콘텐츠 선호를 저장한다. - callback 진입점에서는 성공 callback으로 이어지는 동작이 있지만, `MyPage` 단독 진입은 앱 재시작 경로를 사용한다. - 기존 `ContentSettingsViewModel`은 설정 변경 즉시 로컬 저장과 debounce PATCH를 수행하고 재시작 상태도 설정한다. 미확정 draft에 재사용할 수 없다. - `AppViewModel` 및 `HomeViewModel`의 회원정보 조회는 완료 결과를 반환하지 않고 알림·분석 부수효과도 수행한다. 요청 직후 pending action을 실행하면 동기화 순서가 보장되지 않는다. ## 3. Goals ### 3.1 제품 목표 - 인증 완료 7개 진입점 모두에서 사용자가 설정을 확인하기 전까지 `POST /auth`를 보내지 않는다. - 사용자가 확인한 인증 영수증과 성인 콘텐츠 설정을 한 요청으로 제출한다. - callback 진입점은 인증 성공 후 회원정보를 받아 로컬 인증·콘텐츠 상태를 갱신한 뒤 기존 pending action을 한 번만 실행한다. 마이페이지는 기존 재시작 경로를 유지한다. ### 3.2 UX 목표 - 인증 직후 ON 상태와 전체 콘텐츠를 기본 선택한 설정 sheet를 표시한다. - 바깥 탭, drag, 뒤로/닫기로 확인 단계를 우회해 dismiss하지 못하게 한다. - 실패한 단계별 복구를 구분하고 재요청 또는 action 실행을 잘못 반복하지 않는다. ## 4. Non-Goals - 독립 콘텐츠 설정 화면의 기존 자동 저장/debounce PATCH 동작 변경. - 일반 회원정보 조회 helper 전체의 추상화·리팩터링. - Bootpay 자체 동작, 인증 서버 response schema, 외부 인증 계약의 변경. - 승인 전 앱 코드, UI, 스크립트 또는 프로젝트 설정 변경. - 재시도 idempotency 또는 영수증 만료 정책 보장. ## 5. Target Users and Permissions - 사용자: Bootpay 본인인증을 완료한 로그인 사용자. - 인증 receipt와 앱 token은 외부 입력/민감정보로 취급한다. 원문을 로그나 문서에 남기지 않는다. ## 6. 핵심 사용자 흐름 1. 사용자 또는 기존 기능이 Bootpay 본인인증을 시작한다. 2. Bootpay `onDone`에서 receipt를 보존하고 Bootpay full-screen cover를 닫는다. 3. cover dismiss가 완료된 뒤 사용자 확인을 위한 콘텐츠 설정 sheet를 연다. callback과 pending action도 이 전환 중 보존한다. 4. sheet에서 ON/OFF와 ON일 때 선호를 선택한다. 이 단계에서는 네트워크 요청과 설정 영속화를 하지 않는다. 5. 사용자가 `설정 완료`를 누르면 sheet를 먼저 닫고, 이어 `POST /auth`를 한 번 보낸다. 6. 인증·설정 제출 성공 후 마이페이지는 확인한 콘텐츠 설정을 로컬에 반영하고 기존 앱 재시작 경로로 이동한다. callback 진입점만 `UserRepository.getMemberInfo()` 응답을 기다려 기존 member response를 decode하고 로컬 상태를 갱신한다. 7. callback 진입점은 동기화 성공 뒤 보존된 성공 callback으로 pending action을 한 번 소비한다. 동기화 실패 시 callback을 실행하지 않고 마이페이지와 동일한 앱 재시작 경로로 이동한다. ## 7. 정보 구조와 라우팅 - 앱 route를 추가하지 않는다. 인증 callback 상태를 보존하는 화면 위 modal sheet로 제공한다. - 시작 전에 다른 full-screen cover가 열려 있으면 이를 먼저 dismiss 완료한 뒤 sheet를 표시한다. - sheet는 바깥 탭, swipe/drag, back 또는 close 제스처로 닫히지 않는다. 명시적 확인만 다음 단계로 진행한다. ## 8. 기능 요구사항 ### 8.1 진입점과 확인 순서 | ID | 상태 | 요구사항 | 수용 기준 | |---|---|---|---| | AUTH-001 | 사용자 확정 | 모든 7개 Bootpay 인증 완료 진입점은 확인 전 auth POST를 미룬다. | `MyPageView.swift`, `Main/Home/HomeView.swift`, `Home/HomeTabView.swift`, `Chat/ChatTabView.swift`, `Live/Now/All/LiveNowAllView.swift`, `V2/Main/MainView.swift`, `V2/CreatorChannel/CreatorChannelView.swift`에서 같은 순서를 확인한다. 확인 전 request·설정 저장 모두 0회다. | | AUTH-002 | 사용자 확정 | receipt, draft, action/callback을 cover-dismiss-sheet 표시 순서 동안 보존한다. | `onDone`이 sheet 또는 callback을 잃지 않으며 cover dismiss 완료 전에 sheet가 겹쳐 뜨지 않는다. `onClose` 기존 닫기 동작을 유지한다. | | AUTH-003 | 사용자 확정 | sheet는 확인 외 경로로 dismiss되지 않는다. | 배경 탭, drag, back 및 close 시트 dismiss 0회, 요청 0회다. | | AUTH-004 | 사용자 확정 | 초기 draft는 `isAdultContentVisible=true`, `contentType=ALL`이다. | 초기 렌더 상태와 확인 request가 이를 따른다. | | AUTH-005 | 채택 관례 | OFF 선택 시 `contentType=ALL`로 normalize한다. | 비활성 선호 값은 제출되지 않는다. `ALL` normalize는 기존 코드의 채택 관례이며 별도 사용자 결정은 아니다. | ### 8.2 제출, 동기화, 실패 | ID | 상태 | 요구사항 | 수용 기준 | |---|---|---|---| | AUTH-006 | 사용자 확정 | 확인 시 sheet를 닫은 뒤 combined JSON payload로 `POST /auth`한다. | §11 필드와 enum만 포함하고 중복 탭으로 동시 요청이 발생하지 않는다. | | AUTH-007 | 사용자 확정 | 인증 및 설정 제출 실패 시 오류를 보이고 같은 draft로 sheet를 다시 연다. | receipt와 pending action을 보존하고 사용자가 다시 누를 때만 재전송한다. 자동 제출은 없다. | | AUTH-008 | 사용자 확정 | 인증 성공 후 회원정보 동기화 실패 시 인증을 재전송하지 않는다. | sheet를 다시 열지 않고 pending action도 실행하지 않는다. My Page와 동일하게 `isRestartApp=true`, `setAppStep(.splash)`로 앱 재시작 경로에 보낸다. sync-only retry UI/정책은 추가하지 않는다. | | AUTH-009 | 사용자 확정 | 여섯 callback route는 auth 성공 후 `GET /member/info?container=ios`와 로컬 상태 갱신을 완료하고 callback action을 한 번 소비한다. | 동기화보다 callback이 앞서지 않는다. 일반 fire-and-forget member helper를 부른 직후 진행하지 않는다. | | AUTH-010 | 사용자 확정 | 기존 My Page 재시작 route를 유지하고 auth 응답 gender가 사용자의 content choice를 다시 덮어쓰지 않는다. | gender 기반 선호 저장 제거, callback 없음은 기존 재시작 흐름을 유지한다. | | AUTH-011 | 구현 근거 | shared auth flow에서 기존 `UserRepository.getMemberInfo()` 및 `GetMemberInfoResponse`를 재사용한다. | 성공 response의 기존 필드와 로컬 키를 사용하며 별도 response schema를 만들지 않는다. | ## 9. 반응형 기능 범위 - iPhone SwiftUI sheet만 포함한다. 웹/tablet 반응형 범위는 해당 없음. - bottom safe area 포함 Figma frame: ON 402x372, OFF 402x287. ## 10. UI/UX Expectations ### 10.0 표시 방식 변경 승인 (2026-10-12) - 사용자가 투명 `fullScreenCover`를 표시 컨테이너로 사용하는 커스텀 BottomSheet 전환을 승인했다. 아래 내용은 이전 native `.sheet`/고정 높이 기록보다 우선한다. - 화면 전체에는 기존 UI가 비치는 반투명 검정 배경을 두고 터치를 차단한다. 실제 패널은 좌우·하단에 붙으며 위쪽 모서리만 둥글고, 내용과 safe area에 필요한 높이만 차지한다. - ON/OFF `338/253` 고정 높이 및 접근성 크기에서 `.large`로 전환하는 규칙을 제거한다. 텍스트 줄바꿈과 선호 영역 유무에 따른 자연 높이를 사용한다. - 내용이 화면의 사용 가능한 높이를 초과할 때에만 본문을 스크롤하고 확인 버튼은 접근 가능하게 유지한다. - 확인으로 컨테이너가 닫힌 뒤 기존 `onDismiss` 전송을 실행한다. API·ViewModel·7개 진입점·오류 복구 정책은 변경하지 않는다. - 구현·검증 후속: 커스텀 패널 전환 완료. iOS26 실제 측정은 한국어 ON372pt/OFF287pt, 영어 ON383.67pt이며 고정값이 아닌 내용 높이다. 화면 초과 시 본문 스크롤, 뒤 화면 노출/입력 차단, 두 앱 빌드와 P4 독립 검토를 통과했다. 기존 실제 인증 통합 미검증은 유지한다. - Phase6 승인: 사용자가 배경의 올라오는 애니메이션 제거 방안을 확인하고 적용을 승인했다. fullScreenCover 기본 전환은 끄고 dim 배경은 제자리에 즉시 표시하며, 콘텐츠 패널에만 진입/퇴장 모션을 적용한다. - 패널 퇴장 완료 후 컨테이너를 닫고 기존 onDismiss 제출을 유지한다. ViewModel/API/개발용 mock 정책은 바꾸지 않는다. 동작 줄이기 환경에서는 수직 이동을 줄이고, 같은 닫힘 완료/1회 제출 계약을 유지한다. - 2026-10-13 Phase6 후속: 배경 고정·패널 단독 0.25초 진입/퇴장을 구현했다. 실제 native 중간 프레임, 빠른 확인/재표시/Reduce Motion 검사, 두 앱 빌드와 독립 기능·시각 검토가 통과했다. 제출은 host onDismiss에 유지된다. ### 10.1 시안과 문구 - Figma file `HmN1yNdJ3EIpqknFL0Hkab`, ON `2489:32150`, OFF `2489:32163`을 기준으로 한다. - 시안 링크: [ON](https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/SodaLive-UI-UX?node-id=2489-32150), [OFF](https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/SodaLive-UI-UX?node-id=2489-32163). - 고정 dark background `#202020`, accent `#00bdf7`, 흰색 제목, muted copy `#b5b5b5`, 좌우 padding 14pt. - 제목 18pt medium, 본문 16pt, 안내 caption 12pt. 기존 앱 font/assets와 `I18n.swift` 규칙을 따른다. - 화면 문구는 다음과 같이 고정한다. - `콘텐츠 보기 설정` - `민감한 콘텐츠 보기` - ON 설명: `성인인증 완료 후 성인 콘텐츠를 볼 수 있어요` - OFF 설명: `성인 콘텐츠가 목록과 검색에 노출되지 않아요` - ON일 때만 `선호 콘텐츠` 및 `전체` / `남성향` / `여성향` radio row 표시 - `마이 > 설정 > 콘텐츠 보기 설정에서 언제든 바꿀 수 있어요` - 확인 버튼 `설정 완료` ### 10.2 화면 상태 - sheet는 제출 전 draft만 보유한다. 확인 전 기존 설정 값을 변경하거나 PATCH하지 않는다. - auth+settings POST 실패는 sheet 재개, member info sync 실패는 sheet 재개 없이 restart fallback으로 분리한다. ## 11. API 계약 ### 11.0 개발 타깃 성공 시뮬레이션 승인 (Phase 5) - 사용자가 앱 흐름 테스트 목적의 모의 인증을 승인했다. 실제 서버 인증 검사 API가 거절될 수 있다는 한계를 수용했다. - `SodaLive-dev` Debug/Release에만 `AUTH_VERIFICATION_MOCK` 컴파일 조건을 적용한다. 일반 `SodaLive`는 두 구성 모두 실제 Bootpay/인증 API를 유지하며 모의 실행 코드가 제외된다. - 7개 본인인증 UI에서 개발 모드는 Bootpay를 열지 않고 약 0.5초 로딩 후 기존 ON/ALL 설정 패널로 진행한다. 설정 완료 후 닫힘 완료를 기다리는 기존 순서를 유지한다. - 모의 인증 전송은 실제 POST 없이 정상 성공 응답을 제공해 기존 공통 성공 흐름을 재사용한다. 가짜 receipt를 서버로 보내거나 서버 인증 상태를 바꾸지 않는다. - 후속 회원정보·마이페이지 조회는 정상 서버 응답에서 `isAuth`, `isAdultContentVisible`, `contentType`만 모의 상태로 보정한다. 잔액·역할·개인정보·알림 등 다른 필드는 원본을 유지하고, 실제 조회의 HTTP/API/디코딩 실패를 성공으로 바꾸지 않는다. - 모의 상태는 현재 로그인 세션에 한정한다. 앱 내부 Splash 재시작에는 유지하고, 로그아웃/401 초기화 및 로그인 세션 변경 때 해제한다. 실제 앱 프로세스 종료 후 모의 인증을 영구 유지하는 기능은 만들지 않는다. - 일반 콘텐츠 설정에서 이후 변경한 값을 오래된 모의 선택값으로 덮어쓰지 않도록 상태를 맞춘다. 새 설정 UI/모의 제어 메뉴/서버 권한 우회는 범위 밖이다. - 구현 후속: Phase5 완료. dev Debug/Release만 모의 flag 적용, 표준 Debug 바이너리 mock 심볼 제외, native 로딩/패널 4개 검사, 실제 helper/repository/VM 격리 검사와 두 Debug 앱 빌드 통과. 같은 로그인 세션의 인증 전 GET 지연 응답 회귀를 수정했다. 실제 회원 GET·일반 설정 PATCH는 서버 연결을 유지하며 서버 권한 부여는 하지 않는다. 별도 `api-contract.md`는 만들지 않는다. 아래 계약은 사용자 확인 사항이며 backend 실행/응답은 독립 검증하지 않았다. ### 11.1 인증과 설정 제출 | 항목 | 계약 | |---|---| | 요청 | `POST /auth` | | JSON | `receipt_id: String`, `isAdultContentVisible: Bool`, `contentType: ALL \| MALE \| FEMALE` | | 의미 | Bootpay receipt와 확인된 콘텐츠 설정을 한 번에 제출 | | 성공 decode | 기존 `ApiResponse` 사용, gender 외 새 response field/schema를 가정하지 않음 | | 실패 | transport/API/decode/비성공 envelope를 auth+settings 제출 실패로 처리, 오류 표시와 보존 draft로 사용자 재시도 | | 기존 재시작 진입점 | 성공 시 확인된 설정을 로컬 반영하고 기존 `isRestartApp=true`, `.splash` 전환. 재시작 전에 회원정보 GET을 추가하지 않음 | ### 11.2 callback 진입점의 회원정보 동기화 | 항목 | 계약 | |---|---| | 요청 | `GET /member/info?container=ios` | | response | 기존 `ApiResponse` | | 성공 처리 | `can`, `point`, `auth`, `role`, `isAuditionNotification`, `countryCode`, `isAdultContentVisible`, `contentPreference` 로컬 값 갱신 후 pending action 소비 | | 실패 처리 | sheet 재개 및 auth 재전송 금지. pending action 미실행. `isRestartApp=true` 설정 후 `.splash` 전환 | | 재시작 조회 근거 | `ContentView`가 `isRestartApp=true`일 때 MainView를 제거하고, `SplashView`가 해당 플래그를 해제하면 새 MainView의 `hasLoadedMainData=false` 초기화 경로가 member GET을 호출한다. 실제 조회/로컬 반영은 앱 QA로 확인한다. | | 범위 제약 | Splash 자체에는 member GET이 없다. 앱 active 조회도 `alreadyUpdatedMarketingInfo` 조건에 묶여 있어 단독 동기화 근거로 삼지 않는다. 이번 실패 처리에서 자동 재시작 루프를 추가하지 않는다. | ## 12. 보안과 데이터 취급 - receipt와 token 원문을 문서, 로그, analytics에 기록하지 않는다. - 본 auth flow의 `onDone`에서 receipt payload를 dump하는 기존 `DEBUG_LOG`를 제거한다. 무관한 전체 repo 로그 정리는 하지 않는다. - draft는 확인 전 네트워크와 영속화로 보내지 않는다. ## 13. 성능과 품질 요구사항 - 인증 완료 7 entrypoint, dismiss 차단, sheet 닫힘 후 POST, 중복 확인 방지와 실패 단계별 복구는 수동 앱 QA로 검증한다. - XCTest bundle이 없으므로 test target/framework를 추가하지 않는다. - Foundation 기반 assert self-check는 실제 `AuthVerifyRequest`와 `ContentType` 소스를 compile/run해 serializer payload를 확인한다. 흐름 테스트를 대체하지 않는다. - `SodaLive` 및 `SodaLive-dev` 양쪽 Debug build를 검증한다. - 재시도 idempotency, receipt 유효기간/만료 동작은 미검증 위험으로 기록하며 보장하지 않는다. ## 14. 성공 기준 ### 14.1 기능 수용 기준 - [ ] 일곱 진입점 모두 확인 전 auth POST와 설정 영속화가 0회다. (`AUTH-001`~`AUTH-005`) - [ ] 확인 시 sheet가 먼저 닫히고 정확한 combined payload POST는 한 번만 발생한다. (`AUTH-006`) - [ ] POST 실패 뒤 receipt/draft/action을 보존한 sheet를 재개하고 자동 제출이 없다. (`AUTH-007`) - [ ] member info 실패 시 auth 재전송 0회, sheet 재개 0회, pending action 실행 0회이며 restart fallback이 동작한다. (`AUTH-008`~`AUTH-010`) - [ ] restart fallback이 실제 member refresh를 발생시키고 auth/content 설정을 반영함을 QA에서 확인한다. 소스상 refresh trigger는 런타임 성공 증거가 아니다. ### 14.2 UI/UX 수용 기준 - [ ] Figma ON/OFF, 고정 문구, font scale, radio 표시, safe area가 실제 화면과 일치한다. - [ ] 외부 탭·drag·back·close가 confirmation을 우회하지 않는다. ### 14.3 승인 및 추적성 - [x] PRD와 plan-task 별도 문서 승인을 받기 전 구현을 시작하지 않는다. - [ ] 승인 후 요구사항이 Task와 수동/자동 검증 증거에 연결된다. ## 15. Open Questions - 제품 결정 미결 항목 없음. 요구사항은 사용자 확인 사항이다. - backend combined payload 지원은 사용자 확인에 근거하며 직접 호출하거나 독립 검증한 사실이 아니다. - retry idempotency, receipt 만료, restart 통한 member refresh 실동작은 제품 결정이 아닌 구현/운영 검증 위험이다. ## 16. 요구사항 추적표 | 범위 | 계획 Phase | Goal | 자동 검증 | 수동 검증 | |---|---:|---|---|---| | `AUTH-001`~`AUTH-005` | 2 | `P2-T1` | 실제 request serializer assert, 정적 caller 대조 | 일곱 entrypoint, dismiss, 미확인 network/persistence 0회 | | `AUTH-006`~`AUTH-008` | 2 | `P2-T1` | combined payload serializer assert | duplicate confirm, POST 실패 뒤 값 보존·수동 retry | | `AUTH-009`~`AUTH-011` | 2 | `P2-T1` | source review, response decode 경로 | GET 실패 때 restart, auth 재전송 0회, action 0회; restart member refresh 성공 확인 | | 전체 | 3 | `P3-GATE` | serializer, 두 Debug builds | 최종 흐름 및 두 시안 QA | ## 17. Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 | |---|---|---|---|---|---| | 2026-10-12 | DEC-001 | 사용자 확정 | 모든 일곱 auth completion을 confirm sheet 뒤로 이동하고 최초 ON/ALL로 시작 | 사용자 확인 | `AUTH-001`~`AUTH-005`, `P2-T1` | | 2026-10-12 | DEC-002 | 사용자 확정 | 확인으로 닫은 뒤 combined auth/settings 제출, 여섯 callback은 회원정보 동기화 후 action 1회 소비 | 사용자 확인 | §11, `AUTH-006`, `AUTH-009` | | 2026-10-12 | DEC-003 | 사용자 확정 | POST 실패는 sheet/draft 재개, 회원정보 실패는 재전송 없이 restart fallback | 최신 사용자 확인 | `AUTH-007`~`AUTH-008` | | 2026-10-12 | DEC-004 | 채택 관례 | OFF일 때 `contentType=ALL` | 기존 ContentSettingsViewModel normalize | `AUTH-005` | ## 18. 변경 관리 - 요구사항 변경은 Decision Log와 요구사항 표, §11 계약, plan-task를 함께 갱신한다. - 승인 전 앱 코드는 변경하지 않는다. 사용자가 이 PRD와 계획 문서를 명시적으로 승인하면 구현 시작 허가로 처리하고, 승인 기록만 갱신한 뒤 현재 브랜치에서 계획을 실행한다. 별도 추가 승인을 요구하지 않는다. ## 인터뷰 요약 - 사용자 확정 흐름 및 제약을 기준으로 정리했다. 승인되지 않은 제안은 없다. - 범위/목표/제약/성공 기준 명확도 1.0, 기존 코드 문맥 0.9. - 모호도는 휴리스틱 추정 `0.015 ≈ 0.02` (`1 - (목표×0.30 + 범위×0.20 + 제약×0.15 + 성공×0.20 + 문맥×0.15)`)이며 측정값이 아니다. 문서/구현 승인은 아직 부여되지 않았다. ## 문서 작성 검증 - 2026-10-12: PRD와 계획 샘플, 정책 및 지정 소스 경로를 읽고 요구사항/구현 근거를 분리해 기록했다. backend 호출, 빌드, 앱 실행은 하지 않았다. - 2026-10-12: 마이페이지의 기존 재시작과 callback 경로의 GET 동기화를 분리하고, 문서 승인 한 번을 구현 시작 허가로 처리하도록 사용자 지시와 대조했다. 변경 범위와 공백 오류를 확인했으며 구현 승인은 여전히 대기 중이다. - 2026-10-12 후속: 사용자가 문서를 승인했다. 이전 승인 대기 기록은 이력으로 보존하며, 승인된 범위의 구현·검증을 시작한다. - 2026-10-12 구현 후속: 7개 인증 진입점과 결합 전송·단계별 복구를 구현했다. 실제 DTO/VM 격리 실행, 네이티브 sheet UI 검사, 두 앱 Debug 시뮬레이터 빌드가 통과했다. 실제 인증 7개 경로와 실패 재시작 후 실서버 회원정보 갱신은 QA 환경이 없어 미검증이며 최종 수용 기준을 일괄 완료 처리하지 않는다. 상세 증거와 한계는 plan-task의 Progress 및 `reviews/phase2-auth-content-settings.md`에 기록한다. ## Phase 7 후속 요구사항: 인증 중 설정 패널 종료 ### 상태와 우선순위 - 2026-10-13 사용자 인터뷰로 범위와 문구를 확정했다. 현재 허용 범위는 문서·계획 작성까지이며, **Phase 7 구현은 이 계획에 대한 별도 승인 후 시작한다.** 이전 Phase의 승인이나 문구에 대한 “그대로 진행”은 이번 구현 승인으로 처리하지 않는다. - 아래 요구사항은 기존 `AUTH-003`, §3.2·§7의 닫기 금지, §10.1 문구 및 §14.2 관련 수용 기준을 이번 범위에서 정정한다. 과거 결정·검증 기록은 이력으로 보존한다. - 목표: 최초 설정 및 저장 실패 후 재표시된 패널에서 사용자가 명시적으로 본인인증 과정을 중단할 수 있게 한다. - 범위: 인증 중 `AuthContentSettingsSheet`와 종료 팝업만 변경한다. 일반 콘텐츠 설정 화면, 다른 인증 안내, API·Bootpay·개발 mock 정책은 변경하지 않는다. ### 요구사항과 수용 기준 | ID | 상태 | 요구사항 | 수용 기준 | Goal | |---|---|---|---|---| | AUTH-012 | 사용자 확정 | 패널 우측 상단 X는 종료 확인 팝업을 연다. | 최초 패널과 POST 실패 후 패널 모두에서 동작한다. X 자체는 제출하거나 패널을 닫지 않는다. | `P7-T1` | | AUTH-013 | 사용자 확정 | V2 내부 기존 팝업을 재사용한다. | `V2/Component/Modal/SodaV2ActionModal.swift`를 그대로 사용하며 구형 `SodaDialog`나 새 팝업 컴포넌트를 만들지 않는다. | `P7-T1` | | AUTH-014 | 사용자 확정 | confirm은 패널까지 닫고 인증 과정을 중단한다. | 영수증·요청·성공 callback과 caller의 대기 action을 폐기한다. 종료에 따른 추가 POST/GET·설정 저장·성공 동작·재시작은 0회다. 새 인증으로 다시 진입할 수 있다. | `P7-T1` | | AUTH-015 | 사용자 확정 | cancel과 팝업 바깥 터치는 팝업만 닫는다. | 선택한 ON/OFF·선호와 인증 대기 상태를 유지한다. 팝업 표시 중 뒤 패널의 저장·토글은 조작되지 않는다. | `P7-T1` | | AUTH-016 | 사용자 확정 | 인증 패널과 종료 팝업에서 인증·콘텐츠 용어를 통일한다. | ‘본인인증’과 ‘민감한 콘텐츠’를 사용한다. 일반 설정과 다른 인증 안내 문구는 그대로다. 팝업 본문의 두 문장 사이에 명시적인 줄바꿈이 있다. | `P7-T1` | | AUTH-017 | 설계 제안 | X는 SwiftUI 접근성·터치·레이아웃 기준을 따른다. | SF Symbol `xmark`, 44×44pt 터치 영역, 의미 있는 VoiceOver 레이블, 제목 가운데 정렬, 큰 글자·가로 화면에서도 닫기 접근 가능. | `P7-T1`, `P7-QA` | ### 확정 문구 | 위치 | 한국어 문구 | |---|---| | 패널 제목 / 토글 제목 | 기존 `콘텐츠 보기 설정` / `민감한 콘텐츠 보기` 유지 | | ON 설명 | `본인인증 완료 후 민감한 콘텐츠를 볼 수 있어요` | | OFF 설명 | `민감한 콘텐츠가 목록과 검색에 노출되지 않아요` | | X 접근성 레이블 / 팝업 제목 | `본인인증 종료` | | 팝업 본문 | `본인인증을 종료하시겠어요?\n종료하면 다시 본인인증을 진행해야 해요.` | | confirm / cancel | `종료하기` / `계속하기` | | 저장 CTA | 기존 `설정 완료` 유지 | - 본문에서 `\n`은 실제 줄바꿈으로 표시한다. 화면 폭에 따른 추가 자연 줄바꿈은 허용한다. - 기존 `I18n.pick`의 한국어·영어·일본어 방식을 유지하고 번역도 같은 의미와 두 문장 사이 줄바꿈을 따른다. 공용 `sensitiveContentTitle`은 수정하지 않는다. ### UI와 상태 설계 제안 - `ui-ux-pro-max`의 SwiftUI 레이블·네이티브 Button 및 iOS 44pt 터치 영역 지침을 적용한다. 기존 dark 패널·색상·간격·모션을 유지한다. - 헤더는 최소 54pt 높이, 기존 상단 26pt·좌우 14pt 간격을 유지한다. 좌우 동일한 44pt 영역 사이에 제목을 가운데 배치한다. 우측 16pt medium `xmark`는 `.gray400`을 사용하고 별도 원형 배경은 추가하지 않는다. 눌림 피드백을 제공한다. - 헤더와 저장 CTA는 스크롤 밖에 두고 내용만 필요할 때 스크롤한다. 큰 글자에서는 제목 줄바꿈과 헤더 높이 증가를 허용한다. X 식별자는 `authContentSettings.close`로 한다. - 종료 팝업은 패널이 아닌 전체 화면 host 위에 표시한다. 기존 V2 순서대로 왼쪽 ‘계속하기’, 오른쪽 ‘종료하기’를 배치한다. 팝업 동안 뒤 패널 터치와 접근성 탐색을 차단한다. - 배경 탭·drag로 패널을 바로 닫는 동작은 계속 금지한다. 종료는 X → 팝업 → ‘종료하기’로만 수행한다. 제출 중 취소는 허용하지 않는다. - 패널 단독 퇴장 및 host `onDismiss` 제출 연결을 유지한다. 종료로 대기 상태를 먼저 비우므로 `submitPendingAuthVerification()`의 기존 guard가 추가 제출을 차단한다. ### 제외와 검증 위험 - 서버에 이미 도달했을 수 있는 실패 요청의 인증 결과를 취소하거나 되돌리는 기능은 없다. 종료는 앱의 로컬 진행 중단이며 서버 rollback을 보장하지 않는다. - V2 팝업의 기존 340pt 폭·64pt 제목 높이는 공용 컴포넌트 제약이다. 좁은 화면·큰 글자에서 문제가 확인되면 보고하고, 이번 작업에 공용 팝업 리팩터링을 임의로 포함하지 않는다. - 기능·native UI·두 앱 빌드는 구현 승인 후 검증한다. 기존 실서버 통합 QA 미검증은 별도로 유지한다. ### 후속 결정과 인터뷰 종료 | 날짜 | ID | 상태 | 결정 | 근거 | 영향 | |---|---|---|---|---|---| | 2026-10-13 | DEC-007 | 정정·사용자 확정 | 명시적 종료 확인을 허용하고 변경 범위를 인증 패널·종료 팝업에 제한한다. V2 팝업을 재사용한다. | 사용자 요청과 인터뷰 | `AUTH-012`~`AUTH-016`, `P7-T1` | | 2026-10-13 | DEC-008 | 사용자 확정 | 종료 문구를 채택하고 본문 두 문장 사이를 줄바꿈한다. 계획까지만 작성한 뒤 구현 승인을 기다린다. | 최신 사용자 지시 | 확정 문구·Phase 7 승인 gate | - 최종 모호성 0.02: 목표 1.0, 범위 1.0, 제약 1.0, 성공 기준 1.0, 코드 문맥 0.9의 휴리스틱 평가. 제품 미결 질문 없음. UI 세부 수치는 승인 대상 설계 제안이다. - 2026-10-13 문서 작성: 현재 패널·VM·V2 팝업·호출부·I18n·일반 설정·기존 검증 스크립트를 대조했다. 앱 코드 변경, 빌드, 앱 실행, 서버 요청은 수행하지 않았다. - 2026-10-13 후속 승인: 사용자가 **“구현 승인”**으로 Phase 7 PRD·계획 구현을 허가했다. 위 승인 대기 기록은 이력으로 보존하며 현재 작업 디렉터리에서 승인된 최소 변경과 검증을 진행한다. - 2026-10-13 구현 후속: X·V2 종료 팝업·줄바꿈·scoped 용어·VM/caller 정리를 구현했다. 실제 VM 검사, 전체 기존 shell suite, 두 앱 빌드, native 기능 matrix 20개와 추가 반복 1개 및 coordinator 핵심 시나리오 재실행 1개가 통과했다. Oracle은 코드·VM 로직 범위 APPROVE이며 전체 시각 승인은 아니다. - **[blocked] 추가 범위 결정 필요:** 가로 화면 + 최대 Dynamic Type에서 변경 없는 `SodaV2ActionModal`의 본문과 버튼이 잘려 경고 두 번째 문장과 동작명이 보이지 않는다(`REV-P7-001`). 공용 컴포넌트 수정은 승인 범위 밖이므로 수정하지 않았다. 이 제한 수용 또는 공용 팝업의 큰 글자 대응 수정 승인 전까지 최종 완료로 표시하지 않는다. 상세 증거는 `reviews/phase7-auth-settings-exit.md` 참조. - 2026-10-13 `P7-R1` 승인: 사용자 **“최소한 수정”**으로 공용 `SodaV2ActionModal`의 큰 글자 레이아웃 최소 보정을 승인했다. 공용 modal 무변경 제약은 이번 결함 보정에 한해 해제한다. 기본 크기 디자인·문구·버튼 순서·색상·callback은 보존하고, 글자 크기 제한으로 우회하지 않는다. 화면 공간에 맞춘 크기와 줄바꿈·필요한 overflow 처리만 허용한다. - 2026-10-14 `P7-R1` 완료: 공용 modal 한 파일(+46/-22, 순증24줄)을 수정해 접근성 글자에서 가용 폭·자연 높이를 사용하고 넘치는 제목/본문만 스크롤한다. 버튼은 고정되며 기본340pt 디자인·동작은 보존한다. 최대 글자 가로/375pt세로와 기본 크기·단일/두 버튼 native 검증, 현재 소스의 두 앱 빌드·전체 shell suite 및 독립 기능 APPROVE/집중 시각 PASS를 확인했다. `REV-P7-001`은 해결됐으며 이전 보류 기록은 이력이다. - 검증 한계: 실제 VoiceOver 제스처와 실서버7경로 통합은 미실행이다. native 접근성 노드와 VM doubles를 그 증거로 확대하지 않는다. 기존 일본어 단어 중간 줄바꿈은 의미 생략이 없는 경미 사항으로 유지했다. QA 앱·runner·기기·설정은 정리했다.