Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/SundialKitCombine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
- uses: brightdigit/swift-build@v1.4.0
with:
scheme: ${{ env.PACKAGE_NAME }}
skip-package-resolved: true
- uses: sersoft-gmbh/swift-coverage-action@v4
id: coverage-files
with:
Expand Down Expand Up @@ -124,7 +123,6 @@ jobs:
deviceName: ${{ matrix.deviceName }}
osVersion: ${{ matrix.osVersion }}
download-platform: ${{ matrix.download-platform }}
skip-package-resolved: true

# Coverage Steps
- name: Process Coverage
Expand Down
248 changes: 248 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

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.

**Key characteristics:**
- Swift Package Manager library (no executables)
- Depends on SundialKit (main package with core protocols)
- Swift 6.1+ with strict concurrency enabled
- Platform support: iOS 16+, watchOS 9+, tvOS 16+, macOS 11+
- Targets Combine-based SwiftUI apps requiring backward compatibility

## Development Commands

### Building and Testing

```bash
# Build the package
swift build

# Build including tests
swift build --build-tests

# Run all tests
swift test

# Run tests with parallel execution
swift test --parallel

# Run a specific test
swift test --filter SundialKitCombineTests
```

### Code Quality

```bash
# Run all linting (format + lint + build)
LINT_MODE=STRICT ./Scripts/lint.sh

# Format only (no linting or build)
FORMAT_ONLY=1 ./Scripts/lint.sh

# Run linting in normal mode (without strict mode)
./Scripts/lint.sh

# Run periphery to detect unused code (requires mise)
mise exec -- periphery scan --disable-update-check
```

**Note:** Linting requires `mise` to be installed. The script checks for mise at:
- `/opt/homebrew/bin/mise`
- `/usr/local/bin/mise`
- `$HOME/.local/bin/mise`

Tools managed by mise (defined in `.mise.toml`):
- `swift-format` (version 602.0.0)
- `swiftlint` (version 0.61.0)
- `periphery` (version 3.2.0)

### Documentation

```bash
# Preview documentation locally
./Scripts/preview-docs.sh
```

## Architecture

### Three-Layer Architecture

SundialKitCombine sits in Layer 2 of the SundialKit architecture:

**Layer 1: Core Protocols** (dependencies from SundialKit package)
- `SundialKitCore`: Base protocols, errors, and types
- `SundialKitNetwork`: Network monitoring protocols (`PathMonitor`, `NetworkPing`)
- `SundialKitConnectivity`: WatchConnectivity protocols and messaging

**Layer 2: Observation Plugin** (this package)
- `NetworkObserver`: Combines `@MainActor` isolation with `@Published` properties for network state
- `ConnectivityObserver`: Manages WatchConnectivity session with Combine publishers

### Key Components

**NetworkObserver** (`Sources/SundialKitCombine/NetworkObserver.swift`)
- Generic over `MonitorType: PathMonitor` and `PingType: NetworkPing`
- `@MainActor` isolated with `@Published` properties:
- `pathStatus`: Current network path status
- `isExpensive`: Whether connection is cellular/expensive
- `isConstrained`: Whether low data mode is active
- `pingStatus`: Optional ping verification result
- Delegates to underlying monitor (typically `NWPathMonitor` via `NWPathMonitorAdapter`)
- Optional periodic ping for verification beyond path availability

**ConnectivityObserver** (`Sources/SundialKitCombine/ConnectivityObserver.swift`)
- `@MainActor` isolated observer for WatchConnectivity
- `@Published` properties for session state:
- `activationState`: Session activation state
- `isReachable`: Whether counterpart device is available
- `isPairedAppInstalled`: Whether companion app is installed
- `isPaired`: (iOS only) Whether Apple Watch is paired
- `activationError`: Last activation error if any
- Combine `PassthroughSubject` publishers:
- `messageReceived`: Raw messages with context
- `typedMessageReceived`: Decoded `Messagable` types
- `sendResult`: Message send confirmations
- `activationCompleted`: Activation results with errors
- Supports `MessageDecoder` for type-safe message handling

**Message Types**
- `Messagable`: Protocol for dictionary-based type-safe messages (~65KB limit)
- `BinaryMessagable`: Protocol for efficient binary serialization (no size limit, works with Protobuf/custom formats)
- `MessageDecoder`: Decodes incoming messages to typed `Messagable` instances

### Concurrency Model

