Repository navigation
refactor(afternote): 추모 영상·썸네일을 값 객체로 묶고 picked/server 축을 사진과 대칭으로 (#1406 후속) #1561
Description
작업 유형
refactor — 동작 변경 없는 구조·책임·경계 개선
주 담당 모듈
afternote — 애프터노트
개요
AfternoteTypeForm.Memorial 의 미디어 칸이 사진 축과 영상 축에서 서로 다른 방식으로 출처를 표현한다. 영상 축만 칸이 늘고 규칙이 손으로 지켜진다.
사진 — 칸이 출처를 말한다
val pickedPhotoUri: String? = null // 이 폼에서 고른 것
val photoUrl: String? = null // 서버에 저장된 것
fun displayPhotoUri(): String? = pickedPhotoUri ?: photoUrl삭제는 pickedPhotoUri = null 한 줄이고 표시는 자동으로 서버로 폴백한다.
영상 — 한 칸이 둘을 겸하고, 출처를 문자열에서 추론한다
videoUrl 이 로컬 픽과 서버 URL 을 겸하므로 출처 판정이 videoUrl?.isLocalContentUri() 다(MemorialMediaSourceBottomSheet.removableMemorialMediaTargets). 로컬로 덮이는 순간 서버 값이 그 칸에서 사라져, #1406 이 그림자 두 칸(serverVideoUrl·serverThumbnailUrl)을 붙여 되돌아갈 자리를 만들었다. 결과적으로 영상 축만 4칸이고, 삭제는 EditorFormMutations.withMemorialVideo 의 명시 복원 분기가 됐다.
게다가 영상과 썸네일이 항상 함께 움직여야 한다는 규칙이 타입에 없다. 「영상이 바뀌면 썸네일 무효」·「되돌리면 둘 다 되돌림」을 호출부마다 손으로 지키고 있고, 실제로 한쪽만 바뀌는 상태가 만들어진다 — 영상을 지운 뒤 늦게 도착한 썸네일 업로드가 setMemorialThumbnail 로 썸네일 칸만 채우면 videoUrl = null, thumbnailUrl = <url> 이 된다(#1550 이 다루는 레이스). 서버 계약에서도 이 둘은 짝이다 — BE AfternotePlaylist.MemorialVideo 가 @Embeddable 로 두 칸을 묶어 통째 교체한다.
필요한 동작
- 영상과 썸네일을 값 객체로 묶는다. 썸네일만 nullable 로 두어 「영상 없이 썸네일만」이라는 상태를 타입에서 배제한다.
data class MemorialVideoAttachment(val url: String, val thumbnailUrl: String? = null)- 사진과 같은 picked/server 축으로 정리한다 —
pickedVideo: MemorialVideoAttachment?+serverVideo: MemorialVideoAttachment?, 표시는pickedVideo ?: serverVideo. 영상 축 4칸이 2칸이 된다. - 삭제는
pickedVideo = null로 좁힌다 —withMemorialVideo의 복원 분기가 사라지고, 삭제 가능 판정도 스킴 추론(isLocalContentUri()) 대신pickedVideo != null이 된다. enteredContentOrNull의 제외 마스크가copy(serverVideo = null)하나로 줄어든다. 지금은 세 칸을 손으로 나열한다.- 소비처를 함께 옮긴다 —
EditorFormState파생 getter,EditorFormSnapshot(프로세스 데스 왕복), prefill(AfternoteTypeForm.applyPrefill), 저장 매퍼(videoMediaInput·MemorialMediaUrls),MemorialVideoUpload파라미터. - 동작 변경이 없음을 기존 테스트로 고정한다 —
MemorialVideoServerOriginTest·AfternoteEditorMemorialMediaRemoveTest·MemorialMediaRemovableTargetsTest·EditorContentSignatureCompletenessTest가 수정 없이(또는 타입 치환만으로) 통과해야 한다.
자식 이슈
No response
참고
- fix(afternote): 수정 모드 서버 영상→로컬 교체 후 삭제는 여전히 거짓 삭제 — 폼은 비지만 저장 후 서버 영상 잔존 #1406 후속이다. 그 PR(fix(afternote): 서버 영상을 로컬로 교체한 뒤 지우면 서버 표시로 되돌린다 (#1406) #1527)은
videoUrl소비처를 건드리지 않는 최소 변경으로 그림자 칸을 택했다 — 커밋 메시지가 「영상만 대칭이 깨져 있었다」고 진단해 놓고 대칭을 맞추는 대신 원본을 따로 든 셈이라, 정리가 남았다. fix(afternote): 서버 영상을 로컬로 교체한 뒤 지우면 서버 표시로 되돌린다 (#1406) #1527 머지 이후에 착수한다. - fix(afternote): 추모 영상 썸네일 실패에 재시도 수단이 없다 — 결손이 저장으로 영구 확정된다 (#705 5번 분리) #1550(썸네일 재시도)과 같은 파일을 건드린다. fix(afternote): 추모 영상 썸네일 실패에 재시도 수단이 없다 — 결손이 저장으로 영구 확정된다 (#705 5번 분리) #1550 이 사용자 영향 있는 버그(Priority High)라 그쪽이 먼저고, 이 리팩터는 그 뒤에 붙는 편이 충돌이 적다. 다만 5·6번의 「영상 없이 썸네일만」 배제는 fix(afternote): 추모 영상 썸네일 실패에 재시도 수단이 없다 — 결손이 저장으로 영구 확정된다 (#705 5번 분리) #1550 의 레이스를 타입 단계에서 줄여 준다.
- 서버 계약과도 모양이 맞는다 — BE
AfternotePlaylist.MemorialVideo(videoUrl, thumbnailUrl)가 이미 값 객체다(2026-08-30Afternote-BEorigin/main 실측).
Activity
OnlyReadOneBook commentedon Aug 30, 2026
착수 순서를 참고란에 적어 둔 것과 반대로 뒤집었습니다 — 리팩터가 먼저입니다(#1565), #1550 은 그 위에 스택(#1567).
이유는 두 가지입니다.
참고란의 「#1527 머지 이후에 착수한다」·「#1550 이 먼저」는 이 두 PR 로 대체됐습니다.
refactor(afternote): 표시용 추모 영상을 값 객체로 노출한다 (#1561)
refactor(afternote): 추모 영상 편집 상태를 캡슐화한다 (#1561)
refactor(afternote): 구형 에디터 스냅샷 호환을 제거한다 (#1561)
refactor(afternote): 미저장 교체분 필드를 selection 으로 바꾼다 (#1561)
refactor(afternote): 추모 영상 편집 상태를 data class 로 바꾼다 (#1561)
refactor(afternote): 추모 영상 값 객체의 선택 취소를 별도 연산으로 뗀다 (#1561)
Merge branch 'origin/refactor/1561-memorial-media-value-object' into …
test(afternote): 프로세스 사망 복원 테스트의 스냅샷 키를 v4 로 맞춘다 (#1617)
feat(afternote): 미디어 삭제는 슬롯을 비운다 — 교체분과 서버 원본을 함께 걷고 서버 삭제는 저장이 한다 (#…