Files
sodalive-android/docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md

159 KiB

V2 공통 접근 가드와 도메인 액션 Plan/Task

Assumptions

  • 작업 범위는 kr.co.vividnext.sodalive.v2 하위 신규/기존 코드, 대응 테스트, 사용자 승인을 받은 레거시 DeepLinkActivity, 필요한 경우 이를 조립하는 AppDI.kt, 본 작업 문서로 제한한다.
  • 화면별 최초 집계 API와 Response는 feature가 계속 소유할 수 있다.
  • 공통화 대상은 최소 두 호출부에서 반복되거나 모든 화면에서 동일해야 하는 정책과 사용자 행동이다.
  • 기존 UX, API, Intent extra, Activity result, 화면 새로고침 동작을 보존한다.
  • 직접적인 하위 Activity 결과는 ActivityResult -> 명시적 도메인 결과 -> feature 단일 handler를 기본 전달 방식으로 사용한다.
  • 현재 화면 안에서 Action/Repository가 직접 반환하는 결과에는 불필요하게 ActivityResult를 도입하지 않고 같은 handler를 호출한다.
  • SharedFlow/observer는 Activity 밖의 독립 소비자가 다수 존재하거나 백그라운드 변경 전파 요구가 확인될 때만 사용한다.
  • 레거시 기능은 수정하지 않고 v2 adapter/wrapper에서 호출한다. 단, V2 푸시/딥링크 진입점으로 계속 사용할 DeepLinkActivity의 Action 연결 변경은 사용자가 승인했다.
  • 구현은 Access부터 도메인별 Phase로 진행하며 각 Phase를 독립적으로 검증한다.
  • 모든 도메인에 동일한 형식의 클래스를 강제하지 않고 실제 중복과 정책이 확인된 최소 단위만 추출한다.
  • AppDI.kt는 실제 외부 의존성, 공유 생명주기 또는 구현 교체가 필요한 계약만 조립한다. 상태 없는 Action/Handler는 호출 경계에서 직접 조합할 수 있으며 DI 등록 자체를 완료 조건으로 삼지 않는다.

최종 소유 경계

  • Access: 접근 허용 판단과 기존 로그인/본인인증/성인 설정 UX 실행을 소유한다.
  • 도메인 Action: 안정적인 ID/command 검사, 필요한 AccessRequirement 선택, Access 실행 요청과 Ignored/Blocked/도메인 결과 반환을 소유한다.
  • Action Handler: Access 실행기 주입과 레거시 Intent/Activity/Dialog/navigation adapter를 소유한다.
  • feature: UI model -> command 변환, 화면별 최초 query/API, 단일 화면 생성·mutation API, ActivityResult 수신과 refresh/callback/projection 조합을 소유한다.
  • 보호된 도메인 Action의 호출부는 같은 Access guard를 바깥에서 중복 실행하지 않는다. 공통 Access 직접 호출은 화면 진입 자체 또는 화면 전용 동작에 사용한다.
  • 도메인 관련 코드를 모두 이관하지도, 페이지 이동만 이관하지도 않는다. 여러 호출부에서 같아야 하는 유효성·접근 정책·결과 계약은 Action으로, Android 화면 이동은 Handler로 이관하고 화면별 query/API와 후처리는 feature에 유지한다.
  • parent-child Fragment composition, same-feature child flow, 도메인 Action이 없는 system-only route, Home on-air 목록 화면 진입, 단일 owner 기능은 합의된 반복 정책이 없으면 feature에 유지한다.

Success Criteria

  • v2 로그인/성인 접근 판단의 단일 소유자가 존재한다.
  • Activity와 Fragment가 동일한 Access 공개 진입점을 사용한다.
  • 합의된 도메인별 반복 동작이 단일 Action 공개 진입점을 사용한다.
  • Action 입력은 화면 전용 UI model/DTO가 아닌 ID, 값 또는 명시적 command다.
  • 화면별 최초 query와 UI composition은 유지된다.
  • Community mutation은 명시적 변경 결과를 반환하고 Creator Channel의 단일 composition handler가 Home/Community projection 갱신을 결정한다.
  • Activity result를 사용하는 도메인 흐름은 raw result를 화면마다 해석하지 않고 명시적 결과와 단일 handler를 사용한다.
  • feature 간 직접 Activity/Coordinator 의존 중 합의된 반복 navigation/정책 대상은 공통 Action 또는 Handler 계약으로 대체되고, 최종 소유 경계의 예외는 근거와 함께 유지된다.
  • 푸시/딥링크의 Content, Creator, Community, Chat 이동은 기존 Action을 사용하고 Live 이동은 LiveActionCoordinator를 사용한다.
  • Live room ID는 channel ID보다 우선하며, Community post ID는 creator ID보다 우선한다.
  • 각 Phase의 테스트, 컴파일, ktlint, whitespace 검증 결과가 문서에 누적된다.

Phase 0: 기준선과 Action 카탈로그 확정

  • Task 0.1: v2 구조와 중복 접근 구현 기준선 조사

    • 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/
      • app/src/test/java/kr/co/vividnext/sodalive/v2/
    • 확인 내용:
      • 운영 Kotlin 258개, 약 25,294줄
      • 테스트 Kotlin 127개, 약 23,809줄
      • SharedPreferenceManager 직접 참조 29개 파일, 92곳
      • API 11개, Repository 11개, ViewModel 22개, Activity/Fragment 20개
    • 검증 기준: 전체 재배치가 아닌 반복 동작별 점진 전환이 필요한 근거를 기록한다.
    • 검증 기록:
      • 2026-07-14: find, wc, rgv2 운영/테스트 코드 규모와 직접 의존을 확인했다. widget 64개는 UI 재사용 컴포넌트이므로 초기 도메인 Action 전환 대상에서 제외한다.
  • Task 0.2: 초기 Action 카탈로그 작성

    • 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업: Access, Content, Live, Creator, Community, Chat의 초기 공통 동작과 호출 화면을 기록한다.
    • 검증 기준: 화면별 집계 query와 공통 action을 구분하고, FanTalk/Donation/Schedule은 실제 재사용 수요 확인 전 추출 대상에서 제외한다.
    • 검증 기록:
      • 2026-07-14: 기존 호출부 조사 결과 Access, 오디오/시리즈 진입, Live 상세/입장, Creator Channel 진입, Community 상세, Chat/DM 진입을 초기 후보로 확정했다.
  • Task 0.3: 구현 직전 호출부 및 동작 차이 상세 목록 확정

    • 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/
      • app/src/main/java/kr/co/vividnext/sodalive/mypage/MyPageFragment.kt (호출부 확인만 수행하고 수정하지 않음)
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업: 각 Action별 호출 함수, 접근 조건, Intent extra, Activity result, callback/refresh 차이를 기록한다.
    • 검증 기준:
      • 공통 계약으로 보존할 동작과 화면에 남길 후처리가 구분된다.
      • Activity result 흐름은 adapter에서 만들 명시적 결과와 feature 단일 handler 후보가 기록된다.
      • observer가 필요한 흐름은 Activity 밖의 소비자 수, lifecycle, replay 요구사항이 함께 기록된다.
    • 확인 결과:
      • Access: HomeMainFragment, ContentMainFragment, ChatMainFragment는 기존 ensureMainV2NavigationAllowed를 사용했고, CreatorChannelActivity, HomeOnAirLiveActivity는 자체 ensureLoginAndAdultAuth를 중복 구현하고 있었다.
      • Access: MainV2Activity는 chat tab, audio notification route처럼 로그인 차단 UX가 필요한 navigation guard와 push token/member/deeplink 초기화처럼 부수효과 없이 허용 여부만 확인하는 흐름이 섞여 있었다. Phase 1에서는 두 용도를 ensureV2Access(AccessRequirement.Login)isV2AccessAllowed(AccessRequirement.Login)로 분리해 기존 route 처리 순서와 invalid audio content id 반환 계약을 유지한다.
      • Content: ContentMainFragment의 오디오/시리즈 상세 진입은 ID 검사 후 판단 가능한 성인 여부를 guard에 전달하며, banner/overview/전체 탭 이동은 login guard만 필요하다.
      • Live: HomeMainFragmentHomeOnAirLiveActivity는 로그인 후 room detail을 조회하고, detail의 isAdult로 성인 Access를 다시 검사한다. Creator Channel 현재 라이브도 동일 입장 흐름을 사용한다.
      • Creator/Community: CreatorChannelActivity는 Activity 자체 진입 로그인 guard와 Community/FanTalk/Live ActivityResult refresh를 소유한다. Phase 1에서는 result adapter를 만들지 않고 기존 launcher/refresh를 유지한다.
      • Chat: title bar 및 Home following chat은 login guard 후 기존 AI/DM intent helper를 사용한다. DM room result adapter나 observer 변경은 Phase 5 대상으로 유지한다.
      • Legacy MyPage: MyPageFragment.showLoginActivity는 실행 Activity가 MainV2Activity이면 공개 showLoginActivity()를 호출하므로 해당 호환 진입점을 유지해야 한다.

상세 호출부 및 계약

Action 후보 호출부 Access 조건 Intent extra / 입력 Result / callback 차이 Phase 1 처리
Login/Adult Access HomeMainFragment.openWithLoginGuard, openFollowingTab, title bar cash/search/bell, ContentMainFragment title/overview/banner/detail, ChatMainFragment title bar, CreatorChannelActivity community/FanTalk/live, HomeOnAirLiveActivity live entry Login 또는 forAdultContent(isAdult) 화면별 기존 Activity/Intent helper 그대로 사용 화면별 후처리는 호출부에 유지 ensureV2Access로 전환하고 정책 판단만 공통화
MainV2 chat tab MainV2Activity.selectChatTabWithLoginGuard, openChatTab, initial chat filter Login EXTRA_CHAT_FILTER는 기존 consume/open 흐름 유지 탭 전환은 허용 시에만 viewModel.clickTab(CHAT) ensureV2Access(Login) 사용, legacy showLoginActivity()는 guard 위임만 유지
Legacy MyPage login bridge MyPageFragment.showLoginActivity -> MainV2Activity.showLoginActivity Login MainV2Activity.intent.extras 레거시 Activity 타입 분기와 로그인 화면 이동 계약 유지 MainV2Activity.showLoginActivity()를 유지하고 ensureV2Access(Login)에 위임
MainV2 deeplink/audio notification onCreate, onNewIntent, handleAudioNotificationRoute, executeDeeplink Login 허용 여부 확인 또는 Login guard EXTRA_DATA, deep_link, EXTRA_AUDIO_NOTIFICATION_ROUTE, EXTRA_AUDIO_CONTENT_ID audio route가 먼저 소비되고 invalid audio content id는 false 반환해 기존 후속 처리 가능 route 순서와 반환 계약 보존, side-effect 없는 분기는 isV2AccessAllowed(Login) 사용
Content 상세/전체/overview ContentMainFragment.openAudioContentDetail, openSeriesDetail, openContentOverview, openContentAllTab Login 또는 콘텐츠 성인 여부에 따른 AdultContent Constants.EXTRA_AUDIO_CONTENT_ID, series id, ContentOverviewType, MainContentAllType, ContentSort Activity result 없음, 전체 탭 이동은 MainV2Activity.openContentAllTab 호출 Phase 1에서는 guard만 공통화, Content Action 계약은 Phase 2에서 구현
Live entry HomeMainFragment.onLiveClick, HomeOnAirLiveActivity.enterLiveRoom, CreatorChannelActivity.onCreatorChannelCurrentLiveClicked Login 후 room detail의 isAdult로 AdultContent room id/live id, room detail 기존 liveCoordinator.enterLiveRoom 및 on-air 목록 refresh/상태는 호출부에 유지 Phase 1에서는 중복 adult guard 제거, Live Action은 Phase 3
Creator / Community CreatorChannelActivity.onCreatorChannelCommunityPostClicked, Home creator/profile/community 진입 Login creator id, post id, existing newIntent Creator Channel의 Home/Community/FanTalk refresh 조합과 launcher는 Activity 소유 유지 Phase 1에서는 login guard만 공통화, result adapter는 Phase 4
Chat / DM HomeMainFragment.openFollowingChat, title bar search/cash, MainV2Activity chat route Login AI room id, DM room id, existing chat intent helpers DM room result adapter/observer 없음, 기존 Activity 전환만 수행 Phase 1에서는 login guard만 공통화, Chat Action은 Phase 5

observer 판단

  • Phase 1 Access는 one-shot UI navigation/action 차단이므로 전역 observer나 EventBus를 추가하지 않는다.
  • ActivityResult/refresh가 있는 Creator Channel Community/FanTalk는 기존 launcher와 Activity 내부 composition handler 후보를 Phase 4에서 정리한다.
  • Live room entry는 detail 조회 후 접근 판단이 필요하므로 repository/result observer가 아니라 호출부 callback 조합을 유지하고 Phase 3에서 Action 계약을 분리한다.

검증 기록

  • 2026-07-14: rgexplore 조사로 Access, Content, Live, Creator, Community, Chat 후보의 호출부와 차이를 확정했다. Phase 1 구현 범위는 Access로 제한하고, Content 이후 도메인 Action은 카탈로그 상태로 유지한다.
  • 2026-07-14: 리뷰 보완으로 MainV2Activity의 직접 isLoggedIn()/showLoginActivity() 소유 여부를 재확인했고, 로그인 UX 실행이 필요한 경로와 단순 허용 여부 확인 경로를 분리해 공통 Access 정책으로 이관하기로 확정했다.
  • 2026-07-14: 리뷰 게이트 보완으로 Action 후보별 호출 함수, Access 조건, Intent 입력, Result/callback 차이, observer 판단을 표로 추가해 Task 0.3 검증 기준을 충족하도록 문서를 보강했다.
  • 2026-07-14: Phase 0/1 리뷰 답변에 따라 레거시 MyPageFragment에서 MainV2Activity.showLoginActivity()를 호출하는 호환 경로를 상세 호출부 표에 추가했다. 레거시 파일은 수정하지 않았다.

