From 06cc3f5a6d4292d4e0b7e684e9ad0bbb3ccef976 Mon Sep 17 00:00:00 2001 From: Sadturtleman Date: Tue, 25 Aug 2026 15:23:11 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(mindrecord):=20=EC=9D=BC=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EB=82=A0=EC=A7=9C=EB=A5=BC=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=84=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EB=B0=94?= =?UTF-8?q?=EA=BE=BC=EB=8B=A4=20(#1008)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 날짜를 고를 수 있지만 서버에 전달되지 않아, 사용자는 고른 날짜로 남았다고 믿게 된다. 서버는 생성·수정 어느 쪽에서도 date 를 받지 않는다 — 실측 2026-08-25: POST·PATCH 에 date 를 실어도 저장분은 오늘이고, Swagger 요청 스키마에도 필드가 없다. BE 가 수용할 때까지 피커를 닫는다. 상태(date)는 이어쓰기 프리필이 원래 날짜를 채워 주는 자리라 남긴다. ViewModel 의 변경 창구를 없애 UI 만 되살아나는 일도 막는다. Co-Authored-By: Claude Opus 5 (1M context) --- docs/qa/assumptions.md | 12 +++++++ .../screen/sender/DiaryWriteScreen.kt | 31 +++++-------------- .../viewmodel/DiaryWriteUiState.kt | 5 +++ .../viewmodel/DiaryWriteViewModel.kt | 4 --- .../screen/DiaryWriteDateDisplayTest.kt | 29 +++++++++++++++++ 5 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt diff --git a/docs/qa/assumptions.md b/docs/qa/assumptions.md index c0e0367ea7..d5cd9ef28c 100644 --- a/docs/qa/assumptions.md +++ b/docs/qa/assumptions.md @@ -258,3 +258,15 @@ Compose 테스트로 고정했다. **분리한 것** — 일기의 기록 날짜는 생성·수정 어느 쪽으로도 서버에 보낼 수 없다(양쪽 스키마에 `date` 없음, 보내도 무시됨을 실측). 날짜 선택이 표시 전용인 문제는 BE 계약 확장이나 기획 결정이 필요해 #1008 로 분리했다. + +## #1008 — 일기 기록 날짜를 표시 전용으로 (판단 근거) + +서버가 `date` 를 받지 않는다는 것은 추측이 아니라 실측이다 (2026-08-25). + +- `POST /api/v1/diary` 에 `date: "2026-08-01"` → 저장분 `date: "2026-08-25"` (diaryId 41545) +- `PATCH /api/v1/diary/41545` 에 같은 값 → 저장분 그대로 `2026-08-25` +- Swagger 스키마의 요청 필드: `title · content · isDraft · todayMood · receiverIds` (양쪽 모두 `date` 없음) + +**추측인 부분은 «어느 쪽으로 맞출지» 다.** 이슈가 든 두 선택지 중 (b) 표시 전용을 택했다 — +(a) BE 확장은 우리 손 밖이고, 그때까지 «고를 수 있지만 반영되지 않는» 상태를 두면 사용자가 +고른 날짜로 저장됐다고 믿는다. BE 가 `date` 를 수용하면 피커를 되돌린다. diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt index cba719c78b..52d2efa522 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt @@ -46,7 +46,6 @@ import com.afternote.core.ui.theme.AfternoteDesign import com.afternote.core.ui.theme.AfternoteTheme import com.afternote.core.ui.topbar.DetailTopBar import com.afternote.feature.mindrecord.domain.model.TodayMood -import com.afternote.feature.mindrecord.presentation.component.BottomSheetCalendar import com.afternote.feature.mindrecord.presentation.component.ReceiverSelectBottomSheet import com.afternote.feature.mindrecord.presentation.component.WriteTextField import com.afternote.feature.mindrecord.presentation.viewmodel.DiaryWriteViewModel @@ -65,7 +64,6 @@ fun DiaryWriteScreen( viewModel: DiaryWriteViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() - var showPicker by remember { mutableStateOf(false) } var showReceiverSheet by remember { mutableStateOf(false) } val currentOnSubmitSuccess by rememberUpdatedState(onSubmitSuccess) @@ -169,11 +167,15 @@ fun DiaryWriteScreen( } // Figma 2671:17922 — 날짜 선택 행 ("yyyy년 M월 d일" + 아래 화살표) + // **표시 전용이다.** 서버는 생성·수정 어느 쪽에서도 date 를 받지 않고 기록 날짜를 + // 요청 시각으로 정한다 (실측 2026-08-25: POST·PATCH 에 date 를 실어도 저장분은 + // 오늘, Swagger 스키마에도 필드가 없다). 고를 수 있게 두면 사용자는 고른 날짜로 + // 남았다고 믿는데 실제로는 오늘로 저장된다 — 고르지 못하게 해 거짓을 없앤다 (#1008). + // + // BE 가 date 를 수용하면 이 자리에 피커를 되돌린다. 이어쓰기 프리필이 채워 주는 + // 원래 날짜를 보여 주는 역할은 그대로다. Row( - modifier = - Modifier - .fillMaxWidth() - .clickable { showPicker = !showPicker }, + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -182,12 +184,6 @@ fun DiaryWriteScreen( style = AfternoteDesign.typography.captionLargeR, color = AfternoteDesign.colors.gray9, ) - IconButton(onClick = { showPicker = !showPicker }) { - Icon( - painter = painterResource(R.drawable.core_ui_arrowdown), - contentDescription = null, - ) - } } HorizontalDivider() @@ -308,17 +304,6 @@ fun DiaryWriteScreen( } } - if (showPicker) { - BottomSheetCalendar( - initialDate = uiState.date, - onDismiss = { showPicker = false }, - onDateSelect = { date -> - viewModel.onDateSelected(date) - showPicker = false - }, - ) - } - if (showReceiverSheet) { ReceiverSelectBottomSheet( receivers = uiState.receivers, diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteUiState.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteUiState.kt index bcb1082cfa..096fbc9d59 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteUiState.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteUiState.kt @@ -9,6 +9,11 @@ data class DiaryWriteUiState( val title: String = "", val content: String = "", val mood: TodayMood? = null, + /** + * 화면에 표시할 기록 날짜. **표시 전용이다** — 서버는 생성·수정 어느 쪽에서도 이 값을 + * 받지 않고 기록 날짜를 요청 시각으로 정한다 (#1008). 이어쓰기 프리필이 원래 날짜를 + * 채워 주는 자리라 상태 자체는 남긴다. + */ val date: LocalDate = LocalDate.now(), val imageUrl: String? = null, /** `GET /users/receivers` 로 불러온 내 수신인 목록. 로드 실패 시 빈 목록 (작성은 가능). */ diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt index acd17dcf77..cbe411835b 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt @@ -77,10 +77,6 @@ class DiaryWriteViewModel _uiState.update { it.copy(mood = mood) } } - fun onDateSelected(date: LocalDate) { - _uiState.update { it.copy(date = date) } - } - fun onReceiverToggled(receiverId: Long) { _uiState.update { val selected = it.selectedReceiverIds diff --git a/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt b/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt new file mode 100644 index 0000000000..ff7c0efe5e --- /dev/null +++ b/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt @@ -0,0 +1,29 @@ +package com.afternote.feature.mindrecord.presentation.screen + +import com.afternote.feature.mindrecord.presentation.viewmodel.DiaryWriteViewModel +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * 일기 기록 날짜가 «고를 수 있지만 반영되지 않는» 상태가 아닌지 (#1008). + * + * 서버는 생성·수정 어느 쪽에서도 `date` 를 받지 않고 기록 날짜를 요청 시각으로 정한다 + * (실측 2026-08-25: `POST`·`PATCH` 에 `date: "2026-08-01"` 을 실어 보내도 저장분은 + * `2026-08-25`, Swagger 스키마에도 필드가 없다). 그 상태에서 날짜 피커를 열어 두면 + * 사용자는 고른 날짜로 남았다고 믿는다. + * + * 화면에서 피커를 걷어냈으므로 **ViewModel 에 날짜를 바꾸는 창구가 남아 있으면 안 된다** — + * 남겨 두면 다음 사람이 그 함수를 보고 UI 를 되살린다. BE 가 `date` 를 수용하면 그때 함께 + * 되돌린다. + */ +class DiaryWriteDateDisplayTest { + @Test + fun `날짜를 바꾸는 공개 창구가 없다`() { + val setters = + DiaryWriteViewModel::class.java.methods + .map { it.name } + .filter { it.contains("Date", ignoreCase = true) } + + assertTrue("날짜 변경 창구가 남아 있다: $setters", setters.isEmpty()) + } +} From d3b4844348f61427ad08e5a313ed05adb8dba8f7 Mon Sep 17 00:00:00 2001 From: Sadturtleman Date: Tue, 25 Aug 2026 22:19:33 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(mindrecord):=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=84=EC=9A=A9=20=EC=A0=84=ED=99=98=EC=9D=98=20=EB=82=A8?= =?UTF-8?q?=EC=9D=80=20=EC=9E=90=EA=B5=AD=EC=9D=84=20=EC=A0=95=EB=A6=AC?= =?UTF-8?q?=ED=95=9C=EB=8B=A4=20(#1008=20=EB=A6=AC=EB=B7=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DiaryWriteScreen 의 IconButton import — 이 PR 이 유일한 사용처를 지웠다. - loadDraft 의 «사용자가 등록 전에 직접 고르는 값» 주석이 이 변경으로 거짓이 됐다. 주석이 남으면 «되살려도 된다» 로 읽힌다. - 회귀 가드가 이름으로 걸러 "update" 가 "date" 를 품는 오탐을 냈다. LocalDate 를 받는 공개 함수로 좁혔다 — 날짜 창구가 생기면 실패하고, 무관한 updateXxx 에는 반응하지 않는 것을 양쪽으로 확인했다. Co-Authored-By: Claude Opus 5 (1M context) --- .../presentation/screen/sender/DiaryWriteScreen.kt | 1 - .../presentation/viewmodel/DiaryWriteViewModel.kt | 5 ++--- .../presentation/screen/DiaryWriteDateDisplayTest.kt | 12 +++++++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt index 62c8463dc1..d66b0ca99a 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DiaryWriteScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextField diff --git a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt index cbe411835b..7871cc294c 100644 --- a/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt +++ b/feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/viewmodel/DiaryWriteViewModel.kt @@ -196,9 +196,8 @@ class DiaryWriteViewModel title = draft.title, content = draft.content, mood = draft.todayMood, - // 서버가 날짜를 주지 않은 임시저장이면 날짜 선택기의 현재 값을 - // 유지한다 — 표시용으로 오늘을 지어내는 것과 달리, 여기서는 - // 사용자가 등록 전에 직접 고르는 값이다. + // 표시 전용 값이다 — 서버가 날짜를 주면 그걸 보여 주고, 안 주면 + // 화면에 이미 떠 있던 값을 유지한다. 고르는 수단은 없다 (#1008). date = draft.toUi()?.date ?: it.date, imageUrl = draft.imageUrl, isDraftLoading = false, diff --git a/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt b/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt index ff7c0efe5e..0d1528bf16 100644 --- a/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt +++ b/feature/mindrecord/presentation/src/test/kotlin/com/afternote/feature/mindrecord/presentation/screen/DiaryWriteDateDisplayTest.kt @@ -3,6 +3,7 @@ package com.afternote.feature.mindrecord.presentation.screen import com.afternote.feature.mindrecord.presentation.viewmodel.DiaryWriteViewModel import org.junit.Assert.assertTrue import org.junit.Test +import java.time.LocalDate /** * 일기 기록 날짜가 «고를 수 있지만 반영되지 않는» 상태가 아닌지 (#1008). @@ -19,11 +20,16 @@ import org.junit.Test class DiaryWriteDateDisplayTest { @Test fun `날짜를 바꾸는 공개 창구가 없다`() { - val setters = + // "update" 가 "date" 를 부분 문자열로 품는다 — 날짜와 무관한 updateXxx 가 하나만 + // 생겨도 빨개지므로, 이름 규칙이 아니라 **LocalDate 를 받는 공개 함수**로 좁힌다. + val dateSetters = DiaryWriteViewModel::class.java.methods + .filter { + java.lang.reflect.Modifier + .isPublic(it.modifiers) + }.filter { it.parameterTypes.any { type -> type == LocalDate::class.java } } .map { it.name } - .filter { it.contains("Date", ignoreCase = true) } - assertTrue("날짜 변경 창구가 남아 있다: $setters", setters.isEmpty()) + assertTrue("날짜 변경 창구가 남아 있다: $dateSetters", dateSetters.isEmpty()) } }