Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .agents/skills/feature-ui-development/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: feature-ui-development
description: Todakun의 Projects/Feature 또는 Projects/App에서 SwiftUI 화면을 구현·수정·리뷰하거나 Figma·기획 화면을 기존 DesignSystem으로 조립할 때 적용합니다. TCA 화면 상태와 View 책임, 화면 전용 조합 View의 경계, DesignSystem public API 선택, 반응형 레이아웃·이미지·스크롤·safe area, 버튼 상호작용 및 실제 기기 렌더링 검증을 다룹니다. DesignSystem 모듈 자체 개발, Feature API 연동, 접근성 정책 수립에는 사용하지 않습니다.
---

# Feature UI Development

기획·Figma의 시각적 의도를 특정 캔버스의 고정 좌표가 아니라 SwiftUI 레이아웃으로 해석하고, 기존 DesignSystem과 TCA 경계를 지키며 화면을 구현한다.

## 책임 경계

- 코드나 파일을 변경하는 작업의 개시와 이슈 관리는 `project-management`를 따른다. 읽기 전용 설계·리뷰에는 변경 작업용 이슈 절차를 기계적으로 요구하지 않는다.
- 모듈·타겟·Feature 간 의존성은 `project-structure`를 따른다.
- DesignSystem 소스·Specification·Docs·Catalog·리소스를 변경하면 `design-system-development`로 전환한다.
- Feature API client·DTO·live dependency·네트워크 effect는 `feature-networking`으로 분리한다.
- 특정 Figma 도구나 추출 경로를 요구하지 않는다. 이용 가능한 방법으로 원본 화면과 필요한 상태를 확보한다.
- 접근성, 현지화, motion은 팀의 별도 요구사항이 있을 때만 해당 범위로 추가한다.

## 필요한 참조

- 화면이 여러 섹션, TCA 상태, 컬렉션을 포함하면 [view-composition.md](references/view-composition.md)를 읽는다.
- Figma 화면, 기기별 레이아웃, 이미지, 배경, 스크롤을 다루면 [adaptive-layout.md](references/adaptive-layout.md)를 읽는다.
- DesignSystem을 사용하는 모든 화면은 [design-system-consumption.md](references/design-system-consumption.md)를 읽는다.
- UI 변경 완료 전 [verification.md](references/verification.md)를 읽고 실제 렌더링을 검증한다.

## 구현 흐름

### 1. 화면 계약 파악

1. 구현할 화면, 진입점, 사용자 동작, loading·loaded·failed 등 필요한 상태를 확인한다.
2. 화면 요소를 다음으로 분류한다.
- 기존 DesignSystem 완성 컴포넌트
- DesignSystem의 공개 컬러·타이포그래피·아이콘·이미지·상호작용 API로 조립할 화면 전용 UI
- 여러 화면에서 반복될 가능성이 있는 신규 공용 컴포넌트 후보
- Feature 상태와 사용자 Action
- 다른 Feature 또는 App 조립 계층으로 전달할 이동 의도
3. Figma 수치를 `고정`, `유동`, `최솟값`, `비율`, `콘텐츠 기반`으로 분류한다. Figma 프레임의 결과값을 분류 없이 코드에 옮기지 않는다.
4. 원본에서 모호한 상태·동작·레이아웃은 다른 화면을 근거로 추측하지 않는다.

### 2. TCA와 공개 경계 결정

- State에는 화면 렌더링·동작·effect·테스트의 source of truth가 되는 값과, 화면 계약을 결정하는 immutable 입력·mode·identity를 둔다.
- 고정 섹션 제목, 고정 설명, spacing, radius 같은 표현 값은 State에 넣지 않는다.
- View는 상태를 렌더링하고 사용자 입력을 Action으로 전달한다. API 호출과 비즈니스 effect를 View에서 실행하지 않는다.
- 다른 Feature로의 이동이 필요하면 자식 Feature는 목적지 구현 타입을 import하지 않고 자기 도메인의 의미 기반 delegate를 내보낸다. 목적지 Feature 구현체를 사용하는 최종 전환과 조립은 App 레이어가 담당하며, 같은 Feature 도메인 내부의 child 화면 조립과 구분한다. 목적지 타입을 컴파일 시점에 참조해야 하는 실제 계약이 있을 때만 `project-structure`에 따라 Interface를 사용한다.
- 순수 시각 섹션마다 Reducer를 만들지 않는다. 독립 상태·effect·navigation 수명이 있을 때만 하위 Feature 분리를 검토한다.
- 외부 진입점만 public으로 두고 화면 내부 타입은 필요한 최소 접근 수준을 사용한다.

