Feature/#64 - 온보딩 Touch ID 인증 및 iCloud 동기화 연동 - #66
Conversation
|
Warning Review limit reached
Next review available in: 52 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughiCloud 계정 상태 조회와 동기화 설정 저장을 추가했습니다. CloudKit 기반 저장소와 SwiftData 관계를 조정했습니다. 온보딩 및 잠금 해제에 Touch ID, 오류 알림, 앱 아이콘을 연결했습니다. 로컬 서명용 개발 절차도 추가했습니다. ChangesiCloud 계약과 저장소
인증 및 프레젠테이션
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 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.
Actionable comments posted: 8
🧹 Nitpick comments (6)
Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift (1)
10-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
ubiquitousStore의존성의 역할을 명확히 하세요.Line 10-18은
NSUbiquitousKeyValueStore를 주입하고 저장하지만, Line 20-36의 설정 읽기와 쓰기는 모두UserDefaults를 사용합니다.Projects/DVData/Sources/RepositoryImpl/Settings/UbiquitousStoreKey.swift의 접근자도 주석 처리되어 있습니다.설정을 기기별로 관리할 의도라면
ubiquitousStore프로퍼티와 초기화 파라미터를 제거하세요. 기기 간 설정 동기화가 요구사항이라면 KVS 키와 실제 읽기·쓰기 경로를 구현한 뒤 이 의존성을 사용하세요.🤖 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/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift` around lines 10 - 18, Clarify the role of the ubiquitousStore dependency in SettingsRepositoryImpl: either remove the ubiquitousStore property and initializer parameter if settings are device-local, or implement the KVS keys and route the corresponding settings reads and writes through ubiquitousStore when synchronization is required.Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift (1)
50-53: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
lockClient의존성을private로 좁히세요.
lockClient는bodyreducer 내부에서만 사용됩니다.private로 선언해 노출 범위를 최소화하세요.♻️ 제안
- `@Dependency`(\.lockClient) var lockClient + `@Dependency`(\.lockClient) private var lockClientAs per path instructions, "클래스 프로퍼티는 원칙적으로
private인지 확인하세요."🤖 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/Lock/LockFeature.swift` around lines 50 - 53, LockFeature의 의존성 선언에서 body reducer 내부에서만 사용하는 lockClient 프로퍼티를 private으로 제한하세요.Source: Path instructions
Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift (1)
76-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
onboardingClient의존성을private로 좁히세요.
onboardingClient는bodyreducer 내부에서만 사용됩니다. 외부에서 접근할 필요가 없으므로private로 선언해 캡슐화를 강화하세요.♻️ 제안
- `@Dependency`(\.onboardingClient) var onboardingClient + `@Dependency`(\.onboardingClient) private var onboardingClientAs per path instructions, "클래스 프로퍼티는 원칙적으로
private인지 확인하세요."🤖 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/Onboarding/OnboardingFeature.swift` around lines 76 - 78, Update the onboardingClient dependency declaration in OnboardingFeature to be private, since it is only used within the body reducer; leave its dependency injection behavior unchanged.Source: Path instructions
Projects/Devault/Sources/Composition/Dependencies/OnboardingClient+Live.swift (2)
3-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서를 알파벳순으로 정렬하세요.
현재
DVCore, DVPresentation, DVDomain, DVData순서입니다. 알파벳순으로 정렬하면DVCore, DVData, DVDomain, DVPresentation이 되어야 합니다.♻️ 정렬 예시
import ComposableArchitecture import DVCore -import DVPresentation -import DVDomain import DVData +import DVDomain +import DVPresentationAs 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/OnboardingClient`+Live.swift around lines 3 - 7, OnboardingClient+Live.swift의 모듈 import를 알파벳순으로 정렬하세요. ComposableArchitecture와 DVCore, DVData, DVDomain, DVPresentation 순서를 유지하도록 import 구문만 변경하세요.Source: Path instructions
15-16: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value
SettingsRepositoryImpl생성을 제거하고 공유 인스턴스를 사용하세요.
new SettingsRepositoryImpl()호출이.standard UserDefaults와.default NSUbiquitousKeyValueStore를 그대로 재사용하므로 여기서는LiveSettingsRepository.shared를 사용해 의존성 계층의 설정 저장소 인스턴스를 단순화하세요.🤖 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/OnboardingClient`+Live.swift around lines 15 - 16, Replace the SettingsRepositoryImpl construction in the dependency setup with the shared LiveSettingsRepository.shared instance, while keeping the iCloudSyncSettings use-case initialization wired to the repository variable.Projects/Devault/Sources/Composition/Dependencies/LockClient+Live.swift (1)
3-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서를 알파벳순으로 정렬하세요.
현재
DVPresentation, DVDomain, DVData순서입니다. 알파벳순은DVData, DVDomain, DVPresentation입니다.♻️ 정렬 예시
import ComposableArchitecture -import DVPresentation -import DVDomain import DVData +import DVDomain +import DVPresentationAs 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/LockClient`+Live.swift around lines 3 - 6, LockClient+Live.swift의 모듈 import 순서를 알파벳순으로 정렬하세요. ComposableArchitecture는 기존 위치에 두고, DV 모듈은 DVData, DVDomain, DVPresentation 순서가 되도록 정리하세요.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.
Inline comments:
In `@Projects/Devault/Sources/Composition/LiveStorage.swift`:
- Around line 11-15: 온보딩의 iCloud 동기화 설정 저장 흐름에서 설정 저장 직후 syncingCompleted를 보내지
않도록 수정하세요. LiveStorage.shared가 생성되기 전에 설정을 저장하도록 초기화 순서를 보장하고, 현재 실행 중인
ModelContainer에 변경 사항이 반영되지 않는 경우 성공 상태 대신 앱 재시작이 필요함을 나타내는 상태를 유지하세요.
In
`@Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift`:
- Around line 61-63: Update the project-link filtering condition in the secret
fetch descriptor builder to compare `link.projectID` with `projectID` instead of
accessing `link.project?.id`, so links whose project relationship is nil are
still matched.
In `@Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift`:
- Line 79: Update fetch(_:) around the localSecrets-to-domain conversion to
avoid silently discarding toDomain() failures via try?. Align list-fetch
behavior with fetch(id:) by propagating corruptedStorage conversion errors, or
explicitly establish and preserve a contract that excludes failed items; ensure
the chosen behavior consistently handles missing payload and secretType/subType
mapping failures.
In
`@Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift`:
- Around line 31-33: Update the container.accountStatus() error handling in
CloudKitAccountServiceImpl to map networkUnavailable/networkFailure to a
network-related ICloudAccountStatus and notAuthenticated/badContainer to an
authentication or configuration status, adding the required status categories
and user-facing messages to ICloudAccountStatus. Preserve .couldNotDetermine
only for unrecognized errors.
In
`@Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`:
- Line 41: Update getOrCreateSymmetricKey to detect an existing
non-synchronizable Keychain item returned by the kSecAttrSynchronizableAny query
and migrate it to a synchronizable item while preserving the key data, using
SecItemUpdate or re-saving a new item with the synchronizable attribute before
returning it.
- Around line 69-70: Update the keychain accessibility setting in the
KeychainKeyStore configuration to use kSecAttrAccessibleWhenUnlocked instead of
kSecAttrAccessibleAfterFirstUnlock, while preserving kSecAttrSynchronizable:
true separately.
In `@Projects/DVData/Sources/Storage/Local/LocalStorage.swift`:
- Around line 13-17:
Projects/DVData/Sources/Storage/Local/LocalStorage.swift:13-17에서 makeDefault의
동기화 모델 구성을 BackupRecord.filePath가 포함되지 않는 로컬 전용 저장소/모델과 분리하세요.
Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift:8-15의 filePath는
동기화 스키마에서 제외하고, 동기화 백업 식별이 필요한 경우 로컬 절대 경로 대신 기기 독립 식별자만 사용하도록 관련 모델 참조를 조정하세요.
In `@Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swift`:
- Around line 17-21: Replace the forced UUID unwraps in ProjectItem.previews
with safe initialization using guard let, if let, or an appropriate ?? fallback,
so invalid fixture strings cannot terminate previews initialization.
---
Nitpick comments:
In `@Projects/Devault/Sources/Composition/Dependencies/LockClient`+Live.swift:
- Around line 3-6: LockClient+Live.swift의 모듈 import 순서를 알파벳순으로 정렬하세요.
ComposableArchitecture는 기존 위치에 두고, DV 모듈은 DVData, DVDomain, DVPresentation 순서가
되도록 정리하세요.
In
`@Projects/Devault/Sources/Composition/Dependencies/OnboardingClient`+Live.swift:
- Around line 3-7: OnboardingClient+Live.swift의 모듈 import를 알파벳순으로 정렬하세요.
ComposableArchitecture와 DVCore, DVData, DVDomain, DVPresentation 순서를 유지하도록
import 구문만 변경하세요.
- Around line 15-16: Replace the SettingsRepositoryImpl construction in the
dependency setup with the shared LiveSettingsRepository.shared instance, while
keeping the iCloudSyncSettings use-case initialization wired to the repository
variable.
In
`@Projects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swift`:
- Around line 10-18: Clarify the role of the ubiquitousStore dependency in
SettingsRepositoryImpl: either remove the ubiquitousStore property and
initializer parameter if settings are device-local, or implement the KVS keys
and route the corresponding settings reads and writes through ubiquitousStore
when synchronization is required.
In `@Projects/DVPresentation/Sources/Features/Lock/LockFeature.swift`:
- Around line 50-53: LockFeature의 의존성 선언에서 body reducer 내부에서만 사용하는 lockClient
프로퍼티를 private으로 제한하세요.
In `@Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift`:
- Around line 76-78: Update the onboardingClient dependency declaration in
OnboardingFeature to be private, since it is only used within the body reducer;
leave its dependency injection behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0f9a1234-c402-41c0-a52e-2846e1d583e2
⛔ Files ignored due to path filters (3)
Projects/DVDesign/Resources/Assets.xcassets/Image/appIcon.imageset/appIcon.pdfis excluded by!**/*.pdfProjects/Devault/Resources/AppIcon/Devault_IC.icon/Assets/App Icon_BG.pngis excluded by!**/*.pngProjects/Devault/Resources/AppIcon/Devault_IC.icon/Assets/App Icon_Lock.pngis excluded by!**/*.png
📒 Files selected for processing (56)
Projects/DVCore/Sources/ICloudContainer.swiftProjects/DVCore/Sources/Logger/DVLogger.swiftProjects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swiftProjects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swiftProjects/DVData/Sources/RepositoryImpl/Settings/SettingsRepositoryImpl.swiftProjects/DVData/Sources/RepositoryImpl/Settings/UbiquitousStoreKey.swiftProjects/DVData/Sources/RepositoryImpl/Settings/UserDefaultsKey.swiftProjects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swiftProjects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swiftProjects/DVData/Sources/Storage/Local/LocalStorage.swiftProjects/DVData/Sources/Storage/Local/Models/AppAuditLog.swiftProjects/DVData/Sources/Storage/Local/Models/BackupRecord.swiftProjects/DVData/Sources/Storage/Local/Models/Project.swiftProjects/DVData/Sources/Storage/Local/Models/Secret.swiftProjects/DVData/Sources/Storage/Local/Models/SecretAuditLog.swiftProjects/DVData/Sources/Storage/Local/Models/SecretMetadata.swiftProjects/DVData/Sources/Storage/Local/Models/SecretPayload.swiftProjects/DVData/Sources/Storage/Local/Models/SecretProjectLink.swiftProjects/DVDesign/Resources/Assets.xcassets/Image/Contents.jsonProjects/DVDesign/Resources/Assets.xcassets/Image/appIcon.imageset/Contents.jsonProjects/DVDesign/Resources/Lottie/progress.lottieProjects/DVDesign/Sources/Components/DVFloatingPanel.swiftProjects/DVDesign/Sources/Foundations/Color/Color+DVColor.swiftProjects/DVDesign/Sources/Foundations/Fonts/Font+DVFont.swiftProjects/DVDesign/Sources/Foundations/Fonts/View+DVFont.swiftProjects/DVDesign/Sources/Foundations/Image/DVImage.swiftProjects/DVDesign/Sources/Foundations/Image/Image+DVImage.swiftProjects/DVDomain/Sources/Repository/Interface/SettingsRepository.swiftProjects/DVDomain/Sources/Repository/SecretQuery.swiftProjects/DVDomain/Sources/Service/Interface/ICloudAccountService.swiftProjects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swiftProjects/DVDomain/Sources/UseCase/Impl/Settings/ICloudSyncSettingsUseCaseImpl.swiftProjects/DVDomain/Sources/UseCase/Interface/Secret/PatchSecretUseCase.swiftProjects/DVDomain/Sources/UseCase/Interface/Settings/ICloudSyncSettingsUseCase.swiftProjects/DVPresentation/Resources/Localizable.xcstringsProjects/DVPresentation/Sources/Dependencies/LockClient.swiftProjects/DVPresentation/Sources/Dependencies/OnboardingClient.swiftProjects/DVPresentation/Sources/Dependencies/SecretClient.swiftProjects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swiftProjects/DVPresentation/Sources/Features/Lock/LockFeature.swiftProjects/DVPresentation/Sources/Features/Lock/LockView.swiftProjects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swiftProjects/DVPresentation/Sources/Features/Onboarding/OnboardingView.swiftProjects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem+Mapping.swiftProjects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarView.swiftProjects/DVPresentation/Sources/Support/UserAuthenticationAlert.swiftProjects/DVPresentation/Tests/Lock/LockFeatureTests.swiftProjects/DVPresentation/Tests/Onboarding/OnboardingFeatureTests.swiftProjects/Devault/Project.swiftProjects/Devault/Resources/AppIcon/Devault_IC.icon/icon.jsonProjects/Devault/Resources/Devault.entitlementsProjects/Devault/Sources/Composition/Dependencies/LockClient+Live.swiftProjects/Devault/Sources/Composition/Dependencies/OnboardingClient+Live.swiftProjects/Devault/Sources/Composition/LiveSettingsRepository.swiftProjects/Devault/Sources/Composition/LiveStorage.swift
💤 Files with no reviewable changes (1)
- Projects/DVPresentation/Resources/Localizable.xcstrings
| /// Keychain에서 tag에 해당하는 raw key Data를 조회한다. | ||
| private func loadKeyData(tag: String) throws -> Data? { | ||
| var query = keyQuery(tag: tag) | ||
| query[kSecAttrSynchronizable] = kSecAttrSynchronizableAny |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 6 \
'getOrCreateSymmetricKey|loadKeyData|saveOrLoadExistingKeyData|SecItem(Update|Add|CopyMatching|Delete)|kSecAttrSynchronizable' \
Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swiftRepository: DevaultProject/Devault-macOS
Length of output: 2799
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant KeychainKeyStore implementation without executing repository code.
sed -n '1,120p' Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
# Locate all callers of getOrCreateSymmetricKey to assess write path impact.
rg -n -C 4 'getOrCreateSymmetricKey\(' Projects/DVData/Sources/ServiceImplRepository: DevaultProject/Devault-macOS
Length of output: 5925
기존 비동기화 Keychain 항목을 동기화 항목으로 마이그레이션하세요.
kSecAttrSynchronizableAny로 조회한 값이 있으면 getOrCreateSymmetricKey가 새 항목을 저장하지 않고 반환합니다. 기존 설치에서는 동기화용 암호 키가 iCloud Keychain에 저장되지 않아 새 기기 복호화는 실패합니다. 비동기화 항목을 감지하면 키 데이터를 보존한 상태로 SecItemUpdate 또는 새로운 동기화 항목으로 다시 저장해야 합니다.
🤖 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/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift`
at line 41, Update getOrCreateSymmetricKey to detect an existing
non-synchronizable Keychain item returned by the kSecAttrSynchronizableAny query
and migrate it to a synchronizable item while preserving the key data, using
SecItemUpdate or re-saving a new item with the synchronizable attribute before
returning it.
| public static func makeDefault(iCloudSyncEnabled: Bool) throws -> LocalStorage { | ||
| let configuration = ModelConfiguration( | ||
| isStoredInMemoryOnly: false, | ||
| cloudKitDatabase: iCloudSyncEnabled ? .private(ICloudContainer.identifier) : .none | ||
| ) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# appSchema에 BackupRecord가 포함되는지와 저장소 구성을 확인합니다.
rg -n -C 4 --glob '*.swift' 'appSchema|BackupRecord|ModelConfiguration|filePath' \
Projects/DVData/Sources Projects/Devault/SourcesRepository: DevaultProject/Devault-macOS
Length of output: 5923
BackupRecord.filePath와 iCloud 동기화 경계를 분리하세요.
Schema.appSchema가 BackupRecord를 포함하고 있고 그 스키마에 모두 cloudKit private database를 적용하므로, 로컬 절대 경로가 사용자의 동기화 데이터베이스에 포함될 수 있습니다. filePath는 로컬 전용 저장소/모델로 분리하고, 동기화 백업 식별이 필요하면 기기 독립 식별자만 사용하세요.
📍 Affects 2 files
Projects/DVData/Sources/Storage/Local/LocalStorage.swift#L13-L17(this comment)Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift#L8-L15
🤖 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/DVData/Sources/Storage/Local/LocalStorage.swift` around lines 13 -
17, Projects/DVData/Sources/Storage/Local/LocalStorage.swift:13-17에서
makeDefault의 동기화 모델 구성을 BackupRecord.filePath가 포함되지 않는 로컬 전용 저장소/모델과 분리하세요.
Projects/DVData/Sources/Storage/Local/Models/BackupRecord.swift:8-15의 filePath는
동기화 스키마에서 제외하고, 동기화 백업 식별이 필요한 경우 로컬 절대 경로 대신 기기 독립 식별자만 사용하도록 관련 모델 참조를 조정하세요.
| extension ProjectItem { | ||
| public static let previews: [ProjectItem] = [ | ||
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"), | ||
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"), | ||
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"), |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
프리뷰 UUID의 강제 언래핑을 제거하세요.
문자열이 현재 유효해도 fixture가 변경되면 ProjectItem.previews 초기화 중 프로세스가 종료될 수 있습니다. guard let으로 유효한 항목만 구성하거나, 요구사항에 맞는 ?? 기본값을 사용하세요.
안전한 초기화 예시
-public static let previews: [ProjectItem] = [
- .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"),
- .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"),
- .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"),
-]
+public static let previews: [ProjectItem] = {
+ guard
+ let backendID = UUID(uuidString: "00000000-0000-0000-0000-000000000001"),
+ let infrastructureID = UUID(uuidString: "00000000-0000-0000-0000-000000000002"),
+ let mobileID = UUID(uuidString: "00000000-0000-0000-0000-000000000003")
+ else {
+ return []
+ }
+ return [
+ .init(id: backendID, name: "Backend"),
+ .init(id: infrastructureID, name: "Infrastructure"),
+ .init(id: mobileID, name: "Mobile"),
+ ]
+}()As per path instructions: Swift 파일의 강제 언래핑은 guard let / if let / ?? 대안으로 교체해야 합니다.
📝 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.
| extension ProjectItem { | |
| public static let previews: [ProjectItem] = [ | |
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, name: "Backend"), | |
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, name: "Infrastructure"), | |
| .init(id: UUID(uuidString: "00000000-0000-0000-0000-000000000003")!, name: "Mobile"), | |
| extension ProjectItem { | |
| public static let previews: [ProjectItem] = { | |
| guard | |
| let backendID = UUID(uuidString: "00000000-0000-0000-0000-000000000001"), | |
| let infrastructureID = UUID(uuidString: "00000000-0000-0000-0000-000000000002"), | |
| let mobileID = UUID(uuidString: "00000000-0000-0000-0000-000000000003") | |
| else { | |
| return [] | |
| } | |
| return [ | |
| .init(id: backendID, name: "Backend"), | |
| .init(id: infrastructureID, name: "Infrastructure"), | |
| .init(id: mobileID, name: "Mobile"), | |
| ] | |
| }() |
🤖 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/Sidebar/Model/ProjectItem.swift`
around lines 17 - 21, Replace the forced UUID unwraps in ProjectItem.previews
with safe initialization using guard let, if let, or an appropriate ?? fallback,
so invalid fixture strings cannot terminate previews initialization.
Source: Path instructions
doyeonk429
left a comment
There was a problem hiding this comment.
코드래빗 리뷰 반영하면 크리티컬 이슈는 없네용
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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.
Inline comments:
In
`@Projects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swift`:
- Around line 31-39: Update the CKError handling in ICloudAccountStatus mapping
so .badContainer and .missingEntitlement become a distinct configuration-error
status instead of .couldNotDetermine, while preserving existing network and
authentication mappings. Add the configuration status to ICloudAccountStatus and
handle it in OnboardingFeature’s switch. Log the relevant CKError code before
returning the configuration status, and update related tests to cover these
cases.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: cd2e8d0e-8afe-4b29-b476-8948e7bc42a0
📒 Files selected for processing (8)
Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swiftProjects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swiftProjects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swiftProjects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swiftProjects/DVData/Sources/Storage/Local/LocalStorage.swiftProjects/DVData/Sources/Storage/Local/Models/AppSchema.swiftProjects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swiftProjects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
🚧 Files skipped from review as they are similar to previous changes (4)
- Projects/DVData/Sources/RepositoryImpl/Secret/SecretFetchDescriptorBuilder.swift
- Projects/DVData/Sources/RepositoryImpl/Secret/SecretRepositoryImpl.swift
- Projects/DVData/Sources/Storage/Local/LocalStorage.swift
- Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
|
@dlguszoo 이 PR merge 이후에 배경앱 타겟에 iCloud entitlement가 들어가면서, Apple Developer 팀( generate-local # tuist generate 대신iCloud 동기화 기능만 사용할 수 없고(온보딩에서 켜면 안내 알럿), 나머지는 그대로 동작합니다. 확인 부탁드리는 이유작업 중에 팀 모드(
그래서 팀 모드 설정에 아래 두 줄을 명시했습니다. "CODE_SIGN_STYLE": "Automatic",
"CODE_SIGN_IDENTITY": "Apple Development",수정 후 자동 서명 경로로 넘어가는 것까지는 확인했지만, 제 계정에는 팀 프로파일이 없어서 끝까지 검증하지 못했습니다.
혹시 지금까지 |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Projects/Devault/Project.swift (1)
13-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win파일 전용 서명 설정을
private로 제한하세요.
isLocalSigning,teamSigningSettings,localSigningSettings은 이 파일 밖에서 사용되지 않습니다. 세 선언에private를 추가해 manifest 내부 구현으로 제한하세요.수정 예시
-let isLocalSigning = Environment.localSigning.getBoolean(default: false) +private let isLocalSigning = Environment.localSigning.getBoolean(default: false) -let teamSigningSettings: SettingsDictionary = [ +private let teamSigningSettings: SettingsDictionary = [ ... -let localSigningSettings: SettingsDictionary = [ +private let localSigningSettings: SettingsDictionary = [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/Project.swift` around lines 13 - 29, Update the file-scoped declarations isLocalSigning, teamSigningSettings, and localSigningSettings to use private access control, restricting these manifest-only signing settings to Projects/Devault/Project.swift.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/Project.swift`:
- Around line 13-29: Update the file-scoped declarations isLocalSigning,
teamSigningSettings, and localSigningSettings to use private access control,
restricting these manifest-only signing settings to
Projects/Devault/Project.swift.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 24341999-3c89-4094-9d4d-e08de004ff0f
📒 Files selected for processing (10)
.mise.tomlProjects/DVData/Sources/ServiceImpl/ICloudSync/CloudKitAccountServiceImpl.swiftProjects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swiftProjects/DVPresentation/Resources/Localizable.xcstringsProjects/DVPresentation/Sources/Dependencies/SecretClient.swiftProjects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarView.swiftProjects/Devault/Project.swiftREADME.mdscripts/generate-local
🚧 Files skipped from review as they are similar to previous changes (4)
- Projects/DVDomain/Sources/Service/Interface/ICloudAccountStatus.swift
- Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
- Projects/DVPresentation/Sources/Features/Onboarding/OnboardingFeature.swift
- Projects/DVPresentation/Sources/Dependencies/SecretClient.swift
CKContainer.accountStatus() 기반으로 iCloud 로그인 여부를 확인하고, 동기화 사용 여부를 UserDefaults에 저장하는 도메인/데이터 레이어를 추가한다.
Bundle ID를 com.devault.devault에서 com.devault.app으로 정리하고, iCloud(CloudKit) 컨테이너 capability와 Touch ID Info.plist 설명 문구를 추가한다.
ModelConfiguration의 cloudKitDatabase 기본값(.automatic)은 앱에 iCloud entitlement가 있으면 CloudKit 미러링을 시도한다. 현재 모델들은 CloudKit이 지원하지 않는 @Attribute(.unique)를 쓰고 있어 그대로 두면 ModelContainer 초기화가 loadIssueModelContainer 에러로 실패하므로, cloudKitDatabase를 .none으로 명시해 당장은 로컬 전용으로 유지한다.
didTapUnlock이 LockClient를 통해 실제 LocalAuthentication 인증을 수행하도록 연결하고, 인증 실패 시 alert로 안내한다.
Enable Touch ID는 OnboardingClient를 통해 실제 인증을 수행한 뒤 다음 단계로 넘어가고, Enable Sync는 iCloud 계정 상태를 확인해 사용 가능하면 동기화 설정을 저장한다. 두 경우 모두 실패 시 alert로 안내한다.
CloudKit은 유니크 제약을 지원하지 않아 CloudKit 미러링을 켜면 스키마 검증에서 실패한다. 유일성은 이미 각 RepositoryImpl이 insert 전 fetch로 직접 보장하고 있어(duplicateID/duplicateProjectLink 등), SwiftData 레벨의 제약은 제거해도 동작에 변화가 없다.
CloudKit은 to-one 관계가 optional이어야 한다. SecretProjectLink.project/ secret, SecretPayload.secret, SecretMetadata.secret을 optional로 바꾸고, 관련 소비처(Project.secrets/Secret.projects 계산 프로퍼티, 프로젝트별 조회 predicate)를 nil-safe하게 고친다. 현재 delete rule이 전부 cascade라 로컬 단일 기기 환경에서는 값이 항상 채워지며, 이 optional은 기기 간 CloudKit 동기화 지연으로 관계 대상이 아직 도착하지 않은 순간만 대비한다.
CloudKit은 non-optional 프로퍼티가 선언부에 기본값을 갖고 있어야 스키마로 인식한다. 커스텀 init이 항상 값을 채워주므로 런타임 동작에는 변화가 없고, CloudKit 스키마 검증을 통과하기 위한 메타데이터 목적의 변경이다. schemaVersion 기본값은 실제 SecretContent 타입들이 전부 1을 쓰고 있어 1로 맞춘다.
스키마가 CloudKit 호환 요건(unique 제거, 관계 optional화, 기본값 추가)을 모두 충족했으므로, cloudKitDatabase를 .none에서 등록해둔 iCloud 컨테이너로 명시해 실제 미러링을 켠다.
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 대신 kSecAttrAccessibleWhenUnlocked + kSecAttrSynchronizable를 사용해 마스터 키가 iCloud Keychain으로 기기 간 동기화되도록 한다. 조회 쿼리에는 kSecAttrSynchronizableAny를 추가해 이전 방식으로 저장된 키도 계속 찾을 수 있게 한다. 아직 실사용자 데이터가 없는 개발 단계라 별도 마이그레이션 로직 없이 단순 전환한다.
isICloudSyncEnabled는 기기가 아닌 사용자 단위 설정이라 iCloud Key-value storage(NSUbiquitousKeyValueStore)로 옮겨 기기 간 동기화되게 한다. hasCompletedOnboarding은 기기별로 Touch ID 확인을 다시 거쳐야 하므로 로컬 UserDefaults에 그대로 둔다. entitlements에 ubiquity-kvstore-identifier를 추가한다.
LocalStorage.makeDefault()가 항상 .private(...)로 CloudKit 미러링을 시도해, 온보딩에서 iCloud Sync를 거절해도 데이터가 그대로 올라가고 있었다. iCloudSyncEnabled 파라미터를 받아 false면 cloudKitDatabase를 .none으로 구성하도록 고치고, LiveStorage.shared가 SettingsRepository의 저장된 값을 읽어 넘기도록 한다.
Design모듈에 DVImage, Image extension으로 dv 메서드 추가
코드 가이드에 따라 public extension 대신 개별 선언마다 접근 제어자를 명시한다.
iCloud 컨테이너 식별자를 DVCore의 상수로 일원화한다.
Data Protection Keychain과 AfterFirstUnlock 접근성을 명시하고, 추후 CLI 공유를 위한 keychain access group을 등록한다.
isICloudSyncEnabled는 기기별 설정이므로 UserDefaults로 되돌리고, Composition Root에서 SettingsRepository를 공유 인스턴스로 관리한다.
SecretProjectLink에 project/secret id를 스냅샷으로 보관해 관계 동기화 지연 중에도 목록 조회와 프로젝트 연결 갱신이 흔들리지 않게 한다.
CloudKit은 to-many 관계도 optional이어야 하므로 Project/Secret의 관계 타입과 조회 predicate를 수정한다.
Touch ID/시스템 암호 인증 실패 알럿 생성 로직을 하나의 헬퍼로 통합하고, 도달 불가능한 케이스에 assertionFailure를 추가한다.
tuist generate로 스킴이 재생성되어도 유지되도록 CloudKit 디버그 launch argument를 Project.swift에 코드로 명시한다.
- 프로젝트 필터링 시 동기화 지연 중인 관계 대신 스냅샷 projectID로 판별한다. - Secret 변환 실패 시 목록에서 제외하되 Log.warn으로 남긴다. - iCloud 상태 조회 실패를 네트워크/인증 오류로 구분해 매핑한다. - Keychain 마스터 키 접근성을 WhenUnlocked로 제한한다. - BackupRecord를 별도 ModelConfiguration으로 분리해 로컬 경로가 CloudKit에 동기화되지 않게 한다.
- TUIST_LOCAL_SIGNING=1로 생성하면 iCloud entitlement 없이 ad-hoc 서명하는 로컬 빌드 모드 추가 - 앱 타겟이 프로비저닝 프로파일을 잡지 못해 서명에 실패하던 문제 수정 (CODE_SIGN_STYLE/IDENTITY 명시) - 저장소 안에서만 scripts/를 PATH에 얹어 generate-local을 접두사 없이 실행하도록 구성 - README에 팀 시트가 없는 경우의 워크스페이스 생성 절차 추가
✨ What’s this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
📸 스크린샷 (Optional)
🧪 테스트 / 검증 내역
💬 기타 공유 사항
🙇🏻♀️ 리뷰 가이드 (선택)
KeychainKeyStore.swift의 iCloud Keychain 동기화/접근성 설정Project.swift,Secret.swift,SecretProjectLink.swift의 CloudKit 관계 optional 처리LiveSettingsRepository.swift로 SettingsRepository를 Composition Root에서 단일 인스턴스로 관리하는 방식Summary by CodeRabbit
새로운 기능
버그 수정
문서