Feature/#57 - Sidebar 프로젝트 CRUD 기능 연결 및 메뉴 카테고리에 따른 Content 화면 연결 - #62
Feature/#57 - Sidebar 프로젝트 CRUD 기능 연결 및 메뉴 카테고리에 따른 Content 화면 연결#62dlguszoo wants to merge 13 commits into
Conversation
- ProjectItem: Presentation 레이어 VO (id + name만 노출) - ProjectItem+Mapping: DVDomain.Project → ProjectItem 변환 init - SidebarError: Presentation 레이어 에러 (fetchFailed/createFailed/renameFailed/deleteFailed/nameTaken)
- SidebarFeature: sidebarClient로 프로젝트 fetch/rename/delete 구현 - projectsState: LoadingState<IdentifiedArrayOf<ProjectItem>, SidebarError> - mutation 후 .task refetch (CancelID.fetch로 중복 취소) - 인라인 rename (didTapRename/didConfirmRename/didCancelRename) - 삭제 확인 alert (makeDeleteAlert helper 분리) - E2: .run 캡처 리스트 명시, E5: cancellable(id:cancelInFlight:) 적용 - SidebarView: idle/loading/loaded/failed 4가지 분기 렌더링 - 프로젝트 선택 List.onKeyPress(.return) → rename 진입
…mn 연결 - MainFeature: SidebarFeature delegate 전부 처리 - selectionChanged → SecretListFeature 갱신 - addButtonTapped → selectSecretType 활성화 + isCreatingSecret = true - projectRenamed → delegate payload 이름으로 즉시 secretList 타이틀 갱신 - createProject delegate → sidebarClient refetch 트리거 - MainView: selectSecretType 활성 시 2-column split, 비활성 시 3-column split
DVProjectContainer와 동일한 leading icon 레이아웃에서 이름 부분을 TextField로 교체한 컴포넌트. onAppear 시 @focusstate로 자동 포커스.
…ngs window - LiveStorage: ModelContainer 싱글톤 (모든 RepositoryImpl 공유) - SidebarClient+Live: FetchProject/CreateProject/RenameProject/DeleteProject UseCase 조립. duplicateName → SidebarError.nameTaken 매핑 - DevaultApp: Settings WindowGroup 추가 (openWindow(id: "settings") 연결)
- SidebarFeatureTests: fetch 정렬, 실패 상태, rename 흐름(성공/nameTaken/취소), delete 흐름(alert 표시, selection 초기화, refetch) 검증 - MainFeatureTests: selectionChanged/addButtonTapped/addProjectTapped delegate, projectCreated(isCreatingSecret 분기), projectRenamed contentView 갱신 검증 - CreateProjectFeatureTests: sidebarClient 전환 반영, nameTaken/일반 실패 alert 케이스 추가
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. 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 (3)
Walkthrough사이드바 프로젝트의 조회·생성·이름 변경·삭제를 Changes사이드바 프로젝트 관리
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant SidebarView
participant SidebarFeature
participant SidebarClient
participant MainFeature
User->>SidebarView: 프로젝트 선택 또는 작업 실행
SidebarView->>SidebarFeature: 사용자 액션 전달
SidebarFeature->>SidebarClient: 프로젝트 작업 요청
SidebarClient-->>SidebarFeature: ProjectItem 또는 SidebarError 반환
SidebarFeature-->>MainFeature: 선택·생성·이름 변경 delegate 전달
MainFeature-->>SidebarView: 프로젝트 및 SecretList 상태 갱신
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 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: 4
🧹 Nitpick comments (6)
Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift (1)
66-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win예기치 않은 오류의 매핑 경로도 검증하세요.
현재 stub은
SidebarError분기만 통과하므로,SidebarError가 아닌 오류를.createFailed로 바꾸는 fallback catch가 검증되지 않습니다. 테스트 전용 오류를 throw하도록 바꿔 해당 경로를 확인하세요.🤖 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/CreateProject/CreateProjectFeatureTests.swift` around lines 66 - 72, Update createShowsGenericAlertOnFailure to make sidebarClient.createProject throw a test-only error that is not SidebarError, so the fallback catch mapping unexpected errors to .createFailed is exercised; preserve the existing generic-alert assertions.Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift (1)
122-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
sidebarClient의존성은private로 좁혀도 충분이 프로퍼티는
body내부에서만 참조됩니다. 외부 파일에서 직접 접근하지 않으니private으로 좁히는 게 원칙에 맞습니다.As per path instructions, "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요."
🛠️ 제안 수정
- `@Dependency`(\.sidebarClient) var sidebarClient + `@Dependency`(\.sidebarClient) private var sidebarClient🤖 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/SidebarFeature.swift` at line 122, Change the sidebarClient dependency declaration in SidebarFeature to private access, since it is only referenced within body and does not need external visibility.Source: Path instructions
Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift (1)
31-46: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winTextField에 accessibility label 추가 권장
TextField("", text: $text)는 placeholder도 label도 없어 VoiceOver 사용자가 이 필드의 용도를 알기 어렵습니다.🛠️ 제안 수정
TextField("", text: $text) .dvFont(.bodyMD) .textFieldStyle(.plain) .focused($isFocused) + .accessibilityLabel("프로젝트 이름") .onSubmit(onSubmit) .onExitCommand(perform: onCancel)🤖 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/DVDesign/Sources/Components/DVProjectRenameContainer.swift` around lines 31 - 46, In the DVProjectRenameContainer body, add an accessibility label to the TextField bound to $text so VoiceOver clearly identifies it as the project rename field. Keep the existing focus, submit, cancel, styling, and layout behavior unchanged.Projects/Devault/Sources/Composition/Dependencies/SidebarClient+Live.swift (1)
19-53: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win에러 스왈로우 시 로깅 부재 + create/rename 에러 매핑 중복
catch블록에서 원본 에러를 완전히 버리고SidebarError로만 변환합니다. 프로덕션 디버깅을 위해DVLogger.error로 원본 에러를 남기는 게 좋겠습니다.duplicateName → nameTaken매핑 로직이createProject/renameProject에 중복돼 있습니다. 헬퍼로 추출하면 유지보수가 편해집니다.♻️ 제안 리팩토링
+private func mapProjectUseCaseError(_ error: Error, fallback: SidebarError) -> SidebarError { + if case ProjectUseCaseError.repositoryFailure(.duplicateName) = error { return .nameTaken } + DVLogger.error("SidebarClient 작업 실패: \(error)") + return fallback +}
createProject/renameProject의 catch 블록에서 위 헬퍼를 호출하도록 교체합니다.🤖 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/SidebarClient`+Live.swift around lines 19 - 53, Update the live sidebar dependency closures to log each caught original error with DVLogger.error before mapping it to SidebarError. Extract the shared duplicateName-to-nameTaken mapping used by createProject and renameProject into a helper, then have both catch paths reuse it while preserving their existing createFailed, renameFailed, and nameTaken outcomes.Projects/DVPresentation/Sources/Features/Main/MainFeature.swift (2)
154-156: 🎯 Functional Correctness | 🔵 Trivial
.notice필터가.all로 대체 처리됨(TODO).도메인 레이어
.noticecollection 연동 전까지 임시 처리인 점 확인. 도메인 쪽 작업이 준비되면 알려주시면 연결 도와드릴게요.🤖 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/Main/MainFeature.swift` around lines 154 - 156, Update the .filter(.notice) case in MainFeature so it no longer substitutes the .all collection once the domain layer’s .notice collection is available. Connect this case to the domain .notice collection and remove the temporary TODO, while preserving the existing filter handling structure.
76-84: 🎯 Functional Correctness | 🔵 TrivialTODO 확인:
typeSelected→ createSecret 연결 필요.주석대로 아직 미구현 상태입니다. 후속 작업으로
createSecret플로우 연결이 필요합니다. 구현 초안을 만들어드릴까요, 아니면 이슈로 트래킹할까요?🤖 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/Main/MainFeature.swift` around lines 76 - 84, Connect the .selectSecretType(.delegate(.typeSelected)) case in MainFeature’s reducer to the createSecret flow instead of returning .none. Use the selected type from the delegate action to initialize or route into the existing createSecret state/action path, and remove the TODO once the transition is implemented.
🤖 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/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift`:
- Around line 112-119: Update the AddToProjectFeature project-loading flow
around fetchProjects and the .projectCreated delegate handler so an in-flight
initial response cannot overwrite the newly created project. Serialize or cancel
the existing fetch before applying the creation result, or track request
generations and ignore stale responses; preserve the new item in projectsState
and keep selectedProjectID set to item.id.
In `@Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift`:
- Around line 220-221: Update the failure handling for renameResponse and the
related deleteResponse failure case in the SidebarFeature reducer so generic
failures such as renameFailed and deleteFailed provide user feedback instead of
silently returning .none. Preserve the existing nameTaken-specific notification
behavior while adding the appropriate generic failure notification for other
errors.
- Around line 252-253: Update the .deleteResponse(.failure) handling in the
SidebarFeature reducer to process deletion failures consistently with
renameResponse(.failure), instead of silently returning .none. Reuse the
existing failure-reporting or user-facing error flow established for rename
failures.
- Around line 183-198: Update the .didConfirmRename case to trim
state.renameText and reject empty or whitespace-only names before clearing
rename state or calling sidebarClient.renameProject. Preserve the existing
rename flow for valid trimmed names, passing the validated name to the client.
---
Nitpick comments:
In `@Projects/Devault/Sources/Composition/Dependencies/SidebarClient`+Live.swift:
- Around line 19-53: Update the live sidebar dependency closures to log each
caught original error with DVLogger.error before mapping it to SidebarError.
Extract the shared duplicateName-to-nameTaken mapping used by createProject and
renameProject into a helper, then have both catch paths reuse it while
preserving their existing createFailed, renameFailed, and nameTaken outcomes.
In `@Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift`:
- Around line 31-46: In the DVProjectRenameContainer body, add an accessibility
label to the TextField bound to $text so VoiceOver clearly identifies it as the
project rename field. Keep the existing focus, submit, cancel, styling, and
layout behavior unchanged.
In `@Projects/DVPresentation/Sources/Features/Main/MainFeature.swift`:
- Around line 154-156: Update the .filter(.notice) case in MainFeature so it no
longer substitutes the .all collection once the domain layer’s .notice
collection is available. Connect this case to the domain .notice collection and
remove the temporary TODO, while preserving the existing filter handling
structure.
- Around line 76-84: Connect the .selectSecretType(.delegate(.typeSelected))
case in MainFeature’s reducer to the createSecret flow instead of returning
.none. Use the selected type from the delegate action to initialize or route
into the existing createSecret state/action path, and remove the TODO once the
transition is implemented.
In `@Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift`:
- Line 122: Change the sidebarClient dependency declaration in SidebarFeature to
private access, since it is only referenced within body and does not need
external visibility.
In `@Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift`:
- Around line 66-72: Update createShowsGenericAlertOnFailure to make
sidebarClient.createProject throw a test-only error that is not SidebarError, so
the fallback catch mapping unexpected errors to .createFailed is exercised;
preserve the existing generic-alert assertions.
🪄 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: e7bdf6e5-6b0c-448c-a253-0db42ece996c
📒 Files selected for processing (21)
Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swiftProjects/DVPresentation/Resources/Localizable.xcstringsProjects/DVPresentation/Sources/Dependencies/SidebarClient.swiftProjects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swiftProjects/DVPresentation/Sources/Features/AddToProject/AddToProjectView.swiftProjects/DVPresentation/Sources/Features/CreateProject/CreateProjectFeature.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Model/CreatableSecretType.swiftProjects/DVPresentation/Sources/Features/Main/MainFeature.swiftProjects/DVPresentation/Sources/Features/Main/MainView.swiftProjects/DVPresentation/Sources/Features/SelectSecretType/SelectSecretTypeFeature.swiftProjects/DVPresentation/Sources/Features/Sidebar/Error/SidebarError.swiftProjects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem+Mapping.swiftProjects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarView.swiftProjects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swiftProjects/DVPresentation/Tests/Main/MainFeatureTests.swiftProjects/DVPresentation/Tests/Sidebar/SidebarFeatureTests.swiftProjects/Devault/Sources/Composition/Dependencies/SidebarClient+Live.swiftProjects/Devault/Sources/Composition/LiveStorage.swiftProjects/Devault/Sources/DevaultApp.swift
| case .destination(.presented(.createProject(.delegate(.projectCreated(let item))))): | ||
| var projects: IdentifiedArrayOf<ProjectItem> = { | ||
| if case let .loaded(projects) = state.projectsState { return projects } | ||
| return [] | ||
| }() | ||
| projects.append(project) | ||
| projects.append(item) | ||
| state.projectsState = .loaded(projects) | ||
| state.selectedProjectID = project.id | ||
| state.selectedProjectID = item.id |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
초기 목록 요청과 생성 결과의 순서를 보장하세요.
로딩 중 생성한 뒤 기존 fetchProjects() 응답이 늦게 도착하면, 여기서 추가한 항목을 오래된 목록으로 덮어쓸 수 있습니다. 새 프로젝트가 메뉴에서 사라지고 선택 ID만 남습니다. 생성 후 목록 요청을 직렬화·취소하거나 요청 세대로 오래된 응답을 무시하세요.
🤖 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/AddToProject/AddToProjectFeature.swift`
around lines 112 - 119, Update the AddToProjectFeature project-loading flow
around fetchProjects and the .projectCreated delegate handler so an in-flight
initial response cannot overwrite the newly created project. Serialize or cancel
the existing fetch before applying the creation result, or track request
generations and ignore stale responses; preserve the new item in projectsState
and keep selectedProjectID set to item.id.
There was a problem hiding this comment.
추후 AddToProject 담당 팀원의 TODO로 남겨둘 것.
-> 수동 append가 아닌 refetch 패턴으로
There was a problem hiding this comment.
댓글 이미지들은 다크 테마의 macOS 앱 사이드바 UI를 보여줍니다. 첫 번째는 프로젝트 목록이 보이는 상태에서 중앙에 경고 아이콘, “목록을 불러오지 못했어요”, **“Retry”**가 표시됩니다. 두 번째는 프로젝트 “가나” 행이 파란색으로 선택되고 인라인 이름 편집 필드가 열린 모습입니다. 세 번째는 API Keys/Token, OAuth, Database, SSH & Credentials, EnvSet, Etc 카테고리 타일이 표시됩니다. 네 번째는 “Create Project” 모달과 Project Name, e.g DeVault, Cancel/Create 버튼을, 다섯 번째는 “'가나' 프로젝트를 삭제할까요?” 확인 모달과 취소/삭제 버튼을 보여줍니다.
@dlguszoo, 확인했습니다. 이 건은 현재 PR 범위에서는 보류하겠습니다.
후속 작업에서는 생성 결과를 수동 append하지 않고, 생성 성공 후 프로젝트 목록을 refetch하는 흐름으로 전환하는 것이 적절합니다. 이때 최초 fetch가 늦게 완료되어 최신 목록을 덮어쓰지 않도록, 기존 요청 취소 또는 요청 세대(generation) 관리도 함께 처리해야 합니다.
원하시면 AddToProject 담당자용 GitHub 후속 이슈로 등록하겠습니다.
✏️ Learnings added
Learnt from: dlguszoo
Repo: DevaultProject/Devault-macOS PR: 62
File: Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift:112-119
Timestamp: 2026-07-27T19:52:44.915Z
Learning: `Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift`의 프로젝트 생성 후 목록 동기화는 수동 `ProjectItem` append가 아니라 refetch 패턴으로 개선할 예정이며, AddToProject 담당 팀원의 후속 작업 범위이다.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
|
@dlguszoo 실제로 구현하고자 하는 범위까지만 리뷰 사항 반영하면 될 것 같습니다. 🔴 P0 — 논리 오류/UX 버그 (수정 권장)
SidebarView.swift bottomBar
SidebarFeature.swift case .renameResponse(.failure): // 그 외 실패는 조용히 삭킴
case .didConfirmRename:
MainFeature.swift |
|
P1 MainFeature에서 .send(.sidebar(.task)), .merge(.send(.delegate), .send(.task)) — refetch 목적으로 .task를 재활용. .cancellable로 중복은 막았지만 시맨틱이 흐림. 별도 .refetchProjects (또는 .reload) 액션이 의도가 명확.
return .merge(
Composition Root라 fail-fast가 맞긴 하지만, macOS 앱에서 첫 실행 시 디스크 권한/공간 이슈로 실패 가능. fatalError 대신 root View에 오류 상태를 렌더링하는 게 실사용 회복력 측면에서 안전. 최소한 로그(OSLog)라도 남겨야 사후 분석 가능.
makeSecretListState |
|
🟢 P2 — nit / 사소한 개선 -> @dlguszoo 이부분은 정말 선택반영. 취향대로
projectList (List) 자체에 걸려 있어서 List가 focus를 가진 경우에만 동작. 실사용에서는 프로젝트 클릭 → 그 상태로 Enter니까 대체로 OK지만, contextMenu → Rename 이후 Enter 흐름과 겹치지 않는지 실기기 확인 권장.
DVProjectContainer와 동일 아이콘 유지가 목적인데 각각 하드코딩이라 한쪽만 바뀔 위험. DVProjectContainer.icon 같은 상수 공유하거나, 아이콘을 파라미터로 받는 편이 안전.
같은 모듈 다른 파일들은 4-space가 다수 (DVProjectRenameContainer, 기존 파일들). 스타일 통일. |
doyeonk429
left a comment
There was a problem hiding this comment.
✅ 잘한 점
- Presentation VO(ProjectItem) 도입 — DVDomain의 Project를 Presentation이 직접 노출하지 않고 id + name만 잘라내서 사용. 레이어링 개선 + Equatable/Sendable/Identifiable로 TCA 친화적.
- SidebarError 분리 — UseCaseError coupling을 끊고 UI 문구에 필요한 만큼만 (nameTaken 등) 매핑. Live client에서 duplicateName → nameTaken 변환이 깔끔.
- LoadingState 4-state 완전 커버 — idle/loading/loaded/failed 전부 View에서 분기 처리. 특히 failed 문구 렌더까지 잊지 않고 넣음.
- 테스트 커버리지가 넓고 견고 — SidebarFeature/MainFeature/CreateProjectFeature 3개 feature × success/nameTaken/failure/cancel/refetch 케이스. TestStore exhaustive 검증 좋음.
- .cancellable(id:cancelInFlight:) — refetch race 방어. 흔히 놓치는 부분.
- makeDeleteAlert helper 분리 — reducer 슬림.
- .run 캡처 리스트 명시([id, name]) — Swift concurrency data race 예방 의도가 명확.
- DVProjectRenameContainer의 @focusstate 캡슐화 — 외부에서 focus 관리 안 해도 됨. onAppear에서 자동 focus, onSubmit/onExitCommand 처리까지 keyboard-first UX 완결.
- mutation → refetch 원칙 일관 유지 — manual list 조작 없이 항상 refetch. 상태 sync 버그 원천 차단. (스피너 이슈는 별도 튜닝 문제)
- rename 즉시 title 갱신 — refetch 기다리지 않고 delegate payload로 secretList 타이틀 갱신 → 반응성 확보.
- Composition Root 분리 — LiveStorage.shared 싱글톤으로 ModelContainer 공유, SidebarClient+Live에서 UseCase 조립. Dependency Rule 잘 지킴.
- 삭제 selection 초기화 로직 — 삭제한 프로젝트가 현재 selection일 때만 .filter(.all)로 초기화. 조건 정확.
- View 접미어 컨벤션 준수 — SidebarView, MainView, AddToProjectView 그대로. DVProjectRenameContainer는 Container 계열이라 예외적으로 잘 부합.
1. DVProjectRenameContainer 에서 DVProjectContainer.icon 상수 공유 2. @focusstate로 rename이 끝날 때 List 포커스를 복원 및 DVProjectRenameContainer에서 TextField가 포커스를 잃을 때(isFocused = false) onSubmit을 트리거
1. Settings 버튼 delegate 이중 처리 제거 — View에서 openWindow 직접 호출로 통일 2. renameFailed/deleteFailed 실패 시 무피드백 버그 수정 — generic alert 추가 3. didConfirmRename trim 누락 수정 — 공백 이름 제출 차단 4. isCreatingSecret 부모 직접 쓰기 제거 — setCreatingSecret(Bool) 액션으로 캡슐화 5. renameResponse(.success) .merge → .concatenate로 순서 보장 6. Alert 생성 로직 private extension helper로 분리
1. LiveStorage 초기화 실패 시 OSLog critical 로그 추가 — fatalError 사전 사후 분석 가능
There was a problem hiding this comment.
🧹 Nitpick comments (4)
Projects/DVDesign/Sources/Components/DVProjectContainer.swift (1)
11-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win동일 모듈 내부 상수라면
public으로 공개하지 마세요.
DVProjectRenameContainer와 동일한DVDesign타깃에서만 사용한다면public static let대신 기본 접근 수준인static let으로 제한해 공개 API를 줄이세요. 외부 모듈에서 실제로 참조하는 계약이 있는 경우에만public을 유지해야 합니다.- public static let projectIconSystemName = "tray" + static let projectIconSystemName = "tray"🤖 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/DVDesign/Sources/Components/DVProjectContainer.swift` around lines 11 - 12, Update DVProjectContainer.projectIconSystemName from public static let to static let, limiting it to the DVDesign module’s default access level unless an external-module usage contract requires public visibility.Source: Path instructions
Projects/Devault/Sources/Composition/LiveStorage.swift (1)
3-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win모듈 import 그룹을 분리하세요.
내장 모듈인
OSLog와 프로젝트 모듈인DVData사이에 빈 줄을 추가해 import 규칙을 맞춰 주세요.수정 예시
import OSLog + import DVData🤖 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/LiveStorage.swift` around lines 3 - 4, Separate the OSLog and DVData imports into distinct groups by inserting a blank line between the built-in module import and the project module import.Source: Path instructions
Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift (2)
32-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winbody 내부 레이아웃을 private 뷰로 분리하세요.
body에는private var renameField: some View같은 파라미터 없는 하위 뷰를 두고, 포커스 및 제출 이벤트 로직과 레이아웃을 분리하면 구조를 유지하기 쉬워집니다.🤖 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/DVDesign/Sources/Components/DVProjectRenameContainer.swift` around lines 32 - 49, DVProjectRenameContainer의 body에서 HStack 레이아웃과 TextField 이벤트 처리를 파라미터 없는 private 하위 뷰(예: renameField)로 분리하고, body는 해당 뷰를 반환하도록 변경하세요. 포커스 관리와 onSubmit/onExitCommand 동작은 새 하위 뷰에서 동일하게 유지하세요.Source: Path instructions
11-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win구현에 필요 없는 저장 프로퍼티의
public노출을 줄이세요.외부에는
public init만 제공하고, 바인딩과 콜백은 뷰 내부에서만 사용하도록private로 제한하는 편이 API 표면을 안전하게 유지합니다.수정 예시
- `@Binding` public var text: String - public var onSubmit: () -> Void - public var onCancel: () -> Void + `@Binding` private var text: String + private let onSubmit: () -> Void + private let onCancel: () -> Void🤖 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/DVDesign/Sources/Components/DVProjectRenameContainer.swift` around lines 11 - 13, Reduce the visibility of the stored properties in DVProjectRenameContainer: keep only the initializer publicly exposed, and mark the text binding plus onSubmit and onCancel callbacks as private so they are used only internally by the view.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/LiveStorage.swift`:
- Around line 3-4: Separate the OSLog and DVData imports into distinct groups by
inserting a blank line between the built-in module import and the project module
import.
In `@Projects/DVDesign/Sources/Components/DVProjectContainer.swift`:
- Around line 11-12: Update DVProjectContainer.projectIconSystemName from public
static let to static let, limiting it to the DVDesign module’s default access
level unless an external-module usage contract requires public visibility.
In `@Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift`:
- Around line 32-49: DVProjectRenameContainer의 body에서 HStack 레이아웃과 TextField 이벤트
처리를 파라미터 없는 private 하위 뷰(예: renameField)로 분리하고, body는 해당 뷰를 반환하도록 변경하세요. 포커스 관리와
onSubmit/onExitCommand 동작은 새 하위 뷰에서 동일하게 유지하세요.
- Around line 11-13: Reduce the visibility of the stored properties in
DVProjectRenameContainer: keep only the initializer publicly exposed, and mark
the text binding plus onSubmit and onCancel callbacks as private so they are
used only internally by the view.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c72f84e8-7700-40ce-9233-34cc5b8ef746
📒 Files selected for processing (7)
Projects/DVDesign/Sources/Components/DVProjectContainer.swiftProjects/DVDesign/Sources/Components/DVProjectRenameContainer.swiftProjects/DVPresentation/Sources/Features/Main/MainFeature.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swiftProjects/DVPresentation/Sources/Features/Sidebar/SidebarView.swiftProjects/DVPresentation/Tests/Main/MainFeatureTests.swiftProjects/Devault/Sources/Composition/LiveStorage.swift
🚧 Files skipped from review as they are similar to previous changes (4)
- Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
- Projects/DVPresentation/Tests/Main/MainFeatureTests.swift
- Projects/DVPresentation/Sources/Features/Main/MainFeature.swift
- Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift
1. didConfirmRename trim/empty guard 제거 — 빈 이름은 repository 로직 검증으로 감지해 alert 표시 2. isListFocused 및 onChange 제거 — macOS SwiftUI에서 List 프로그래매틱 포커스 복원 불가
✨ What’s this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
ProjectItem), 에러(SidebarError), 전용 클라이언트(SidebarClient) 추가SidebarFeature에 실제 데이터 fetch / 인라인 rename / 삭제 기능 연결LoadingState<IdentifiedArrayOf<ProjectItem>, SidebarError>기반 idle/loading/loaded/failed 4-state 모델링MainFeature에서 Sidebar delegate 전부 처리 및 2-column/3-column 전환 연결DVProjectRenameContainer— 인라인 rename 전용 컴포넌트 (자동 포커스, Enter/ESC 처리)LiveStorage,SidebarClient+Live)TestStore기반 단위 테스트 추가📸 스크린샷 (Optional)
🧪 테스트 / 검증 내역
SelectSecretType2-column 진입 시 sidebar selection 해제 확인SelectSecretType진입 중 프로젝트 생성해도 새 프로젝트가 select되지 않는지 확인.all초기화 확인DVPresentationTests전체 통과 확인💬 기타 공유 사항
AddToProjectFeature의 project 목록도 우선은sidebarClient로 전환했습니다. 후에 수정필요하면 해주세요!DVProjectRenameContainer의@FocusState를 컴포넌트 내부로 캡슐화해 외부에서 포커스 관리가 필요 없습니다.LiveStorage.shared싱글톤으로 모든 RepositoryImpl이 동일한ModelContainer를 공유합니다.🙇🏻♀️ 리뷰 가이드 (선택)
Summary by CodeRabbit
새로운 기능
개선 사항