Repository navigation
fix(mindrecord): 일기 기록 날짜를 표시 전용으로 바꾼다 (#1008) - #1121
Conversation
날짜를 고를 수 있지만 서버에 전달되지 않아, 사용자는 고른 날짜로 남았다고 믿게 된다. 서버는 생성·수정 어느 쪽에서도 date 를 받지 않는다 — 실측 2026-08-25: POST·PATCH 에 date 를 실어도 저장분은 오늘이고, Swagger 요청 스키마에도 필드가 없다. BE 가 수용할 때까지 피커를 닫는다. 상태(date)는 이어쓰기 프리필이 원래 날짜를 채워 주는 자리라 남긴다. ViewModel 의 변경 창구를 없애 UI 만 되살아나는 일도 막는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1hyok
left a comment
There was a problem hiding this comment.
변경 요청
판단은 지지한다. 서버가 date 를 받지 않는다는 것은 실측으로 확정됐고, 「고를 수 있지만 반영되지 않는」 상태를 그대로 두는 것보다 고르지 못하게 하는 쪽이 맞다. 되돌릴 조건(BE 수용)도 명시돼 있다.
막는 것은 회귀 가드 하나와 남은 자국 둘이다. 셋 다 몇 줄이면 끝난다.
1. 미사용 import 가 남았다 (DiaryWriteScreen.kt:20)
import androidx.compose.material3.IconButton // ← 이 PR 이 유일한 사용처를 지웠다실측 — 이 브랜치의 DiaryWriteScreen.kt 에서 IconButton 은 import 줄에만 등장한다(사용 0건). ktlint 는 통과했지만 미사용인 것은 사실이다. painterResource · core.ui.R 은 수신자 행·감정 아이콘이 계속 쓰므로 그대로 두면 된다.
2. 이 PR 의 논리와 정면으로 어긋나는 주석이 남았다 (DiaryWriteViewModel.kt:203-205)
loadDraft 안에 이 문장이 그대로 있다:
// 서버가 날짜를 주지 않은 임시저장이면 날짜 선택기의 현재 값을
// 유지한다 — 표시용으로 오늘을 지어내는 것과 달리, 여기서는
// 사용자가 등록 전에 직접 고르는 값이다.
date = draft.toUi()?.date ?: it.date,「사용자가 등록 전에 직접 고르는 값」이 이 PR 로 거짓이 된다. 「날짜 선택기」도 이제 없다. PR 본문이 onDateSelected 를 지운 이유로 «창구가 남아 있으면 다음 사람이 그 함수를 보고 UI 를 되살린다» 를 들었는데, 같은 이유가 이 주석에 그대로 적용된다 — 오히려 이쪽이 «되살려도 된다» 고 읽힌다. 표시 전용이라는 사실로 바꿔 주면 된다.
3. 회귀 가드가 엉뚱한 것을 잡는다
인라인 참조. 요약하면 "update" 라는 낱말이 "date" 를 부분 문자열로 품고 있어서, 날짜와 아무 상관 없는 updateXxx 공개 함수가 하나만 생겨도 이 테스트가 빨개진다.
시안 대조 — 의도적 이탈이다 (기록용, 블로커 아님)
Figma 2671:17922 을 받아 대조했다. 시안의 날짜 행에는 chevron(∨)이 그려져 있다 — 고를 수 있는 UI 다. 이 PR 은 그것을 걷어내므로 시안 이탈이 맞다.
그래도 막지 않는 이유: 디자인 미답변 질의는 FE 재량이라는 위임이 있고, 이 결정의 근거가 「시안에 없다」가 아니라 「서버가 못 받는다」는 실측이기 때문이다. 다만 시안과 다른 화면이 나가는 것이므로 디자이너에게 한 줄 알려 두는 편이 좋다 — BE 가 date 를 수용하면 시안대로 돌아간다는 것까지 함께.
그 밖에 확인한 것 (문제 없음)
BottomSheetCalendar는 죽지 않는다 — settingDatePickerBottomSheet, timeletterTimeLetterWriteScreen:201이 계속 쓴다.core_ui_arrowdown도DailyQuestionBanner·WeeklyReportReviewCard·ReceiverRegisterScreen에서 살아 있다.DiaryWriteUiState.date를 남긴 판단은 맞다 —loadDraft가 실제로draft.toUi()?.date로 채운다(위 2번 인용 줄).DiaryUpdatePayload.date를 손대지 않은 것도 맞다. #1007 소관이라 겹치지 않는다.- androidTest 게이트 보유.
머지 전 주의 — #1120 과 텍스트 충돌한다
docs/qa/assumptions.md 끝에 두 PR 이 각자 새 절을 붙여 git merge-tree 에서 충돌한다(코드는 자동 병합됨). 나중에 들어가는 쪽이 해소해야 하고, 충돌 상태로 두면 CI 가 아예 안 돈다.
| val setters = | ||
| DiaryWriteViewModel::class.java.methods | ||
| .map { it.name } | ||
| .filter { it.contains("Date", ignoreCase = true) } |
There was a problem hiding this comment.
"update" 안에 "date" 가 들어 있다 — 이 필터는 날짜와 무관한 함수를 잡는다.
$ echo "updateTitle" | grep -i date → 매치
$ echo "updateDraft" | grep -i date → 매치
지금 DiaryWriteViewModel 에 update… 로 시작하는 공개 함수가 없어서 통과하는 것뿐이다. 누군가 updateTitle 같은 함수를 하나 추가하는 순간 이 테스트가 빨개지고, 실패 메시지는 「날짜 변경 창구가 남아 있다」라고 말한다 — 날짜를 건드린 적 없는 사람이 이 메시지를 받는다. 원인을 찾기까지 시간이 걸리고, 최악의 경우 가드가 통째로 지워진다.
이름 매칭 대신 지키려는 것을 직접 고정하는 편이 낫다. 예를 들어 date 를 인자로 받는 공개 함수가 없는지:
val dateSetters =
DiaryWriteViewModel::class.java.methods
.filter { m -> m.parameterTypes.any { it == LocalDate::class.java } }
assertTrue("날짜를 받는 공개 창구가 남아 있다: ${dateSetters.map { it.name }}", dateSetters.isEmpty())이러면 이름과 무관하게 «날짜를 바꿀 수 있는 표면» 만 잡고, update… 오탐도 사라진다.
|
이 PR 은
|
- DiaryWriteScreen 의 IconButton import — 이 PR 이 유일한 사용처를 지웠다. - loadDraft 의 «사용자가 등록 전에 직접 고르는 값» 주석이 이 변경으로 거짓이 됐다. 주석이 남으면 «되살려도 된다» 로 읽힌다. - 회귀 가드가 이름으로 걸러 "update" 가 "date" 를 품는 오탐을 냈다. LocalDate 를 받는 공개 함수로 좁혔다 — 날짜 창구가 생기면 실패하고, 무관한 updateXxx 에는 반응하지 않는 것을 양쪽으로 확인했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
지적 3건 모두 반영했습니다. 1. 미사용 2. 어긋난 주석 — 「사용자가 등록 전에 직접 고르는 값」이 이 PR 로 거짓이 되는 게 맞습니다. 사실로 바꿨습니다. // 표시 전용 값이다 — 서버가 날짜를 주면 그걸 보여 주고, 안 주면 화면에 이미 떠 있던
// 값을 유지한다. 고르는 수단은 없다 (#1008).
date = draft.toUi()?.date ?: it.date,3. 가드가 엉뚱한 것을 잡는다 — 양쪽으로 확인했습니다. 시안 이탈 기록 — chevron 이 시안에 있다는 확인 감사합니다. 디자이너 쪽에 «BE 가
|
assumptions.md 인접 추가라 양쪽을 모두 살렸다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
assumptions.md 인접 추가라 양쪽을 모두 살렸다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1hyok
left a comment
There was a problem hiding this comment.
재리뷰 — 승인. 지적 3건 모두 닫혔다
d3b48443 하나로 셋 다 정리됐다.
| 앞선 지적 | 결과 |
|---|---|
1. 미사용 IconButton import |
제거됨 |
2. loadDraft 의 어긋난 주석 |
교체됨 — 「표시 전용 값이다 … 고르는 수단은 없다」로 사실과 맞다 |
3. 회귀 가드의 "update" 오탐 |
타입 기반으로 좁혀 오탐이 사라졌다 |
새 blocking 은 없다. 수정 커밋이 건드린 세 파일 밖으로 새어 나간 것도 없고(#1061 에서 겪었던 「고친 태스크 옆으로 새는」 유형), CI 도 그대로 초록이다.
남은 것 하나는 막지 않는다 — [normal] 회귀 가드에 아직 손볼 데가 있다 → DiaryWriteDateDisplayTest.kt:29 인라인 코멘트. 그중 하나는 내가 제안한 방식이 남긴 구멍이라 함께 적었다.
검증
| 항목 | 결과 |
|---|---|
CI (head 2127c9b8) |
전 항목 pass |
git merge-tree origin/develop |
clean |
#1120 과의 docs/qa/assumptions.md 충돌 |
해소됨 — #1120 이 08-25 13:13 에 머지됐고 이 브랜치가 그 뒤 develop 을 병합했다 |
| 교차 확인 — #1152 와 상호 병합 | clean (두 PR 이 DiaryWriteScreen·DiaryWriteViewModel·DiaryWriteUiState 를 함께 건드린다) |
실기동 QA — 이번 증분은 미수행. 증분이 import 한 줄·주석 한 덩이·테스트 한 곳이라 화면 거동이 바뀌지 않는다. 앞 회차에서 확인한 「날짜 행을 눌러도 피커가 열리지 않는다」는 그대로 유효하다.
시안 대조 — 앞 회차 결론 유지. Figma 2671:17922 의 날짜 행에는 chevron 이 있어 이 PR 은 의도적 시안 이탈이다. 근거가 「시안에 없다」가 아니라 「서버가 못 받는다」는 실측이므로 막지 않는다 — 다만 디자이너에게 한 줄 알려 두는 편이 좋다는 앞 회차 권고는 그대로다. BE 가 date 를 수용하면 시안대로 되돌아간다는 것까지 함께.
| DiaryWriteViewModel::class.java.methods | ||
| .filter { | ||
| java.lang.reflect.Modifier | ||
| .isPublic(it.modifiers) |
There was a problem hiding this comment.
[normal] 둘 다 막는 것은 아니다. 다음에 이 파일을 열 때 같이 정리하면 된다.
1. 이 필터는 항상 참이다. Class.getMethods() 는 정의상 public 멤버만 돌려준다(상속분 포함). 그래서 isPublic 은 걸러 내는 것이 없고, ktlint 가 이 호출 때문에 줄을 셋으로 쪼갰다. 지우면 한 줄로 돌아온다.
2. 내가 제안한 타입 기반에는 구멍이 있다 — 짚어 두는 게 맞겠다. LocalDate 를 받는 함수만 보므로, 날짜 창구가 onDateSelected(millis: Long) 이나 onDateSelected(iso: String) 으로 되살아나면 이 가드는 침묵한다. 가상의 이야기만은 아니다 — 피커를 되살리는 사람은 BottomSheetCalendar 의 콜백 시그니처를 그대로 받아 적을 가능성이 높고, Compose 의 날짜 피커 계열은 epoch millis 를 넘기는 쪽이 흔하다.
이름과 타입을 둘 다 보면 오탐 없이 촘촘해진다. 이름 쪽은 앞서 문제가 된 update… 접두만 빼면 되고, 그 예외를 주석이 아니라 코드로 적어 두면 다음 사람이 왜 빠졌는지 알 수 있다.
val dateSetters =
DiaryWriteViewModel::class.java.methods
.filter { method ->
method.parameterTypes.any { it == LocalDate::class.java } ||
// "update" 가 "date" 를 부분 문자열로 품는다 — 그 접두만 빼고 이름으로도 본다.
(method.name.contains("date", ignoreCase = true) && !method.name.startsWith("update"))
}.map { it.name }updateDate 같은 이름이 생기면 이 예외에 가려지지만, 그때는 LocalDate 인자를 받든 다른 타입을 받든 첫 조건이나 리뷰가 잡는다. 지금처럼 한 조건만 두는 것보다는 넓다.
#1008 의 선택지 중 (b) 표시 전용을 택한 결과다. (a) BE 가
date를 수용하게 되면 피커를 되돌린다.서버가 받지 않는다 — 오늘 다시 실측했다
datedatePOST /api/v1/diary2026-08-012026-08-25(diaryId 41545)PATCH /api/v1/diary/415452026-08-012026-08-25Swagger 요청 스키마도 그대로다 — 양쪽 모두
title · content · isDraft · todayMood · receiverIds뿐이고date가 없다.왜 «그냥 두기» 가 안 되나
고를 수 있게 두면 사용자는 고른 날짜로 기록이 남았다고 믿는다. 저장 결과는 항상 오늘이라 목록·상세에서 다른 날짜로 보인다. 최근 리뷰에서 반복해 잡아 온 「그럴듯한 거짓」과 같은 모양이고, 이쪽은 사용자가 직접 조작한 값이라 더 세게 배신한다.
무엇을 바꿨나
clickable과 chevron 버튼을 걷어 표시 전용으로 둔다.BottomSheetCalendar호출도 함께 제거.DiaryWriteViewModel.onDateSelected삭제 — 창구가 남아 있으면 다음 사람이 그 함수를 보고 UI 를 되살린다.DiaryWriteUiState.date는 남긴다. 이어쓰기 프리필이 원래 날짜를 채워 주는 자리다.회귀 가드
DiaryWriteDateDisplayTest— ViewModel 에 날짜를 바꾸는 공개 창구가 없는지 리플렉션으로 확인한다. 피커를 되살리려면 이 테스트를 함께 고쳐야 하므로, BE 수용 없이 UI 만 되돌아오지 않는다.실기동 확인
에뮬레이터에서 일기 작성 진입 → 날짜 행 탭 → 피커가 열리지 않는다. 날짜 텍스트(
2026년 8월 25일)는 그대로 보이고, chevron 이 빠진 만큼만 행 높이가 줄었다(제목 필드 y 439 → 이전 대비 위로 당겨짐).검증
testDebugUnitTest:app:assembleDebug·:app:compileDebugAndroidTestKotlinbaseline 재생성 불필요 —
feature:mindrecord:presentation은 screenshot 검증 대상 5개 모듈에 없다.판단 근거는
docs/qa/assumptions.md에도 남겼다 — 실측은 사실이고, 추측인 부분은 «어느 쪽으로 맞출지» 뿐이라는 점을 그대로 적었다.