Skip to content

Feat/#27 검색api연결#32

Merged
ChoiAnYong merged 25 commits into
developfrom
feat/#27-검색API연결
Feb 21, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#27-\uac80\uc0c9API\uc5f0\uacb0"
Merged

Feat/#27 검색api연결#32
ChoiAnYong merged 25 commits into
developfrom
feat/#27-검색API연결

Conversation

@ChoiAnYong
Copy link
Copy Markdown
Contributor

@ChoiAnYong ChoiAnYong commented Feb 20, 2026

🔗 연결된 이슈

📄 작업 내용

  • RIBs 기반 검색 기능 구현: 검색 바 입력 및 결과 처리 로직 완성
  • Network 연동: 검색 API 연결
  • Setting 모듈 기능 구현: 사용자 정보 관리 및 클립보드 복사(UUID) 로직 구현
  • 공통 컴포넌트 NDGLToast 구현: UIWindow 기반의 전역 토스트 시스템 구축 및 다양한 ToastType 대응
  • 릴리즈 header 추가

💻 주요 코드 설명

Toast

  • 사용방법
Toast.show(
    type: .success, 
    message: "클립보드에 복사되었습니다.", 
    bottomPadding: 105
)

Summary by CodeRabbit

  • 새로운 기능

    • 여행 템플릿 검색 플로우 및 검색 결과 화면 추가
    • 검색바의 포커스·검색 버튼 이벤트 공개
    • 토스트 컴포넌트 및 URL 열기 유틸 추가
    • 키보드 자동 해제 편의 기능 추가
  • UI/UX 개선

    • 앱 아이콘 갱신 및 런치 화면에 새 로고·초록 배경 적용
    • 검색 결과 화면 레이아웃·헤더·빈 상태 개선
    • 설정에 인증 코드 복사 및 개인정보 항목 추가
  • 인프라

    • 빌드 번호 생성 보강 및 Info.plist에 API 키 항목 추가
    • 네트워크 요청에 환경별 API 키 헤더 자동 포함

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

Walkthrough

검색 흐름(SearchViewController → SearchInteractor → SearchRouter → SearchResult RIB → TemplatesSearchUsecase → TravelTemplateRepository → TravelTemplateService → 외부 API)을 추가·연결하고, 네트워크에 X_API_KEY 주입, 토스트·유틸·설정 관련 UI/유틸·런치·아이콘 리소스 변경을 포함합니다.

Changes

