Skip to content

[Refactor] #256 - 피드 새로고침 경로 통일·피드 탭 상시 mount 보존 + 온보딩 QA·UI 마감 - #258

Merged
Naknakk merged 22 commits into
developfrom
Refactor/#256
Sep 9, 2026
Merged

Naknakk merged 22 commits into
developfrom
Refactor/#256

Conversation

@Naknakk

@Naknakk Naknakk commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

💡 Issue


💭 Summary

피드 최신화 전략을 앱 전역에서 하나로 통일했어요 — "재진입엔 다녀온 셀만 상세 API로 동기화, 새 글은 그 화면발 작성 성공 복귀 신호로만 리셋"이 소소피드와 작품 상세 피드 탭에 똑같이 적용돼요.

여기에 피드 서브탭 상시 mount(탭별 스크롤 보존), 온보딩 QA(#257 통합), UI 마감 수정 여러 건이 함께 실려 있어요.


🔑 Key Changes

피드 새로고침 통일 (핵심)

  • 소소피드·작품 상세 피드 탭 모두 재진입 시 목록을 다시 받지 않고, 다녀온 셀만 피드 상세 API로 동기화해요(삭제·숨김이면 셀 제거, 스크롤·목록 길이 보존).
  • 작품 상세의 "보던 개수만큼 window 재조회"(NovelFeedPageSizePolicy)는 새 글 유입 시 기존 글이 밀려나는 실결함이 있어 삭제했어요.
  • 새 글 반영은 피드 탭 연필 작성 성공 복귀 신호(탭 Root 로컬 Binding)로만 — 예전 앱 전역 신호(FeedListInvalidation)는 제거했어요.
  • 셀 동기화 대기열은 진행 중 동기화가 끝나는 즉시 이어서 소비해요(리뷰에서 잡힌 drain 누락 수정).
  • 피드 목록의 로드 실패 표현([Feat] 내 서재 재진입 시 보고 있던 개수만큼 갱신 #195 계약)도 함께 구현했어요 — 어느 로드 실패든 그 탭 콘텐츠 자리에 NetworkErrorView(재시도)가 뜨고, 좋아요·삭제·신고 실패는 토스트예요(예전엔 전부 무표시 — 아래 To Reviewer 참고).

피드 서브탭 상시 mount (2026-09-09 확정)

  • 내 피드↔소소피드 전환 시 재조회하지 않고, 두 리스트를 ZStack에 상시 mount해 탭별 스크롤 깊이까지 보존해요.
  • 로딩 표시는 loadingTab: FeedTab?으로 "어느 탭의 로드인지"를 들고 다녀요(공유 Bool이면 엉뚱한 탭에 스피너가 뜨는 문제 — 리뷰 반영).
  • 내 피드 빈 화면의 "피드 작성" CTA를 실제 작성 진입(onRoute(.createFeed))으로 배선했어요(죽은 버튼이었어요).

온보딩 QA (#257 통합)

  • 미가입 재로그인 시 재온보딩 게이트(isOnboardingCompleted 로컬 저장 + 부트스트랩 게이트, Domain 테스트 5개 포함).
  • 인트로 배너 캐러셀을 TabView → 순수 SwiftUI 페이저로 교체(실기기에서 selection 어긋나는 UIKit 브리지 버그 회피).
  • 로그인 취소 무토스트·SE 토스트 위치·장르 전환 슬라이드·키보드·로띠 1회 재생·닉네임 필드 포커스 유지 등 세부 QA.

UI 마감·기타

  • 반응형 네비바(작품·컬렉션 상세) 배경 히트테스트를 표시 상태와 연동 — 바에 가려진 콘텐츠로 탭이 관통하던 문제 수정.
  • 서재 매력포인트 정렬을 평가·필터와 통일(enum 선언 순서를 디자인 정본으로 승격 + 순서 고정 테스트 추가).
  • WSSNovelCoverImage.backdrop placeholder 신설 — 블러 아래 로딩 스피너가 회색 네모 잔상으로 보이던 문제 수정(홈 오늘의 발견·작품 상세 헤더).
  • 오늘의 발견 카드 눌림 효과를 카드 단위 균일 페이드로 교체(compositingGroup — 기본 스타일은 층별로 옅어져 표지만 도드라졌어요).
  • 작품 상세 네비바 종·threedots 버튼 터치영역을 우측 끝까지 확장했어요.
  • 완결·휴재 복귀 알림 목록 시작점에 상단 여백 8을 추가했어요.
  • 닫기 확인 알럿이 있는 3화면(피드 작성·작품 평가·컬렉션 만들기)의 스와이프 뒤로가기를 통일했어요 — 예전엔 조용히 막혀 "스와이프가 안 먹는 화면"이었는데, 이제 스와이프 시도를 감지해 back 버튼과 같은 확인 알럿을 띄워요(wssCustomNavigationBarswipeBackEnabled: BoolswipeBackConfirmation: (() -> Void)? 교체).
  • QueryItemConvertible 빈 배열은 쿼리 자체를 생략(서버는 미전송과 동일 취급).
  • App 최소버전 17.0 명시·Mac(Designed for iPad) 실행 대상 제거.
  • V1 동작 계약서 2개(Feed·NovelDetail)의 stale 판정 대거 갱신(추출 후 구현 완료된 항목들), docs/ARCHITECTURE.md SplashFeature 배선 서술 정정, [Fix] 작품 상세 — 피드 목록 변경 직후 화면 전환 시 무한 레이아웃 루프(라이브락) #239 라이브락 옛 결정론 조건(피드 3개+작성 복귀) 3회 재관찰 미재현 기록.

📱 Simulation

(첨부 예정)


🧑‍🧒‍🧒 To Reviewer

리뷰 수렴 내역 — wss-pr-reviewer(opus)·wss-feature-reviewer(opus)·Codex CLI(코드)·Codex CLI(문서 감사) 4자 병렬 리뷰를 돌렸고, 🔴 0·🟡 9건을 전부 반영/처리했어요(2라운드 delta 검증까지 완료).

결정이 필요한 것 1 → 해소 — 작성 복귀 리셋의 실패 갭 (2026-09-10 구현)

  • 이 화면의 로드 실패 표현([Feat] 내 서재 재진입 시 보고 있던 개수만큼 갱신 #195 계약)을 이번 PR에서 구현하는 것으로 결정·반영했어요 — 죽은 errorMessage를 탭별 RepositoryError?로 교체하고, 첫 페이지·더보기·당겨서 새로고침·작성 복귀 재로드 어느 실패든 그 탭 콘텐츠 자리에 NetworkErrorView(재시도)가 떠요.
  • 재시도는 그 탭을 비우고 처음부터 다시 세워요(NovelDetail.retryFeeds와 같은 결) — "아직 남긴 기록이 없어요" 빈 화면 트랩이 사라졌어요. 좋아요·삭제·신고 실패는 .networkDelay 토스트로 표현해요(역시 무표시였던 갭).
  • 남은 갭: 인증 만료 라우팅은 여전히 미배선이라 401도 "일시적 오류" 실패 뷰로 흡수돼요(예전 조용한 빈 화면보단 개선) — docs/TODO.md 12절에 후속 등재.

확인 요청 1 → 확인 완료 — 상세탐색 무필터 서버 회귀 없음

  • dev 서버(/novels/filtered)에 파라미터 생략 버전과 빈 값(?genres=&…) 버전을 각각 보내 비교했어요(2026-09-09).
  • 무필터: 두 방식 모두 resultCount 95,598·반환 목록 완전 동일 / 장르(romance)만: 두 방식 모두 38,944·목록 동일.
  • 서버가 둘을 동일 취급하는 게 확인돼 회귀 없음으로 닫아요(기록: Projects/Core/Networking/CLAUDE.md).

반영하지 않고 남긴 Nit (전부 리포트 원문 기준, 판단 필요하거나 무해)

  • 온보딩 인트로 페이저 selection 상한 클램프 — 도달 경로를 못 찾았고 실기기 검증된 코드라 재검증 없이 안 건드렸어요.
  • needs…ForCreatedFeed: Binding<Bool> = .constant(false) Factory 기본값 제거 — 새 조립 지점의 배선 누락을 컴파일러가 잡게 하려면 제거가 맞지만 API 변경이라 보류.
  • 작품 상세 리셋 신호에 novelID 미탑재(엉뚱한 상세가 소비해도 피해는 재조회 1회), 작성 복귀 직후 "0개의 기록" 순간 노출, cellTopYs 딕셔너리의 옛 ID 누적(무해), 상시 mount 렌더 비용(SE급 스크롤 프레임 확인 권장), tabButton 높이 44pt 미달, UnifiedFadeButtonStyle 두 번째 사용처 생기면 WSSComponent 승격.

※ Reference

🤖 Generated with Claude Code

Naknakk and others added 19 commits September 8, 2026 22:41
…g+초기 로드식 재로드로 통일

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbPWwtae6Rk1k1dZdC4s6k
- BaseData: StorageKey `.isRegistered`(Bool) 추가 — 키 없음=가입 완료로 간주(기존 로그인 유저 하위호환)
- AuthData: 로그인 성공 시 서버 `isRegister`를 로컬에 저장, 로그아웃/탈퇴/재로그인 세션 경계(clearUserScopedCache)에서 삭제
- ProfileData: `registerProfile` 서버 성공 뒤 `.isRegistered=true` 기록(서버 확정 후에만 로컬 갱신)
- SplashDomain: `LaunchGateRepository.isOnboardingCompleted()` 추가, 부트스트랩 게이트 2-1(세션 다음)로 미가입이면 `.intro` 반환
- SplashData: `DefaultLaunchGateRepository`가 AppStorage에서 `?? true`로 읽음(BaseData 의존 추가), Factory·App DI 배선
- 테스트: Bootstrap 게이트 5건·LaunchGate 3건·Auth 저장/삭제 4건·Profile 등록 기록 2건 추가(전부 green)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5LLGdzCkgZstkLt7m776m
- 인트로: 사용자 취소(Apple `.canceled`·Kakao `.Cancelled`)는 서버 오류와 구분해 토스트 미표시(`.loginCancelled` 액션 추가)
- 인트로: SE에서 토스트가 밀려나던 문제 수정 — GeometryReader(.ignoresSafeArea)로 배경 full-bleed·전경 세이프에어리어 패딩 분리, 토스트는 윈도우 바닥 기준 고정(1.5s 자동 닫힘은 공용 모디파이어와 동일 패턴)
- 장르 선택: 성별/출생년도 확정 시 lazy mount가 만들던 opacity 삽입 전환 제거 — GenreSelectionViewModel을 항상 mount하고 프로필 문맥은 `.setProfileContext`로 확정 시점에 주입, 닉네임→성별과 동일한 슬라이드로 통일
- 닉네임: "다음으로" 진행 시 키보드 내림(`isKeyboardFocused = false`)
- 완료: 로띠 `.playOnce`로 1회만 재생

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5LLGdzCkgZstkLt7m776m
- WSSNicknameField에 `treatsFilledAsFocused: Bool = false` opt-in 추가 — 켜면 값이 비어있지 않은 동안 포커스 시 모양(흰 배경+gray70 폴백 테두리)을 유지. 배경만 희게 하면 검증 테두리 없는 상태에서 흰 화면 위 필드 윤곽이 사라져 배경·테두리를 한 조건으로 묶음
- 온보딩 닉네임 화면만 켬 — MyPageEditView는 기본값(꺼짐) 유지(닉네임이 항상 채워져 있어 상시 흰 배경이 되고 소개글 필드와 톤이 갈라짐, 통일은 디자인 확인 후)
- 시뮬 실측: 입력 후 포커스 해제 → 흰 배경 유지 / 값 비우면 gray50 복귀

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5LLGdzCkgZstkLt7m776m
내 피드↔소소피드 전환 시 이미 세운 탭은 재조회하지 않고 캐시를 그대로
보여준다(selectTab이 hasLoaded면 생략). View는 두 탭 ScrollView를 ZStack에
상시 mount하고 보이는 쪽만 켠다 — if/else 갈아끼우기는 branch 이탈 시
UIScrollView가 파괴돼 스크롤이 리셋되기 때문. 파생 조치로 threedots 앵커
딕셔너리를 탭별로 분리(내 글은 같은 feedId가 양쪽에 있어 덮어씀 방지)하고,
.loadMore에 발화 탭을 실어 숨은 리스트 셀 실현이 현재 탭 페이지를 당기지
않게 가드했다. scrollIdentity는 탭 축을 빼고 자기 축만 남김.

시뮬레이터 실측 8항목(전환 0요청·탭별 깊이 보존·첫 전환만 로드·작성 복귀
리셋·축 전환은 그 탭만·앵커 분리·당겨서 새로고침·두 배열 동시 갱신) PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbPWwtae6Rk1k1dZdC4s6k
삭제·셀 동기화(notFound/forbidden)로 removeCell이 셀을 지워도
myFeedsTotalCount는 그대로라, 다 지운 뒤 헤더는 "4개의 기록"·본문은
빈 뷰인 모순이 남았다(탭 전환 검증 중 실측 발견 — 이번 상시 mount
변경과 무관한 기존 결함). myFeeds에 있던 셀을 지울 때 카운트를 함께
내린다. 로드된 범위만 판별 가능한 best-effort고 놓친 케이스는 당겨서
새로고침이 서버 값으로 복구한다.

시뮬 실측: 작성 2건(0→1→2개) 후 소소피드 크로스탭 삭제 → 내 피드
전환 요청 0건에 헤더 2→1, 마지막 삭제 후 "0개의 기록"+빈 뷰 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbPWwtae6Rk1k1dZdC4s6k
- #257 토스트 작업의 화면 전체 ignoresSafeArea 배선이 페이지형 TabView를 깨던 회귀(두 번째 페이지부터 상태바 높이만큼 솟구침·전환 멈춤) → 소거를 배경 레이어에만 한정, 토스트는 세이프에어리어 프레임 클램프 구조로 동일 위치 유지
- 레이아웃을 고쳐도 실기기에선 withAnimation 프로그램 전환이 선두 클론으로 애니메이션된 뒤 내부 페이지와 selection이 어긋나는 별개 결함 잔존(시뮬레이터 미재현) → ScrollView + scrollTargetBehavior(.paging) + scrollPosition(id:) 페이저로 교체(클론 패딩·자동 전환·클론 되감기 로직은 그대로)
- Feature·OnboardingFeature CLAUDE.md에 함정 기록(자동 캐러셀에 TabView(.page) 금지)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbPWwtae6Rk1k1dZdC4s6k
@Naknakk Naknakk self-assigned this Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

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.6%
HomeFeature 0.0%
Keychain 0.0%
LibraryFeature 0.0%
Networking 78.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 45.7%
ProfileDomain 97.1%
RecommendationData 54.7%
RecommendationDomain 91.7%
SearchData 5.1%
SearchDomain 100.0%
SettingData 31.1%
SettingDomain 99.0%
SocialData 46.4%
SocialDomain 87.5%
SplashData 77.1%
SplashDomain 95.5%
SplashFeature 32.9%

Generated by CI on 2026-09-10 01:49 KST

… 실패 토스트)

- 죽은 errorMessage를 탭별 myFeeds/sosoFeedsLoadError(RepositoryError?)로 교체,
  실패 시 그 탭 콘텐츠 자리를 NetworkErrorView(재시도)로 대체(실패를 목록보다 먼저 판단)
- 재시도(.retryLoad)는 그 탭을 비우고 reloadFromScratch — 작성 복귀 재로드 실패 시
  "아직 남긴 기록이 없어요" 빈 화면 트랩(감수 사항) 해소
- 좋아요·삭제·신고 실패는 .networkDelay 토스트 lane(FeedDetail과 동일)
- 인증 만료 라우팅은 여전히 미배선(실패 뷰로 흡수) — docs/TODO.md 12절에 잔여 갭 등재

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbPWwtae6Rk1k1dZdC4s6k
@Naknakk
Naknakk merged commit 360557e into develop Sep 9, 2026
38 checks passed
@Naknakk
Naknakk deleted the Refactor/#256 branch September 9, 2026 15:29
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.

[Refactor] 피드 목록 새로고침 경로 통일 - feedCreatedVersion 기구 제거

1 participant