Feature/#69~72 - CreateSecret 화면 구현 완성 (Live 연결·감지·에러·로딩) - #73
Conversation
- DetectionClient TCA 의존성 정의 및 liveValue 조립 (DetectSecretUseCaseImpl 래핑) - SecretMetaFields에 primaryDetectionValue / primaryDetectionFieldID 추가 - binding에서 DetectionClient 동기 호출 → serviceCandidates / detectedServices 갱신 - CreateSecretFeature 전체 구현 (isLoadingProjects, 에러 매핑, 감지 연결) - CreateSecretFeature 단위 테스트 추가 (감지·에러·로딩·delegate 포함)
- CreateSecretError: SecretUseCaseError → 표시용 에러 매핑 + AlertState 프리셋 - ProjectLoadError: ProjectUseCaseError → 표시용 에러 매핑 + AlertState 프리셋
- FormLayoutMode에 isProjectLoading 환경 키 추가 - ProjectFieldView: 프로젝트 로딩 중 ProgressView 표시 - CreateSecretView: 저장 중 스크롤 비활성화 + 반투명 오버레이 + ProgressView
- SecretManagementClient / ProjectClient / SecretClient liveValue 조립 (UseCase 기반) - MainFeature: SelectSecretType → CreateSecret NavigationSplitView 전환 연결 - MainView: CreateSecretView를 detail 컬럼에서 화면 교체 방식으로 표시 - AddToProjectFeatureTests: ProjectItem 타입 수정 및 sidebarClient 의존성 픽스 - MainFeatureTests / SidebarFeatureTests: 기존 테스트 시나리오 픽스 - mockdata_for_ui_test.txt: UI 수동 테스트용 시크릿 타입별 샘플 데이터
- cancelled 핸들러에서 selectSecretType을 nil 대신 .init()으로 리셋해 타입 선택 화면으로 복귀 - typeSelected / secretCreated / cancelled 라우팅 3개 테스트 커버리지 추가
- ifLet 해제 시 발생하는 취소가 오류로 오인되지 않도록 CancellationError catch 분리
- LiveRepositories enum으로 secret/project Repository 인스턴스 통합해 modelContainer 공유 - 각 Live Client의 불필요한 do-catch 래핑 제거 (UseCase가 이미 도메인 에러로 매핑)
WalkthroughCreateSecret 기능에 자동 감지, 프로젝트 로딩 표시, 저장 오류 알림을 추가했습니다. 라이브 의존성을 구성하고, 메인 화면에서 생성 화면의 진입·완료·취소 흐름을 연결했습니다. 관련 상태 전이와 오류 매핑 테스트도 확장했습니다. ChangesCreateSecret 기능 통합
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant 사용자
participant MainView
participant CreateSecretFeature
participant DetectionClient
participant ProjectClient
participant SecretManagementClient
사용자->>MainView: 시크릿 유형 선택
MainView->>CreateSecretFeature: 생성 화면 표시
CreateSecretFeature->>ProjectClient: 프로젝트 목록 조회
사용자->>CreateSecretFeature: 시크릿 필드 입력
CreateSecretFeature->>DetectionClient: 감지 값 전달
CreateSecretFeature->>SecretManagementClient: 시크릿 생성 요청
SecretManagementClient-->>CreateSecretFeature: 생성 결과 반환
CreateSecretFeature-->>MainView: 생성 완료 또는 취소 전달
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (5)
Projects/DVPresentation/Tests/Main/MainFeatureTests.swift (1)
43-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win사이드바 선택 시
createSecret종료도 검증하세요.
handleSidebarDelegate는 선택 변경 시createSecret = nil을 수행합니다. 현재 테스트는selectSecretType만 검증합니다. 생성 폼이 열린 상태를 초기값에 추가하고createSecret이 제거되는지 검증하세요.제안 변경
initial.selectSecretType = .init() + initial.createSecret = CreateSecretFeature.State(secretType: .apiKeyToken) initial.sidebar.isCreatingSecret = true initial.sidebar.selection = .filter(.starred) @@ await store.receive(.sidebar(.delegate(.selectionChanged(.filter(.all))))) { $0.selectSecretType = nil + $0.createSecret = nil }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVPresentation/Tests/Main/MainFeatureTests.swift` around lines 43 - 54, Update the MainFeature test setup around initial.sidebar.selection to initialize the createSecret state as open, then extend the .sidebar(.delegate(.selectionChanged(.filter(.all)))) assertion to verify createSecret is cleared to nil alongside selectSecretType. Keep the existing sidebar selection assertions unchanged.Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swift (1)
30-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win로딩 뷰와 선택 뷰를
body밖으로 분리하세요.Line 31-52는
body에 두 개의 중첩 레이아웃을 직접 포함합니다.loadingContent와projectSelectionContent를 extension의private var로 분리하세요.body에는 상태 분기만 유지하세요.제안 변경
var body: some View { if isProjectLoading { - DVLabeledField(.module("Project"), size: size) { - HStack(spacing: 6) { - ProgressView() - .controlSize(.small) - Spacer() - } - } + loadingContent } else { - DVLabeledField(.module("Project"), size: size) { - DVMultiSelectDropdown(...) - } + projectSelectionContent } } + +extension ProjectFieldView { + private var loadingContent: some View { /* existing loading layout */ } + private var projectSelectionContent: some View { /* existing dropdown layout */ } +}As per path instructions, "var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private var/func로 분리를 제안하세요."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swift` around lines 30 - 53, Extract the loading layout from the isProjectLoading branch into a private loadingContent computed property, and extract the project-selection DVMultiSelectDropdown layout into a private projectSelectionContent computed property in an extension. Keep body limited to selecting between these two properties based on isProjectLoading, preserving all existing configuration and behavior.Source: Path instructions
Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift (1)
3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win내장 모듈을 먼저 배치하세요.
세 파일이
Foundation을 다른 모듈 뒤에 배치합니다.Foundation을 먼저 두고 빈 줄 뒤에ComposableArchitecture,DVDomain을 유지하세요.
Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift#L3-L5:Foundation을 첫 import로 이동하세요.Projects/DVPresentation/Sources/Features/CreateSecret/Model/CreateSecretError.swift#L3-L5:Foundation을 첫 import로 이동하세요.Projects/DVPresentation/Sources/Features/CreateSecret/Model/ProjectLoadError.swift#L3-L5:Foundation을 첫 import로 이동하세요.As per path instructions, "모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분)"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift` around lines 3 - 5, Reorder imports so the built-in Foundation import appears first, followed by a blank line and the alphabetized ComposableArchitecture and DVDomain imports. Apply this change in Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift (lines 3-5), Projects/DVPresentation/Sources/Features/CreateSecret/Model/CreateSecretError.swift (lines 3-5), and Projects/DVPresentation/Sources/Features/CreateSecret/Model/ProjectLoadError.swift (lines 3-5).Source: Path instructions
Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift (1)
144-154: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win자동 감지 범위를 secret-content 변경으로 제한하세요.
.bindingfallthrough는 이름, 메모, 서비스, 프로젝트 선택 변경에도detectionClient.detect를 실행합니다. 실제 필드 바인딩은$store.meta.content.typed(...)로 전달되므로,\.meta.content변경에서만 감지하고 그 외 binding에서는检测结果을 유지되도록 해주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift` around lines 144 - 154, Update the .binding handling to run detectionClient.detect only when the binding represents a change to \.meta.content, matching the $store.meta.content.typed(...) binding. Preserve existing serviceCandidates and detectedServices for all other binding changes, while retaining the current empty-content clearing behavior.Projects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swift (1)
3-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value두 파일에서 내장 프레임워크 import 순서를 수정하세요.
두 파일 모두
Foundation을 다른 모듈 뒤에 import합니다.Foundation을 첫 그룹으로 이동하고 빈 줄로 나머지 모듈과 구분하세요.
Projects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swift#L3-L7:Foundation을 첫 import로 이동하세요.Projects/Devault/Sources/Composition/Dependencies/SecretManagementClient+Live.swift#L3-L7:Foundation을 첫 import로 이동하세요.As per path instructions, "모듈 임포트가 알파벳 순으로 정렬되어 있는지 확인하세요. (내장 프레임워크 먼저, 빈 줄로 서드파티 구분)".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/Devault/Sources/Composition/Dependencies/SecretClient`+Live.swift around lines 3 - 7, 두 파일의 import 블록을 수정하세요: Projects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swift 3-7행과 Projects/Devault/Sources/Composition/Dependencies/SecretManagementClient+Live.swift 3-7행에서 Foundation을 첫 번째 내장 프레임워크 import로 이동하고, Foundation과 나머지 모듈 사이에 빈 줄을 추가하세요. 각 그룹 내 import는 알파벳순으로 유지하세요.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@Projects/Devault/Sources/Composition/Dependencies/SecretClient`+Live.swift:
- Around line 3-7: 두 파일의 import 블록을 수정하세요:
Projects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swift 3-7행과
Projects/Devault/Sources/Composition/Dependencies/SecretManagementClient+Live.swift
3-7행에서 Foundation을 첫 번째 내장 프레임워크 import로 이동하고, Foundation과 나머지 모듈 사이에 빈 줄을
추가하세요. 각 그룹 내 import는 알파벳순으로 유지하세요.
In `@Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift`:
- Around line 3-5: Reorder imports so the built-in Foundation import appears
first, followed by a blank line and the alphabetized ComposableArchitecture and
DVDomain imports. Apply this change in
Projects/DVPresentation/Sources/Dependencies/DetectionClient.swift (lines 3-5),
Projects/DVPresentation/Sources/Features/CreateSecret/Model/CreateSecretError.swift
(lines 3-5), and
Projects/DVPresentation/Sources/Features/CreateSecret/Model/ProjectLoadError.swift
(lines 3-5).
In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swift`:
- Around line 30-53: Extract the loading layout from the isProjectLoading branch
into a private loadingContent computed property, and extract the
project-selection DVMultiSelectDropdown layout into a private
projectSelectionContent computed property in an extension. Keep body limited to
selecting between these two properties based on isProjectLoading, preserving all
existing configuration and behavior.
In
`@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift`:
- Around line 144-154: Update the .binding handling to run
detectionClient.detect only when the binding represents a change to
\.meta.content, matching the $store.meta.content.typed(...) binding. Preserve
existing serviceCandidates and detectedServices for all other binding changes,
while retaining the current empty-content clearing behavior.
In `@Projects/DVPresentation/Tests/Main/MainFeatureTests.swift`:
- Around line 43-54: Update the MainFeature test setup around
initial.sidebar.selection to initialize the createSecret state as open, then
extend the .sidebar(.delegate(.selectionChanged(.filter(.all)))) assertion to
verify createSecret is cleared to nil alongside selectSecretType. Keep the
existing sidebar selection assertions unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 69e0b949-3575-4110-af43-890752a14aea
📒 Files selected for processing (20)
Projects/DVPresentation/Sources/Dependencies/DetectionClient.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swiftProjects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swiftProjects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Model/CreateSecretError.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Model/ProjectLoadError.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields.swiftProjects/DVPresentation/Sources/Features/Main/MainFeature.swiftProjects/DVPresentation/Sources/Features/Main/MainView.swiftProjects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swiftProjects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swiftProjects/DVPresentation/Tests/Main/MainFeatureTests.swiftProjects/DVPresentation/Tests/Sidebar/SidebarFeatureTests.swiftProjects/Devault/Sources/Composition/Dependencies/DetectionClient+Live.swiftProjects/Devault/Sources/Composition/Dependencies/LiveRepositories.swiftProjects/Devault/Sources/Composition/Dependencies/ProjectClient+Live.swiftProjects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swiftProjects/Devault/Sources/Composition/Dependencies/SecretManagementClient+Live.swiftProjects/Devault/Sources/Composition/Dependencies/SidebarClient+Live.swift
✨ What's this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
SecretClient역할 분리 (SecretManagementClient/ProjectClient/DetectionClient) 및 Client 구조 정리SecretManagementClient/SecretClient/ProjectClient/DetectionClientliveValue 조립 및LiveRepositories공유 싱글턴 도입MainFeature: SelectSecretType → CreateSecret NavigationSplitView 컬럼 교체 진입점 연결,cancelled시selectSecretType초기화로 타입 선택 화면 복귀DetectionClient연결 — primaryDetectionValue 기반 실시간 서비스 자동 감지 (serviceCandidates/detectedServices상태 반영)CreateSecretError/ProjectLoadError모델 및AlertState프리셋 구현 — save / projectLoad 실패 처리isLoadingProjects/isSaving로딩 상태 구현 —ProjectFieldView스피너 및 Save 버튼 disable 연동📸 스크린샷 (Optional)
🧪 테스트 / 검증 내역
CreateSecretFeatureTests— save 성공/실패, 프로젝트 로드, 자동 감지, 에러 alert 흐름 통과MainFeatureTests— typeSelected / secretCreated / cancelled 라우팅 3개 추가 테스트 통과SidebarFeatureTests— delete 흐름 테스트 통과💬 기타 공유 사항
taskeffect의CancellationError명시적 catch 추가 — ifLet 해제 시 발생하는 취소가 에러로 오인되지 않도록 처리LiveRepositoriesenum으로 통합해 SwiftData modelContainer 공유 일관성 확보🙇🏻♀️ 리뷰 가이드 (선택)
MainFeature.swift:cancelled핸들러에서selectSecretType = .init()(nil이 아닌 초기화) — 타입 선택 화면으로 되돌아가는 의도된 동작LiveRepositories.swift: 새 파일 — Composition Root 전체에서 Repository 인스턴스 공유 구조