Cohort / File(s) Summary
앱 리소스
Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json, Projects/App/Resources/LaunchScreen.storyboard
앱 아이콘 파일명 변경 및 LaunchScreen storyboard에 이미지뷰(LaunchLogo.png), named color(green300) 및 IB 메타데이터/constraints 변경
앱 컴포넌트·DI
Projects/App/Sources/Application/AppComponent.swift, Projects/Features/MainFeature/Sources/MainBuilder.swift, Projects/Features/RootFeature/Sources/RootBuilder.swift
AppComponent에 templateSearchUsecase 추가 및 Main/Root 컴포넌트에 해당 의존성 노출(wiring)
도메인: UseCase·인터페이스
Projects/Domain/Sources/UseCase/TemplatesSearchUsecase.swift, Projects/Domain/Sources/Interface/.../TravelTemplateRepositoryInterface.swift
TemplatesSearchUsecase 프로토콜·구현 추가; TravelTemplateRepositoryInterface.searchTemplate 시그니처가 (keyword:page:size:)로 변경되어 [TripInfo] 반환
데이터 레포지토리
Projects/Data/Sources/Repository/.../TravelTemplateRepository.swift
리포지토리 메서드 시그니처 변경에 맞춰 서비스 호출을 매핑하여 [TripInfo] 반환으로 수정
네트워크 레이어
Projects/Modules/Networks/Sources/Service/TravelTemplateService.swift, Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift, .../TargetType/*API.swift, Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift
검색 API에 keyword/page/size 파라미터 추가 및 TripResponse 반환으로 변경. 여러 API 타겟에서 non-DEBUG 시 X-API-KEY 헤더 주입; NetworkConfiguration에 apiKey 프로퍼티 추가
SearchResult RIB (신규)
Projects/Features/SearchFeature/Sources/ChildRIBs/...
SearchResultBuilder.swift, SearchResultInteractor.swift, SearchResultRouter.swift, SearchResultViewController.swift, Models/*, Views/*, Views/CollectionView/*
SearchResult RIB 새로 추가: 빌더·인터랙터·라우터·뷰·프레젠테이션 모델·컴포지셔널 레이아웃·셀/헤더 등록 등—검색어로 데이터 조회 후 컬렉션뷰로 렌더링(로딩/에러/빈 상태 포함)
Search 기능 연동·확장
Projects/Features/SearchFeature/Sources/SearchBuilder.swift, Projects/Features/SearchFeature/Sources/SearchInteractor.swift, Projects/Features/SearchFeature/Sources/SearchRouter.swift, Projects/Features/SearchFeature/Sources/SearchViewController.swift
SearchComponent에 usecase 노출, interactor에 usecase 주입, Router에 SearchResult 빌더/자식 라우터 관리 추가, 뷰컨에서 검색 이벤트 및 자식 내비게이션 트리거 추가
UI 컴포넌트·토스트·아이콘
Projects/Modules/DSKit/Sources/Component/NDGLSearchBar.swift, Projects/Modules/DSKit/Sources/Component/NDGLToastView.swift, Projects/Modules/DSKit/Sources/Component/Toast.swift, Projects/Modules/DSKit/Resources/Assets.xcassets/ic_trip_bag.imageset/Contents.json, Projects/Modules/DSKit/Resources/Assets.xcassets/ic_check.imageset/Contents.json
검색바에 ControlEvent 노출 및 focus() 추가. NDGLToastView/Toast 유틸 추가. 아이콘 이미지셋 및 Contents.json 추가
설정 화면·클립보드·링크
Projects/Features/SettingFeature/Sources/SettingInteractor.swift, .../SettingViewController.swift, .../UITableView/SettingCellItem.swift, .../SettingSection.swift
식별코드 복사(copyToClipboard) 추가, notification 항목 제거·personalInformation 추가, FAQ/추천링크/개인정보 링크 열기 등에 URLHelper.openURL 사용
공통 유틸·확장
Projects/Core/Sources/Extensions/UIKit+/UIViewController+.swift, Projects/Core/Sources/Utils/URLHelper.swift
UIViewController 확장에 hideKeyboard()/dismissKeyboard() 추가. URLHelper.openURL(_:) 추가
빌드·환경 설정
fastlane/Fastfile, Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift
App Store 릴리스에 날짜 기반 빌드번호 helper 추가 및 increment 반영. InfoPlist에 X_API_KEY 항목 추가(플레이스홀더)
사소한 변경·헤더 등
여러 Projects/Domain/Sources/Interface/..., Projects/Features/HomeFeature/..., Projects/Features/MainFeature/..., Projects/Features/RootFeature/Sources/RootInteractor.swift, Projects/Features/TabBarFeature/Sources/TabBarViewController.swift
파일 헤더 주석명 수정, Home dataSource IUO 변경, 홈 섹션 추천문구 개인화(닉네임 사용), MainRouter 검색 네비게이션 애니메이션 비활성화, 로그인/회원가입 시 UserDefaults에 uuid/nickname 저장, TabBar 뷰 배경색 조정 등

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant SearchVC as SearchViewController
    participant SearchInteractor as SearchInteractor
    participant SearchRouter as SearchRouter
    participant SRView as SearchResultViewController
    participant SRInteractor as SearchResultInteractor
    participant Usecase as TemplatesSearchUsecase
    participant Repo as TravelTemplateRepository
    participant Service as TravelTemplateService
    participant API as ExternalAPI

    User->>SearchVC: 검색어 입력 및 검색 트리거
    SearchVC->>SearchInteractor: search(keyword:)
    SearchInteractor->>SearchRouter: attachSearchResult(keyword:)
    SearchRouter->>SRView: SearchResult 화면 생성/표시
    SRView->>SRInteractor: 초기화(with keyword, usecase)
    SRInteractor->>Usecase: searchTemplate(keyword:..., page:..., size:...)
    Usecase->>Repo: searchTemplate(keyword:..., page:..., size:...)
    Repo->>Service: searchTemplate(keyword:..., page:..., size:...)
    Service->>API: HTTP 요청 (검색, X-API-KEY 포함 가능)
    API-->>Service: TripResponse
    Service-->>Repo: TripResponse
    Repo-->>Usecase: [TripInfo]
    Usecase-->>SRInteractor: [TripInfo]
    SRInteractor->>SRView: update(with: PresentationModel)
    SRView-->>User: 결과 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 폴짝 뛰어와 키보드 톡,

검색어 한 줌 바람에 실어,
API로 달려가 답을 줍고,
컬렉션에 정성껏 놓아두네,
토스트로 웃고 UUID는 클립보드에!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive 검색 API 연결 외 Setting 모듈, Toast 컴포넌트, 빌드 번호 자동화 등 부분적으로 관련이 있지만 이슈 #27의 주요 목표와의 직접적 관련성이 명확하지 않습니다. 이슈 #27의 범위를 명확히 하거나, 관련 없는 변경사항이 있다면 별도의 이슈로 분리할 것을 권장합니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 검색 API 연결을 명확히 반영하고 있으며, 이슈 번호 #27을 포함하여 구체적입니다.
Description check ✅ Passed PR 설명은 템플릿을 충실히 따르며, 연결된 이슈, 작업 내용, 주요 코드 설명이 상세히 포함되어 있습니다.
Linked Issues check ✅ Passed 이슈 #27의 검색 API 연결 요구사항이 충족되었으며, 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 feat/#27-검색API연결

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: 18

Caution

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

⚠️ Outside diff range comments (3)
Projects/Domain/Sources/Interface/Place/PlaceRepositoryInterface.swift (1)

12-12: ⚠️ Potential issue | 🟠 Major

searchPlaces() 반환 타입을 실제 도메인 모델로 변경 필요

searchPlaces()Int 타입을 반환하고 //임시 주석이 붙어 있습니다. PlaceAPI에서도 // 아직 적용x 주석으로 표시되어 있습니다. fetchPlacePhotos()fetchPlaceDetail()이 각각 [PlacePhoto], PlaceDetail 같은 도메인 타입을 반환하는 반면, 검색 메서드만 여전히 불완전한 상태입니다. 검색 결과를 Int로 받는 것은 실제 사용이 불가능하므로, [PlaceInfo] 또는 새로운 도메인 모델을 정의하여 반환하도록 수정해야 합니다.

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

In `@Projects/Domain/Sources/Interface/Place/PlaceRepositoryInterface.swift` at
line 12, searchPlaces() currently returns Int as a placeholder; change its
signature in PlaceRepositoryInterface to return a proper domain model (e.g.,
async throws -> [PlaceInfo] or a new PlaceSearchResult type), add/define the
PlaceInfo (or PlaceSearchResult) model if it doesn't exist, and update all
implementations (including PlaceAPI and any classes conforming to
PlaceRepositoryInterface) to return that domain type instead of Int; ensure
related callers are updated to handle the new collection/object return from
searchPlaces(), and keep fetchPlacePhotos() and fetchPlaceDetail() signatures
consistent with existing domain types.
Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json (1)

10-30: ⚠️ Potential issue | 🟡 Minor

다크/틴티드 앱 아이콘 파일 미지정

darktinted appearance 항목에 "filename" 키가 없습니다. 의도적으로 비워둔 것이라면 문제없지만, Xcode가 경고를 발생시킬 수 있고 해당 모드에서 앱 아이콘이 표시되지 않습니다. 다크/틴티드 아이콘을 추후 제공할 계획이라면 이슈로 추적하는 것을 권장합니다.

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

In `@Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json`
around lines 10 - 30, Contents.json contains appearance entries for "dark" and
"tinted" without "filename" keys, which will cause Xcode warnings and no icon
shown in those modes; update the two appearance objects (the entries with
"appearance":"luminosity" and "value":"dark" and the one with "value":"tinted")
to include the appropriate "filename" fields pointing to the dark and tinted
icon asset names, or if you don't have those assets yet remove the appearance
blocks and create an issue to track adding them later so Xcode stops warning.
Projects/Features/TabBarFeature/Sources/TabBarViewController.swift (1)

62-69: ⚠️ Potential issue | 🟠 Major

TabBarRouter에서 Setting 탭이 누락되어 있음

PR에서 Setting 모듈 구현을 포함하겠다고 했지만, TabBarRouter.attachTabs()는 Home과 Travel 탭만 제공합니다(line 66-69). 반면 TabBarViewControllersetViewControllers는 3개 탭을 기대하고 있으며(line 68), 세 번째 탭인 infoNav는 여전히 내용 없는 infoDummy 플레이스홀더로 남아 있습니다.

Projects/Features/SettingFeature가 존재하므로, TabBarBuilderTabBarRouterSettingBuilder를 주입하고 attachTabs()에서 Setting RIB을 첫 번째 탭으로 연결하세요. 또는 Setting 탭이 불필요하다면 infoDummy 플레이스홀더와 해당 UINavigationController 생성을 완전히 제거하세요.

참고: TabBarInteractor에는 routeToSetting() 프로토콜 메서드가 있지만, 이는 Home 탭 내에서의 네비게이션으로 보입니다. 탭 바 자체의 세 번째 탭으로 Setting을 노출하는 것과는 별개입니다.

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

In `@Projects/Features/TabBarFeature/Sources/TabBarViewController.swift` around
lines 62 - 69, TabBarViewController currently sets three view controllers
including a placeholder infoDummy/infoNav while TabBarRouter.attachTabs() only
attaches Home and Travel; either wire the real Setting RIB into the tab bar or
remove the placeholder: update TabBarBuilder to accept a SettingBuilder
dependency and pass it into TabBarRouter, then modify TabBarRouter.attachTabs()
to call the SettingBuilder to build and attach the Setting RIB as the first tab
(replacing infoDummy/infoNav) so setViewControllers([settingNav, homeNav,
travelNav]) matches, or if Settings should not be a tab remove the infoDummy and
its infoNav creation from TabBarViewController and adjust setViewControllers to
only include homeNav and travelNav and remove SettingBuilder injection/usages
accordingly; reference TabBarRouter.attachTabs(), TabBarBuilder,
TabBarViewController.setViewControllers, infoDummy/infoNav, and SettingBuilder
when making the change.
🧹 Nitpick comments (25)
Projects/Modules/DSKit/Sources/Component/NDGLSearchBar.swift (1)

62-77: 신규 공개 멤버에 Doc Comment 누락

searchButtonClicked, editingDidBegin, focus() 모두 기존 공개 프로퍼티(leadingButtonDidTap, trailingButtonDidTap, searchText)와 달리 /// 문서 주석이 없습니다. DSKit 컴포넌트는 외부 모듈에서 직접 소비되므로, 공개 API 표면의 일관된 문서화가 유지보수에 도움이 됩니다.

📝 제안: Doc Comment 추가
+    /// 키보드 검색(Return) 버튼 탭 이벤트 (`returnKeyType = .search` 기준)
     public var searchButtonClicked: ControlEvent<Void> {
         textField.rx.controlEvent(.editingDidEndOnExit)
     }
     
+    /// 텍스트 필드 편집 시작 이벤트
     public var editingDidBegin: ControlEvent<Void> {
         textField.rx.controlEvent(.editingDidBegin)
     }
     
+    /// 텍스트 필드에 포커스를 프로그래밍 방식으로 설정합니다.
     public func focus() {
         textField.becomeFirstResponder()
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/DSKit/Sources/Component/NDGLSearchBar.swift` around lines 62
- 77, Public API members searchButtonClicked, editingDidBegin, and focus() lack
/// doc comments; add concise /// documentation for each to match existing
public members (e.g., leadingButtonDidTap, trailingButtonDidTap, searchText).
For searchButtonClicked describe it emits when the
return/editing-did-end-on-exit is triggered; for editingDidBegin describe it
emits when the text field begins editing; for focus() describe that it makes the
text field first responder. Keep phrasing consistent, brief, and placed
immediately above the respective declarations.
Projects/Features/HomeFeature/Sources/HomeViewController.swift (1)

47-47: ! = nil 조합은 불필요하며, 단순 옵셔널(?)이 의도를 더 명확하게 표현합니다.

현재 모든 접근부(dataSource?.apply(...), dataSource?.itemIdentifier(...) 등)가 옵셔널 체이닝(?.)을 사용하므로 동작 차이는 없습니다. 그러나 IUO(!)와 = nil의 조합은 다음 두 가지 이유로 혼란을 줍니다:

  1. !는 "초기화 후 항상 non-nil"임을 암시하지만, = nil은 그 반대를 명시합니다.
  2. 향후 기여자가 ! 선언만 보고 ?. 없이 직접 접근(dataSource.apply(...))을 추가할 경우, setDataSource()가 호출되기 전 타이밍에 크래시가 발생할 수 있습니다.

?로 되돌리거나, 실제로 IUO를 의도한다면 = nil을 제거하는 것이 권장됩니다.

♻️ 수정 제안
-    private var dataSource: UICollectionViewDiffableDataSource<HomeSectionKind, HomeItem>! = nil
+    private var dataSource: UICollectionViewDiffableDataSource<HomeSectionKind, HomeItem>?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/HomeFeature/Sources/HomeViewController.swift` at line 47,
The declaration of dataSource uses an implicitly unwrapped optional with an
explicit = nil which is confusing; change the property declaration in
HomeViewController from an IUO to a plain optional by replacing
"UICollectionViewDiffableDataSource<HomeSectionKind, HomeItem>! = nil" with a
nullable type (use "?") so callers keep using optional chaining (e.g.,
dataSource?.apply(...), dataSource?.itemIdentifier(...)); alternatively, if you
truly intend an IUO, remove the "= nil" initializer and ensure setDataSource()
always runs before any access—prefer the "?" change for clarity.
Projects/Core/Sources/Extensions/UIKit+/UIViewController+.swift (1)

18-21: dismissKeyboard()private으로 제한하는 것이 적절합니다.

이 메서드는 hideKeyboard()의 내부 구현 세부사항(셀렉터 타깃)으로, 외부에서 직접 호출할 의도가 없습니다. public extension 내에서 명시적 접근 제어자가 없으면 암묵적으로 public이 되어 불필요하게 API 표면을 넓히고, 서브클래스나 다른 익스텐션과의 이름 충돌 위험도 생깁니다. @objc private으로 선언해도 셀렉터는 정상적으로 동작합니다.

♻️ 접근 제어자 수정안
 `@objc`
-func dismissKeyboard() {
+private func dismissKeyboard() {
     view.endEditing(true)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Extensions/UIKit`+/UIViewController+.swift around lines
18 - 21, The dismissKeyboard() method is currently implicitly public in the
public extension and should be made private to avoid expanding the API and
risking name collisions; change the declaration of dismissKeyboard() to use
explicit access control (e.g., mark it as `@objc` private func dismissKeyboard())
so it remains callable via selector from hideKeyboard() but is not exposed
publicly.
Projects/Features/SearchFeature/Sources/Views/EmptyView.swift (2)

100-108: containerStackView에 가로 폭 제약 없음 — 텍스트 오버플로우 위험

containerStackViewcenter.equalToSuperview()만 설정되어 있어 가로 폭 제약이 없습니다. subTitleLabelnumberOfLines = 0이 설정되어 있더라도 width constraint가 없으면 자동 줄바꿈이 동작하지 않습니다. 현재 enum 값들은 명시적 \n을 포함하고 있어 즉각적인 문제는 없지만, 향후 문자열이 \n 없이 변경되거나 현지화될 경우 레이블이 화면 밖으로 넘칠 수 있습니다.

♻️ 제안: 가로 폭 제약 추가
     func setLayout() {
         imageView.snp.makeConstraints {
             $0.size.equalTo(100.adjustedH)
         }
         
         containerStackView.snp.makeConstraints {
             $0.center.equalToSuperview()
+            $0.width.lessThanOrEqualToSuperview().offset(-48)
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SearchFeature/Sources/Views/EmptyView.swift` around lines
100 - 108, The containerStackView is only centered in setLayout() so it has no
horizontal constraints, which prevents subTitleLabel (even with numberOfLines =
0) from wrapping; update setLayout() to add a horizontal width constraint for
containerStackView (e.g., leading/trailing or width <= superview with safe area
insets) so the stack view has a bounded width and subTitleLabel can line-break
properly; reference containerStackView and subTitleLabel inside the setLayout()
method and use SnapKit constraints to pin the stack view's horizontal edges or
set an explicit max width.

20-52: setStyle()changeType() 간의 콘텐츠 설정 로직 중복 및 self.type의 사용되지 않는 상태

setStyle()(Lines 58, 63-68, 73-78)에서 초기 콘텐츠(이미지, 타이틀, 서브타이틀)를 직접 설정하는 코드가 changeType()(Lines 37-51)과 정확히 동일하게 중복되어 있습니다. 색상 토큰이나 텍스트 스타일을 변경할 때 두 곳을 모두 수정해야 하는 문제가 있습니다.

또한 self.type(Line 20)은 changeType()에서 저장되지만 이후 어디서도 읽히지 않아 사용되지 않는 상태로 남아 있습니다.

setStyle()에서 콘텐츠 설정 코드를 제거하고, 초기화 시 changeType(.start)를 호출하는 방식으로 단일 경로로 통합하는 것을 권장합니다.

♻️ 제안: 콘텐츠 설정 로직 중복 제거
 final class EmptyView: UIView {
     private let imageView = UIImageView()
     private let titleLabel = UILabel()
     private let subTitleLabel = UILabel()
     private let containerStackView = UIStackView()
     private let titleStackView = UIStackView()
     
-    private var type: EmptyViewType = .start
-    
     override init(frame: CGRect) {
         super.init(frame: frame)
         
         setStyle()
         setUI()
         setLayout()
+        changeType(.start)
     }
     func setStyle() {
         imageView.do {
-            $0.image = type.image
             $0.contentMode = .scaleAspectFit
         }
         
-        titleLabel.do {
-            $0.setText(
-                .subTitleMSB,
-                text: type.title,
-                color: DSKitAsset.Colors.black500.color,
-                alignment: .center
-            )
-        }
-        
         subTitleLabel.do {
             $0.numberOfLines = 0
-            $0.setText(
-                .bodyLR,
-                text: type.subTitle,
-                color: DSKitAsset.Colors.black400.color,
-                alignment: .center
-            )
         }
         // ...
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SearchFeature/Sources/Views/EmptyView.swift` around lines
20 - 52, Remove the duplicated content setup from setStyle() (the
imageView.image and both title/subTitle setText calls) and consolidate content
initialization by calling changeType(.start) from the initializer (override
init(frame:)) after setStyle()/setUI()/setLayout(); ensure changeType(_:)
remains the single place that assigns imageView.image and sets
titleLabel/subTitleLabel styles and text. Also verify whether the stored
property self.type is needed elsewhere—if not, remove the property; if you keep
it, ensure it is only mutated in changeType(_:). Use the function names
setStyle(), changeType(_:) and the property self.type to locate changes.
Projects/Features/SettingFeature/Sources/SettingInteractor.swift (1)

55-56: 주석 처리된 코드 제거 권장

.notification 케이스가 enum에서도 제거되었으므로 해당 주석을 함께 제거하면 코드가 더 명확해집니다.

♻️ 주석 제거 제안
 func didTapMenu(item: SettingCellItem) {
     // 각 메뉴 타이틀에 따른 동작 처리
     switch item {
-//        case .notification:
-//            print("알림")
     case .faq:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SettingFeature/Sources/SettingInteractor.swift` around
lines 55 - 56, Remove the leftover commented code for the removed enum case:
delete the two commented lines "//        case .notification:" and "//          
print("알림")" from SettingInteractor.swift (these correspond to the removed
.notification enum case) so the SettingInteractor code no longer contains stale
commented branches and remains clear.
Projects/Features/SettingFeature/Sources/UITableView/SettingCellItem.swift (1)

12-12: 주석 처리된 코드 제거 권장

notification 케이스가 완전히 제거되었으므로 관련 주석 코드(lines 12, 22, 34–35)도 함께 정리하는 것이 좋습니다.

♻️ 주석 제거 제안
 public enum SettingCellItem: Int, CaseIterable {
-//    case notification
     case faq
     ...
     var title: String {
         switch self {
-//        case .notification: return "알림 설정"
         case .faq: return "FAQ"
     ...
     var cellType: SettingCellType {
         switch self {
-//        case .notification:
-//            return .toggle(isOn: true)
         case .version:

Also applies to: 22-22, 34-35

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

In `@Projects/Features/SettingFeature/Sources/UITableView/SettingCellItem.swift`
at line 12, Remove leftover commented-out enum case and related commented lines
in SettingCellItem.swift: delete the commented "//    case notification" and any
other commented fragments at the same locations (lines referenced in review: 12,
22, 34–35) so the enum and surrounding code contain no stale commented cases;
ensure the enum (likely named SettingCellItem or similar) compiles cleanly after
removal and run a quick build to verify no references to the removed case
remain.
Projects/Core/Sources/Utils/URLHelper.swift (1)

12-15: [선택적] UIApplication.shared.open은 메인 스레드에서 호출되어야 합니다.

현재 호출부(SettingInteractor.didTapMenu)가 UIKit 탭 이벤트에 의해 메인 스레드에서 호출되므로 실제로 문제는 없지만, 유틸리티 함수 자체에 메인 스레드 보장이 없어 향후 백그라운드 스케줄러에서 호출될 경우 런타임 경고가 발생할 수 있습니다.

♻️ 메인 스레드 보장 추가 제안
 public static func openURL(_ urlString: String) {
     guard let url = URL(string: urlString) else { return }
-    UIApplication.shared.open(url)
+    DispatchQueue.main.async {
+        UIApplication.shared.open(url)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Utils/URLHelper.swift` around lines 12 - 15, The
openURL(_:) helper (URLHelper.openURL) must ensure UIApplication.shared.open is
invoked on the main thread to avoid runtime warnings when called from background
contexts; update the method to keep the existing URL guard and dispatch the open
call to the main thread (e.g., check Thread.isMainThread and call directly
otherwise wrap UIApplication.shared.open(url) in DispatchQueue.main.async) so
all callers (including future background callers) are safe.
Projects/Features/SettingFeature/Sources/SettingViewController.swift (1)

132-136: 주석 처리된 코드 제거 권장

.notification 케이스 제거와 함께 해당 주석 블록도 정리하면 좋습니다.

♻️ 주석 제거 제안
-//        if menu == .notification {
-//            cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tableView.bounds.width)
-//        } else {
         cell.separatorInset = UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right: 24.adjusted)
-//        }
🤖 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 132 - 136, Remove the obsolete commented conditional block that checks
`menu == .notification` and the alternate `cell.separatorInset` so only the
active assignment remains; locate the code around `cell.separatorInset =
UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right: 24.adjusted)` in
SettingViewController and delete the commented lines referencing `menu ==
.notification` and its inset to keep the code clean.
Projects/Modules/DSKit/Sources/Component/NDGLToastView.swift (2)

60-62: 아이콘 크기 제약 조건에 adjustedadjustedH를 분리 적용해야 합니다.

$0.size.equalTo(24.adjustedH)는 너비와 높이 모두를 높이 비율(screenHeight/852)로 스케일링합니다. 가이드라인에 따르면 수평 치수는 adjusted(너비 비율), 수직 치수는 adjustedH를 사용해야 하므로, 너비에 adjustedH를 적용하면 일부 기기에서 아이콘이 정사각형으로 보이지 않을 수 있습니다.

♻️ 제안 수정
 iconImageView.snp.makeConstraints {
-    $0.size.equalTo(24.adjustedH)
+    $0.width.equalTo(24.adjusted)
+    $0.height.equalTo(24.adjustedH)
 }

Based on learnings: DSKit 컴포넌트에서 수평 간격에는 adjustedW/adjusted, 수직 간격에는 adjustedH를 일관되게 사용해야 합니다.

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

In `@Projects/Modules/DSKit/Sources/Component/NDGLToastView.swift` around lines 60
- 62, In NDGLToastView's iconImageView.snp.makeConstraints block the size is
scaled using 24.adjustedH for both dimensions; change it to set width and height
separately so horizontal uses adjusted (or adjustedW) and vertical uses
adjustedH—i.e. replace the $0.size.equalTo(24.adjustedH) usage with separate
$0.width.equalTo(24.adjusted) and $0.height.equalTo(24.adjustedH) constraints on
iconImageView.

79-82: .success 케이스의 미완성 아이콘을 추적해야 합니다.

현재 icStarFill1은 임시 대체 이미지로, 주석에 "아직 아이콘 추가 안됨"이라고 명시되어 있습니다. 코드에 // TODO: 형식으로 남겨두지 않으면 나중에 놓칠 수 있습니다.

💡 제안 수정
-            case .success:
-                // 아직 아이콘 추가 안됨
-                return DSKitAsset.Assets.icStarFill1.image
+            case .success:
+                // TODO: 실제 success 아이콘으로 교체 필요
+                return DSKitAsset.Assets.icStarFill1.image

별도 이슈로 추적을 원하시면 생성해 드릴 수 있습니다.

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

In `@Projects/Modules/DSKit/Sources/Component/NDGLToastView.swift` around lines 79
- 82, The .success case in NDGLToastView currently returns a temporary
DSKitAsset.Assets.icStarFill1.image and has a plain comment; replace that
comment with a persistent TODO (e.g., "// TODO: replace with real success icon —
see ISSUE-XXXX") so the missing icon is tracked, keep the current image as a
fallback for now, and if you prefer create an issue and reference its ID in the
TODO; target the .success case in the NDGLToastView computed-image (or
imageForState) code path so it’s easy to find later.
Projects/Modules/DSKit/Sources/Component/Toast.swift (2)

11-11: 정적 유틸리티 클래스에 private init()을 추가하거나 enum으로 변경하세요.

Toast는 인스턴스 멤버 없이 정적 메서드만 갖는 클래스입니다. 현재는 Toast() 인스턴스를 생성할 수 있어 혼란을 줄 수 있습니다.

♻️ 제안 수정 (private init 추가)
 public final class Toast {
+    private init() {}
+
     public static func show(

또는 Swift 관용적 방식으로 enum을 사용하면 인스턴스 생성 자체를 컴파일러 수준에서 막을 수 있습니다.

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

In `@Projects/Modules/DSKit/Sources/Component/Toast.swift` at line 11, The Toast
type is a static-utility holder with no instance members so allow-list instances
by either adding a private initializer or converting it to an enum: update the
declaration for Toast accordingly—either keep "public final class Toast" and add
"private init() {}" inside the type, or change the declaration to "public enum
Toast" and ensure all members remain static; this prevents external
instantiation and matches Swift conventions.

18-18: scene.windows는 iOS 15.0에서 deprecated — scene.keyWindow로 교체하세요.

UIWindowScene.windows는 iOS 15.0에서 deprecated되었으며, 대신 관련 window scene에서 keyWindow를 사용하도록 권고됩니다. 프로젝트의 배포 타깃은 iOS 17.0이므로 scene.keyWindow를 안전하게 사용할 수 있습니다.

♻️ 제안 수정
-        guard let window = scene.windows.first(where: { $0.isKeyWindow }) else { return }
+        guard let window = scene.keyWindow else { return }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/DSKit/Sources/Component/Toast.swift` at line 18, Update the
deprecated usage of scene.windows in Toast.swift by replacing the lookup
"scene.windows.first(where: { $0.isKeyWindow })" with the modern API
"scene.keyWindow"; specifically, change the guard that finds the window (used by
the Toast presentation logic) to "guard let window = scene.keyWindow else {
return }" so the toast uses the scene's keyWindow safely for iOS 17.0 targets.
Projects/Features/MainFeature/Sources/MainBuilder.swift (1)

24-24: SwiftLint: 쉼표 뒤 공백 누락 (comma)

PopularTravelDependency,SearchDependency 사이에 공백이 없습니다.

♻️ 제안 수정
-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 24, In the
MainComponent class declaration, add a missing space after the comma between the
protocols "PopularTravelDependency,SearchDependency" so it reads
"PopularTravelDependency, SearchDependency" to satisfy SwiftLint's comma spacing
rule; edit the declaration of final class MainComponent:
Component<MainDependency>, FollowDetailDependency, PopularTravelDependency,
SearchDependency, SettingDependency, TabBarDependency to insert the space.
Projects/Features/SearchFeature/Sources/ChildRIBs/Models/SearchResultPresentationModel.swift (1)

28-38: SwiftLint: 암묵적 반환 사용 권장 (implicit_return)

toSearchResultModel()에서 return 키워드를 제거하여 SwiftLint 경고를 해소할 수 있습니다.

♻️ 제안 수정
 extension TripInfo {
     func toSearchResultModel() -> SearchResultPresentationModel.ResultTrip {
-        return SearchResultPresentationModel.ResultTrip(
+        SearchResultPresentationModel.ResultTrip(
             id: self.id,
             title: self.title,
             thumbnailUrl: self.thumbnailUrl,
             creator: self.creator,
             schedule: "\(self.nights)박 \(self.days)일",
             country: self.country,
             city: self.city
         )
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/Models/SearchResultPresentationModel.swift`
around lines 28 - 38, The function toSearchResultModel() uses an explicit return
which triggers SwiftLint's implicit_return rule; remove the `return` keyword and
convert the body to an implicit single-expression return by directly evaluating
SearchResultPresentationModel.ResultTrip(...) so the function returns that value
without `return`, updating the function in
SearchResultPresentationModel.toSearchResultModel (and any identical helpers)
accordingly.
Projects/Features/MainFeature/Sources/MainInteractor.swift (1)

55-57: 임시 주석 // 이건 뭐임 정리 필요

빈 구현에 남겨진 비공식 주석입니다. TODO 형식으로 의도를 명확히 표기하거나, 구현이 없는 이유를 설명하는 주석으로 교체해 주세요.

♻️ 제안 수정
 func followDetailDidAddTrip(title: String, startDate: Date, endDate: Date) {
-    // 이건 뭐임
+    // TODO: 여행 추가 후 처리 로직 구현
 }
🤖 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 55 -
57, The informal comment inside followDetailDidAddTrip(title:startDate:endDate:)
should be replaced with a clear TODO or explanatory comment explaining why the
method is intentionally empty (or implement it if it was forgotten); update the
comment to either a TODO with expected behavior and related task/issue ID or a
short rationale like “intentionally no-op — handled by X” so maintainers know
whether to implement logic here or leave it blank.
Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/SectionKind/SearchResultSectionKind.swift (1)

9-9: import Foundation 불필요

SearchResultSectionKindInt 원시값과 CaseIterable만 사용하므로 Foundation 타입이 전혀 없습니다. import Foundation을 제거할 수 있습니다.

♻️ 제안 수정
-import Foundation
-
 enum SearchResultSectionKind: Int, CaseIterable {
     case resultTrip
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/SectionKind/SearchResultSectionKind.swift`
at line 9, SearchResultSectionKind에 불필요한 Foundation 임포트가 포함되어 있으니 제거하세요: 파일의
import Foundation 줄을 삭제해서 빌드에서 불필요한 의존성을 없애고, enum SearchResultSectionKind(원시값
Int, CaseIterable 사용)는 Foundation 없이도 잘 동작하는지 확인하세요.
Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/CompositionalLayout/SearchResultCompositionalLayout.swift (2)

72-88: emptyLayout()에서 NSCollectionLayoutGroup 직접 초기화 — 팩토리 메서드 사용 권장

NSCollectionLayoutGroup(layoutSize: groupSize)NSCollectionLayoutItem으로부터 상속된 이니셜라이저를 직접 호출합니다. 공식 문서 및 관례상 그룹은 .horizontal(layoutSize:subitems:), .vertical(layoutSize:subitems:), .custom(layoutSize:itemProvider:) 팩토리 메서드로 생성하는 것이 표준입니다.

♻️ 제안된 수정
-        let group = NSCollectionLayoutGroup(layoutSize: groupSize)
+        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/CompositionalLayout/SearchResultCompositionalLayout.swift`
around lines 72 - 88, In emptyLayout(), avoid calling
NSCollectionLayoutGroup(layoutSize:) directly; instead create the group via a
factory method such as NSCollectionLayoutGroup.horizontal(layoutSize:subitems:)
or .vertical(layoutSize:subitems:) (or .custom if needed) using the existing
item (NSCollectionLayoutItem named item) and groupSize, so replace the direct
initializer with the appropriate factory call to produce a group built from
subitems.

15-15: SwiftLint 경고 2건 수정

  1. Line 15 (implicit_return): sectionProvider 클로저에서 암시적 반환 사용
  2. Line 61 (operator_usage_whitespace): = 연산자 뒤에 공백이 2개
♻️ 제안된 수정
-        return UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
+        UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
-        let header =  NSCollectionLayoutBoundarySupplementaryItem(
+        let header = NSCollectionLayoutBoundarySupplementaryItem(

Also applies to: 61-61

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

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/CompositionalLayout/SearchResultCompositionalLayout.swift`
at line 15, In SearchResultCompositionalLayout fix the two SwiftLint issues by
(1) making the sectionProvider closure passed to
UICollectionViewCompositionalLayout use an explicit return statement instead of
an implicit single-expression return (i.e., add "return" before the
compositional layout section being produced inside the closure) and (2)
normalize operator spacing around the assignment found in this file (ensure
exactly one space before and after the '=' in the assignment near where the
layout or configuration property is set); update the sectionProvider closure and
the assignment so they comply with implicit_return and operator_usage_whitespace
rules respectively.
Projects/Features/SearchFeature/Sources/SearchInteractor.swift (1)

32-60: SearchInteractor에서 사용되지 않는 usecase 제거

private let usecase: TemplatesSearchUsecaseProtocol는 초기화 시 주입되지만 SearchInteractor 내의 어떤 메서드에서도 호출되지 않습니다. 실제 검색은 SearchResultInteractor에서 수행되며, 해당 인터랙터는 SearchComponent를 통해 자체 의존성으로 usecase를 받습니다. SearchInteractor에 저장된 usecase는 불필요한 참조이므로 제거하세요.

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

In `@Projects/Features/SearchFeature/Sources/SearchInteractor.swift` around lines
32 - 60, The stored property `usecase: TemplatesSearchUsecaseProtocol` in
SearchInteractor is unused and should be removed; update the initializer
`init(presenter: SearchPresentable, usecase: TemplatesSearchUsecaseProtocol)` to
drop the `usecase` parameter and remove the `self.usecase = usecase` assignment,
leaving `init(presenter: SearchPresentable)` and its existing presenter setup
intact, then update any caller(s) that instantiate `SearchInteractor` to no
longer pass a usecase so there are no dangling unused references to
`TemplatesSearchUsecaseProtocol`.
Projects/Features/SearchFeature/Sources/SearchViewController.swift (2)

150-166: searchButtonClickedtrailingButtonDidTap 핸들러 중복 → Observable.merge로 통합 권장

두 구독이 완전히 동일한 로직을 수행하고 있습니다.

♻️ 개선 제안
-        searchBar.searchButtonClicked
-            .withLatestFrom(searchBar.searchText)
-            .compactMap { $0 }
-            .filter { !$0.isEmpty }
-            .subscribe(with: self) { owner, text in
-                owner.listener?.search(keyword: text)
-            }
-            .disposed(by: disposeBag)
-        
-        searchBar.trailingButtonDidTap
-            .withLatestFrom(searchBar.searchText)
-            .compactMap { $0 }
-            .filter { !$0.isEmpty }
-            .subscribe(with: self) { owner, text in
-                owner.listener?.search(keyword: text)
-            }
-            .disposed(by: disposeBag)
+        Observable.merge(searchBar.searchButtonClicked, searchBar.trailingButtonDidTap)
+            .withLatestFrom(searchBar.searchText)
+            .compactMap { $0 }
+            .filter { !$0.isEmpty }
+            .subscribe(with: self) { owner, text in
+                owner.listener?.search(keyword: text)
+            }
+            .disposed(by: disposeBag)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SearchFeature/Sources/SearchViewController.swift` around
lines 150 - 166, Duplicate subscriptions to searchBar.searchButtonClicked and
searchBar.trailingButtonDidTap perform identical work; merge them into a single
observable pipeline. Use Observable.merge(searchBar.searchButtonClicked,
searchBar.trailingButtonDidTap) (or equivalent) then
.withLatestFrom(searchBar.searchText).compactMap { $0 }.filter { !$0.isEmpty
}.subscribe(with: self) { owner, text in owner.listener?.search(keyword: text)
}.disposed(by: disposeBag) so there's only one subscription and shared handling
of search input.

179-181: SwiftLint implicit_return 경고 해소

♻️ 개선 제안
     func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
-        return gestureRecognizer == contentNavigationController.interactivePopGestureRecognizer
+        gestureRecognizer == contentNavigationController.interactivePopGestureRecognizer
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SearchFeature/Sources/SearchViewController.swift` around
lines 179 - 181, The function gestureRecognizer(_:shouldBeRequiredToFailBy:)
contains a single-expression return; remove the explicit "return" to satisfy
SwiftLint's implicit_return rule by changing the body to a single expression
using gestureRecognizer ==
contentNavigationController.interactivePopGestureRecognizer; update the method
gestureRecognizer(_:shouldBeRequiredToFailBy:) in SearchViewController to use
the implicit return form referencing
contentNavigationController.interactivePopGestureRecognizer.
Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/Registration/SearchResultRegistration.swift (1)

14-40: SwiftLint 경고 4건 수정 필요: implicit_return 및 미사용 클로저 파라미터

♻️ 개선 제안
-    func createResultTripCellRegistration()
-    -> UICollectionView.CellRegistration<PopularInfoCell, SearchResultPresentationModel.ResultTrip> {
-        return UICollectionView.CellRegistration { cell, indexPath, item in
+    func createResultTripCellRegistration()
+    -> UICollectionView.CellRegistration<PopularInfoCell, SearchResultPresentationModel.ResultTrip> {
+        UICollectionView.CellRegistration { cell, _, item in
             cell.configure(
                 thumbnailUrl: item.thumbnailUrl,
                 city: item.city,
                 title: item.title,
                 nation: item.country,
                 schedule: item.schedule
             )
         }
     }
     
     func createHeaderRegistration(
         dataSource: UICollectionViewDiffableDataSource<SearchResultSectionKind, SearchResultItem>
     ) -> UICollectionView.SupplementaryRegistration<SearchResultHeaderView> {
-        return UICollectionView.SupplementaryRegistration<SearchResultHeaderView>(
+        UICollectionView.SupplementaryRegistration<SearchResultHeaderView>(
             elementKind: UICollectionView.elementKindSectionHeader
-        ) { [weak dataSource] headerView, elementKind, indexPath in
+        ) { [weak dataSource] headerView, _, _ in
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/Views/CollectionView/Registration/SearchResultRegistration.swift`
around lines 14 - 40, The functions createResultTripCellRegistration and
createHeaderRegistration should remove the explicit "return" to satisfy the
implicit_return rule and mark unused closure parameters as "_" to silence
unused-parameter warnings: in createResultTripCellRegistration drop the leading
"return" and change the CellRegistration closure signature to use "_" for the
unused indexPath parameter (e.g., { cell, _, item in ... }), and in
createHeaderRegistration drop "return" and change the SupplementaryRegistration
closure signature to { [weak dataSource] headerView, _, _ in ... } (or name only
the parameters you use) so only headerView is referenced inside the closure
while keeping the guard let dataSource ... logic intact.
Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultViewController.swift (1)

150-166: SearchResultSectionKind 유효성 검사 실패 시 빈 UICollectionReusableView() 반환은 개선이 필요합니다.

Line 155에서 UICollectionReusableView()를 직접 생성하여 반환하고 있습니다. UIKit의 supplementary view provider에서 dequeue하지 않은 뷰를 반환하면 예기치 않은 동작이 발생할 수 있습니다. 현재 섹션 구조상 이 guard에 도달할 가능성은 낮지만, 방어적으로 nil을 반환하는 것이 더 안전합니다.

♻️ 수정 제안
         dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
             guard SearchResultSectionKind(rawValue: indexPath.section) != nil else {
-                return UICollectionReusableView()
+                return nil
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultViewController.swift`
around lines 150 - 166, In configureSupplementaryView, the
supplementaryViewProvider closure currently returns a newly allocated
UICollectionReusableView when SearchResultSectionKind validation fails; replace
that defensive branch to return nil instead (do not instantiate a view), so
change the guard in dataSource.supplementaryViewProvider to return nil on
invalid section; keep the rest of the closure (kind check and dequeue using
headerRegistration from createHeaderRegistration) unchanged.
Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultInteractor.swift (1)

94-96: searchBtnTapped()이 빈 구현으로 남아있습니다.

SearchResultPresentableListener 프로토콜에서 요구하는 메서드이지만, 구현이 비어있습니다. 향후 구현 예정이라면 // TODO: 주석을 추가하여 의도를 명확히 해주세요.

이 메서드의 구현을 도와드릴까요? 또는 추적을 위해 별도 이슈를 생성해드릴까요?

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

In
`@Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultInteractor.swift`
around lines 94 - 96, searchBtnTapped() in SearchResultInteractor is left empty
but required by the SearchResultPresentableListener protocol; either implement
the intended action (e.g., call the search use-case, update state via
SearchResultInteractor methods, or notify the router/presenter) or, if not
ready, add a clear TODO comment describing the planned behavior and a
ticket/issue ID; update the implementation in the
SearchResultInteractor.searchBtnTapped() method (or adjust the protocol if this
callback is unnecessary) so the intent is explicit and traceable.

"images" : [
{
"filename" : "AppIcon~ios-marketing.png",
"filename" : "AppIcon_Spring_IOS_1024.png",
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:

#!/bin/bash
# PNG 파일 존재 여부 확인
fd -e png "AppIcon_Spring_IOS_1024" --search-path Projects/App/Resources/Assets.xcassets/AppIcon.appiconset

# 전체 AppIcon.appiconset 디렉터리 구조 확인
echo "=== AppIcon.appiconset 디렉터리 내용 ==="
ls -la Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/

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

Length of output: 451


🏁 Script executed:

#!/bin/bash
# Contents.json 파일의 구조 확인 (dark/tinted 변형 확인)
cat -n Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json | head -40

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

Length of output: 983


AppIcon_Spring_IOS_1024.png 파일 확인됨

새 아이콘 파일이 디렉터리에 존재합니다. 그러나 Dark 및 Tinted 모양 변형에 대해 filename 속성이 누락되어 있습니다. 이들 항목에도 해당하는 PNG 파일을 할당하거나, 다크 모드/틴트 변형을 지원하지 않는다면 이 항목들을 제거하세요.

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

In `@Projects/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json` at
line 4, Contents.json currently lists the new icon "AppIcon_Spring_IOS_1024.png"
but the Dark and Tinted appearance variants in the AppIcon.appiconset entries
are missing their "filename" properties; update the Contents.json entries for
those appearances to point to the appropriate PNG files (e.g., the dark and
tinted asset filenames) or remove the appearance entries entirely if you do not
support dark/tinted variants, ensuring you modify the entries that reference
"AppIcon_Spring_IOS_1024.png" and the appearance objects (dark/tinted) in
Contents.json.

Comment on lines +31 to +32
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerY" secondItem="Bcu-3y-fUS" secondAttribute="centerY" id="lzf-rq-KXE"/>
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="tjh-yU-7rh"/>
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

safeArea 기준 중앙 정렬 → 화면 기준으로 변경 권장

현재 centerX/centerY 제약이 safeArea(Bcu-3y-fUS)를 기준으로 설정되어 있습니다. Safe area는 Dynamic Island/노치(상단 ~59pt)와 홈 인디케이터(하단 ~34pt)를 제외한 영역이므로, 로고가 시각적 화면 중앙보다 약간 아래에 표시됩니다. 런치 스크린은 일반적으로 전체 화면 기준으로 중앙 정렬하는 것이 자연스럽습니다.

🛠️ 수정 제안: safeArea → 루트 뷰 기준 중앙 정렬
-    <constraint firstItem="cNo-yc-A0v" firstAttribute="centerY" secondItem="Bcu-3y-fUS" secondAttribute="centerY" id="lzf-rq-KXE"/>
-    <constraint firstItem="cNo-yc-A0v" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="tjh-yU-7rh"/>
+    <constraint firstItem="cNo-yc-A0v" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="lzf-rq-KXE"/>
+    <constraint firstItem="cNo-yc-A0v" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="tjh-yU-7rh"/>
📝 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
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerY" secondItem="Bcu-3y-fUS" secondAttribute="centerY" id="lzf-rq-KXE"/>
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerX" secondItem="Bcu-3y-fUS" secondAttribute="centerX" id="tjh-yU-7rh"/>
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="lzf-rq-KXE"/>
<constraint firstItem="cNo-yc-A0v" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="tjh-yU-7rh"/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/App/Resources/LaunchScreen.storyboard` around lines 31 - 32, 현재 런치
스크린의 로고 뷰(cNo-yc-A0v)를 safeArea(Bcu-3y-fUS)에 맞춰 중앙 정렬하는 제약(lzf-rq-KXE,
tjh-yU-7rh)이 설정되어 있어 화면 시각적 중앙보다 아래에 배치됩니다; 이 두 제약을 루트 뷰(또는 루트 컨테이너 뷰)의 중심을 기준으로
변경하여 centerX/centerY가 전체 화면 기준으로 정렬되도록 수정하세요 (즉, constraint id lzf-rq-KXE와
tjh-yU-7rh의 secondItem을 safeArea(Bcu-3y-fUS)에서 루트 뷰 id로 교체하거나 루트 뷰에 상대적으로 정렬되게
재생성).

Comment on lines +12 to +16
func hideKeyboard() {
let tap = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
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

hideKeyboard() 반복 호출 시 제스처 인식기가 누적됩니다.

hideKeyboard()를 호출할 때마다 새 UITapGestureRecognizerview에 추가됩니다. viewWillAppear 등 생명주기 메서드에서 호출하거나, 뷰 컨트롤러가 push/pop을 반복하는 경우 인식기가 계속 쌓이게 됩니다. view.endEditing(true)는 멱등(idempotent)하므로 즉각적인 오동작은 없지만, 불필요한 메모리 사용과 제스처 인식기 중복 등록이 발생합니다.

🛡️ 중복 등록 방지 수정안
 func hideKeyboard() {
+    guard !(view.gestureRecognizers?.contains(where: { $0 is UITapGestureRecognizer }) ?? false) else { return }
     let tap = UITapGestureRecognizer(target: self, action: `#selector`(UIViewController.dismissKeyboard))
     tap.cancelsTouchesInView = false
     view.addGestureRecognizer(tap)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Core/Sources/Extensions/UIKit`+/UIViewController+.swift around lines
12 - 16, The hideKeyboard() extension currently adds a new
UITapGestureRecognizer every call causing duplicates; update hideKeyboard() to
first check existing view.gestureRecognizers for an installed
UITapGestureRecognizer whose target/action matches
`#selector`(UIViewController.dismissKeyboard) (or use an associated-object stored
reference) and only add a new recognizer if none exists (alternatively remove
any previous matching recognizer before adding); keep tap.cancelsTouchesInView =
false and the selector name dismissKeyboard so behavior is unchanged.

Comment on lines +15 to +23
public extension TemplatesSearchUsecaseProtocol {
func searchTemplate(
keyword: String,
page: Int? = nil,
size: Int? = nil
) async throws -> [TripInfo] {
try await self.searchTemplate(keyword: keyword, page: page, size: size)
}
}
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

프로토콜 요구 메서드와 동일한 기본 시그니처를 가진 extension 기본 구현 → 무한 재귀 위험

TemplatesSearchUsecaseProtocol의 extension이 프로토콜 요구사항인 searchTemplate(keyword:page:size:)와 동일한 기본 시그니처를 가지며, 그 내부에서 self.searchTemplate(keyword:page:size:)를 호출합니다.

searchTemplate(keyword:page:size:)를 직접 구현하지 않고 extension에 의존하는 타입(예: 테스트용 Mock)의 경우, searchTemplate(keyword: "test")처럼 기본값을 사용하는 호출이 extension → self.searchTemplate(keyword:page:size:) → extension 순으로 무한 재귀를 일으켜 BAD_ACCESS 크래시가 발생합니다. extension 함수 foo가 코드가 BAD_ACCESS로 크래시될 때까지 재귀적으로 자신을 호출합니다. 두 경우 모두 치명적인 런타임 문제인 무한 재귀 호출이 발생하며, 메서드는 결국 크래시로 이어지는 반복 호출을 계속합니다.

현재 TemplatesSearchUsecase는 메서드를 직접 구현하므로 프로덕션 코드는 안전하지만, 구현 없이 프로토콜만 채택하는 Mock이 추가될 때 즉시 크래시가 발생합니다.

권장 해결책은 extension 내 기본 파라미터를 가진 메서드의 이름을 바꿔 프로토콜 요구 메서드와 명확히 구분하는 것입니다. 예를 들어 foo(i1:i2:=) 대신 fooDefault(i1:i2:=)를 사용하면, extension의 기본 구현이 프로토콜 요구 메서드와 명확히 구분되어 재귀 호출 문제를 방지합니다.

🐛 무한 재귀를 방지하는 수정 제안
 public extension TemplatesSearchUsecaseProtocol {
-    func searchTemplate(
-        keyword: String,
-        page: Int? = nil,
-        size: Int? = nil
-    ) async throws -> [TripInfo] {
-        try await self.searchTemplate(keyword: keyword, page: page, size: size)
-    }
+    // 편의 메서드: page/size 없이 키워드만으로 검색 (프로토콜 요구 메서드와 다른 시그니처)
+    func searchTemplate(keyword: String) async throws -> [TripInfo] {
+        try await searchTemplate(keyword: keyword, page: nil, size: nil)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Domain/Sources/UseCase/TemplatesSearchUsecase.swift` around lines 15
- 23, The extension on TemplatesSearchUsecaseProtocol defines a
default-signature method searchTemplate(keyword:page:size:) that simply calls
self.searchTemplate(...) which causes infinite recursion for types that don't
implement the protocol method (e.g., mocks); fix it by renaming the extension
helper to a different name (e.g., searchTemplateDefault or
searchTemplateWithDefaults) inside the TemplatesSearchUsecaseProtocol extension,
have that helper apply the defaulting logic and then call the protocol
requirement self.searchTemplate(keyword:page:size:) with explicit parameters,
and update any internal callers to use the new helper name so the protocol
requirement and the extension helper are no longer the same symbol.

Comment on lines +22 to +23
let nickname = UserDefaults.standard.string(forKey: "nickname") ?? "알 수 없음"
return "\(nickname)님께 추천하는\n따라가기 여행 콘텐츠에요!"
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for "nickname" key usage across the codebase
rg -n '"nickname"' --type swift -A2 -B2

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

Length of output: 2041


🏁 Script executed:

#!/bin/bash
# Examine the HomeSectionKind.swift file to confirm current state
find . -name "HomeSectionKind.swift" -type f

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

Length of output: 169


🏁 Script executed:

#!/bin/bash
# Look for UserDefaults patterns across the codebase
rg -n 'UserDefaults\.standard' --type swift | head -30

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

Length of output: 1010


HomeSectionKind 열거형에서 UserDefaults를 직접 읽는 것은 아키텍처 위반입니다.

headerTitle computed property에서 UserDefaults.standard라는 전역 가변 상태에 직접 의존하고 있습니다. 이로 인해:

  • 숨겨진 의존성: 호출부는 headerTitle이 외부 상태를 읽는다는 사실을 알 수 없습니다.
  • 테스트 어려움: 이 값을 단위 테스트하려면 UserDefaults에 사전 데이터를 세팅해야 합니다.
  • 전역 상태 결합: 순수한 값 타입인 열거형이 global state에 몰래 의존합니다.

코드에 // 임시 주석이 있듯이 이 구현은 프로덕션 진입 전에 제거해야 합니다. 닉네임을 ViewModel 또는 presenter에서 주입받도록 리팩터링하세요.

♻️ 제안

headerTitle을 파라미터를 받는 메서드로 변경하거나 닉네임을 외부에서 주입받도록 수정합니다.

-    var headerTitle: String {
+    func headerTitle(nickname: String? = nil) -> String {
         switch self {
         case .category:
             return "인기 여행 따라가기"
         case .recommendedTrip:
-            let nickname = UserDefaults.standard.string(forKey: "nickname") ?? "알 수 없음"
-            return "\(nickname)님께 추천하는\n따라가기 여행 콘텐츠에요!"
+            let name = nickname ?? "알 수 없음"
+            return "\(name)님께 추천하는\n따라가기 여행 콘텐츠에요!"
         default: return ""
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Projects/Features/HomeFeature/Sources/Views/CollectionView/SectionKind/HomeSectionKind.swift`
around lines 22 - 23, The headerTitle computed property in the HomeSectionKind
enum currently reads UserDefaults.standard directly (hidden global dependency);
change it to accept the nickname from the outside (e.g., convert headerTitle
into a method like headerTitle(nickname: String) or add a function on
HomeSectionKind that takes a nickname parameter) and remove any direct
UserDefaults access from HomeSectionKind; update call sites
(ViewModel/presenter/View) to fetch the nickname from UserDefaults or its own
state and pass it into HomeSectionKind.headerTitle(...) so the enum no longer
depends on global state.

Comment thread Projects/Features/SettingFeature/Sources/UITableView/SettingSection.swift Outdated
Comment thread Projects/Modules/DSKit/Sources/Component/Toast.swift
Comment on lines +23 to +29
public static var apiKey: String {
let bundle = Bundle.main
guard let apiHeader = bundle.infoDictionary?["X-API-KEY"] as? String else {
fatalError("X-API-KEY not found in Info.plist")
}
return apiHeader
}
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

apiKey가 테스트 환경에서 fatalError로 충돌할 수 있음

기존 baseURL은 테스트 번들을 우선 탐색하는 로직(Bundle.allBundles.first { $0.bundlePath.hasSuffix(".xctest") } ?? Bundle.main)을 포함하고 있지만, 새로 추가된 apiKeyBundle.main만 사용합니다. 유닛 테스트 실행 시 Bundle.main은 Xcode 테스트 러너를 가리키므로 Info.plistX-API-KEY가 없으면 fatalError가 발생합니다.

baseURL과 동일한 번들 탐색 전략을 적용해 주세요.

🛡️ 제안 수정
 public static var apiKey: String {
-    let bundle = Bundle.main
+    let bundle = Bundle.allBundles.first { $0.bundlePath.hasSuffix(".xctest") } ?? Bundle.main
     guard let apiHeader = bundle.infoDictionary?["X-API-KEY"] as? String else {
         fatalError("X-API-KEY not found in Info.plist")
     }
     return apiHeader
 }
📝 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
public static var apiKey: String {
let bundle = Bundle.main
guard let apiHeader = bundle.infoDictionary?["X-API-KEY"] as? String else {
fatalError("X-API-KEY not found in Info.plist")
}
return apiHeader
}
public static var apiKey: String {
let bundle = Bundle.allBundles.first { $0.bundlePath.hasSuffix(".xctest") } ?? Bundle.main
guard let apiHeader = bundle.infoDictionary?["X-API-KEY"] as? String else {
fatalError("X-API-KEY not found in Info.plist")
}
return apiHeader
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift`
around lines 23 - 29, The apiKey getter currently uses Bundle.main and can
fatalError during tests; change its bundle lookup to mirror baseURL's logic by
first checking Bundle.allBundles.first { $0.bundlePath.hasSuffix(".xctest") } ??
Bundle.main (use that selected bundle to read "X-API-KEY") and then guard for
the key as before so unit tests resolve the test bundle instead of crashing;
update the apiKey computed property (reference: apiKey) to use this bundle
resolution strategy.

Comment on lines +43 to 50
public var headers: [String: String]? {
var headers = ["Content-Type": "application/json"]
#if !DEBUG
headers["X-API-KEY"] = NetworkConfiguration.apiKey
#endif

return headers
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# TravelProgramAPI와 TravelTemplateAPI의 headers 구현 비교
rg -n "var headers" --type swift -A 8 \
  Projects/Modules/Networks/Sources/TargetType/

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

Length of output: 3055


🏁 Script executed:

#!/bin/bash
# NetworkConfiguration.apiKey의 소스 확인
rg -n "apiKey" --type swift \
  Projects/Modules/Networks/Sources/ | head -30

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

Length of output: 318


🏁 Script executed:

#!/bin/bash
# NetworkConfiguration.swift 전체 내용 확인
cat -n Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift

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

Length of output: 1168


TravelTemplateAPI에서 API 키 하드코딩 제거 필요

TravelTemplateAPI.swift는 X-API-KEY를 하드코딩("ndgl_ios_7d61390564126f8e66deea15e20bb126c3be9190d9faf9d7c84fbe04ff544d4a")하고 있습니다. 팀 가이드라인에 따라 TravelProgramAPI처럼 NetworkConfiguration.apiKey를 사용하도록 변경하세요. 이 방식은 Info.plist에서 값을 읽으므로 비밀값이 소스 코드에 노출되지 않습니다.

추가로, TravelProgramAPI의 #if !DEBUG 조건은 DEBUG 빌드에서 API 키를 전송하지 않습니다. 개발/스테이징 서버에서도 키를 요구하면 DEBUG 빌드의 요청이 실패할 수 있으므로 의도를 확인하세요.

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

In `@Projects/Modules/Networks/Sources/TargetType/TravelProgramAPI.swift` around
lines 43 - 50, TravelTemplateAPI currently hardcodes the X-API-KEY string inside
the headers computed property; replace that hardcoded value by reading
NetworkConfiguration.apiKey (same pattern as TravelProgramAPI.headers) so the
key comes from Info.plist, and ensure the headers dictionary uses
["Content-Type": "application/json"] plus headers["X-API-KEY"] =
NetworkConfiguration.apiKey instead of the literal; also review the conditional
compilation used in TravelProgramAPI (the `#if` !DEBUG block around setting
X-API-KEY) and confirm whether DEBUG builds must also include the API key—adjust
or remove the `#if` !DEBUG around assignment to NetworkConfiguration.apiKey if
development/staging servers require the key.

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: 2

🧹 Nitpick comments (1)
Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift (1)

58-59: taskgetContentCard 케이스에서 불필요한 바인딩 제거

let idtask 내부에서 사용되지 않아 컴파일러 경고(Immutable value 'id' was never used)가 발생합니다.

♻️ 제안 수정
-        case .getContentCard(let id):
+        case .getContentCard:
             return .requestPlain
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift` around
lines 58 - 59, Remove the unused binding in the TargetType/TravelTemplateAPI
enum case: in TravelTemplateAPI's task implementation, change the pattern "case
.getContentCard(let id)" to not bind id (e.g., "case .getContentCard") since the
id is not used and causes the "never used" warning; update only the case pattern
so it still returns .requestPlain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fastlane/Fastfile`:
- Around line 50-55: The current build number uses date_based_build_number
(build_num) which can collide across retries/parallel jobs and timezones;
replace this by querying TestFlight for the latest build via
latest_testflight_build_number and set increment_build_number to that value + 1
(use latest_testflight_build_number(app_identifier: ...) .to_i and pass latest +
1 to increment_build_number), and remove/stop relying on date_based_build_number
(or keep a UTC fallback like Time.now.utc.strftime only as a last-resort).
Ensure CFBundleVersion is updated via increment_build_number with the computed
latest+1.

In `@Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift`:
- Around line 48-54: The headers dictionary currently uses the wrong header key
"X_API_KEY" which causes nginx to drop it; update the header key to "X-API-KEY"
wherever headers is built (e.g., in AuthAPI.swift's headers variable and the
same pattern in PlaceAPI.swift, TravelProgramAPI.swift, TravelTemplateAPI.swift,
UserTravelAPI.swift) so the API key from NetworkConfiguration.apiKey is sent as
the correct HTTP header; also review the `#if` !DEBUG guard around adding the API
key in the headers (in the same headers-building code) and either remove or make
it configurable so development/staging builds that require the key still send it
if intended.

---

Duplicate comments:
In `@Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift`:
- Around line 23-29: The apiKey getter currently uses Bundle.main directly which
can cause fatalError during tests; update NetworkConfiguration.apiKey to mirror
the baseURL lookup by first checking Bundle(for: <test-friendly class>.self) or
Bundle(identifier: "com.apple.XCTest")/Bundle.allBundles for an .xctest bundle
before falling back to Bundle.main, then read "X_API_KEY" from that resolved
bundle and avoid calling fatalError (return a sensible default or throw) so
tests won't crash; modify the apiKey computed property accordingly to perform
bundle resolution similar to baseURL's implementation.

In `@Projects/Modules/Networks/Sources/TargetType/PlaceAPI.swift`:
- Around line 57-63: The header key for the API key is incorrect (uses
"X_API_KEY")—mirror the fix from AuthAPI.swift by renaming the header key to the
expected "X-API-KEY" in the same method where headers is built in
PlaceAPI.swift; update headers["X_API_KEY"] to headers["X-API-KEY"] (keeping the
`#if` !DEBUG guard and NetworkConfiguration.apiKey usage intact) so the outgoing
requests use the correct header name.

In `@Projects/Modules/Networks/Sources/TargetType/TravelProgramAPI.swift`:
- Around line 43-50: The headers property in TravelProgramAPI is setting the
wrong header key ("X_API_KEY") and is conditionally omitted in DEBUG; update
public var headers to use the correct header name (e.g. "X-API-KEY" or the
project-standard "x-api-key") and remove the `#if` !DEBUG conditional so
NetworkConfiguration.apiKey is included in headers consistently; locate the
public var headers in TravelProgramAPI and adjust the key string and
preprocessor logic to match the fix applied in AuthAPI (use
NetworkConfiguration.apiKey).

In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift`:
- Around line 87-94: The header key in TravelTemplateAPI.public var headers
currently uses the wrong underscored name "X_API_KEY"; update it to the correct
header name used elsewhere (e.g., "X-API-KEY") so it matches AuthAPI.swift's
expected header key, and preferably centralize the header name into a single
constant (for example in NetworkConfiguration as apiKeyHeaderName) and reference
that constant from TravelTemplateAPI.headers and AuthAPI to avoid future
mismatches.

In `@Projects/Modules/Networks/Sources/TargetType/UserTravelAPI.swift`:
- Around line 51-57: The header key "X_API_KEY" is incorrect and must match the
expected header name used elsewhere (same issue as in AuthAPI); update the
headers dictionary in the function that builds request headers (the local
variable headers in UserTravelAPI.swift) to use the correct key "X-API-KEY"
instead of "X_API_KEY", keeping the conditional compilation (`#if` !DEBUG) and
assignment to NetworkConfiguration.apiKey intact so the API key is still added
in non-DEBUG builds.

---

Nitpick comments:
In `@Projects/Modules/Networks/Sources/TargetType/TravelTemplateAPI.swift`:
- Around line 58-59: Remove the unused binding in the
TargetType/TravelTemplateAPI enum case: in TravelTemplateAPI's task
implementation, change the pattern "case .getContentCard(let id)" to not bind id
(e.g., "case .getContentCard") since the id is not used and causes the "never
used" warning; update only the case pattern so it still returns .requestPlain.

Comment thread fastlane/Fastfile
Comment on lines +50 to +55
build_num = date_based_build_number

increment_build_number(
build_number: build_num,
xcodeproj: "./Projects/App/App.xcodeproj"
)
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

🧩 Analysis chain

🏁 Script executed:

cat -n fastlane/Fastfile | head -150

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

Length of output: 1724


🌐 Web query:

Apple CFBundleVersion requirements TestFlight build number uniqueness monotonic

💡 Result:

  • CFBundleVersion (build number) must be a monotonically increasing string of dot-separated integers (for example 42 or 3.1.2). Apple describes it as a “monotonically increased” version string. [1]
  • For TestFlight/App Store Connect on iOS/iPadOS/tvOS/watchOS/visionOS: each uploaded build must have a unique (CFBundleShortVersionString, CFBundleVersion) pair. This means:
    • Within the same marketing version (CFBundleShortVersionString), every new upload must use a higher CFBundleVersion than the last build in that same “release train”. [2]
    • When you change the marketing version, you may reuse build numbers (because uniqueness is by the pair). [3]
  • For macOS apps: build numbers must keep increasing across all versions (you can’t reuse a build number in a later version). [2]

Sources
[1] Apple Doc Archive: Bundle Structures (CFBundleVersion) https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html
[2] Apple Doc Archive: Technical Note TN2420 – Version Numbers and Build Numbers https://developer.apple.com/library/archive/technotes/tn2420/_index.html
[3] Apple Developer Documentation: Setting the next build number for Xcode Cloud builds (App Store Connect requires unique combination of CFBundleShortVersionString + CFBundleVersion) https://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds/


🌐 Web query:

fastlane increment_build_number behavior build number monotonic

💡 Result:

increment_build_number (fastlane) does not guarantee a globally monotonic build number by itself—it only makes the build number monotonic relative to whatever value is currently in your local project at build time.

What it actually does

  • If you call increment_build_number with no args, it increments by 1 from the build number currently stored in your Xcode project/versioning settings. It returns the new value. [1]
  • If you pass build_number:, it sets the build number to that specific value (and, per the docs, this can work even without Apple Generic Versioning enabled). [1]
  • By default it also updates Info.plist (unless skip_info_plist: true). [1]
  • For the common “automatic bump” setup, your project is typically configured to use Apple Generic Versioning (“Current Project Version” / CURRENT_PROJECT_VERSION) so the project setting and Info.plist stay in sync. [6]

Why it’s not inherently monotonic across all builds

Even if it always “+1”, it only reads your local value. So build numbers can go backwards if you:

  • build an older commit/branch whose project file has a lower build number,
  • run multiple CI jobs in parallel from the same base value,
  • rebuild without committing/persisting the bumped number.

And App Store Connect/TestFlight effectively requires each uploaded build to have a higher build number than a previously uploaded build for the same version, otherwise you’ll hit errors like “Redundant Binary Upload… Make sure you increment the build string…”. [2]

How to make it monotonic (recommended pattern)

Get the latest build number from App Store Connect/TestFlight, then set the next one explicitly:

latest = latest_testflight_build_number(app_identifier: "com.example.app").to_i
increment_build_number(build_number: latest + 1)

This “remote + 1” approach is commonly used specifically to ensure monotonicity against what’s already uploaded. [3]


빌드 번호가 분 단위/로컬 타임 기반이라 중복·역순 위험이 있습니다.

재시도나 병렬 빌드가 같은 분에 발생하거나 타임존이 다른 환경에서 실행되면 동일/더 작은 빌드 번호가 생성되어 TestFlight 업로드가 실패합니다. Apple은 CFBundleVersion이 같은 마케팅 버전 내에서 항상 이전 업로드본보다 높아야 한다고 요구합니다.

Time.now.utc.strftime(...)로 개선할 수 있으나, 더 견고한 방식은 TestFlight의 최신 빌드 번호를 조회하여 그보다 1 높은 값을 설정하는 것입니다:

latest = latest_testflight_build_number(app_identifier: "com.example.app").to_i
increment_build_number(build_number: latest + 1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fastlane/Fastfile` around lines 50 - 55, The current build number uses
date_based_build_number (build_num) which can collide across retries/parallel
jobs and timezones; replace this by querying TestFlight for the latest build via
latest_testflight_build_number and set increment_build_number to that value + 1
(use latest_testflight_build_number(app_identifier: ...) .to_i and pass latest +
1 to increment_build_number), and remove/stop relying on date_based_build_number
(or keep a UTC fallback like Time.now.utc.strftime only as a last-resort).
Ensure CFBundleVersion is updated via increment_build_number with the computed
latest+1.

Comment on lines +48 to 54
var headers = ["Content-Type": "application/json"]
#if !DEBUG
headers["X_API_KEY"] = NetworkConfiguration.apiKey
#endif

return headers
}
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

HTTP 헤더 키 이름 회귀 버그: X_API_KEYX-API-KEY 수정 필요

딕셔너리 키 "X_API_KEY"(언더스코어)는 서버로 전송되는 실제 HTTP 헤더 이름이 됩니다. Nginx는 기본적으로 언더스코어가 포함된 HTTP 헤더를 드롭하며, 이 동작은 경고나 오류 메시지 없이 조용히 발생합니다. 이전 PR에서 하드코딩된 코드는 "X-API-KEY"(하이픈)를 사용하고 있었으므로, 현재 코드는 프록시 환경에서 API 키가 백엔드에 전달되지 않는 회귀 버그를 도입합니다.

동일한 문제가 PlaceAPI.swift, TravelProgramAPI.swift, TravelTemplateAPI.swift, UserTravelAPI.swift 파일에도 동일하게 존재합니다.

추가로, #if !DEBUG 조건으로 인해 DEBUG 빌드에서는 API 키 헤더가 전혀 전송되지 않습니다. 개발/스테이징 서버에서도 키가 필요한 경우 DEBUG 빌드 요청이 실패합니다. 의도적 설계인지 확인이 필요합니다.

🔧 제안 수정 (모든 API 파일 공통 적용)
-        headers["X_API_KEY"] = NetworkConfiguration.apiKey
+        headers["X-API-KEY"] = NetworkConfiguration.apiKey
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift` around lines 48 -
54, The headers dictionary currently uses the wrong header key "X_API_KEY" which
causes nginx to drop it; update the header key to "X-API-KEY" wherever headers
is built (e.g., in AuthAPI.swift's headers variable and the same pattern in
PlaceAPI.swift, TravelProgramAPI.swift, TravelTemplateAPI.swift,
UserTravelAPI.swift) so the API key from NetworkConfiguration.apiKey is sent as
the correct HTTP header; also review the `#if` !DEBUG guard around adding the API
key in the headers (in the same headers-building code) and either remove or make
it configurable so development/staging builds that require the key still send it
if intended.

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift`:
- Around line 35-36: NetworkConfiguration.apiKey is currently attempting to read
X_API_KEY on all builds—change it to only read the plist value in DEBUG (use `#if`
DEBUG or a runtime buildConfiguration check inside NetworkConfiguration.apiKey)
and otherwise return nil or a clear warning/error; update callers to handle a
nil apiKey gracefully. Also add the "X_API_KEY": .string("$(X_API_KEY)") entry
to demoInfoPlist in InfoPlist.swift (the same place where BASE_URL is defined)
so the demo target has the key available in DEBUG builds, or alternatively gate
demo targets' network calls to DEBUG only. Ensure references to
NetworkConfiguration.apiKey and demoInfoPlist are updated to avoid runtime
crashes when the key is absent.

Comment on lines +35 to +36
"BASE_URL": .string("$(BASE_URL)"),
"X_API_KEY": .string("$(X_API_KEY)")
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify xcconfig gitignore coverage and X_API_KEY definition

echo "=== Checking .gitignore for xcconfig patterns ==="
cat .gitignore 2>/dev/null | grep -E "\.xcconfig|xcconfig/" || echo "No .gitignore or no xcconfig patterns found"

echo ""
echo "=== Finding all xcconfig files in repo ==="
find . -name "*.xcconfig" -type f 2>/dev/null | grep -v node_modules | head -20

echo ""
echo "=== Checking which xcconfig files contain X_API_KEY ==="
find . -name "*.xcconfig" -type f 2>/dev/null -exec grep -l "X_API_KEY" {} \;

echo ""
echo "=== Checking git status for xcconfig files ==="
git ls-files "*.xcconfig" 2>/dev/null || echo "No xcconfig files tracked in git"

echo ""
echo "=== Checking if any xcconfig with X_API_KEY is tracked ==="
for file in $(find . -name "*.xcconfig" -type f 2>/dev/null | grep -v node_modules); do
  if git ls-files --error-unmatch "$file" 2>/dev/null | grep -q .; then
    if grep -q "X_API_KEY" "$file"; then
      echo "⚠️  TRACKED: $file contains X_API_KEY"
    fi
  fi
done
echo "✓ Check complete"

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

Length of output: 361


🏁 Script executed:

#!/bin/bash
# Check for template xcconfig, INFO PLIST context, and X_API_KEY usage

echo "=== Looking for xcconfig templates or examples ==="
find . -type f \( -name "*xcconfig*" -o -name "*Config.template*" \) 2>/dev/null | head -20

echo ""
echo "=== Checking InfoPlist.swift full structure (lines 30-70) ==="
sed -n '30,70p' Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift

echo ""
echo "=== Searching for X_API_KEY usage in codebase ==="
rg "X_API_KEY" --max-count 20

echo ""
echo "=== Searching for xcconfig setup or configuration docs ==="
find . -type f \( -name "*.md" -o -name "*.txt" \) 2>/dev/null -exec grep -l "xcconfig\|X_API_KEY\|BASE_URL" {} \; 2>/dev/null | head -10

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

Length of output: 2825


🏁 Script executed:

#!/bin/bash
# Check NetworkConfiguration implementation and demo target setup

echo "=== NetworkConfiguration.swift around X_API_KEY read ==="
rg -A 5 -B 5 "X_API_KEY.*infoDictionary" Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift

echo ""
echo "=== Check if xcconfig is provided during build phase ==="
find . -type f \( -name "*.swift" -o -name "*.yml" -o -name "*.yaml" \) 2>/dev/null -exec grep -l "xcconfig\|BASE_URL.*inject\|X_API_KEY.*inject" {} \; 2>/dev/null | head -10

echo ""
echo "=== Check Project.swift for xcconfig settings ==="
find . -type f -name "Project.swift" 2>/dev/null | head -5 | xargs -I {} grep -A 10 -B 5 "xcconfig\|BASE_URL\|X_API_KEY" {} 2>/dev/null | head -50

echo ""
echo "=== Check if there's a build configuration documentation ==="
cat README.md 2>/dev/null | grep -A 20 -B 5 "xcconfig\|X_API_KEY\|BASE_URL\|setup\|config" | head -80

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

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Direct file inspection for X_API_KEY handling and xcconfig setup

echo "=== NetworkConfiguration.swift full file ==="
cat -n Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift

echo ""
echo "=== Configurations.swift for xcconfig setup ==="
cat -n Plugins/ConfigPlugin/ProjectDescriptionHelpers/Configurations.swift

echo ""
echo "=== CI.yml for build environment ==="
head -100 .github/workflows/CI.yml

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

Length of output: 4035


X_API_KEY를 Info.plist에서 조건부로만 읽도록 수정하고, demoInfoPlist에도 추가하세요.

원래 우려했던 xcconfig 보안 설정은 올바릅니다. .gitignore에서 *.xcconfig를 제대로 차단하고 있으며, CI 워크플로우에서 비공개 저장소로부터 해당 파일을 주입하고 있습니다. 그러나 두 가지 실행 문제가 있습니다:

  1. NetworkConfiguration.apiKey (Projects/Modules/Networks/Sources/Foundation/NetworkConfiguration.swift:25-26)는 모든 빌드에서 X_API_KEY를 읽으려 시도합니다. 팀의 지침에 따르면 DEBUG 모드에서만 읽어야 합니다. DEBUG가 아닐 때는 조건부로 처리하거나 경고하도록 수정하세요.

  2. demoInfoPlist (Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift:39-64)에는 X_API_KEY가 정의되어 있지 않습니다. 반면 demo 타겟이 비-DEBUG 빌드에서 AuthAPI, TravelTemplateAPI 등을 호출하면 Info.plist 조회 실패로 런타임 크래시가 발생합니다. demoInfoPlist에도 X_API_KEY 항목을 추가하거나, 해당 APIs 호출을 DEBUG 모드로 제한하세요.

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

In `@Plugins/EnvPlugin/ProjectDescriptionHelpers/InfoPlist.swift` around lines 35
- 36, NetworkConfiguration.apiKey is currently attempting to read X_API_KEY on
all builds—change it to only read the plist value in DEBUG (use `#if` DEBUG or a
runtime buildConfiguration check inside NetworkConfiguration.apiKey) and
otherwise return nil or a clear warning/error; update callers to handle a nil
apiKey gracefully. Also add the "X_API_KEY": .string("$(X_API_KEY)") entry to
demoInfoPlist in InfoPlist.swift (the same place where BASE_URL is defined) so
the demo target has the key available in DEBUG builds, or alternatively gate
demo targets' network calls to DEBUG only. Ensure references to
NetworkConfiguration.apiKey and demoInfoPlist are updated to avoid runtime
crashes when the key is absent.

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: 1

🧹 Nitpick comments (3)
Projects/Features/SettingFeature/Sources/SettingViewController.swift (2)

51-51: "uuid" UserDefaults 키를 상수로 추출하세요.

원시 문자열 리터럴이 여러 곳에서 사용될 경우 오타로 인한 버그 위험이 있습니다. 공유 상수로 정의해 타입 안전성을 높이는 것을 권장합니다.

// 예: 공통 상수 파일에 정의
enum UserDefaultsKey {
    static let uuid = "uuid"
}
-    guard let uuid = UserDefaults.standard.string(forKey: "uuid") else {
+    guard let uuid = UserDefaults.standard.string(forKey: UserDefaultsKey.uuid) else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SettingFeature/Sources/SettingViewController.swift` at line
51, 추출되지 않은 문자열 리터럴 "uuid"를 상수로 바꿔 타입 안전성을 높이세요: 새로 정의한 상수(예: enum
UserDefaultsKey { static let uuid = "uuid" })를 만들고 SettingViewController의 guard
let uuid = UserDefaults.standard.string(forKey: "uuid") 구문과 프로젝트 내 다른 모든 raw
"uuid" 참조를 UserDefaultsKey.uuid로 교체하여 오타/중복을 방지하세요.

141-145: 주석 처리된 코드를 제거하세요.

.notification 분기가 제거된 것으로 보이므로 관련 주석 코드를 남겨 둘 필요가 없습니다. 버전 이력은 Git으로 관리됩니다.

♻️ 수정 제안
-//        if menu == .notification {
-//            cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: tableView.bounds.width)
-//        } else {
             cell.separatorInset = UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right: 24.adjusted)
-//        }
🤖 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 141 - 145, Remove the leftover commented conditional that checks for menu
== .notification and its alternate separatorInset; in SettingViewController
locate the code around the cell.separatorInset assignment (e.g., inside
tableView(_:cellForRowAt:) or wherever cell configuration occurs) and delete the
commented-out if/else block so only the active line setting cell.separatorInset
= UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right: 24.adjusted)
remains.
Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultBuilder.swift (1)

17-21: SearchResultComponent.searchUsecasefileprivate 추가 권장

이 프로퍼티는 같은 파일의 SearchResultBuilder.build(...) 내에서만 사용됩니다. RIBs 가이드에서는 자식 스코프에 전달할 필요가 없는 컴포넌트 프로퍼티에는 fileprivate을 권장합니다. 현재 internal 접근 수준은 모듈 내 다른 파일에서도 접근 가능하므로 캡슐화가 약해집니다.

♻️ 수정 제안
 final class SearchResultComponent: Component<SearchResultDependency> {
-    var searchUsecase: TemplatesSearchUsecaseProtocol {
+    fileprivate var searchUsecase: TemplatesSearchUsecaseProtocol {
         dependency.searchUsecase
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultBuilder.swift`
around lines 17 - 21, The searchUsecase property on SearchResultComponent is
currently internal but only used inside this file (specifically in
SearchResultBuilder.build(...)), weakening encapsulation; change the declaration
of SearchResultComponent.searchUsecase to fileprivate so it is only accessible
within this file and not exposed to other files in the module, ensuring the
component only exposes what child scopes need.
🤖 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/Features/SettingFeature/Sources/SettingViewController.swift`:
- Around line 52-56: In SettingViewController where Toast.show(...) is called to
report failure to load the UUID, change the toast type from .normal to a more
appropriate error level (e.g., .warning or .error) so the UI reflects a failure
state; locate the Toast.show invocation in SettingViewController (the call with
message "식별코드를 불러올 수 없습니다.") and update the first parameter from type: .normal
to type: .warning (or .error) and keep the existing message and bottomPadding
unchanged.

---

Duplicate comments:
In `@Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultBuilder.swift`:
- Around line 9-11: The import ordering is already adjusted as requested: keep
the two imports exactly as "import Domain" followed by a blank line then "import
RIBs" in SearchResultBuilder.swift; no further changes are needed—ensure future
edits preserve this ordering and the blank line between the two import
statements.

In `@Projects/Modules/Networks/Sources/TargetType/AuthAPI.swift`:
- Around line 47-54: The headers computed property currently only sets
"X-API-KEY" when the build is not DEBUG (see var headers and the `#if` !DEBUG
block), which causes DEBUG builds to omit the API key; update the logic in the
headers property to always include NetworkConfiguration.apiKey (or gate
inclusion on a runtime flag in NetworkConfiguration such as requiresApiKey)
instead of using the compile-time `#if` !DEBUG so development/staging requests
that require the key succeed; modify the headers getter to add
headers["X-API-KEY"] = NetworkConfiguration.apiKey unconditionally or controlled
by a runtime config flag.

---

Nitpick comments:
In `@Projects/Features/SearchFeature/Sources/ChildRIBs/SearchResultBuilder.swift`:
- Around line 17-21: The searchUsecase property on SearchResultComponent is
currently internal but only used inside this file (specifically in
SearchResultBuilder.build(...)), weakening encapsulation; change the declaration
of SearchResultComponent.searchUsecase to fileprivate so it is only accessible
within this file and not exposed to other files in the module, ensuring the
component only exposes what child scopes need.

In `@Projects/Features/SettingFeature/Sources/SettingViewController.swift`:
- Line 51: 추출되지 않은 문자열 리터럴 "uuid"를 상수로 바꿔 타입 안전성을 높이세요: 새로 정의한 상수(예: enum
UserDefaultsKey { static let uuid = "uuid" })를 만들고 SettingViewController의 guard
let uuid = UserDefaults.standard.string(forKey: "uuid") 구문과 프로젝트 내 다른 모든 raw
"uuid" 참조를 UserDefaultsKey.uuid로 교체하여 오타/중복을 방지하세요.
- Around line 141-145: Remove the leftover commented conditional that checks for
menu == .notification and its alternate separatorInset; in SettingViewController
locate the code around the cell.separatorInset assignment (e.g., inside
tableView(_:cellForRowAt:) or wherever cell configuration occurs) and delete the
commented-out if/else block so only the active line setting cell.separatorInset
= UIEdgeInsets(top: 0, left: 25.adjusted, bottom: 0, right: 24.adjusted)
remains.

Comment on lines +52 to +56
Toast.show(
type: .normal,
message: "식별코드를 불러올 수 없습니다.",
bottomPadding: 50
)
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

실패 시 토스트 타입을 .warning 또는 .error로 변경하세요.

UUID를 불러오지 못한 경우는 오류 상황임에도 .normal 타입 토스트를 사용하면 사용자가 단순 안내 메시지로 오인할 수 있습니다. 이전 리뷰에서도 .warning 타입이 제안된 바 있습니다.

🐛 수정 제안
-            Toast.show(
-                type: .normal,
-                message: "식별코드를 불러올 수 없습니다.",
-                bottomPadding: 50
-            )
+            Toast.show(
+                type: .warning,
+                message: "식별코드를 불러올 수 없습니다.",
+                bottomPadding: 50
+            )
📝 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
Toast.show(
type: .normal,
message: "식별코드를 불러올 수 없습니다.",
bottomPadding: 50
)
Toast.show(
type: .warning,
message: "식별코드를 불러올 수 없습니다.",
bottomPadding: 50
)
🤖 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 52 - 56, In SettingViewController where Toast.show(...) is called to
report failure to load the UUID, change the toast type from .normal to a more
appropriate error level (e.g., .warning or .error) so the UI reflects a failure
state; locate the Toast.show invocation in SettingViewController (the call with
message "식별코드를 불러올 수 없습니다.") and update the first parameter from type: .normal
to type: .warning (or .error) and keep the existing message and bottomPadding
unchanged.

@ChoiAnYong ChoiAnYong merged commit 726967c into develop Feb 21, 2026
2 checks passed
@ChoiAnYong ChoiAnYong deleted the feat/#27-검색API연결 branch February 21, 2026 07:46
@coderabbitai coderabbitai Bot mentioned this pull request Feb 21, 2026
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.

feat: 검색 API 연결

1 participant