Skip to content

refactor(core): 동일 화살표 렌더링을 기존 ArrowIcon에 위임한다 #635

Closed

Description

@1hyok

작업 유형

refactor — 동작 변경 없는 구조·책임·경계 개선

주 담당 모듈

core — 코어

개요

현재 core 내부에서 실제 소비 중인 동일 렌더링 중복을 기존 공개 함수로 통합한다. RightArrowIcon과 같은 파일의 ArrowIcon은 동일한 Icon 구현을 사용하므로 RightArrowIcon이 ArrowIcon에 위임하도록 정리한다.

이번 이슈의 완료 범위

  • core/ui/src/main/kotlin/com/afternote/core/ui/icon/RightArrowIcon.kt 한 파일에서 기존 구현에 위임한다.
  • 기존 drawable, modifier, tint, null contentDescription과 두 공개 함수의 시그니처·기본값을 유지한다. 다른 모듈의 현재 호출부는 수정하지 않는다.
  • 관련 컴파일·기존 테스트·기존 스크린샷 검증으로 렌더링과 접근성 계약의 보존을 확인한다. 새 시안이나 baseline을 만들지 않는다.
  • 최신 실제 base 대비 diff에 기능 모듈 변경이 없는지 소유권 검사를 통과한다.

기존 항목의 현재 위치와 제외 근거

  • timeletter의 image/file 벡터 및 프로필 placeholder 사본은 기존 담당 이슈 refactor(timeletter): core 자원과 렌더가 같은 벡터 사본 3개를 core:ui 로 수렴 (#635 인계) #2084, #2085에서만 추적한다. 이번 core 함수 위임은 그 구현을 필요로 하지 않으므로 두 이슈를 이 이슈의 blocked_by에서 분리한다. timeletter 파일과 담당자는 변경하지 않는다.
  • 현재 소스에는 ReadOnlyField가 없다. setting의 receiver/ReceiverRegisterScreen.kt 관계 앵커는 밑줄과 펼침 상태를 가진 단일 화면 구현이고 기존 core 입력 컴포넌트와 같은 규격이 아니다. 이를 동일 중복으로 간주한 예약은 제거한다.
  • 현재 passkey/PassKeyViewModel.kt는 UserProfileDataSource.isPasskeyRegisteredFlow()와 savePasskeyRegistered(true)로 임시 로컬 등록 상태를 관리한다. #1944의 서버 목록·등록 성공 소비가 이 흐름을 대체한다. 닫힌 PR #1927의 오래된 MVI 구조를 복원하거나 이 이슈에서 옛 UserProfileRepository 추출을 예약하지 않는다. 현재 생산 계약에 대한 #1944의 재판정 범위를 중복 구현하지 않는다.
  • core 드로어블끼리 동일 사본은 없으며 팝업·프로필 이미지는 이미 공용 구현을 소비한다. 업로드 MIME/실패 계약, 별도 네트워크 바인딩, 서로 다른 디자인 컴포넌트는 동일 계약의 중복으로 취급하지 않는다.

범위 경계

현재 렌더·접근성·기능을 보존하는 위임만 완료 조건으로 둔다. 아이콘 형태·크기·tint, 헤더·입력 팝업·셰브론의 미확정 시각 통일, core 파라미터 확장, 에셋 재크롭 및 컴포넌트 치환을 포함하지 않는다. 이 이슈는 다른 담당자의 timeletter 작업 완료를 기다리는 부모 추적 이슈로 사용하지 않는다.

Activity

  1. self-assigned this
    on Jul 30, 2026
  2. added theissue type on Jul 30, 2026
  3. 1hyok commented on Aug 1, 2026

    @1hyok
    CollaboratorAuthor

    담당 재확인 결과 #634와 담당이 동일하다는 기존 전제가 잘못되어 이 이슈를 재개했다.

    #634는 마음의 기록 6건만 남겨 Sadturtleman에게 배정했다. 이 이슈는 koongmai 담당인 타임레터 6건과 설정 4건을 함께 추적한다. 설정 단독 이슈 #636은 같은 담당의 동일 작업이므로 이 이슈로 통합한다.

  4. assigned and unassigned on Aug 4, 2026
  5. changed the title [-]refactor(timeletter): core:ui 자원 미사용으로 생긴 중복 6건 수렴 — 아이콘 사본·뷰 전환 토글·섹션 헤더·M3 다이얼로그[/-] [+]refactor(timeletter,setting): core 자원 미사용으로 생긴 중복 10건 수렴 — 타임레터 6 · 설정 4[/+] on Aug 4, 2026
  6. reopened this on Aug 4, 2026
  7. 1hyok commented on Aug 24, 2026

    @1hyok
    CollaboratorAuthor

    #1071(리소스 네이밍) 조사 중에 이 이슈 범위인 두 모듈에서 함께 정리할 만한 위생 건이 나와 붙입니다. 실측 리비전 6b381db8(origin/develop).

    1. android.R 오import 2건 — 두 모듈에 하나씩

    feature/timeletter/presentation/.../component/ViewToggleButton.kt:3
        import android.R.attr.contentDescription
    
    feature/setting/presentation/.../component/InsertPasswordContent.kt:3
        import android.R.attr.password
    

    둘 다 실제로 쓰이지 않는 죽은 import 입니다. IDE 자동완성이 named argument 를 심볼로 오인해 끼워 넣은 흔적으로 보입니다.

    • ViewToggleButton.kt:55,79 — contentDescription = "리스트형" / "블록형" 은 named argument
    • InsertPasswordContent.kt:23,44 — passwordLength · PasswordDots 뿐, password 단독 참조 없음

    동작에는 영향이 없습니다(import android.R.attr.password 는 password 라는 이름만 가져오므로 같은 파일 R.drawable.ic_lock 은 그대로 모듈 R 로 해석). 다만 ktlint 의 미사용 import 검사를 빠져나가는 형태라 자동으로는 안 잡힙니다 — 파일 본문에 contentDescription · password 라는 문자열이 named argument 로 등장해서 사용 중으로 오인됩니다. 손으로 지우는 수밖에 없습니다.

    2. 덤 — timeletter 의 자기 모듈 R 을 FQN 으로 참조 6곳

    ViewToggleButton.kt:54,78      painterResource(com.afternote.feature.timeletter.presentation.R.drawable.ic_list / ic_block)
    TimeLetterBlockItem.kt:66,98   … R.drawable.container / setting
    TimeletterListItem.kt:79       … R.drawable.setting
    TimeLetterContent.kt:100       … R.drawable.down_vector
    

    세 파일 모두 import ...presentation.R 도, android.R 오import 도 없이 FQN 만 씁니다. 이름 충돌 회피가 아니라 관행으로 보이고, import 한 줄로 줄일 수 있습니다. 이 이슈가 같은 파일들(ViewToggleButton 은 아이콘 사본 항목과 겹칩니다)을 어차피 건드리므로 함께 처리하면 비용이 0에 가깝습니다.

    참고

    여기 나오는 ic_list 는 feature/setting 에도 같은 이름·같은 54줄 드로어블이 있습니다(모듈 간 이름 충돌 3건 중 하나). 이 이슈가 다루는 ic_image·ic_file 사본과 같은 계열이라, 정리 범위에 넣을지 판단이 필요합니다. 네이밍 규칙 자체(resourcePrefix 도입)는 #1071 에서 따로 다룹니다.

  8. 1hyok commented on Aug 24, 2026

    @1hyok
    CollaboratorAuthor

    #1071(리소스 네이밍)을 모듈별로 쪼개면서 feature/timeletter 몫은 이 이슈로 넘겼습니다. 별도 이슈를 내지 않은 이유와 남는 범위를 적어 둡니다.

    timeletter 는 문자열 9개가 이미 전량 timeletter_ 프리픽스를 지키고, 미준수는 드로어블 13개뿐입니다.

    container  default_profile  down_vector  ic_align_center  ic_align_left  ic_align_right
    ic_block   ic_file          ic_image     ic_list          ic_mic         ic_text        setting
    

    이 중 ic_image · ic_file · default_profile · ic_list 4개는 이 이슈가 이미 다루는 사본입니다. core:ui 로 치환되면 파일이 삭제되어 리네임 대상에서 아예 사라집니다. 그래서 순서가 뒤집히면 헛일이 됩니다 — 사본 정리가 먼저입니다.

    남는 9개(container · down_vector · setting · ic_align_* 3 · ic_block · ic_mic · ic_text)는 timeletter_ 프리픽스로 리네임하고 build.gradle.kts 에 resourcePrefix = "timeletter_" 를 걸면 끝입니다. 이 모듈은 문자열이 이미 준수라 남은 작업이 작고, 담당도 이 이슈와 같아 여기 얹는 게 맞다고 봤습니다.

    feature/setting 의 드로어블 15개도 같은 사정이라 #1077 에서 이 이슈 뒤로 순서를 잡아 뒀습니다.

  9. changed the title [-]refactor(timeletter,setting): core 자원 미사용으로 생긴 중복 10건 수렴 — 타임레터 6 · 설정 4[/-] [+]core 자원 미사용으로 생긴 중복 10건 수렴 — 타임레터 6 · 설정 4[/+] on Aug 28, 2026
  10. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    and removed
    enhancementNew feature or request
    on Aug 28, 2026
  11. 1hyok commented on Aug 28, 2026

    @1hyok
    CollaboratorAuthor

    setting 3번(PassKeyViewModel 의 DataStore 직접 참조) — 0829 기준 여전히 유효

    #1282 계약 분리 작업 중 프로필 계약 사용처를 훑다가 재확인했다. 본문 3번의 실측이 지금도 그대로다.

    • PassKeyViewModel.kt:5,17 이 core.datastore.UserProfileDataSource 를 직접 주입받는다 — UserProfileRepository 미경유.
    • feature/ 전 소스셋에서 com.afternote.core.datastore 를 import 하는 파일은 여전히 이 하나뿐이고, implementation(projects.core.datastore) 를 선언한 presentation 모듈도 setting 하나뿐이다. 본문대로 그래들 한 줄까지 같이 빠진다.
    • 참고로 같은 모듈의 implementation(projects.core.data) 는 main 소스 참조 0건이고 유일한 참조가 SettingCompletionTest.kt:7 의 UserRepositoryImpl 직접 조립이다(core:di 모듈 삭제 — 바인딩을 구현 옆으로 옮기고 repository impl 을 internal 로 #930 과 얽힌 그 조립).

    이 위반을 놓치게 만든 자동 게이트 부재는 별도로 #1432 에 올렸다 — konsist 의 레이어 가드가 domain 패키지 파일만 스캔해서 presentation → core:datastore 는 규칙 사정거리 밖이다. 다만 그 가드는 main 소스 위반이 0이어야 초록으로 들어가므로, 이 항목 수복이 선행이다. 순서만 그렇고 이 이슈에 추가 작업이 붙지는 않는다.

  12. assigned and unassigned on Sep 6, 2026
  13. changed the title [-]core 자원 미사용으로 생긴 중복 10건 수렴 — 타임레터 6 · 설정 4[/-] [+]core 중복 중 기존 렌더·동작을 보존하는 범위만 정리한다[/+] on Sep 12, 2026
  14. 1hyok commented on Sep 13, 2026

    @1hyok
    CollaboratorAuthor

    착수 전 점검에서 본문의 "독립적으로 남길 범위" 3항목 중 2항목이 현실과 어긋난 것을 확인했습니다. 기준 리비전은 origin/develop 86ab223 이고, 중복은 전부 심볼명으로 세었습니다.

    살아 있는 항목 — timeletter 벡터 사본 (1번)

    feature/timeletter/presentation/src/main/res/drawable/ic_image.xml 과 ic_file.xml 은 core/ui 의 core_ui_ic_image.xml · core_ui_ic_file.xml 과 들여쓰기(2칸 대 4칸)만 빼고 전 속성이 같습니다. 24dp · viewport 24 · path · strokeAlpha 0.6 · strokeWidth 2 · fillColor #00000000 · strokeColor #000000 · cap/join round 까지 같습니다. 소비처는 세 곳입니다(MediaBottomSheetContent.kt:44,56, TimeLetterWriteScreen.kt:567). 같은 함수가 이미 core_ui_ic_mic(:50)과 core_ui_ic_link(:62)를 CoreUiR 로 쓰고 있어 두 개만 모듈 사본으로 남은 상태입니다. 세 자리 모두 명시 크기 없이 intrinsic 24dp 로 그리므로 치환해도 렌더가 같습니다.

    default_profile.xml(67x68dp)은 core_ui_ic_profile_placeholder.xml(134x136dp)의 정확한 1/2 축소본입니다. 좌표를 하나씩 2배 해서 전부 일치했습니다. 원 33.75,33.25 r33.25 → 67.5,66.5 r66.5, 머리 33.5,29 C38.471,29 42.5,24.971 42.5,20 → 67,58 C76.94,58 85,49.94 85,40, 몸통 53.843→107.69 · 48.607→97.22 · 43.146→86.29 · 23.504→47.01 · 13.157→26.31, strokeWidth 4 대 8. 종횡비 67:68 = 134:136 으로 같습니다. 소비처 두 곳(RecipientCard.kt:39 size(67.dp), RecipientListItem.kt:39 size(50.dp))이 모두 명시 크기라 치환해도 렌더가 같습니다.

    선례도 있습니다. 7c1c9e9e1(#640)이 mic 아이콘 바이트 동일 사본 2벌을 core_ui_ic_mic 으로 이관했고 #640 은 COMPLETED 로 닫혔습니다.

    빠져야 할 항목 — setting ReadOnlyField (2-a)

    private fun ReadOnlyField 는 RecipientSetScreen.kt:134 에 그대로 있고 스펙도 AfternoteFieldContainer 와 같습니다. 그런데 RecipientSetScreen 은 전 저장소 참조가 자기 파일 안 3건(선언 :38, Preview :159, Preview 호출 :160)뿐인 도달 불가 화면이고, 열린 PR #2005(#1064)가 이 파일을 통째로 삭제합니다. #1064 본문 33행이 이미 "#635 의 setting 1번 항목은 화면째 삭제되면 사라진다. 이 이슈를 먼저 처리하고 #635 에서는 그 항목을 빼는 쪽이 맞다" 고 적어 뒀습니다.

    이미 해소된 항목 — setting 관계 드롭다운 앵커 (2-b)

    근거 이슈 #636 이 지목한 형태는 ReceiverRegisterScreen.kt:157-184 의 Row(...background(white, shape).border(1.dp, gray2, shape).clickable{}.padding(24, 13)) 였습니다. origin/develop 의 같은 자리(:243-278)는 Column(...).clickable{ relationExpanded = true }.padding(top = 13.dp) 안에 Row(Text + core_ui_arrowdown 18dp)와 HorizontalDivider(0.8.dp, gray3) 를 두는 밑줄형 앵커로 바뀌어 있습니다. 파일 전체에 .border 는 0건이고 RoundedCornerShape 도 0건이며, 남은 .background 1건(:285)은 앵커가 아니라 DropdownMenu 팝업 자체입니다. AfternoteFieldContainer 로 수렴할 중복이 없습니다. 바뀐 시점은 7612fab11(#577)과 뒤이은 9aeb1e437 이고 원형은 cd5e4e49b 에만 남아 있습니다.

    남의 PR 이 이미 수복 중인 항목 — PassKeyViewModel (3번)

    PassKeyViewModel.kt:5,17 이 여전히 core.datastore.UserProfileDataSource 를 직접 주입받습니다. 본문이 건 게이트대로 #1944 / PR #1927 과 대조한 결과, 그 PR 이 바로 이 파일을 고쳐 UserProfileDataSource 를 UserProfileCacheRepository 와 feature.setting.domain.PasskeyRepository 로 갈아끼우고 MVI 로 전환합니다. 여기서 손대면 그 PR 과 충돌합니다. develop 게이트는 PresentationLayerDependencyKonsistTest.kt:137 의 KNOWN_LAYER_BYPASSES 등재로 초록입니다.

    제안

    본문에서 2-a · 2-b · 3 을 빼고 1번만 남긴 범위로 재작성하는 것이 맞습니다. 그러면 남는 일은 timeletter 벡터 사본 3개(ic_image · ic_file · default_profile)를 core/ui 로 수렴하고 소비처 5곳을 CoreUiR 로 바꾸는 것입니다.

    다만 착수 전에 확인할 것이 둘 있습니다. timeletter 는 다른 사람의 모듈이라 프로덕션 변경은 담당자 게이트에 걸립니다. 그리고 RecipientCard · RecipientListItem 은 #1402(프로필 크기 시안 소수점 복원)와 #1470(contentDescription 리소스화)이 함께 보고 있어 같은 줄을 건드리면 겹칩니다.

    git log --grep '#635' origin/develop 은 2건인데 둘 다 이 이슈를 처리한 커밋이 아닙니다. 5c8663940(#1433)은 개명만 했고, a984a59ab(#1432)는 "남은 위반 1건(PassKeyViewModel)은 KNOWN_LAYER_BYPASSES 에 등재했다. 수복은 setting 담당의 #635 몫" 이라고 적었습니다.

  15. 1hyok commented on Sep 13, 2026

    @1hyok
    CollaboratorAuthor

    착수 결과입니다. 이 이슈로 제가 쓸 수 있는 프로덕션 코드가 0줄이라, 남은 범위를 두 이슈로 넘기고 관계를 걸었습니다. 기준 리비전은 origin/develop 7d87aa3f4 입니다.

    왜 0줄인가

    앞선 코멘트에서 잰 변경면(벡터 사본 3개 삭제, 소비처 5곳 치환)은 파일이 전부 feature/timeletter 입니다. 그 목록으로 PR JSON 을 만들어 가드를 직접 돌렸습니다.

    $ node .github/scripts/validate-pr-module-owner.mjs pr.json files.json
    ::error::PR #9999 작성자 @1hyok 가 담당하지 않는 모듈의 프로덕션 파일을 바꿨습니다.
      timeletter(담당 @koongmai) 7건: .../MediaBottomSheetContent.kt, .../RecipientCard.kt,
      .../RecipientListItem.kt, .../TimeLetterWriteScreen.kt, .../default_profile.xml,
      .../ic_file.xml, .../ic_image.xml
    EXIT=1
    

    선례로 삼을 만한 것이 7c1c9e9e1(#640, 2026-08-28) 인데, 같은 성격의 mic 사본 이관을 제가 직접 했습니다. 다만 그 커밋은 가드 도입 969ecaad4(#1889, 2026-09-04)보다 일주일 앞섭니다. 그 커밋의 파일 목록을 지금 가드에 먹이면 exit 1 이고 timeletter 3건과 mindrecord 1건을 지목합니다. 선례는 해도 된다는 근거가 아니라 가드가 없던 때라 통과했다는 근거입니다. 이 이슈 본문의 완료 조건 마지막 문장도 타인 담당 범위를 직접 수정하지 않는다입니다.

    issue-assignee-exempt 라벨은 인계를 기다릴 수 없는 긴급 수선용이라, 렌더 보존 정리에는 맞지 않습니다.

    인계한 것

    이슈 범위
    #2084 벡터 사본 3개 삭제와 소비처 5곳 CoreUiR 치환. 렌더 동일성 근거와 줄 번호를 본문에 실었습니다
    #2085 timeletter_ 접두사. 0824 코멘트로 이 이슈에 얹혀 있던 #1071 의 timeletter 몫입니다

    관계는 #635 blocked_by #2084, #635 blocked_by #2085, #2085 blocked_by #2084 입니다. 후속 분리분이라 자식이 아니라 blocked_by 로 이었습니다.

    #2084 에는 blocked_by #714 도 걸었습니다. PR #1365 가 RecipientCard.kt 와 TimeLetterWriteScreen.kt 를 이미 건드리는데, 그쪽이 TimeletterScreenScreenshotTest 골든을 물고 있어 먼저 들어가야 재생성이 한 번으로 끝납니다. 사본 정리는 렌더가 같아 골든에 영향이 없습니다. 확인해 보니 그 골든 4장은 TimeletterScreen 만 그리고 여기 나오는 자원 셋을 하나도 렌더하지 않습니다.

    #1613 과는 MediaBottomSheetContent 의 소비처 2곳이 겹칩니다. #1613 이 먼저 들어오면 그 2곳은 자연히 없어지지만 #2084 가 비지는 않습니다. ic_file 은 TimeLetterWriteScreen.kt:567 이 따로 쓰고 default_profile 은 #1613 범위 밖입니다.

    나머지 세 항목이 이 이슈 몫이 아닌 이유

    setting 의 ReadOnlyField

    ReadOnlyField 는 RecipientSetScreen.kt 안의 private 함수(:134)이고 호출부도 같은 파일 두 곳(:86, :102)뿐입니다. 제 PR #2005 가 이 파일을 161줄 통째로 지웁니다. 중복을 수렴할 게 아니라 한쪽이 사라지는 건이라 이 이슈에서 손댈 것이 없습니다.

    관계 드롭다운 앵커

    develop 에서 feature/setting/presentation 의 DropdownMenu 는 ReceiverRegisterScreen.kt:279 한 곳뿐입니다. RecipientSetScreen.kt 에는 드롭다운이 없고 "관계" 정적 라벨과 ReadOnlyField 만 있습니다(:74). 중복 쌍이 이미 해소됐고, 남은 그 파일도 #2005 가 지웁니다.

    PassKeyViewModel 의 DataStore 직접 의존

    본문이 #1944·PR #1927 과 먼저 대조하라고 적어 둔 항목입니다. 대조했습니다. develop 의 PassKeyViewModel 은 core.datastore.UserProfileDataSource 를 직접 주입받지만, PR #1927 의 head 는 그 자리를 core.domain.repository.UserRepository 로 바꾸고 파일을 131줄 다시 씁니다(PasskeyRepository·PasskeyRepositoryImpl 신설 포함). 옛 경계를 이 이슈에서 따로 건드리면 그 PR 과 충돌만 납니다. #1927 소관으로 둡니다.

    덤: 0824 코멘트의 ic_list 정정

    그 코멘트가 ic_list 를 core 사본 4개 중 하나로 셌는데 다시 재 보니 사본이 아닙니다. timeletter 판은 28dp·viewport 28 의 스트로크 아이콘이고 core/ui/.../core_ui_list.xml 은 24dp·viewport 24 의 Material 채움 아이콘이라 형태가 다릅니다. 통일하면 시각 변경이고, 이 이슈 본문이 뷰 전환 아이콘의 크기·tint 통일을 제외 범위로 못박아 두었습니다. 그래서 #2084 에 넣지 않고 #2085 의 리네임 대상으로만 남겼습니다.

    같은 이유로 프리픽스 대상 수도 달라집니다. 0824 에 13개였던 미준수 드로어블이 지금 9개이고(ic_mic 와 ic_align_* 3개가 그사이 정리됐습니다) #2084 가 3개를 지우면 6개가 남습니다.

    이 이슈에 남는 것

    제 담당인 core 쪽은 할 일이 없습니다. 세 자원이 core/ui 에 이미 있고 core:ui 자신과 afternote·mindrecord·receiver 가 씁니다. #2084 와 #2085 가 닫히면 이 이슈도 닫힙니다.

  16. changed the title [-]core 중복 중 기존 렌더·동작을 보존하는 범위만 정리한다[/-] [+]refactor(core): 동일 화살표 렌더링을 기존 ArrowIcon에 위임한다[/+] on Sep 21, 2026
  17. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 21, 2026
  18. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:setting설정 기능 영역area:timeletter타임레터 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions