[#36] feat: Secret 목록 화면 구현 - #46
Conversation
|
Warning Review limit reached
Next review available in: 11 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 (12)
WalkthroughSecret 목록의 검색·정렬·만료 필터와 날짜 검색을 구현하고, 목록 화면에서 프로젝트 생성 및 Secret 연결 흐름을 추가했습니다. macOS floating 패널, 로딩 상태, 공개 오류 매핑 API와 TCA 테스트도 포함됩니다. ChangesSecret 목록 기능
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant SecretListView
participant SecretListFeature
participant secretClient
User->>SecretListView: 검색어 또는 정렬 변경
SecretListView->>SecretListFeature: didChangeSearchText / didSelectSort
SecretListFeature->>secretClient: fetchByQuery(query)
secretClient-->>SecretListFeature: Secret 목록 또는 오류
SecretListFeature-->>SecretListView: loaded / failed 상태
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: 1
🧹 Nitpick comments (7)
Projects/DVPresentation/Sources/Features/SecretList/SecretListView.swift (1)
256-287: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
SortMenuRow가extension SecretListView밖에 선언돼 있어요.독립
@State(isHovered)를 가진 하위뷰인데extension블록 바깥 최상위에 있습니다. 최소한extension SecretListView { }안으로 옮기는 게 스타일 가이드에 맞고, "재사용 필요해지면 DVDesign으로 승격"할 때도 경계가 더 명확해집니다.As per path instructions, "하위뷰가
extension밖에 선언되어 있으면 이동을 제안하세요" 그리고 "독립적인 State/로직을 갖거나 ... 별도 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/DVPresentation/Sources/Features/SecretList/SecretListView.swift` around lines 256 - 287, Move the SortMenuRow nested view into the extension SecretListView block, preserving its existing `@State`, layout, styling, and action behavior; do not otherwise refactor the component.Source: Path instructions
Projects/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swift (2)
3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서가 스타일 가이드와 다릅니다.
내장 프레임워크(
Foundation)를 맨 위에 두고 빈 줄로 서드파티(DVCore,DVDomain)를 구분해야 합니다. 지금은Foundation이 맨 아래에 있고 구분 줄도 없네요.🍎 제안
-import DVCore -import DVDomain -import Foundation +import Foundation + +import DVCore +import DVDomainAs 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/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swift` around lines 3 - 5, Reorder the imports in InMemorySecretQueryFilter so Foundation appears first, followed by a blank line and the alphabetically ordered DVCore and DVDomain imports.Source: Path instructions
10-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
matchesExpiry가Date.now를 직접 참조합니다 — 다른 UseCase의 날짜 주입 패턴과 어긋나요.
DeleteSecretUseCaseImpl등은dateProvider()로 현재 시각을 주입받는데, 여기만Date.now를 하드코딩해서 경계값(만료 시각이 정확히 지금인 케이스) 테스트가 불가능합니다.@Dependency(\.date)같은 걸 주입받아 일관성을 맞추는 게 좋겠습니다.🤖 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/Secret/InMemorySecretQueryFilter.swift` around lines 10 - 29, Update SecretQueryFilter’s matchesExpiry to obtain the current time through the project’s existing injected date dependency, matching the dateProvider() pattern used by other use cases, instead of directly referencing Date.now. Preserve the current collection filtering and expiry comparison behavior while making the time source controllable for boundary-value tests.Projects/DVDesign/Sources/Components/DVFloatingPanel.swift (1)
20-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
public extension View패턴 대신 선언별 접근제어 명시 권장
public extension View { func floatingPanel... }형태 대신, extension은 기본(internal)으로 두고floatingPanel함수에만public을 명시하는 편이 낫습니다. 다른 헬퍼가 추가될 때 의도치 않게 전부 public 노출되는 걸 방지합니다.♻️ 제안
-public extension View { - func floatingPanel<PanelContent: View>( +extension View { + public func floatingPanel<PanelContent: View>( isPresented: Binding<Bool>, `@ViewBuilder` content: `@escaping` () -> PanelContent ) -> some View {As per path instructions, "
public extension패턴 대신 각 선언에 직접 접근 제어가 명시되어 있는지 확인하세요."🤖 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/DVFloatingPanel.swift` around lines 20 - 29, Update the View extension containing floatingPanel to use the default internal extension access level, and mark only the floatingPanel method as public. Preserve its existing signature and behavior while preventing future declarations in the extension from being implicitly public.Source: Path instructions
Projects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swift (1)
1-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win실패(에러) 경로 테스트 커버리지 부재
두 테스트 파일 모두 성공 경로만 검증하고, 각 리듀서의 실패 분기(
.projectsResponse(.failure),.linkResponse(.failure),.createResponse(.failure))에 대한 테스트가 빠져 있습니다. 에러 매핑(ProjectUseCaseError/SecretUseCaseError)이 상태에 올바르게 반영되는지 확인하는 테스트를 추가하면 좋겠습니다.
Projects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swift#L1-L127:secretClient.fetchProjects/linkProject가 에러를 던질 때.projectsResponse(.failure),.linkResponse(.failure)처리 테스트 추가.Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift#L1-L65:secretClient.createProject가 에러를 던질 때.createResponse(.failure)및isCreating복귀 테스트 추가.🤖 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/AddToProject/AddToProjectFeatureTests.swift` around lines 1 - 127, 실패 경로 테스트가 누락되어 있으므로 Projects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swift의 AddToProjectFeature 테스트에 fetchProjects 실패 시 .projectsResponse(.failure) 상태 반영과 linkProject 실패 시 .linkResponse(.failure) 처리 테스트를 추가하세요. Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift의 CreateProjectFeature 테스트에는 createProject 실패 시 .createResponse(.failure)를 처리하고 isCreating이 원래 상태로 복귀하는지 검증하는 테스트를 추가하세요.Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift (1)
106-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
LoadingState에value프로퍼티를 추가하여 코드를 간결하게 정리해 보세요.만약 앞서 리뷰에서 제안해 드린
value프로퍼티를LoadingState에 추가하신다면, IIFE(즉시 실행 함수) 블록을 제거하고 닐 코얼레싱(??) 연산자를 이용해 코드를 훨씬 짧고 직관적으로 다듬을 수 있습니다.♻️ 제안하는 코드 개선안
- var projects: IdentifiedArrayOf<Project> = { - if case let .loaded(projects) = state.projectsState { return projects } - return [] - }() + var projects = state.projectsState.value ?? [] projects.append(project) state.projectsState = .loaded(projects)🤖 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 106 - 111, LoadingState에 현재 값을 반환하는 value 프로퍼티를 추가한 뒤, AddToProjectFeature의 projects 초기화 IIFE와 case 분기를 제거하고 state.projectsState.value ?? []를 사용해 기존 loaded 값 또는 빈 배열을 가져오도록 단순화하세요.Projects/DVPresentation/Sources/Support/LoadingState.swift (1)
7-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value연관 값 추출을 위한 편의 프로퍼티 추가를 고려해 보세요.
현재 구조도 아주 좋습니다만,
.loaded상태의 값을 손쉽게 꺼내 쓸 수 있도록value프로퍼티를 추가해 두면 사용하는 곳에서 패턴 매칭 없이 훨씬 깔끔하게 다룰 수 있어 추천합니다.💡 제안하는 익스텐션 구현
public enum LoadingState<Value: Equatable, Failure: Equatable>: Equatable { case idle case loading case loaded(Value) case failed(Failure) } + +public extension LoadingState { + var value: Value? { + guard case let .loaded(val) = self else { return nil } + return val + } +}🤖 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/Support/LoadingState.swift` around lines 7 - 12, LoadingState에 loaded 연관 값을 편리하게 추출할 수 있는 value 프로퍼티를 추가하세요. .loaded(Value)인 경우 해당 값을 반환하고, idle·loading·failed 상태에서는 기존 API와 일관된 방식으로 부재를 표현하도록 구현하며, LoadingState 선언의 Equatable 동작은 유지하세요.
🤖 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/SecretList/SecretListFeature.swift`:
- Around line 152-156: Update the mutation failure handling in
SecretListFeature’s mutationResponse reducer to use the mapped
SecretUseCaseError and present it through the feature’s `@Presents` alert
destination instead of returning .none. Ensure failures from didTapDelete,
didTapRecover, and didTapDeleteForever produce an appropriate user-visible alert
while preserving the existing success refresh behavior, and add coverage in
SecretListFeatureTests.swift for this failure path.
---
Nitpick comments:
In
`@Projects/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swift`:
- Around line 3-5: Reorder the imports in InMemorySecretQueryFilter so
Foundation appears first, followed by a blank line and the alphabetically
ordered DVCore and DVDomain imports.
- Around line 10-29: Update SecretQueryFilter’s matchesExpiry to obtain the
current time through the project’s existing injected date dependency, matching
the dateProvider() pattern used by other use cases, instead of directly
referencing Date.now. Preserve the current collection filtering and expiry
comparison behavior while making the time source controllable for boundary-value
tests.
In `@Projects/DVDesign/Sources/Components/DVFloatingPanel.swift`:
- Around line 20-29: Update the View extension containing floatingPanel to use
the default internal extension access level, and mark only the floatingPanel
method as public. Preserve its existing signature and behavior while preventing
future declarations in the extension from being implicitly public.
In
`@Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift`:
- Around line 106-111: LoadingState에 현재 값을 반환하는 value 프로퍼티를 추가한 뒤,
AddToProjectFeature의 projects 초기화 IIFE와 case 분기를 제거하고 state.projectsState.value
?? []를 사용해 기존 loaded 값 또는 빈 배열을 가져오도록 단순화하세요.
In `@Projects/DVPresentation/Sources/Features/SecretList/SecretListView.swift`:
- Around line 256-287: Move the SortMenuRow nested view into the extension
SecretListView block, preserving its existing `@State`, layout, styling, and
action behavior; do not otherwise refactor the component.
In `@Projects/DVPresentation/Sources/Support/LoadingState.swift`:
- Around line 7-12: LoadingState에 loaded 연관 값을 편리하게 추출할 수 있는 value 프로퍼티를 추가하세요.
.loaded(Value)인 경우 해당 값을 반환하고, idle·loading·failed 상태에서는 기존 API와 일관된 방식으로 부재를
표현하도록 구현하며, LoadingState 선언의 Equatable 동작은 유지하세요.
In `@Projects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swift`:
- Around line 1-127: 실패 경로 테스트가 누락되어 있으므로
Projects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swift의
AddToProjectFeature 테스트에 fetchProjects 실패 시 .projectsResponse(.failure) 상태 반영과
linkProject 실패 시 .linkResponse(.failure) 처리 테스트를 추가하세요.
Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift의
CreateProjectFeature 테스트에는 createProject 실패 시 .createResponse(.failure)를 처리하고
isCreating이 원래 상태로 복귀하는지 검증하는 테스트를 추가하세요.
🪄 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: 83ed6e10-503f-4250-be5f-2e44954accbf
📒 Files selected for processing (18)
Projects/DVCore/Sources/Formatting/SecretDateFormatter.swiftProjects/DVData/Sources/RepositoryImpl/Secret/InMemorySecretQueryFilter.swiftProjects/DVDesign/Sources/Components/DVFloatingPanel.swiftProjects/DVDesign/Sources/Components/DVTitleBar.swiftProjects/DVDesign/Sources/Components/DVVaultContainer.swiftProjects/DVDomain/Sources/UseCase/Error/ProjectUseCaseError.swiftProjects/DVDomain/Sources/UseCase/Error/SecretUseCaseError.swiftProjects/DVPresentation/Project.swiftProjects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swiftProjects/DVPresentation/Sources/Features/AddToProject/AddToProjectView.swiftProjects/DVPresentation/Sources/Features/CreateProject/CreateProjectFeature.swiftProjects/DVPresentation/Sources/Features/CreateProject/CreateProjectView.swiftProjects/DVPresentation/Sources/Features/SecretList/SecretListFeature.swiftProjects/DVPresentation/Sources/Features/SecretList/SecretListView.swiftProjects/DVPresentation/Sources/Support/LoadingState.swiftProjects/DVPresentation/Tests/AddToProject/AddToProjectFeatureTests.swiftProjects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swiftProjects/DVPresentation/Tests/SecretList/SecretListFeatureTests.swift
| case .mutationResponse(.success): | ||
| return fetchSecretsEffect(query: state.query, debounced: false) | ||
|
|
||
| case .mutationResponse(.failure): | ||
| return .none |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
mutation 실패가 완전히 무시됩니다.
didTapDelete/didTapRecover/didTapDeleteForever가 실패해도 .mutationResponse(.failure)는 state 변경 없이 .none을 반환해요. 사용자는 삭제/복구가 성공한 것처럼 보이지만 실제로는 아무 일도 일어나지 않은 상태가 됩니다. SecretUseCaseError.map이 이미 세분화된 에러를 주는데(context snippet 참고) 전혀 활용되지 않고 있어요. @Presents var alert destination을 추가해서 실패 시 사용자에게 알려주는 게 필요합니다. SecretListFeatureTests.swift에도 이 실패 경로에 대한 테스트가 하나도 없다는 점도 같은 근본 원인입니다.
🚨 제안 방향
case .mutationResponse(.failure):
- return .none
+ case .mutationResponse(.failure(let error)):
+ state.destination = .alert(AlertState { TextState(error.localizedDescription) })
+ return .none🤖 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/SecretList/SecretListFeature.swift`
around lines 152 - 156, Update the mutation failure handling in
SecretListFeature’s mutationResponse reducer to use the mapped
SecretUseCaseError and present it through the feature’s `@Presents` alert
destination instead of returning .none. Ensure failures from didTapDelete,
didTapRecover, and didTapDeleteForever produce an appropriate user-visible alert
while preserving the existing success refresh behavior, and add coverage in
SecretListFeatureTests.swift for this failure path.
doyeonk429
left a comment
There was a problem hiding this comment.
claude-code 리뷰 항목 중에서 현재 pr에서 개선하면 좋을 것 위주로 comment 남김!
| } | ||
|
|
||
| func showPanel(with content: PanelContent) { | ||
| guard panel == nil, let anchorView, let window = anchorView.window else { |
There was a problem hiding this comment.
- DVFloatingPanel 콘텐츠 갱신 안 됨
- DVFloatingPanel.swift:73 guard panel == nil 때문에 패널이 열려 있는 동안 SwiftUI 상태 변화가 반영 안 됨.
- 재현: AddToProject에서 프로젝트 메뉴 열어놓은 상태로 "New Project…"로 새 프로젝트 만들면, 목록에 즉시 안 나타남 (stale). 이 PR의 핵심 플로우가 깨짐.
- updateNSView에서 열려 있는 경우 hostingView.rootView = content 로 갱신 필요.
There was a problem hiding this comment.
- DVFloatingPanel 초기 isPresented=true 상태에서 안 뜸
- DVFloatingPanel.swift:74 anchorView.window == nil일 때 조용히 return. 첫 렌더에 attach 되기 전이면 그대로 못 뜸.
- Sheet에서 열자마자 flag가 true인 시나리오나 화면 재진입 시 잠재 버그.
| #Preview("Project - CheerLot에 속한 Secret만") { | ||
| SecretListView( | ||
| store: Store( | ||
| initialState: SecretListFeature.State( | ||
| collection: .project(id: [Project].preview[0].id), | ||
| projectName: [Project].preview[0].name | ||
| ) | ||
| ) { | ||
| SecretListFeature() | ||
| } withDependencies: { | ||
| $0.secretClient = .previewValue | ||
| } | ||
| ) | ||
| .frame(width: 300, height: 500) |
There was a problem hiding this comment.
- [Project].preview 미정의 → Xcode Preview 빌드 실패
- SecretListView.swift:328-329 — "Project - CheerLot에 속한 Secret만" 프리뷰 자체가 안 뜸. PR body에 "Xcode Canvas Preview 확인" 체크되어 있는데 이 하나는
컴파일 안 됨.
There was a problem hiding this comment.
- 에러 UI가 화면에 없음
- SecretListFeature가 .failed(error) 상태로 저장은 하는데, SecretListView의 secrets computed가 .loaded만 처리 → 실패 시 "빈 리스트"로 보임. 유저는 로딩
완료+빈 상태인지, 실패인지 구분 불가. - 최소한 .failed 케이스에 재시도 UI 필요.
doyeonk429
left a comment
There was a problem hiding this comment.
👍 리뷰 모두 수정된거 확인했고, 나머지는 후속 작업하면서 자연스럽게 수정될 것 같네용
✨ What's this PR?
📌 관련 이슈
🧶 주요 변경 내용
DVFloatingPanel(화살표 없는 드롭다운 컴포넌트) 추가🧪 테스트 / 검증 내역
💬 기타 공유 사항
Summary by CodeRabbit