Phase 1: V2 공통 Access 정책과 실행기

  • Task 1.1: Access 판단 단위 테스트 작성

    • 생성 예정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/access/AccessPolicyTest.kt
    • 검증 대상:
      • 로그인 필요 요구사항의 로그인/미로그인 결과
      • 성인 콘텐츠 요구사항의 미로그인 결과
      • 국내 미인증 사용자의 본인인증 필요 결과
      • 국외 사용자의 본인인증 생략
      • 성인 콘텐츠 표시 설정 비활성 결과
      • 모든 조건 충족 시 허용 결과
    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.AccessPolicyTest"
      
    • 기대 결과: 구현 전 RED, 구현 후 GREEN.
    • 검증 기록:
      • 2026-07-14: 테스트를 먼저 추가한 뒤 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.AccessPolicyTest"를 실행해 AccessPolicy, AccessRequirement, AccessDecision, AccessState 미정의 컴파일 오류로 RED를 확인했다.
  • Task 1.2: Access 입력·출력 계약과 순수 정책 구현

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessRequirement.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessDecision.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessState.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/AccessPolicy.kt
    • 작업: 로그인, 국가/본인인증, 성인 콘텐츠 표시 설정을 명시적 입력과 결과로 판단한다.
    • 검증 기준: Android, Activity, Fragment, Dialog, Retrofit 의존 없이 Task 1.1 테스트가 통과한다.
    • 검증 기록:
      • 2026-07-14: AccessRequirement, AccessDecision, AccessState, AccessPolicy를 추가하고 같은 단일 테스트 명령을 재실행해 GREEN을 확인했다.
  • Task 1.3: Activity/Fragment 공용 Access 실행기 구현

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt
    • 수정 가능 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • AccessDecision을 로그인, 본인인증, 콘텐츠 설정 UX로 연결한다.
      • 기존 LoginActivity, V2ModalDialog, Auth.auth, MyPageViewModel.authVerify, ContentSettingsActivity를 재사용한다.
      • Activity와 Fragment에서 같은 공개 함수를 호출할 수 있게 한다.
    • 검증 기준:
      • Allowed에서만 원래 action을 한 번 실행한다.
      • 거부 결과에서 원래 action을 실행하지 않는다.
      • 기존 Intent flag/extra와 인증 완료 후 흐름을 유지한다.
    • 검증 기록:
      • 2026-07-14: V2AccessGuard를 추가해 Activity/Fragment 공용 ensureV2Access를 제공하고, 기존 LoginActivity extra/flag, V2ModalDialog, Auth.auth, MyPageViewModel.authVerify, SplashActivity, ContentSettingsActivity extra 계약을 source test로 고정했다.
      • 2026-07-14: MainV2Activity의 초기화/조건부 실행처럼 로그인 UX를 띄우지 않아야 하는 경로를 위해 같은 AccessPolicyAccessStateProvider를 사용하는 isV2AccessAllowed를 추가했다.
      • 2026-07-14: 리뷰 보완으로 기존 MainV2Activity.isLoggedIn()token.isNotBlank() && token.length > 10 기준을 AccessPolicy에 보존하고, 10자/11자/공백 토큰 경계 테스트를 추가했다.
      • 2026-07-14: 레거시 MyPageFragment 본문을 수정하지 않고 기존 MainV2Activity.showLoginActivity() 호출 계약을 유지하기 위해 MainV2Activity에 얇은 호환 메서드를 남기고 내부에서 ensureV2Access(AccessRequirement.Login)을 호출하게 했다.
      • 2026-07-14: 사용자 확인 결과 현재 token.length > 10 기준은 실제 토큰 계약에서 동작 변경을 만들지 않으므로 추가 코드 변경 없이 유지한다.
  • Task 1.4: 기존 Access 구현을 공통 실행기로 전환

    • 수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuard.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt
    • 작업:
      • 기존 호출부를 V2AccessGuard로 전환한다.
      • 화면 진입 guard와 클릭 action guard의 기존 종료/후처리 차이를 보존한다.
      • 모든 호출부 전환 후 중복 ensureLoginAndAdultAuth 구현을 제거한다.
    • 검증 기준:
      • CreatorChannelActivity, HomeOnAirLiveActivity에 별도 로그인/성인 판단 함수가 남지 않는다.
      • 대상 클릭 handler에 직접 token 조건문이 남지 않는다.
    • 검증 기록:
      • 2026-07-14: HomeMainFragment, ContentMainFragment, ChatMainFragment, CreatorChannelActivity, HomeOnAirLiveActivity 호출부를 ensureV2AccessAccessRequirement로 전환했다. MainV2LoginGuard.kt와 Activity별 ensureLoginAndAdultAuth 구현을 제거했다.
      • 2026-07-14: MainV2Activity의 chat tab, audio notification route는 ensureV2Access(AccessRequirement.Login)로 전환하고, deeplink 실행 전 조건, push token update, member info 조회 등은 isV2AccessAllowed(AccessRequirement.Login)로 전환했다. Activity 내부 직접 isLoggedIn() 구현은 제거했고, 기존 외부 호출 호환용 showLoginActivity()는 공통 guard 위임만 수행한다.
      • 2026-07-14: rg -n "ensureMainV2NavigationAllowed|ensureLoginAndAdultAuth" app/src/main/java/kr/co/vividnext/sodalive/v2 app/src/test/java/kr/co/vividnext/sodalive/v2 --glob '*.kt' 결과 production 잔존은 없고, source test의 제거 확인 assertion만 남았다.
      • 2026-07-14: 대상 UI 파일의 SharedPreferenceManager.(token|countryCode|isAuth|isAdultContentVisible) 검색 결과 Creator Channel의 API auth header 생성 함수만 남았고, 접근 정책 직접 판단은 제거됐다.
  • Task 1.5: 기존 Access source test를 새 계약에 맞게 전환

    • 수정 예정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2AudioNotificationRouteSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainTitleBarActionSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentLoginGuardSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainTitleBarActionSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainTitleBarActionSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
    • 작업: 이전 함수명 고정 assertion을 새 공통 Access 사용 및 기존 UX 보존 검증으로 최소 수정한다.
    • 검증 기준: 정책은 AccessPolicyTest, 호출 연결은 필요한 source test가 담당한다.
    • 검증 기록:
      • 2026-07-14: MainV2LoginGuardSourceTest, Home/Content/Chat title bar source test, Home/Content login guard source test, CreatorChannelActivitySourceTest의 Access 관련 assertion을 새 V2AccessGuard 계약 기준으로 전환했다.
      • 2026-07-14: MainV2ActivitySourceTestMainV2AudioNotificationRouteSourceTest를 새 ensureV2Access/isV2AccessAllowed 경계 기준으로 갱신하고, MainV2Activity에 직접 isLoggedIn() 판단 함수가 남지 않음을 고정했다.
      • 2026-07-14: MainV2LoginGuardSourceTestMainV2Activity.showLoginActivity()가 기존 호출명은 유지하되 ensureV2Access(AccessRequirement.Login)으로 위임하는지 확인하는 assertion을 추가했다.
  • Task 1.6: Access Phase 검증 및 문서 반영

    • 파일:
      • docs/agent-guides/build-test-style.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck --rerun-tasks
      git diff --check HEAD
      
    • 기대 결과: 신규 Access 테스트와 영향받은 기존 테스트, 컴파일, whitespace 검증이 통과한다. ktlintCheck는 전체 기준선을 재실행하고, 저장소 기존 위반으로 실패하면 Phase 1 변경 파일에 신규 위반이 없음을 완료 gate로 확인한다.
    • 검증 기록:
      • 2026-07-14: Access 관련 좁은 회귀 명령 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.현재 라이브 카드는 동일한 enterLiveRoom 플로우로 입장한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk fixed plus source는 Activity overlay와 write result refresh 계약을 연결한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk item click source는 owner gate와 상세 ActivityResult refresh를 연결한다"BUILD SUCCESSFUL로 통과했다.
      • 2026-07-14: 더 넓은 관련 테스트 묶음은 497개 중 CreatorChannelActivitySourceTest > 팬Talk 섹션은 Figma ListComment layout과 bind로 렌더링한다 1개가 @drawable/ic_chevron_down_white source assertion으로 실패했다. Access 변경 파일과 무관한 기존 FanTalk layout assertion으로 확인해 별도 관찰로 남긴다.
      • 2026-07-14: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다.
      • 2026-07-14: ./gradlew :app:ktlintCheck는 이번 변경과 무관한 app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt:118-120 기존 max line length/argument wrapping 위반으로 실패했다. 해당 파일에는 이번 diff가 없음을 git diff -- app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt로 확인했다.
      • 2026-07-14: git diff --check는 whitespace 오류 없이 통과했다.
      • 2026-07-14: MainV2LoginGuardSourceTest의 신규 adapter assertion line length를 수정한 뒤 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" && ./gradlew :app:compileDebugKotlin && ./gradlew :app:ktlintCheck && git diff --check를 재실행해 모두 BUILD SUCCESSFUL 또는 무출력 통과를 확인했다.
      • 2026-07-14: 리뷰 게이트에서 token.isNotBlank() && token.length > 10 보존과 mypage adapter 제거가 차단 이슈로 확인되어, AccessPolicy 로그인 기준과 MainV2Activity.showLoginActivity() 호환 메서드 위임 방식으로 보완했다.
      • 2026-07-14: 보완 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" && ./gradlew :app:compileDebugKotlin && ./gradlew :app:ktlintCheck && git diff --check를 재실행해 모두 BUILD SUCCESSFUL 또는 무출력 통과를 확인했다.
      • 2026-07-14: 추가 리뷰에서 11자 이상 공백 토큰이 허용되는 회귀를 확인해 AccessPolicyisNotBlank() 조건을 복원하고 공백 토큰 회귀 테스트와 PRD 문구를 보강했다.
      • 2026-07-14: Kotlin LSP 진단은 환경에 kotlin-lsp가 설치되어 있지 않아 실행할 수 없었고, Gradle compile/test로 대체 검증했다.
  • Task 1.7: 리뷰 보완으로 Access 실행 계약과 source test 범위 강화

    • 생성 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuardTest.kt
    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/V2AccessGuard.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
      • docs/agent-guides/build-test-style.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • AccessDecision 실행 분기를 Android 비의존 함수로 분리해 Allowed callback과 거부 UX callback 실행 횟수를 실제 입력·출력으로 검증한다.
      • Creator Channel 커뮤니티 게시물 클릭 source test가 Activity 전체가 아닌 해당 handler 본문에서 guard와 launcher 순서를 확인하게 한다.
      • Task 1.4와 Task 1.5의 실제 변경 파일 목록을 현재 diff와 일치시킨다.
    • 검증 기준:
      • Allowed는 원래 action을 정확히 한 번 실행하고 거부 UX callback은 실행하지 않는다.
      • 모든 거부 결과는 원래 action을 실행하지 않고 대응 UX callback만 정확히 한 번 실행한다.
      • 커뮤니티 게시물 handler 밖의 다른 로그인 guard로 source test가 통과하지 않는다.
    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.V2AccessGuardTest"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.커뮤니티 게시물 클릭 source는 상세 Activity newIntent로 진입하고 invalid postId를 무시한다"
      
    • 기대 결과: 실행 계약 테스트는 구현 전 RED, 구현 후 GREEN이고 강화된 source test가 통과한다.
    • 검증 기록:
      • 2026-07-14: V2AccessGuardTest를 먼저 추가하고 단일 테스트를 실행해 executeAccessDecision 미정의 컴파일 오류로 RED를 확인했다.
      • 2026-07-14: 기존 V2AccessGuard.handleDecision의 분기를 Android 비의존 executeAccessDecision으로 추출한 뒤 같은 단일 테스트가 BUILD SUCCESSFUL로 전환되는 것을 확인했다. 네 가지 AccessDecision별 반환값과 실행 callback이 정확히 하나인지 검증한다.
      • 2026-07-14: Creator Channel 커뮤니티 handler source에서 guard 문자열을 일시 제거한 변이 입력으로 대상 테스트가 실패하는 것을 확인한 뒤, 실제 handler source 검사로 복원해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-14: Access, Main, On-air 전체와 변경 영향 Creator Channel 4개 테스트를 묶어 재실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-14: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintCheck, ./gradlew tasks --all을 실행해 모두 BUILD SUCCESSFUL을 확인했다. ktlint에는 기존 .editorconfigdisabled_rules 속성 사용에 대한 deprecation warning이 남아 있다.
      • 2026-07-14: git diff --check HEAD를 실행해 staged와 unstaged를 합친 전체 변경에 whitespace 오류가 없음을 확인했다.
      • 2026-07-14: 사용자 확인에 따라 현재 layout에 존재하지 않아 기존 테스트를 실패시키던 ic_chevron_down_white source assertion은 수동 삭제한 상태를 유지한다. Phase 1 운영 코드 변경과는 무관한 기존 테스트 정리다.
  • Task 1.8: 코드 리뷰 후 ktlint 재현성 보완

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • 캐시를 배제한 ktlintCheck에서 재현된 기존 source test 3줄의 max line length와 argument wrapping 위반만 수정한다.
      • 기존 인증 문자열과 테스트 의미는 변경하지 않는다.
    • 검증 기준:
      • ./gradlew :app:ktlintCheck --rerun-tasks로 전체 기준선을 재검사하고, Phase 1 변경 파일에 신규 위반이 없음을 보고서에서 확인한다.
      • 수정한 source test와 Access 관련 회귀 테스트가 통과한다.
      • git diff --check HEAD가 통과한다.
    • RED 검증 기록:
      • 2026-07-14: ./gradlew :app:ktlintCheck --rerun-tasks를 실행해 HomeFollowingFragmentSourceTest.kt:118-120의 max line length와 argument wrapping 위반으로 BUILD FAILED를 재현했다. 해당 파일은 기존 Phase 1 diff에 없었으므로, 이전 통과 기록은 Gradle up-to-date 캐시에 의해 실제 전체 린트를 재실행하지 않은 결과였다.
    • GREEN 검증 기록:
      • 2026-07-14: 세 문자열 assertion만 ktlint 규칙에 맞게 줄바꿈하고 HomeFollowingFragmentSourceTest--rerun-tasks로 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-14: 수정 후 ./gradlew :app:ktlintCheck --rerun-tasks를 재실행한 결과 ktlintTestSourceSetCheck 보고서는 0건이 되었고 Phase 1 변경 파일에서 위반이 검출되지 않았다. 전체 명령은 Phase 1 범위 밖 파일의 기존 ktlint 위반으로 실패했으며, 레거시 운영 코드 위반과 함께 app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt:29의 기존 V2 위반 1건도 포함되므로 전체 통과가 아닌 기준선 제약으로 기록한다.
      • 2026-07-14: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest" --rerun-tasks를 실행해 Access 회귀와 수정한 source test가 BUILD SUCCESSFUL로 통과함을 확인했다.
      • 2026-07-14: git diff --check HEAD는 whitespace 오류 없이 통과했다.
  • Task 1.9: 코드 리뷰 후 FanTalk source test와 ktlint gate 정정

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • FanTalk write와 item click source test가 Activity 전체가 아닌 해당 함수 본문에서 login guard와 launcher 순서를 확인하게 한다.
      • PRD의 ktlint 완료 기준을 현재 저장소 기준선에 맞춰 ktlintCheck 전체 재실행과 Phase 변경 파일 신규 위반 없음으로 명확히 한다.
      • 기존 ktlint 실패 기록에서 V2 파일 위반 1건을 레거시 위반으로 잘못 분류한 표현을 정정한다.
    • 검증 기준:
      • FanTalk write/item click handler 밖의 다른 ensureV2Access 문자열로 source test가 통과하지 않는다.
      • Phase 1 완료 판정은 전체 ktlint 통과로 오인되지 않고 기준선 제약과 변경 파일 신규 위반 여부를 함께 기록한다.
      • 관련 source test와 whitespace 검증이 통과한다.
    • 검증 기록:
      • 2026-07-14: openFanTalkWriteonCreatorChannelFanTalkItemClicked 본문만 sourceSection으로 추출해 guard, launcher, 실행 순서를 검증하도록 source test를 강화했다.
      • 2026-07-14: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk fixed plus source는 Activity overlay와 write result refresh 계약을 연결한다" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest.FanTalk item click source는 owner gate와 상세 ActivityResult refresh를 연결한다" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-14: ./gradlew :app:ktlintCheck --rerun-tasksktlintMainSourceSetCheck 기준선 위반으로 실패했다. 보고서에는 기존 main source 위반 551줄이 남아 있고, 그중 app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentCommentedAudioAdapter.kt:29의 V2 max line length 위반 1건이 포함된다.
      • 2026-07-14: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과해 이번에 수정한 source test 파일에는 신규 ktlint 위반이 없음을 확인했다.
      • 2026-07-14: git diff --check HEAD는 whitespace 오류 없이 통과했다.
      • 2026-07-14: 리뷰어 게이트에서 P2/P3 반영, assertion 약화 없음, 문서 모순 없음에 대해 조건부 없는 PASS를 확인했다.
  • Task 1.10: Phase 0/1 재리뷰 결과 반영

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2LoginGuardSourceTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • V2AccessGuard.handleDecision이 각 거부 결과를 로그인, 본인인증, 콘텐츠 설정 UX callback에 연결하는 실제 결선을 source assertion으로 고정한다.
      • Task 1.6의 ktlint 기대 결과를 PRD의 최종 gate와 일치시킨다.
    • 검증 기준:
      • 로그인, 본인인증, 콘텐츠 설정 callback 결선이 서로 바뀌면 source test가 실패한다.
      • ktlintCheck는 전체 기준선을 재실행하고, 기존 위반으로 실패하면 Phase 1 변경 파일의 신규 위반이 없음을 확인한다.
      • 관련 회귀 테스트, 컴파일, test source ktlint, whitespace 검증이 통과한다.
    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck --rerun-tasks
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      git diff --check HEAD
      
    • 기대 결과: 결선 source test, 컴파일, test source ktlint, whitespace 검증이 통과하고 전체 ktlint 기준선 제약이 기존 기록과 일치한다.
    • 검증 기록:
      • 2026-07-14: handleDecision 본문만 추출해 로그인, 본인인증, 콘텐츠 설정 AccessDecision이 각각 startLogin, showAuthDialog, startContentSettings callback에 연결되는지 검증하도록 source test를 강화했다.
      • 2026-07-14: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2LoginGuardSourceTest" --rerun-tasks를 실행해 테스트 1건이 실패 없이 통과하고 BUILD SUCCESSFUL임을 확인했다.
      • 2026-07-14: 위 회귀 테스트에서 production/test source를 새로 컴파일했으며, 별도 ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다.
      • 2026-07-14: ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반 551줄로 실패했다. Phase 1 변경 main source의 신규 위반은 0건이고, V2 위반 1건은 변경하지 않은 ContentCommentedAudioAdapter.kt:29의 기존 max line length 위반이다.
      • 2026-07-14: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했으며 test source 보고서 위반은 0건이다.
      • 2026-07-14: ./gradlew tasks --allBUILD SUCCESSFUL로 통과해 문서 변경 후 Gradle task 구성을 확인했다.
      • 2026-07-14: git diff --check HEAD는 whitespace 오류 없이 통과했다.
  • Task 1.11: Phase 0/1 리뷰의 Activity별 Access 결선 테스트 보강

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveEntryPolicyTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • On-air 라이브 입장이 로그인 가드, room detail 조회, 성인 콘텐츠 가드, 기존 입장 함수 순서로 실행되는지 함수 본문 source로 검증한다.
      • Creator Channel 화면 진입이 유효한 creator ID 확인 후 로그인 가드를 거치며, 거부 시 화면 조립 전에 종료되는지 검증한다.
      • Creator Channel AI 채팅이 성인 콘텐츠 가드 통과 후에만 채팅방 생성을 요청하는지 클릭 listener 범위에서 검증한다.
    • 검증 기준:
      • 각 함수 밖의 다른 ensureV2Access 문자열로 테스트가 통과하지 않는다.
      • 각 가드 문자열을 제거한 변이 source에서는 대응 테스트가 실패한다.
      • production 코드는 변경하지 않고 관련 source test와 기존 Access 회귀 테스트가 통과한다.
    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.onair.HomeOnAirLiveEntryPolicyTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      git diff --check HEAD
      
    • 기대 결과: 변이 source RED 확인 후 실제 source 기준 대상 테스트와 관련 회귀 테스트, 컴파일, test source ktlint, whitespace 검증이 통과한다.
    • RED 검증 기록:
      • 2026-07-14: 세 결선 테스트에서 각 대상 가드 문자열을 제거한 변이 source를 사용해 실행한 결과, On-air 로그인/성인 결선 1건과 Creator Channel 진입/AI 채팅 결선 2건이 각각 실패하고 BUILD FAILED가 발생함을 확인했다.
    • GREEN 검증 기록:
      • 2026-07-14: 변이를 제거하고 실제 production source를 대상으로 신규 결선 테스트 3건을 --rerun-tasks로 실행해 모두 통과하고 BUILD SUCCESSFUL임을 확인했다.
      • 2026-07-14: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.access.*" --tests "kr.co.vividnext.sodalive.v2.main.*" --tests "kr.co.vividnext.sodalive.v2.live.onair.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks를 실행해 관련 회귀 테스트 503건이 실패, 오류, 스킵 없이 통과했다.
      • 2026-07-14: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다.
      • 2026-07-14: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과해 test source 신규 위반이 없음을 확인했다.
      • 2026-07-14: ./gradlew tasks --allBUILD SUCCESSFUL로 통과해 문서 변경 후 Gradle task 구성을 확인했다.
      • 2026-07-14: git diff --check HEAD는 whitespace 오류 없이 통과했다.
      • 2026-07-14: Task 1.11에서는 production 코드를 추가 변경하지 않고 기존 source test 클래스 두 곳과 계획 문서만 보강했다.

