diff --git a/docs/20260610_DM_채팅화면/plan-task.md b/docs/20260610_DM_채팅화면/plan-task.md index ae54f63f..ea7b0371 100644 --- a/docs/20260610_DM_채팅화면/plan-task.md +++ b/docs/20260610_DM_채팅화면/plan-task.md @@ -22,6 +22,11 @@ - 전송 직후 local pending 메시지를 추가한다. - 성공 시 서버 응답 메시지로 교체한다. - 실패 시 실패 상태와 재시도 버튼을 표시한다. +- Phase 3 ViewModel 전송 정책은 단일 `isSending` guard로 한 번에 하나의 전송만 허용한다. + - 이번 범위에서는 “전송 중 중복 요청 방지” 요구사항을 우선 충족한다. + - 서로 다른 메시지의 연속 병렬 전송 허용 여부는 Phase 5 Activity/input UX 연결 시 필요하면 재검토한다. +- Phase 3 ViewModel의 pagination/reconnect 동기화 실패는 화면 종료나 Error 화면 전환 없이 기존 메시지 상태를 유지하고 내부 loading 상태만 복구한다. + - 사용자 노출 toast/retry UI는 Phase 5 Activity 연결 시 필요하면 별도 처리한다. ## 3. 파일 구조 - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomActivity.kt` @@ -141,7 +146,7 @@ ### Phase 3: ViewModel 상태와 단위 테스트 추가 -- [ ] **Task 3.1: ViewModel 초기 진입 흐름 구현** +- [x] **Task 3.1: ViewModel 초기 진입 흐름 구현** - Files: - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt` - Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt` @@ -155,7 +160,7 @@ - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"` - Expected: roomId 진입, creatorId 진입, invalid 진입, OpenRoom 정렬 반영 테스트가 PASS. -- [ ] **Task 3.2: pagination 상태 구현** +- [x] **Task 3.2: pagination 상태 구현** - Files: - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt` - Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatPaginationStateTest.kt` @@ -167,7 +172,7 @@ - Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatPaginationStateTest"` - Expected: 요청 조건, cursor 전달, prepend, 스크롤 보정용 추가 개수 반환 테스트가 PASS. -- [ ] **Task 3.3: 전송/재시도/SSE 반영 상태 구현** +- [x] **Task 3.3: 전송/재시도/SSE 반영 상태 구현** - Files: - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModel.kt` - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/chat/dm/DmChatRoomViewModelTest.kt` @@ -331,3 +336,9 @@ - 2026-06-10: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventParserTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행해 모두 PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning이 출력됐지만 실패는 없었다. - 2026-06-10: Phase 2 리뷰에서 SSE stream read 중 `IOException`이 failure callback으로 전달되지 않는 문제가 발견되어, 취소되지 않은 call의 read 실패만 `listener.onFailure(e)`로 전달하도록 `DmChatEventClient`를 수정했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck` PASS를 재확인했다. - 2026-06-10: SSE 클라이언트 보강으로 비정상 HTTP 응답 failure callback 전달, trailing blank line 없는 마지막 frame dispatch, `data:` 뒤 단일 공백 제거 정책, listener `@Volatile` 가시성 보완을 반영했다. 보강 전 `DmChatEventClientTest`에서 HTTP 500 failure 누락과 마지막 frame 누락 RED를 확인했고, 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatEventClientTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS를 확인했다. +- 2026-06-10: Phase 3 구현 전 `DmChatRoomViewModelTest`, `DmChatPaginationStateTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest"`를 실행해 `DmChatRoomViewModel` 및 `DmChatRoomUiState` 심볼 부재로 실패하는 RED 상태를 확인했다. +- 2026-06-10: Phase 3 범위로 `DmChatRoomViewModel.kt`, `DmChatUiModels.kt`, `DmChatRoomViewModelTest.kt`, `DmChatPaginationStateTest.kt`를 추가/수정했다. roomId/creatorId 진입, invalid 종료 이벤트, OpenRoom 정렬, pagination guard/prepend/중복 제거, blank 전송 무시, pending 추가, 성공 교체, 실패 상태, retry 성공, SSE 중복 제거, 재연결 후 최신 메시지 동기화를 ViewModel 상태로 구현했다. +- 2026-06-10: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatPaginationStateTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"` PASS를 확인했다. +- 2026-06-10: Phase 3 최종 확인으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS를 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` deprecation warning이 출력됐지만 실패는 없었다. +- 2026-06-10: Phase 3 리뷰에서 전송 성공 전 SSE echo가 먼저 도착하면 같은 `messageId`가 중복될 수 있는 문제가 발견되어 `DmChatRoomViewModelTest`에 재현 테스트를 추가했다. 보강 전 해당 테스트는 중복 메시지 assertion으로 RED를 확인했고, 전송 성공 local 교체 후 동일 `messageId`를 한 개로 정리하도록 `DmChatRoomViewModel`을 수정했다. 이후 해당 단일 테스트와 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatPaginationStateTest"` PASS를 확인했다. +- 2026-06-10: Phase 3 추가 리뷰 보강으로 retry 중 SSE echo가 먼저 도착해도 성공 교체 후 동일 `messageId`가 한 개만 남는 테스트, 과거 메시지 요청 실패 시 `isLoadingOlder=false`로 복구하고 기존 목록을 유지하는 테스트, 재연결 최신 메시지 동기화 실패 시 기존 메시지를 유지하는 테스트를 추가했다. `isSending` 단일 전송 guard와 pagination/reconnect 실패의 silent 유지 정책은 Phase 3 ViewModel 범위의 의도된 제약으로 문서화했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomViewModelTest" --tests "kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatPaginationStateTest"` PASS를 확인했다.