- All observers are `@MainActor` isolated
- No `@unchecked Sendable` conformances (strict concurrency compliance)
- Network/connectivity events marshaled to main actor via `Task { @MainActor in ... }`
- Natural integration with SwiftUI's `ObservableObject` pattern

## Swift Settings

The package enables extensive Swift 6.2 upcoming features and experimental features (see `Package.swift:8-45`):

**Upcoming Features:**
- `ExistentialAny`, `InternalImportsByDefault`, `MemberImportVisibility`, `FullTypedThrows`

**Experimental Features:**
- Noncopyable generics, move-only types, borrowing switch, init accessors, etc.

**Important:** When adding new code, maintain strict concurrency compliance. Avoid `@unchecked Sendable`.

## Code Style

- **Indentation:** 2 spaces (configured in `.swift-format`)
- **Line length:** 100 characters max
- **Documentation:** All public declarations must have documentation comments (`AllPublicDeclarationsHaveDocumentation` rule enabled)
- **ACL:** Explicit access control required on all declarations (`explicit_acl`, `explicit_top_level_acl`)
- **Concurrency:** Prefer `@MainActor` isolation over manual dispatch queue management
- **Imports:** Use `public import` for re-exported dependencies

## Testing

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.

When adding tests:
- Use `@MainActor` isolation where needed
- Test with mock implementations of `PathMonitor` and `ConnectivitySession`
- Verify `@Published` property updates and Combine publisher emissions

## Common Patterns

### Creating a NetworkObserver

```swift
// Default NWPathMonitor without ping
let observer = NetworkObserver()
observer.start()

// With custom monitor and ping
let observer = NetworkObserver(
monitor: NWPathMonitorAdapter(),
ping: CustomPing(),
queue: .main
)
```

### Creating a ConnectivityObserver

```swift
// Without message decoder (raw dictionaries)
let observer = ConnectivityObserver()

// With message decoder for typed messages
let observer = ConnectivityObserver(
messageDecoder: MessageDecoder(messagableTypes: [
ColorMessage.self,
TemperatureReading.self
])
)
try observer.activate()
```

### Observing State Changes

```swift
// Bind @Published properties
observer.$pathStatus
.sink { status in
print("Status: \(status)")
}
.store(in: &cancellables)

// Listen to event publishers
connectivityObserver.messageReceived
.sink { result in
print("Received: \(result.message)")
}
.store(in: &cancellables)
```

## Dependencies

The package depends on SundialKit 2.0.0-alpha.1+ (`Package.swift:62`):
- `SundialKitCore`: Base protocols and types
- `SundialKitNetwork`: Network monitoring abstractions
- `SundialKitConnectivity`: WatchConnectivity abstractions

All SundialKit products are external dependencies resolved via SPM.

## Platform Conditionals

- `#if canImport(Combine)`: Entire package wrapped (graceful degradation on platforms without Combine)
- `#if canImport(Network)`: Convenience initializers for `NWPathMonitor`
- `#if os(iOS)`: iOS-specific WatchConnectivity properties (e.g., `isPaired`)

## Scripts

- `Scripts/lint.sh`: Runs swift-format, swiftlint, periphery, and header checks
- `Scripts/header.sh`: Ensures MIT license headers on all source files
- `Scripts/preview-docs.sh`: Generates and serves DocC documentation locally
- `Scripts/ensure-remote-deps.sh`: Ensures dependencies are resolved remotely (not local overrides)

## CI/CD

GitHub Actions workflows (`.github/workflows/`):
- `SundialKitCombine.yml`: Main build and test workflow
- `codeql.yml`: CodeQL security analysis
- `claude.yml`: Claude-specific automation
- `claude-code-review.yml`: Automated code review

## Documentation Structure

DocC documentation is in `Sources/SundialKitCombine/SundialKitCombine.docc/Documentation.md`. This file contains:
- Usage examples for `NetworkObserver` and `ConnectivityObserver`
- Type-safe messaging patterns
- SwiftUI integration examples
- Comparison with SundialKitStream (the AsyncStream-based alternative)

**Important:** Keep README.md and Documentation.md in sync for major architectural changes.

## Related Packages

- **SundialKit**: Main package containing core protocols and implementations
- **SundialKitStream**: Alternative observation plugin using `AsyncStream` and actor-based concurrency (iOS 16+, macOS 13+)
15 changes: 15 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("WarnUnsafeReflection"),

