|
2 | 2 |
|
3 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
4 | 4 |
|
5 | | -## Project Overview |
6 | | - |
7 | | -SNUTT is an iOS app for Seoul National University's timetable management system built with Swift using a modular architecture. The project uses Tuist for project management and generation. |
8 | | - |
9 | | -- **Language**: Swift 6.0 |
10 | | -- **Deployment Target**: iOS 17.0+ |
11 | | -- **Architecture Pattern**: Modular architecture with feature modules and interfaces |
12 | | - |
13 | 5 | ## Development Commands |
14 | 6 |
|
15 | | -### Environment Setup |
| 7 | +This project uses **Tuist** for project generation and **Just** for task automation. |
16 | 8 |
|
| 9 | +### Essential Setup Commands |
17 | 10 | ```bash |
18 | | -# Install prerequisites |
| 11 | +# Install prerequisites (if not already installed) |
19 | 12 | curl https://mise.run | sh |
20 | | -brew install just |
| 13 | +brew install just mint pre-commit swift-format |
21 | 14 |
|
22 | | -# Install dependencies |
| 15 | +# Install dependencies (mise will auto-select versions from .mise.toml) |
23 | 16 | mise install |
24 | 17 | tuist install |
25 | 18 |
|
26 | 19 | # Setup development environment |
27 | | -just dev # Generate dev OpenAPI + project |
28 | | -just prod # Generate prod OpenAPI + project |
| 20 | +just dev # Generate dev OpenAPI + project |
| 21 | +just prod # Generate prod OpenAPI + project |
29 | 22 | ``` |
30 | 23 |
|
31 | | -### Project Generation |
32 | | - |
| 24 | +### Common Development Tasks |
33 | 25 | ```bash |
34 | | -# Clean and regenerate |
35 | | -just clean-build # Clean build |
36 | | -just clean-generate # Clean generate |
37 | | - |
38 | | -# OpenAPI |
39 | | -just openapi-dev # Generate dev OpenAPI |
40 | | -just openapi-prod # Generate prod OpenAPI |
41 | | - |
42 | | -# Tuist commands |
| 26 | +# Project Generation & Building |
| 27 | +tuist generate # Generate Xcode project |
| 28 | +tuist clean # Clean Tuist cache |
43 | 29 | tuist install # Install dependencies |
44 | | -tuist generate # Generate project |
45 | | -tuist clean # Clean cache |
46 | | -tuist test # Run tests |
47 | | -tuist build # Build the project (use this to verify code changes) |
48 | | -``` |
49 | | - |
50 | | -## Project Architecture |
51 | | - |
52 | | -### Module Structure |
53 | | - |
54 | | -The codebase follows a modular architecture pattern with three main module categories: |
55 | | - |
56 | | -1. **Feature Modules**: Self-contained features with their own UI, business logic, and data access layers |
57 | | - - Examples: Timetable, Auth, Settings, Vacancy, Notifications |
| 30 | +tuist test # Run all tests |
| 31 | +tuist build # Build the project |
58 | 32 |
|
59 | | -2. **Feature Interface Modules**: Interfaces/contracts that define the public API of feature modules |
60 | | - - Examples: TimetableInterface, AuthInterface, APIClientInterface |
| 33 | +# Clean Operations |
| 34 | +just clean-build # Full clean build (clears DerivedData, installs deps, builds) |
| 35 | +just clean-generate # Clean generate (clears DerivedData, installs deps, generates) |
| 36 | +just clean-derived-data # Clear Xcode DerivedData only |
61 | 37 |
|
62 | | -3. **Utility and Shared Modules**: Reusable components and utilities |
63 | | - - **Shared**: Common UI components and app metadata |
64 | | - - **Utility**: Foundation extensions, SwiftUI utilities, etc. |
| 38 | +# Code Quality |
| 39 | +just format # Format Swift code using swift-format |
| 40 | +just check # Run all checks (dependencies, imports, formatting) |
65 | 41 |
|
66 | | -### Module Dependencies |
| 42 | +# OpenAPI Generation |
| 43 | +just openapi-dev # Generate dev environment OpenAPI |
| 44 | +just openapi-prod # Generate prod environment OpenAPI |
67 | 45 |
|
68 | | -Features can only depend on interfaces of other features, not their implementations. This enforces loose coupling between modules. The dependency rules are: |
69 | | - |
70 | | -- **Feature โ FeatureInterface**: A feature can only depend on interfaces of other features |
71 | | -- **Feature โ Utility/Shared**: Features can depend on utilities and shared components |
72 | | -- **FeatureInterface โ Utility**: Interfaces can depend on utilities |
73 | | -- **Utility โ Utility**: Utilities can depend on other utilities |
74 | | - |
75 | | -### Key Modules |
76 | | - |
77 | | -- **Timetable**: Core timetable functionality |
78 | | -- **APIClient**: OpenAPI-generated API client for backend communication |
79 | | -- **Auth**: Authentication and user management |
80 | | -- **Themes**: Theme management for the app |
81 | | -- **Settings**: App settings and preferences |
82 | | -- **Vacancy**: Feature related to course vacancies |
| 46 | +# Testing |
| 47 | +tuist test # Run all module tests |
| 48 | +# Test individual modules using Xcode schemes (e.g., "ModuleTests" scheme) |
| 49 | +``` |
83 | 50 |
|
84 | | -### External Dependencies |
| 51 | +### Build Configurations |
| 52 | +- **Dev**: Development configuration with debug symbols |
| 53 | +- **Prod**: Production/release configuration |
| 54 | +- The project uses scheme-based configuration switching |
85 | 55 |
|
86 | | -The project uses several third-party dependencies: |
87 | | -- Dependencies (Point-Free's dependency injection) |
88 | | -- OpenAPI Runtime and URLSession |
89 | | -- SwiftUI Introspect |
90 | | -- Firebase Core and Messaging |
91 | | -- KakaoMapsSDK |
| 56 | +## Architecture Overview |
92 | 57 |
|
93 | | -## Dependency Injection Pattern |
| 58 | +SNUTT follows a **strict modular architecture** with Clean Architecture principles: |
94 | 59 |
|
95 | | -### Interface Module Pattern |
| 60 | +### Module Categories |
| 61 | +1. **Feature Modules** (`Modules/Feature/`): Self-contained features with implementations |
| 62 | +2. **Feature Interface Modules** (`Modules/Feature/*Interface/`): Public contracts for features |
| 63 | +3. **Shared Modules** (`Modules/Shared/`): Reusable UI components and app-wide utilities |
| 64 | +4. **Utility Modules** (`Modules/Utility/`): Low-level utilities and helpers |
96 | 65 |
|
97 | | -Interface modules should follow this pattern to maintain clean separation between interface definitions and implementations: |
| 66 | +### Critical Dependency Rules |
| 67 | +- **Feature โ FeatureInterface**: Features can ONLY depend on interfaces of other features |
| 68 | +- **FeatureInterface โ Feature**: **STRICTLY FORBIDDEN** - Interfaces cannot depend on implementations |
| 69 | +- **Feature/FeatureInterface โ Utility/Shared**: Can depend on utilities and shared modules |
| 70 | +- No circular dependencies between modules at the same level |
98 | 71 |
|
99 | | -1. **Interface Module (e.g., VacancyInterface)**: |
100 | | - ```swift |
101 | | - @Spyable |
102 | | - public protocol SomeRepository: Sendable { |
103 | | - func someMethod() async throws -> ReturnType |
104 | | - } |
105 | | - |
106 | | - public enum SomeRepositoryKey: TestDependencyKey { |
107 | | - public static let testValue: any SomeRepository = { |
108 | | - let spy = SomeRepositorySpy() |
109 | | - spy.someMethodReturnValue = defaultValue |
110 | | - return spy |
111 | | - }() |
112 | | - } |
113 | | - |
114 | | - extension DependencyValues { |
115 | | - public var someRepository: any SomeRepository { |
116 | | - get { self[SomeRepositoryKey.self] } |
117 | | - set { self[SomeRepositoryKey.self] = newValue } |
118 | | - } |
119 | | - } |
120 | | - ``` |
| 72 | +### Clean Architecture Layers (within each feature) |
| 73 | +``` |
| 74 | +UI Layer (SwiftUI Views) |
| 75 | + โ |
| 76 | +Presentation Layer (ViewModels with @Observable) |
| 77 | + โ |
| 78 | +Business Logic Layer (Use Cases, Domain Models, Repository Protocols) |
| 79 | + โ |
| 80 | +Infrastructure Layer (Repository Implementations) |
| 81 | +``` |
121 | 82 |
|
122 | | -2. **Live Implementation (in LiveDependencies.swift)**: |
123 | | - ```swift |
124 | | - extension SomeRepositoryKey: @retroactive DependencyKey { |
125 | | - public static let liveValue: any SomeRepository = SomeAPIRepository() |
126 | | - } |
127 | | - ``` |
| 83 | +**Layer Rules:** |
| 84 | +- UI can ONLY depend on ViewModels (Presentation layer) |
| 85 | +- UI CANNOT directly access repositories or use cases |
| 86 | +- ViewModels use dependency injection to access business logic |
| 87 | +- Business logic depends on repository interfaces, not implementations |
| 88 | + |
| 89 | +### Dependency Injection Pattern |
| 90 | + |
| 91 | +The project uses **swift-dependencies** with the following pattern: |
| 92 | + |
| 93 | +**Interface Definition** (in FeatureInterface modules): |
| 94 | +```swift |
| 95 | +@Spyable |
| 96 | +public protocol SomeRepository: Sendable { |
| 97 | + func someMethod() async throws -> ReturnType |
| 98 | +} |
| 99 | + |
| 100 | +public enum SomeRepositoryKey: TestDependencyKey { |
| 101 | + public static let testValue: any SomeRepository = SomeRepositorySpy() |
| 102 | +} |
| 103 | + |
| 104 | +extension DependencyValues { |
| 105 | + public var someRepository: any SomeRepository { |
| 106 | + get { self[SomeRepositoryKey.self] } |
| 107 | + set { self[SomeRepositoryKey.self] = newValue } |
| 108 | + } |
| 109 | +} |
| 110 | +``` |
128 | 111 |
|
129 | | -### Key Principles |
| 112 | +**Live Implementation** (in Feature module's `LiveDependencies.swift`): |
| 113 | +```swift |
| 114 | +extension SomeRepositoryKey: DependencyKey { |
| 115 | + public static let liveValue: any SomeRepository = SomeAPIRepository() |
| 116 | +} |
| 117 | +``` |
130 | 118 |
|
131 | | -- **Interface modules** only define protocols and test dependencies using `TestDependencyKey` |
132 | | -- **Live implementations** are injected via `@retroactive DependencyKey` in `LiveDependencies.swift` |
133 | | -- This pattern ensures interface modules don't depend on concrete implementations |
134 | | -- Follows the same pattern as other modules (TimetableRepository, AuthRepository, etc.) |
| 119 | +**Usage in ViewModels:** |
| 120 | +```swift |
| 121 | +@Observable |
| 122 | +@MainActor |
| 123 | +public class FeatureViewModel { |
| 124 | + @Dependency(\.someRepository) private var someRepository |
| 125 | + // Implementation |
| 126 | +} |
| 127 | +``` |
135 | 128 |
|
136 | | -## Build Configurations |
| 129 | +### Key Features |
| 130 | +- **Timetable**: Core timetable functionality and UI components |
| 131 | +- **Auth**: Authentication with Kakao, Facebook, Google integrations |
| 132 | +- **Vacancy**: Classroom vacancy checking |
| 133 | +- **Themes**: UI theming system |
| 134 | +- **Notifications**: Push notification handling |
| 135 | +- **Settings**: App configuration and user preferences |
| 136 | +- **Reviews**: App store review integration |
| 137 | +- **Popup**: App-wide popup system |
| 138 | + |
| 139 | +## Technology Stack |
| 140 | + |
| 141 | +- **Language**: Swift 6.1+ with modern Swift Concurrency (`async/await`) |
| 142 | +- **UI Framework**: SwiftUI with Observation framework (`@Observable`, `@Bindable`) |
| 143 | +- **Minimum iOS Version**: 17.0+ |
| 144 | +- **Xcode Version**: 16.0+ |
| 145 | +- **Project Management**: Tuist for modular project generation |
| 146 | +- **Code Formatting**: swift-format with 120 character line length, 4-space indentation |
| 147 | +- **API Integration**: OpenAPI-generated client with separate dev/prod configurations |
| 148 | +- **Dependency Injection**: swift-dependencies package |
| 149 | +- **Maps**: Kakao Maps SDK |
| 150 | +- **Testing**: Built-in unit testing with Spyable for mock generation |
| 151 | + |
| 152 | +## Code Style & Conventions |
| 153 | + |
| 154 | +- **Formatting**: Automatic formatting via swift-format (configured in `.swift-format`) |
| 155 | +- **Pre-commit Hooks**: swift-format runs automatically on commit |
| 156 | +- **Sendable**: All repository protocols and implementations must be `Sendable` |
| 157 | +- **MainActor**: ViewModels should be annotated with `@MainActor` |
| 158 | +- **Async/Await**: Use Swift Concurrency for all asynchronous operations |
| 159 | +- **Observation**: Use `@Observable` macro for ViewModels instead of ObservableObject |
| 160 | + |
| 161 | +## File Structure Template |
| 162 | + |
| 163 | +Each feature should follow this structure: |
| 164 | +``` |
| 165 | +Modules/Feature/FeatureName/ |
| 166 | +โโ Sources/ |
| 167 | +โ โโ UI/ # SwiftUI Views |
| 168 | +โ โโ Presentation/ # ViewModels |
| 169 | +โ โโ BusinessLogic/ # Use Cases, Domain Models |
| 170 | +โ โโ Infra/ # Repository Implementations |
| 171 | +โ โโ Composition/ # Dependency Registration (LiveDependencies.swift) |
| 172 | +โโ Tests/ |
| 173 | + โโ UnitTests/ # Tests for all layers |
| 174 | +``` |
137 | 175 |
|
138 | | -The project has two main configurations: |
139 | | -- **Dev**: Development environment |
140 | | -- **Prod**: Production environment |
| 176 | +## OpenAPI Integration |
141 | 177 |
|
142 | | -Each configuration uses its own xcconfig file located in the XCConfigs directory. |
| 178 | +- API specs are automatically downloaded and generated |
| 179 | +- Separate configurations for dev and prod environments |
| 180 | +- Generated clients are located in `Modules/Feature/APIClientInterface/` |
| 181 | +- Use `just openapi-dev` or `just openapi-prod` to regenerate API clients |
143 | 182 |
|
144 | 183 | ## Widget Extension |
145 | 184 |
|
146 | | -The project includes a widget extension for iOS home screen widgets that displays timetable information. |
| 185 | +The project includes a widget extension (`SNUTTWidget`) with its own target and dependencies, primarily using timetable functionality. |
0 commit comments