|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance to agents such as Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +This is the Rover iOS SDK, a modular collection of Swift frameworks for mobile experiences, campaigns automation, and marketing. The SDK follows a modular architecture allowing inclusion of only relevant functionality. |
| 8 | + |
| 9 | +## Architecture |
| 10 | + |
| 11 | +### Relationships with External Systems |
| 12 | + |
| 13 | +- **Rover GraphQL API Gateway**: The GraphQL API is used for outbound events and classic experiences, and various legacy features. These outbound events are used for behavioural automation, setting up APNs push tokens, and other local device/user context and preferences into the Rover cloud. |
| 14 | +- **Bobcat/Engage API**: Rover is moving towards a new set of cloud services. This is the new API for the Rover platform, and use of the old API will be eventually phased out. |
| 15 | + |
| 16 | +### Modular Design |
| 17 | +The SDK is organized into independent modules (Swift Package Manager targets): |
| 18 | + |
| 19 | +- **RoverFoundation**: Core dependency injection container, utilities, and base types |
| 20 | +- **RoverData**: HTTP client, event queue, sync coordination, context management |
| 21 | +- **RoverUI**: UI services, routing, session management, image handling |
| 22 | +- **RoverExperiences**: Rendering of experiences, dynamic UI content. There are versions of the Experiences product, classic and modern. Classic is the old legacy version of experiences, whereas modern is modelled on a subset of SwiftUI. They have separate authoring tools. |
| 23 | +- **RoverNotifications**: Push notifications, a legacy version Inbox (sometimes called Notification Center), and Communication Hub (the new Inbox that replaces it) |
| 24 | +- **RoverLocation**: Capturing of device location for targeting purposes, along with Geofencing and beacon support. Deprecated for privacy reasons. |
| 25 | +- **RoverDebug**: Development tools and settings UI |
| 26 | +- **RoverTelephony**: Telephony context provider. Deprecated for privacy reasons. |
| 27 | +- **Third-party integrations**: RoverTicketmaster, RoverSeatGeek, RoverAxs, RoverAdobeExperience |
| 28 | +- **RoverAppExtensions**: Notification service extension support. This is used to add behaviour at APNs push reception time, such as enabling rich media support, or persisting notification content in local storage. |
| 29 | + |
| 30 | +### Module Dependencies |
| 31 | +``` |
| 32 | +RoverFoundation (base) |
| 33 | +├── RoverData (depends on Foundation) |
| 34 | +│ ├── RoverUI (depends on Data) |
| 35 | +│ │ ├── RoverExperiences (depends on UI, Foundation, Data) |
| 36 | +│ │ ├── RoverNotifications (depends on Data, UI) |
| 37 | +│ │ └── RoverDebug (depends on UI) |
| 38 | +│ ├── RoverLocation (depends on Data) |
| 39 | +│ ├── RoverTelephony (depends on Data) |
| 40 | +│ ├── RoverTicketmaster (depends on Data) |
| 41 | +│ ├── RoverSeatGeek (depends on Data) |
| 42 | +│ ├── RoverAxs (depends on Data) |
| 43 | +│ └── RoverAdobeExperience (depends on Data) |
| 44 | +└── RoverAppExtensions (depends on Foundation) |
| 45 | +``` |
| 46 | + |
| 47 | +### Dependency Injection |
| 48 | +The SDK uses a custom DI container (`Sources/Foundation/Container/`) with assemblers for each module: |
| 49 | +- Each module has an `*Assembler.swift` that registers services |
| 50 | +- Services are resolved through the main `Rover` singleton |
| 51 | +- Supports singleton and transient scopes with factory patterns |
| 52 | +- `Assembler` protocol defines module configuration |
| 53 | +- `Container` manages service registration |
| 54 | +- `Resolver` handles service resolution |
| 55 | + |
| 56 | +Each module follows the pattern: |
| 57 | +```swift |
| 58 | +class ModuleAssembler: Assembler { |
| 59 | + func assemble(container: Container) { |
| 60 | + // Register services |
| 61 | + } |
| 62 | + |
| 63 | + func containerDidAssemble(resolver: Resolver) { |
| 64 | + // Post-registration configuration |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +### Communication Hub (New Inbox) |
| 70 | +New messaging feature located in `Sources/Notifications/Communication Hub/`: |
| 71 | +- Core Data models for posts and subscriptions |
| 72 | +- SwiftUI views for inbox and post detail |
| 73 | +- Sync logic using the new Bobcat/Engage API |
| 74 | +- Sync participant for syncing when the rest of the SDK is syncing |
| 75 | + |
| 76 | +### Experience Rendering Architecture |
| 77 | +The Experiences module has dual rendering paths: |
| 78 | +- **Classic Experiences**: Legacy UIKit-based renderer (`Sources/Experiences/ClassicExperiences/`) |
| 79 | +- **Modern Experiences**: SwiftUI-style declarative renderer (`Sources/Experiences/Experiences/`) |
| 80 | + |
| 81 | +They are different products with different formats (and different authoring tools to be found elsewhere). Experiences are rendered through a hierarchical node system where each UI element is a `Node` with view-specific implementations in both classic and modern renderers. |
| 82 | + |
| 83 | +### Data Management |
| 84 | +- **Event System**: Queue-based event tracking with offline support (`Sources/Data/EventQueue/`) |
| 85 | +- **Sync System**: Paginated data synchronization framework (`Sources/Data/SyncCoordinator/`) |
| 86 | +- **Context System**: Device and user context collection (`Sources/Data/Context/`) |
| 87 | + |
| 88 | +### External Dependencies |
| 89 | +- `ZIPFoundation`: For experience asset archive handling |
| 90 | +- `iOS-TicketmasterSDK`: The RoverTicketmaster module directly depends on Ticketmaster's Ignite SDK. However, this strategy is being phased out. |
| 91 | + |
| 92 | + |
| 93 | +## Development Commands |
| 94 | + |
| 95 | +### Build and Test |
| 96 | +```bash |
| 97 | +# Build the Testbench app (main development target) |
| 98 | + |
| 99 | +xcodebuild -scheme "Rover Bench" -project "Testbench/Rover Bench.xcodeproj" build |
| 100 | + 2>&1 | grep -A2 -B2 "error:" | head -20 |
| 101 | + |
| 102 | +### Running Tests |
| 103 | +```bash |
| 104 | +# Run unit tests for specific modules |
| 105 | +xcodebuild test -scheme "RoverFoundation" -project "Testbench/Rover Bench.xcodeproj" -destination "platform=iOS Simulator,name=iPhone 15" |
| 106 | +xcodebuild test -scheme "RoverData" -project "Testbench/Rover Bench.xcodeproj" -destination "platform=iOS Simulator,name=iPhone 15" |
| 107 | +# (Similar for other modules) |
| 108 | +
|
| 109 | +# Alternative: Test using the Example project |
| 110 | +xcodebuild test -project Example/Example.xcodeproj -scheme Example -destination "platform=iOS Simulator,name=iPhone 15" |
| 111 | +
|
| 112 | +# Test individual modules via workspace (if needed) |
| 113 | +xcodebuild test -workspace Example/Example.xcworkspace -scheme RoverFoundation -destination "platform=iOS Simulator,name=iPhone 15" |
| 114 | +``` |
| 115 | + |
| 116 | +### Package Verification |
| 117 | +Since this is a Swift Package, you can verify the package structure: |
| 118 | +```bash |
| 119 | +swift package dump-package |
| 120 | +swift package resolve |
| 121 | +``` |
| 122 | + |
| 123 | +## Key Files and Patterns |
| 124 | + |
| 125 | +### Entry Points |
| 126 | +- `Sources/Foundation/Rover.swift`: Main SDK singleton and container |
| 127 | +- `Testbench/Rover Bench/TestbenchApp.swift`: SwiftUI test app |
| 128 | +- `Example/Example/AppDelegate.swift`: UIKit example app |
| 129 | + |
| 130 | +### Service Registration |
| 131 | +Services are registered in assemblers following this pattern: |
| 132 | +```swift |
| 133 | +container.register(ServiceProtocol.self) { resolver in |
| 134 | + ServiceImplementation(dependency: resolver.resolve(Dependency.self)!) |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +### Core Data Models |
| 139 | +- Location: `Sources/Location/Model/RoverLocation.xcdatamodeld/` |
| 140 | +- Communication Hub: `Sources/Notifications/Communication Hub/RoverCommHubModel.xcdatamodeld/` |
| 141 | + |
| 142 | +### Extension Points |
| 143 | +- Context Providers: `Sources/Data/Context/Providers/` for adding app context |
| 144 | +- Route Handlers: Handle deep links and navigation |
| 145 | +- Sync Participants: Integrate with server synchronization |
| 146 | + |
| 147 | +## Common Development Patterns |
| 148 | + |
| 149 | +### Adding New Features |
| 150 | +1. Create service interface in appropriate module |
| 151 | +2. Implement service with dependency injection |
| 152 | +3. Register service in module's assembler |
| 153 | +4. Add route handler if navigation is needed |
| 154 | +5. Update sync participant (or standalone sync participant if it is not using the GraphQL API) if server communication is required |
| 155 | +
|
| 156 | +### Testing |
| 157 | +- Unit tests are in `Tests/` directory organized by module |
| 158 | +- Use XCTest framework with `@testable import` for internal access |
| 159 | +- Mock services using the dependency injection container |
| 160 | +
|
| 161 | +### Privacy |
| 162 | +Each module includes `Resources/PrivacyInfo.xcprivacy` for App Store privacy declarations. |
| 163 | +
|
| 164 | +## WORKFLOW INSTRUCTIONS |
| 165 | +
|
| 166 | +- You must always ensure tests pass between each step. |
| 167 | +- You must always use `swift format -i` on a file after you've edited it. |
| 168 | +- If tests do not already exist for the components involved in feature being planned, include steps adding them in the same plan. |
| 169 | + |
| 170 | +### CODE STYLE |
| 171 | + |
| 172 | +- Prefer guard statements over if statements with else blocks. |
| 173 | +- Use Swift structured concurrency when possible. |
| 174 | +- Never change Public APIs (any API that is marked as public), without explicit consent from the developer. This is an SDK with a public API with existing customers. |
0 commit comments