Repository: arkivanov/Decompose
Pull request #323 (2023-02-02 19:27:27)

Merge v1


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index 21f5257a..08634a31 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -57,6 +57,7 @@ public final class com/arkivanov/decompose/DefaultComponentContext : com/arkivan
 public final class com/arkivanov/decompose/DefaultComponentContextBuilderKt {
 	public static final fun DefaultComponentContext (Landroidx/lifecycle/Lifecycle;Landroidx/savedstate/SavedStateRegistry;Landroidx/lifecycle/ViewModelStore;Landroidx/activity/OnBackPressedDispatcher;)Lcom/arkivanov/decompose/DefaultComponentContext;
 	public static synthetic fun DefaultComponentContext$default (Landroidx/lifecycle/Lifecycle;Landroidx/savedstate/SavedStateRegistry;Landroidx/lifecycle/ViewModelStore;Landroidx/activity/OnBackPressedDispatcher;ILjava/lang/Object;)Lcom/arkivanov/decompose/DefaultComponentContext;
+	public static final fun defaultComponentContext (Landroidx/fragment/app/Fragment;Landroidx/activity/OnBackPressedDispatcher;)Lcom/arkivanov/decompose/DefaultComponentContext;
 	public static final fun defaultComponentContext (Landroidx/savedstate/SavedStateRegistryOwner;)Lcom/arkivanov/decompose/DefaultComponentContext;
 }
 
