Coding

CLAUDE.md

<!-- AUTO-MANAGED: project-description -->

promptBeginner5 min to valuemarkdown
0 views
Feb 8, 2026

Sign in to like and favorite skills

Prompt Playground

1 Variables

Fill Variables

Preview

# 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>]
Share: