|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +SundialKitCombine is a Combine-based observation plugin for SundialKit that provides reactive network monitoring and WatchConnectivity communication for SwiftUI applications. It uses `@Published` properties and Combine publishers to deliver state updates, with strict `@MainActor` isolation for thread safety. |
| 8 | + |
| 9 | +**Key characteristics:** |
| 10 | +- Swift Package Manager library (no executables) |
| 11 | +- Depends on SundialKit (main package with core protocols) |
| 12 | +- Swift 6.1+ with strict concurrency enabled |
| 13 | +- Platform support: iOS 16+, watchOS 9+, tvOS 16+, macOS 11+ |
| 14 | +- Targets Combine-based SwiftUI apps requiring backward compatibility |
| 15 | + |
| 16 | +## Development Commands |
| 17 | + |
| 18 | +### Building and Testing |
| 19 | + |
| 20 | +```bash |
| 21 | +# Build the package |
| 22 | +swift build |
| 23 | + |
| 24 | +# Build including tests |
| 25 | +swift build --build-tests |
| 26 | + |
| 27 | +# Run all tests |
| 28 | +swift test |
| 29 | + |
| 30 | +# Run tests with parallel execution |
| 31 | +swift test --parallel |
| 32 | + |
| 33 | +# Run a specific test |
| 34 | +swift test --filter SundialKitCombineTests |
| 35 | +``` |
| 36 | + |
| 37 | +### Code Quality |
| 38 | + |
| 39 | +```bash |
| 40 | +# Run all linting (format + lint + build) |
| 41 | +LINT_MODE=STRICT ./Scripts/lint.sh |
| 42 | + |
| 43 | +# Format only (no linting or build) |
| 44 | +FORMAT_ONLY=1 ./Scripts/lint.sh |
| 45 | + |
| 46 | +# Run linting in normal mode (without strict mode) |
| 47 | +./Scripts/lint.sh |
| 48 | + |
| 49 | +# Run periphery to detect unused code (requires mise) |
| 50 | +mise exec -- periphery scan --disable-update-check |
| 51 | +``` |
| 52 | + |
| 53 | +**Note:** Linting requires `mise` to be installed. The script checks for mise at: |
| 54 | +- `/opt/homebrew/bin/mise` |
| 55 | +- `/usr/local/bin/mise` |
| 56 | +- `$HOME/.local/bin/mise` |
| 57 | + |
| 58 | +Tools managed by mise (defined in `.mise.toml`): |
| 59 | +- `swift-format` (version 602.0.0) |
| 60 | +- `swiftlint` (version 0.61.0) |
| 61 | +- `periphery` (version 3.2.0) |
| 62 | + |
| 63 | +### Documentation |
| 64 | + |
| 65 | +```bash |
| 66 | +# Preview documentation locally |
| 67 | +./Scripts/preview-docs.sh |
| 68 | +``` |
| 69 | + |
| 70 | +## Architecture |
| 71 | + |
| 72 | +### Three-Layer Architecture |
| 73 | + |
| 74 | +SundialKitCombine sits in Layer 2 of the SundialKit architecture: |
| 75 | + |
| 76 | +**Layer 1: Core Protocols** (dependencies from SundialKit package) |
| 77 | +- `SundialKitCore`: Base protocols, errors, and types |
| 78 | +- `SundialKitNetwork`: Network monitoring protocols (`PathMonitor`, `NetworkPing`) |
| 79 | +- `SundialKitConnectivity`: WatchConnectivity protocols and messaging |
| 80 | + |
| 81 | +**Layer 2: Observation Plugin** (this package) |
| 82 | +- `NetworkObserver`: Combines `@MainActor` isolation with `@Published` properties for network state |
| 83 | +- `ConnectivityObserver`: Manages WatchConnectivity session with Combine publishers |
| 84 | + |
| 85 | +### Key Components |
| 86 | + |
| 87 | +**NetworkObserver** (`Sources/SundialKitCombine/NetworkObserver.swift`) |
| 88 | +- Generic over `MonitorType: PathMonitor` and `PingType: NetworkPing` |
| 89 | +- `@MainActor` isolated with `@Published` properties: |
| 90 | + - `pathStatus`: Current network path status |
| 91 | + - `isExpensive`: Whether connection is cellular/expensive |
| 92 | + - `isConstrained`: Whether low data mode is active |
| 93 | + - `pingStatus`: Optional ping verification result |
| 94 | +- Delegates to underlying monitor (typically `NWPathMonitor` via `NWPathMonitorAdapter`) |
| 95 | +- Optional periodic ping for verification beyond path availability |
| 96 | + |
| 97 | +**ConnectivityObserver** (`Sources/SundialKitCombine/ConnectivityObserver.swift`) |
| 98 | +- `@MainActor` isolated observer for WatchConnectivity |
| 99 | +- `@Published` properties for session state: |
| 100 | + - `activationState`: Session activation state |
| 101 | + - `isReachable`: Whether counterpart device is available |
| 102 | + - `isPairedAppInstalled`: Whether companion app is installed |
| 103 | + - `isPaired`: (iOS only) Whether Apple Watch is paired |
| 104 | + - `activationError`: Last activation error if any |
| 105 | +- Combine `PassthroughSubject` publishers: |
| 106 | + - `messageReceived`: Raw messages with context |
| 107 | + - `typedMessageReceived`: Decoded `Messagable` types |
| 108 | + - `sendResult`: Message send confirmations |
| 109 | + - `activationCompleted`: Activation results with errors |
| 110 | +- Supports `MessageDecoder` for type-safe message handling |
| 111 | + |
| 112 | +**Message Types** |
| 113 | +- `Messagable`: Protocol for dictionary-based type-safe messages (~65KB limit) |
| 114 | +- `BinaryMessagable`: Protocol for efficient binary serialization (no size limit, works with Protobuf/custom formats) |
| 115 | +- `MessageDecoder`: Decodes incoming messages to typed `Messagable` instances |
| 116 | + |
| 117 | +### Concurrency Model |
| 118 | + |
| 119 | +- All observers are `@MainActor` isolated |
| 120 | +- No `@unchecked Sendable` conformances (strict concurrency compliance) |
| 121 | +- Network/connectivity events marshaled to main actor via `Task { @MainActor in ... }` |
| 122 | +- Natural integration with SwiftUI's `ObservableObject` pattern |
| 123 | + |
| 124 | +## Swift Settings |
| 125 | + |
| 126 | +The package enables extensive Swift 6.2 upcoming features and experimental features (see `Package.swift:8-45`): |
| 127 | + |
| 128 | +**Upcoming Features:** |
| 129 | +- `ExistentialAny`, `InternalImportsByDefault`, `MemberImportVisibility`, `FullTypedThrows` |
| 130 | + |
| 131 | +**Experimental Features:** |
| 132 | +- Noncopyable generics, move-only types, borrowing switch, init accessors, etc. |
| 133 | + |
| 134 | +**Important:** When adding new code, maintain strict concurrency compliance. Avoid `@unchecked Sendable`. |
| 135 | + |
| 136 | +## Code Style |
| 137 | + |
| 138 | +- **Indentation:** 2 spaces (configured in `.swift-format`) |
| 139 | +- **Line length:** 100 characters max |
| 140 | +- **Documentation:** All public declarations must have documentation comments (`AllPublicDeclarationsHaveDocumentation` rule enabled) |
| 141 | +- **ACL:** Explicit access control required on all declarations (`explicit_acl`, `explicit_top_level_acl`) |
| 142 | +- **Concurrency:** Prefer `@MainActor` isolation over manual dispatch queue management |
| 143 | +- **Imports:** Use `public import` for re-exported dependencies |
| 144 | + |
| 145 | +## Testing |
| 146 | + |
| 147 | +Tests are minimal stubs (`Tests/SundialKitCombineTests/SundialKitCombineTests.swift`). The package primarily provides observer wrappers around SundialKit protocols, with integration testing typically done in consuming applications. |
| 148 | + |
| 149 | +When adding tests: |
| 150 | +- Use `@MainActor` isolation where needed |
| 151 | +- Test with mock implementations of `PathMonitor` and `ConnectivitySession` |
| 152 | +- Verify `@Published` property updates and Combine publisher emissions |
| 153 | + |
| 154 | +## Common Patterns |
| 155 | + |
| 156 | +### Creating a NetworkObserver |
| 157 | + |
| 158 | +```swift |
| 159 | +// Default NWPathMonitor without ping |
| 160 | +let observer = NetworkObserver() |
| 161 | +observer.start() |
| 162 | + |
| 163 | +// With custom monitor and ping |
| 164 | +let observer = NetworkObserver( |
| 165 | + monitor: NWPathMonitorAdapter(), |
| 166 | + ping: CustomPing(), |
| 167 | + queue: .main |
| 168 | +) |
| 169 | +``` |
| 170 | + |
| 171 | +### Creating a ConnectivityObserver |
| 172 | + |
| 173 | +```swift |
| 174 | +// Without message decoder (raw dictionaries) |
| 175 | +let observer = ConnectivityObserver() |
| 176 | + |
| 177 | +// With message decoder for typed messages |
| 178 | +let observer = ConnectivityObserver( |
| 179 | + messageDecoder: MessageDecoder(messagableTypes: [ |
| 180 | + ColorMessage.self, |
| 181 | + TemperatureReading.self |
| 182 | + ]) |
| 183 | +) |
| 184 | +try observer.activate() |
| 185 | +``` |
| 186 | + |
| 187 | +### Observing State Changes |
| 188 | + |
| 189 | +```swift |
| 190 | +// Bind @Published properties |
| 191 | +observer.$pathStatus |
| 192 | + .sink { status in |
| 193 | + print("Status: \(status)") |
| 194 | + } |
| 195 | + .store(in: &cancellables) |
| 196 | + |
| 197 | +// Listen to event publishers |
| 198 | +connectivityObserver.messageReceived |
| 199 | + .sink { result in |
| 200 | + print("Received: \(result.message)") |
| 201 | + } |
| 202 | + .store(in: &cancellables) |
| 203 | +``` |
| 204 | + |
| 205 | +## Dependencies |
| 206 | + |
| 207 | +The package depends on SundialKit 2.0.0-alpha.1+ (`Package.swift:62`): |
| 208 | +- `SundialKitCore`: Base protocols and types |
| 209 | +- `SundialKitNetwork`: Network monitoring abstractions |
| 210 | +- `SundialKitConnectivity`: WatchConnectivity abstractions |
| 211 | + |
| 212 | +All SundialKit products are external dependencies resolved via SPM. |
| 213 | + |
| 214 | +## Platform Conditionals |
| 215 | + |
| 216 | +- `#if canImport(Combine)`: Entire package wrapped (graceful degradation on platforms without Combine) |
| 217 | +- `#if canImport(Network)`: Convenience initializers for `NWPathMonitor` |
| 218 | +- `#if os(iOS)`: iOS-specific WatchConnectivity properties (e.g., `isPaired`) |
| 219 | + |
| 220 | +## Scripts |
| 221 | + |
| 222 | +- `Scripts/lint.sh`: Runs swift-format, swiftlint, periphery, and header checks |
| 223 | +- `Scripts/header.sh`: Ensures MIT license headers on all source files |
| 224 | +- `Scripts/preview-docs.sh`: Generates and serves DocC documentation locally |
| 225 | +- `Scripts/ensure-remote-deps.sh`: Ensures dependencies are resolved remotely (not local overrides) |
| 226 | + |
| 227 | +## CI/CD |
| 228 | + |
| 229 | +GitHub Actions workflows (`.github/workflows/`): |
| 230 | +- `SundialKitCombine.yml`: Main build and test workflow |
| 231 | +- `codeql.yml`: CodeQL security analysis |
| 232 | +- `claude.yml`: Claude-specific automation |
| 233 | +- `claude-code-review.yml`: Automated code review |
| 234 | + |
| 235 | +## Documentation Structure |
| 236 | + |
| 237 | +DocC documentation is in `Sources/SundialKitCombine/SundialKitCombine.docc/Documentation.md`. This file contains: |
| 238 | +- Usage examples for `NetworkObserver` and `ConnectivityObserver` |
| 239 | +- Type-safe messaging patterns |
| 240 | +- SwiftUI integration examples |
| 241 | +- Comparison with SundialKitStream (the AsyncStream-based alternative) |
| 242 | + |
| 243 | +**Important:** Keep README.md and Documentation.md in sync for major architectural changes. |
| 244 | + |
| 245 | +## Related Packages |
| 246 | + |
| 247 | +- **SundialKit**: Main package containing core protocols and implementations |
| 248 | +- **SundialKitStream**: Alternative observation plugin using `AsyncStream` and actor-based concurrency (iOS 16+, macOS 13+) |
0 commit comments