Skip to content

Feature/#57 - Sidebar 프로젝트 CRUD 기능 연결 및 메뉴 카테고리에 따른 Content 화면 연결 - #62

Open
dlguszoo wants to merge 13 commits into
developfrom
feature/#57
Open

Feature/#57 - Sidebar 프로젝트 CRUD 기능 연결 및 메뉴 카테고리에 따른 Content 화면 연결#62
dlguszoo wants to merge 13 commits into
developfrom
feature/#57

Conversation

@dlguszoo

@dlguszoo dlguszoo commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

✨ What’s this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

  • Presentation 레이어 VO(ProjectItem), 에러(SidebarError), 전용 클라이언트(SidebarClient) 추가
  • SidebarFeature에 실제 데이터 fetch / 인라인 rename / 삭제 기능 연결
  • mutation(생성/수정/삭제) 후 항상 refetch — 수동 리스트 조작 없음
  • LoadingState<IdentifiedArrayOf<ProjectItem>, SidebarError> 기반 idle/loading/loaded/failed 4-state 모델링
  • MainFeature에서 Sidebar delegate 전부 처리 및 2-column/3-column 전환 연결
  • DVProjectRenameContainer — 인라인 rename 전용 컴포넌트 (자동 포커스, Enter/ESC 처리)
  • Composition Root 분리 (LiveStorage, SidebarClient+Live)
  • TestStore 기반 단위 테스트 추가

📸 스크린샷 (Optional)

스크린샷 2026-07-25 오후 4 36 05 스크린샷 2026-07-25 오후 4 47 11 스크린샷 2026-07-25 오후 4 36 47 스크린샷 2026-07-25 오후 4 37 34 스크린샷 2026-07-25 오후 4 38 02 ---

🧪 테스트 / 검증 내역

  • 앱 실행 후 사이드바에 프로젝트 목록이 로딩되는지 확인
  • 프로젝트 생성 → 목록에 알파벳 순 추가, 새 프로젝트로 selection 전환 확인
  • + 버튼 → SelectSecretType 2-column 진입 시 sidebar selection 해제 확인
  • SelectSecretType 진입 중 프로젝트 생성해도 새 프로젝트가 select되지 않는지 확인
  • 프로젝트 우클릭 Rename / Enter키 rename 진입 → 인라인 TextField 전환, ESC 취소 동작 확인
  • 중복 이름으로 create/rename 시 각각의 alert 표시 확인
  • rename 후 ContentView 상단 타이틀 즉시 갱신 확인
  • 프로젝트 삭제 → 확인 alert 후 목록 갱신 및 selection .all 초기화 확인
  • DVPresentationTests 전체 통과 확인

💬 기타 공유 사항

  • AddToProjectFeature의 project 목록도 우선은 sidebarClient로 전환했습니다. 후에 수정필요하면 해주세요!
  • DVProjectRenameContainer@FocusState를 컴포넌트 내부로 캡슐화해 외부에서 포커스 관리가 필요 없습니다.
  • LiveStorage.shared 싱글톤으로 모든 RepositoryImpl이 동일한 ModelContainer를 공유합니다.

🙇🏻‍♀️ 리뷰 가이드 (선택)

  • 후에 window 사이즈 상의 후 최종 결정이 필요합니다
  • 2-column/3-column 전환시 sidebar layout에 약간의 오류가 있는 것 같습니다. 시크릿 리스트 조회 실패 해결하면서 봐야할 것 같습니다. SecretClient 한번 봐주시면 감사하겠습니다.
  • Project CUD 메서드시, 항상 refresh (fetch)를 부르는데, loading 화면으로 스피너가 자주 뜹니다. 추후 수정 가능합니다.
  • alert 문구는 임시문구입니다. 추후 온보딩 + sidebar localization 및 접근성 이슈 파서 한번에 처리할 예정입니다.

Summary by CodeRabbit

  • 새로운 기능

    • 사이드바에서 프로젝트를 생성, 이름 변경, 삭제할 수 있습니다.
    • 프로젝트 이름을 인라인으로 편집하고 Enter 또는 포커스 해제로 저장할 수 있습니다.
    • 프로젝트 삭제 확인 및 오류 알림을 제공합니다.
    • 프로젝트별 시크릿 목록과 시크릿 유형 선택 화면을 지원합니다.
    • 설정 창을 별도 창으로 열 수 있습니다.
  • 개선 사항

    • 프로젝트 작업 후 목록과 시크릿 화면이 자동으로 갱신됩니다.
    • 프로젝트 생성 및 이름 중복 등 상황별 오류 메시지를 제공합니다.
    • 프로젝트 목록 로딩 중 진행 상태와 실패 상태를 표시합니다.

