Skip to content

[Fix] #260 - 작품 알림 기능 QA 후속 수정 모음 - #263

Merged
Guryss merged 14 commits into
developfrom
Fix/#260
Sep 12, 2026
Merged

Guryss merged 14 commits into
developfrom
Fix/#260

Conversation

@Guryss

@Guryss Guryss commented Sep 12, 2026

Copy link
Copy Markdown
Member

💡 Issue


💭 Summary

여러 QA 후속 수정과 작품 상세 알림 기능, 키워드 캐시 재동기화 정책 리팩토링을 한 브랜치에 묶었어요.


🔑 Key Changes

작품 상세 알림 기능

  • 종 아이콘이 완결/휴재복귀 알림 둘 중 하나라도 켜져 있으면 채워지고 wssPrimary100 색으로 바뀌어요.
  • 종 아이콘을 탭하면 시스템 푸시 권한을 먼저 확인해요.
    denied면 알림 설정 시트 대신 기기 설정 유도 알럿만 띄워요(SettingFeature의 알림 메뉴와 동일한 패턴이에요).
  • 알림 설정 시트 ViewModel을 화면 진입 시 한 번만 만들어 재사용하도록 바꿨고, 그 과정에서 생긴 상태 누수 3건(재진입 시 isClosing/isSyncing 미리셋 등)을 고쳤어요.
  • 리뷰 과정에서 이 권한 확인 로직을 View가 아니라 ViewModel이 갖도록 다시 옮겼어요.
    SettingFeature/HomeFeature의 확립된 패턴과 맞추고, 연타·화면 종료 시 Task 취소 가드도 함께 정리했어요.

키워드 캐시 재시도 책임 이관

  • 캐시 미스 시 서버에 재동기화하는 책임을 Repository가 아니라 Domain의 LoadTotalKeywordsUseCase로 옮겼어요.
    Repository는 데이터 소스 관리, 재시도 정책은 UseCase라는 관심사 분리예요.
  • 이 UseCase를 감싸 쓰는 소비 UseCase 4개(LoadNovelUseCase/LoadMyLibraryUseCase/LoadUserLibraryUseCase/LoadNovelPreferencesUseCase)와 App·Demo 조립부, Domain 테스트를 전부 갱신했어요.
  • AppDependencies.init()의 무조건 키워드 동기화 호출은 항상 중복이거나 실패할 뿐이라 제거했어요.
    안전망은 이제 위 UseCase의 캐시 미스 재시도가 맡아요.

MainTabView 리팩토링

  • iOS 18+는 SwiftUI.Tab API, iOS 17은 tabItem으로 분기해서 구성하도록 바꿨어요(Liquid Glass 탭바 전환 애니메이션 대응).

UI 버그 수정

  • WSSNavigationBar 타이틀이 길어지면 양쪽 버튼 영역을 넘어서던 문제를 고쳤어요.
    lineLimit(1) + 좌우 안전여백(뒤로가기 고정폭/trailing 실측 폭 중 큰 값)으로 겹치지 않게 말줄임돼요.
  • 탭 아이콘 색상 렌더링을 iOS 26+/iOS 17~25로 분기했어요.
  • 필터/캐릭터 편집 시트의 배경이 삐져나오게 하던 corner radius를 제거했어요.

폰트 크기 QA 반영

  • 컬렉션 상세/목록, 작품 알림 등록 시트 토글 라벨의 폰트 크기를 QA 결과에 맞게 조정했어요.

📱 Simulation

UI 변경이 여러 건 포함돼 있어요. 시뮬레이터 스크린샷/녹화를 첨부해주세요.

  • 작품 상세 종 아이콘 채움/알림 권한 알럿
  • 긴 타이틀 화면에서 WSSNavigationBar
  • 탭바 전환(가능하면 iOS 18~25, iOS 26 둘 다)

🧑‍🧒‍🧒 To Reviewer

  • 이번 브랜치는 여러 QA 수정이 누적된 상태라 커밋 단위(관심사별)로 봐주시면 리뷰가 편하실 거예요.
  • 푸시 권한 확인 로직을 View→ViewModel로 옮기면서 isClosing 가드를 다른 액션들과 동일하게 맞췄어요 — 이 부분 위주로 봐주세요.
  • 아래 시나리오를 시뮬레이터에서 확인해주시면 좋겠어요.
    1. 작품 상세 종 아이콘 탭 시 권한 authorized/denied/notDetermined 3분기
    2. 알림 설정 시트 토글 진행 중 닫았다가 다시 열기(상태 누수 회귀 여부)
    3. 긴 타이틀 화면에서 WSSNavigationBar 겹침 여부
    4. iOS 18~25 시뮬레이터에서 탭 전환 시 아이콘 선택/비선택 색이 실제로 갈리는지

