This document contains project-specific conventions and best practices for the Convos iOS codebase.
- Main App: SwiftUI app with UIKit integration where needed
- ConvosCore: Swift Package containing core business logic, storage, and XMTP client
- App Clips: Separate target for lightweight experiences
- Notification Service: Extension for push notification handling
- Use
ConvosCorefor shared business logic, models, and services - Keep UI-specific code in the main app target
- Use protocols for dependency injection (e.g.,
SessionManagerProtocol)
- Modern Observation Framework: Use
@Observablewith@Statefor new code@Observable class MyViewModel { var property: String = "" } // In views: @State private var viewModel = MyViewModel()
- Legacy code may still use
ObservableObjectwith@StateObject/@ObservedObject`
Always extract button actions to avoid closure compilation errors:
// ✅ Good
let action = { /* action code */ }
Button(action: action) {
// view content
}
// ❌ Bad - causes compilation issues
Button(action: { /* action */ }) {
// view content
}Use @Previewable for preview state variables:
@Previewable @State var text: String = "Preview"- Trim whitespace always
- Use closure-only for stripping unused arguments
- Braces follow K&R style, opening brace on the same line (not Allman)
- Trailing commas in multi-line collections and parameter lists
Key enforced rules:
- No force unwrapping
- Prefer
first(where:)over filter operations - Use explicit types for public interfaces
- Sort imports alphabetically
- Private over fileprivate
- No implicitly unwrapped optionals
- ViewModels:
ConversationViewModel,ProfileViewModel - Views:
ConversationsView,MessageView - Storage:
SceneURLStorage,DatabaseManager - Repositories:
ConversationsCountRepository - Use descriptive names over abbreviations
All dependencies managed through SPM. See ConvosCore/Package.swift for current versions.
- Use
ConfigManagerfor environment-specific settings - Environments: Production, Development, Local
- Firebase configuration per environment
SceneURLStorage: Coordinates URL handling between SceneDelegate and SwiftUIConvosSceneDelegate: Handles both Universal Links and custom URL schemesDeepLinkHandler: Validates and processes deep links- Store pending URLs for cold launch scenarios
Logger.configure(environment: environment)
Logger.info("Message")
Logger.error("Error message")- Production vs development logging levels
- Environment-specific configuration
- Use
.mockstatic methods for preview/test data - Example:
ConversationViewModel.mock
- Unit tests in
ConvosTests - Core logic tests in
ConvosCoreTests - UI tests in separate target
- Never commit secrets or API keys
- Use environment variables for sensitive configuration
- Validate all deep links before processing
- Use Firebase App Check for API protection
- Use
AvatarViewwith built-in caching - Lazy load images where appropriate
- Handle image state (loading, loaded, error)
- Use
@MainActorfor UI-related classes - Minimize view body complexity
- Extract complex views into separate components
# Check for linting issues
swiftlint
# Auto-fix linting issues
swiftlint --fix
# Format code
swiftformat .
# Run tests (Local environment on iOS Simulator)
xcodebuild test -scheme "Convos (Local)" -destination "platform=iOS Simulator,name=iPhone 17"
# Build for device (Local environment)
xcodebuild build -scheme "Convos (Local)" -configuration Debug
# Clean build folder
xcodebuild clean -scheme "Convos (Local)"- Minimum iOS version: 26.0
- Swift language mode: 5
- Single project structure with local SPM packages
When migrating from ObservableObject:
- Remove
ObservableObjectconformance - Add
@Observablemacro andimport Observation - Remove
@Publishedproperty wrappers - Change
@StateObject/@ObservedObjectto@Statein views
- No trailing whitespace on any lines
- Don't add comments unless specifically requested
- Prefer editing existing files over creating new ones
- Follow existing patterns in neighboring code
- Check dependencies before using any library