Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7da0412
분석 도구 안내에 Sentry 사용을 명시한다
Roy-wonji Sep 29, 2026
958a251
앱 내비게이션 코디네이터 이름에서 App 접두사를 제거한다
Roy-wonji Sep 30, 2026
e2095f6
클래스 화면이 공통 도메인 계약을 통해 동작하도록 한다
Roy-wonji Sep 30, 2026
6cbc9fa
클래스 탭 아이콘과 인트로 이미지 에셋을 추가한다
Roy-wonji Sep 30, 2026
27be6da
클래스 탭과 인트로 화면을 추가한다
Roy-wonji Sep 30, 2026
fb35ef4
클래스 목 저장소의 상태를 화면 간에 유지한다
Roy-wonji Sep 30, 2026
3a7c013
탭바 외형과 탭 라벨을 공용 UI 로 분리한다
Roy-wonji Sep 30, 2026
55be0f1
클래스 조건 선택과 배틀 카드에 필요한 도메인 정보를 제공한다
Roy-wonji Sep 30, 2026
97e1d23
입력 필드와 선택 칩, 박스형 세그먼트를 디자인 시스템에 추가한다
Roy-wonji Sep 30, 2026
b28ea2f
클래스 주제·조건 선택 화면을 추가한다
Roy-wonji Sep 30, 2026
e797463
입력 필드와 박스형 세그먼트를 모디파이어 체이닝 방식으로 바꾼다
Roy-wonji Sep 30, 2026
e4ccecb
sub-view 는 자식 수에 따라 func / var 로 나눈다
Roy-wonji Sep 30, 2026
a813cca
원형 체크박스와 캡슐형 조건 뱃지를 디자인 시스템에 추가한다
Roy-wonji Sep 30, 2026
1e5a48a
클래스 추천 배틀 화면을 추가한다
Roy-wonji Sep 30, 2026
64fd9fd
스위치 토글 스타일을 디자인 시스템에 추가한다
Roy-wonji Sep 30, 2026
9f57e43
클래스 설정 화면을 추가한다
Roy-wonji Sep 30, 2026
b9d79a3
클래스 참여와 내 클래스 화면을 추가한다
Roy-wonji Sep 30, 2026
7cd812b
클래스 공유 화면을 추가한다
Roy-wonji Sep 30, 2026
84230c7
클래스 화면 테스트의 TCA 액션 수신을 수정한다
Roy-wonji Sep 30, 2026
e48dd46
클래스 참여와 상세 화면을 앱 경로에 연결한다
Roy-wonji Sep 30, 2026
c79b9fb
클래스 비동기 Reducer 구조를 기존 모듈에 맞춘다
Roy-wonji Sep 30, 2026
31fe746
클래스 마감일 변경과 삭제를 목 저장소에 반영한다
Roy-wonji Sep 30, 2026
8c84141
클래스 참여 코드 확인 후 이름 입력 모달을 순차로 띄운다
Roy-wonji Oct 1, 2026
9844a97
클래스 참여 코드와 이름 입력 모달을 분리한다
Roy-wonji Oct 1, 2026
f438565
내 클래스 역할 탭을 진행 상태 필터 칩으로 바꾼다
Roy-wonji Oct 1, 2026
6ca5383
클래스 추천 콘텐츠 로딩 스켈레톤과 에러 재시도를 추가한다
Roy-wonji Oct 1, 2026
3cd3314
클래스 주제·설정 화면 제목 행간과 난이도 세그먼트 전환을 피그마에 맞춘다
Roy-wonji Oct 1, 2026
e247355
클래스 리포트 화면을 피그마 구성에 맞춤
Roy-wonji Oct 1, 2026
20cc6df
클래스 소개·추천·설정 화면 간격과 입력 안내 폰트를 피그마에 맞춘다
Roy-wonji Oct 1, 2026
b7207cd
클래스 멤버와 리포트 문구를 피그마에 맞춤
Roy-wonji Oct 1, 2026
602d923
클래스 화면의 탭바 표시와 홈 복귀 경로를 수정
Roy-wonji Oct 1, 2026
d4ba3c7
클래스 상세 모달 상태 리듀서를 추가
Roy-wonji Oct 1, 2026
900cdab
클래스 소개 화면에서 탭바 숨김을 코디네이터에 맡긴다
Roy-wonji Oct 1, 2026
be364a0
클래스 관리 모달과 멤버 화면에서 탭바를 숨긴다
Roy-wonji Oct 1, 2026
6815262
앱 버전과 배포 노트를 갱신한다
Roy-wonji Oct 1, 2026
a942718
배틀 리포트 탭 전환과 차트 애니메이션을 개선한다
Roy-wonji Oct 1, 2026
8b4b578
채팅 메시지 타임라인 계산을 유스케이스로 분리한다
Roy-wonji Oct 1, 2026
9e72ae1
클래스 운영자 화면과 모달을 추가한다
Roy-wonji Oct 1, 2026
cb61493
PR 리뷰 지적 사항 수정
Roy-wonji Oct 1, 2026
7881f6f
클래스 화면의 데이터 상태와 디자인을 정리한다
Roy-wonji Oct 2, 2026
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
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
--swiftversion 6.0
--maxwidth 120
--disable redundantReturn
--disable redundantViewBuilder
58 changes: 20 additions & 38 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,57 +148,39 @@ public var body: some View {
- 한 메서드 안에서 다시 큰 블록이 생기면 더 작게 쪼개기 (재귀 적용)
- 공통 컴포넌트는 별도 파일 (`Components/*.swift`) 로 추출

#### 🧱 `@ViewBuilder` + `private func` — 모든 sub-view 는 함수 형태 (필수)
#### 🧱 sub-view 형태 — 자식 2개 이상 stack 은 `@ViewBuilder func`, 단일 뷰는 `var` (필수)

분리한 sub-view 는 **자식 개수 / 분기 유무와 상관없이 무조건 `@ViewBuilder` + `private func` 형태** 로 통일한다.
분리한 sub-view 는 **자식 개수로 형태를 정한다**.

`private var ...: some View` 형태는 **금지** — 모든 sub-view 는 호출부에서 일관되게 `()` 호출이 보이도록 함수 형태로만 작성한다.
- stack / grid 안에 자식이 **2개 이상** (`ForEach` 반복, `if` 분기 포함) → `@ViewBuilder private func name() -> some View`
- **단일 뷰** (Button 하나, Text 하나, 이미 만든 컴포넌트 하나) → `private var name: some View`
- 파라미터가 필요하면 자식 수와 상관없이 func (`func thumbnail(url:)`)

```swift
// ✅ 다중 자식 / 분기 / 반복
// ✅ 자식 2개 이상 — @ViewBuilder func
@ViewBuilder
private func hotBattlesSection() -> some View {
VStack(alignment: .leading, spacing: 12) {
HomeSectionHeader(title: "지금 뜨는 배틀") { send(.seeMoreTapped(.hotBattles)) }
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(store.hotBattles) { HotBattleCardView(battle: $0) }
}
}
private func titleSection() -> some View {
VStack(alignment: .leading, spacing: 6) {
Text("어떤 주제로\n이야기 나눌까요?")
Text("관심 있는 주제나 수업 조건을 선택해 주세요.")
}
}

@ViewBuilder
private func thumbnail(url: URL?) -> some View {
if let url {
KFImage(url).resizable().scaledToFill()
} else {
Color.neutral200
// ✅ 단일 뷰 — var
private var searchButton: some View {
Button {
send(.searchTapped)
} label: {
Text("조건에 맞는 콘텐츠 보기")
}
.ctaButtonStyle(.primary, size: .large, height: 52)
}

// ✅ 단일 뷰여도 함수 형태로
@ViewBuilder
private func primaryButton() -> some View {
CustomButton(
action: { send(.primaryButtonTapped) },
title: "사전 투표하기",
config: CustomButtonConfig.primary(.large, height: 52),
isEnable: store.isPrimaryButtonEnabled
)
}

// ❌ 금지 — sub-view 를 var 로 선언
private var primaryButton: some View { ... }
private var section: some View { VStack { ... } }
// ❌ 금지 — 단일 뷰를 func 로, 다중 자식을 var 로
private func searchButton() -> some View { Button { ... } }
private var titleSection: some View { VStack { Text(...); Text(...) } }
```

규칙:
- **모든 sub-view = `@ViewBuilder private func name() -> some View`** (단일 뷰 / 다중 자식 / 분기 무관)
- `private var ...: some View` 패턴 금지 — 호출부에서 `name` vs `name()` 형태 혼재되는 것을 방지
- `body` 만 `var body: some View` 유지 (View 프로토콜 요구사항)
- body 안에서 호출은 항상 `()` 가 붙은 함수 형태로 — 가독성 통일

#### 📏 함수 시그니처 — 파라미터 2개 이상이면 멀티 라인 (필수)

함수 파라미터가 **2개 이상이면 각 파라미터를 새 줄에** 풀어 쓴다. 인라인 한 줄 시그니처는 1-파라미터 이하에서만 허용.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum FeatureModule: String, CaseIterable {
case hifi = "Hifi"
case web = "Web"
case battle = "Battle"
case classroom = "Class"
case profile = "Profile"
case notification = "Notification"
case ad = "Ad"
Expand Down Expand Up @@ -64,6 +65,7 @@ public enum DomainModule: String, CaseIterable {
case attendance = "AttendanceDomain"
case auth = "AuthDomain"
case battle = "BattleDomain"
case classroom = "ClassDomain"
case comment = "CommentDomain"
case ad = "AdDomain"
case home = "HomeDomain"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import Foundation
import ProjectDescription

public extension String {
static func appVersion(version: String = "1.1.0") -> String {
static func appVersion(version: String = "1.1.2") -> String {
return version
}

static func mainBundleID() -> String {
return Project.Environment.bundlePrefix
}

static func appBuildVersion(buildVersion: String = "2609141641") -> String {
static func appBuildVersion(buildVersion: String = "2610012046") -> String {
return buildVersion
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AppAuthCoordinator.swift
// AuthCoordinator.swift
// Picke
//

Expand All @@ -9,13 +9,13 @@ import ComposableArchitecture
import FeatureAssembly
import TCAFlow

@FlowCoordinator(screen: "AppAuthScreen", navigation: true)
public struct AppAuthCoordinator {
@FlowCoordinator(screen: "AuthScreen", navigation: true)
public struct AuthCoordinator {
public init() {}

@ObservableState
public struct State: Equatable {
var routes: [Route<AppAuthScreen.State>]
var routes: [Route<AuthScreen.State>]

public init() {
routes = [.root(.login(.init()), embedInNavigationView: true)]
Expand All @@ -24,7 +24,7 @@ public struct AppAuthCoordinator {

@CasePathable
public enum Action {
case router(IndexedRouterActionOf<AppAuthScreen>)
case router(IndexedRouterActionOf<AuthScreen>)
case view(View)
case navigation(NavigationAction)
}
Expand Down Expand Up @@ -56,10 +56,10 @@ public struct AppAuthCoordinator {
}
}

private extension AppAuthCoordinator {
private extension AuthCoordinator {
func routerAction(
state: inout State,
action: IndexedRouterActionOf<AppAuthScreen>
action: IndexedRouterActionOf<AuthScreen>
) -> Effect<Action> {
switch action {
case .routeAction(_, action: .login(.delegate(.presentOnboarding))):
Expand Down Expand Up @@ -101,9 +101,9 @@ private extension AppAuthCoordinator {
}

// swiftformat:disable extensionAccessControl
extension AppAuthCoordinator {
extension AuthCoordinator {
@Reducer
public enum AppAuthScreen {
public enum AuthScreen {
case login(LoginFeature)
case onboarding(OnBoardingFeature)
case web(WebReducer)
Expand All @@ -112,4 +112,4 @@ extension AppAuthCoordinator {

// swiftformat:enable extensionAccessControl

extension AppAuthCoordinator.AppAuthScreen.State: Equatable {}
extension AuthCoordinator.AuthScreen.State: Equatable {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AppAuthCoordinatorView.swift
// AuthCoordinatorView.swift
// Picke
//

Expand All @@ -9,10 +9,10 @@ import ComposableArchitecture
import FeatureAssembly
import TCAFlow

public struct AppAuthCoordinatorView: View {
@Bindable private var store: StoreOf<AppAuthCoordinator>
public struct AuthCoordinatorView: View {
@Bindable private var store: StoreOf<AuthCoordinator>

public init(store: StoreOf<AppAuthCoordinator>) {
public init(store: StoreOf<AuthCoordinator>) {
self.store = store
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AppBattleCoordinator.swift
// BattleCoordinator.swift
// Picke
//

Expand All @@ -12,15 +12,15 @@ import PickeDesignKit
import FeatureAssembly
import TCAFlow

@FlowCoordinator(screen: "AppBattleScreen", navigation: true)
public struct AppBattleCoordinator {
@FlowCoordinator(screen: "BattleScreen", navigation: true)
public struct BattleCoordinator {
public init() {}

@Dependency(\.audioPlayer) private var audioPlayer

@ObservableState
public struct State: Equatable {
public var routes: [Route<AppBattleScreen.State>]
public var routes: [Route<BattleScreen.State>]

public init() {
routes = [.root(.battle(.init()), embedInNavigationView: true)]
Expand All @@ -29,7 +29,7 @@ public struct AppBattleCoordinator {

@CasePathable
public enum Action {
case router(IndexedRouterActionOf<AppBattleScreen>)
case router(IndexedRouterActionOf<BattleScreen>)
case view(View)
case async(AsyncAction)
case inner(InnerAction)
Expand Down Expand Up @@ -61,10 +61,10 @@ public struct AppBattleCoordinator {
}
}

private extension AppBattleCoordinator {
private extension BattleCoordinator {
func routerAction(
state: inout State,
action: IndexedRouterActionOf<AppBattleScreen>
action: IndexedRouterActionOf<BattleScreen>
) -> Effect<Action> {
switch action {
case let .routeAction(_, action: .battle(.delegate(.openBattle(battleId)))):
Expand Down Expand Up @@ -127,9 +127,9 @@ private extension AppBattleCoordinator {
}

// swiftformat:disable extensionAccessControl
extension AppBattleCoordinator {
extension BattleCoordinator {
@Reducer
public enum AppBattleScreen {
public enum BattleScreen {
case battle(BattleFeature)
case chatRoom(ChatRoomFeature)
case chat(ChatCoordinator)
Expand All @@ -138,4 +138,4 @@ extension AppBattleCoordinator {

// swiftformat:enable extensionAccessControl

extension AppBattleCoordinator.AppBattleScreen.State: Equatable {}
extension BattleCoordinator.BattleScreen.State: Equatable {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AppBattleCoordinatorView.swift
// BattleCoordinatorView.swift
// Picke
//

Expand All @@ -9,10 +9,10 @@ import ComposableArchitecture
import FeatureAssembly
import TCAFlow

public struct AppBattleCoordinatorView: View {
@Bindable private var store: StoreOf<AppBattleCoordinator>
public struct BattleCoordinatorView: View {
@Bindable private var store: StoreOf<BattleCoordinator>

public init(store: StoreOf<AppBattleCoordinator>) {
public init(store: StoreOf<BattleCoordinator>) {
self.store = store
}

Expand Down
Loading
Loading