Phase 2: Content 공통 Action

  • Task 2.1: 오디오/시리즈 진입 호출부와 계약 확정

    • 확인 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업: ID, 성인 여부, Activity result, route source, 기존 extra 차이를 목록화하고 Action 계약을 확정한다.
    • 검증 기준:
      • 화면 전용 UI model을 받지 않는 최소 command/result 계약이 정해진다.
      • Activity result 또는 호출 화면 후처리가 있는 경로는 Content result adapter와 단일 handler 필요 여부가 결정된다.
    • 확인 결과:
      • 공개 입력은 audioContentId 또는 seriesId와 호출부가 이미 알고 있는 성인 콘텐츠 여부만 받는 AudioDetail/SeriesDetail command로 제한한다. Home/Content/Creator 전용 UI model과 Response는 Action에 전달하지 않는다.
      • Action 결과는 유효하지 않은 ID의 Ignored, Access가 거부된 Blocked, 허용된 오디오/시리즈 navigation 결과로 구분한다. ContentActionHandler는 허용 결과만 기존 Constants.EXTRA_AUDIO_CONTENT_ID 또는 Constants.EXTRA_SERIES_ID로 변환한다.
      • Home banner의 series와 최근 활동의 audio는 성인 여부를 제공하지 않으므로 기존처럼 AccessRequirement.Login을 사용한다. Home의 event/creator/link와 community route는 Content Action 범위에 포함하지 않는다.
      • Content 추천 카드와 전체 탭은 showAdultBadge를 알고 있는 경우 이를 전달한다. 댓글/랭킹/원작 series/banner처럼 성인 여부가 없는 경로는 로그인만 확인한다.
      • Content Overview audio, Creator Channel Home audio, Audio tab, Live replay, Series tab은 성인 여부를 보유하므로 command에 전달한다. Creator Channel schedule audio/live replay와 Home series는 성인 여부가 없어 로그인만 확인한다.
      • 상세 진입은 모두 일반 startActivity이며 ActivityResultLauncher, route source extra, 호출 화면 후처리가 없다. 따라서 별도 Content result adapter나 feature composition handler를 추가하지 않는다.
      • MainV2Activity의 audio notification/deeplink/mini player와 series deeplink는 Phase 1에서 route 소비 순서와 boolean 반환 계약을 별도로 고정한 시스템 route이므로, PRD에 합의된 Home/Content/Content Overview/Creator Channel 전환 범위에는 포함하지 않는다.
    • 검증 기록:
      • 2026-07-14: rg와 대상 source 확인으로 V2 상세 Activity 직접 참조, ID/extra, 성인 여부 보유 여부, Activity result 사용 여부를 대조해 위 command/result 계약과 전환 범위를 확정했다.
      • 2026-07-14: 격리 worktree에서 변경 전 ./gradlew :app:testDebugUnitTest를 실행해 전체 debug 단위 테스트가 BUILD SUCCESSFUL로 통과하는 기준선을 확인했다. 기존 dependency/manifest/Kotlin deprecation 경고는 있었지만 실패는 없었다.
  • Task 2.2: Content Action 순수 계약 테스트 및 구현

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentAction.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionCommand.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionTest.kt
    • 검증 대상: invalid ID, audio/series route, 성인 Access 요구사항 전달, 허용/차단 결과.
    • 검증 기준: feature UI model과 DTO 의존 없이 입력·출력이 테스트된다.
    • RED 검증 기록:
      • 2026-07-14: ContentActionTest를 먼저 추가하고 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.content.action.ContentActionTest"를 실행해 ContentAction, ContentActionCommand, ContentActionResult, ContentActionHandler 미정의 컴파일 오류와 BUILD FAILED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-14: ContentActionCommand, ContentAction, ContentActionResult, ContentActionHandler를 최소 구현하고 같은 단일 테스트 명령을 재실행해 7개 테스트와 빌드가 통과했다. Action은 ID/성인 여부와 Access 함수만 입력받고 feature UI model, DTO, Android UI에 의존하지 않는다.
  • Task 2.3: Content navigation adapter 구현 및 전체 호출부 전환

    • 생성/수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/ContentActionHandler.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/model/AudioRecommendationsUiModels.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ContentOverviewActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/ui/ContentOverviewAdapter.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/CreatorChannelSeriesFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/series/ui/CreatorChannelSeriesAdapter.kt
      • 위 호출부 계약을 검증하는 기존 source/layout 테스트
    • 작업: 기존 extra와 Access UX를 보존하면서 오디오/시리즈 상세 진입을 한 곳으로 통합한다.
    • 검증 기준: 대상 UI에서 상세 Activity Intent 직접 생성이 제거되고 Content Action을 사용한다.
    • RED 검증 기록:
      • 2026-07-14: 기존 source/layout 테스트를 새 Content Action 결선 계약으로 먼저 변경하고 관련 6개 테스트 클래스 207건을 --rerun-tasks로 실행했다. 대상 화면의 직접 상세 Intent, series banner Intent, ID-only callback이 남아 있어 예상한 13건이 실패하고 BUILD FAILED가 발생함을 확인했다.
    • GREEN 검증 기록:
      • 2026-07-14: Home, Content, Content Overview, Creator Channel 호출부를 handleContentAction으로 전환하고 대상 feature/model source의 AudioContentDetailActivity/SeriesDetailActivity 직접 참조를 제거했다. 상세 Activity와 기존 ID extra 생성은 ContentActionHandler만 소유한다.
      • 2026-07-14: Content Overview와 Creator Channel Audio/Live replay/Series tab callback이 기존 UI model의 showAdultBadge를 command까지 전달하도록 연결했다. 성인 정보를 제공하지 않는 Home, schedule, Creator Home series 경로는 기존 Login 요구사항을 유지했다.
      • 2026-07-14: 같은 6개 테스트 클래스 207건을 --rerun-tasks로 재실행해 실패, 오류, 스킵 없이 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-14: 결선 GREEN 중 발견한 기존 전체 탭 source assertion은 다른 helper의 openSeriesDetail(seriesId) 문자열에 우연히 의존하고 있었다. 실제 계약인 openSeriesDetail(seriesId, findAllTabSeriesAdultAccess(seriesId))를 검증하도록 강화한 뒤 단일 테스트와 207건 결선을 다시 통과시켰다.
  • Task 2.4: Content Phase 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.content.action.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.content.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck
      git diff --check
      
    - 수동 확인: Home, Content, Content Overview, Creator Channel에서 audio/series 상세 진입과 성인 차단 UX가 기존과 동일하다.
    - 검증 기록:
      - 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.content.action.*"`, `--tests "kr.co.vividnext.sodalive.v2.main.content.*"`, `--tests "kr.co.vividnext.sodalive.v2.creator.channel.*"`, 추가 Home 회귀 `--tests "kr.co.vividnext.sodalive.v2.main.home.*"`를 각각 실행해 모두 `BUILD SUCCESSFUL`을 확인했다. Content 묶음은 99개 테스트가 통과했다.
      - 2026-07-14: Phase 1에서 작성된 `ContentMainFragmentLoginGuardSourceTest`가 이전 화면 내부 `ensureV2Access` 구현을 고정해 Content 묶음 3건이 실패했다. 실제 Access 계약은 `ContentMainFragment -> ContentAction -> ContentActionHandler` 순으로 이동했으므로 invalid ID, 성인 요구사항, Access-before-navigation을 새 소유자에서 검증하도록 테스트를 갱신한 뒤 단일 클래스와 Content 99건을 다시 통과시켰다.
      - 2026-07-14: `./gradlew :app:testDebugUnitTest`를 실행해 전체 debug 단위 테스트가 44초에 `BUILD SUCCESSFUL`로 통과했고, `./gradlew :app:compileDebugKotlin`도 `BUILD SUCCESSFUL`로 통과했다.
      - 2026-07-14: `./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks`는 `BUILD SUCCESSFUL`로 통과했고 test source 리포트는 0건이다. `./gradlew :app:ktlintCheck`는 이번 변경과 무관한 기존 main source 기준선 위반 550줄로 실패했으며, 리포트에서 Phase 2 변경 Kotlin 파일의 위반은 0건임을 확인했다. 레거시 파일은 수정하지 않았다.
      - 2026-07-14: 대상 Home, Content, Content Overview, Creator Channel 운영 source에서 `AudioContentDetailActivity`, `SeriesDetailActivity` 및 상세 ID extra를 검색한 결과 잔존이 없고, `ContentActionHandler`만 기존 Activity/extra 생성을 소유함을 확인했다. Phase 1에서 계약을 고정한 `MainV2Activity` 시스템 route는 범위대로 유지했다.
      - 2026-07-14: `./gradlew tasks --all`은 `BUILD SUCCESSFUL`로 통과했다. `adb devices`는 연결된 기기가 없음을 반환해 실기기 수동 탭 검증은 수행하지 못했고, 대신 Robolectric handler 테스트로 허용/차단과 기존 extra를 검증했다.
      - 2026-07-14: `git diff --check HEAD`는 whitespace 오류 없이 통과했다.
    
    

