SKILL.md
iOS App Architecture Review
Review code changes in app/FlyFunEuroAIP/ for compliance with the architecture defined in designs/IOSAPPDESIGN.md.
Architecture Rules
- RZFlight Model Reuse (CRITICAL):
- Use RZFlight.Airport, RZFlight.Runway, RZFlight.Procedure, RZFlight.AIPEntry directly - No duplicate models - Query through KnownAirports - Extend RZFlight library when functionality is missing
- Composed AppState:
- Single source of truth with domain objects - Domains: AirportDomain, ChatDomain, NavigationDomain, SystemDomain, SettingsDomain - Each domain ~200-400 lines - Cross-domain coordination via callbacks - Inject via @Environment(\.appState)
- No ViewModels:
- Domains replace ViewModels - No standalone *ViewModel.swift files - ViewCoordinators are structs with computed properties only
- Repository Pattern:
- AirportRepositoryProtocol defines unified API - LocalAirportDataSource uses KnownAirports - Repository returns RZFlight.Airport directly
- FilterConfig is Pure Data:
- Simple Codable struct - No apply(to:db:) method - Filtering logic lives in repository
- API Adapters:
- API response models are internal only - APIAirportAdapter.toRZFlight() converts immediately - Never expose APIAirport outside adapters
- Region-Based Map Loading:
- Use airportsInRegion(boundingBox:filters:limit:) - Debounce 300ms - Limit 500 markers - Prefetch with 1.3x padding
- File-Based Caching:
- Simple JSON files - No SwiftData - Cache in Caches/ directory
- Modern Swift Only (iOS 18.0+/macOS 15.0+):
- @Observable macro (no ObservableObject) - @Environment injection (no @EnvironmentObject) - async/await everywhere (no Combine) - Modern MapKit with Map { } builder
- DB is Canonical Source:
- Local SQLite is truth - API is read-only view - Full DB replacement for sync
Red Flags
Flag these violations immediately:
- Duplicate models: Creating
Airport,Runwayin app instead of using RZFlight - ViewModel files: Any
*ViewModel.swiftfile - God-class AppState: Single file >500 lines without domain composition
- Direct SQL queries: Using
FMDatabasedirectly instead ofKnownAirports - FilterConfig with DB logic:
apply(to:db:)method - Exposed API models:
APIAirportused outside adapter files - SwiftData usage: Any
@Modelor SwiftData imports - ObservableObject: Using instead of
@Observable - Combine imports: Using for state management
- All airports loaded: Loading all 10K+ airports at once for map
Review Process
- Analyze changed files in
app/FlyFunEuroAIP/ - Check imports - should see
import RZFlight, noimport Combine - Verify model usage - search for
RZFlight.Airportvs localAirport - Check state management - domains composed in AppState?
- Verify repository pattern - data access through repository?
- Check UI patterns - adaptive layouts, environment injection?
- Identify violations with file paths and line numbers
- Suggest fixes with code examples
Output Format
APPROVED:
file:line- Explanation of why it's correct
VIOLATION:
file:line- Description- Problem: Why it violates architecture
- Fix: Suggested corrected implementation
- Impact: What breaks (performance, maintainability, etc.)
Key Files
Core Architecture
App/State/AppState.swift- Composed domains?App/State/Domains/*.swift- Each domain ~200-400 lines?App/Data/Repositories/AirportRepository.swift- Returns RZFlight types?
Model Usage
App/Models/*.swift- Should be minimal (app-specific only)- Any
struct Airport- Should NOT exist (use RZFlight.Airport)
UI Layer
UserInterface/Views/*.swift- Uses@Environment(\.appState)?UserInterface/Views/Map/*.swift- Region-based loading?
Approved Patterns
RZFlight Model Usage:
// GOOD
import RZFlight
let airport: Airport // This is RZFlight.Airport
// BAD
struct AppAirport { let icao: String; let name: String }
Composed AppState:
// GOOD
@Observable @MainActor
final class AppState {
let airports: AirportDomain
let chat: ChatDomain
let navigation: NavigationDomain
}
Environment Injection:
// GOOD
struct AirportMapView: View {
@Environment(\.appState) private var state
}
// BAD
@StateObject private var viewModel = AirportMapViewModel()
Region-Based Loading:
// GOOD
func onRegionChange(_ region: MKCoordinateRegion) {
regionUpdateTask?.cancel()
regionUpdateTask = Task {
try? await Task.sleep(for: .milliseconds(300))
airports = try await repository.airportsInRegion(
boundingBox: region.paddedBy(factor: 1.3).boundingBox,
filters: filters, limit: 500
)
}
}
// BAD
airports = try await repository.allAirports() // 10K+ airports!
Reference
See designs/IOSAPPDESIGN.md for full design details.