Skip to content

Feature/#41 - CreateSecretView Form 조립 (9 subtype · 반응형 · Project 다중선택 · Save 검증 UX) - #61

Merged
dlguszoo merged 22 commits into
developfrom
feature/#41
Jul 28, 2026
Merged

Feature/#41 - CreateSecretView Form 조립 (9 subtype · 반응형 · Project 다중선택 · Save 검증 UX)#61
dlguszoo merged 22 commits into
developfrom
feature/#41

Conversation

@doyeonk429

@doyeonk429 doyeonk429 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

✨ What’s this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

DVDesign 컴포넌트 확장

  • DVMultiSelectDropdown — Figma 9 variants(chips trigger · sectioned/flat body · search · create footer · empty states) 커버
  • DVFlowLayoutDVChipFlow를 공용으로 승격, DVMultiSelectDropdown이 재사용
  • DVButton.secondaryProminent — Save/Create 확정 액션 스타일 (vaultGreen 강조). width→minWidth로 로컬라이제이션 대응

CreateSecret 폼 조립 (9 subtype SectionView)

  • APIKeysTokenSectionView (apiKey/accessToken/webhookSecret 3 subtype 공유)
  • OAuthClientSectionView, ServiceAccountSectionView
  • DatabaseSectionView
  • SSHKeySectionView, SSLTLSCertSectionView
  • EnvSetSectionView
  • LicenseKeySectionView, CustomSectionView
  • 각 body는 FormSectionScaffold(Name/Memo bookend + 16pt spacing + maxWidth infinity 강제) 위에서 type-specific rows만 구성

핵심 필드 컴포넌트 · Reducer 라우팅

  • ProjectFieldViewDVMultiSelectDropdown 소비, [Project.ID]Set<Project.ID> 어댑터
  • CreateProject 시트 라우팅 — didTapCreateProject.createProject PresentationAction, delegate 수신 시 availableProjects 갱신
  • ExpireDateFieldView 재설계 — No expired 트리거(gray300 컨테이너) + 인라인 .stepperField DatePicker + trash 버튼

반응형 레이아웃 wiring

  • CreateSecretView.bodyGeometryReader + .environment(\.formLayoutMode, ...) 주입 — dual↔single 자동 전환
  • AdaptiveFieldRow가 left/right 슬롯 optional 지원 (Service Account의 Environment 미노출 케이스)
  • CreateSecretView 최소 폭 520pt — 헤더 3-radio 자연 폭 floor

유틸리티 · 리팩터

  • Binding+CasePathable.typed(_:default:) — CasePathable enum case 바인딩 헬퍼 (전 SectionView가 소비)
  • FormSlotSizeLabeledTextFieldView.SizeMode leak 제거
  • CreateSecretSectionHintProviding — hintFor 중복 프로토콜 default로 통합
  • APIKeyTokenFields.scope.authorityScope (TCA Scope와 dynamic member lookup 충돌 회피)
  • UI 문자열 .module(_:) 일괄 로컬라이즈 (신규 String.module 헬퍼)

검증 UX

  • 필수 필드 다중 누락 시 warning 동시 노출 (FormError.missingRequired([FieldID]))
  • Save 시 payload/draft 콘솔 dump — #if DEBUG 가드

🧪 테스트 / 검증 내역

  • DVPresentation 테스트 40개 전체 통과
  • DVDesign · Devault 앱 빌드 통과 (경고 0)
  • Xcode 프리뷰: 9 subtype × Wide/Narrow 시각 확인
  • 실 앱 실행: 사이드바 + → CreateSecret 진입 (Composition Root wiring은 후속 PR-확인 후 관련 코드 삭제 처리)

💬 기타 공유 사항

  • 외부 client 연결 스코프 밖: 저장/SelectSecretType 진입 flow 등 실제 wiring은 다음 브랜치.
  • 후속 이슈로 이월:
    • Composition Root — Live dependency 조립
    • SelectSecretType → CreateSecret 정식 flow
    • saveResponse 실패 사용자 노출 (alert / inline)
    • detectedServices 감지 엔진 연결
    • DVMultiSelectDropdown chip trigger "+N" overflow indicator

🙇🏻‍♀️ 리뷰 가이드

  • CreateSecretView.body — GeometryReader 기반 반응형 wiring + 최소 너비 제약
  • DVButton.swift.secondaryProminent 신규 + width/height → minWidth/minHeight 리팩터 (기존 소비처 회귀 확인 필요)

Summary by CodeRabbit

  • 새 기능

    • 시크릿 유형별 생성 폼(API 키, OAuth, 데이터베이스, SSH/SSL 인증서, 환경셋 등)을 추가했습니다.
    • 프로젝트 다중 선택(검색/그룹/칩), 선택값이 있는 경우와 없는 경우 UI를 제공하며 새 항목 추가도 지원합니다.
    • 강조형 보조 버튼 스타일(콘텐츠에 맞게 최소 크기 이상 확장)을 추가했습니다.
  • 개선

    • 필수 항목이 여러 개 누락되면 각 필드에 경고를 표시합니다.
    • 만료일은 설정/해제 상태에 따라 Date 선택 또는 “No expiration” 액션으로 전환됩니다.
    • 칩/레이아웃이 화면 폭에 맞춰 안정적으로 줄바꿈·적응합니다.
    • 하단 액션 문구가 “Cancel + Create”로 변경되었습니다.