Phase 3: Live 공통 Action

  • Task 3.1: 기존 Live 흐름 차이와 레거시 의존 목록 확정

    • 확인 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt
    • 작업: 상세, 입장, 예약, 결제, 비밀번호, 오디오 중단, Activity result, refresh callback, navigation 차이를 기록한다.
    • 검증 기준:
      • Live 공통 정책과 화면별 후처리가 구분된다.
      • 라이브 생성 결과를 포함한 raw Activity result의 Live 결과 변환과 feature 단일 handler가 확정된다.
    • 확인 결과:
      • Home은 로그인 후 상세를 조회해 isAdult를 확인하고 기존 coordinator에 상세를 전달하며, Creator Channel 현재 라이브는 응답의 isAdult를 먼저 확인한 뒤 coordinator가 상세를 다시 조회한다. On-air는 로그인, 상세 조회, channel 유효성, 성인 접근을 확인한 뒤 Activity 안에 복제된 입장 분기를 실행한다.
      • 공통 입장 결정 순서는 channel 없음의 상세 표시, 관리자 직접 입장, 무료/결제 완료 방의 비밀번호 또는 직접 입장, 미결제 유료 비밀번호 또는 결제 Dialog다. 오디오 서비스 중단과 입장 성공 후 LiveRoomActivity 이동도 세 흐름에서 동일하다.
      • 일정 상세의 예약, 수정, 시작, 취소는 기존 coordinator만 소유하며 레거시 LiveViewModel, Dialog, Activity를 호출한다. 이 동작은 Live 소유 coordinator로 함께 이동하되 정책 판단과 화면 adapter를 추가로 분리하지 않는다.
      • On-air는 channel이 없으면 공통 오류 Toast를 표시하고, Home/Creator Channel은 라이브 상세를 표시한다. 이 차이는 공통 입장 정책이 아닌 호출 화면의 unavailable callback으로 유지한다.
      • Creator Channel의 라이브 생성 raw Activity result는 성공 여부, room ID, channel name을 LiveCreationResult.Enter, Created, RefreshOnly, Ignored로 변환한다. Activity의 handleLiveCreationResult 단일 handler가 성공 결과의 Home refresh 후 즉시 입장 또는 생성 안내를 결정한다. 실패/cancel은 Ignored, channel은 있지만 room ID가 유효하지 않은 성공 결과는 RefreshOnly로 변환한다.
      • 별도 observer/SharedFlow는 추가하지 않는다. Live 생성 결과의 소비자는 Creator Channel Activity 하나이고 Home/On-air refresh 요구도 각각 현재 화면 callback으로 충분하다.
    • 검증 기록:
      • 2026-07-14: 대상 운영 코드와 source test를 대조해 위 공통 정책, 레거시 의존, Activity result 및 화면별 후처리 차이를 확정했다.
      • 2026-07-14: 구현 전 ./gradlew :app:testDebugUnitTest를 실행해 전체 debug 단위 테스트 기준선이 BUILD SUCCESSFUL임을 확인했다.
  • Task 3.2: Live 입장 결정 계약과 단위 테스트 작성

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryDecision.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryPolicy.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryPolicyTest.kt
    • 검증 대상: 관리자, 무료/결제 완료, 비밀번호, 유료, 상세 표시 분기.
    • 검증 기준: Activity/Dialog 없이 기존 분기 순서를 순수 입력·출력으로 검증한다.
    • RED 검증 기록:
      • 2026-07-14: LiveEntryPolicyTest를 먼저 추가하고 단일 테스트를 실행해 LiveEntryDecision, LiveEntryContext, LiveEntryPolicy 미정의 컴파일 오류와 BUILD FAILED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-14: channel 없음, 관리자, 무료/결제 완료 공개·비밀번호 방, 미결제 유료 공개·비밀번호 방만 표현하는 순수 정책을 최소 구현하고 같은 단일 테스트를 재실행해 BUILD SUCCESSFUL을 확인했다.
  • Task 3.3: Live Action/Coordinator 단일 구현과 호환 adapter 구성

    • 생성/이동/수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveCreationResult.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelLiveCoordinator.kt
    • 작업:
      • 기존 Coordinator 동작을 Live 소유 진입점으로 옮기고 필요한 동안 기존 이름은 얇은 adapter로 유지한다.
      • 하위 Activity가 반환하는 Live 결과는 명시적 result로 변환하고 호출 feature의 단일 handler로 전달한다.
    • 검증 기준: 새 구현이 Creator Channel feature를 import하지 않고 레거시 Live 기능만 adapter로 호출한다.
    • RED 검증 기록:
      • 2026-07-14: LiveActionsTestLiveActionCoordinatorSourceTest를 먼저 추가했다. 결과 타입/변환 함수 미정의 컴파일 실패를 확인한 뒤 최소 결과 계약을 추가했고, 새 coordinator 파일과 세 화면 결선 및 Creator Channel handler가 없어 source 결선 테스트 3건이 실패함을 확인했다.
      • 2026-07-14: diff 검토에서 channel은 있지만 room ID가 유효하지 않은 기존 무동작 edge를 발견해 Ignored 기대 테스트로 변경하고 실제 Created 반환 실패를 재현했다. 이 당시 계약은 후속 Task 3.6에서 성공 결과의 Home refresh를 보장하도록 RefreshOnly로 변경했다.
    • GREEN 검증 기록:
      • 2026-07-14: 기존 coordinator를 v2.live.action.LiveActionCoordinator로 이동하고 LiveEntryPolicy 결과에 따라 기존 LiveViewModel, Dialog, Activity를 호출하도록 전환했다. 모든 호출부를 함께 전환해 이전 이름의 호환 adapter는 남길 필요가 없었으며 feature 역의존도 제거했다.
      • 2026-07-14: LiveCreationResultresolveLiveCreationResult를 구현하고 Creator Channel의 handleLiveCreationResult 단일 handler가 Ignored, Created, Enter 후처리를 소유하도록 연결했다. invalid room ID는 기존처럼 Ignored로 보존한 뒤 결과 테스트를 통과시켰다. 이 동작은 후속 Task 3.6에서 RefreshOnly로 변경했다.
  • Task 3.4: Home/Creator Channel/On-air 호출부 전환과 중복 제거

    • 수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt
    • 검증 기준: 세 화면이 동일 Live Action을 사용하고 On-air의 중복 입장 조건이 제거된다.
    • 검증 기록:
      • 2026-07-14: Home은 lifecycle 확인 callback과 함께 공통 coordinator를 사용하고, Creator Channel은 현재 라이브의 알려진 성인 여부를 전달하며 일정 상세도 같은 coordinator를 사용하도록 전환했다.
      • 2026-07-14: On-air Activity의 상세 이후 관리자/무료/결제/비밀번호 분기와 Dialog/navigation 구현을 제거하고 공통 coordinator에 unavailable Toast callback만 전달했다. 이에 따라 단일 boolean helper와 파일도 제거했다.
      • 2026-07-14: 신규 결선 테스트와 기존 Home login guard, Creator Channel, On-air source 테스트를 함께 실행해 BUILD SUCCESSFUL을 확인했다.
  • Task 3.5: Live Phase 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck
      git diff --check
      
    - 수동 확인: 무료, 유료, 비밀번호, 관리자, 상세 표시, 예약/취소 흐름과 화면별 refresh 동작이 기존과 동일하다.
    - 검증 기록:
      - 2026-07-14: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.*" --tests "kr.co.vividnext.sodalive.v2.creator.channel.live.*" --rerun-tasks`를 실행해 Live 관련 테스트가 `BUILD SUCCESSFUL`로 통과했다.
      - 2026-07-14: 최종 변경 후 `./gradlew :app:testDebugUnitTest --rerun-tasks`를 실행해 전체 debug 단위 테스트와 production/test 컴파일이 1분 43초에 `BUILD SUCCESSFUL`로 통과했다. 기존 deprecation 및 manifest namespace 경고는 있었지만 실패는 없었다.
      - 2026-07-14: `./gradlew :app:compileDebugKotlin`은 별도 실행에서도 `BUILD SUCCESSFUL`로 통과했다.
      - 2026-07-14: `./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks`는 `BUILD SUCCESSFUL`로 통과했다. `./gradlew :app:ktlintCheck --rerun-tasks`는 기존 main source 기준선 위반으로 실패했으며, filename 위반을 수정한 뒤 main source 보고서 550줄에서 Phase 3 변경 Kotlin 파일 위반이 0건임을 확인했다.
      - 2026-07-14: `./gradlew tasks --all`은 `BUILD SUCCESSFUL`로 통과했고 `git diff --check HEAD`도 whitespace 오류 없이 통과했다.
      - 2026-07-14: `adb devices`에 연결된 기기/에뮬레이터가 없어 무료, 유료, 비밀번호, 관리자, 상세, 예약/취소의 실제 Dialog/Activity 수동 탭 검증은 수행하지 못했다. 해당 분기는 순수 정책 테스트와 기존 레거시 결선 source 테스트로 회귀 검증했다.
    
    
  • Task 3.6: Live 진입·생성 결과 회귀와 리뷰 발견사항 보완

    • 수정 대상 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveCreationResult.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionsTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 정리 대상 파일:
      • docs/20260715_라이브_진입과_생성_결과_회귀_수정/prd.md
      • docs/20260715_라이브_진입과_생성_결과_회귀_수정/plan-task.md
    • 작업:
      • unavailable을 성인 접근 확인과 오디오 중단보다 먼저 처리하되, 상세 fallback은 호출자 성인 힌트와 상세 응답을 OR로 합성한 접근 조건을 정상 진입과 함께 사용한다.
      • 라이브 생성 성공 후 즉시 입장할 room ID가 유효하지 않아도 RefreshOnly로 Home refresh를 보장한다.
      • 신규 source test의 ktlint 위반 3건을 고치고 test source 검증 게이트를 복구한다.
      • 연속 작업으로 별도 생성된 2026-07-15 문서의 요구사항과 검증 기록을 본 PRD와 Phase 3에 이관한 뒤 중복 문서를 제거한다.
    • 검증 기준:
      • 합성 성인 접근 조건이 정상 진입과 unavailable 상세 fallback에서 동일하게 사용된다.
      • LiveActionCoordinatorSourceTest와 Live action 관련 테스트가 통과한다.
      • :app:ktlintTestSourceSetCheck가 통과하고 Phase 3 변경 Kotlin 파일의 신규 main source 위반이 없다.
      • 별도 2026-07-15 문서가 남지 않고 현행 생성 결과 계약이 구현과 일치한다.
    • 이관한 구현·검증 기록:
      • 2026-07-15: 성공했지만 channel 정보가 있고 room ID가 유효하지 않은 결과를 RefreshOnly로 기대하는 테스트를 먼저 추가해 타입 미정의 컴파일 오류로 RED를 확인했다.
      • 2026-07-15: LiveCreationResult.RefreshOnly를 추가하고 Creator Channel의 단일 handler가 해당 결과에서 homeActionDelegate?.refreshHome()만 실행하도록 구현했다.
      • 2026-07-15: ShowDetail을 성인 접근 확인과 오디오 중단보다 먼저 처리하도록 source test와 구현을 보완했다. source 문자열을 실제 구현 호출 형태로 조정한 뒤 테스트가 통과했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.*"BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:assembleDebugBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: 리뷰에서 상세 조회 전 성인 접근 확인이 실행되는 순서 문제와 callback 없는 ShowDetail fallback이 성인 접근 확인 없이 열리는 문제가 확인되었다. 로그인만 먼저 확인하고, fallback은 roomDetail.isAdult로 성인 접근을 확인하도록 보완한 뒤 Live action 테스트, Creator Channel source test, assembleDebug가 통과했다.
      • 2026-07-15: 보안 재리뷰에서 호출자 힌트가 false여도 상세 응답이 true이면 성인 접근을 요구해야 하는 문제가 확인되었다. 정상 진입 조건을 requiresAdultContentAccess == true || roomDetail.isAdult로 바꾸는 테스트에서 기존 구현의 assertion 2건 실패로 RED를 확인한 뒤 보완했고, Live action 테스트, Creator Channel source test, assembleDebug가 통과했다.
    • 리뷰 발견 및 RED 검증 기록:
      • 2026-07-15: Creator Channel 목록의 isAdult == true와 unavailable 상세 응답의 isAdult == false가 다를 때 fallback이 roomDetail.isAdult만 사용해 호출자 힌트를 잃는 P1 회귀를 코드 경로 대조로 확인했다.
      • 2026-07-15: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks를 실행해 LiveActionCoordinatorSourceTest.kt:79max-line-length 1건과 argument-list-wrapping 2건, 총 3건으로 BUILD FAILED를 재현했다.
      • 2026-07-15: 합성 성인 접근 조건의 단일 계산과 정상 진입·fallback 재사용을 요구하도록 LiveActionCoordinatorSourceTest를 먼저 변경했다. 대상 테스트를 실행해 5개 중 기존 구현과 불일치한 3개가 실패하고 BUILD FAILED가 되는 RED를 확인했다.
    • GREEN 및 회귀 검증 기록:
      • 2026-07-15: requiresAdultContentAccess == true || roomDetail.isAdultrequiresAdultAccess로 한 번 계산하고 정상 진입 guard와 unavailable 상세 fallback에 함께 전달하도록 수정했다.
      • 2026-07-15: source test가 정상 진입과 fallback 함수 구간을 분리해 동일 성인 조건을 검증하고, unavailable 처리 후 return@handleRoomDetail, callback의 return, 성인 guard, 상세 표시 순서를 고정하도록 보강했다. RefreshOnly 분기의 Home refresh와 invalid room ID null, 0L, 음수도 함께 검증했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.*"와 최종 LiveActionCoordinatorSourceTest 단일 실행은 각각 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: 최종 ./gradlew :app:testDebugUnitTest --rerun-tasks는 39개 작업을 모두 실행해 3분 20초에 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: 최종 ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했고 test source 리포트는 0건이다.
      • 2026-07-15: ./gradlew :app:assembleDebugBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:ktlintMainSourceSetCheck --rerun-tasks는 기존 main source 기준선 550건으로 실패했으며, 갱신된 리포트에서 Phase 3 변경 Kotlin 파일의 위반은 0건임을 확인했다.
      • 2026-07-15: ./gradlew tasks --allBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: 별도 docs/20260715_라이브_진입과_생성_결과_회귀_수정/의 두 문서 내용을 본 PRD와 Task 3.6으로 이관한 뒤 중복 문서 파일을 제거했다.
      • 2026-07-15: 독립 코드 재리뷰에서 정상·fallback 성인 guard, 두 unavailable short-circuit, RefreshOnly refresh 계약을 재확인했고 Critical/Important 이슈 없이 Ready 판정을 받았다.
      • 2026-07-15: 최종 git diff --check HEAD는 whitespace 오류 없이 통과했다.
  • Task 3.7: Live coordinator lifecycle·중복 표시·계약 리뷰 반영

    • 수정 대상 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorSourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinatorUiGateTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/LiveEntryPolicyTest.kt
      • docs/agent-guides/build-test-style.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • 비동기 enterRoom 성공 callback에서 LiveRoomActivity를 열기 직전에 lifecycle predicate를 다시 검사한다.
      • 라이브 상세 bottom sheet는 Activity/호출 화면 predicate, FragmentManager.isStateSaved, 안정적인 tag의 기존 fragment 존재 여부를 모두 통과할 때만 표시한다.
      • channelName 없음과 본인 manager 조건이 동시에 있을 때 ShowDetail이 manager 직접 입장보다 우선함을 실행형 정책 테스트로 고정한다.
      • LiveActionCoordinator가 presentation adapter로서 refreshHome callback을 주입받는 예외와 순수 도메인 계약의 callback 금지를 문서에서 명확히 구분한다.
    • 검증 기준:
      • canHandleResult 기본값은 Activity 종료/파괴 상태를 반영하고, Fragment 호출부의 더 엄격한 predicate override는 유지된다.
      • 모든 비동기 UI 후처리 직전에는 canHandleResult가 재검사된다.
      • 상세 sheet 중복 방지는 새 fragment 인스턴스의 isAdded가 아니라 안정적인 tag와 표시 가능 상태를 기준으로 동작한다.
      • 정책 테스트와 coordinator gate/source 테스트, 컴파일, test source ktlint, whitespace 검증이 통과한다.
    • RED 검증 기록:
      • 2026-07-15: lifecycle/detail gate 실행형 테스트와 source test를 먼저 추가한 뒤 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.LiveEntryPolicyTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorUiGateTest" --rerun-tasks를 실행해 runLiveActionUiSideEffect, canShowLiveRoomDetail 미정의 컴파일 오류로 BUILD FAILED를 확인했다.
    • GREEN 및 회귀 검증 기록:
      • 2026-07-15: LiveActionCoordinator의 기본 canHandleResult를 Activity 종료/파괴 상태 기반으로 바꾸고, canHandleUiResult()에서 Activity 상태, FragmentManager.isStateSaved, 호출 화면 predicate를 합성하도록 보완했다.
      • 2026-07-15: 상세 sheet는 LIVE_ROOM_DETAIL_TAGfindFragmentByTag를 먼저 확인하고, 같은 tag로 show하도록 수정했다. 새 LiveRoomDetailFragment 인스턴스의 isAdded와 nullable tag 기반 중복 방지는 제거했다.
      • 2026-07-15: enterRoom 성공 callback과 startLive 성공 callback은 runOnUiThread 내부에서 runLiveActionUiSideEffect(::canHandleUiResult)를 통과한 뒤에만 openLiveRoom(roomId)를 호출하도록 보강했다. 비밀번호/결제/예약/수정/시작/취소 UI side effect도 표시 또는 실행 직전 canHandleUiResult()를 검사한다.
      • 2026-07-15: LiveEntryPolicyTestchannelName == null과 본인 manager 조건이 동시에 있을 때 LiveEntryDecision.ShowDetail을 반환하는 실행형 회귀 테스트를 추가했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.LiveEntryPolicyTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorUiGateTest" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-15: 최종 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.*" --rerun-tasks를 실행해 Live action 테스트 묶음이 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했다. ./gradlew :app:ktlintMainSourceSetCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했으며, ktlint main/test 리포트에서 이번 변경 Kotlin 파일명 검색 결과는 0건이다.
      • 2026-07-15: ./gradlew tasks --allBUILD SUCCESSFUL로 통과했고 git diff --check HEAD는 whitespace 오류 없이 통과했다.
      • 2026-07-15: adb devices는 연결된 기기/에뮬레이터가 없음을 반환해 실제 탭 기반 Dialog/Activity 수동 검증은 수행하지 못했다. 대신 lifecycle/detail gate 실행형 단위 테스트와 source order 테스트로 회귀를 고정했다.
    • 리뷰 게이트 후속 보완 기록:
      • 2026-07-15: 리뷰 게이트에서 DialogFragment.show()가 비동기 commit()을 사용해 첫 transaction 반영 전 연속 호출 시 findFragmentByTag가 중복을 막지 못할 수 있다는 차단 이슈와, unavailable/adult guard/audio 순서가 source test에만 의존한다는 차단 이슈를 확인했다.
      • 2026-07-15: LiveActionCoordinatorSourceTest를 먼저 showNow(fragmentManager, LIVE_ROOM_DETAIL_TAG) 기대값으로 바꾸고, LiveActionCoordinatorUiGateTestrunLiveEntryDecisionFlow 실행형 순서 테스트를 추가한 뒤 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorUiGateTest" --rerun-tasks를 실행해 runLiveEntryDecisionFlow 미정의 컴파일 오류로 BUILD FAILED를 확인했다.
      • 2026-07-15: 상세 sheet 표시를 showNow(fragmentManager, LIVE_ROOM_DETAIL_TAG)로 바꿔 pending transaction 경쟁 구간을 제거하고, 입장 분기 순서를 Android 비의존 runLiveEntryDecisionFlow로 분리해 ShowDetail -> unavailable, Enter -> adult guard -> audio/entry, adult guard 거부 시 audio/entry 미실행을 실행형 테스트로 고정했다.
      • 2026-07-15: 후속 보완 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorUiGateTest" --rerun-tasks를 재실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-15: 후속 보완 최종 확인으로 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.*" --rerun-tasks를 재실행해 39개 작업이 모두 실행되고 BUILD SUCCESSFUL로 종료됨을 확인했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --rerun-tasks 전체 debug 단위 테스트를 재실행해 39개 작업이 모두 실행되고 BUILD SUCCESSFUL로 종료됨을 확인했다.
      • 2026-07-15: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL, ./gradlew tasks --allBUILD SUCCESSFUL, git diff --check HEAD는 무출력으로 통과했다.
      • 2026-07-15: ./gradlew :app:ktlintMainSourceSetCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했으며, 갱신된 ktlint main 리포트에서 이번 변경 파일명 검색 결과는 0건이다.
      • 2026-07-15: adb devices는 연결된 기기/에뮬레이터가 없음을 반환해 실제 탭 기반 수동 QA는 수행하지 못했다. 대신 Live action 대상 실행형 테스트, 전체 debug 단위 테스트, 컴파일, ktlint test source, whitespace 검증으로 대체 확인했다.
    • 보안 리뷰 후속 보완 기록:
      • 2026-07-15: 재리뷰 게이트에서 Creator Channel 일정 경로가 showLiveRoomDetail(roomId)를 직접 호출할 때 공개 상세 진입점 자체에는 로그인·성인 접근 확인이 없어 성인 라이브 상세를 열 수 있다는 보안 차단 이슈를 확인했다.
      • 2026-07-15: LiveActionCoordinatorSourceTest에 공개 showLiveRoomDetail(roomId)AccessRequirement.Login, liveViewModel.getRoomDetail(roomId), AccessRequirement.forAdultContent(roomDetail.isAdult)를 거친 뒤 내부 표시 함수로 이동해야 한다는 source test를 먼저 추가했다. 생산 코드 변경 전 단일 source test 실행에서 해당 테스트가 실패해 RED를 확인했다.
      • 2026-07-15: 공개 showLiveRoomDetail(roomId)는 로그인 확인 후 room detail을 조회하고 roomDetail.isAdult 기준 성인 접근을 통과한 뒤 showLiveRoomDetailContent(roomId)를 호출하도록 분리했다. 이미 성인 가드를 통과한 unavailable fallback은 공개 진입점 재호출 대신 내부 표시 함수만 호출하도록 수정했다.
      • 2026-07-15: 보안 보완 후 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinatorSourceTest" --rerun-tasks./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.live.action.*" --rerun-tasks가 모두 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: 전체 ./gradlew :app:testDebugUnitTest --rerun-tasks는 최초 재실행에서 이번 변경과 직접 관련 없는 CreatorChannelHomeViewModelTest 1건이 실패했으나, 해당 단일 테스트 재실행과 전체 재실행이 모두 BUILD SUCCESSFUL로 통과해 일시적 격리 실패로 기록한다.
      • 2026-07-15: 보안 보완 후 ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL, git diff --check HEAD는 무출력으로 통과했다. ./gradlew :app:ktlintMainSourceSetCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했으며 변경 파일명 검색 결과는 0건이다.
  • Task 3.8: Phase 3 실기기 수동 회귀 테스트

    • 확인 대상 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt
    • 대상 화면:
      • HomeMainFragment 추천 라이브
      • CreatorChannelActivity 현재 라이브, 일정 상세, owner 라이브 생성
      • HomeOnAirLiveActivity 라이브 목록
    • 완료 기준:
      • 아래 필수 시나리오가 모두 기대 결과와 일치하면 각 항목을 [x]로 변경한다.
      • 재현 데이터나 외부 환경이 없어 실행하지 못한 항목은 체크하지 않고 검증 기록에 BLOCKED와 사유를 남긴다.
      • 최종 수동 검증에서 사용자가 테스트 불가능 또는 의미 없음으로 판단한 항목은 미체크 상태를 유지하고 SKIPPED로 기록하며 완료 판정 대상에서 제외한다.
      • 기대 결과와 다른 항목은 체크하지 않고 화면, 계정 조건, room ID, 실제 결과, 로그 또는 캡처 위치를 검증 기록에 남긴다.

    사전 준비

    • M3-P01: 테스트 환경 준비

      • 절차: 최신 debug 빌드를 실기기 또는 에뮬레이터에 설치하고 네트워크 연결, 알림/오디오 재생, 로그인 화면 이동이 가능한지 확인한다.
      • 기대 결과: 앱을 실행해 Home, Creator Channel, On-air 화면에 진입할 수 있고 라이브 상세/입장 API 응답을 받을 수 있다.
    • M3-P02: 접근 상태별 계정 준비

      • 절차: 로그아웃 상태, 국내 미인증 계정, 본인인증 완료·성인 콘텐츠 표시 비활성 계정, 성인 콘텐츠 접근 허용 일반 계정, 라이브 관리자 계정을 준비한다.
      • 기대 결과: 각 계정의 로그인, isAuth, 성인 콘텐츠 표시 설정, 관리자 여부를 테스트 전에 구분할 수 있다.
    • M3-P03: 라이브 분기별 데이터 준비

      • 절차: 아래 조건을 각각 만족하는 현재 라이브 또는 예약 라이브의 room ID와 노출 화면을 기록한다.
      데이터 조건 주요 검증
      R1 Home 추천, Creator Channel, On-air에 노출되는 무료 공개 현재 라이브 세 화면 공통 진입
      R2 무료 비밀번호 현재 라이브 결제액 없는 비밀번호 Dialog
      R3 결제 완료 유료 공개 현재 라이브 결제 없는 직접 입장
      R4 결제 완료 유료 비밀번호 현재 라이브 결제액 없는 비밀번호 Dialog
      R5 미결제 유료 공개 현재 라이브 결제 Dialog
      R6 미결제 유료 비밀번호 현재 라이브 가격이 표시되는 비밀번호 Dialog
      R7 관리자 본인의 유료·비밀번호 현재 라이브 관리자 직접 입장 우선순위
      R8 각 대상 화면에 노출 가능한 channelName null 또는 blank 라이브 화면별 unavailable 처리
      R9 성인 현재 라이브 본인인증/표시 설정 가드
      R10 일반 사용자가 예약 가능한 무료·유료·비밀번호 예약 라이브 예약 분기
      • 기대 결과: 각 데이터의 가격, 결제 여부, 비밀번호 여부, 성인 여부, 관리자 ID, channelName 조건을 QA 환경에서 확인할 수 있다.

    화면 결선과 접근 가드

    • M3-01: 세 화면의 무료 공개 라이브 진입
      • 절차: 접근 허용 일반 계정으로 R1을 Home 추천, Creator Channel 현재 라이브, On-air 목록에서 각각 한 번 선택한다.
      • 기대 결과: 세 경로 모두 기존 로딩 표시 후 같은 room ID의 LiveRoomActivity로 이동하며 중복 상세 조회 화면이나 추가 Dialog가 나타나지 않는다.
    • M3-02: 로그아웃 상태의 라이브 선택
      • 절차: 로그아웃 상태에서 Home 추천 라이브를 선택하고, Home의 On-air 더보기 진입도 시도한다.
      • 기대 결과: 기존 LoginActivity가 열리고 라이브 상세 조회, 오디오 중단, LiveRoomActivity 이동은 실행되지 않는다.
    • M3-03: 국내 미인증 계정의 성인 라이브 선택
      • 절차: 국내 미인증 계정으로 R9를 Home 추천 또는 Creator Channel 현재 라이브에서 선택한다.
      • 기대 결과: 로그인 확인과 상세 조회 뒤 기존 V2 본인인증 안내가 표시되고 오디오 중단, 비밀번호/결제 Dialog, 라이브 입장은 실행되지 않는다.
    • M3-04: 성인 콘텐츠 표시가 비활성인 계정의 성인 라이브 선택
      • 절차: 본인인증 완료·성인 콘텐츠 표시 비활성 계정으로 R9를 선택한다.
      • 기대 결과: ContentSettingsActivity가 성인 콘텐츠 안내 상태로 열리고 라이브 입장과 오디오 중단은 실행되지 않는다.
    • M3-05: 성인 콘텐츠 접근 허용 계정의 성인 라이브 선택
      • 절차: 성인 콘텐츠 접근 허용 계정으로 R9를 선택한다.
      • 기대 결과: 추가 접근 안내 없이 R9의 입장 정책에 맞는 직접 입장, 비밀번호 Dialog 또는 결제 Dialog가 실행된다.
    • M3-06: 호출자 성인 힌트와 상세 응답 불일치
      • 절차: QA fixture에서 Creator Channel 목록 isAdult=true·상세 isAdult=false인 방과 목록 isAdult=false·상세 isAdult=true인 방을 각각 선택한다.
      • 기대 결과: 두 경우 모두 성인 콘텐츠 접근 조건을 요구하며, 접근이 거부되면 상세 표시와 라이브 입장이 실행되지 않는다.

    Live 입장 분기

    • 공통 절차: 각 항목 실행 전에 오디오 콘텐츠를 재생하고, 접근 허용 일반 계정 또는 명시된 관리자 계정으로 대상 라이브를 선택한다.
    완료 시나리오 기대 결과
    [x] M3-07: 관리자 본인의 유료·비밀번호 현재 라이브 R7 선택 시 비밀번호/결제 Dialog 없이 오디오가 중단되고 LiveRoomActivity로 이동한다.
    [x] M3-08: 무료 공개 현재 라이브 R1 선택 시 오디오가 중단되고 LiveRoomActivity로 이동한다.
    [x] M3-09: 무료 또는 결제 완료 비밀번호 현재 라이브 R2R4 선택 시 결제액 없는 비밀번호 Dialog가 표시되고 올바른 비밀번호 입력 성공 후 입장한다.
    [x] M3-10: 결제 완료 유료 공개 현재 라이브 R3 선택 시 결제 Dialog 없이 오디오가 중단되고 바로 입장한다.
    [x] M3-11: 미결제 유료 공개 현재 라이브 R5 선택 시 오디오가 중단되고 기존 금액·시간 문구의 결제 Dialog가 표시되며, 취소 시 현재 화면을 유지하고 확인 성공 시 입장한다.
    [x] M3-12: 미결제 유료 비밀번호 현재 라이브 R6 선택 시 결제 Dialog 대신 가격이 표시되는 비밀번호 Dialog가 나타나고 올바른 비밀번호 입력 성공 후 입장한다.
    [x] M3-13: 비밀번호 또는 입장 API 실패 잘못된 비밀번호 또는 QA 실패 응답에서 기존 오류 안내가 표시되고 LiveRoomActivity는 열리지 않으며 로딩 표시가 종료된다.

    unavailable과 예약 라이브 상세

    • M3-14: Home과 Creator Channel의 unavailable 상세 fallback
      • 절차: 오디오를 재생한 상태에서 R8을 Home 추천과 Creator Channel 현재 라이브에서 각각 선택한다.
      • 기대 결과: 성인 접근 조건을 통과한 뒤 라이브 상세 bottom sheet가 표시되고 오디오는 계속 재생된다.
    • M3-15: On-air의 unavailable 처리
      • 절차: 오디오를 재생한 상태에서 On-air 목록에 노출된 R8을 선택한다.
      • 기대 결과: 기존 공통 오류 Toast만 표시되고 성인 안내, 라이브 상세, 비밀번호/결제 Dialog, 오디오 중단, 라이브 입장은 실행되지 않는다.
    • M3-16: 예약 라이브 상세의 로그인·성인 접근 확인
      • 절차: Creator Channel 일정에서 일반 예약 라이브와 성인 예약 라이브를 각각 선택하고, 성인 예약 라이브는 접근 거부 계정과 허용 계정으로 나누어 확인한다.
      • 기대 결과: 일반 예약 라이브는 로그인 후 상세가 표시되고, 성인 예약 라이브는 성인 접근 허용 시에만 상세가 표시된다.
    • M3-17: 라이브 상세 중복 표시 방지
      • 절차: Creator Channel 일정의 같은 예약 라이브를 빠르게 연속 선택한다.
      • 기대 결과: 같은 LiveRoomDetailFragment가 한 번만 표시되며 중복 bottom sheet나 FragmentManager 예외가 발생하지 않는다.
    • M3-18: 일반 사용자의 무료·유료·비밀번호 예약
      • 절차: 일반 계정으로 R10의 무료 공개, 미결제 유료 공개, 비밀번호 예약 라이브를 각각 열어 예약을 진행한다.
      • 기대 결과: 무료 공개는 바로 예약되고, 유료 공개는 기존 결제 Dialog를 거치며, 비밀번호 방은 기존 비밀번호 Dialog를 거친다. 성공 후 LiveReservationCompleteActivity가 열리고 Creator Channel Home이 새로고침된다.
    • M3-19: 관리자 예약 라이브 수정
      • 절차: 관리자 계정으로 본인 예약 라이브 상세에서 수정을 선택한다.
      • 기대 결과: 기존 room detail extra를 가진 LiveRoomEditActivity가 열린다.
    • M3-20: 관리자 예약 라이브 시작
      • 절차: 관리자 계정으로 본인 예약 라이브 상세에서 시작을 선택하고 API 성공까지 기다린다.
      • 기대 결과: Creator Channel Home이 새로고침되고 해당 room ID의 LiveRoomActivity로 이동한다.
    • M3-21: 관리자 예약 라이브 취소
      • 절차: 관리자 계정으로 본인 예약 라이브 상세에서 취소를 선택해 사유를 입력하고 완료한다.
      • 기대 결과: 기존 취소 성공 Toast가 표시되고 Creator Channel Home이 새로고침되며 취소된 일정 상태가 반영된다.

    라이브 생성 결과

    • M3-22: 예약 라이브 생성 완료
      • 절차: Creator Channel owner FAB에서 즉시 시작되지 않는 예약 라이브를 생성한다.
      • 기대 결과: Creator Channel Home이 새로고침되고 기존 생성 완료 Toast가 표시되며 LiveRoomActivity는 열리지 않는다.
    • M3-23: 즉시 시작 라이브 생성 완료
      • 절차: Creator Channel owner FAB에서 channel과 유효한 room ID가 반환되는 즉시 시작 라이브를 생성한다.
      • 기대 결과: Creator Channel Home이 새로고침되고 생성된 room ID의 입장 흐름을 거쳐 LiveRoomActivity로 이동한다.
    • M3-24: 유효한 room ID가 없는 생성 성공 결과
      • 절차: QA backend에서 channel은 반환하지만 room ID가 null, 0L 또는 음수인 성공 응답으로 라이브 생성을 완료한다.
      • 기대 결과: Creator Channel Home만 새로고침되고 생성 Toast, 비밀번호/결제 Dialog, LiveRoomActivity 이동은 실행되지 않는다.
    • M3-25: 라이브 생성 취소
      • 절차: 라이브 생성 화면을 결과 없이 닫는다.
      • 기대 결과: Creator Channel Home 새로고침, 생성 Toast, 라이브 입장이 실행되지 않는다.
    • M3-26: 라이브 생성 실패 응답
      • 절차: QA backend에서 라이브 생성 실패 응답을 반환하도록 설정하고 생성 요청을 완료한다.
      • 기대 결과: 기존 생성 실패 안내만 표시되고 Creator Channel Home 새로고침, 생성 완료 Toast, 라이브 입장은 실행되지 않는다.

    lifecycle과 오류 회귀

    • M3-27: 상세 조회 중 호출 화면 이탈
      • 절차: 느린 네트워크에서 라이브를 선택한 직후 Home Fragment를 벗어나는 경우와 Activity를 종료하는 경우를 각각 실행하고 상세 응답을 기다린다.
      • 기대 결과: 이탈한 화면 위에 Toast, Dialog, bottom sheet, LiveRoomActivity가 늦게 표시되지 않고 앱이 종료되지 않는다.
    • M3-28: 입장 또는 라이브 시작 요청 중 화면 이탈
      • 절차: 비밀번호/결제 확인 또는 관리자 라이브 시작 직후 요청이 완료되기 전에 Activity 종료와 앱 백그라운드 전환을 각각 실행한다.
      • 기대 결과: lifecycle이 유효하지 않은 동안 LiveRoomActivity가 늦게 열리지 않고 FragmentManager/Activity state 예외가 발생하지 않는다.
    • M3-29: 상세 조회 API 실패
      • 절차: QA 실패 응답 또는 네트워크 차단 상태에서 Home, Creator Channel, On-air 중 한 경로의 라이브를 선택한다.
      • 기대 결과: 기존 라이브 상세 조회 오류 안내가 표시되고 오디오 중단, Dialog, 상세 bottom sheet, 라이브 입장은 실행되지 않으며 로딩 표시가 종료된다.
    • M3-30: 수동 테스트 종료 후 화면 상태 확인
      • 절차: 네트워크를 복구하고 Home, Creator Channel, On-air를 다시 열어 목록 로딩과 라이브 선택을 한 번씩 수행한다.
      • 기대 결과: 이전 실패/취소/화면 이탈로 인한 고정 loading, 중복 Dialog, 중복 상세, 잘못된 Home 상태가 남지 않고 정상 동작한다.

    수동 검증 기록 형식

    • 각 실행 결과는 다음 형식으로 본 Task 아래에 누적한다.
      • YYYY-MM-DD / M3-번호 / 기기·OS / 계정 조건 / room ID / PASS·FAILED·BLOCKED·SKIPPED / 실제 결과와 증빙 위치
    • 전체 항목 완료 후 여러 화면에 걸친 최종 결과는 문서 최하단 Verification Log에도 요약한다.
    • 체크리스트 작성 검증 기록:
      • 2026-07-15: Phase 3 요구사항과 구현 분기를 대조해 사전 준비 3개와 수동 시나리오 M3-01~M3-30을 작성했다. 시나리오 번호 중복과 placeholder가 없음을 확인했고, ./gradlew tasks --allBUILD SUCCESSFUL, git diff --check HEAD는 무출력으로 통과했다.
    • 사용자 수동 검증 기록:
      • 2026-07-15 / M3-P01~M3-P03 / 기기·OS·계정·room ID 상세 미제공 / PASS / 사용자가 사전 준비 3개 항목을 모두 완료로 확인했다.
      • 2026-07-15 / M3-01, M3-02, M3-05, M3-07M3-14, M3-16M3-23, M3-25, M3-27, M3-28 / 기기·OS·계정·room ID 상세 미제공 / PASS / 사용자가 체크한 22개 시나리오를 기대 결과와 일치한 것으로 확인했다.
      • 2026-07-15 / M3-03, M3-04, M3-06, M3-15, M3-24, M3-26, M3-29, M3-30 / 기기·OS·계정·room ID 상세 미제공 / SKIPPED / 사용자가 테스트 불가능 또는 의미 없음으로 판단했으며, 항목은 미체크 상태로 유지했다.
      • 2026-07-15: 사전 준비 3개와 시나리오 22개는 PASS, 시나리오 8개는 SKIPPED, FAILED는 0개로 판정되어 Task 3.8을 완료 처리했다.

