This file provides context for Claude Code when working on the iTea project.
iTea is a native iOS/macOS client for self-hosted Gitea instances. It's built with SwiftUI and uses Mac Catalyst to run on macOS from a single codebase.
iTea/
├── GiteaApp.swift # App entry point
├── Core/
│ ├── Authentication/ # Auth state & secure storage
│ ├── Configuration/ # App constants
│ ├── Navigation/ # Deep link handling
│ └── Networking/ # API client & endpoints
├── Models/ # Data models (Codable, Sendable)
├── Services/ # API wrappers by domain
├── Utilities/ # Helpers (Keychain, URL parsing)
└── Views/
├── App/ # Root views (ContentView, MainTabView)
├── Authentication/ # Login flow
├── Components/ # Reusable UI components
├── Issues/ # Issue list, detail, create
├── Notifications/ # Notification list
├── PullRequests/ # PR list, detail, create
├── Repositories/ # Repository list, detail
└── Settings/ # Settings, licenses
Use conditional compilation for iOS vs macOS differences:
#if targetEnvironment(macCatalyst)
macOSLayout
#else
iOSLayout
#endif- Uses UIKit, not AppKit:
Color(uiColor: .separator)notColor(nsColor:) - NavigationStack toolbar buttons get cramped - use custom VStack layouts for modals
- Sheets need
.presentationSizing(.fitted)to respect custom frame sizes - Keyboard shortcuts:
.keyboardShortcut(.cancelAction)for Cancel,.keyboardShortcut(.defaultAction)for primary action
@StateObject/@EnvironmentObjectforAuthenticationManager@Observablefor newer patterns likeDeepLinkHandlerActorfor thread-safe storage (TokenStorage)
Each domain has a Service that wraps APIClient:
struct IssueService {
let apiClient: APIClient
func getIssues(...) async throws -> [Issue] {
try await apiClient.request(.listIssues(...))
}
}All models conform to: Codable, Identifiable, Hashable, Sendable
Important: Always use the latest iOS simulator available (currently iOS 26.2 on iPhone 17 Pro).
# Build for Mac Catalyst
xcodebuild -project "iTea.xcodeproj" \
-scheme "iTea" \
-destination "platform=macOS,variant=Mac Catalyst" \
build
# Build for iOS Simulator
xcodebuild -project "iTea.xcodeproj" \
-scheme "iTea" \
-destination "platform=iOS Simulator,name=iPhone 17 Pro" \
build- GitHub:
https://github.com/philippgerard/itea - GPG signing may fail - use
--no-gpg-signif needed
- Add endpoint case to
Core/Networking/APIEndpoint.swift - Add method to appropriate Service in
Services/ - Call from View using
async/await
- Create in appropriate
Views/subdirectory - For modals on macOS, create separate
macOSLayoutandiOSLayoutcomputed properties - Use
.presentationSizing(.fitted)on sheets for macOS
- Create in
Models/ - Conform to
Codable, Identifiable, Hashable, Sendable - Use
CodingKeysif API field names differ
- MarkdownUI (
swift-markdown-ui) - Markdown rendering in issues/PRs/comments
- Views:
*View.swift(e.g.,IssueListView.swift,CreateIssueView.swift) - Row views:
*RowView.swift(e.g.,IssueRowView.swift) - Services:
*Service.swift - Models: Singular noun (e.g.,
Issue.swift,PullRequest.swift)
Always use the latest Gitea API documentation: https://docs.gitea.com/api/1.24/
Key endpoints used:
- Notifications:
GET/PUT /notifications,PATCH /notifications/threads/{id} - Issues:
GET/POST /repos/{owner}/{repo}/issues - Pull Requests:
GET/POST /repos/{owner}/{repo}/pulls
Requires:
- A Gitea server URL
- A personal access token with scopes:
read:user,read:repository,read:issue,write:issue,read:notification,write:notification
Key files for submission:
PrivacyInfo.xcprivacy- Privacy manifest (required iOS 17+)PRIVACY.md- Privacy policy (hosted on GitHub)Info.plist- IncludesITSAppUsesNonExemptEncryption: falseandNSAllowsArbitraryLoads: true
Display name: iTea (set in project build settings, not Info.plist)