dlguszoo added 8 commits July 25, 2026 16:28
- 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 케이스 추가
@dlguszoo dlguszoo self-assigned this Jul 25, 2026
@dlguszoo dlguszoo added the ✨ Feature 새로운 기능 개발 label Jul 25, 2026
@dlguszoo dlguszoo linked an issue Jul 25, 2026 that may be closed by this pull request
4 tasks
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@dlguszoo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca7dc606-602e-4a2c-9cc2-69cbd8b7b659

📥 Commits

Reviewing files that changed from the base of the PR and between 73842e9 and c4931c7.

📒 Files selected for processing (3)
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift

Walkthrough

사이드바 프로젝트의 조회·생성·이름 변경·삭제를 SidebarClient와 TCA에 연결했습니다. MainFeature와 SwiftUI 화면은 프로젝트 선택, SecretList 갱신, 생성 시트, 인라인 이름 변경 및 설정 창을 지원하며 관련 테스트가 추가되었습니다.

Changes

사이드바 프로젝트 관리

Layer / File(s) Summary
프로젝트 모델과 라이브 의존성
Projects/DVPresentation/Sources/Features/Sidebar/Model/*, Projects/DVPresentation/Sources/Dependencies/SidebarClient.swift, Projects/Devault/Sources/Composition/...
ProjectItem, SidebarError, SidebarClient, 공유 저장소와 저장소 기반 live 구현을 추가했습니다.
사이드바 로딩·이름 변경·삭제
Projects/DVPresentation/Sources/Features/Sidebar/*, Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift, Projects/DVPresentation/Tests/Sidebar/*
프로젝트 목록 로딩, 선택, 인라인 이름 변경, 중복 이름 알림, 삭제 확인 및 갱신 흐름을 구현했습니다.
프로젝트 생성 및 추가 대상 전환
Projects/DVPresentation/Sources/Features/CreateProject/*, Projects/DVPresentation/Sources/Features/AddToProject/*, Projects/DVPresentation/Tests/CreateProject/*
기존 도메인 의존성을 SidebarClientProjectItem 기반으로 전환하고 생성 오류별 알림을 검증했습니다.
메인 화면 통합
Projects/DVPresentation/Sources/Features/Main/*, Projects/DVPresentation/Sources/Features/SelectSecretType/*, Projects/Devault/Sources/DevaultApp.swift, Projects/DVPresentation/Tests/Main/*
사이드바 이벤트를 SecretList, 타입 선택, 프로젝트 생성 시트와 연결하고 2·3열 레이아웃 및 설정 창을 추가했습니다.

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 상태 갱신
Loading

Possibly related issues

  • DevaultProject/Devault-macOS#44 — SidebarClient 의존성 인프라와 사이드바 프로젝트 기능 연결과 직접 관련됩니다.

Possibly related PRs

Suggested labels: ✅ Test

Suggested reviewers: yeseonglee

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 이슈 범위를 넘는 프로젝트 생성/이름 변경/삭제, LiveStorage, SidebarClient live 연동, 추가 테스트가 함께 포함됩니다. 링크된 이슈 범위만 남기고 CRUD 확장, 컴포지션 루트 변경, 추가 테스트는 별도 PR로 분리하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 Sidebar 기능 연결과 CRUD/Content 전환이라는 핵심 변경을 잘 반영해 관련성이 높습니다.
Linked Issues check ✅ Passed 프로젝트 목록 로딩, Sidebar 렌더링, MainFeature delegate, MainView 컬럼 전환이 모두 구현되어 이슈 요구를 충족합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#57

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

TextField에 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).

도메인 레이어 .notice collection 연동 전까지 임시 처리인 점 확인. 도메인 쪽 작업이 준비되면 알려주시면 연결 도와드릴게요.

🤖 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 | 🔵 Trivial

TODO 확인: 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

📥 Commits

Reviewing files that changed from the base of the PR and between c7eb099 and 5f627e0.

📒 Files selected for processing (21)
  • Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift
  • Projects/DVPresentation/Resources/Localizable.xcstrings
  • Projects/DVPresentation/Sources/Dependencies/SidebarClient.swift
  • Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectFeature.swift
  • Projects/DVPresentation/Sources/Features/AddToProject/AddToProjectView.swift
  • Projects/DVPresentation/Sources/Features/CreateProject/CreateProjectFeature.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Model/CreatableSecretType.swift
  • Projects/DVPresentation/Sources/Features/Main/MainFeature.swift
  • Projects/DVPresentation/Sources/Features/Main/MainView.swift
  • Projects/DVPresentation/Sources/Features/SelectSecretType/SelectSecretTypeFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/Error/SidebarError.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem+Mapping.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/Model/ProjectItem.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
  • Projects/DVPresentation/Tests/CreateProject/CreateProjectFeatureTests.swift
  • Projects/DVPresentation/Tests/Main/MainFeatureTests.swift
  • Projects/DVPresentation/Tests/Sidebar/SidebarFeatureTests.swift
  • Projects/Devault/Sources/Composition/Dependencies/SidebarClient+Live.swift
  • Projects/Devault/Sources/Composition/LiveStorage.swift
  • Projects/Devault/Sources/DevaultApp.swift

Comment on lines +112 to +119
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

@coderabbitai coderabbitai Bot Jul 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 AddToProject 담당 팀원의 TODO로 남겨둘 것.
-> 수동 append가 아닌 refetch 패턴으로

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글 이미지들은 다크 테마의 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.

@doyeonk429

Copy link
Copy Markdown
Contributor

@dlguszoo 실제로 구현하고자 하는 범위까지만 리뷰 사항 반영하면 될 것 같습니다.

🔴 P0 — 논리 오류/UX 버그 (수정 권장)

  1. Settings 버튼: didTapSettingsButton send + openWindow(id:) 이중 처리

SidebarView.swift bottomBar
circleIconButton(icon: "gearshape") {
store.send(.didTapSettingsButton) // delegate로 흘려도
openWindow(id: "settings") // View에서 이미 열어버림
}
MainFeature의 case .settingsButtonTapped: return .none이 dead code가 됨. 사이드 이펙트를 View에서 직접 실행하면 TCA의 흐름을 우회하는 것 + delegate handler 자체가 무의미해짐. 둘 중 하나로 통일 — reducer에서 openWindow env를 갖는 client로 넘기거나, View에서 직접 처리하고 delegate 케이스는 삭제.

  1. renameResponse(.failure) / deleteResponse(.failure) 일반 실패 시 사용자 무피드백

SidebarFeature.swift
case .renameResponse(.failure(.nameTaken)):
state.alert = AlertState { ... } // 여기만 처리
return .none

case .renameResponse(.failure): // 그 외 실패는 조용히 삭킴
return .none
renameFailed, deleteFailed는 사용자에게 아무 알림 없이 사라짐. 실제로 rename이 실패했는데 UI는 원래 이름으로 돌아가서 "왜 안 됐지?" 상태가 됨. generic alert 하나 추가 필요.

  1. didConfirmRename에서 trim 안 함

case .didConfirmRename:
guard let id = state.renamingProjectID else { return .none }
let name = state.renameText // ← 공백/빈 문자열 그대로 전달
CreateProjectFeature는 trimmingCharacters + blank guard를 하는데, rename만 없음. " " 이름으로 rename이 시도됨. CreateProject와 동일한 blank/trim 처리 필요.

  1. isCreatingSecret을 MainFeature가 sidebar state에 직접 write

MainFeature.swift
case .addButtonTapped:
state.selectSecretType = .init()
state.sidebar.isCreatingSecret = true // sidebar 캡슐 파괴
internal(set)로 방어했지만 같은 모듈이라 뚫림. Sidebar 내부 상태를 부모가 직접 조작 → Sidebar 단독 테스트로 이 상태 변화를 다룰 수 없고 sync가 흐트러지기 쉬움. Sidebar에 .setCreatingSecret(Bool) 액션을 만들거나, isCreatingSecret을 MainFeature로 승격 중 하나.

@doyeonk429

Copy link
Copy Markdown
Contributor

P1
6. .task가 최초 fetch + refetch 트리거 겸용

MainFeature에서 .send(.sidebar(.task)), .merge(.send(.delegate), .send(.task)) — refetch 목적으로 .task를 재활용. .cancellable로 중복은 막았지만 시맨틱이 흐림. 별도 .refetchProjects (또는 .reload) 액션이 의도가 명확.

  1. renameResponse(.success)의 .merge 순서 비결정성

return .merge(
.send(.delegate(.projectRenamed(updated))),
.send(.task) // .task는 projectsState = .loading으로 만듦
)
merge는 concurrent라 .task가 먼저 처리되면 loaded → loading 깜빡임. .concatenate로 delegate → task 순서를 명시하는 편이 예측 가능.

  1. LiveStorage.shared의 fatalError

Composition Root라 fail-fast가 맞긴 하지만, macOS 앱에서 첫 실행 시 디스크 권한/공간 이슈로 실패 가능. fatalError 대신 root View에 오류 상태를 렌더링하는 게 실사용 회복력 측면에서 안전. 최소한 로그(OSLog)라도 남겨야 사후 분석 가능.

  1. .notice TODO가 조용히 .all로 대체

makeSecretListState
case .filter(.notice):
// TODO: 도메인 레이어에 .notice collection 추가 후 연결
return .init(collection: .all) // notice 눌러도 전체가 뜸 — 사용자는 모름
사용자 관점에서 “notice 필터가 동작 안 함”. 일시적으로 disabled/hidden 처리 or empty state가 낫습니다.

@doyeonk429

Copy link
Copy Markdown
Contributor

🟢 P2 — nit / 사소한 개선 -> @dlguszoo 이부분은 정말 선택반영. 취향대로

  1. .onKeyPress(.return) scope

projectList (List) 자체에 걸려 있어서 List가 focus를 가진 경우에만 동작. 실사용에서는 프로젝트 클릭 → 그 상태로 Enter니까 대체로 OK지만, contextMenu → Rename 이후 Enter 흐름과 겹치지 않는지 실기기 확인 권장.

  1. DVProjectRenameContainer의 “tray” 아이콘 하드코딩

DVProjectContainer와 동일 아이콘 유지가 목적인데 각각 하드코딩이라 한쪽만 바뀔 위험. DVProjectContainer.icon 같은 상수 공유하거나, 아이콘을 파라미터로 받는 편이 안전.

  1. SidebarClient.swift 들여쓰기 2-space -> 이거는 추후 stencil 파일을 수정해야할 것 같음

같은 모듈 다른 파일들은 4-space가 다수 (DVProjectRenameContainer, 기존 파일들). 스타일 통일.

@doyeonk429 doyeonk429 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 잘한 점

  1. Presentation VO(ProjectItem) 도입 — DVDomain의 Project를 Presentation이 직접 노출하지 않고 id + name만 잘라내서 사용. 레이어링 개선 + Equatable/Sendable/Identifiable로 TCA 친화적.
  2. SidebarError 분리 — UseCaseError coupling을 끊고 UI 문구에 필요한 만큼만 (nameTaken 등) 매핑. Live client에서 duplicateName → nameTaken 변환이 깔끔.
  3. LoadingState 4-state 완전 커버 — idle/loading/loaded/failed 전부 View에서 분기 처리. 특히 failed 문구 렌더까지 잊지 않고 넣음.
  4. 테스트 커버리지가 넓고 견고 — SidebarFeature/MainFeature/CreateProjectFeature 3개 feature × success/nameTaken/failure/cancel/refetch 케이스. TestStore exhaustive 검증 좋음.
  5. .cancellable(id:cancelInFlight:) — refetch race 방어. 흔히 놓치는 부분.
  6. makeDeleteAlert helper 분리 — reducer 슬림.
  7. .run 캡처 리스트 명시([id, name]) — Swift concurrency data race 예방 의도가 명확.
  8. DVProjectRenameContainer의 @focusstate 캡슐화 — 외부에서 focus 관리 안 해도 됨. onAppear에서 자동 focus, onSubmit/onExitCommand 처리까지 keyboard-first UX 완결.
  9. mutation → refetch 원칙 일관 유지 — manual list 조작 없이 항상 refetch. 상태 sync 버그 원천 차단. (스피너 이슈는 별도 튜닝 문제)
  10. rename 즉시 title 갱신 — refetch 기다리지 않고 delegate payload로 secretList 타이틀 갱신 → 반응성 확보.
  11. Composition Root 분리 — LiveStorage.shared 싱글톤으로 ModelContainer 공유, SidebarClient+Live에서 UseCase 조립. Dependency Rule 잘 지킴.
  12. 삭제 selection 초기화 로직 — 삭제한 프로젝트가 현재 selection일 때만 .filter(.all)로 초기화. 조건 정확.
  13. View 접미어 컨벤션 준수 — SidebarView, MainView, AddToProjectView 그대로. DVProjectRenameContainer는 Container 계열이라 예외적으로 잘 부합.

dlguszoo added 3 commits July 28, 2026 03:37
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 사전 사후 분석 가능

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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 win

body 내부 레이아웃을 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5f627e0 and 73842e9.

📒 Files selected for processing (7)
  • Projects/DVDesign/Sources/Components/DVProjectContainer.swift
  • Projects/DVDesign/Sources/Components/DVProjectRenameContainer.swift
  • Projects/DVPresentation/Sources/Features/Main/MainFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarFeature.swift
  • Projects/DVPresentation/Sources/Features/Sidebar/SidebarView.swift
  • Projects/DVPresentation/Tests/Main/MainFeatureTests.swift
  • Projects/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

dlguszoo added 2 commits July 28, 2026 04:09
1. didConfirmRename trim/empty guard 제거 — 빈 이름은 repository 로직 검증으로 감지해 alert 표시
2. isListFocused 및 onChange 제거 — macOS SwiftUI에서 List 프로그래매틱 포커스 복원 불가
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Sidebar 기능 연결

2 participants