Skip to content

Design/#33 내여행탭#36

Merged
KimNahun merged 23 commits into
developfrom
design/#33-내여행탭
Feb 23, 2026

Hidden character warning

The head ref may contain hidden characters: "design/#33-\ub0b4\uc5ec\ud589\ud0ed"
Merged

Design/#33 내여행탭#36
KimNahun merged 23 commits into
developfrom
design/#33-내여행탭

Conversation

@ChoiAnYong
Copy link
Copy Markdown
Contributor

@ChoiAnYong ChoiAnYong commented Feb 23, 2026

🔗 연결된 이슈

📄 작업 내용

  • 내 여행 탭 UI 구현: RIBs 아키텍처 기반의 내 여행(MyTravel) 메인 화면 구현 및 배너 영역 구성
  • UserManager 구현: Core 모듈 내 UserDefaultsWrapper를 활용한 사용자 세션 및 상태 관리 로직 구현
  • 서비스 이용 안내 모달 구현: 앱 최초 실행 시 노출되는 서비스 이용 고지 및 AI 기술 활용 안내 모달

💻 주요 코드 설명

UserManager.swift

  • UserDefaultsWrapper를 통해 isFirstOpen, nickname 등 사용자 설정 데이터를 프로퍼티 래퍼 형식으로 간결하게 관리하도록 구현했습니다.
  • Core 모듈에 위치시켜 모든 Feature 모듈에서 공통적으로 사용자 상태에 접근할 수 있도록 설계했습니다.
import Foundation

public enum UserDefaultKeys: String {
    case uuid
    case nickname
    case isFirstOpen
}

public final class UserManager {
    @UserDefaultWrapper(key: .uuid) public var uuid: String?
    @UserDefaultWrapper(key: .nickname) public var nickname: String?
    @UserDefaultWrapper(key: .isFirstOpen) private var isFirstOpen: Bool?
    
    public static let shared = UserManager()
    
    private init() {}
    
    public func isFirstOpenApp() -> Bool {
        guard let isFirstOpen else {
            self.isFirstOpen = true
            return true
        }
        return !isFirstOpen
    }
}

// 1. 데이터 저장 및 조회
// UserManager.shared.nickname = "여행자"
// print(UserManager.shared.nickname) // "여행자"

// 2. 앱 최초 실행 여부 확인 및 로직 분기
// MainInteractor.swift 등에서 활용
// if UserManager.shared.isFirstOpenApp() {
     // 최초 실행일 때만 실행되는 로직 (예: 모달 띄우기)
     // presenter.showServiceNoticeModal()
// }

ServiceNoticeViewController.swift

  • 앱 최초 실행 시 AI 서비스 특성상 필요한 고지 사항을 전달하기 위해 overFullScreen 타입의 모달을 구현했습니다.

Summary by CodeRabbit

출시 노트

  • 새로운 기능

    • MyTravel 기능으로 업그레이드되어 예정된 여행 및 추천 여행을 한 곳에서 확인 가능합니다.
    • 첫 앱 사용 시 서비스 안내 모달이 표시됩니다.
    • 한국식 날짜 형식으로 여행 일정을 더 명확하게 표시합니다.
    • 사용자 정보 저장 및 관리 기능이 개선되었습니다.
  • 버그 수정

    • 홈 화면 데이터 로드 시점을 개선했습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 23, 2026

🎯 요약

Walkthrough

Travel 기능을 MyTravel로 완전히 교체하면서 새로운 도메인 모델, UseCase, Repository 메서드, UI 컴포넌트, 저장소 관리 레이어를 추가했습니다. 기존 Travel Feature의 모든 파일을 삭제하고 MyTravel Feature로 재구현하며, 의존성 그래프 전체에서 참조를 업데이트했습니다.

Changes

Cohort / File(s) Summary
Feature 의존성 정의
Plugins/DependencyPlugin/ProjectDescriptionHelpers/Dependency+Project.swift, Projects/Features/TabBarFeature/Project.swift
Travel 기능을 MyTravel로 교체하여 공개 Feature 정의 업데이트 및 TabBar의 기능 의존성 재지정
도메인 모델 추가
Projects/Domain/Sources/Model/Home/UpcomingInfo.swift
여행 예정 정보를 나타내는 새로운 공개 도메인 모델 추가 (id, title, country, city, startDate, endDate, nights, days, templateId, thumbnail, profileImage)
Repository 인터페이스 확장
Projects/Domain/Sources/Interface/UserTravel/UserTravelRepositoryInterface.swift
페이지네이션이 포함된 여행 목록 조회 메서드 fetchUpcomingList 추가
MyTravel UseCase 구현
Projects/Domain/Sources/UseCase/MyTravelUsecase.swift
MyTravelUsecaseProtocol 및 구현체로, fetchMyTripInfo, fetchUpcomingList, fetchRecommendTripList 세 가지 비동기 작업 제공
데이터 변환 계층
Projects/Data/Sources/Transform/UserTravelTransform.swift
UpcomingListResponse를 [UpcomingInfo] 도메인 배열로 변환하는 toDomain() 확장 메서드 추가
Repository 구현
Projects/Data/Sources/Repository/UserTravel/UserTravelRepository.swift
fetchUpcomingList 메서드 구현으로 서비스에서 조회 후 도메인 변환
네트워크 DTO 및 API
Projects/Modules/Networks/Sources/DTO/Home/UpcomingListResponse.swift, Projects/Modules/Networks/Sources/DTO/Home/UserContentCardResponse.swift, Projects/Modules/Networks/Sources/Service/UserTravelService.swift, Projects/Modules/Networks/Sources/TargetType/UserTravelAPI.swift
여행 목록 조회 및 콘텐츠 카드 조회를 위한 DTO, 서비스 메서드, API 엔드포인트 추가
저장소 및 유틸리티
Projects/Core/Sources/Storage/UserDefaultWrapper.swift, Projects/Core/Sources/Storage/UserManager.swift, Projects/Core/Sources/Extensions/Foundation+/Date+.swift, Projects/Core/Sources/Extensions/Foundation+/String+.swift
사용자 기본값 래퍼, 싱글톤 UserManager (uuid, nickname, isFirstOpen), 날짜 포맷팅 및 파싱 유틸리티 추가
MyTravel Feature 핵심 구조
Projects/Features/MyTravelFeature/Project.swift, Projects/Features/MyTravelFeature/Sources/MyTravelBuilder.swift, Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift, Projects/Features/MyTravelFeature/Sources/MyTravelRouter.swift, Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift
RIBs 패턴으로 구현된 MyTravel 피처의 빌더, 상호작용자, 라우터, 뷰 컨트롤러
MyTravel 프리젠테이션 모델
Projects/Features/MyTravelFeature/Sources/Models/MyTravelPresentationModel.swift
Banner, Schedule, RecommendedTrip, Upcoming 중첩 구조를 가진 프리젠테이션 모델 및 도메인 매핑 확장
MyTravel UI 컴포넌트
Projects/Features/MyTravelFeature/Sources/Views/Cells/MyTravelBannerCell.swift, Projects/Features/MyTravelFeature/Sources/Views/Cells/UpcomingCell.swift, Projects/Features/MyTravelFeature/Sources/Views/Cells/MyTravelHeaderView.swift, Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Item/MyTravelItem.swift, Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift
컬렉션 뷰 셀, 헤더, 아이템 정의, 섹션 종류 정의
MyTravel 레이아웃 및 등록
Projects/Features/MyTravelFeature/Sources/Views/CollectionView/CompositionalLayout/MyTravelCompositionalLayout.swift, Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift
컬렉션 뷰 컴포지셔널 레이아웃 및 셀/헤더 등록 설정
Home Feature 업데이트
Projects/Features/HomeFeature/Sources/HomeViewController.swift, Projects/Features/HomeFeature/Sources/HomeInteractor.swift, Projects/Features/HomeFeature/Sources/Models/HomePresentationModel.swift, Projects/Features/HomeFeature/Sources/Views/CollectionView/Registration/HomeRegistration.swift, Projects/Features/HomeFeature/Sources/Views/CollectionView/SectionKind/HomeSectionKind.swift
viewDidLoad 라이프사이클 변경, toKoreanMMdd 제거, RecommendInfoCell.configure 시그니처 변경, UserManager 사용
Main Feature 업데이트
Projects/Features/MainFeature/Sources/MainBuilder.swift, Projects/Features/MainFeature/Sources/MainInteractor.swift, Projects/Features/MainFeature/Sources/MainViewController.swift, Projects/Features/MainFeature/Sources/VC/ServiceNoticeViewController.swift
myTravelUsecase 의존성 추가, 첫 실행 서비스 공지 모달 기능 추가
TabBar Feature 업데이트
Projects/Features/TabBarFeature/Sources/TabBarBuilder.swift, Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift, Projects/Features/TabBarFeature/Sources/TabBarRouter.swift, Projects/Features/TabBarFeature/Sources/TabBarViewController.swift
Travel에서 MyTravel로 빌더, 라우터, 리스너 완전 교체
Root 및 App 컴포넌트
Projects/Features/RootFeature/Sources/RootBuilder.swift, Projects/App/Sources/Application/AppComponent.swift
myTravelUsecase 의존성 및 인스턴스 추가
설정 Feature 업데이트
Projects/Features/SettingFeature/Sources/SettingViewController.swift
UserDefaults 대신 UserManager.shared.uuid 사용
기존 Travel Feature 제거
Projects/Features/TravelFeature/Sources/TravelBuilder.swift, Projects/Features/TravelFeature/Sources/TravelInteractor.swift, Projects/Features/TravelFeature/Sources/TravelRouter.swift, Projects/Features/TravelFeature/Sources/TravelViewController.swift, Projects/Features/TravelFeature/Sources/Model/UpcomingTrip.swift, Projects/Features/TravelFeature/Sources/Views/UpcomingTripCell.swift
Travel Feature의 모든 파일 삭제 (Builder, Interactor, Router, ViewController, 데이터 모델, 셀)
DSKit 모듈 업데이트
Projects/Modules/DSKit/Sources/Component/EmptyUpcomingCell.swift, Projects/Modules/DSKit/Sources/Component/RecommendInfoCell.swift
EmptyUpcomingCell 새로 추가, RecommendInfoCell을 공개로 변경 및 configure 시그니처 리팩토링

Sequence Diagram