### 3. DesignSystem으로 화면 조립

- 화면과 의미가 일치하는 완성 컴포넌트가 있으면 public API를 사용한다.
- 완성 컴포넌트가 없고 화면에만 속하는 카드·배너·섹션이면 공개 DesignSystem API로 Feature 내부에서 조립한다.
- 이름이 비슷하다는 이유로 배경·상태·동작 계약이 다른 컴포넌트를 억지로 사용하지 않는다.
- Feature에서 DesignSystem 내부 구현이나 private/internal API를 복제하거나 의존하지 않는다.
- 누락된 공용 계약을 Feature의 수치 복사로 우회하지 않는다. 공용화가 필요하면 후보와 근거를 보고하고 승인된 범위에서 DesignSystem 작업으로 전환한다.

### 4. SwiftUI 레이아웃 구성

- 부모가 제안하는 크기, 콘텐츠 intrinsic size, 명시적으로 합의된 컴포넌트 규격을 우선한다.
- 화면·시트·일반 섹션 너비를 Figma 캔버스나 특정 기기 너비로 고정하지 않는다.
- padding·spacing·radius·아이콘·의도된 카드 규격은 역할이 명확하면 고정 디자인 값으로 사용할 수 있다.
- 고정 높이는 텍스트 변화와 작은 화면에서 잘림을 만드는지 확인하고, 가능하면 콘텐츠 기반 크기나 `minHeight`를 우선 검토한다.
- 이미지의 표시 목적을 콘텐츠, 배경, 장식으로 구분하고 크기 기준과 content mode를 명시한다.
- `GeometryReader`는 부모 크기가 실제 계산 입력일 때만 좁은 범위에서 사용한다.
- modifier 순서가 시각 영역, clip, content shape, pressed overlay, hit area에 미치는 영향을 확인한다.

### 5. 상호작용 연결

- 단일 탭 액션은 기본적으로 `Button`을 사용하고 Store Action으로 연결한다.
- 카드 전체와 내부 액션 중 실제 탭 범위를 디자인과 동작 의미에 맞게 결정한다.
- 특정 최소 터치 크기를 일괄 강제하지 않는다. 필요하면 시각 영역과 터치 영역을 분리하되 인접 액션과 겹치지 않게 한다.
- 공용 pressed 효과는 화면 루트가 아니라 해당 surface 또는 icon에 선택적으로 적용한다.
- `onTapGesture`는 Button으로 표현할 수 없는 제스처가 실제 요구될 때만 사용한다.

### 6. 완료 검증

- reducer를 변경했다면 Action과 상태 전이를 TestStore로 검증한다.
- 화면 fixture는 production 기본 State에 넣지 않고 Example 또는 Test에 둔다.
- Feature 단독 UI는 대상 Feature 테스트와 FeatureExample에서, App 소유 tab·navigation·safe area 조립은 App host에서 검증한다.
- 프로젝트 코드 변경 후 `./scripts/sync-and-validate.sh`를 실행한다.
- 작은 화면, 디자인 기준 화면, 큰 화면에서 실제 렌더링을 확인한다.
- 최신 바이너리를 설치한 뒤 기존 프로세스를 종료하고 다시 실행하여, 새 실행 화면을 screenshot과 UI hierarchy로 확인한다.
- 빌드 성공이나 프로세스 존재만으로 사용자에게 보이는 화면이 최신이라고 판단하지 않는다.

## 금지 사항