- DVChipsField 내부 private struct였던 flow layout을 별도 파일로 이관
- 이름을 chip 비종속으로 변경 (DVChipFlow → DVFlowLayout)
- 접근제어를 internal로 열어 다른 컴포넌트도 재사용 가능
- 로직·동작 변화 없음
- Generic Item 기반 다중 선택 드롭다운 (Identifiable & Hashable)
- 트리거는 값 없을 때 placeholder + chevron, 있을 때 chip flow (read-only)
- searchText binding으로 헤더 검색 opt-in, onCreate 콜백으로 푸터 opt-in
- 팝오버 open 시점 selection 스냅샷 기반 섹션 배치 (세션 중 재정렬 방지)
- 검색어 하이라이트 (AttributedString semibold + vaultGreenDark)
- 9종 Figma variants 커버하는 프리뷰
- ProjectFieldView: DVMultiSelectDropdown 소비, [Project.ID]↔Set 어댑터, Add-new 콜백 노출
- CreateSecretFeature: @presents createProject + didTapCreateProject Action + ifLet
- .projectCreated 델리게이트 수신 시 availableProjects에 append
- CreateSecretView: CreateProjectView sheet 라우팅
- DVMultiSelectDropdown Item constraint 완화 (Identifiable & Hashable → Identifiable)
- DVMultiSelectDropdown: placeholder 텍스트와 chevron을 gray600으로 통일
- ExpireDateFieldView: 체크박스 dummy 제거, Mac Calendar 앱 패턴으로 재설계
  - nil 상태: gray300 textContainer 안에 No expired 트리거
  - set 상태: 인라인 stepperField DatePicker
  - 옆에 trash 버튼 (nil일 땐 비활성 회색)
- $binding.scope가 TCA Scope 심볼과 dynamic member lookup 충돌해 컴파일 실패
- authorityScope로 개명해 회피, 도메인 매핑은 APIKeyMetadata.scope 그대로
- APIKeysTokenSectionView: apiKeyToken 타입의 6-row 폼 섹션 조립 (Name/Value/Project|Services/Expire|Env/Auth/Memo)
  - Common + type-specific 필드 바인딩, validation/detected hint 소비
  - Preview 4종 (Empty·Dual / Filled+hint·Dual / Filled·Single / Validation errors)
- Binding+CasePathable.typed(_:default:): CasePathable enum에서 특정 case의 non-optional Binding<T> 추출 헬퍼
  - Value: CasePathable 제네릭이라 다른 도메인 enum에도 재사용 가능