sequenceDiagram
    participant User
    participant MyTravelVC as MyTravelViewController
    participant MyTravelInteractor
    participant MyTravelUsecase
    participant UserTravelRepository
    participant TravelTemplateRepository
    participant Network as UserTravelService

    User->>MyTravelVC: viewDidLoad
    activate MyTravelVC
    MyTravelVC->>MyTravelInteractor: viewWillAppear()
    deactivate MyTravelVC

    activate MyTravelInteractor
    MyTravelInteractor->>MyTravelInteractor: fetchMyTravelData()
    MyTravelInteractor->>MyTravelVC: setLoading(true)
    activate MyTravelVC
    deactivate MyTravelVC

    par 병렬 조회
        MyTravelInteractor->>MyTravelUsecase: fetchMyTripInfo()
        activate MyTravelUsecase
        MyTravelUsecase->>UserTravelRepository: fetchUpcoming()
        activate UserTravelRepository
        UserTravelRepository->>Network: getUpcoming()
        activate Network
        Network-->>UserTravelRepository: MyTripSummary
        deactivate Network
        UserTravelRepository-->>MyTravelUsecase: MyTripSummary
        deactivate UserTravelRepository
        MyTravelUsecase-->>MyTravelInteractor: MyTripSummary
        deactivate MyTravelUsecase

        MyTravelInteractor->>MyTravelUsecase: fetchUpcomingList()
        activate MyTravelUsecase
        MyTravelUsecase->>UserTravelRepository: fetchUpcomingList(page:size:)
        activate UserTravelRepository
        UserTravelRepository->>Network: getUpcomingList()
        activate Network
        Network-->>UserTravelRepository: [UpcomingInfo]
        deactivate Network
        UserTravelRepository-->>MyTravelUsecase: [UpcomingInfo]
        deactivate UserTravelRepository
        MyTravelUsecase-->>MyTravelInteractor: [UpcomingInfo]
        deactivate MyTravelUsecase

        MyTravelInteractor->>MyTravelUsecase: fetchRecommendTripList()
        activate MyTravelUsecase
        MyTravelUsecase->>TravelTemplateRepository: fetchRecommendTripList(page:size:)
        activate TravelTemplateRepository
        TravelTemplateRepository->>Network: getRecommendTripList()
        activate Network
        Network-->>TravelTemplateRepository: [TripInfo]
        deactivate Network
        TravelTemplateRepository-->>MyTravelUsecase: [TripInfo]
        deactivate TravelTemplateRepository
        MyTravelUsecase-->>MyTravelInteractor: [TripInfo]
        deactivate MyTravelUsecase
    end

    MyTravelInteractor->>MyTravelInteractor: 섹션 구성
    MyTravelInteractor->>MyTravelVC: update(with sections:)
    activate MyTravelVC
    deactivate MyTravelVC
    MyTravelInteractor->>MyTravelVC: setLoading(false)
    activate MyTravelVC
    MyTravelVC->>User: 화면 렌더링
    deactivate MyTravelVC
    deactivate MyTravelInteractor
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 여행의 꿈을 모아 담고
스탭들이 춤을 춘다 한 발씩
내 여행의 이야기 시작되네
배너와 셀들이 손을 잡고
새로운 탭이 반짝인다 ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 일부 변경 사항이 #33의 직접적 범위를 벗어납니다. 레거시 Travel 기능 제거, UserManager/UserDefaultWrapper 구현, ServiceNoticeViewController 구현은 추가 요구사항이며, Home 기능 수정도 scope 외입니다. 이슈 #33은 MyTravel 탭 UI 구현에만 집중하므로, 나머지 변경 사항(사용자 관리, 서비스 모달, Home 수정, Travel 제거)은 별도의 명확한 이슈나 PR로 분리하는 것을 권장합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '내여행탭'은 변경 사항의 주요 내용(MyTravel 탭 UI 구현)을 명확하게 반영하고 있습니다.
Description check ✅ Passed PR 설명은 제공된 템플릿의 주요 섹션(연결된 이슈, 작업 내용, 주요 코드 설명)을 포함하고 있으며 구현 내용이 충분히 설명되어 있습니다.
Linked Issues check ✅ Passed 변경 사항들은 #33 이슈의 내 여행 탭 UI 구현 요구사항을 충족합니다. MyTravel RIBs 구조 구현, UI 컴포넌트(배너, 예정 여행), 데이터 연동 등이 포함되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch design/#33-내여행탭

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Projects/Features/RootFeature/Sources/RootInteractor.swift (1)

67-100: ⚠️ Potential issue | 🟠 Major

인증 실패 시 앱이 무응답 상태에 빠질 수 있습니다.

performAuthFlow에서 에러 발생 시 print만 수행하고 아무런 복구 로직이 없습니다. 인증 실패 시 router?.attachMain()이 호출되지 않아 사용자는 빈 화면에 머물게 됩니다. 또한, willResignActive에서 Task를 취소하지 않아 RIB 비활성화 후에도 비동기 작업이 계속 실행될 수 있습니다.

🛠️ Task 취소 및 에러 핸들링 개선 제안
+    private var authTask: Task<Void, Never>?
+
     private func performAuthFlow() {
-        Task { [weak self] in
+        authTask = Task { [weak self] in
             guard let self else { return }
             // ... existing code ...
             } catch {
                 print("[RootInteractor] Auth flow failed: \(error)")
+                // TODO: 에러 화면 표시 또는 재시도 로직 추가
             }
         }
     }
+
+    override func willResignActive() {
+        super.willResignActive()
+        authTask?.cancel()
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/RootFeature/Sources/RootInteractor.swift` around lines 67 -
100, performAuthFlow currently spawns an untracked Task and only prints errors,
which can leave the UI empty on failure and allows the work to continue after
RIB deactivation; change performAuthFlow to store the Task in a property (e.g.,
authTask) so it can be cancelled from willResignActive, add cancellation checks
or handle Task.isCancelled inside the Task, and improve error handling by
ensuring a fallback UI flow is always invoked (e.g., call router?.attachMain()
or an error/placeholder routing path) in the catch path or via a defer so the
app never remains blank; also replace the plain print with proper logging.
Reference: performAuthFlow, router?.attachMain(), willResignActive, and add a
stored Task property (authTask) to manage cancellation.
🧹 Nitpick comments (27)
Projects/Core/Sources/Extensions/Foundation+/Date+.swift (1)

12-17: DateFormatter 인스턴스를 매 호출마다 생성하지 않도록 static 캐싱 권장

DateFormatter 초기화 비용이 높습니다. 셀 표시 등 반복 호출 경로에서 매번 생성하면 불필요한 오버헤드가 발생합니다. UI(메인 스레드) 전용 유틸리티이므로 static let으로 캐싱해도 안전합니다.

참고: DateFormatter는 스레드 안전하지 않으므로, 백그라운드 스레드에서 호출되는 경우엔 별도 처리가 필요합니다.

♻️ static 캐싱 적용 제안
 public extension Date {
+    private static let koreanMMddFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.locale = Locale(identifier: "ko_KR")
+        formatter.dateFormat = "M월 d일"
+        return formatter
+    }()
+
     func toKoreanMMdd() -> String {
-        let formatter = DateFormatter()
-        formatter.locale = Locale(identifier: "ko_KR")
-        formatter.dateFormat = "M월 d일"
-        return formatter.string(from: self)
+        return Self.koreanMMddFormatter.string(from: self)
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Extensions/Foundation`+/Date+.swift around lines 12 -
17, The toKoreanMMdd() function creates a new DateFormatter on every call;
change it to use a cached static DateFormatter (e.g. a static let
koreanMMddFormatter) configured with locale "ko_KR" and dateFormat "M월 d일" and
return koreanMMddFormatter.string(from: self) inside toKoreanMMdd(); if this
utility may be called off the main thread, either document that it’s
main-thread-only or create/return a thread-confined formatter per thread (or use
a DateFormatter factory) to avoid thread-safety issues.
Projects/Features/SettingFeature/Sources/SettingViewController.swift (1)

142-146: 주석 처리된 코드 정리 권장

주석 코드가 남아 있으면 유지보수 시 혼란을 줄 수 있습니다. 해당 로직이 불필요하다면 제거하고, 추후 필요하면 버전 관리(Git)에서 복원하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SettingFeature/Sources/SettingViewController.swift` around
lines 142 - 146, Remove the commented-out conditional that checks `menu ==
.notification` and the alternate `cell.separatorInset` assignment in
SettingViewController.swift; keep only the active line setting
`cell.separatorInset = UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right:
24.adjusted)` and delete the commented block referencing `.notification` so the
codebase is cleaner (the removed logic can be recovered from Git history if
needed).
Projects/Core/Sources/Storage/UserManager.swift (2)

11-15: SwiftLint: redundant_string_enum_value — rawValue 생략 가능

case 이름과 동일한 rawValue는 Swift에서 자동 합성되므로 명시적 선언이 불필요합니다.

♻️ 중복 rawValue 제거 제안
 public enum UserDefaultKeys: String {
-    case uuid = "uuid"
-    case nickname = "nickname"
-    case isFirstOpen = "isFirstOpen"
+    case uuid
+    case nickname
+    case isFirstOpen
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Storage/UserManager.swift` around lines 11 - 15, The
enum UserDefaultKeys currently specifies raw string values identical to the case
names; update UserDefaultKeys (the enum and its cases uuid, nickname,
isFirstOpen) to omit the redundant rawValue literals so Swift auto-synthesizes
them (e.g. declare the cases without = "..."), keeping the enum conforming to
String.

26-32: 코드 명확성 개선 제안 — 로직은 정상이나 가독성 향상 가능

현재 구현은 첫 실행 감지 로직으로 정상 작동합니다. 첫 호출 시 isFirstOpennil이므로 true로 설정 후 true를 반환하고, 이후 호출 시는 !isFirstOpen으로 false를 반환합니다. 다만 self.isFirstOpen = true로 저장한 뒤 !isFirstOpen을 반환하는 방식은 의도와 코드가 직관적으로 연결되지 않아 가독성을 해칩니다.

제안된 리팩토링은 이 명확성을 개선합니다:

♻️ 개선된 구현
 public func isFirstOpenApp() -> Bool {
-    guard let isFirstOpen else {
-        self.isFirstOpen = true
-        return true
-    }
-    return !isFirstOpen
+    guard isFirstOpen == nil else { return false }
+    self.isFirstOpen = true
+    return true
 }

변경된 방식은 "아직 설정되지 않았으면 true 저장 후 반환, 이미 설정되었으면 false 반환"이라는 의도를 더 명확하게 표현합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Storage/UserManager.swift` around lines 26 - 32, 현재
isFirstOpenApp()의 논리는 동작하지만 self.isFirstOpen = true 후 반환값을 !isFirstOpen으로 처리해
의도가 직관적이지 않습니다; isFirstOpenApp() 내에서 옵셔널 바인딩 실패 시 self.isFirstOpen을 true로 설정하고
즉시 true를 반환하고, 옵셔널이 존재할 경우는 false를 반환하도록 리팩토링해 가독성을 개선하세요 (참조 심볼:
isFirstOpenApp(), isFirstOpen).
Projects/Features/MyTravelFeature/Project.swift (1)

9-11: SwiftLint: sorted_imports 경고 — import 순서 조정 필요

현재 import 순서가 알파벳 정렬을 따르지 않습니다.

♻️ 정렬 수정 제안
-import ProjectDescription
-import ProjectDescriptionHelpers
-import DependencyPlugin
+import DependencyPlugin
+import ProjectDescription
+import ProjectDescriptionHelpers
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Project.swift` around lines 9 - 11, The
imports at the top of Project.swift are not alphabetically sorted, triggering
SwiftLint's sorted_imports rule; reorder the two import statements so they
follow alphabetical order (import DependencyPlugin before import
ProjectDescriptionHelpers) to satisfy the lint rule and keep imports
consistently sorted.
Projects/Core/Sources/Storage/UserDefaultWrapper.swift (1)