- `UIScreen.main.bounds` 또는 Figma 캔버스 너비로 화면 전체 레이아웃 계산
- 텍스트 컨테이너를 측정된 Figma width·height로 이유 없이 고정
- `body` 평가마다 UUID 생성 또는 배열 index를 `ForEach` identity로 사용
- 화면 전용 View를 근거 없이 DesignSystem public 컴포넌트로 승격
- Feature가 다른 Feature Implementation을 import
- production State에 Example mock 콘텐츠 삽입
- DesignSystem과 동일한 색상·타이포그래피·pressed 값을 Feature에 복제
- 오래 실행 중인 앱을 최신 빌드로 간주하고 실제 재실행 검증 생략
4 changes: 4 additions & 0 deletions .agents/skills/feature-ui-development/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Feature UI Development"
short_description: "DesignSystem 기반 SwiftUI Feature 화면 구현 가이드"
default_prompt: "Use $feature-ui-development to implement and verify a Todakun Feature screen with the existing DesignSystem."
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Adaptive Layout

## Figma 수치 분류

| 수치의 역할 | 기본 구현 |
| --- | --- |
| padding, spacing, radius | 디자인 상수로 사용 가능 |
| 아이콘, 고정 badge, 명확한 control 규격 | 의도된 고정 크기 사용 가능 |
| 가로 carousel의 카드 규격 | 반복 리듬이 요구되면 고정 가능 |
| 화면, 시트, 일반 section width | 부모가 제안한 너비 사용 |
| 텍스트 container height | 콘텐츠 기반 우선 |
| 카드 높이 | 고정보다 intrinsic 또는 `minHeight` 검토 |
| 이미지 | 원본 비율과 crop 정책 명시 |
| safe area와 tab bar 공간 | 실제 소유 계층에서 처리 |

고정값을 없애는 것이 목적이 아니다. 수치가 디자인 계약인지 특정 Figma 캔버스에서 나온 결과인지 구분한다.

## 컨테이너

- 기본 화면 너비는 부모가 제안한다. `UIScreen.main.bounds`를 레이아웃 입력으로 사용하지 않는다.
- 일반 세로 화면은 유동 width의 root, 명시적인 horizontal padding, 콘텐츠 기반 height로 시작한다.
- 자식의 큰 intrinsic size가 부모 너비를 밀어내는지 확인한다. 큰 이미지와 긴 고정 frame을 먼저 의심한다.
- `GeometryReader`는 부모 치수를 계산에 사용하는 배경, overlay, 비율 기반 배치 등에만 격리한다. 일반 콘텐츠 root로 사용해 크기를 강제하지 않는다.

## 이미지

이미지마다 다음을 결정한다.

1. 원본 크기를 유지하는가, `resizable`인가?
2. 비율을 유지하는가, 의도적으로 변형하는가?
3. 전체가 보여야 하는가, crop을 허용하는가?
4. 너비와 높이 중 무엇이 기준인가?
5. 이미지가 레이아웃 크기를 결정하는가, 배경으로만 그려지는가?

- 콘텐츠 이미지는 일반적으로 비율을 유지하고 부모가 허용한 영역 안에서 표시한다.
- 장식 배경은 기본적으로 스크롤 콘텐츠의 intrinsic size를 결정하지 않게 분리한다.
- 장식 배경의 크기는 viewport, container width, 고정 비율 중 디자인 의도에 맞는 기준으로 결정한다.
- 이미지 이후 남는 영역을 단색 배경이 채우는 디자인이라면 이미지 높이를 스크롤 높이에 맞춰 확대하지 않는다.
- 전체 콘텐츠와 함께 이어지는 패턴·그라데이션처럼 배경이 콘텐츠 높이를 따라야 하는 디자인은 예외이며 요구사항을 확인한다.
- 원본 asset 비율을 코드에 적어야 한다면 그 값이 화면 크기가 아니라 asset metadata라는 의미를 이름으로 드러낸다.

## 스크롤과 safe area

- 배경, scroll content, overlay, tab bar의 레이어와 크기 책임을 분리한다.
- Feature는 App이 소유한 MainTab이나 BottomNavigation을 내부에 중복 배치하지 않는다.
- tab bar로 인한 하단 공간은 최종 조립 계층이 소유한다.
- header가 safe area 안쪽인지 배경만 safe area를 무시하는지 구분한다.
- 고정 전체 높이로 스크롤 가능 여부를 제어하지 않는다. 콘텐츠가 작은 화면에서 자연스럽게 스크롤되는지 확인한다.

