Skip to content

Conversation

@ohitsdaniel
Copy link
Contributor

@ohitsdaniel ohitsdaniel commented Mar 23, 2022

We ran into a race-condition where force unwrapping the cached value lead to a crash. I'd like to suggest switching to a safer if-var pattern as see in the IfLetStore implementation to avoid potential crashes.

@stephencelis
Copy link
Member

@ohitsdaniel Thanks for taking the time to PR!

Can you share a repro of the race condition?

We have to be very careful when making changes to ForEachStore due to subtle SwiftUI behaviors, and there've been a lot of PRs, issues, and discussions around it. We should probably have some kind of regression test app in place where we can ensure that changes don't impact performance.

I think we'll want to take a little time with this PR to ensure we don't introduce any performance regressions, but it would help to also have a reproducible crash, should we need to find an alternate fix.

@ohitsdaniel
Copy link
Contributor Author

Thanks for the feedback! I can also see some issues with the content of the ForEach view becoming optional (an optional resolves to an EmptyView in a ViewBuilder, so it's still counted as an element and could lead to some empty rows).

I'll try to come up with a small example application that reproduces the crash. My assumption is that it has something to do with a .id() view modifier being applied to a view further out in the View hierarchy and that changing leads to the crash. Let's see, if I can reproduce it.

@stephencelis
Copy link
Member

@ohitsdaniel This PR does unfortunately regress some behavior, where each row's view is initialized eagerly, even if the row is not in view. There was an issue/discussion about this, and a fix, but I wasn't able to quickly dig it up.

The easiest way to see the problem might be in the Todos project. On your branch:

  1. Update AppState to be initialized with a bunch of todos:

    var todos: IdentifiedArrayOf<Todo> = .init(
      uniqueElements: (1...100).map { _ in Todo(id: .init()) }
    )
  2. (Optional) Remove the .debug() line from appReducer to reduce noise.

  3. Add the following initializer to TodoView:

    init(store: Store<Todo, TodoAction>) {
      print("TodoView.init")
      self.store = store
    }
  4. Run the app.

You'll notice TodoView.init is printed 100 times, even though only 12-20 rows are rendered.

If you switch over to main and try again, TodoView.init will only be printed 12-20 times.

Initializing views should be very lightweight, so this could be a trade-off to take on if the problem is big enough. However, we also worry that incurring this amount of initialization could be quite bad for very large lists. Ideally there's another workaround to the problem.

@ohitsdaniel
Copy link
Contributor Author

ohitsdaniel commented Mar 25, 2022

Thanks for this easy way to test if the new behaviour matches the old one. I think the reason for the crash mentioned in the feedback is ForEachStore's conformance to DynamicViewContent, but I decided not to investigate too much.

By introducing a EachState: Equatable constraint, we can use a plain ForEach in the content closure. ForEach captures the element in the ForEach content builder closure context, in case the element is removed and we run into the situation mentioned in the Feedback. This approach uses the 'cached' value to retrieve the ID and scopes the store using the cached ID and falls back to the element captured in the closure context.

If introducing a EachState: Equatable constraint on the init is too much of a breaking change from your perspective, I will try to think of something else. ;)

@ohitsdaniel
Copy link
Contributor Author

We managed to reproduce the crash reliably.

To reproduce

Apply the diff below to the example TCA app, Todos.
Use XCode to run in simulator or on real device.
Press the Go Crazy button at the top. This will make the main thread busy for a couple seconds, creating and deleting many items many times.
Swipe down on the list of TODOs.
With the proposed fix, the Go Crazy button is still slow but the crash goes away.

Diff

diff --git a/Examples/Todos/Todos/Todos.swift b/Examples/Todos/Todos/Todos.swift
index 6945fa2..cca92aa 100644
--- a/Examples/Todos/Todos/Todos.swift
+++ b/Examples/Todos/Todos/Todos.swift
@@ -21,6 +21,9 @@ struct AppState: Equatable {
   }
 }
 
+let crazyNumberOfTodos = 100
+let crazyNumberOfTimes : UInt = 40
+
 enum AppAction: Equatable {
   case addTodoButtonTapped
   case clearCompletedButtonTapped
@@ -30,6 +33,7 @@ enum AppAction: Equatable {
   case move(IndexSet, Int)
   case sortCompletedTodos
   case todo(id: Todo.ID, action: TodoAction)
+  case goCrazy(howManyTimes: UInt)
 }
 
 struct AppEnvironment {
@@ -94,10 +98,22 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
 
     case .todo:
       return .none
+
+    case let .goCrazy(howManyTimes):
+      state.todos = []
+      if howManyTimes > 0 {
+        for _ in 1 ... crazyNumberOfTodos {
+          state.todos.insert(Todo(id: environment.uuid(), isComplete: true), at: 0)
+        }
+        return Effect(value: .goCrazy(howManyTimes: howManyTimes - 1))
+          .receive(on: environment.mainQueue)
+          .eraseToEffect()
+      } else {
+        return .none
+      }
     }
   }
 )