13-15: SwiftLint: implicit_return — getter의 명시적 return 생략 가능

♻️ 암묵적 반환으로 변경 제안
     get {
-        return UserDefaults.standard.object(forKey: self.key.rawValue) as? T
+        UserDefaults.standard.object(forKey: self.key.rawValue) as? T
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Storage/UserDefaultWrapper.swift` around lines 13 - 15,
The getter in UserDefaultWrapper's computed property uses an explicit "return"
that SwiftLint flags; change the get block to use implicit return by replacing
"get { return UserDefaults.standard.object(forKey: self.key.rawValue) as? T }"
with "get { UserDefaults.standard.object(forKey: self.key.rawValue) as? T }"
(reference the computed property in UserDefaultWrapper that accesses self.key
and UserDefaults.standard).
Projects/Modules/DSKit/Sources/Component/RecommendInfoCell.swift (2)

58-75: configure 메서드의 파라미터가 6개로 SwiftLint 규칙(5개 이하)을 초과합니다.

설정 구조체를 도입하면 가독성과 유지보수성이 개선됩니다.

♻️ 구조체 도입 제안
+struct RecommendInfoCellModel {
+    let title: String
+    let thumbnailUrl: String
+    let countryCode: String
+    let creator: String
+    let city: String
+    let schedule: String
+}
+
-    public func configure(
-        title: String,
-        thumbnailUrl: String,
-        countryCode: String,
-        creator: String,
-        city: String,
-        schedule: String
-    ) {
+    public func configure(with model: RecommendInfoCellModel) {
+        let title = model.title
+        let thumbnailUrl = model.thumbnailUrl
+        let countryCode = model.countryCode
+        let creator = model.creator
+        let city = model.city
+        let schedule = model.schedule
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/DSKit/Sources/Component/RecommendInfoCell.swift` around
lines 58 - 75, The
configure(title:thumbnailUrl:countryCode:creator:city:schedule:) method on
RecommendInfoCell exceeds the SwiftLint 5-parameter rule; refactor by
introducing a single configuration struct (e.g., RecommendInfo or
RecommendInfoViewModel) that contains title, thumbnailUrl, countryCode, creator,
city, and schedule, update the Configure signature to accept that struct (e.g.,
configure(with: RecommendInfo)), and update all call sites to build and pass the
struct; ensure internal usage maps struct properties to
thumbnailView.kf.setImage, nationalFlagLabel, nationLabel, titleLabel,
nameLabel, cityLabel and scheduleLabel as before.

78-122: 일부 spacing/cornerRadius 값이 스케일링 확장 없이 하드코딩되어 있습니다.

Line 81(cornerRadius = 8), Line 107/113/120(spacing = 4)에서 .adjusted/.adjustedH 없이 하드코딩된 값이 사용되고 있어 다양한 화면 크기에서 일관성이 깨질 수 있습니다.

Based on learnings: "In the NDGL iOS design system, ensure all components use the defined scaling extensions: adjustedH scales by height ratio, and adjustedW and adjusted scale by width ratio."

♻️ 스케일링 확장 적용 제안
-            $0.layer.cornerRadius = 8
+            $0.layer.cornerRadius = 8.adjusted

-            $0.spacing = 4
+            $0.spacing = 4.adjusted

-            $0.spacing = 4
+            $0.spacing = 4.adjusted

-            $0.spacing = 4
+            $0.spacing = 4.adjustedH
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/DSKit/Sources/Component/RecommendInfoCell.swift` around
lines 78 - 122, The setStyle() function uses hardcoded geometry; replace the
literal values with the project scaling extensions: change
thumbnailView.layer.cornerRadius = 8 to use a scaled value (e.g. 8.adjusted),
and change the stack view spacing assignments (nationStackView.spacing,
subInfoStackView.spacing, infoStackView.spacing) from 4 to scaled values (e.g.
4.adjusted or 4.adjustedH depending on whether you want width- or height-based
scaling), keeping use of .adjustedH where vertical scaling is intended; update
these in the RecommendInfoCell.setStyle() method referencing thumbnailView,
nationStackView, subInfoStackView, and infoStackView.
Projects/Features/MainFeature/Sources/MainBuilder.swift (1)

26-26: SwiftLint: 쉼표 뒤 공백 누락.

PopularTravelDependency,SearchDependency 사이에 공백이 없습니다 (SwiftLint comma 규칙). 기존 코드이지만 이번 변경과 함께 수정하면 좋겠습니다.

♻️ 수정 제안
-final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency,SearchDependency, SettingDependency, TabBarDependency {
+final class MainComponent: Component<MainDependency>, FollowDetailDependency, PopularTravelDependency, SearchDependency, SettingDependency, TabBarDependency {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MainFeature/Sources/MainBuilder.swift` at line 26, The
dependency list in the MainComponent declaration has a missing space after a
comma between PopularTravelDependency and SearchDependency; update the class
declaration (final class MainComponent: Component<MainDependency>,
FollowDetailDependency, PopularTravelDependency,SearchDependency,
SettingDependency, TabBarDependency) to insert a space after the comma so it
reads ..., PopularTravelDependency, SearchDependency, ... to satisfy SwiftLint's
comma rule.
Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift (1)

96-96: // MARK: - MyTravelListener 주석 누락

HomeListener, TravelToolListener 확장에는 모두 MARK 주석이 있으나 이 확장만 빠져 있습니다.

♻️ 제안
+// MARK: - MyTravelListener
+
 extension TabBarInteractor: MyTravelListener {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift` at line 96,
Add the missing section comment by inserting a "// MARK: - MyTravelListener"
comment immediately above the TabBarInteractor extension declaration for
MyTravelListener (extension TabBarInteractor: MyTravelListener) so it matches
the existing MARK comments used for HomeListener and TravelToolListener and
keeps the file's sectioning consistent.
Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift (1)

16-24: 하드코딩된 한국어 문자열 — 현지화 고려

"다가오는 여행", "추천하는 따라가기 여행" 등의 UI 문자열이 소스에 직접 포함되어 있습니다. 향후 다국어 지원을 고려한다면 NSLocalizedString으로 추출하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift`
around lines 16 - 24, The headerTitle computed property currently returns
hardcoded Korean strings; update it to use NSLocalizedString (or a String
extension/localization helper) instead of literals so UI text is localized:
replace the direct returns in headerTitle (for cases .upcomingTrips and
.recommendedTrip) with NSLocalizedString calls using clear keys (e.g.
"section.upcoming_trips.title", "section.recommended_trip.title") and provide
those keys in Localizable.strings; keep the same enum/case names and behavior
(headerTitle) and ensure the default still returns an empty localized string if
needed.
Projects/Domain/Sources/Model/Home/UpcomingInfo.swift (1)

11-49: public init 명시적 제공은 올바르나, 파일 위치 일관성 확인 권장

public struct의 멤버와이즈 이니셜라이저는 Swift에서 internal로 생성되므로 public init을 명시적으로 제공한 것은 정확합니다. 다만 이 파일도 Domain/Sources/Model/Home/에 위치하고 있는데, MyTravel 도메인 모델이라면 UserContentCardResponse와 마찬가지로 적절한 MyTravel 하위 경로에 두는 것이 일관성에 맞습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Domain/Sources/Model/Home/UpcomingInfo.swift` around lines 11 - 49,
The UpcomingInfo struct currently defines a public init correctly but lives
under Domain/Sources/Model/Home/, which is inconsistent with other MyTravel
domain models (e.g., UserContentCardResponse); move the file to the MyTravel
domain model folder (mirror the location used by UserContentCardResponse) so
UpcomingInfo and its public init remain in the appropriate MyTravel
module/namespace, and update any imports/usages to the new location.
Projects/Features/MainFeature/Sources/MainInteractor.swift (1)

60-64: UserManager.shared 직접 접근 — RIBs 의존성 주입 권장

RIBs 아키텍처에서 싱글턴을 인터랙터 내부에서 직접 접근하는 것은 테스트 가능성 및 의존성 방향성 원칙에 어긋납니다. UserManagerMainDependency 프로토콜에 추가하고 빌더에서 주입하는 방식을 권장합니다.

또한 PR 설명에 따르면 isFirstOpenApp()은 조회와 동시에 isFirstOpen 기본값을 설정하는 부수 효과가 있어 CQS(Command-Query Separation) 원칙에 위배됩니다. 읽기(isFirstOpen)와 쓰기(기본값 설정)를 분리하는 것을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MainFeature/Sources/MainInteractor.swift` around lines 60 -
64, The interactor directly accesses UserManager.shared in
checkFirstOpenStatus(), which breaks RIBs dependency-injection and testability
and also relies on isFirstOpenApp() that mixes read and write; update the design
by adding a UserManager (or a minimal interface exposing isFirstOpen and a
separate initializer/markFirstOpen method) to the MainDependency protocol, have
the builder inject that dependency into MainInteractor, replace
UserManager.shared usage in checkFirstOpenStatus() to read the injected
dependency's isFirstOpen property, and ensure any side-effect that sets the
default is separated into an explicit write method (e.g., markFirstOpen) invoked
where appropriate instead of inside isFirstOpenApp().
Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift (3)

143-155: guard let으로 바인딩한 지역 변수 dataSource를 사용해 주세요.

Line 145에서 guard let self, let dataSource = self.dataSource로 바인딩했지만, Line 153에서 self.dataSource.apply(...)로 다시 self를 통해 접근하고 있습니다. 바인딩된 지역 변수 dataSource를 사용하는 것이 더 안전하고 일관적입니다.

♻️ 제안된 수정
-            self.dataSource.apply(snapshot, animatingDifferences: true)
+            dataSource.apply(snapshot, animatingDifferences: true)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`
around lines 143 - 155, In update(with sections:) change the use of
self.dataSource to the locally bound dataSource from the guard; inside the
DispatchQueue.main.async block use the dataSource variable (bound in guard let
dataSource = self.dataSource) when calling apply(snapshot,
animatingDifferences:) to avoid re-accessing self.dataSource and ensure the
safe, consistent use of the guarded reference.

66-73: contentInset이 중복 설정되고 있습니다.

Line 70에서 .zero로 설정한 후 Line 72에서 바로 덮어쓰고 있습니다. Line 70은 불필요합니다.

♻️ 제안된 수정
         collectionView.do {
             $0.showsVerticalScrollIndicator = false
             $0.showsHorizontalScrollIndicator = false
             $0.backgroundColor = .clear
-            $0.contentInset = .zero
             $0.isScrollEnabled = true
             $0.contentInset = .init(top: 21.adjustedH, left: 0, bottom: 0, right: 0)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`
around lines 66 - 73, collectionView 설정 블록에서 contentInset이 두 번 설정되어 .zero가 바로
덮어씌워지므로 중복을 제거하세요: locate the collectionView.do closure and remove the line
setting $0.contentInset = .zero, leaving only the intended $0.contentInset =
.init(top: 21.adjustedH, left: 0, bottom: 0, right: 0) so the final layout uses
the adjusted top inset.

111-139: UICollectionView.CellRegistrationSupplementaryRegistration을 사용할 때 수동 register 호출은 불필요합니다.

setDataSource()configureSupplementaryView()에서 dequeueConfiguredReusableCelldequeueConfiguredReusableSupplementary를 사용하고 있으므로, setCollectionView()의 수동 register 호출은 모두 중복입니다. 현대적 등록 API는 자동으로 셀과 보조 뷰를 등록 처리하므로 lines 113-137의 모든 register 호출을 제거할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`
around lines 111 - 139, The setCollectionView() function currently manually
registers cells and a supplementary view even though you use
UICollectionView.CellRegistration/SupplementaryRegistration with
dequeueConfiguredReusableCell and dequeueConfiguredReusableSupplementary in
setDataSource() and configureSupplementaryView(); remove the manual register
calls inside setCollectionView() for MyTravelBannerCell, EmptyUpcomingCell,
UpcomingCell, RecommendInfoCell and MyTravelHeaderView so the modern
registration API handles registration automatically, leaving any other
non-registration setup in setCollectionView() intact.
Projects/Features/MainFeature/Sources/VC/ServiceNoticeViewController.swift (2)

32-44: 하드코딩된 hex 컬러 문자열 대신 DSKit 에셋 사용을 고려해 주세요.

Line 34에서 UIColor.init(hexCode: "#000000", alpha: 0.7)로 직접 색상을 생성하고 있습니다. 다른 색상들은 모두 DSKitAsset.Colors를 통해 참조하고 있으므로, 일관성을 위해 DSKit에 정의된 색상 에셋이나 공통 유틸리티를 사용하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MainFeature/Sources/VC/ServiceNoticeViewController.swift`
around lines 32 - 44, Replace the hardcoded UIColor.init(hexCode: "#000000",
alpha: 0.7) in ServiceNoticeViewController.setStyle with the DSKit asset
equivalent to keep styling consistent; use the DSKitAsset.Colors entry (for
example DSKitAsset.Colors.black900.color) and apply the alpha via
withAlphaComponent(0.7) before assigning to view.backgroundColor so the
background uses the shared design token instead of a literal hex string.

86-111: confirmButton에 하단 제약 조건이 없어 containerView에서 콘텐츠가 잘릴 수 있습니다.

containerView의 높이가 318.adjustedH로 고정되어 있으나 confirmButton의 bottom이 containerView에 연결되어 있지 않습니다. 디바이스 크기에 따라 adjustedH 스케일링이 달라질 경우, 버튼이 컨테이너 영역 밖으로 나갈 수 있습니다.

고정 높이 대신 confirmButton.bottomcontainerView.bottom에 연결하여 컨테이너가 콘텐츠에 맞게 자동으로 크기를 조절하도록 하는 것을 권장합니다.

♻️ 제안된 수정
         containerView.snp.makeConstraints {
             $0.center.equalToSuperview()
             $0.width.equalTo(314.adjusted)
-            $0.height.equalTo(318.adjustedH)
         }
         confirmButton.snp.makeConstraints {
             $0.top.equalTo(termsButton.snp.bottom).offset(28.adjustedH)
             $0.horizontalEdges.equalToSuperview().inset(28.adjusted)
+            $0.bottom.equalToSuperview().inset(28.adjustedH)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MainFeature/Sources/VC/ServiceNoticeViewController.swift`
around lines 86 - 111, In setLayout(), tie the confirmButton's bottom to the
containerView so the container won't cut off content: update
confirmButton.snp.makeConstraints to include
$0.bottom.equalToSuperview().inset(28.adjustedH) (or an appropriate inset), and
remove or relax the fixed containerView height
(containerView.snp.makeConstraints: delete height.equalTo(318.adjustedH) or
change to height.greaterThanOrEqualTo(318.adjustedH)) so containerView can size
to its content; ensure these changes reference containerView, setLayout(), and
confirmButton so the constraints are adjusted in the same method.
Projects/Data/Sources/Transform/UserTravelTransform.swift (1)

52-69: 날짜 파싱 실패 시 .now 폴백이 데이터 문제를 숨길 수 있습니다.

$0.startDate.toDate() ?? .now 패턴은 기존 UpcomingResponse.toDomain()(Line 38-39)과 동일하지만, 서버에서 잘못된 날짜 형식이 반환되면 현재 날짜로 대체되어 UI에 잘못된 여행 일정이 표시될 수 있습니다.

향후 디버깅을 위해 파싱 실패 시 로깅을 추가하는 것을 고려해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Data/Sources/Transform/UserTravelTransform.swift` around lines 52 -
69, In UpcomingListResponse.toDomain(), avoid silently falling back to .now when
$0.startDate.toDate() or $0.endDate.toDate() returns nil: detect the failed
parse for $0.startDate and $0.endDate, log a clear error (including the source
string, the item's id/title/templateId) using the project's logger (same
approach as used in UpcomingResponse.toDomain() if present), and then decide a
safe fallback or skip/mark the item; update the mapping that constructs
UpcomingInfo to perform the parse+logging before calling .init so parsing
failures are recorded for debugging.
Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift (1)

24-47: 미사용 클로저 파라미터 indexPath_로 교체하세요.

SwiftLint unused_closure_parameter 경고가 여러 registration 클로저에서 발생합니다. 사용하지 않는 indexPath 파라미터를 _로 변경하세요.

🔧 수정 제안 (예시: createBannerCellRegistration)
-        return UICollectionView.CellRegistration { cell, indexPath, item in
+        return UICollectionView.CellRegistration { cell, _, item in

createRecommedTripCellRegistration (Line 31)과 createUpcomingCell (Line 44)에도 동일하게 적용하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift`
around lines 24 - 47, Replace the unused closure parameter indexPath with _ in
each CellRegistration closure to satisfy SwiftLint's unused_closure_parameter
rule: update the closures in createBannerCellRegistration,
createRecommedTripCellRegistration, and createUpcomingCell so their registration
blocks use the signature (cell, _, item) instead of (cell, indexPath, item); no
logic changes needed inside the closures.
Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift (5)

79-79: print 대신 구조화된 로깅을 사용하세요.

프로덕션 코드에서 print는 릴리즈 빌드에서도 출력되며, 필터링이 어렵습니다. os.Logger 또는 프로젝트 로깅 유틸을 사용하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift` at line
79, Replace the use of print in MyTravelInteractor (the print("Log: MyTripInfo
로드 실패 - \(error)") call) with the project's structured logger (e.g., an
os.Logger instance or the shared logging utility) so logs can be filtered and
configured; update the MyTravelInteractor to inject or obtain a Logger (or use
the existing logging utility), and change the print call to a structured log
call that includes the error as context (error: or metadata) and a clear message
about MyTripInfo load failure.

127-129: viewWillAppear마다 데이터를 매번 fetch합니다.

탭 전환 시마다 네트워크 요청이 발생합니다. 캐싱이나 마지막 fetch 시간 기반 throttle을 고려하면 불필요한 네트워크 호출을 줄일 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift` around
lines 127 - 129, The viewWillAppear() currently calls fetchMyTravelData() on
every appearance causing redundant network requests; modify MyTravelInteractor
to add a cachedData property and a lastFetchDate (or similar) and change
viewWillAppear() to check the cache/lastFetchDate before calling
fetchMyTravelData(), e.g., only call fetchMyTravelData() if cachedData is nil or
lastFetchDate is older than a defined TTL (or add a forceRefresh flag to
fetchMyTravelData()); ensure fetchMyTravelData() updates cachedData and
lastFetchDate after a successful load and provide a way to invalidate the cache
when necessary.

51-53: super 호출만 하는 불필요한 오버라이드를 제거하세요.

SwiftLint unneeded_override 경고에 해당합니다. didBecomeActive에서 추가 로직이 없으므로 제거 가능합니다.

🔧 수정 제안
-    override func didBecomeActive() {
-        super.didBecomeActive()
-    }
-    
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift` around
lines 51 - 53, 현재 MyTravelInteractor.swift의 override func didBecomeActive()는 오직
super.didBecomeActive()만 호출하므로 SwiftLint의 unneeded_override 경고 대상입니다;
MyTravelInteractor 클래스에서 불필요한 오버라이드를 제거하여 didBecomeActive() 메서드를 삭제하면 해결됩니다 (즉,
해당 override 블록을 제거하여 기본 구현인 super.didBecomeActive()가 호출되도록 함).

62-119: Task 취소 후에도 UI 업데이트가 실행될 수 있습니다.

Line 69에서 Task.isCancelled를 한 번 체크하지만, 이후 여러 await 호출(fetchUpcomingList, fetchRecommendTripList, MainActor.run) 사이에는 취소 여부를 재확인하지 않습니다. willResignActive에서 task를 취소해도, 진행 중인 네트워크 응답 이후의 UI 업데이트는 그대로 수행됩니다.

try Task.checkCancellation()을 주요 await 지점 사이에 추가하면 불필요한 작업을 조기에 중단할 수 있습니다.

🔧 수정 제안 (예시)
                 upcomingList = try await usecase.fetchUpcomingList().map { $0.toMyTravelModel() }
+                try Task.checkCancellation()
                 
                 var recommendItems: [MyTravelPresentationModel.RecommendedTrip] = []
                 if myTripBanner == nil && upcomingList.isEmpty {
                     recommendItems = try await usecase.fetchRecommendTripList().map { $0.toRecommendMyTravelModel() }
+                    try Task.checkCancellation()
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift` around
lines 62 - 119, fetchMyTravelData uses a single early check of Task.isCancelled
but does not re-check cancellation between await points, so cancelled tasks can
still continue and update the UI; modify the Task body (associated with
fetchDataTask) to call try Task.checkCancellation() (or guard !Task.isCancelled
else { return }) immediately after each major await (after
usecase.fetchMyTripInfo(), after usecase.fetchUpcomingList(), after
fetchRecommendTripList() if called, and before MainActor.run) so work bails out
promptly and no UI updates occur for cancelled tasks; keep the existing weak
self capture and use the same presenter update path when not cancelled.

41-41: myTravelRelay가 선언만 되고 사용되지 않습니다.

BehaviorRelay가 초기화되었지만 구독하거나 값을 설정하는 코드가 없습니다. 사용 계획이 없다면 제거하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift` at line
41, The declared BehaviorRelay myTravelRelay in MyTravelInteractor.swift is
never used; either remove the unused property or wire it up: if not needed
delete the private let myTravelRelay = BehaviorRelay<[(MyTravelSectionKind,
[MyTravelItem])]>(value: []) to remove dead code, otherwise update the
interactor methods that build/receive travel data to .accept(...) new values
into myTravelRelay, expose it as an observable via myTravelRelay.asObservable()
or a read-only Relay for callers, and ensure consumers subscribe to that
observable (references: myTravelRelay, MyTravelSectionKind, MyTravelItem,
MyTravelInteractor).
Projects/Features/MyTravelFeature/Sources/Views/CollectionView/CompositionalLayout/MyTravelCompositionalLayout.swift (1)

15-15: 미사용 클로저 파라미터 environment_로 교체하세요.

SwiftLint unused_closure_parameter 경고에 해당합니다.

🔧 수정 제안
-        return UICollectionViewCompositionalLayout { [weak self] sectionIndex, environment in
+        return UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/CompositionalLayout/MyTravelCompositionalLayout.swift`
at line 15, 클로저의 미사용 파라미터 `environment` 때문에 SwiftLint의 unused_closure_parameter
경고가 발생하므로 UICollectionViewCompositionalLayout 생성 클로저 시그니처에서 `environment`를 `_`로
교체하세요 (즉, the closure passed to UICollectionViewCompositionalLayout that
currently reads `{ [weak self] sectionIndex, environment in ... }` should become
`{ [weak self] sectionIndex, _ in ... }`) so the unused parameter is ignored
while preserving `sectionIndex` and the `[weak self]` capture.
Projects/Domain/Sources/UseCase/MyTravelUsecase.swift (1)

33-44: travelTemplateRepository 사용 범위를 확인하세요.

현재 travelTemplateRepositoryfetchRecommendTripList에서만 사용되고, userTravelRepository는 나머지 두 메서드에서 사용됩니다. 향후 확장 계획이 없다면, 각 리포지토리를 별도 UseCase로 분리하는 것도 고려할 수 있지만, 현재 규모에서는 문제 없습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Domain/Sources/UseCase/MyTravelUsecase.swift` around lines 33 - 44,
MyTravelUsecase declares two repositories but travelTemplateRepository is only
used by fetchRecommendTripList while userTravelRepository is used by the other
methods; verify and either (a) keep both in MyTravelUsecase if you expect future
shared behavior, or (b) refactor by moving travelTemplateRepository and
fetchRecommendTripList into a dedicated TravelTemplateUsecase and keep
UserTravelRepository methods in a separate UserTravelUsecase; locate symbols
MyTravelUsecase, travelTemplateRepository, fetchRecommendTripList, and
userTravelRepository to perform the split or confirm retention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Projects/Core/Sources/Extensions/Foundation`+/String+.swift:
- Around line 35-40: The String.toDate() implementation creates a new
DateFormatter on every call which is expensive; update it to avoid per-call
allocations: if toDate() is only ever invoked on the main thread, replace the
local formatter with a cached static DateFormatter used on the main thread
(e.g., a lazily-initialized static property referenced from toDate()); if
toDate() can be called from background threads (e.g., from UserTravelTransform),
keep the current per-call creation or protect a shared formatter via
thread-local storage or a serial queue/lock, or use a thread-safe alternative
like ISO8601DateFormatter where appropriate; modify the String.toDate() method
accordingly to use one of these safe caching strategies.

In
`@Projects/Domain/Sources/Interface/UserTravel/UserTravelRepositoryInterface.swift`:
- Line 13: 제거할 미완성 주석 선언 func fetchContentCard(id: Int) async throws -> 는 반환 타입이
없어 불완전하므로 유지할 계획이 없다면 해당 주석(미완성 선언)을 삭제하세요; 만약 추후 구현할 계획이면 완전한 시그니처(예: func
fetchContentCard(id: Int) async throws -> ContentCard) 또는 적절한 placeholder 반환 타입을
명시하고 구현 또는 TODO 주석을 추가해 주세요 — 참조 심볼: fetchContentCard(id: Int) async throws ->.

In `@Projects/Features/MainFeature/Sources/MainViewController.swift`:
- Around line 59-61: The Notion link assigned in noticeVC.termsHandler uses an
unnecessary tracking query parameter and is hard-coded; remove the trailing
"?source=copy_link" from the URL and replace the literal string with a
centralized constant (e.g., TermsURL or NotionTermsURL) defined in your
constants/config file, then update the handler to call URLHelper.openURL with
that constant (referencing noticeVC.termsHandler and URLHelper.openURL to locate
the change).

In
`@Projects/Features/MyTravelFeature/Sources/Models/MyTravelPresentationModel.swift`:
- Around line 123-137: UpcomingInfo.toMyTravelModel currently computes dDay and
MyTravelBannerCell.configure duplicates that logic; remove the duplication by
having the cell accept the precomputed dDay from the presentation model. Update
UpcomingInfo.toMyTravelModel to continue providing dDay (it already does) and
modify MyTravelBannerCell.configure to stop recomputing dDay and instead use the
dDay property from MyTravelPresentationModel.Upcoming (refer to the
UpcomingInfo.toMyTravelModel, MyTravelPresentationModel.Upcoming, and
MyTravelBannerCell.configure symbols) so all D-day computation lives in the
presentation model.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`:
- Around line 193-198: The code uses a magic number check (banner.id == 0)
inside the switch over MyTravelItem (case .banner(let banner)) to decide between
emptyReg and bannerReg; replace this fragile check by modeling empty state
explicitly—either add a new MyTravelItem case (e.g., .emptyBanner) and switch on
that, or add an explicit flag/property on the banner presentation model (e.g.,
banner.isEmpty) and use that instead—then update the switch in
MyTravelViewController (the case .banner branch and any callers that construct
items) to dequeueConfiguredReusableCell using emptyReg only for the explicit
empty case/flag and bannerReg for normal banners, leaving
collectionView.dequeueConfiguredReusableCell calls and emptyReg/bannerReg usage
unchanged.

In
`@Projects/Features/MyTravelFeature/Sources/Views/Cells/MyTravelBannerCell.swift`:
- Around line 43-73: The cell contains business logic: move date comparison and
D-day calculation out of MyTravelBannerCell.configure(_ model:
MyTravelPresentationModel.Banner) into the presentation/model mapping layer so
the cell only receives a resolved MyTravelBannerType (e.g., .onGoing /
.upComming) or a Banner that already exposes bannerType, dDay and transport
info; remove Calendar/date math and setting of self.type inside configure and
instead accept the precomputed type and call updateViewWithCurrentType(). Also
stop hardcoding transport assets/strings: expose transportIcon and
transportLabel (or a transportType enum) from
MyTravelPresentationModel.Banner/tripSchedule and use those in configure rather
than DSKitAsset.Assets.icBus2.image or the literal "대중교통".
- Around line 127-137: The enum case name MyTravelBannerType.upComming is
misspelled; rename it to MyTravelBannerType.upcoming (and update any related
view identifiers that used "upComming", e.g., MyTravelBannerCell references) and
then update all usages: switch/case patterns, initializers, unit tests,
storyboard/xib identifiers, and any string-based keys so the code compiles and
runtime lookups remain consistent. Ensure you update associated labels or
factory methods that construct the case with the new name and run tests/build to
catch any remaining references.
- Around line 62-69: The current else branch treats any non-future case as
upcoming, causing past trips to be shown as "upComming" with negative D-day;
update the conditional logic around startOfToday, startOfTravel and startOfEnd
so only true future trips set self.type = .upComming (keep the existing
dDayValue computation), add an explicit branch for past trips (startOfEnd <
startOfToday) to set a .past or other appropriate type/handling, and optionally
handle ongoing trips when startOfTravel <= startOfToday && startOfEnd >=
startOfToday; modify the code that sets self.type = .upComming(...) to be inside
the strict future check and implement the past/ongoing branches instead of the
current else.

In
`@Projects/Features/MyTravelFeature/Sources/Views/Cells/MyTravelHeaderView.swift`:
- Around line 28-32: prepareForReuse currently only clears titleLabel.text, but
configure(title:) uses DSKit's setText which sets titleLabel.attributedText, so
previous styled text can persist; update the MyTravelHeaderView.prepareForReuse
implementation to also clear titleLabel.attributedText (and keep text = nil) so
any attributed styling from prior reuse is removed before the cell is reused.

In `@Projects/Features/MyTravelFeature/Sources/Views/Cells/UpcomingCell.swift`:
- Line 110: 현재 셀에 직접 addSubview(stackView) 하고 있어서 선택/하이라이트/레이아웃에서 문제를 일으킵니다;
stackView를 셀이 아닌 contentView에 추가하도록 변경하고, setLayout에서 stackView의
제약(leading/trailing/top/bottom, center 등)을 contentView의 anchors(예:
contentView.topAnchor, contentView.leadingAnchor 등)를 기준으로 설정하도록 수정하세요; 관련 식별자는
stackView와 setLayout 메서드(또는 레이아웃 설정 코드)를 찾아 대상을 contentView로 바꾸면 됩니다.
- Around line 38-48: The configure(title:date:dDay:imageUrl:) method currently
formats dDay poorly and leaves a debug print; update d-day formatting so that
when dDay == 0 you set dDayLabel to "D-Day", when dDay > 0 you set "D-\(dDay)",
and when dDay < 0 you set "D+\(abs(dDay))"; remove the debug print(imageUrl) and
instead keep the existing fallback behavior for imageView (e.g., set
backgroundColor or a placeholder image) when URL(string: imageUrl) is nil;
modify the dDayLabel assignment inside configure and delete the print call
referencing imageUrl to avoid leaking debug logs.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift`:
- Line 30: Rename the misspelled function createRecommedTripCellRegistration to
createRecommendTripCellRegistration and update all references/usages
accordingly; specifically rename the declaration of
createRecommedTripCellRegistration() -> createRecommendTripCellRegistration()
and update any call sites or tests that reference it (including places using
RecommendInfoCell and MyTravelPresentationModel.RecommendedTrip) so the new
symbol name is used consistently across the codebase.
- Around line 1-3: Rename the file MyTravelRegistraion.swift to
MyTravelRegistration.swift and fix the header comment lines to spell
"Registration" correctly; search the file for any symbols or identifiers that
use "MyTravelRegistraion" (file-level comments, MARKs,
class/struct/enum/extension names such as a type named MyTravelRegistraion) and
rename them to "MyTravelRegistration" so the filename, header comment, and any
identifier names are consistent.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift`:
- Around line 11-14: 현재 MyTravelSectionKind의 upcomingTrips(isEmpty: Bool) 연관값
때문에 섹션 식별자가 상태 변경시 달라져서 섹션 전체가 삭제·재삽입됩니다; 수정 방법은 MyTravelSectionKind의
upcomingTrips 케이스에서 연관값을 제거(예: case upcomingTrips)하고 빈 상태 표현을 섹션 식별자에서 제거한 뒤,
MyTravelItem에 빈 상태용 케이스(예: .emptyUpcomingTrip)를 추가하여 인터랙터(또는 해당 데이터를 만드는 메서드)가
아이템 배열이 비어있을 때 해당 빈 아이템을 반환하도록 변경하세요; 스냅샷 생성 로직을 업데이트해 이제 upcomingTrips 섹션에는 실제
아이템 또는 .emptyUpcomingTrip이 포함되도록 하고, MyTravelSectionKind 및 스냅샷 관련 코드에서 더 이상
isEmpty 연관값을 사용하지 않도록 정리하세요.

In `@Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift`:
- Around line 96-108: The three methods in TabBarInteractor that implement
MyTravelListener are misspelled (missing the "l"): update the method names
myTraveDidTapFollowDetail, myTraveDidTapSearch, and myTraveDidTapPopularTravel
to myTravelDidTapFollowDetail, myTravelDidTapSearch, and
myTravelDidTapPopularTravel respectively so they match the MyTravelListener
protocol, and ensure any references like listener?.routeToFollow,
listener?.routeToSearch, and listener?.routeToPopularTravel remain unchanged;
also run a compile to find and fix any other callsites or protocol declarations
that need the same rename.

In `@Projects/Modules/DSKit/Sources/Component/EmptyUpcomingCell.swift`:
- Around line 45-47: In EmptyUpcomingCell, change the prepareForReuse
declaration to put the override modifier before the access level (use "override
public func prepareForReuse()") and inside the method call
super.prepareForReuse() as the first statement, then reset the disposeBag
(disposeBag = DisposeBag()) so the cell is properly reset and adheres to
SwiftLint modifier order.

In `@Projects/Modules/Networks/Sources/TargetType/UserTravelAPI.swift`:
- Around line 33-34: The enum case .getUpcomingList returns a path missing the
leading '/', causing incorrect URL composition; update the path returned for
.getUpcomingList to include a leading slash (i.e.,
"/api/v1/travels/upcoming/list") so it matches the other cases and composes
correctly with Moya's baseURL; verify in the TargetType implementation that
other paths follow the same leading-slash pattern for consistency.

---

Outside diff comments:
In `@Projects/Features/RootFeature/Sources/RootInteractor.swift`:
- Around line 67-100: performAuthFlow currently spawns an untracked Task and
only prints errors, which can leave the UI empty on failure and allows the work
to continue after RIB deactivation; change performAuthFlow to store the Task in
a property (e.g., authTask) so it can be cancelled from willResignActive, add
cancellation checks or handle Task.isCancelled inside the Task, and improve
error handling by ensuring a fallback UI flow is always invoked (e.g., call
router?.attachMain() or an error/placeholder routing path) in the catch path or
via a defer so the app never remains blank; also replace the plain print with
proper logging. Reference: performAuthFlow, router?.attachMain(),
willResignActive, and add a stored Task property (authTask) to manage
cancellation.

---

Nitpick comments:
In `@Projects/Core/Sources/Extensions/Foundation`+/Date+.swift:
- Around line 12-17: The toKoreanMMdd() function creates a new DateFormatter on
every call; change it to use a cached static DateFormatter (e.g. a static let
koreanMMddFormatter) configured with locale "ko_KR" and dateFormat "M월 d일" and
return koreanMMddFormatter.string(from: self) inside toKoreanMMdd(); if this
utility may be called off the main thread, either document that it’s
main-thread-only or create/return a thread-confined formatter per thread (or use
a DateFormatter factory) to avoid thread-safety issues.

In `@Projects/Core/Sources/Storage/UserDefaultWrapper.swift`:
- Around line 13-15: The getter in UserDefaultWrapper's computed property uses
an explicit "return" that SwiftLint flags; change the get block to use implicit
return by replacing "get { return UserDefaults.standard.object(forKey:
self.key.rawValue) as? T }" with "get { UserDefaults.standard.object(forKey:
self.key.rawValue) as? T }" (reference the computed property in
UserDefaultWrapper that accesses self.key and UserDefaults.standard).

In `@Projects/Core/Sources/Storage/UserManager.swift`:
- Around line 11-15: The enum UserDefaultKeys currently specifies raw string
values identical to the case names; update UserDefaultKeys (the enum and its
cases uuid, nickname, isFirstOpen) to omit the redundant rawValue literals so
Swift auto-synthesizes them (e.g. declare the cases without = "..."), keeping
the enum conforming to String.
- Around line 26-32: 현재 isFirstOpenApp()의 논리는 동작하지만 self.isFirstOpen = true 후
반환값을 !isFirstOpen으로 처리해 의도가 직관적이지 않습니다; isFirstOpenApp() 내에서 옵셔널 바인딩 실패 시
self.isFirstOpen을 true로 설정하고 즉시 true를 반환하고, 옵셔널이 존재할 경우는 false를 반환하도록 리팩토링해 가독성을
개선하세요 (참조 심볼: isFirstOpenApp(), isFirstOpen).

In `@Projects/Data/Sources/Transform/UserTravelTransform.swift`:
- Around line 52-69: In UpcomingListResponse.toDomain(), avoid silently falling
back to .now when $0.startDate.toDate() or $0.endDate.toDate() returns nil:
detect the failed parse for $0.startDate and $0.endDate, log a clear error
(including the source string, the item's id/title/templateId) using the
project's logger (same approach as used in UpcomingResponse.toDomain() if
present), and then decide a safe fallback or skip/mark the item; update the
mapping that constructs UpcomingInfo to perform the parse+logging before calling
.init so parsing failures are recorded for debugging.

In `@Projects/Domain/Sources/Model/Home/UpcomingInfo.swift`:
- Around line 11-49: The UpcomingInfo struct currently defines a public init
correctly but lives under Domain/Sources/Model/Home/, which is inconsistent with
other MyTravel domain models (e.g., UserContentCardResponse); move the file to
the MyTravel domain model folder (mirror the location used by
UserContentCardResponse) so UpcomingInfo and its public init remain in the
appropriate MyTravel module/namespace, and update any imports/usages to the new
location.

In `@Projects/Domain/Sources/UseCase/MyTravelUsecase.swift`:
- Around line 33-44: MyTravelUsecase declares two repositories but
travelTemplateRepository is only used by fetchRecommendTripList while
userTravelRepository is used by the other methods; verify and either (a) keep
both in MyTravelUsecase if you expect future shared behavior, or (b) refactor by
moving travelTemplateRepository and fetchRecommendTripList into a dedicated
TravelTemplateUsecase and keep UserTravelRepository methods in a separate
UserTravelUsecase; locate symbols MyTravelUsecase, travelTemplateRepository,
fetchRecommendTripList, and userTravelRepository to perform the split or confirm
retention.

In `@Projects/Features/MainFeature/Sources/MainBuilder.swift`:
- Line 26: The dependency list in the MainComponent declaration has a missing
space after a comma between PopularTravelDependency and SearchDependency; update
the class declaration (final class MainComponent: Component<MainDependency>,
FollowDetailDependency, PopularTravelDependency,SearchDependency,
SettingDependency, TabBarDependency) to insert a space after the comma so it
reads ..., PopularTravelDependency, SearchDependency, ... to satisfy SwiftLint's
comma rule.

In `@Projects/Features/MainFeature/Sources/MainInteractor.swift`:
- Around line 60-64: The interactor directly accesses UserManager.shared in
checkFirstOpenStatus(), which breaks RIBs dependency-injection and testability
and also relies on isFirstOpenApp() that mixes read and write; update the design
by adding a UserManager (or a minimal interface exposing isFirstOpen and a
separate initializer/markFirstOpen method) to the MainDependency protocol, have
the builder inject that dependency into MainInteractor, replace
UserManager.shared usage in checkFirstOpenStatus() to read the injected
dependency's isFirstOpen property, and ensure any side-effect that sets the
default is separated into an explicit write method (e.g., markFirstOpen) invoked
where appropriate instead of inside isFirstOpenApp().

In `@Projects/Features/MainFeature/Sources/VC/ServiceNoticeViewController.swift`:
- Around line 32-44: Replace the hardcoded UIColor.init(hexCode: "#000000",
alpha: 0.7) in ServiceNoticeViewController.setStyle with the DSKit asset
equivalent to keep styling consistent; use the DSKitAsset.Colors entry (for
example DSKitAsset.Colors.black900.color) and apply the alpha via
withAlphaComponent(0.7) before assigning to view.backgroundColor so the
background uses the shared design token instead of a literal hex string.
- Around line 86-111: In setLayout(), tie the confirmButton's bottom to the
containerView so the container won't cut off content: update
confirmButton.snp.makeConstraints to include
$0.bottom.equalToSuperview().inset(28.adjustedH) (or an appropriate inset), and
remove or relax the fixed containerView height
(containerView.snp.makeConstraints: delete height.equalTo(318.adjustedH) or
change to height.greaterThanOrEqualTo(318.adjustedH)) so containerView can size
to its content; ensure these changes reference containerView, setLayout(), and
confirmButton so the constraints are adjusted in the same method.

In `@Projects/Features/MyTravelFeature/Project.swift`:
- Around line 9-11: The imports at the top of Project.swift are not
alphabetically sorted, triggering SwiftLint's sorted_imports rule; reorder the
two import statements so they follow alphabetical order (import DependencyPlugin
before import ProjectDescriptionHelpers) to satisfy the lint rule and keep
imports consistently sorted.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelInteractor.swift`:
- Line 79: Replace the use of print in MyTravelInteractor (the print("Log:
MyTripInfo 로드 실패 - \(error)") call) with the project's structured logger (e.g.,
an os.Logger instance or the shared logging utility) so logs can be filtered and
configured; update the MyTravelInteractor to inject or obtain a Logger (or use
the existing logging utility), and change the print call to a structured log
call that includes the error as context (error: or metadata) and a clear message
about MyTripInfo load failure.
- Around line 127-129: The viewWillAppear() currently calls fetchMyTravelData()
on every appearance causing redundant network requests; modify
MyTravelInteractor to add a cachedData property and a lastFetchDate (or similar)
and change viewWillAppear() to check the cache/lastFetchDate before calling
fetchMyTravelData(), e.g., only call fetchMyTravelData() if cachedData is nil or
lastFetchDate is older than a defined TTL (or add a forceRefresh flag to
fetchMyTravelData()); ensure fetchMyTravelData() updates cachedData and
lastFetchDate after a successful load and provide a way to invalidate the cache
when necessary.
- Around line 51-53: 현재 MyTravelInteractor.swift의 override func
didBecomeActive()는 오직 super.didBecomeActive()만 호출하므로 SwiftLint의
unneeded_override 경고 대상입니다; MyTravelInteractor 클래스에서 불필요한 오버라이드를 제거하여
didBecomeActive() 메서드를 삭제하면 해결됩니다 (즉, 해당 override 블록을 제거하여 기본 구현인
super.didBecomeActive()가 호출되도록 함).
- Around line 62-119: fetchMyTravelData uses a single early check of
Task.isCancelled but does not re-check cancellation between await points, so
cancelled tasks can still continue and update the UI; modify the Task body
(associated with fetchDataTask) to call try Task.checkCancellation() (or guard
!Task.isCancelled else { return }) immediately after each major await (after
usecase.fetchMyTripInfo(), after usecase.fetchUpcomingList(), after
fetchRecommendTripList() if called, and before MainActor.run) so work bails out
promptly and no UI updates occur for cancelled tasks; keep the existing weak
self capture and use the same presenter update path when not cancelled.
- Line 41: The declared BehaviorRelay myTravelRelay in MyTravelInteractor.swift
is never used; either remove the unused property or wire it up: if not needed
delete the private let myTravelRelay = BehaviorRelay<[(MyTravelSectionKind,
[MyTravelItem])]>(value: []) to remove dead code, otherwise update the
interactor methods that build/receive travel data to .accept(...) new values
into myTravelRelay, expose it as an observable via myTravelRelay.asObservable()
or a read-only Relay for callers, and ensure consumers subscribe to that
observable (references: myTravelRelay, MyTravelSectionKind, MyTravelItem,
MyTravelInteractor).

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`:
- Around line 143-155: In update(with sections:) change the use of
self.dataSource to the locally bound dataSource from the guard; inside the
DispatchQueue.main.async block use the dataSource variable (bound in guard let
dataSource = self.dataSource) when calling apply(snapshot,
animatingDifferences:) to avoid re-accessing self.dataSource and ensure the
safe, consistent use of the guarded reference.
- Around line 66-73: collectionView 설정 블록에서 contentInset이 두 번 설정되어 .zero가 바로
덮어씌워지므로 중복을 제거하세요: locate the collectionView.do closure and remove the line
setting $0.contentInset = .zero, leaving only the intended $0.contentInset =
.init(top: 21.adjustedH, left: 0, bottom: 0, right: 0) so the final layout uses
the adjusted top inset.
- Around line 111-139: The setCollectionView() function currently manually
registers cells and a supplementary view even though you use
UICollectionView.CellRegistration/SupplementaryRegistration with
dequeueConfiguredReusableCell and dequeueConfiguredReusableSupplementary in
setDataSource() and configureSupplementaryView(); remove the manual register
calls inside setCollectionView() for MyTravelBannerCell, EmptyUpcomingCell,
UpcomingCell, RecommendInfoCell and MyTravelHeaderView so the modern
registration API handles registration automatically, leaving any other
non-registration setup in setCollectionView() intact.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/CompositionalLayout/MyTravelCompositionalLayout.swift`:
- Line 15: 클로저의 미사용 파라미터 `environment` 때문에 SwiftLint의 unused_closure_parameter
경고가 발생하므로 UICollectionViewCompositionalLayout 생성 클로저 시그니처에서 `environment`를 `_`로
교체하세요 (즉, the closure passed to UICollectionViewCompositionalLayout that
currently reads `{ [weak self] sectionIndex, environment in ... }` should become
`{ [weak self] sectionIndex, _ in ... }`) so the unused parameter is ignored
while preserving `sectionIndex` and the `[weak self]` capture.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift`:
- Around line 24-47: Replace the unused closure parameter indexPath with _ in
each CellRegistration closure to satisfy SwiftLint's unused_closure_parameter
rule: update the closures in createBannerCellRegistration,
createRecommedTripCellRegistration, and createUpcomingCell so their registration
blocks use the signature (cell, _, item) instead of (cell, indexPath, item); no
logic changes needed inside the closures.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift`:
- Around line 16-24: The headerTitle computed property currently returns
hardcoded Korean strings; update it to use NSLocalizedString (or a String
extension/localization helper) instead of literals so UI text is localized:
replace the direct returns in headerTitle (for cases .upcomingTrips and
.recommendedTrip) with NSLocalizedString calls using clear keys (e.g.
"section.upcoming_trips.title", "section.recommended_trip.title") and provide
those keys in Localizable.strings; keep the same enum/case names and behavior
(headerTitle) and ensure the default still returns an empty localized string if
needed.

In `@Projects/Features/SettingFeature/Sources/SettingViewController.swift`:
- Around line 142-146: Remove the commented-out conditional that checks `menu ==
.notification` and the alternate `cell.separatorInset` assignment in
SettingViewController.swift; keep only the active line setting
`cell.separatorInset = UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right:
24.adjusted)` and delete the commented block referencing `.notification` so the
codebase is cleaner (the removed logic can be recovered from Git history if
needed).

In `@Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift`:
- Line 96: Add the missing section comment by inserting a "// MARK: -
MyTravelListener" comment immediately above the TabBarInteractor extension
declaration for MyTravelListener (extension TabBarInteractor: MyTravelListener)
so it matches the existing MARK comments used for HomeListener and
TravelToolListener and keeps the file's sectioning consistent.

In `@Projects/Modules/DSKit/Sources/Component/RecommendInfoCell.swift`:
- Around line 58-75: The
configure(title:thumbnailUrl:countryCode:creator:city:schedule:) method on
RecommendInfoCell exceeds the SwiftLint 5-parameter rule; refactor by
introducing a single configuration struct (e.g., RecommendInfo or
RecommendInfoViewModel) that contains title, thumbnailUrl, countryCode, creator,
city, and schedule, update the Configure signature to accept that struct (e.g.,
configure(with: RecommendInfo)), and update all call sites to build and pass the
struct; ensure internal usage maps struct properties to
thumbnailView.kf.setImage, nationalFlagLabel, nationLabel, titleLabel,
nameLabel, cityLabel and scheduleLabel as before.
- Around line 78-122: The setStyle() function uses hardcoded geometry; replace
the literal values with the project scaling extensions: change
thumbnailView.layer.cornerRadius = 8 to use a scaled value (e.g. 8.adjusted),
and change the stack view spacing assignments (nationStackView.spacing,
subInfoStackView.spacing, infoStackView.spacing) from 4 to scaled values (e.g.
4.adjusted or 4.adjustedH depending on whether you want width- or height-based
scaling), keeping use of .adjustedH where vertical scaling is intended; update
these in the RecommendInfoCell.setStyle() method referencing thumbnailView,
nationStackView, subInfoStackView, and infoStackView.

Comment on lines +35 to +40
func toDate() -> Date? {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter.date(from: self)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the toDate() implementation in String+.swift
find . -name "String+.swift" -type f | head -20

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 135


🏁 Script executed:

# Search for all usages of toDate()
rg -n "\.toDate\(\)" --type swift -B 2 -A 2

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1299


🏁 Script executed:

# Also check the Date+.swift file for comparison
find . -name "Date+.swift" -type f | head -20

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 133


🏁 Script executed:

# Search for UserTravelTransform to understand the context
rg -n "UserTravelTransform" --type swift -A 5 -B 5

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 625


🏁 Script executed:

# Read the String+.swift file to see the toDate() implementation
cat -n ./Projects/Core/Sources/Extensions/Foundation+/String+.swift | head -50

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1427


🏁 Script executed:

# Read the Date+.swift file to see the caching approach
cat -n ./Projects/Core/Sources/Extensions/Foundation+/Date+.swift | head -80

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 583


🏁 Script executed:

# Check the UserTravelTransform full context to understand the threading model
cat -n ./Projects/Data/Sources/Transform/UserTravelTransform.swift | head -80

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 2441


🏁 Script executed:

# Search for all DateFormatter usage in Date+.swift and other Date extensions
rg -n "DateFormatter" --type swift -B 2 -A 2 Projects/Core/Sources/Extensions/Foundation+/

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1060


🏁 Script executed:

# Check if there are other Date extensions or if DateFormatter is cached elsewhere
rg -n "static.*DateFormatter\|DateFormatter.*static" --type swift

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 57


🏁 Script executed:

# Look at where UpcomingResponse and UpcomingListResponse are used to understand the threading context
rg -n "\.toDomain\(\)" --type swift -B 3 -A 3

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 14607


🏁 Script executed:

# Double-check by examining the full context of how these formatters are used
rg -n "DateFormatter" --type swift -B 1 -A 3

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 4249


String.toDate() 메서드의 성능 최적화 고려

DateFormatter는 초기화 비용이 크므로 매 호출마다 새로 생성하는 것은 성능상 비효율적입니다. 다만, DateFormatter는 스레드 안전하지 않으므로 static으로 캐싱할 경우 UserTravelTransform처럼 비동기 작업 중 호출되는 시나리오에서는 동기화 문제가 발생할 수 있습니다.

메인 스레드에서만 호출된다면 캐싱을 적용하고, 백그라운드 스레드에서도 호출될 가능성이 있다면 현재 방식을 유지하거나 스레드 로컬 저장소(thread-local storage) 또는 직렬 큐로 보호하는 것을 검토해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Extensions/Foundation`+/String+.swift around lines 35 -
40, The String.toDate() implementation creates a new DateFormatter on every call
which is expensive; update it to avoid per-call allocations: if toDate() is only
ever invoked on the main thread, replace the local formatter with a cached
static DateFormatter used on the main thread (e.g., a lazily-initialized static
property referenced from toDate()); if toDate() can be called from background
threads (e.g., from UserTravelTransform), keep the current per-call creation or
protect a shared formatter via thread-local storage or a serial queue/lock, or
use a thread-safe alternative like ISO8601DateFormatter where appropriate;
modify the String.toDate() method accordingly to use one of these safe caching
strategies.


public protocol UserTravelRepositoryInterface {
func createUserTravel(request: CreateTravelRequest) async throws -> CreateTravelResponse
// func fetchContentCard(id: Int) async throws ->
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

불완전한 주석 처리된 코드 제거 필요

반환 타입이 없는 미완성 선언입니다. 완성할 계획이 없다면 삭제하세요.

🗑️ 제거 제안
-//    func fetchContentCard(id: Int) async throws ->
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// func fetchContentCard(id: Int) async throws ->
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Domain/Sources/Interface/UserTravel/UserTravelRepositoryInterface.swift`
at line 13, 제거할 미완성 주석 선언 func fetchContentCard(id: Int) async throws -> 는 반환
타입이 없어 불완전하므로 유지할 계획이 없다면 해당 주석(미완성 선언)을 삭제하세요; 만약 추후 구현할 계획이면 완전한 시그니처(예: func
fetchContentCard(id: Int) async throws -> ContentCard) 또는 적절한 placeholder 반환 타입을
명시하고 구현 또는 TODO 주석을 추가해 주세요 — 참조 심볼: fetchContentCard(id: Int) async throws ->.

Comment on lines +59 to +61
noticeVC.termsHandler = {
URLHelper.openURL("https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a?source=copy_link")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Notion URL의 source=copy_link 파라미터 및 URL 상수화 권장

  1. URL의 ?source=copy_link는 Notion에서 링크 복사 시 자동으로 붙는 트래킹 파라미터로, 브라우저 주소에서 그대로 복사된 것으로 보입니다. 의도된 파라미터가 아니라면 제거하세요.
  2. 하드코딩된 URL은 상수 파일로 분리하면 향후 변경 및 관리가 용이합니다.
🔧 수정 제안
-        noticeVC.termsHandler = {
-            URLHelper.openURL("https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a?source=copy_link")
-        }
+        noticeVC.termsHandler = {
+            URLHelper.openURL("https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a")
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
noticeVC.termsHandler = {
URLHelper.openURL("https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a?source=copy_link")
}
noticeVC.termsHandler = {
URLHelper.openURL("https://repeated-tapir-33f.notion.site/2c8cbdc5a3838070a8d8ccdcd0631c9a")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MainFeature/Sources/MainViewController.swift` around lines
59 - 61, The Notion link assigned in noticeVC.termsHandler uses an unnecessary
tracking query parameter and is hard-coded; remove the trailing
"?source=copy_link" from the URL and replace the literal string with a
centralized constant (e.g., TermsURL or NotionTermsURL) defined in your
constants/config file, then update the handler to call URLHelper.openURL with
that constant (referencing noticeVC.termsHandler and URLHelper.openURL to locate
the change).

Comment on lines +123 to +137
extension UpcomingInfo {
func toMyTravelModel() -> MyTravelPresentationModel.Upcoming {
let calendar = Calendar.current
let startOfToday = calendar.startOfDay(for: Date())
let startOfTravel = calendar.startOfDay(for: self.startDate)
let dDayValue = calendar.dateComponents([.day], from: startOfToday, to: startOfTravel).day ?? 0

return MyTravelPresentationModel.Upcoming(
id: self.id,
title: self.title,
profileImage: self.thumbnail ?? "",
dDay: dDayValue,
duration: "\(self.startDate.toKoreanMMdd())~\(self.endDate.toKoreanMMdd())"
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

D-day 계산 로직이 MyTravelBannerCell.configure에도 중복 존재합니다.

이 프레젠테이션 모델이 D-day 계산의 올바른 위치입니다. MyTravelBannerCell.swift(Line 46-63)에서도 동일한 Calendar/dateComponents 로직으로 D-day를 계산하고 있으므로, 배너 셀은 이미 계산된 값을 받도록 통합하면 중복을 제거할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Models/MyTravelPresentationModel.swift`
around lines 123 - 137, UpcomingInfo.toMyTravelModel currently computes dDay and
MyTravelBannerCell.configure duplicates that logic; remove the duplication by
having the cell accept the precomputed dDay from the presentation model. Update
UpcomingInfo.toMyTravelModel to continue providing dDay (it already does) and
modify MyTravelBannerCell.configure to stop recomputing dDay and instead use the
dDay property from MyTravelPresentationModel.Upcoming (refer to the
UpcomingInfo.toMyTravelModel, MyTravelPresentationModel.Upcoming, and
MyTravelBannerCell.configure symbols) so all D-day computation lives in the
presentation model.

Comment on lines +193 to +198
switch item {
case .banner(let banner):
if banner.id == 0 {
return collectionView.dequeueConfiguredReusableCell(using: emptyReg, for: indexPath, item: banner)
}
return collectionView.dequeueConfiguredReusableCell(using: bannerReg, for: indexPath, item: banner)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

빈 상태 판별에 매직 넘버 0을 사용하고 있습니다.

banner.id == 0으로 빈 상태를 판별하는 것은 취약합니다. 실제 서버 데이터에서 id가 0인 배너가 있을 경우 의도치 않게 빈 상태 셀이 표시됩니다. 별도의 MyTravelItem case(예: .emptyBanner)를 추가하거나, Presentation Model에 명시적인 empty 상태 플래그를 두는 것이 더 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/MyTravelFeature/Sources/MyTravelViewController.swift`
around lines 193 - 198, The code uses a magic number check (banner.id == 0)
inside the switch over MyTravelItem (case .banner(let banner)) to decide between
emptyReg and bannerReg; replace this fragile check by modeling empty state
explicitly—either add a new MyTravelItem case (e.g., .emptyBanner) and switch on
that, or add an explicit flag/property on the banner presentation model (e.g.,
banner.isEmpty) and use that instead—then update the switch in
MyTravelViewController (the case .banner branch and any callers that construct
items) to dequeueConfiguredReusableCell using emptyReg only for the explicit
empty case/flag and bannerReg for normal banners, leaving
collectionView.dequeueConfiguredReusableCell calls and emptyReg/bannerReg usage
unchanged.

}
}

func createRecommedTripCellRegistration() -> UICollectionView.CellRegistration<RecommendInfoCell, MyTravelPresentationModel.RecommendedTrip> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

메서드명 오타: createRecommedTripCellRegistrationcreateRecommendTripCellRegistration.

"Recommend"의 철자가 잘못되었습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/Registration/MyTravelRegistraion.swift`
at line 30, Rename the misspelled function createRecommedTripCellRegistration to
createRecommendTripCellRegistration and update all references/usages
accordingly; specifically rename the declaration of
createRecommedTripCellRegistration() -> createRecommendTripCellRegistration()
and update any call sites or tests that reference it (including places using
RecommendInfoCell and MyTravelPresentationModel.RecommendedTrip) so the new
symbol name is used consistently across the codebase.

Comment on lines +11 to +14
enum MyTravelSectionKind: Hashable {
case banner
case upcomingTrips(isEmpty: Bool)
case recommendedTrip
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

upcomingTrips(isEmpty: Bool) 섹션 식별자 설계 오류 — 섹션 전체가 삭제·재삽입됨

DiffableDataSource는 모든 식별자의 해시값을 사용해 변경 사항을 판별합니다. Swift가 연관 값이 있는 열거형에 대해 Hashable을 자동 합성하므로, upcomingTrips(isEmpty: true)upcomingTrips(isEmpty: false)는 서로 다른 해시값을 가집니다.

결과적으로 빈 상태(예: 데이터 로드 전 isEmpty: true)에서 데이터가 채워지는 시점(isEmpty: false)으로 스냅샷이 전환될 때, DiffableDataSource는 이전 스냅샷과 새 스냅샷의 차이를 계산하며, 이 변경 사항(삽입·삭제 등)을 애니메이션으로 처리합니다. 즉, 섹션 내 아이템만 교체되는 것이 아니라 섹션 자체가 삭제되고 새 섹션이 삽입되어 의도치 않은 애니메이션 또는 레이아웃 깨짐이 발생합니다.

섹션의 빈 상태는 섹션 식별자에서 분리하고, 아이템 타입으로 표현하는 방식을 권장합니다:

🔧 제안된 수정
 enum MyTravelSectionKind: Hashable {
     case banner
-    case upcomingTrips(isEmpty: Bool)
+    case upcomingTrips
     case recommendedTrip
 }

빈 상태는 MyTravelItem에 별도 케이스(예: .emptyUpcomingTrip)를 추가하고, 인터랙터에서 아이템 배열이 비어 있을 때 해당 아이템을 섹션에 포함시키는 방식으로 처리하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/MyTravelFeature/Sources/Views/CollectionView/SectionKind/MyTravelSectionKind.swift`
around lines 11 - 14, 현재 MyTravelSectionKind의 upcomingTrips(isEmpty: Bool) 연관값
때문에 섹션 식별자가 상태 변경시 달라져서 섹션 전체가 삭제·재삽입됩니다; 수정 방법은 MyTravelSectionKind의
upcomingTrips 케이스에서 연관값을 제거(예: case upcomingTrips)하고 빈 상태 표현을 섹션 식별자에서 제거한 뒤,
MyTravelItem에 빈 상태용 케이스(예: .emptyUpcomingTrip)를 추가하여 인터랙터(또는 해당 데이터를 만드는 메서드)가
아이템 배열이 비어있을 때 해당 빈 아이템을 반환하도록 변경하세요; 스냅샷 생성 로직을 업데이트해 이제 upcomingTrips 섹션에는 실제
아이템 또는 .emptyUpcomingTrip이 포함되도록 하고, MyTravelSectionKind 및 스냅샷 관련 코드에서 더 이상
isEmpty 연관값을 사용하지 않도록 정리하세요.

Comment on lines +96 to +108
extension TabBarInteractor: MyTravelListener {
func myTraveDidTapFollowDetail(with recommendationId: Int) {
listener?.routeToFollow(with: recommendationId)
}

func myTraveDidTapSearch() {
listener?.routeToSearch()
}

func myTraveDidTapPopularTravel() {
listener?.routeToPopularTravel()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

myTravelListener 메서드명 오타 — myTraveDidTap...myTravelDidTap...

세 메서드 모두 "Travel"에서 l이 누락된 채로 정의되어 있습니다. 이 오타는 MyTravelListener 프로토콜 정의에서 기인한 것으로, 현재 PR에서 수정되어야 합니다.

-    func myTraveDidTapFollowDetail(with recommendationId: Int) {
+    func myTravelDidTapFollowDetail(with recommendationId: Int) {
         listener?.routeToFollow(with: recommendationId)
     }
     
-    func myTraveDidTapSearch() {
+    func myTravelDidTapSearch() {
         listener?.routeToSearch()
     }
     
-    func myTraveDidTapPopularTravel() {
+    func myTravelDidTapPopularTravel() {
         listener?.routeToPopularTravel()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/TabBarFeature/Sources/TabBarInteractor.swift` around lines
96 - 108, The three methods in TabBarInteractor that implement MyTravelListener
are misspelled (missing the "l"): update the method names
myTraveDidTapFollowDetail, myTraveDidTapSearch, and myTraveDidTapPopularTravel
to myTravelDidTapFollowDetail, myTravelDidTapSearch, and
myTravelDidTapPopularTravel respectively so they match the MyTravelListener
protocol, and ensure any references like listener?.routeToFollow,
listener?.routeToSearch, and listener?.routeToPopularTravel remain unchanged;
also run a compile to find and fix any other callsites or protocol declarations
that need the same rename.

Comment on lines +45 to +47
public override func prepareForReuse() {
disposeBag = DisposeBag()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

super.prepareForReuse() 호출 누락 및 modifier 순서 오류.

prepareForReuse()에서 super.prepareForReuse()를 호출하지 않고 있습니다. UIKit 문서에 따르면 반드시 super를 호출해야 합니다. 또한 SwiftLint에서 지적한 대로 override modifier가 public 앞에 와야 합니다.

🐛 수정 제안
-    public override func prepareForReuse() {
+    override public func prepareForReuse() {
+        super.prepareForReuse()
         disposeBag = DisposeBag()
     }
🧰 Tools
🪛 SwiftLint (0.63.2)

[Warning] 45-45: override modifier should come before public

(modifier_order)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/DSKit/Sources/Component/EmptyUpcomingCell.swift` around
lines 45 - 47, In EmptyUpcomingCell, change the prepareForReuse declaration to
put the override modifier before the access level (use "override public func
prepareForReuse()") and inside the method call super.prepareForReuse() as the
first statement, then reset the disposeBag (disposeBag = DisposeBag()) so the
cell is properly reset and adheres to SwiftLint modifier order.

Comment on lines +33 to +34
case .getUpcomingList:
return "api/v1/travels/upcoming/list"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

getUpcomingList 경로에 선행 /가 누락되어 API 호출이 실패합니다.

다른 모든 경로(/api/v1/travels, /api/v1/travels/upcoming 등)는 /로 시작하지만, Line 34의 getUpcomingList"api/v1/..." 으로 /가 빠져 있습니다. Moya의 baseURL + path 결합 시 잘못된 URL이 생성됩니다.

🐛 수정 제안
         case .getUpcomingList:
-            return "api/v1/travels/upcoming/list"
+            return "/api/v1/travels/upcoming/list"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case .getUpcomingList:
return "api/v1/travels/upcoming/list"
case .getUpcomingList:
return "/api/v1/travels/upcoming/list"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/TargetType/UserTravelAPI.swift` around
lines 33 - 34, The enum case .getUpcomingList returns a path missing the leading
'/', causing incorrect URL composition; update the path returned for
.getUpcomingList to include a leading slash (i.e.,
"/api/v1/travels/upcoming/list") so it matches the other cases and composes
correctly with Moya's baseURL; verify in the TargetType implementation that
other paths follow the same leading-slash pattern for consistency.

@KimNahun KimNahun merged commit bc7eef4 into develop Feb 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

design: 내 여행 탭 구현

2 participants