## modifier 순서

다음 순서를 의식적으로 결정한다.

- content layout
- padding과 visual frame
- background·overlay
- clip shape
- content shape
- pressed ButtonStyle
- 추가 hit area

시각 영역보다 큰 hit area가 필요하면 pressed style이 보는 label 크기와 바깥 hit area를 분리한다. modifier 순서를 바꾼 뒤에는 screenshot과 실제 터치로 확인한다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# DesignSystem Consumption

## 탐색 원칙

모든 요소에 동일한 탐색 비용을 강제하지 않는다. 먼저 완성 컴포넌트인지 기초 API인지 분류한다.

### 완성 컴포넌트 후보

Button, Header, SelectField, Dialog, BottomNavigation처럼 완성 컴포넌트 후보가 있으면 다음 순서를 따른다.

1. 화면 원본에서 요소의 역할과 상태를 파악한다.
2. `Projects/Core/DesignSystem/Docs/Figma_Specification.md`의 이미지와 구현 매핑으로 시각적·의미적 후보를 찾는다.
3. `Projects/Core/DesignSystem/Sources/Components`의 public 선언에서 실제 initializer, variant, size, state, Binding, action 입력을 확인한다.
4. 배경·상태·레이아웃·동작 계약이 모호할 때만 관련 `Docs/Components/*.md`를 읽는다.
5. presentation이나 상태 조립이 복잡할 때만 `DesignSystem/Example/Sources/Playgrounds`를 참고한다.

Docs는 디자인 의미를, public 코드는 실제 사용 가능한 API를 판단한다. Example은 사용 예시이며 SSOT가 아니다.

### 기초 공개 API

컬러, 타이포그래피, 아이콘, 이미지, 공용 pressed API는 public 코드와 Catalog에서 직접 탐색한다. 상세 Component Docs 확인을 기계적으로 요구하지 않는다.

- `DesignSystemColor.swift`
- `DesignSystemFont.swift`
- `DesignSystemIcon.swift`
- `DesignSystemImage.swift`
- `Components/Common`

### 화면 전용 조합 UI

- 동일한 완성 DS 컴포넌트가 없음을 확인한다.
- 공개 컬러·타이포그래피·아이콘·이미지·shape·pressed API로 Feature 내부에서 조립한다.
- 모든 Figma group을 별도 View로 만들지 않고 의미 있는 섹션, 반복되는 카드, 독립 상호작용 단위만 추출한다.
- 한 화면에서만 쓰인다는 이유로 모든 값을 raw literal로 복제하지 않고 기존 공개 token과 helper를 우선한다.
- Feature 고유 값에 공용 token이 없으면 의미 있는 로컬 이름으로 둘 수 있다.
- 한 화면에서만 쓰면 private, 같은 Feature의 여러 화면에서 재사용하면 Feature-internal로 둔다. 여러 Feature에서 반복되거나 공용 계약으로 승인될 때 DesignSystem 후보가 된다.

## 이미지 리소스 소유권

- **Feature 전용 이미지**: 특정 Feature에서만 소비하는 이미지/일러스트 에셋은 해당 Feature의 `Resources/Assets.xcassets` 하위에 두고 `Project.swift`에 `resources: ["Resources/**"]`를 선언한다. Tuist가 생성하는 Feature 전용 typed accessor(예: `{Feature}Asset.Images.*.swiftUIImage`)를 사용하며 raw string 입력을 금지한다.
- **Cross-feature 공용 이미지**: 온보딩, 브랜드 탭, 복수 Feature 간 공유가 확인된 에셋(예: `fortuneLogo`, `fortuneSpaceBackground`, `chevronSmallRight`)만 DesignSystem의 `DSImageAsset` / `DSIconAsset` 등의 typed public API로 관리한다.
- 신규 이미지가 추가되거나 리소스 소유권이 조정될 때는 Figma 재사용 분석에 기반하여 Feature 전용 에셋과 Cross-feature 공용 에셋을 분리하고 해당 모듈의 asset bundle test를 작성한다.

## 불일치와 누락

