Skip to content

fix(mindrecord): 일기 기록일 선택을 서버 계약대로 되살린다 (#1008) - #1899

Merged
Sadturtleman merged 2 commits into
developfrom
fix/1008-restore-diary-date-picker
Sep 6, 2026
Merged

Sadturtleman merged 2 commits into
developfrom
fix/1008-restore-diary-date-picker

Conversation

@Sadturtleman

Copy link
Copy Markdown
Collaborator

📌𝘐𝘴𝘴𝘶𝘦𝘴

📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯

전제가 바뀌었습니다 — 서버가 date 를 받습니다

이슈는 「(a) BE 에 date 수용 요청 / (b) 날짜 선택 UI 를 읽기 전용으로」 중 하나를 정하라고 남아 있었고, FE 는 그동안 (b) 를 임시로 적용해 두고 있었습니다(#1121). 그런데 (a) 가 이미 나왔습니다.

Afternote-BE#244 는 2026-08-29 에 «완료» 로 닫혔고 구현은 PR #262 입니다. BE 실코드로 확인했습니다.

항목 현재 서버
DiaryCreateRequest.date 「기록일 (yyyy-MM-dd). 미전송 시 오늘(Asia/Seoul). 미래 날짜는 거부(code 2101). 과거는 제한 없음」
DiaryUpdateRequest.date 「기록일 (yyyy-MM-dd). 생략 시 유지. 미래 날짜는 거부(code 2101)」
저장 Diary.entry_date 컬럼 신설 + (user_id, entry_date) 인덱스
집계 월 조회·주간 리포트 week[] 가 entry_date 기준

우리가 실측한 2026-08-25 로부터 나흘 뒤였고, FE 쪽에서 다시 확인한 사람이 없어 이슈가 「BE 판단 대기」인 채로 남아 있었습니다. 지금 FE 는 서버가 받아 주는 기능을 안 쓰고 버리는 상태입니다.

그래서 시안대로 되돌립니다

Figma 2671:17922 의 날짜 행에는 chevron(∨)이 있습니다 — 고를 수 있는 UI 가 원래 시안이고, #1121 이 그것을 뺀 것은 「서버가 못 받는다」는 근거의 의도적 이탈이었습니다. 그 근거가 사라졌습니다.

  • 날짜 행에 chevron 과 클릭을 되돌리고, 탭하면 core:ui 의 BottomSheetCalendar 를 엽니다.
  • 도메인·DTO·매퍼에 date 를 싣습니다. 생성은 non-null, 수정은 nullable — 서버가 「생성은 미전송 시 오늘, 수정은 생략 시 유지」로 가르는 그대로 타입에 드러냅니다.

수정 경로에서 날짜를 조용히 옮기지 않습니다

여기가 이 PR 에서 제일 조심한 자리입니다.

DiaryWriteUiState.date 의 기본값은 오늘입니다. 수정 진입에서 프리필이 실패하면 화면에는 오늘이 떠 있는데, 그걸 그대로 PATCH 에 실으면 기존 기록일이 조용히 오늘로 옮겨집니다. 그리고 이 경로는 막혀 있지도 않습니다 — DiaryWriteUiState.isReady 는 isEditingDraft && draftLoadError != null 일 때만 저장을 막아서, 발행분 수정은 프리필이 실패해도 저장이 열립니다.

그래서 isDateChosen 을 두고 화면 값이 서버에서 왔거나 사용자가 직접 고른 것일 때만 싣습니다. 아니면 키를 생략해 서버가 기존 값을 유지하게 합니다 — 바로 옆 receiverIds 가 「빈 목록을 보내면 기존 지정이 지워진다」로 같은 함정을 이미 겪은 자리입니다(#955).

미래 날짜는 고르는 순간 막습니다

서버가 400(code 2101)으로 거절합니다. 상태에 넣어 두면 사용자는 저장을 눌러 봐야 실패를 압니다.

BottomSheetCalendar 는 core:ui 공용이고 상한 인자가 없습니다(타임레터는 미래 날짜를 고르는 화면이라 의도된 설계입니다). 남의 모듈 프로덕션 코드를 이 PR 에 넣지 않기 위해 달력을 고치는 대신 ViewModel 이 받아서 되돌립니다 — 미래 날짜면 상태를 바꾸지 않고 dateError 로 사유를 남깁니다.

Production visibility

  • src/main·src/debug·src/release 공개 범위는 프로덕션 사용처만으로 정했고, 테스트 직접 접근을 이유로 넓히지 않았습니다.
  • 새로 만들거나 수정한 선언은 같은 파일 private → 같은 모듈 internal → 다른 모듈 public 순으로 최소 범위를 확인했습니다.
  • 테스트 seam이 필요하면 공개 프로덕션 API 대신 공개 동작·test fixture·테스트 소스셋을 사용했습니다.
  • explicit API warning/strict 진단과 visibility Konsist 결과를 확인하고, 예외라면 생성 코드·프레임워크 근거를 변경 근처에 남겼습니다.

새 공개 선언은 DiaryWriteViewModel.onDateSelected 하나이고 화면이 부릅니다. DiaryWriteScreenContent 의 onDateRowClick 에는 no-op 디폴트를 두지 않았습니다 — NoOpCallbackDefaultKonsistTest 가 지적한 대로 프리뷰·테스트가 {} 를 명시합니다.

🧪 CI Test Plan

{
  "androidTest": {
    "mode": "none",
    "reason": "mindrecord presentation·data·domain 안에서 끝난다. 계측 진입점·내비게이션·매니페스트를 건드리지 않고, 날짜 배선과 미래 날짜 차단은 Robolectric 로 JVM 에서 그대로 재현된다(DiaryRecordDateWireTest 5건).",
    "tests": []
  }
}

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵

날짜 행에 chevron 이 다시 붙어 골든이 바뀝니다. macOS 재생성 금지 규약대로 손으로 만들지 않았습니다 — screenshot-baseline 라벨로 CI 표준 환경에서 생성·적용을 요청합니다.

종전(#1121 이후) 이 PR
날짜 행 2026년 9월 5일 (텍스트만, 누를 수 없음) 2026년 9월 5일 + ∨ (탭하면 달력)

💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴

DiaryWriteDateDisplayTest(「날짜를 바꾸는 공개 창구가 없다」)를 지우고 DiaryRecordDateWireTest 로 대체했습니다. 단언이 정반대가 됐습니다 — 그 가드는 계약이 없던 동안의 것이라 남겨 두면 이 PR 과 모순됩니다. 새 가드 5건입니다.

단언 무엇을 막는가
신규 작성은 고른 날짜를 그대로 싣는다 배선이 끊기면 다시 «표시 전용» 이 된다
고르지 않으면 오늘이 실린다 기본값 회귀
미래 날짜는 상태에 들어가지 않는다 서버 400 을 사용자가 저장 후에 만나는 것
프리필이 날짜를 못 주면 수정은 기록일을 싣지 않는다 기존 기록일이 조용히 오늘로 옮겨지는 것
프리필이 준 날짜는 수정 요청에 그대로 남는다 위 가드가 과해져 늘 생략하게 되는 것

FakeDiaryRepository.update 도 서버 규칙에 맞췄습니다 — 종전 주석이 「date 는 수정 요청 계약에서 빠졌다(#955)」였는데, 이제 null 이면 기존 값 유지로 흉내 냅니다. 이걸 안 고치면 위 두 단언이 fake 의 낡은 전제 위에서 통과합니다.

검증: :feature:mindrecord:presentation:testDebugUnitTest(296) · :feature:mindrecord:data:testDebugUnitTest · 세 모듈 ktlintCheck · :konsist:test · :app:compileDebugKotlin · :app:compileDebugAndroidTestKotlin 통과했습니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ea5nJ6nL7c2pdCNzKGztW2

서버가 2026-08-29 부터 `date` 를 받는다 (`Afternote-BE#244` → PR #262 머지).
`DiaryCreateRequest.date`·`DiaryUpdateRequest.date` 가 생겼고 `Diary.entry_date`
컬럼과 인덱스가 서고, 월 조회·주간 리포트가 그 값을 기준으로 돈다.

#1121 이 날짜 행을 표시 전용으로 잠근 것은 그 계약이 없던 2026-08-25 실측에
근거한 임시 조치였다. 계약이 왔으므로 시안(2671:17922)대로 되돌린다.

- 도메인·DTO·매퍼에 `date` 를 싣는다. 생성은 non-null, 수정은 nullable —
  서버가 «생성은 미전송 시 오늘, 수정은 생략 시 유지» 로 가르는 그대로다.
- 수정 경로는 **화면 값이 서버에서 왔거나 사용자가 고른 것일 때만** 싣는다.
  프리필이 실패한 채 오늘을 실어 보내면 기존 기록일이 조용히 오늘로 옮겨진다 —
  수신자 목록을 빈 목록으로 보내면 안 되는 것과 같은 함정이다.
- 미래 날짜는 서버가 400(code 2101)으로 거절한다. 고르는 순간 사유와 함께
  막는다 — 상태에 넣으면 저장을 눌러 봐야 실패를 안다.
- `DiaryWriteDateDisplayTest`(「날짜를 바꾸는 공개 창구가 없다」)를
  `DiaryRecordDateWireTest` 로 대체한다. 단언이 정반대가 됐다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ea5nJ6nL7c2pdCNzKGztW2
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📸 CI 표준 환경에서 screenshot baseline 2개를 생성·검증해 cba91f2 로 커밋했습니다.
실행 로그·생성 artifact: https://github.com/Afternote/Afternote-FE/actions/runs/33900404090
이미지 내용은 이 PR의 PNG diff에서 눈으로 최종 확인해 주세요. 필수 검사도 새 SHA에 다시 요청했습니다.

@github-actions github-actions Bot removed the screenshot-baseline CI 표준 환경에서 PR screenshot baseline 생성·적용 요청 label Sep 4, 2026

@1hyok 1hyok left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로덕션·테스트·문서 16파일을 head cba91f24c 기준으로 읽었고, 전제(서버가 date 를 받는다)를 BE 실코드로 다시 확인했습니다. 승인합니다.

검증

  • BE 실코드 대조 (origin/main, 마지막 deploy 성공 SHA 6364a6c 0831): DiaryCreateRequest.date·DiaryUpdateRequest.date 둘 다 LocalDate, DiaryService 는 LocalDate.now(SEOUL) 보다 뒤면 DIARY_INVALID_DATE(400·2101), 수정은 null 이면 유지 — 본문 표와 같습니다. PR #262 머지 커밋이 main 에 있고 그 뒤 deploy 가 성공했으니 실서버에 나가 있습니다.
  • 시안 대조: 2671:17922 를 커넥터 MCP 로 렌더해 봤습니다 — 날짜 행 우측 chevron + 밑줄. PR head 골든(diaryWriteScreenScreenshot_90705b76_0.png)이 같은 배치이고 develop 골든엔 chevron 이 없습니다. #1121 이 뺀 것이 «의도적 이탈» 이었다는 서술도 맞습니다.
  • 배선: 생성은 state.date 를 항상, 수정은 isDateChosen 일 때만 싣고 아니면 키를 생략합니다. DiaryRecordDateWireTest 5건이 그 경계를 잠그고(특히 «프리필 실패 시 수정은 기록일을 싣지 않는다»), MindRecordDtoContractTest 가 수정 DTO 의 키 생략을, FakeDiaryRepository.update 가 «null 이면 유지» 를 서버와 같게 흉내 냅니다. LocalDate.toString() 은 ISO yyyy-MM-dd 라 형식도 맞습니다.
  • 가드 교체: DiaryWriteDateDisplayTest(«바꾸는 창구가 없다») 삭제는 맞습니다 — 남기면 이 PR 과 모순됩니다.
  • CI 15 pass, 골든은 봇 커밋으로 재생성됐습니다.
  • 실기동 QA 는 하지 않았습니다. 날짜 행 탭 → 바텀시트 → 선택 → 행 갱신은 골든에 안 잡히고, VM 쪽은 Robolectric 5건이 덮습니다. 그 상호작용은 실기 확인 없이 승인하는 것이니 머지 전 한 번 눌러 보시면 좋겠습니다.

지적

  1. [nit] LocalDate.now() 의 시간대 → DiaryWriteViewModel.kt:96 인라인 코멘트. 이 PR 의 회귀는 아닙니다.

* 사용자는 저장을 눌러 봐야 실패를 안다. 고르는 순간 사유와 함께 막는다.
*/
fun onDateSelected(date: LocalDate) {
if (date.isAfter(LocalDate.now())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] 서버는 LocalDate.now(Asia/Seoul) 기준으로 미래를 판정하는데 여기는 기기 시간대입니다. 서울보다 앞선 시간대(UTC+10 이상)에서 자정 전후에 «오늘» 을 고르면 화면은 통과시키고 서버가 2101 을 줍니다. 기본값 DiaryWriteUiState.date 도 같습니다. 레포 전체가 LocalDate.now() 관례라 이 PR 의 회귀는 아니고, 2101 을 dateError 문구로 매핑하거나 ZoneId.of("Asia/Seoul") 로 고정하는 건 별건으로 두셔도 됩니다.

@Sadturtleman
Sadturtleman added this pull request to the merge queue Sep 6, 2026
Merged via the queue into develop with commit 538a1cb Sep 6, 2026
23 checks passed
@Sadturtleman
Sadturtleman deleted the fix/1008-restore-diary-date-picker branch September 6, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

일기 기록 날짜 선택이 서버에 전송되지 않음 — 생성·수정 양쪽 스키마에 date 부재, 표시 전용

2 participants