60 KiB
크리에이터의 리스너 DM 시작 구현 계획
| 문서 항목 | 내용 |
|---|---|
| 상태 | 구현 완료 |
| 작성일 | 2026-09-14 |
| 요구사항 기준 | docs/20260914_크리에이터의_리스너_DM_시작/prd.md |
| API 기준 | 서버 CreateUserCreatorChatRoomRequest(recipientId?, creatorId?) 계약 제공됨 |
| 현재 Phase | Phase 5 완료 |
| 현재 활성 Goal | 없음 |
목표
크리에이터가 메인 대화 탭에서 리스너를 선택하고 기존 DM 생성 흐름으로 먼저 대화를 시작할 수 있게 한다.
현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---|---|---|---|---|
| 1 | 완료 | 2/2 |
없음 | 없음 |
| 2 | 완료 | 4/4 |
없음 | 없음 |
| 3 | 완료 | 1/1 |
없음 | 없음 |
| 4 | 완료 | 2/2 |
없음 | 없음 |
| 5 | 완료 | 1/1 |
없음 | 없음 |
- 동시에 하나의 미완료 goal만 운용한다.
- 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다.
- 사용자가 명시적으로 요청하지 않으면 goal에 token budget을 설정하지 않는다.
범위
포함
- 메인 대화 탭 우측 하단
+버튼 추가 계획. - 크리에이터 역할 사용자에게만 신규 DM 시작 진입점 노출.
- Figma
2372:23496기준 수신자 선택 화면. - 최초 진입 시 기존 팔로우 리스트 API 호출.
GET /member/search와nickname을 사용하는 2글자 이상 500ms debounce 검색.- 검색 결과 empty 문구와 모든 신규 문구의 다국어 리소스 처리.
- Figma
2372:23521기준 확인 팝업, v2 공통 다이얼로그 재사용. - 기존 크리에이터 채널 DM 생성 API 재사용, 신규 리스너 DM은
recipientId, 기존 크리에이터 채널 DM은creatorIdrequest 계약 유지. - DM 방에서 뒤로 돌아와 대화 탭이 다시 보일 때 현재 filter의 대화방 목록 첫 페이지 갱신.
- Figma
2372:23496,2372:23506기준 수신자 선택/검색 결과 화면 상단 toolbar와검색결과 N라벨 정합성 수정. - 수신자 선택 화면에서 팔로워 0명일 때
팔로워섹션 문구 숨김. 검색결과 0과팔로워문구 크기 통일, 검색결과 숫자만gray/500색상 적용.
제외
- 이 문서 작성 단계의 코드 구현.
- 신규 백엔드 endpoint 추가.
- DM 메시지 화면, 소켓, 음성 메시지 정책 변경.
- 팔로우/검색 결과의 프로필 상세, 차단, 팔로우 토글 정책 신규 정의.
app/src/androidTest, 기기·에뮬레이터 조작, 스크린샷·시각 QA.- DM 방 내부 메시지 읽음/전송/소켓 상태 변경.
기술적 제약
- 기술 스택: Android, Kotlin, RxJava, Retrofit, LiveData, Koin 기반 기존 구조.
- 신규
Activity,Fragment,ViewModel및 연결 하위 코드는kr.co.vividnext.sodalive.v2패키지 하위에 작성한다. - 레거시 코드는 v2 wrapper/adapter를 우선 사용한다. 기능 구현에 불가피한 경우 최소 수정만 허용한다.
CreateDmChatRoomRequest계약 변경은recipientId신규 경로와 기존creatorId크리에이터 채널 DM 경로의 회귀를 반드시 포함한다.- 제공되지 않은 endpoint, DTO, enum, 오류 status/key를 추정하지 않는다.
- 테스트 범위는
app/src/test의 로직/local unit test로 한정한다. - UI 변경 자동 테스트는 adapter 분기, mapper, formatter, presentation model, 라우팅처럼 화면 표현을 결정하는 로직만 검증한다. 레이아웃 크기·간격·constraint·visibility 직접 검증은 하지 않는다.
- 구현 Task는
RED → RED 확인 → GREEN → GREEN 확인 → REFACTOR순서를 따른다. - v2 대화 목록 첫 페이지 갱신은 cursor 기반 API의
cursor = null요청으로 검증한다. 별도 page counter를 추가하지 않는다.
Task TDD 작성 규칙
- RED: 가장 작은 실패 test를 작성한다.
- RED 확인: focused test를 실행해 요구 동작 미구현 때문에 발생한 의도한 assertion 실패를 확인한다.
- GREEN: RED를 통과시키는 최소 구현을 작성한다.
- GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
- REFACTOR: 이번 Task가 만든 중복만 정리하고 focused test·직접 영향 회귀·lint 결과를 Progress에 기록한다.
읽기 전용 계약 확인 Task는 TDD 단계를 형식적으로 만들지 않는다. 대신 TDD 예외 사유와 대체 검증 방법을 적는다.
Phase 1. 계약·파일 지도 확정
Phase 결과: 구현에 필요한 기존 파일, 신규 파일 후보, 서버 제공 계약 적용 위치, 역할 판정 기준을 확정한다.
선행조건: prd.md의 확정 요구사항과 Open Questions 확인.
Phase 완료 조건: P1-T1, P1-GATE 완료, 확인 결과를 Progress와 Decision Log에 누적.
구현 항목
Task 1.1 계약·파일 지도 확인
Goal 실행 P1-T1: 기존 코드와 서버 제공 계약을 대조해 구현 시작 조건을 확정한다.
- 시작 조건:
prd.md의DM-001~DM-019,EXT-001해결 상태 확인. - 완료 증거: 아래 대조표가 실제 파일 경로와 결정 상태로 갱신되고, 서버 계약 적용 위치가 Decision Log에 기록된다.
- 범위 밖: production 코드 변경, 임의 debounce 상수 적용, 서버 계약과 다른 request contract 구현.
- TDD 예외 사유: 동작 변경 없는 읽기 전용 계약·파일 확인 Task다.
- 대체 검증 방법: 기존 코드 심볼과 PRD 요구사항을 대조하고 누락·차단 항목을 문서화한다.
Files:
- Confirm:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt - Confirm:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatApi.kt - Confirm:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt - Confirm:
app/src/main/java/kr/co/vividnext/sodalive/user/UserApi.kt - Confirm:
app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt - Confirm:
app/src/main/java/kr/co/vividnext/sodalive/following/FollowingCreatorRepository.kt - Confirm: v2 공통 다이얼로그 실제 파일 경로
- Confirm: 홈 화면
+버튼 실제 파일과 drawable/style 경로 - Confirm: string resource 지원 언어 파일 경로
- Planned Create: v2 수신자 선택 화면 파일
- Planned Test:
app/src/test하위 DM request, 검색 상태, 라우팅 test 파일
Interfaces:
-
Consumes: 기존 팔로우 목록 API,
UserRepository.searchUser,DmChatRepository.createOrGetRoom, v2 공통 다이얼로그. -
Produces: 구현 대상 파일 지도, 서버 계약 상태, 다국어 문구 표.
-
기존 크리에이터 채널 DM 생성 호출부와 request DTO를 확인한다.
-
서버가 제공한
CreateUserCreatorChatRoomRequest(recipientId?, creatorId?)계약을EXT-001에 기록한다. -
팔로우 리스트 페이지가 사용하는 API와 응답 ID 필드를 확인한다.
-
검색 결과
GetRoomDetailUser의 사용자 ID, 닉네임, 프로필 이미지 필드를 확인한다. -
크리에이터 역할 판정 기준과 메인 대화 탭 진입점 노출 위치를 확인한다.
-
v2 공통 다이얼로그 파일과 재사용 가능한 문구 주입 방식을 확인한다.
-
홈 화면
+버튼 리소스와 위치 구현 패턴을 확인한다. -
확정된
OQ-001~OQ-005결정이 실제 구현 파일 지도와 충돌하지 않는지 확인한다.
완료 조건
P1-T1의 체크박스와 대체 검증 기록이 완료됐다.EXT-001해결 상태와 호출 경로별 request field 사용 기준이 구현 Task의 시작 조건에 반영됐다.- 실제 파일 경로와 예정 파일 경로가 구분돼 있다.
검증 방법
Phase 1 Gate
Goal 실행 P1-GATE: Phase 2 구현을 시작해도 되는지 문서 기준으로 판정한다.
- 시작 조건:
P1-T1완료. - 완료 증거: PRD 요구사항, Open Questions, 외부 의존, 파일 지도 대조표 통과.
- 범위 밖: Gate 통과를 위한 요구사항 삭제 또는 임의 확정.
수동 문서 검증:
DM-001~DM-019가 최소 하나의 Phase 2 Task에 연결돼 있다.OQ-001~OQ-005가 모두 확정 상태로 유지된다.EXT-001이 해결됐고P2-T1은 신규recipientId와 기존creatorId경로를 모두 검증하도록 표시돼 있다.- 레거시 파일 변경 필요 여부가 확인돼 있다.
Phase 2. 기능 구현 계획
Phase 결과: 크리에이터가 메인 대화 탭에서 리스너를 선택해 DM을 시작하는 흐름을 구현한다.
선행조건: P1-GATE 완료.
Phase 완료 조건: P2-T1~P2-T3, P2-GATE 완료, 검증 기록 누적.
구현 항목
Task 2.1 DM 요청 계약 적용
Goal 실행 P2-T1: DM 방 생성 요청 DTO가 recipientId와 creatorId를 모두 표현하도록 계약을 적용하고 호출 경로별로 하나의 ID만 보내게 한다.
- 시작 조건:
P1-GATE완료. - 완료 증거: 신규 리스너 DM의
recipientIdrequest serialization test, 기존 크리에이터 채널 DM의creatorIdrequest serialization 회귀 test, focused test 결과 기록. - 범위 밖: 신규 endpoint 추가, DM 메시지 화면 수정.
Files:
- Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatApi.kt또는 실제 DTO 파일 - Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/data/DmChatRepository.kt - Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/하위 request/repository test
Interfaces:
-
Consumes: 서버가 제공한
CreateUserCreatorChatRoomRequest(recipientId: Long? = null, creatorId: Long? = null)호환 contract. -
Produces: 신규 리스너 DM용
CreateDmChatRoomRequest(recipientId = selectedUserId), 기존 크리에이터 채널 DM용CreateDmChatRoomRequest(creatorId = creatorId)contract. -
RED: 신규 리스너 DM 생성 request를 JSON으로 직렬화했을 때
recipientId가 있고creatorId가 없음을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해 신규
recipientId경로가 없어 실패함을 확인한다. -
GREEN: DTO에 nullable
recipientId,creatorId를 모두 표현하고 신규 리스너 DM 요청 생성 경로만recipientId를 채우도록 최소 구현한다. -
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
RED: 기존 크리에이터 채널 DM 생성 request가 계속
creatorId를 보내고recipientId를 보내지 않음을 검증하는 회귀 test를 작성한다. -
RED 확인: focused test를 실행해 DTO 변경 전후의 기대 차이를 확인한다.
-
GREEN: 기존
createOrGetRoom(token, creatorId)경로가creatorIdcontract를 유지하게 한다. -
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
REFACTOR: 신규/기존 DM 생성 경로의 중복만 정리하고 focused test와 lint 결과를 Progress에 기록한다.
Task 2.2 팔로우·검색 상태 로직
Goal 실행 P2-T2: 최초 팔로우 목록과 debounce 검색 상태를 제공한다.
- 시작 조건:
P1-GATE완료. - 완료 증거: 팔로우 최초 조회, 0~1자 팔로우 리스트 복귀, 500ms debounce 후 최종 검색어 요청, empty 상태 test 통과.
- 범위 밖: 화면 레이아웃 속성 직접 검증, 신규 검색 endpoint 추가.
Files:
- Planned Create: v2 수신자 선택 ViewModel/state/mapper 파일
- Planned Modify: 기존 repository wrapper 또는 신규 v2 repository adapter
- Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/하위 검색 상태 test
Interfaces:
-
Consumes: 기존 팔로우 목록 API,
UserRepository.searchUser(nickname, token). -
Produces: 팔로우 목록 상태, 검색 결과 상태, 검색 empty 상태, 로딩/오류 상태.
-
RED: 최초 진입 시 팔로우 목록 API가 1회 호출되는 실패 test를 작성한다.
-
RED 확인: focused test를 실행해 호출이 없어 실패함을 확인한다.
-
GREEN: 최초 상태에서 기존 팔로우 목록 API를 호출하는 최소 로직을 작성한다.
-
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
RED: 검색어 0~1자에서는
/member/search요청 0회와 팔로우 리스트 복귀, 2자 이상 연속 입력 중에는 500ms 전 요청 0회와 500ms 후 최종 검색어 1회 요청을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해 debounce/길이 조건 미구현으로 실패함을 확인한다.
-
GREEN: 500ms debounce와 길이 조건으로 검색 로직을 작성한다.
-
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
REFACTOR: empty/error/loading 상태 mapper 중 이번 Task가 만든 중복만 정리하고 focused test 결과를 Progress에 기록한다.
Task 2.3 진입·화면·확인 팝업 연결
Goal 실행 P2-T3: 크리에이터 전용 + 버튼, 신규 화면, 확인 팝업, 보내기 액션을 연결한다.
- 시작 조건:
P2-T1,P2-T2완료. - 완료 증거: 역할별 진입 판단, 문구 resource mapping, 취소/보내기 action, navigation test 통과.
- 범위 밖: 새 다이얼로그 구현, 기존 DM 방 UI 변경.
Files:
- Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt - Planned Create: v2 수신자 선택 화면 Activity/Fragment, adapter, layout, ViewModel 연결 파일
- Modify:
app/src/main/res/values/strings.xml및 기존 지원 언어 string resource 파일 - Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/하위 라우팅/문구/action test
Interfaces:
-
Consumes: Phase 2.1 DM 생성 contract, Phase 2.2 수신자 선택 상태, v2 공통 다이얼로그.
-
Produces: 크리에이터 전용 진입점, 수신자 선택 화면, 확인 팝업, DM 방 이동 action.
-
RED: 크리에이터 역할이면
+버튼 진입 action이 활성이고 비크리에이터면 비활성임을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해 역할별 분기가 없어 실패함을 확인한다.
-
GREEN: 메인 대화 탭에 홈과 유사한
+진입점을 최소 변경으로 연결한다. -
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
RED: 사용자 선택 후 팝업 문구가
메시지 보내기,%s에게 메시지를 보낼까요?,취소,보내기resource를 사용하고 취소 시 요청 0회임을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해 팝업 action 미구현으로 실패함을 확인한다.
-
GREEN: v2 공통 다이얼로그를 재사용해 문구와 취소/보내기 action을 연결한다.
-
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
RED:
보내기시 선택한 사용자 ID가recipientId로 전달되고 성공 시 기존 DM 방 이동 action이 실행됨을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해 보내기 연결 미구현으로 실패함을 확인한다.
-
GREEN: 기존 DM 생성 repository/action을 재사용해 보내기와 이동을 연결한다.
-
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
REFACTOR: 신규 화면 파일의 이번 Task 중복만 정리하고 focused test·직접 영향 회귀·lint 결과를 Progress에 기록한다.
P2-T3 API 연결 보완: 실제 비동기 provider 연결에 필요한 요청 실패 상태와 이전 요청 취소를 같은 Task에서 최소 추가한다. 기존 P2-T2 완료 기록은 유지한다.
- RED: 요청 실패 2건과 늦은 팔로워/검색 응답 2건의 회귀 test를 추가하고 실패를 확인한다.
- GREEN: 요청 오류를 오류 상태로 전달하고 검색 모드 전환 시 이전 API 구독을 취소한다.
- 검증: 수신자 focused test와 기존 DM/ChatAction 회귀를 실행한다.
P2-T3 검증 기록 — 2026-09-14
- 사용자 승인에 따라
DESIGN.md없이 기존 v2 FAB, toolbar, typography/spacing/color, 프로필 이미지 loader,V2ModalDialog를 재사용했다. - 신규 화면은 기존 repository를 ViewModel factory에서 연결한다. 팔로워는 최초 1페이지 20명, 검색은 기존 정책대로 2글자 이상 500ms 후 실행하며 전체 검색 응답을 표시한다. 이번 최소 구현에는 팔로워 추가 페이지 로딩을 넣지 않았다.
- 취소 무부작용과 확인 후 생성/이동은 요청을 confirm callback 안에만 연결하고 cancel callback을 지정하지 않는 source wiring test로 검증했다. 기기 UI 자동화는 실행하지 않았다.
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerSourceTest": RED 5개 assertion 실패 확인. 역할/신규 화면/문구/생성 연결 미구현이 원인이다../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerViewModelTest": 오류 상태 추가 전isError부재 컴파일 실패, 이후 늦은 응답 회귀 2개 assertion 실패를 각각 확인했다../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.*": GREEN 성공. 이후 늦은 응답 회귀도 아래 최종 통합 명령에서 통과했다../gradlew :app:ktlintCheck: 신규 test 줄바꿈 오류로 최초 실패했고 해당 줄바꿈만 수정했다. 최종 통합 실행에서 성공했다../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" :app:assembleDebug :app:ktlintCheck: 최종BUILD SUCCESSFUL, exit 0. 수신자 13개를 포함한 18개 suite 181개 test 실패/오류 0, APK 및 layout/resource binding 컴파일 성공, ktlint 성공.- 앞선 test+assemble 통합 실행은 도구 제한 120초로 중단되어 600초 제한으로 재실행했고 성공했다.
- 신규 Kotlin production 비공백 LOC: Activity 89, factory 38, adapter 29. 변경 ViewModel 84, 기존 ChatMainFragment 234. 기존 Fragment는 다음 확장 시 분리 검토가 필요한 200~250 경고 구간이며 이번에는 요청 진입점만 추가했다.
- LSP diagnostics 도구는 제공되지 않아 실행하지 못했으며 Kotlin 컴파일과 ktlint로 검증했다. 외부 서비스 호출, 기기/에뮬레이터, screenshot, androidTest는 요청대로 실행하지 않았다.
완료 조건
P2-T1,P2-T2,P2-T3의 체크박스와 완료 증거가 모두 충족됐다.- 크리에이터 전용 진입, 팔로우 목록, 검색, empty, 팝업, DM 생성 흐름이 PRD와 일치한다.
- 기존 크리에이터 채널 DM 진입 회귀가 통과했다.
검증 방법
Phase 2 Gate
Goal 실행 P2-GATE: 크리에이터의 리스너 DM 시작 흐름과 기존 DM 회귀를 최종 판정한다.
- 시작 조건: Phase 2의 모든 활성 Task goal 완료.
- 완료 증거: 아래 focused/영향 범위 검증 통과와 Progress 기록.
- 범위 밖: 실패와 무관한 리팩터링, 테스트 삭제·완화.
./gradlew :app:test --tests "*Dm*"
./gradlew :app:test --tests "*Chat*"
./gradlew :app:ktlintCheck
Expected: 0 exit code. DM request contract, 검색 debounce, 역할별 진입, 팝업 action, 기존 크리에이터 채널 DM 회귀가 통과한다.
전체 회귀가 필요하다고 판단되면 아래 명령을 별도로 실행하고, 생략 시 생략 근거와 focused 검증 결과를 Progress에 기록한다.
./gradlew :app:test
Phase 3. DM 방 복귀 목록 갱신
Phase 결과: 사용자가 DM 방에서 뒤로 돌아와 대화 탭을 다시 보면 현재 filter 기준 최신 대화방 목록 첫 페이지가 표시된다.
선행조건: Phase 2 완료, 사용자 확인으로 채팅방 뒤로가기 시 대화 탭 복귀 방식이 기능적으로 맞다고 확정.
Phase 완료 조건: P3-T1, P3-GATE 완료, 검증 기록 누적.
구현 항목
Task 3.1 DM 방 복귀 시 대화 목록 첫 페이지 갱신
Goal 실행 P3-T1: 대화 탭이 다시 resume될 때 최초 진입 중복 호출은 피하고, 이후 복귀에서는 기존 표시 목록을 비운 뒤 현재 filter 첫 페이지를 다시 요청한다.
- 시작 조건:
P2-GATE완료와 PRDDM-020확정. - 완료 증거: Fragment source wiring RED/GREEN, ViewModel 첫 페이지 reset 회귀 확인, focused test와 영향 범위 검증 결과 기록.
- 범위 밖:
DmChatRoomActivity내부 메시지 정책 변경, 신규 API 추가, 기기·에뮬레이터 UI 조작.
Files:
- Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt - Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragmentLayoutTest.kt - Confirm:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainViewModelTest.kt
Interfaces:
-
Consumes:
ChatMainViewModel.loadFirstPage(filter)의cursor = null, 목록 초기화, stale response 방지 contract. -
Produces: 복귀 시
selectedFilter를 유지한 첫 페이지 재조회와 기존 표시 목록 clear wiring. -
RED:
ChatMainFragmentsource가onResume()에서 첫 resume을 skip하고 이후cancelChatPullRefresh(),bindChatRooms(emptyList(), showEmpty = false),viewModel.loadFirstPage(selectedFilter)를 호출함을 검증하는 실패 test를 작성한다. -
RED 확인: focused test를 실행해
onResume()복귀 갱신 wiring 미구현으로 실패함을 확인한다. -
GREEN:
ChatMainFragment에 view 생성 시 초기화되는 first-resume guard와 subsequent resume refresh를 최소 구현한다. -
GREEN 확인: 같은 focused test를 다시 실행해 성공하고, 기존
ChatMainViewModelTest의 첫 페이지 reset 회귀가 통과함을 확인한다. -
REFACTOR: 신규 abstraction 없이 중복만 점검하고 focused test·직접 영향 회귀·lint 결과를 Progress에 기록한다.
완료 조건
P3-T1의 체크박스와 완료 증거가 모두 충족됐다.- 최초 대화 탭 진입은 기존
onViewCreated()첫 페이지 요청만 사용하고 중복 resume 요청을 만들지 않는다. - DM 방 복귀 시 현재 filter 기준 첫 페이지 요청이 발생하고 기존 표시 목록은 먼저 비워진다.
검증 방법
Phase 3 Gate
Goal 실행 P3-GATE: DM 방 복귀 대화 목록 갱신과 기존 채팅 회귀를 최종 판정한다.
- 시작 조건:
P3-T1완료. - 완료 증거: 아래 focused/영향 범위 검증 통과와 Progress 기록.
- 범위 밖: 실패와 무관한 UI 정렬, DM 방 내부 로직 수정.
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainViewModelTest"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" :app:assembleDebug :app:ktlintCheck
Expected: 0 exit code. 복귀 갱신 source wiring, 첫 페이지 cursor = null reset, 기존 pagination/filter/action 회귀가 통과한다.
Phase 4. 수신자 검색 화면 Figma 정합성 수정
Phase 결과: 수신자 선택 화면과 검색 결과 화면이 Figma 2372:23496, 2372:23506의 상단 검색 toolbar, 섹션/검색결과 라벨, 리스트 행 구조와 일치한다.
선행조건: Phase 2 기능 구현 완료, 사용자 지적으로 Figma UI 불일치와 검색결과 0 미표시가 확인됨.
Phase 완료 조건: P4-T1, P4-GATE 완료, 검증 기록 누적.
구현 항목
Task 4.1 수신자 선택·검색 결과 UI 정합성 수정
Goal 실행 P4-T1: 기존 수신자 선택 기능을 유지하면서 Figma 검색 toolbar 구조와 검색 결과 count 라벨을 적용한다.
- 시작 조건: PRD
DM-021확정과 Figma2372:23496,2372:23506metadata/screenshot 확인. - 완료 증거: layout/source RED/GREEN, 검색 결과 0 count state RED/GREEN, focused test와 영향 범위 검증 결과 기록.
- 범위 밖: 신규 API, 검색 정책 변경, DM 방 내부 화면 수정, 기기·에뮬레이터 UI 조작.
Files:
- Modify:
app/src/main/res/layout/activity_dm_recipient_picker.xml - Modify:
app/src/main/res/layout/item_dm_recipient.xml - Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerActivity.kt - Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerViewModel.kt - Modify:
app/src/main/res/values/strings.xml,values-en/strings.xml,values-ja/strings.xml - Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerSourceTest.kt - Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerViewModelTest.kt
Interfaces:
-
Consumes: 기존 follower/search/empty/error state와
DmRecipientAdapter목록 표시. -
Produces: Figma형 search toolbar, follower/search count label, 검색 결과 0건 count 표시.
-
RED: layout/source test가
detail_toolbar미사용, 뒤로가기와 검색 input의 같은 toolbar row,tv_search_result_count, row 66dp와 profile 42dp, count string resource를 검증하도록 작성한다. -
RED 확인: focused source test를 실행해 현재 layout/Activity/string wiring 미구현으로 실패함을 확인한다.
-
RED: ViewModel test가 검색 결과 0건에서도
searchResultCount = 0을 state에 담는지 검증하도록 작성한다. -
RED 확인: focused ViewModel test를 실행해 count field 부재 또는 값 미구현으로 실패함을 확인한다.
-
GREEN: XML, Activity, ViewModel, 문자열 리소스를 최소 수정해 RED를 통과시킨다.
-
GREEN 확인: 같은 focused tests를 다시 실행해 성공을 확인한다.
-
REFACTOR: 신규 abstraction 없이 중복만 점검하고 focused test·직접 영향 회귀·build/ktlint 결과를 Progress에 기록한다.
Task 4.2 리뷰 후속 회귀 수정
Goal 실행 P4-R1: Oracle 리뷰에서 확인된 검색 empty 위치, 레이아웃 속성 직접 검증, 대화 탭 resume guard 재생성 회귀를 최소 수정한다.
- 시작 조건:
P4-T1완료 후 read-only 리뷰 결과 확인. - 완료 증거: 회귀 RED/GREEN, 관련 focused test와 Phase 4 Gate 재실행 결과 기록.
- 범위 밖: Figma 치수 자동 검증 추가, 기기·에뮬레이터 UI 조작, 화면 전체 재설계.
Files:
-
Modify:
app/src/main/res/layout/activity_dm_recipient_picker.xml -
Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragment.kt -
Modify:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerSourceTest.kt -
Modify:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/ChatMainFragmentLayoutTest.kt -
Modify:
docs/20260914_크리에이터의_리스너_DM_시작/prd.md,plan-task.md -
RED: 검색 empty가 리스트 콘텐츠 영역에서 전환되는 구조와
ChatMainFragmentview 재생성 시 resume guard 초기화를 검증하는 실패 test를 작성한다. -
RED 확인: focused tests를 실행해 현재 XML sibling 구조와 guard 초기화 부재로 실패함을 확인한다.
-
GREEN: empty/list 콘텐츠 컨테이너와
onViewCreated()guard 초기화를 최소 수정하고, 레이아웃 치수 직접 assertion을 제거한다. -
GREEN 확인: 같은 focused tests를 다시 실행해 성공을 확인한다.
-
REFACTOR: 문서 상태와 issue 해결 상태를 최신화하고 Phase 4 Gate를 재실행한다.
완료 조건
P4-T1의 체크박스와 완료 증거가 모두 충족됐다.- Figma
2372:23496기본 화면은 한 줄 검색 toolbar와팔로워섹션 및 66dp row 구조를 사용한다. - Figma
2372:23506검색 화면은 검색 결과가 0건이어도검색결과 0을 표시한다. - 기존 debounce, empty state, 확인 팝업, DM 생성 흐름이 유지된다.
검증 방법
Phase 4 Gate
Goal 실행 P4-GATE: Figma 수신자 검색 UI 정합성과 기존 수신자 기능 회귀를 최종 판정한다.
- 시작 조건:
P4-T1완료. - 완료 증거: 아래 focused/영향 범위 검증 통과와 Progress 기록.
- 범위 밖: 실패와 무관한 전체 디자인 시스템 개편.
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerViewModelTest"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.*" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" :app:assembleDebug :app:ktlintCheck
Expected: 0 exit code. Figma source contract, 검색결과 0 state, 기존 recipient flow와 DM request 회귀가 통과한다.
Phase 5. 팔로워 0명 섹션 라벨 숨김
Phase 결과: 수신자 선택 화면의 팔로워 목록이 비어 있으면 팔로워 섹션 라벨을 표시하지 않고, 검색결과 0과 팔로워 라벨 스타일이 요구사항과 일치한다.
선행조건: Phase 4 수신자 선택 화면 구현 완료, 사용자 후속 요구 DM-022, DM-023 확정.
Phase 완료 조건: P5-T1, P5-GATE 완료, 검증 기록 누적.
구현 항목
Task 5.1 팔로워 라벨 표시 조건 수정
Goal 실행 P5-T1: 팔로워 모드라도 표시할 수신자가 0명이면 팔로워 라벨을 숨기고, 검색결과 0과 팔로워 라벨의 문구 크기와 검색결과 숫자 색상을 맞춘다.
- 시작 조건: PRD
DM-022,DM-023확정. - 완료 증거: source/layout RED/GREEN, focused test와 영향 범위 검증 결과 기록.
- 범위 밖: 검색 결과 count 정책, 팔로워 empty 문구 신규 정의, 기기·에뮬레이터 UI 조작.
Files:
-
Modify:
app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerActivity.kt -
Modify:
app/src/main/res/layout/activity_dm_recipient_picker.xml -
Test:
app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/recipient/DmRecipientPickerSourceTest.kt -
Modify:
docs/20260914_크리에이터의_리스너_DM_시작/prd.md,plan-task.md -
RED: source test가
tvFollowers표시 조건이 팔로워 모드와 수신자 목록 non-empty 조건을 함께 사용하는지, layout/source test가 두 라벨 style 통일과 검색결과 숫자gray_500span을 검증하도록 작성한다. -
RED 확인: focused source test를 실행해 현재 팔로워 모드만 보는 조건, 라벨 style 불일치, 숫자 span 부재 때문에 실패함을 확인한다.
-
GREEN: Activity binding과 XML을 최소 수정해 팔로워 0명 라벨 숨김, 라벨 크기 통일, 검색결과 숫자
gray_500span을 적용한다. -
GREEN 확인: 같은 focused test를 다시 실행해 성공을 확인한다.
-
REFACTOR: 신규 state나 abstraction 없이 중복만 점검하고 focused test·직접 영향 회귀·build/ktlint 결과를 Progress에 기록한다.
완료 조건
P5-T1의 체크박스와 완료 증거가 모두 충족됐다.- 팔로워 모드에서 수신자가 0명이면
팔로워라벨이 숨겨진다. 검색결과 0과팔로워라벨의 style이 같고 검색결과 숫자 범위에만@color/gray_500span을 적용한다.- 검색 모드의
검색결과 N, empty state, 기존 DM 생성 흐름이 유지된다.
검증 방법
Phase 5 Gate
Goal 실행 P5-GATE: 팔로워 라벨 숨김과 기존 수신자 기능 회귀를 최종 판정한다.
- 시작 조건:
P5-T1완료. - 완료 증거: 아래 focused/영향 범위 검증 통과와 Progress 기록.
- 범위 밖: 기기·에뮬레이터 UI 조작, screenshot, visual QA.
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerSourceTest"
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.*" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" :app:assembleDebug :app:ktlintCheck
Expected: 0 exit code. 팔로워 라벨 source contract, 라벨 typography/숫자 color contract, 기존 recipient flow와 DM request 회귀가 통과한다.
Phase 6. 메인 대화 탭 신규 DM 아이콘 변경
Phase 결과: 크리에이터에게만 보이는 메인 대화 탭 우측 하단 버튼이 ic_new_talk_add 아이콘을 사용한다.
선행조건: 기존 btn_new_dm 노출 조건과 클릭 동작을 그대로 유지한다.
Phase 완료 조건: P6-T1 완료와 리소스 merge 검증 기록 누적.
구현 항목
Task 6.1 신규 DM 버튼 아이콘 리소스 교체
Goal 실행 P6-T1: fragment_v2_main_chat.xml의 btn_new_dm 아이콘 참조만 ic_plus_no_bg에서 ic_new_talk_add로 변경한다.
- 완료 증거: XML 참조 확인,
:app:mergeDebugResources, 관련 layout test,git diff --check통과. - 범위 밖: 버튼 크기·배경·노출 조건·클릭 동작과 다른 기능 변경.
Files:
-
Modify:
app/src/main/res/layout/fragment_v2_main_chat.xml -
Modify:
docs/20260914_크리에이터의_리스너_DM_시작/prd.md,plan-task.md -
btn_new_dm의android:src만@drawable/ic_new_talk_add로 변경한다. -
리소스 merge와 관련 layout test를 실행하고 결과를
Verification Log에 기록한다.
실행 순서와 의존성
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|---|---|---|---|---|
| 1 | P1-T1 |
없음 | 아니요 | 서버 계약 확인 |
| 2 | P1-GATE |
P1-T1 |
아니요 | 누락 요구사항 문서 보정 |
| 3 | P2-T1 |
P1-GATE |
아니요 | DTO 적용 위치 확인 |
| 4 | P2-T2 |
P1-GATE |
P2-T1 이후 일부 병행 가능 |
팔로우/검색 상태 로직 확인 |
| 5 | P2-T3 |
P2-T1, P2-T2 |
아니요 | 선행 contract 또는 상태 로직 완료 |
| 6 | P2-GATE |
Phase 2 Task 전체 | 아니요 | 실패 소유 Task의 회귀 수정 goal 생성 |
| 7 | P3-T1 |
P2-GATE |
아니요 | 복귀 refresh contract 재확인 |
| 8 | P3-GATE |
P3-T1 |
아니요 | 실패 소유 Task의 회귀 수정 goal 생성 |
| 9 | P4-T1 |
P2-GATE |
아니요 | Figma UI contract 재확인 |
| 10 | P4-GATE |
P4-T1 |
아니요 | 실패 소유 Task의 회귀 수정 goal 생성 |
| 11 | P6-T1 |
기존 btn_new_dm 구현 |
아니요 | 신규 drawable 존재와 XML 참조 확인 |
P1-T1 -> P1-GATE -> P2-T1 -> P2-T2 -> P2-T3 -> P2-GATE -> P3-T1 -> P3-GATE -> P4-T1 -> P4-GATE -> P6-T1
변경 금지 항목
- 코드 구현 전 PRD와 이 계획 문서를 먼저 갱신하지 않고 범위를 변경하지 않는다.
- 제공되지 않은 서버 오류 status/key를 추정하지 않는다.
- 레거시 코드는 v2 wrapper/adapter를 우선 사용하고, 기능 구현에 불가피한 경우 최소 수정만 허용한다.
- 신규 백엔드 endpoint나 신규 다이얼로그 구현을 만들지 않는다.
- 테스트를 삭제·skip·완화하거나 타입 오류를 우회해 Gate를 통과시키지 않는다.
- token, Authorization header, 민감 식별자를 log·Toast·fixture·문서에 기록하지 않는다.
의사결정 및 중단 규칙
- PRD와 서버 계약이 충돌하면
EXT-001을 갱신하고 구현을 중단한다. OQ-001~OQ-005결정과 충돌하는 구현 변경이 필요하면 PRD와 이 계획을 먼저 갱신한다.- 안전한 최소 기본값이 문서에 없으면 임의 구현하지 않는다.
- 같은 차단 사유가 3회 반복되고 독립 문서화도 불가능하면 goal을
blocked로 갱신한다. - 완료 증거와 Progress 기록까지 충족한 뒤에만 goal을 완료로 표시한다.
Progress
기존 기록을 삭제하거나 덮어쓰지 않고 실제 실행 결과를 차수별로 누적한다.
문서 작성 1차 실행 — 2026-09-14
- 상태: 완료
- 무엇을: PRD와 구현 계획 초안을 생성했다.
- 왜: 사용자 요청에 따라 코드 구현 전 요구사항과 계획 문서를 준비했다.
- 어떻게:
- Figma
2372:23496metadata/screenshot 확인 — 검색바, 팔로우 섹션, 사용자 리스트 구조 확인. - Figma
2372:23521metadata/screenshot 확인 — 확인 팝업 제목, 본문, 취소/보내기 구조 확인. - 기존 코드 심볼 확인 —
ChatMainFragment,DmChatApi,DmChatRepository,UserApi.searchUser,UserRepository.searchUser,FollowingCreatorRepository근거 확인. - 사용자 인터뷰 — 진입점은 크리에이터에게만 노출한다고 확정.
- Figma
- 남은 항목: 서버 계약은 이후
EXT-001에서 해결됨. - 다음 행동: 구현 요청이 들어오면
P1-T1부터 시작한다.
Open Questions 확정 1차 실행 — 2026-09-14
- 상태: 완료
- 무엇을:
OQ-001~OQ-005를 사용자 인터뷰로 확정하고 PRD/계획 문서에 반영했다. - 왜: 구현 전 검색 호출 기준, 화면 상태 전환, 선택 가능 사용자 범위, 문구, 레거시 수정 허용 범위를 고정하기 위해서다.
- 어떻게:
OQ-001— debounce500ms확정.OQ-002— 검색어 0~1자에서 팔로우 리스트 복귀 확정.OQ-003— API 검색 결과 모두 선택 가능 확정.OQ-004— Figma 한국어 문구 최종 확정.OQ-005— 레거시 수정이 필요하면 최소 수정 허용 확정.
- 남은 항목: 서버 계약은 이후
EXT-001에서 해결됨. - 다음 행동: 구현 요청이 들어오면
P1-T1에서 파일 지도와 서버 계약을 확인한다.
서버 계약 반영 1차 실행 — 2026-09-14
- 상태: 완료
- 무엇을: 서버가
recipientId와 기존creatorId를 모두 수용하는 호환 계약을 제공한 사실을 PRD/계획 문서에 반영했다. - 왜:
EXT-001차단을 해소하고 신규 리스너 DM과 기존 크리에이터 채널 DM의 request field 기준을 분리하기 위해서다. - 어떻게:
- 신규 크리에이터→리스너 DM은
recipientId만 사용하도록 계획했다. - 기존 크리에이터 채널 DM은
creatorId를 유지하도록 계획했다. P2-T1테스트 기준을 신규recipientIdserialization과 기존creatorId회귀 검증으로 갱신했다.
- 신규 크리에이터→리스너 DM은
- 남은 항목: 없음. 구현 요청이 들어오면
P1-T1에서 실제 파일 지도 확인 후 시작한다.
P1 계약·파일 지도 확인 — 2026-09-14
- 상태: 완료
- 무엇을: 구현 전 파일 지도와 재사용 패턴을 확정했다.
- 왜: Phase 2 구현이 기존 API, role, v2 modal, FAB, string resource 관례와 충돌하지 않게 하기 위해서다.
- 어떻게:
CreateDmChatRoomRequest는DmChatModels.kt, DM 생성 위임은DmChatRepository.kt에 있음을 확인했다.- 기존 크리에이터 채널 DM은
createOrGetRoom(token, creatorId)와DmChatRoomViewModel의creatorId진입 경로를 사용함을 확인했다. - 팔로우 리스트는
ExplorerApi.getFollowerList()의items.userId/profileImage/nickname응답을 사용할 수 있음을 확인했다. - 검색은 기존
UserRepository.searchUser(nickname, token)와/member/search?nickname=경로를 사용함을 확인했다. - 역할 판정은
SharedPreferenceManager.role == MemberRole.CREATOR.name, 공통 팝업은V2ModalDialog재사용으로 확정했다.
- 검증: 코드 심볼 확인과 백그라운드 탐색 결과 대조를 완료했다.
- 남은 항목:
P2-T2팔로우·검색 상태 로직 구현.
P2-T1 DM 요청 계약 적용 — 2026-09-14
- 상태: 완료
- 무엇을:
CreateDmChatRoomRequest가recipientId와creatorId를 모두 표현하고, 신규 리스너 DM 생성용 repository 경로를 추가했다. - 왜: 신규 크리에이터→리스너 DM은
recipientId, 기존 크리에이터 채널 DM은creatorId를 보내야 하기 때문이다. - 어떻게:
- RED:
CreateDmChatRoomRequest(recipientId = 22L)테스트가recipientId파라미터 부재로 컴파일 실패함을 확인했다. - GREEN: DTO를 nullable
recipientId, nullablecreatorId로 변경했다. - RED:
createOrGetRoomByRecipientId호출 테스트가 메서드 부재로 컴파일 실패함을 확인했다. - GREEN:
DmChatRepository.createOrGetRoomByRecipientId(token, recipientId)를 추가했다. - 회귀: 기존
createOrGetRoom(token, creatorId)와creatorIdonly 직렬화를 함께 검증했다.
- RED:
- 검증:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest"성공. - 남은 항목: lint/전체 회귀는 Phase 2 Gate에서 실행한다.
P2-T2 팔로우·검색 상태 로직 — 2026-09-14
- 상태: 완료
- 무엇을: 수신자 선택용
DmRecipientPickerViewModel과 UI state를 추가했다. - 왜: 메인 대화 탭의 신규 DM 시작 화면에서 최초 팔로우 목록, 0~1자 복귀, 2자 이상 500ms 검색, 검색 empty 상태를 제공하기 위해서다.
- 어떻게:
- RED: 신규 ViewModel/state 부재로 최초 팔로우 목록 로드 test 컴파일 실패를 확인했다.
- GREEN:
enter()에서 팔로우 목록 provider를 1회 호출하고FOLLOWERSstate를 발행했다. - RED:
onSearchQueryChanged와debounceScheduler부재로 검색 test 컴파일 실패를 확인했다. - GREEN: 0~1자 입력 시 pending 검색을 취소하고 팔로우 목록으로 복귀, 2자 이상은 500ms 후 마지막 검색어만 요청하도록 구현했다.
- RED/GREEN: 검색 결과가 비어 있으면
isSearchEmpty = true가 되도록 상태를 추가했다.
- 검증:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerViewModelTest"성공../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerViewModelTest"성공.- 신규 production 파일 pure LOC:
72, 신규 test 파일 pure LOC:115.
- 남은 항목:
P2-T3에서 실제 화면, repository provider, 확인 팝업, DM 방 이동 action을 연결한다.
P3-T1 DM 방 복귀 목록 갱신 — 2026-09-14
- 상태: 완료
- 무엇을:
ChatMainFragment가 최초 resume은 건너뛰고, 이후 resume에서 기존 표시 목록을 비운 뒤 현재 filter의 첫 페이지를 다시 요청하도록 연결했다. - 왜: 사용자가 확인한 뒤로가기 복귀 방식은 유지하되, 복귀 후 대화 탭 목록이 최신 방 목록으로 갱신되어야 하기 때문이다.
- 어떻게:
- RED:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest"실행 결과채팅방에서 복귀하면 현재 filter 첫 페이지로 목록을 갱신한다1개 test가onResume()복귀 갱신 wiring 부재로 실패했다. - GREEN:
ChatMainFragment에shouldRefreshChatRoomsOnResumeguard를 추가하고 subsequentonResume()에서cancelChatPullRefresh(),bindChatRooms(emptyList(), showEmpty = false),viewModel.loadFirstPage(selectedFilter)를 호출했다. - GREEN 확인:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainViewModelTest"성공. - 영향 범위 검증:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" :app:assembleDebug :app:ktlintCheck성공,BUILD SUCCESSFUL. git diff --check: 출력 없음.
- RED:
- 검증 보완:
ChatMainViewModel.loadFirstPage()는 기존 구현과 test에서cursor = null,currentItems = emptyList(), 결과 replace를 이미 보장한다.ChatMainFragment.ktpure LOC는 245로 200~250 경고 구간이다. 이번 변경은 기존 파일에 최소 lifecycle hook만 추가했고, 다음 확장 시 분리 검토가 필요하다.- LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 남은 항목: 없음.
P4-T1 수신자 선택·검색 결과 UI 정합성 수정 — 2026-09-14
- 상태: 완료
- 무엇을: 수신자 선택 화면의 상단 구조를 Figma형 한 줄 검색 toolbar로 바꾸고 검색 모드에서
검색결과 N라벨을 표시하도록 수정했다. - 왜: 기존 화면이 별도
detail_toolbar아래 검색창을 사용해 Figma2372:23496,2372:23506과 다르고, 검색 결과가 0건일 때검색결과 0이 보이지 않았기 때문이다. - 어떻게:
- RED:
DmRecipientPickerSourceTest에detail_toolbar미사용,btn_back/검색 input 동일 toolbar row,tv_search_result_count, toolbar 54dp, 검색 input 42dp, section 40dp, row 66dp, profile 42dp, 다국어 count string resource 검증을 추가했다. - RED:
DmRecipientPickerViewModelTest에 검색 성공/0건 검색 결과에서searchResultCountstate를 검증하는 test를 추가했고, count field 부재로 실패를 확인했다. - GREEN:
activity_dm_recipient_picker.xml,item_dm_recipient.xml,DmRecipientPickerActivity,DmRecipientPickerViewModel,strings.xml/values-en/values-ja를 최소 수정해 검색 toolbar와 count 표시를 연결했다. - GREEN 보완: source contract의 count string 참조와 기존 검색 성공 expected state의
searchResultCount누락을 수정했다.
- RED:
- 검증:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerViewModelTest"성공../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.*" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRepositoryTest" :app:assembleDebug :app:ktlintCheck성공,BUILD SUCCESSFUL.git diff --check출력 없음.- 변경 Kotlin 파일 pure LOC:
DmRecipientPickerActivity.kt93,DmRecipientPickerViewModel.kt86,DmRecipientPickerSourceTest.kt103,DmRecipientPickerViewModelTest.kt177.
- 검증 보완:
- 기기·에뮬레이터 조작, screenshot, visual QA,
androidTest는 사용자 명시 요청이 없어 저장소 지침대로 실행하지 않았다. - LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 기기·에뮬레이터 조작, screenshot, visual QA,
- 남은 항목: 없음.
P4-R1 리뷰 후속 회귀 수정 — 2026-09-14
- 상태: 완료
- 무엇을: read-only 리뷰에서 확인된 검색 empty 위치, 레이아웃 속성 직접 검증, 대화 탭 resume guard 재생성 회귀를 수정했다.
- 왜:
DM-010중앙 empty state와 저장소의 레이아웃 속성 직접 검증 금지,P3-T1의 첫 resume 중복 요청 방지 조건을 동시에 만족해야 하기 때문이다. - 어떻게:
- RED:
DmRecipientPickerSourceTest에recipient_content_container안에서rv_recipients와tv_search_empty가 전환되는 구조 검증을 추가했다. - RED:
ChatMainFragmentLayoutTest에onViewCreated()에서shouldRefreshChatRoomsOnResume = false가 초기화되는지 검증을 추가했고, 기존 구현에서 실패를 확인했다. - GREEN:
activity_dm_recipient_picker.xml에 콘텐츠FrameLayout을 추가하고 empty/list를 같은 영역에서 전환하도록 바꿨다. - GREEN:
DmRecipientPickerActivity가 empty 상태에서rvRecipients를 숨기도록 연결했다. - GREEN:
ChatMainFragment.onViewCreated()에서 resume guard를 초기화해 같은 Fragment 인스턴스의 view 재생성 직후 첫 resume 중복 갱신을 막았다. - REFACTOR:
DmRecipientPickerSourceTest의 toolbar/row/profile 치수 문자열 assertion을 제거하고 구조·상태 contract 검증만 남겼다.
- RED:
- 검증:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.recipient.DmRecipientPickerSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest"최초 RED 실패 1건 확인 후 GREEN 성공.- 변경 Kotlin 파일 pure LOC:
ChatMainFragment.kt246,DmRecipientPickerActivity.kt94,DmRecipientPickerSourceTest.kt115,ChatMainFragmentLayoutTest.kt230.
- 검증 보완:
ChatMainFragment.kt와ChatMainFragmentLayoutTest.kt는 200~250 경고 구간이다. 이번 변경은 guard 초기화와 회귀 assertion 최소 추가이며, 다음 확장 시 분리 검토가 필요하다.- 기기·에뮬레이터 조작, screenshot, visual QA,
androidTest는 사용자 명시 요청이 없어 저장소 지침대로 실행하지 않았다.
- 남은 항목: Phase 4 Gate 재실행 결과를 Verification Log에 누적한다.
Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|---|---|---|---|---|---|
2026-09-14 |
DEC-001 | 확정 | 이번 세션에서는 문서만 생성하고 코드 구현은 하지 않는다. | 사용자 요청 | 전체 |
2026-09-14 |
DEC-002 | 확정 | 메인 대화 탭의 + 버튼과 신규 DM 시작 화면은 크리에이터에게만 노출한다. |
사용자 인터뷰 답변 | P2-T3, prd.md |
2026-09-14 |
DEC-003 | 확정 | 검색 API는 기존 GET /member/search와 nickname 파라미터를 사용한다. |
사용자 요청과 기존 코드 확인 | P2-T2, prd.md |
2026-09-14 |
DEC-004 | 정정 | 서버 계약 확인 전에는 CreateDmChatRoomRequest.creatorId를 recipientId로 변경하는 방향으로 계획했다. |
사용자 요청 | P2-T1, EXT-001 |
2026-09-14 |
DEC-005 | 확정 | 검색 debounce 대기 시간은 500ms로 한다. | 사용자 인터뷰 답변 | P2-T2, prd.md |
2026-09-14 |
DEC-006 | 확정 | 검색어가 0~1자로 줄어들면 팔로우 리스트로 복귀한다. | 사용자 인터뷰 답변 | P2-T2, prd.md |
2026-09-14 |
DEC-007 | 확정 | /member/search API 결과는 앱에서 추가 필터 없이 모두 선택 가능하게 둔다. |
사용자 인터뷰 답변 | P2-T2, P2-T3, prd.md |
2026-09-14 |
DEC-008 | 확정 | Figma 한국어 문구를 최종 문구로 확정하고 기존 지원 언어 리소스에 번역을 추가한다. | 사용자 인터뷰 답변 | P2-T3, prd.md |
2026-09-14 |
DEC-009 | 확정 | 레거시 수정이 필요하면 기능에 필요한 최소 수정은 허용한다. | 사용자 인터뷰 답변 | 전체 |
2026-09-14 |
DEC-010 | 확정 | 서버가 recipientId와 기존 creatorId를 모두 수용하는 호환 계약을 제공했다. 신규 크리에이터→리스너 DM은 recipientId, 기존 크리에이터 채널 DM은 creatorId를 사용한다. |
서버 변경 공유 | P2-T1, prd.md |
2026-09-14 |
DEC-011 | 확정 | DM 방에서 뒤로 돌아와 대화 탭이 다시 보이면 현재 filter의 첫 페이지를 재조회한다. page 0은 cursor 기반 API의 cursor = null로 처리한다. |
사용자 확인 및 추가 요청 | P3-T1, prd.md |
2026-09-14 |
DEC-012 | 확정 | 수신자 선택 화면은 Figma 2372:23496, 검색 결과 화면은 Figma 2372:23506과 동일한 검색 toolbar와 검색결과 N 라벨을 사용한다. |
사용자 지적 및 Figma 재확인 | P4-T1, prd.md |
발견된 문제
| ID | 심각도 | 상태 | 발견 내용 | 영향 Goal | 처리 계획 |
|---|---|---|---|---|---|
| ISSUE-001 | High | 해결 | 서버가 기존 DM 생성 endpoint에서 recipientId와 기존 creatorId를 모두 수용하는 호환 계약을 제공했다. |
P2-T1, P2-T3 |
신규 리스너 DM은 recipientId, 기존 크리에이터 채널 DM은 creatorId로 구현 |
| ISSUE-002 | Medium | 해결 | 기존 검색 ViewModel에는 2글자 조건은 있으나 debounce 구현 여부는 확인되지 않았다. OQ-001에서 신규 기능 debounce는 500ms로 확정했다. |
P2-T2 |
500ms debounce 기준으로 신규 검색 로직 구현 |
| ISSUE-003 | Medium | 해결 | ChatMainFragment는 최초 진입에서만 loadFirstPage()를 호출하고, DmChatRoomActivity에서 뒤로 돌아오는 resume 시점의 대화 목록 재조회 wiring이 없다. |
P3-T1, P4-R1 |
first-resume guard 후 subsequent resume에서 목록 clear와 첫 페이지 재조회 추가, view 재생성 시 guard 초기화 보완 |
| ISSUE-004 | Medium | 해결 | 수신자 선택 화면이 Figma와 달리 별도 detail_toolbar 아래 검색창을 쓰고, 검색 모드에서 0건 count인 검색결과 0을 표시하지 않는다. |
P4-T1, P4-R1 |
Figma형 search toolbar와 검색 결과 count state/binding 추가, empty/list 콘텐츠 영역 전환 보완 |
최종 보고 형식
구현 결과: 완료한 Phase와 사용자 흐름을 한 문장으로 작성한다.
- 변경: 주요 파일과 동작을 적는다.
- 결정: 중요한 Decision Log ID와 내용을 적는다.
- 검증:
- 실행 명령 — 성공/실패와 핵심 수치를 적는다.
- 수동 검증 — 성공/실패/불가 사유를 적는다.
- 남은 항목: 외부 의존, 후속 범위 또는 없음을 적는다.
- 문서: 갱신한 PRD/API Contract/plan/review 링크를 적는다.
Verification Log
문서 생성 검증 — 2026-09-14
- 상태: 완료
- 무엇을:
prd.md와plan-task.md가 요청 범위, Figma 노드, 기존 API 근거, 사용자 인터뷰 결과를 포함하는지 점검했다. - 왜: 코드 구현 없이 문서만 생성한다는 사용자 요청을 충족하기 위해서다.
- 어떻게:
- 요구사항
DM-001DM-019와 계획 TaskP1-T1,P2-T1P2-T3연결을 확인했다. OQ-001~OQ-005가 확정됐고, 이후EXT-001서버 계약도 해결 상태로 갱신됐는지 확인했다.- Android local unit test만 계획하고
androidTest, 기기 조작, 스크린샷 QA를 제외했는지 확인했다.
- 요구사항
- 남은 항목: 실제 구현 전 Phase 1의 계약·파일 지도 확인.
Phase 2 Gate 검증 — 2026-09-14
- 상태: 완료
- 무엇을: 크리에이터의 리스너 DM 시작 흐름과 기존 DM/Chat 회귀를 최종 검증했다.
- 왜:
P2-T1~P2-T3가 문서 요구사항과 구현 완료 조건을 모두 충족하는지 확인하기 위해서다. - 어떻게:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" :app:assembleDebug :app:ktlintCheck를 실행해 성공을 확인했다.git diff --check를 실행해 whitespace 오류가 없음을 확인했다../gradlew :app:testDebugUnitTest --tests "*Dm*" --tests "*Chat*" :app:assembleDebug :app:ktlintCheck를 실행해 DM/Chat 영향 범위 회귀를 재확인했다.
- 결과:
- 최종 Gradle 명령:
BUILD SUCCESSFUL, exit 0. testDebugUnitTest,assembleDebug,ktlintCheck통과.git diff --check출력 없음.
- 최종 Gradle 명령:
- 수동/제외 검증:
- 사용자 지시에 따라
app/src/androidTest, 기기·에뮬레이터 조작, screenshot, visual QA는 실행하지 않았다. - LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 사용자 지시에 따라
- 남은 항목: 없음.
Phase 6 아이콘 변경 검증 — 2026-09-17
- 상태: 완료
- 무엇을: 메인 대화 탭 우측 하단
btn_new_dm아이콘 참조만@drawable/ic_new_talk_add로 변경했는지 확인했다. - 왜: 신규 PNG 리소스를 크리에이터 전용
+버튼 아이콘으로 사용하고, 버튼의 다른 기능은 유지해야 하기 때문이다. - 어떻게:
fragment_v2_main_chat.xml의btn_new_dmandroid:src를@drawable/ic_plus_no_bg에서@drawable/ic_new_talk_add로 변경했다../gradlew :app:mergeDebugResources --max-workers=1성공,BUILD SUCCESSFUL.git diff --check출력 없음../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --max-workers=1은ChatMainFragmentLayoutTest.kt:71의 기존 title bar source assertion에서 실패했다. 이번 변경 파일인fragment_v2_main_chat.xml의 아이콘 참조와는 무관한llTitleBarActions.addViewsource contract 실패다.
- 수동/제외 검증:
- 아이콘만 교체하는 리소스 참조 변경이므로 신규 local unit test는 작성하지 않았다.
- 사용자 지시에 따라 기기·에뮬레이터 조작, screenshot, visual QA는 실행하지 않았다.
- 남은 항목: 없음.
Phase 3 Gate 검증 — 2026-09-14
- 상태: 완료
- 무엇을: DM 방 복귀 시 대화 탭 목록 첫 페이지 갱신과 기존 채팅 회귀를 검증했다.
- 왜:
P3-T1이DM-020의 page 0 초기화, 기존 아이템 clear, 현재 filter 재조회 요구를 충족하는지 확인하기 위해서다. - 어떻게:
./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.ChatMainViewModelTest"를 실행해 성공을 확인했다../gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.*" --tests "kr.co.vividnext.sodalive.v2.chat.action.*" :app:assembleDebug :app:ktlintCheck를 실행해 성공을 확인했다.git diff --check를 실행해 whitespace 오류가 없음을 확인했다.
- 결과:
- 최종 Gradle 명령:
BUILD SUCCESSFUL, exit 0. testDebugUnitTest,assembleDebug,ktlintCheck통과.git diff --check출력 없음.
- 최종 Gradle 명령:
- 수동/제외 검증:
- 사용자 확인으로 뒤로가기 시 대화 탭으로 복귀하는 흐름은 기능적으로 맞다고 확정했다.
- 사용자 지시에 따라
app/src/androidTest, 기기·에뮬레이터 조작, screenshot, visual QA는 실행하지 않았다. - LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 남은 항목: 없음.
Phase 4 Gate 검증 — 2026-09-14
- 상태: 완료
- 무엇을: Figma 수신자 검색 UI 정합성과 기존 수신자/DM 생성 회귀를 검증했다.
- 왜:
P4-T1이DM-021,DEC-012,ISSUE-004의 검색 toolbar와검색결과 0표시 요구를 충족하는지 확인하기 위해서다. - 어떻게:
- focused test로 layout/source contract와 ViewModel count state를 확인했다.
- recipient 영향 범위와 기존
DmChatRepositoryTest,assembleDebug,ktlintCheck를 한 번에 실행했다. git diff --check로 whitespace 오류가 없음을 확인했다.
- 결과:
- 최종 Gradle 명령:
BUILD SUCCESSFUL, exit 0. testDebugUnitTest,assembleDebug,ktlintCheck통과.git diff --check출력 없음.
- 최종 Gradle 명령:
- 수동/제외 검증:
- 사용자 지시에 따라
app/src/androidTest, 기기·에뮬레이터 조작, screenshot, visual QA는 실행하지 않았다. - LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 사용자 지시에 따라
- 남은 항목: 없음.
Phase 5 Gate 검증 — 2026-09-14
- 상태: 완료
- 무엇을: 팔로워 0명 라벨 숨김,
팔로워/검색결과 0라벨 문구 크기 통일, 검색결과 숫자gray_500span 적용을 검증했다. - 왜:
P5-T1이DM-022,DM-023의 후속 UI 요구를 충족하는지 확인하기 위해서다. - 어떻게:
- RED focused test로 기존 구현이 팔로워 모드만 보고 라벨을 표시하고, 검색결과 라벨 style과 숫자 색상 span이 요구와 다름을 확인했다.
- GREEN focused test로
DmRecipientPickerSourceTest통과를 확인했다. - recipient 영향 범위와 기존
DmChatRepositoryTest,assembleDebug,ktlintCheck를 한 번에 실행했다. git diff --check로 whitespace 오류가 없음을 확인했다.
- 결과:
- 최종 Gradle 명령:
BUILD SUCCESSFUL, exit 0. testDebugUnitTest,assembleDebug,ktlintCheck통과.git diff --check출력 없음.
- 최종 Gradle 명령:
- 수동/제외 검증:
- 사용자 지시에 따라
app/src/androidTest, 기기·에뮬레이터 조작, screenshot, visual QA는 실행하지 않았다. - LSP diagnostics 도구는 제공되지 않아 Kotlin 컴파일과 ktlint로 대체했다.
- 사용자 지시에 따라
- 남은 항목: 없음.