feat: 홈 메인 + 네트워크 인프라 + 로그인→홈 진입 연결#33
Conversation
- Home/MainTab 모듈과 GNB 리소스를 앱 라우팅에 연결 - Auth 온보딩 완료와 Splash 토큰 보유 상태에 따라 MainTab 진입을 위임 처리 - AppReducer scope 라우팅을 상태 매칭 검증 후 handleScopeNavigation으로 분리 Rejected: Splash scope onAppear를 AppReducer에서 무시하는 방식 | 사용자가 해당 분기는 건드리지 말라고 요청함
- SwiftUI View의 Store 보유 방식은 @bindable 기준으로 통일 - public/private 가시성 기준과 금지 패턴을 AGENTS 가이드에 추가 Rejected: 로그인 홈 진입 커밋에 문서 변경을 섞는 방식 | 기능 커밋과 지침 변경을 분리하기 위해 제외
- 백엔드 합의 스펙 (`https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=...&redirect_uri=...`) 과 동일하게 정렬 - 기존 강제 로그인 프롬프트는 제거 (필요 시 후속 옵션화)
탭바 UIKit 스타일에서 직접 hex를 들고 있지 않도록 DesignSystem UIColor 토큰을 기준으로 맞춥니다. Rejected: MainTabView 로컬 hex enum 유지 | 디자인시스템 색상 정의와 중복됨
- Entity/Home: HeroBattle / HotBattle / BestBattle / QuizQuestion / VoteQuestion / NewBattle (각 mock 포함) - HomeFeature: 6개 Entity 보유 State + heroIndex 캐러셀 인덱스 + 더보기 액션 - HomeView: 상단 헤더 + ScrollView 안 5개 섹션 (캐러셀 / 지금 뜨는 배틀 / Best 배틀 / 오늘의 Pické / 새로운 배틀) — 헤더도 본문과 함께 스크롤 - Components: HomeHeaderView / HomeSectionHeader / MetaLabelView / TagBadgeView / HeroCarouselView (TabView paging + 3초 자동 스크롤 wrap-around) / HotBattleCardView / BestBattleCardView / QuizCardView / VoteCardView / NewBattleCardView - VoteCardView 는 Pencil .pen wZ4Yt 스펙(beige50 카드 / beige200 빈칸 / beige300 옵션 / 14pt 뱃지) 1:1 매핑 - 컬러/스트로크/배경/필 모두 디자인 토큰 점 단축형(.fill(.beige200) / .stroke(.beige700)) 사용
- KakaoOAuthRepository: OAuthWebPresenter.present 호출 시 usesEphemeralSession: true 전달 — 기존 카카오 로그인 쿠키로 자동 redirect 되어 폼이 뜨기 전에 닫히는 문제 해결 - OAuthWebViewController: usesEphemeralSession 파라미터(default false) 추가, 켜진 경우만 config.websiteDataStore = .nonPersistent() — Google 은 기존 SSO 유지 - finish(result:) 호출 시 webView.isHidden = true 로 가려서 401 응답 흰 화면 노출 방지 (Google/Kakao 공통) - OAuthWebPresenter.present 시그니처에도 usesEphemeralSession 파라미터 전파
- AppReducer.handleScopeNavigation: splash.delegate(.presentAuth) / .presentMainTab 수신 시 clock.sleep(for: .seconds(3)) 후 화면 전환 - splash.view(.onAppear) 자체는 더 이상 즉시 presentAuth 보내지 않음 (스플래시 내부에서 분기 결정) - SplashFeature 동기 정리 (스플래시 → 인증 분기 흐름 위임)
- ShapeStyle 을 받는 모든 modifier 에 점 단축형 사용 명시: .foregroundStyle(.neutral900) / .fill(.beige200) / .stroke(.beige700, lineWidth:) / .background(.beige50, in:) / .tint(.primary500) - Color 가 View 자체로 쓰여 메서드 체이닝을 받는 경우(Color.beige50.ignoresSafeArea() 등) 는 예외로 두는 규칙 추가 - 등록된 토큰 목록에 beige50…beige900 추가
- HeroCarouselView: TabView 의 frame(height:) 360 → 341 (.pen 합산: control 53 + thumbnail 167 + subject 121) - BestBattleCardView: padding(16) → padding(.vertical, 16) + padding(.horizontal, 12) (.pen 7UgWh padding [16, 12])
- ImageAssets.xcassets/Home/appLogo.imageset, bell.imageset (각 SVG) - ImageAsset enum 에 appLogo / bell 케이스 추가 (Home 카테고리)
- BattleTag · HomeBundle 신규 + 기존 Hero/Hot/Best/New/Quiz/Vote Entity 를 battleId · BattleTag[] · audioDuration 기준으로 정렬 - HomeInterface 프로토콜 + DependencyKey + Default/Mock 구현체 추가 — DomainInterface 계층에서 홈 API 사용처 통일
…pl) 구성 - HomeAPI · HomeService(BaseTargetType) · HomeDataDTO + Mapper · HomeRepositoryImpl 풀체인 추가 - PieckeDomain 에 case home 추가하여 baseURL 조립
- Extension+TargetDependencySPM 에 kingfisher 외부 의존성 등록 - Home 프레젠테이션 모듈이 Kingfisher 를 참조하도록 Project.swift 갱신 - AppDIManager 에서 HomeInterface ↔ HomeRepositoryImpl 바인딩
- HomeFeature: Result + mapError(AuthError.from) 단일 Response 패턴으로 fetchHome 비동기 액션 구성, State 는 inline default + 빈 init() 으로 정리 - HeroCarousel · HotBattle · NewBattle 카드에 Kingfisher KFImage 적용, HomeView 스켈레톤/skipping 로직 정리
- Optional + nil 합치기 + MoyaProviderPool 인다이렉션 제거 - init 기본값으로 MoyaProvider<AuthService>.default / .authorized 를 그대로 사용해 호출처 노이즈 제거
…lt+mapError 규칙 명시
- RepositoryImpl 의 MoyaProvider 는 let + .default / .authorized 팩토리 기본값 직접 주입 (Pool / Optional 인다이렉션 금지)
- TCA State 는 inline default + 빈 public init() {} 패턴
- AsyncAction 은 Result { try await } + mapError + 단일 Response InnerAction
refresh API가 토큰을 body가 아닌 X-Refresh-Token 헤더로 요구해 인증 갱신 요청 형식을 서버 계약에 맞춘다.
홈 진입 시 onAppear 중복 호출을 막고, 서버가 빈 섹션을 내려줄 때 화면 검수용 mock 데이터를 채워 홈 UI가 비어 보이지 않게 한다. Rejected: View 내부 중복 onAppear 유지 | 로딩 완료 뒤 다시 fetchHome이 반복됨
홈 스켈레톤을 HomeSkeletonView로 분리하고 attendance-ios 방식의 shimmer overlay를 공통 블록에 적용한다. Rejected: 정적 스켈레톤 유지 | 로딩 상태 피드백이 부족함
TuistSpider 산출물을 docs/graphs 아래로 정리하고 README에서 Grouped/Expanded 그래프를 보여준다. Service 모듈은 DTO Model을 직접 쓰지 않으므로 불필요한 Model 의존성도 제거한다. Rejected: 루트 PNG 유지 | 프로젝트 루트가 산출물로 지저분해짐
README의 프로젝트 트리와 의존성 방향 설명을 Home/MainTab 및 Home 데이터 모듈 추가 상태에 맞춘다.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc3fab617c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| state.hasLoadedHome = true | ||
| switch result { | ||
| case let .success(bundle): | ||
| let home = bundle.replacingEmptySectionsWithMocks |
There was a problem hiding this comment.
Remove mock substitution from successful home payloads
Applying bundle.replacingEmptySectionsWithMocks in the success path causes the app to render fabricated battles/quizzes whenever the backend intentionally returns an empty section, so users can see data that never came from the server. This is a production data-integrity regression; successful responses should be rendered as-is, with empty-state UI handled in the view layer instead of mutating domain data.
Useful? React with 👍 / 👎.
| switch action { | ||
| case let .homeResponse(result): | ||
| state.isLoading = false | ||
| state.hasLoadedHome = true |
There was a problem hiding this comment.
Mark home as loaded only after a successful fetch
hasLoadedHome is set to true before checking whether the fetch succeeded, but .onAppear refuses to fetch when this flag is true. If the first request fails (e.g., transient network/auth issue), revisiting the screen will no longer retry automatically, leaving the user stuck with stale/empty content unless they manually pull to refresh.
Useful? React with 👍 / 👎.
요약
GET /api/v1/home연동, Kingfisher KFImage 적용let + 팩토리 기본값(.default / .authorized) 직접 주입패턴으로 통일, refresh token 헤더 전송 방식 보정, 모듈 그래프 / README 최신화닫을 이슈 (Closes)
Closes #19— [Network] API 클라이언트 인프라 셋업 (Provider 풀체인 + Repository 패턴 통일 완료)Closes #32— [Auth] 로그인 성공 → 홈 화면 진입 흐름부분 완료 (Refs — 후속 PR 필요)
Refs #3— 홈 메인은 완료, 큐레이팅 진입·소진 / 알림 받기 모달은 미구현Refs #18— 홈 스켈레톤만 적용, 다른 화면 스켈레톤은 후속Refs #20— Kakao OAuth ephemeral 세션 / authorize URL 정리 일부Refs #2— 온보딩 일러스트 PNG → SVG 교체 일부테스트 플랜
onAppear시GET /api/v1/home호출 + 스켈레톤 → 데이터 전환 확인