@@ -87,7 +88,8 @@ public final class com/arkivanov/decompose/router/children/ChildNavState$Status
 }
 
 public final class com/arkivanov/decompose/router/children/ChildrenFactoryKt {
-	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun children$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/children/NavState {
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 61b7006e..08c21db6 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -74,7 +74,8 @@ public final class com/arkivanov/decompose/router/children/ChildNavState$Status
 }
 
 public final class com/arkivanov/decompose/router/children/ChildrenFactoryKt {
-	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static final fun children (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value;
+	public static synthetic fun children$default (Lcom/arkivanov/decompose/ComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value;
 }
 
 public abstract interface class com/arkivanov/decompose/router/children/NavState {
diff --git a/decompose/build.gradle.kts b/decompose/build.gradle.kts
index 9cd34d72..0ba8ebd5 100644
--- a/decompose/build.gradle.kts
+++ b/decompose/build.gradle.kts
@@ -51,6 +51,7 @@ kotlin {
 
         android.main.dependencies {
             implementation(deps.androidx.activity.activityKtx)
+            implementation(deps.androidx.fragment.fragmentKtx)
             implementation(deps.androidx.lifecycle.lifecycleCommonJava8)
         }
     }
diff --git a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
index 5cecda0d..6324cee9 100644
--- a/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
+++ b/decompose/src/androidMain/kotlin/com/arkivanov/decompose/DefaultComponentContextBuilder.kt
@@ -2,6 +2,7 @@ package com.arkivanov.decompose
 
 import androidx.activity.OnBackPressedDispatcher
 import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.fragment.app.Fragment
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
@@ -26,11 +27,36 @@ fun DefaultComponentContext(
         backHandler = onBackPressedDispatcher?.let(::BackHandler),
     )
 
+/**
+ * Creates a default implementation of [ComponentContext] and attaches it
+ * to the receiver (e.g. an [Activity][android.app.Activity]).
+ */
 fun <T> T.defaultComponentContext(): DefaultComponentContext where
     T : SavedStateRegistryOwner, T : OnBackPressedDispatcherOwner, T : ViewModelStoreOwner, T : LifecycleOwner =
     DefaultComponentContext(
         lifecycle = (this as LifecycleOwner).lifecycle,
         savedStateRegistry = savedStateRegistry,
         viewModelStore = viewModelStore,
-        onBackPressedDispatcher = onBackPressedDispatcher
+        onBackPressedDispatcher = onBackPressedDispatcher,
+    )
+
+/**
+ * Creates a default implementation of [ComponentContext] and attaches it to the [Fragment].
+ *
+ * @param onBackPressedDispatcher an optional [OnBackPressedDispatcher] for back button handling,
+ * or `null` if back button handling is not required. Can be obtained via `requireActivity().onBackPressedDispatcher`.
+ */
+fun Fragment.defaultComponentContext(
+    onBackPressedDispatcher: OnBackPressedDispatcher?,
+): DefaultComponentContext =
+    DefaultComponentContext(
+        lifecycle = lifecycle.asEssentyLifecycle(),
+        stateKeeper = StateKeeper(savedStateRegistry),
+        instanceKeeper = InstanceKeeper(viewModelStore),
+        backHandler = onBackPressedDispatcher?.let {
+            BackHandler(
+                onBackPressedDispatcher = it,
+                lifecycleOwner = this,
+            )
+        },
     )
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
index fc91e951..c506aeb4 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents a child navigation state.
  */
-@ExperimentalDecomposeApi
 interface ChildNavState<out C : Any> {
 
     /**
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
index 6ad7056a..28e5f4be 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.backhandler.child
 import com.arkivanov.decompose.value.MutableValue
 import com.arkivanov.decompose.value.Value
@@ -39,35 +38,37 @@ import com.arkivanov.essenty.statekeeper.consume
  * @param source a source of navigation events.
  * @param key a key of this `children` collection, must be unique if there are multiple
  * `children` used in the same component.
- * @param initialNavState an initial navigation state that should be used if there is no previously saved state.
- * @param saveNavState a function that saves the provided navigation state into [ParcelableContainer].
+ * @param initialState an initial navigation state that should be used if there is no previously saved state.
+ * @param saveState a function that saves the provided navigation state into [ParcelableContainer].
  * The navigation state is not saved if `null` is returned.
- * @param restoreNavState a function that restores the navigation state from the provided [ParcelableContainer].
- * If `null` is returned then [initialNavState] is used instead.
+ * @param restoreState a function that restores the navigation state from the provided [ParcelableContainer].
+ * If `null` is returned then [initialState] is used instead.
  * The restored navigation state must have the same amount of child configurations and in the same order.
  * The restored child [Statuses][ChildNavState.Status] can be any, e.g. a previously active child may become
  * destroyed, etc.
  * @param navTransformer a function that transforms the current navigation state to a new one using the provided
  * navigation event.
+ * @param stateMapper combines the provided navigation state and list of child components to a resulting state.
+ * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when
+ * called first time during initialisation.
  * @param onEventComplete called when a navigation event is processed and the navigation completed.
  * @param backTransformer a function that checks the provided navigation state, and either returns another function
  * transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called
- * during the initialisation and after each navigation event.
- * @param stateMapper combines the provided navigation state and list of child components to a resulting state.
+ * during initialisation and after each navigation event.
  * @param childFactory a factory function that creates new child component instances.
  * @return an observable [Value] of the resulting children state.
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    initialNavState: () -> N,
-    saveNavState: (navState: N) -> ParcelableContainer?,
-    restoreNavState: (container: ParcelableContainer) -> N?,
-    navTransformer: (navState: N, event: E) -> N,
-    onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    backTransformer: (navState: N) -> (() -> N)?,
-    stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    initialState: () -> N,
+    saveState: (state: N) -> ParcelableContainer?,
+    restoreState: (container: ParcelableContainer) -> N?,
+    navTransformer: (state: N, event: E) -> N,
+    stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    backTransformer: (state: N) -> (() -> N)? = { null },
     childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S> {
     val mainBackHandler = backHandler.child()
@@ -82,15 +83,15 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
                     backHandler = backHandler.child(),
                     childFactory = childFactory,
                 ),
-                navState = savedState?.navState?.let(restoreNavState) ?: initialNavState(),
+                navState = savedState?.navState?.let(restoreState) ?: initialState(),
                 savedChildState = savedState?.childState,
             )
         }
 
     stateKeeper.register(key = key) {
-        saveNavState(navigator.navState)?.let { savedNavState ->
+        saveState(navigator.navState)?.let { savedState ->
             SavedState(
-                navState = savedNavState,
+                navState = savedState,
                 childState = navigator.saveChildState(),
             )
         }
@@ -102,17 +103,20 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
 
     lateinit var backCallback: BackCallback
 
-    fun onAfterNavigate(navState: N) {
-        bt = backTransformer(navState)
+    fun onAfterNavigate(newState: N, oldState: N) {
+        bt = backTransformer(newState)
         backCallback.isEnabled = bt != null
-        state.value = stateMapper(navState, navigator.children)
+        state.value = stateMapper(newState, navigator.children)
+        onStateChanged(newState, oldState)
     }
 
     backCallback =
         BackCallback(isEnabled = bt != null) {
-            bt?.invoke()?.also { navState ->
-                navigator.navigate(navState = navState)
-                onAfterNavigate(navState)
+            bt?.invoke()?.also { state ->
+                val oldState = navigator.navState
+                navigator.navigate(navState = state)
+                val newState = navigator.navState
+                onAfterNavigate(newState, oldState)
             }
         }
 
@@ -120,11 +124,11 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
 
     val eventObserver: (E) -> Unit =
         { event ->
-            val oldNavState = navigator.navState
+            val oldState = navigator.navState
             navigator.navigate(navState = navTransformer(navigator.navState, event))
-            val newNavState = navigator.navState
-            onAfterNavigate(newNavState)
-            onEventComplete(event, newNavState, oldNavState)
+            val newState = navigator.navState
+            onAfterNavigate(newState, oldState)
+            onEventComplete(event, newState, oldState)
         }
 
     source.subscribe(eventObserver)
@@ -135,6 +139,8 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
         mainBackHandler.unregister(backCallback)
     }
 
+    onStateChanged(navigator.navState, null)
+
     return state
 }
 
@@ -143,27 +149,28 @@ fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.child
  * so it's automatically saved and restored. This method can be used if the custom save/restore logic
  * is not required.
  */
-@ExperimentalDecomposeApi
 inline fun <C : Parcelable, T : Any, E : Any, reified N, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    noinline initialNavState: () -> N,
-    noinline navTransformer: (navState: N, event: E) -> N,
-    noinline onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    noinline backTransformer: (navState: N) -> (() -> N)?,
-    noinline stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    noinline initialState: () -> N,
+    noinline navTransformer: (state: N, event: E) -> N,
+    noinline stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    noinline onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    noinline onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    noinline backTransformer: (state: N) -> (() -> N)? = { null },
     noinline childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S> where N : NavState<C>, N : Parcelable =
     children(
         source = source,
         key = key,
-        initialNavState = initialNavState,
-        saveNavState = { ParcelableContainer(it) },
-        restoreNavState = { it.consumeRequired(N::class) },
+        initialState = initialState,
+        saveState = { ParcelableContainer(it) },
+        restoreState = { it.consumeRequired(N::class) },
         navTransformer = navTransformer,
+        stateMapper = stateMapper,
+        onStateChanged = onStateChanged,
         onEventComplete = onEventComplete,
         backTransformer = backTransformer,
-        stateMapper = stateMapper,
         childFactory = childFactory,
     )
 
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
index 709791bf..29307f4c 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenNavigator.kt
@@ -1,7 +1,6 @@
 package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.Child
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildItem.Created
 import com.arkivanov.decompose.router.children.ChildItem.Created.*
 import com.arkivanov.decompose.router.children.ChildNavState.Status
@@ -14,7 +13,6 @@ import com.arkivanov.essenty.lifecycle.resume
 import com.arkivanov.essenty.lifecycle.stop
 import com.arkivanov.essenty.parcelable.ParcelableContainer
 
-@OptIn(ExperimentalDecomposeApi::class)
 internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
     lifecycle: Lifecycle,
     retainedInstanceSupplier: (factory: () -> InstanceKeeper.Instance) -> InstanceKeeper.Instance,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
index 3b5a1f17..b69198cc 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents an entire navigation state.
  */
-@ExperimentalDecomposeApi
 interface NavState<out C : Any> {
 
     /**
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
index f77453a3..b0673402 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleChildNavState.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * A simple implementation of the [ChildNavState] interface.
  */
-@ExperimentalDecomposeApi
 data class SimpleChildNavState<out C : Any>(
     override val configuration: C,
     override val status: ChildNavState.Status,
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
index 1ee4ec6e..a2e5b710 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt
@@ -1,13 +1,11 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.Relay
 
 /**
  * A simple implementation of the [NavigationSource] interface.
  * Broadcasts navigation events to every subscribed observer.
  */
-@ExperimentalDecomposeApi
 class SimpleNavigation<T : Any> : NavigationSource<T> {
 
     private val relay = Relay<T>()
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
index 21ccaa4c..673705e9 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.overlay
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildNavState.Status
 import com.arkivanov.decompose.router.children.NavState
 import com.arkivanov.decompose.router.children.SimpleChildNavState
@@ -27,7 +26,6 @@ import kotlin.reflect.KClass
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildOverlay].
  */
-@OptIn(ExperimentalDecomposeApi::class)
 fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
     source: OverlayNavigationSource<C>,
     configurationClass: KClass<out C>,
@@ -61,7 +59,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildOverlay].
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> ComponentContext.childOverlay(
     source: OverlayNavigationSource<C>,
     saveConfiguration: (C?) -> ParcelableContainer?,
@@ -74,19 +71,19 @@ fun <C : Any, T : Any> ComponentContext.childOverlay(
     children(
         source = source,
         key = key,
-        initialNavState = { OverlayNavState(configuration = initialConfiguration()) },
-        saveNavState = { saveConfiguration(it.configuration) },
-        restoreNavState = { OverlayNavState(restoreConfiguration(it)) },
-        navTransformer = { navState, event -> OverlayNavState(configuration = event.transformer(navState.configuration)) },
-        onEventComplete = { event, newNavState, oldNavState -> event.onComplete(newNavState.configuration, oldNavState.configuration) },
-        backTransformer = { navState ->
-            if (handleBackButton && (navState.configuration != null)) {
+        initialState = { OverlayNavState(configuration = initialConfiguration()) },
+        saveState = { saveConfiguration(it.configuration) },
+        restoreState = { OverlayNavState(restoreConfiguration(it)) },
+        navTransformer = { state, event -> OverlayNavState(configuration = event.transformer(state.configuration)) },
+        stateMapper = { _, children -> ChildOverlay(overlay = children.firstOrNull() as? Child.Created?) },
+        onEventComplete = { event, newState, oldState -> event.onComplete(newState.configuration, oldState.configuration) },
+        backTransformer = { state ->
+            if (handleBackButton && (state.configuration != null)) {
                 { OverlayNavState(configuration = null) }
             } else {
                 null
             }
         },
-        stateMapper = { _, children -> ChildOverlay(overlay = children.firstOrNull() as? Child.Created?) },
         childFactory = childFactory,
     )
 
@@ -111,7 +108,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childOverlay(
         childFactory = childFactory,
     )
 
-@OptIn(ExperimentalDecomposeApi::class)
 private data class OverlayNavState<out C : Any>(
     val configuration: C?,
 ) : NavState<C> {
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 c200d23c..93e8d812 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
@@ -2,7 +2,6 @@ package com.arkivanov.decompose.router.stack
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.GettingList
 import com.arkivanov.decompose.router.children.ChildNavState.Status
 import com.arkivanov.decompose.router.children.NavState
@@ -94,7 +93,6 @@ fun <C : Parcelable, T : Any> ComponentContext.childStack(
  * @param childFactory a factory function that creates new child instances.
  * @return an observable [Value] of [ChildStack].
  */
-@OptIn(ExperimentalDecomposeApi::class)
 fun <C : Any, T : Any> ComponentContext.childStack(
     source: StackNavigationSource<C>,
     initialStack: () -> List<C>,
@@ -108,24 +106,24 @@ fun <C : Any, T : Any> ComponentContext.childStack(
     children(
         source = source,
         key = key,
-        initialNavState = {
+        initialState = {
             StackNavState(
                 configurations = initialStack(),
                 backStackCreateDepth = backStackCreateDepth,
             )
         },
-        saveNavState = { saveStack(it.configurations) },
-        restoreNavState = { container ->
+        saveState = { saveStack(it.configurations) },
+        restoreState = { container ->
             StackNavState(
                 configurations = restoreStack(container) ?: initialStack(),
                 backStackCreateDepth = backStackCreateDepth,
             )
         },
-        navTransformer = { navState, event ->
-            val newStack = event.transformer(navState.configurations)
+        navTransformer = { state, event ->
+            val newStack = event.transformer(state.configurations)
             check(newStack.isNotEmpty()) { "Configuration stack must not be empty" }
 
-            val oldStatuses = navState.children.associateBy(keySelector = { it.configuration }, valueTransform = { it.status })
+            val oldStatuses = state.children.associateBy(keySelector = { it.configuration }, valueTransform = { it.status })
 
             StackNavState(
                 active = newStack.last(),
@@ -145,27 +143,27 @@ fun <C : Any, T : Any> ComponentContext.childStack(
                     .withCreateDepth(createDepth = backStackCreateDepth),
             )
         },
-        onEventComplete = { event, newNavState, oldNavState ->
-            event.onComplete(newNavState.configurations, oldNavState.configurations)
+        stateMapper = { _, children ->
+            ChildStack(
+                active = children.last() as Child.Created,
+                backStack = children.dropLast(1),
+            )
+        },
+        onEventComplete = { event, newState, oldState ->
+            event.onComplete(newState.configurations, oldState.configurations)
         },
-        backTransformer = { navState ->
-            if (handleBackButton && navState.backStack.isNotEmpty()) {
+        backTransformer = { state ->
+            if (handleBackButton && state.backStack.isNotEmpty()) {
                 {
                     StackNavState(
-                        active = navState.backStack.last().configuration,
-                        backStack = navState.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
+                        active = state.backStack.last().configuration,
+                        backStack = state.backStack.dropLast(1).withCreateDepth(createDepth = backStackCreateDepth),
                     )
                 }
             } else {
                 null
             }
         },
-        stateMapper = { _, children ->
-            ChildStack(
-                active = children.last() as Child.Created,
-                backStack = children.dropLast(1),
-            )
-        },
         childFactory = childFactory,
     )
 
@@ -215,7 +213,6 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childStack(
         childFactory = childFactory,
     )
 
-@ExperimentalDecomposeApi
 private fun <C : Any> List<SimpleChildNavState<C>>.withCreateDepth(createDepth: Int): List<SimpleChildNavState<C>> =
     if (isCreateRequired(createDepth = createDepth)) {
         mapIndexed { index, item ->
@@ -229,7 +226,6 @@ private fun <C : Any> List<SimpleChildNavState<C>>.withCreateDepth(createDepth:
         this
     }
 
-@ExperimentalDecomposeApi
 private fun List<SimpleChildNavState<*>>.isCreateRequired(createDepth: Int): Boolean {
     for (i in lastIndex downTo max(lastIndex - createDepth + 1, 0)) {
         if (get(i).status == Status.DESTROYED) {
@@ -240,7 +236,6 @@ private fun List<SimpleChildNavState<*>>.isCreateRequired(createDepth: Int): Boo
     return false
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 private data class StackNavState<out C : Any>(
     val active: C,
     val backStack: List<SimpleChildNavState<C>>,
@@ -259,7 +254,6 @@ private data class StackNavState<out C : Any>(
         }
 }
 
-@ExperimentalDecomposeApi
 private fun <C : Any> StackNavState(configurations: List<C>, backStackCreateDepth: Int): StackNavState<C> {
     require(configurations.isNotEmpty()) { "Configuration stack must not be empty" }
 
diff --git a/deps.versions.toml b/deps.versions.toml
index 79db0b2a..e2c42b29 100644
--- a/deps.versions.toml
+++ b/deps.versions.toml
@@ -1,8 +1,8 @@
 [versions]
 
-decompose = "1.0.0-beta-04"
+decompose = "1.0.0"
 kotlin = "1.8.0"
-essenty = "0.10.0"
+essenty = "1.0.0"
 parcelizeDarwin = "0.1.3"
 reaktive = "1.2.3"
 junit = "4.13.2"
@@ -19,6 +19,7 @@ androidxCore = "1.9.0"
 androidxAppcompat = "1.6.0"
 androidxLifecycle = "2.5.1"
 androidxActivity = "1.6.1"
+androidxFragment = "1.1.0"
 androidxTestCore = "1.5.0"
 
 [libraries]
@@ -58,4 +59,5 @@ androidx-appcompat-appcompat  = { group = "androidx.appcompat", name = "appcompa
 androidx-lifecycle-lifecycleCommonJava8 = { group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref = "androidxLifecycle" }
 androidx-activity-activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidxActivity" }
 androidx-activity-activityCompose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
+androidx-fragment-fragmentKtx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "androidxFragment" }
 androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
diff --git a/docs/component/overview.md b/docs/component/overview.md
index 52fb5121..70f1df76 100644
--- a/docs/component/overview.md
+++ b/docs/component/overview.md
@@ -51,20 +51,22 @@ Decompose provides a few handy [helper functions](https://github.com/arkivanov/D
 
 For this case Decompose provides `defaultComponentContext()` extension function, which can be called in scope of an `Activity`.
 
-#### Root ComponentContext in Fragment
+```kotlin
+class MainActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
 
-The `defaultComponentContext()` extension function can not be used in a Fragment. This is because the `Fragment` class does not implement the `OnBackPressedDispatcherOwner` interface, and so by default can't handle back button events. It is advised to use the Android-specific `DefaultComponentContext(AndroidLifecycle, SavedStateRegistry?, ViewModelStore?, OnBackPressedDispatcher?)` factory function, and supply all the arguments manually. The first three arguments (`AndroidLifecycle`, `SavedStateRegistry` and `ViewModelStore`) can be obtained directly from `Fragment`. However the last argument `OnBackPressedDispatcher` - can not. If you don't need to handle back button events in your Decompose components, then you can just ignore this argument. Otherwise, a manual solution is required.
+        val root = DefaultRootComponent(defaultComponentContext())
+    }
+}
+```
 
-!!!warning
-    Don't take any argument values from the hosting `Activity` (e.g. `requireActivity().onBackPressedDispatcher`), as it may produce memory leaks.
+#### Root ComponentContext in Fragment
 
-Here is an example with using Decompose in a `DialogFragment`.
+Use `defaultComponentContext(OnBackPressedDispatcher?)` extension function, which can be called in scope of `Fragment`.
 
 ```kotlin
-class MyFragment : DialogFragment() {
-    // Create custom OnBackPressedDispatcher
-    private val onBackPressedDispatcher = OnBackPressedDispatcher(::dismiss)
-
+class SomeFragment : Fragment() {
     private lateinit var root: RootComponent
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -72,24 +74,11 @@ class MyFragment : DialogFragment() {
 
         root =
             DefaultRootComponent(
-                DefaultComponentContext(
-                    lifecycle = lifecycle,
-                    savedStateRegistry = savedStateRegistry,
-                    viewModelStore = viewModelStore,
-                    onBackPressedDispatcher = onBackPressedDispatcher,
+                componentContext = defaultComponentContext(
+                    onBackPressedDispatcher = requireActivity().onBackPressedDispatcher
                 )
             )
     }
-
-    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
-        object : Dialog(requireContext(), theme) {
-            override fun onBackPressed() {
-                onBackPressedDispatcher.onBackPressed()
-            }
-        }
-
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
-        // Start Compose here
 }
 ```
 
diff --git a/docs/extensions/compose.md b/docs/extensions/compose.md
index 8daffcdb..009c88e7 100644
--- a/docs/extensions/compose.md
+++ b/docs/extensions/compose.md
@@ -140,9 +140,9 @@ fun DetailsContent(component: DetailsComponent) {
 }
 ```
 
-### Animations (experimental)
+### Animations
 
-Decompose provides the [Child Animation API](https://github.com/arkivanov/Decompose/tree/master/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/animation/child) for Compose, as well as some predefined animation specs. To enable child animations you need to pass the `animation` argument to the `Children` function. There are predefined animators provided by Decompose.
+Decompose provides [Child Animation API](https://github.com/arkivanov/Decompose/tree/master/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/animation/child) for Compose, as well as some predefined animation specs. To enable child animations you need to pass the `animation` argument to the `Children` function. There are predefined animators provided by Decompose.
 
 #### Fade animation
 
@@ -286,23 +286,23 @@ fun someAnimator(): StackAnimator =
 
 Please refer to the predefined animators (`fade`, `slide`, etc.) for implementation examples.
 
-## Compose for iOS and macOS
+## Compose for iOS, macOS and Web (Canvas)
 
-Compose for iOS and macOS is still work in progress and was not oficially announced. However, Decompose already supports it. The support is also experimental and is not part of the main branch - see [#74](https://github.com/arkivanov/Decompose/issues/74) for more information.
+Compose for iOS, macOS and Web (Canvas) is still work in progress and was not officially announced. However, Decompose already supports it. The support is also **experimental** and is not part of the main branch - see [#74](https://github.com/arkivanov/Decompose/issues/74) for more information.
 
-If you want to use Decompose with Compose for iOS/macOS, you have to use special versions of `extensions-compose-jetbrains` module.
+If you want to use Decompose with Compose for iOS/macOS/Web, you have to use special versions of `extensions-compose-jetbrains` module.
 
 === "Groovy"
 
     ``` groovy
-    implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>-native-compose"
+    implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental"
     ```
 
 === "Kotlin"
 
     ``` kotlin
-    implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>-native-compose")
+    implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental")
 
 ### Samples
 
-You can find samples in a separate branch - [compose-darwin/sample/app-darwin-compose](https://github.com/arkivanov/Decompose/tree/compose-darwin/sample/app-darwin-compose).
+You can find samples in a separate branch - [compose-darwin/sample/app-darwin-compose](https://github.com/arkivanov/Decompose/tree/compose-experimental/sample).
diff --git a/docs/navigation/children/overview.md b/docs/navigation/children/overview.md
index 15ea0261..20a2421f 100644
--- a/docs/navigation/children/overview.md
+++ b/docs/navigation/children/overview.md
@@ -30,13 +30,14 @@ Using the `Generic Navigation` is pretty similar to any other navigation model,
 fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.children(
     source: NavigationSource<E>,
     key: String,
-    initialNavState: () -> N,
-    saveNavState: (navState: N) -> ParcelableContainer,
-    restoreNavState: (container: ParcelableContainer) -> N,
-    navTransformer: (navState: N, event: E) -> N,
-    onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit,
-    backTransformer: (navState: N) -> (() -> N)?,
-    stateMapper: (navState: N, children: List<Child<C, T>>) -> S,
+    initialState: () -> N,
+    saveState: (state: N) -> ParcelableContainer?,
+    restoreState: (container: ParcelableContainer) -> N?,
+    navTransformer: (state: N, event: E) -> N,
+    stateMapper: (state: N, children: List<Child<C, T>>) -> S,
+    onStateChanged: (newState: N, oldState: N?) -> Unit = { _, _ -> },
+    onEventComplete: (event: E, newState: N, oldState: N) -> Unit = { _, _, _ -> },
+    backTransformer: (state: N) -> (() -> N)? = { null },
     childFactory: (configuration: C, componentContext: ComponentContext) -> T,
 ): Value<S>
 ```
@@ -53,13 +54,14 @@ The `children` function accepts the following arguments:
 
 - `source: NavigationSource<E>` - an observable source of navigation events, the `Generic Navigation` subscribes to the source and performs the navigation. The [SimpleNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/SimpleNavigation.kt) class can be used in simple cases when custom implementation is not required.
 - `key: String` - a key of the navigation, must be unique if there are multiple `children` used in the same component.
-- `initialNavState: () -> N` - an initial navigation state that should be used if there is no previously saved state.
-- `saveNavState: (navState: N) -> ParcelableContainer` - a function that saves the provided navigation state into `ParcelableContainer`, called when the hosting component goes to background.
-- `restoreNavState: (container: ParcelableContainer) -> N` - a function that restores the navigation state from the provided `ParcelableContainer`. The restored navigation state must have the same amount of child configurations and in the same order. The restored child `Statuses` can be any, e.g. a previously active child may become destroyed, etc.
-- `navTransformer: (navState: N, event: E) -> N` - a function that transforms the current navigation state to a new one using the provided navigation event. The implementation diffs both navigation states and manipulates child components as needed.
-- `onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit` - called when a navigation event is processed and the navigation completed.
-- `backTransformer: (navState: N) -> (() -> N)?` - a function that checks the provided navigation state, and either returns another function transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called during the initialisation and after each navigation event.
-- `stateMapper: (navState: N, children: List<Child<C, T>>) -> S` - combines the provided navigation state and list of child components to a resulting custom state.
+- `initialState: () -> N` - an initial navigation state that should be used if there is no previously saved state.
+- `saveState: (state: N) -> ParcelableContainer` - a function that saves the provided navigation state into `ParcelableContainer`, called when the hosting component goes to background.
+- `restoreState: (container: ParcelableContainer) -> N` - a function that restores the navigation state from the provided `ParcelableContainer`. The restored navigation state must have the same amount of child configurations and in the same order. The restored child `Statuses` can be any, e.g. a previously active child may become destroyed, etc.
+- `navTransformer: (state: N, event: E) -> N` - a function that transforms the current navigation state to a new one using the provided navigation event. The implementation diffs both navigation states and manipulates child components as needed.
+- `stateMapper: (state: N, children: List<Child<C, T>>) -> S` - combines the provided navigation state and list of child components to a resulting custom state.
+- `onStateChanged: (newState: N, oldState: N?) -> Unit` - called every time the navigation state changes, `oldState` is `null` when called first time during initialisation. 
+- `onEventComplete: (event: E, newState: N, oldState: N) -> Unit` - called when a navigation event is processed and the navigation completed.
+- `backTransformer: (state: N) -> (() -> N)?` - a function that checks the provided navigation state, and either returns another function transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called during the initialisation and after each navigation event.
 - `childFactory: (configuration: C, componentContext: ComponentContext) -> T` - childFactory a factory function that creates new child component instances.
 
 The `children` function returns an observable `Value` of the resulting children state.
diff --git a/extensions-android/api/extensions-android.api b/extensions-android/api/extensions-android.api
index d58b0a6d..5d2e6faa 100644
--- a/extensions-android/api/extensions-android.api
+++ b/extensions-android/api/extensions-android.api
@@ -11,8 +11,7 @@ public final class com/arkivanov/decompose/extensions/android/DefaultViewContext
 	public fun getParent ()Landroid/view/ViewGroup;
 }
 
-public abstract interface class com/arkivanov/decompose/extensions/android/ViewContext {
-	public abstract fun getLifecycle ()Lcom/arkivanov/essenty/lifecycle/Lifecycle;
+public abstract interface class com/arkivanov/decompose/extensions/android/ViewContext : com/arkivanov/essenty/lifecycle/LifecycleOwner {
 	public abstract fun getParent ()Landroid/view/ViewGroup;
 }
 
diff --git a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/ViewContext.kt b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/ViewContext.kt
index 08e256a1..8b6c1080 100644
--- a/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/ViewContext.kt
+++ b/extensions-android/src/main/java/com/arkivanov/decompose/extensions/android/ViewContext.kt
@@ -2,11 +2,10 @@ package com.arkivanov.decompose.extensions.android
 
 import android.view.ViewGroup
 import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.LifecycleOwner
 
 @ExperimentalDecomposeApi
-interface ViewContext {
+interface ViewContext : LifecycleOwner {
 
     val parent: ViewGroup
-    val lifecycle: Lifecycle
 }
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 e60afdcc..a34c28d1 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
@@ -7,14 +7,12 @@ 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.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.emptyStackAnimation
 import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
 import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.Value
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <C : Any, T : Any> Children(
     stack: ChildStack<C, T>,
@@ -35,7 +33,6 @@ fun <C : Any, T : Any> Children(
     }
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <C : Any, T : Any> Children(
     stack: Value<ChildStack<C, T>>,
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 70d26106..9e92fa5e 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
@@ -1,5 +1,3 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
 import androidx.compose.foundation.layout.Box
@@ -15,10 +13,8 @@ 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.stack.ChildStack
 
-@ExperimentalDecomposeApi
 internal class DefaultStackAnimation<C : Any, T : Any>(
     private val disableInputDuringAnimation: Boolean,
     private val selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
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 b4b6ed1e..1ceb38be 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
@@ -5,9 +5,7 @@ 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 DefaultStackAnimator(
     private val animationSpec: FiniteAnimationSpec<Float> = tween(),
     private val frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit
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 834049ab..a3506710 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
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents a direction in which child widgets are animated.
  */
-@ExperimentalDecomposeApi
 enum class Direction {
 
     /**
@@ -29,30 +26,28 @@ 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/EmptyStackAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/EmptyStackAnimation.kt
index dbb589f8..aa5090e0 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/EmptyStackAnimation.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/EmptyStackAnimation.kt
@@ -1,9 +1,7 @@
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
 import androidx.compose.foundation.layout.Box
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 
-@ExperimentalDecomposeApi
 internal fun <C : Any, T : Any> emptyStackAnimation(): StackAnimation<C, T> =
     StackAnimation { stack, modifier, childContent ->
         Box(modifier = modifier) {
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Fade.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Fade.kt
index 715006eb..29823093 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Fade.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Fade.kt
@@ -4,14 +4,12 @@ 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 [minAlpha] to 1.0.
  * Disappearing children's `alpha` is animated from 1.0 to [minAlpha].
  */
-@ExperimentalDecomposeApi
 fun fade(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     minAlpha: Float = 0F,
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Scale.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Scale.kt
index 7bb26533..31fa790e 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Scale.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Scale.kt
@@ -4,13 +4,11 @@ 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].
  */
-@ExperimentalDecomposeApi
 fun scale(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     frontFactor: Float = 1.15F,
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Slide.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Slide.kt
index 2c7a490e..95013a11 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Slide.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/Slide.kt
@@ -4,12 +4,10 @@ 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.
  */
-@ExperimentalDecomposeApi
 fun slide(animationSpec: FiniteAnimationSpec<Float> = tween()): StackAnimator =
     stackAnimator(animationSpec = animationSpec) { factor, _, content ->
         content(Modifier.offsetXFactor(factor = factor))
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 343aa1a6..a0a89ef9 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
@@ -1,17 +1,13 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
 package com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation
 
 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.stack.ChildStack
 
 /**
  * Tracks the [ChildStack] changes and animates between child widget.
  */
-@ExperimentalDecomposeApi
 fun interface StackAnimation<C : Any, T : Any> {
 
     @Composable
@@ -28,7 +24,6 @@ fun interface StackAnimation<C : Any, T : Any> {
  * @param disableInputDuringAnimation disables input and touch events while animating, default value is `true`.
  * @param selector provides a [StackAnimator] for current [Child], other [Child] and [Direction].
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(
     disableInputDuringAnimation: Boolean = true,
     selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
@@ -44,7 +39,6 @@ fun <C : Any, T : Any> stackAnimation(
  * @param animator a [StackAnimator] to be used for animation, default is [fade].
  * @param disableInputDuringAnimation disables input and touch events while animating, default value is `true`.
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(
     animator: StackAnimator = fade(),
     disableInputDuringAnimation: Boolean = true,
diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator.kt
index a779ce42..a2fc03b7 100644
--- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator.kt
+++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimator.kt
@@ -5,12 +5,10 @@ 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].
  */
-@ExperimentalDecomposeApi
 fun interface StackAnimator {
 
     /**
@@ -30,13 +28,11 @@ fun interface StackAnimator {
  * @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]
  */
-@ExperimentalDecomposeApi
 fun stackAnimator(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit,
@@ -49,7 +45,6 @@ fun stackAnimator(
 /**
  * Combines (merges) the receiver [StackAnimator] with the [other] [StackAnimator].
  */
-@ExperimentalDecomposeApi
 operator fun StackAnimator.plus(other: StackAnimator): StackAnimator =
     StackAnimator { direction, onFinished, content ->
         val finished = remember(direction) { BooleanArray(2) }
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/Children.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/Children.kt
index b1258a9a..4406a414 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/Children.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/Children.kt
@@ -8,14 +8,12 @@ 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.jetpack.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.emptyStackAnimation
 import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
 import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.Value
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <C : Any, T : Any> Children(
     stack: ChildStack<C, T>,
@@ -36,7 +34,6 @@ fun <C : Any, T : Any> Children(
     }
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <C : Any, T : Any> Children(
     stack: Value<ChildStack<C, T>>,
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimation.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimation.kt
index eca68923..09f57287 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimation.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimation.kt
@@ -1,5 +1,3 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
 package com.arkivanov.decompose.extensions.compose.jetpack.stack.animation
 
 import androidx.compose.foundation.layout.Box
@@ -15,10 +13,8 @@ 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.stack.ChildStack
 
-@ExperimentalDecomposeApi
 internal class DefaultStackAnimation<C : Any, T : Any>(
     private val disableInputDuringAnimation: Boolean,
     private val selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
@@ -45,6 +41,7 @@ internal class DefaultStackAnimation<C : Any, T : Any>(
                                 when (direction) {
                                     Direction.EXIT_FRONT,
                                     Direction.EXIT_BACK -> items - configuration
+
                                     Direction.ENTER_FRONT,
                                     Direction.ENTER_BACK -> items + (configuration to AnimationItem.Single(item.child))
                                 }
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimator.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimator.kt
index 54b77ca8..5190d7f2 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimator.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/DefaultStackAnimator.kt
@@ -9,9 +9,7 @@ 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 DefaultStackAnimator(
     private val animationSpec: FiniteAnimationSpec<Float> = tween(),
     private val frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Direction.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Direction.kt
index 390fb31a..f24d7706 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Direction.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Direction.kt
@@ -1,11 +1,8 @@
 package com.arkivanov.decompose.extensions.compose.jetpack.stack.animation
 
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-
 /**
  * Represents a direction in which child widgets are animated.
  */
-@ExperimentalDecomposeApi
 enum class Direction {
 
     /**
@@ -29,30 +26,28 @@ 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-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/EmptyStackAnimation.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/EmptyStackAnimation.kt
index 3c3ac946..e8cee56b 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/EmptyStackAnimation.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/EmptyStackAnimation.kt
@@ -1,9 +1,7 @@
 package com.arkivanov.decompose.extensions.compose.jetpack.stack.animation
 
 import androidx.compose.foundation.layout.Box
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 
-@ExperimentalDecomposeApi
 internal fun <C : Any, T : Any> emptyStackAnimation(): StackAnimation<C, T> =
     StackAnimation { stack, modifier, childContent ->
         Box(modifier = modifier) {
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Fade.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Fade.kt
index 019be73c..6700306c 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Fade.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Fade.kt
@@ -4,14 +4,12 @@ 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 [minAlpha] to 1.0.
  * Disappearing children's `alpha` is animated from 1.0 to [minAlpha].
  */
-@ExperimentalDecomposeApi
 fun fade(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     minAlpha: Float = 0F,
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Scale.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Scale.kt
index 0b4bd2c2..ce7c3b83 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Scale.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Scale.kt
@@ -4,13 +4,11 @@ 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].
  */
-@ExperimentalDecomposeApi
 fun scale(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     frontFactor: Float = 1.15F,
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Slide.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Slide.kt
index 8c90c9ab..ceca3ddb 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Slide.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/Slide.kt
@@ -4,12 +4,10 @@ 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.
  */
-@ExperimentalDecomposeApi
 fun slide(animationSpec: FiniteAnimationSpec<Float> = tween()): StackAnimator =
     stackAnimator(animationSpec = animationSpec) { factor, _, content ->
         content(Modifier.offsetXFactor(factor = factor))
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimation.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimation.kt
index c0fb5d27..3f99416b 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimation.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimation.kt
@@ -1,17 +1,13 @@
-@file:OptIn(ExperimentalDecomposeApi::class)
-
 package com.arkivanov.decompose.extensions.compose.jetpack.stack.animation
 
 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.stack.ChildStack
 
 /**
  * Tracks the [ChildStack] changes and animates between child widget.
  */
-@ExperimentalDecomposeApi
 fun interface StackAnimation<C : Any, T : Any> {
 
     @Composable
@@ -28,7 +24,6 @@ fun interface StackAnimation<C : Any, T : Any> {
  * @param disableInputDuringAnimation disables input and touch events while animating, default value is `true`.
  * @param selector provides a [StackAnimator] for current [Child], other [Child] and [Direction].
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(
     disableInputDuringAnimation: Boolean = true,
     selector: (child: Child.Created<C, T>, otherChild: Child.Created<C, T>, direction: Direction) -> StackAnimator?,
@@ -44,7 +39,6 @@ fun <C : Any, T : Any> stackAnimation(
  * @param animator a [StackAnimator] to be used for animation, default is [fade].
  * @param disableInputDuringAnimation disables input and touch events while animating, default value is `true`.
  */
-@ExperimentalDecomposeApi
 fun <C : Any, T : Any> stackAnimation(
     animator: StackAnimator = fade(),
     disableInputDuringAnimation: Boolean = true,
diff --git a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimator.kt b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimator.kt
index af34afc6..0a55ffb0 100644
--- a/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimator.kt
+++ b/extensions-compose-jetpack/src/main/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/animation/StackAnimator.kt
@@ -5,12 +5,10 @@ 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].
  */
-@ExperimentalDecomposeApi
 fun interface StackAnimator {
 
     /**
@@ -36,7 +34,6 @@ fun interface StackAnimator {
  * - From -1F to 0F for [Direction.ENTER_BACK]
  * - From 0F to -1F for [Direction.EXIT_BACK]
  */
-@ExperimentalDecomposeApi
 fun stackAnimator(
     animationSpec: FiniteAnimationSpec<Float> = tween(),
     frame: @Composable (factor: Float, direction: Direction, content: @Composable (Modifier) -> Unit) -> Unit,
@@ -49,7 +46,6 @@ fun stackAnimator(
 /**
  * Combines (merges) the receiver [StackAnimator] with the [other] [StackAnimator].
  */
-@ExperimentalDecomposeApi
 operator fun StackAnimator.plus(other: StackAnimator): StackAnimator =
     StackAnimator { direction, onFinished, content ->
         val finished = remember(direction) { BooleanArray(2) }
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 229dfbfa..185d4cf0 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
@@ -10,7 +10,6 @@ import androidx.compose.ui.Modifier
 import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.defaultComponentContext
 import com.arkivanov.decompose.extensions.android.DefaultViewContext
-import com.arkivanov.decompose.extensions.android.child
 import com.arkivanov.essenty.lifecycle.essentyLifecycle
 import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DefaultFeatureInstaller
 import com.arkivanov.sample.shared.root.RootComponent
@@ -58,9 +57,7 @@ class MainActivity : AppCompatActivity() {
             )
 
         viewContext.apply {
-            child(parent) {
-                RootView(root)
-            }
+            parent.addView(RootView(root))
         }
     }
 
diff --git a/sample/app-ios/app-ios/MultiPaneView.swift b/sample/app-ios/app-ios/MultiPaneView.swift
index b46c4114..39bced19 100644
--- a/sample/app-ios/app-ios/MultiPaneView.swift
+++ b/sample/app-ios/app-ios/MultiPaneView.swift
@@ -15,75 +15,70 @@ struct MultiPaneView: View {
     private let component: MultiPaneComponent
     
     @ObservedObject
-    private var observableModel: ObservableValue<MultiPaneComponentModel>
+    private var children: ObservableValue<MultiPaneComponentChildren>
     
-    @ObservedObject
-    private var listChildStack: ObservableValue<ChildStack<AnyObject, MultiPaneComponentListChild>>
-    
-    @ObservedObject
-    private var detailsChildStack: ObservableValue<ChildStack<AnyObject, MultiPaneComponentDetailsChild>>
-    
-    private var model: MultiPaneComponentModel { observableModel.value }
-    private var activeListChild: MultiPaneComponentListChild { listChildStack.value.active.instance }
-    private var activeDetailsChild: MultiPaneComponentDetailsChild { detailsChildStack.value.active.instance }
+    private var isMultiPane: Bool { children.value.isMultiPane }
+    private var listComponent: ArticleListComponent { children.value.listChild.instance }
+    private var detailsComponent: ArticleDetailsComponent? { children.value.detailsChild?.instance }
     
     init(_ component: MultiPaneComponent) {
         self.component = component
-        observableModel = ObservableValue(component.models)
-        listChildStack = ObservableValue(component.listChildStack)
-        detailsChildStack = ObservableValue(component.detailsChildStack)
+        children = ObservableValue(component.children)
     }
     
     var body: some View {
         ZStack(alignment: .top) {
-            ListPane(listChild: activeListChild, isMultiPane: model.isMultiPane)
-            DetailsPane(detailsChild: activeDetailsChild, isMultiPane: model.isMultiPane)
+            ListPane(
+                listComponent: listComponent,
+                isMultiPane: isMultiPane,
+                isVisible: isMultiPane || (detailsComponent == nil)
+            )
+            
+            DetailsPane(
+                detailsComponent: detailsComponent,
+                isMultiPane: isMultiPane
+            )
         }.onAppear { component.setMultiPane(isMultiPane: deviceRequiresMultiPane()) }
     }
 }
 
 struct ListPane: View {
-    let listChild: MultiPaneComponentListChild
+    let listComponent: ArticleListComponent
     let isMultiPane: Bool
+    let isVisible: Bool
     
     var body: some View {
-        switch listChild {
-        case let list as MultiPaneComponentListChild.List:
-            GeometryReader { metrics in
-                HStack {
-                    ArticleListView(list.component)
-                        .frame(width: isMultiPane ? metrics.size.width * listPaneWeight : metrics.size.width)
-                    
-                    if isMultiPane {
-                        Spacer().frame(width: metrics.size.width * detailsPaneWeight)
-                    }
+        GeometryReader { metrics in
+            HStack {
+                ArticleListView(listComponent)
+                    .frame(width: isMultiPane ? metrics.size.width * listPaneWeight : metrics.size.width)
+                
+                if isMultiPane {
+                    Spacer().frame(width: metrics.size.width * detailsPaneWeight)
                 }
             }
-            
-        default: EmptyView()
-        }
+        }.opacity(isVisible ? 1 : 0)
     }
 }
 
 struct DetailsPane: View {
-    let detailsChild: MultiPaneComponentDetailsChild
+    let detailsComponent: ArticleDetailsComponent?
     let isMultiPane: Bool
     
     var body: some View {
-        switch detailsChild {
-        case let details as MultiPaneComponentDetailsChild.Details:
+        if (detailsComponent != nil) {
             GeometryReader { metrics in
                 HStack {
                     if isMultiPane {
                         Spacer().frame(width: metrics.size.width * listPaneWeight)
                     }
                     
-                    ArticleDetailsView(details.component)
+                    ArticleDetailsView(detailsComponent!)
                         .frame(width: isMultiPane ? metrics.size.width * detailsPaneWeight : metrics.size.width)
                 }
             }
-            
-        default: EmptyView()
+        } else {
+            EmptyView()
         }
     }
 }
@@ -99,13 +94,15 @@ struct MultiPaneView_Previews: PreviewProvider {
 }
 
 class PreviewMultiPaneComponent: MultiPaneComponent {
-    var listChildStack: Value<ChildStack<AnyObject, MultiPaneComponentListChild>> =
-        simpleChildStack(.List(component: PreviewArticleListComponent()))
-
-    var detailsChildStack: Value<ChildStack<AnyObject, MultiPaneComponentDetailsChild>> =
-        simpleChildStack(.Details(component: PreviewArticleDetailsComponent()))
-
-    var models: Value<MultiPaneComponentModel> = mutableValue(MultiPaneComponentModel(isMultiPane: true))
-
+    var listComponent: ArticleListComponent = PreviewArticleListComponent()
+    
+    var children: Value<MultiPaneComponentChildren> = mutableValue(
+        MultiPaneComponentChildren(
+            isMultiPane: false,
+            listChild: ChildCreated(configuration: "list" as AnyObject, instance: PreviewArticleListComponent()),
+            detailsChild: nil
+        )
+    )
+    
     func setMultiPane(isMultiPane: Bool) {}
 }
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/CountersContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/CountersContent.kt
index bcae4de8..57e0f09d 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/CountersContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/counters/CountersContent.kt
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.material.MaterialTheme
 import androidx.compose.runtime.Composable
 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.fade
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.isFront
@@ -21,7 +20,6 @@ import com.arkivanov.sample.shared.counters.counter.CounterComponent
 import com.arkivanov.sample.shared.counters.counter.CounterContent
 import com.arkivanov.sample.shared.counters.counter.PreviewCounterComponent
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 internal fun CountersContent(component: CountersComponent, modifier: Modifier = Modifier) {
     Children(
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/DynamicFeaturesContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/DynamicFeaturesContent.kt
index 79614a95..cfcc3df3 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/DynamicFeaturesContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/DynamicFeaturesContent.kt
@@ -3,7 +3,6 @@ package com.arkivanov.sample.shared.dynamicfeatures
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 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.fade
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation
@@ -13,7 +12,6 @@ import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DynamicFeature
 import com.arkivanov.sample.shared.dynamicfeatures.feature1.feature1Content
 import com.arkivanov.sample.shared.dynamicfeatures.feature2.feature2Content
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 internal fun DynamicFeaturesContent(component: DynamicFeaturesComponent, modifier: Modifier = Modifier) {
     Children(
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/dynamicfeature/DynamicFeatureContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/dynamicfeature/DynamicFeatureContent.kt
index e3a4dce7..ea0b0859 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/dynamicfeature/DynamicFeatureContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/dynamicfeatures/dynamicfeature/DynamicFeatureContent.kt
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 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.fade
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation
@@ -16,7 +15,6 @@ import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DynamicFeature
 import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DynamicFeatureComponent.Child.FeatureChild
 import com.arkivanov.sample.shared.dynamicfeatures.dynamicfeature.DynamicFeatureComponent.Child.LoadingChild
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun <T : Any> DynamicFeatureContent(
     component: DynamicFeatureComponent<T>,
diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
index 09a76ba1..54154a06 100644
--- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
+++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
@@ -3,57 +3,80 @@ package com.arkivanov.sample.shared.multipane
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.SaveableStateHolder
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.extensions.compose.jetbrains.stack.Children
-import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade
-import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation
+import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.value.Value
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.DetailsChild
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.ListChild
+import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent
 import com.arkivanov.sample.shared.multipane.details.ArticleDetailsContent
+import com.arkivanov.sample.shared.multipane.list.ArticleListComponent
 import com.arkivanov.sample.shared.multipane.list.ArticleListContent
 
-private val MULTI_PANE_WIDTH_THRESHOLD = 800.dp
-private const val LIST_PANE_WEIGHT = 0.4F
-private const val DETAILS_PANE_WEIGHT = 0.6F
-
 @Composable
 internal fun MultiPaneContent(component: MultiPaneComponent, modifier: Modifier = Modifier) {
-    BoxWithConstraints(modifier = modifier) {
-        val model by component.models.subscribeAsState()
-        val isMultiPane = model.isMultiPane
+    val children by component.children.subscribeAsState()
+    val listChild = children.listChild
+    val detailsChild = children.detailsChild
 
-        Row(modifier = Modifier.fillMaxSize()) {
-            ListPane(
-                stack = component.listChildStack,
-                modifier = Modifier.weight(if (isMultiPane) LIST_PANE_WEIGHT else 1F),
-            )
+    val saveableStateHolder = rememberSaveableStateHolder()
 
-            if (isMultiPane) {
-                Box(modifier = Modifier.weight(DETAILS_PANE_WEIGHT))
+    val listPane: @Composable (Child.Created<*, ArticleListComponent>) -> Unit =
+        remember {
+            movableContentOf { (config, component) ->
+                saveableStateHolder.SaveableStateProvider(key = config) {
+                    ArticleListContent(
+                        component = component,
+                        modifier = Modifier.fillMaxSize(),
+                    )
+                }
             }
         }
 
-        Row(modifier = Modifier.fillMaxSize()) {
-            if (isMultiPane) {
-                Box(modifier = Modifier.weight(LIST_PANE_WEIGHT))
+    val detailsPane: @Composable (Child.Created<*, ArticleDetailsComponent>) -> Unit =
+        remember {
+            movableContentOf { (config, component) ->
+                saveableStateHolder.SaveableStateProvider(key = config.hashCode()) {
+                    ArticleDetailsContent(
+                        component = component,
+                        modifier = Modifier.fillMaxSize(),
+                    )
+                }
             }
+        }
+
+    saveableStateHolder.OldDetailsKeyRemoved(selectedDetailsKey = children.detailsChild?.configuration?.hashCode())
+
+    BoxWithConstraints(modifier = modifier) {
+        when {
+            children.isMultiPane ->
+                Row(modifier = Modifier.fillMaxSize()) {
+                    Box(modifier = Modifier.fillMaxHeight().weight(0.4F)) {
+                        listPane(children.listChild)
+                    }
+
+                    Box(modifier = Modifier.fillMaxHeight().weight(0.6F)) {
+                        children.detailsChild?.also {
+                            detailsPane(it)
+                        }
+                    }
+                }
 
-            DetailsPane(
-                stack = component.detailsChildStack,
-                modifier = Modifier.weight(if (isMultiPane) DETAILS_PANE_WEIGHT else 1F),
-            )
+            detailsChild != null -> detailsPane(detailsChild)
+            else -> listPane(listChild)
         }
 
-        val isMultiPaneRequired = this@BoxWithConstraints.maxWidth >= MULTI_PANE_WIDTH_THRESHOLD
+        val isMultiPaneRequired = this@BoxWithConstraints.maxWidth >= 800.dp
 
         DisposableEffect(isMultiPaneRequired) {
             component.setMultiPane(isMultiPaneRequired)
@@ -62,32 +85,23 @@ internal fun MultiPaneContent(component: MultiPaneComponent, modifier: Modifier
     }
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
-private fun ListPane(stack: Value<ChildStack<*, ListChild>>, modifier: Modifier) {
-    Children(
-        stack = stack,
-        modifier = modifier,
-        animation = stackAnimation(fade()),
-    ) {
-        when (val child = it.instance) {
-            is ListChild.List -> ArticleListContent(component = child.component, modifier = Modifier.fillMaxSize())
-            is ListChild.None -> Box {}
-        }.let {}
+private fun SaveableStateHolder.OldDetailsKeyRemoved(selectedDetailsKey: Any?) {
+    var lastDetailsKey by remember { mutableStateOf(selectedDetailsKey) }
+
+    if (selectedDetailsKey == lastDetailsKey) {
+        return
     }
-}
 
-@OptIn(ExperimentalDecomposeApi::class)
-@Composable
-private fun DetailsPane(stack: Value<ChildStack<*, DetailsChild>>, modifier: Modifier) {
-    Children(
-        stack = stack,
-        modifier = modifier,
-        animation = stackAnimation(fade()),
-    ) {
-        when (val child = it.instance) {
-            is DetailsChild.None -> Box {}
-            is DetailsChild.Details -> ArticleDetailsContent(component = child.component, modifier = Modifier.fillMaxSize())
-        }.let {}
+    val keyToRemove = lastDetailsKey
+    lastDetailsKey = selectedDetailsKey
+
+    if (keyToRemove == null) {
+        return
+    }
+
+    DisposableEffect(keyToRemove) {
+        removeState(key = keyToRemove)
+        onDispose {}
     }
 }
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 5634aec8..2b518a2f 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
@@ -16,7 +16,6 @@ import androidx.compose.material.icons.filled.Refresh
 import androidx.compose.runtime.Composable
 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
@@ -40,7 +39,6 @@ import com.arkivanov.sample.shared.root.RootComponent.Child.CustomNavigationChil
 import com.arkivanov.sample.shared.root.RootComponent.Child.DynamicFeaturesChild
 import com.arkivanov.sample.shared.root.RootComponent.Child.MultiPaneChild
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 fun RootContent(component: RootComponent, modifier: Modifier = Modifier) {
     val childStack by component.childStack.subscribeAsState()
@@ -112,7 +110,6 @@ fun RootContent(component: RootComponent, modifier: Modifier = Modifier) {
     }
 }
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Composable
 private fun tabAnimation(): StackAnimation<Any, Child> =
     stackAnimation { child, otherChild, direction ->
@@ -131,7 +128,6 @@ private val Child.index: Int
             is CustomNavigationChild -> 3
         }
 
-@OptIn(ExperimentalDecomposeApi::class)
 private fun StackAnimator.flipSide(): StackAnimator =
     StackAnimator { direction, onFinished, content ->
         invoke(
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
index 75b43592..bb1e43fe 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/customnavigation/DefaultCustomNavigationComponent.kt
@@ -2,7 +2,6 @@ package com.arkivanov.sample.shared.customnavigation
 
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildNavState
 import com.arkivanov.decompose.router.children.NavState
 import com.arkivanov.decompose.router.children.SimpleChildNavState
@@ -15,7 +14,6 @@ import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent.Ch
 import com.arkivanov.sample.shared.customnavigation.CustomNavigationComponent.Mode
 import com.arkivanov.sample.shared.customnavigation.KittenComponent.ImageType
 
-@OptIn(ExperimentalDecomposeApi::class)
 class DefaultCustomNavigationComponent(
     componentContext: ComponentContext,
 ) : CustomNavigationComponent, ComponentContext by componentContext {
@@ -26,7 +24,7 @@ class DefaultCustomNavigationComponent(
         children(
             source = navigation,
             key = "carousel",
-            initialNavState = {
+            initialState = {
                 NavigationState(
                     configurations = ImageType.values().map { imageType ->
                         Config(imageType = imageType)
@@ -35,20 +33,19 @@ class DefaultCustomNavigationComponent(
                     mode = Mode.CAROUSEL,
                 )
             },
-            navTransformer = { navState, transformer -> transformer(navState) },
-            onEventComplete = { _, _, _ -> },
-            backTransformer = {
-                it.takeIf { it.index > 0 }?.let { navState ->
-                    { navState.copy(index = navState.index - 1) }
-                }
-            },
-            stateMapper = { navState, children ->
+            navTransformer = { state, transformer -> transformer(state) },
+            stateMapper = { state, children ->
                 Children(
                     items = children.map { it as Child.Created },
-                    index = navState.index,
-                    mode = navState.mode,
+                    index = state.index,
+                    mode = state.mode,
                 )
             },
+            backTransformer = {
+                it.takeIf { it.index > 0 }?.let { state ->
+                    { state.copy(index = state.index - 1) }
+                }
+            },
             childFactory = { config, componentContext ->
                 DefaultKittenComponent(
                     componentContext = componentContext,
@@ -60,32 +57,32 @@ class DefaultCustomNavigationComponent(
     override val children: Value<Children<*, KittenComponent>> = _children
 
     override fun onSwitchToPagerClicked() {
-        navigation.navigate { navState ->
-            navState.copy(mode = Mode.PAGER)
+        navigation.navigate { state ->
+            state.copy(mode = Mode.PAGER)
         }
     }
 
     override fun onSwitchToCarouselClicked() {
-        navigation.navigate { navState ->
-            navState.copy(mode = Mode.CAROUSEL)
+        navigation.navigate { state ->
+            state.copy(mode = Mode.CAROUSEL)
         }
     }
 
     override fun onForwardClicked() {
-        navigation.navigate { navState ->
-            navState.copy(index = (navState.index + 1) % navState.configurations.size)
+        navigation.navigate { state ->
+            state.copy(index = (state.index + 1) % state.configurations.size)
         }
     }
 
     override fun onBackwardClicked() {
-        navigation.navigate { navState ->
-            navState.copy(index = (navState.index - 1) % navState.configurations.size)
+        navigation.navigate { state ->
+            state.copy(index = (state.index - 1) % state.configurations.size)
         }
     }
 
     override fun onShuffleClicked() {
-        navigation.navigate { navState ->
-            navState.copy(configurations = navState.configurations.shuffled())
+        navigation.navigate { state ->
+            state.copy(configurations = state.configurations.shuffled())
         }
     }
 
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt
index 770c470b..4cdb4db0 100644
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt
+++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt
@@ -1,109 +1,99 @@
 package com.arkivanov.sample.shared.multipane
 
+import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.value.MutableValue
+import com.arkivanov.decompose.router.children.ChildNavState
+import com.arkivanov.decompose.router.children.ChildNavState.Status
+import com.arkivanov.decompose.router.children.NavState
+import com.arkivanov.decompose.router.children.SimpleChildNavState
+import com.arkivanov.decompose.router.children.SimpleNavigation
+import com.arkivanov.decompose.router.children.children
 import com.arkivanov.decompose.value.Value
-import com.arkivanov.decompose.value.reduce
-import com.arkivanov.essenty.backhandler.BackCallback
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.DetailsChild
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.ListChild
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.Model
+import com.arkivanov.essenty.parcelable.Parcelable
+import com.arkivanov.essenty.parcelable.Parcelize
+import com.arkivanov.sample.shared.multipane.MultiPaneComponent.Children
 import com.arkivanov.sample.shared.multipane.database.DefaultArticleDatabase
+import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent
+import com.arkivanov.sample.shared.multipane.details.DefaultArticleDetailsComponent
+import com.arkivanov.sample.shared.multipane.list.ArticleListComponent
+import com.arkivanov.sample.shared.multipane.list.DefaultArticleListComponent
+import com.arkivanov.sample.shared.multipane.utils.disposableScope
+import com.badoo.reaktive.disposable.scope.DisposableScope
+import com.badoo.reaktive.observable.map
+import com.badoo.reaktive.observable.notNull
 import com.badoo.reaktive.subject.behavior.BehaviorSubject
 
 internal class DefaultMultiPaneComponent(
     componentContext: ComponentContext
-) : MultiPaneComponent, ComponentContext by componentContext {
+) : MultiPaneComponent, ComponentContext by componentContext, DisposableScope by componentContext.disposableScope() {
 
     private val database = DefaultArticleDatabase()
+    private val navigation = SimpleNavigation<(NavigationState) -> NavigationState>()
+    private val navState = BehaviorSubject<NavigationState?>(null)
+
+    override val children: Value<Children> =
+        children(
+            source = navigation,
+            key = "children",
+            initialState = ::NavigationState,
+            navTransformer = { navState, event -> event(navState) },
+            stateMapper = { navState, children ->
+                @Suppress("UNCHECKED_CAST")
+                Children(
+                    isMultiPane = navState.isMultiPane,
+                    listChild = children.first { it.instance is ArticleListComponent } as Child.Created<*, ArticleListComponent>,
+                    detailsChild = children.find { it.instance is ArticleDetailsComponent } as Child.Created<*, ArticleDetailsComponent>?,
+                )
+            },
+            onStateChanged = { newState, _ -> navState.onNext(newState) },
+            childFactory = ::child,
+        )
 
-    private val _models = MutableValue(Model())
-    override val models: Value<Model> = _models
-
-    private val isDetailsToolbarVisible = BehaviorSubject(!_models.value.isMultiPane)
-    private val selectedArticleIdSubject = BehaviorSubject<Long?>(null)
+    private fun child(config: Config, componentContext: ComponentContext): Any =
+        when (config) {
+            is Config.List -> listComponent(componentContext)
+            is Config.Details -> detailsComponent(config, componentContext)
+        }
 
-    private val listRouter =
-        ListRouter(
-            componentContext = this,
+    private fun listComponent(componentContext: ComponentContext): ArticleListComponent =
+        DefaultArticleListComponent(
+            componentContext = componentContext,
             database = database,
-            selectedArticleId = selectedArticleIdSubject,
-            onArticleSelected = ::onArticleSelected
+            selectedArticleId = navState.notNull().map { if (it.isMultiPane) it.articleId else null },
+            onArticleSelected = { id -> navigation.navigate { it.copy(articleId = id) } },
         )
 
-    override val listChildStack: Value<ChildStack<*, ListChild>> = listRouter.stack
-
-    private val detailsRouter =
-        DetailsRouter(
-            componentContext = this,
+    private fun detailsComponent(config: Config.Details, componentContext: ComponentContext): ArticleDetailsComponent =
+        DefaultArticleDetailsComponent(
+            componentContext = componentContext,
             database = database,
-            isToolbarVisible = isDetailsToolbarVisible,
-            onFinished = ::closeDetailsAndShowList
+            articleId = config.articleId,
+            isToolbarVisible = navState.notNull().map { !it.isMultiPane },
+            onFinished = { navigation.navigate { it.copy(articleId = null) } },
         )
 
-    override val detailsChildStack: Value<ChildStack<*, DetailsChild>> = detailsRouter.stack
-
-    private val backCallback = BackCallback(isEnabled = false, onBack = ::closeDetailsAndShowList)
-
-    init {
-        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()
-    }
-
-    private fun onArticleSelected(id: Long) {
-        detailsRouter.showArticle(id = id)
-
-        if (isMultiPaneMode()) {
-            listRouter.show()
-        } else {
-            listRouter.moveToBackStack()
-        }
-    }
-
     override fun setMultiPane(isMultiPane: Boolean) {
-        _models.reduce { it.copy(isMultiPane = isMultiPane) }
-        isDetailsToolbarVisible.onNext(!isMultiPane)
-
-        if (isMultiPane) {
-            switchToMultiPane()
-        } else {
-            switchToSinglePane()
-        }
+        navigation.navigate { it.copy(isMultiPane = isMultiPane) }
     }
 
-    private fun switchToMultiPane() {
-        listRouter.show()
-    }
+    private sealed interface Config : Parcelable {
+        @Parcelize
+        object List : Config
 
-    private fun switchToSinglePane() {
-        if (detailsRouter.isShown()) {
-            listRouter.moveToBackStack()
-        } else {
-            listRouter.show()
-        }
+        @Parcelize
+        data class Details(val articleId: Long) : Config
     }
 
-    private fun isMultiPaneMode(): Boolean = _models.value.isMultiPane
-
-    private fun DetailsRouter.Config.getArticleId(): Long? =
-        when (this) {
-            is DetailsRouter.Config.None -> null
-            is DetailsRouter.Config.Details -> articleId
-        }
+    @Parcelize
+    private data class NavigationState(
+        val isMultiPane: Boolean = false,
+        val articleId: Long? = null,
+    ) : NavState<Config>, Parcelable {
+        override val children: List<ChildNavState<Config>>
+            get() =
+                listOfNotNull(
+                    SimpleChildNavState(Config.List, if (isMultiPane || (articleId == null)) Status.ACTIVE else Status.INACTIVE),
+                    if (articleId != null) SimpleChildNavState(Config.Details(articleId), Status.ACTIVE) else null,
+                )
+    }
 }
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DetailsRouter.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DetailsRouter.kt
deleted file mode 100644
index 51769453..00000000
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DetailsRouter.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.arkivanov.sample.shared.multipane
-
-import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.router.stack.StackNavigation
-import com.arkivanov.decompose.router.stack.active
-import com.arkivanov.decompose.router.stack.childStack
-import com.arkivanov.decompose.router.stack.navigate
-import com.arkivanov.decompose.router.stack.popWhile
-import com.arkivanov.decompose.value.Value
-import com.arkivanov.essenty.parcelable.Parcelable
-import com.arkivanov.essenty.parcelable.Parcelize
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.DetailsChild
-import com.arkivanov.sample.shared.multipane.database.ArticleDatabase
-import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent
-import com.arkivanov.sample.shared.multipane.details.DefaultArticleDetailsComponent
-import com.badoo.reaktive.observable.Observable
-
-internal class DetailsRouter(
-    componentContext: ComponentContext,
-    private val database: ArticleDatabase,
-    private val isToolbarVisible: Observable<Boolean>,
-    private val onFinished: () -> Unit
-) {
-
-    private val navigation = StackNavigation<Config>()
-
-    val stack: Value<ChildStack<Config, DetailsChild>> =
-        componentContext.childStack(
-            source = navigation,
-            initialConfiguration = Config.None,
-            key = "DetailsRouter",
-            childFactory = ::createChild,
-        )
-
-    private fun createChild(config: Config, componentContext: ComponentContext): DetailsChild =
-        when (config) {
-            is Config.None -> DetailsChild.None
-            is Config.Details -> DetailsChild.Details(articleDetails(componentContext = componentContext, articleId = config.articleId))
-        }
-
-    private fun articleDetails(componentContext: ComponentContext, articleId: Long): ArticleDetailsComponent =
-        DefaultArticleDetailsComponent(
-            componentContext = componentContext,
-            database = database,
-            articleId = articleId,
-            isToolbarVisible = isToolbarVisible,
-            onFinished = onFinished
-        )
-
-    fun showArticle(id: Long) {
-        navigation.navigate { stack ->
-            stack
-                .dropLastWhile { it is Config.Details }
-                .plus(Config.Details(articleId = id))
-        }
-    }
-
-    fun closeArticle() {
-        navigation.popWhile { it !is Config.None }
-    }
-
-    fun isShown(): Boolean =
-        when (stack.active.configuration) {
-            is Config.None -> false
-            is Config.Details -> true
-        }
-
-    sealed class Config : Parcelable {
-        @Parcelize
-        object None : Config()
-
-        @Parcelize
-        data class Details(val articleId: Long) : Config()
-    }
-}
diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/ListRouter.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/ListRouter.kt
deleted file mode 100644
index 4a3215f0..00000000
--- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/ListRouter.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.arkivanov.sample.shared.multipane
-
-import com.arkivanov.decompose.ComponentContext
-import com.arkivanov.decompose.router.stack.ChildStack
-import com.arkivanov.decompose.router.stack.StackNavigation
-import com.arkivanov.decompose.router.stack.active
-import com.arkivanov.decompose.router.stack.childStack
-import com.arkivanov.decompose.router.stack.pop
-import com.arkivanov.decompose.router.stack.push
-import com.arkivanov.decompose.value.Value
-import com.arkivanov.essenty.parcelable.Parcelable
-import com.arkivanov.essenty.parcelable.Parcelize
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.ListChild
-import com.arkivanov.sample.shared.multipane.database.ArticleDatabase
-import com.arkivanov.sample.shared.multipane.list.ArticleListComponent
-import com.arkivanov.sample.shared.multipane.list.DefaultArticleListComponent
-import com.badoo.reaktive.observable.Observable
-
-internal class ListRouter(
-    componentContext: ComponentContext,
-    private val database: ArticleDatabase,
-    private val selectedArticleId: Observable<Long?>,
-    private val onArticleSelected: (id: Long) -> Unit
-) {
-
-    private val navigation = StackNavigation<Config>()
-
-    val stack: Value<ChildStack<Config, ListChild>> =
-        componentContext.childStack(
-            source = navigation,
-            initialConfiguration = Config.List,
-            key = "MainRouter",
-            childFactory = ::createChild,
-        )
-
-    private fun createChild(config: Config, componentContext: ComponentContext): ListChild =
-        when (config) {
-            is Config.List -> ListChild.List(articleList(componentContext))
-            is Config.None -> ListChild.None
-        }
-
-    private fun articleList(componentContext: ComponentContext): ArticleListComponent =
-        DefaultArticleListComponent(
-            componentContext = componentContext,
-            database = database,
-            selectedArticleId = selectedArticleId,
-            onArticleSelected = onArticleSelected
-        )
-
-    fun moveToBackStack() {
-        if (stack.active.configuration !is Config.None) {
-            navigation.push(Config.None)
-        }
-    }
-
-    fun show() {
-        if (stack.active.configuration !is Config.List) {
-            navigation.pop()
-        }
-    }
-
-    sealed class Config : Parcelable {
-        @Parcelize
-        object List : Config()
-
-        @Parcelize
-        object None : Config()
-    }
-}
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 f55f1fe7..67da404a 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
@@ -1,29 +1,19 @@
 package com.arkivanov.sample.shared.multipane
 
-import com.arkivanov.decompose.router.stack.ChildStack
+import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.value.Value
 import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent
 import com.arkivanov.sample.shared.multipane.list.ArticleListComponent
 
 interface MultiPaneComponent {
 
-    val models: Value<Model>
-    val listChildStack: Value<ChildStack<*, ListChild>>
-    val detailsChildStack: Value<ChildStack<*, DetailsChild>>
+    val children: Value<Children>
 
     fun setMultiPane(isMultiPane: Boolean)
 
-    data class Model(
-        val isMultiPane: Boolean = false
+    data class Children(
+        val isMultiPane: Boolean,
+        val listChild: Child.Created<*, ArticleListComponent>,
+        val detailsChild: Child.Created<*, ArticleDetailsComponent>?,
     )
-
-    sealed class ListChild {
-        class List(val component: ArticleListComponent) : ListChild()
-        object None : ListChild()
-    }
-
-    sealed class DetailsChild {
-        object None : DetailsChild()
-        data class Details(val component: ArticleDetailsComponent) : DetailsChild()
-    }
 }
diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
index f84745e2..850c07cd 100644
--- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
+++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt
@@ -2,9 +2,9 @@ package com.arkivanov.sample.shared.multipane
 
 import com.arkivanov.sample.shared.RProps
 import com.arkivanov.sample.shared.componentContent
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.DetailsChild
-import com.arkivanov.sample.shared.multipane.MultiPaneComponent.ListChild
+import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent
 import com.arkivanov.sample.shared.multipane.details.ArticleDetailsContent
+import com.arkivanov.sample.shared.multipane.list.ArticleListComponent
 import com.arkivanov.sample.shared.multipane.list.ArticleListContent
 import com.arkivanov.sample.shared.useAsState
 import csstype.Display
@@ -23,9 +23,9 @@ import react.useEffectOnce
 private const val MULTI_PANE_WIDTH_THRESHOLD = 960
 
 val MultiPaneContent: FC<RProps<MultiPaneComponent>> = FC { props ->
-    val model by props.component.models.useAsState()
-    val listChildStack by props.component.listChildStack.useAsState()
-    val detailsChildStack by props.component.detailsChildStack.useAsState()
+    val children by props.component.children.useAsState()
+    val listComponent = children.listChild.instance
+    val detailsComponent = children.detailsChild?.instance
 
     fun onWindowResized() {
         props.component.setMultiPane(window.innerWidth >= MULTI_PANE_WIDTH_THRESHOLD)
@@ -39,44 +39,44 @@ val MultiPaneContent: FC<RProps<MultiPaneComponent>> = FC { props ->
         cleanup { window.removeEventListener(type = "resize", callback = resizeCallback) }
     }
 
-    if (model.isMultiPane) {
+    if (children.isMultiPane) {
         DoublePane {
-            listChild = listChildStack.active.instance
-            detailsChild = detailsChildStack.active.instance
+            this.listComponent = listComponent
+            this.detailsComponent = detailsComponent
         }
     } else {
         SinglePane {
-            listChild = listChildStack.active.instance
-            detailsChild = detailsChildStack.active.instance
+            this.listComponent = listComponent
+            this.detailsComponent = detailsComponent
         }
     }
 }
 
 private external interface SinglePaneProps : Props {
-    var listChild: ListChild
-    var detailsChild: DetailsChild
+    var listComponent: ArticleListComponent
+    var detailsComponent: ArticleDetailsComponent?
 }
 
 private val SinglePane: FC<SinglePaneProps> = FC { props ->
+    val detailsComponent = props.detailsComponent
+
     Box {
         sx {
             width = 100.pct
             height = 100.pct
         }
 
-        ListPane {
-            child = props.listChild
-        }
-
-        DetailsPane {
-            child = props.detailsChild
+        if (detailsComponent != null) {
+            componentContent(component = detailsComponent, content = ArticleDetailsContent)
+        } else {
+            componentContent(component = props.listComponent, content = ArticleListContent)
         }
     }
 }
 
 private external interface MultiPaneProps : Props {
-    var listChild: ListChild
-    var detailsChild: DetailsChild
+    var listComponent: ArticleListComponent
+    var detailsComponent: ArticleDetailsComponent?
 }
 
 private val DoublePane: FC<MultiPaneProps> = FC { props ->
@@ -93,9 +93,7 @@ private val DoublePane: FC<MultiPaneProps> = FC { props ->
                 flex = Flex(grow = number(4.0), shrink = number(0.0), basis = 0.px)
             }
 
-            ListPane {
-                child = props.listChild
-            }
+            componentContent(component = props.listComponent, content = ArticleListContent)
         }
 
         Box {
@@ -104,31 +102,9 @@ private val DoublePane: FC<MultiPaneProps> = FC { props ->
                 flex = Flex(grow = number(6.0), shrink = number(0.0), basis = 0.px)
             }
 
-            DetailsPane {
-                child = props.detailsChild
+            props.detailsComponent?.also {
+                componentContent(component = it, content = ArticleDetailsContent)
             }
         }
     }
 }
-
-private external interface ListPaneProps : Props {
-    var child: ListChild
-}
-
-private val ListPane: FC<ListPaneProps> = FC { props ->
-    when (val child = props.child) {
-        is ListChild.List -> componentContent(component = child.component, content = ArticleListContent)
-        is ListChild.None -> Unit
-    }.let {}
-}
-
-private external interface DetailsPaneProps : Props {
-    var child: DetailsChild
-}
-
-private val DetailsPane: FC<DetailsPaneProps> = FC { props ->
-    when (val child = props.child) {
-        is DetailsChild.Details -> componentContent(component = child.component, content = ArticleDetailsContent)
-        is DetailsChild.None -> Unit
-    }.let {}
-}


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
index 99f07c90..eaf56b1d 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBackPressedTest.kt
@@ -16,7 +16,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_active_child_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
         backDispatcher.back()
@@ -27,7 +27,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_active_child_and_disabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = false) { isCalled = true })
 
         backDispatcher.back()
@@ -38,7 +38,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_inactive_child_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
         backDispatcher.back()
@@ -49,7 +49,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_children_created_with_inactive_child_and_disabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = false) { isCalled = true })
 
         backDispatcher.back()
@@ -60,7 +60,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_destroyed_to_inactive_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
         children.getById(id = 1).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -72,7 +72,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_destroyed_to_active_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by ACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
         children.getById(id = 1).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -84,7 +84,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_inactive_to_active_and_enabled_callback_registered_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -96,7 +96,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_child_switched_from_active_to_inactive_and_enabled_callback_registered_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
 
@@ -108,7 +108,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_inactive_child_and_switched_to_active_WHEN_back_THEN_callback_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
 
@@ -120,7 +120,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_inactive_child_and_switched_to_destroyed_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 2).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
 
@@ -132,7 +132,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_active_child_and_switched_to_inactive_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
 
@@ -144,7 +144,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_enabled_callback_registered_in_active_child_and_switched_to_destroyed_WHEN_back_THEN_callback_not_called() {
         var isCalled = false
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().backHandler.register(BackCallback(isEnabled = true) { isCalled = true })
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -156,7 +156,7 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_backTransformer_returns_null_WHEN_back_THEN_returned_false() {
         context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
             backTransformer = { null },
         )
 
@@ -168,8 +168,8 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_backTransformer_returns_not_null_WHEN_back_THEN_navigated() {
         val children by context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
-            backTransformer = { { emptyList() } },
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { { stateOf() } },
         )
 
         backDispatcher.back()
@@ -180,10 +180,10 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
     @Test
     fun GIVEN_navigated_and_backTransformer_returns_null_WHEN_back_THEN_returned_false() {
         context.children(
-            initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
-            backTransformer = { navState ->
-                if (navState.isNotEmpty()) {
-                    { emptyList() }
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { state ->
+                if (state.children.isNotEmpty()) {
+                    { stateOf() }
                 } else {
                     null
                 }
@@ -195,4 +195,19 @@ internal class ChildrenBackPressedTest : ChildrenTestBase() {
 
         assertFalse(result)
     }
+
+    @Test
+    fun WHEN_back_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            backTransformer = { { stateOf() } },
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+        results.clear()
+
+        backDispatcher.back()
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
index a9a7566c..f199c16f 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenBasicTest.kt
@@ -1,6 +1,8 @@
 package com.arkivanov.decompose.router.children
 
-import com.arkivanov.decompose.router.children.ChildNavState.Status.*
+import com.arkivanov.decompose.router.children.ChildNavState.Status.ACTIVE
+import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
+import com.arkivanov.decompose.router.children.ChildNavState.Status.INACTIVE
 import kotlin.test.Test
 import kotlin.test.assertContentEquals
 
@@ -8,15 +10,56 @@ import kotlin.test.assertContentEquals
 internal class ChildrenBasicTest : ChildrenTestBase() {
 
     @Test
-    fun WHEN_navigate_THEN_onComplete_called() {
-        val results = ArrayList<Pair<List<SimpleChildNavState<Config>>, List<SimpleChildNavState<Config>>>>()
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+    fun WHEN_navigate_THEN_onEventComplete_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onEventComplete = { _, newState, oldState -> results += newState to oldState },
+        )
+
+        navigate { emptyList() }
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
+
+    @Test
+    fun WHEN_created_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+
+        assertContentEquals(listOf(stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE) to null), results)
+    }
 
-        navigate(transformer = { emptyList() }, onComplete = { newNavState, oldNavState -> results += newNavState to oldNavState })
+    @Test
+    fun WHEN_navigate_THEN_onStateChanged_called() {
+        val results = ArrayList<Pair<TestNavState, TestNavState?>>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onStateChanged = { newState, oldState -> results += newState to oldState },
+        )
+        results.clear()
 
-        assertContentEquals(
-            listOf(emptyList<SimpleChildNavState<Config>>() to listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)),
-            results,
+        navigate { emptyList() }
+
+        assertContentEquals(listOf(stateOf() to stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)), results)
+    }
+
+    @Test
+    fun WHEN_navigate_THEN_onEventComplete_called_after_onStateChanged_called() {
+        val results = ArrayList<String>()
+        context.children(
+            initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE),
+            onEventComplete = { _, _, _ -> results += "onEventComplete" },
+            onStateChanged = { _, _ -> results += "onStateChanged" },
         )
+        results.clear()
+
+        navigate { emptyList() }
+
+        assertContentEquals(listOf("onStateChanged", "onEventComplete"), results)
     }
 }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
index 7b763c70..ffad7849 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenLifecycleTest.kt
@@ -15,14 +15,14 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_created_THEN_initial_state() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         children.assertChildren(1 to null, 2 to 2, 3 to 3)
     }
 
     @Test
     fun WHEN_created_THEN_lifecycles_correct() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         assertEquals(Lifecycle.State.CREATED, children.getById(id = 2).requireInstance().lifecycle.state)
         assertEquals(Lifecycle.State.RESUMED, children.getById(id = 3).requireInstance().lifecycle.state)
@@ -53,54 +53,54 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_destroyed_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 1 } }
+        navigate { state -> state.filterNot { it.configuration.id == 1 } }
 
         children.assertChildren(2 to 2, 3 to 3)
     }
 
     @Test
     fun WHEN_inactive_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 2 } }
+        navigate { state -> state.filterNot { it.configuration.id == 2 } }
 
         children.assertChildren(1 to null, 3 to 3)
     }
 
     @Test
     fun WHEN_inactive_child_removed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 2 } }
+        navigate { state -> state.filterNot { it.configuration.id == 2 } }
 
         assertEquals(Lifecycle.State.DESTROYED, component.lifecycle.state)
     }
 
     @Test
     fun WHEN_active_child_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 3 } }
+        navigate { state -> state.filterNot { it.configuration.id == 3 } }
 
         children.assertChildren(1 to null, 2 to 2)
     }
 
     @Test
     fun WHEN_active_child_removed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
 
-        navigate { navState -> navState.filterNot { it.configuration.id == 3 } }
+        navigate { state -> state.filterNot { it.configuration.id == 3 } }
 
         assertEquals(Lifecycle.State.DESTROYED, component.lifecycle.state)
     }
 
     @Test
     fun WHEN_all_children_removed_THEN_state_updated() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { emptyList() }
 
@@ -109,7 +109,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_all_children_removed_THEN_lifecycles_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component2 = children.getById(id = 2).requireInstance()
         val component3 = children.getById(id = 3).requireInstance()
 
@@ -121,7 +121,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_destroyed_to_inactive_THEN_lifecycle_created() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by INACTIVE, 2 by INACTIVE, 3 by ACTIVE) }
 
@@ -130,7 +130,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_active_THEN_lifecycle_resumed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by DESTROYED, 2 by ACTIVE, 3 by ACTIVE) }
 
@@ -139,7 +139,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
 
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
@@ -149,7 +149,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_lifecycle_stopped() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
 
@@ -158,7 +158,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_lifecycle_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
@@ -168,7 +168,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_context_lifecycle_stopped_THEN_all_component_lifecycles_stopped() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         lifecycle.stop()
 
@@ -178,7 +178,7 @@ internal class ChildrenLifecycleTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_context_lifecycle_destroyed_THEN_all_component_lifecycles_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component2 = children.getById(id = 2).requireInstance()
         val component3 = children.getById(id = 3).requireInstance()
 
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
index 7bf0789b..e486b297 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenRetainedInstanceTest.kt
@@ -20,7 +20,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_instance_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 2).requireInstance()
         val instance = component.instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
@@ -31,7 +31,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_instance_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val component = children.getById(id = 3).requireInstance()
         val instance = component.instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
@@ -42,7 +42,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_instance_not_destroyed() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = children.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
@@ -52,7 +52,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_inactive_to_destroyed_WHEN_child_switched_to_inactive_THEN_instance_not_retained() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = children.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by DESTROYED, 3 by ACTIVE) }
 
@@ -64,7 +64,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_active_THEN_instance_not_retained() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = children.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -79,7 +79,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = oldChildren.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -96,7 +96,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 2).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -112,7 +112,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val oldInstance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -129,7 +129,7 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
@@ -145,13 +145,13 @@ internal class ChildrenRetainedInstanceTest : ChildrenTestBase() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val instanceKeeper = InstanceKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper, instanceKeeper = instanceKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         val instance = oldChildren.getById(id = 3).requireInstance().instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper, instanceKeeper = instanceKeeper)
-        newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         assertTrue(instance.isDestroyed)
     }
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
index 078bb286..b281db7b 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenSavedStateTest.kt
@@ -1,7 +1,6 @@
 package com.arkivanov.decompose.router.children
 
 import com.arkivanov.decompose.DefaultComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
 import com.arkivanov.decompose.router.children.ChildNavState.Status.ACTIVE
 import com.arkivanov.decompose.router.children.ChildNavState.Status.DESTROYED
 import com.arkivanov.decompose.router.children.ChildNavState.Status.INACTIVE
@@ -16,13 +15,12 @@ import kotlin.test.assertFalse
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Suppress("TestFunctionName")
 internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_inactive_THEN_state_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -36,7 +34,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_active_to_destroyed_THEN_state_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -50,7 +48,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_destroyed_THEN_state_not_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 2).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -64,7 +62,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun WHEN_child_switched_from_inactive_to_active_THEN_state_not_saved() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         var isCalled = false
         children.getById(id = 2).requireInstance().stateKeeper.register(key = "key") {
             isCalled = true
@@ -79,7 +77,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_destroyed_to_inactive_THEN_state_not_saved() {
         var isCalled = false
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+        context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
             val component = Component(config, componentContext)
             if (config.id == 1) {
                 component.stateKeeper.register(key = "key") {
@@ -98,7 +96,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     @Test
     fun WHEN_child_switched_from_destroyed_to_active_THEN_state_not_saved() {
         var isCalled = false
-        context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
+        context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE)) { config, componentContext ->
             val component = Component(config, componentContext)
             if (config.id == 1) {
                 component.stateKeeper.register(key = "key") {
@@ -116,7 +114,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_active_THEN_state_restored() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -128,7 +126,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
 
     @Test
     fun GIVEN_child_switched_from_active_to_destroyed_WHEN_child_switched_to_inactive_THEN_state_restored() {
-        val children by context.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val children by context.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         children.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) }
 
@@ -142,7 +140,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_saving_state_WHEN_recreated_THEN_navigation_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
@@ -156,12 +154,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_saving_state_WHEN_recreated_THEN_navigation_state_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE), saveNavState = { null })
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE), saveState = { null })
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE))
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE))
 
         newChildren.assertChildren(1 to 1)
     }
@@ -170,12 +168,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_restoring_state_WHEN_recreated_THEN_navigation_state_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE), restoreNavState = { null })
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE), restoreState = { null })
 
         newChildren.assertChildren(1 to 1)
     }
@@ -184,7 +182,7 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_saving_state_WHEN_recreated_THEN_child_states_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
@@ -200,13 +198,13 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_saving_state_WHEN_recreated_THEN_child_states_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by ACTIVE), saveNavState = { null })
+        val oldChildren by oldContext.children(initialState = stateOf(1 by ACTIVE), saveState = { null })
         oldChildren.getById(id = 1).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE))
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE))
         val restoredState3 = newChildren.getById(id = 1).requireInstance().stateKeeper.consume<Config>(key = "key")
 
         assertNull(restoredState3)
@@ -216,13 +214,13 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_not_restoring_state_WHEN_recreated_THEN_child_states_not_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by ACTIVE), saveNavState = { null })
+        val oldChildren by oldContext.children(initialState = stateOf(1 by ACTIVE), saveState = { null })
         oldChildren.getById(id = 1).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
 
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(initialNavState = listOf(1 by ACTIVE), restoreNavState = { null })
+        val newChildren by newContext.children(initialState = stateOf(1 by ACTIVE), restoreState = { null })
         val restoredState3 = newChildren.getById(id = 1).requireInstance().stateKeeper.consume<Config>(key = "key")
 
         assertNull(restoredState3)
@@ -232,12 +230,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_recreated_and_child_restored_as_destroyed_WHEN_child_switched_to_inactive_THEN_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        val newChildren by newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by INACTIVE) }
         val restoredState3 = newChildren.getById(id = 3).requireInstance().stateKeeper.consume<Config>(key = "key")
@@ -249,12 +247,12 @@ internal class ChildrenSavedStateTest : ChildrenTestBase() {
     fun GIVEN_recreated_and_child_restored_as_destroyed_WHEN_child_switched_to_active_THEN_state_restored() {
         val oldStateKeeper = TestStateKeeperDispatcher()
         val oldContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = oldStateKeeper)
-        val oldChildren by oldContext.children(initialNavState = listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
+        val oldChildren by oldContext.children(initialState = stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE))
         oldChildren.getById(id = 3).requireInstance().stateKeeper.register(key = "key") { Config(id = 30) }
         val savedState = oldStateKeeper.save()
         val newStateKeeper = TestStateKeeperDispatcher(savedState)
         val newContext = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = newStateKeeper)
-        val newChildren by newContext.children(restoreNavState = { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
+        val newChildren by newContext.children(restoreState = { stateOf(1 by DESTROYED, 2 by INACTIVE, 3 by DESTROYED) })
 
         navigate { listOf(1 by DESTROYED, 2 by INACTIVE, 3 by ACTIVE) }
         val restoredState3 = newChildren.getById(id = 3).requireInstance().stateKeeper.consume<Config>(key = "key")
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
index c4bf74e1..6f52ac11 100644
--- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/children/ChildrenTestBase.kt
@@ -3,8 +3,6 @@ package com.arkivanov.decompose.router.children
 import com.arkivanov.decompose.Child
 import com.arkivanov.decompose.ComponentContext
 import com.arkivanov.decompose.DefaultComponentContext
-import com.arkivanov.decompose.ExperimentalDecomposeApi
-import com.arkivanov.decompose.Relay
 import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher
 import com.arkivanov.decompose.value.Value
 import com.arkivanov.essenty.backhandler.BackDispatcher
@@ -18,10 +16,9 @@ import com.arkivanov.essenty.parcelable.consumeRequired
 import kotlin.test.BeforeTest
 import kotlin.test.assertContentEquals
 
-@OptIn(ExperimentalDecomposeApi::class)
 internal open class ChildrenTestBase {
 
-    private val navigation = TestNavigation()
+    private val navigation = SimpleNavigation<(TestNavState) -> TestNavState>()
     protected val lifecycle = LifecycleRegistry()
     protected val backDispatcher = BackDispatcher()
 
@@ -39,63 +36,59 @@ internal open class ChildrenTestBase {
     }
 
     protected fun ComponentContext.children(
-        initialNavState: List<SimpleChildNavState<Config>> = emptyList(),
-        saveNavState: (navState: List<SimpleChildNavState<Config>>) -> ParcelableContainer? = { navState ->
+        initialState: TestNavState = TestNavState(),
+        saveState: (state: TestNavState) -> ParcelableContainer? = { state ->
             ParcelableContainer(
                 SavedNavState(
-                    configurations = navState.map { it.configuration },
-                    statuses = navState.map { it.status },
+                    configurations = state.children.map { it.configuration },
+                    statuses = state.children.map { it.status },
                 )
             )
         },
-        restoreNavState: (container: ParcelableContainer) -> List<SimpleChildNavState<Config>>? = { container ->
+        restoreState: (container: ParcelableContainer) -> TestNavState? = { container ->
             val savedState = container.consumeRequired<SavedNavState>()
-            savedState.configurations.zip(savedState.statuses).map { (configuration, status) ->
-                SimpleChildNavState(
-                    configuration = configuration,
-                    status = status,
-                )
-            }
+            TestNavState(
+                children = savedState.configurations.zip(savedState.statuses).map { (configuration, status) ->
+                    SimpleChildNavState(
+                        configuration = configuration,
+                        status = status,
+                    )
+                },
+            )
         },
-        backTransformer: (navState: List<SimpleChildNavState<Config>>) -> (() -> List<SimpleChildNavState<Config>>)? = { null },
+        onStateChanged: (newState: TestNavState, oldState: TestNavState?) -> Unit = { _, _ -> },
+        onEventComplete: (
+            event: (TestNavState) -> TestNavState,
+            newState: TestNavState,
+            oldState: TestNavState,
+        ) -> Unit = { _, _, _ -> },
+        backTransformer: (state: TestNavState) -> (() -> TestNavState)? = { null },
         childFactory: (Config, ComponentContext) -> Component = ::Component,
     ): Value<List<Child<Config, Component>>> =
         children(
             source = navigation,
             key = "Key",
-            initialNavState = { TestNavState(children = initialNavState) },
-            saveNavState = { navState -> saveNavState(navState.children) },
-            restoreNavState = { container -> restoreNavState(container)?.let(::TestNavState) },
-            navTransformer = { navState, event -> event.transformer(navState) },
-            onEventComplete = { event, newNavState, oldNavState -> event.onComplete(newNavState, oldNavState) },
-            backTransformer = {
-                backTransformer(it.children)?.let { transformer ->
-                    { TestNavState(children = transformer()) }
-                }
-            },
+            initialState = { initialState },
+            saveState = saveState,
+            restoreState = restoreState,
+            navTransformer = { state, event -> event(state) },
+            onStateChanged = onStateChanged,
+            onEventComplete = onEventComplete,
+            backTransformer = backTransformer,
             stateMapper = { _, children -> children },
             childFactory = childFactory,
         )
 
     protected fun navigate(transformer: (List<SimpleChildNavState<Config>>) -> List<SimpleChildNavState<Config>>) {
-        navigate(transformer = transformer, onComplete = null)
-    }
-
-    protected fun navigate(
-        transformer: (List<SimpleChildNavState<Config>>) -> List<SimpleChildNavState<Config>>,
-        onComplete: ((newNavState: List<SimpleChildNavState<Config>>, oldNavState: List<SimpleChildNavState<Config>>) -> Unit)?,
-    ) {
-        navigation.navigate(
-            Event(
-                transformer = { TestNavState(children = transformer(it.children)) },
-                onComplete = { newNavState, oldNavState -> onComplete?.invoke(newNavState.children, oldNavState.children) },
-            )
-        )
+        navigation.navigate { it.copy(children = transformer(it.children)) }
     }
 
     protected infix fun Int.by(status: ChildNavState.Status): SimpleChildNavState<Config> =
         SimpleChildNavState(configuration = Config(id = this), status = status)
 
+    protected fun stateOf(vararg children: SimpleChildNavState<Config>): TestNavState =
+        TestNavState(children = children.asList())
+
     protected fun List<Child<Config, Component>>.assertChildren(vararg children: Pair<Int, Int?>) {
         assertContentEquals(
             children.toList(),
@@ -135,25 +128,4 @@ internal open class ChildrenTestBase {
         val configurations: List<Config>,
         val statuses: List<ChildNavState.Status>,
     ) : Parcelable
-
-    protected class Event(
-        val transformer: (TestNavState) -> TestNavState,
-        val onComplete: (newNavState: TestNavState, oldNavState: TestNavState) -> Unit,
-    )
-
-    protected class TestNavigation : NavigationSource<Event> {
-        private val relay = Relay<Event>()
-
-        override fun subscribe(observer: (Event) -> Unit) {
-            relay.subscribe(observer)
-        }
-
-        override fun unsubscribe(observer: (Event) -> Unit) {
-            relay.unsubscribe(observer)
-        }
-
-        fun navigate(event: Event) {
-            relay.accept(event)
-        }
-    }
 }
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 9c43d278..5704c4fb 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
@@ -14,7 +14,6 @@ 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.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.fade
 import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.plus
@@ -29,7 +28,6 @@ import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Suppress("TestFunctionName")
 @RunWith(Parameterized::class)
 class ChildrenTest(
diff --git a/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt b/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
index bf3838d0..621685f5 100644
--- a/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
+++ b/extensions-compose-jetpack/src/androidTest/java/com/arkivanov/decompose/extensions/compose/jetpack/stack/ChildrenTest.kt
@@ -14,7 +14,6 @@ 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.jetpack.stack.animation.StackAnimation
 import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.fade
 import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.plus
@@ -28,7 +27,6 @@ import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 import java.io.Serializable
 
-@OptIn(ExperimentalDecomposeApi::class)
 @Suppress("TestFunctionName")
 @RunWith(Parameterized::class)
 class ChildrenTest(