Phase 4: Creator와 Community 공통 Action

  • Task 4.1: Creator Channel 진입 호출부와 계약 확정

    • 확인 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/
    • 검증 기준: creatorId, 로그인 요구 여부, route source, 기존 extra 차이가 정리된다.
    • 확인 결과:
      • Home 추천/랭킹/AI 캐릭터와 Content banner의 Creator Channel 이동은 creatorId만 있으면 충분하며 기존 CreatorChannelActivity.EXTRA_CREATOR_ID extra는 action handler가 유지한다.
      • MainV2Activity의 deeplink/system route는 Phase 1에서 별도 순서와 반환 계약을 고정한 경로이므로 Phase 4 사용자 액션 전환 범위에서 제외한다.
      • 모델 helper는 feature UI model에서 route ID만 반환하고 Creator Channel Activity Intent 생성은 Creator Action handler로 이동한다.
    • 검증 기록:
      • 2026-07-15: HomeMainFragment, HomeRecommendationUiModels, ContentMainFragment, AudioRecommendationsUiModels, MainV2Activity 직접 확인과 rg 검색으로 Creator Channel 진입 호출부를 확정했다.
  • Task 4.2: Creator Action 구현과 호출부 전환

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/action/CreatorAction.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/action/CreatorActionTest.kt
    • 검증 기준: 유효 ID와 AccessRequirement.Login을 Action에서 처리하고 feature UI model을 입력받지 않으며, Handler가 Access 실행기와 Creator Channel navigation을 조합한다.
    • RED 검증 기록:
      • 2026-07-15: CreatorActionTest를 먼저 추가하고 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*" --tests "kr.co.vividnext.sodalive.v2.community.action.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*"를 실행해 CreatorAction, CreatorActionCommand, CreatorActionResult, CreatorActionHandler 미정의 컴파일 오류로 RED를 확인했다.
      • 2026-07-15: source 전환 assertion을 추가한 뒤 같은 명령에서 CreatorActionTest > 대상 화면은 Creator Channel Activity 대신 Creator Action을 사용한다 실패로 기존 직접 Intent 잔존 RED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-15: CreatorActionCommand.Profile, CreatorAction, CreatorActionHandler, Activity/Fragment handleCreatorAction을 추가하고 Home/Content 호출부를 전환했다. Handler만 기존 CreatorChannelActivity.newIntentEXTRA_CREATOR_ID extra를 소유한다.
      • 2026-07-15: Creator/Community/Chat action 테스트 묶음과 Home/Content/Creator source 관련 테스트 묶음이 각각 BUILD SUCCESSFUL로 통과했다.
  • Task 4.3: Community 게시글 진입 계약 구현과 호출부 전환

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityAction.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActionTest.kt
    • 수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 검증 기준: 로그인, ID 검사, Activity result 차이를 보존하고 공통 게시글 진입점을 사용한다.
    • RED 검증 기록:
      • 2026-07-15: CommunityActionTest를 먼저 추가하고 action 테스트 묶음을 실행해 CommunityAction, CommunityActionCommand, CommunityActionResult, CommunityActionHandler 미정의 컴파일 오류로 RED를 확인했다.
      • 2026-07-15: source 전환 assertion 추가 후 CommunityActionTest > 대상 화면은 Community Detail Activity 대신 Community Action을 사용한다 실패로 Home/Creator 직접 상세 Intent 잔존 RED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-15: CommunityActionCommand.PostDetail, CommunityAction, CommunityActionHandler를 추가하고 Home의 following news/recent activity/popular community 및 Creator Channel community post click을 handleCommunityAction으로 전환했다.
      • 2026-07-15: Creator Channel의 ActivityResultLauncher 계약 보존을 위해 launchIntent 주입 경계를 두었고, action/result에는 Fragment refresh callback을 넣지 않았다.
  • Task 4.4: Community 변경 결과와 레거시 ActivityResult adapter 구현

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChange.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityActivityResultMapper.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt
    • 작업:
      • 작성, 수정, 삭제, 고정 변경을 Created, Updated, Deleted, PinChanged로 표현한다.
      • 레거시 작성/수정 Activity의 RESULT_OK를 명시적인 Community 변경 결과로 변환한다.
      • 결과 계약에는 Home/Community 탭 refresh callback 또는 Fragment 참조를 포함하지 않는다.
    • 검증 기준: legacy Activity result는 result code와 source로 변경 종류를 판정하고 CommunityChange의 post ID payload를 단위 테스트한다. delete/pin mutation 성공 경로의 실제 post ID wiring은 후속 Task 6.4에서 별도 회귀 검증한다.
    • RED 검증 기록:
      • 2026-07-15: CommunityChangeTest를 먼저 추가하고 action 테스트 묶음을 실행해 CommunityChange, CommunityActivityResultSource, resolveCommunityActivityResult 미정의 컴파일 오류로 RED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-15: CommunityChange.Ignored/Created/Updated/Deleted/PinChangedresolveCommunityActivityResult를 추가했다. Write RESULT_OKCreated, Modify/Detail RESULT_OKUpdated, 실패/취소는 Ignored로 변환한다.
  • Task 4.5: Creator Channel Community 변경 composition handler 통합

    • 수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업:
      • handleCommunityChange(change) 단일 함수에서 Home/Community projection 갱신을 조합한다.
      • Community 작성, 수정, 삭제, 고정 변경 성공 경로가 위 함수를 사용하도록 전환한다.
      • 기존 ActivityResultLauncher, Activity result 수신 시점, Fragment initial load 동작은 유지한다.
    • 검증 기준:
      • Community Action/결과가 Creator Channel Home 또는 Community Fragment를 알지 않는다.
      • Activity 내부의 개별 성공 경로에 refreshHome()refreshCreatorChannelCommunity() 조합이 반복되지 않는다.
      • 현재 Activity 내부 전파를 위해 전역 EventBus/observer singleton을 추가하지 않는다.
    • 검증 기록:
      • 2026-07-15: CreatorChannelActivity의 community write/modify/detail launcher가 raw result를 CommunityChange로 변환한 뒤 handleCommunityChange로 전달하도록 통합했다.
      • 2026-07-15: pin/delete 성공 경로도 각각 CommunityChange.PinChanged, CommunityChange.Deleted를 거쳐 단일 handler에서 Community projection refresh를 결정하게 했다. Created만 Home과 Community를 함께 갱신하고 Updated/Deleted/PinChanged는 Community만 갱신한다.
      • 2026-07-15: Activity 밖 소비자가 없고 Creator Channel Activity 내부 projection 조합으로 충분해 SharedFlow, EventBus, singleton observer는 추가하지 않았다.
  • Task 4.6: Creator/Community Phase 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.community.action.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck
      git diff --check
      
    • 검증 기록:
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*" --tests "kr.co.vividnext.sodalive.v2.community.action.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*"BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: Home/Content/Chat/Creator source 회귀 묶음 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.content.ContentMainFragmentSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: rg로 대상 호출부를 검색해 CreatorChannelActivity.newIntent, CreatorChannelCommunityDetailActivity.newIntent는 action handler 및 Phase 1에서 제외한 MainV2Activity system/deeplink route에만 남음을 확인했다.
      • 2026-07-15: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했고, ./gradlew tasks --allBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했으며 test source 보고서는 0건이다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만 갱신된 main/test ktlint 리포트에서 Phase 4 변경 파일명 검색 결과는 0건이다.