※ Reference

해당 없음

Guryss and others added 14 commits September 12, 2026 16:42
SwiftUI.Tab(value:content:label:)과 이름이 겹치지 않도록 private enum Tab을 MainTab으로
리네임하고, iOS 18+ 전용 modernTabView(Tab API)와 iOS 17용 legacyTabView(tabItem)로
TabView 구성을 나눴다. 4탭 Root 뷰는 두 분기가 공유하도록 별도 computed property로 뽑았다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
iOS 26 Liquid Glass 탭바 대응용 baked(.alwaysOriginal) 이미지를 모든 버전에 쓰던 것을,
iOS 26+만 baked 방식을 쓰고 iOS 17~25는 template 렌더링 + foregroundStyle로 되돌렸다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
presentationBackground(Color)와 presentationCornerRadius를 같이 쓰면 배경 사각형이 시트
둥근 모서리에 클립되지 않아 양 옆·하단이 화면 밖으로 삐져나온다(LibrarySortSheet에 이미
문서화된 것과 같은 함정).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
제목은 body2 → title2, 설명은 body5 → body4로 키웠다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
완결/휴재복귀 알림 둘 중 하나라도 켜져 있으면 네비바 종 아이콘을 채운 모양(icAnnouncementFill)
+ wssPrimary100으로 바꾸고, 꺼져 있으면 기존 윤곽선(icAnnouncement)+wssBlack을 유지한다.

이를 위해 NovelNotificationSettingSheetViewModel을 시트가 열릴 때마다 새로 만들던 것에서
NovelDetailView 진입 시 한 번만 만들어 화면 수명 내내 재사용하도록 바꿨다 — 시트를 열지
않아도 아이콘이 현재 상태를 비춰야 하고, 시트 안 토글도 실시간으로 아이콘에 반영돼야 해서다.
인스턴스가 재사용되면서 isClosing 가드가 시트를 한 번 닫으면 영구히 걸려버리는 문제가 있어
load()가 시트 재진입마다 isClosing을 다시 풀도록 고쳤고, 인증 만료 리스닝도 시트가 안 떠
있을 때 신호를 놓칠 수 있어 NovelDetailView 쪽으로 옮겼다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
PR 리뷰에서 발견된 문제:
- isSyncing이 토글 도중 시트를 닫으면 영구히 true로 남아 알림 토글이 조용히 먹통되던 버그.
  load()가 isClosing만 재진입마다 리셋하고 isSyncing은 놓쳤던 것이 원인.
- 시트 onDisappear(disappear())가 loadTask까지 취소해, 시트를 열자마자 바로 닫으면
  종 아이콘이 서버 상태와 무관하게 부정확한 채로 고착되던 문제 → disappear()는 syncTask만
  취소하도록 좁히고, 화면 자체가 닫힐 때 loadTask까지 정리하는 screenClosed()를 새로 분리.
- 무동작이던 아이콘 전환 애니메이션 제거(서로 다른 리소스+tint 전환이라 애초에 안 먹었음).

screenClosed()는 .onDisappear가 아니라 viewModel.state.shouldDismiss(뒤로가기 버튼)에서만
명시적으로 호출한다 — 이 화면은 여러 화면을 push하는 허브 화면이라 .onDisappear로 걸면
forward push마다 오발화한다(CollectionFeature/CLAUDE.md에 이미 같은 함정이 실제 회귀로
기록돼 있어 명시적 액션 패턴으로 감).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
시스템 푸시 권한이 denied면 알림 설정 시트를 열지 않고 기기 설정 유도 알럿(setAppNotification)만
띄운다(SettingFeature의 '알림 설정' 메뉴와 동일 판단 — 권한 없이 그 시트에 들어갈 이유가 없다,
사용자 확정). notDetermined면 시스템 프롬프트를 띄운 뒤 시트로 이동한다.

PushAuthorization 모듈을 NovelDetailFeature에 새로 의존시키고, Factory/Assembly/Demo
호출부에 pushAuthorizationChecker를 배선했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
…ordsUseCase로 리네임