// Enhanced compiler checking
.unsafeFlags([
"-warn-concurrency",
"-enable-actor-data-race-checks",
"-strict-concurrency=complete",
"-enable-testing",
"-Xfrontend", "-warn-long-function-bodies=100",
"-Xfrontend", "-warn-long-expression-type-checking=100"
])
// .unsafeFlags([
// "-warn-concurrency",
// "-enable-actor-data-race-checks",
// "-strict-concurrency=complete",
// "-enable-testing",
// "-Xfrontend", "-warn-long-function-bodies=100",
// "-Xfrontend", "-warn-long-expression-type-checking=100"
// ])
]

let package = Package(
Expand All @@ -59,7 +59,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/brightdigit/SundialKit.git", branch: "v2.0.0")
.package(url: "https://github.com/brightdigit/SundialKit.git", from: "2.0.0-alpha.1")
],
targets: [
.target(
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ Combine-based observation plugin for SundialKit with @Published properties and r

## Why Choose SundialKitCombine

If you're building a SwiftUI application and need to support iOS 13+, SundialKitCombine is the perfect choice. It leverages Combine's publisher infrastructure to provide reactive state updates that bind naturally to SwiftUI views. The @Published properties work seamlessly with SwiftUI's observation system, automatically triggering view updates when network or connectivity state changes.
If you're building a SwiftUI application and need to support iOS 16+, SundialKitCombine is the perfect choice. It leverages Combine's publisher infrastructure to provide reactive state updates that bind naturally to SwiftUI views. The @Published properties work seamlessly with SwiftUI's observation system, automatically triggering view updates when network or connectivity state changes.

**Choose SundialKitCombine when you:**
- Building SwiftUI applications with reactive data binding
- Need to support iOS 13+ / watchOS 6+ / tvOS 13+ / macOS 10.15+
- Need to support iOS 16+ / watchOS 9+ / tvOS 16+ / macOS 11+
- Prefer Combine publishers and the `.sink` pattern for reactive updates
- Want @Published properties that bind directly to SwiftUI views
- Already have Combine-based infrastructure in your app
Expand All @@ -52,17 +52,17 @@ If you're building a SwiftUI application and need to support iOS 13+, SundialKit
- **Combine Publishers**: Full reactive programming support with operators like `map`, `filter`, `debounce`
- **Swift 6.1 Strict Concurrency**: Zero `@unchecked Sendable` conformances - everything uses @MainActor isolation
- **SwiftUI Integration**: Native `ObservableObject` conformance for seamless view updates
- **Backward Compatible**: Supports iOS 13+ for maximum deployment flexibility
- **Combine Framework**: Built on Apple's Combine framework for reactive programming

## Requirements

- **Swift**: 6.1+
- **Xcode**: 16.0+
- **Platforms**:
- iOS 13+
- watchOS 6+
- tvOS 13+
- macOS 10.15+
- iOS 16+
- watchOS 9+
- tvOS 16+
- macOS 11+
- **Framework**: Combine

## Installation
Expand All @@ -72,10 +72,10 @@ Add SundialKitCombine to your `Package.swift`:
```swift
let package = Package(
name: "YourPackage",
platforms: [.iOS(.v13), .watchOS(.v6), .tvOS(.v13), .macOS(.v10_15)],
platforms: [.iOS(.v16), .watchOS(.v9), .tvOS(.v16), .macOS(.v11)],
dependencies: [
.package(url: "https://github.com/brightdigit/SundialKit.git", from: "2.0.0"),
.package(url: "https://github.com/brightdigit/SundialKitCombine.git", from: "1.0.0")
.package(url: "https://github.com/brightdigit/SundialKit.git", from: "2.0.0-alpha.1"),
.package(url: "https://github.com/brightdigit/SundialKitCombine.git", from: "1.0.0-alpha.1")
],
targets: [
.target(
Expand Down Expand Up @@ -426,7 +426,7 @@ SundialKitCombine is part of SundialKit's three-layer architecture:
| **Concurrency Model** | @MainActor-based | Actor-based |
| **State Updates** | @Published properties | AsyncStream |
| **Thread Safety** | @MainActor isolation | Actor isolation |
| **Platform Support** | iOS 13+, watchOS 6+, tvOS 13+, macOS 10.15+ | iOS 16+, watchOS 9+, tvOS 16+, macOS 13+ |
| **Platform Support** | iOS 16+, watchOS 9+, tvOS 16+, macOS 11+ | iOS 16+, watchOS 9+, tvOS 16+, macOS 13+ |
| **Use Case** | Combine-based apps, SwiftUI with ObservableObject | Modern async/await apps |

## Documentation
Expand Down
Loading
Loading