- scrollContent를 secretType별 SectionView로 분기 (apiKeyToken → APIKeysTokenSectionView)
- 나머지 SecretType은 placeholder + TODO(#41-followup) 유지
- 프리뷰 5종 재정비 (Wide·Empty / Wide·Filled+hint / Narrow·Filled·scroll / Medium·Empty / Database·placeholder)
- 프리셋 데이터 주입 헬퍼(previewState)로 조합된 화면 시각 확인 가능
- CreateSecretHeaderView: maxWidth infinity leading 정렬로 컨테이너 크기 반영
- OAuthClientSectionView: 8-row 폼 (Name/Client ID/Client Secret/Project|Services/Expire|Env/Redirect URL/Scope/Memo)
- ServiceAccountSectionView: 6-row 폼 (Name/Certification JSON/Project|Services/Expire|_/Authority/Memo)
  - Figma상 Environment 미노출 — 오른쪽 슬롯 Color.clear로 유지해 wide/narrow 전환 자연스럽게
- CreateSecretView: .oauth 라우팅 + selectedSubType별 subview 분기
- 프리뷰: 각 SectionView 4종 + CreateSecretView에 oauth Wide/Narrow · Client/ServiceAccount 4종 추가
- DatabaseSectionView: 6-row 폼 (Name/Link String/Project|Services/Expire|Env/SSL Required/Memo)
- 기존 SSLRequiredFieldView + Project/ExpireDate 공통 컴포넌트 재사용
- CreateSecretView: .database 라우팅 추가, 기존 placeholder 제거
- 프리뷰: SectionView 4종 + CreateSecretView Database Wide/Narrow 2종
- SSHKeySectionView: 7-row 폼 (Name/Private Key/Project|Env/Public Key/PassPhrase/Host|Username/Memo)
- SSLTLSCertSectionView: 7-row 폼 (Name/Certificate/Private Key/Project|Env/Cert Chain/Renew Cmd/Memo)
- 두 케이스 모두 Services / Expire Date 미노출 — common 바인딩에서도 제외 (Figma 기준)
- 새 페어링 [Project | Environment] — 지금까지의 [Project | Services] 패턴과 다름
- CreateSecretView: .sshAndCredentials 라우팅 + selectedSubType별 subview 분기
- EnvSetSectionView: 4-row 폼 (Name/envSet List/Project|Env/Memo)
- Services / Expire Date 미노출 — common 바인딩에서도 제외 (Figma 기준)
- CreateSecretView: .environmentVariableSet 라우팅 + EnvSet Wide/Narrow 프리뷰 2종
- LicenseKeySectionView: 8-row 폼 (Name/License Key/Project|Services/Expire|Type/Support Email/Website/Order Number/Memo)
  - Environment 미노출 — LicenseTier "Type"이 오른쪽 페어 슬롯 차지
- CustomSectionView: 5-row 폼 (Name/Value/Project|Services/Expire|Env/Memo) — 최소 구성
- CreateSecretView: .etc 라우팅 + selectedSubType별 subview 분기
- default 케이스 제거 — 모든 SecretType (apiKey/oauth/database/ssh/env/etc) 커버 완료
- 프리뷰 4종 추가 (License Key W/N, Custom W/N)
- GeometryReader로 실 컨테이너 폭 측정 → FormLayoutMode.mode(for:) → .environment 주입
- 하위 SectionView / AdaptiveFieldRow / Field 뷰들이 자동으로 dual↔single 전환
- .frame(minWidth: 520)로 헤더 3-radio 자연 폭 이하 축소 차단 (필드 clipping 방지)
- 프리뷰의 explicit .environment(\.formLayoutMode, ...) 20개 제거 (폭 기반 자동 선택)
- FormLayoutMode: wiring 완료로 TODO 코멘트 제거
- 신규 String.module(_:) 헬퍼 — LocalizedStringResource.module 결과를 String으로 반환
- SectionView 9개 + Field 뷰 8개 + FooterActions 하드코딩 label/placeholder/hint 이관
- Auto-detected hint 프리픽스, "No expired", "SSL Required", Cancel/Save 등 포함
- CreateSecretFeature validation "Required" 이관
- Preview seed 데이터는 유지 (필요시 개별 rollback 가능)
- FormError.missingRequired 단일 FieldID → [FieldID] 목록으로 변경
- SecretMetaFields: missingRequiredFieldID → missingRequiredFieldIDs (모든 누락 수집)
- toCreateSecretPayload에서 name + type-specific missing을 한 번에 반환
- 리듀서: fieldIDs iterate로 validationErrors 다중 세팅, missing 케이스에 Log.warn
- handleSave 성공 경로에 payload/draft/projectIds Log.info 콘솔 출력 (client 연결 전 임시 검증용)
- 테스트: 초기 상태(name+value 둘 다 빈값) → 다중 warning 반영, OAuth Client 3개 동시 missing 케이스 신규
- 신규 CreateSecretSectionHintProviding 프로토콜 + extension default 구현
- 9개 SectionView가 conformance만 추가하면 로컬 hintFor 12줄 블록 삭제
- callsite 무변경 (hintFor(.value) 그대로)
- LabeledTextFieldView.SizeMode를 FormLayoutMode 파일의 FormSlotSize enum으로 이관
- ProjectFieldView가 더 이상 LabeledTextFieldView 내부 타입을 참조하지 않음
- 새 폼 필드가 추가되어도 FormSlotSize만 알면 됨 (텍스트필드 개념 non-leaky)
- DVButton.Style에 .secondaryProminent 신규 (secondary 지오메트리 + vaultGreen 강조 배경)
- width/height 하드코딩 → minWidth/minHeight로 변경 (text 길이에 자연 확장, floor로만 유지)
- FooterActionsView: SaveButtonView + SaveButtonStyle 로컬 struct 삭제
- Cancel 옆에 DVButton(.secondaryProminent) 사용, 라벨 "Save" → "Create" (Figma 준수)
- CreateSecretFeature.handleSave 평문 payload/draft 로그를 #if DEBUG로 감쌈
- AdaptiveFieldRow: left/right 슬롯 모두 optional화, 1-arg convenience init 2종
  - ServiceAccountSectionView의 Color.clear 제거 → 1-arg 형태로 대체
- SecretMetaFields.isValid 함수 + metaIsValid_* 테스트 8개 삭제 — 프로덕션 dead code
- saveResponse(.failure) → isSaving 해제 테스트 신규
- ExpireDateFieldView dateBinding fallback → Date() 단순화, defaultInitialDate 중복 호출 제거
- FormSectionScaffold 신규: NameFieldView + content + MemoFieldView + 16pt spacing + maxWidth infinity 컨테이너를 타입 시스템으로 강제
- 9개 SectionView가 스캐폴드 소비 — 각 body에서 outer VStack/frame/Name/Memo 반복 제거 (~4 lines × 9 = ~36 LOC 절감)
- AdaptiveFieldRow spacing 20 → 16pt 통일 (Figma 실측)
@doyeonk429 doyeonk429 self-assigned this Jul 25, 2026
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b2c7b48-889b-41fc-a134-94db73ebc2d0

📥 Commits

Reviewing files that changed from the base of the PR and between d7d9b86 and a0a6a12.

📒 Files selected for processing (8)
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/OAuthClientSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields+Mapping.swift
  • Projects/DVPresentation/Sources/PreviewSupport/PreviewWidth.swift
  • Projects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swift
🚧 Files skipped from review as they are similar to previous changes (6)
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/OAuthClientSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift
  • Projects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift

Walkthrough

CreateSecret 화면에 secret 유형별 입력 섹션, 반응형 폼 레이아웃, 프로젝트 다중 선택 및 생성 시트가 추가되었습니다. 필수 필드 검증은 여러 누락 필드를 동시에 표시하도록 변경되었고, 공통 버튼·칩 레이아웃 컴포넌트가 확장되었습니다.

Changes

디자인 컴포넌트와 선택 UI

Layer / File(s) Summary
버튼 및 칩 레이아웃 확장
Projects/DVDesign/Sources/Components/DVButton.swift, Projects/DVDesign/Sources/Components/DVFlowLayout.swift, Projects/DVDesign/Sources/Components/DVChipsField.swift
secondaryProminent 스타일과 최소 크기 기반 버튼 레이아웃이 추가되었고, 칩 배치는 캐시형 DVFlowLayout을 사용하도록 변경되었습니다.
다중 선택 드롭다운
Projects/DVDesign/Sources/Components/DVMultiSelectDropdown.swift
검색, 선택 항목 그룹화, 텍스트 하이라이트, 빈 상태, 새 항목 생성 푸터와 선택 토글을 지원하는 공개 컴포넌트가 추가되었습니다.

폼 공통 기반과 필드 동작

Layer / File(s) Summary
반응형 폼 행과 필드 크기
.../CreateSecret/Components/AdaptiveFieldRow.swift, .../Components/FormLayoutMode.swift, .../Fields/LabeledTextFieldView.swift
빈 슬롯을 지원하는 행 레이아웃과 FormSlotSize 기반 필드 크기가 도입되었습니다.
공통 입력 필드와 프로젝트 선택
.../Components/Fields/*
만료일 입력이 nil 상태 기반으로 변경되고, 필드 문자열이 모듈 로컬라이제이션을 사용하며, 프로젝트 배열 바인딩과 드롭다운 Set 바인딩이 연결되었습니다.
Create 액션 및 지원 헬퍼
.../Components/FooterActionsView.swift, Sources/Localization/String+Module.swift, Sources/Support/Binding+CasePathable.swift
하단 액션이 secondaryProminent Create 버튼으로 교체되었고, 모듈 문자열 및 enum case 바인딩 헬퍼가 추가되었습니다.

Secret 유형별 섹션 뷰

Layer / File(s) Summary
공통 섹션 구조와 힌트
.../Sections/FormSectionScaffold.swift, .../Sections/CreateSecretSectionHintProviding.swift
이름·본문·메모를 조합하는 공통 스캐폴드와 검증 경고 우선 힌트 규칙이 추가되었습니다.
유형별 입력 섹션
.../Sections/*SectionView.swift
API 키, 커스텀, 데이터베이스, EnvSet, 라이선스 키, OAuth, SSH, SSL/TLS 인증서, 서비스 계정용 섹션과 프리뷰가 추가되었습니다.

CreateSecret 연결과 다중 검증

Layer / File(s) Summary
화면 분기 및 프로젝트 생성
.../CreateSecretView.swift, .../CreateSecretFeature.swift
secret 유형·서브타입에 따라 전용 섹션을 렌더링하고, 프로젝트 생성 시트를 표시한 뒤 생성된 프로젝트를 목록에 추가하도록 연결했습니다.
검증 모델과 테스트
.../Model/FormError.swift, .../Model/SecretContentFields.swift, .../Model/SecretMetaFields+Mapping.swift, .../Tests/Features/CreateSecret/CreateSecretFeatureTests.swift
필수 필드 누락 결과가 배열로 변경되었고, 공백 필드·다중 경고·저장 실패·프로젝트 생성 흐름 테스트가 추가 또는 갱신되었습니다.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CreateSecretView
  participant CreateSecretFeature
  participant ProjectFieldView
  participant CreateProjectView
  CreateSecretView->>CreateSecretFeature: 유형별 섹션과 바인딩 렌더링
  ProjectFieldView->>CreateSecretFeature: 프로젝트 생성 액션 전달
  CreateSecretFeature->>CreateProjectView: 생성 시트 표시
  CreateProjectView-->>CreateSecretFeature: 생성된 프로젝트 delegate 전달
  CreateSecretFeature->>CreateSecretView: availableProjects 갱신
Loading

Possibly related PRs

Suggested labels: 🎨 Design, ✅ Test

Suggested reviewers: yeseonglee, dlguszoo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 CreateSecretView 폼 조립과 반응형·다중선택·검증 UX 변경을 잘 요약합니다.
Linked Issues check ✅ Passed 9개 subtype별 시크릿 생성 뷰와 공통 서브뷰, DEBUG 로그 확인 흐름이 구현되어 이슈 목표를 충족합니다.
Out of Scope Changes check ✅ Passed 추가된 레이아웃·로컬라이제이션·헬퍼 변경은 모두 CreateSecret 생성 UX를 지원하는 범위 안입니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#41

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: 6

🧹 Nitpick comments (10)
Projects/DVDesign/Sources/Components/DVFlowLayout.swift (1)

16-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

hSpacing/vSpacingprivate로 좁히는 게 좋겠습니다.

외부에서 읽을 필요가 없는 내부 상태인데 internal로 열려 있네요. 캡슐화 관점에서 private로 제한하는 게 안전합니다.

♻️ 제안
-    let hSpacing: CGFloat
-    let vSpacing: CGFloat
+    private let hSpacing: CGFloat
+    private let vSpacing: CGFloat
🤖 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/DVFlowLayout.swift` around lines 16 -
17, Update the hSpacing and vSpacing properties in DVFlowLayout to use private
access control instead of the default internal visibility, preserving their
existing types and behavior.

Source: Path instructions

Projects/DVDesign/Sources/Components/DVMultiSelectDropdown.swift (3)

363-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

toggle(_:)FlatListViewSectionedListView에 그대로 중복돼 있습니다.

동일한 selection insert/remove 로직이 두 곳에 복사돼 있어 나중에 한쪽만 고치는 실수가 생기기 쉽습니다. 공용 헬퍼로 빼시죠.

♻️ 제안
private func toggleSelection<ID: Hashable>(_ id: ID, in selection: inout Set<ID>) {
    if selection.contains(id) {
        selection.remove(id)
    } else {
        selection.insert(id)
    }
}
-    private func toggle(_ id: Item.ID) {
-        if selection.contains(id) {
-            selection.remove(id)
-        } else {
-            selection.insert(id)
-        }
-    }
+    private func toggle(_ id: Item.ID) {
+        toggleSelection(id, in: &selection)
+    }

(양쪽 toggle 모두 동일하게 적용)

Also applies to: 420-426

🤖 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/DVMultiSelectDropdown.swift` around
lines 363 - 369, Extract the duplicated selection toggle logic from
FlatListView.toggle(_:) and SectionedListView.toggle(_:) into one shared generic
helper accepting an ID and inout Set<ID>. Update both toggle methods to delegate
to that helper while preserving the existing insert/remove behavior.

251-314: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

PopoverContentView(64줄)/SectionedListView(56줄)는 50줄을 넘고, RowView는 2곳 이상에서 재사용됩니다.

경로 가이드라인상 이 조건들은 별도 View 파일 분리 대상입니다. 지금 당장 급한 건 아니지만, 파일이 계속 커지면 가독성이 떨어질 수 있어요.

As per path instructions, "동일한 뷰가 2곳 이상 사용되거나, 독립적인 State/로직을 갖거나, 50줄 이상이면 별도 View 파일 분리를 제안하세요."

Also applies to: 372-427, 431-468

🤖 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/DVMultiSelectDropdown.swift` around
lines 251 - 314, 분리 대상인 PopoverContentView, SectionedListView, 그리고 여러 위치에서 재사용되는
RowView를 각각 별도 View 파일로 이동하세요. 각 뷰의 현재 입력값, 바인딩, 상태 및 렌더링 동작은 유지하고, 호출부에서는 새 파일의
타입을 참조하도록 정리하세요.

Source: Path instructions


10-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

fileprivateprivate로 교체하세요.

top-level 선언이라 privatefileprivate가 동일하게 동작합니다. SwiftLint도 동일하게 지적하고 있습니다.

♻️ 제안
-fileprivate enum DropdownMetrics {
+private enum DropdownMetrics {
🤖 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/DVMultiSelectDropdown.swift` at line 10,
Change the top-level DropdownMetrics declaration from fileprivate to private,
preserving its current behavior and scope.

Source: Linters/SAST tools

Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift (1)

18-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

본문 레이아웃을 private 서브뷰로 분리하세요.

body의 조건부 HStackprivate var로 추출하면 화면 조립과 상태 전환 로직이 분리됩니다.

개선 예시
 var body: some View {
     DVLabeledField(.module("Expire Date"), size: size) {
+        fieldContent
+    }
+}
+
+private var fieldContent: some View {
     HStack(spacing: 6) {
         Group {
             if isSet {
                 stepperPicker
             } else {
                 activateButton
             }
         }
         .frame(maxWidth: .infinity, alignment: .leading)
 
         trashButton
     }
     .frame(width: size.width)
-    }
 }

As per path instructions, “var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extensionprivate var/func로 분리를 제안하세요.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift`
around lines 18 - 33, Extract the conditional HStack layout from body into a
private computed subview in an extension, such as a private var that contains
the isSet switch between stepperPicker and activateButton plus trashButton. Keep
the existing sizing, spacing, alignment, and DVLabeledField composition
unchanged, and have body use the extracted subview.

Source: Path instructions

Projects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swift (1)

104-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

테스트 함수명이 검증 내용과 어긋납니다.

이제 name과 value 둘 다 비어 있는 케이스를 검증하므로 didTapSave_missingNamedidTapSave_missingNameAndValue 정도가 정확합니다.

🤖 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/Features/CreateSecret/CreateSecretFeatureTests.swift`
around lines 104 - 105, Rename the test method didTapSave_missingName to
didTapSave_missingNameAndValue so its name accurately reflects that it verifies
both name and value are empty, without changing the test behavior.
Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/APIKeysTokenSectionView.swift (1)

150-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

프리뷰 @State 프로퍼티에 private를 붙여주세요.

SwiftLint private_swiftui_state 경고 8건이 그대로 남습니다. 프리뷰 전용 상태이므로 외부 노출이 필요 없습니다. expireDate= nil도 불필요합니다.

♻️ 제안 수정
-    `@State` var name: String
-    `@State` var value: String
-    `@State` var authorityScope: String = ""
-    `@State` var projectIds: [Project.ID]
-    `@State` var service: String
-    `@State` var expireDate: Date? = nil
-    `@State` var environment: SecretEnvironment = .staging
-    `@State` var memo: String = ""
+    `@State` private var name: String
+    `@State` private var value: String
+    `@State` private var authorityScope: String = ""
+    `@State` private var projectIds: [Project.ID]
+    `@State` private var service: String
+    `@State` private var expireDate: Date?
+    `@State` private var environment: SecretEnvironment = .staging
+    `@State` private var memo: String = ""

접근 제어가 가능한 가장 엄격한 수준인지 확인하라는 path instructions 기준입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/APIKeysTokenSectionView.swift`
around lines 150 - 157, Update the preview state properties in the relevant view
by marking each `@State` property private, including name, value, authorityScope,
projectIds, service, expireDate, environment, and memo. Remove the redundant =
nil initializer from expireDate while preserving the existing defaults for the
other properties.

Sources: Path instructions, Linters/SAST tools

Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift (2)

527-544: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

프리뷰의 PEM 헤더 문자열이 시크릿 스캐너에 high로 잡힙니다.

실제 키가 아닌 플레이스홀더지만 Betterleaks가 private-key로 오탐합니다. "<preview-private-key>" 같은 중립 문자열로 바꾸거나 스캐너 예외를 등록해 CI 소음을 줄이는 편이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift`
around lines 527 - 544, Update the preview SSH and SSL/TLS certificate fixture
values in CreateSecretView so PEM header strings are replaced with neutral
placeholder text such as "<preview-private-key>" and "<preview-certificate>".
Preserve the fixture structure and non-sensitive example fields while removing
scanner-triggering private-key and certificate markers.

Source: Linters/SAST tools


122-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

subtype 불일치 시 EmptyView로 무음 처리되는 경로에 최소한의 흔적을 남기세요.

세 분기 모두 default: EmptyView()라서 예상 밖 subtype이 들어오면 사용자는 헤더/푸터만 있는 빈 폼을 보고 원인을 알 수 없습니다. Log.warn 한 줄 또는 placeholder 텍스트를 두면 디버깅이 훨씬 수월합니다. (init에서 availableSubTypes.first가 보장되므로 현재는 도달 불가에 가깝지만, subtype 추가 시 회귀 지점입니다.)

Also applies to: 163-195, 199-236

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift`
around lines 122 - 159, Add a minimal diagnostic for unexpected values in the
default branches of etcSection and the corresponding subtype switch sections,
replacing silent EmptyView handling with a Log.warn call or visible placeholder
text while preserving the existing behavior. Ensure all three default paths
leave a trace when an unsupported selectedSubType is encountered.
Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields+Mapping.swift (1)

10-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

isValid 주석을 현재 구현과 맞춰 갱신하세요.

isValidmissingRequiredFieldIDs에 의존하지 않는 사라진 검증 진입점입니다. 주석을 이 프로퍼티와 toCreateSecretPayload가 공유하는 “단일 진실” required 규칙 정의부라고만 수정하면 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields`+Mapping.swift
around lines 10 - 13, Update the documentation above missingRequiredFieldIDs to
state only that this property is the single source of truth for the
required-field rules shared by the property and toCreateSecretPayload; remove
the claim that isValid shares or depends on it.
🤖 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/CreateSecret/Components/AdaptiveFieldRow.swift`:
- Around line 45-90: The dual layout gap and threshold calculation use
inconsistent values. Keep the 16-point spacing in AdaptiveFieldRow’s dual
HStack, and update FormLayoutMode.dualThreshold’s comment and constant
calculation to use the actual 16-point column gap; apply the corresponding
change at AdaptiveFieldRow.swift lines 45-90 and FormLayoutMode.swift lines
6-12.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift`:
- Around line 44-60: Update the activateButton label in ExpireDateFieldView to
use a clear, grammatically correct localized string such as “Set expiration
date,” reflecting that tapping it assigns the default expiration date via
Self.defaultInitialDate().

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/OAuthClientSectionView.swift`:
- Around line 43-59: Update the Client ID LabeledTextFieldView in
OAuthClientSectionView to use hintFor(.clientId) for trailingHint, matching the
existing Client Secret field and other SectionView implementations so
detected-service fallback and validation messaging are handled consistently.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift`:
- Around line 243-248: Update the .failure(.missingRequired(let fieldIDs))
branch in CreateSecretFeature to clear state.validationErrors before assigning
errors for the currently missing fieldIDs, matching the reset behavior used by
the success path while preserving the existing Required assignments.
- Around line 215-228: Update the DEBUG-only Log.info block in
CreateSecretFeature to stop interpolating the full draft and payload, which may
contain plaintext secrets. Log only non-sensitive debugging metadata such as
secret type, subtype, field count, and value lengths, while preserving the
existing request-preparation context and project IDs.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields`+Mapping.swift:
- Around line 13-40: Update missingRequiredFieldIDs to treat whitespace-only
strings as missing by reusing a shared String blank-check helper such as
isBlank. Apply the helper consistently to every required field across all
content cases, including names and secret values, while preserving the existing
FieldID results.

---

Nitpick comments:
In `@Projects/DVDesign/Sources/Components/DVFlowLayout.swift`:
- Around line 16-17: Update the hSpacing and vSpacing properties in DVFlowLayout
to use private access control instead of the default internal visibility,
preserving their existing types and behavior.

In `@Projects/DVDesign/Sources/Components/DVMultiSelectDropdown.swift`:
- Around line 363-369: Extract the duplicated selection toggle logic from
FlatListView.toggle(_:) and SectionedListView.toggle(_:) into one shared generic
helper accepting an ID and inout Set<ID>. Update both toggle methods to delegate
to that helper while preserving the existing insert/remove behavior.
- Around line 251-314: 분리 대상인 PopoverContentView, SectionedListView, 그리고 여러 위치에서
재사용되는 RowView를 각각 별도 View 파일로 이동하세요. 각 뷰의 현재 입력값, 바인딩, 상태 및 렌더링 동작은 유지하고, 호출부에서는
새 파일의 타입을 참조하도록 정리하세요.
- Line 10: Change the top-level DropdownMetrics declaration from fileprivate to
private, preserving its current behavior and scope.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift`:
- Around line 18-33: Extract the conditional HStack layout from body into a
private computed subview in an extension, such as a private var that contains
the isSet switch between stepperPicker and activateButton plus trashButton. Keep
the existing sizing, spacing, alignment, and DVLabeledField composition
unchanged, and have body use the extracted subview.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/APIKeysTokenSectionView.swift`:
- Around line 150-157: Update the preview state properties in the relevant view
by marking each `@State` property private, including name, value, authorityScope,
projectIds, service, expireDate, environment, and memo. Remove the redundant =
nil initializer from expireDate while preserving the existing defaults for the
other properties.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift`:
- Around line 527-544: Update the preview SSH and SSL/TLS certificate fixture
values in CreateSecretView so PEM header strings are replaced with neutral
placeholder text such as "<preview-private-key>" and "<preview-certificate>".
Preserve the fixture structure and non-sensitive example fields while removing
scanner-triggering private-key and certificate markers.
- Around line 122-159: Add a minimal diagnostic for unexpected values in the
default branches of etcSection and the corresponding subtype switch sections,
replacing silent EmptyView handling with a Log.warn call or visible placeholder
text while preserving the existing behavior. Ensure all three default paths
leave a trace when an unsupported selectedSubType is encountered.

In
`@Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields`+Mapping.swift:
- Around line 10-13: Update the documentation above missingRequiredFieldIDs to
state only that this property is the single source of truth for the
required-field rules shared by the property and toCreateSecretPayload; remove
the claim that isValid shares or depends on it.

In
`@Projects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swift`:
- Around line 104-105: Rename the test method didTapSave_missingName to
didTapSave_missingNameAndValue so its name accurately reflects that it verifies
both name and value are empty, without changing the test behavior.
🪄 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: e4fb9e3e-2975-4ccc-a6ed-86cd983e96a5

📥 Commits

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

📒 Files selected for processing (37)
  • Projects/DVDesign/Sources/Components/DVButton.swift
  • Projects/DVDesign/Sources/Components/DVChipsField.swift
  • Projects/DVDesign/Sources/Components/DVFlowLayout.swift
  • Projects/DVDesign/Sources/Components/DVMultiSelectDropdown.swift
  • Projects/DVPresentation/Resources/Localizable.xcstrings
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/AdaptiveFieldRow.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/CreateSecretHeaderView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/EnvironmentFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/LabeledTextFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/LicenseTierFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/MemoFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/NameFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/SSLRequiredFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ServicesFieldView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/FooterActionsView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/APIKeysTokenSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/CreateSecretSectionHintProviding.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/CustomSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/DatabaseSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/EnvSetSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/FormSectionScaffold.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/LicenseKeySectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/OAuthClientSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/SSHKeySectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/SSLTLSCertSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/ServiceAccountSectionView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretFeature.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Model/FormError.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretContentFields.swift
  • Projects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields+Mapping.swift
  • Projects/DVPresentation/Sources/Localization/String+Module.swift
  • Projects/DVPresentation/Sources/Support/Binding+CasePathable.swift
  • Projects/DVPresentation/Tests/Features/CreateSecret/CreateSecretFeatureTests.swift
💤 Files with no reviewable changes (1)
  • Projects/DVPresentation/Resources/Localizable.xcstrings

@yeseonglee

Copy link
Copy Markdown
Contributor
  1. 🔴 (신규) 같은 폼 안에서 탭만 바꿔도 입력값이 통째로 날아감
    API Keys/Token 타입은 상단에 API Key / Access Token / Webhook Secret 3개 탭이 있는데, 이 3개는 완전히 같은 입력 폼(Value, Authority/Scope)을 공유합니다. 그런데 탭을 바꾸는 순간(CreateSecretFeature.swift의 .binding(.selectedSubType)) 무조건 content를 빈 값으로 초기화해버립니다.
    → 사용자가 "API Key" 탭에서 Value에 값을 입력해놓고, 실수로 "Access Token" 탭을 눌렀다가 다시 "API Key"로 돌아오면 방금 입력한 값이 사라져 있습니다. 같은 화면인데 탭 전환만으로 데이터가 날아가는 건 사용자 입장에서 매우 당황스러운 동작이고, 신규 기능 폼의 핵심 골든패스(첫 화면, 기본 타입)에서 발생하는 문제라 우선순위가 높습니다. (참고로 다른 secretType들 — OAuth Client↔Service Account, License Key↔Custom 등 — 은 탭마다 폼 자체가 달라서 리셋되는 게 맞는 동작입니다. 이번 버그는 "폼이 같은데 리셋되는" 경우만 해당.)

  2. 🟠 새 프로젝트 만들어도 자동 선택 안 됨 (이전 리뷰와 동일)
    Project 드롭다운에서 "+ Add new project"로 프로젝트를 만들어도 현재 작성 중인 시크릿에는 체크가 안 됩니다.

@doyeonk429 doyeonk429 added the ✨ Feature 새로운 기능 개발 label Jul 25, 2026
@doyeonk429

Copy link
Copy Markdown
Contributor Author

@yeseonglee , @dlguszoo

  • 신규) 같은 폼 안에서 탭만 바꿔도 입력값이 통째로 날아감
    이 부분은 정책적으로 정의가 필요할 것 같은데, 공통필드 값은 유지를 시킬까요? 리뷰에서 요구하는 정도로 하려면 각 서브타입마다 오브젝트가 존재해야 값 복구가 가능한데..

@doyeonk429

Copy link
Copy Markdown
Contributor Author

새 프로젝트 만들어도 자동 선택 안 됨 @yeseonglee
-> 이 부분은 기능 연결 pr에서 처리하겠습니당

- validationErrors 재-Save 시 리셋 (이전 warning 잔존 방지) + 회귀 테스트 신규
- String.isBlank 확장 — 공백만 입력한 필수 필드 검증 통과 방지
- OAuthClient Client ID trailingHint를 hintFor 프로토콜로 통합 — detected 값 누출 방지
- FormLayoutMode.dualThreshold 828 → 816 (spacing 16 반영 재계산)
- ExpireDate "No expired" → "No expiration"
@dlguszoo

Copy link
Copy Markdown
Contributor

@yeseonglee , @dlguszoo

  • 신규) 같은 폼 안에서 탭만 바꿔도 입력값이 통째로 날아감
    이 부분은 정책적으로 정의가 필요할 것 같은데, 공통필드 값은 유지를 시킬까요? 리뷰에서 요구하는 정도로 하려면 각 서브타입마다 오브젝트가 존재해야 값 복구가 가능한데..

저는 API Key / Access Token / Webhook Secret은 폼 UI가 같아 보여도 각 서브타입에서 value가 갖는 의미 자체가 다르기 때문에, 탭 전환 시 초기화되는 게 맞는 동작이라고 생각합니다.

@dlguszoo dlguszoo 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.

와.. 다중 선택 드롭다운.... 수고하셨습니다!

@dlguszoo
dlguszoo merged commit 107f85e into develop Jul 28, 2026
1 check passed
@dlguszoo
dlguszoo deleted the feature/#41 branch July 28, 2026 12:28
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: Secret Creation View 개발

3 participants