Phase 5: Chat 공통 Action

  • Task 5.1: Chat/DM 진입 유형과 결과 계약 확정

    • 확인 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 작업: 기존 room 진입, creator 기반 DM 생성, AI/DM 타입, owner/non-owner, Activity result 차이를 구분한다.
    • 검증 기준:
      • 서로 다른 동작을 하나의 nullable/boolean 다목적 함수로 합치지 않는다.
      • 호출 화면 후처리가 필요한 Activity result는 명시적 Chat 결과와 feature 단일 handler로 연결한다.
    • 확인 결과:
      • Chat list와 Home following chat은 기존 room ID로 AI legacy ChatRoomActivity 또는 V2 DmChatRoomActivity에 진입한다.
      • Creator Channel AI chat은 AccessRequirement.AdultContent 통과 후 기존 CreatorChannelHomeViewModel.createChatRoom(characterId) API 소유를 유지하고, 성공 event의 room ID만 Chat Action으로 전달한다.
      • Creator Channel DM은 owner의 MainV2Activity.newChatDmIntent와 non-owner의 creator ID 기반 DmChatRoomActivity 진입을 분리한다.
      • 현재 Chat/DM 하위 Activity는 result를 반환하지 않으므로 ActivityResult adapter는 추가하지 않는다.
    • 검증 기록:
      • 2026-07-15: ChatMain/Home/Creator/MainV2/DM Activity와 관련 tests를 확인해 AiRoom, DmRoom, DmCreator, OwnerDmList command 계약을 확정했다. MainV2Activity deeplink/system route는 기존 계약대로 제외했다.
  • Task 5.2: Chat Action 계약과 단위 테스트 작성

    • 생성 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatAction.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionCommand.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionTest.kt
    • 검증 기준: room/creator 식별자와 진입 유형이 명시적이고 Home/Creator UI model에 의존하지 않으며, 유효 ID 확인 뒤 AccessRequirement.Login을 Action이 소유한다.
    • RED 검증 기록:
      • 2026-07-15: ChatActionTest를 먼저 추가하고 action 테스트 묶음을 실행해 ChatAction, ChatActionCommand, ChatActionResult, ChatActionHandler 미정의 컴파일 오류로 RED를 확인했다.
      • 2026-07-15: source 전환 assertion 추가 후 ChatActionTest > 대상 화면은 채팅방 Activity 대신 Chat Action을 사용한다 실패로 직접 chat Activity helper 잔존 RED를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-15: ChatActionCommand.AiRoom/DmRoom/DmCreator/OwnerDmList, ChatAction, ChatActionHandler, Activity/Fragment handleChatAction을 추가했다. Handler만 기존 ChatRoomActivity, DmChatRoomActivity, MainV2Activity.newChatDmIntent를 소유한다.
  • Task 5.3: Home/Chat/Creator Channel 호출부 전환

    • 수정 예정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt
    • 검증 기준: Chat Action이 로그인 정책을 소유해 호출 화면의 중복 login guard는 제거하고, Creator Channel의 AdultContent 사전 조건과 room 생성 API, 생성 성공 후 이동, owner/non-owner 분기는 유지한다.
    • 검증 기록:
      • 2026-07-15: HomeMainFragment.openFollowingChat은 기존 로그인 guard를 유지한 채 ChatActionCommand.AiRoom/DmRoom으로 전환했다.
      • 2026-07-15: ChatMainFragment.onChatRoomClick은 기존 AI/DM 분기를 유지하되 navigation을 handleChatAction으로 위임했다.
      • 2026-07-15: CreatorChannelActivity의 AI chat 생성 API는 homeActionDelegate?.createChatRoom(characterId)에 그대로 남기고, 생성 완료 room ID와 owner/non-owner DM navigation만 ChatAction으로 전환했다.
      • 2026-07-16: 첫 번째 기록의 화면-local login guard 유지는 후속 P1에서 대체되었다. 최종 계약은 ChatActionAccessRequirement.Login을 소유하고 HomeMainFragment.openFollowingChat은 guard를 중복 실행하지 않는 것이다.
  • Task 5.4: Chat Phase 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.chat.action.*"
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck
      git diff --check
      
    • 검증 기록:
      • 2026-07-15: Chat Action 테스트와 Home/Chat/Creator source 회귀 묶음이 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: rg 검색 결과 대상 화면의 직접 ChatRoomActivity.newIntent, DmChatRoomActivity.newIntentByRoomId, DmChatRoomActivity.newIntentByCreatorId 호출은 제거됐고, handler 및 Phase 1에서 제외한 MainV2Activity deeplink route만 남았다.
      • 2026-07-15: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했고, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만 갱신된 ktlint 리포트에서 Phase 5 변경 파일명 검색 결과는 0건이다.
      • 2026-07-15: git diff --check HEAD는 whitespace 오류 없이 통과했다.

Phase 6: 최종 소유 경계와 문서·코드 동기화

  • Task 6.1: 최종 소유 경계를 PRD와 계획 문서에 고정

    • 수정 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • Access는 접근 판단과 로그인/본인인증/성인 설정 UX 실행을 소유한다.
      • 도메인 Action은 안정적인 입력 검사, 필요한 AccessRequirement 선택, Access 실행 요청과 명시적 결과를 소유한다.
      • Action Handler는 Access 실행기 주입과 레거시 navigation adapter를 소유한다.
      • feature는 UI model -> command 변환, 화면별 query/API와 생성·mutation API, ActivityResult 및 refresh/callback/projection 조합을 소유한다.
      • 도메인 관련 코드 전체가 아니라 여러 호출부에서 같아야 하는 유효성·접근 정책·결과 계약만 Action으로 이관하고, 실제 Android 이동은 Handler에 둔다.
      • 보호된 도메인 Action 호출부의 중복 Access guard 제거 원칙과 직접 Access 사용 예외를 명시한다.
      • AppDI.kt는 실제 외부 의존성, 공유 생명주기 또는 구현 교체가 필요한 경우에만 조립 경계로 사용하고 상태 없는 Action/Handler의 DI 등록을 강제하지 않는다.
    • 검증 기준: PRD와 계획 문서가 같은 네 계층 소유 경계와 이관/예외 기준을 사용한다.
    • 검증 기록:
      • 2026-07-16: 합의한 최종 소유 경계를 두 문서에 명시했고, 도메인 전체 이관과 navigation-only 이관을 모두 배제했다. 이번 Task에서는 문서만 수정하고 운영/테스트 코드는 변경하지 않았다.
  • Task 6.2: Phase 1~5의 규범 계약과 실제 파일명 동기화

    • 수정 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 작업:
      • Phase 1 Access와 Phase 2/3 Content·Live 소유 경계는 유지한다.
      • Phase 4 Creator Action에 Login/Blocked 계약을 명시하고 Creator/Community Action 파일명을 실제 singular 이름으로 정정한다.
      • Phase 5 Chat Action에 Login 소유를 명시하고 화면-local 중복 guard 제거를 규범 계약으로 확정한다.
      • Creator Channel AI Chat의 AdultContent 사전 조건과 createChatRoom(characterId) API는 feature에, 생성 성공 room navigation은 Chat Action에 유지한다.
      • 기존 2026-07-15 검증 기록은 삭제하지 않고, 후속 P1에서 대체된 login guard 기록에 최종 계약을 덧붙인다.
      • 합의된 반복 정책 대상과 parent-child/same-feature/system/deeplink/notification/목록/단일 owner 예외를 구분한다.
    • 검증 기준: Phase 1~5 본문, Review Follow-up, PRD 카탈로그가 서로 모순되지 않고 실제 파일명을 가리킨다.
    • 검증 기록:
      • 2026-07-16: Creator/Community/Chat Action 파일명, Creator/Chat Login 계약, Creator AI Chat 생성 소유권, 직접 의존 예외를 문서에 동기화했다. 과거 검증 기록은 이력으로 보존했다.
  • Task 6.3: 최종 소유 경계 기준 코드 재점검과 최소 보완

    • 확인 범위:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/access/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/content/action/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/creator/action/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/
      • app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/
      • 해당 Action 호출 Activity/Fragment와 app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
    • 작업:
      • Action이 안정적인 입력 검사와 접근 요구를 소유하고 Handler가 Access/navigation을 조합하는지 확인한다.
      • 보호된 Action 호출부에 같은 Access guard가 중복되거나, Action/Handler에 화면별 query/API/refresh callback이 유입된 곳이 있는지 확인한다.
      • AppDI.kt 등록은 실제 조립 필요성이 확인된 경우에만 추가하고, 현재 구조가 최종 소유 경계와 일치하면 변경하지 않는다.
    • 검증 기준: 발견된 불일치만 최소 수정하고, 합의된 예외나 단일 owner 기능을 형식적인 Action/DI로 이관하지 않는다.
    • 검증 기록:
      • 2026-07-16: Access, Content, Live, Creator, Community, Chat Action/Handler와 호출 Activity/Fragment, AppDI.kt를 재점검했다. Action은 ID/command 검사와 접근 요구 선택을 소유하고 Handler는 Access 실행기와 navigation adapter를 조합하는 구조를 유지한다.
      • 2026-07-16: 보호된 Creator/Community/Chat 호출부의 중복 guard와 Action/Handler에 feature query/API/refresh callback이 유입된 위치를 검색했다. LiveActionCoordinator는 문서화된 presentation adapter 예외로 refreshHome callback을 주입받고, Community/Chat/Creator/Content Action 결과 타입에는 화면별 callback이 없다.
      • 2026-07-16: AppDI.kt에 상태 없는 Action/Handler 등록을 추가할 필요가 없음을 확인했다. 실제 불일치 보완은 Task 6.4의 Community mutation wiring 회귀 테스트 추가로 제한했다.
  • Task 6.4: Community delete/pin mutation 성공 경로 wiring 회귀 테스트 보강

    • 수정 예정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt
      • 필요 시 app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt
    • 작업:
      • pin 성공 경로가 실제 item.postIdhandleCommunityChange(CommunityChange.PinChanged(...))를 호출하는지 검증한다.
      • delete 성공 경로가 실제 item.postIdhandleCommunityChange(CommunityChange.Deleted(...))를 호출하는지 검증한다.
      • Created는 Home/Community, Updated/Deleted/PinChanged는 Community projection으로 연결되는 composition 계약을 함께 고정한다.
    • 검증 기준: CommunityChange 객체의 payload 보유만이 아니라 mutation 성공 경로에서 실제 post ID 전달과 단일 handler 연결이 회귀 테스트로 보호된다.
    • RED/GREEN 검증 기록:
      • 2026-07-16: CreatorChannelActivitySourceTest에 pin/delete 성공 경로가 실제 item.postIdCommunityChange.PinChanged/Deleted를 생성하고 handleCommunityChange로만 전달하는지 검증하는 source test 2건과, handleCommunityChange의 projection 갱신 소유권 테스트 1건을 추가했다.
      • 2026-07-16: 새 테스트는 현재 구현을 회귀 고정하는 characterization 성격이므로 별도 production 변경 없이 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: Kotlin LSP는 환경에 kotlin-ls가 설치되어 있지 않아 실행할 수 없었고, Gradle test/compile로 대체 검증했다.
  • Task 6.5: 소유 경계 동기화 집중 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*" --tests "kr.co.vividnext.sodalive.v2.community.action.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" --rerun-tasks
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      ./gradlew :app:ktlintCheck --rerun-tasks
      git diff --check HEAD
      
    • 검증 기준: focused 테스트와 컴파일, test source ktlint, whitespace 검증이 통과한다. 전체 main ktlint가 기존 기준선으로 실패하면 Phase 6 변경 파일의 신규 위반이 없음을 리포트에서 확인하고 실패 위치를 기록한다.
    • 검증 기록:
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.action.*" --tests "kr.co.vividnext.sodalive.v2.community.action.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD는 통과했다.
      • 2026-07-16: ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했다. app/build/reports/ktlint/ktlintMainSourceSetCheck, ktlintTestSourceSetCheck 리포트에서 Phase 6/7 변경 파일명 검색 결과는 0건이다.