-.debug()
 
 struct AppView: View {
   let store: Store<AppState, AppAction>
@@ -147,10 +163,9 @@ struct AppView: View {
       .navigationBarItems(
         trailing: HStack(spacing: 20) {
           EditButton()
-          Button("Clear Completed") {
-            self.viewStore.send(.clearCompletedButtonTapped, animation: .default)
+          Button("Go Crazy") {
+            self.viewStore.send(.goCrazy(howManyTimes: crazyNumberOfTimes))
           }
-          .disabled(self.viewStore.isClearCompletedButtonDisabled)
           Button("Add Todo") { self.viewStore.send(.addTodoButtonTapped, animation: .default) }
         }
       )

Comment on lines 97 to 110
self.content = {
WithViewStore(store.scope(state: { $0.ids })) { viewStore in
ForEach(viewStore.state, id: \.self) { id -> EachContent in
WithViewStore(store) { viewStore in
ForEach(viewStore.state, id: viewStore.state.id) { element in
// NB: We cache elements here to avoid a potential crash where SwiftUI may re-evaluate
// views for elements no longer in the collection.
//
// Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
var element = store.state.value[id: id]!
return content(
let id = element[keyPath: viewStore.state.id]
content(
store.scope(
state: {
element = $0[id: id] ?? element
return element
},
state: { $0[id: id] ?? element },
action: { (id, $0) }
)
)
Copy link
Member

@stephencelis stephencelis Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ohitsdaniel What if we have an outer fallback cache, as well? Does this fix things or cause any weird behavior?

    var outer: EachState?
    self.data = store.state.value
    self.content = {
      WithViewStore(store.scope(state: { $0.ids })) { viewStore in
        ForEach(viewStore.state, id: \.self) { id -> EachContent in
          // NB: We cache elements here to avoid a potential crash where SwiftUI may re-evaluate
          //     views for elements no longer in the collection.
          //
          // Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
          var inner = store.state.value[id: id] ?? outer!
          outer = inner
          return content(
            store.scope(
              state: {
                inner = $0[id: id] ?? inner
                return inner
              },
              action: { (id, $0) }
            )
          )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Stephen,

Your fix works for my example! The race condition is reduced. But it still breaks reliably in a more complicated example.

With a class, it can happen that an IdentifiedArray is modified during view execution. This is of course not a good pattern, ideally devs only use structs in their states, but some types from libraries might be funny classes and outside of the control of developer. I guess TCA should be robust to that.

The repro makes lots of 1-element IdentifiedArrays, each todo inside a separate IdentifiedArray, and it tries to empty some of the inner arrays before the corresponding view can be evaluated.

I show the repro in two parts.

Repro diff 1/2 from upstream TCA: core stuff

diff --git a/Examples/Todos/Todos/Todo.swift b/Examples/Todos/Todos/Todo.swift
index 327b7a5d8..279e38363 100644
--- a/Examples/Todos/Todos/Todo.swift
+++ b/Examples/Todos/Todos/Todo.swift
@@ -2,6 +2,22 @@ import ComposableArchitecture
 import Foundation
 import SwiftUI
 
+class IdentifiedCell<T> : Equatable, Identifiable where T: Equatable {
+  public var id: UUID
+  public var value: T
+  public var pointer: IdentifiedCell<T>?
+
+  init(_ value: T, pointer: IdentifiedCell<T>? = .none) {
+    self.id = .init()
+    self.value = value
+    self.pointer = pointer
+  }
+
+  static func ==(lhs: IdentifiedCell<T>, rhs: IdentifiedCell<T>) -> Bool {
+      return lhs.id == rhs.id && lhs.value == rhs.value
+  }
+}
+
 struct Todo: Equatable, Identifiable {
   var description = ""
   let id: UUID
diff --git a/Examples/Todos/Todos/Todos.swift b/Examples/Todos/Todos/Todos.swift
index 6945fa294..bd2b618ac 100644
--- a/Examples/Todos/Todos/Todos.swift
+++ b/Examples/Todos/Todos/Todos.swift
@@ -7,10 +7,12 @@ enum Filter: LocalizedStringKey, CaseIterable, Hashable {
   case completed = "Completed"
 }
 
+typealias IdentifiedCellTodoArray = IdentifiedCell<IdentifiedArrayOf<Todo>>
+
 struct AppState: Equatable {
   var editMode: EditMode = .inactive
   var filter: Filter = .all
-  var todos: IdentifiedArrayOf<Todo> = []
+  var todos: IdentifiedArrayOf<IdentifiedCellTodoArray> = []
 
   var filteredTodos: IdentifiedArrayOf<Todo> {
     switch filter {
@@ -21,6 +23,9 @@ struct AppState: Equatable {
   }
 }
 
+let crazyNumberOfTodos = 100
+let crazyNumberOfTimes = 40
+
 enum AppAction: Equatable {
   case addTodoButtonTapped
   case clearCompletedButtonTapped
@@ -30,6 +35,7 @@ enum AppAction: Equatable {
   case move(IndexSet, Int)
   case sortCompletedTodos
   case todo(id: Todo.ID, action: TodoAction)
+  case goCrazy(howManyTimes: UInt)
 }
 
 struct AppEnvironment {
@@ -94,10 +100,25 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
 
     case .todo:
       return .none
+
+    case let .goCrazy(howManyTimes):
+      state.todos = []
+      if howManyTimes > 0 {
+        var next : IdentifiedCellTodoArray?
+        for i in 0 ..< crazyNumberOfTodos {
+          let todo = Todo(description: "\(howManyTimes) / \(i)", id: environment.uuid(), isComplete: true)
+          next = .init([todo], pointer: next)
+          state.todos.insert(next!, at: 0)
+        }
+        return Effect(value: .goCrazy(howManyTimes: howManyTimes - 1))
+          .receive(on: environment.mainQueue)
+          .eraseToEffect()
+      } else {
+        return .none
+      }
     }
   }
 )
-.debug()
 
 struct AppView: View {
   let store: Store<AppState, AppAction>
@@ -135,10 +156,19 @@ struct AppView: View {
         .padding(.horizontal)
 
         List {
-          ForEachStore(
-            self.store.scope(state: \.filteredTodos, action: AppAction.todo(id:action:)),
-            content: TodoView.init(store:)
-          )
+          ForEachStore(self.store.scope(
+              state: \.todos,
+              action: AppAction.todo(id:action:))) { firstStore in
+            WithViewStore(firstStore) { viewStore in
+              ForEachStore(firstStore.scope(
+                  state: \.value,
+                  action: TodoActionWrapper.Wrap(id:action:))) { secondStore in
+                // On every second TodoView, this quickly removes the next Todo
+                let _ = viewStore.state.pointer?.value = []
+                TodoView.init(store: secondStore)
+              }
+            }
+          }
           .onDelete { self.viewStore.send(.delete($0)) }
           .onMove { self.viewStore.send(.move($0, $1)) }
         }
@@ -147,10 +177,9 @@ struct AppView: View {
       .navigationBarItems(
         trailing: HStack(spacing: 20) {
           EditButton()
-          Button("Clear Completed") {
-            self.viewStore.send(.clearCompletedButtonTapped, animation: .default)
+          Button("Go Crazy") {
+            self.viewStore.send(.goCrazy(howManyTimes: crazyNumberOfTimes))
           }
-          .disabled(self.viewStore.isClearCompletedButtonDisabled)
           Button("Add Todo") { self.viewStore.send(.addTodoButtonTapped, animation: .default) }
         }
       )

Repro diff 2/2 from upstream TCA: support

diff --git a/Examples/Todos/Todos/Todos.swift b/Examples/Todos/Todos/Todos.swift
index bd2b618ac..5ccef637d 100644
--- a/Examples/Todos/Todos/Todos.swift
+++ b/Examples/Todos/Todos/Todos.swift
@@ -14,11 +14,16 @@ struct AppState: Equatable {
   var filter: Filter = .all
   var todos: IdentifiedArrayOf<IdentifiedCellTodoArray> = []
 
+  var flatTodos: IdentifiedArrayOf<Todo> {
+    let elements = self.todos.elements.flatMap { inner in inner.value.elements };
+    return IdentifiedArrayOf.init(uniqueElements: elements)
+  }
+
   var filteredTodos: IdentifiedArrayOf<Todo> {
     switch filter {
-    case .active: return self.todos.filter { !$0.isComplete }
-    case .all: return self.todos
-    case .completed: return self.todos.filter(\.isComplete)
+    case .active: return self.flatTodos.filter { !$0.isComplete }
+    case .all: return self.flatTodos
+    case .completed: return self.flatTodos.filter(\.isComplete)
     }
   }
 }
@@ -34,8 +39,12 @@ enum AppAction: Equatable {
   case filterPicked(Filter)
   case move(IndexSet, Int)
   case sortCompletedTodos
-  case todo(id: Todo.ID, action: TodoAction)
-  case goCrazy(howManyTimes: UInt)
+  case todo(id: Todo.ID, action: TodoActionWrapper)
+  case goCrazy(howManyTimes: Int)
+}
+
+enum TodoActionWrapper: Equatable {
+  case Wrap(id: Todo.ID, action: TodoAction)
 }
 
 struct AppEnvironment {
@@ -45,6 +54,10 @@ struct AppEnvironment {
 
 let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
   todoReducer.forEach(
+    state: \.value,
+    action: /TodoActionWrapper.Wrap(id:action:),
+    environment: { _ in TodoEnvironment() }
+  ).forEach(
     state: \.todos,
     action: /AppAction.todo(id:action:),
     environment: { _ in TodoEnvironment() }
@@ -52,11 +65,10 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
   Reducer { state, action, environment in
     switch action {
     case .addTodoButtonTapped:
-      state.todos.insert(Todo(id: environment.uuid()), at: 0)
+      state.todos.insert(.init([Todo(id: environment.uuid())]), at: 0)
       return .none
 
     case .clearCompletedButtonTapped:
-      state.todos.removeAll(where: \.isComplete)
       return .none
 
     case let .delete(indexSet):
@@ -90,14 +102,8 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
         .eraseToEffect()
 
     case .sortCompletedTodos:
-      state.todos.sort { $1.isComplete && !$0.isComplete }
       return .none
 
-    case .todo(id: _, action: .checkBoxToggled):
-      struct TodoCompletionId: Hashable {}
-      return Effect(value: .sortCompletedTodos)
-        .debounce(id: TodoCompletionId(), for: 1, scheduler: environment.mainQueue.animation())
-
     case .todo:
       return .none
 
@@ -137,7 +143,7 @@ struct AppView: View {
     init(state: AppState) {
       self.editMode = state.editMode
       self.filter = state.filter
-      self.isClearCompletedButtonDisabled = !state.todos.contains(where: \.isComplete)
+      self.isClearCompletedButtonDisabled = !state.flatTodos.contains(where: \.isComplete)
     }
   }
 
@@ -192,31 +198,33 @@ struct AppView: View {
   }
 }
 
-extension IdentifiedArray where ID == Todo.ID, Element == Todo {
-  static let mock: Self = [
-    Todo(
-      description: "Check Mail",
-      id: UUID(uuidString: "DEADBEEF-DEAD-BEEF-DEAD-BEEDDEADBEEF")!,
-      isComplete: false
-    ),
-    Todo(
-      description: "Buy Milk",
-      id: UUID(uuidString: "CAFEBEEF-CAFE-BEEF-CAFE-BEEFCAFEBEEF")!,
-      isComplete: false
-    ),
-    Todo(
-      description: "Call Mom",
-      id: UUID(uuidString: "D00DCAFE-D00D-CAFE-D00D-CAFED00DCAFE")!,
-      isComplete: true
-    ),
-  ]
-}
+let mock: [Todo] = [
+  Todo(
+    description: "Check Mail",
+    id: UUID(uuidString: "DEADBEEF-DEAD-BEEF-DEAD-BEEDDEADBEEF")!,
+    isComplete: false
+  ),
+  Todo(
+    description: "Buy Milk",
+    id: UUID(uuidString: "CAFEBEEF-CAFE-BEEF-CAFE-BEEFCAFEBEEF")!,
+    isComplete: false
+  ),
+  Todo(
+    description: "Call Mom",
+    id: UUID(uuidString: "D00DCAFE-D00D-CAFE-D00D-CAFED00DCAFE")!,
+    isComplete: true
+  ),
+]
 
 struct AppView_Previews: PreviewProvider {
+  static let todos = IdentifiedArrayOf.init(uniqueElements: mock.map {
+    todo in IdentifiedCell(IdentifiedArrayOf.init(uniqueElements: [todo]))
+  })
+
   static var previews: some View {
     AppView(
       store: Store(
-        initialState: AppState(todos: .mock),
+        initialState: AppState(todos: todos),
         reducer: appReducer,
         environment: AppEnvironment(
           mainQueue: .main,

@iblagajic
Copy link

Hey guys 👋

I am able to reproduce the crash on my side, but still trying to figure out a reasonably sized example I could provide here.

My app crashes when using a search bar to filter out a LazyVStack subview that's not visible at that time (is "beyond" the bottom of the stack).
However, it isn't easy to reproduce since it depends on some factors that seem unrelated at first (e.g. type of subviews in the LazyVStack).

Adding a small delay (10ms) before loading the new state fixes it, but ofc I'm not happy with that solution. 😀
My best guess so far is that it depends on rendering time, but still digging to find some concrete evidence.

If you have any tips how to proceed, I'd be thankful. 😇

@tgrapperon
Copy link
Contributor

Hey @iblagajic!
I have a branch that reworks ForEachStore implementation. I think it should build in its current form. Would you mind giving it a spin to check if it helps? You only have to replace your TCA dependency by:

.package(url: "https://github.com/tgrapperon/swift-composable-architecture", branch: "generic-foreach"),

It should build without touching anything hopefully.

@iblagajic
Copy link

@tgrapperon thanks for the reply! 🙇

Unfortunately (or fortunately?), since I reset the cache to switch to your branch, I can't reproduce it on either branch anymore (generic-foreach or 0.44.1). I had one specific case that consistently crashed and now it's working fine on 0.44.1.
If I'm able to reproduce it again, I'll post here. 🤷

@tgrapperon
Copy link
Contributor

@iblagajic It is also possible that you hit this crash if this is something fixed when building from a clean state.

@iblagajic
Copy link

@tgrapperon Thanks for the heads up.

Unfortunately, I was able to reproduce this crash again on 0.44.1 and this time I tried to check out your branch too. However, it still crashes (IterableContainer, line 43).

@tgrapperon
Copy link
Contributor

@iblagajic. Yes, this is indeed the equivalent of the force-unwrap. It means that no data can be found for the given id, which shouldn't happen. Feel free to open a dedicated issue/discussion if you manage to extract a reproduction.

Copy link
Member

@stephencelis stephencelis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for sitting on this for so long, but just revisited and rebased and think it's a good change, thanks!

@stephencelis stephencelis merged commit bd23264 into pointfreeco:main Jun 22, 2023
renovate bot referenced this pull request in cgrindel/rules_swift_package_manager Jul 6, 2023
…ure to from: "0.55.0" (#452)

[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
|
[pointfreeco/swift-composable-architecture](https://togithub.com/pointfreeco/swift-composable-architecture)
| minor | `from: "0.51.0"` -> `from: "0.55.0"` |

---

### Release Notes

<details>
<summary>pointfreeco/swift-composable-architecture
(pointfreeco/swift-composable-architecture)</summary>

###
[`v0.55.0`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.55.0)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.54.1...0.55.0)

#### What's Changed

- Added: The Composable Architecture's SwiftUI bindings integration has
been greatly improved, with better support for view state bindings
([https://github.com/pointfreeco/swift-composable-architecture/pull/2215](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2215)).
- Added: `Store.send` and `Store.withState` have been added, for sending
actions to stores and accessing store state without needing a view store
([https://github.com/pointfreeco/swift-composable-architecture/pull/2222](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2222)).
- Added: `ReducerProtocol.onChange`
([https://github.com/pointfreeco/swift-composable-architecture/pull/2226](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2226)).
- Added: `EffectOf<Reducer>` convenience type alias to
`EffectTask<Reducer.Action>`
([https://github.com/pointfreeco/swift-composable-architecture/pull/2237](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2237)).
- Updated: `swiftui-navigation` has been bumped to 0.8.0
([https://github.com/pointfreeco/swift-composable-architecture/pull/2239](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2239)).
- Improved: `TestStore` failure messages have been improved
([https://github.com/pointfreeco/swift-composable-architecture/pull/2227](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2227),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2236](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2236)).
- Fixed: `ForEachStore` no longer force-unwraps its cached value,
avoiding crashes in race condition-heavy code (thanks
[@&#8203;ohitsdaniel](https://togithub.com/ohitsdaniel),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1036](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1036)).
- Fixed: Addressed a few Xcode 15 warnings (Swift 6 errors)
([https://github.com/pointfreeco/swift-composable-architecture/pull/2213](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2213)).
- Deprecated: `Effect.cancel(ids:)` has been deprecated
([https://github.com/pointfreeco/swift-composable-architecture/pull/2221](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2221)).
- Infrastructure: Documentation improvements (thanks
[@&#8203;ccxla](https://togithub.com/ccxla),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2185](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2185),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2184](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2184),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2183](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2183);
[@&#8203;tomu28](https://togithub.com/tomu28),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2209](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2209);
[@&#8203;alexhunsley](https://togithub.com/alexhunsley),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2204](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2204);
[@&#8203;oronbz](https://togithub.com/oronbz),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2173](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2173);
[https://github.com/pointfreeco/swift-composable-architecture/pull/2225](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2225);
[https://github.com/pointfreeco/swift-composable-architecture/pull/2238](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2238)).

#### New Contributors

- [@&#8203;tomu28](https://togithub.com/tomu28) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2209](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2209)
- [@&#8203;alexhunsley](https://togithub.com/alexhunsley) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2204](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2204)
- [@&#8203;ohitsdaniel](https://togithub.com/ohitsdaniel) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/1036](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1036)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.54.1...0.55.0

###
[`v0.54.1`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.54.1)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.54.0...0.54.1)

#### What's Changed

- Fixed: A regression introduced in 0.54.0 prevented some Composable
Architecture projects from building in release due to a Swift compiler
bug. We have removed some `@inlineable` attributes to work around this
bug
([https://github.com/pointfreeco/swift-composable-architecture/pull/2201](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2201)).
While we don't anticipate noticeable runtime performance regressions in
release builds of applications, please report any issues you may see.
- Infrastructure: Added Hindi (Indian) translation of README.md (thanks
[@&#8203;akashsoni01](https://togithub.com/akashsoni01),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2171](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2171))
- Infrastructure: Documentation updates and fixes (thanks
[@&#8203;Sajjon](https://togithub.com/Sajjon),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2150](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2150);
[@&#8203;tatsuz0u](https://togithub.com/tatsuz0u),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2155](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2155);
[@&#8203;hmhv](https://togithub.com/hmhv),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2152](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2152);
[@&#8203;MarshalGeazipp](https://togithub.com/MarshalGeazipp),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2154](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2154);
[@&#8203;Ryu0118](https://togithub.com/Ryu0118),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2153](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2153);
[@&#8203;Czajnikowski](https://togithub.com/Czajnikowski),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2157](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2157);
[@&#8203;kristofferjohansson](https://togithub.com/kristofferjohansson),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2159](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2159);
[@&#8203;jaesung-0o0](https://togithub.com/jaesung-0o0),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2160](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2160);
[https://github.com/pointfreeco/swift-composable-architecture/pull/2161](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2161);
[@&#8203;takehilo](https://togithub.com/takehilo),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2165](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2165);
[@&#8203;nickkohrn](https://togithub.com/nickkohrn),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2168](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2168)
[https://github.com/pointfreeco/swift-composable-architecture/pull/2169](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2169);
[@&#8203;d-date](https://togithub.com/d-date),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2174](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2174);
[@&#8203;oronbz](https://togithub.com/oronbz),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2175](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2175),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2176](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2176),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2177](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2177);
[@&#8203;devMinseok](https://togithub.com/devMinseok),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2180](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2180);
[@&#8203;ccxla](https://togithub.com/ccxla),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2181](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2181);
[@&#8203;filblue](https://togithub.com/filblue),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2188](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2188);
[@&#8203;thomastosoni](https://togithub.com/thomastosoni),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2190](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2190)).

#### New Contributors

- [@&#8203;Sajjon](https://togithub.com/Sajjon) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2150](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2150)
- [@&#8203;MarshalGeazipp](https://togithub.com/MarshalGeazipp) made
their first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2154](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2154)
- [@&#8203;takehilo](https://togithub.com/takehilo) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2165](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2165)
- [@&#8203;nickkohrn](https://togithub.com/nickkohrn) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2168](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2168)
- [@&#8203;akashsoni01](https://togithub.com/akashsoni01) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2171](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2171)
- [@&#8203;d-date](https://togithub.com/d-date) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2174](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2174)
- [@&#8203;oronbz](https://togithub.com/oronbz) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2175](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2175)
- [@&#8203;devMinseok](https://togithub.com/devMinseok) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2180](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2180)
- [@&#8203;ccxla](https://togithub.com/ccxla) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2181](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2181)
- [@&#8203;thomastosoni](https://togithub.com/thomastosoni) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2190](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2190)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.54.0...0.54.1

###
[`v0.54.0`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.54.0)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.53.2...0.54.0)

#### What's Changed

- Added: All-new navigation tools for presenting child features
([https://github.com/pointfreeco/swift-composable-architecture/pull/1945](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1945),
[pointfreeco/swift-composable-architecture#1944,
[pointfreeco/swift-composable-architecture#2048).

See the associated
[documentation](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/navigation)
and
[tutorial](https://pointfreeco.github.io/swift-composable-architecture/main/tutorials/meetcomposablearchitecture/#navigation)
for how to incorporate these tools into your applications today!

- Added: `TestStore.assert`, for asserting state changes on
non-exhaustive stores at any time
([https://github.com/pointfreeco/swift-composable-architecture/pull/2123](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2123)).

- Fixed: Ensure that a test store helper runs on the main actor
([https://github.com/pointfreeco/swift-composable-architecture/pull/2117](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2117)).

- Added: Ukrainian translation of TCA's README (thanks
[@&#8203;barabashd](https://togithub.com/barabashd),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2121](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2121)).

- Infrastructure: DocC organization
([https://github.com/pointfreeco/swift-composable-architecture/pull/2118](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2118)).

- Infrastructure: Ensure CI runs library tests in release
([https://github.com/pointfreeco/swift-composable-architecture/pull/2120](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2120)).

- Fix assertion values by
[@&#8203;tomassliz](https://togithub.com/tomassliz) in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2128](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2128)

- Infrastructure: Documentation fixes (thanks
[@&#8203;tomassliz](https://togithub.com/tomassliz),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2124](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2124),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2128](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2128);
[@&#8203;jaesung-0o0](https://togithub.com/jaesung-0o0),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2144](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2144)).

#### New Contributors

- [@&#8203;tomassliz](https://togithub.com/tomassliz) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2124](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2124)
- [@&#8203;jaesung-0o0](https://togithub.com/jaesung-0o0) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2144](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2144)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.53.2...0.54.0

###
[`v0.53.2`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.53.2)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.53.1...0.53.2)

#### What's Changed

- Make `Send` sendable
([https://github.com/pointfreeco/swift-composable-architecture/pull/2112](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2112))
- When test exhaustivity is off, `receive` now waits for the expected
action rather than taking the first action (thanks
[@&#8203;alex-reilly-pronto](https://togithub.com/alex-reilly-pronto),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2100](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2100))
- Fix typo in the "Meet the Composable Architecture" (thanks
[@&#8203;redryerye](https://togithub.com/redryerye),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2114](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2114))
- Fix compile error in Xcode <14.3 (thanks
[@&#8203;hj56775](https://togithub.com/hj56775),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2115](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2115))

#### New Contributors

- [@&#8203;alex-reilly-pronto](https://togithub.com/alex-reilly-pronto)
made their first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2100](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2100)
- [@&#8203;redryerye](https://togithub.com/redryerye) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2114](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2114)
- [@&#8203;hj56775](https://togithub.com/hj56775) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2115](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2115)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.53.1...0.53.2

###
[`v0.53.1`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.53.1)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.53.0...0.53.1)

#### What's Changed

- Fixed: A regression was introduced in 0.53.0 where `TestStore.init`'s
`prepareDependencies` was called twice. It will not be called just a
single time again
([https://github.com/pointfreeco/swift-composable-architecture/pull/2111](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2111)).
- Infrastructure: Added a "Meet the Composable Architecture" tutorial
([https://github.com/pointfreeco/swift-composable-architecture/pull/2107](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2107),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2109](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2109)).
- Infrastructure: Docs fixes (thanks
[@&#8203;Ryu0118](https://togithub.com/Ryu0118),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2110](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2110))

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.53.0...0.53.1

###
[`v0.53.0`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.53.0)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.52.0...0.53.0)

#### What's Changed

- Added: `Store.init` and `TestStore.init` now take reducer builders
([https://github.com/pointfreeco/swift-composable-architecture/pull/2087](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2087)).
    ```swift
    // Before:
    Store(
      initialState: Feature.State(),
      reducer: Feature()
    )

    // After:
    Store(initialState: Feature.State()) {
      Feature()
    }
    ```

- Changed: `SwitchStore` has gotten some quality-of-life improvements
([https://github.com/pointfreeco/swift-composable-architecture/pull/2029](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2029)).
- `SwitchStore.init` can now take the initial enum state so that it can
be switched over exhaustively. This initializer also relaxes certain
compile-time constraints previously requiring only `CaseLet` views and
an optional, trailing `Default` view.
- `CaseLet` can now omit the `state` parameter label, making it more
consistent with other APIs, like `Reducer.ifCaseLet`.
- The older `SwitchStore` and `CaseLet` initializers have been
soft-deprecated along with the `Default` view.

        ```swift
        // Before:
        SwitchStore(self.store) {
CaseLet(state: /App.State.loggedIn, action: App.Action.loggedIn) {
loggedInStore in
            LoggedInView(store: loggedInStore)
          }
CaseLet(state: /App.State.loggedOut, action: App.Action.loggedOut) {
loggedOutStore in
            LoggedOutView(store: loggedOutStore)
          }
        }

        // After:
        SwitchStore(self.store) {
switch $0 { // Can now switch over initial state for exhaustivity at
compile time
          case .loggedIn:
CaseLet(/App.State.loggedIn, action: App.Action.loggedIn) {
loggedInStore in
              LoggedInView(store: loggedInStore)
            }
.buttonStyle(.plain) // Can now render arbitrary views/modifiers in the
view builder
          case .loggedOut:
CaseLet(/App.State.loggedOut, action: App.Action.loggedOut) {
loggedOutStore in
              LoggedOutView(store: loggedOutStore)
            }
          }
        }
        ```

- Changed: `WithViewStore.debug` has been renamed to
`WithViewStore._printChanges` for consistency with
`Reducer._printChanges`
([https://github.com/pointfreeco/swift-composable-architecture/pull/2101](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2101)).

- Fixed: `EffectTask.publisher` now properly escapes dependencies
accessed within it
([https://github.com/pointfreeco/swift-composable-architecture/pull/1988](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1988)).

- Fixed: `Reducer._printChanges()` is no longer disabled in tests
([https://github.com/pointfreeco/swift-composable-architecture/pull/1995](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1995)).
This allows it to be used for debugging purposes during test runs.

- Changed: The internal `Task.megaYield` tool, for more predictably
testing concurrent code, is now configurable via the
`TASK_MEGA_YIELD_COUNT` environment variable
([https://github.com/pointfreeco/swift-composable-architecture/pull/2064](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2064)).

- Improved: The output format of `WithViewStore._printChanges()` has
been improved
([https://github.com/pointfreeco/swift-composable-architecture/pull/1973](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1973)).

- Improved: Runtime warnings will now emit XCTest failures in test code
rather than in app code
([https://github.com/pointfreeco/swift-composable-architecture/pull/2059](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2059)).

- Deprecated: Type-based cancel IDs have been deprecated
([https://github.com/pointfreeco/swift-composable-architecture/pull/2091](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2091)).
Use hashable values, instead.

- Deprecated: The actionless overload of `Store.scope(state:)` has been
deprecated in favor of the `observe` parameter on view stores
([https://github.com/pointfreeco/swift-composable-architecture/pull/2097](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2097)).

- Deprecated: `Effect.task` and `Effect.fireAndForget` have been
soft-deprecated in favor of `Effect.run`
([https://github.com/pointfreeco/swift-composable-architecture/pull/2099](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2099)).

- Infrastructure: Added test coverage for child/parent effect
cancellation behavior
([https://github.com/pointfreeco/swift-composable-architecture/pull/1970](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1970)).

- Infrastructure: Clean up effect cancellation logic
([https://github.com/pointfreeco/swift-composable-architecture/pull/1977](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1977)).

-   Infrastructure: Miscellaneous documentation/formatting fixes:

Fixed missing `action` parameter in `ForEachStore` documentation (thanks
[@&#8203;m-housh](https://togithub.com/m-housh),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1998](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1998)).

Number fact tutorial fix (thanks
[@&#8203;siliconsorcery](https://togithub.com/siliconsorcery),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1962](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1962)).

`BindingAction` fix (thanks
[@&#8203;Ryu0118](https://togithub.com/Ryu0118),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2019](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2019)).

`withTaskCancellation(id:)` fix (thanks
[@&#8203;bjford](https://togithub.com/bjford),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2049](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2049)).

Formatting fix (thanks
[@&#8203;mooyoung2309](https://togithub.com/mooyoung2309),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2056](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2056)).

Update 'bindable state' to 'binding state' (thanks
[@&#8203;Jager-yoo](https://togithub.com/Jager-yoo),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2054](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2054)).

- Infrastructure: Added Russian README translation (thanks
[@&#8203;artyom-ivanov](https://togithub.com/artyom-ivanov),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2014](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2014)).

- Infrastructure: Added Polish README translation (thanks
[@&#8203;MarcelStarczyk](https://togithub.com/MarcelStarczyk),
[https://github.com/pointfreeco/swift-composable-architecture/pull/2040](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2040)).

-   Infrastructure: Bump dependencies.

- Infrastructure: Bump Xcode demo project settings
([https://github.com/pointfreeco/swift-composable-architecture/pull/2042](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2042)).

- Infrastructure: Clean up and test `TestStore.skipInFlightEffects`
([https://github.com/pointfreeco/swift-composable-architecture/pull/2057](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2057)).

- Infrastructure: CI updates
([https://github.com/pointfreeco/swift-composable-architecture/pull/2060](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2060)).

- Infrastructure: Document how exhaustive vs. non-exhaustive test stores
work
([https://github.com/pointfreeco/swift-composable-architecture/pull/2096](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2096)).

#### New Contributors

- [@&#8203;m-housh](https://togithub.com/m-housh) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/1998](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1998)
- [@&#8203;siliconsorcery](https://togithub.com/siliconsorcery) made
their first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/1962](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1962)
- [@&#8203;artyom-ivanov](https://togithub.com/artyom-ivanov) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2014](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2014)
- [@&#8203;Ryu0118](https://togithub.com/Ryu0118) made their first
contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2019](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2019)
- [@&#8203;MarcelStarczyk](https://togithub.com/MarcelStarczyk) made
their first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2040](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2040)
- [@&#8203;mooyoung2309](https://togithub.com/mooyoung2309) made their
first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/2056](https://togithub.com/pointfreeco/swift-composable-architecture/pull/2056)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.52.0...0.53.0

###
[`v0.52.0`](https://togithub.com/pointfreeco/swift-composable-architecture/releases/tag/0.52.0)

[Compare
Source](https://togithub.com/pointfreeco/swift-composable-architecture/compare/0.51.0...0.52.0)

#### What's Changed

- Added: Support for `XCTModify` and non-exhaustive testing
([https://github.com/pointfreeco/swift-composable-architecture/pull/1939](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1939)).
- Added: Library reducer operators are now annotated with
`@warn_unqualified_access` to prevent accidental bugs
([https://github.com/pointfreeco/swift-composable-architecture/pull/1950](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1950)).
- Added: `Effect.publisher` for bridging effects from Combine
([https://github.com/pointfreeco/swift-composable-architecture/pull/1958](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1958)).
- Changed: `Effect<Action>.Send` has been renamed to `Send<Action>`
(thanks [@&#8203;tgrapperon](https://togithub.com/tgrapperon),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1930](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1930)).
- Changed: Dependencies have been bumped to their latest versions to
encourage adoption of bug fixes
([https://github.com/pointfreeco/swift-composable-architecture/pull/1964](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1964)).
- Fixed: Dependencies are no longer recursively propagated over effects
([https://github.com/pointfreeco/swift-composable-architecture/pull/1954](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1954)).
- Fixed: `TestStore.init` now calls `prepareDependencies` in a
`withDependencies` block
([https://github.com/pointfreeco/swift-composable-architecture/pull/1955](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1955)).
- Infrastructure: Fix UI test for `ForEach` bindings
([https://github.com/pointfreeco/swift-composable-architecture/pull/1933](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1933)).
- Infrastructure: Add missing documentation to `Store.init` (thanks
[@&#8203;kristofferjohansson](https://togithub.com/kristofferjohansson),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1940](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1940)).
- Infrastructure: DocC fixes
([https://github.com/pointfreeco/swift-composable-architecture/pull/1942](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1942),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1956](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1956)).
- Infrastructure: Update latest version documentation link in README
(thanks [@&#8203;yimajo](https://togithub.com/yimajo),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1943](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1943))
- Infrastructure: Fix `.forEach()` documentation (thanks
[@&#8203;finestructure](https://togithub.com/finestructure),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1957](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1957)).
- Infrastructure: Documentation grammar fixes (thanks
[@&#8203;bjford](https://togithub.com/bjford),
[https://github.com/pointfreeco/swift-composable-architecture/pull/1963](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1963))

#### New Contributors

-
[@&#8203;kristofferjohansson](https://togithub.com/kristofferjohansson)
made their first contribution in
[https://github.com/pointfreeco/swift-composable-architecture/pull/1940](https://togithub.com/pointfreeco/swift-composable-architecture/pull/1940)

**Full Changelog**:
pointfreeco/swift-composable-architecture@0.51.0...0.52.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/cgrindel/rules_swift_package_manager).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS4xNDQuMiIsInVwZGF0ZWRJblZlciI6IjM1LjE0NC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants