Repository: arkivanov/Decompose
Pull request #167 (2022-08-04 18:28:15)

Merge master to compose-darwin


Patch:
diff --git a/README.md b/README.md
index 0c782046..e578d333 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ Here are some key concepts of the library, more details can be found in the docu
 
 * [Component](https://arkivanov.github.io/Decompose/component/overview/) - every component represents a piece of logic with its own lifecycle, the UI is optional and is plugged externally
 * [ComponentContext](https://arkivanov.github.io/Decompose/component/overview/#componentcontext) - every component has its own [ComponentContext], which makes components lifecycle-aware and allows state preservation, instances retaining (aka AndroidX `ViewModel`) and back button handling
-* [Router](https://arkivanov.github.io/Decompose/router/overview/) - enables navigation between child components, nested navigation is also supported
+* [Child Stack](https://arkivanov.github.io/Decompose/child-stack/overview/) - enables navigation between child components, nested navigation is also supported
 * [Lifecycle](https://arkivanov.github.io/Decompose/component/lifecycle/) - provides a way to listen for lifecycle events in components
 * [StateKeeper](https://arkivanov.github.io/Decompose/component/state-preservation/) - makes it possible to preserve state or data in a component when it gets destroyed
 * [InstanceKeeper](https://arkivanov.github.io/Decompose/component/instance-retaining/) - retains instances in your components (similar to AndroidX `ViewModel`)
diff --git a/build.gradle.kts b/build.gradle.kts
index 15805a27..2a5b2e39 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -38,8 +38,8 @@ setupDefaults(
     },
     androidConfig = AndroidConfig(
         minSdkVersion = 21,
-        compileSdkVersion = 31,
-        targetSdkVersion = 31,
+        compileSdkVersion = 33,
+        targetSdkVersion = 33,
     ),
     publicationConfig = PublicationConfig(
         group = "com.arkivanov.decompose",
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index 1600f829..20eb0812 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -33,7 +33,7 @@ public final class com/arkivanov/decompose/Child$Destroyed : com/arkivanov/decom
 	public fun toString ()Ljava/lang/String;
 }
 
-public abstract interface class com/arkivanov/decompose/ComponentContext : com/arkivanov/essenty/backpressed/BackPressedHandlerOwner, com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner, com/arkivanov/essenty/lifecycle/LifecycleOwner, com/arkivanov/essenty/statekeeper/StateKeeperOwner {
+public abstract interface class com/arkivanov/decompose/ComponentContext : com/arkivanov/essenty/backhandler/BackHandlerOwner, com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner, com/arkivanov/essenty/lifecycle/LifecycleOwner, com/arkivanov/essenty/statekeeper/StateKeeperOwner {
 }
 
 public final class com/arkivanov/decompose/ComponentContextExtKt {
@@ -43,9 +43,9 @@ public final class com/arkivanov/decompose/ComponentContextExtKt {
 
 public final class com/arkivanov/decompose/DefaultComponentContext : com/arkivanov/decompose/ComponentContext {
 	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;)V
-	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backpressed/BackPressedHandler;)V
-	public synthetic fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backpressed/BackPressedHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-	public fun getBackPressedHandler ()Lcom/arkivanov/essenty/backpressed/BackPressedHandler;
+	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backhandler/BackHandler;)V
+	public synthetic fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backhandler/BackHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler;
 	public fun getInstanceKeeper ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
 	public fun getLifecycle ()Lcom/arkivanov/essenty/lifecycle/Lifecycle;
 	public fun getStateKeeper ()Lcom/arkivanov/essenty/statekeeper/StateKeeper;
@@ -70,67 +70,6 @@ public final class com/arkivanov/decompose/lifecycle/MergedLifecycle : com/arkiv
 	public fun unsubscribe (Lcom/arkivanov/essenty/lifecycle/Lifecycle$Callbacks;)V
 }
 
-public abstract interface class com/arkivanov/decompose/router/Router {
-	public abstract fun getState ()Lcom/arkivanov/decompose/value/Value;
-	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
-}
-
-public final class com/arkivanov/decompose/router/RouterExtKt {
-	public static final synthetic fun bringToFront (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun bringToFront (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun bringToFront$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-	public static final fun getActiveChild (Lcom/arkivanov/decompose/router/Router;)Lcom/arkivanov/decompose/Child$Created;
-	public static final fun navigate (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static final fun pop (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun pop$default (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public static final fun popWhile (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static final fun popWhile (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
-	public static final synthetic fun push (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun push (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun push$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-	public static final synthetic fun replaceCurrent (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun replaceCurrent (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun replaceCurrent$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-}
-
-public final class com/arkivanov/decompose/router/RouterFactoryExtKt {
-	public static final fun router (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/Router;
-	public static final fun router (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/Router;
-	public static synthetic fun router$default (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/Router;
-	public static synthetic fun router$default (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/Router;
-}
-
-public final class com/arkivanov/decompose/router/RouterState {
-	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
-	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
-	public final fun component1 ()Lcom/arkivanov/decompose/Child$Created;
-	public final fun component2 ()Ljava/util/List;
-	public final fun copy (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)Lcom/arkivanov/decompose/router/RouterState;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/RouterState;Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/RouterState;
-	public fun equals (Ljava/lang/Object;)Z
-	public final fun getActiveChild ()Lcom/arkivanov/decompose/Child$Created;
-	public final fun getBackStack ()Ljava/util/List;
-	public fun hashCode ()I
-	public fun toString ()Ljava/lang/String;
-}
-
-public final class com/arkivanov/decompose/router/StackSaverImpl$SavedEntry$Creator : android/os/Parcelable$Creator {
-	public fun <init> ()V
-	public final fun createFromParcel (Landroid/os/Parcel;)Lcom/arkivanov/decompose/router/StackSaverImpl$SavedEntry;
-	public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
-	public final fun newArray (I)[Lcom/arkivanov/decompose/router/StackSaverImpl$SavedEntry;
-	public synthetic fun newArray (I)[Ljava/lang/Object;
-}
-
-public final class com/arkivanov/decompose/router/StackSaverImpl$SavedState$Creator : android/os/Parcelable$Creator {
-	public fun <init> ()V
-	public final fun createFromParcel (Landroid/os/Parcel;)Lcom/arkivanov/decompose/router/StackSaverImpl$SavedState;
-	public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
-	public final fun newArray (I)[Lcom/arkivanov/decompose/router/StackSaverImpl$SavedState;
-	public synthetic fun newArray (I)[Ljava/lang/Object;
-}
-
 public final class com/arkivanov/decompose/router/stack/ChildStack {
 	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
 	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -209,10 +148,6 @@ public abstract interface class com/arkivanov/decompose/router/stack/webhistory/
 	public abstract fun attach (Lcom/arkivanov/decompose/router/stack/StackNavigator;Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
 }
 
-public abstract interface class com/arkivanov/decompose/router/webhistory/WebHistoryController {
-	public abstract fun attach (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
-}
-
 public abstract class com/arkivanov/decompose/value/MutableValue : com/arkivanov/decompose/value/Value {
 	public fun <init> ()V
 	public abstract fun getValue ()Ljava/lang/Object;
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 1d38c29e..affb1641 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -26,7 +26,7 @@ public final class com/arkivanov/decompose/Child$Destroyed : com/arkivanov/decom
 	public fun toString ()Ljava/lang/String;
 }
 
-public abstract interface class com/arkivanov/decompose/ComponentContext : com/arkivanov/essenty/backpressed/BackPressedHandlerOwner, com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner, com/arkivanov/essenty/lifecycle/LifecycleOwner, com/arkivanov/essenty/statekeeper/StateKeeperOwner {
+public abstract interface class com/arkivanov/decompose/ComponentContext : com/arkivanov/essenty/backhandler/BackHandlerOwner, com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner, com/arkivanov/essenty/lifecycle/LifecycleOwner, com/arkivanov/essenty/statekeeper/StateKeeperOwner {
 }
 
 public final class com/arkivanov/decompose/ComponentContextExtKt {
@@ -36,9 +36,9 @@ public final class com/arkivanov/decompose/ComponentContextExtKt {
 
 public final class com/arkivanov/decompose/DefaultComponentContext : com/arkivanov/decompose/ComponentContext {
 	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;)V
-	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backpressed/BackPressedHandler;)V
-	public synthetic fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backpressed/BackPressedHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-	public fun getBackPressedHandler ()Lcom/arkivanov/essenty/backpressed/BackPressedHandler;
+	public fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backhandler/BackHandler;)V
+	public synthetic fun <init> (Lcom/arkivanov/essenty/lifecycle/Lifecycle;Lcom/arkivanov/essenty/statekeeper/StateKeeper;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Lcom/arkivanov/essenty/backhandler/BackHandler;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler;
 	public fun getInstanceKeeper ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
 	public fun getLifecycle ()Lcom/arkivanov/essenty/lifecycle/Lifecycle;
 	public fun getStateKeeper ()Lcom/arkivanov/essenty/statekeeper/StateKeeper;
@@ -57,51 +57,6 @@ public final class com/arkivanov/decompose/lifecycle/MergedLifecycle : com/arkiv
 	public fun unsubscribe (Lcom/arkivanov/essenty/lifecycle/Lifecycle$Callbacks;)V
 }
 
-public abstract interface class com/arkivanov/decompose/router/Router {
-	public abstract fun getState ()Lcom/arkivanov/decompose/value/Value;
-	public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
-}
-
-public final class com/arkivanov/decompose/router/RouterExtKt {
-	public static final synthetic fun bringToFront (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun bringToFront (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun bringToFront$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-	public static final fun getActiveChild (Lcom/arkivanov/decompose/router/Router;)Lcom/arkivanov/decompose/Child$Created;
-	public static final fun navigate (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static final fun pop (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static synthetic fun pop$default (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-	public static final fun popWhile (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;)V
-	public static final fun popWhile (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
-	public static final synthetic fun push (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun push (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun push$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-	public static final synthetic fun replaceCurrent (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;)V
-	public static final fun replaceCurrent (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
-	public static synthetic fun replaceCurrent$default (Lcom/arkivanov/decompose/router/Router;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
-}
-
-public final class com/arkivanov/decompose/router/RouterFactoryExtKt {
-	public static final fun router (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/Router;
-	public static final fun router (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/router/Router;
-	public static synthetic fun router$default (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/Router;
-	public static synthetic fun router$default (Lcom/arkivanov/decompose/ComponentContext;Lkotlin/jvm/functions/Function0;Lkotlin/reflect/KClass;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/Router;
-}
-
-public final class com/arkivanov/decompose/router/RouterState {
-	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
-	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-	public fun <init> (Ljava/lang/Object;Ljava/lang/Object;)V
-	public final fun component1 ()Lcom/arkivanov/decompose/Child$Created;
-	public final fun component2 ()Ljava/util/List;
-	public final fun copy (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)Lcom/arkivanov/decompose/router/RouterState;
-	public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/RouterState;Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/RouterState;
-	public fun equals (Ljava/lang/Object;)Z
-	public final fun getActiveChild ()Lcom/arkivanov/decompose/Child$Created;
-	public final fun getBackStack ()Ljava/util/List;
-	public fun hashCode ()I
-	public fun toString ()Ljava/lang/String;
-}
-
 public final class com/arkivanov/decompose/router/stack/ChildStack {
 	public fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;)V
 	public synthetic fun <init> (Lcom/arkivanov/decompose/Child$Created;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -164,10 +119,6 @@ public abstract interface class com/arkivanov/decompose/router/stack/webhistory/
 	public abstract fun attach (Lcom/arkivanov/decompose/router/stack/StackNavigator;Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
 }
 
-public abstract interface class com/arkivanov/decompose/router/webhistory/WebHistoryController {
-	public abstract fun attach (Lcom/arkivanov/decompose/router/Router;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
-}
-
 public abstract class com/arkivanov/decompose/value/MutableValue : com/arkivanov/decompose/value/Value {
 	public fun <init> ()V
 	public abstract fun getValue ()Ljava/lang/Object;
diff --git a/decompose/build.gradle.kts b/decompose/build.gradle.kts
index f375db73..3f0a1d31 100644
--- a/decompose/build.gradle.kts
+++ b/decompose/build.gradle.kts
@@ -39,7 +39,7 @@ kotlin {
             api(deps.essenty.lifecycle)
             api(deps.essenty.stateKeeper)
             api(deps.essenty.instanceKeeper)
-            api(deps.essenty.backPressed)
+            api(deps.essenty.backHandler)
         }
 
         android.main.dependencies {
diff --git a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
index 80f0737d..5cecda0d 100644
--- a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
+++ b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
@@ -7,7 +7,7 @@ import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryOwner
-import com.arkivanov.essenty.backpressed.BackPressedHandler
+import com.arkivanov.essenty.backhandler.BackHandler
 import com.arkivanov.essenty.instancekeeper.InstanceKeeper
 import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
 import com.arkivanov.essenty.statekeeper.StateKeeper
@@ -23,7 +23,7 @@ fun DefaultComponentContext(
         lifecycle = lifecycle.asEssentyLifecycle(),
         stateKeeper = savedStateRegistry?.let(::StateKeeper),
         instanceKeeper = viewModelStore?.let(::InstanceKeeper),
-        backPressedHandler = onBackPressedDispatcher?.let(::BackPressedHandler)
+        backHandler = onBackPressedDispatcher?.let(::BackHandler),
     )
 
 fun <T> T.defaultComponentContext(): DefaultComponentContext where
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt
index e763a303..ff6e31dc 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt
@@ -1,6 +1,6 @@
 package com.arkivanov.decompose
 
-import com.arkivanov.essenty.backpressed.BackPressedHandlerOwner
+import com.arkivanov.essenty.backhandler.BackHandlerOwner
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
 import com.arkivanov.essenty.lifecycle.LifecycleOwner
 import com.arkivanov.essenty.statekeeper.StateKeeperOwner
@@ -9,4 +9,4 @@ interface ComponentContext :
     LifecycleOwner,
     StateKeeperOwner,
     InstanceKeeperOwner,
-    BackPressedHandlerOwner
+    BackHandlerOwner
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
index 618941d2..111cf285 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContextExt.kt
@@ -1,6 +1,6 @@
 package com.arkivanov.decompose
 
-import com.arkivanov.decompose.backpressed.child
+import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.instancekeeper.child
 import com.arkivanov.decompose.lifecycle.MergedLifecycle
 import com.arkivanov.decompose.statekeeper.child
@@ -20,5 +20,5 @@ fun ComponentContext.childContext(key: String, lifecycle: Lifecycle? = null): Co
         lifecycle = if (lifecycle == null) this.lifecycle else MergedLifecycle(this.lifecycle, lifecycle),
         stateKeeper = stateKeeper.child(key, lifecycle),
         instanceKeeper = instanceKeeper.child(key, lifecycle),
-        backPressedHandler = backPressedHandler.child(lifecycle),
+        backHandler = backHandler.child(lifecycle),
     )
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt
index 743ad74f..c8aa6cc1 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/DefaultComponentContext.kt
@@ -1,8 +1,8 @@
 package com.arkivanov.decompose
 
 import com.arkivanov.decompose.instancekeeper.attachTo
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedHandler
+import com.arkivanov.essenty.backhandler.BackDispatcher
+import com.arkivanov.essenty.backhandler.BackHandler
 import com.arkivanov.essenty.instancekeeper.InstanceKeeper
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.lifecycle.Lifecycle
@@ -13,17 +13,17 @@ class DefaultComponentContext(
     override val lifecycle: Lifecycle,
     stateKeeper: StateKeeper? = null,
     instanceKeeper: InstanceKeeper? = null,
-    backPressedHandler: BackPressedHandler? = null,
+    backHandler: BackHandler? = null,
 ) : ComponentContext {
 
     override val stateKeeper: StateKeeper = stateKeeper ?: StateKeeperDispatcher()
     override val instanceKeeper: InstanceKeeper = instanceKeeper ?: InstanceKeeperDispatcher().attachTo(lifecycle)
-    override val backPressedHandler: BackPressedHandler = backPressedHandler ?: BackPressedDispatcher()
+    override val backHandler: BackHandler = backHandler ?: BackDispatcher()
 
     constructor(lifecycle: Lifecycle) : this(
         lifecycle = lifecycle,
         stateKeeper = null,
         instanceKeeper = null,
-        backPressedHandler = null
+        backHandler = null,
     )
 }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt
new file mode 100644
index 00000000..011ca11b
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/ChildBackHandler.kt
@@ -0,0 +1,27 @@
+package com.arkivanov.decompose.backhandler
+
+import com.arkivanov.essenty.backhandler.BackHandler
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.doOnDestroy
+
+internal interface ChildBackHandler : BackHandler {
+
+    fun start()
+
+    fun stop()
+}
+
+internal fun BackHandler.child(lifecycle: Lifecycle?): BackHandler {
+    val handler = DefaultChildBackHandler(parent = this)
+
+    if (lifecycle == null) {
+        handler.start()
+    } else if (lifecycle.state != Lifecycle.State.DESTROYED) {
+        handler.start()
+        lifecycle.doOnDestroy(handler::stop)
+    }
+
+    return handler
+}
+
+internal fun BackHandler.child(): ChildBackHandler = DefaultChildBackHandler(parent = this)
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt
new file mode 100644
index 00000000..30ac7f0e
--- /dev/null
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandler.kt
@@ -0,0 +1,47 @@
+package com.arkivanov.decompose.backhandler
+
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.backhandler.BackHandler
+
+internal class DefaultChildBackHandler(
+    private val parent: BackHandler,
+) : ChildBackHandler {
+
+    private val parentCallback = BackCallback(isEnabled = false, onBack = ::onBack)
+    private var set = emptySet<BackCallback>()
+    private val enabledChangedListener: (Boolean) -> Unit = { onEnabledChanged() }
+
+    override fun start() {
+        parent.register(parentCallback)
+    }
+
+    override fun stop() {
+        parent.unregister(parentCallback)
+    }
+
+    override fun register(callback: BackCallback) {
+        check(callback !in set) { "Callback is already registered" }
+
+        this.set += callback
+        callback.addEnabledChangedListener(enabledChangedListener)
+        onEnabledChanged()
+    }
+
+    override fun unregister(callback: BackCallback) {
+        check(callback in set) { "Callback is not registered" }
+
+        callback.removeEnabledChangedListener(enabledChangedListener)
+        this.set -= callback
+        onEnabledChanged()
+    }
+
+    private fun onEnabledChanged() {
+        parentCallback.isEnabled = set.any(BackCallback::isEnabled)
+    }
+
+    private fun onBack() {
+        set.lastOrNull(BackCallback::isEnabled)?.also {
+            it.onBack()
+        }
+    }
+}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backpressed/ChildBackPressedHandler.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backpressed/ChildBackPressedHandler.kt
deleted file mode 100644
index bcb87d06..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backpressed/ChildBackPressedHandler.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.arkivanov.decompose.backpressed
-
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedHandler
-import com.arkivanov.essenty.lifecycle.Lifecycle
-import com.arkivanov.essenty.lifecycle.doOnDestroy
-
-internal fun BackPressedHandler.child(lifecycle: Lifecycle?): BackPressedHandler {
-    val child = BackPressedDispatcher()
-    val handler: () -> Boolean = child::onBackPressed
-    register(handler)
-    lifecycle?.doOnDestroy { unregister(handler) }
-
-    return child
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/Router.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/Router.kt
deleted file mode 100644
index 1eec51d8..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/Router.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.value.Value
-
-/**
- * Deprecated. Please use Child Stack instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Deprecated(message = "Use Child Stack instead")
-interface Router<C : Any, out T : Any> {
-
-    /**
-     * Returns the current stack of components, which can also be observed for changes
-     */
-    val state: Value<RouterState<C, T>>
-
-    /**
-     * Transforms the current stack of configurations into a new one.
-     * The stack is represented as [List], where the last element is the top of the stack,
-     * and the first element is the bottom of the stack. The returned stack must not be empty.
-     *
-     * During the navigation process, the `Router` compares the new stack of configurations with
-     * the previous one. The `Router` ensures that all removed components are destroyed, and that
-     * there is only one component resumed at a time - the top one. All components in the back stack
-     * are always either stopped or destroyed.
-     *
-     * The `Router` usually performs the navigation synchronously, which means that by the time
-     * the `navigate` method returns, the navigation is finished and all component lifecycles are
-     * moved into required states. However the navigation is performed asynchronously in case of
-     * recursive invocations - e.g. `pop` is called from `onResume` lifecycle callback of a
-     * component being pushed. All recursive invocations are queued and performed one by one once
-     * the current navigation is finished.
-     *
-     * @param transformer transforms the current configuration stack to a new one.
-     * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
-     */
-    fun navigate(transformer: (stack: List<C>) -> List<C>, onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit)
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntry.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntry.kt
deleted file mode 100644
index 67d7e16b..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntry.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.lifecycle.LifecycleRegistry
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
-
-internal sealed class RouterEntry<out C : Any, out T : Any> {
-    abstract val configuration: C
-    abstract val savedState: ParcelableContainer?
-
-    data class Created<out C : Any, out T : Any>(
-        override val configuration: C,
-        override val savedState: ParcelableContainer? = null,
-        val instance: T,
-        val lifecycleRegistry: LifecycleRegistry,
-        val stateKeeperDispatcher: StateKeeperDispatcher,
-        val instanceKeeperDispatcher: InstanceKeeperDispatcher,
-        val backPressedDispatcher: BackPressedDispatcher
-    ) : RouterEntry<C, T>()
-
-    data class Destroyed<out C : Any>(
-        override val configuration: C,
-        override val savedState: ParcelableContainer? = null
-    ) : RouterEntry<C, Nothing>()
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryExt.kt
deleted file mode 100644
index 7c31e2bc..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryExt.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.essenty.lifecycle.destroy
-
-internal fun List<RouterEntry<*, *>>.destroy() {
-    asReversed().forEach { entry ->
-        when (entry) {
-            is RouterEntry.Created -> {
-                entry.instanceKeeperDispatcher.destroy()
-                entry.lifecycleRegistry.destroy()
-            }
-
-            is RouterEntry.Destroyed -> Unit
-        }.let {}
-    }
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactory.kt
deleted file mode 100644
index 40460bee..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactory.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-
-internal interface RouterEntryFactory<C : Any, out T : Any> {
-
-    operator fun invoke(
-        configuration: C,
-        savedState: ParcelableContainer? = null,
-        instanceKeeperDispatcher: InstanceKeeperDispatcher? = null
-    ): RouterEntry.Created<C, T>
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactoryImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactoryImpl.kt
deleted file mode 100644
index ebfc4854..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterEntryFactoryImpl.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.DefaultComponentContext
-import com.arkivanov.decompose.lifecycle.MergedLifecycle
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.lifecycle.Lifecycle
-import com.arkivanov.essenty.lifecycle.LifecycleRegistry
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
-
-internal class RouterEntryFactoryImpl<C : Any, out T : Any>(
-    private val lifecycle: Lifecycle,
-    private val childFactory: (configuration: C, ComponentContext) -> T
-) : RouterEntryFactory<C, T> {
-
-    override fun invoke(
-        configuration: C,
-        savedState: ParcelableContainer?,
-        instanceKeeperDispatcher: InstanceKeeperDispatcher?
-    ): RouterEntry.Created<C, T> {
-        val componentLifecycleRegistry = LifecycleRegistry()
-        val mergedLifecycle = MergedLifecycle(lifecycle, componentLifecycleRegistry)
-        val stateKeeperDispatcher = StateKeeperDispatcher(savedState)
-        val instanceKeeperRegistry = instanceKeeperDispatcher ?: InstanceKeeperDispatcher()
-        val backPressedDispatcher = BackPressedDispatcher()
-
-        val component =
-            childFactory(
-                configuration,
-                DefaultComponentContext(
-                    mergedLifecycle,
-                    stateKeeperDispatcher,
-                    instanceKeeperRegistry,
-                    backPressedDispatcher
-                )
-            )
-
-        return RouterEntry.Created(
-            configuration = configuration,
-            instance = component,
-            lifecycleRegistry = componentLifecycleRegistry,
-            stateKeeperDispatcher = stateKeeperDispatcher,
-            instanceKeeperDispatcher = instanceKeeperRegistry,
-            backPressedDispatcher = backPressedDispatcher
-        )
-    }
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterExt.kt
deleted file mode 100644
index 54505155..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterExt.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.Child
-
-/**
- * A convenience method for [Router.navigate].
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Deprecated("Use Child Stack instead")
-fun <C : Any> Router<C, *>.navigate(transformer: (stack: List<C>) -> List<C>) {
-    navigate(transformer = transformer, onComplete = { _, _ -> })
-}
-
-@Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
-fun <C : Any> Router<C, *>.push(configuration: C) {
-    push(configuration = configuration, onComplete = {})
-}
-
-/**
- * Pushes the provided [configuration] at the top of the stack.
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- *
- * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
- */
-@Deprecated("Use Child Stack instead")
-fun <C : Any> Router<C, *>.push(configuration: C, onComplete: () -> Unit = {}) {
-    navigate(transformer = { it + configuration }, onComplete = { _, _ -> onComplete() })
-}
-
-/**
- * Pops the latest configuration at the top of the stack.
- *
- * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
- * The `isSuccess` argument is `true` if the stack size was greater than 1 and a component was popped,
- * `false` otherwise.
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Deprecated("Use Child Stack instead")
-fun <C : Any> Router<C, *>.pop(onComplete: (isSuccess: Boolean) -> Unit = {}) {
-    navigate(
-        transformer = { stack -> stack.takeIf { it.size > 1 }?.dropLast(1) ?: stack },
-        onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) },
-    )
-}
-
-/**
- * Drops the configurations at the top of the stack while the [predicate] returns `true`.
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Suppress("DeprecatedCallableAddReplaceWith")
-@Deprecated("Use Child Stack instead")
-inline fun <C : Any> Router<C, *>.popWhile(crossinline predicate: (C) -> Boolean) {
-    popWhile(predicate = predicate, onComplete = {})
-}
-
-/**
- * Drops the configurations at the top of the stack while the [predicate] returns true
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- *
- * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
- * The `isSuccess` argument is `true` if at least one component has been popped.
- */
-@Deprecated("Use Child Stack instead")
-inline fun <C : Any> Router<C, *>.popWhile(
-    crossinline predicate: (C) -> Boolean,
-    crossinline onComplete: (isSuccess: Boolean) -> Unit,
-) {
-    navigate(
-        transformer = { stack -> stack.dropLastWhile(predicate) },
-        onComplete = { newStack, oldStack -> onComplete(newStack.size < oldStack.size) },
-    )
-}
-
-@Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
-fun <C : Any> Router<C, *>.replaceCurrent(configuration: C) {
-    replaceCurrent(configuration = configuration, onComplete = {})
-}
-
-/**
- * Replaces the current configuration at the top of the stack with the provided [configuration].
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- *
- * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
- */
-@Deprecated("Use Child Stack instead")
-fun <C : Any> Router<C, *>.replaceCurrent(configuration: C, onComplete: () -> Unit = {}) {
-    navigate(
-        transformer = { it.dropLast(1) + configuration },
-        onComplete = { _, _ -> onComplete() },
-    )
-}
-
-@Deprecated(message = "For binary compatibility", level = DeprecationLevel.HIDDEN)
-fun <C : Any> Router<C, *>.bringToFront(configuration: C) {
-    bringToFront(configuration = configuration, onComplete = {})
-}
-
-/**
- * Removes all components with configurations of [configuration]'s class, and adds the provided [configuration] to the top of the stack.
- * The operation is performed as one transaction. If there is already a component with the same configuration, it will not be recreated.
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Deprecated("Use Child Stack instead")
-fun <C : Any> Router<C, *>.bringToFront(configuration: C, onComplete: () -> Unit = {}) {
-    navigate(
-        transformer = { stack -> stack.filterNot { it::class == configuration::class } + configuration },
-        onComplete = { _, _ -> onComplete() },
-    )
-}
-
-/**
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Suppress("DeprecatedCallableAddReplaceWith")
-@Deprecated("Use Child Stack instead")
-val <C : Any, T : Any> Router<C, T>.activeChild: Child.Created<C, T>
-    get() = state.value.activeChild
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterFactoryExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterFactoryExt.kt
deleted file mode 100644
index 1ef369d8..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterFactoryExt.kt
+++ /dev/null
@@ -1,163 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.essenty.parcelable.Parcelable
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import kotlin.reflect.KClass
-
-/**
- * Creates a new [Router].
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- *
- * @param initialStack a stack of component configurations (from tail to head) that should be set if there is
- * no saved state, must be not empty and unique
- * @param configurationClass a [KClass] of the component configurations
- * @param key a key of the [Router], should be unique if there are multiple [Router]s in the same component
- * @param handleBackButton determines whether the [Router] should handle back button clicks or not
- * @param childFactory a factory function that creates new child instances
- * @return a new instance of [Router]
- */
-@Deprecated(message = "Use Child Stack instead")
-fun <C : Parcelable, T : Any> ComponentContext.router(
-    initialStack: () -> List<C>,
-    configurationClass: KClass<out C>,
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> {
-    val routerEntryFactory = RouterEntryFactoryImpl(lifecycle = lifecycle, childFactory = childFactory)
-
-    return RouterImpl(
-        lifecycle = lifecycle,
-        backPressedHandler = backPressedHandler,
-        popOnBackPressed = handleBackButton,
-        stackHolder = StackHolderImpl(
-            initialStack = initialStack,
-            lifecycle = lifecycle,
-            key = key,
-            stackSaver = StackSaverImpl(
-                configurationClass = configurationClass,
-                stateKeeper = stateKeeper,
-                parcelableContainerFactory = ::ParcelableContainer
-            ),
-            instanceKeeper = instanceKeeper,
-            routerEntryFactory = routerEntryFactory
-        ),
-        navigator = StackNavigatorImpl(routerEntryFactory = routerEntryFactory)
-    )
-}
-
-/**
- * A convenience extension function for [ComponentContext.router].
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Suppress("DeprecatedCallableAddReplaceWith")
-@Deprecated(message = "Use Child Stack instead")
-inline fun <reified C : Parcelable, T : Any> ComponentContext.router(
-    noinline initialStack: () -> List<C>,
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    noinline childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> =
-    router(
-        initialStack = initialStack,
-        configurationClass = C::class,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory
-    )
-
-/**
- * A convenience extension function for [ComponentContext.router].
- *
- * Deprecated. Please use `Child Stack` instead. See the [documentation](https://arkivanov.github.io/Decompose/child-stack/overview/).
- */
-@Suppress("DeprecatedCallableAddReplaceWith")
-@Deprecated(message = "Use Child Stack instead")
-inline fun <reified C : Parcelable, T : Any> ComponentContext.router(
-    initialConfiguration: C,
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    noinline childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> =
-    router(
-        initialStack = { listOf(initialConfiguration) },
-        configurationClass = C::class,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory
-    )
-
-/**
- * A convenience extension function for [ComponentContext.router].
- */
-@Suppress("DeprecatedCallableAddReplaceWith")
-@Deprecated(
-    message = "Please use ComponentContext.router extension function with initialStack argument",
-    level = DeprecationLevel.ERROR,
-)
-fun <C : Parcelable, T : Any> ComponentContext.router(
-    initialConfiguration: () -> C,
-    initialBackStack: () -> List<C> = ::emptyList,
-    configurationClass: KClass<out C>,
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> =
-    router(
-        initialStack = { initialBackStack() + initialConfiguration() },
-        configurationClass = configurationClass,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory,
-    )
-
-/**
- * A convenience extension function for [ComponentContext.router].
- */
-@Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION_ERROR")
-@Deprecated(
-    message = "Please use ComponentContext.router extension function with initialStack argument",
-    level = DeprecationLevel.ERROR,
-)
-inline fun <reified C : Parcelable, T : Any> ComponentContext.router(
-    initialConfiguration: C,
-    initialBackStack: List<C> = emptyList(),
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    noinline childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> =
-    router(
-        initialConfiguration = { initialConfiguration },
-        initialBackStack = { initialBackStack },
-        configurationClass = C::class,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory
-    )
-
-/**
- * A convenience extension function for [ComponentContext.router].
- */
-@Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION_ERROR")
-@Deprecated(
-    message = "Please use ComponentContext.router extension function with initialStack argument",
-    level = DeprecationLevel.ERROR,
-)
-inline fun <reified C : Parcelable, T : Any> ComponentContext.router(
-    noinline initialConfiguration: () -> C,
-    noinline initialBackStack: () -> List<C> = ::emptyList,
-    key: String = "DefaultRouter",
-    handleBackButton: Boolean = false,
-    noinline childFactory: (configuration: C, ComponentContext) -> T
-): Router<C, T> =
-    router(
-        initialConfiguration = initialConfiguration,
-        initialBackStack = initialBackStack,
-        configurationClass = C::class,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory
-    )
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterImpl.kt
deleted file mode 100644
index 906178bf..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterImpl.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.SerializedQueue
-import com.arkivanov.decompose.ensureNeverFrozen
-import com.arkivanov.decompose.value.MutableValue
-import com.arkivanov.essenty.backpressed.BackPressedHandler
-import com.arkivanov.essenty.lifecycle.Lifecycle
-import com.arkivanov.essenty.lifecycle.doOnDestroy
-
-internal class RouterImpl<C : Any, T : Any>(
-    lifecycle: Lifecycle,
-    private val backPressedHandler: BackPressedHandler,
-    private val popOnBackPressed: Boolean,
-    private val stackHolder: StackHolder<C, T>,
-    private val navigator: StackNavigator<C, T>
-) : Router<C, T> {
-
-    init {
-        ensureNeverFrozen()
-    }
-
-    private val onBackPressedHandler = ::onBackPressed
-    override val state: MutableValue<RouterState<C, T>> = MutableValue(stackHolder.stack.toState())
-    private val queue = SerializedQueue(::navigateActual)
-
-    init {
-        backPressedHandler.register(onBackPressedHandler)
-        lifecycle.doOnDestroy(::destroy)
-    }
-
-    private fun destroy() {
-        backPressedHandler.unregister(onBackPressedHandler)
-    }
-
-    override fun navigate(transformer: (stack: List<C>) -> List<C>, onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit) {
-        queue.offer(NavigationItem(transformer = transformer, onComplete = onComplete))
-    }
-
-    private fun navigateActual(item: NavigationItem<C>) {
-        val oldStack = stackHolder.stack
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = item.transformer)
-        stackHolder.stack = newStack
-        state.value = newStack.toState()
-        item.onComplete(newStack.configurationStack, oldStack.configurationStack)
-    }
-
-    private fun onBackPressed(): Boolean =
-        when {
-            stackHolder.stack.active.backPressedDispatcher.onBackPressed() -> true
-
-            popOnBackPressed && stackHolder.stack.backStack.isNotEmpty() -> {
-                pop()
-                true
-            }
-
-            else -> false
-        }
-
-    private fun RouterStack<C, T>.toState(): RouterState<C, T> =
-        RouterState(
-            activeChild = Child.Created(configuration = active.configuration, instance = active.instance),
-            backStack = backStack.map { it.toRouterStateEntry() }
-        )
-
-    private fun RouterEntry<C, T>.toRouterStateEntry(): Child<C, T> =
-        when (this) {
-            is RouterEntry.Created -> Child.Created(configuration = configuration, instance = instance)
-            is RouterEntry.Destroyed -> Child.Destroyed(configuration = configuration)
-        }
-
-    private class NavigationItem<C : Any>(
-        val transformer: (stack: List<C>) -> List<C>,
-        val onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit,
-    )
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStack.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStack.kt
deleted file mode 100644
index f9f07da4..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStack.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.arkivanov.decompose.router
-
-internal data class RouterStack<out C : Any, out T : Any>(
-    val active: RouterEntry.Created<C, T>,
-    val backStack: List<RouterEntry<C, T>> = emptyList()
-)
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStackExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStackExt.kt
deleted file mode 100644
index 498501cb..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterStackExt.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.arkivanov.decompose.router
-
-internal val <C : Any> RouterStack<C, *>.configurationBackStack: List<C>
-    get() =
-        object : AbstractList<C>() {
-            override val size: Int get() = backStack.size
-
-            override fun get(index: Int): C = backStack[index].configuration
-        }
-
-internal val <C : Any> RouterStack<C, *>.configurationStack: List<C>
-    get() =
-        object : AbstractList<C>() {
-            override val size: Int get() = backStack.size + 1
-
-            override fun get(index: Int): C = (backStack.getOrNull(index) ?: active).configuration
-        }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterState.kt
deleted file mode 100644
index d8638094..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterState.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.Child
-
-@Deprecated("Use com.arkivanov.decompose.router.stack.RouterState instead")
-data class RouterState<out C : Any, out T : Any>(
-    val activeChild: Child.Created<C, T>,
-    val backStack: List<Child<C, T>> = emptyList()
-) {
-    constructor(configuration: C, instance: T) : this(
-        activeChild = Child.Created(
-            configuration = configuration,
-            instance = instance
-        )
-    )
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolder.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolder.kt
deleted file mode 100644
index 6ba028b7..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolder.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.arkivanov.decompose.router
-
-internal interface StackHolder<C : Any, T : Any> {
-
-    var stack: RouterStack<C, T>
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolderImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolderImpl.kt
deleted file mode 100644
index d63dd06a..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackHolderImpl.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.isUnique
-import com.arkivanov.essenty.instancekeeper.InstanceKeeper
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.instancekeeper.getOrCreate
-import com.arkivanov.essenty.lifecycle.Lifecycle
-import com.arkivanov.essenty.lifecycle.destroy
-import com.arkivanov.essenty.lifecycle.doOnDestroy
-import com.arkivanov.essenty.lifecycle.resume
-import kotlin.properties.Delegates.observable
-
-internal class StackHolderImpl<C : Any, T : Any>(
-    private val initialStack: () -> List<C>,
-    lifecycle: Lifecycle,
-    private val key: String,
-    private val stackSaver: StackSaver<C>,
-    instanceKeeper: InstanceKeeper,
-    private val routerEntryFactory: RouterEntryFactory<C, T>
-) : StackHolder<C, T> {
-
-    private val retainedInstance: RetainedInstance<C, T> = instanceKeeper.getOrCreate(key, ::RetainedInstance)
-
-    override var stack: RouterStack<C, T>
-        by observable(restoreStack() ?: createInitialStack()) { _, _, newValue ->
-            retainedInstance.activeEntry = newValue.active
-        }
-
-    init {
-        stackSaver.register(key) { stack }
-        retainedInstance.activeEntry = stack.active
-        stack.active.lifecycleRegistry.resume()
-
-        lifecycle.doOnDestroy(::destroy)
-    }
-
-    private fun destroy() {
-        stackSaver.unregister(key)
-
-        val stack = stack
-        stack.active.lifecycleRegistry.destroy()
-        stack.backStack.destroy()
-    }
-
-    private fun createInitialStack(): RouterStack<C, T> {
-        val initialStack = initialStack()
-
-        check(initialStack.isNotEmpty()) { "Initial stack can not be empty" }
-        check(initialStack.isUnique()) { "Configurations in the initial stack must be unique" }
-
-        return RouterStack(
-            active = routerEntryFactory(initialStack.last()),
-            backStack = initialStack.dropLast(1).map { RouterEntry.Destroyed(configuration = it) }
-        )
-    }
-
-    private fun restoreStack(): RouterStack<C, T>? {
-        val savedStack: StackSaver.RestoredStack<C>? = stackSaver.restore(key)
-        val activeRetainedEntry: RouterEntry.Created<C, T>? = retainedInstance.activeEntry
-
-        var activeInstanceKeeperDispatcher: InstanceKeeperDispatcher? = null
-        if (activeRetainedEntry != null) {
-            if (savedStack != null) {
-                activeInstanceKeeperDispatcher = activeRetainedEntry.instanceKeeperDispatcher
-            } else {
-                retainedInstance.destroyActiveEntry()
-            }
-        }
-
-        return savedStack?.restore(activeInstanceKeeperDispatcher)
-    }
-
-    private fun StackSaver.RestoredStack<C>.restore(activeInstanceKeeperDispatcher: InstanceKeeperDispatcher?): RouterStack<C, T> =
-        RouterStack(
-            active = routerEntryFactory(
-                configuration = active.configuration,
-                savedState = active.savedState,
-                instanceKeeperDispatcher = activeInstanceKeeperDispatcher
-            ),
-            backStack = backStack
-        )
-
-    private class RetainedInstance<C : Any, T : Any> : InstanceKeeper.Instance {
-        var activeEntry: RouterEntry.Created<C, T>? = null
-
-        override fun onDestroy() {
-            destroyActiveEntry()
-        }
-
-        fun destroyActiveEntry() {
-            activeEntry?.instanceKeeperDispatcher?.destroy()
-            activeEntry = null
-        }
-    }
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigator.kt
deleted file mode 100644
index 47386346..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigator.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.arkivanov.decompose.router
-
-internal interface StackNavigator<C : Any, T : Any> {
-
-    fun navigate(oldStack: RouterStack<C, T>, transformer: (stack: List<C>) -> List<C>): RouterStack<C, T>
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigatorImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigatorImpl.kt
deleted file mode 100644
index 98234666..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackNavigatorImpl.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.isUnique
-import com.arkivanov.essenty.lifecycle.destroy
-import com.arkivanov.essenty.lifecycle.pause
-import com.arkivanov.essenty.lifecycle.resume
-import com.arkivanov.essenty.lifecycle.stop
-
-internal class StackNavigatorImpl<C : Any, T : Any>(
-    private val routerEntryFactory: RouterEntryFactory<C, T>
-) : StackNavigator<C, T> {
-
-    override fun navigate(oldStack: RouterStack<C, T>, transformer: (stack: List<C>) -> List<C>): RouterStack<C, T> {
-        val oldConfigurationStack = oldStack.configurationStack
-        val newConfigurationStack = transformer(oldConfigurationStack)
-
-        if (newConfigurationStack === oldConfigurationStack) {
-            return oldStack
-        }
-
-        check(newConfigurationStack.isNotEmpty()) { "Configuration stack can not be empty" }
-        check(newConfigurationStack.isUnique()) { "Configurations in the stack must be unique" }
-
-        val newActiveConfiguration = newConfigurationStack.last()
-        val newActiveEntry: RouterEntry.Created<C, *>
-        if (newActiveConfiguration === oldStack.active.configuration) {
-            newActiveEntry = oldStack.active
-        } else {
-            newActiveEntry =
-                when (val entry = oldStack.backStack.find { it.configuration === newActiveConfiguration }) {
-                    is RouterEntry.Created -> entry.copy(savedState = null)
-                    is RouterEntry.Destroyed -> routerEntryFactory(entry.configuration, entry.savedState)
-                    null -> routerEntryFactory(newActiveConfiguration)
-                }
-
-            oldStack.active.lifecycleRegistry.pause()
-            newActiveEntry.lifecycleRegistry.resume()
-            oldStack.active.lifecycleRegistry.stop()
-            if (newConfigurationStack.none { it === oldStack.active.configuration }) {
-                oldStack.active.instanceKeeperDispatcher.destroy()
-                oldStack.active.lifecycleRegistry.destroy()
-            }
-        }
-
-        val newBackStackEntries =
-            newConfigurationStack.dropLast(1).map { configuration ->
-                if (oldStack.active.configuration === configuration) {
-                    oldStack.active.copy(savedState = oldStack.active.stateKeeperDispatcher.save())
-                } else {
-                    oldStack
-                        .backStack
-                        .find { it.configuration === configuration }
-                        ?: RouterEntry.Destroyed(configuration = configuration)
-                }
-            }
-
-        oldStack
-            .backStack
-            .filter { entry -> newConfigurationStack.none { it === entry.configuration } }
-            .destroy()
-
-        return RouterStack(active = newActiveEntry, backStack = newBackStackEntries)
-    }
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaver.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaver.kt
deleted file mode 100644
index 324cbba9..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaver.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.arkivanov.decompose.router
-
-internal interface StackSaver<C : Any> {
-
-    fun register(key: String, supplier: () -> RouterStack<C, *>)
-
-    fun unregister(key: String)
-
-    fun restore(key: String): RestoredStack<C>?
-
-    class RestoredStack<out C : Any>(
-        val active: RouterEntry.Destroyed<C>,
-        val backStack: List<RouterEntry.Destroyed<C>>
-    )
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaverImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaverImpl.kt
deleted file mode 100644
index a8797cdb..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/StackSaverImpl.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.router.StackSaver.RestoredStack
-import com.arkivanov.essenty.parcelable.Parcelable
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import com.arkivanov.essenty.parcelable.Parcelize
-import com.arkivanov.essenty.parcelable.consumeRequired
-import com.arkivanov.essenty.statekeeper.StateKeeper
-import com.arkivanov.essenty.statekeeper.consume
-import kotlin.reflect.KClass
-
-internal class StackSaverImpl<C : Parcelable>(
-    private val configurationClass: KClass<out C>,
-    private val stateKeeper: StateKeeper,
-    private val parcelableContainerFactory: (Parcelable?) -> ParcelableContainer
-) : StackSaver<C> {
-
-    override fun register(key: String, supplier: () -> RouterStack<C, *>) {
-        stateKeeper.register(key) { supplier().save() }
-    }
-
-    private fun RouterStack<C, *>.save(): SavedState =
-        SavedState(
-            active = SavedEntry(
-                configuration = parcelableContainerFactory(active.configuration),
-                savedState = active.stateKeeperDispatcher.save()
-            ),
-            backStack = backStack.map {
-                SavedEntry(
-                    configuration = parcelableContainerFactory(it.configuration),
-                    savedState = it.savedState
-                )
-            }
-        )
-
-    override fun unregister(key: String) {
-        stateKeeper.unregister(key)
-    }
-
-    override fun restore(key: String): RestoredStack<C>? =
-        stateKeeper
-            .consume<SavedState>(key)
-            ?.restore()
-
-    private fun SavedState.restore(): RestoredStack<C> =
-        RestoredStack(
-            active = active.restore(),
-            backStack = backStack.map { it.restore() }
-        )
-
-    private fun SavedEntry.restore(): RouterEntry.Destroyed<C> =
-        RouterEntry.Destroyed(
-            configuration = configuration.consumeRequired(configurationClass),
-            savedState = savedState
-        )
-
-    @Parcelize
-    private class SavedState(
-        val active: SavedEntry,
-        val backStack: List<SavedEntry>
-    ) : Parcelable
-
-    @Parcelize
-    private class SavedEntry(
-        val configuration: ParcelableContainer,
-        val savedState: ParcelableContainer?
-    ) : Parcelable
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackController.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackController.kt
index a9a5b815..06797676 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackController.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackController.kt
@@ -6,15 +6,15 @@ import com.arkivanov.decompose.ensureNeverFrozen
 import com.arkivanov.decompose.router.stack.StackNavigationSource.Event
 import com.arkivanov.decompose.value.MutableValue
 import com.arkivanov.decompose.value.Value
-import com.arkivanov.essenty.backpressed.BackPressedHandler
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.backhandler.BackHandler
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.doOnDestroy
 
 internal class ChildStackController<out C : Any, out T : Any>(
     private val source: StackNavigationSource<C>,
     lifecycle: Lifecycle,
-    private val backPressedHandler: BackPressedHandler,
-    private val popOnBackPressed: Boolean,
+    private val backHandler: BackHandler?,
     private val stackHolder: StackHolder<C, T>,
     private val controller: StackController<C, T>,
 ) {
@@ -27,39 +27,28 @@ internal class ChildStackController<out C : Any, out T : Any>(
     private val _stack = MutableValue(stackHolder.stack.toState())
     val stack: Value<ChildStack<C, T>> get() = _stack
 
+    private val backCallback: BackCallback =
+        BackCallback(isEnabled = _stack.value.backStack.isNotEmpty()) {
+            queue.offer(Event(transformer = { it.dropLast(1) }))
+        }
+
     init {
+        backHandler?.register(backCallback)
+
         val eventObserver = queue::offer
         source.subscribe(eventObserver)
-
-        val onBackPressedHandler = ::onBackPressed
-        backPressedHandler.register(onBackPressedHandler)
-
-        lifecycle.doOnDestroy {
-            backPressedHandler.unregister(onBackPressedHandler)
-            source.unsubscribe(eventObserver)
-        }
+        lifecycle.doOnDestroy { source.unsubscribe(eventObserver) }
     }
 
     private fun navigateActual(event: Event<C>) {
         val oldStack = stackHolder.stack
         val newStack = controller.navigate(oldStack = oldStack, transformer = event.transformer)
         stackHolder.stack = newStack
+        backCallback.isEnabled = newStack.backStack.isNotEmpty()
         _stack.value = newStack.toState()
         event.onComplete(newStack.configurationStack, oldStack.configurationStack)
     }
 
-    private fun onBackPressed(): Boolean =
-        when {
-            stackHolder.stack.active.backPressedDispatcher.onBackPressed() -> true
-
-            popOnBackPressed && stackHolder.stack.backStack.isNotEmpty() -> {
-                queue.offer(Event(transformer = { it.dropLast(1) }))
-                true
-            }
-
-            else -> false
-        }
-
     private fun RouterStack<C, T>.toState(): ChildStack<C, T> =
         ChildStack(
             active = Child.Created(configuration = active.configuration, instance = active.instance),
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
index 80b80fce..4b4f59cc 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.value.Value
 import com.arkivanov.essenty.parcelable.Parcelable
 import com.arkivanov.essenty.parcelable.ParcelableContainer
@@ -26,13 +27,17 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
     handleBackButton: Boolean = false,
     childFactory: (configuration: C, ComponentContext) -> T,
 ): Value<ChildStack<C, T>> {
-    val routerEntryFactory = RouterEntryFactoryImpl(lifecycle = lifecycle, childFactory = childFactory)
+    val routerEntryFactory =
+        RouterEntryFactoryImpl(
+            lifecycle = lifecycle,
+            backHandler = backHandler.child(lifecycle = null),
+            childFactory = childFactory,
+        )
 
     val controller =
         ChildStackController(
             lifecycle = lifecycle,
-            backPressedHandler = backPressedHandler,
-            popOnBackPressed = handleBackButton,
+            backHandler = backHandler.takeIf { handleBackButton }?.child(lifecycle = null),
             stackHolder = StackHolderImpl(
                 initialStack = initialStack,
                 lifecycle = lifecycle,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntry.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntry.kt
index 161c07ca..f731a2ad 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntry.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntry.kt
@@ -1,6 +1,6 @@
 package com.arkivanov.decompose.router.stack
 
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
+import com.arkivanov.decompose.backhandler.ChildBackHandler
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.parcelable.ParcelableContainer
@@ -17,7 +17,7 @@ internal sealed class RouterEntry<out C : Any, out T : Any> {
         val lifecycleRegistry: LifecycleRegistry,
         val stateKeeperDispatcher: StateKeeperDispatcher,
         val instanceKeeperDispatcher: InstanceKeeperDispatcher,
-        val backPressedDispatcher: BackPressedDispatcher
+        val backHandler: ChildBackHandler,
     ) : RouterEntry<C, T>()
 
     data class Destroyed<out C : Any>(
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntryFactoryImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntryFactoryImpl.kt
index ff769a7e..b02c7e7d 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntryFactoryImpl.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/RouterEntryFactoryImpl.kt
@@ -2,8 +2,9 @@ package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.ComponentContext
 import com.arkivanov.decompose.DefaultComponentContext
+import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.lifecycle.MergedLifecycle
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
+import com.arkivanov.essenty.backhandler.BackHandler
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
@@ -12,6 +13,7 @@ import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
 
 internal class RouterEntryFactoryImpl<C : Any, out T : Any>(
     private val lifecycle: Lifecycle,
+    private val backHandler: BackHandler,
     private val childFactory: (configuration: C, ComponentContext) -> T
 ) : RouterEntryFactory<C, T> {
 
@@ -24,16 +26,16 @@ internal class RouterEntryFactoryImpl<C : Any, out T : Any>(
         val mergedLifecycle = MergedLifecycle(lifecycle, componentLifecycleRegistry)
         val stateKeeperDispatcher = StateKeeperDispatcher(savedState)
         val instanceKeeperRegistry = instanceKeeperDispatcher ?: InstanceKeeperDispatcher()
-        val backPressedDispatcher = BackPressedDispatcher()
+        val backHandler = backHandler.child()
 
         val component =
             childFactory(
                 configuration,
                 DefaultComponentContext(
-                    mergedLifecycle,
-                    stateKeeperDispatcher,
-                    instanceKeeperRegistry,
-                    backPressedDispatcher
+                    lifecycle = mergedLifecycle,
+                    stateKeeper = stateKeeperDispatcher,
+                    instanceKeeper = instanceKeeperRegistry,
+                    backHandler = backHandler,
                 )
             )
 
@@ -43,7 +45,7 @@ internal class RouterEntryFactoryImpl<C : Any, out T : Any>(
             lifecycleRegistry = componentLifecycleRegistry,
             stateKeeperDispatcher = stateKeeperDispatcher,
             instanceKeeperDispatcher = instanceKeeperRegistry,
-            backPressedDispatcher = backPressedDispatcher
+            backHandler = backHandler,
         )
     }
 }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackControllerImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackControllerImpl.kt
index 5f9d4a1d..351c9bc7 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackControllerImpl.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackControllerImpl.kt
@@ -36,6 +36,8 @@ internal class StackControllerImpl<C : Any, T : Any>(
                     null -> routerEntryFactory(newActiveConfiguration)
                 }
 
+            oldStack.active.backHandler.stop()
+            newActiveEntry.backHandler.start()
             oldStack.active.lifecycleRegistry.pause()
             newActiveEntry.lifecycleRegistry.resume()
             oldStack.active.lifecycleRegistry.stop()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackHolderImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackHolderImpl.kt
index d77bee8d..4453e47d 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackHolderImpl.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackHolderImpl.kt
@@ -29,6 +29,7 @@ internal class StackHolderImpl<C : Any, T : Any>(
     init {
         stackSaver.register(key) { stack }
         retainedInstance.activeEntry = stack.active
+        stack.active.backHandler.start()
         stack.active.lifecycleRegistry.resume()
 
         lifecycle.doOnDestroy(::destroy)
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackSaverImpl.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackSaverImpl.kt
index 25411571..5c4fd716 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackSaverImpl.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackSaverImpl.kt
@@ -16,6 +16,12 @@ internal class StackSaverImpl<C : Parcelable>(
 ) : StackSaver<C> {
 
     override fun register(key: String, supplier: () -> RouterStack<C, *>) {
+        check(!stateKeeper.isRegistered(key)) {
+            "The key \"$key\" is already in use. If there are multiple Child Stacks in one component, " +
+                "make sure you supplied different key for each Child Stack. Also make sure there is only instance " +
+                "of the root component at a time."
+        }
+
         stateKeeper.register(key) { supplier().save() }
     }
 
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/webhistory/WebHistoryController.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/webhistory/WebHistoryController.kt
deleted file mode 100644
index 29670d24..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/webhistory/WebHistoryController.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.arkivanov.decompose.router.webhistory
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.Router
-
-/**
- * Connects the [Router] and the Web [History](https://developer.mozilla.org/en-US/docs/Web/API/History) API together.
- */
-@ExperimentalDecomposeApi
-interface WebHistoryController {
-
-
-    /**
-     * Listens for the [Router] state changes and updates the Web [History](https://developer.mozilla.org/en-US/docs/Web/API/History)
-     * accordingly. Also listens for the `History` changes and navigates the [Router].
-     *
-     * @param router a [Router] that should be observed and manipulated
-     * @param getPath a mapper from the [Router] configuration to a corresponding Web page path (starting from '/')
-     * @param getConfiguration a mapper from the Web page path (starting from '/') to a corresponding [Router] configuration
-     */
-    fun <C : Any> attach(
-        router: Router<C, *>,
-        getPath: (configuration: C) -> String,
-        getConfiguration: (path: String) -> C
-    )
-}
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/ChildStateKeeper.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/ChildStateKeeper.kt
index 8aacce22..57cfa8ab 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/ChildStateKeeper.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/ChildStateKeeper.kt
@@ -7,6 +7,8 @@ import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
 import com.arkivanov.essenty.statekeeper.consume
 
 internal fun StateKeeper.child(key: String, lifecycle: Lifecycle?): StateKeeper {
+    check(!isRegistered(key = key)) { "The key \"$key\" is already in use." }
+
     val stateKeeper = StateKeeperDispatcher(consume(key))
     register(key, stateKeeper::save)
     lifecycle?.doOnDestroy { unregister(key) }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/ValueObserver.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/ValueObserver.kt
deleted file mode 100644
index 92c40b80..00000000
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/ValueObserver.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.arkivanov.decompose.value
-
-@Deprecated("Use `(T) -> Unit` instead")
-typealias ValueObserver<T> = (T) -> Unit
diff --git a/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/Utils.kt b/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/Utils.kt
index 830687bd..b1fc31f3 100644
--- a/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/Utils.kt
+++ b/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/Utils.kt
@@ -1,6 +1,5 @@
 package com.arkivanov.decompose.router
 
-import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.value.Value
 import kotlin.math.min
 
@@ -33,9 +32,6 @@ internal fun <T> List<T>.findFirstDifferentIndex(other: List<T>): Int {
     return i
 }
 
-internal fun <C : Any> RouterState<C, *>.configurations(): List<C> =
-    backStack.map(Child<C, *>::configuration) + activeChild.configuration
-
 internal fun <T : Any> Value<T>.subscribe(observer: (new: T, old: T) -> Unit) {
     var old = value
     subscribe { new ->
diff --git a/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryController.kt b/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryController.kt
deleted file mode 100644
index 752632e1..00000000
--- a/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryController.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-package com.arkivanov.decompose.router.webhistory
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.Router
-import com.arkivanov.decompose.router.RouterState
-import com.arkivanov.decompose.router.configurations
-import com.arkivanov.decompose.router.findFirstDifferentIndex
-import com.arkivanov.decompose.router.navigate
-import com.arkivanov.decompose.router.startsWith
-import com.arkivanov.decompose.router.subscribe
-import org.w3c.dom.PopStateEvent
-
-@Deprecated("Use com.arkivanov.decompose.router.stack.webhistory.DefaultWebHistoryController instead")
-@ExperimentalDecomposeApi
-class DefaultWebHistoryController internal constructor(
-    private val window: Window,
-) : WebHistoryController {
-
-    @Suppress("unused") // Public API
-    constructor() : this(WindowImpl())
-
-    override fun <C : Any> attach(
-        router: Router<C, *>,
-        getPath: (configuration: C) -> String,
-        getConfiguration: (path: String) -> C,
-    ) {
-        val impl = Impl(router, window, getPath, getConfiguration)
-        router.state.subscribe(impl::onStateChanged)
-        window.onPopState = impl::onPopState
-    }
-
-    private class Impl<in C : Any>(
-        private val router: Router<C, *>,
-        private val window: Window,
-        private val getPath: (C) -> String,
-        private val getConfiguration: (String) -> C,
-    ) {
-        private var isStateObserverFirstPass = true
-        private var isStateObserverEnabled = true
-
-        fun onStateChanged(newState: RouterState<C, *>, oldState: RouterState<C, *>) {
-            if (!isStateObserverEnabled) {
-                return
-            }
-
-            val newStack = newState.configurations()
-            val oldStack = oldState.configurations()
-            val firstDifferentIndex = oldStack.findFirstDifferentIndex(newStack)
-
-            when {
-                // Initialize the history
-                isStateObserverFirstPass -> {
-                    isStateObserverFirstPass = false
-                    window.history.replaceState(newStack[0])
-                    for (i in 1..newStack.lastIndex) {
-                        window.history.pushState(newStack[i])
-                    }
-                }
-
-                newStack == oldStack -> return
-
-                // One or more configurations were popped from the stack
-                oldStack.startsWith(newStack) -> { // Pop removed pages from the history
-                    window.history.go(delta = newStack.size - oldStack.size)
-                }
-
-                // One or more configurations were pushed to the history
-                newStack.startsWith(oldStack) -> { // Push new pages to the history
-                    for (i in oldStack.size..newStack.lastIndex) {
-                        window.history.pushState(newStack[i])
-                    }
-                }
-
-                // The active configuration was changed, and new configurations could be pushed
-                firstDifferentIndex == oldStack.lastIndex -> {
-                    // Replace the current page with a new one
-                    window.history.replaceState(newStack[firstDifferentIndex])
-
-                    // Push the rest of the pages to the history
-                    for (i in (firstDifferentIndex + 1)..newStack.lastIndex) {
-                        window.history.pushState(newStack[i])
-                    }
-                }
-
-                // Some configurations were popped, and one or more configurations were pushed
-                firstDifferentIndex > 0 -> {
-                    window.onPopState = {
-                        window.onPopState = ::onPopState
-
-                        // Push new pages to the history
-                        for (i in firstDifferentIndex..newStack.lastIndex) {
-                            window.history.pushState(newStack[i])
-                        }
-                    }
-
-                    // Pop removed pages from the history
-                    window.history.go(delta = firstDifferentIndex - oldStack.size)
-                }
-
-                // All configurations were popped, and one or more configurations were pushed
-                else -> {
-                    window.onPopState = {
-                        window.onPopState = ::onPopState
-
-                        // Replace the current page with a new one
-                        window.history.replaceState(newStack[firstDifferentIndex])
-
-                        // Push the rest of the pages to the history
-                        // Corner case: if there is nothing to push, old pages will remain in the history
-                        for (i in (firstDifferentIndex + 1)..newStack.lastIndex) {
-                            window.history.pushState(newStack[i])
-                        }
-                    }
-
-                    // Pop removed pages from the history, except the first one
-                    window.history.go(delta = -oldStack.lastIndex)
-                }
-            }
-        }
-
-        fun onPopState(event: PopStateEvent) {
-            val newData = event.getData() ?: return
-            val newConfigurationKey = newData.last().configurationKey
-
-            isStateObserverEnabled = false
-
-            router.navigate { stack ->
-                val indexInStack = stack.indexOfLast { it.hashCode() == newConfigurationKey }
-                if (indexInStack >= 0) {
-                    // History popped, pop from the Router
-                    stack.take(indexInStack + 1)
-                } else {
-                    // History pushed, push to the Router
-                    stack + newData.drop(stack.size).map { getConfiguration(it.path) }
-                }
-            }
-
-            isStateObserverEnabled = true
-        }
-
-        private fun History.pushState(configuration: C) {
-            val nextItem = PageItem(configurationKey = configuration.hashCode(), path = getPath(configuration))
-            val newData = getCurrentData() + nextItem
-            pushState(data = newData, url = nextItem.path)
-        }
-
-        private fun History.replaceState(configuration: C) {
-            val newItem = PageItem(configurationKey = configuration.hashCode(), path = getPath(configuration))
-            val newData = getCurrentData().dropLast(1).toTypedArray() + newItem
-            replaceState(data = newData, url = newItem.path)
-        }
-
-        private fun getCurrentData(): Array<PageItem> = window.history.state?.unsafeCast<Array<PageItem>>() ?: emptyArray()
-
-        private fun PopStateEvent.getData(): Array<PageItem>? = state?.unsafeCast<Array<PageItem>>()
-    }
-
-    private data class PageItem(
-        val configurationKey: Int,
-        val path: String,
-    )
-
-    internal interface Window {
-        val history: History
-        var onPopState: ((PopStateEvent) -> Unit)?
-    }
-
-    internal interface History {
-        val state: Any?
-
-        fun go(delta: Int)
-        fun pushState(data: Any?, url: String?)
-        fun replaceState(data: Any?, url: String?)
-    }
-
-    private class WindowImpl : Window {
-        override val history: History = HistoryImpl()
-        override var onPopState: ((PopStateEvent) -> Unit)? by kotlinx.browser.window::onpopstate
-    }
-
-    private class HistoryImpl : History {
-        override val state: Any? by kotlinx.browser.window.history::state
-
-        override fun go(delta: Int) {
-            kotlinx.browser.window.history.go(delta = delta)
-        }
-
-        override fun pushState(data: Any?, url: String?) {
-            kotlinx.browser.window.history.pushState(data = data, title = "", url = url)
-        }
-
-        override fun replaceState(data: Any?, url: String?) {
-            kotlinx.browser.window.history.replaceState(data = data, title = "", url = url)
-        }
-    }
-}
diff --git a/deps.versions.toml b/deps.versions.toml
index fb8f296b..1ce2eb64 100644
--- a/deps.versions.toml
+++ b/deps.versions.toml
@@ -2,13 +2,14 @@
 
 decompose = "0.8.0-native-compose-02"
 kotlin = "1.7.0"
-essenty = "0.4.2"
+essenty = "0.5.1"
 reaktive = "1.2.1"
 junit = "4.13.2"
-jetbrainsCompose = "1.2.0-alpha01-dev745"
+jetbrainsCompose = "1.2.0-alpha01-dev753"
 jetbrainsKotlinWrappers = "0.0.1-pre.325-kotlin-1.6.10"
 jetbrainsKotlinxCoroutines = "1.6.1"
 jetbrainsBinaryCompatibilityValidator = "0.10.0"
+jetpackCompose = "1.2.0"
 androidGradle = "7.2.0"
 androidMaterial = "1.3.0"
 androidPlay = "1.10.0"
@@ -25,7 +26,7 @@ kotlin-kotlinGradlePlug = { group = "org.jetbrains.kotlin", name = "kotlin-gradl
 essenty-lifecycle = { group = "com.arkivanov.essenty", name = "lifecycle", version.ref = "essenty" }
 essenty-stateKeeper = { group = "com.arkivanov.essenty", name = "state-keeper", version.ref = "essenty" }
 essenty-instanceKeeper = { group = "com.arkivanov.essenty", name = "instance-keeper", version.ref = "essenty" }
-essenty-backPressed = { group = "com.arkivanov.essenty", name = "back-pressed", version.ref = "essenty" }
+essenty-backHandler = { group = "com.arkivanov.essenty", name = "back-handler", version.ref = "essenty" }
 
 reaktive-reaktive = { group = "com.badoo.reaktive", name = "reaktive", version.ref = "reaktive" }
 
@@ -37,6 +38,8 @@ jetbrains-kotlinWrappers-kotlinWrappersBom = { group = "org.jetbrains.kotlin-wra
 jetbrains-kotlinx-binaryCompatibilityValidator = { group = "org.jetbrains.kotlinx", name = "binary-compatibility-validator", version.ref = "jetbrainsBinaryCompatibilityValidator" }
 jetbrains-kotlinx-kotlinxCoroutinesSwing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "jetbrainsKotlinxCoroutines" }
 
+androidx-compose-runtime-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "jetpackCompose" }
+
 android-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradle" }
 android-material-material = { group = "com.google.android.material", name = "material", version.ref = "androidMaterial" }
 android-play-core = { group = "com.google.android.play", name = "core", version.ref = "androidPlay" }
diff --git a/docs/child-stack/browser-history.md b/docs/child-stack/browser-history.md
index e0a31eec..9af18b07 100644
--- a/docs/child-stack/browser-history.md
+++ b/docs/child-stack/browser-history.md
@@ -59,9 +59,7 @@ navigation (typically it is the root component)
 
 ### Example
 
-The Counter sample demonstrates the use of `WebHistoryController`:
+The sample project demonstrates the use of `WebHistoryController`:
 
-- [App.kt](https://github.com/arkivanov/Decompose/blob/master/sample/counter/app-js/src/main/kotlin/com/arkivanov/sample/counter/app/App.kt) -
-demonstrates passing `WebHistoryController` and a deeplink via constructor to `CounterRootComponent`
-- [CounterRootComponent](https://github.com/arkivanov/Decompose/blob/master/sample/counter/shared/src/commonMain/kotlin/com/arkivanov/sample/counter/shared/root/CounterRootComponent.kt) -
-demonstrates generating the initial back from the deeplink, as well as calling `WebHistoryController.attach` and supplying the arguments
+- [Main.kt](https://github.com/arkivanov/Decompose/blob/master/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt) - demonstrates passing `WebHistoryController` and a deeplink via constructor to `RootComponent`
+- [RootComponent](https://github.com/arkivanov/Decompose/blob/master/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootComponent.kt) - demonstrates generating the initial stack from the deeplink, as well as calling `WebHistoryController.attach` and supplying the arguments
diff --git a/docs/component/overview.md b/docs/component/overview.md
index b3b2ebcd..5a423cc0 100644
--- a/docs/component/overview.md
+++ b/docs/component/overview.md
@@ -92,7 +92,7 @@ class MyFragment : DialogFragment() {
 
 ### Root ComponentContext in Jetpack/JetBrains Compose
 
-It is advised to not create the root `ComponentContext` (and a root component) directly in a `Composable` function. Compositions may be performed in a background thread, which may brake things. The preferred way is to create the root component outside of Compose.
+It is advised to not create the root `ComponentContext` (and a root component) directly in a `Composable` function. Compositions may be performed in a background thread, which may break things. The preferred way is to create the root component outside of Compose.
 
 !!!warning
     If you can't avoid creating the root component in a `Composable` function, please make sure you use `remember`. This will prevent the root component and its `ComponentContext` from being recreated on each composition.
diff --git a/docs/component/scopes.md b/docs/component/scopes.md
new file mode 100644
index 00000000..fba6af45
--- /dev/null
+++ b/docs/component/scopes.md
@@ -0,0 +1,121 @@
+# Scoping jobs and subscriptions
+
+Due to the fact that components are lifecycle-aware, it is very easy to manage coroutine scopes and reactive subscriptions.
+
+## Creating a CoroutineScope in a component
+
+```kotlin
+fun CoroutineScope(context: CoroutineContext, lifecycle: Lifecycle): CoroutineScope {
+    val scope = CoroutineScope(context)
+    lifecycle.doOnDestroy(scope::cancel)
+    return scope
+}
+
+fun LifecycleOwner.coroutineScope(context: CoroutineContext): CoroutineScope =
+    CoroutineScope(context, lifecycle)
+
+class SomeComponent(
+    componentContext: ComponentContext,
+    mainContext: CoroutineContext,
+    private val ioContext: CoroutineContext,
+) : ComponentContext by componentContext {
+
+    // The scope is automatically cancelled when the component is destroyed
+    private val scope = coroutineScope(mainContext + SupervisorJob())
+
+    fun foo() {
+        scope.launch {
+            val result =
+                withContext(ioContext) {
+                    "Result" // Result from background thread
+                }
+
+            println(result) // Handle the result on main thread
+        }
+    }
+}
+```
+
+## Creating a CoroutineScope that survives Android configuration changes
+
+```kotlin
+internal class SomeRetainedInstance(mainContext: CoroutineContext) : InstanceKeeper.Instance {
+    // The scope survives Android configuration changes
+    private val scope = CoroutineScope(mainContext + SupervisorJob())
+
+    fun foo() {
+        scope.launch {
+            // Do the job
+        }
+    }
+    
+    override fun onDestroy() {
+        scope.cancel() // Cancel the scope when the instance is destroyed
+    }
+}
+
+class SomeComponent(
+    componentContext: ComponentContext,
+    mainContext: CoroutineContext,
+) : ComponentContext by componentContext {
+
+    private val someRetainedInstance = instanceKeeper.getOrCreate { SomeRetainedInstance(mainContext) }
+}
+```
+
+## Creating a Reaktive DisposableScope in a component
+
+```kotlin
+fun DisposableScope(lifecycle: Lifecycle): DisposableScope {
+    val scope = DisposableScope()
+    lifecycle.doOnDestroy(scope::dispose)
+    return scope
+}
+
+fun LifecycleOwner.disposableScope(): DisposableScope =
+    DisposableScope(lifecycle)
+
+class SomeComponent(
+    componentContext: ComponentContext,
+) : ComponentContext by componentContext,
+    // The scope is automatically disposed when the component is destroyed
+    DisposableScope by componentContext.disposableScope() {
+
+    fun foo() {
+        singleFromFunction {
+            "Result" // Result from background thread
+        }
+            .subscribeOn(ioScheduler)
+            .observeOn(mainScheduler)
+            .subscribeScoped { // Subscribe using the DisposableScope
+                println(it) // Handle the result on main thread
+            }
+    }
+}
+```
+
+## Creating a Reaktive DisposableScope that survives Android configuration changes
+
+```kotlin
+internal class SomeRetainedInstance : InstanceKeeper.Instance,
+    // The scope survives Android configuration changes
+    DisposableScope by DisposableScope() {
+
+    fun foo() {
+        completableFromFunction {
+            // Do the job
+        }.subscribeScoped()
+    }
+
+    override fun onDestroy() {
+        dispose() // Dispose the scope when the instance is destroyed
+    }
+}
+
+class SomeComponent(
+    componentContext: ComponentContext,
+) : ComponentContext by componentContext {
+
+    private val someRetainedInstance = instanceKeeper.getOrCreate { SomeRetainedInstance() }
+}
+```
diff --git a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
index c1353a18..6d538ed0 100644
--- a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
+++ b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/stack/StackRouterView.kt
@@ -126,6 +126,7 @@ class StackRouterView @JvmOverloads constructor(
         if (inactiveChild != null) {
             newChildView.restoreHierarchyState(inactiveChild.savedState)
         } else {
+            @Suppress("DEPRECATION")
             val childSavedState: SparseArray<Parcelable>? = savedState?.getSparseParcelableArray(activeChildKey)
             if (childSavedState != null) {
                 savedState?.remove(activeChildKey)
@@ -171,10 +172,11 @@ class StackRouterView @JvmOverloads constructor(
         override fun describeContents(): Int = 0
 
         companion object CREATOR : Parcelable.ClassLoaderCreator<SavedState> {
+            @Suppress("DEPRECATION")
             override fun createFromParcel(source: Parcel, loader: ClassLoader?): SavedState =
                 SavedState(
                     superState = source.readParcelable(loader),
-                    state = requireNotNull(source.readBundle(loader))
+                    state = requireNotNull(source.readBundle(loader)),
                 )
 
             override fun createFromParcel(parcel: Parcel): SavedState = createFromParcel(parcel, null)
diff --git a/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api b/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api
index 03701207..f7f6471c 100644
--- a/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api
+++ b/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api
@@ -5,76 +5,10 @@ public final class com/arkivanov/decompose/extensions/compose/jetbrains/BuildCon
 	public fun <init> ()V
 }
 
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/ChildrenKt {
-	public static final fun Children (Lcom/arkivanov/decompose/router/RouterState;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
-	public static final fun Children (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
-}
-
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/SubscribeAsStateKt {
 	public static final fun subscribeAsState (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
 }
 
-public abstract interface class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation {
-	public abstract fun invoke (Lcom/arkivanov/decompose/router/RouterState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimationKt {
-	public static final fun ChildAnimation (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static final fun childAnimation (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static final fun childAnimation (Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static synthetic fun childAnimation$default (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-}
-
-public abstract interface class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator {
-	public abstract fun invoke (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimatorKt {
-	public static final fun ChildAnimator (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static final fun childAnimator (Landroidx/compose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun childAnimator$default (Landroidx/compose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function5;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static final fun plus (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$FadeKt {
-	public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$FadeKt;
-	public static field lambda-1 Lkotlin/jvm/functions/Function5;
-	public fun <init> ()V
-	public final fun getLambda-1$extensions_compose_jetbrains_release ()Lkotlin/jvm/functions/Function5;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$SlideKt {
-	public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$SlideKt;
-	public static field lambda-1 Lkotlin/jvm/functions/Function5;
-	public fun <init> ()V
-	public final fun getLambda-1$extensions_compose_jetbrains_release ()Lkotlin/jvm/functions/Function5;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction : java/lang/Enum {
-	public static final field ENTER_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field ENTER_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field EXIT_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field EXIT_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field IDLE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static fun values ()[Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/FadeKt {
-	public static final fun fade (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun fade$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ScaleKt {
-	public static final fun scale (Landroidx/compose/animation/core/FiniteAnimationSpec;FF)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun scale$default (Landroidx/compose/animation/core/FiniteAnimationSpec;FFILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/SlideKt {
-	public static final fun slide (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun slide$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenKt {
 	public static final fun Children (Lcom/arkivanov/decompose/router/stack/ChildStack;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
 	public static final fun Children (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
@@ -99,11 +33,17 @@ public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/an
 	public static final field ENTER_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static final field EXIT_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static final field EXIT_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
-	public static final field IDLE Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static fun values ()[Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 }
 
+public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DirectionKt {
+	public static final fun isBack (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isEnter (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isExit (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isFront (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+}
+
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/FadeKt {
 	public static final fun fade (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;
 	public static synthetic fun fade$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;
@@ -126,7 +66,7 @@ public abstract interface class com/arkivanov/decompose/extensions/compose/jetbr
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimationKt {
 	public static final fun StackAnimation (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 	public static final fun stackAnimation (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
-	public static final fun stackAnimation (Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
+	public static final fun stackAnimation (Lkotlin/jvm/functions/Function3;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 	public static synthetic fun stackAnimation$default (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 }
 
diff --git a/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api b/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api
index 25bf0876..e048e195 100644
--- a/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api
+++ b/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api
@@ -1,73 +1,7 @@
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/ChildrenKt {
-	public static final fun Children (Lcom/arkivanov/decompose/router/RouterState;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
-	public static final fun Children (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
-}
-
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/SubscribeAsStateKt {
 	public static final fun subscribeAsState (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State;
 }
 
-public abstract interface class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation {
-	public abstract fun invoke (Lcom/arkivanov/decompose/router/RouterState;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimationKt {
-	public static final fun ChildAnimation (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static final fun childAnimation (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static final fun childAnimation (Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-	public static synthetic fun childAnimation$default (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation;
-}
-
-public abstract interface class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator {
-	public abstract fun invoke (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;I)V
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimatorKt {
-	public static final fun ChildAnimator (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static final fun childAnimator (Landroidx/compose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun childAnimator$default (Landroidx/compose/animation/core/FiniteAnimationSpec;Lkotlin/jvm/functions/Function5;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static final fun plus (Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$FadeKt {
-	public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$FadeKt;
-	public static field lambda-1 Lkotlin/jvm/functions/Function5;
-	public fun <init> ()V
-	public final fun getLambda-1$extensions_compose_jetbrains ()Lkotlin/jvm/functions/Function5;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$SlideKt {
-	public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ComposableSingletons$SlideKt;
-	public static field lambda-1 Lkotlin/jvm/functions/Function5;
-	public fun <init> ()V
-	public final fun getLambda-1$extensions_compose_jetbrains ()Lkotlin/jvm/functions/Function5;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction : java/lang/Enum {
-	public static final field ENTER_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field ENTER_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field EXIT_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field EXIT_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static final field IDLE Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-	public static fun values ()[Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/FadeKt {
-	public static final fun fade (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun fade$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ScaleKt {
-	public static final fun scale (Landroidx/compose/animation/core/FiniteAnimationSpec;FF)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun scale$default (Landroidx/compose/animation/core/FiniteAnimationSpec;FFILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
-public final class com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/SlideKt {
-	public static final fun slide (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-	public static synthetic fun slide$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator;
-}
-
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/lifecycle/LifecycleControllerKt {
 	public static final fun LifecycleController (Lcom/arkivanov/essenty/lifecycle/LifecycleRegistry;Landroidx/compose/ui/window/WindowState;Landroidx/compose/runtime/Composer;I)V
 }
@@ -96,11 +30,17 @@ public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/an
 	public static final field ENTER_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static final field EXIT_BACK Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static final field EXIT_FRONT Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
-	public static final field IDLE Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 	public static fun values ()[Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;
 }
 
+public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DirectionKt {
+	public static final fun isBack (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isEnter (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isExit (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+	public static final fun isFront (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction;)Z
+}
+
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/FadeKt {
 	public static final fun fade (Landroidx/compose/animation/core/FiniteAnimationSpec;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;
 	public static synthetic fun fade$default (Landroidx/compose/animation/core/FiniteAnimationSpec;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;
@@ -123,7 +63,7 @@ public abstract interface class com/arkivanov/decompose/extensions/compose/jetbr
 public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimationKt {
 	public static final fun StackAnimation (Lkotlin/jvm/functions/Function5;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 	public static final fun stackAnimation (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
-	public static final fun stackAnimation (Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
+	public static final fun stackAnimation (Lkotlin/jvm/functions/Function3;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 	public static synthetic fun stackAnimation$default (Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator;ILjava/lang/Object;)Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;
 }
 
diff --git a/extensions-compose-jetbrains/build.gradle.kts b/extensions-compose-jetbrains/build.gradle.kts
index 41a61d4e..d8a55472 100644
--- a/extensions-compose-jetbrains/build.gradle.kts
+++ b/extensions-compose-jetbrains/build.gradle.kts
@@ -35,6 +35,9 @@ kotlin {
 
         android.main.dependencies {
             implementation(deps.androidx.activity.activityKtx)
+
+            // Temporary dependency until JB Compose has movableContentOf for Android
+            implementation(deps.androidx.compose.runtime.runtime)
         }
 
         jvm.test.dependencies {
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/Children.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/Children.kt
deleted file mode 100644
index 8fba111f..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/Children.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.SaveableStateHolder
-import androidx.compose.runtime.saveable.rememberSaveableStateHolder
-import androidx.compose.ui.Modifier
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.ChildAnimation
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.emptyChildAnimation
-import com.arkivanov.decompose.router.RouterState
-import com.arkivanov.decompose.value.Value
-
-typealias ChildContent<C, T> = @Composable (child: Child.Created<C, T>) -> Unit
-
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children instead")
-@OptIn(ExperimentalDecomposeApi::class)
-@Composable
-fun <C : Any, T : Any> Children(
-    routerState: RouterState<C, T>,
-    modifier: Modifier = Modifier,
-    animation: ChildAnimation<C, T> = emptyChildAnimation(),
-    content: ChildContent<C, T>
-) {
-    val holder = rememberSaveableStateHolder()
-
-    holder.retainStates(routerState.getConfigurations())
-
-    animation(routerState, modifier) { child ->
-        holder.SaveableStateProvider(child.configuration.key()) {
-            content(child)
-        }
-    }
-}
-
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children")
-@OptIn(ExperimentalDecomposeApi::class)
-@Composable
-fun <C : Any, T : Any> Children(
-    routerState: Value<RouterState<C, T>>,
-    modifier: Modifier = Modifier,
-    animation: ChildAnimation<C, T> = emptyChildAnimation(),
-    content: ChildContent<C, T>
-) {
-    val state = routerState.subscribeAsState()
-
-    Children(
-        routerState = state.value,
-        modifier = modifier,
-        animation = animation,
-        content = content
-    )
-}
-
-private fun RouterState<*, *>.getConfigurations(): Set<String> {
-    val set = HashSet<String>()
-    backStack.forEach { set += it.configuration.key() }
-    set += activeChild.configuration.key()
-
-    return set
-}
-
-private fun Any.key(): String = "${this::class.simpleName}_${hashCode().toString(radix = 36)}"
-
-@Composable
-private fun SaveableStateHolder.retainStates(currentKeys: Set<Any>) {
-    val keys = remember(this) { Keys(currentKeys) }
-
-    DisposableEffect(this, currentKeys) {
-        keys.set.forEach {
-            if (it !in currentKeys) {
-                removeState(it)
-            }
-        }
-
-        keys.set = currentKeys
-
-        onDispose {}
-    }
-}
-
-private class Keys(
-    var set: Set<Any>
-)
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation.kt
deleted file mode 100644
index 0430c31b..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimation.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.RouterState
-
-/**
- * Tracks the [RouterState] changes and animates between child widget.
- */
-@Deprecated(
-    message = "Use StackAnimation instead",
-    replaceWith = ReplaceWith(
-        "StackAnimation",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation",
-    ),
-)
-@ExperimentalDecomposeApi
-interface ChildAnimation<C : Any, T : Any> {
-
-    @Composable
-    operator fun invoke(state: RouterState<C, T>, modifier: Modifier, content: @Composable (child: Child.Created<C, T>) -> Unit)
-}
-
-/**
- * Factory function for [ChildAnimation] while `fun interface` with a `@Composable` function
- * is not supported - [b/221488059](https://issuetracker.google.com/issues/221488059).
- */
-@Deprecated(
-    message = "Use StackAnimation instead",
-    replaceWith = ReplaceWith(
-        "StackAnimation(content = content)",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation",
-    ),
-)
-@Suppress("FunctionName") // Factory function
-@ExperimentalDecomposeApi
-inline fun <C : Any, T : Any> ChildAnimation(
-    crossinline content: @Composable (
-        state: RouterState<C, T>,
-        modifier: Modifier,
-        content: @Composable (child: Child.Created<C, T>) -> Unit
-    ) -> Unit
-): ChildAnimation<C, T> =
-    object : ChildAnimation<C, T> {
-        @Composable
-        override operator fun invoke(
-            state: RouterState<C, T>,
-            modifier: Modifier,
-            content: @Composable (child: Child.Created<C, T>) -> Unit
-        ) {
-            content(state, modifier, content)
-        }
-    }
-
-/**
- * Creates an implementation of [ChildAnimation] that allows different [ChildAnimator]s.
- *
- * @param selector provides a [ChildAnimator] for [Child] and [Direction].
- */
-@Deprecated(
-    message = "Use stackAnimation instead",
-    replaceWith = ReplaceWith(
-        "stackAnimation(selector = selector)",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation",
-    ),
-)
-@ExperimentalDecomposeApi
-fun <C : Any, T : Any> childAnimation(
-    selector: (child: Child.Created<C, T>, direction: Direction) -> ChildAnimator
-): ChildAnimation<C, T> =
-    DefaultChildAnimation(selector)
-
-/**
- * Creates an implementation of [ChildAnimation] with the provided [ChildAnimator].
- *
- * @param animator a [ChildAnimator] to be used for animation, default is [fade].
- */
-@Deprecated(
-    message = "Use stackAnimation instead",
-    replaceWith = ReplaceWith(
-        "stackAnimation(animator = animator)",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation",
-    ),
-)
-@ExperimentalDecomposeApi
-fun <C : Any, T : Any> childAnimation(animator: ChildAnimator = fade()): ChildAnimation<C, T> =
-    childAnimation { _, _ -> animator }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator.kt
deleted file mode 100644
index 61298423..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/ChildAnimator.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-/**
- * Animates a child widget in the given [Direction].
- */
-@Deprecated(
-    message = "Use StackAnimator instead",
-    replaceWith = ReplaceWith(
-        "StackAnimator",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimator",
-    ),
-)
-@ExperimentalDecomposeApi
-interface ChildAnimator {
-
-    /**
-     * Animates the [content] in the given [Direction], and calls [onFinished] at the end.
-     */
-    @Composable
-    operator fun invoke(direction: Direction, onFinished: () -> Unit, content: @Composable (Modifier) -> Unit)
-}
-
-/**
- * Factory function for [ChildAnimator] while `fun interface` with a `@Composable` function
- * is not supported - [b/221488059](https://issuetracker.google.com/issues/221488059).
- */
-@Deprecated(
-    message = "Use StackAnimator instead",
-    replaceWith = ReplaceWith(
-        "StackAnimator(content = content)",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimator",
-    ),
-)
-@ExperimentalDecomposeApi
-inline fun ChildAnimator(
-    crossinline content: @Composable (Direction, onFinished: () -> Unit, content: @Composable (Modifier) -> Unit) -> Unit
-): ChildAnimator =
-    object : ChildAnimator {
-        @Composable
-        override operator fun invoke(direction: Direction, onFinished: () -> Unit, content: @Composable (Modifier) -> Unit) {
-            content(direction, onFinished, content)
-        }
-    }
-
-/**
- * Creates an implementation of [ChildAnimator] with a convenient frame-by-frame rendering.
- *
- * @param animationSpec a [FiniteAnimationSpec] to configure the animation.
- * @param frame renders the `content` using the provided `factor` and [Direction]. Called for every animation frame.
- * The `factor` argument changes as follows:
- * - Always 0F for [Direction.IDLE]
- * - From 1F to 0F for [Direction.ENTER_FRONT]
- * - From 0F to 1F for [Direction.EXIT_FRONT]
- * - From -1F to 0F for [Direction.ENTER_BACK]
- * - From 0F to -1F for [Direction.EXIT_BACK]
- */
-@Deprecated(
-    message = "Use stackAnimator instead",
-    replaceWith = ReplaceWith(
-        "stackAnimator(animationSpec = animationSpec, frame = frame)",
-        "com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimator",
-    ),
-)
-@ExperimentalDecomposeApi
-fun childAnimator(
-    animationSpec: FiniteAnimationSpec<Float> = tween(),
-    frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit
-): ChildAnimator =
-    DefaultChildAnimator(
-        animationSpec = animationSpec,
-        frame = frame
-    )
-
-/**
- * Combines (merges) the receiver [ChildAnimator] with the [other] [ChildAnimator].
- */
-@Deprecated(message = "Use StackAnimator instead")
-@ExperimentalDecomposeApi
-operator fun ChildAnimator.plus(other: ChildAnimator): ChildAnimator =
-    ChildAnimator { direction, onFinished, content ->
-        val finished = remember(direction) { BooleanArray(2) }
-
-        this(
-            direction = direction,
-            onFinished = {
-                finished[0] = true
-                if (finished.all { it }) {
-                    onFinished()
-                }
-            },
-        ) { thisModifier ->
-            other(
-                direction = direction,
-                onFinished = {
-                    finished[1] = true
-                    if (finished.all { it }) {
-                        onFinished()
-                    }
-                },
-            ) { otherModifier ->
-                content(thisModifier.then(otherModifier))
-            }
-        }
-    }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimation.kt
deleted file mode 100644
index f99d92a5..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimation.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.pointer.consumeAllChanges
-import androidx.compose.ui.input.pointer.pointerInput
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.RouterState
-
-@ExperimentalDecomposeApi
-internal class DefaultChildAnimation<C : Any, T : Any>(
-    private val selector: (child: Child.Created<C, T>, direction: Direction) -> ChildAnimator
-) : ChildAnimation<C, T> {
-
-    @Composable
-    override operator fun invoke(state: RouterState<C, T>, modifier: Modifier, content: @Composable (child: Child.Created<C, T>) -> Unit) {
-        var activePage by remember { mutableStateOf(state.activePage()) }
-        var items by remember { mutableStateOf(getAnimationItems(newPage = activePage, oldPage = null)) }
-
-        if (state.activeChild.configuration != activePage.child.configuration) {
-            val oldPage = activePage
-            activePage = state.activePage()
-            items = getAnimationItems(newPage = activePage, oldPage = oldPage)
-        }
-
-        Box(modifier = modifier) {
-            items.forEach { (configuration, item) ->
-                val (child, direction) = item
-
-                key(configuration) {
-                    val animator = remember(direction) { selector(child, direction) }
-
-                    animator(
-                        direction = direction,
-                        onFinished = {
-                            when (direction) {
-                                Direction.EXIT_FRONT,
-                                Direction.EXIT_BACK -> items = items - configuration
-                                Direction.ENTER_FRONT,
-                                Direction.ENTER_BACK -> items = items + (configuration to item.copy(direction = Direction.IDLE))
-                                Direction.IDLE -> Unit
-                            }
-                        }
-                    ) { modifier ->
-                        Box(modifier = modifier) {
-                            content(child)
-                        }
-                    }
-                }
-            }
-
-            // A workaround until https://issuetracker.google.com/issues/214231672.
-            // Normally only the exiting child be disabled.
-            if (items.size > 1) {
-                Overlay(modifier = Modifier.matchParentSize())
-            }
-        }
-    }
-
-    @Composable
-    private fun Overlay(modifier: Modifier) {
-        Box(
-            modifier = modifier.pointerInput(Unit) {
-                awaitPointerEventScope {
-                    while (true) {
-                        val event = awaitPointerEvent()
-                        event.changes.forEach { it.consumeAllChanges() }
-                    }
-                }
-            }
-        )
-    }
-
-    private fun RouterState<C, T>.activePage(): Page<C, T> =
-        Page(child = activeChild, index = backStack.size)
-
-    private fun getAnimationItems(newPage: Page<C, T>, oldPage: Page<C, T>?): Map<C, AnimationItem<C, T>> =
-        when {
-            oldPage == null ->
-                listOf(AnimationItem(newPage.child, Direction.IDLE))
-
-            newPage.index >= oldPage.index ->
-                listOf(
-                    AnimationItem(oldPage.child, Direction.EXIT_BACK),
-                    AnimationItem(newPage.child, Direction.ENTER_FRONT),
-                )
-
-            else ->
-                listOf(
-                    AnimationItem(newPage.child, Direction.ENTER_BACK),
-                    AnimationItem(oldPage.child, Direction.EXIT_FRONT),
-                )
-        }.associateBy { it.child.configuration }
-
-    private data class AnimationItem<out C : Any, out T : Any>(
-        val child: Child.Created<C, T>,
-        val direction: Direction,
-    )
-
-    private class Page<out C : Any, out T : Any>(
-        val child: Child.Created<C, T>,
-        val index: Int,
-    )
-}
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimator.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimator.kt
deleted file mode 100644
index ff001ae6..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/DefaultChildAnimator.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.isFinished
-import androidx.compose.animation.core.tween
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-@ExperimentalDecomposeApi
-internal class DefaultChildAnimator(
-    private val animationSpec: FiniteAnimationSpec<Float> = tween(),
-    private val frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit
-) : ChildAnimator {
-
-    @Composable
-    override operator fun invoke(direction: Direction, onFinished: () -> Unit, content: @Composable (Modifier) -> Unit) {
-        val animationState = remember(direction) { AnimationState(initialValue = if (direction == Direction.IDLE) 0F else 1F) }
-
-        LaunchedEffect(animationState) {
-            animationState.animateTo(
-                targetValue = 0F,
-                animationSpec = animationSpec,
-                sequentialAnimation = !animationState.isFinished,
-            )
-
-            onFinished()
-        }
-
-        val factor =
-            when (direction) {
-                Direction.IDLE,
-                Direction.ENTER_FRONT -> animationState.value
-                Direction.EXIT_FRONT -> 1F - animationState.value
-                Direction.ENTER_BACK -> -animationState.value
-                Direction.EXIT_BACK -> animationState.value - 1F
-            }
-
-        frame(factor, direction, content)
-    }
-}
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction.kt
deleted file mode 100644
index dbec9f20..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Direction.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-/**
- * Represents a direction in which child widgets are animated.
- */
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.Direction instead")
-@ExperimentalDecomposeApi
-enum class Direction {
-
-    /**
-     * The child is not being animated.
-     */
-    IDLE,
-
-    /**
-     * The child is entering from the front (push).
-     */
-    ENTER_FRONT,
-
-    /**
-     * The child is exiting to the front (pop).
-     */
-    EXIT_FRONT,
-
-    /**
-     * The child is entering from the back (move from the backstack).
-     */
-    ENTER_BACK,
-
-    /**
-     * The child is exiting to the back (move to the backstack).
-     */
-    EXIT_BACK,
-}
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/EmptyChildAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/EmptyChildAnimation.kt
deleted file mode 100644
index 7e199290..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/EmptyChildAnimation.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.foundation.layout.Box
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-@ExperimentalDecomposeApi
-internal fun <C : Any, T : Any> emptyChildAnimation(): ChildAnimation<C, T> =
-    ChildAnimation { routerState, modifier, childContent ->
-        Box(modifier = modifier) {
-            childContent(routerState.activeChild)
-        }
-    }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Fade.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Fade.kt
deleted file mode 100644
index eb8b95be..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Fade.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import kotlin.math.abs
-
-/**
- * A simple fading animation. Appearing children's `alpha` is animated from 0.0 to 1.0.
- * Disappearing children's `alpha` is animated from 1.0 to 0.0.
- */
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade instead")
-@ExperimentalDecomposeApi
-fun fade(animationSpec: FiniteAnimationSpec<Float> = tween()): ChildAnimator =
-    childAnimator(animationSpec = animationSpec) { factor, _, content ->
-        content(Modifier.alpha(1F - abs(factor)))
-    }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Scale.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Scale.kt
deleted file mode 100644
index c5e47c51..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Scale.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.scale
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-/**
- * A simple scaling animation. Front (above) children are scaling from [frontFactor] to 1.0.
- * Back (below) children are scaling from 1.0 to [backFactor].
- */
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.scale instead")
-@ExperimentalDecomposeApi
-fun scale(
-    animationSpec: FiniteAnimationSpec<Float> = tween(),
-    frontFactor: Float = 1.15F,
-    backFactor: Float = 0.95F,
-): ChildAnimator =
-    childAnimator(animationSpec = animationSpec) { factor, _, content ->
-        content(
-            Modifier.scale(
-                if (factor >= 0F) {
-                    factor * (frontFactor - 1F) + 1F
-                } else {
-                    factor * (1F - backFactor) + 1F
-                }
-            )
-        )
-    }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Slide.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Slide.kt
deleted file mode 100644
index 55b10cc3..00000000
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/animation/child/Slide.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains.animation.child
-
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.tween
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.layout
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
-/**
- * A simple sliding animation. Children enter from one side and exit to another side.
- */
-@Deprecated("Use com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.slide instead")
-@ExperimentalDecomposeApi
-fun slide(animationSpec: FiniteAnimationSpec<Float> = tween()): ChildAnimator =
-    childAnimator(animationSpec = animationSpec) { factor, _, content ->
-        content(Modifier.offsetXFactor(factor = factor))
-    }
-
-private fun Modifier.offsetXFactor(factor: Float): Modifier =
-    layout { measurable, constraints ->
-        val placeable = measurable.measure(constraints)
-
-        layout(placeable.width, placeable.height) {
-            placeable.placeRelative(x = (placeable.width.toFloat() * factor).toInt(), y = 0)
-        }
-    }
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/Children.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/Children.kt
index 7e2dcfe0..e60afdcc 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/Children.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/Children.kt
@@ -19,14 +19,16 @@ import com.arkivanov.decompose.value.Value
 fun <C : Any, T : Any> Children(
     stack: ChildStack<C, T>,
     modifier: Modifier = Modifier,
-    animation: StackAnimation<C, T> = emptyStackAnimation(),
+    animation: StackAnimation<C, T>? = null,
     content: @Composable (child: Child.Created<C, T>) -> Unit,
 ) {
     val holder = rememberSaveableStateHolder()
 
     holder.retainStates(stack.getConfigurations())
 
-    animation(stack = stack, modifier = modifier) { child ->
+    val anim = animation ?: emptyStackAnimation()
+
+    anim(stack = stack, modifier = modifier) { child ->
         holder.SaveableStateProvider(child.configuration.key()) {
             content(child)
         }
@@ -38,7 +40,7 @@ fun <C : Any, T : Any> Children(
 fun <C : Any, T : Any> Children(
     stack: Value<ChildStack<C, T>>,
     modifier: Modifier = Modifier,
-    animation: StackAnimation<C, T> = emptyStackAnimation(),
+    animation: StackAnimation<C, T>? = null,
     content: @Composable (child: Child.Created<C, T>) -> Unit,
 ) {
     val state = stack.subscribeAsState()
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimation.kt
index 81c10937..2b207163 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimation.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimation.kt
@@ -4,8 +4,10 @@ package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
+import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -18,7 +20,7 @@ import com.arkivanov.decompose.router.stack.ChildStack
 
 @ExperimentalDecomposeApi
 internal class DefaultStackAnimation<C : Any, T : Any>(
-    private val selector: (child: Child.Created<C, T>, direction: Direction) -> StackAnimator
+    private val selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
 ) : StackAnimation<C, T> {
 
     @Composable
@@ -34,27 +36,20 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
 
         Box(modifier = modifier) {
             items.forEach { (configuration, item) ->
-                val (child, direction) = item
-
                 key(configuration) {
-                    val animator = remember(direction) { selector(child, direction) }
-
-                    animator(
-                        direction = direction,
-                        onFinished = {
-                            when (direction) {
-                                Direction.EXIT_FRONT,
-                                Direction.EXIT_BACK -> items = items - configuration
-                                Direction.ENTER_FRONT,
-                                Direction.ENTER_BACK -> items = items + (configuration to item.copy(direction = Direction.IDLE))
-                                Direction.IDLE -> Unit
-                            }
-                        }
-                    ) { modifier ->
-                        Box(modifier = modifier) {
-                            content(child)
-                        }
-                    }
+                    Item(
+                        item = item,
+                        onFinished = { direction ->
+                            items =
+                                when (direction) {
+                                    Direction.EXIT_FRONT,
+                                    Direction.EXIT_BACK -> items - configuration
+                                    Direction.ENTER_FRONT,
+                                    Direction.ENTER_BACK -> items + (configuration to AnimationItem.Single(item.child))
+                                }
+                        },
+                        content = content,
+                    )
                 }
             }
 
@@ -66,6 +61,54 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
         }
     }
 
+    @Composable
+    private fun Item(
+        item: AnimationItem<C, T>,
+        onFinished: (Direction) -> Unit,
+        content: @Composable (child: Child.Created<C, T>) -> Unit,
+    ) {
+        val childContent =
+            remember {
+                movableContentOf<Modifier> { modifier ->
+                    Box(modifier = modifier) {
+                        content(item.child)
+                    }
+                }
+            }
+
+        when (item) {
+            is AnimationItem.Single -> childContent(Modifier)
+            is AnimationItem.Pair -> ItemPair(item = item, onFinished = onFinished, content = childContent)
+        }
+    }
+
+    @Composable
+    private fun ItemPair(
+        item: AnimationItem.Pair<C, T>,
+        onFinished: (Direction) -> Unit,
+        content: @Composable (Modifier) -> Unit,
+    ) {
+        val direction = item.direction
+        val animator: StackAnimator? = remember(direction) { selector(item.child, item.otherChild, direction) }
+
+        if (animator == null) {
+            if (direction.isEnter) {
+                content(Modifier)
+            }
+
+            DisposableEffect(Unit) {
+                onFinished(direction)
+                onDispose {}
+            }
+        } else {
+            animator(
+                direction = direction,
+                onFinished = { onFinished(direction) },
+                content = content,
+            )
+        }
+    }
+
     @Composable
     private fun Overlay(modifier: Modifier) {
         Box(
@@ -86,25 +129,34 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
     private fun getAnimationItems(newPage: Page<C, T>, oldPage: Page<C, T>?): Map<C, AnimationItem<C, T>> =
         when {
             oldPage == null ->
-                listOf(AnimationItem(newPage.child, Direction.IDLE))
+                listOf(AnimationItem.Single(newPage.child))
 
             newPage.index >= oldPage.index ->
                 listOf(
-                    AnimationItem(oldPage.child, Direction.EXIT_BACK),
-                    AnimationItem(newPage.child, Direction.ENTER_FRONT),
+                    AnimationItem.Pair(oldPage.child, newPage.child, Direction.EXIT_BACK),
+                    AnimationItem.Pair(newPage.child, oldPage.child, Direction.ENTER_FRONT),
                 )
 
             else ->
                 listOf(
-                    AnimationItem(newPage.child, Direction.ENTER_BACK),
-                    AnimationItem(oldPage.child, Direction.EXIT_FRONT),
+                    AnimationItem.Pair(newPage.child, oldPage.child, Direction.ENTER_BACK),
+                    AnimationItem.Pair(oldPage.child, newPage.child, Direction.EXIT_FRONT),
                 )
         }.associateBy { it.child.configuration }
 
-    private data class AnimationItem<out C : Any, out T : Any>(
-        val child: Child.Created<C, T>,
-        val direction: Direction,
-    )
+    private sealed interface AnimationItem<out C : Any, out T : Any> {
+        val child: Child.Created<C, T>
+
+        data class Single<out C : Any, out T : Any>(
+            override val child: Child.Created<C, T>,
+        ) : AnimationItem<C, T>
+
+        data class Pair<out C : Any, out T : Any>(
+            override val child: Child.Created<C, T>,
+            val otherChild: Child.Created<C, T>,
+            val direction: Direction,
+        ) : AnimationItem<C, T>
+    }
 
     private class Page<out C : Any, out T : Any>(
         val child: Child.Created<C, T>,
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimator.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimator.kt
index ebe32cc9..b4b6ed1e 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimator.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/DefaultStackAnimator.kt
@@ -1,10 +1,6 @@
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.isFinished
-import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.*
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
@@ -19,7 +15,7 @@ internal class DefaultStackAnimator(
 
     @Composable
     override operator fun invoke(direction: Direction, onFinished: () -> Unit, content: @Composable (Modifier) -> Unit) {
-        val animationState = remember(direction) { AnimationState(initialValue = if (direction == Direction.IDLE) 0F else 1F) }
+        val animationState = remember(direction) { AnimationState(initialValue = 1F) }
 
         LaunchedEffect(animationState) {
             animationState.animateTo(
@@ -33,7 +29,6 @@ internal class DefaultStackAnimator(
 
         val factor =
             when (direction) {
-                Direction.IDLE,
                 Direction.ENTER_FRONT -> animationState.value
                 Direction.EXIT_FRONT -> 1F - animationState.value
                 Direction.ENTER_BACK -> -animationState.value
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction.kt
index e5d5cdb8..834049ab 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Direction.kt
@@ -8,11 +8,6 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi
 @ExperimentalDecomposeApi
 enum class Direction {
 
-    /**
-     * The child is not being animated.
-     */
-    IDLE,
-
     /**
      * The child is entering from the front (push).
      */
@@ -33,3 +28,31 @@ enum class Direction {
      */
     EXIT_BACK,
 }
+
+@ExperimentalDecomposeApi
+val Direction.isEnter: Boolean
+    get() =
+        when (this) {
+            Direction.ENTER_FRONT,
+            Direction.ENTER_BACK -> true
+            Direction.EXIT_FRONT,
+            Direction.EXIT_BACK -> false
+        }
+
+@ExperimentalDecomposeApi
+val Direction.isExit: Boolean
+    get() = !isEnter
+
+@ExperimentalDecomposeApi
+val Direction.isFront: Boolean
+    get() =
+        when (this) {
+            Direction.ENTER_FRONT,
+            Direction.EXIT_FRONT -> true
+            Direction.ENTER_BACK,
+            Direction.EXIT_BACK -> false
+        }
+
+@ExperimentalDecomposeApi
+val Direction.isBack: Boolean
+    get() = !isFront
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation.kt
index 6d4bc8b7..97b9eb72 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation.kt
@@ -2,6 +2,7 @@
 
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import com.arkivanov.decompose.Child
@@ -49,11 +50,11 @@ inline fun <C : Any, T : Any> StackAnimation(
 /**
  * Creates an implementation of [StackAnimation] that allows different [StackAnimator]s.
  *
- * @param selector provides a [StackAnimator] for [Child] and [Direction].
+ * @param selector provides a [StackAnimator] for current [Child], other [Child] and [Direction].
  */
 @ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(
-    selector: (child: Child.Created<C, T>, direction: Direction) -> StackAnimator,
+    selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
 ): StackAnimation<C, T> =
     DefaultStackAnimation(selector)
 
@@ -64,4 +65,6 @@ fun <C : Any, T : Any> stackAnimation(
  */
 @ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(animator: StackAnimator = fade()): StackAnimation<C, T> =
-    stackAnimation { _, _ -> animator }
+    stackAnimation { _, _, _ -> animator }
+
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 6836aeb6..2cb9a685 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -26,6 +26,7 @@ nav:
     - State preservation: component/state-preservation.md
     - Instance retaining: component/instance-retaining.md
     - Back button: component/back-button.md
+    - Scoping jobs and subscriptions: component/scopes.md
     - Custom ComponentContext: component/custom-component-context.md
 
   - Child Stack:
diff --git a/sample/app-android/build.gradle.kts b/sample/app-android/build.gradle.kts
index b4d0a7c3..d632ef7e 100644
--- a/sample/app-android/build.gradle.kts
+++ b/sample/app-android/build.gradle.kts
@@ -36,4 +36,7 @@ dependencies {
     implementation(deps.androidx.lifecycle.lifecycleCommonJava8)
     implementation(deps.androidx.activity.activityCompose)
     implementation(deps.android.material.material)
+
+    // Temporary dependency until stable release
+    implementation("androidx.activity:activity-ktx:1.6.0-alpha05")
 }
diff --git a/sample/app-android/src/main/AndroidManifest.xml b/sample/app-android/src/main/AndroidManifest.xml
index a5dcfbc8..d6b2c0ba 100644
--- a/sample/app-android/src/main/AndroidManifest.xml
+++ b/sample/app-android/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
 
     <application
         android:allowBackup="true"
+        android:enableOnBackInvokedCallback="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
diff --git a/sample/app-android/src/main/java/com/arkivanov/sample/app/MainActivity.kt b/sample/app-android/src/main/java/com/arkivanov/sample/app/MainActivity.kt
index 18c9a4e9..087e785c 100644
--- a/sample/app-android/src/main/java/com/arkivanov/sample/app/MainActivity.kt
+++ b/sample/app-android/src/main/java/com/arkivanov/sample/app/MainActivity.kt
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Surface
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
 import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.defaultComponentContext
 import com.arkivanov.decompose.extensions.android.DefaultViewContext
@@ -26,8 +25,6 @@ class MainActivity : AppCompatActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        LocalContext
-
         val root =
             RootComponent(
                 componentContext = defaultComponentContext(),
diff --git a/sample/app-desktop/src/jvmMain/kotlin/com/arkivanov/sample/app/Main.kt b/sample/app-desktop/src/jvmMain/kotlin/com/arkivanov/sample/app/Main.kt
index b4ffabc2..08ede969 100644
--- a/sample/app-desktop/src/jvmMain/kotlin/com/arkivanov/sample/app/Main.kt
+++ b/sample/app-desktop/src/jvmMain/kotlin/com/arkivanov/sample/app/Main.kt
@@ -12,13 +12,14 @@ import androidx.compose.ui.window.Window
 import androidx.compose.ui.window.application
 import androidx.compose.ui.window.rememberWindowState
 import com.arkivanov.decompose.DefaultComponentContext
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.extensions.compose.jetbrains.lifecycle.LifecycleController
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DefaultFeatureInstaller
 import com.arkivanov.sample.shared.root.RootComponent
 import com.arkivanov.sample.shared.root.RootContent
 
-@OptIn(ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalDecomposeApi::class)
 fun main() {
     val lifecycle = LifecycleRegistry()
 
diff --git a/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt b/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt
index f8e48c4e..70109511 100644
--- a/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt
+++ b/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt
@@ -1,6 +1,7 @@
 package com.arkivanov.sample.app
 
 import com.arkivanov.decompose.DefaultComponentContext
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.stack.webhistory.DefaultWebHistoryController
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.lifecycle.resume
@@ -14,6 +15,7 @@ import org.w3c.dom.Document
 import react.create
 import react.dom.client.createRoot
 
+@OptIn(ExperimentalDecomposeApi::class)
 fun main() {
     val lifecycle = LifecycleRegistry()
 
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootContent.kt
index 4c57e3af..c7c8fc4e 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootContent.kt
@@ -17,6 +17,12 @@ import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.Direction
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimator
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.isEnter
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.slide
+import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation
 import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
 import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.MutableValue
@@ -39,6 +45,7 @@ fun RootContent(root: Root, modifier: Modifier = Modifier) {
         Children(
             stack = childStack,
             modifier = Modifier.weight(weight = 1F),
+            animation = tabAnimation(),
         ) {
             when (val child = it.instance) {
                 is CountersChild -> CountersContent(component = child.component, modifier = Modifier.fillMaxSize())
@@ -87,6 +94,43 @@ fun RootContent(root: Root, modifier: Modifier = Modifier) {
     }
 }
 
+@OptIn(ExperimentalDecomposeApi::class)
+@Composable
+private fun tabAnimation(): StackAnimation<Any, Root.Child> =
+    stackAnimation { child, otherChild, direction ->
+        val index = child.instance.index
+        val otherIndex = otherChild.instance.index
+        val anim = slide()
+        if ((index > otherIndex) == direction.isEnter) anim else anim.flipSide()
+    }
+
+private val Root.Child.index: Int
+    get() =
+        when (this) {
+            is CountersChild -> 0
+            is MultiPaneChild -> 1
+            is DynamicFeaturesChild -> 2
+        }
+
+@OptIn(ExperimentalDecomposeApi::class)
+private fun StackAnimator.flipSide(): StackAnimator =
+    StackAnimator { direction, onFinished, content ->
+        invoke(
+            direction = direction.flipSide(),
+            onFinished = onFinished,
+            content = content,
+        )
+    }
+
+@Suppress("OPT_IN_USAGE")
+private fun Direction.flipSide(): Direction =
+    when (this) {
+        Direction.ENTER_FRONT -> Direction.ENTER_BACK
+        Direction.EXIT_FRONT -> Direction.EXIT_BACK
+        Direction.ENTER_BACK -> Direction.ENTER_FRONT
+        Direction.EXIT_BACK -> Direction.EXIT_FRONT
+    }
+
 @Preview
 @Composable
 internal fun RootContentPreview() {
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt
index fc49953d..6e95b801 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt
@@ -5,6 +5,7 @@ import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.MutableValue
 import com.arkivanov.decompose.value.Value
 import com.arkivanov.decompose.value.reduce
+import com.arkivanov.essenty.backhandler.BackCallback
 import com.arkivanov.sample.shared.multipane.MultiPane.DetailsChild
 import com.arkivanov.sample.shared.multipane.MultiPane.ListChild
 import com.arkivanov.sample.shared.multipane.MultiPane.Model
@@ -43,21 +44,23 @@ internal class MultiPaneComponent(
 
     override val detailsChildStack: Value<ChildStack<*, DetailsChild>> = detailsRouter.stack
 
+    private val backCallback = BackCallback(isEnabled = false, onBack = ::closeDetailsAndShowList)
+
     init {
-        backPressedHandler.register {
-            if (isMultiPaneMode() || !detailsRouter.isShown()) {
-                false
-            } else {
-                closeDetailsAndShowList()
-                true
-            }
-        }
+        backHandler.register(backCallback)
+
+        _models.subscribe { updateBackCallback() }
 
         detailsRouter.stack.subscribe {
+            updateBackCallback()
             selectedArticleIdSubject.onNext(it.active.configuration.getArticleId())
         }
     }
 
+    private fun updateBackCallback() {
+        backCallback.isEnabled = !isMultiPaneMode() && detailsRouter.isShown()
+    }
+
     private fun closeDetailsAndShowList() {
         listRouter.show()
         detailsRouter.closeArticle()
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt
index 4dc9737b..1a2e4bc7 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt
@@ -11,7 +11,7 @@ internal class DefaultArticleDatabase : ArticleDatabase {
             ArticleEntity(
                 id = index.toLong() + 1L,
                 title = generate(count = Random.nextInt(3, 7), minWordLength = 3)
-                    .joinToString(separator = " ", transform = String::capitalize),
+                    .joinToString(separator = " ") { it.replaceFirstChar(Char::uppercase) },
                 text = List(50) { generateSentence() }
                     .joinToString(separator = " ")
             )
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt
index 1f19dda0..422b9ce0 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt
@@ -17,7 +17,7 @@ internal object LorenIpsumGenerator {
         generate(count = Random.nextInt(1, 15))
             .joinToString(
                 separator = " ",
-                prefix = words.random().capitalize(),
+                prefix = words.random().replaceFirstChar(Char::uppercase),
                 postfix = "."
             )
 
diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Utils.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Utils.kt
index 7a1aa14e..e5d68cf0 100644
--- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Utils.kt
+++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Utils.kt
@@ -1,7 +1,6 @@
 package com.arkivanov.sample.shared
 
 import com.arkivanov.decompose.value.Value
-import com.arkivanov.decompose.value.ValueObserver
 import react.ChildrenBuilder
 import react.FC
 import react.StateInstance
@@ -14,7 +13,7 @@ internal fun <T : Any> Value<T>.useAsState(): StateInstance<T> {
     val (_, set) = state
 
     useEffectOnce {
-        val observer: ValueObserver<T> = { set(it) }
+        val observer: (T) -> Unit = { set(it) }
         subscribe(observer)
         cleanup { unsubscribe(observer) }
     }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index c8d29652..82dea982 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -18,7 +18,7 @@ pluginManagement {
     resolutionStrategy {
         eachPlugin {
             if (requested.id.toString() == "com.arkivanov.gradle.setup") {
-                useModule("com.github.arkivanov:gradle-setup-plugin:715d466a74")
+                useModule("com.github.arkivanov:gradle-setup-plugin:f5e40dcc04")
             }
         }
     }


Test patch:
diff --git a/decompose/src/androidTest/kotlin/com/arkivanov/decompose/statekeeper/ParcelableStub.kt b/decompose/src/androidTest/kotlin/com/arkivanov/decompose/statekeeper/ParcelableStub.kt
index 12c0f2ed..65e0515c 100644
--- a/decompose/src/androidTest/kotlin/com/arkivanov/decompose/statekeeper/ParcelableStub.kt
+++ b/decompose/src/androidTest/kotlin/com/arkivanov/decompose/statekeeper/ParcelableStub.kt
@@ -7,7 +7,7 @@ actual class ParcelableStub actual constructor() : Parcelable {
 
     override fun describeContents(): Int = 0
 
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
+    override fun writeToParcel(dest: Parcel, flags: Int) {
     }
 
     companion object CREATOR : android.os.Parcelable.Creator<ParcelableStub> {
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextTest.kt
index d803c00e..98bba269 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/ChildContextTest.kt
@@ -3,7 +3,8 @@ package com.arkivanov.decompose
 import com.arkivanov.decompose.router.TestInstance
 import com.arkivanov.decompose.statekeeper.ParcelableStub
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.backhandler.BackDispatcher
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.instancekeeper.getOrCreate
 import com.arkivanov.essenty.lifecycle.Lifecycle
@@ -210,38 +211,48 @@ class ChildContextTest {
     }
 
     @Test
-    fun GIVEN_child_BackPressedHandler_returns_true_WHEN_backPressedHandler_onBackPressed_THEN_returns_true() {
+    fun GIVEN_enabled_child_BackHandler_registered_WHEN_backHandler_back_THEN_returns_true() {
         val context = TestContext()
         val childContext = context.childContext(key = "key")
-        childContext.backPressedHandler.register { true }
+        childContext.backHandler.register(BackCallback(isEnabled = true, onBack = {}))
 
-        val result = context.backPressedHandler.onBackPressed()
+        val result = context.backHandler.back()
 
         assertTrue(result)
     }
 
     @Test
-    fun GIVEN_child_BackPressedHandler_returns_false_WHEN_backPressedHandler_onBackPressed_THEN_returns_false() {
+    fun GIVEN_disabled_child_BackHandler_registered_WHEN_backHandler_back_THEN_returns_false() {
         val context = TestContext()
         val childContext = context.childContext(key = "key")
-        childContext.backPressedHandler.register { false }
+        childContext.backHandler.register(BackCallback(isEnabled = false, onBack = {}))
 
-        val result = context.backPressedHandler.onBackPressed()
+        val result = context.backHandler.back()
 
         assertFalse(result)
     }
 
     @Test
-    fun WHEN_child_lifecycle_destroyed_THEN_child_unregistered_from_BackPressedHandler() {
+    fun GIVEN_child_BackHandler_not_registered_WHEN_backHandler_back_THEN_returns_false() {
+        val context = TestContext()
+        context.childContext(key = "key")
+
+        val result = context.backHandler.back()
+
+        assertFalse(result)
+    }
+
+    @Test
+    fun GIVEN_enabled_child_BackHandler_registered_and_lifecycle_destroyed_WHEN_backHandler_back_THEN_returns_false() {
         val context = TestContext()
         val childLifecycle = LifecycleRegistry()
         val childContext = context.childContext(key = "key", lifecycle = childLifecycle)
-        childContext.backPressedHandler.register { true }
+        childContext.backHandler.register(BackCallback(isEnabled = true, onBack = {}))
         childLifecycle.resume()
 
         childLifecycle.destroy()
 
-        assertFalse(context.backPressedHandler.onBackPressed())
+        assertFalse(context.backHandler.back())
     }
 
     private class TestContext(
@@ -250,6 +261,6 @@ class ChildContextTest {
     ) : ComponentContext {
         override val lifecycle: LifecycleRegistry = LifecycleRegistry()
         override val stateKeeper: TestStateKeeperDispatcher = TestStateKeeperDispatcher(savedState)
-        override val backPressedHandler: BackPressedDispatcher = BackPressedDispatcher()
+        override val backHandler: BackDispatcher = BackDispatcher()
     }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandlerTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandlerTest.kt
new file mode 100644
index 00000000..b6caa566
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/DefaultChildBackHandlerTest.kt
@@ -0,0 +1,177 @@
+package com.arkivanov.decompose.backhandler
+
+import com.arkivanov.essenty.backhandler.BackCallback
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@Suppress("TestFunctionName")
+class DefaultChildBackHandlerTest {
+
+    private val parent = TestBackHandler()
+    private val handler = DefaultChildBackHandler(parent)
+
+    @Test
+    fun WHEN_created_THEN_not_registered_in_parent() {
+        assertEquals(0, parent.size)
+    }
+
+    @Test
+    fun GIVEN_no_callbacks_registered_WHEN_start_THEN_registered_in_parent() {
+        handler.start()
+
+        assertEquals(1, parent.size)
+    }
+
+    @Test
+    fun GIVEN_started_and_no_callbacks_registered_WHEN_stop_THEN_removed_from_parent() {
+        handler.start()
+
+        handler.stop()
+
+        assertEquals(0, parent.size)
+    }
+
+    @Test
+    fun GIVEN_enabled_callback_registered_WHEN_start_THEN_registered_in_parent() {
+        handler.register(callback(isEnabled = true))
+
+        handler.start()
+
+        assertEquals(1, parent.size)
+    }
+
+    @Test
+    fun GIVEN_disabled_callback_registered_WHEN_start_THEN_registered_in_parent() {
+        handler.register(callback(isEnabled = false))
+
+        handler.start()
+
+        assertEquals(1, parent.size)
+    }
+
+    @Test
+    fun GIVEN_callback_registered_and_started_WHEN_stop_THEN_removed_from_parent() {
+        handler.register(callback())
+        handler.start()
+
+        handler.stop()
+
+        assertEquals(0, parent.size)
+    }
+
+    @Test
+    fun GIVEN_not_started_WHEN_callback_registered_THEN_not_registered_in_parent() {
+        handler.register(callback())
+
+        assertEquals(0, parent.size)
+    }
+
+    @Test
+    fun WHEN_created_THEN_parent_disabled() {
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_not_started_WHEN_enabled_callback_registered_THEN_parent_disabled() {
+        handler.register(callback(isEnabled = true))
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_not_started_WHEN_disabled_callback_registered_THEN_parent_disabled() {
+        handler.register(callback(isEnabled = false))
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_not_started_and_two_enabled_callbacks_registered_WHEN_one_callback_disabled_THEN_parent_disabled() {
+        val callback1 = callback(isEnabled = true)
+        handler.register(callback1)
+        handler.register(callback(isEnabled = true))
+
+        callback1.isEnabled = false
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_not_started_and_two_enabled_callbacks_registered_WHEN_all_callbacks_disabled_THEN_parent_disabled() {
+        val callback1 = callback(isEnabled = true)
+        handler.register(callback1)
+        handler.register(callback(isEnabled = true))
+
+        callback1.isEnabled = false
+        callback1.isEnabled = false
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_no_callbacks_registered_WHEN_start_THEN_parent_disabled() {
+        handler.start()
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_started_WHEN_enabled_callback_registered_THEN_parent_enabled() {
+        handler.start()
+
+        handler.register(callback(isEnabled = true))
+
+        assertTrue(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_started_WHEN_disabled_callback_registered_THEN_parent_disabled() {
+        handler.start()
+
+        handler.register(callback(isEnabled = false))
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_started_and_one_enabled_callback_registered_WHEN_callback_disabled_THEN_parent_disabled() {
+        handler.start()
+        val callback = callback(isEnabled = true)
+        handler.register(callback)
+
+        callback.isEnabled = false
+
+        assertFalse(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_started_and_two_enabled_callbacks_registered_WHEN_one_callback_disabled_THEN_parent_enabled() {
+        handler.start()
+        val callback1 = callback(isEnabled = true)
+        handler.register(callback1)
+        handler.register(callback(isEnabled = true))
+
+        callback1.isEnabled = false
+
+        assertTrue(parent.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_started_and_two_enabled_callbacks_registered_WHEN_all_callbacks_disabled_THEN_parent_disabled() {
+        handler.start()
+        val callback1 = callback(isEnabled = true)
+        val callback2 = callback(isEnabled = true)
+        handler.register(callback1)
+        handler.register(callback2)
+
+        callback1.isEnabled = false
+        callback2.isEnabled = false
+
+        assertFalse(parent.isEnabled)
+    }
+
+    private fun callback(isEnabled: Boolean = true, onBack: () -> Unit = {}): BackCallback =
+        BackCallback(isEnabled = isEnabled, onBack = onBack)
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackHandler.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackHandler.kt
new file mode 100644
index 00000000..5ecb1d42
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackHandler.kt
@@ -0,0 +1,28 @@
+package com.arkivanov.decompose.backhandler
+
+import com.arkivanov.decompose.ensureNeverFrozen
+import com.arkivanov.essenty.backhandler.BackCallback
+import com.arkivanov.essenty.backhandler.BackHandler
+
+internal class TestBackHandler : BackHandler {
+
+    init {
+        ensureNeverFrozen()
+    }
+
+    private var set = emptySet<BackCallback>()
+    val size: Int get() = set.size
+    val isEnabled: Boolean get() = set.any(BackCallback::isEnabled)
+
+    override fun register(callback: BackCallback) {
+        check(callback !in set) { "Callback is already registered" }
+
+        this.set += callback
+    }
+
+    override fun unregister(callback: BackCallback) {
+        check(callback in set) { "Callback is not registered" }
+
+        this.set -= callback
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt
new file mode 100644
index 00000000..359ad0ff
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt
@@ -0,0 +1,29 @@
+package com.arkivanov.decompose.backhandler
+
+import com.arkivanov.decompose.ensureNeverFrozen
+import com.arkivanov.essenty.backhandler.BackCallback
+
+class TestChildBackHandler(var isStarted: Boolean = false) : ChildBackHandler {
+
+    init {
+        ensureNeverFrozen()
+    }
+
+    override fun start() {
+        check(!isStarted)
+        isStarted = true
+    }
+
+    override fun stop() {
+        check(isStarted)
+        isStarted = false
+    }
+
+    override fun register(callback: BackCallback) {
+        TODO("Not yet implemented")
+    }
+
+    override fun unregister(callback: BackCallback) {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterBringToFrontTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterBringToFrontTest.kt
deleted file mode 100644
index 247761b6..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterBringToFrontTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.arkivanov.decompose.router
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-@Suppress("TestFunctionName")
-class RouterBringToFrontTest {
-
-    @Test
-    fun WHEN_does_not_contain_THEN_new_configuration_pushed() {
-        val router = TestRouter(listOf(Config.A, Config.B))
-
-        router.bringToFront(Config.C(0))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(0)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_same_value_at_start_THEN_existing_configuration_moved_to_end() {
-        val router = TestRouter(listOf(Config.C(0), Config.A, Config.B))
-
-        router.bringToFront(Config.C(0))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(0)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_same_value_in_the_middle_THEN_existing_configuration_moved_to_end() {
-        val router = TestRouter(listOf(Config.A, Config.C(0), Config.B))
-
-        router.bringToFront(Config.C(0))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(0)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_same_value_at_the_end_THEN_existing_configuration_moved_to_end() {
-        val router = TestRouter(listOf(Config.A, Config.B, Config.C(0)))
-
-        router.bringToFront(Config.C(0))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(0)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_different_value_at_start_THEN_removed_and_new_added_to_end() {
-        val router = TestRouter(listOf(Config.C(0), Config.A, Config.B))
-
-        router.bringToFront(Config.C(1))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(1)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_different_value_in_the_middle_THEN_removed_and_new_added_to_end() {
-        val router = TestRouter(listOf(Config.A, Config.C(0), Config.B))
-
-        router.bringToFront(Config.C(1))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(1)), router.stack)
-    }
-
-    @Test
-    fun WHEN_exists_with_different_value_at_the_end_THEN_removed_and_new_added_to_end() {
-        val router = TestRouter(listOf(Config.A, Config.B, Config.C(0)))
-
-        router.bringToFront(Config.C(1))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(1)), router.stack)
-    }
-
-    @Test
-    fun WHEN_multiple_exist_THEN_all_removed_and_new_added_to_end() {
-        val router = TestRouter(listOf(Config.C(0), Config.A, Config.C(1), Config.B))
-
-        router.bringToFront(Config.C(1))
-
-        assertEquals(listOf(Config.A, Config.B, Config.C(1)), router.stack)
-    }
-
-    private sealed class Config {
-        object A : Config()
-        object B : Config()
-        data class C(val value: Int) : Config()
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopTest.kt
deleted file mode 100644
index 819921ec..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.arkivanov.decompose.router
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class RouterPopTest {
-
-    @Test
-    fun GIVEN_stack_size_2_WHEN_pop_THEN_popped() {
-        val router = TestRouter(listOf(Config.A, Config.B))
-
-        router.pop()
-
-        assertEquals(listOf(Config.A), router.stack)
-    }
-
-    @Test
-    fun GIVEN_stack_size_2_WHEN_pop_THEN_onComplete_success() {
-        val router = TestRouter(listOf(Config.A, Config.B))
-        var isSuccess = false
-
-        router.pop { isSuccess = it }
-
-        assertTrue(isSuccess)
-    }
-
-    @Test
-    fun GIVEN_stack_size_1_WHEN_pop_THEN_not_popped() {
-        val router = TestRouter(listOf(Config.A))
-
-        router.pop()
-
-        assertEquals(listOf(Config.A), router.stack)
-    }
-
-
-    @Test
-    fun GIVEN_stack_size_1_WHEN_pop_THEN_onComplete_not_success() {
-        val router = TestRouter(listOf(Config.A))
-        var isSuccess = true
-
-        router.pop { isSuccess = it }
-
-        assertFalse(isSuccess)
-    }
-
-    private sealed class Config {
-        object A : Config()
-        object B : Config()
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopWhileTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopWhileTest.kt
deleted file mode 100644
index edd55965..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPopWhileTest.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.arkivanov.decompose.router
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class RouterPopWhileTest {
-
-    @Test
-    fun WHEN_popWhile_THEN_popped() {
-        val router = TestRouter(listOf(1, 2, 3, 4))
-
-        router.popWhile { it != 2 }
-
-        assertEquals(listOf(1, 2), router.stack)
-    }
-
-    @Test
-    fun WHEN_popWhile_THEN_onComplete_called() {
-        val router = TestRouter(listOf(1, 2, 3, 4))
-        var isCalled = false
-
-        router.popWhile(predicate = { it != 2 }, onComplete = { isCalled = true })
-
-        assertTrue(isCalled)
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPushTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPushTest.kt
deleted file mode 100644
index d29a8a61..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterPushTest.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.arkivanov.decompose.router
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class RouterPushTest {
-
-    @Test
-    fun WHEN_push_THEN_pushed() {
-        val router = TestRouter(listOf(1, 2))
-
-        router.push(3)
-
-        assertEquals(listOf(1, 2, 3), router.stack)
-    }
-
-    @Test
-    fun WHEN_push_THEN_onComplete_called() {
-        val router = TestRouter(listOf(1, 2))
-        var isCalled = false
-
-        router.push(3) { isCalled = true }
-
-        assertTrue(isCalled)
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterReplaceCurrentTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterReplaceCurrentTest.kt
deleted file mode 100644
index 6008ae9f..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterReplaceCurrentTest.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.arkivanov.decompose.router
-
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class RouterReplaceCurrentTest {
-
-    @Test
-    fun WHEN_replaceCurrent_THEN_last_configuration_replaced() {
-        val router = TestRouter(listOf(1, 2, 3))
-
-        router.replaceCurrent(4)
-
-        assertEquals(listOf(1, 2, 4), router.stack)
-    }
-
-    @Test
-    fun WHEN_replaceCurrent_THEN_onComplete_called() {
-        val router = TestRouter(listOf(1, 2, 3))
-        var isCalled = false
-
-        router.replaceCurrent(4) { isCalled = true }
-
-        assertTrue(isCalled)
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterTest.kt
deleted file mode 100644
index 739b9449..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/RouterTest.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.lifecycle.LifecycleRegistry
-import com.arkivanov.essenty.parcelable.Parcelable
-import com.arkivanov.essenty.parcelable.Parcelize
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-
-@Suppress("TestFunctionName")
-class RouterTest {
-
-    @Test
-    fun WHEN_navigate_THEN_new_stack_applied() {
-        val config1 = Config()
-        val config2 = Config()
-        val config3 = Config()
-        val config4 = Config()
-        val router = router(initialStack = listOf(config1, config2, config3))
-
-        router.navigate { listOf(config1, config3, config4) }
-
-        assertEquals(listOf(config1, config3, config4), router.configurations)
-    }
-
-    @Test
-    fun WHEN_navigate_THEN_onComplete_called_synchronously() {
-        val config1 = Config()
-        val config2 = Config()
-        val config3 = Config()
-        val config4 = Config()
-        val router = router(initialStack = listOf(config1, config2, config3))
-
-        var resultNewStack: List<Config>? = null
-        var resultOldStack: List<Config>? = null
-        router.navigate(transformer = { listOf(config1, config3, config4) }) { newStack, oldStack ->
-            resultNewStack = newStack
-            resultOldStack = oldStack
-        }
-
-        assertEquals(listOf(config1, config3, config4), resultNewStack)
-        assertEquals(listOf(config1, config2, config3), resultOldStack)
-    }
-
-    @Test
-    fun WHEN_navigate_recursively_THEN_last_stack_applied() {
-        val config1 = Config()
-        val config2 = Config()
-        val config3 = Config()
-        val navigator = TestStackNavigator()
-        val router = router(initialStack = listOf(config1), navigator = navigator)
-
-        navigator.onNavigate =
-            { newConfigs ->
-                if (newConfigs == listOf(config2)) {
-                    router.navigate { listOf(config3) }
-                }
-            }
-
-        router.navigate { listOf(config2) }
-
-        assertEquals(listOf(config3), router.configurations)
-    }
-
-
-    @Test
-    fun WHEN_navigate_recursively_THEN_onComplete_not_called_synchronously() {
-        val config1 = Config()
-        val config2 = Config()
-        val config3 = Config()
-        val navigator = TestStackNavigator()
-        val router = router(initialStack = listOf(config1), navigator = navigator)
-
-        var isCalledSynchronously = false
-        navigator.onNavigate =
-            { newConfigs ->
-                if (newConfigs == listOf(config2)) {
-                    var isCalled = false
-                    router.navigate(transformer = { listOf(config3) }) { _, _ ->
-                        isCalled = true
-                    }
-                    isCalledSynchronously = isCalled
-                }
-            }
-
-        router.navigate { listOf(config2) }
-
-        assertFalse(isCalledSynchronously)
-    }
-
-
-    @Test
-    fun WHEN_navigate_recursively_THEN_onComplete_called() {
-        val config1 = Config()
-        val config2 = Config()
-        val config3 = Config()
-        val navigator = TestStackNavigator()
-        val router = router(initialStack = listOf(config1), navigator = navigator)
-
-        var resultNewStack: List<Config>? = null
-        var resultOldStack: List<Config>? = null
-        navigator.onNavigate =
-            { newConfigs ->
-                if (newConfigs == listOf(config2)) {
-                    router.navigate(transformer = { listOf(config3) }) { newStack, oldStack ->
-                        resultNewStack = newStack
-                        resultOldStack = oldStack
-                    }
-                }
-            }
-
-        router.navigate { listOf(config2) }
-
-        assertEquals(listOf(config3), resultNewStack)
-        assertEquals(listOf(config2), resultOldStack)
-    }
-
-    private fun router(initialStack: List<Config>, navigator: TestStackNavigator = TestStackNavigator()): Router<Config, Config> =
-        RouterImpl(
-            lifecycle = LifecycleRegistry(),
-            backPressedHandler = BackPressedDispatcher(),
-            popOnBackPressed = false,
-            stackHolder = TestStackHolder(routerStack(initialStack)),
-            navigator = navigator,
-        )
-
-    private companion object {
-        private fun routerStack(stack: List<Config>): RouterStack<Config, Config> =
-            RouterStack(
-                active = RouterEntry.Created(
-                    configuration = stack.last(),
-                    instance = stack.last(),
-                    lifecycleRegistry = LifecycleRegistry(),
-                    stateKeeperDispatcher = TestStateKeeperDispatcher(),
-                    instanceKeeperDispatcher = InstanceKeeperDispatcher(),
-                    backPressedDispatcher = BackPressedDispatcher()
-                ),
-                backStack = stack.dropLast(1).map {
-                    RouterEntry.Destroyed(configuration = it)
-                }
-            )
-
-        private val Router<Config, *>.configurations: List<Config>
-            get() = state.value.configurations
-
-        private val RouterState<Config, *>.configurations: List<Config>
-            get() = backStack.map { it.configuration } + activeChild.configuration
-    }
-
-    @Parcelize
-    private class Config : Parcelable
-
-    private class TestStackHolder(
-        override var stack: RouterStack<Config, Config>
-    ) : StackHolder<Config, Config>
-
-    private class TestStackNavigator : StackNavigator<Config, Config> {
-        var onNavigate: (newConfigs: List<Config>) -> Unit = {}
-
-        override fun navigate(
-            oldStack: RouterStack<Config, Config>,
-            transformer: (stack: List<Config>) -> List<Config>
-        ): RouterStack<Config, Config> {
-            val oldConfigs = oldStack.configurationStack
-            val newConfigs = transformer(oldConfigs)
-            onNavigate(newConfigs)
-
-            return routerStack(newConfigs)
-        }
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackHolderImplTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackHolderImplTest.kt
deleted file mode 100644
index 771daa2c..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackHolderImplTest.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.statekeeper.ParcelableStub
-import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.lifecycle.LifecycleRegistry
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import com.arkivanov.essenty.statekeeper.consume
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class StackHolderImplTest {
-
-    @Test
-    fun WHEN_recreated_THEN_instances_retained_in_active_component() {
-        val instanceKeeperDispatcher = InstanceKeeperDispatcher()
-        val stackSaver = stackSaver()
-        val oldHolder = stackHolder(instanceKeeperDispatcher = instanceKeeperDispatcher, stackSaver = stackSaver)
-        val instance = TestInstance()
-        oldHolder.stack.active.instanceKeeperDispatcher.put("key", instance)
-        val savedState = stackSaver.save()
-
-        val newHolder = stackHolder(instanceKeeperDispatcher = instanceKeeperDispatcher, stackSaver = stackSaver(savedState = savedState))
-
-        assertSame(instance, newHolder.stack.active.instanceKeeperDispatcher.get("key"))
-    }
-
-    @Test
-    fun WHEN_recreated_without_saved_state_THEN_previous_retained_instance_destroyed() {
-        val instanceKeeperDispatcher = InstanceKeeperDispatcher()
-        val oldHolder = stackHolder(instanceKeeperDispatcher = instanceKeeperDispatcher)
-        val instance = TestInstance()
-        oldHolder.stack.active.instanceKeeperDispatcher.put("key", instance)
-
-        stackHolder(instanceKeeperDispatcher = instanceKeeperDispatcher)
-
-        assertTrue(instance.isDestroyed)
-    }
-
-    @Test
-    fun GIVEN_created_WHEN_InstanceKeeper_destroyed_THEN_active_InstanceKeeper_destroyed() {
-        val instanceKeeperDispatcher = InstanceKeeperDispatcher()
-        val holder = stackHolder(instanceKeeperDispatcher = instanceKeeperDispatcher)
-        val instance = TestInstance()
-        holder.stack.active.instanceKeeperDispatcher.put("key", instance)
-
-        instanceKeeperDispatcher.destroy()
-
-        assertTrue(instance.isDestroyed)
-    }
-
-    @Test
-    fun WHEN_recreated_THEN_active_component_restored() {
-        val stackSaver = stackSaver()
-        val oldHolder = stackHolder(stackSaver = stackSaver)
-        val originalStack = oldHolder.stack
-        val savedState = stackSaver.save()
-
-        val newHolder = stackHolder(stackSaver = stackSaver(savedState = savedState))
-
-        assertEquals(originalStack.active.configuration, newHolder.stack.active.configuration)
-    }
-
-    @Test
-    fun WHEN_recreated_THEN_back_stack_restored() {
-        val stackSaver = stackSaver()
-        val oldHolder = stackHolder(stackSaver = stackSaver)
-        val originalStack = oldHolder.stack
-        val savedState = stackSaver.save()
-
-        val newHolder = stackHolder(stackSaver = stackSaver(savedState = savedState))
-
-        assertEquals(originalStack.backStack.map { it.configuration }, newHolder.stack.backStack.map { it.configuration })
-    }
-
-    @Test
-    fun WHEN_recreated_THEN_state_restored_for_active_component() {
-        val stackSaver = stackSaver()
-        val oldHolder = stackHolder(stackSaver = stackSaver)
-        val originalStack = oldHolder.stack
-        val savedComponentState = ParcelableStub()
-        originalStack.active.stateKeeperDispatcher.register("key") { savedComponentState }
-        val savedState = stackSaver.save()
-
-        val newHolder = stackHolder(stackSaver = stackSaver(savedState = savedState))
-
-        val restoredComponentState = newHolder.stack.active.stateKeeperDispatcher.consume<ParcelableStub>("key")
-        assertSame(savedComponentState, restoredComponentState)
-    }
-
-    @Test
-    fun WHEN_recreated_THEN_state_restored_for_back_stack() {
-        val stackSaver = stackSaver()
-        val oldHolder = stackHolder(stackSaver = stackSaver)
-        val savedState1 = ParcelableContainer()
-        val savedState2 = ParcelableContainer()
-
-        val originalStack =
-            RouterStack(
-                active = TestRouterEntryFactory.invoke(configuration = Config("3")),
-                backStack = listOf(
-                    RouterEntry.Destroyed(configuration = Config("1"), savedState = savedState1),
-                    RouterEntry.Destroyed(configuration = Config("2"), savedState = savedState2)
-                )
-            )
-
-        oldHolder.stack = originalStack
-        val savedState = stackSaver.save()
-
-        val newHolder = stackHolder(stackSaver = stackSaver(savedState = savedState))
-
-        assertEquals(listOf(savedState1, savedState2), newHolder.stack.backStack.map { it.savedState })
-    }
-
-    private fun stackHolder(
-        initialConfiguration: Config = Config("1"),
-        stackSaver: StackSaver<Config> = stackSaver(),
-        instanceKeeperDispatcher: InstanceKeeperDispatcher = InstanceKeeperDispatcher(),
-        routerEntryFactory: RouterEntryFactory<Config, Component> = TestRouterEntryFactory
-    ): StackHolderImpl<Config, Component> =
-        StackHolderImpl(
-            initialStack = { listOf(initialConfiguration) },
-            lifecycle = LifecycleRegistry(),
-            key = "key",
-            stackSaver = stackSaver,
-            instanceKeeper = instanceKeeperDispatcher,
-            routerEntryFactory = routerEntryFactory
-        )
-
-    private fun stackSaver(savedState: TestStackSaver.SavedState<Config>? = null): TestStackSaver<Config> =
-        TestStackSaver(
-            copyConfiguration = Config::copy,
-            savedState = savedState
-        )
-
-    private data class Config(val key: String)
-
-    private class Component
-
-    private object TestRouterEntryFactory : RouterEntryFactory<Config, Component> {
-        override fun invoke(
-            configuration: Config,
-            savedState: ParcelableContainer?,
-            instanceKeeperDispatcher: InstanceKeeperDispatcher?
-        ): RouterEntry.Created<Config, Component> {
-            val instanceKeeper = instanceKeeperDispatcher ?: InstanceKeeperDispatcher()
-            val stateKeeper = TestStateKeeperDispatcher(savedState)
-
-            return RouterEntry.Created(
-                configuration = configuration,
-                savedState = null,
-                instance = Component(),
-                lifecycleRegistry = LifecycleRegistry(),
-                stateKeeperDispatcher = stateKeeper,
-                instanceKeeperDispatcher = instanceKeeper,
-                backPressedDispatcher = BackPressedDispatcher()
-            )
-        }
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackNavigatorTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackNavigatorTest.kt
deleted file mode 100644
index 30f066c4..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/StackNavigatorTest.kt
+++ /dev/null
@@ -1,568 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.statekeeper.TestParcelableContainer
-import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
-import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
-import com.arkivanov.essenty.lifecycle.Lifecycle
-import com.arkivanov.essenty.lifecycle.LifecycleRegistry
-import com.arkivanov.essenty.lifecycle.create
-import com.arkivanov.essenty.lifecycle.resume
-import com.arkivanov.essenty.parcelable.ParcelableContainer
-import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
-
-@Suppress("TestFunctionName")
-class StackNavigatorTest {
-
-    private val navigator = StackNavigatorImpl(routerEntryFactory = TestRouterEntryFactory())
-
-    @Test
-    fun GIVEN_empty_back_stack_WHEN_push_THEN_active_entry_is_new() {
-        val newConfig = Config()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = emptyList()
-                ),
-                transformer = { it + newConfig }
-            )
-
-        assertSame(newConfig, newStack.active.configuration)
-    }
-
-    @Test
-    fun GIVEN_empty_back_stack_WHEN_push_THEN_back_stack_equals_old() {
-        val oldConfig = Config()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = oldConfig),
-                    backStack = emptyList()
-                ),
-                transformer = { it + Config() }
-            )
-
-        assertEquals(listOf(oldConfig), newStack.configurationBackStack)
-    }
-
-    @Test
-    fun GIVEN_not_empty_back_stack_WHEN_push_THEN_back_stack_equals_old_back_stack_plus_old_active() {
-        val oldConfig1 = Config()
-        val oldConfig2 = Config()
-        val oldConfig3 = Config()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = oldConfig3),
-                    backStack = listOf(createdEntry(configuration = oldConfig1), createdEntry(configuration = oldConfig2))
-                ),
-                transformer = { it + Config() }
-            )
-
-        assertEquals(listOf(oldConfig1, oldConfig2, oldConfig3), newStack.configurationBackStack)
-    }
-
-    @Test
-    fun GIVEN_empty_back_stack_WHEN_push_THEN_old_component_not_recreated() {
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = Config()),
-                backStack = emptyList()
-            )
-
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = { it + Config() })
-
-        assertSame(newStack.backStack[0].asCreated().instance, oldStack.active.instance)
-    }
-
-    @Test
-    fun GIVEN_not_empty_back_stack_WHEN_push_THEN_old_components_not_recreated() {
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = Config()),
-                backStack = listOf(createdEntry(configuration = Config()), createdEntry(configuration = Config()))
-            )
-
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = { it + Config() })
-
-        assertSame(oldStack.backStack[0].asCreated().instance, newStack.backStack[0].asCreated().instance)
-        assertSame(oldStack.backStack[1].asCreated().instance, newStack.backStack[1].asCreated().instance)
-        assertSame(oldStack.active.instance, newStack.backStack[2].asCreated().instance)
-    }
-
-    @Test
-    fun WHEN_push_THEN_state_saved_for_old_active_component() {
-        val stateKeeperDispatcher = TestStateKeeperDispatcher()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config(), stateKeeperDispatcher = stateKeeperDispatcher),
-                    backStack = emptyList()
-                ),
-                transformer = { it + Config() }
-            )
-
-        assertNotNull(stateKeeperDispatcher.lastSavedState)
-        assertSame(stateKeeperDispatcher.lastSavedState, newStack.backStack[0].savedState)
-    }
-
-    @Test
-    fun WHEN_push_THEN_new_active_component_resumed() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = emptyList()
-                ),
-                transformer = { it + Config() }
-            )
-
-        assertSame(Lifecycle.State.RESUMED, newStack.active.lifecycleRegistry.state)
-    }
-
-    @Test
-    fun WHEN_push_THEN_old_active_component_stopped() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = emptyList()
-                ),
-                transformer = { it + Config() }
-            )
-
-        assertSame(Lifecycle.State.CREATED, (newStack.backStack[0] as RouterEntry.Created).lifecycleRegistry.state)
-    }
-
-    @Test
-    fun WHEN_push_THEN_old_components_not_recreated() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = emptyList()
-                ),
-                transformer = { it + Config() }
-            )
-
-        newStack.backStack
-        assertSame(Lifecycle.State.CREATED, (newStack.backStack[0] as RouterEntry.Created).lifecycleRegistry.state)
-    }
-
-    @Test
-    fun GIVEN_back_stack_WHEN_pop_THEN_active_entry_is_back() {
-        val backConfig = Config()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = backConfig))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertSame(backConfig, newStack.active.configuration)
-    }
-
-    @Test
-    fun GIVEN_back_stack_with_one_entry_WHEN_pop_THEN_back_stack_is_empty() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = Config()))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertTrue(newStack.backStack.isEmpty())
-    }
-
-    @Test
-    fun GIVEN_back_stack_WHEN_pop_THEN_old_active_component_destroyed() {
-        val lifecycleRegistry = LifecycleRegistry().apply { resume() }
-
-        navigator.navigate(
-            oldStack = RouterStack(
-                active = activeEntry(configuration = Config(), lifecycleRegistry = lifecycleRegistry),
-                backStack = listOf(createdEntry(configuration = Config()))
-            ),
-            transformer = { it.dropLast(1) }
-        )
-
-        assertEquals(Lifecycle.State.DESTROYED, lifecycleRegistry.state)
-    }
-
-    @Test
-    fun GIVEN_back_stack_WHEN_pop_THEN_old_active_component_retained_instance_destroyed() {
-        val instanceKeeperDispatcher = InstanceKeeperDispatcher()
-        val instance = TestInstance()
-        instanceKeeperDispatcher.put("key", instance)
-
-        navigator.navigate(
-            oldStack = RouterStack(
-                active = activeEntry(configuration = Config(), instanceKeeperDispatcher = instanceKeeperDispatcher),
-                backStack = listOf(createdEntry(configuration = Config()))
-            ),
-            transformer = { it.dropLast(1) }
-        )
-
-        assertTrue(instance.isDestroyed)
-    }
-
-    @Test
-    fun GIVEN_back_stack_WHEN_pop_THEN_new_active_component_resumed() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = Config()))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertEquals(Lifecycle.State.RESUMED, newStack.active.lifecycleRegistry.state)
-    }
-
-    @Test
-    fun GIVEN_back_stack_WHEN_pop_THEN_old_components_not_recreated() {
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = Config()),
-                backStack = listOf(createdEntry(configuration = Config()), createdEntry(configuration = Config()))
-            )
-
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = { it.dropLast(1) })
-
-        assertSame(oldStack.backStack[0].asCreated().instance, newStack.backStack[0].asCreated().instance)
-        assertSame(oldStack.backStack[1].asCreated().instance, newStack.active.instance)
-    }
-
-    @Test
-    fun GIVEN_back_stack_with_created_entry_and_saved_state_WHEN_pop_THEN_new_active_component_without_saved_state() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = Config(), savedState = TestParcelableContainer()))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertNull(newStack.active.savedState)
-    }
-
-    @Test
-    fun GIVEN_back_stack_with_destroyed_entry_and_saved_state_WHEN_pop_THEN_state_restored_for_new_active_component() {
-        val savedState = TestParcelableContainer()
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(destroyedEntry(configuration = Config(), savedState = savedState))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertSame(savedState, (newStack.active.stateKeeperDispatcher as TestStateKeeperDispatcher).initialSavedState)
-    }
-
-    @Test
-    fun GIVEN_back_stack_with_destroyed_entry_WHEN_pop_THEN_new_active_component_without_saved_state() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(destroyedEntry(configuration = Config()))
-                ),
-                transformer = { it.dropLast(1) }
-            )
-
-        assertNull(newStack.active.savedState)
-    }
-
-    @Test
-    fun WHEN_navigate_to_different_stack_THEN_old_active_component_destroyed() {
-        val lifecycleRegistry = LifecycleRegistry().apply { resume() }
-
-        navigator.navigate(
-            oldStack = RouterStack(
-                active = activeEntry(configuration = Config(), lifecycleRegistry = lifecycleRegistry),
-                backStack = listOf(createdEntry(configuration = Config()))
-            ),
-            transformer = { listOf(Config(), Config()) }
-        )
-
-        assertEquals(Lifecycle.State.DESTROYED, lifecycleRegistry.state)
-    }
-
-    @Test
-    fun WHEN_navigate_to_different_stack_THEN_old_back_stack_components_destroyed() {
-        val lifecycleRegistry = LifecycleRegistry().apply { create() }
-
-        navigator.navigate(
-            oldStack = RouterStack(
-                active = activeEntry(configuration = Config()),
-                backStack = listOf(createdEntry(configuration = Config(), lifecycleRegistry = lifecycleRegistry))
-            ),
-            transformer = { listOf(Config(), Config()) }
-        )
-
-        assertEquals(Lifecycle.State.DESTROYED, lifecycleRegistry.state)
-    }
-
-    @Test
-    fun WHEN_navigate_to_different_stack_THEN_new_stack_contains_only_new_configurations() {
-        val lifecycleRegistry = LifecycleRegistry().apply { resume() }
-        val newConfigurationStack = listOf(Config(), Config())
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config(), lifecycleRegistry = lifecycleRegistry),
-                    backStack = listOf(createdEntry(configuration = Config()))
-                ),
-                transformer = { newConfigurationStack }
-            )
-
-        assertEquals(newConfigurationStack, newStack.configurationStack)
-    }
-
-    @Test
-    fun WHEN_navigate_to_different_stack_THEN_new_active_component_resumed() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = Config()))
-                ),
-                transformer = { listOf(Config(), Config()) }
-            )
-
-        assertEquals(Lifecycle.State.RESUMED, newStack.active.lifecycleRegistry.state)
-    }
-
-    @Test
-    fun WHEN_navigate_to_different_stack_THEN_new_back_stack_components_not_created() {
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(createdEntry(configuration = Config()))
-                ),
-                transformer = { listOf(Config(), Config()) }
-            )
-
-        assertTrue(newStack.backStack.all { it is RouterEntry.Destroyed })
-    }
-
-    @Test
-    fun WHEN_navigate_to_partially_different_stack_with_new_active_component_THEN_new_stack_contains_same_old_configurations_and_new_configurations() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-        val newConfigurationStack = listOf(Config(), sameConfig1, sameConfig2, Config(), Config())
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = Config()),
-                    backStack = listOf(
-                        createdEntry(configuration = Config()),
-                        createdEntry(configuration = sameConfig1),
-                        createdEntry(configuration = Config()),
-                        createdEntry(configuration = sameConfig2)
-                    )
-                ),
-                transformer = { newConfigurationStack }
-            )
-
-        assertEquals(newConfigurationStack, newStack.configurationStack)
-    }
-
-    @Test
-    fun WHEN_navigate_to_partially_different_stack_with_new_active_component_THEN_same_old_components_not_recreated() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = Config()),
-                backStack = listOf(
-                    createdEntry(configuration = Config()),
-                    createdEntry(configuration = sameConfig1),
-                    createdEntry(configuration = Config()),
-                    createdEntry(configuration = sameConfig2)
-                )
-            )
-
-        val newStack =
-            navigator.navigate(
-                oldStack = oldStack,
-                transformer = { listOf(Config(), sameConfig1, sameConfig2, Config(), Config()) }
-            )
-
-        assertSame(oldStack.backStack[1].asCreated().instance, newStack.backStack[1].asCreated().instance)
-        assertSame(oldStack.backStack[3].asCreated().instance, newStack.backStack[2].asCreated().instance)
-    }
-
-    @Test
-    fun WHEN_navigate_to_partially_different_stack_with_same_active_component_THEN_new_stack_contains_same_old_configurations_and_new_configurations() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-        val sameConfig3 = Config()
-        val newConfigurationStack = listOf(Config(), sameConfig1, sameConfig2, Config(), sameConfig3)
-
-        val newStack =
-            navigator.navigate(
-                oldStack = RouterStack(
-                    active = activeEntry(configuration = sameConfig3),
-                    backStack = listOf(
-                        createdEntry(configuration = Config()),
-                        createdEntry(configuration = sameConfig1),
-                        createdEntry(configuration = Config()),
-                        createdEntry(configuration = sameConfig2)
-                    )
-                ),
-                transformer = { newConfigurationStack }
-            )
-
-        assertEquals(newConfigurationStack, newStack.configurationStack)
-    }
-
-    @Test
-    fun WHEN_navigate_to_partially_different_stack_with_same_active_component_THEN_same_old_components_not_recreated() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-        val sameConfig3 = Config()
-
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = sameConfig3),
-                backStack = listOf(
-                    createdEntry(configuration = Config()),
-                    createdEntry(configuration = sameConfig1),
-                    createdEntry(configuration = Config()),
-                    createdEntry(configuration = sameConfig2)
-                )
-            )
-
-        val newStack =
-            navigator.navigate(
-                oldStack = oldStack,
-                transformer = { listOf(Config(), sameConfig1, sameConfig2, Config(), sameConfig3) }
-            )
-
-        assertSame(oldStack.backStack[1].asCreated().instance, newStack.backStack[1].asCreated().instance)
-        assertSame(oldStack.backStack[3].asCreated().instance, newStack.backStack[2].asCreated().instance)
-        assertSame(oldStack.active.instance, newStack.active.instance)
-    }
-
-    @Test
-    fun WHEN_navigate_to_equal_stack_THEN_stack_not_changed() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = sameConfig1),
-                backStack = listOf(createdEntry(configuration = sameConfig2))
-            )
-
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = { listOf(sameConfig2, sameConfig1) })
-
-        assertEquals(oldStack, newStack)
-    }
-
-    @Test
-    fun WHEN_navigate_to_same_stack_THEN_stack_not_changed() {
-        val sameConfig1 = Config()
-        val sameConfig2 = Config()
-
-        val oldStack =
-            RouterStack(
-                active = activeEntry(configuration = sameConfig1),
-                backStack = listOf(createdEntry(configuration = sameConfig2))
-            )
-
-        val newStack = navigator.navigate(oldStack = oldStack, transformer = { it })
-
-        assertEquals(oldStack, newStack)
-    }
-
-    private fun <C : Any, T : Any> RouterEntry<C, T>.asCreated(): RouterEntry.Created<C, T> =
-        this as RouterEntry.Created<C, T>
-
-    private companion object {
-        private fun activeEntry(
-            configuration: Config,
-            component: Component = Component(),
-            lifecycleRegistry: LifecycleRegistry = LifecycleRegistry().apply { resume() },
-            stateKeeperDispatcher: StateKeeperDispatcher = TestStateKeeperDispatcher(),
-            instanceKeeperDispatcher: InstanceKeeperDispatcher = InstanceKeeperDispatcher()
-        ): RouterEntry.Created<Config, Component> =
-            RouterEntry.Created(
-                configuration = configuration,
-                instance = component,
-                lifecycleRegistry = lifecycleRegistry,
-                stateKeeperDispatcher = stateKeeperDispatcher,
-                instanceKeeperDispatcher = instanceKeeperDispatcher,
-                backPressedDispatcher = BackPressedDispatcher()
-            )
-
-        private fun createdEntry(
-            configuration: Config,
-            component: Component = Component(),
-            savedState: ParcelableContainer? = null,
-            lifecycleRegistry: LifecycleRegistry = LifecycleRegistry().apply { create() },
-            stateKeeperDispatcher: StateKeeperDispatcher = TestStateKeeperDispatcher(savedState)
-        ): RouterEntry.Created<Config, Component> =
-            RouterEntry.Created(
-                configuration = configuration,
-                savedState = savedState,
-                instance = component,
-                lifecycleRegistry = lifecycleRegistry,
-                stateKeeperDispatcher = stateKeeperDispatcher,
-                instanceKeeperDispatcher = InstanceKeeperDispatcher(),
-                backPressedDispatcher = BackPressedDispatcher()
-            )
-
-        private fun destroyedEntry(
-            configuration: Config,
-            savedState: ParcelableContainer? = null
-        ): RouterEntry.Destroyed<Config> =
-            RouterEntry.Destroyed(
-                configuration = configuration,
-                savedState = savedState
-            )
-    }
-
-    private class Config
-
-    private class Component
-
-    private class TestRouterEntryFactory : RouterEntryFactory<Config, Component> {
-        override fun invoke(
-            configuration: Config,
-            savedState: ParcelableContainer?,
-            instanceKeeperDispatcher: InstanceKeeperDispatcher?
-        ): RouterEntry.Created<Config, Component> =
-            createdEntry(
-                configuration = configuration,
-                component = Component(),
-                stateKeeperDispatcher = TestStateKeeperDispatcher(savedState)
-            )
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestRouter.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestRouter.kt
deleted file mode 100644
index d064ce63..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestRouter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.value.MutableValue
-
-class TestRouter<C : Any>(stack: List<C>) : Router<C, Any> {
-
-    override val state: MutableValue<RouterState<C, Any>> = MutableValue(stack.toRouterState())
-
-    var stack: List<C>
-        get() = state.value.backStack.map(Child<C, *>::configuration) + state.value.activeChild.configuration
-        set(value) {
-            state.value = value.toRouterState()
-        }
-
-    private fun List<C>.toRouterState(): RouterState<C, Any> =
-        RouterState<C, Any>(
-            activeChild = Child.Created(
-                configuration = last(),
-                instance = last(),
-            ),
-            backStack = dropLast(1).map {
-                Child.Created(
-                    configuration = it,
-                    instance = it,
-                )
-            }
-        )
-
-    override fun navigate(transformer: (stack: List<C>) -> List<C>, onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit) {
-        val oldStack = stack
-        val newStack = transformer(stack)
-        stack = newStack
-        onComplete(newStack, oldStack)
-    }
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestStackSaver.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestStackSaver.kt
deleted file mode 100644
index d079c18e..00000000
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/TestStackSaver.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.arkivanov.decompose.router
-
-import com.arkivanov.decompose.router.StackSaver.RestoredStack
-
-internal class TestStackSaver<C : Any>(
-    private val copyConfiguration: (C) -> C,
-    savedState: SavedState<C>? = null
-) : StackSaver<C> {
-
-    private val map: MutableMap<String, RestoredStack<C>> = savedState?.map ?: HashMap()
-    private val suppliers = HashMap<String, () -> RouterStack<C, *>>()
-
-    override fun register(key: String, supplier: () -> RouterStack<C, *>) {
-        check(key !in suppliers)
-        suppliers[key] = supplier
-    }
-
-    override fun unregister(key: String) {
-        check(key in suppliers)
-        suppliers -= key
-    }
-
-    override fun restore(key: String): RestoredStack<C>? =
-        map.remove(key)
-
-    fun save(): SavedState<C> =
-        SavedState(
-            suppliers.mapValuesTo(HashMap()) { (_, suppliers) ->
-                suppliers().save()
-            }
-        )
-
-    private fun RouterStack<C, *>.save(): RestoredStack<C> =
-        RestoredStack(
-            active = RouterEntry.Destroyed(
-                configuration = copyConfiguration(active.configuration),
-                savedState = active.stateKeeperDispatcher.save()
-            ),
-            backStack = backStack.map {
-                RouterEntry.Destroyed(
-                    configuration = copyConfiguration(it.configuration),
-                    savedState = it.savedState
-                )
-            }
-        )
-
-    class SavedState<C : Any>(
-        val map: MutableMap<String, RestoredStack<C>>
-    )
-}
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackControllerTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackControllerTest.kt
index 8bac11be..7b59e12c 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackControllerTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/ChildStackControllerTest.kt
@@ -1,17 +1,9 @@
-package com.arkivanov.decompose.controller.stack
-
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.router.stack.ChildStackController
-import com.arkivanov.decompose.router.stack.RouterEntry
-import com.arkivanov.decompose.router.stack.RouterStack
-import com.arkivanov.decompose.router.stack.StackController
-import com.arkivanov.decompose.router.stack.StackHolder
-import com.arkivanov.decompose.router.stack.StackNavigation
-import com.arkivanov.decompose.router.stack.StackNavigationSource
-import com.arkivanov.decompose.router.stack.configurationStack
-import com.arkivanov.decompose.router.stack.navigate
+package com.arkivanov.decompose.router.stack
+
+import com.arkivanov.decompose.backhandler.TestChildBackHandler
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
+import com.arkivanov.essenty.backhandler.BackDispatcher
+import com.arkivanov.essenty.backhandler.BackHandler
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.parcelable.Parcelable
@@ -19,10 +11,29 @@ import com.arkivanov.essenty.parcelable.Parcelize
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 
 @Suppress("TestFunctionName")
 class ChildStackControllerTest {
 
+    @Test
+    fun GIVEN_initial_back_stack_empty_WHEN_created_THEN_back_handler_disabled() {
+        val backDispatcher = BackDispatcher()
+
+        controller(initialStack = listOf(Config()), backHandler = backDispatcher)
+
+        assertFalse(backDispatcher.isEnabled)
+    }
+
+    @Test
+    fun GIVEN_initial_back_stack_not_empty_WHEN_created_THEN_back_handler_enabled() {
+        val backDispatcher = BackDispatcher()
+
+        controller(initialStack = listOf(Config(), Config()), backHandler = backDispatcher)
+
+        assertTrue(backDispatcher.isEnabled)
+    }
+
     @Test
     fun WHEN_navigate_THEN_new_stack_applied() {
         val config1 = Config()
@@ -105,7 +116,6 @@ class ChildStackControllerTest {
         assertFalse(isCalledSynchronously)
     }
 
-
     @Test
     fun WHEN_navigate_recursively_THEN_onComplete_called() {
         val config1 = Config()
@@ -134,15 +144,15 @@ class ChildStackControllerTest {
     }
 
     private fun controller(
-        source: StackNavigationSource<Config>,
+        source: StackNavigationSource<Config> = StackNavigation(),
         initialStack: List<Config>,
+        backHandler: BackHandler = BackDispatcher(),
         controller: TestStackController = TestStackController(),
     ): ChildStackController<Config, Config> =
         ChildStackController(
             source = source,
             lifecycle = LifecycleRegistry(),
-            backPressedHandler = BackPressedDispatcher(),
-            popOnBackPressed = false,
+            backHandler = backHandler,
             stackHolder = TestStackHolder(routerStack(initialStack)),
             controller = controller,
         )
@@ -156,7 +166,7 @@ class ChildStackControllerTest {
                     lifecycleRegistry = LifecycleRegistry(),
                     stateKeeperDispatcher = TestStateKeeperDispatcher(),
                     instanceKeeperDispatcher = InstanceKeeperDispatcher(),
-                    backPressedDispatcher = BackPressedDispatcher()
+                    backHandler = TestChildBackHandler(),
                 ),
                 backStack = stack.dropLast(1).map {
                     RouterEntry.Destroyed(configuration = it)
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackControllerTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackControllerTest.kt
index 8042b695..89be50e5 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackControllerTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackControllerTest.kt
@@ -1,9 +1,10 @@
 package com.arkivanov.decompose.router.stack
 
+import com.arkivanov.decompose.backhandler.ChildBackHandler
+import com.arkivanov.decompose.backhandler.TestChildBackHandler
 import com.arkivanov.decompose.router.TestInstance
 import com.arkivanov.decompose.statekeeper.TestParcelableContainer
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
@@ -13,6 +14,7 @@ import com.arkivanov.essenty.parcelable.ParcelableContainer
 import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
 import kotlin.test.Test
 import kotlin.test.assertEquals
+import kotlin.test.assertFalse
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.assertSame
@@ -132,6 +134,20 @@ class StackControllerTest {
         assertSame(Lifecycle.State.RESUMED, newStack.active.lifecycleRegistry.state)
     }
 
+    @Test
+    fun WHEN_push_THEN_new_active_component_back_handler_started() {
+        val newStack =
+            controller.navigate(
+                oldStack = RouterStack(
+                    active = activeEntry(configuration = Config()),
+                    backStack = emptyList()
+                ),
+                transformer = { it + Config() }
+            )
+
+        assertTrue(newStack.active.backHandler.asTest().isStarted)
+    }
+
     @Test
     fun WHEN_push_THEN_old_active_component_stopped() {
         val newStack =
@@ -143,7 +159,21 @@ class StackControllerTest {
                 transformer = { it + Config() }
             )
 
-        assertSame(Lifecycle.State.CREATED, (newStack.backStack[0] as RouterEntry.Created).lifecycleRegistry.state)
+        assertSame(Lifecycle.State.CREATED, newStack.backStack.first().asCreated().lifecycleRegistry.state)
+    }
+
+    @Test
+    fun WHEN_push_THEN_old_active_component_back_handler_stopped() {
+        val newStack =
+            controller.navigate(
+                oldStack = RouterStack(
+                    active = activeEntry(configuration = Config()),
+                    backStack = emptyList()
+                ),
+                transformer = { it + Config() }
+            )
+
+        assertFalse(newStack.backStack.first().asCreated().backHandler.asTest().isStarted)
     }
 
     @Test
@@ -506,13 +536,16 @@ class StackControllerTest {
     private fun <C : Any, T : Any> RouterEntry<C, T>.asCreated(): RouterEntry.Created<C, T> =
         this as RouterEntry.Created<C, T>
 
+    private fun ChildBackHandler.asTest(): TestChildBackHandler = this as TestChildBackHandler
+
     private companion object {
         private fun activeEntry(
             configuration: Config,
             component: Component = Component(),
             lifecycleRegistry: LifecycleRegistry = LifecycleRegistry().apply { resume() },
             stateKeeperDispatcher: StateKeeperDispatcher = TestStateKeeperDispatcher(),
-            instanceKeeperDispatcher: InstanceKeeperDispatcher = InstanceKeeperDispatcher()
+            instanceKeeperDispatcher: InstanceKeeperDispatcher = InstanceKeeperDispatcher(),
+            backHandler: ChildBackHandler = TestChildBackHandler(isStarted = true),
         ): RouterEntry.Created<Config, Component> =
             RouterEntry.Created(
                 configuration = configuration,
@@ -520,7 +553,7 @@ class StackControllerTest {
                 lifecycleRegistry = lifecycleRegistry,
                 stateKeeperDispatcher = stateKeeperDispatcher,
                 instanceKeeperDispatcher = instanceKeeperDispatcher,
-                backPressedDispatcher = BackPressedDispatcher()
+                backHandler = backHandler,
             )
 
         private fun createdEntry(
@@ -528,7 +561,8 @@ class StackControllerTest {
             component: Component = Component(),
             savedState: ParcelableContainer? = null,
             lifecycleRegistry: LifecycleRegistry = LifecycleRegistry().apply { create() },
-            stateKeeperDispatcher: StateKeeperDispatcher = TestStateKeeperDispatcher(savedState)
+            stateKeeperDispatcher: StateKeeperDispatcher = TestStateKeeperDispatcher(savedState),
+            backHandler: ChildBackHandler = TestChildBackHandler(),
         ): RouterEntry.Created<Config, Component> =
             RouterEntry.Created(
                 configuration = configuration,
@@ -537,7 +571,7 @@ class StackControllerTest {
                 lifecycleRegistry = lifecycleRegistry,
                 stateKeeperDispatcher = stateKeeperDispatcher,
                 instanceKeeperDispatcher = InstanceKeeperDispatcher(),
-                backPressedDispatcher = BackPressedDispatcher()
+                backHandler = backHandler,
             )
 
         private fun destroyedEntry(
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackHolderImplTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackHolderImplTest.kt
index 0a105028..b68a31a4 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackHolderImplTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/StackHolderImplTest.kt
@@ -1,10 +1,12 @@
 package com.arkivanov.decompose.router.stack
 
+import com.arkivanov.decompose.backhandler.ChildBackHandler
+import com.arkivanov.decompose.backhandler.TestChildBackHandler
 import com.arkivanov.decompose.router.TestInstance
 import com.arkivanov.decompose.statekeeper.ParcelableStub
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
-import com.arkivanov.essenty.backpressed.BackPressedDispatcher
 import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
+import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import com.arkivanov.essenty.parcelable.ParcelableContainer
 import com.arkivanov.essenty.statekeeper.consume
@@ -16,6 +18,20 @@ import kotlin.test.assertTrue
 @Suppress("TestFunctionName")
 class StackHolderImplTest {
 
+    @Test
+    fun WHEN_created_THEN_active_component_resumed() {
+        val holder = stackHolder()
+
+        assertEquals(Lifecycle.State.RESUMED, holder.stack.active.lifecycleRegistry.state)
+    }
+
+    @Test
+    fun WHEN_created_THEN_active_component_back_handler_started() {
+        val holder = stackHolder()
+
+        assertTrue(holder.stack.active.backHandler.asTest().isStarted)
+    }
+
     @Test
     fun WHEN_recreated_THEN_instances_retained_in_active_component() {
         val instanceKeeperDispatcher = InstanceKeeperDispatcher()
@@ -138,6 +154,8 @@ class StackHolderImplTest {
             savedState = savedState
         )
 
+    private fun ChildBackHandler.asTest(): TestChildBackHandler = this as TestChildBackHandler
+
     private data class Config(val key: String)
 
     private class Component
@@ -158,7 +176,7 @@ class StackHolderImplTest {
                 lifecycleRegistry = LifecycleRegistry(),
                 stateKeeperDispatcher = stateKeeper,
                 instanceKeeperDispatcher = instanceKeeper,
-                backPressedDispatcher = BackPressedDispatcher()
+                backHandler = TestChildBackHandler(),
             )
         }
     }
diff --git a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryControllerTest.kt b/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryControllerTest.kt
deleted file mode 100644
index 68a4ab97..00000000
--- a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/DefaultWebHistoryControllerTest.kt
+++ /dev/null
@@ -1,431 +0,0 @@
-package com.arkivanov.decompose.router.webhistory
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.TestRouter
-import com.arkivanov.decompose.router.navigate
-import com.arkivanov.decompose.router.pop
-import com.arkivanov.decompose.router.push
-import com.arkivanov.decompose.router.replaceCurrent
-import com.arkivanov.essenty.parcelable.Parcelable
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-@OptIn(ExperimentalDecomposeApi::class)
-@Suppress("TestFunctionName")
-class DefaultWebHistoryControllerTest {
-
-    private val window = TestWindow()
-    private val history = window.history
-    private val controller = DefaultWebHistoryController(window)
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_one_config_WHEN_attach_THEN_history_stack_is_correct() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-
-        attach(router)
-
-        history.assertStack(listOf("/0"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_two_configs_WHEN_attach_THEN_history_stack_is_correct() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-
-        attach(router)
-
-        history.assertStack(listOf("/0", "/1"))
-    }
-
-    @Test
-    fun WHEN_router_push_THEN_url_pushed_to_history() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-
-        router.push(Config(1))
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_two_configs_WHEN_router_pop_THEN_history_changed_to_previous_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-
-        router.pop()
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1"), 0)
-    }
-
-    @Test
-    fun GIVEN_router_push_WHEN_router_pop_THEN_history_changed_to_previous_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-
-        router.pop()
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1"), 0)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_tree_configs_WHEN_router_pop_two_THEN_history_changed_to_previous_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1), Config(2)))
-        attach(router)
-
-        router.navigate { it.dropLast(2) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1", "/2"), 0)
-    }
-
-    @Test
-    fun GIVEN_router_push_two_WHEN_router_pop_two_THEN_history_changed_to_previous_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.navigate { it + listOf(Config(1), Config(2)) }
-        window.runPendingOperations()
-
-        router.navigate { it.dropLast(2) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1", "/2"), 0)
-    }
-
-    @Test
-    fun GIVEN_router_push_and_pop_WHEN_router_push_same_config_THEN_history_changed_to_next_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        router.pop()
-        window.runPendingOperations()
-
-        router.push(Config(1))
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/1"))
-    }
-
-    @Test
-    fun GIVEN_router_push_and_pop_WHEN_router_push_another_config_THEN_history_changed_to_next_page() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        router.pop()
-        window.runPendingOperations()
-
-        router.push(Config(2))
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/2"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_c_WHEN_navigate_to_a_d_THEN_history_is_a_d_and_d_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1), Config(2)))
-        attach(router)
-
-        router.navigate { listOf(Config(0), Config(3)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/3"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_c_WHEN_navigate_to_a_d_e_THEN_history_is_a_d_e_and_e_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1), Config(2)))
-        attach(router)
-
-        router.navigate { listOf(Config(0), Config(3), Config(4)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/3", "/4"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_WHEN_navigate_to_a_c_THEN_history_is_a_c_and_c_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-
-        router.navigate { listOf(Config(0), Config(2)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/2"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_WHEN_navigate_to_a_c_d_THEN_history_is_a_c_d_and_d_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-
-        router.navigate { listOf(Config(0), Config(2), Config(3)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/0", "/2", "/3"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_WHEN_navigate_to_c_THEN_history_is_c_b_and_c_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-
-        router.navigate { listOf(Config(2)) }
-        window.runPendingOperations()
-
-        // Corner case: old pages remain in the history
-        history.assertStack(listOf("/2", "/1"), 0)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_WHEN_navigate_to_c_d_THEN_history_is_c_d_and_d_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-
-        router.navigate { listOf(Config(2), Config(3)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/2", "/3"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_WHEN_navigate_to_b_THEN_history_is_b_and_b_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-
-        router.navigate { listOf(Config(1)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/1"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_WHEN_navigate_to_b_c_THEN_history_is_b_c_and_c_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-
-        router.navigate { listOf(Config(1), Config(2)) }
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/1", "/2"))
-    }
-
-    // Browser buttons tests
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_and_pushed_b_WHEN_go_back_THEN_stack_is_a() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-
-        window.history.go(delta = -1)
-        window.runPendingOperations()
-
-        assertEquals(listOf(Config(0)), router.stack)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_and_pushed_b_and_go_back_WHEN_go_forward_THEN_history_is_a_b_and_b_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        window.history.go(delta = -1)
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        window.history.assertStack(listOf("/0", "/1"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_and_pushed_b_and_go_back_WHEN_go_forward_THEN_stack_is_a_b() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        window.history.go(delta = -1)
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        assertEquals(listOf(Config(0), Config(1)), router.stack)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_and_pushed_b_and_go_back_and_replace_current_with_c_WHEN_go_forward_THEN_history_is_c_b_and_b_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        window.history.go(delta = -1)
-        window.runPendingOperations()
-        router.replaceCurrent(Config(2))
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        window.history.assertStack(listOf("/2", "/1"))
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_and_pushed_b_and_go_back_and_replace_current_with_c_WHEN_go_forward_THEN_stack_is_c_b() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0)))
-        attach(router)
-        router.push(Config(1))
-        window.runPendingOperations()
-        window.history.go(delta = -1)
-        window.runPendingOperations()
-        router.replaceCurrent(Config(2))
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        assertEquals(listOf(Config(2), Config(1)), router.stack)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_and_navigate_to_c_WHEN_go_forward_THEN_stack_is_c_b() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-        router.navigate { listOf(Config(2)) }
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        assertEquals(listOf(Config(2), Config(1)), router.stack)
-    }
-
-    @Test
-    fun GIVEN_router_with_initial_stack_of_a_b_and_navigate_to_c_WHEN_go_forward_THEN_history_is_c_b_and_b_is_active() {
-        if (isNode()) {
-            return
-        }
-
-        val router = TestRouter(listOf(Config(0), Config(1)))
-        attach(router)
-        router.navigate { listOf(Config(2)) }
-        window.runPendingOperations()
-
-        window.history.go(delta = 1)
-        window.runPendingOperations()
-
-        history.assertStack(listOf("/2", "/1"))
-    }
-
-    private fun attach(router: TestRouter<Config>) {
-        controller.attach(
-            router = router,
-            getPath = { "/${it.value}" },
-            getConfiguration = { Config(it.removePrefix("/").toInt()) },
-        )
-
-        window.runPendingOperations()
-    }
-
-    private fun isNode(): Boolean = jsTypeOf(kotlinx.browser.window) == "undefined"
-
-    private data class Config(val value: Int) : Parcelable
-}
diff --git a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestHistory.kt b/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestHistory.kt
deleted file mode 100644
index df10b53f..00000000
--- a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestHistory.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.arkivanov.decompose.router.webhistory
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.webhistory.DefaultWebHistoryController.History
-import org.w3c.dom.PopStateEvent
-import org.w3c.dom.PopStateEventInit
-import kotlin.test.assertEquals
-
-@OptIn(ExperimentalDecomposeApi::class)
-class TestHistory(
-    private val scheduleOperation: (() -> Unit) -> Unit,
-    private val onPopState: (PopStateEvent) -> Unit,
-) : History {
-
-    private val stack: MutableList<Entry> = mutableListOf(Entry())
-    private var index = 0
-
-    override val state: Any? get() = stack[index].data
-
-    override fun go(delta: Int) {
-        scheduleOperation {
-            index += delta
-
-            onPopState(
-                PopStateEvent(
-                    type = "PopStateEvent",
-                    eventInitDict = PopStateEventInit(state = stack[index].data)
-                )
-            )
-        }
-    }
-
-    override fun pushState(data: Any?, url: String?) {
-        while (stack.lastIndex > index) {
-            stack.removeLast()
-        }
-
-        stack += Entry(data = data?.serializeAndDeserialize(), url = url)
-        index++
-    }
-
-    override fun replaceState(data: Any?, url: String?) {
-        stack[index] = Entry(data = data?.serializeAndDeserialize(), url = url)
-    }
-
-    fun assertStack(urls: List<String?>, index: Int = urls.lastIndex) {
-        assertEquals(urls, stack.map(Entry::url))
-        assertEquals(index, this.index)
-    }
-
-    private fun Any.serializeAndDeserialize(): Any = JSON.parse(JSON.stringify(this))
-
-    class Entry(
-        val data: Any? = null,
-        val url: String? = null,
-    )
-}
diff --git a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestWindow.kt b/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestWindow.kt
deleted file mode 100644
index 41b7d008..00000000
--- a/decompose/src/jsTest/kotlin/com/arkivanov/decompose/router/webhistory/TestWindow.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.arkivanov.decompose.router.webhistory
-
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.router.webhistory.DefaultWebHistoryController.Window
-import org.w3c.dom.PopStateEvent
-
-@OptIn(ExperimentalDecomposeApi::class)
-class TestWindow : Window {
-
-    private val pendingOperations = ArrayList<() -> Unit>()
-
-    override var onPopState: ((PopStateEvent) -> Unit)? = null
-
-    override val history: TestHistory =
-        TestHistory(
-            scheduleOperation = pendingOperations::add,
-            onPopState = { onPopState?.invoke(it) }
-        )
-
-    fun runPendingOperations() {
-        val operations = pendingOperations.toList()
-        pendingOperations.clear()
-        operations.forEach { it() }
-    }
-}
diff --git a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/ChildrenTest.kt b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/ChildrenTest.kt
deleted file mode 100644
index 833b2bf9..00000000
--- a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/ChildrenTest.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-package com.arkivanov.decompose.extensions.compose.jetbrains
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.ChildAnimation
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.childAnimation
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.emptyChildAnimation
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.fade
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.plus
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.scale
-import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.slide
-import com.arkivanov.decompose.router.RouterState
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-@OptIn(ExperimentalDecomposeApi::class)
-@Suppress("TestFunctionName")
-@RunWith(Parameterized::class)
-class ChildrenTest(
-    private val animation: ChildAnimation<Config, Config>
-) {
-
-    @get:Rule
-    val composeRule = createComposeRule()
-
-    @Test
-    fun WHEN_active_child_and_no_back_stack_THEN_active_child_displayed() {
-        runBlocking(Dispatchers.Main) {
-            val state = mutableStateOf(routerState(activeConfig = Config.A))
-
-            setContent(state)
-
-            composeRule.onNodeWithText(text = "ChildA", substring = true).assertExists()
-        }
-    }
-
-    @Test
-    fun GIVEN_child_A_displayed_WHEN_push_child_B_THEN_child_B_displayed() {
-        runBlocking(Dispatchers.Main) {
-            val state = mutableStateOf(routerState(activeConfig = Config.A))
-            setContent(state)
-
-            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
-
-            composeRule.onNodeWithText(text = "ChildB", substring = true).assertExists()
-        }
-    }
-
-    @Test
-    fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_child_A_displayed() {
-        runBlocking(Dispatchers.Main) {
-            val state = mutableStateOf(routerState(activeConfig = Config.A))
-            setContent(state)
-            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
-
-            state.setValueOnIdle(routerState(activeConfig = Config.A))
-
-            composeRule.onNodeWithText(text = "ChildA", substring = true).assertExists()
-        }
-    }
-
-    @Test
-    fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_state_restored_for_child_A() {
-        runBlocking(Dispatchers.Main) {
-            val state = mutableStateOf(routerState(activeConfig = Config.A))
-            setContent(state)
-            composeRule.onNodeWithText(text = "ChildA=0").performClick()
-            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
-
-            state.setValueOnIdle(routerState(activeConfig = Config.A))
-
-            composeRule.onNodeWithText(text = "ChildA=1").assertExists()
-        }
-    }
-
-    @Test
-    fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_and_push_child_B_THEN_state_not_restored_for_child_B() {
-        runBlocking(Dispatchers.Main) {
-            val state = mutableStateOf(routerState(activeConfig = Config.A))
-            setContent(state)
-
-            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
-            composeRule.onNodeWithText(text = "ChildB=0").performClick()
-
-            state.setValueOnIdle(routerState(activeConfig = Config.A))
-            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
-
-            composeRule.onNodeWithText(text = "ChildB=0").assertExists()
-        }
-    }
-
-    private suspend fun setContent(state: State<RouterState<Config, Config>>) {
-        composeRule.setContent {
-            Children(routerState = state.value, animation = animation) { child ->
-                when (child.configuration) {
-                    Config.A -> Child(name = "A")
-                    Config.B -> Child(name = "B")
-                }.let {}
-            }
-        }
-
-        runOnIdle {}
-    }
-
-    private fun routerState(activeConfig: Config, backstack: List<Config> = emptyList()): RouterState<Config, Config> =
-        RouterState(
-            activeChild = Child.Created(configuration = activeConfig, instance = activeConfig),
-            backStack = backstack.map { Child.Destroyed(configuration = it) }
-        )
-
-    @Composable
-    private fun Child(name: String) {
-        var count by rememberSaveable { mutableStateOf(0) }
-
-        BasicText(
-            text = "Child$name=$count",
-            modifier = Modifier.clickable { count++ }
-        )
-    }
-
-    private suspend fun <T> MutableState<T>.setValueOnIdle(value: T) {
-        runOnIdle { this.value = value }
-        runOnIdle {}
-    }
-
-    private suspend fun runOnIdle(block: () -> Unit) {
-        composeRule.awaitIdle()
-        block()
-    }
-
-    companion object {
-        @Parameterized.Parameters
-        @JvmStatic
-        fun parameters(): List<Array<out Any>> =
-            getParameters().map { arrayOf(it) }
-
-        private fun getParameters(): List<ChildAnimation<Config, Config>> =
-            listOf(
-                emptyChildAnimation(),
-                childAnimation(scale()),
-                childAnimation(fade()),
-                childAnimation(slide()),
-                childAnimation(scale() + fade() + slide()),
-            )
-    }
-
-    // Can be enum, workaround https://issuetracker.google.com/issues/195185633
-    sealed class Config {
-        object A : Config()
-        object B : Config()
-    }
-}
diff --git a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/lifecycle/LifecycleControllerTest.kt b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/lifecycle/LifecycleControllerTest.kt
index 5517f370..a60a61ee 100644
--- a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/lifecycle/LifecycleControllerTest.kt
+++ b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/lifecycle/LifecycleControllerTest.kt
@@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.window.WindowState
+import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.essenty.lifecycle.Lifecycle
 import com.arkivanov.essenty.lifecycle.LifecycleRegistry
 import kotlinx.coroutines.Dispatchers
@@ -14,6 +15,7 @@ import org.junit.Rule
 import org.junit.Test
 import kotlin.test.assertEquals
 
+@OptIn(ExperimentalDecomposeApi::class)
 @Suppress("TestFunctionName")
 class LifecycleControllerTest {
 
diff --git a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
index abbf22bb..9c43d278 100644
--- a/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
+++ b/extensions-compose-jetbrains/src/jvmTest/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenTest.kt
@@ -16,7 +16,6 @@ import androidx.compose.ui.test.performClick
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation
-import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.emptyStackAnimation
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.plus
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.scale
@@ -34,7 +33,7 @@ import org.junit.runners.Parameterized
 @Suppress("TestFunctionName")
 @RunWith(Parameterized::class)
 class ChildrenTest(
-    private val animation: StackAnimation<Config, Config>
+    private val animation: StackAnimation<Config, Config>?,
 ) {
 
     @get:Rule
@@ -106,6 +105,31 @@ class ChildrenTest(
         }
     }
 
+    @Test
+    fun GIVEN_child_A_displayed_WHEN_push_child_B_THEN_child_A_disposed() {
+        runBlocking(Dispatchers.Main) {
+            val state = mutableStateOf(routerState(activeConfig = Config.A))
+            setContent(state)
+
+            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+
+            composeRule.onNodeWithText(text = "ChildA", substring = true).assertDoesNotExist()
+        }
+    }
+
+    @Test
+    fun GIVEN_child_B_displayed_and_child_A_in_back_stack_WHEN_pop_child_B_THEN_child_B_disposed() {
+        runBlocking(Dispatchers.Main) {
+            val state = mutableStateOf(routerState(activeConfig = Config.A))
+            setContent(state)
+            state.setValueOnIdle(routerState(activeConfig = Config.B, backstack = listOf(Config.A)))
+
+            state.setValueOnIdle(routerState(activeConfig = Config.A))
+
+            composeRule.onNodeWithText(text = "ChildB", substring = true).assertDoesNotExist()
+        }
+    }
+
     private suspend fun setContent(state: State<ChildStack<Config, Config>>) {
         composeRule.setContent {
             Children(stack = state.value, animation = animation) { child ->
@@ -148,12 +172,13 @@ class ChildrenTest(
     companion object {
         @Parameterized.Parameters
         @JvmStatic
-        fun parameters(): List<Array<out Any>> =
+        fun parameters(): List<Array<out Any?>> =
             getParameters().map { arrayOf(it) }
 
-        private fun getParameters(): List<StackAnimation<Config, Config>> =
+        private fun getParameters(): List<StackAnimation<Config, Config>?> =
             listOf(
-                emptyStackAnimation(),
+                null,
+                stackAnimation { _, _, _ -> null },
                 stackAnimation(scale()),
                 stackAnimation(fade()),
                 stackAnimation(slide()),