Phase 7: 최종 의존 방향 및 통합 검증

  • 상태: Phase 6의 코드 점검과 집중 검증 완료 후 최종 기준으로 다시 실행한다. 아래 2026-07-15 검증 기록은 번호 변경 전 Phase 6의 기준선 이력으로 보존하며, Task 7.1~7.5는 모두 재검증 대상으로 재개방한다.

  • Task 7.1: feature 간 직접 navigation/Coordinator 의존 재검사

    • 확인 범위: app/src/main/java/kr/co/vividnext/sodalive/v2/**/*.kt
    • 실행 명령:
      rg -n '^import kr\.co\.vividnext\.sodalive\.v2\.(main|creator|live)\..*(Activity|Fragment|Coordinator)' app/src/main/java/kr/co/vividnext/sodalive/v2 --glob '*.kt'
      
    • 검증 기준: 합의된 반복 navigation/정책의 직접 의존은 Action/Handler로 대체된다. parent-child Fragment composition, same-feature child flow, MainV2Activity system/deeplink/audio notification route, Home on-air 목록 화면 진입과 단일 owner 기능은 예외 근거가 기록되어 있으면 유지한다.
    • 검증 기록:
      • 2026-07-15: 직접 feature Activity/Fragment/Coordinator import와 destination Activity newIntent/Intent(...) 호출을 검색했다. 남은 직접 destination 생성은 ContentActionHandler, CreatorActionHandler, CommunityActionHandler, ChatActionHandler, LiveActionCoordinator, parent-child Fragment composition, same-feature child flow, Phase 1에서 제외한 MainV2Activity system/deeplink/audio notification route로 분류했다.
      • 2026-07-15: HomeMainFragmentHomeOnAirLiveActivity.newIntent는 on-air 목록 화면 자체 진입이고, MainV2Activity 직접 route는 기존 deeplink/system 계약 보존 대상이므로 Phase 6에서 Action으로 옮기지 않는다.
      • 2026-07-16: 동일 import 검색을 재실행했다. 남은 항목은 Action/Handler, MainV2Activity system/deeplink/audio notification route, Home on-air 목록 화면 진입, parent-child Fragment composition, same-feature child flow로 분류되어 추가 이관 대상은 없었다.
  • Task 7.2: 이전 helper와 중복 함수 제거

    • 삭제/수정 대상: 각 Phase에서 호환을 위해 남긴 adapter, 이전 ensure..., 중복 open..., 직접 route helper.
    • 검증 기준: 모든 호출부가 새 공개 진입점을 사용하는 것이 확인된 구현만 제거한다.
    • 검증 기록:
      • 2026-07-15: ensureMainV2NavigationAllowed, ensureLoginAndAdultAuth, MainV2LoginGuard, isLoggedIn(, showLoginActivity( 검색 결과 운영 코드에는 레거시 MyPageFragment 호환을 위한 MainV2Activity.showLoginActivity()만 남았다. 해당 메서드는 Phase 1에서 의도적으로 유지한 호환 진입점이므로 삭제하지 않는다.
      • 2026-07-15: 남은 openAudioContentDetail, openSeriesDetail, openCreatorProfile, openFollowingChat, enterLiveRoom은 UI model을 explicit command 또는 coordinator 호출로 변환하는 로컬 adapter이거나 기존 목록 화면 진입 wrapper라 동작 보존을 위해 유지한다.
      • 2026-07-16: 이전 helper와 중복 함수 검색을 재실행했다. MainV2Activity.showLoginActivity()는 레거시 MyPage 호환 진입점으로 유지하고, 나머지 open.../enter... 함수는 UI model을 command로 바꾸거나 기존 목록 화면을 여는 로컬 adapter라 삭제하지 않았다.
  • Task 7.3: 미추출 도메인 재평가

    • 확인 대상: FanTalk, Donation, Schedule, Ranking, Discovery.
    • 작업: 실제 반복 호출과 동일 정책이 생겼는지 확인하고 추출 또는 현행 유지 근거를 기록한다.
    • 검증 기준: 형식적 Action 추가 없이 단일 사용 기능은 기존 feature에 유지한다.
    • 검증 기록:
      • 2026-07-15: FanTalk는 Creator Channel 단일 owner에서 write/detail result 후 FanTalk와 Home projection을 함께 갱신하는 흐름이며, 별도 독립 호출부가 없어 Action 추출 대상에서 제외했다.
      • 2026-07-15: Donation, Schedule은 Creator Channel 내부 tab/host composition과 UI projection 성격이 강하고 동일 navigation/action 정책을 공유하는 독립 호출부가 없어 현행 feature 소유로 유지한다.
      • 2026-07-15: Ranking/Discovery는 현재 Content/Home의 집계·표시 흐름 또는 widget 성격으로 남아 있고, 이미 Content Action이 상세 진입만 소유하므로 추가 도메인 Action을 만들지 않는다.
      • 2026-07-16: FanTalk, Donation, Schedule, Ranking, Discovery 관련 호출부를 다시 검색했다. 새 반복 navigation/접근 정책은 확인되지 않았고, 단일 owner 또는 화면별 집계/표시 흐름으로 남기는 기존 판단을 유지한다.
  • Task 7.4: 결과 전달 방식과 observer 필요성 재검사

    • 확인 대상: Content, Live, Community, Chat 및 후속 mutation 흐름.
    • 작업:
      • 하위 Activity 결과가 ActivityResult -> 명시적 결과 -> feature 단일 handler로 연결되는지 확인한다.
      • 동일한 raw result 해석과 refresh 조합이 여러 호출부에 남아 있는지 확인한다.
      • SharedFlow/observer 도입 지점이 있다면 Activity 밖의 독립 소비자, lifecycle, replay, 중복 처리 근거를 기록한다.
    • 검증 기준: 직접 결과 전달로 충분한 흐름에 전역 EventBus 또는 singleton observer가 추가되지 않는다.
    • 검증 기록:
      • 2026-07-15: ActivityResult, RESULT_OK, resolve*ActivityResult, SharedFlow, EventBus, observeForever 계열 검색으로 결과 전달 방식을 재검사했다. Community는 resolveCommunityActivityResulthandleCommunityChange, Live 생성은 resolveLiveCreationResulthandleLiveCreationResult를 통해 명시적 결과와 단일 handler로 연결된다.
      • 2026-07-15: FanTalk의 raw RESULT_OK 처리는 Creator Channel 내부 단일 owner 흐름이고 Phase 6에서 추출하지 않기로 한 도메인이므로 유지한다. BannerViewlifecycleObserver는 UI lifecycle local observer이며 전역 event/observer가 아니다.
      • 2026-07-15: 신규 SharedFlow, EventBus, singleton observer, observeForever 기반 전파는 추가되지 않았음을 확인했다.
      • 2026-07-16: 결과 전달과 observer 검색을 재실행했다. Community와 Live 생성은 명시적 결과와 단일 handler를 유지하고, FanTalk raw RESULT_OK는 단일 owner 예외로 남았다. 새 SharedFlow, EventBus, singleton observer, production observeForever 전파는 확인되지 않았다.
  • Task 7.5: 통합 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*"
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintCheck
      git diff --check
      
    • 수동 확인:
      • 로그인/본인인증/성인 설정 차단과 허용 흐름
      • Home, Content, Creator Channel, On-air의 오디오/시리즈/라이브 진입
      • Creator Channel 및 Community 상세 진입
      • Chat/DM 생성과 기존 room 진입
      • 기존 Activity result와 새로고침 동작
    • 기대 결과: v2 전체 관련 테스트와 컴파일, test source ktlint, whitespace 검증이 통과한다. 전체 main ktlint가 기존 기준선으로 실패하면 Phase 6/7 변경 파일의 신규 위반이 없음을 확인하고 기록한다.
    • 검증 기록:
      • 2026-07-15: Action 통합 테스트 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.content.action.*" --tests "kr.co.vividnext.sodalive.v2.live.action.*" --tests "kr.co.vividnext.sodalive.v2.creator.action.*" --tests "kr.co.vividnext.sodalive.v2.community.action.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" --rerun-tasksBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*" --rerun-tasksBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:compileDebugKotlinBUILD SUCCESSFUL로 통과했다.
      • 2026-07-15: ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했고 test source 리포트는 0건이다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만, main/test ktlint 리포트에서 현재 변경 Kotlin 파일명 검색 결과는 0건이다.
      • 2026-07-15: git diff --check HEAD는 whitespace 오류 없이 통과했다. Kotlin LSP는 환경에 kotlin-ls가 없어 Gradle compile/test로 대체 검증했다.
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*" --rerun-tasks를 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD는 통과했다.
      • 2026-07-16: ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했다. main/test ktlint 리포트에서 Phase 6/7 변경 Kotlin 파일명 검색 결과는 0건이다.

Phase 8: 푸시/딥링크의 도메인 Action 연결

  • 상태: 사용자 후속 요구로 기존 system/deeplink 예외를 재개방한다. 새 범용 router는 만들지 않고 기존 payload 해석 위치에서 이미 분리된 Action을 호출한다.

  • Task 8.1: 푸시/딥링크 계약과 우선순위 문서화

    • 수정 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 확정 계약:
      • DM deep link room은 Chat Action, 그 외 room은 Live 진입으로 처리하고 channel ID보다 우선한다.
      • 진행 중 Live는 기존 입장 정책을 실행하고 예약/미진행 Live는 레거시 Live Detail을 표시한다.
      • Community postId가 있으면 PostDetail Action을 실행하고, 없을 때만 creator profile로 fallback한다.
      • Community deep_link_sub5/path ID는 기존 creatorId이므로 routeByDeepLinkValue("community")는 Creator fallback이며, 별도 query/canonical postId를 ID로 대체 해석하지 않는다.
      • 현재 DM은 ${URISCHEME}://chat/{roomId}를 Chat Action으로 처리한다. legacy message_id는 room ID로 재해석하지 않고 과거 알림 호환 route로만 유지한다.
      • Content/Series/Creator/Community/DM은 기존 Action을 직접 재사용하며 새 PushRouteAction을 만들지 않는다.
      • message/audition/audio player 등 Action이 없는 system-only route는 현행 처리를 유지한다.
    • 검증 기록:
      • 2026-07-16: 사용자가 DeepLinkActivity의 최소 변경을 승인했고, Live 및 Community payload 우선순위와 fallback 계약을 PRD와 본 계획에 반영했다.
  • Task 8.2: 푸시/딥링크 라우팅 회귀 테스트를 RED로 추가

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/main/DeepLinkActivitySourceTest.kt
    • 검증 기준:
      • Main과 DeepLink 진입점이 Content/Creator/Community/Chat Action을 호출한다.
      • non-DM room이 channel보다 먼저 Live로 라우팅되고, Community post가 creator fallback보다 먼저 처리된다.
      • audio detail notification이 Content Action을 사용한다.
      • 구현 전 focused 테스트가 새 기대 조건으로 실패하고, 실패 원인이 아직 연결되지 않은 Action/Live route임을 확인한다.
    • 검증 기록:
      • 2026-07-16: MainV2ActivitySourceTestDeepLinkActivitySourceTest에 DM/Content/Series/Creator/Community Action 연결, non-DM Live 우선, Community post 우선, audio detail notification Action 연결 기대 조건을 추가했다.
      • 2026-07-16: 운영 코드 변경 전에 두 테스트 클래스를 --rerun-tasks로 실행했고 15개 중 신규 기대 조건 8개가 실패했다. 실패 지점은 기존 직접 Activity 이동, MainV2Activity의 non-DM room 분기 누락, Community post 우선 분기 누락으로 확인되어 의도한 RED 상태다.
  • Task 8.3: MainV2Activity cold start/onNewIntent Action 연결

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
    • 작업:
      • DM, Content, Series, Creator, Community route를 기존 도메인 Action에 연결한다.
      • non-DM room을 channel보다 먼저 LiveActionCoordinator.enterLiveRoom에 연결한다.
      • Community post를 creator fallback보다 먼저 처리한다.
      • audio detail notification을 Content Action에 연결하고 system-only route는 유지한다.
    • 검증 기준: focused source 테스트와 LiveEntryPolicyTest가 통과한다.
    • 검증 기록:
      • 2026-07-16: DM, Content, Series, Creator, Community 경로를 기존 Action에 연결하고, non-DM room을 channel보다 먼저 LiveActionCoordinator.enterLiveRoom으로 처리하도록 보완했다. deep_link_value=live fallback과 audio detail notification도 각각 Live coordinator와 Content Action에 연결했다.
      • 2026-07-16: Live coordinator는 기존 LiveEntryPolicy를 그대로 사용하므로 channel 정보가 없는 예약/미진행 방은 레거시 Live Detail을 표시하고, 진행 중 방은 기존 무료/유료/비밀번호 정책에 따라 입장한다.
  • Task 8.4: 승인된 DeepLinkActivity foreground Action 연결

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt
    • 작업:
      • foreground Content, Series, Creator, Community, DM 직접 navigation을 기존 Action 호출로 교체한다.
      • Live는 현재 Main 전달 계약을 유지하고 Main의 Live Action 진입점에서 처리한다.
      • Community post 우선 및 creator fallback 계약을 적용한다.
      • FCM payload 보존, foreground Live broadcast, system-only route는 변경하지 않는다.
    • 검증 기준: DeepLink source 테스트와 FCM payload 회귀 테스트가 통과한다.
    • 검증 기록:
      • 2026-07-16: 승인 범위 안에서 DeepLinkActivity의 foreground DM, Content, Series, Creator, Community 직접 navigation을 기존 Action 호출로 교체했다. Live의 Main 전달, foreground LiveRoom broadcast, message/audition/payment route는 유지했다.
      • 2026-07-16: Community postId 분기를 creator fallback보다 앞에 두어 PostDetail Action을 우선하고, post ID가 없을 때만 Creator Action으로 이동하도록 연결했다.
  • Task 8.5: 집중 및 최종 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --tests "kr.co.vividnext.sodalive.main.DeepLinkActivitySourceTest" --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.LiveEntryPolicyTest" --rerun-tasks
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      ./gradlew :app:ktlintCheck --rerun-tasks
      git diff --check HEAD
      
    • 수동 확인 항목:
      • 진행 중 Live push는 라이브에 입장한다.
      • 예약 Live push는 레거시 Live Detail을 표시한다.
      • Community push는 post ID가 있으면 게시글 상세, 없으면 Creator Channel을 표시한다.
      • cold start, foreground, onNewIntent, 앱 내 알림 목록 경로가 동일 우선순위를 사용한다.
      • 실행 상태: 연결 기기가 검증 직전 해제되어 실제 알림 터치/화면 표시 확인은 이번 실행에서 수행하지 못했다. 라우팅 계약과 우선순위는 아래 자동화 검증으로 확인했고 실기기 시나리오는 잔여 수동 확인 항목으로 기록한다.
    • 기대 결과: focused/전체 V2 테스트, 컴파일, test ktlint, whitespace 검증이 통과한다. 전체 main ktlint가 기존 기준선으로 실패하면 이번 변경 파일의 신규 위반이 없음을 리포트에서 확인해 기록한다.
    • 검증 기록:
      • 2026-07-16: RED 확인 후 MainV2ActivitySourceTestDeepLinkActivitySourceTest를 재실행해 15개 전체가 통과했다.
      • 2026-07-16: 위 두 테스트와 SodaFirebaseMessagingServiceSourceTest, LiveEntryPolicyTest 집중 묶음을 --rerun-tasks로 실행해 BUILD SUCCESSFUL을 확인했다. FCM의 기존 deep-link/room payload 보존과 Live의 상세/입장 정책이 함께 통과했다.
      • 2026-07-16: 후속 리뷰에서 Community deep_link_sub5가 legacy creatorId, 별도 postId query가 게시글 ID라는 기존 계약을 재확인했다. 따라서 상위 post Action 우선 분기와 routeByDeepLinkValue("community")의 Creator fallback을 유지했다.
      • 2026-07-16: 현재 DM 발행 계약은 ${URISCHEME}://chat/{roomId}이고 Chat Action으로 연결되어 있음을 확인했다. legacy message_id는 DM room ID 근거가 없어 재해석하지 않고 과거 알림 호환 경로로만 유지하기로 결정했다.
      • 2026-07-16: MainV2Activity가 raw deep-link URL의 postId query를 canonical EXTRA_COMMUNITY_POST_ID로 정규화한다는 테스트를 먼저 추가해 11개 중 1개 실패를 확인하고, 매핑을 보완한 뒤 11개 전체 통과를 확인했다.
      • 2026-07-16: 전체 V2 테스트 첫 실행에서 기존 MainV2AudioNotificationRouteSourceTest의 직접 Activity 기대 1건이 실패해 Content Action 기대 조건으로 정정했고, 단일 테스트와 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.*" --rerun-tasks 재실행이 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: 최종 집중 묶음에 MainV2ActivitySourceTest, MainV2AudioNotificationRouteSourceTest, DeepLinkActivitySourceTest, SodaFirebaseMessagingServiceSourceTest, LiveEntryPolicyTest를 포함해 BUILD SUCCESSFUL을 확인했다. 이후 Community fallback과 legacy message 계약을 명시한 두 source 테스트도 재실행해 통과했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin./gradlew :app:ktlintTestSourceSetCheck --rerun-tasksBUILD SUCCESSFUL로 통과했다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 550건으로 실패했지만 DeepLinkActivity.kt, MainV2Activity.kt는 main ktlint 리포트 검색 결과 0건이다.
      • 2026-07-16: git diff --check HEAD는 whitespace 오류 없이 통과했다. 코드 리뷰에서 푸시/딥링크 Action 연결 범위의 P0~P2 발견 사항은 없었다.
      • 2026-07-16: adb devices에서 기기 1대를 처음 확인했으나 설치 상태 확인 전에 연결이 해제됐고 재조회 결과 기기가 없어 실기기 알림 터치 검증은 수행하지 못했다.

Phase 9: Live 푸시 목적지 판단 로딩 표시

  • 상태: LiveViewModel.getRoomDetail()은 비동기 처리 중 isLoading, 실패 시 toastLiveData를 발행하지만 MainV2Activity가 두 상태를 관찰하지 않아 푸시 진입 중 진행·실패 여부가 보이지 않는 문제를 보완한다.

  • 후속 결정: Task 9.1~9.4의 screen_live_loading 문구 계약과 검증 기록은 당시 실행 이력으로 유지한다. 현재 계약은 cold start 딥링크의 공통 1초 대기와 Live 추가 조회가 같은 문구 없는 spinner를 공유하는 Task 9.5 이후 내용이다.

  • Task 9.1: Live 푸시 로딩 UX 계약 문서화

    • 수정 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 확정 계약:
      • MainV2Activity에서 Live room detail 조회 중 기존 LoadingDialogR.string.screen_live_loading 문구를 표시한다.
      • 조회 성공·실패로 LiveViewModel.isLoadingfalse가 되면 로딩을 해제한다.
      • 조회 실패 메시지는 LiveViewModel.toastLiveData를 관찰해 기존 Toast 방식으로 표시한다.
      • 별도 네트워크 판단이 없는 다른 도메인 route까지 범용 push loader로 확장하지 않는다.
    • 검증 기록:
      • 2026-07-16: HomeMainFragment, HomeOnAirLiveActivity, CreatorChannelActivity는 동일 LiveViewModel 상태를 LoadingDialog와 Toast에 연결하지만 MainV2Activity만 관찰하지 않는 차이를 확인했고, 이를 원인으로 확정했다.
      • 2026-07-16: 사용자가 기존 LoadingDialog/Toast 패턴을 Live 푸시 판단 구간에 적용하는 최소 설계를 승인했다.
  • Task 9.2: MainV2Activity Live 진입 상태 회귀 테스트 RED 추가

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
    • 검증 기준:
      • MainV2ActivityLiveViewModel.isLoadingtoastLiveData를 관찰한다.
      • loading true에는 screen_live_loading 문구로 LoadingDialog를 표시하고 false에는 해제한다.
      • 구현 전 focused 테스트가 아직 observer가 없다는 이유로 실패한다.
    • 검증 기록:
      • 2026-07-16: MainV2ActivitySourceTest에 Live 푸시 판단 중 LoadingDialog 표시·해제와 실패 Toast observer 기대 조건을 추가했다.
      • 2026-07-16: 운영 코드 변경 전에 focused 테스트를 --rerun-tasks로 실행해 12개 중 새 테스트 1개가 setupLiveEntryObservers() 부재로 실패하는 의도한 RED 상태를 확인했다.
  • Task 9.3: MainV2Activity Live 푸시 로딩·실패 안내 연결

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
    • 작업:
      • 기존 LoadingDialog를 재사용한다.
      • setupView()에서 Live 진입 상태 observer를 등록해 cold start와 onNewIntent 경로에 공통 적용한다.
      • Live Action/정책, payload 우선순위, 다른 도메인 route는 변경하지 않는다.
    • 검증 기준: Task 9.2 focused 테스트와 기존 푸시/Live 회귀 테스트가 통과한다.
    • 검증 기록:
      • 2026-07-16: MainV2Activity.setupLiveEntryObservers()를 추가해 LiveViewModel.isLoadingtrue이면 기존 LoadingDialogR.string.screen_live_loading을 표시하고 false이면 해제하도록 연결했다.
      • 2026-07-16: LiveViewModel.toastLiveData를 기존 BaseActivity.showToast에 연결했으며 Live Action, payload 우선순위, 다른 도메인 route는 변경하지 않았다.
      • 2026-07-16: 구현 후 MainV2ActivitySourceTest 12개 전체가 BUILD SUCCESSFUL로 통과했다.
  • Task 9.4: 집중 및 최종 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --rerun-tasks
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.*" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      git diff --check HEAD
      
    • 수동 확인 항목:
      • Live 푸시를 터치하면 상태 판단 중 라이브를 불러오고 있습니다. 로딩이 표시된다.
      • 진행 중 Live 입장 또는 예약 Live Detail 표시 직전에 로딩이 해제된다.
      • Live 조회 실패 시 로딩이 해제되고 오류 Toast가 표시된다.
    • 기대 결과: focused/Live 회귀 테스트, 컴파일, test ktlint, whitespace 검증이 통과한다.
    • 검증 기록:
      • 2026-07-16: MainV2ActivitySourceTest focused 테스트를 --rerun-tasks로 실행해 12개 전체가 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: MainV2ActivitySourceTest, SodaFirebaseMessagingServiceSourceTest, v2.live.action.* 회귀 묶음을 --rerun-tasks로 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, ./gradlew tasks --all, git diff --check HEAD가 통과했다. test ktlint 보고서는 0건이다.
      • 2026-07-16: ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 550건으로 실패했지만, 현재 생성된 main ktlint 보고서에서 MainV2Activity.kt 검색 결과는 0건이다.
      • 2026-07-16: LiveViewModel.getRoomDetail()이 성공 callback보다 먼저 isLoading=false를 발행하는 순서와 MainV2Activity 생명주기 observer 등록 위치를 재검토했으며, 이번 로딩 연결 범위에서 P0~P2 추가 발견 사항은 없었다.
      • 2026-07-16: adb devices 결과 연결 기기가 없어 Live 푸시 터치, 진행/예약 분기, 실패 Toast의 실기기 수동 확인은 수행하지 못했다. 자동 회귀 검증은 완료했고 수동 시나리오는 연결 기기와 유효한 Live payload가 준비되면 확인할 잔여 항목으로 기록한다.
  • Task 9.5: 딥링크 공통 대기 로딩 UX 계약 보완

    • 수정 파일:
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/prd.md
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 확정 계약:
      • cold start에서 Constants.EXTRA_DATA 또는 audio notification route가 있으면 기존 1초 지연 동안 문구 없는 LoadingDialog를 표시한다.
      • Live route는 공통 대기 종료 후 room detail 조회가 끝날 때까지 같은 spinner를 유지한다.
      • 다른 도메인 route는 공통 대기 종료 시 spinner를 해제하고 기존 Action으로 이동한다.
      • 일반 앱 실행과 지연이 없는 onNewIntent에 인위적인 1초 대기를 추가하지 않는다.
      • Live 조회 실패 Toast 계약은 유지한다.
    • 검증 기록:
      • 2026-07-16: 모든 cold start route가 MainV2Activity.onCreate()의 기존 1초 지연 블록을 공유하고, Live만 이후 LiveViewModel.getRoomDetail() 네트워크 조회를 수행함을 확인했다.
      • 2026-07-16: 사용자가 도메인별 문구 대신 공통 1초 대기와 Live 추가 조회에 문구 없는 spinner를 사용하는 권장 방향을 승인했다.
  • Task 9.6: 공통 대기와 Live 조회 로딩 상태 결합

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt
    • 작업:
      • cold start route 대기 상태와 Live 조회 상태를 별도 Boolean으로 관리한다.
      • 두 상태 중 하나라도 진행 중이면 기존 LoadingDialog.show(screenWidth)를 호출하고 둘 다 종료되면 해제한다.
      • R.string.screen_live_loading 문구 의존을 제거하고 새 문자열 리소스는 추가하지 않는다.
    • 검증 기준:
      • 일반 앱 실행에는 공통 로딩 상태가 활성화되지 않는다.
      • cold start 딥링크와 audio notification route는 1초 대기 동안 로딩 상태가 활성화된다.
      • Live 조회가 시작되면 공통 대기 종료 시점에도 로딩이 유지된다.
      • focused 테스트에서 구현 전 RED, 구현 후 GREEN을 확인한다.
    • RED 검증 기록:
      • 2026-07-16: 기존 Live 전용 문구 테스트를 cold start 공통 대기, Live 연속 로딩, 문구 없는 spinner 계약으로 변경했다.
      • 2026-07-16: 운영 코드 변경 전에 MainV2ActivitySourceTest--rerun-tasks로 실행해 12개 중 새 계약 테스트 1개가 updateRouteLoadingDialog() 부재로 실패하는 의도한 RED 상태를 확인했다.
    • GREEN 검증 기록:
      • 2026-07-16: MainV2ActivityisDeferredRouteLoadingisLiveEntryLoading을 추가하고 updateRouteLoadingDialog()에서 두 상태를 결합했다.
      • 2026-07-16: cold start의 Constants.EXTRA_DATA 또는 audio notification route가 있으면 기존 1초 지연 전에 공통 상태를 활성화하고, route 실행 후 비활성화하도록 연결했다.
      • 2026-07-16: Live observer는 별도 조회 상태만 갱신하며, LoadingDialog.show(screenWidth)를 사용해 screen_live_loading 문구 의존과 신규 문자열 추가 없이 같은 spinner를 공유한다.
      • 2026-07-16: 구현 후 MainV2ActivitySourceTest 12개 전체가 BUILD SUCCESSFUL로 통과했다.
  • Task 9.7: 집중 및 최종 회귀 검증

    • 실행 명령:
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --rerun-tasks
      ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.MainV2ActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.main.MainV2AudioNotificationRouteSourceTest" --tests "kr.co.vividnext.sodalive.fcm.SodaFirebaseMessagingServiceSourceTest" --tests "kr.co.vividnext.sodalive.v2.live.action.*" --rerun-tasks
      ./gradlew :app:compileDebugKotlin
      ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks
      git diff --check HEAD
      
    • 수동 확인 항목:
      • Content, Community, Creator, DM 등 cold start 푸시를 터치하면 공통 1초 대기 동안 문구 없는 spinner가 표시되고 목적지 이동 시 해제된다.
      • Live cold start 푸시는 공통 대기에서 room detail 조회까지 spinner가 끊기지 않는다.
      • 일반 앱 실행과 지연 없는 non-Live onNewIntent에는 불필요한 spinner가 표시되지 않는다.
    • 기대 결과: focused/route/FCM/Live 회귀 테스트, 컴파일, test ktlint, whitespace 검증이 통과한다.
    • 검증 기록:
      • 2026-07-16: MainV2ActivitySourceTest focused 테스트를 --rerun-tasks로 실행해 12개 전체가 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: MainV2ActivitySourceTest, MainV2AudioNotificationRouteSourceTest, SodaFirebaseMessagingServiceSourceTest, v2.live.action.* 회귀 묶음을 --rerun-tasks로 실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, ./gradlew tasks --all, git diff --check HEAD가 통과했다. test ktlint 보고서는 0건이다.
      • 2026-07-16: test ktlint 첫 실행은 sandbox의 Gradle wrapper lock 접근 제한으로 시작되지 않았고, 동일 명령을 승인된 캐시 접근으로 재실행해 BUILD SUCCESSFUL을 확인했다.
      • 2026-07-16: ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 550건으로 실패했지만, 현재 main ktlint 보고서에서 MainV2Activity.kt 검색 결과는 0건이다.
      • 2026-07-16: cold start route 상태와 Live 조회 상태의 전환 순서, 일반 실행의 초기 false 조건, onNewIntent 비지연 경로를 재검토했으며 이번 후속 범위에서 P0~P2 추가 발견 사항은 없었다.
      • 2026-07-16: adb devices 결과 연결 기기가 없어 도메인별 cold start 푸시와 Live 연속 spinner의 실기기 수동 확인은 수행하지 못했다. 자동 회귀 검증은 완료했고 수동 시나리오는 잔여 확인 항목으로 기록한다.

Historical Review Follow-up: Action 계약 보완

  • 이 절은 Phase 6 추가 전에 완료한 P1/P2/P3 이력이다. P2에서 남은 mutation 성공 경로 wiring 검증은 Task 6.4에서 별도 재개한다.

  • P0~P3는 Phase가 아니라 리뷰 이슈 우선순위다. P0은 즉시 차단해야 하는 치명적 문제, P1은 병합 전 해결할 높은 우선순위 문제, P2는 중요하지만 후속 보완 가능한 문제, P3는 낮은 우선순위의 정리·개선 사항을 뜻한다.

  • P1: Chat Action 로그인 접근 정책 소유

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatAction.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionHandler.kt
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/chat/action/ChatActionTest.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt
    • 검증 기록:
      • 2026-07-16: ChatAction이 유효 ID 확인 후 AccessRequirement.Login을 검사하고 차단 시 ChatActionResult.Blocked(AccessRequirement.Login)을 반환하도록 보완했다. ChatActionHandler의 Activity/Fragment 확장 함수는 ensureV2Access를 주입하며, BlockedIgnored에서는 Activity를 시작하지 않는다.
      • 2026-07-16: HomeMainFragment.openFollowingChat의 화면-local login guard는 제거하고 handleChatAction(ChatActionCommand.AiRoom/DmRoom) 호출만 남겨 Chat Action이 동일 정책을 소유하게 했다.
  • P2: Community ActivityResult 계약 문서와 테스트 정정

    • 수정 파일:
      • app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt
      • docs/20260714_V2_공통_접근_가드와_도메인_액션/plan-task.md
    • 검증 기록:
      • 2026-07-16: 기존 write/modify/detail Activity result는 post ID extra를 반환하지 않으므로 resolveCommunityActivityResult(source, resultCode)RESULT_OK와 source만 명시적 변경으로 변환하는 계약으로 유지했다.
      • 2026-07-16: CommunityChangeTest의 post ID 검증은 동일 객체끼리 비교하지 않고 Deleted.postId, PinChanged.postId payload를 직접 확인하도록 정정했다. post ID 유효성은 상세 진입 CommunityAction과 delete/pin 성공 경로에서 다룬다.
  • P3: 미사용 AI Character Intent helper 제거

    • 수정 파일:
      • app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt
      • app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt
    • 검증 기록:
      • 2026-07-16: 운영 호출부 없이 항상 null을 반환하던 HomeRecommendationAiCharacterRoute.toHomeRecommendationAiCharacterIntent()와 해당 테스트/import를 제거했다. AI Character route는 creatorId 보존과 invalid ID 무시만 검증하고, 실제 이동은 HomeMainFragmenthandleCreatorAction(CreatorActionCommand.Profile(...))가 담당한다.
  • 리뷰 보완 검증

    • 검증 기록:
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.chat.action.ChatActionTest" --rerun-tasksBUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.community.action.CommunityChangeTest" --rerun-tasksBUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --rerun-tasks./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" --rerun-tasks가 각각 BUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: Chat 관련 회귀 묶음 ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --rerun-tasksBUILD SUCCESSFUL로 통과했다.
      • 2026-07-16: ./gradlew :app:compileDebugKotlin, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD는 통과했다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만, main/test ktlint 리포트에서 이번 리뷰 보완 변경 파일명 검색 결과는 0건이다. Kotlin LSP는 환경에 kotlin-ls가 없어 Gradle compile/test로 대체 검증했다.

Verification Log

  • 2026-07-14: 사용자 요청에 따라 이번 단계에서는 PRD와 구현 계획 문서만 작성했다. 운영 코드, 테스트 코드, DI는 아직 변경하지 않았고 Gradle 검증은 실행하지 않았다.
  • 2026-07-14: 문서 범위는 Access부터 도메인별로 순차 전환하는 하나의 연속 작업으로 정의했다. 화면별 최초 집계 API는 유지하고 반복 action만 단일 소유 진입점으로 통합한다.
  • 2026-07-14: 문서 작성 후 ./gradlew tasks --all을 실행해 계획에 사용하는 :app:testDebugUnitTest, :app:compileDebugKotlin, :app:ktlintCheck 작업이 존재함을 확인했으며 명령은 BUILD SUCCESSFUL로 종료되었다. git diff --check도 whitespace 오류 없이 통과했다.
  • 2026-07-14: Community 업로드 후 Home/Community 탭 갱신 설계를 구체화했다. 초기 구현은 전역 observer 대신 CommunityChange 결과와 Creator Channel의 단일 handleCommunityChange composition handler를 사용하며, 레거시 ActivityResultv2 adapter에서 변경 결과로 변환한다.
  • 2026-07-14: 사용자 확인에 따라 Activity result가 필요한 모든 도메인 Action에 공통 결과 전달 원칙을 확장했다. 기본 방식은 ActivityResult -> 명시적 도메인 결과 -> feature 단일 handler이며, Action/Repository 직접 결과도 동일 handler로 합류한다. SharedFlow/observer는 Activity 밖의 독립 소비자와 lifecycle/replay 요구가 확인될 때만 도입한다.
  • 2026-07-14: Phase 0/1 리뷰 답변을 반영해 Legacy MyPage 호출부와 로그인 token 정책 확인, FanTalk source assertion 수동 삭제 사유를 기록했다. 문서 변경 후 ./gradlew tasks --allBUILD SUCCESSFUL, git diff --check HEAD는 무출력으로 통과했다.
  • 2026-07-14: Phase 2 Content Action 전환 후 전체 :app:testDebugUnitTest, :app:compileDebugKotlin, tasks --all, git diff --check HEAD가 통과했다. 전체 ktlint는 기존 main source 550줄 기준선 위반으로 실패했지만 Phase 2 변경 Kotlin 파일과 test source의 신규 위반은 0건이다. 연결된 Android 기기가 없어 실기기 수동 탭 검증은 후속 확인 항목으로 남겼다.
  • 2026-07-15: 사용자 수동 테스트 결과 Phase 3 사전 준비 3개와 시나리오 22개를 PASS로 확인했다. 미체크 시나리오 M3-03, M3-04, M3-06, M3-15, M3-24, M3-26, M3-29, M3-30은 사용자 판단에 따라 테스트 불가능 또는 의미 없음으로 보아 SKIPPED 처리했으며, FAILED는 0개다. 모든 항목의 판정이 완료되어 Task 3.8과 Phase 3 수동 회귀 검증을 완료 처리했다.
  • 2026-07-15: Phase 4/5 Creator/Community/Chat Action 전환 후 action 테스트 묶음, Home/Content/Chat/Creator source 회귀 묶음, 넓은 관련 테스트 묶음, :app:compileDebugKotlin, tasks --all, git diff --check HEAD가 통과했다. :app:ktlintTestSourceSetCheck --rerun-tasks는 통과했고 test source 보고서는 0건이다. :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만 Phase 4/5 변경 Kotlin 파일의 신규 ktlint 위반은 리포트 검색 결과 0건이다. Kotlin LSP는 환경에 kotlin-ls가 없어 Gradle compile/test로 대체 검증했다.
  • 2026-07-15: Phase 6 의존 방향/결과 전달/미추출 도메인 재검사를 완료했다. 추가 코드 삭제나 신규 Action 추출은 필요하지 않았고, MainV2Activity system/deeplink route와 MainV2Activity.showLoginActivity() 호환 진입점, parent-child composition, FanTalk 단일 owner raw result 처리는 의도적으로 유지했다. Action 통합 테스트, v2.* 전체 단위 테스트, :app:compileDebugKotlin, :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD는 통과했다. :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만 현재 변경 Kotlin 파일의 신규 ktlint 위반은 0건이다.
  • 2026-07-16: 코드 리뷰 보완으로 Chat Action 로그인 접근 정책 소유, Community ActivityResult 계약 문서/테스트 정정, 미사용 AI Character Intent helper 제거를 완료했다. 관련 focused 테스트, Chat 회귀 묶음, :app:compileDebugKotlin, :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD는 통과했다. :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 위반으로 실패했지만 리뷰 보완 변경 Kotlin 파일의 신규 ktlint 위반은 0건이다.
  • 2026-07-16: 최종 소유 경계를 기준으로 PRD와 Phase 15 규범 문구를 동기화하고 별도 Phase 6을 추가했다. 기존 최종 점검 Phase 6은 과거 검증 기록을 보존한 채 Phase 7로 이동하고 Task 7.17.5를 모두 재개방했다. 이번 단계에서는 두 문서만 수정했으며 운영/테스트 코드는 변경하지 않았고, 코드 재점검·Community mutation wiring 테스트·집중/최종 검증은 Task 6.3~7.5에 미완료로 남겼다.
  • 2026-07-16: 두 문서에 대해 git diff --check HEAD가 무출력으로 통과했다. Phase 07 번호와 Task 6/7 체크 상태를 검색해 새 Phase 6의 문서 Task 6.16.2만 완료, Task 6.3~7.5는 미완료임을 확인했고, 정정 대상이던 Creator/Community/Chat 복수형 파일명과 이전 카탈로그 문구는 더 이상 남지 않음을 확인했다. 문서만 수정하는 단계이므로 Gradle 검증은 실행하지 않았다.
  • 2026-07-16: 직전 두 항목은 Phase 6/7 후속 작업을 시작하기 전 재개방 시점의 기록으로 유지한다. 이후 Task 6.37.5와 코드 리뷰 보완을 완료했으며, 최종 재검증에서 ./gradlew :app:testDebugUnitTest --rerun-tasks, ./gradlew :app:assembleDebug, ./gradlew :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check cc8d061b, git diff --check HEAD가 통과했다. ./gradlew :app:ktlintCheck --rerun-tasks는 기존 main source 기준선 550건으로 실패했지만 v2 경로와 test/androidTest source의 신규 위반은 0건이며, 기능 코드에서 P0P2 수준의 리뷰 발견 사항은 없었다.
  • 2026-07-16: Phase 8에서 DeepLinkActivityMainV2Activity의 DM/Content/Series/Creator/Community route를 기존 Action으로 연결하고 Live를 LiveActionCoordinator에 연결했다. Community는 별도 postId가 있으면 PostDetail Action을 우선하고, legacy community/{creatorId} 또는 deep_link_sub5만 있으면 Creator Action으로 fallback한다. 현재 DM은 chat/{roomId}만 Chat Action으로 처리하고 legacy message는 과거 호환 경로로 유지한다.
  • 2026-07-16: Phase 8 최종 검증에서 focused route/FCM/Live 테스트, 전체 v2.* 테스트, :app:compileDebugKotlin, :app:ktlintTestSourceSetCheck --rerun-tasks, git diff --check HEAD가 통과했다. 전체 main ktlint는 기존 기준선 550건으로 실패했지만 Phase 8 production 변경 파일의 신규 위반은 0건이고 코드 리뷰의 P0~P2 발견 사항은 없었다. 연결 기기가 검증 도중 해제되어 실제 푸시 터치 수동 시나리오는 잔여 확인 항목으로 남겼다.
  • 2026-07-16: Phase 9에서 MainV2Activity가 Live 푸시/딥링크 목적지 판단에 사용하는 LiveViewModel.isLoadingtoastLiveData를 관찰하도록 연결했다. 조회 중에는 기존 LoadingDialog라이브를 불러오고 있습니다.를 표시하고, 성공·실패 종료 시 해제하며 실패 메시지는 Toast로 표시한다. 다른 동기식 도메인 route에는 범용 로딩을 추가하지 않았다.
  • 2026-07-16: Phase 9 최종 검증에서 focused 12개 테스트, FCM/Live Action 회귀 묶음, :app:compileDebugKotlin, :app:ktlintTestSourceSetCheck --rerun-tasks, tasks --all, git diff --check HEAD가 통과했다. 전체 main ktlint는 기존 기준선 550건으로 실패했지만 MainV2Activity.kt의 신규 위반은 0건이다. 연결 기기가 없어 실제 Live 푸시 터치 수동 시나리오는 잔여 확인 항목으로 남겼다.
  • 2026-07-16: Phase 9 후속 결정으로 Live 전용 라이브를 불러오고 있습니다. 문구를 제거하고, cold start 푸시/딥링크의 기존 1초 대기와 Live 추가 room detail 조회가 하나의 문구 없는 LoadingDialog spinner를 공유하도록 변경했다. 일반 앱 실행과 지연 없는 non-Live onNewIntent에는 공통 대기를 추가하지 않았다.
  • 2026-07-16: Phase 9 후속 최종 검증에서 focused 12개 테스트, route/오디오 알림/FCM/Live Action 회귀 묶음, :app:compileDebugKotlin, :app:ktlintTestSourceSetCheck --rerun-tasks, tasks --all, git diff --check HEAD가 통과했다. 전체 main ktlint는 기존 기준선 550건으로 실패했지만 MainV2Activity.kt의 신규 위반은 0건이다. 연결 기기가 없어 실기기 수동 시나리오는 잔여 확인 항목으로 남겼다.