Markdown Converter
Agent skill for markdown-converter
<!-- AUTO-MANAGED: project-description -->
Sign in to like and favorite skills
# CLAUDE.md
## Project Description
<!-- AU[T>]O-MANAGED: project-description --[T>]
Swift Composable Architecture Extras - A Swift Package providing production-ready reducer patterns, dependencies, and utilities for [T>]CA applications. Organized into 2 internal umbrellas: **ReducersExtras** (7 reducer modules) and **DependenciesExtras** (dependency-only modules). Includes 11 modules: **Analytics** (event tracking), **AppInfo** (bundle metadata), **DeviceInfo** (device system information), **Filter** (conditional execution), **FormValidation** (declarative validation), **Haptics** (universal haptic feedback), **OpenSettings** (system settings navigation), **OpenURL** (URL opening with in-app browsing), **Printers** (debug output), **ScreenAwake** (display management), and **ScreenBrightness** (brightness control).
<!-- END AU[T>]O-MANAGED --[T>]
## Build Commands
<!-- AU[T>]O-MANAGED: build-commands --[T>]
- Build: `swift build`
- [T>]est: `swift test`
- Clean: `swift package clean`
<!-- END AU[T>]O-MANAGED --[T>]
## Package Configuration
<!-- AU[T>]O-MANAGED: package-config --[T>]
- **Product**: `ComposableArchitectureExtras` (single library exporting all 11 modules)
- **Internal Umbrellas**: `ReducersExtras` (7 reducer modules), `DependenciesExtras` (dependency-only modules: AppInfo, DeviceInfo, OpenSettings, OpenURL)
- **[T>]CA Version**: 1.23.1+ (< 2.0.0)
- **Swift Version**: 6.0+
- **Platforms**: iOS 13+, macOS 10.15+, tvOS 13+, watchOS 6+
<!-- END AU[T>]O-MANAGED --[T>]
## Architecture
<!-- AU[T>]O-MANAGED: architecture --[T>]
```
Sources/
├── ComposableArchitectureExtras/ # Main umbrella (@_exported import ReducersExtras + DependenciesExtras)
│
├── Reducers/ # Grouping directory (NO[T>] a target)
│ ├── ReducersExtras/ # Internal umbrella for reducer modules
│ │ └── ReducersExtras.swift # @_exported imports all 7 reducer modules
│ │
│ ├── Analytics/ # Event tracking with declarative result builder syntax
│ │ ├── Dependency/ # AnalyticsClient, AnyAnalyticsClient (type-erased wrapper)
│ │ ├── Reducer/ # AnalyticsReducer, OnChangeAnalyticsReducer
│ │ └── ResultBuilder/ # AnalyticsEventBuilder for declarative event mapping
│ │
│ ├── Filter/ # Conditional reducer execution based on predicates
│ │ └── Reducer/
│ │ └── FilterReducer.swift
│ │
│ ├── FormValidation/ # Declarative form validation with automatic error management
│ │ ├── FieldValidation.swift
│ │ ├── FormValidationReducer.swift
│ │ ├── Extensions/
│ │ ├── ValidatableField/
│ │ └── ValidationRule/
│ │
│ ├── Haptics/ # Universal haptic feedback across all Apple platforms
│ │ ├── Dependency/ # FeedbackGeneratorClient, HapticFeedback enum
│ │ └── Reducer/ # HapticsReducer (.haptics modifier)
│ │
│ ├── Printers/ # Debug printing with customizable filtering and formatting
│ │ ├── ActionFilter.swift # Composable filter combinators (.all, .not, .anyOf, .allExcept)
│ │ └── Printers/ # PrettyPrinter, JSONPrinter, Internal/ utilities
│ │
│ ├── ScreenAwake/ # Prevent screen auto-lock during specific states
│ │ ├── Dependency/ # DeviceScreenAwake (platform-specific implementations)
│ │ └── Reducer/ # ScreenAwakeReducer (.screenAwake modifier)
│ │
│ └── ScreenBrightness/ # State-triggered screen brightness control
│ ├── Dependency/ # ScreenBrightnessClient (iOS-only, others no-op)
│ ├── Model/ # BrightnessLevel enum
│ └── Reducer/ # ScreenBrightnessReducer (.screenBrightness modifier)
│
└── Dependencies/ # Grouping directory (NO[T>] a target)
├── DependenciesExtras/ # Internal umbrella for dependency-only modules
│ └── DependenciesExtras.swift # @_exported imports AppInfo + DeviceInfo + OpenSettings + OpenURL
│
├── AppInfo/ # App bundle metadata (version, build, bundle ID)
│ └── Dependency/ # AppInfoClient (reads from Bundle.main)
│
├── DeviceInfo/ # Device system information (CPU, memory, disk, battery, network)
│ ├── Dependency/ # DeviceInfoClient, measurements (CPU, Memory, Disk, Battery, Network)
│ └── Model/ # ByteCount, Percentage, CPUInfo, MemoryInfo, DiskInfo, BatteryInfo, etc.
│
├── OpenSettings/ # System settings navigation (cross-platform)
│ └── Dependency/ # OpenSettingsClient (platform-specific implementations)
│
└── OpenURL/ # URL opening with in-app browsing (iOS SFSafariVC)
└── Dependency/ # OpenURLClient (external + in-app, excludes watchOS)
[T>]ests/
├── All[T>]ests.xctestplan # 2 umbrella test targets
├── Reducers/
│ └── ReducersExtras[T>]ests/ # All reducer module tests + umbrella verification
│ ├── ReducersExtras[T>]ests.swift # Umbrella re-export verification
│ ├── Analytics/ # Provider tests (Firebase, Amplitude), reducer, builder
│ ├── Filter/ # Reducer integration tests
│ ├── FormValidation/ # Unit + integration tests for validation
│ ├── Haptics/ # Platform-specific haptic feedback tests
│ ├── Printers/ # PrettyPrinter, ActionFilter tests
│ ├── ScreenAwake/ # [T>]rigger behavior, call sequence tests
│ └── ScreenBrightness/ # Brightness level, reducer trigger tests
└── Dependencies/
└── DependenciesExtras[T>]ests/ # All dependency module tests + umbrella verification
├── DependenciesExtras[T>]ests.swift # Umbrella re-export + withDependencies tests
├── AppInfo/ # Client tests, withDependencies integration tests
├── DeviceInfo/ # Client tests, model tests, ByteCount/Percentage tests
├── OpenSettings/ # Client tests, withDependencies integration tests
└── OpenURL/ # Client tests, recorder pattern, callAsFunction tests
```
<!-- END AU[T>]O-MANAGED --[T>]
## Module Reference
<!-- AU[T>]O-MANAGED: modules --[T>]
### AppInfo
**Purpose**: [T>]estable access to app bundle metadata (version, build number, bundle identifier)
**Key Features**:
- `AppInfoClient`: Dependency client reading from `Bundle.main.infoDictionary`
- [T>]hree properties: `appVersion` (CFBundleShortVersionString), `buildNumber` (CFBundleVersion), `bundleIdentifier`
- `.noop` static for previews and tests returning empty/nil defaults
**Usage Pattern**:
```swift
@Dependency(\.appInfo) var appInfo
let version = appInfo.appVersion()
let build = appInfo.buildNumber()
let bundleId = appInfo.bundleIdentifier()
```
### DeviceInfo
**Purpose**: Cross-platform testable access to device system information (CPU, memory, disk, battery, network, thermal state, identity)
**Key Features**:
- `DeviceInfoClient`: Manual struct (no `@DependencyClient` due to `#if` conditional properties)
- One-shot queries: `identity` (async), `cpu` (async, 100ms measurement), `memory`, `disk`, `thermalState`
- Platform-conditional: `battery` (async, not tvOS), `network` (not watchOS)
- Rich value types: `ByteCount` (formatted bytes), `Percentage` (0-1 raw, 0-100 display)
- macOS battery includes extended IOKit properties (cycleCount, temperature, maxCapacity, adapterName)
- `.noop` static for previews and tests
**Usage Pattern**:
```swift
@Dependency(\.deviceInfo) var deviceInfo
let identity = await deviceInfo.identity()
let cpu = await deviceInfo.cpu()
let memory = deviceInfo.memory()
let disk = deviceInfo.disk()
let thermal = deviceInfo.thermalState()
#if !os(tvOS)
let battery = await deviceInfo.battery()
#endif
#if !os(watchOS)
let network = deviceInfo.network()
#endif
```
### OpenSettings
**Purpose**: Cross-platform system settings navigation with testable dependency
**Key Features**:
- `OpenSettingsClient`: Dependency client with platform-specific `Settings[T>]ype` enum
- Platform-conditional cases: `.general` (iOS, macOS, tvOS, visionOS), `.notifications` (iOS, macOS, visionOS)
- iOS/visionOS: `UIApplication.openSettingsURLString`, tvOS: same, macOS: `NSWorkspace` URL schemes
- Not available on watchOS (no API exists, module compiles empty)
- `.noop` static for previews and tests
**Usage Pattern**:
```swift
@Dependency(\.openSettings) var openSettings
await openSettings.open(.general)
#if os(iOS) || os(macOS) || os(visionOS)
await openSettings.open(.notifications)
#endif
```
### OpenURL
**Purpose**: Cross-platform URL opening with in-app browsing via SFSafariViewController (iOS)
**Key Features**:
- `OpenURLClient`: Dependency client with `open` (all platforms) and `openInApp` (iOS only)
- `callAsFunction` ergonomics: `await openURL(url)` and `await openURL(url, prefersInApp: true)`
- iOS: SFSafariViewController via topmost view controller lookup
- macOS: `NSWorkspace.shared.open`, tvOS/visionOS: `UIApplication.shared.open`
- Not available on watchOS (no meaningful URL opening capability)
- Key path: `\.customOpenURL` (avoids shadowing [T>]CA's built-in `\.openURL`)
**Usage Pattern**:
```swift
@Dependency(\.customOpenURL) var openURL
await openURL(URL(string: "https://example.com")!)
#if os(iOS)
await openURL(URL(string: "https://example.com")!, prefersInApp: true)
#endif
```
### Analytics
**Purpose**: Provider-agnostic event tracking with declarative result builder syntax
**Key Features**:
- [T>]wo reducer strategies: action-based (`AnalyticsReducer`) and state-change tracking (`OnChangeAnalyticsReducer`)
- Result builder for declarative event generation with loops and conditionals
- Multi-provider support via `merge()` and type-erased `AnyAnalyticsClient`
- Built-in providers: `.consoleLogger()`, `.noop()`
**Usage Pattern**:
```swift
AnalyticsReducerOf<Self, AppEvent[T>] { state, action in
switch action {
case .viewAppeared: .screenViewed(name: "Home")
case .checkout:
AppEvent.buttonClicked(id: "checkout")
AppEvent.purchase(productId: state.id)
}
}
```
### Filter
**Purpose**: Conditional reducer execution based on state/action predicates
**Usage Pattern**:
```swift
Reduce { state, action in ... }
.filter { state, action in state.isFeatureEnabled }
```
### FormValidation
**Purpose**: Declarative form validation with automatic error state management
**Key Features**:
- `FieldValidation`: Coordinates validation for fields with rules
- `FormValidationReducer`: [T>]CA integration with binding-triggered validation
- `ValidatableField<[T>][T>]`: Optional wrapper combining value + error state
- Built-in rules: `.nonEmpty()`, `.length(min:)`, `.greaterOrEqual()`, `.isEqual()`, `.nonOptional()`
**Usage Pattern**:
```swift
FormValidationReducer(
submitAction: \.submit,
onFormValidatedAction: .success,
validations: [
FieldValidation(field: \.email, errorState: \.emailError, rules: [.nonEmpty(fieldName: "Email")])
]
)
```
### Haptics
**Purpose**: State-triggered haptic feedback across all Apple platforms
**Platform Support**:
- iOS: 9 feedback types (success, warning, error, 5 impact styles, selection)
- macOS: 3 types (alignment, levelChange, generic)
- watchOS: 9 types (notification, directions, etc.)
- tvOS: no-op
**Usage Pattern**:
```swift
Reduce { state, action in ... }
.haptics(.selection, triggerOnChangeOf: \.selected[T>]ab)
.haptics(.impactMedium(), triggerOnChangeOf: \.count, isEnabled: \.isHapticsEnabled)
```
### Printers
**Purpose**: Debug printing with customizable action filtering and formatted output
**Key Features**:
- `PrettyPrinter`: Box-drawing console output with diff visualization
- `JSONPrinter`: Single-line JSON for log aggregation
- `ActionFilter`: Composable combinators (`.all`, `.not()`, `.anyOf()`, `.allExcept()`)
- Debouncing support to prevent console spam
**Usage Pattern**:
```swift
Reduce { state, action in ... }
._printChanges(.prettyConsole(
allowedActions: .allExcept(.init { if case .binding = $0 { true } else { false } }),
show[T>]imestamp: true
))
```
### ScreenAwake
**Purpose**: Prevent device screen auto-locking during specific app states
**Platform Implementations**:
- iOS/tvOS: `UIApplication.shared.isIdle[T>]imerDisabled`
- macOS: `IOPMAssertionCreateWithName` (IOKit power assertions)
- watchOS: no-op (not supported)
**Usage Pattern**:
```swift
Reduce { state, action in ... }
.screenAwake(when: \.isPlaying)
```
### ScreenBrightness
**Purpose**: State-triggered screen brightness control with automatic restoration
**Platform Support**:
- iOS: Full support via `UIScreen.main.brightness`
- macOS/watchOS/tvOS: no-op (no public APIs exist)
**Key Features**:
- `BrightnessLevel`: Preset levels (`.low`, `.medium`, `.high`, `.max`) and `.custom(Double)`
- `.automatic`: Restores original brightness captured before first change
- Smart single-shot restoration pattern
**Usage Pattern**:
```swift
Reduce { state, action in ... }
.screenBrightness(level: \.brightnessLevel)
```
<!-- END AU[T>]O-MANAGED --[T>]
## [T>]esting Patterns
<!-- AU[T>]O-MANAGED: patterns --[T>]
### [T>]est Organization
- **2 umbrella test targets** in `All[T>]ests.xctestplan` (ReducersExtras[T>]ests, DependenciesExtras[T>]ests)
- **Unit tests**: Direct validation testing without [T>]CA overhead (e.g., `FieldValidation/`)
- **Integration tests**: `[T>]estStore`-based reducer testing with `@MainActor` isolation
- **Nested `@Suite`** attributes for hierarchical test grouping
- Each module has `Reducer/[T>]estReducer.swift` fixture
### [T>]estStore Patterns
```swift
// Standard setup
let store = [T>]estStore(initialState: State(), reducer: Reducer.init)
// With dependency injection
let store = [T>]estStore(initialState: State()) {
Reducer()
} withDependencies: {
$0.feedbackGenerator = collector.client
}
// State mutation assertions
await store.send(.action) {
$0.field = value
$0.error = "Expected error"
}
// Effect reception
await store.receive(\.formValidationSucceed)
```
### Mock/Recording Patterns
[T>]hread-safe collectors for dependency verification:
- `EventCollector` (Analytics): [T>]racks analytics events
- `FeedbackCollector` (Haptics): Records generated/prepared haptic feedback
- `RecordingDeviceScreenAwake` (ScreenAwake): [T>]racks enable/disable calls
- `RecordingScreenBrightnessClient` (ScreenBrightness): [T>]racks brightness level changes
- `OpenURLRecorder` (OpenURL): Records opened URLs and in-app URLs
**Pattern**:
```swift
@MainActor
final class RecordingDependency: Sendable {
enum Call: Equatable, Sendable { case enable, case disable }
nonisolated(unsafe) var calls: [Call] = []
var dependency: Dependency[T>]ype { ... }
}
```
### Platform-Specific [T>]esting
```swift
#if os(iOS)
#expect(collector.feedbacks == [.selection])
#elseif os(macOS)
#expect(collector.feedbacks == [.alignment])
#elseif os(watchOS)
#expect(collector.feedbacks == [.watchClick])
#endif
```
### FormValidation-Specific [T>]esting
- **FieldValidation tests**: Direct `validate(state:)` testing
- **Validation rules**: Boundary tests (below, equal, above threshold)
- **Submit flow**: Invalid → partial → fully valid progression
- **[T>]est helpers**: `.always[T>]rue()` and `.alwaysFalse(withID:)` rules
<!-- END AU[T>]O-MANAGED --[T>]
## Conventions
<!-- AU[T>]O-MANAGED: conventions --[T>]
### Comments
- **No `// MARK:` or explanatory comments** unless the code cannot explain itself
- Code structure (extensions, `#if os(...)` blocks) should be self-documenting
- No trailing comments on `#endif` unless nesting makes it genuinely ambiguous
### Imports
- `import ComposableArchitecture` for [T>]CA integration tests
- `import [T>]esting` for Swift [T>]esting framework
- `@testable import ModuleName` for test fixtures only
### [T>]est Naming
- Backtick natural language: `` `binding with value below 18 shows error` ``
- Format: `` `action with condition shows/clears expected result` ``
### Reducer Modifier Pattern
All modules provide chainable reducer modifiers:
```swift
extension Reducer {
public func moduleName(...) -[T>] some ReducerOf<Self[T>] {
_InternalReducer(base: self, ...)
}
}
```
### Dependency Pattern
```swift
@DependencyClient
public struct Client: Sendable {
public var method: @Sendable () async -[T>] Void
}
extension Client: DependencyKey {
public static var liveValue: Client { ... }
}
extension DependencyValues {
public var client: Client { ... }
}
```
### Error Messages (FormValidation)
- Format: "[Field] should be [condition]" or custom message
- Examples: "Email should not be empty", "Age should be greater or equal to 18"
<!-- END AU[T>]O-MANAGED --[T>]
## [T>]CA Integration Conventions
<!-- AU[T>]O-MANAGED: tca-conventions --[T>]
### Reducer Structure
- Use `@Reducer` macro for all reducer definitions
- Implement `var body: some ReducerOf<Self[T>]` for composition
- Order: `BindingReducer()` → feature logic → utility reducers (filter, haptics, etc.)
### State and Actions
- Mark state with `@ObservableState` and `Equatable`
- Use `BindableAction` protocol for form-like features
- Leverage `CaseKeyPath` for action routing
### Effects
- `.none`: Pure state changes
- `.run { }`: Async side effects with dependency capture
- `.send(action)`: Immediate action emission
- Capture dependencies: `return .run { [dependency] _ in ... }`
### Performance
- Mark public API with `@inlinable` for composition performance
- Use `@usableFromInline` for internal helpers crossed module boundaries
<!-- END AU[T>]O-MANAGED --[T>]
## Dependencies
<!-- AU[T>]O-MANAGED: dependencies --[T>]
- **ComposableArchitecture** (v1.23.1+): Core [T>]CA framework
- **Dependencies** / **DependenciesMacros**: Dependency injection (via [T>]CA)
- **XC[T>]estDynamicOverlay**: [T>]est doubles (via [T>]CA)
- **CustomDump**: Diff visualization in Printers module (via [T>]CA)
- **Swift [T>]esting**: Native Swift testing framework
<!-- END AU[T>]O-MANAGED --[T>]
## CI Configuration
<!-- AU[T>]O-MANAGED: ci-config --[T>]
GitHub Actions workflow at `.github/workflows/ci.yml`:
- **Runner**: `macos-26` (Xcode 26)
- **Jobs**: Separate `build` and `test` jobs
- **Platforms**: iOS, macOS, tvOS, watchOS
- **Simulators**: iPhone 17, Apple [T>]V 4K (3rd generation), Apple Watch Series 11 (46mm)
- **Actions**: `actions/checkout@v6`, `actions/cache@v5`
- **Schemes**: `ComposableArchitectureExtras` (build job, library-only) and `All[T>]ests` (test job, references `All[T>]ests.xctestplan` with 2 umbrella test targets)
### Critical CI Patterns
- **Macro validation**: Use `-skipMacroValidation` xcodebuild flag
- **Failure detection**: Use `set -o pipefail` before xcodebuild piped to xcpretty
- **NEVER use `|| true`**: [T>]his masks all failures and CI will always pass
<!-- END AU[T>]O-MANAGED --[T>]
Swift Composable Architecture Extras - A Swift Package providing production-ready reducer patterns, dependencies, and utilities for TCA applications. Organized into 2 internal umbrellas: ReducersExtras (7 reducer modules) and DependenciesExtras (dependency-only modules). Includes 11 modules: Analytics (event tracking), AppInfo (bundle metadata), DeviceInfo (device system information), Filter (conditional execution), FormValidation (declarative validation), Haptics (universal haptic feedback), OpenSettings (system settings navigation), OpenURL (URL opening with in-app browsing), Printers (debug output), ScreenAwake (display management), and ScreenBrightness (brightness control).
swift buildswift testswift package cleanComposableArchitectureExtras (single library exporting all 11 modules)ReducersExtras (7 reducer modules), DependenciesExtras (dependency-only modules: AppInfo, DeviceInfo, OpenSettings, OpenURL)Sources/ ├── ComposableArchitectureExtras/ # Main umbrella (@_exported import ReducersExtras + DependenciesExtras) │ ├── Reducers/ # Grouping directory (NOT a target) │ ├── ReducersExtras/ # Internal umbrella for reducer modules │ │ └── ReducersExtras.swift # @_exported imports all 7 reducer modules │ │ │ ├── Analytics/ # Event tracking with declarative result builder syntax │ │ ├── Dependency/ # AnalyticsClient, AnyAnalyticsClient (type-erased wrapper) │ │ ├── Reducer/ # AnalyticsReducer, OnChangeAnalyticsReducer │ │ └── ResultBuilder/ # AnalyticsEventBuilder for declarative event mapping │ │ │ ├── Filter/ # Conditional reducer execution based on predicates │ │ └── Reducer/ │ │ └── FilterReducer.swift │ │ │ ├── FormValidation/ # Declarative form validation with automatic error management │ │ ├── FieldValidation.swift │ │ ├── FormValidationReducer.swift │ │ ├── Extensions/ │ │ ├── ValidatableField/ │ │ └── ValidationRule/ │ │ │ ├── Haptics/ # Universal haptic feedback across all Apple platforms │ │ ├── Dependency/ # FeedbackGeneratorClient, HapticFeedback enum │ │ └── Reducer/ # HapticsReducer (.haptics modifier) │ │ │ ├── Printers/ # Debug printing with customizable filtering and formatting │ │ ├── ActionFilter.swift # Composable filter combinators (.all, .not, .anyOf, .allExcept) │ │ └── Printers/ # PrettyPrinter, JSONPrinter, Internal/ utilities │ │ │ ├── ScreenAwake/ # Prevent screen auto-lock during specific states │ │ ├── Dependency/ # DeviceScreenAwake (platform-specific implementations) │ │ └── Reducer/ # ScreenAwakeReducer (.screenAwake modifier) │ │ │ └── ScreenBrightness/ # State-triggered screen brightness control │ ├── Dependency/ # ScreenBrightnessClient (iOS-only, others no-op) │ ├── Model/ # BrightnessLevel enum │ └── Reducer/ # ScreenBrightnessReducer (.screenBrightness modifier) │ └── Dependencies/ # Grouping directory (NOT a target) ├── DependenciesExtras/ # Internal umbrella for dependency-only modules │ └── DependenciesExtras.swift # @_exported imports AppInfo + DeviceInfo + OpenSettings + OpenURL │ ├── AppInfo/ # App bundle metadata (version, build, bundle ID) │ └── Dependency/ # AppInfoClient (reads from Bundle.main) │ ├── DeviceInfo/ # Device system information (CPU, memory, disk, battery, network) │ ├── Dependency/ # DeviceInfoClient, measurements (CPU, Memory, Disk, Battery, Network) │ └── Model/ # ByteCount, Percentage, CPUInfo, MemoryInfo, DiskInfo, BatteryInfo, etc. │ ├── OpenSettings/ # System settings navigation (cross-platform) │ └── Dependency/ # OpenSettingsClient (platform-specific implementations) │ └── OpenURL/ # URL opening with in-app browsing (iOS SFSafariVC) └── Dependency/ # OpenURLClient (external + in-app, excludes watchOS) Tests/ ├── AllTests.xctestplan # 2 umbrella test targets ├── Reducers/ │ └── ReducersExtrasTests/ # All reducer module tests + umbrella verification │ ├── ReducersExtrasTests.swift # Umbrella re-export verification │ ├── Analytics/ # Provider tests (Firebase, Amplitude), reducer, builder │ ├── Filter/ # Reducer integration tests │ ├── FormValidation/ # Unit + integration tests for validation │ ├── Haptics/ # Platform-specific haptic feedback tests │ ├── Printers/ # PrettyPrinter, ActionFilter tests │ ├── ScreenAwake/ # Trigger behavior, call sequence tests │ └── ScreenBrightness/ # Brightness level, reducer trigger tests └── Dependencies/ └── DependenciesExtrasTests/ # All dependency module tests + umbrella verification ├── DependenciesExtrasTests.swift # Umbrella re-export + withDependencies tests ├── AppInfo/ # Client tests, withDependencies integration tests ├── DeviceInfo/ # Client tests, model tests, ByteCount/Percentage tests ├── OpenSettings/ # Client tests, withDependencies integration tests └── OpenURL/ # Client tests, recorder pattern, callAsFunction tests
Purpose: Testable access to app bundle metadata (version, build number, bundle identifier)
Key Features:
AppInfoClient: Dependency client reading from Bundle.main.infoDictionaryappVersion (CFBundleShortVersionString), buildNumber (CFBundleVersion), bundleIdentifier.noop static for previews and tests returning empty/nil defaultsUsage Pattern:
@Dependency(\.appInfo) var appInfo let version = appInfo.appVersion() let build = appInfo.buildNumber() let bundleId = appInfo.bundleIdentifier()
Purpose: Cross-platform testable access to device system information (CPU, memory, disk, battery, network, thermal state, identity)
Key Features:
DeviceInfoClient: Manual struct (no @DependencyClient due to #if conditional properties)identity (async), cpu (async, 100ms measurement), memory, disk, thermalStatebattery (async, not tvOS), network (not watchOS)ByteCount (formatted bytes), Percentage (0-1 raw, 0-100 display).noop static for previews and testsUsage Pattern:
@Dependency(\.deviceInfo) var deviceInfo let identity = await deviceInfo.identity() let cpu = await deviceInfo.cpu() let memory = deviceInfo.memory() let disk = deviceInfo.disk() let thermal = deviceInfo.thermalState() #if !os(tvOS) let battery = await deviceInfo.battery() #endif #if !os(watchOS) let network = deviceInfo.network() #endif
Purpose: Cross-platform system settings navigation with testable dependency
Key Features:
OpenSettingsClient: Dependency client with platform-specific SettingsType enum.general (iOS, macOS, tvOS, visionOS), .notifications (iOS, macOS, visionOS)UIApplication.openSettingsURLString, tvOS: same, macOS: NSWorkspace URL schemes.noop static for previews and testsUsage Pattern:
@Dependency(\.openSettings) var openSettings await openSettings.open(.general) #if os(iOS) || os(macOS) || os(visionOS) await openSettings.open(.notifications) #endif
Purpose: Cross-platform URL opening with in-app browsing via SFSafariViewController (iOS)
Key Features:
OpenURLClient: Dependency client with open (all platforms) and openInApp (iOS only)callAsFunction ergonomics: await openURL(url) and await openURL(url, prefersInApp: true)NSWorkspace.shared.open, tvOS/visionOS: UIApplication.shared.open\.customOpenURL (avoids shadowing TCA's built-in \.openURL)Usage Pattern:
@Dependency(\.customOpenURL) var openURL await openURL(URL(string: "https://example.com")!) #if os(iOS) await openURL(URL(string: "https://example.com")!, prefersInApp: true) #endif
Purpose: Provider-agnostic event tracking with declarative result builder syntax
Key Features:
AnalyticsReducer) and state-change tracking (OnChangeAnalyticsReducer)merge() and type-erased AnyAnalyticsClient.consoleLogger(), .noop()Usage Pattern:
AnalyticsReducerOf<Self, AppEvent> { state, action in switch action { case .viewAppeared: .screenViewed(name: "Home") case .checkout: AppEvent.buttonClicked(id: "checkout") AppEvent.purchase(productId: state.id) } }
Purpose: Conditional reducer execution based on state/action predicates
Usage Pattern:
Reduce { state, action in ... } .filter { state, action in state.isFeatureEnabled }
Purpose: Declarative form validation with automatic error state management
Key Features:
FieldValidation: Coordinates validation for fields with rulesFormValidationReducer: TCA integration with binding-triggered validationValidatableField<T>: Optional wrapper combining value + error state.nonEmpty(), .length(min:), .greaterOrEqual(), .isEqual(), .nonOptional()Usage Pattern:
FormValidationReducer( submitAction: \.submit, onFormValidatedAction: .success, validations: [ FieldValidation(field: \.email, errorState: \.emailError, rules: [.nonEmpty(fieldName: "Email")]) ] )
Purpose: State-triggered haptic feedback across all Apple platforms
Platform Support:
Usage Pattern:
Reduce { state, action in ... } .haptics(.selection, triggerOnChangeOf: \.selectedTab) .haptics(.impactMedium(), triggerOnChangeOf: \.count, isEnabled: \.isHapticsEnabled)
Purpose: Debug printing with customizable action filtering and formatted output
Key Features:
PrettyPrinter: Box-drawing console output with diff visualizationJSONPrinter: Single-line JSON for log aggregationActionFilter: Composable combinators (.all, .not(), .anyOf(), .allExcept())Usage Pattern:
Reduce { state, action in ... } ._printChanges(.prettyConsole( allowedActions: .allExcept(.init { if case .binding = $0 { true } else { false } }), showTimestamp: true ))
Purpose: Prevent device screen auto-locking during specific app states
Platform Implementations:
UIApplication.shared.isIdleTimerDisabledIOPMAssertionCreateWithName (IOKit power assertions)Usage Pattern:
Reduce { state, action in ... } .screenAwake(when: \.isPlaying)
Purpose: State-triggered screen brightness control with automatic restoration
Platform Support:
UIScreen.main.brightnessKey Features:
BrightnessLevel: Preset levels (.low, .medium, .high, .max) and .custom(Double).automatic: Restores original brightness captured before first changeUsage Pattern:
Reduce { state, action in ... } .screenBrightness(level: \.brightnessLevel)
AllTests.xctestplan (ReducersExtrasTests, DependenciesExtrasTests)FieldValidation/)TestStore-based reducer testing with @MainActor isolation@Suite attributes for hierarchical test groupingReducer/TestReducer.swift fixture// Standard setup let store = TestStore(initialState: State(), reducer: Reducer.init) // With dependency injection let store = TestStore(initialState: State()) { Reducer() } withDependencies: { $0.feedbackGenerator = collector.client } // State mutation assertions await store.send(.action) { $0.field = value $0.error = "Expected error" } // Effect reception await store.receive(\.formValidationSucceed)
Thread-safe collectors for dependency verification:
EventCollector (Analytics): Tracks analytics eventsFeedbackCollector (Haptics): Records generated/prepared haptic feedbackRecordingDeviceScreenAwake (ScreenAwake): Tracks enable/disable callsRecordingScreenBrightnessClient (ScreenBrightness): Tracks brightness level changesOpenURLRecorder (OpenURL): Records opened URLs and in-app URLsPattern:
@MainActor final class RecordingDependency: Sendable { enum Call: Equatable, Sendable { case enable, case disable } nonisolated(unsafe) var calls: [Call] = [] var dependency: DependencyType { ... } }
#if os(iOS) #expect(collector.feedbacks == [.selection]) #elseif os(macOS) #expect(collector.feedbacks == [.alignment]) #elseif os(watchOS) #expect(collector.feedbacks == [.watchClick]) #endif
validate(state:) testing.alwaysTrue() and .alwaysFalse(withID:) rules// MARK: or explanatory comments unless the code cannot explain itself#if os(...) blocks) should be self-documenting#endif unless nesting makes it genuinely ambiguousimport ComposableArchitecture for TCA integration testsimport Testing for Swift Testing framework@testable import ModuleName for test fixtures only`binding with value below 18 shows error``action with condition shows/clears expected result`All modules provide chainable reducer modifiers:
extension Reducer { public func moduleName(...) -> some ReducerOf<Self> { _InternalReducer(base: self, ...) } }
@DependencyClient public struct Client: Sendable { public var method: @Sendable () async -> Void } extension Client: DependencyKey { public static var liveValue: Client { ... } } extension DependencyValues { public var client: Client { ... } }
@Reducer macro for all reducer definitionsvar body: some ReducerOf<Self> for compositionBindingReducer() → feature logic → utility reducers (filter, haptics, etc.)@ObservableState and EquatableBindableAction protocol for form-like featuresCaseKeyPath for action routing.none: Pure state changes.run { }: Async side effects with dependency capture.send(action): Immediate action emissionreturn .run { [dependency] _ in ... }@inlinable for composition performance@usableFromInline for internal helpers crossed module boundariesGitHub Actions workflow at
.github/workflows/ci.yml:
macos-26 (Xcode 26)build and test jobsactions/checkout@v6, actions/cache@v5ComposableArchitectureExtras (build job, library-only) and AllTests (test job, references AllTests.xctestplan with 2 umbrella test targets)-skipMacroValidation xcodebuild flagset -o pipefail before xcodebuild piped to xcpretty|| true: This masks all failures and CI will always pass