Feature/#75 - 시크릿 조회 화면 9개 타입 구현 및 가변 폼 레이아웃 도입 - #82
Conversation
- DVComponentWidthPolicy(.fixed/.fill) Environment 값과 dvComponentWidth modifier 추가 - .fill에서 DVComponentSize를 최소 폭으로 해석해 컴포넌트가 컨테이너를 채우도록 함 - 기본값이 .fixed이므로 기존 호출부 동작은 변하지 않음
- 고정 .frame(width: size.width)를 dvComponentWidth modifier로 교체 - 대상: DVLabeledField / DVTextField / DVTextContainer / DVMultilineTextField / DVDropdown / DVChipsField / DVMultiSelectDropdown - DVMultiSelectDropdown은 트리거만 적용하고 팝오버 패널 폭은 토큰 고정 유지
- isInteractive 파라미터(기본 true)와 init(readOnly:isSelected:) 편의 생성자 추가 - 읽기 전용에서는 탭·포커스·Space 핸들러를 뷰 트리에 부착하지 않고 .isButton 트레이트도 제외
- 다중 선택 값을 chip 그리드로 표시만 하는 컴포넌트 (DVMultiSelectDropdown 트리거의 read-only 대응) - DVChip이 Button이라 컨테이너 전체에 allowsHitTesting(false)로 인터랙션 차단 - 값이 비어도 다른 읽기 전용 필드와 높이를 맞추도록 minHeight 28 유지
- FormLayoutMode를 FormLayout(해석 결과) + FormLayoutContext(화면 맥락)로 대체 - StandaloneFormLayout(CreateSecret) / DetailColumnFormLayout(detail 컬럼)이 각자 임계값과 사이즈 매핑을 소유 - 배열 프리셋 3개: dual / single / detailFluid — detailFluid는 컨테이너를 채우고 페어를 절반씩 나눔 - FormLayoutMetrics로 임계값(816)과 프레임 상한을 상수에서 계산하도록 통일 - AdaptiveFieldRow를 Sources/Form으로 이동하고 pairAxis 기반으로 전환 - 레이아웃과 무관한 isProjectLoading은 CreateSecretEnvironment로 분리
- Field 8종·Section 9종·CreateSecretView·PreviewWidth를 formLayoutMode에서 formLayout으로 전환 - 슬롯 사이즈 해석을 layout.size(for:) 한 줄로 통일 - ExpireDateFieldView가 내부 컨트롤을 .frame(width:)로 직접 고정하던 것을 dvComponentWidth로 교체 — fill 정책에서 래퍼만 늘어나 어긋나던 문제 - PreviewWidth 문서의 dual 임계값 표기를 실제 값(816)으로 정정
- 창을 넓히면 본문 필드는 제자리인데 footer 버튼만 오른쪽 끝까지 밀려나던 문제 - formMaxWidth()로 화면 루트 프레임을 816(2열 폼 콘텐츠 776 + 좌우 padding)에서 멈춤 - header/footer는 본문과 분리된 채 각자 이 프레임을 따라가므로 좌우 끝이 함께 움직임 - 컬럼이 창을 채우지 못하면 윈도우 배경이 양옆에 드러남
- 생성 화면 LabeledTextFieldView의 read-only 대응 (DVLabeledField + DVTextContainer) - isSensitive / isCopyable 두 플래그의 4조합 분기 — 마스킹+복사 동시 조합만 accessories init 사용 - 마스킹·복사를 재구현하지 않고 DVTextContainer 편의 init에 위임 - 클립보드 쓰기(NSPasteboard)를 컴포넌트가 수행해 호출부는 isCopyable만 지정
- Figma 1768:30819 — 즐겨찾기(좌) + 타입명 + 공유/수정/삭제(우) + 서브타입 탭바 - 서브타입 탭바는 조회·수정 양쪽 모두 읽기 전용 — 서브타입이 바뀌면 payload 스키마가 달라져 수정이 아니라 재생성이 됨 - 탭바 간격은 24 — Figma 시각 간격 28에서 DVRadioButton 좌우 padding 4를 뺀 값 - 공유는 동작이 정해지지 않아 비활성으로 렌더 - displayName 조회를 위해 SecretType/SecretSubType → Creatable 역변환 추가 (6:6, 9:9 완전 대응)
- DetailSectionScaffoldView — 생성 화면 FormSectionScaffold의 read-only 대응 - 필드 순서를 CreateSecret SectionView와 동일하게 맞추고, 공통 페어 행 앞뒤로 payload 필드 슬롯 2개를 둠 - 공통 메타 필드는 Secret에서 바로 나오므로 payload 복호화를 기다리지 않고 그림 - Project는 다중 값이라 DetailProjectFieldView로 chip 표시 (생성 화면 DVMultiSelectDropdown 트리거와 동일한 모습) - environment/expireDate 등 도메인 값의 표시 문자열 변환을 SecretDetailDisplayText로 분리
- 즐겨찾기 갱신은 PatchSecretUseCase.updateSimple 경유 — payload 복호화가 없어 생체인증을 타지 않음 - PatchSecretUseCase를 앱 타겟에 조립 - Secret 엔티티에 프로젝트 정보가 없어 연결 프로젝트는 FetchSecretUseCase.fetchProjects로 별도 조회
- 즐겨찾기 토글은 setLiked 성공 시 state.secret을 교체하고 secretUpdated delegate 발송 - 연타 시 응답 순서가 뒤바뀌어 liked가 반대로 굳는 것을 cancelInFlight로 방지 - 삭제는 확인 alert를 거친 뒤 softDelete 호출, 성공하면 deleted delegate 발송 - 진행 중 중복 조작을 막기 위해 isDeleting 추가 - state.secret을 let에서 internal(set) var로 변경 — reducer가 갱신해야 하고 private(set)은 State 선언 스코프 밖에서 쓸 수 없음 - 즐겨찾기·삭제 실패 alert 프리셋 추가
- Secret 엔티티에 프로젝트 정보가 없어 task에서 fetchLinkedProjects를 함께 조회 - 조회 실패 시 alert를 띄우지 않는다 — 부가 정보이고 payload 복호화 실패 alert와 겹치면 원인을 오해하게 됨 - 수정 모드 진입 시 projectIds 초기값으로도 재사용 예정
- close(×) 버튼과 Divider를 담고 있던 topBar 제거 — 닫기는 사이드바 전환·리스트 선택 해제로만 - DetailColumnFormLayout으로 레이아웃을 측정해 주입하고 formMaxWidth로 프레임 상한 적용 - 헤더와 공통 메타 섹션 연결. 수정 버튼은 상태 전이가 없어 비활성으로 렌더 - 삭제 진행 중에는 헤더 전체를 disabled 처리 - task(id:)로 교체 — 다른 리스트 셀을 선택하면 새 State가 할당되지만 뷰 아이덴티티가 그대로여서 .task가 재실행되지 않아 연결 프로젝트가 빈 채로 남던 문제 - finish()를 await해 secret 전환 시 이전 조회 effect가 취소되도록 함
- secretUpdated / deleted delegate 수신 시 목록을 재조회 - 항목 교체가 아니라 재조회인 이유: liked·expired·project 같은 필터 컬렉션에서 조건을 벗어난 항목이 그대로 남음 (Star 컬렉션에서 즐겨찾기를 해제해도 행이 보이는 문제) - deleted 수신 시 secretDetail과 selectedSecretID 초기화 - SecretListFeature에 refresh 액션 추가 — task/didTapRetry와 달리 loading으로 바꾸지 않아 목록이 깜빡이지 않음
- 즐겨찾기 3종: 성공 시 secret 교체 + delegate, 현재 값의 반대값 전달, 실패 시 alert - 삭제 3종: 확인 alert만 띄우고 즉시 삭제하지 않음, 성공 시 deleted delegate, 실패 시 isDeleting 해제 - 연결 프로젝트 2종: 로드 성공, 실패 시 alert 없음 - MainFeature 2종: secretUpdated·deleted 수신 시 목록 재조회 및 detail 유지/해제 - task 테스트의 merge 수신 순서 의존 제거 — 관심 없는 dependency를 CancellationError로 스텁하면 액션이 발행되지 않아 순서와 무관해짐
- 인증 취소 시 alert를 닫으면 다시 시도할 방법이 없어 화면에 갇히던 문제 - didTapRetryReveal은 reveal effect만 재실행한다 — projects·연결 프로젝트는 재조회하지 않음 - .task와 재시도가 공유하도록 revealEffect(secret:)를 헬퍼로 추출 - cancelInFlight로 인증 프롬프트가 중복 누적되지 않게 처리 - 실패 원인별 안내 문구를 revealFailureMessage로 분리 — alert는 닫히면 사라지므로 화면에서도 원인이 읽혀야 한다 - 재시도 성공·실패와 인증 취소 후 재시도 흐름 테스트 추가
- 인증 전에는 필드 스캐폴드를 뷰 트리에서 제외하고 남은 영역을 로딩·실패 뷰가 채운다 - 반투명 오버레이로 덮는 방식을 버린 이유: 뒤의 필드가 비쳐 보여 어디까지가 복호화된 정보인지 구분되지 않는다 - 헤더는 두 경우 모두 남긴다 — 즐겨찾기·삭제는 복호화 없이 수행되므로 로딩 중에도 열려 있어야 한다 - 복호화 전 경로는 ScrollView로 감싸지 않는다. 감싸면 상태 뷰가 남은 높이를 채우지 못해 헤더 바로 아래에 작게 붙는다 - 헤더와 본문의 padding·정렬이 두 경로에서 어긋나지 않도록 bodyStack으로 묶음 - 실패 시 안내 문구와 재시도 버튼 노출 (SecretListView 에러 표현과 스타일 일치)
- 기존에는 secretType만 분기해 accessToken·webhookSecret·serviceAccount·sslTlsCertificate·custom이 실제와 다른 payload 타입을 반환했다 - live dispatchRevealPayload와 case 구성·순서·default 처리를 1:1로 일치시킴 - metadata도 채워 조회 화면에서 metadata 기반 필드까지 프리뷰로 확인 가능하게 함 - CreateSecret 폼이 입력받지 않는 metadata 필드는 live 경로에서도 항상 nil이라 그대로 nil 유지 - 한쪽만 바뀌면 프리뷰가 조용히 어긋나므로 정합 유지 제약을 주석으로 남김
- 기존 픽스처 14개가 전부 subType nil이어서 dummy revealPayload의 서브타입 분기에 도달할 수 없었다 - previewSubTypeMatrix 신설 — dummy·live가 분기하는 11개 (secretType, subType) 조합 전수 커버 - secret 헬퍼에 subType·environment·memo 파라미터 추가 (전부 기본값 nil이라 기존 호출부 무변경) - 메타 필드는 값 있는 항목과 nil 항목을 섞음 — optional 필드의 빈 상태 렌더도 프리뷰로 확인해야 한다 - preview 배열은 아바타 폴백·컬렉션 필터 검증 의도로 구성이 맞춰져 있어 수정하지 않음
- 공통 페어 행(Project/Services/Expire Date/Environment)을 스캐폴드에서 제거하고 각 섹션이 조립하도록 변경 - 구성이 타입마다 달라서다 — SSHKey·SSLTLSCert·EnvSet은 Project|Environment에 둘째 행이 없고, ServiceAccount는 Expire Date가 단독 행, LicenseKey는 Environment 자리에 License Tier가 온다 - 고정 구성으로는 9개 중 4개만 맞았다. 생성 화면 FormSectionScaffold도 같은 이유로 Name·Memo만 갖는다 - primary/trailing 2슬롯을 단일 content 슬롯으로 통합하고 linkedProjects는 섹션이 받도록 이동
- 각 섹션이 DetailSectionScaffoldView를 감싸고 자기 공통 행을 조립 — 생성 화면 SectionView와 1:1 미러링 - 라벨·필드 순서·sizeMode를 대응 생성 섹션에서 그대로 가져옴 - SSL Required와 License Tier는 조회에서 값 변경 컨트롤을 쓸 수 없어 텍스트로 표시. metadata가 nil이면 Not Required로 해석 - isSensitive·isCopyable은 필드별로 지정 — 실제 비밀값만 마스킹, 복사해 쓰는 값은 복사만, 짧은 메타는 표시만 - 디스패처는 default 없이 11 case를 모두 명시해 도메인에 case가 늘면 컴파일러가 잡도록 함 - 프리뷰는 Store·dependency 없이 payload 값만 주입. 조합 전수는 sweep 프리뷰 하나로 훑는다
- payloadState가 .loaded일 때 DetailPayloadSectionView를 렌더 - 복호화 전 경로(ScrollView 미사용·bodyStack·헤더 유지)는 그대로 유지 - 소비처가 사라진 isPayloadRevealed 판정 제거 - 프리뷰 픽스처를 previewSubTypeMatrix 참조로 교체 — 파일마다 secret을 새로 만들지 않는다
- task에서 payloadState가 .loaded면 reveal effect를 태우지 않는다 - 평문이 State에 남아 있어 재인증이 보안을 더해주지 않고, 뷰가 다시 나타날 때마다 Touch ID가 뜨는 것만 성가시다 - .failed는 가드하지 않는다 — 재진입은 재시도 기회다 - 프로젝트·연결 프로젝트 조회는 가드와 무관하게 그대로 수행 - revealPayload 호출 횟수로 검증하는 테스트 추가
- 생성 화면이 그 타입에서 입력받지 않는 필드를 픽스처가 채우고 있었다 — 실제 데이터에 존재할 수 없는 상태라 조회 화면에서 값이 왜 안 보이는지 오해하게 된다 - serviceAccount의 environment 제거 (Environment 입력 없음) - sshKey·sslTlsCertificate의 expiresAt 제거 (Expire Date 입력 없음) - 만료 케이스는 Expire Date를 실제로 입력받는 webhookSecret으로 이동 - 타입별 입력 가능 필드 표를 배열 doc에 남겨 같은 실수를 막는다
- 스캐폴드가 formLayout을 Environment로 읽게 되면서 본문에서 layout 참조가 사라졌다 - 파라미터로 받는 이유를 설명하던 doc도 함께 정리
- EnvSet placeholder가 화면에 리터럴 백슬래시+n을 그대로 노출하고 있었다. 한 줄 필드이므로 개행 예시를 뺀다 - projectIds 주석이 UI가 단일 선택으로 제한한다고 적혀 있으나 DVMultiSelectDropdown에 개수 제한이 없어 실제로는 다중 선택된다. 다중 연결이 의도된 동작이므로 주석을 실제에 맞춘다
- DVTextContainer가 lineLimit(1)이라 개행이 있는 값은 첫 줄만 보이고 나머지를 확인할 방법이 없었다. - DVMultilineTextField의 read-only 대응으로 DVMultilineTextContainer 추가. 스타일은 DVTextContainer와 동일하게 맞추고 내용이 넘치면 세로 스크롤 - 마스킹은 줄 단위로 채워 한 줄 값일 때 기존 출력과 동일하고, 여러 줄에서는 줄 구조가 남아 필드가 제대로 채워졌는지 확인할 수 있다 - 개행 판정은 DetailReadOnlyFieldView 한 곳에서 하므로 섹션 호출부는 변경 없음 - 빈 값에는 복사 버튼을 달지 않는다
- Figma 1529:139917 / 1529:139939 반영. 3일 이내는 danger + 경고 삼각형, 7일 이내는 warning + calendar - 임계값을 SecretExpiryStatus 한 곳에 모았다. 목록과 조회가 각자 들고 있으면 같은 시크릿이 한쪽에서만 강조되는 모순이 생긴다 - 판정은 SecretExpiryStatus, 표현은 DVExpiryEmphasis로 나눴다. - 날짜를 yy.MM.dd 고정으로 바꿨다. 추후, 정책 확정 시 처리(locale)
- addButtonTapped에서 secretDetail과 secretList.selectedSecretID를 정리 - 완료·취소 어느 경로로 나가도 stale 상태가 남지 않도록 secretCreated가 아니라 진입 시점에서 끊음
- generate-local 워크스페이스는 ad-hoc 서명이라 Touch ID 평가가 실패한다. 온보딩의 Touch ID 단계와 잠금 해제가 모두 인증을 요구해 메인 화면까지 도달할 수 없으므로 우회 통로를 만든다 - AppFeature에 DEBUG 전용 debugSkipToMain을 추가해 onboarding·locked를 건너뛰고 main으로 진입 - hasCompletedOnboarding은 세우지 않는다. 다음 실행에서 온보딩이 다시 나와야 실제 흐름을 계속 검증할 수 있고 디버그 통로가 영구 상태로 굳지 않는다 - Debug 메뉴에서 옵션+커맨드+M으로 실행 (CommandMenu, 파일 전체 #if DEBUG) - 메뉴가 같은 Store를 참조해야 해서 DevaultApp의 Store를 프로퍼티로 승격. ViewBuilder 안에서 생성되어 body 재평가 시 Store가 새로 만들어질 수 있던 문제도 함께 해소 - 팀 시트 발급으로 generate-local이 필요 없어지면 제거하도록 TODO에 #64 연결
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (7)
Walkthrough시크릿 상세 조회 화면을 9개 payload 유형의 읽기 전용 섹션으로 확장했습니다. 공통 폼 레이아웃과 폭 정책을 도입하고, 만료 강조·연결 프로젝트·즐겨찾기·삭제·복호화 재시도 흐름을 연결했습니다. DEBUG 메인 진입 명령과 미리보기 데이터도 추가했습니다. Changes시크릿 상세 조회
폼 레이아웃과 만료 표현
데이터와 앱 연결
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (10)
Projects/DVDesign/Sources/Components/DVMultilineTextContainer.swift (1)
77-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
fileprivate를private로 축소하세요.
MultilineContainerMetrics는 Line 91에서만 참조됩니다. 파일 내부 전체에 노출할 필요가 없습니다.private로 변경해 접근 범위를 축소하세요.수정 예시
-fileprivate enum MultilineContainerMetrics { +private enum MultilineContainerMetrics {As per path instructions,
접근 제어가 가능한 가장 엄격한 수준인지 확인하세요. (private>fileprivate>internal).🤖 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/DVMultilineTextContainer.swift` at line 77, Change the access modifier on MultilineContainerMetrics from fileprivate to private, since it is only referenced within its enclosing scope at line 91; leave its definition and usage otherwise unchanged.Sources: Path instructions, Linters/SAST tools
Projects/DVDesign/Sources/Components/DVChipsContainer.swift (1)
60-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSwiftUI 하위 레이아웃을
private extension으로 분리하세요.
body는 화면 조합만 담당하게 유지하세요. 하위 레이아웃과 하위뷰를private var또는private func로 이동하세요.
Projects/DVDesign/Sources/Components/DVChipsContainer.swift#L60-L75:DVFlowLayout구성을private var chipsContent로 분리하세요.Projects/DVDesign/Sources/Components/DVMultilineTextContainer.swift#L135-L148: 컨테이너HStack구성을private var content로 분리하세요.Projects/DVDesign/Sources/Components/DVTextContainer.swift#L146-L183: 행 레이아웃과leadingIconView를private extension DVTextContainer으로 이동하세요.Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift#L108-L147:labelRow와indicator를private extension DVRadioButton으로 이동하세요.As per path instructions,
var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private var/func로 분리를 제안하세요.및하위뷰가 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/DVChipsContainer.swift` around lines 60 - 75, Refactor the SwiftUI sublayouts out of body into private extensions or private computed properties: in Projects/DVDesign/Sources/Components/DVChipsContainer.swift:60-75, move the DVFlowLayout into private var chipsContent; in Projects/DVDesign/Sources/Components/DVMultilineTextContainer.swift:135-148, move the HStack into private var content; in Projects/DVDesign/Sources/Components/DVTextContainer.swift:146-183, move the row layout and leadingIconView into private extension DVTextContainer; and in Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift:108-147, move labelRow and indicator into private extension DVRadioButton. Keep each body focused on composition and move any related subviews inside the appropriate private extension.Source: Path instructions
Projects/DVPresentation/Sources/Form/AdaptiveFieldRow.swift (1)
47-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
body에서 행 레이아웃을 분리하세요.Line 47과 Line 63은
body에서HStack와VStack를 직접 구현합니다.horizontalRow(spacing:)와verticalRow(spacing:)를private extension AdaptiveFieldRow의private func로 분리하세요.body에는pairAxis분기만 유지하세요.As per path instructions, "
var body안에 중첩 레이아웃이 직접 구현되어 있으면extension의private 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/Form/AdaptiveFieldRow.swift` around lines 47 - 72, AdaptiveFieldRow의 body에서 직접 구현된 HStack와 VStack 레이아웃을 분리하세요. body에는 layout.pairAxis에 따른 분기와 horizontalRow(spacing:), verticalRow(spacing:) 호출만 남기고, 각 레이아웃 구현은 private extension AdaptiveFieldRow의 private func horizontalRow(spacing:) 및 private func verticalRow(spacing:)로 이동하세요.Source: Path instructions
Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift (1)
24-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
body의 중첩 레이아웃을 private 구성요소로 분리하세요.
GeometryReader내부의VStack과 폭 계산이body에 직접 남아 있습니다. 이번 변경으로 패딩, 최대 폭,FormLayout환경 주입이 같은 체인에 추가되었습니다.extension CreateSecretView에private func formContent(width: CGFloat) -> some View를 두고body에서는 호출만 하세요.As per path instructions:
var body안에 중첩 레이아웃이 직접 구현되어 있으면extension의private 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/CreateSecretView.swift` around lines 24 - 28, Extract the nested GeometryReader/VStack form layout from CreateSecretView.body into a private formContent(width: CGFloat) -> some View function in an extension CreateSecretView. Keep the existing padding, formMaxWidth(), and formLayout(StandaloneFormLayout.layout(for: proxy.size.width)) modifiers unchanged, and have body only invoke the extracted component.Source: Path instructions
Projects/DVPresentation/Sources/Features/SecretDetail/Components/SecretDetailHeaderView.swift (1)
48-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
body의 레이아웃을 extension의 private 서브뷰로 분리하세요.
body가VStack과 탭바 조건 분기를 직접 소유합니다. 이 구성을 extension의private var content로 이동하고body에서는 해당 서브뷰만 반환하세요.As per path instructions, "var body 안에 중첩 레이아웃이 직접 구현되어 있으면 extension의 private 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/SecretDetail/Components/SecretDetailHeaderView.swift` around lines 48 - 55, SecretDetailHeaderView의 body에 직접 구현된 VStack 레이아웃과 tabBar 조건 분기를 extension의 private var content로 이동하세요. body는 해당 content 서브뷰만 반환하도록 변경하고, spacing·typeRow·tabBar 및 frame 동작은 그대로 유지하세요.Source: Path instructions
Projects/DVPresentation/Sources/Features/SecretDetail/Components/DetailSectionScaffoldView.swift (1)
22-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value프로퍼티 접근 수준을 통일하세요.
content는private이고secret은 internal입니다.secret도 뷰 내부에서만 읽으므로private으로 좁힐 수 있습니다. 다른 Detail 섹션 뷰들과 함께 정책을 맞추면 의도가 분명해집니다.♻️ 제안 diff
- let secret: Secret + private let secret: Secret경로 지시의 "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요" 항목을 따랐습니다.
🤖 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/SecretDetail/Components/DetailSectionScaffoldView.swift` around lines 22 - 24, DetailSectionScaffoldView의 secret 프로퍼티 접근 수준을 private으로 변경하세요. content의 기존 private 접근 수준과 다른 Detail 섹션 뷰들의 정책을 유지해 뷰 내부에서만 읽을 수 있도록 맞추세요.Source: Path instructions
Projects/DVPresentation/Sources/Features/SecretDetail/SecretDetailView.swift (1)
106-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win수직 여백에 수평 상수를 재사용하지 마세요.
L111이
.padding(.vertical, FormLayoutMetrics.horizontalPadding)을 사용합니다. 값은 같아도 이름과 용도가 어긋나 이후 수평 여백만 바꾸면 수직 여백까지 함께 바뀝니다.FormLayoutMetrics에 수직 상수를 추가하고 그 값을 쓰세요.♻️ 제안 diff
.padding(.horizontal, FormLayoutMetrics.horizontalPadding) - .padding(.vertical, FormLayoutMetrics.horizontalPadding) + .padding(.vertical, FormLayoutMetrics.verticalPadding)
FormLayout.swift에 상수를 추가하세요.enum FormLayoutMetrics { static let verticalPadding: CGFloat = horizontalPadding }🤖 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/SecretDetail/SecretDetailView.swift` around lines 106 - 113, Update the VStack padding in SecretDetailView to use a dedicated FormLayoutMetrics.verticalPadding constant instead of horizontalPadding, and add that verticalPadding constant in FormLayout.swift while preserving the current value.Projects/DVPresentation/Sources/Dependencies/SecretClient.swift (1)
145-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
default대신 남은 조합을 명시하세요.주석은 live
dispatchRevealPayload와 case 구성이 1:1로 일치해야 한다고 말합니다. 그런데default가 그 안전망을 없앱니다.SecretType이나SecretSubType에 case를 추가해도 컴파일러가 이 switch를 지적하지 않습니다. 남은 조합을 명시적으로 나열하고, 유효하지 않은 조합만 한 곳에서 처리하세요.♻️ 제안 방향
- default: + case (.apiKeyToken, _), (.oauth, _), (.sshAndCredentials, _), (.etc, _): assertionFailure("Unexpected (secretType, subType) combination: \(secret.secretType), \(String(describing: secret.subType))") throw SecretUseCaseError.unexpected🤖 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/Dependencies/SecretClient.swift` around lines 145 - 148, Update the switch in the secret dispatch logic around the `(secret.secretType, secret.subType)` handling to remove the default branch and explicitly enumerate every remaining valid combination from live dispatchRevealPayload. Keep the assertion and SecretUseCaseError.unexpected handling in a single branch for only invalid combinations, while preserving exhaustive compiler checking when SecretType or SecretSubType gains a new case.Projects/DVPresentation/Sources/Dependencies/SecretClient+Preview.swift (1)
79-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win프리뷰가
previewSubTypeMatrix를 위치 인덱스로 참조합니다. 픽스처 배열의 순서가 곧 계약이 되어 있습니다. 항목을 하나 추가하거나 재정렬하면 프리뷰가 조용히 다른 타입을 그리고, 항목을 줄이면 인덱스 범위를 벗어나 크래시합니다. 배열 정의 쪽에(secretType, subType)으로 조회하는 접근자 또는 명명된 정적 프로퍼티를 추가하고, 각 프리뷰가 그것을 쓰게 하세요.
Projects/DVPresentation/Sources/Dependencies/SecretClient+Preview.swift#L79-L167:previewSubTypeMatrix에static func previewSecret(_ type: SecretType, _ subType: SecretSubType?) -> Secret같은 조회 헬퍼 또는previewAPIKey,previewCustom등 명명된 프로퍼티를 추가하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/DetailSectionScaffoldView.swift#L58-L100:[0],[1]을 명명된 픽스처로 교체하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailAPIKeysTokenSectionView.swift#L60-L88:[0],[1]을 apiKey / accessToken 픽스처로 교체하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailDatabaseSectionView.swift#L63-L90:[5]를 database 픽스처로 교체하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailEnvSetSectionView.swift#L46-L70:[8]을 envSet 픽스처로 교체하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailServiceAccountSectionView.swift#L57-L85:[4]를 serviceAccount 픽스처로 교체하세요.Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailCustomSectionView.swift#L54-L78:[10]을 custom 픽스처로 교체하세요.🤖 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/Dependencies/SecretClient`+Preview.swift around lines 79 - 167, Replace positional previewSubTypeMatrix access with stable named lookup fixtures. In Projects/DVPresentation/Sources/Dependencies/SecretClient+Preview.swift#L79-L167, add a (SecretType, SecretSubType?) lookup helper or named static properties; update DetailSectionScaffoldView.swift#L58-L100 to use the corresponding named fixtures, DetailAPIKeysTokenSectionView.swift#L60-L88 to use apiKey/accessToken, DetailDatabaseSectionView.swift#L63-L90 to use database, DetailEnvSetSectionView.swift#L46-L70 to use environmentVariableSet, DetailServiceAccountSectionView.swift#L57-L85 to use serviceAccount, and DetailCustomSectionView.swift#L54-L78 to use custom.Projects/DVPresentation/Tests/Main/MainFeatureTests.swift (1)
112-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value테스트 Secret 생성을 헬퍼로 묶으세요.
이번 변경으로 동일한
Secret(...)생성 블록이 3곳 더 늘었습니다.SecretDetailFeatureTests의makeSecret처럼 정적 헬퍼를 하나 두고 이름·liked만 인자로 받으세요. 이후Secret이니셜라이저가 바뀌면 수정 지점이 한 곳으로 줄어듭니다.♻️ 제안 diff
+ private static func makeSecret( + name: String = "Test Token", + liked: Bool = false + ) -> Secret { + Secret( + id: UUID(), + name: name, + secretType: .apiKeyToken, + liked: liked, + createdAt: Date(), + updatedAt: Date(), + payload: SecretPayload(encryptedData: Data(), keyTag: "test", schemaVersion: 1) + ) + }Also applies to: 368-376, 400-407
🤖 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/Main/MainFeatureTests.swift` around lines 112 - 119, Extract the repeated Secret(...) construction in MainFeatureTests into a static makeSecret helper, following SecretDetailFeatureTests.makeSecret. Have the helper accept only name and liked, centralize the remaining default fields and payload values there, and replace all three duplicated construction blocks with calls to this helper.
🤖 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/DVDesign/Sources/Components/DVMultilineTextContainer.swift`:
- Around line 238-246: DVMultilineTextContainerPreviewData의 pem 샘플에서 PEM
private-key 헤더와 형식화된 키 데이터를 제거하세요. 여러 줄 스크롤 프리뷰가 계속 동작하도록 동일한 용도의 더미 멀티라인 문자열을
사용하되, 정적 분석기가 키로 인식할 수 있는 PEM 형식은 포함하지 마세요.
In `@Projects/DVPresentation/Sources/Dependencies/SecretClient.swift`:
- Around line 150-154: Update the setLiked stub to use the requested secret ID
instead of always starting from Secret.preview. Resolve and return the fixture
corresponding to the request ID, then apply the liked value while preserving
that secret’s existing fields.
In `@Projects/DVPresentation/Sources/Features/Main/MainFeature.swift`:
- Around line 107-115: Update the .secretDetail(.delegate(.secretUpdated)) and
.deleted cases in MainFeature to also send sidebar(.countsRefreshRequested),
alongside the existing secretList(.refresh) action, so sidebar collection counts
refresh after edits and deletions.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Components/DetailReadOnlyFieldView.swift`:
- Around line 297-301: Remove the PEM private-key markers and key-shaped sample
from the preview content in DetailReadOnlyFieldView, replacing it with a
multiline placeholder that cannot be detected as a private key while preserving
the preview’s placeholder purpose.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Components/SecretDetailHeaderView.swift`:
- Around line 29-30: Update SecretDetailHeaderView’s type inputs so its title
and selected subtype are derived from the decrypted CreateSecretPayload case
used by DetailPayloadSectionView, rather than only store.secret.secretType and
store.secret.subType. Pass the normalized type/subtype from the decrypted
payload into the header, or show an error state whenever the metadata and
payload cannot be reconciled.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailEnvSetSectionView.swift`:
- Around line 20-25: Update the label passed to DetailReadOnlyFieldView in
DetailEnvSetSectionView from “envSet List” to Title Case “EnvSet List”, matching
the capitalization used by labels such as “Link String” and “Certification
JSON”.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Model/SecretDetailDisplayText.swift`:
- Around line 35-40: Update the expiration-date creation mapping and
SecretDetailDisplayText.expireDateDisplayText formatter to use the same fixed
calendar and time zone, rather than Calendar.current and the device’s default
time zone. Ensure the 30-day initial expiration and formatted expiration date
both follow the stored local-date-at-midnight convention.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Model/SecretDetailError.swift`:
- Around line 38-44: Update SecretDetailError.revealFailureMessage so
.unexpected returns a separate secret-load failure message rather than the
decryption failure message, while keeping .decryptionFailed mapped to the
existing decryption text and leaving .authRequired unchanged.
---
Nitpick comments:
In `@Projects/DVDesign/Sources/Components/DVChipsContainer.swift`:
- Around line 60-75: Refactor the SwiftUI sublayouts out of body into private
extensions or private computed properties: in
Projects/DVDesign/Sources/Components/DVChipsContainer.swift:60-75, move the
DVFlowLayout into private var chipsContent; in
Projects/DVDesign/Sources/Components/DVMultilineTextContainer.swift:135-148,
move the HStack into private var content; in
Projects/DVDesign/Sources/Components/DVTextContainer.swift:146-183, move the row
layout and leadingIconView into private extension DVTextContainer; and in
Projects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swift:108-147,
move labelRow and indicator into private extension DVRadioButton. Keep each body
focused on composition and move any related subviews inside the appropriate
private extension.
In `@Projects/DVDesign/Sources/Components/DVMultilineTextContainer.swift`:
- Line 77: Change the access modifier on MultilineContainerMetrics from
fileprivate to private, since it is only referenced within its enclosing scope
at line 91; leave its definition and usage otherwise unchanged.
In `@Projects/DVPresentation/Sources/Dependencies/SecretClient.swift`:
- Around line 145-148: Update the switch in the secret dispatch logic around the
`(secret.secretType, secret.subType)` handling to remove the default branch and
explicitly enumerate every remaining valid combination from live
dispatchRevealPayload. Keep the assertion and SecretUseCaseError.unexpected
handling in a single branch for only invalid combinations, while preserving
exhaustive compiler checking when SecretType or SecretSubType gains a new case.
In `@Projects/DVPresentation/Sources/Dependencies/SecretClient`+Preview.swift:
- Around line 79-167: Replace positional previewSubTypeMatrix access with stable
named lookup fixtures. In
Projects/DVPresentation/Sources/Dependencies/SecretClient+Preview.swift#L79-L167,
add a (SecretType, SecretSubType?) lookup helper or named static properties;
update DetailSectionScaffoldView.swift#L58-L100 to use the corresponding named
fixtures, DetailAPIKeysTokenSectionView.swift#L60-L88 to use apiKey/accessToken,
DetailDatabaseSectionView.swift#L63-L90 to use database,
DetailEnvSetSectionView.swift#L46-L70 to use environmentVariableSet,
DetailServiceAccountSectionView.swift#L57-L85 to use serviceAccount, and
DetailCustomSectionView.swift#L54-L78 to use custom.
In
`@Projects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swift`:
- Around line 24-28: Extract the nested GeometryReader/VStack form layout from
CreateSecretView.body into a private formContent(width: CGFloat) -> some View
function in an extension CreateSecretView. Keep the existing padding,
formMaxWidth(), and formLayout(StandaloneFormLayout.layout(for:
proxy.size.width)) modifiers unchanged, and have body only invoke the extracted
component.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Components/DetailSectionScaffoldView.swift`:
- Around line 22-24: DetailSectionScaffoldView의 secret 프로퍼티 접근 수준을 private으로
변경하세요. content의 기존 private 접근 수준과 다른 Detail 섹션 뷰들의 정책을 유지해 뷰 내부에서만 읽을 수 있도록
맞추세요.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/Components/SecretDetailHeaderView.swift`:
- Around line 48-55: SecretDetailHeaderView의 body에 직접 구현된 VStack 레이아웃과 tabBar 조건
분기를 extension의 private var content로 이동하세요. body는 해당 content 서브뷰만 반환하도록 변경하고,
spacing·typeRow·tabBar 및 frame 동작은 그대로 유지하세요.
In
`@Projects/DVPresentation/Sources/Features/SecretDetail/SecretDetailView.swift`:
- Around line 106-113: Update the VStack padding in SecretDetailView to use a
dedicated FormLayoutMetrics.verticalPadding constant instead of
horizontalPadding, and add that verticalPadding constant in FormLayout.swift
while preserving the current value.
In `@Projects/DVPresentation/Sources/Form/AdaptiveFieldRow.swift`:
- Around line 47-72: AdaptiveFieldRow의 body에서 직접 구현된 HStack와 VStack 레이아웃을 분리하세요.
body에는 layout.pairAxis에 따른 분기와 horizontalRow(spacing:), verticalRow(spacing:)
호출만 남기고, 각 레이아웃 구현은 private extension AdaptiveFieldRow의 private func
horizontalRow(spacing:) 및 private func verticalRow(spacing:)로 이동하세요.
In `@Projects/DVPresentation/Tests/Main/MainFeatureTests.swift`:
- Around line 112-119: Extract the repeated Secret(...) construction in
MainFeatureTests into a static makeSecret helper, following
SecretDetailFeatureTests.makeSecret. Have the helper accept only name and liked,
centralize the remaining default fields and payload values there, and replace
all three duplicated construction blocks with calls to this helper.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 35ba77b0-43c6-4915-97da-afea8582695e
📒 Files selected for processing (73)
Projects/DVDesign/SampleApp/Sources/DVVaultContainerPreviewView.swiftProjects/DVDesign/Sources/Components/DVChipsContainer.swiftProjects/DVDesign/Sources/Components/DVChipsField.swiftProjects/DVDesign/Sources/Components/DVDropdown.swiftProjects/DVDesign/Sources/Components/DVLabeledField.swiftProjects/DVDesign/Sources/Components/DVMultiSelectDropdown.swiftProjects/DVDesign/Sources/Components/DVMultilineTextContainer.swiftProjects/DVDesign/Sources/Components/DVMultilineTextField.swiftProjects/DVDesign/Sources/Components/DVTextContainer.swiftProjects/DVDesign/Sources/Components/DVTextField.swiftProjects/DVDesign/Sources/Components/DVVaultContainer.swiftProjects/DVDesign/Sources/Components/RadioButton/DVRadioButton.swiftProjects/DVDesign/Sources/Foundations/Expiry/DVExpiryEmphasis.swiftProjects/DVDesign/Sources/Foundations/Sizing/DVComponentWidthPolicy.swiftProjects/DVPresentation/Sources/Dependencies/SecretClient+Preview.swiftProjects/DVPresentation/Sources/Dependencies/SecretClient.swiftProjects/DVPresentation/Sources/Features/AppFeature.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/CreateSecretEnvironment.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/EnvironmentFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ExpireDateFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/LabeledTextFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/LicenseTierFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/MemoFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/NameFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ProjectFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Fields/ServicesFieldView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/APIKeysTokenSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/CustomSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/DatabaseSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/EnvSetSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/LicenseKeySectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/OAuthClientSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/SSHKeySectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/SSLTLSCertSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Components/Sections/ServiceAccountSectionView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/CreateSecretView.swiftProjects/DVPresentation/Sources/Features/CreateSecret/Model/SecretMetaFields.swiftProjects/DVPresentation/Sources/Features/Main/MainFeature.swiftProjects/DVPresentation/Sources/Features/Main/MainView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/DetailExpireDateFieldView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/DetailProjectFieldView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/DetailReadOnlyFieldView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/DetailSectionScaffoldView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/SecretDetailHeaderView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailAPIKeysTokenSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailCustomSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailDatabaseSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailEnvSetSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailLicenseKeySectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailOAuthClientSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailPayloadSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailSSHKeySectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailSSLTLSCertSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Components/Sections/DetailServiceAccountSectionView.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Model/SecretDetailDisplayText.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Model/SecretDetailError.swiftProjects/DVPresentation/Sources/Features/SecretDetail/Model/SecretTypeReverseMapping.swiftProjects/DVPresentation/Sources/Features/SecretDetail/SecretDetailFeature.swiftProjects/DVPresentation/Sources/Features/SecretDetail/SecretDetailView.swiftProjects/DVPresentation/Sources/Features/SecretList/SecretListFeature.swiftProjects/DVPresentation/Sources/Features/SecretList/SecretListView.swiftProjects/DVPresentation/Sources/Form/AdaptiveFieldRow.swiftProjects/DVPresentation/Sources/Form/FormLayout.swiftProjects/DVPresentation/Sources/Form/FormLayoutContext.swiftProjects/DVPresentation/Sources/PreviewSupport/PreviewWidth.swiftProjects/DVPresentation/Sources/Support/SecretExpiryStatus.swiftProjects/DVPresentation/Tests/Features/SecretExpiryStatusTests.swiftProjects/DVPresentation/Tests/Main/MainFeatureTests.swiftProjects/DVPresentation/Tests/SecretDetail/SecretDetailFeatureTests.swiftProjects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swiftProjects/Devault/Sources/DevaultApp.swiftProjects/Devault/Sources/DevaultDebugCommands.swift
💤 Files with no reviewable changes (1)
- Projects/DVPresentation/Sources/Features/CreateSecret/Components/FormLayoutMode.swift
| setLiked: { _, liked in | ||
| var secret = Secret.preview | ||
| secret.liked = liked | ||
| return secret | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
setLiked가 요청 id를 무시합니다.
이 스텁은 항상 Secret.preview("GitHub API Key")를 반환합니다. SecretDetailFeature는 likeResponse 성공 시 state.secret을 응답으로 교체합니다(Projects/DVPresentation/Tests/SecretDetail/SecretDetailFeatureTests.swift의 toggleLike_success 참고). 그래서 프리뷰에서 다른 시크릿의 즐겨찾기를 누르면 상세 화면이 엉뚱한 시크릿으로 바뀝니다. 요청 id에 해당하는 픽스처를 찾아 반환하세요.
🐛 제안 diff
setLiked: { id, liked in
- var secret = Secret.preview
+ var secret = ([Secret].preview + [Secret].previewSubTypeMatrix)
+ .first { $0.id == id } ?? .preview
secret.liked = liked
return secret
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setLiked: { _, liked in | |
| var secret = Secret.preview | |
| secret.liked = liked | |
| return secret | |
| }, | |
| setLiked: { id, liked in | |
| var secret = ([Secret].preview + [Secret].previewSubTypeMatrix) | |
| .first { $0.id == id } ?? .preview | |
| secret.liked = liked | |
| return secret | |
| }, |
🤖 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/Dependencies/SecretClient.swift` around lines
150 - 154, Update the setLiked stub to use the requested secret ID instead of
always starting from Secret.preview. Resolve and return the fixture
corresponding to the request ID, then apply the liked value while preserving
that secret’s existing fields.
| DetailReadOnlyFieldView( | ||
| label: .module("envSet List"), | ||
| value: payload.content, | ||
| isSensitive: true, | ||
| isCopyable: true | ||
| ) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
라벨 표기를 다른 섹션과 맞추세요.
"envSet List"만 소문자로 시작합니다. 다른 섹션 라벨은 "Link String", "Certification JSON"처럼 Title Case입니다. 사용자에게 보이는 문자열이므로 표기를 통일하세요.
✏️ 제안 diff
- label: .module("envSet List"),
+ label: .module("Env Set List"),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| DetailReadOnlyFieldView( | |
| label: .module("envSet List"), | |
| value: payload.content, | |
| isSensitive: true, | |
| isCopyable: true | |
| ) | |
| DetailReadOnlyFieldView( | |
| label: .module("Env Set List"), | |
| value: payload.content, | |
| isSensitive: true, | |
| isCopyable: true | |
| ) |
🤖 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/SecretDetail/Components/Sections/DetailEnvSetSectionView.swift`
around lines 20 - 25, Update the label passed to DetailReadOnlyFieldView in
DetailEnvSetSectionView from “envSet List” to Title Case “EnvSet List”, matching
the capitalization used by labels such as “Link String” and “Certification
JSON”.
- secretUpdated·deleted가 secretList(.refresh)만 보내 사이드바 개수가 그대로 남았다. .refresh는 목록만 다시 읽고 secretsChanged를 발신하지 않으며, 그 delegate는 mutationResponse 성공 경로에서만 나온다 - 즐겨찾기는 Starred, 삭제는 All·Deleted 개수를 바꾸므로 sidebar(.countsRefreshRequested)를 함께 지시 - secretCreated의 기존 선례처럼 .concatenate로 순차 실행 - 두 테스트에 countsRefreshRequested·countsResponse 수신과 date·fetchCounts 스텁 추가
- SecretDetailError: map이 조회·저장소 실패까지 .unexpected로 모으는데 복호화 문구를 보여주고 있었다. alert가 이 케이스에 쓰는 문구와 같은 값으로 분리 (기존 키 재사용, xcstrings 변경 없음) - DVMultilineTextContainer: 프리뷰 전체를 #if DEBUG로 감쌌다. 감싸지 않아 프리뷰 문자열이 릴리즈 바이너리에 포함되고 있었다 - 프리뷰 픽스처의 개인키 형태 문자열 2곳 교체. 값이 가짜여도 시크릿 스캐너가 매번 탐지 결과를 올려 같은 지적이 반복된다 - FormLayoutMetrics.verticalPadding 추가. 세로 여백이 좌우 상수를 재사용하고 있어 좌우 조정 시 함께 끌려갔다 - 만료일 포맷터 주석 정정: timeZone 미지정으로 기기 로컬 시간대로 렌더된다는 사실과 그 이유, 자정 정규화가 생성 화면 매핑 사안이라는 점 명시
dlguszoo
left a comment
There was a problem hiding this comment.
Q1.
- Environment, Type 필드를 정적 라디오 필드로 하지 않은 이유가 궁금합니다
- SSL Required 필드를 정적 체크박스 필드로 하지 않은 이유가 궁금합니다
R1.
- OAuth Client의 Client ID, SSH&Credentials/SSL Certification의 Certificate·certificateChain은 복사 가능한 정적 텍스트 필드로 수정 부탁드립니다.
R2.
- 암호화 Multiline 처리 필요: ServiceAccountFields.credentialJSON, SSHKeyFields.privateKey, SSLCertFields.certificate, SSLCertFields.sslPrivateKey, SSLCertFields.certificateChain, EnvSetFields.envContent, CustomFields.value
R3.
- Copy 버튼 필요: SSHKeyFields.passphrase(암호화) / SSHKeyFields.publicKey, SSLCertFields.renewCommand(비암호화)
R4.
- Create/Update 시 암호화 대상 값은 Secure(마스킹) TextField로: API Key Value, OAuth Client Secret, Service Account credentialJSON, Database Link String, SSH/SSL PassPhrase, Certificate, Certificate Chain, Private Key, EnvSet List, License Key, Custom Value
R5.
- 인증 시점: SecretDetailView 진입 시엔 인증 불필요, reveal 시·Secure 필드 복사 시에만 인증
✨ What’s this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
1. 컴포넌트 폭 정책 도입 (DVDesign)
DVComponentWidthPolicy(.fixed/.fill)를 Environment로 추가하고dvComponentWidth(_:height:alignment:)헬퍼로 7개 입력 컴포넌트에 적용.fixed라 기존 호출부는 한 줄도 바뀌지 않습니다. 폭이 늘어야 하는 화면에서만.fill을 주입DVMultiSelectDropdown의 팝오버 패널은 의도적으로 고정 폭 유지 — 트리거만 늘어나는 게 macOS에서 자연스럽습니다DVRadioButton읽기 전용 모드,DVChipsContainer(read-only chip),DVMultilineTextContainer추가2. 폼 레이아웃을 "화면 맥락 × 배열" 두 계층으로 분리
기존 단일
FormLayoutMode로는 생성 화면(단일 컬럼)과 조회 화면(3컬럼 중 detail 컬럼)의 요구가 충돌했습니다.FormLayoutContext(protocol, 화면 맥락) ×FormLayout(컨테이너 폭에서 파생된 실제 배열)로 분리StandaloneFormLayout(생성 화면) /DetailColumnFormLayout(조회 컬럼) 두 구현md(380) × 2 + 간격 16 + 좌우 패딩 40. 이보다 넓어지면 xs 듀얼 → md 듀얼로 승격3. 조회 전용 read-only 컴포넌트
DetailReadOnlyFieldView,DetailProjectFieldView,SecretDetailHeaderView,DetailSectionScaffoldViewKEY=value목록)은DVMultilineTextContainer로 자동 전환. 기존lineLimit(1)에서는 첫 줄만 보여 값을 읽을 수 없었습니다4. 타입별 payload 조회 섹션 9종 + 디스패처
Detail{APIKeysToken, OAuthClient, ServiceAccount, Database, SSHKey, SSLTLSCert, EnvSet, LicenseKey, Custom}SectionViewDetailPayloadSectionView가 11개(secretType, subType)조합을default없이 분기 — 타입이 늘면 컴파일 에러로 잡힙니다5. 기능 구현
SecretClient에setLiked·fetchLinkedProjects·revealPayload추가)6. 만료 임박 표기를 목록·조회 통일
SecretExpiryStatus(정책: 이미 만료 또는 3일 이내 →.critical, 7일 이내 →.upcoming) /DVExpiryEmphasis(표현: 아이콘·색)로 레이어 분리7. 라우팅 버그 수정
secretDetail·목록 선택을 해제. 생성 중에는 2컬럼이라 상세가 화면에서 사라지지만 State는 살아남아, 생성을 마치고 3컬럼으로 돌아오면 이전 상세가 되살아나고 Touch ID를 다시 요구했습니다🧪 테스트 / 검증 내역
develop리베이스 후 재검증 (develop..HEAD32커밋,HEAD..develop0)💬 기타 공유 사항
PreviewSupport/PreviewWidth.swift가 파일 전체#if DEBUG인데CreateSecretHeaderView·FooterActionsView등의#Preview블록은 감싸이지 않아 Release에서previewWidth를 찾지 못합니다. 이 PR에서 수정한 파일이 아니라 손대지 않았고, 별도 이슈로 정리가 필요합니다generate-local빌드에서 Touch ID가 실패합니다. ad-hoc 서명이라 온보딩·잠금을 통과할 수 없어, 화면 검증용으로#if DEBUGDebug 메뉴(⌥⌘M)를 임시로 넣었습니다. #64로 팀 시트가 해소되면 제거하도록 TODO를 걸어두었습니다. 원인 확정은LocalUserAuthenticationServiceImpl이 실제LAError를 버리고 있어 로그 추가가 필요하며, DVData 변경이라 이 PR 범위에서 제외했습니다credentialJSON·envContent도 여전히 한 줄 입력입니다. 파리티 원칙과 어긋나는 방향이라 별도 판단이 필요합니다notAfter파싱(SecCertificateCopyValues)은 이 PR 범위 밖으로 두고 별도 이슈로 분리했습니다revealPayload마다 인증이 뜹니다 — 의도된 현재 상태이고 별도 작업 예정입니다SecretListFeature.swift:152,155의 미사용 반환값 경고는 develop에서softDelete/restore가Secret을 반환하도록 바뀐 데서 오는 선행 경고입니다🙇🏻♀️ 리뷰 가이드 (선택)
대부분 생성 -> 조회 동작에서 이상 없는지 확인하면 충분함. 기타 이상해보이는 동작은, 노션에 메모해두면 추후 QA 기간 때 수정 예정
Form/FormLayout.swift,Form/FormLayoutContext.swift— 맥락 × 배열 2계층 설계와 816pt 임계값 산출Foundations/Sizing/DVComponentWidthPolicy.swift— 기본값.fixed로 기존 호출부에 영향이 없는지SecretDetail/Components/Sections/DetailPayloadSectionView.swift— 11개 조합 분기, 타입별 공통 메타 행 구성Support/SecretExpiryStatus.swift+Foundations/Expiry/DVExpiryEmphasis.swift— 정책/표현 분리 경계SecretDetail/Components/DetailReadOnlyFieldView.swift— 개행 감지 후 멀티라인 전환, 빈 값일 때 복사 버튼 숨김Main/MainFeature.swift— 생성 진입 시 상세 해제를 완료 시점이 아니라 진입 시점에 둔 이유Summary by CodeRabbit