LoadTotalKeywordsUseCase 프로토콜명과 구현 클래스명이 어긋나 있던 걸(Load↔Fetch) 다른
Default*UseCase 관례에 맞춰 통일했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
키워드 캐시가 비어있을 때(첫 설치 등) 서버와 재동기화한 뒤 재조회하는 정책을 Repository가
아니라 Domain(UseCase)이 갖도록 옮겼다 — Repository는 데이터 소스 접근만, 실패 시 정책은
UseCase가 정한다는 판단(사용자 확정).

ProfileDomain.LoadNovelPreferencesUseCase, NovelDomain.LoadNovelUseCase/LoadMyLibraryUseCase/
LoadUserLibraryUseCase 4개가 KeywordRepository를 직접 잡던 걸 BaseDomain.LoadTotalKeywordsUseCase
(캐시 미스 시 자체 재시도)를 통해 캐시를 읽도록 바꿨다. 조립 지점(App 7파일·Demo 4파일)과
테스트 4개(재시도로 인한 fetchKeywordsCallCount 변화 반영)도 함께 갱신.

이 변경으로 캐시 미스가 더 이상 화면을 통째로 빈 채로 만들지 않아, 관련 문서
(Domain/CLAUDE.md, BaseDomain/NovelDomain/ProfileDomain/LibraryFeature의 CLAUDE.md)도 갱신했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
앱이 뜰 때마다(세션 유무 무관) keywordRepository.syncKeywords()를 부르던 호출을 걷어냈다.

AppDependencies()는 최초 실행과 resetToOnboarding()(세션이 끝나는 경로)에만 재생성되고
로그인 시점엔 재생성되지 않아, 이 호출이 원래 근거로 들었던 '비로그인 런치 → 로그인' 캐시
프리로드 시나리오는 애초에 이 호출로 해결되지 않았다. 실제로는 세션이 있으면 부트스트랩의
동일 호출과 중복이고, 세션이 없으면(첫 설치·만료) /keywords가 requireToken이라 항상 401 +
재인증 시도만 낭비하는 것으로 확인됐다(실기기 로그로 실측). 이제 키워드 동기화는 부트스트랩의
세션 게이트를 통과했을 때만 실행된다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
타이틀 Text에 lineLimit·좌우 안전여백이 없어 자연 폭 그대로 렌더링되며
뒤로가기/우측 액션과 겹쳤다. lineLimit(1)+truncationMode(.tail)과 함께
좌우 동일한 sidePadding(뒤로가기 고정폭 vs trailing 실측 폭 중 큰 값)을
줘 ZStack 중앙 정렬을 유지하면서 말줄임되게 했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
SettingFeature.notificationMenuTapped와 동일 패턴(Action/State + Binding
경유)으로 맞춰 View→VM 단일 handle 입력 원칙을 지키고, 연타 시 이전
권한 조회를 취소하는 Task 관리와 화면 종료(close()) 시 취소, 닫히는
중 재진입을 막는 isClosing 가드까지 함께 정리했다(PR 리뷰 2·3라운드
반영).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
DefaultFetchTotalKeywordsUseCase가 DefaultLoadTotalKeywordsUseCase로
리네임됐는데 이 문서만 옛 이름이 남아있었다(PR 리뷰에서 발견).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdFp6C1YC8B5BffFCRtnMU
@Guryss Guryss self-assigned this Sep 12, 2026
@github-actions

Copy link
Copy Markdown

테스트 커버리지 리포트

Module Coverage
AuthData 55.3%
AuthDomain 98.2%
BaseDomain 70.3%
CollectionDomain 98.3%
CollectionFeature 0.0%
CommentData 0.0%
CommentDomain 94.9%
FeedData 62.8%
FeedDomain 98.7%
HomeFeature 0.0%
LibraryFeature 0.0%
NotificationData 33.9%
NotificationDomain 100.0%
NotificationFeature 0.0%
NovelData 15.4%
NovelDetailFeature 0.0%
NovelDomain 92.9%
NovelReviewData 77.4%
NovelReviewDomain 96.8%
NovelReviewFeature 5.1%
ProfileData 46.1%
ProfileDomain 97.1%
RecommendationData 54.7%
RecommendationDomain 91.7%
SearchData 5.1%
SearchDomain 100.0%
SettingData 31.1%
SettingDomain 99.0%
SocialData 53.4%
SocialDomain 87.5%
SplashData 77.1%
SplashDomain 95.5%
SplashFeature 32.9%

Generated by CI on 2026-09-12 18:19 KST

@Guryss
Guryss merged commit 83a690e into develop Sep 12, 2026
38 checks passed
@Guryss
Guryss deleted the Fix/#260 branch September 12, 2026 14:23
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.

[Fix] QA 수정사항 반영

1 participant