- Docs의 디자인 계약과 public 코드가 다르면 Feature에서 수치나 동작을 복제해 우회하지 않는다.
- 현재 컴파일 가능한 계약은 public 코드가 결정하지만, 디자인 불일치는 별도 DesignSystem 결함으로 보고한다.
- 공용 컴포넌트 또는 리소스 변경이 필요하면 `design-system-development` 절차를 적용한다.
- Tuist가 생성한 Derived source를 직접 수정하지 않는다.

## 상호작용 API

- DesignSystem 완성 control이 interaction을 이미 소유하면 외부에서 중복 Button이나 gesture를 감싸지 않는다.
- 화면 전용 surface Button에는 `dsSurfaceButtonStyle(shape:)`를 선택적으로 사용한다.
- 화면 전용 icon Button에는 `dsIconButtonStyle(_:width:height:)`를 선택적으로 사용한다.
- pressed overlay의 색상·opacity를 Feature가 복제하지 않는다.
- 공용 ButtonStyle을 화면 root에 일괄 적용하지 않는다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# UI Verification

## 정적 검증

변경 범위에 맞는 최소 검증을 실행한다.

| 변경 범위 | 필수 검증 |
| --- | --- |
| SwiftUI View만 변경 | 변경 Swift 파일 SwiftLint, 소유 target 빌드 |
| Feature reducer·State·Action 변경 | 관련 TestStore 테스트 추가·실행 |
| Feature 단독 화면 변경 | FeatureExample이 있으면 빌드·실행하여 isolated rendering 확인 |
| App의 tab·navigation·safe area 조립 변경 | App target 빌드·실행하여 integration rendering 확인 |
| workspace 재생성이 필요한 설정 변경 | `mise exec -- tuist generate --no-open` |
| `Projects/App`, `Projects/Feature`, `Projects/Core`, `Tuist`, `Project.swift`, `docs/graph.dot` 변경 | 최종 `./scripts/sync-and-validate.sh` |

DesignSystem 소스 또는 리소스도 변경했다면 `design-system-development`의 추가 테스트와 asset 검증을 따른다.

## 실제 화면 검증

화면 전체·스크롤·이미지·텍스트 레이아웃을 변경했다면 최소 다음 폭 범주를 확인한다. 국소적인 색상·아이콘 교체는 영향 범위에 맞게 줄일 수 있다.

- 작은 지원 화면
- Figma 기준 화면과 가까운 화면
- 더 큰 화면

각 화면에서 다음을 확인한다.

- 좌우 edge와 의도한 horizontal inset
- 긴 텍스트와 여러 줄 텍스트의 잘림·겹침
- 이미지 비율, crop, 빈 배경 영역
- 흰 sheet나 card가 부모 너비를 넘지 않는지
- header와 status bar, bottom content와 tab bar 관계
- 최초 화면과 스크롤 후 화면
- 각 Button의 실제 탭 범위와 pressed surface
- loading·loaded·failed 상태가 있다면 각 상태

## 최신 바이너리 확인

UI 변경 후 검증은 다음 상태를 구분한다.

1. 소스가 수정됨
2. 새 바이너리 빌드가 성공함
3. 새 바이너리가 대상 simulator에 설치됨
4. 이전 앱 프로세스가 종료됨
5. 새 앱 프로세스가 실행됨
6. 사용자가 보는 simulator pane이 새 화면을 표시함

빌드 성공, PID 존재, UI hierarchy 접근 중 하나만으로 최신 화면이라고 결론 내리지 않는다. 마지막에는 새 실행의 screenshot과 UI hierarchy를 함께 확인한다. simulator·pane을 유지하라는 요청이 있으면 검증 후 임의 종료하지 않는다.

## 판정 방식

- Figma의 모든 좌표를 pixel 단위로 일치시키는 것을 목표로 하지 않는다.
- 디자인 의도, 컴포넌트 규격, 정렬, 간격, 비율, 상호작용 범위를 비교한다.
- Debug layout inspector가 있으면 App 또는 Example root에 이미 설치된 진입점을 사용하고 Feature View에 중복 설치하지 않는다.
- 검증에서 발견한 회귀와 요구사항 밖의 관찰을 구분해 보고한다.
Loading