[FRP] Functional reactive programming library

Flag: EXEMPT new library, unused
Test: atest kt-frp-test

Change-Id: Iaa7fe31dbd8612542730e1821384fd82f551c9f1
diff --git a/packages/SystemUI/frp/Android.bp b/packages/SystemUI/frp/Android.bp
new file mode 100644
index 0000000..c3381db
--- /dev/null
+++ b/packages/SystemUI/frp/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "kt-frp",
+    host_supported: true,
+    kotlincflags: ["-opt-in=com.android.systemui.experimental.frp.ExperimentalFrpApi"],
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "kotlin-stdlib",
+        "kotlinx_coroutines",
+    ],
+}
+
+java_test {
+    name: "kt-frp-test",
+    optimize: {
+        enabled: false,
+    },
+    srcs: [
+        "test/**/*.kt",
+    ],
+    static_libs: [
+        "kt-frp",
+        "junit",
+        "kotlin-stdlib",
+        "kotlin-test",
+        "kotlinx_coroutines",
+        "kotlinx_coroutines_test",
+    ],
+}
diff --git a/packages/SystemUI/frp/OWNERS b/packages/SystemUI/frp/OWNERS
new file mode 100644
index 0000000..8876ad6
--- /dev/null
+++ b/packages/SystemUI/frp/OWNERS
@@ -0,0 +1,3 @@
+steell@google.com
+nijamkin@google.com
+evanlaird@google.com
diff --git a/packages/SystemUI/frp/README.md b/packages/SystemUI/frp/README.md
new file mode 100644
index 0000000..9c5bdb0
--- /dev/null
+++ b/packages/SystemUI/frp/README.md
@@ -0,0 +1,64 @@
+# kt-frp
+
+A functional reactive programming (FRP) library for Kotlin.
+
+This library is **experimental** and should not be used for general production
+code. The APIs within are subject to change, and there may be bugs.
+
+## About FRP
+
+Functional reactive programming is a type of reactive programming system that
+follows a set of clear and composable rules, without sacrificing consistency.
+FRP exposes an API that should be familiar to those versed in Kotlin `Flow`.
+
+### Details for nerds
+
+`kt-frp` implements an applicative / monadic flavor of FRP, using a push-pull
+methodology to allow for efficient updates.
+
+"Real" functional reactive programming should be specified with denotational
+semantics ([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)):
+you can view the semantics for `kt-frp` [here](docs/semantics.md).
+
+## Usage
+
+First, stand up a new `FrpNetwork`. All reactive events and state is kept
+consistent within a single network.
+
+``` kotlin
+val coroutineScope: CoroutineScope = ...
+val frpNetwork = coroutineScope.newFrpNetwork()
+```
+
+You can use the `FrpNetwork` to stand-up a network of reactive events and state.
+Events are modeled with `TFlow` (short for "transactional flow"), and state
+`TState` (short for "transactional state").
+
+``` kotlin
+suspend fun activate(network: FrpNetwork) {
+    network.activateSpec {
+        val input = network.mutableTFlow<Unit>()
+        // Launch a long-running side-effect that emits to the network
+        // every second.
+        launchEffect {
+            while (true) {
+                input.emit(Unit)
+                delay(1.seconds)
+            }
+        }
+        // Accumulate state
+        val count: TState<Int> = input.fold { _, i -> i + 1 }
+        // Observe events to perform side-effects in reaction to them
+        input.observe {
+            println("Got event ${count.sample()} at time: ${System.currentTimeMillis()}")
+        }
+    }
+}
+```
+
+`FrpNetwork.activateSpec` will suspend indefinitely; cancelling the invocation
+will tear-down all effects and obervers running within the lambda.
+
+## Resources
+
+- [Cheatsheet for those coming from Kotlin Flow](docs/flow-to-frp-cheatsheet.md)
diff --git a/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md b/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md
new file mode 100644
index 0000000..e20f3e6
--- /dev/null
+++ b/packages/SystemUI/frp/docs/flow-to-frp-cheatsheet.md
@@ -0,0 +1,330 @@
+# From Flows to FRP
+
+## Key differences
+
+* FRP evaluates all events (`TFlow` emissions + observers) in a transaction.
+
+* FRP splits `Flow` APIs into two distinct types: `TFlow` and `TState`
+
+    * `TFlow` is roughly equivalent to `SharedFlow` w/ a replay cache that
+      exists for the duration of the current FRP transaction and shared with
+      `SharingStarted.WhileSubscribed()`
+
+    * `TState` is roughly equivalent to `StateFlow` shared with
+      `SharingStarted.Eagerly`, but the current value can only be queried within
+      a FRP transaction, and the value is only updated at the end of the
+      transaction
+
+* FRP further divides `Flow` APIs based on how they internally use state:
+
+  * **FrpTransactionScope:** APIs that internally query some state need to be
+    performed within an FRP transaction
+
+    * this scope is available from the other scopes, and from most lambdas
+      passed to other FRP APIs
+
+  * **FrpStateScope:** APIs that internally accumulate state in reaction to
+    events need to be performed within an FRP State scope (akin to a
+    `CoroutineScope`)
+
+    * this scope is a side-effect-free subset of FrpBuildScope, and so can be
+      used wherever you have an FrpBuildScope
+
+  * **FrpBuildScope:** APIs that perform external side-effects (`Flow.collect`)
+    need to be performed within an FRP Build scope (akin to a `CoroutineScope`)
+
+    * this scope is available from `FrpNetwork.activateSpec { … }`
+
+  * All other APIs can be used anywhere
+
+## emptyFlow()
+
+Use `emptyTFlow`
+
+``` kotlin
+// this TFlow emits nothing
+val noEvents: TFlow<Int> = emptyTFlow
+```
+
+## map { … }
+
+Use `TFlow.map` / `TState.map`
+
+``` kotlin
+val anInt: TState<Int> = …
+val squared: TState<Int> = anInt.map { it * it }
+val messages: TFlow<String> = …
+val messageLengths: TFlow<Int> = messages.map { it.size }
+```
+
+## filter { … } / mapNotNull { … }
+
+### I have a TFlow
+
+Use `TFlow.filter` / `TFlow.mapNotNull`
+
+``` kotlin
+val messages: TFlow<String> = …
+val nonEmpty: TFlow<String> = messages.filter { it.isNotEmpty() }
+```
+
+### I have a TState
+
+Convert the `TState` to `TFlow` using `TState.stateChanges`, then use
+`TFlow.filter` / `TFlow.mapNotNull`
+
+If you need to convert back to `TState`, use `TFlow.hold(initialValue)` on the
+result.
+
+``` kotlin
+tState.stateChanges.filter { … }.hold(initialValue)
+```
+
+Note that `TFlow.hold` is only available within an `FrpStateScope` in order to
+track the lifetime of the state accumulation.
+
+## combine(...) { … }
+
+### I have TStates
+
+Use `combine(TStates)`
+
+``` kotlin
+val someInt: TState<Int> = …
+val someString: TState<String> = …
+val model: TState<MyModel> = combine(someInt, someString) { i, s -> MyModel(i, s) }
+```
+
+### I have TFlows
+
+Convert the TFlows to TStates using `TFlow.hold(initialValue)`, then use
+`combine(TStates)`
+
+If you want the behavior of Flow.combine where nothing is emitted until each
+TFlow has emitted at least once, you can use filter:
+
+``` kotlin
+// null used as an example, can use a different sentinel if needed
+combine(tFlowA.hold(null), tFlowB.hold(null)) { a, b ->
+        a?.let { b?.let { … } }
+    }
+    .filterNotNull()
+```
+
+Note that `TFlow.hold` is only available within an `FrpStateScope` in order to
+track the lifetime of the state accumulation.
+
+#### Explanation
+
+`Flow.combine` always tracks the last-emitted value of each `Flow` it's
+combining. This is a form of state-accumulation; internally, it collects from
+each `Flow`, tracks the latest-emitted value, and when anything changes, it
+re-runs the lambda to combine the latest values.
+
+An effect of this is that `Flow.combine` doesn't emit until each combined `Flow`
+has emitted at least once. This often bites developers. As a workaround,
+developers generally append `.onStart { emit(initialValue) }` to the `Flows`
+that don't immediately emit.
+
+FRP avoids this gotcha by forcing usage of `TState` for `combine`, thus ensuring
+that there is always a current value to be combined for each input.
+
+## collect { … }
+
+Use `observe { … }`
+
+``` kotlin
+val job: Job = tFlow.observe { println("observed: $it") }
+```
+
+Note that `observe` is only available within an `FrpBuildScope` in order to
+track the lifetime of the observer. `FrpBuildScope` can only come from a
+top-level `FrpNetwork.transaction { … }`, or a sub-scope created by using a
+`-Latest` operator.
+
+## sample(flow) { … }
+
+### I want to sample a TState
+
+Use `TState.sample()` to get the current value of a `TState`. This can be
+invoked anywhere you have access to an `FrpTransactionScope`.
+
+``` kotlin
+// the lambda passed to map receives an FrpTransactionScope, so it can invoke
+// sample
+tFlow.map { tState.sample() }
+```
+
+#### Explanation
+
+To keep all state-reads consistent, the current value of a TState can only be
+queried within an FRP transaction, modeled with `FrpTransactionScope`. Note that
+both `FrpStateScope` and `FrpBuildScope` extend `FrpTransactionScope`.
+
+### I want to sample a TFlow
+
+Convert to a `TState` by using `TFlow.hold(initialValue)`, then use `sample`.
+
+Note that `hold` is only available within an `FrpStateScope` in order to track
+the lifetime of the state accumulation.
+
+## stateIn(scope, sharingStarted, initialValue)
+
+Use `TFlow.hold(initialValue)`. There is no need to supply a sharingStarted
+argument; all states are accumulated eagerly.
+
+``` kotlin
+val ints: TFlow<Int> = …
+val lastSeenInt: TState<Int> = ints.hold(initialValue = 0)
+```
+
+Note that `hold` is only available within an `FrpStateScope` in order to track
+the lifetime of the state accumulation (akin to the scope parameter of
+`Flow.stateIn`). `FrpStateScope` can only come from a top-level
+`FrpNetwork.transaction { … }`, or a sub-scope created by using a `-Latest`
+operator. Also note that `FrpBuildScope` extends `FrpStateScope`.
+
+## distinctUntilChanged()
+
+Use `distinctUntilChanged` like normal. This is only available for `TFlow`;
+`TStates` are already `distinctUntilChanged`.
+
+## merge(...)
+
+### I have TFlows
+
+Use `merge(TFlows) { … }`. The lambda argument is used to disambiguate multiple
+simultaneous emissions within the same transaction.
+
+#### Explanation
+
+Under FRP's rules, a `TFlow` may only emit up to once per transaction. This
+means that if we are merging two or more `TFlows` that are emitting at the same
+time (within the same transaction), the resulting merged `TFlow` must emit a
+single value. The lambda argument allows the developer to decide what to do in
+this case.
+
+### I have TStates
+
+If `combine` doesn't satisfy your needs, you can use `TState.stateChanges` to
+convert to a `TFlow`, and then `merge`.
+
+## conflatedCallbackFlow { … }
+
+Use `tFlow { … }`.
+
+As a shortcut, if you already have a `conflatedCallbackFlow { … }`, you can
+convert it to a TFlow via `Flow.toTFlow()`.
+
+Note that `tFlow` is only available within an `FrpBuildScope` in order to track
+the lifetime of the input registration.
+
+## first()
+
+### I have a TState
+
+Use `TState.sample`.
+
+### I have a TFlow
+
+Use `TFlow.nextOnly`, which works exactly like `Flow.first` but instead of
+suspending it returns a `TFlow` that emits once.
+
+The naming is intentionally different because `first` implies that it is the
+first-ever value emitted from the `Flow` (which makes sense for cold `Flows`),
+whereas `nextOnly` indicates that only the next value relative to the current
+transaction (the one `nextOnly` is being invoked in) will be emitted.
+
+Note that `nextOnly` is only available within an `FrpStateScope` in order to
+track the lifetime of the state accumulation.
+
+## flatMapLatest { … }
+
+If you want to use -Latest to cancel old side-effects, similar to what the Flow
+-Latest operators offer for coroutines, see `mapLatest`.
+
+### I have a TState…
+
+#### …and want to switch TStates
+
+Use `TState.flatMap`
+
+``` kotlin
+val flattened = tState.flatMap { a -> getTState(a) }
+```
+
+#### …and want to switch TFlows
+
+Use `TState<TFlow<T>>.switch()`
+
+``` kotlin
+val tFlow = tState.map { a -> getTFlow(a) }.switch()
+```
+
+### I have a TFlow…
+
+#### …and want to switch TFlows
+
+Use `hold` to convert to a `TState<TFlow<T>>`, then use `switch` to switch to
+the latest `TFlow`.
+
+``` kotlin
+val tFlow = tFlowOfFlows.hold(emptyTFlow).switch()
+```
+
+#### …and want to switch TStates
+
+Use `hold` to convert to a `TState<TState<T>>`, then use `flatMap` to switch to
+the latest `TState`.
+
+``` kotlin
+val tState = tFlowOfStates.hold(tStateOf(initialValue)).flatMap { it }
+```
+
+## mapLatest { … } / collectLatest { … }
+
+`FrpStateScope` and `FrpBuildScope` both provide `-Latest` operators that
+automatically cancel old work when new values are emitted.
+
+``` kotlin
+val currentModel: TState<SomeModel> = …
+val mapped: TState<...> = currentModel.mapLatestBuild { model ->
+    effect { "new model in the house: $model" }
+    model.someState.observe { "someState: $it" }
+    val someData: TState<SomeInfo> =
+        getBroadcasts(model.uri)
+            .map { extractInfo(it) }
+            .hold(initialInfo)
+    …
+}
+```
+
+## flowOf(...)
+
+### I want a TState
+
+Use `tStateOf(initialValue)`.
+
+### I want a TFlow
+
+Use `now.map { initialValue }`
+
+Note that `now` is only available within an `FrpTransactionScope`.
+
+#### Explanation
+
+`TFlows` are not cold, and so there isn't a notion of "emit this value once
+there is a collector" like there is for `Flow`. The closest analog would be
+`TState`, since the initial value is retained indefinitely until there is an
+observer. However, it is often useful to immediately emit a value within the
+current transaction, usually when using a `flatMap` or `switch`. In these cases,
+using `now` explicitly models that the emission will occur within the current
+transaction.
+
+``` kotlin
+fun <T> FrpTransactionScope.tFlowOf(value: T): TFlow<T> = now.map { value }
+```
+
+## MutableStateFlow / MutableSharedFlow
+
+Use `MutableTState(frpNetwork, initialValue)` and `MutableTFlow(frpNetwork)`.
diff --git a/packages/SystemUI/frp/docs/semantics.md b/packages/SystemUI/frp/docs/semantics.md
new file mode 100644
index 0000000..b533190
--- /dev/null
+++ b/packages/SystemUI/frp/docs/semantics.md
@@ -0,0 +1,225 @@
+# FRP Semantics
+
+`kt-frp`'s pure API is based off of the following denotational semantics
+([wikipedia](https://en.wikipedia.org/wiki/Denotational_semantics)).
+
+The semantics model `kt-frp` types as time-varying values; by making `Time` a
+first-class value, we can define a referentially-transparent API that allows us
+to reason about the behavior of the pure FRP combinators. This is
+implementation-agnostic; we can compare the behavior of any implementation with
+expected behavior denoted by these semantics to identify bugs.
+
+The semantics are written in pseudo-Kotlin; places where we are deviating from
+real Kotlin are noted with comments.
+
+``` kotlin
+
+sealed class Time : Comparable<Time> {
+  object BigBang : Time()
+  data class At(time: BigDecimal) : Time()
+  object Infinity : Time()
+
+  override final fun compareTo(other: Time): Int =
+    when (this) {
+      BigBang -> if (other === BigBang) 0 else -1
+      is At -> when (other) {
+        BigBang -> 1
+        is At -> time.compareTo(other.time)
+        Infinity -> -1
+      }
+      Infinity -> if (other === Infinity) 0 else 1
+    }
+}
+
+typealias Transactional<T> = (Time) -> T
+
+typealias TFlow<T> = SortedMap<Time, T>
+
+private fun <T> SortedMap<Time, T>.pairwise(): List<Pair<Pair<Time, T>, Pair<Time<T>>>> =
+  // NOTE: pretend evaluation is lazy, so that error() doesn't immediately throw
+  (toList() + Pair(Time.Infinity, error("no value"))).zipWithNext()
+
+class TState<T> internal constructor(
+  internal val current: Transactional<T>,
+  val stateChanges: TFlow<T>,
+)
+
+val emptyTFlow: TFlow<Nothing> = emptyMap()
+
+fun <A, B> TFlow<A>.map(f: FrpTransactionScope.(A) -> B): TFlow<B> =
+  mapValues { (t, a) -> FrpTransactionScope(t).f(a) }
+
+fun <A> TFlow<A>.filter(f: FrpTransactionScope.(A) -> Boolean): TFlow<A> =
+  filter { (t, a) -> FrpTransactionScope(t).f(a) }
+
+fun <A> merge(
+  first: TFlow<A>,
+  second: TFlow<A>,
+  onCoincidence: Time.(A, A) -> A,
+): TFlow<A> =
+  first.toMutableMap().also { result ->
+    second.forEach { (t, a) ->
+      result.merge(t, a) { f, s ->
+        FrpTranscationScope(t).onCoincidence(f, a)
+      }
+    }
+  }.toSortedMap()
+
+fun <A> TState<TFlow<A>>.switch(): TFlow<A> {
+  val truncated = listOf(Pair(Time.BigBang, current.invoke(Time.BigBang))) +
+    stateChanges.dropWhile { (time, _) -> time < time0 }
+  val events =
+    truncated
+      .pairwise()
+      .flatMap { ((t0, sa), (t2, _)) ->
+        sa.filter { (t1, a) -> t0 < t1 && t1 <= t2 }
+      }
+  return events.toSortedMap()
+}
+
+fun <A> TState<TFlow<A>>.switchPromptly(): TFlow<A> {
+  val truncated = listOf(Pair(Time.BigBang, current.invoke(Time.BigBang))) +
+    stateChanges.dropWhile { (time, _) -> time < time0 }
+  val events =
+    truncated
+      .pairwise()
+      .flatMap { ((t0, sa), (t2, _)) ->
+        sa.filter { (t1, a) -> t0 <= t1 && t1 <= t2 }
+      }
+  return events.toSortedMap()
+}
+
+typealias GroupedTFlow<K, V> = TFlow<Map<K, V>>
+
+fun <K, V> TFlow<Map<K, V>>.groupByKey(): GroupedTFlow<K, V> = this
+
+fun <K, V> GroupedTFlow<K, V>.eventsForKey(key: K): TFlow<V> =
+  map { m -> m[k] }.filter { it != null }.map { it!! }
+
+fun <A, B> TState<A>.map(f: (A) -> B): TState<B> =
+  TState(
+    current = { t -> f(current.invoke(t)) },
+    stateChanges = stateChanges.map { f(it) },
+  )
+
+fun <A, B, C> TState<A>.combineWith(
+  other: TState<B>,
+  f: (A, B) -> C,
+): TState<C> =
+  TState(
+    current = { t -> f(current.invoke(t), other.current.invoke(t)) },
+    stateChanges = run {
+      val aChanges =
+        stateChanges
+          .map { a ->
+            val b = other.current.sample()
+            Triple(a, b, f(a, b))
+          }
+      val bChanges =
+        other
+          .stateChanges
+          .map { b ->
+            val a = current.sample()
+            Triple(a, b, f(a, b))
+          }
+      merge(aChanges, bChanges) { (a, _, _), (_, b, _) ->
+          Triple(a, b, f(a, b))
+        }
+        .map { (_, _, zipped) -> zipped }
+    },
+  )
+
+fun <A> TState<TState<A>>.flatten(): TState<A> {
+  val changes =
+    stateChanges
+      .pairwise()
+      .flatMap { ((t0, oldInner), (t2, _)) ->
+        val inWindow =
+          oldInner
+            .stateChanges
+            .filter { (t1, b) -> t0 <= t1 && t1 < t2 }
+        if (inWindow.firstOrNull()?.time != t0) {
+          listOf(Pair(t0, oldInner.current.invoke(t0))) + inWindow
+        } else {
+          inWindow
+        }
+      }
+  return TState(
+    current = { t -> current.invoke(t).current.invoke(t) },
+    stateChanges = changes.toSortedMap(),
+  )
+}
+
+open class FrpTranscationScope internal constructor(
+  internal val currentTime: Time,
+) {
+  val now: TFlow<Unit> =
+    sortedMapOf(currentTime to Unit)
+
+  fun <A> Transactional<A>.sample(): A =
+    invoke(currentTime)
+
+  fun <A> TState<A>.sample(): A =
+    current.sample()
+}
+
+class FrpStateScope internal constructor(
+  time: Time,
+  internal val stopTime: Time,
+): FrpTransactionScope(time) {
+
+  fun <A, B> TFlow<A>.fold(
+    initialValue: B,
+    f: FrpTransactionScope.(B, A) -> B,
+  ): TState<B> {
+    val truncated =
+      dropWhile { (t, _) -> t < currentTime }
+        .takeWhile { (t, _) -> t <= stopTime }
+    val folded =
+      truncated
+        .scan(Pair(currentTime, initialValue)) { (_, b) (t, a) ->
+          Pair(t, FrpTransactionScope(t).f(a, b))
+        }
+    val lookup = { t1 ->
+      folded.lastOrNull { (t0, _) -> t0 < t1 }?.value ?: initialValue
+    }
+    return TState(lookup, folded.toSortedMap())
+  }
+
+  fun <A> TFlow<A>.hold(initialValue: A): TState<A> =
+    fold(initialValue) { _, a -> a }
+
+  fun <K, V> TFlow<Map<K, Maybe<V>>>.foldMapIncrementally(
+    initialValues: Map<K, V>
+  ): TState<Map<K, V>> =
+    fold(initialValues) { patch, map ->
+      val eithers = patch.map { (k, v) ->
+        if (v is Just) Left(k to v.value) else Right(k)
+      }
+      val adds = eithers.filterIsInstance<Left>().map { it.left }
+      val removes = eithers.filterIsInstance<Right>().map { it.right }
+      val removed: Map<K, V> = map - removes.toSet()
+      val updated: Map<K, V> = removed + adds
+      updated
+    }
+
+  fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+    initialTFlows: Map<K, TFlow<V>>,
+  ): TFlow<Map<K, V>> =
+    foldMapIncrementally(initialTFlows).map { it.merge() }.switch()
+
+  fun <K, A, B> TFlow<Map<K, Maybe<A>>.mapLatestStatefulForKey(
+    transform: suspend FrpStateScope.(A) -> B,
+  ): TFlow<Map<K, Maybe<B>>> =
+    pairwise().map { ((t0, patch), (t1, _)) ->
+      patch.map { (k, ma) ->
+        ma.map { a ->
+          FrpStateScope(t0, t1).transform(a)
+        }
+      }
+    }
+  }
+
+}
+
+```
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt
new file mode 100644
index 0000000..298c071
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Combinators.kt
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.util.These
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.none
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.flow.conflate
+
+/**
+ * Returns a [TFlow] that emits the value sampled from the [Transactional] produced by each emission
+ * of the original [TFlow], within the same transaction of the original emission.
+ */
+fun <A> TFlow<Transactional<A>>.sampleTransactionals(): TFlow<A> = map { it.sample() }
+
+/** @see FrpTransactionScope.sample */
+fun <A, B, C> TFlow<A>.sample(
+    state: TState<B>,
+    transform: suspend FrpTransactionScope.(A, B) -> C,
+): TFlow<C> = map { transform(it, state.sample()) }
+
+/** @see FrpTransactionScope.sample */
+fun <A, B, C> TFlow<A>.sample(
+    transactional: Transactional<B>,
+    transform: suspend FrpTransactionScope.(A, B) -> C,
+): TFlow<C> = map { transform(it, transactional.sample()) }
+
+/**
+ * Like [sample], but if [state] is changing at the time it is sampled ([stateChanges] is emitting),
+ * then the new value is passed to [transform].
+ *
+ * Note that [sample] is both more performant, and safer to use with recursive definitions. You will
+ * generally want to use it rather than this.
+ *
+ * @see sample
+ */
+fun <A, B, C> TFlow<A>.samplePromptly(
+    state: TState<B>,
+    transform: suspend FrpTransactionScope.(A, B) -> C,
+): TFlow<C> =
+    sample(state) { a, b -> These.thiz<Pair<A, B>, B>(a to b) }
+        .mergeWith(state.stateChanges.map { These.that(it) }) { thiz, that ->
+            These.both((thiz as These.This).thiz, (that as These.That).that)
+        }
+        .mapMaybe { these ->
+            when (these) {
+                // both present, transform the upstream value and the new value
+                is These.Both -> just(transform(these.thiz.first, these.that))
+                // no upstream present, so don't perform the sample
+                is These.That -> none()
+                // just the upstream, so transform the upstream and the old value
+                is These.This -> just(transform(these.thiz.first, these.thiz.second))
+            }
+        }
+
+/**
+ * Returns a [TState] containing a map with a snapshot of the current state of each [TState] in the
+ * original map.
+ */
+fun <K, A> Map<K, TState<A>>.combineValues(): TState<Map<K, A>> =
+    asIterable()
+        .map { (k, state) -> state.map { v -> k to v } }
+        .combine()
+        .map { entries -> entries.toMap() }
+
+/**
+ * Returns a cold [Flow] that, when collected, emits from this [TFlow]. [network] is needed to
+ * transactionally connect to / disconnect from the [TFlow] when collection starts/stops.
+ */
+fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, emits from this [TState]. [network] is needed to
+ * transactionally connect to / disconnect from the [TState] when collection starts/stops.
+ */
+fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [FrpSpec] in a new transaction in this
+ * [network], and then emits from the returned [TFlow].
+ *
+ * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
+ */
+@JvmName("flowSpecToColdConflatedFlow")
+fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [FrpSpec] in a new transaction in this
+ * [network], and then emits from the returned [TState].
+ *
+ * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
+ */
+@JvmName("stateSpecToColdConflatedFlow")
+fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
+ * this [network], and then emits from the returned [TFlow].
+ */
+@JvmName("transactionalFlowToColdConflatedFlow")
+fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
+ * this [network], and then emits from the returned [TState].
+ */
+@JvmName("transactionalStateToColdConflatedFlow")
+fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [FrpStateful] in a new transaction in
+ * this [network], and then emits from the returned [TFlow].
+ *
+ * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
+ */
+@JvmName("statefulFlowToColdConflatedFlow")
+fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
+
+/**
+ * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
+ * this [network], and then emits from the returned [TState].
+ *
+ * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
+ */
+@JvmName("statefulStateToColdConflatedFlow")
+fun <A> FrpStateful<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
+    channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
+
+/** Return a [TFlow] that emits from the original [TFlow] only when [state] is `true`. */
+fun <A> TFlow<A>.filter(state: TState<Boolean>): TFlow<A> = filter { state.sample() }
+
+private fun Iterable<Boolean>.allTrue() = all { it }
+
+private fun Iterable<Boolean>.anyTrue() = any { it }
+
+/** Returns a [TState] that is `true` only when all of [states] are `true`. */
+fun allOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.allTrue() }
+
+/** Returns a [TState] that is `true` when any of [states] are `true`. */
+fun anyOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.anyTrue() }
+
+/** Returns a [TState] containing the inverse of the Boolean held by the original [TState]. */
+fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
+
+/**
+ * Represents a modal FRP sub-network.
+ *
+ * When [enabled][enableMode], all network modifications are applied immediately to the FRP network.
+ * When the returned [TFlow] emits a [FrpBuildMode], that mode is enabled and replaces this mode,
+ * undoing all modifications in the process (any registered [observers][FrpBuildScope.observe] are
+ * unregistered, and any pending [side-effects][FrpBuildScope.effect] are cancelled).
+ *
+ * Use [compiledFrpSpec] to compile and stand-up a mode graph.
+ *
+ * @see FrpStatefulMode
+ */
+fun interface FrpBuildMode<out A> {
+    /**
+     * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
+     * new mode.
+     */
+    suspend fun FrpBuildScope.enableMode(): Pair<A, TFlow<FrpBuildMode<A>>>
+}
+
+/**
+ * Returns an [FrpSpec] that, when [applied][FrpBuildScope.applySpec], stands up a modal-transition
+ * graph starting with this [FrpBuildMode], automatically switching to new modes as they are
+ * produced.
+ *
+ * @see FrpBuildMode
+ */
+val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>>
+    get() = frpSpec {
+        var modeChangeEvents by TFlowLoop<FrpBuildMode<A>>()
+        val activeMode: TState<Pair<A, TFlow<FrpBuildMode<A>>>> =
+            modeChangeEvents
+                .map { it.run { frpSpec { enableMode() } } }
+                .holdLatestSpec(frpSpec { enableMode() })
+        modeChangeEvents =
+            activeMode.map { statefully { it.second.nextOnly() } }.applyLatestStateful().switch()
+        activeMode.map { it.first }
+    }
+
+/**
+ * Represents a modal FRP sub-network.
+ *
+ * When [enabled][enableMode], all state accumulation is immediately started. When the returned
+ * [TFlow] emits a [FrpBuildMode], that mode is enabled and replaces this mode, stopping all state
+ * accumulation in the process.
+ *
+ * Use [compiledStateful] to compile and stand-up a mode graph.
+ *
+ * @see FrpBuildMode
+ */
+fun interface FrpStatefulMode<out A> {
+    /**
+     * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
+     * new mode.
+     */
+    suspend fun FrpStateScope.enableMode(): Pair<A, TFlow<FrpStatefulMode<A>>>
+}
+
+/**
+ * Returns an [FrpStateful] that, when [applied][FrpStateScope.applyStateful], stands up a
+ * modal-transition graph starting with this [FrpStatefulMode], automatically switching to new modes
+ * as they are produced.
+ *
+ * @see FrpBuildMode
+ */
+val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>>
+    get() = statefully {
+        var modeChangeEvents by TFlowLoop<FrpStatefulMode<A>>()
+        val activeMode: TState<Pair<A, TFlow<FrpStatefulMode<A>>>> =
+            modeChangeEvents
+                .map { it.run { statefully { enableMode() } } }
+                .holdLatestStateful(statefully { enableMode() })
+        modeChangeEvents =
+            activeMode.map { statefully { it.second.nextOnly() } }.applyLatestStateful().switch()
+        activeMode.map { it.first }
+    }
+
+/**
+ * Runs [spec] in this [FrpBuildScope], and then re-runs it whenever [rebuildSignal] emits. Returns
+ * a [TState] that holds the result of the currently-active [FrpSpec].
+ */
+fun <A> FrpBuildScope.rebuildOn(rebuildSignal: TFlow<*>, spec: FrpSpec<A>): TState<A> =
+    rebuildSignal.map { spec }.holdLatestSpec(spec)
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt
new file mode 100644
index 0000000..6e4c9eb
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpBuildScope.kt
@@ -0,0 +1,864 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.map
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.RestrictsSuspension
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.launch
+
+/** A function that modifies the FrpNetwork. */
+typealias FrpSpec<A> = suspend FrpBuildScope.() -> A
+
+/**
+ * Constructs an [FrpSpec]. The passed [block] will be invoked with an [FrpBuildScope] that can be
+ * used to perform network-building operations, including adding new inputs and outputs to the
+ * network, as well as all operations available in [FrpTransactionScope].
+ */
+@ExperimentalFrpApi
+@Suppress("NOTHING_TO_INLINE")
+inline fun <A> frpSpec(noinline block: suspend FrpBuildScope.() -> A): FrpSpec<A> = block
+
+/** Applies the [FrpSpec] within this [FrpBuildScope]. */
+@ExperimentalFrpApi
+inline operator fun <A> FrpBuildScope.invoke(block: FrpBuildScope.() -> A) = run(block)
+
+/** Operations that add inputs and outputs to an FRP network. */
+@ExperimentalFrpApi
+@RestrictsSuspension
+interface FrpBuildScope : FrpStateScope {
+
+    /** TODO: Javadoc */
+    @ExperimentalFrpApi
+    fun <R> deferredBuildScope(block: suspend FrpBuildScope.() -> R): FrpDeferredValue<R>
+
+    /** TODO: Javadoc */
+    @ExperimentalFrpApi fun deferredBuildScopeAction(block: suspend FrpBuildScope.() -> Unit)
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * Unlike [mapLatestBuild], these modifications are not undone with each subsequent emission of
+     * the original [TFlow].
+     *
+     * **NOTE:** This API does not [observe] the original [TFlow], meaning that unless the returned
+     * (or a downstream) [TFlow] is observed separately, [transform] will not be invoked, and no
+     * internal side-effects will occur.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapBuild(transform: suspend FrpBuildScope.(A) -> B): TFlow<B>
+
+    /**
+     * Invokes [block] whenever this [TFlow] emits a value, allowing side-effects to be safely
+     * performed in reaction to the emission.
+     *
+     * Specifically, [block] is deferred to the end of the transaction, and is only actually
+     * executed if this [FrpBuildScope] is still active by that time. It can be deactivated due to a
+     * -Latest combinator, for example.
+     *
+     * Shorthand for:
+     * ```kotlin
+     *   tFlow.observe { effect { ... } }
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.observe(
+        coroutineContext: CoroutineContext = EmptyCoroutineContext,
+        block: suspend FrpEffectScope.(A) -> Unit = {},
+    ): Job
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpSpec] emitted from the original
+     * [TFlow], and a [FrpDeferredValue] containing the result of applying [initialSpecs]
+     * immediately.
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] with the same
+     * key are undone (any registered [observers][observe] are unregistered, and any pending
+     * [side-effects][effect] are cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpSpec] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<FrpSpec<A>>>>.applyLatestSpecForKey(
+        initialSpecs: FrpDeferredValue<Map<K, FrpSpec<B>>>,
+        numKeys: Int? = null,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>>
+
+    /**
+     * Creates an instance of a [TFlow] with elements that are from [builder].
+     *
+     * [builder] is run in its own coroutine, allowing for ongoing work that can emit to the
+     * provided [MutableTFlow].
+     *
+     * By default, [builder] is only running while the returned [TFlow] is being
+     * [observed][observe]. If you want it to run at all times, simply add a no-op observer:
+     * ```kotlin
+     * tFlow { ... }.apply { observe() }
+     * ```
+     */
+    @ExperimentalFrpApi fun <T> tFlow(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T>
+
+    /**
+     * Creates an instance of a [TFlow] with elements that are emitted from [builder].
+     *
+     * [builder] is run in its own coroutine, allowing for ongoing work that can emit to the
+     * provided [MutableTFlow].
+     *
+     * By default, [builder] is only running while the returned [TFlow] is being
+     * [observed][observe]. If you want it to run at all times, simply add a no-op observer:
+     * ```kotlin
+     * tFlow { ... }.apply { observe() }
+     * ```
+     *
+     * In the event of backpressure, emissions are *coalesced* into batches. When a value is
+     * [emitted][FrpCoalescingProducerScope.emit] from [builder], it is merged into the batch via
+     * [coalesce]. Once the batch is consumed by the frp network in the next transaction, the batch
+     * is reset back to [getInitialValue].
+     */
+    @ExperimentalFrpApi
+    fun <In, Out> coalescingTFlow(
+        getInitialValue: () -> Out,
+        coalesce: (old: Out, new: In) -> Out,
+        builder: suspend FrpCoalescingProducerScope<In>.() -> Unit,
+    ): TFlow<Out>
+
+    /**
+     * Creates a new [FrpBuildScope] that is a child of this one.
+     *
+     * This new scope can be manually cancelled via the returned [Job], or will be cancelled
+     * automatically when its parent is cancelled. Cancellation will unregister all
+     * [observers][observe] and cancel all scheduled [effects][effect].
+     *
+     * The return value from [block] can be accessed via the returned [FrpDeferredValue].
+     */
+    @ExperimentalFrpApi fun <A> asyncScope(block: FrpSpec<A>): Pair<FrpDeferredValue<A>, Job>
+
+    // TODO: once we have context params, these can all become extensions:
+
+    /**
+     * Returns a [TFlow] containing the results of applying the given [transform] function to each
+     * value of the original [TFlow].
+     *
+     * Unlike [TFlow.map], [transform] can perform arbitrary asynchronous code. This code is run
+     * outside of the current FRP transaction; when [transform] returns, the returned value is
+     * emitted from the result [TFlow] in a new transaction.
+     *
+     * Shorthand for:
+     * ```kotlin
+     * tflow.mapLatestBuild { a -> asyncTFlow { transform(a) } }.flatten()
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapAsyncLatest(transform: suspend (A) -> B): TFlow<B> =
+        mapLatestBuild { a -> asyncTFlow { transform(a) } }.flatten()
+
+    /**
+     * Invokes [block] whenever this [TFlow] emits a value. [block] receives an [FrpBuildScope] that
+     * can be used to make further modifications to the FRP network, and/or perform side-effects via
+     * [effect].
+     *
+     * @see observe
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.observeBuild(block: suspend FrpBuildScope.(A) -> Unit = {}): Job =
+        mapBuild(block).observe()
+
+    /**
+     * Returns a [StateFlow] whose [value][StateFlow.value] tracks the current
+     * [value of this TState][TState.sample], and will emit at the same rate as
+     * [TState.stateChanges].
+     *
+     * Note that the [value][StateFlow.value] is not available until the *end* of the current
+     * transaction. If you need the current value before this time, then use [TState.sample].
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.toStateFlow(): StateFlow<A> {
+        val uninitialized = Any()
+        var initialValue: Any? = uninitialized
+        val innerStateFlow = MutableStateFlow<Any?>(uninitialized)
+        deferredBuildScope {
+            initialValue = sample()
+            stateChanges.observe {
+                innerStateFlow.value = it
+                initialValue = null
+            }
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        fun getValue(innerValue: Any?): A =
+            when {
+                innerValue !== uninitialized -> innerValue as A
+                initialValue !== uninitialized -> initialValue as A
+                else ->
+                    error(
+                        "Attempted to access StateFlow.value before FRP transaction has completed."
+                    )
+            }
+
+        return object : StateFlow<A> {
+            override val replayCache: List<A>
+                get() = innerStateFlow.replayCache.map(::getValue)
+
+            override val value: A
+                get() = getValue(innerStateFlow.value)
+
+            override suspend fun collect(collector: FlowCollector<A>): Nothing {
+                innerStateFlow.collect { collector.emit(getValue(it)) }
+            }
+        }
+    }
+
+    /**
+     * Returns a [SharedFlow] configured with a replay cache of size [replay] that emits the current
+     * [value][TState.sample] of this [TState] followed by all [stateChanges].
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.toSharedFlow(replay: Int = 0): SharedFlow<A> {
+        val result = MutableSharedFlow<A>(replay, extraBufferCapacity = 1)
+        deferredBuildScope {
+            result.tryEmit(sample())
+            stateChanges.observe { a -> result.tryEmit(a) }
+        }
+        return result
+    }
+
+    /**
+     * Returns a [SharedFlow] configured with a replay cache of size [replay] that emits values
+     * whenever this [TFlow] emits.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.toSharedFlow(replay: Int = 0): SharedFlow<A> {
+        val result = MutableSharedFlow<A>(replay, extraBufferCapacity = 1)
+        observe { a -> result.tryEmit(a) }
+        return result
+    }
+
+    /**
+     * Returns a [TState] that holds onto the value returned by applying the most recently emitted
+     * [FrpSpec] from the original [TFlow], or the value returned by applying [initialSpec] if
+     * nothing has been emitted since it was constructed.
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<FrpSpec<A>>.holdLatestSpec(initialSpec: FrpSpec<A>): TState<A> {
+        val (changes: TFlow<A>, initApplied: FrpDeferredValue<A>) = applyLatestSpec(initialSpec)
+        return changes.holdDeferred(initApplied)
+    }
+
+    /**
+     * Returns a [TState] containing the value returned by applying the [FrpSpec] held by the
+     * original [TState].
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<FrpSpec<A>>.applyLatestSpec(): TState<A> {
+        val (appliedChanges: TFlow<A>, init: FrpDeferredValue<A>) =
+            stateChanges.applyLatestSpec(frpSpec { sample().applySpec() })
+        return appliedChanges.holdDeferred(init)
+    }
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpSpec] emitted from the original
+     * [TFlow].
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<FrpSpec<A>>.applyLatestSpec(): TFlow<A> = applyLatestSpec(frpSpec {}).first
+
+    /**
+     * Returns a [TFlow] that switches to a new [TFlow] produced by [transform] every time the
+     * original [TFlow] emits a value.
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * When the original [TFlow] emits a new value, those changes are undone (any registered
+     * [observers][observe] are unregistered, and any pending [effects][effect] are cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.flatMapLatestBuild(
+        transform: suspend FrpBuildScope.(A) -> TFlow<B>
+    ): TFlow<B> = mapCheap { frpSpec { transform(it) } }.applyLatestSpec().flatten()
+
+    /**
+     * Returns a [TState] by applying [transform] to the value held by the original [TState].
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * When the value held by the original [TState] changes, those changes are undone (any
+     * registered [observers][observe] are unregistered, and any pending [effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TState<A>.flatMapLatestBuild(
+        transform: suspend FrpBuildScope.(A) -> TState<B>
+    ): TState<B> = mapLatestBuild { transform(it) }.flatten()
+
+    /**
+     * Returns a [TState] that transforms the value held inside this [TState] by applying it to the
+     * [transform].
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * When the value held by the original [TState] changes, those changes are undone (any
+     * registered [observers][observe] are unregistered, and any pending [effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TState<A>.mapLatestBuild(transform: suspend FrpBuildScope.(A) -> B): TState<B> =
+        mapCheapUnsafe { frpSpec { transform(it) } }.applyLatestSpec()
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpSpec] emitted from the original
+     * [TFlow], and a [FrpDeferredValue] containing the result of applying [initialSpec]
+     * immediately.
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A : Any?, B> TFlow<FrpSpec<B>>.applyLatestSpec(
+        initialSpec: FrpSpec<A>
+    ): Pair<TFlow<B>, FrpDeferredValue<A>> {
+        val (flow, result) =
+            mapCheap { spec -> mapOf(Unit to just(spec)) }
+                .applyLatestSpecForKey(initialSpecs = mapOf(Unit to initialSpec), numKeys = 1)
+        val outFlow: TFlow<B> =
+            flow.mapMaybe {
+                checkNotNull(it[Unit]) { "applyLatest: expected result, but none present in: $it" }
+            }
+        val outInit: FrpDeferredValue<A> = deferredBuildScope {
+            val initResult: Map<Unit, A> = result.get()
+            check(Unit in initResult) {
+                "applyLatest: expected initial result, but none present in: $initResult"
+            }
+            @Suppress("UNCHECKED_CAST")
+            initResult.getOrDefault(Unit) { null } as A
+        }
+        return Pair(outFlow, outInit)
+    }
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapLatestBuild(transform: suspend FrpBuildScope.(A) -> B): TFlow<B> =
+        mapCheap { frpSpec { transform(it) } }.applyLatestSpec()
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValue] immediately.
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapLatestBuild(
+        initialValue: A,
+        transform: suspend FrpBuildScope.(A) -> B,
+    ): Pair<TFlow<B>, FrpDeferredValue<B>> =
+        mapLatestBuildDeferred(deferredOf(initialValue), transform)
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValue] immediately.
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapLatestBuildDeferred(
+        initialValue: FrpDeferredValue<A>,
+        transform: suspend FrpBuildScope.(A) -> B,
+    ): Pair<TFlow<B>, FrpDeferredValue<B>> =
+        mapCheap { frpSpec { transform(it) } }
+            .applyLatestSpec(initialSpec = frpSpec { transform(initialValue.get()) })
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpSpec] emitted from the original
+     * [TFlow], and a [FrpDeferredValue] containing the result of applying [initialSpecs]
+     * immediately.
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] with the same
+     * key are undone (any registered [observers][observe] are unregistered, and any pending
+     * [side-effects][effect] are cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpSpec] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<FrpSpec<A>>>>.applyLatestSpecForKey(
+        initialSpecs: Map<K, FrpSpec<B>>,
+        numKeys: Int? = null,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> =
+        applyLatestSpecForKey(deferredOf(initialSpecs), numKeys)
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpSpec] emitted from the original
+     * [TFlow].
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] with the same
+     * key are undone (any registered [observers][observe] are unregistered, and any pending
+     * [side-effects][effect] are cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpSpec] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpSpec<A>>>>.applyLatestSpecForKey(
+        numKeys: Int? = null
+    ): TFlow<Map<K, Maybe<A>>> =
+        applyLatestSpecForKey<K, A, Nothing>(deferredOf(emptyMap()), numKeys).first
+
+    /**
+     * Returns a [TState] containing the latest results of applying each [FrpSpec] emitted from the
+     * original [TFlow].
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] with the same
+     * key are undone (any registered [observers][observe] are unregistered, and any pending
+     * [side-effects][effect] are cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpSpec] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpSpec<A>>>>.holdLatestSpecForKey(
+        initialSpecs: FrpDeferredValue<Map<K, FrpSpec<A>>>,
+        numKeys: Int? = null,
+    ): TState<Map<K, A>> {
+        val (changes, initialValues) = applyLatestSpecForKey(initialSpecs, numKeys)
+        return changes.foldMapIncrementally(initialValues)
+    }
+
+    /**
+     * Returns a [TState] containing the latest results of applying each [FrpSpec] emitted from the
+     * original [TFlow].
+     *
+     * When each [FrpSpec] is applied, changes from the previously-active [FrpSpec] with the same
+     * key are undone (any registered [observers][observe] are unregistered, and any pending
+     * [side-effects][effect] are cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpSpec] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpSpec<A>>>>.holdLatestSpecForKey(
+        initialSpecs: Map<K, FrpSpec<A>> = emptyMap(),
+        numKeys: Int? = null,
+    ): TState<Map<K, A>> = holdLatestSpecForKey(deferredOf(initialSpecs), numKeys)
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValues] immediately.
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpBuildScope] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestBuildForKey(
+        initialValues: FrpDeferredValue<Map<K, A>>,
+        numKeys: Int? = null,
+        transform: suspend FrpBuildScope.(A) -> B,
+    ): Pair<TFlow<Map<K, Maybe<B>>>, FrpDeferredValue<Map<K, B>>> =
+        map { patch -> patch.mapValues { (_, v) -> v.map { frpSpec { transform(it) } } } }
+            .applyLatestSpecForKey(
+                deferredBuildScope {
+                    initialValues.get().mapValues { (_, v) -> frpSpec { transform(v) } }
+                },
+                numKeys = numKeys,
+            )
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValues] immediately.
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpBuildScope] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestBuildForKey(
+        initialValues: Map<K, A>,
+        numKeys: Int? = null,
+        transform: suspend FrpBuildScope.(A) -> B,
+    ): Pair<TFlow<Map<K, Maybe<B>>>, FrpDeferredValue<Map<K, B>>> =
+        mapLatestBuildForKey(deferredOf(initialValues), numKeys, transform)
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform modifications to the FRP network via its [FrpBuildScope] receiver.
+     * With each invocation of [transform], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpBuildScope] will be undone with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestBuildForKey(
+        numKeys: Int? = null,
+        transform: suspend FrpBuildScope.(A) -> B,
+    ): TFlow<Map<K, Maybe<B>>> = mapLatestBuildForKey(emptyMap(), numKeys, transform).first
+
+    /** Returns a [Deferred] containing the next value to be emitted from this [TFlow]. */
+    @ExperimentalFrpApi
+    fun <R> TFlow<R>.nextDeferred(): Deferred<R> {
+        lateinit var next: CompletableDeferred<R>
+        val job = nextOnly().observe { next.complete(it) }
+        next = CompletableDeferred<R>(parent = job)
+        return next
+    }
+
+    /** Returns a [TState] that reflects the [StateFlow.value] of this [StateFlow]. */
+    @ExperimentalFrpApi
+    fun <A> StateFlow<A>.toTState(): TState<A> {
+        val initial = value
+        return tFlow { dropWhile { it == initial }.collect { emit(it) } }.hold(initial)
+    }
+
+    /** Returns a [TFlow] that emits whenever this [Flow] emits. */
+    @ExperimentalFrpApi fun <A> Flow<A>.toTFlow(): TFlow<A> = tFlow { collect { emit(it) } }
+
+    /**
+     * Shorthand for:
+     * ```kotlin
+     * flow.toTFlow().hold(initialValue)
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A> Flow<A>.toTState(initialValue: A): TState<A> = toTFlow().hold(initialValue)
+
+    /**
+     * Invokes [block] whenever this [TFlow] emits a value. [block] receives an [FrpBuildScope] that
+     * can be used to make further modifications to the FRP network, and/or perform side-effects via
+     * [effect].
+     *
+     * With each invocation of [block], changes from the previous invocation are undone (any
+     * registered [observers][observe] are unregistered, and any pending [side-effects][effect] are
+     * cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.observeLatestBuild(block: suspend FrpBuildScope.(A) -> Unit = {}): Job =
+        mapLatestBuild { block(it) }.observe()
+
+    /**
+     * Invokes [block] whenever this [TFlow] emits a value, allowing side-effects to be safely
+     * performed in reaction to the emission.
+     *
+     * With each invocation of [block], running effects from the previous invocation are cancelled.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.observeLatest(block: suspend FrpEffectScope.(A) -> Unit = {}): Job {
+        var innerJob: Job? = null
+        return observeBuild {
+            innerJob?.cancel()
+            innerJob = effect { block(it) }
+        }
+    }
+
+    /**
+     * Invokes [block] with the value held by this [TState], allowing side-effects to be safely
+     * performed in reaction to the state changing.
+     *
+     * With each invocation of [block], running effects from the previous invocation are cancelled.
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.observeLatest(block: suspend FrpEffectScope.(A) -> Unit = {}): Job =
+        launchScope {
+            var innerJob = effect { block(sample()) }
+            stateChanges.observeBuild {
+                innerJob.cancel()
+                innerJob = effect { block(it) }
+            }
+        }
+
+    /**
+     * Applies [block] to the value held by this [TState]. [block] receives an [FrpBuildScope] that
+     * can be used to make further modifications to the FRP network, and/or perform side-effects via
+     * [effect].
+     *
+     * [block] can perform modifications to the FRP network via its [FrpBuildScope] receiver. With
+     * each invocation of [block], changes from the previous invocation are undone (any registered
+     * [observers][observe] are unregistered, and any pending [side-effects][effect] are cancelled).
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.observeLatestBuild(block: suspend FrpBuildScope.(A) -> Unit = {}): Job =
+        launchScope {
+            var innerJob: Job = launchScope { block(sample()) }
+            stateChanges.observeBuild {
+                innerJob.cancel()
+                innerJob = launchScope { block(it) }
+            }
+        }
+
+    /** Applies the [FrpSpec] within this [FrpBuildScope]. */
+    @ExperimentalFrpApi suspend fun <A> FrpSpec<A>.applySpec(): A = this()
+
+    /**
+     * Applies the [FrpSpec] within this [FrpBuildScope], returning the result as an
+     * [FrpDeferredValue].
+     */
+    @ExperimentalFrpApi
+    fun <A> FrpSpec<A>.applySpecDeferred(): FrpDeferredValue<A> = deferredBuildScope { applySpec() }
+
+    /**
+     * Invokes [block] on the value held in this [TState]. [block] receives an [FrpBuildScope] that
+     * can be used to make further modifications to the FRP network, and/or perform side-effects via
+     * [effect].
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.observeBuild(block: suspend FrpBuildScope.(A) -> Unit = {}): Job =
+        launchScope {
+            block(sample())
+            stateChanges.observeBuild(block)
+        }
+
+    /**
+     * Invokes [block] with the current value of this [TState], re-invoking whenever it changes,
+     * allowing side-effects to be safely performed in reaction value changing.
+     *
+     * Specifically, [block] is deferred to the end of the transaction, and is only actually
+     * executed if this [FrpBuildScope] is still active by that time. It can be deactivated due to a
+     * -Latest combinator, for example.
+     *
+     * If the [TState] is changing within the *current* transaction (i.e. [stateChanges] is
+     * presently emitting) then [block] will be invoked for the first time with the new value;
+     * otherwise, it will be invoked with the [current][sample] value.
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<A>.observe(block: suspend FrpEffectScope.(A) -> Unit = {}): Job =
+        now.map { sample() }.mergeWith(stateChanges) { _, new -> new }.observe { block(it) }
+}
+
+/**
+ * Returns a [TFlow] that emits the result of [block] once it completes. [block] is evaluated
+ * outside of the current FRP transaction; when it completes, the returned [TFlow] emits in a new
+ * transaction.
+ *
+ * Shorthand for:
+ * ```
+ * tFlow { emitter: MutableTFlow<A> ->
+ *     val a = block()
+ *     emitter.emit(a)
+ * }
+ * ```
+ */
+@ExperimentalFrpApi
+fun <A> FrpBuildScope.asyncTFlow(block: suspend () -> A): TFlow<A> =
+    tFlow {
+            // TODO: if block completes synchronously, it would be nice to emit within this
+            //  transaction
+            emit(block())
+        }
+        .apply { observe() }
+
+/**
+ * Performs a side-effect in a safe manner w/r/t the current FRP transaction.
+ *
+ * Specifically, [block] is deferred to the end of the current transaction, and is only actually
+ * executed if this [FrpBuildScope] is still active by that time. It can be deactivated due to a
+ * -Latest combinator, for example.
+ *
+ * Shorthand for:
+ * ```kotlin
+ *   now.observe { block() }
+ * ```
+ */
+@ExperimentalFrpApi
+fun FrpBuildScope.effect(block: suspend FrpEffectScope.() -> Unit): Job = now.observe { block() }
+
+/**
+ * Launches [block] in a new coroutine, returning a [Job] bound to the coroutine.
+ *
+ * This coroutine is not actually started until the *end* of the current FRP transaction. This is
+ * done because the current [FrpBuildScope] might be deactivated within this transaction, perhaps
+ * due to a -Latest combinator. If this happens, then the coroutine will never actually be started.
+ *
+ * Shorthand for:
+ * ```kotlin
+ *   effect { frpCoroutineScope.launch { block() } }
+ * ```
+ */
+@ExperimentalFrpApi
+fun FrpBuildScope.launchEffect(block: suspend CoroutineScope.() -> Unit): Job = asyncEffect(block)
+
+/**
+ * Launches [block] in a new coroutine, returning the result as a [Deferred].
+ *
+ * This coroutine is not actually started until the *end* of the current FRP transaction. This is
+ * done because the current [FrpBuildScope] might be deactivated within this transaction, perhaps
+ * due to a -Latest combinator. If this happens, then the coroutine will never actually be started.
+ *
+ * Shorthand for:
+ * ```kotlin
+ *   CompletableDeferred<R>.apply {
+ *       effect { frpCoroutineScope.launch { complete(coroutineScope { block() }) } }
+ *     }
+ *     .await()
+ * ```
+ */
+@ExperimentalFrpApi
+fun <R> FrpBuildScope.asyncEffect(block: suspend CoroutineScope.() -> R): Deferred<R> {
+    val result = CompletableDeferred<R>()
+    val job = now.observe { frpCoroutineScope.launch { result.complete(coroutineScope(block)) } }
+    val handle = job.invokeOnCompletion { result.cancel() }
+    result.invokeOnCompletion {
+        handle.dispose()
+        job.cancel()
+    }
+    return result
+}
+
+/** Like [FrpBuildScope.asyncScope], but ignores the result of [block]. */
+@ExperimentalFrpApi fun FrpBuildScope.launchScope(block: FrpSpec<*>): Job = asyncScope(block).second
+
+/**
+ * Creates an instance of a [TFlow] with elements that are emitted from [builder].
+ *
+ * [builder] is run in its own coroutine, allowing for ongoing work that can emit to the provided
+ * [MutableTFlow].
+ *
+ * By default, [builder] is only running while the returned [TFlow] is being
+ * [observed][FrpBuildScope.observe]. If you want it to run at all times, simply add a no-op
+ * observer:
+ * ```kotlin
+ * tFlow { ... }.apply { observe() }
+ * ```
+ *
+ * In the event of backpressure, emissions are *coalesced* into batches. When a value is
+ * [emitted][FrpCoalescingProducerScope.emit] from [builder], it is merged into the batch via
+ * [coalesce]. Once the batch is consumed by the FRP network in the next transaction, the batch is
+ * reset back to [initialValue].
+ */
+@ExperimentalFrpApi
+fun <In, Out> FrpBuildScope.coalescingTFlow(
+    initialValue: Out,
+    coalesce: (old: Out, new: In) -> Out,
+    builder: suspend FrpCoalescingProducerScope<In>.() -> Unit,
+): TFlow<Out> = coalescingTFlow(getInitialValue = { initialValue }, coalesce, builder)
+
+/**
+ * Creates an instance of a [TFlow] with elements that are emitted from [builder].
+ *
+ * [builder] is run in its own coroutine, allowing for ongoing work that can emit to the provided
+ * [MutableTFlow].
+ *
+ * By default, [builder] is only running while the returned [TFlow] is being
+ * [observed][FrpBuildScope.observe]. If you want it to run at all times, simply add a no-op
+ * observer:
+ * ```kotlin
+ * tFlow { ... }.apply { observe() }
+ * ```
+ *
+ * In the event of backpressure, emissions are *conflated*; any older emissions are dropped and only
+ * the most recent emission will be used when the FRP network is ready.
+ */
+@ExperimentalFrpApi
+fun <T> FrpBuildScope.conflatedTFlow(
+    builder: suspend FrpCoalescingProducerScope<T>.() -> Unit
+): TFlow<T> =
+    coalescingTFlow<T, Any?>(initialValue = Any(), coalesce = { _, new -> new }, builder = builder)
+        .mapCheap {
+            @Suppress("UNCHECKED_CAST")
+            it as T
+        }
+
+/** Scope for emitting to a [FrpBuildScope.coalescingTFlow]. */
+interface FrpCoalescingProducerScope<in T> {
+    /**
+     * Inserts [value] into the current batch, enqueueing it for emission from this [TFlow] if not
+     * already pending.
+     *
+     * Backpressure occurs when [emit] is called while the FRP network is currently in a
+     * transaction; if called multiple times, then emissions will be coalesced into a single batch
+     * that is then processed when the network is ready.
+     */
+    fun emit(value: T)
+}
+
+/** Scope for emitting to a [FrpBuildScope.tFlow]. */
+interface FrpProducerScope<in T> {
+    /**
+     * Emits a [value] to this [TFlow], suspending the caller until the FRP transaction containing
+     * the emission has completed.
+     */
+    suspend fun emit(value: T)
+}
+
+/**
+ * Suspends forever. Upon cancellation, runs [block]. Useful for unregistering callbacks inside of
+ * [FrpBuildScope.tFlow] and [FrpBuildScope.coalescingTFlow].
+ */
+suspend fun awaitClose(block: () -> Unit): Nothing =
+    try {
+        awaitCancellation()
+    } finally {
+        block()
+    }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt
new file mode 100644
index 0000000..a8ec98f
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpEffectScope.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import kotlin.coroutines.RestrictsSuspension
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Scope for external side-effects triggered by the Frp network. This still occurs within the
+ * context of a transaction, so general suspending calls are disallowed to prevent blocking the
+ * transaction. You can use [frpCoroutineScope] to [launch] new coroutines to perform long-running
+ * asynchronous work. This scope is alive for the duration of the containing [FrpBuildScope] that
+ * this side-effect scope is running in.
+ */
+@RestrictsSuspension
+@ExperimentalFrpApi
+interface FrpEffectScope : FrpTransactionScope {
+    /**
+     * A [CoroutineScope] whose lifecycle lives for as long as this [FrpEffectScope] is alive. This
+     * is generally until the [Job] returned by [FrpBuildScope.effect] is cancelled.
+     */
+    @ExperimentalFrpApi val frpCoroutineScope: CoroutineScope
+
+    /**
+     * A [FrpNetwork] instance that can be used to transactionally query / modify the FRP network.
+     *
+     * The lambda passed to [FrpNetwork.transact] on this instance will receive an [FrpBuildScope]
+     * that is lifetime-bound to this [FrpEffectScope]. Once this [FrpEffectScope] is no longer
+     * alive, any modifications to the FRP network performed via this [FrpNetwork] instance will be
+     * undone (any registered [observers][FrpBuildScope.observe] are unregistered, and any pending
+     * [side-effects][FrpBuildScope.effect] are cancelled).
+     */
+    @ExperimentalFrpApi val frpNetwork: FrpNetwork
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt
new file mode 100644
index 0000000..acc76d9
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpNetwork.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.internal.BuildScopeImpl
+import com.android.systemui.experimental.frp.internal.Network
+import com.android.systemui.experimental.frp.internal.StateScopeImpl
+import com.android.systemui.experimental.frp.internal.util.awaitCancellationAndThen
+import com.android.systemui.experimental.frp.internal.util.childScope
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.job
+import kotlinx.coroutines.launch
+
+/**
+ * Marks declarations that are still **experimental** and shouldn't be used in general production
+ * code.
+ */
+@RequiresOptIn(
+    message = "This API is experimental and should not be used in general production code."
+)
+@Retention(AnnotationRetention.BINARY)
+annotation class ExperimentalFrpApi
+
+/**
+ * External interface to an FRP network. Can be used to make transactional queries and modifications
+ * to the network.
+ */
+@ExperimentalFrpApi
+interface FrpNetwork {
+    /**
+     * Runs [block] inside of a transaction, suspending until the transaction is complete.
+     *
+     * The [FrpBuildScope] receiver exposes methods that can be used to query or modify the network.
+     * If the network is cancelled while the caller of [transact] is suspended, then the call will
+     * be cancelled.
+     */
+    @ExperimentalFrpApi suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R
+
+    /**
+     * Activates [spec] in a transaction, suspending indefinitely. While suspended, all observers
+     * and long-running effects are kept alive. When cancelled, observers are unregistered and
+     * effects are cancelled.
+     */
+    @ExperimentalFrpApi suspend fun activateSpec(spec: FrpSpec<*>)
+
+    /** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
+    @ExperimentalFrpApi
+    fun <In, Out> coalescingMutableTFlow(
+        coalesce: (old: Out, new: In) -> Out,
+        getInitialValue: () -> Out,
+    ): CoalescingMutableTFlow<In, Out>
+
+    /** Returns a [MutableTFlow] that can emit values into this [FrpNetwork]. */
+    @ExperimentalFrpApi fun <T> mutableTFlow(): MutableTFlow<T>
+
+    /** Returns a [MutableTState]. with initial state [initialValue]. */
+    @ExperimentalFrpApi
+    fun <T> mutableTStateDeferred(initialValue: FrpDeferredValue<T>): MutableTState<T>
+}
+
+/** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
+@ExperimentalFrpApi
+fun <In, Out> FrpNetwork.coalescingMutableTFlow(
+    coalesce: (old: Out, new: In) -> Out,
+    initialValue: Out,
+): CoalescingMutableTFlow<In, Out> =
+    coalescingMutableTFlow(coalesce, getInitialValue = { initialValue })
+
+/** Returns a [MutableTState]. with initial state [initialValue]. */
+@ExperimentalFrpApi
+fun <T> FrpNetwork.mutableTState(initialValue: T): MutableTState<T> =
+    mutableTStateDeferred(deferredOf(initialValue))
+
+/** Returns a [MutableTState]. with initial state [initialValue]. */
+@ExperimentalFrpApi
+fun <T> MutableTState(network: FrpNetwork, initialValue: T): MutableTState<T> =
+    network.mutableTState(initialValue)
+
+/** Returns a [MutableTFlow] that can emit values into this [FrpNetwork]. */
+@ExperimentalFrpApi
+fun <T> MutableTFlow(network: FrpNetwork): MutableTFlow<T> = network.mutableTFlow()
+
+/** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
+@ExperimentalFrpApi
+fun <In, Out> CoalescingMutableTFlow(
+    network: FrpNetwork,
+    coalesce: (old: Out, new: In) -> Out,
+    initialValue: Out,
+): CoalescingMutableTFlow<In, Out> = network.coalescingMutableTFlow(coalesce) { initialValue }
+
+/** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
+@ExperimentalFrpApi
+fun <In, Out> CoalescingMutableTFlow(
+    network: FrpNetwork,
+    coalesce: (old: Out, new: In) -> Out,
+    getInitialValue: () -> Out,
+): CoalescingMutableTFlow<In, Out> = network.coalescingMutableTFlow(coalesce, getInitialValue)
+
+/**
+ * Activates [spec] in a transaction and invokes [block] with the result, suspending indefinitely.
+ * While suspended, all observers and long-running effects are kept alive. When cancelled, observers
+ * are unregistered and effects are cancelled.
+ */
+@ExperimentalFrpApi
+suspend fun <R> FrpNetwork.activateSpec(spec: FrpSpec<R>, block: suspend (R) -> Unit) {
+    activateSpec {
+        val result = spec.applySpec()
+        launchEffect { block(result) }
+    }
+}
+
+internal class LocalFrpNetwork(
+    private val network: Network,
+    private val scope: CoroutineScope,
+    private val endSignal: TFlow<Any>,
+) : FrpNetwork {
+    override suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R {
+        val result = CompletableDeferred<R>(coroutineContext[Job])
+        @Suppress("DeferredResultUnused")
+        network.transaction {
+            val buildScope =
+                BuildScopeImpl(
+                    stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
+                    coroutineScope = scope,
+                )
+            buildScope.runInBuildScope { effect { result.complete(block()) } }
+        }
+        return result.await()
+    }
+
+    override suspend fun activateSpec(spec: FrpSpec<*>) {
+        val job =
+            network
+                .transaction {
+                    val buildScope =
+                        BuildScopeImpl(
+                            stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
+                            coroutineScope = scope,
+                        )
+                    buildScope.runInBuildScope { launchScope(spec) }
+                }
+                .await()
+        awaitCancellationAndThen { job.cancel() }
+    }
+
+    override fun <In, Out> coalescingMutableTFlow(
+        coalesce: (old: Out, new: In) -> Out,
+        getInitialValue: () -> Out,
+    ): CoalescingMutableTFlow<In, Out> = CoalescingMutableTFlow(coalesce, network, getInitialValue)
+
+    override fun <T> mutableTFlow(): MutableTFlow<T> = MutableTFlow(network)
+
+    override fun <T> mutableTStateDeferred(initialValue: FrpDeferredValue<T>): MutableTState<T> =
+        MutableTState(network, initialValue.unwrapped)
+}
+
+/**
+ * Combination of an [FrpNetwork] and a [Job] that, when cancelled, will cancel the entire FRP
+ * network.
+ */
+@ExperimentalFrpApi
+class RootFrpNetwork
+internal constructor(private val network: Network, private val scope: CoroutineScope, job: Job) :
+    Job by job, FrpNetwork by LocalFrpNetwork(network, scope, emptyTFlow)
+
+/** Constructs a new [RootFrpNetwork] in the given [CoroutineScope]. */
+@ExperimentalFrpApi
+fun CoroutineScope.newFrpNetwork(
+    context: CoroutineContext = EmptyCoroutineContext
+): RootFrpNetwork {
+    val scope = childScope(context)
+    val network = Network(scope)
+    scope.launch(CoroutineName("newFrpNetwork scheduler")) { network.runInputScheduler() }
+    return RootFrpNetwork(network, scope, scope.coroutineContext.job)
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt
new file mode 100644
index 0000000..a5a7977
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpScope.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import kotlin.coroutines.RestrictsSuspension
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/** Denotes [FrpScope] interfaces as [DSL markers][DslMarker]. */
+@DslMarker annotation class FrpScopeMarker
+
+/**
+ * Base scope for all FRP scopes. Used to prevent implicitly capturing other scopes from in lambdas.
+ */
+@FrpScopeMarker
+@RestrictsSuspension
+@ExperimentalFrpApi
+interface FrpScope {
+    /**
+     * Returns the value held by the [FrpDeferredValue], suspending until available if necessary.
+     */
+    @ExperimentalFrpApi
+    @OptIn(ExperimentalCoroutinesApi::class)
+    suspend fun <A> FrpDeferredValue<A>.get(): A = suspendCancellableCoroutine { k ->
+        unwrapped.invokeOnCompletion { ex ->
+            ex?.let { k.resumeWithException(ex) } ?: k.resume(unwrapped.getCompleted())
+        }
+    }
+}
+
+/**
+ * A value that may not be immediately (synchronously) available, but is guaranteed to be available
+ * before this transaction is completed.
+ *
+ * @see FrpScope.get
+ */
+@ExperimentalFrpApi
+class FrpDeferredValue<out A> internal constructor(internal val unwrapped: Deferred<A>)
+
+/**
+ * Returns the value held by this [FrpDeferredValue], or throws [IllegalStateException] if it is not
+ * yet available.
+ *
+ * This API is not meant for general usage within the FRP network. It is made available mainly for
+ * debugging and logging. You should always prefer [get][FrpScope.get] if possible.
+ *
+ * @see FrpScope.get
+ */
+@ExperimentalFrpApi
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <A> FrpDeferredValue<A>.getUnsafe(): A = unwrapped.getCompleted()
+
+/** Returns an already-available [FrpDeferredValue] containing [value]. */
+@ExperimentalFrpApi
+fun <A> deferredOf(value: A): FrpDeferredValue<A> = FrpDeferredValue(CompletableDeferred(value))
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt
new file mode 100644
index 0000000..61336f4
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpStateScope.kt
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.combine as combinePure
+import com.android.systemui.experimental.frp.map as mapPure
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Left
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.Right
+import com.android.systemui.experimental.frp.util.WithPrev
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.experimental.frp.util.partitionEithers
+import com.android.systemui.experimental.frp.util.zipWith
+import kotlin.coroutines.RestrictsSuspension
+
+typealias FrpStateful<R> = suspend FrpStateScope.() -> R
+
+/**
+ * Returns a [FrpStateful] that, when [applied][FrpStateScope.applyStateful], invokes [block] with
+ * the applier's [FrpStateScope].
+ */
+// TODO: caching story? should each Scope have a cache of applied FrpStateful instances?
+@ExperimentalFrpApi
+@Suppress("NOTHING_TO_INLINE")
+inline fun <A> statefully(noinline block: suspend FrpStateScope.() -> A): FrpStateful<A> = block
+
+/**
+ * Operations that accumulate state within the FRP network.
+ *
+ * State accumulation is an ongoing process that has a lifetime. Use `-Latest` combinators, such as
+ * [mapLatestStateful], to create smaller, nested lifecycles so that accumulation isn't running
+ * longer than needed.
+ */
+@ExperimentalFrpApi
+@RestrictsSuspension
+interface FrpStateScope : FrpTransactionScope {
+
+    /** TODO */
+    @ExperimentalFrpApi
+    // TODO: wish this could just be `deferred` but alas
+    fun <A> deferredStateScope(block: suspend FrpStateScope.() -> A): FrpDeferredValue<A>
+
+    /**
+     * Returns a [TState] that holds onto the most recently emitted value from this [TFlow], or
+     * [initialValue] if nothing has been emitted since it was constructed.
+     *
+     * Note that the value contained within the [TState] is not updated until *after* all [TFlow]s
+     * have been processed; this keeps the value of the [TState] consistent during the entire FRP
+     * transaction.
+     */
+    @ExperimentalFrpApi fun <A> TFlow<A>.holdDeferred(initialValue: FrpDeferredValue<A>): TState<A>
+
+    /**
+     * Returns a [TFlow] that emits from a merged, incrementally-accumulated collection of [TFlow]s
+     * emitted from this, following the same "patch" rules as outlined in [foldMapIncrementally].
+     *
+     * Conceptually this is equivalent to:
+     * ```kotlin
+     *   fun <K, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+     *     initialTFlows: Map<K, TFlow<V>>,
+     *   ): TFlow<Map<K, V>> =
+     *     foldMapIncrementally(initialTFlows).map { it.merge() }.switch()
+     * ```
+     *
+     * While the behavior is equivalent to the conceptual definition above, the implementation is
+     * significantly more efficient.
+     *
+     * @see merge
+     */
+    @ExperimentalFrpApi
+    fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+        initialTFlows: FrpDeferredValue<Map<K, TFlow<V>>>
+    ): TFlow<Map<K, V>>
+
+    /**
+     * Returns a [TFlow] that emits from a merged, incrementally-accumulated collection of [TFlow]s
+     * emitted from this, following the same "patch" rules as outlined in [foldMapIncrementally].
+     *
+     * Conceptually this is equivalent to:
+     * ```kotlin
+     *   fun <K, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPrompt(
+     *     initialTFlows: Map<K, TFlow<V>>,
+     *   ): TFlow<Map<K, V>> =
+     *     foldMapIncrementally(initialTFlows).map { it.merge() }.switchPromptly()
+     * ```
+     *
+     * While the behavior is equivalent to the conceptual definition above, the implementation is
+     * significantly more efficient.
+     *
+     * @see merge
+     */
+    @ExperimentalFrpApi
+    fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPromptly(
+        initialTFlows: FrpDeferredValue<Map<K, TFlow<V>>>
+    ): TFlow<Map<K, V>>
+
+    // TODO: everything below this comment can be made into extensions once we have context params
+
+    /**
+     * Returns a [TFlow] that emits from a merged, incrementally-accumulated collection of [TFlow]s
+     * emitted from this, following the same "patch" rules as outlined in [foldMapIncrementally].
+     *
+     * Conceptually this is equivalent to:
+     * ```kotlin
+     *   fun <K, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+     *     initialTFlows: Map<K, TFlow<V>>,
+     *   ): TFlow<Map<K, V>> =
+     *     foldMapIncrementally(initialTFlows).map { it.merge() }.switch()
+     * ```
+     *
+     * While the behavior is equivalent to the conceptual definition above, the implementation is
+     * significantly more efficient.
+     *
+     * @see merge
+     */
+    @ExperimentalFrpApi
+    fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+        initialTFlows: Map<K, TFlow<V>> = emptyMap()
+    ): TFlow<Map<K, V>> = mergeIncrementally(deferredOf(initialTFlows))
+
+    /**
+     * Returns a [TFlow] that emits from a merged, incrementally-accumulated collection of [TFlow]s
+     * emitted from this, following the same "patch" rules as outlined in [foldMapIncrementally].
+     *
+     * Conceptually this is equivalent to:
+     * ```kotlin
+     *   fun <K, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPrompt(
+     *     initialTFlows: Map<K, TFlow<V>>,
+     *   ): TFlow<Map<K, V>> =
+     *     foldMapIncrementally(initialTFlows).map { it.merge() }.switchPromptly()
+     * ```
+     *
+     * While the behavior is equivalent to the conceptual definition above, the implementation is
+     * significantly more efficient.
+     *
+     * @see merge
+     */
+    @ExperimentalFrpApi
+    fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPromptly(
+        initialTFlows: Map<K, TFlow<V>> = emptyMap()
+    ): TFlow<Map<K, V>> = mergeIncrementallyPromptly(deferredOf(initialTFlows))
+
+    /** Applies the [FrpStateful] within this [FrpStateScope]. */
+    @ExperimentalFrpApi suspend fun <A> FrpStateful<A>.applyStateful(): A = this()
+
+    /**
+     * Applies the [FrpStateful] within this [FrpStateScope], returning the result as an
+     * [FrpDeferredValue].
+     */
+    @ExperimentalFrpApi
+    fun <A> FrpStateful<A>.applyStatefulDeferred(): FrpDeferredValue<A> = deferredStateScope {
+        applyStateful()
+    }
+
+    /**
+     * Returns a [TState] that holds onto the most recently emitted value from this [TFlow], or
+     * [initialValue] if nothing has been emitted since it was constructed.
+     *
+     * Note that the value contained within the [TState] is not updated until *after* all [TFlow]s
+     * have been processed; this keeps the value of the [TState] consistent during the entire FRP
+     * transaction.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.hold(initialValue: A): TState<A> = holdDeferred(deferredOf(initialValue))
+
+    /**
+     * Returns a [TFlow] the emits the result of applying [FrpStatefuls][FrpStateful] emitted from
+     * the original [TFlow].
+     *
+     * Unlike [applyLatestStateful], state accumulation is not stopped with each subsequent emission
+     * of the original [TFlow].
+     */
+    @ExperimentalFrpApi fun <A> TFlow<FrpStateful<A>>.applyStatefuls(): TFlow<A>
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. Unlike
+     * [mapLatestStateful], accumulation is not stopped with each subsequent emission of the
+     * original [TFlow].
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapStateful(transform: suspend FrpStateScope.(A) -> B): TFlow<B> =
+        mapPure { statefully { transform(it) } }.applyStatefuls()
+
+    /**
+     * Returns a [TState] the holds the result of applying the [FrpStateful] held by the original
+     * [TState].
+     *
+     * Unlike [applyLatestStateful], state accumulation is not stopped with each state change.
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<FrpStateful<A>>.applyStatefuls(): TState<A> =
+        stateChanges
+            .applyStatefuls()
+            .holdDeferred(initialValue = deferredStateScope { sampleDeferred().get()() })
+
+    /** Returns a [TFlow] that switches to the [TFlow] emitted by the original [TFlow]. */
+    @ExperimentalFrpApi fun <A> TFlow<TFlow<A>>.flatten() = hold(emptyTFlow).switch()
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. With each
+     * invocation of [transform], state accumulation from previous invocation is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapLatestStateful(transform: suspend FrpStateScope.(A) -> B): TFlow<B> =
+        mapPure { statefully { transform(it) } }.applyLatestStateful()
+
+    /**
+     * Returns a [TFlow] that switches to a new [TFlow] produced by [transform] every time the
+     * original [TFlow] emits a value.
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. With each
+     * invocation of [transform], state accumulation from previous invocation is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.flatMapLatestStateful(
+        transform: suspend FrpStateScope.(A) -> TFlow<B>
+    ): TFlow<B> = mapLatestStateful(transform).flatten()
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpStateful] emitted from the
+     * original [TFlow].
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<FrpStateful<A>>.applyLatestStateful(): TFlow<A> = applyLatestStateful {}.first
+
+    /**
+     * Returns a [TState] containing the value returned by applying the [FrpStateful] held by the
+     * original [TState].
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<FrpStateful<A>>.applyLatestStateful(): TState<A> {
+        val (changes, init) = stateChanges.applyLatestStateful { sample()() }
+        return changes.holdDeferred(init)
+    }
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpStateful] emitted from the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [init]
+     * immediately.
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<FrpStateful<B>>.applyLatestStateful(
+        init: FrpStateful<A>
+    ): Pair<TFlow<B>, FrpDeferredValue<A>> {
+        val (flow, result) =
+            mapCheap { spec -> mapOf(Unit to just(spec)) }
+                .applyLatestStatefulForKey(init = mapOf(Unit to init), numKeys = 1)
+        val outFlow: TFlow<B> =
+            flow.mapMaybe {
+                checkNotNull(it[Unit]) { "applyLatest: expected result, but none present in: $it" }
+            }
+        val outInit: FrpDeferredValue<A> = deferredTransactionScope {
+            val initResult: Map<Unit, A> = result.get()
+            check(Unit in initResult) {
+                "applyLatest: expected initial result, but none present in: $initResult"
+            }
+            @Suppress("UNCHECKED_CAST")
+            initResult.getOrDefault(Unit) { null } as A
+        }
+        return Pair(outFlow, outInit)
+    }
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpStateful] emitted from the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [init]
+     * immediately.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateful] will be stopped with no replacement.
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] with the same key is stopped.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<FrpStateful<A>>>>.applyLatestStatefulForKey(
+        init: FrpDeferredValue<Map<K, FrpStateful<B>>>,
+        numKeys: Int? = null,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>>
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpStateful] emitted from the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [init]
+     * immediately.
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] with the same key is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateful] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<FrpStateful<A>>>>.applyLatestStatefulForKey(
+        init: Map<K, FrpStateful<B>>,
+        numKeys: Int? = null,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> =
+        applyLatestStatefulForKey(deferredOf(init), numKeys)
+
+    /**
+     * Returns a [TState] containing the latest results of applying each [FrpStateful] emitted from
+     * the original [TFlow].
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] with the same key is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateful] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpStateful<A>>>>.holdLatestStatefulForKey(
+        init: FrpDeferredValue<Map<K, FrpStateful<A>>>,
+        numKeys: Int? = null,
+    ): TState<Map<K, A>> {
+        val (changes, initialValues) = applyLatestStatefulForKey(init, numKeys)
+        return changes.foldMapIncrementally(initialValues)
+    }
+
+    /**
+     * Returns a [TState] containing the latest results of applying each [FrpStateful] emitted from
+     * the original [TFlow].
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] with the same key is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateful] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpStateful<A>>>>.holdLatestStatefulForKey(
+        init: Map<K, FrpStateful<A>> = emptyMap(),
+        numKeys: Int? = null,
+    ): TState<Map<K, A>> = holdLatestStatefulForKey(deferredOf(init), numKeys)
+
+    /**
+     * Returns a [TFlow] containing the results of applying each [FrpStateful] emitted from the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [init]
+     * immediately.
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] with the same key is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateful] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A> TFlow<Map<K, Maybe<FrpStateful<A>>>>.applyLatestStatefulForKey(
+        numKeys: Int? = null
+    ): TFlow<Map<K, Maybe<A>>> =
+        applyLatestStatefulForKey(init = emptyMap<K, FrpStateful<*>>(), numKeys = numKeys).first
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValues] immediately.
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. With each
+     * invocation of [transform], state accumulation from previous invocation is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateScope] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestStatefulForKey(
+        initialValues: FrpDeferredValue<Map<K, A>>,
+        numKeys: Int? = null,
+        transform: suspend FrpStateScope.(A) -> B,
+    ): Pair<TFlow<Map<K, Maybe<B>>>, FrpDeferredValue<Map<K, B>>> =
+        mapPure { patch -> patch.mapValues { (_, v) -> v.map { statefully { transform(it) } } } }
+            .applyLatestStatefulForKey(
+                deferredStateScope {
+                    initialValues.get().mapValues { (_, v) -> statefully { transform(v) } }
+                },
+                numKeys = numKeys,
+            )
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow], and a [FrpDeferredValue] containing the result of applying [transform] to
+     * [initialValues] immediately.
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. With each
+     * invocation of [transform], state accumulation from previous invocation is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateScope] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestStatefulForKey(
+        initialValues: Map<K, A>,
+        numKeys: Int? = null,
+        transform: suspend FrpStateScope.(A) -> B,
+    ): Pair<TFlow<Map<K, Maybe<B>>>, FrpDeferredValue<Map<K, B>>> =
+        mapLatestStatefulForKey(deferredOf(initialValues), numKeys, transform)
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow].
+     *
+     * [transform] can perform state accumulation via its [FrpStateScope] receiver. With each
+     * invocation of [transform], state accumulation from previous invocation is stopped.
+     *
+     * If the [Maybe] contained within the value for an associated key is [none], then the
+     * previously-active [FrpStateScope] will be stopped with no replacement.
+     */
+    @ExperimentalFrpApi
+    fun <K, A, B> TFlow<Map<K, Maybe<A>>>.mapLatestStatefulForKey(
+        numKeys: Int? = null,
+        transform: suspend FrpStateScope.(A) -> B,
+    ): TFlow<Map<K, Maybe<B>>> = mapLatestStatefulForKey(emptyMap(), numKeys, transform).first
+
+    /**
+     * Returns a [TFlow] that will only emit the next event of the original [TFlow], and then will
+     * act as [emptyTFlow].
+     *
+     * If the original [TFlow] is emitting an event at this exact time, then it will be the only
+     * even emitted from the result [TFlow].
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.nextOnly(): TFlow<A> =
+        if (this === emptyTFlow) {
+            this
+        } else {
+            TFlowLoop<A>().also {
+                it.loopback = it.mapCheap { emptyTFlow }.hold(this@nextOnly).switch()
+            }
+        }
+
+    /** Returns a [TFlow] that skips the next emission of the original [TFlow]. */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.skipNext(): TFlow<A> =
+        if (this === emptyTFlow) {
+            this
+        } else {
+            nextOnly().mapCheap { this@skipNext }.hold(emptyTFlow).switch()
+        }
+
+    /**
+     * Returns a [TFlow] that emits values from the original [TFlow] up until [stop] emits a value.
+     *
+     * If the original [TFlow] emits at the same time as [stop], then the returned [TFlow] will emit
+     * that value.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.takeUntil(stop: TFlow<*>): TFlow<A> =
+        if (stop === emptyTFlow) {
+            this
+        } else {
+            stop.mapCheap { emptyTFlow }.nextOnly().hold(this).switch()
+        }
+
+    /**
+     * Invokes [stateful] in a new [FrpStateScope] that is a child of this one.
+     *
+     * This new scope is stopped when [stop] first emits a value, or when the parent scope is
+     * stopped. Stopping will end all state accumulation; any [TStates][TState] returned from this
+     * scope will no longer update.
+     */
+    @ExperimentalFrpApi
+    fun <A> childStateScope(stop: TFlow<*>, stateful: FrpStateful<A>): FrpDeferredValue<A> {
+        val (_, init: FrpDeferredValue<Map<Unit, A>>) =
+            stop
+                .nextOnly()
+                .mapPure { mapOf(Unit to none<FrpStateful<A>>()) }
+                .applyLatestStatefulForKey(init = mapOf(Unit to stateful), numKeys = 1)
+        return deferredStateScope { init.get().getValue(Unit) }
+    }
+
+    /**
+     * Returns a [TFlow] that emits values from the original [TFlow] up to and including a value is
+     * emitted that satisfies [predicate].
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.takeUntil(predicate: suspend FrpTransactionScope.(A) -> Boolean): TFlow<A> =
+        takeUntil(filter(predicate))
+
+    /**
+     * Returns a [TState] that is incrementally updated when this [TFlow] emits a value, by applying
+     * [transform] to both the emitted value and the currently tracked state.
+     *
+     * Note that the value contained within the [TState] is not updated until *after* all [TFlow]s
+     * have been processed; this keeps the value of the [TState] consistent during the entire FRP
+     * transaction.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.fold(
+        initialValue: B,
+        transform: suspend FrpTransactionScope.(A, B) -> B,
+    ): TState<B> {
+        lateinit var state: TState<B>
+        return mapPure { a -> transform(a, state.sample()) }.hold(initialValue).also { state = it }
+    }
+
+    /**
+     * Returns a [TState] that is incrementally updated when this [TFlow] emits a value, by applying
+     * [transform] to both the emitted value and the currently tracked state.
+     *
+     * Note that the value contained within the [TState] is not updated until *after* all [TFlow]s
+     * have been processed; this keeps the value of the [TState] consistent during the entire FRP
+     * transaction.
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.foldDeferred(
+        initialValue: FrpDeferredValue<B>,
+        transform: suspend FrpTransactionScope.(A, B) -> B,
+    ): TState<B> {
+        lateinit var state: TState<B>
+        return mapPure { a -> transform(a, state.sample()) }
+            .holdDeferred(initialValue)
+            .also { state = it }
+    }
+
+    /**
+     * Returns a [TState] that holds onto the result of applying the most recently emitted
+     * [FrpStateful] this [TFlow], or [init] if nothing has been emitted since it was constructed.
+     *
+     * When each [FrpStateful] is applied, state accumulation from the previously-active
+     * [FrpStateful] is stopped.
+     *
+     * Note that the value contained within the [TState] is not updated until *after* all [TFlow]s
+     * have been processed; this keeps the value of the [TState] consistent during the entire FRP
+     * transaction.
+     *
+     * Shorthand for:
+     * ```kotlin
+     * val (changes, initApplied) = applyLatestStateful(init)
+     * return changes.toTStateDeferred(initApplied)
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<FrpStateful<A>>.holdLatestStateful(init: FrpStateful<A>): TState<A> {
+        val (changes, initApplied) = applyLatestStateful(init)
+        return changes.holdDeferred(initApplied)
+    }
+
+    /**
+     * Returns a [TFlow] that emits the two most recent emissions from the original [TFlow].
+     * [initialValue] is used as the previous value for the first emission.
+     *
+     * Shorthand for `sample(hold(init)) { new, old -> Pair(old, new) }`
+     */
+    @ExperimentalFrpApi
+    fun <S, T : S> TFlow<T>.pairwise(initialValue: S): TFlow<WithPrev<S, T>> {
+        val previous = hold(initialValue)
+        return mapCheap { new -> WithPrev(previousValue = previous.sample(), newValue = new) }
+    }
+
+    /**
+     * Returns a [TFlow] that emits the two most recent emissions from the original [TFlow]. Note
+     * that the returned [TFlow] will not emit until the original [TFlow] has emitted twice.
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.pairwise(): TFlow<WithPrev<A, A>> =
+        mapCheap { just(it) }
+            .pairwise(none)
+            .mapMaybe { (prev, next) -> prev.zipWith(next, ::WithPrev) }
+
+    /**
+     * Returns a [TState] that holds both the current and previous values of the original [TState].
+     * [initialPreviousValue] is used as the first previous value.
+     *
+     * Shorthand for `sample(hold(init)) { new, old -> Pair(old, new) }`
+     */
+    @ExperimentalFrpApi
+    fun <S, T : S> TState<T>.pairwise(initialPreviousValue: S): TState<WithPrev<S, T>> =
+        stateChanges
+            .pairwise(initialPreviousValue)
+            .holdDeferred(deferredTransactionScope { WithPrev(initialPreviousValue, sample()) })
+
+    /**
+     * Returns a [TState] holding a [Map] that is updated incrementally whenever this emits a value.
+     *
+     * The value emitted is used as a "patch" for the tracked [Map]; for each key [K] in the emitted
+     * map, an associated value of [Just] will insert or replace the value in the tracked [Map], and
+     * an associated value of [none] will remove the key from the tracked [Map].
+     */
+    @ExperimentalFrpApi
+    fun <K, V> TFlow<Map<K, Maybe<V>>>.foldMapIncrementally(
+        initialValues: FrpDeferredValue<Map<K, V>>
+    ): TState<Map<K, V>> =
+        foldDeferred(initialValues) { patch, map ->
+            val (adds: List<Pair<K, V>>, removes: List<K>) =
+                patch
+                    .asSequence()
+                    .map { (k, v) -> if (v is Just) Left(k to v.value) else Right(k) }
+                    .partitionEithers()
+            val removed: Map<K, V> = map - removes.toSet()
+            val updated: Map<K, V> = removed + adds
+            updated
+        }
+
+    /**
+     * Returns a [TState] holding a [Map] that is updated incrementally whenever this emits a value.
+     *
+     * The value emitted is used as a "patch" for the tracked [Map]; for each key [K] in the emitted
+     * map, an associated value of [Just] will insert or replace the value in the tracked [Map], and
+     * an associated value of [none] will remove the key from the tracked [Map].
+     */
+    @ExperimentalFrpApi
+    fun <K, V> TFlow<Map<K, Maybe<V>>>.foldMapIncrementally(
+        initialValues: Map<K, V> = emptyMap()
+    ): TState<Map<K, V>> = foldMapIncrementally(deferredOf(initialValues))
+
+    /**
+     * Returns a [TFlow] that wraps each emission of the original [TFlow] into an [IndexedValue],
+     * containing the emitted value and its index (starting from zero).
+     *
+     * Shorthand for:
+     * ```
+     *   val index = fold(0) { _, oldIdx -> oldIdx + 1 }
+     *   sample(index) { a, idx -> IndexedValue(idx, a) }
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.withIndex(): TFlow<IndexedValue<A>> {
+        val index = fold(0) { _, old -> old + 1 }
+        return sample(index) { a, idx -> IndexedValue(idx, a) }
+    }
+
+    /**
+     * Returns a [TFlow] containing the results of applying [transform] to each value of the
+     * original [TFlow] and its index (starting from zero).
+     *
+     * Shorthand for:
+     * ```
+     *   withIndex().map { (idx, a) -> transform(idx, a) }
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TFlow<A>.mapIndexed(transform: suspend FrpTransactionScope.(Int, A) -> B): TFlow<B> {
+        val index = fold(0) { _, i -> i + 1 }
+        return sample(index) { a, idx -> transform(idx, a) }
+    }
+
+    /** Returns a [TFlow] where all subsequent repetitions of the same value are filtered out. */
+    @ExperimentalFrpApi
+    fun <A> TFlow<A>.distinctUntilChanged(): TFlow<A> {
+        val state: TState<Any?> = hold(Any())
+        return filter { it != state.sample() }
+    }
+
+    /**
+     * Returns a new [TFlow] that emits at the same rate as the original [TFlow], but combines the
+     * emitted value with the most recent emission from [other] using [transform].
+     *
+     * Note that the returned [TFlow] will not emit anything until [other] has emitted at least one
+     * value.
+     */
+    @ExperimentalFrpApi
+    fun <A, B, C> TFlow<A>.sample(
+        other: TFlow<B>,
+        transform: suspend FrpTransactionScope.(A, B) -> C,
+    ): TFlow<C> {
+        val state = other.mapCheap { just(it) }.hold(none)
+        return sample(state) { a, b -> b.map { transform(a, it) } }.filterJust()
+    }
+
+    /**
+     * Returns a [TState] that samples the [Transactional] held by the given [TState] within the
+     * same transaction that the state changes.
+     */
+    @ExperimentalFrpApi
+    fun <A> TState<Transactional<A>>.sampleTransactionals(): TState<A> =
+        stateChanges
+            .sampleTransactionals()
+            .holdDeferred(deferredTransactionScope { sample().sample() })
+
+    /**
+     * Returns a [TState] that transforms the value held inside this [TState] by applying it to the
+     * given function [transform].
+     */
+    @ExperimentalFrpApi
+    fun <A, B> TState<A>.map(transform: suspend FrpTransactionScope.(A) -> B): TState<B> =
+        mapPure { transactionally { transform(it) } }.sampleTransactionals()
+
+    /**
+     * Returns a [TState] whose value is generated with [transform] by combining the current values
+     * of each given [TState].
+     *
+     * @see TState.combineWith
+     */
+    @ExperimentalFrpApi
+    fun <A, B, Z> combine(
+        stateA: TState<A>,
+        stateB: TState<B>,
+        transform: suspend FrpTransactionScope.(A, B) -> Z,
+    ): TState<Z> =
+        com.android.systemui.experimental.frp
+            .combine(stateA, stateB) { a, b -> transactionally { transform(a, b) } }
+            .sampleTransactionals()
+
+    /**
+     * Returns a [TState] whose value is generated with [transform] by combining the current values
+     * of each given [TState].
+     *
+     * @see TState.combineWith
+     */
+    @ExperimentalFrpApi
+    fun <A, B, C, D, Z> combine(
+        stateA: TState<A>,
+        stateB: TState<B>,
+        stateC: TState<C>,
+        stateD: TState<D>,
+        transform: suspend FrpTransactionScope.(A, B, C, D) -> Z,
+    ): TState<Z> =
+        com.android.systemui.experimental.frp
+            .combine(stateA, stateB, stateC, stateD) { a, b, c, d ->
+                transactionally { transform(a, b, c, d) }
+            }
+            .sampleTransactionals()
+
+    /** Returns a [TState] by applying [transform] to the value held by the original [TState]. */
+    @ExperimentalFrpApi
+    fun <A, B> TState<A>.flatMap(
+        transform: suspend FrpTransactionScope.(A) -> TState<B>
+    ): TState<B> = mapPure { transactionally { transform(it) } }.sampleTransactionals().flatten()
+
+    /**
+     * Returns a [TState] whose value is generated with [transform] by combining the current values
+     * of each given [TState].
+     *
+     * @see TState.combineWith
+     */
+    @ExperimentalFrpApi
+    fun <A, Z> combine(
+        vararg states: TState<A>,
+        transform: suspend FrpTransactionScope.(List<A>) -> Z,
+    ): TState<Z> = combinePure(*states).map(transform)
+
+    /**
+     * Returns a [TState] whose value is generated with [transform] by combining the current values
+     * of each given [TState].
+     *
+     * @see TState.combineWith
+     */
+    @ExperimentalFrpApi
+    fun <A, Z> Iterable<TState<A>>.combine(
+        transform: suspend FrpTransactionScope.(List<A>) -> Z
+    ): TState<Z> = combinePure().map(transform)
+
+    /**
+     * Returns a [TState] by combining the values held inside the given [TState]s by applying them
+     * to the given function [transform].
+     */
+    @ExperimentalFrpApi
+    fun <A, B, C> TState<A>.combineWith(
+        other: TState<B>,
+        transform: suspend FrpTransactionScope.(A, B) -> C,
+    ): TState<C> = combine(this, other, transform)
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt
new file mode 100644
index 0000000..b0b9dbc
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/FrpTransactionScope.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import kotlin.coroutines.RestrictsSuspension
+
+/**
+ * FRP operations that are available while a transaction is active.
+ *
+ * These operations do not accumulate state, which makes [FrpTransactionScope] weaker than
+ * [FrpStateScope], but allows them to be used in more places.
+ */
+@ExperimentalFrpApi
+@RestrictsSuspension
+interface FrpTransactionScope : FrpScope {
+
+    /**
+     * Returns the current value of this [Transactional] as a [FrpDeferredValue].
+     *
+     * @see sample
+     */
+    @ExperimentalFrpApi fun <A> Transactional<A>.sampleDeferred(): FrpDeferredValue<A>
+
+    /**
+     * Returns the current value of this [TState] as a [FrpDeferredValue].
+     *
+     * @see sample
+     */
+    @ExperimentalFrpApi fun <A> TState<A>.sampleDeferred(): FrpDeferredValue<A>
+
+    /** TODO */
+    @ExperimentalFrpApi
+    fun <A> deferredTransactionScope(
+        block: suspend FrpTransactionScope.() -> A
+    ): FrpDeferredValue<A>
+
+    /** A [TFlow] that emits once, within this transaction, and then never again. */
+    @ExperimentalFrpApi val now: TFlow<Unit>
+
+    /**
+     * Returns the current value held by this [TState]. Guaranteed to be consistent within the same
+     * transaction.
+     */
+    @ExperimentalFrpApi suspend fun <A> TState<A>.sample(): A = sampleDeferred().get()
+
+    /**
+     * Returns the current value held by this [Transactional]. Guaranteed to be consistent within
+     * the same transaction.
+     */
+    @ExperimentalFrpApi suspend fun <A> Transactional<A>.sample(): A = sampleDeferred().get()
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt
new file mode 100644
index 0000000..cca6c9a
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TFlow.kt
@@ -0,0 +1,560 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.internal.DemuxImpl
+import com.android.systemui.experimental.frp.internal.Init
+import com.android.systemui.experimental.frp.internal.InitScope
+import com.android.systemui.experimental.frp.internal.InputNode
+import com.android.systemui.experimental.frp.internal.Network
+import com.android.systemui.experimental.frp.internal.NoScope
+import com.android.systemui.experimental.frp.internal.TFlowImpl
+import com.android.systemui.experimental.frp.internal.activated
+import com.android.systemui.experimental.frp.internal.cached
+import com.android.systemui.experimental.frp.internal.constInit
+import com.android.systemui.experimental.frp.internal.filterNode
+import com.android.systemui.experimental.frp.internal.init
+import com.android.systemui.experimental.frp.internal.map
+import com.android.systemui.experimental.frp.internal.mapImpl
+import com.android.systemui.experimental.frp.internal.mapMaybeNode
+import com.android.systemui.experimental.frp.internal.mergeNodes
+import com.android.systemui.experimental.frp.internal.mergeNodesLeft
+import com.android.systemui.experimental.frp.internal.neverImpl
+import com.android.systemui.experimental.frp.internal.switchDeferredImplSingle
+import com.android.systemui.experimental.frp.internal.switchPromptImpl
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.util.Either
+import com.android.systemui.experimental.frp.util.Left
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.Right
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.experimental.frp.util.toMaybe
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+/** A series of values of type [A] available at discrete points in time. */
+@ExperimentalFrpApi
+sealed class TFlow<out A> {
+    companion object {
+        /** A [TFlow] with no values. */
+        val empty: TFlow<Nothing> = EmptyFlow
+    }
+}
+
+/** A [TFlow] with no values. */
+@ExperimentalFrpApi val emptyTFlow: TFlow<Nothing> = TFlow.empty
+
+/**
+ * A forward-reference to a [TFlow]. Useful for recursive definitions.
+ *
+ * This reference can be used like a standard [TFlow], but will hold up evaluation of the FRP
+ * network until the [loopback] reference is set.
+ */
+@ExperimentalFrpApi
+class TFlowLoop<A> : TFlow<A>() {
+    private val deferred = CompletableDeferred<TFlow<A>>()
+
+    internal val init: Init<TFlowImpl<A>> =
+        init(name = null) { deferred.await().init.connect(evalScope = this) }
+
+    /** The [TFlow] this reference is referring to. */
+    @ExperimentalFrpApi
+    var loopback: TFlow<A>? = null
+        set(value) {
+            value?.let {
+                check(deferred.complete(value)) { "TFlowLoop.loopback has already been set." }
+                field = value
+            }
+        }
+
+    operator fun getValue(thisRef: Any?, property: KProperty<*>): TFlow<A> = this
+
+    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: TFlow<A>) {
+        loopback = value
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+/** TODO */
+@ExperimentalFrpApi fun <A> Lazy<TFlow<A>>.defer(): TFlow<A> = deferInline { value }
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> FrpDeferredValue<TFlow<A>>.defer(): TFlow<A> = deferInline { unwrapped.await() }
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> deferTFlow(block: suspend FrpScope.() -> TFlow<A>): TFlow<A> = deferInline {
+    NoScope.runInFrpScope(block)
+}
+
+/** Returns a [TFlow] that emits the new value of this [TState] when it changes. */
+@ExperimentalFrpApi
+val <A> TState<A>.stateChanges: TFlow<A>
+    get() = TFlowInit(init(name = null) { init.connect(evalScope = this).changes })
+
+/**
+ * Returns a [TFlow] that contains only the [just] results of applying [transform] to each value of
+ * the original [TFlow].
+ *
+ * @see mapNotNull
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<A>.mapMaybe(transform: suspend FrpTransactionScope.(A) -> Maybe<B>): TFlow<B> {
+    val pulse =
+        mapMaybeNode({ init.connect(evalScope = this) }) { runInTransactionScope { transform(it) } }
+    return TFlowInit(constInit(name = null, pulse))
+}
+
+/**
+ * Returns a [TFlow] that contains only the non-null results of applying [transform] to each value
+ * of the original [TFlow].
+ *
+ * @see mapMaybe
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<A>.mapNotNull(transform: suspend FrpTransactionScope.(A) -> B?): TFlow<B> =
+    mapMaybe {
+        transform(it).toMaybe()
+    }
+
+/** Returns a [TFlow] containing only values of the original [TFlow] that are not null. */
+@ExperimentalFrpApi fun <A> TFlow<A?>.filterNotNull(): TFlow<A> = mapNotNull { it }
+
+/** Shorthand for `mapNotNull { it as? A }`. */
+@ExperimentalFrpApi
+inline fun <reified A> TFlow<*>.filterIsInstance(): TFlow<A> = mapNotNull { it as? A }
+
+/** Shorthand for `mapMaybe { it }`. */
+@ExperimentalFrpApi fun <A> TFlow<Maybe<A>>.filterJust(): TFlow<A> = mapMaybe { it }
+
+/**
+ * Returns a [TFlow] containing the results of applying [transform] to each value of the original
+ * [TFlow].
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<A>.map(transform: suspend FrpTransactionScope.(A) -> B): TFlow<B> {
+    val mapped: TFlowImpl<B> =
+        mapImpl({ init.connect(evalScope = this) }) { a -> runInTransactionScope { transform(a) } }
+    return TFlowInit(constInit(name = null, mapped.cached()))
+}
+
+/**
+ * Like [map], but the emission is not cached during the transaction. Use only if [transform] is
+ * fast and pure.
+ *
+ * @see map
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<A>.mapCheap(transform: suspend FrpTransactionScope.(A) -> B): TFlow<B> =
+    TFlowInit(
+        constInit(
+            name = null,
+            mapImpl({ init.connect(evalScope = this) }) { a ->
+                runInTransactionScope { transform(a) }
+            },
+        )
+    )
+
+/**
+ * Returns a [TFlow] that invokes [action] before each value of the original [TFlow] is emitted.
+ * Useful for logging and debugging.
+ *
+ * ```
+ *   pulse.onEach { foo(it) } == pulse.map { foo(it); it }
+ * ```
+ *
+ * Note that the side effects performed in [onEach] are only performed while the resulting [TFlow]
+ * is connected to an output of the FRP network. If your goal is to reliably perform side effects in
+ * response to a [TFlow], use the output combinators available in [FrpBuildScope], such as
+ * [FrpBuildScope.toSharedFlow] or [FrpBuildScope.observe].
+ */
+@ExperimentalFrpApi
+fun <A> TFlow<A>.onEach(action: suspend FrpTransactionScope.(A) -> Unit): TFlow<A> = map {
+    action(it)
+    it
+}
+
+/**
+ * Returns a [TFlow] containing only values of the original [TFlow] that satisfy the given
+ * [predicate].
+ */
+@ExperimentalFrpApi
+fun <A> TFlow<A>.filter(predicate: suspend FrpTransactionScope.(A) -> Boolean): TFlow<A> {
+    val pulse =
+        filterNode({ init.connect(evalScope = this) }) { runInTransactionScope { predicate(it) } }
+    return TFlowInit(constInit(name = null, pulse.cached()))
+}
+
+/**
+ * Splits a [TFlow] of pairs into a pair of [TFlows][TFlow], where each returned [TFlow] emits half
+ * of the original.
+ *
+ * Shorthand for:
+ * ```kotlin
+ * val lefts = map { it.first }
+ * val rights = map { it.second }
+ * return Pair(lefts, rights)
+ * ```
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<Pair<A, B>>.unzip(): Pair<TFlow<A>, TFlow<B>> {
+    val lefts = map { it.first }
+    val rights = map { it.second }
+    return lefts to rights
+}
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from both.
+ *
+ * Because [TFlow]s can only emit one value per transaction, the provided [transformCoincidence]
+ * function is used to combine coincident emissions to produce the result value to be emitted by the
+ * merged [TFlow].
+ */
+@ExperimentalFrpApi
+fun <A> TFlow<A>.mergeWith(
+    other: TFlow<A>,
+    transformCoincidence: suspend FrpTransactionScope.(A, A) -> A = { a, _ -> a },
+): TFlow<A> {
+    val node =
+        mergeNodes(
+            getPulse = { init.connect(evalScope = this) },
+            getOther = { other.init.connect(evalScope = this) },
+        ) { a, b ->
+            runInTransactionScope { transformCoincidence(a, b) }
+        }
+    return TFlowInit(constInit(name = null, node))
+}
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from all. All coincident
+ * emissions are collected into the emitted [List], preserving the input ordering.
+ *
+ * @see mergeWith
+ * @see mergeLeft
+ */
+@ExperimentalFrpApi
+fun <A> merge(vararg flows: TFlow<A>): TFlow<List<A>> = flows.asIterable().merge()
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from all. In the case of
+ * coincident emissions, the emission from the left-most [TFlow] is emitted.
+ *
+ * @see merge
+ */
+@ExperimentalFrpApi
+fun <A> mergeLeft(vararg flows: TFlow<A>): TFlow<A> = flows.asIterable().mergeLeft()
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from all.
+ *
+ * Because [TFlow]s can only emit one value per transaction, the provided [transformCoincidence]
+ * function is used to combine coincident emissions to produce the result value to be emitted by the
+ * merged [TFlow].
+ */
+// TODO: can be optimized to avoid creating the intermediate list
+fun <A> merge(vararg flows: TFlow<A>, transformCoincidence: (A, A) -> A): TFlow<A> =
+    merge(*flows).map { l -> l.reduce(transformCoincidence) }
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from all. All coincident
+ * emissions are collected into the emitted [List], preserving the input ordering.
+ *
+ * @see mergeWith
+ * @see mergeLeft
+ */
+@ExperimentalFrpApi
+fun <A> Iterable<TFlow<A>>.merge(): TFlow<List<A>> =
+    TFlowInit(constInit(name = null, mergeNodes { map { it.init.connect(evalScope = this) } }))
+
+/**
+ * Merges the given [TFlows][TFlow] into a single [TFlow] that emits events from all. In the case of
+ * coincident emissions, the emission from the left-most [TFlow] is emitted.
+ *
+ * @see merge
+ */
+@ExperimentalFrpApi
+fun <A> Iterable<TFlow<A>>.mergeLeft(): TFlow<A> =
+    TFlowInit(constInit(name = null, mergeNodesLeft { map { it.init.connect(evalScope = this) } }))
+
+/**
+ * Creates a new [TFlow] that emits events from all given [TFlow]s. All simultaneous emissions are
+ * collected into the emitted [List], preserving the input ordering.
+ *
+ * @see mergeWith
+ */
+@ExperimentalFrpApi fun <A> Sequence<TFlow<A>>.merge(): TFlow<List<A>> = asIterable().merge()
+
+/**
+ * Creates a new [TFlow] that emits events from all given [TFlow]s. All simultaneous emissions are
+ * collected into the emitted [Map], and are given the same key of the associated [TFlow] in the
+ * input [Map].
+ *
+ * @see mergeWith
+ */
+@ExperimentalFrpApi
+fun <K, A> Map<K, TFlow<A>>.merge(): TFlow<Map<K, A>> =
+    asSequence().map { (k, flowA) -> flowA.map { a -> k to a } }.toList().merge().map { it.toMap() }
+
+/**
+ * Returns a [GroupedTFlow] that can be used to efficiently split a single [TFlow] into multiple
+ * downstream [TFlow]s.
+ *
+ * The input [TFlow] emits [Map] instances that specify which downstream [TFlow] the associated
+ * value will be emitted from. These downstream [TFlow]s can be obtained via
+ * [GroupedTFlow.eventsForKey].
+ *
+ * An example:
+ * ```
+ *   val sFoo: TFlow<Map<String, Foo>> = ...
+ *   val fooById: GroupedTFlow<String, Foo> = sFoo.groupByKey()
+ *   val fooBar: TFlow<Foo> = fooById["bar"]
+ * ```
+ *
+ * This is semantically equivalent to `val fooBar = sFoo.mapNotNull { map -> map["bar"] }` but is
+ * significantly more efficient; specifically, using [mapNotNull] in this way incurs a `O(n)`
+ * performance hit, where `n` is the number of different [mapNotNull] operations used to filter on a
+ * specific key's presence in the emitted [Map]. [groupByKey] internally uses a [HashMap] to lookup
+ * the appropriate downstream [TFlow], and so operates in `O(1)`.
+ *
+ * Note that the result [GroupedTFlow] should be cached and re-used to gain the performance benefit.
+ *
+ * @see selector
+ */
+@ExperimentalFrpApi
+fun <K, A> TFlow<Map<K, A>>.groupByKey(numKeys: Int? = null): GroupedTFlow<K, A> =
+    GroupedTFlow(DemuxImpl({ init.connect(this) }, numKeys))
+
+/**
+ * Shorthand for `map { mapOf(extractKey(it) to it) }.groupByKey()`
+ *
+ * @see groupByKey
+ */
+@ExperimentalFrpApi
+fun <K, A> TFlow<A>.groupBy(
+    numKeys: Int? = null,
+    extractKey: suspend FrpTransactionScope.(A) -> K,
+): GroupedTFlow<K, A> = map { mapOf(extractKey(it) to it) }.groupByKey(numKeys)
+
+/**
+ * Returns two new [TFlow]s that contain elements from this [TFlow] that satisfy or don't satisfy
+ * [predicate].
+ *
+ * Using this is equivalent to `upstream.filter(predicate) to upstream.filter { !predicate(it) }`
+ * but is more efficient; specifically, [partition] will only invoke [predicate] once per element.
+ */
+@ExperimentalFrpApi
+fun <A> TFlow<A>.partition(
+    predicate: suspend FrpTransactionScope.(A) -> Boolean
+): Pair<TFlow<A>, TFlow<A>> {
+    val grouped: GroupedTFlow<Boolean, A> = groupBy(numKeys = 2, extractKey = predicate)
+    return Pair(grouped.eventsForKey(true), grouped.eventsForKey(false))
+}
+
+/**
+ * Returns two new [TFlow]s that contain elements from this [TFlow]; [Pair.first] will contain
+ * [Left] values, and [Pair.second] will contain [Right] values.
+ *
+ * Using this is equivalent to using [filterIsInstance] in conjunction with [map] twice, once for
+ * [Left]s and once for [Right]s, but is slightly more efficient; specifically, the
+ * [filterIsInstance] check is only performed once per element.
+ */
+@ExperimentalFrpApi
+fun <A, B> TFlow<Either<A, B>>.partitionEither(): Pair<TFlow<A>, TFlow<B>> {
+    val (left, right) = partition { it is Left }
+    return Pair(left.mapCheap { (it as Left).value }, right.mapCheap { (it as Right).value })
+}
+
+/**
+ * A mapping from keys of type [K] to [TFlow]s emitting values of type [A].
+ *
+ * @see groupByKey
+ */
+@ExperimentalFrpApi
+class GroupedTFlow<in K, out A> internal constructor(internal val impl: DemuxImpl<K, A>) {
+    /**
+     * Returns a [TFlow] that emits values of type [A] that correspond to the given [key].
+     *
+     * @see groupByKey
+     */
+    @ExperimentalFrpApi
+    fun eventsForKey(key: K): TFlow<A> = TFlowInit(constInit(name = null, impl.eventsForKey(key)))
+
+    /**
+     * Returns a [TFlow] that emits values of type [A] that correspond to the given [key].
+     *
+     * @see groupByKey
+     */
+    @ExperimentalFrpApi operator fun get(key: K): TFlow<A> = eventsForKey(key)
+}
+
+/**
+ * Returns a [TFlow] that switches to the [TFlow] contained within this [TState] whenever it
+ * changes.
+ *
+ * This switch does take effect until the *next* transaction after [TState] changes. For a switch
+ * that takes effect immediately, see [switchPromptly].
+ */
+@ExperimentalFrpApi
+fun <A> TState<TFlow<A>>.switch(): TFlow<A> {
+    return TFlowInit(
+        constInit(
+            name = null,
+            switchDeferredImplSingle(
+                getStorage = {
+                    init.connect(this).getCurrentWithEpoch(this).first.init.connect(this)
+                },
+                getPatches = {
+                    mapImpl({ init.connect(this).changes }) { newFlow ->
+                        newFlow.init.connect(this)
+                    }
+                },
+            ),
+        )
+    )
+}
+
+/**
+ * Returns a [TFlow] that switches to the [TFlow] contained within this [TState] whenever it
+ * changes.
+ *
+ * This switch takes effect immediately within the same transaction that [TState] changes. In
+ * general, you should prefer [switch] over this method. It is both safer and more performant.
+ */
+// TODO: parameter to handle coincidental emission from both old and new
+@ExperimentalFrpApi
+fun <A> TState<TFlow<A>>.switchPromptly(): TFlow<A> {
+    val switchNode =
+        switchPromptImpl(
+            getStorage = {
+                mapOf(Unit to init.connect(this).getCurrentWithEpoch(this).first.init.connect(this))
+            },
+            getPatches = {
+                val patches = init.connect(this).changes
+                mapImpl({ patches }) { newFlow -> mapOf(Unit to just(newFlow.init.connect(this))) }
+            },
+        )
+    return TFlowInit(constInit(name = null, mapImpl({ switchNode }) { it.getValue(Unit) }))
+}
+
+/**
+ * A mutable [TFlow] that provides the ability to [emit] values to the flow, handling backpressure
+ * by coalescing all emissions into batches.
+ *
+ * @see FrpNetwork.coalescingMutableTFlow
+ */
+@ExperimentalFrpApi
+class CoalescingMutableTFlow<In, Out>
+internal constructor(
+    internal val coalesce: (old: Out, new: In) -> Out,
+    internal val network: Network,
+    private val getInitialValue: () -> Out,
+    internal val impl: InputNode<Out> = InputNode(),
+) : TFlow<Out>() {
+    internal val name: String? = null
+    internal val storage = AtomicReference(false to getInitialValue())
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+
+    /**
+     * Inserts [value] into the current batch, enqueueing it for emission from this [TFlow] if not
+     * already pending.
+     *
+     * Backpressure occurs when [emit] is called while the FRP network is currently in a
+     * transaction; if called multiple times, then emissions will be coalesced into a single batch
+     * that is then processed when the network is ready.
+     */
+    @ExperimentalFrpApi
+    fun emit(value: In) {
+        val (scheduled, _) = storage.getAndUpdate { (_, old) -> true to coalesce(old, value) }
+        if (!scheduled) {
+            @Suppress("DeferredResultUnused")
+            network.transaction {
+                impl.visit(this, storage.getAndSet(false to getInitialValue()).second)
+            }
+        }
+    }
+}
+
+/**
+ * A mutable [TFlow] that provides the ability to [emit] values to the flow, handling backpressure
+ * by suspending the emitter.
+ *
+ * @see FrpNetwork.coalescingMutableTFlow
+ */
+@ExperimentalFrpApi
+class MutableTFlow<T>
+internal constructor(internal val network: Network, internal val impl: InputNode<T> = InputNode()) :
+    TFlow<T>() {
+    internal val name: String? = null
+
+    private val storage = AtomicReference<Job?>(null)
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+
+    /**
+     * Emits a [value] to this [TFlow], suspending the caller until the FRP transaction containing
+     * the emission has completed.
+     */
+    @ExperimentalFrpApi
+    suspend fun emit(value: T) {
+        coroutineScope {
+            val newEmit =
+                async(start = CoroutineStart.LAZY) {
+                    network.transaction { impl.visit(this, value) }.await()
+                }
+            val jobOrNull = storage.getAndSet(newEmit)
+            if (jobOrNull?.isActive != true) {
+                newEmit.await()
+            } else {
+                jobOrNull.join()
+            }
+        }
+    }
+
+    //    internal suspend fun emitInCurrentTransaction(value: T, evalScope: EvalScope) {
+    //        if (storage.getAndSet(just(value)) is None) {
+    //            impl.visit(evalScope)
+    //        }
+    //    }
+}
+
+private data object EmptyFlow : TFlow<Nothing>()
+
+internal class TFlowInit<out A>(val init: Init<TFlowImpl<A>>) : TFlow<A>() {
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+internal val <A> TFlow<A>.init: Init<TFlowImpl<A>>
+    get() =
+        when (this) {
+            is EmptyFlow -> constInit("EmptyFlow", neverImpl)
+            is TFlowInit -> init
+            is TFlowLoop -> init
+            is CoalescingMutableTFlow<*, A> -> constInit(name, impl.activated())
+            is MutableTFlow -> constInit(name, impl.activated())
+        }
+
+private inline fun <A> deferInline(crossinline block: suspend InitScope.() -> TFlow<A>): TFlow<A> =
+    TFlowInit(init(name = null) { block().init.connect(evalScope = this) })
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt
new file mode 100644
index 0000000..a5ec503
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/TState.kt
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.internal.DerivedMapCheap
+import com.android.systemui.experimental.frp.internal.Init
+import com.android.systemui.experimental.frp.internal.InitScope
+import com.android.systemui.experimental.frp.internal.Network
+import com.android.systemui.experimental.frp.internal.NoScope
+import com.android.systemui.experimental.frp.internal.Schedulable
+import com.android.systemui.experimental.frp.internal.TFlowImpl
+import com.android.systemui.experimental.frp.internal.TStateImpl
+import com.android.systemui.experimental.frp.internal.TStateSource
+import com.android.systemui.experimental.frp.internal.activated
+import com.android.systemui.experimental.frp.internal.cached
+import com.android.systemui.experimental.frp.internal.constInit
+import com.android.systemui.experimental.frp.internal.constS
+import com.android.systemui.experimental.frp.internal.filterNode
+import com.android.systemui.experimental.frp.internal.flatMap
+import com.android.systemui.experimental.frp.internal.init
+import com.android.systemui.experimental.frp.internal.map
+import com.android.systemui.experimental.frp.internal.mapCheap
+import com.android.systemui.experimental.frp.internal.mapImpl
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.internal.zipStates
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * A time-varying value with discrete changes. Essentially, a combination of a [Transactional] that
+ * holds a value, and a [TFlow] that emits when the value changes.
+ */
+@ExperimentalFrpApi sealed class TState<out A>
+
+/** A [TState] that never changes. */
+@ExperimentalFrpApi
+fun <A> tStateOf(value: A): TState<A> {
+    val operatorName = "tStateOf"
+    val name = "$operatorName($value)"
+    return TStateInit(constInit(name, constS(name, operatorName, value)))
+}
+
+/** TODO */
+@ExperimentalFrpApi fun <A> Lazy<TState<A>>.defer(): TState<A> = deferInline { value }
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> FrpDeferredValue<TState<A>>.defer(): TState<A> = deferInline { unwrapped.await() }
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> deferTState(block: suspend FrpScope.() -> TState<A>): TState<A> = deferInline {
+    NoScope.runInFrpScope(block)
+}
+
+/**
+ * Returns a [TState] containing the results of applying [transform] to the value held by the
+ * original [TState].
+ */
+@ExperimentalFrpApi
+fun <A, B> TState<A>.map(transform: suspend FrpScope.(A) -> B): TState<B> {
+    val operatorName = "map"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            init.connect(evalScope = this).map(name, operatorName) {
+                NoScope.runInFrpScope { transform(it) }
+            }
+        }
+    )
+}
+
+/**
+ * Returns a [TState] that transforms the value held inside this [TState] by applying it to the
+ * [transform].
+ *
+ * Note that unlike [map], the result is not cached. This means that not only should [transform] be
+ * fast and pure, it should be *monomorphic* (1-to-1). Failure to do this means that [stateChanges]
+ * for the returned [TState] will operate unexpectedly, emitting at rates that do not reflect an
+ * observable change to the returned [TState].
+ */
+@ExperimentalFrpApi
+fun <A, B> TState<A>.mapCheapUnsafe(transform: suspend FrpScope.(A) -> B): TState<B> {
+    val operatorName = "map"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            init.connect(evalScope = this).mapCheap(name, operatorName) {
+                NoScope.runInFrpScope { transform(it) }
+            }
+        }
+    )
+}
+
+/**
+ * Returns a [TState] by combining the values held inside the given [TState]s by applying them to
+ * the given function [transform].
+ */
+@ExperimentalFrpApi
+fun <A, B, C> TState<A>.combineWith(
+    other: TState<B>,
+    transform: suspend FrpScope.(A, B) -> C,
+): TState<C> = combine(this, other, transform)
+
+/**
+ * Splits a [TState] of pairs into a pair of [TFlows][TState], where each returned [TState] holds
+ * hald of the original.
+ *
+ * Shorthand for:
+ * ```kotlin
+ * val lefts = map { it.first }
+ * val rights = map { it.second }
+ * return Pair(lefts, rights)
+ * ```
+ */
+@ExperimentalFrpApi
+fun <A, B> TState<Pair<A, B>>.unzip(): Pair<TState<A>, TState<B>> {
+    val left = map { it.first }
+    val right = map { it.second }
+    return left to right
+}
+
+/**
+ * Returns a [TState] by combining the values held inside the given [TStates][TState] into a [List].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A> Iterable<TState<A>>.combine(): TState<List<A>> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            zipStates(name, operatorName, states = map { it.init.connect(evalScope = this) })
+        }
+    )
+}
+
+/**
+ * Returns a [TState] by combining the values held inside the given [TStates][TState] into a [Map].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <K : Any, A> Map<K, TState<A>>.combine(): TState<Map<K, A>> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            zipStates(
+                name,
+                operatorName,
+                states = mapValues { it.value.init.connect(evalScope = this) },
+            )
+        }
+    )
+}
+
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B> Iterable<TState<A>>.combine(transform: suspend FrpScope.(List<A>) -> B): TState<B> =
+    combine().map(transform)
+
+/**
+ * Returns a [TState] by combining the values held inside the given [TState]s into a [List].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A> combine(vararg states: TState<A>): TState<List<A>> = states.asIterable().combine()
+
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B> combine(
+    vararg states: TState<A>,
+    transform: suspend FrpScope.(List<A>) -> B,
+): TState<B> = states.asIterable().combine(transform)
+
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B, Z> combine(
+    stateA: TState<A>,
+    stateB: TState<B>,
+    transform: suspend FrpScope.(A, B) -> Z,
+): TState<Z> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            coroutineScope {
+                val dl1: Deferred<TStateImpl<A>> = async {
+                    stateA.init.connect(evalScope = this@init)
+                }
+                val dl2: Deferred<TStateImpl<B>> = async {
+                    stateB.init.connect(evalScope = this@init)
+                }
+                zipStates(name, operatorName, dl1.await(), dl2.await()) { a, b ->
+                    NoScope.runInFrpScope { transform(a, b) }
+                }
+            }
+        }
+    )
+}
+
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B, C, Z> combine(
+    stateA: TState<A>,
+    stateB: TState<B>,
+    stateC: TState<C>,
+    transform: suspend FrpScope.(A, B, C) -> Z,
+): TState<Z> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            coroutineScope {
+                val dl1: Deferred<TStateImpl<A>> = async {
+                    stateA.init.connect(evalScope = this@init)
+                }
+                val dl2: Deferred<TStateImpl<B>> = async {
+                    stateB.init.connect(evalScope = this@init)
+                }
+                val dl3: Deferred<TStateImpl<C>> = async {
+                    stateC.init.connect(evalScope = this@init)
+                }
+                zipStates(name, operatorName, dl1.await(), dl2.await(), dl3.await()) { a, b, c ->
+                    NoScope.runInFrpScope { transform(a, b, c) }
+                }
+            }
+        }
+    )
+}
+
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B, C, D, Z> combine(
+    stateA: TState<A>,
+    stateB: TState<B>,
+    stateC: TState<C>,
+    stateD: TState<D>,
+    transform: suspend FrpScope.(A, B, C, D) -> Z,
+): TState<Z> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            coroutineScope {
+                val dl1: Deferred<TStateImpl<A>> = async {
+                    stateA.init.connect(evalScope = this@init)
+                }
+                val dl2: Deferred<TStateImpl<B>> = async {
+                    stateB.init.connect(evalScope = this@init)
+                }
+                val dl3: Deferred<TStateImpl<C>> = async {
+                    stateC.init.connect(evalScope = this@init)
+                }
+                val dl4: Deferred<TStateImpl<D>> = async {
+                    stateD.init.connect(evalScope = this@init)
+                }
+                zipStates(name, operatorName, dl1.await(), dl2.await(), dl3.await(), dl4.await()) {
+                    a,
+                    b,
+                    c,
+                    d ->
+                    NoScope.runInFrpScope { transform(a, b, c, d) }
+                }
+            }
+        }
+    )
+}
+
+/** Returns a [TState] by applying [transform] to the value held by the original [TState]. */
+@ExperimentalFrpApi
+fun <A, B> TState<A>.flatMap(transform: suspend FrpScope.(A) -> TState<B>): TState<B> {
+    val operatorName = "flatMap"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            init.connect(this).flatMap(name, operatorName) { a ->
+                NoScope.runInFrpScope { transform(a) }.init.connect(this)
+            }
+        }
+    )
+}
+
+/** Shorthand for `flatMap { it }` */
+@ExperimentalFrpApi fun <A> TState<TState<A>>.flatten() = flatMap { it }
+
+/**
+ * Returns a [TStateSelector] that can be used to efficiently check if the input [TState] is
+ * currently holding a specific value.
+ *
+ * An example:
+ * ```
+ *   val lInt: TState<Int> = ...
+ *   val intSelector: TStateSelector<Int> = lInt.selector()
+ *   // Tracks if lInt is holding 1
+ *   val isOne: TState<Boolean> = intSelector.whenSelected(1)
+ * ```
+ *
+ * This is semantically equivalent to `val isOne = lInt.map { i -> i == 1 }`, but is significantly
+ * more efficient; specifically, using [TState.map] in this way incurs a `O(n)` performance hit,
+ * where `n` is the number of different [TState.map] operations used to track a specific value.
+ * [selector] internally uses a [HashMap] to lookup the appropriate downstream [TState] to update,
+ * and so operates in `O(1)`.
+ *
+ * Note that the result [TStateSelector] should be cached and re-used to gain the performance
+ * benefit.
+ *
+ * @see groupByKey
+ */
+@ExperimentalFrpApi
+fun <A> TState<A>.selector(numDistinctValues: Int? = null): TStateSelector<A> =
+    TStateSelector(
+        this,
+        stateChanges
+            .map { new -> mapOf(new to true, sampleDeferred().get() to false) }
+            .groupByKey(numDistinctValues),
+    )
+
+/**
+ * Tracks the currently selected value of type [A] from an upstream [TState].
+ *
+ * @see selector
+ */
+@ExperimentalFrpApi
+class TStateSelector<A>
+internal constructor(
+    private val upstream: TState<A>,
+    private val groupedChanges: GroupedTFlow<A, Boolean>,
+) {
+    /**
+     * Returns a [TState] that tracks whether the upstream [TState] is currently holding the given
+     * [value].
+     *
+     * @see selector
+     */
+    @ExperimentalFrpApi
+    fun whenSelected(value: A): TState<Boolean> {
+        val operatorName = "TStateSelector#whenSelected"
+        val name = "$operatorName[$value]"
+        return TStateInit(
+            init(name) {
+                DerivedMapCheap(
+                    name,
+                    operatorName,
+                    upstream = upstream.init.connect(evalScope = this),
+                    changes = groupedChanges.impl.eventsForKey(value),
+                ) {
+                    it == value
+                }
+            }
+        )
+    }
+
+    @ExperimentalFrpApi operator fun get(value: A): TState<Boolean> = whenSelected(value)
+}
+
+/** TODO */
+@ExperimentalFrpApi
+class MutableTState<T>
+internal constructor(internal val network: Network, initialValue: Deferred<T>) : TState<T>() {
+
+    private val input: CoalescingMutableTFlow<Deferred<T>, Deferred<T>?> =
+        CoalescingMutableTFlow(
+            coalesce = { _, new -> new },
+            network = network,
+            getInitialValue = { null },
+        )
+
+    internal val tState = run {
+        val changes = input.impl
+        val name = null
+        val operatorName = "MutableTState"
+        lateinit var state: TStateSource<T>
+        val calm: TFlowImpl<T> =
+            filterNode({ mapImpl(upstream = { changes.activated() }) { it!!.await() } }) { new ->
+                    new != state.getCurrentWithEpoch(evalScope = this).first
+                }
+                .cached()
+        state = TStateSource(name, operatorName, initialValue, calm)
+        @Suppress("DeferredResultUnused")
+        network.transaction {
+            calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
+                (connection, needsEval) ->
+                state.upstreamConnection = connection
+                if (needsEval) {
+                    schedule(state)
+                }
+            }
+        }
+        TStateInit(constInit(name, state))
+    }
+
+    /** TODO */
+    @ExperimentalFrpApi fun setValue(value: T) = input.emit(CompletableDeferred(value))
+
+    @ExperimentalFrpApi
+    fun setValueDeferred(value: FrpDeferredValue<T>) = input.emit(value.unwrapped)
+}
+
+/** A forward-reference to a [TState], allowing for recursive definitions. */
+@ExperimentalFrpApi
+class TStateLoop<A> : TState<A>() {
+
+    private val name: String? = null
+
+    private val deferred = CompletableDeferred<TState<A>>()
+
+    internal val init: Init<TStateImpl<A>> =
+        init(name) { deferred.await().init.connect(evalScope = this) }
+
+    /** The [TState] this [TStateLoop] will forward to. */
+    @ExperimentalFrpApi
+    var loopback: TState<A>? = null
+        set(value) {
+            value?.let {
+                check(deferred.complete(value)) { "TStateLoop.loopback has already been set." }
+                field = value
+            }
+        }
+
+    @ExperimentalFrpApi
+    operator fun getValue(thisRef: Any?, property: KProperty<*>): TState<A> = this
+
+    @ExperimentalFrpApi
+    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: TState<A>) {
+        loopback = value
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+internal class TStateInit<A> internal constructor(internal val init: Init<TStateImpl<A>>) :
+    TState<A>() {
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+internal val <A> TState<A>.init: Init<TStateImpl<A>>
+    get() =
+        when (this) {
+            is TStateInit -> init
+            is TStateLoop -> init
+            is MutableTState -> tState.init
+        }
+
+private inline fun <A> deferInline(
+    crossinline block: suspend InitScope.() -> TState<A>
+): TState<A> = TStateInit(init(name = null) { block().init.connect(evalScope = this) })
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt
new file mode 100644
index 0000000..0e7b420
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/Transactional.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.internal.InitScope
+import com.android.systemui.experimental.frp.internal.NoScope
+import com.android.systemui.experimental.frp.internal.TransactionalImpl
+import com.android.systemui.experimental.frp.internal.init
+import com.android.systemui.experimental.frp.internal.transactionalImpl
+import com.android.systemui.experimental.frp.internal.util.hashString
+import kotlinx.coroutines.CompletableDeferred
+
+/**
+ * A time-varying value. A [Transactional] encapsulates the idea of some continuous state; each time
+ * it is "sampled", a new result may be produced.
+ *
+ * Because FRP operates over an "idealized" model of Time that can be passed around as a data type,
+ * [Transactional]s are guaranteed to produce the same result if queried multiple times at the same
+ * (conceptual) time, in order to preserve _referential transparency_.
+ */
+@ExperimentalFrpApi
+class Transactional<out A> internal constructor(internal val impl: TState<TransactionalImpl<A>>) {
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+/** A constant [Transactional] that produces [value] whenever it is sampled. */
+@ExperimentalFrpApi
+fun <A> transactionalOf(value: A): Transactional<A> =
+    Transactional(tStateOf(TransactionalImpl.Const(CompletableDeferred(value))))
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> FrpDeferredValue<Transactional<A>>.defer(): Transactional<A> = deferInline {
+    unwrapped.await()
+}
+
+/** TODO */
+@ExperimentalFrpApi fun <A> Lazy<Transactional<A>>.defer(): Transactional<A> = deferInline { value }
+
+/** TODO */
+@ExperimentalFrpApi
+fun <A> deferTransactional(block: suspend FrpScope.() -> Transactional<A>): Transactional<A> =
+    deferInline {
+        NoScope.runInFrpScope(block)
+    }
+
+private inline fun <A> deferInline(
+    crossinline block: suspend InitScope.() -> Transactional<A>
+): Transactional<A> =
+    Transactional(TStateInit(init(name = null) { block().impl.init.connect(evalScope = this) }))
+
+/**
+ * Returns a [Transactional]. The passed [block] will be evaluated on demand at most once per
+ * transaction; any subsequent sampling within the same transaction will receive a cached value.
+ */
+@ExperimentalFrpApi
+fun <A> transactionally(block: suspend FrpTransactionScope.() -> A): Transactional<A> =
+    Transactional(tStateOf(transactionalImpl { runInTransactionScope(block) }))
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt
new file mode 100644
index 0000000..8062341
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/debug/Debug.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.debug
+
+import com.android.systemui.experimental.frp.MutableTState
+import com.android.systemui.experimental.frp.TState
+import com.android.systemui.experimental.frp.TStateInit
+import com.android.systemui.experimental.frp.TStateLoop
+import com.android.systemui.experimental.frp.internal.DerivedFlatten
+import com.android.systemui.experimental.frp.internal.DerivedMap
+import com.android.systemui.experimental.frp.internal.DerivedMapCheap
+import com.android.systemui.experimental.frp.internal.DerivedZipped
+import com.android.systemui.experimental.frp.internal.Init
+import com.android.systemui.experimental.frp.internal.TStateDerived
+import com.android.systemui.experimental.frp.internal.TStateImpl
+import com.android.systemui.experimental.frp.internal.TStateSource
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.none
+import com.android.systemui.experimental.frp.util.orElseGet
+
+// object IdGen {
+//    private val counter = AtomicLong()
+//    fun getId() = counter.getAndIncrement()
+// }
+
+typealias StateGraph = Graph<ActivationInfo>
+
+sealed class StateInfo(
+    val name: String,
+    val value: Maybe<Any?>,
+    val operator: String,
+    val epoch: Long?,
+)
+
+class Source(name: String, value: Maybe<Any?>, operator: String, epoch: Long) :
+    StateInfo(name, value, operator, epoch)
+
+class Derived(
+    name: String,
+    val type: DerivedStateType,
+    value: Maybe<Any?>,
+    operator: String,
+    epoch: Long?,
+) : StateInfo(name, value, operator, epoch)
+
+sealed interface DerivedStateType
+
+data object Flatten : DerivedStateType
+
+data class Mapped(val cheap: Boolean) : DerivedStateType
+
+data object Combine : DerivedStateType
+
+sealed class InitInfo(val name: String)
+
+class Uninitialized(name: String) : InitInfo(name)
+
+class Initialized(val state: StateInfo) : InitInfo(state.name)
+
+sealed interface ActivationInfo
+
+class Inactive(val name: String) : ActivationInfo
+
+class Active(val nodeInfo: StateInfo) : ActivationInfo
+
+class Dead(val name: String) : ActivationInfo
+
+data class Edge(val upstream: Any, val downstream: Any, val tag: Any? = null)
+
+data class Graph<T>(val nodes: Map<Any, T>, val edges: List<Edge>)
+
+internal fun TState<*>.dump(infoMap: MutableMap<Any, InitInfo>, edges: MutableList<Edge>) {
+    val init: Init<TStateImpl<Any?>> =
+        when (this) {
+            is TStateInit -> init
+            is TStateLoop -> init
+            is MutableTState -> tState.init
+        }
+    when (val stateMaybe = init.getUnsafe()) {
+        None -> {
+            infoMap[this] = Uninitialized(init.name ?: init.toString())
+        }
+        is Just -> {
+            stateMaybe.value.dump(infoMap, edges)
+        }
+    }
+}
+
+internal fun TStateImpl<*>.dump(infoById: MutableMap<Any, InitInfo>, edges: MutableList<Edge>) {
+    val state = this
+    if (state in infoById) return
+    val stateInfo =
+        when (state) {
+            is TStateDerived -> {
+                val type =
+                    when (state) {
+                        is DerivedFlatten -> {
+                            state.upstream.dump(infoById, edges)
+                            edges.add(
+                                Edge(upstream = state.upstream, downstream = state, tag = "outer")
+                            )
+                            state.upstream
+                                .getUnsafe()
+                                .orElseGet { null }
+                                ?.let {
+                                    edges.add(
+                                        Edge(upstream = it, downstream = state, tag = "inner")
+                                    )
+                                    it.dump(infoById, edges)
+                                }
+                            Flatten
+                        }
+                        is DerivedMap<*, *> -> {
+                            state.upstream.dump(infoById, edges)
+                            edges.add(Edge(upstream = state.upstream, downstream = state))
+                            Mapped(cheap = false)
+                        }
+                        is DerivedZipped<*, *> -> {
+                            state.upstream.forEach { (key, upstream) ->
+                                edges.add(
+                                    Edge(upstream = upstream, downstream = state, tag = "key=$key")
+                                )
+                                upstream.dump(infoById, edges)
+                            }
+                            Combine
+                        }
+                    }
+                Derived(
+                    state.name ?: state.operatorName,
+                    type,
+                    state.getCachedUnsafe(),
+                    state.operatorName,
+                    state.invalidatedEpoch,
+                )
+            }
+            is TStateSource ->
+                Source(
+                    state.name ?: state.operatorName,
+                    state.getStorageUnsafe(),
+                    state.operatorName,
+                    state.writeEpoch,
+                )
+            is DerivedMapCheap<*, *> -> {
+                state.upstream.dump(infoById, edges)
+                edges.add(Edge(upstream = state.upstream, downstream = state))
+                val type = Mapped(cheap = true)
+                Derived(
+                    state.name ?: state.operatorName,
+                    type,
+                    state.getUnsafe(),
+                    state.operatorName,
+                    null,
+                )
+            }
+        }
+    infoById[state] = Initialized(stateInfo)
+}
+
+private fun <A> TStateImpl<A>.getUnsafe(): Maybe<A> =
+    when (this) {
+        is TStateDerived -> getCachedUnsafe()
+        is TStateSource -> getStorageUnsafe()
+        is DerivedMapCheap<*, *> -> none
+    }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt
new file mode 100644
index 0000000..127abd8
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/BuildScopeImpl.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.CoalescingMutableTFlow
+import com.android.systemui.experimental.frp.FrpBuildScope
+import com.android.systemui.experimental.frp.FrpCoalescingProducerScope
+import com.android.systemui.experimental.frp.FrpDeferredValue
+import com.android.systemui.experimental.frp.FrpEffectScope
+import com.android.systemui.experimental.frp.FrpNetwork
+import com.android.systemui.experimental.frp.FrpProducerScope
+import com.android.systemui.experimental.frp.FrpSpec
+import com.android.systemui.experimental.frp.FrpStateScope
+import com.android.systemui.experimental.frp.FrpTransactionScope
+import com.android.systemui.experimental.frp.GroupedTFlow
+import com.android.systemui.experimental.frp.LocalFrpNetwork
+import com.android.systemui.experimental.frp.MutableTFlow
+import com.android.systemui.experimental.frp.TFlow
+import com.android.systemui.experimental.frp.TFlowInit
+import com.android.systemui.experimental.frp.groupByKey
+import com.android.systemui.experimental.frp.init
+import com.android.systemui.experimental.frp.internal.util.childScope
+import com.android.systemui.experimental.frp.internal.util.launchOnCancel
+import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
+import com.android.systemui.experimental.frp.launchEffect
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.map
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.startCoroutine
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CompletableJob
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.completeWith
+import kotlinx.coroutines.job
+
+internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope: CoroutineScope) :
+    BuildScope, StateScope by stateScope {
+
+    private val job: Job
+        get() = coroutineScope.coroutineContext.job
+
+    override val frpScope: FrpBuildScope = FrpBuildScopeImpl()
+
+    override suspend fun <R> runInBuildScope(block: suspend FrpBuildScope.() -> R): R {
+        val complete = CompletableDeferred<R>(parent = coroutineContext.job)
+        block.startCoroutine(
+            frpScope,
+            object : Continuation<R> {
+                override val context: CoroutineContext
+                    get() = EmptyCoroutineContext
+
+                override fun resumeWith(result: Result<R>) {
+                    complete.completeWith(result)
+                }
+            },
+        )
+        return complete.await()
+    }
+
+    private fun <A, T : TFlow<A>, S> buildTFlow(
+        constructFlow: (InputNode<A>) -> Pair<T, S>,
+        builder: suspend S.() -> Unit,
+    ): TFlow<A> {
+        var job: Job? = null
+        val stopEmitter = newStopEmitter()
+        val handle = this.job.invokeOnCompletion { stopEmitter.emit(Unit) }
+        // Create a child scope that will be kept alive beyond the end of this transaction.
+        val childScope = coroutineScope.childScope()
+        lateinit var emitter: Pair<T, S>
+        val inputNode =
+            InputNode<A>(
+                activate = {
+                    check(job == null) { "already activated" }
+                    job =
+                        reenterBuildScope(this@BuildScopeImpl, childScope).runInBuildScope {
+                            launchEffect {
+                                builder(emitter.second)
+                                handle.dispose()
+                                stopEmitter.emit(Unit)
+                            }
+                        }
+                },
+                deactivate = {
+                    checkNotNull(job) { "already deactivated" }.cancel()
+                    job = null
+                },
+            )
+        emitter = constructFlow(inputNode)
+        return with(frpScope) { emitter.first.takeUntil(stopEmitter) }
+    }
+
+    private fun <T> tFlowInternal(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T> =
+        buildTFlow(
+            constructFlow = { inputNode ->
+                val flow = MutableTFlow(network, inputNode)
+                flow to
+                    object : FrpProducerScope<T> {
+                        override suspend fun emit(value: T) {
+                            flow.emit(value)
+                        }
+                    }
+            },
+            builder = builder,
+        )
+
+    private fun <In, Out> coalescingTFlowInternal(
+        getInitialValue: () -> Out,
+        coalesce: (old: Out, new: In) -> Out,
+        builder: suspend FrpCoalescingProducerScope<In>.() -> Unit,
+    ): TFlow<Out> =
+        buildTFlow(
+            constructFlow = { inputNode ->
+                val flow = CoalescingMutableTFlow(coalesce, network, getInitialValue, inputNode)
+                flow to
+                    object : FrpCoalescingProducerScope<In> {
+                        override fun emit(value: In) {
+                            flow.emit(value)
+                        }
+                    }
+            },
+            builder = builder,
+        )
+
+    private fun <A> asyncScopeInternal(block: FrpSpec<A>): Pair<FrpDeferredValue<A>, Job> {
+        val childScope = mutableChildBuildScope()
+        return FrpDeferredValue(deferAsync { childScope.runInBuildScope(block) }) to childScope.job
+    }
+
+    private fun <R> deferredInternal(block: suspend FrpBuildScope.() -> R): FrpDeferredValue<R> =
+        FrpDeferredValue(deferAsync { runInBuildScope(block) })
+
+    private fun deferredActionInternal(block: suspend FrpBuildScope.() -> Unit) {
+        deferAction { runInBuildScope(block) }
+    }
+
+    private fun <A> TFlow<A>.observeEffectInternal(
+        context: CoroutineContext,
+        block: suspend FrpEffectScope.(A) -> Unit,
+    ): Job {
+        val subRef = AtomicReference<Maybe<Output<A>>>(null)
+        val childScope = coroutineScope.childScope()
+        // When our scope is cancelled, deactivate this observer.
+        childScope.launchOnCancel(CoroutineName("TFlow.observeEffect")) {
+            subRef.getAndSet(None)?.let { output ->
+                if (output is Just) {
+                    @Suppress("DeferredResultUnused")
+                    network.transaction { scheduleDeactivation(output.value) }
+                }
+            }
+        }
+        // Defer so that we don't suspend the caller
+        deferAction {
+            val outputNode =
+                Output<A>(
+                    context = context,
+                    onDeath = { subRef.getAndSet(None)?.let { childScope.cancel() } },
+                    onEmit = { output ->
+                        if (subRef.get() is Just) {
+                            // Not cancelled, safe to emit
+                            val coroutine: suspend FrpEffectScope.() -> Unit = { block(output) }
+                            val complete = CompletableDeferred<Unit>(parent = coroutineContext.job)
+                            coroutine.startCoroutine(
+                                object : FrpEffectScope, FrpTransactionScope by frpScope {
+                                    override val frpCoroutineScope: CoroutineScope = childScope
+                                    override val frpNetwork: FrpNetwork =
+                                        LocalFrpNetwork(network, childScope, endSignal)
+                                },
+                                completion =
+                                    object : Continuation<Unit> {
+                                        override val context: CoroutineContext
+                                            get() = EmptyCoroutineContext
+
+                                        override fun resumeWith(result: Result<Unit>) {
+                                            complete.completeWith(result)
+                                        }
+                                    },
+                            )
+                            complete.await()
+                        }
+                    },
+                )
+            with(frpScope) { this@observeEffectInternal.takeUntil(endSignal) }
+                .init
+                .connect(evalScope = stateScope.evalScope)
+                .activate(evalScope = stateScope.evalScope, outputNode.schedulable)
+                ?.let { (conn, needsEval) ->
+                    outputNode.upstream = conn
+                    if (!subRef.compareAndSet(null, just(outputNode))) {
+                        // Job's already been cancelled, schedule deactivation
+                        scheduleDeactivation(outputNode)
+                    } else if (needsEval) {
+                        outputNode.schedule(evalScope = stateScope.evalScope)
+                    }
+                } ?: childScope.cancel()
+        }
+        return childScope.coroutineContext.job
+    }
+
+    private fun <A, B> TFlow<A>.mapBuildInternal(
+        transform: suspend FrpBuildScope.(A) -> B
+    ): TFlow<B> {
+        val childScope = coroutineScope.childScope()
+        return TFlowInit(
+            constInit(
+                "mapBuild",
+                mapImpl({ init.connect(evalScope = this) }) { spec ->
+                        reenterBuildScope(outerScope = this@BuildScopeImpl, childScope)
+                            .runInBuildScope {
+                                val (result, _) = asyncScope { transform(spec) }
+                                result.get()
+                            }
+                    }
+                    .cached(),
+            )
+        )
+    }
+
+    private fun <K, A, B> TFlow<Map<K, Maybe<FrpSpec<A>>>>.applyLatestForKeyInternal(
+        init: FrpDeferredValue<Map<K, FrpSpec<B>>>,
+        numKeys: Int?,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> {
+        val eventsByKey: GroupedTFlow<K, Maybe<FrpSpec<A>>> = groupByKey(numKeys)
+        val initOut: Deferred<Map<K, B>> = deferAsync {
+            init.unwrapped.await().mapValuesParallel { (k, spec) ->
+                val newEnd = with(frpScope) { eventsByKey[k].skipNext() }
+                val newScope = childBuildScope(newEnd)
+                newScope.runInBuildScope(spec)
+            }
+        }
+        val childScope = coroutineScope.childScope()
+        val changesNode: TFlowImpl<Map<K, Maybe<A>>> =
+            mapImpl(upstream = { this@applyLatestForKeyInternal.init.connect(evalScope = this) }) {
+                upstreamMap ->
+                reenterBuildScope(this@BuildScopeImpl, childScope).run {
+                    upstreamMap.mapValuesParallel { (k: K, ma: Maybe<FrpSpec<A>>) ->
+                        ma.map { spec ->
+                            val newEnd = with(frpScope) { eventsByKey[k].skipNext() }
+                            val newScope = childBuildScope(newEnd)
+                            newScope.runInBuildScope(spec)
+                        }
+                    }
+                }
+            }
+        val changes: TFlow<Map<K, Maybe<A>>> =
+            TFlowInit(constInit("applyLatestForKey", changesNode.cached()))
+        // Ensure effects are observed; otherwise init will stay alive longer than expected
+        changes.observeEffectInternal(EmptyCoroutineContext) {}
+        return changes to FrpDeferredValue(initOut)
+    }
+
+    private fun newStopEmitter(): CoalescingMutableTFlow<Unit, Unit> =
+        CoalescingMutableTFlow(
+            coalesce = { _, _: Unit -> },
+            network = network,
+            getInitialValue = {},
+        )
+
+    private suspend fun childBuildScope(newEnd: TFlow<Any>): BuildScopeImpl {
+        val newCoroutineScope: CoroutineScope = coroutineScope.childScope()
+        return BuildScopeImpl(
+                stateScope = stateScope.childStateScope(newEnd),
+                coroutineScope = newCoroutineScope,
+            )
+            .apply {
+                // Ensure that once this transaction is done, the new child scope enters the
+                // completing state (kept alive so long as there are child jobs).
+                scheduleOutput(
+                    OneShot {
+                        // TODO: don't like this cast
+                        (newCoroutineScope.coroutineContext.job as CompletableJob).complete()
+                    }
+                )
+                runInBuildScope { endSignal.nextOnly().observe { newCoroutineScope.cancel() } }
+            }
+    }
+
+    private fun mutableChildBuildScope(): BuildScopeImpl {
+        val stopEmitter = newStopEmitter()
+        val childScope = coroutineScope.childScope()
+        childScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) }
+        // Ensure that once this transaction is done, the new child scope enters the completing
+        // state (kept alive so long as there are child jobs).
+        scheduleOutput(
+            OneShot {
+                // TODO: don't like this cast
+                (childScope.coroutineContext.job as CompletableJob).complete()
+            }
+        )
+        return BuildScopeImpl(
+            stateScope = StateScopeImpl(evalScope = stateScope.evalScope, endSignal = stopEmitter),
+            coroutineScope = childScope,
+        )
+    }
+
+    private inner class FrpBuildScopeImpl : FrpBuildScope, FrpStateScope by stateScope.frpScope {
+
+        override fun <T> tFlow(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T> =
+            tFlowInternal(builder)
+
+        override fun <In, Out> coalescingTFlow(
+            getInitialValue: () -> Out,
+            coalesce: (old: Out, new: In) -> Out,
+            builder: suspend FrpCoalescingProducerScope<In>.() -> Unit,
+        ): TFlow<Out> = coalescingTFlowInternal(getInitialValue, coalesce, builder)
+
+        override fun <A> asyncScope(block: FrpSpec<A>): Pair<FrpDeferredValue<A>, Job> =
+            asyncScopeInternal(block)
+
+        override fun <R> deferredBuildScope(
+            block: suspend FrpBuildScope.() -> R
+        ): FrpDeferredValue<R> = deferredInternal(block)
+
+        override fun deferredBuildScopeAction(block: suspend FrpBuildScope.() -> Unit) =
+            deferredActionInternal(block)
+
+        override fun <A> TFlow<A>.observe(
+            coroutineContext: CoroutineContext,
+            block: suspend FrpEffectScope.(A) -> Unit,
+        ): Job = observeEffectInternal(coroutineContext, block)
+
+        override fun <A, B> TFlow<A>.mapBuild(transform: suspend FrpBuildScope.(A) -> B): TFlow<B> =
+            mapBuildInternal(transform)
+
+        override fun <K, A, B> TFlow<Map<K, Maybe<FrpSpec<A>>>>.applyLatestSpecForKey(
+            initialSpecs: FrpDeferredValue<Map<K, FrpSpec<B>>>,
+            numKeys: Int?,
+        ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> =
+            applyLatestForKeyInternal(initialSpecs, numKeys)
+    }
+}
+
+private fun EvalScope.reenterBuildScope(
+    outerScope: BuildScopeImpl,
+    coroutineScope: CoroutineScope,
+) =
+    BuildScopeImpl(
+        stateScope = StateScopeImpl(evalScope = this, endSignal = outerScope.endSignal),
+        coroutineScope,
+    )
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt
new file mode 100644
index 0000000..f72ba5f
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/DeferScope.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.asyncImmediate
+import com.android.systemui.experimental.frp.internal.util.launchImmediate
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
+
+internal typealias DeferScope = CoroutineScope
+
+internal inline fun DeferScope.deferAction(
+    start: CoroutineStart = CoroutineStart.UNDISPATCHED,
+    crossinline block: suspend () -> Unit,
+): Job {
+    check(isActive) { "Cannot perform deferral, scope already closed." }
+    return launchImmediate(start, CoroutineName("deferAction")) { block() }
+}
+
+internal inline fun <R> DeferScope.deferAsync(
+    start: CoroutineStart = CoroutineStart.UNDISPATCHED,
+    crossinline block: suspend () -> R,
+): Deferred<R> {
+    check(isActive) { "Cannot perform deferral, scope already closed." }
+    return asyncImmediate(start, CoroutineName("deferAsync")) { block() }
+}
+
+internal suspend inline fun <A> deferScope(noinline block: suspend DeferScope.() -> A): A =
+    coroutineScope(block)
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt
new file mode 100644
index 0000000..418220f
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Demux.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.flatMap
+import com.android.systemui.experimental.frp.util.getMaybe
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+internal class DemuxNode<K, A>(
+    private val branchNodeByKey: ConcurrentHashMap<K, DemuxBranchNode<K, A>>,
+    val lifecycle: DemuxLifecycle<K, A>,
+    private val spec: DemuxActivator<K, A>,
+) : SchedulableNode {
+
+    val schedulable = Schedulable.N(this)
+
+    inline val mutex
+        get() = lifecycle.mutex
+
+    lateinit var upstreamConnection: NodeConnection<Map<K, A>>
+
+    fun getAndMaybeAddDownstream(key: K): DemuxBranchNode<K, A> =
+        branchNodeByKey.getOrPut(key) { DemuxBranchNode(key, this) }
+
+    override suspend fun schedule(evalScope: EvalScope) {
+        val upstreamResult = upstreamConnection.getPushEvent(evalScope)
+        if (upstreamResult is Just) {
+            coroutineScope {
+                val outerScope = this
+                mutex.withLock {
+                    coroutineScope {
+                        for ((key, _) in upstreamResult.value) {
+                            launch {
+                                branchNodeByKey[key]?.let { branch ->
+                                    outerScope.launch { branch.schedule(evalScope) }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override suspend fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int) {
+        coroutineScope {
+            mutex.withLock {
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.adjustDirectUpstream(
+                        coroutineScope = this,
+                        scheduler,
+                        oldDepth,
+                        newDepth,
+                    )
+                }
+            }
+        }
+    }
+
+    override suspend fun moveIndirectUpstreamToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+        newDirectDepth: Int,
+    ) {
+        coroutineScope {
+            mutex.withLock {
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.moveIndirectUpstreamToDirect(
+                        coroutineScope = this,
+                        scheduler,
+                        oldIndirectDepth,
+                        oldIndirectSet,
+                        newDirectDepth,
+                    )
+                }
+            }
+        }
+    }
+
+    override suspend fun adjustIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) {
+        coroutineScope {
+            mutex.withLock {
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.adjustIndirectUpstream(
+                        coroutineScope = this,
+                        scheduler,
+                        oldDepth,
+                        newDepth,
+                        removals,
+                        additions,
+                    )
+                }
+            }
+        }
+    }
+
+    override suspend fun moveDirectUpstreamToIndirect(
+        scheduler: Scheduler,
+        oldDirectDepth: Int,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        coroutineScope {
+            mutex.withLock {
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.moveDirectUpstreamToIndirect(
+                        coroutineScope = this,
+                        scheduler,
+                        oldDirectDepth,
+                        newIndirectDepth,
+                        newIndirectSet,
+                    )
+                }
+            }
+        }
+    }
+
+    override suspend fun removeIndirectUpstream(
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        coroutineScope {
+            mutex.withLock {
+                lifecycle.lifecycleState = DemuxLifecycleState.Dead
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.removeIndirectUpstream(
+                        coroutineScope = this,
+                        scheduler,
+                        depth,
+                        indirectSet,
+                    )
+                }
+            }
+        }
+    }
+
+    override suspend fun removeDirectUpstream(scheduler: Scheduler, depth: Int) {
+        coroutineScope {
+            mutex.withLock {
+                lifecycle.lifecycleState = DemuxLifecycleState.Dead
+                for ((_, branchNode) in branchNodeByKey) {
+                    branchNode.downstreamSet.removeDirectUpstream(
+                        coroutineScope = this,
+                        scheduler,
+                        depth,
+                    )
+                }
+            }
+        }
+    }
+
+    suspend fun removeDownstreamAndDeactivateIfNeeded(key: K) {
+        val deactivate =
+            mutex.withLock {
+                branchNodeByKey.remove(key)
+                branchNodeByKey.isEmpty()
+            }
+        if (deactivate) {
+            // No need for mutex here; no more concurrent changes to can occur during this phase
+            lifecycle.lifecycleState = DemuxLifecycleState.Inactive(spec)
+            upstreamConnection.removeDownstreamAndDeactivateIfNeeded(downstream = schedulable)
+        }
+    }
+}
+
+internal class DemuxBranchNode<K, A>(val key: K, private val demuxNode: DemuxNode<K, A>) :
+    PushNode<A> {
+
+    private val mutex = Mutex()
+
+    val downstreamSet = DownstreamSet()
+
+    override val depthTracker: DepthTracker
+        get() = demuxNode.upstreamConnection.depthTracker
+
+    override suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean =
+        demuxNode.upstreamConnection.hasCurrentValue(transactionStore)
+
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<A> =
+        demuxNode.upstreamConnection.getPushEvent(evalScope).flatMap { it.getMaybe(key) }
+
+    override suspend fun addDownstream(downstream: Schedulable) {
+        mutex.withLock { downstreamSet.add(downstream) }
+    }
+
+    override suspend fun removeDownstream(downstream: Schedulable) {
+        mutex.withLock { downstreamSet.remove(downstream) }
+    }
+
+    override suspend fun removeDownstreamAndDeactivateIfNeeded(downstream: Schedulable) {
+        val canDeactivate =
+            mutex.withLock {
+                downstreamSet.remove(downstream)
+                downstreamSet.isEmpty()
+            }
+        if (canDeactivate) {
+            demuxNode.removeDownstreamAndDeactivateIfNeeded(key)
+        }
+    }
+
+    override suspend fun deactivateIfNeeded() {
+        if (mutex.withLock { downstreamSet.isEmpty() }) {
+            demuxNode.removeDownstreamAndDeactivateIfNeeded(key)
+        }
+    }
+
+    override suspend fun scheduleDeactivationIfNeeded(evalScope: EvalScope) {
+        if (mutex.withLock { downstreamSet.isEmpty() }) {
+            evalScope.scheduleDeactivation(this)
+        }
+    }
+
+    suspend fun schedule(evalScope: EvalScope) {
+        if (!coroutineScope { mutex.withLock { scheduleAll(downstreamSet, evalScope) } }) {
+            evalScope.scheduleDeactivation(this)
+        }
+    }
+}
+
+internal fun <K, A> DemuxImpl(
+    upstream: suspend EvalScope.() -> TFlowImpl<Map<K, A>>,
+    numKeys: Int?,
+): DemuxImpl<K, A> =
+    DemuxImpl(
+        DemuxLifecycle(
+            object : DemuxActivator<K, A> {
+                override suspend fun activate(
+                    evalScope: EvalScope,
+                    lifecycle: DemuxLifecycle<K, A>,
+                ): Pair<DemuxNode<K, A>, Boolean>? {
+                    val dmux = DemuxNode(ConcurrentHashMap(numKeys ?: 16), lifecycle, this)
+                    return upstream
+                        .invoke(evalScope)
+                        .activate(evalScope, downstream = dmux.schedulable)
+                        ?.let { (conn, needsEval) ->
+                            dmux.apply { upstreamConnection = conn } to needsEval
+                        }
+                }
+            }
+        )
+    )
+
+internal class DemuxImpl<in K, out A>(private val dmux: DemuxLifecycle<K, A>) {
+    fun eventsForKey(key: K): TFlowImpl<A> = TFlowCheap { downstream ->
+        dmux.activate(evalScope = this, key)?.let { (branchNode, needsEval) ->
+            branchNode.addDownstream(downstream)
+            val branchNeedsEval = needsEval && branchNode.getPushEvent(evalScope = this) is Just
+            ActivationResult(
+                connection = NodeConnection(branchNode, branchNode),
+                needsEval = branchNeedsEval,
+            )
+        }
+    }
+}
+
+internal class DemuxLifecycle<K, A>(@Volatile var lifecycleState: DemuxLifecycleState<K, A>) {
+    val mutex = Mutex()
+
+    override fun toString(): String = "TFlowDmuxState[$hashString][$lifecycleState][$mutex]"
+
+    suspend fun activate(evalScope: EvalScope, key: K): Pair<DemuxBranchNode<K, A>, Boolean>? =
+        coroutineScope {
+            mutex
+                .withLock {
+                    when (val state = lifecycleState) {
+                        is DemuxLifecycleState.Dead -> null
+                        is DemuxLifecycleState.Active ->
+                            state.node.getAndMaybeAddDownstream(key) to
+                                async {
+                                    state.node.upstreamConnection.hasCurrentValue(
+                                        evalScope.transactionStore
+                                    )
+                                }
+                        is DemuxLifecycleState.Inactive -> {
+                            state.spec
+                                .activate(evalScope, this@DemuxLifecycle)
+                                .also { result ->
+                                    lifecycleState =
+                                        if (result == null) {
+                                            DemuxLifecycleState.Dead
+                                        } else {
+                                            DemuxLifecycleState.Active(result.first)
+                                        }
+                                }
+                                ?.let { (node, needsEval) ->
+                                    node.getAndMaybeAddDownstream(key) to
+                                        CompletableDeferred(needsEval)
+                                }
+                        }
+                    }
+                }
+                ?.let { (branch, result) -> branch to result.await() }
+        }
+}
+
+internal sealed interface DemuxLifecycleState<out K, out A> {
+    class Inactive<K, A>(val spec: DemuxActivator<K, A>) : DemuxLifecycleState<K, A> {
+        override fun toString(): String = "Inactive"
+    }
+
+    class Active<K, A>(val node: DemuxNode<K, A>) : DemuxLifecycleState<K, A> {
+        override fun toString(): String = "Active(node=$node)"
+    }
+
+    data object Dead : DemuxLifecycleState<Nothing, Nothing>
+}
+
+internal interface DemuxActivator<K, A> {
+    suspend fun activate(
+        evalScope: EvalScope,
+        lifecycle: DemuxLifecycle<K, A>,
+    ): Pair<DemuxNode<K, A>, Boolean>?
+}
+
+internal inline fun <K, A> DemuxLifecycle(onSubscribe: DemuxActivator<K, A>) =
+    DemuxLifecycle(DemuxLifecycleState.Inactive(onSubscribe))
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt
new file mode 100644
index 0000000..38bc22f
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/EvalScopeImpl.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.FrpDeferredValue
+import com.android.systemui.experimental.frp.FrpTransactionScope
+import com.android.systemui.experimental.frp.TFlow
+import com.android.systemui.experimental.frp.TFlowInit
+import com.android.systemui.experimental.frp.TFlowLoop
+import com.android.systemui.experimental.frp.TState
+import com.android.systemui.experimental.frp.TStateInit
+import com.android.systemui.experimental.frp.Transactional
+import com.android.systemui.experimental.frp.emptyTFlow
+import com.android.systemui.experimental.frp.init
+import com.android.systemui.experimental.frp.mapCheap
+import com.android.systemui.experimental.frp.switch
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.startCoroutine
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.completeWith
+import kotlinx.coroutines.job
+
+internal class EvalScopeImpl(networkScope: NetworkScope, deferScope: DeferScope) :
+    EvalScope, NetworkScope by networkScope, DeferScope by deferScope {
+
+    private suspend fun <A> Transactional<A>.sample(): A =
+        impl.sample().sample(this@EvalScopeImpl).await()
+
+    private suspend fun <A> TState<A>.sample(): A =
+        init.connect(evalScope = this@EvalScopeImpl).getCurrentWithEpoch(this@EvalScopeImpl).first
+
+    private val <A> Transactional<A>.deferredValue: FrpDeferredValue<A>
+        get() = FrpDeferredValue(deferAsync { sample() })
+
+    private val <A> TState<A>.deferredValue: FrpDeferredValue<A>
+        get() = FrpDeferredValue(deferAsync { sample() })
+
+    private val nowInternal: TFlow<Unit> by lazy {
+        var result by TFlowLoop<Unit>()
+        result =
+            TStateInit(
+                    constInit(
+                        "now",
+                        mkState(
+                            "now",
+                            "now",
+                            this,
+                            { result.mapCheap { emptyTFlow }.init.connect(evalScope = this) },
+                            CompletableDeferred(
+                                TFlowInit(
+                                    constInit(
+                                        "now",
+                                        TFlowCheap {
+                                            ActivationResult(
+                                                connection = NodeConnection(AlwaysNode, AlwaysNode),
+                                                needsEval = true,
+                                            )
+                                        },
+                                    )
+                                )
+                            ),
+                        ),
+                    )
+                )
+                .switch()
+        result
+    }
+
+    private fun <R> deferredInternal(
+        block: suspend FrpTransactionScope.() -> R
+    ): FrpDeferredValue<R> = FrpDeferredValue(deferAsync { runInTransactionScope(block) })
+
+    override suspend fun <R> runInTransactionScope(block: suspend FrpTransactionScope.() -> R): R {
+        val complete = CompletableDeferred<R>(parent = coroutineContext.job)
+        block.startCoroutine(
+            frpScope,
+            object : Continuation<R> {
+                override val context: CoroutineContext
+                    get() = EmptyCoroutineContext
+
+                override fun resumeWith(result: Result<R>) {
+                    complete.completeWith(result)
+                }
+            },
+        )
+        return complete.await()
+    }
+
+    override val frpScope: FrpTransactionScope = FrpTransactionScopeImpl()
+
+    inner class FrpTransactionScopeImpl : FrpTransactionScope {
+        override fun <A> Transactional<A>.sampleDeferred(): FrpDeferredValue<A> = deferredValue
+
+        override fun <A> TState<A>.sampleDeferred(): FrpDeferredValue<A> = deferredValue
+
+        override fun <R> deferredTransactionScope(
+            block: suspend FrpTransactionScope.() -> R
+        ): FrpDeferredValue<R> = deferredInternal(block)
+
+        override val now: TFlow<Unit>
+            get() = nowInternal
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt
new file mode 100644
index 0000000..4f2a769
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/FilterNode.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.none
+
+internal inline fun <A, B> mapMaybeNode(
+    crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline f: suspend EvalScope.(A) -> Maybe<B>,
+): TFlowImpl<B> {
+    return DemuxImpl(
+            {
+                mapImpl(getPulse) {
+                    val maybeResult = f(it)
+                    if (maybeResult is Just) {
+                        mapOf(Unit to maybeResult.value)
+                    } else {
+                        emptyMap()
+                    }
+                }
+            },
+            numKeys = 1,
+        )
+        .eventsForKey(Unit)
+}
+
+internal inline fun <A> filterNode(
+    crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline f: suspend EvalScope.(A) -> Boolean,
+): TFlowImpl<A> = mapMaybeNode(getPulse) { if (f(it)) just(it) else none }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt
new file mode 100644
index 0000000..9425870
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Graph.kt
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Bag
+import java.util.TreeMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Tracks all upstream connections for Mux nodes.
+ *
+ * Connections come in two flavors:
+ * 1. **DIRECT** :: The upstream node may emit events that would cause the owner of this depth
+ *    tracker to also emit.
+ * 2. **INDIRECT** :: The upstream node will not emit events, but may start doing so in a future
+ *    transaction (at which point its depth will change to DIRECT).
+ *
+ * DIRECT connections are the standard, active connections that propagate events through the graph.
+ * They are used to calculate the evaluation depth of a node, so that it is only visited once it is
+ * certain that all DIRECT upstream connections have already been visited (or are not emitting in
+ * the current transaction).
+ *
+ * It is *invalid* for a node to be directly upstream of itself. Doing so is an error.
+ *
+ * INDIRECT connections identify nodes that are still "alive" (should not be garbage-collected) but
+ * are presently "dormant". This only occurs when a MuxDeferredNode has nothing switched-in, but is
+ * still connected to its "patches" upstream node, implying that something *may* be switched-in at a
+ * later time.
+ *
+ * It is *invalid* for a node to be indirectly upstream of itself. These connections are
+ * automatically filtered out.
+ *
+ * When there are no connections, either DIRECT or INDIRECT, a node *dies* and all incoming/outgoing
+ * connections are freed so that it can be garbage-collected.
+ *
+ * Note that there is an edge case where a MuxDeferredNode is connected to itself via its "patches"
+ * upstream node. In this case:
+ * 1. If the node has switched-in upstream nodes, then this is perfectly valid. Downstream nodes
+ *    will see a direct connection to this MuxDeferredNode.
+ * 2. Otherwise, the node would normally be considered "dormant" and downstream nodes would see an
+ *    indirect connection. However, because a node cannot be indirectly upstream of itself, then the
+ *    MuxDeferredNode sees no connection via its patches upstream node, and so is considered "dead".
+ *    Conceptually, this makes some sense: The only way for this recursive MuxDeferredNode to become
+ *    non-dormant is to switch some upstream nodes back in, but since the patches node is itself,
+ *    this will never happen.
+ *
+ * This behavior underpins the recursive definition of `nextOnly`.
+ */
+internal class DepthTracker {
+
+    @Volatile var snapshotIsDirect = true
+    @Volatile private var snapshotIsIndirectRoot = false
+
+    private inline val snapshotIsIndirect: Boolean
+        get() = !snapshotIsDirect
+
+    @Volatile var snapshotIndirectDepth: Int = 0
+    @Volatile var snapshotDirectDepth: Int = 0
+
+    private val _snapshotIndirectRoots = HashSet<MuxDeferredNode<*, *>>()
+    val snapshotIndirectRoots
+        get() = _snapshotIndirectRoots.toSet()
+
+    private val indirectAdditions = HashSet<MuxDeferredNode<*, *>>()
+    private val indirectRemovals = HashSet<MuxDeferredNode<*, *>>()
+    private val dirty_directUpstreamDepths = TreeMap<Int, Int>()
+    private val dirty_indirectUpstreamDepths = TreeMap<Int, Int>()
+    private val dirty_indirectUpstreamRoots = Bag<MuxDeferredNode<*, *>>()
+    @Volatile var dirty_directDepth = 0
+    @Volatile private var dirty_indirectDepth = 0
+    @Volatile private var dirty_depthIsDirect = true
+    @Volatile private var dirty_isIndirectRoot = false
+
+    suspend fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) {
+        if (dirty_depthIsDirect) {
+            scheduler.schedule(dirty_directDepth, node)
+        } else {
+            scheduler.scheduleIndirect(dirty_indirectDepth, node)
+        }
+    }
+
+    // only used by MuxDeferred
+    // and only when there is a direct connection to the patch node
+    fun setIsIndirectRoot(isRoot: Boolean): Boolean {
+        if (isRoot != dirty_isIndirectRoot) {
+            dirty_isIndirectRoot = isRoot
+            return !dirty_depthIsDirect
+        }
+        return false
+    }
+
+    // adds an upstream connection, and recalcs depth
+    // returns true if depth has changed
+    fun addDirectUpstream(oldDepth: Int?, newDepth: Int): Boolean {
+        if (oldDepth != null) {
+            dirty_directUpstreamDepths.compute(oldDepth) { _, count ->
+                count?.minus(1)?.takeIf { it > 0 }
+            }
+        }
+        dirty_directUpstreamDepths.compute(newDepth) { _, current -> current?.plus(1) ?: 1 }
+        return recalcDepth()
+    }
+
+    private fun recalcDepth(): Boolean {
+        val newDepth =
+            dirty_directUpstreamDepths.lastEntry()?.let { (maxDepth, _) -> maxDepth + 1 } ?: 0
+
+        val isDirect = dirty_directUpstreamDepths.isNotEmpty()
+        val isDirectChanged = dirty_depthIsDirect != isDirect
+        dirty_depthIsDirect = isDirect
+
+        return (newDepth != dirty_directDepth).also { dirty_directDepth = newDepth } or
+            isDirectChanged
+    }
+
+    private fun recalcIndirDepth(): Boolean {
+        val newDepth =
+            dirty_indirectUpstreamDepths.lastEntry()?.let { (maxDepth, _) -> maxDepth + 1 } ?: 0
+        return (!dirty_depthIsDirect && !dirty_isIndirectRoot && newDepth != dirty_indirectDepth)
+            .also { dirty_indirectDepth = newDepth }
+    }
+
+    fun removeDirectUpstream(depth: Int): Boolean {
+        dirty_directUpstreamDepths.compute(depth) { _, count -> count?.minus(1)?.takeIf { it > 0 } }
+        return recalcDepth()
+    }
+
+    fun addIndirectUpstream(oldDepth: Int?, newDepth: Int): Boolean =
+        if (oldDepth == newDepth) {
+            false
+        } else {
+            if (oldDepth != null) {
+                dirty_indirectUpstreamDepths.compute(oldDepth) { _, current ->
+                    current?.minus(1)?.takeIf { it > 0 }
+                }
+            }
+            dirty_indirectUpstreamDepths.compute(newDepth) { _, current -> current?.plus(1) ?: 1 }
+            recalcIndirDepth()
+        }
+
+    fun removeIndirectUpstream(depth: Int): Boolean {
+        dirty_indirectUpstreamDepths.compute(depth) { _, current ->
+            current?.minus(1)?.takeIf { it > 0 }
+        }
+        return recalcIndirDepth()
+    }
+
+    fun updateIndirectRoots(
+        additions: Set<MuxDeferredNode<*, *>>? = null,
+        removals: Set<MuxDeferredNode<*, *>>? = null,
+        butNot: MuxDeferredNode<*, *>? = null,
+    ): Boolean {
+        val addsChanged =
+            additions
+                ?.let { dirty_indirectUpstreamRoots.addAll(additions, butNot) }
+                ?.let {
+                    indirectAdditions.addAll(indirectRemovals.applyRemovalDiff(it))
+                    true
+                } ?: false
+        val removalsChanged =
+            removals
+                ?.let { dirty_indirectUpstreamRoots.removeAll(removals) }
+                ?.let {
+                    indirectRemovals.addAll(indirectAdditions.applyRemovalDiff(it))
+                    true
+                } ?: false
+        return (!dirty_depthIsDirect && (addsChanged || removalsChanged))
+    }
+
+    private fun <T> HashSet<T>.applyRemovalDiff(changeSet: Set<T>): Set<T> {
+        val remainder = HashSet<T>()
+        for (element in changeSet) {
+            if (!add(element)) {
+                remainder.add(element)
+            }
+        }
+        return remainder
+    }
+
+    suspend fun propagateChanges(scheduler: Scheduler, muxNode: MuxNode<*, *, *>) {
+        if (isDirty()) {
+            schedule(scheduler, muxNode)
+        }
+    }
+
+    fun applyChanges(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        downstreamSet: DownstreamSet,
+        muxNode: MuxNode<*, *, *>,
+    ) {
+        when {
+            dirty_depthIsDirect -> {
+                if (snapshotIsDirect) {
+                    downstreamSet.adjustDirectUpstream(
+                        coroutineScope,
+                        scheduler,
+                        oldDepth = snapshotDirectDepth,
+                        newDepth = dirty_directDepth,
+                    )
+                } else {
+                    downstreamSet.moveIndirectUpstreamToDirect(
+                        coroutineScope,
+                        scheduler,
+                        oldIndirectDepth = snapshotIndirectDepth,
+                        oldIndirectSet =
+                            buildSet {
+                                addAll(snapshotIndirectRoots)
+                                if (snapshotIsIndirectRoot) {
+                                    add(muxNode as MuxDeferredNode<*, *>)
+                                }
+                            },
+                        newDirectDepth = dirty_directDepth,
+                    )
+                }
+            }
+
+            dirty_hasIndirectUpstream() || dirty_isIndirectRoot -> {
+                if (snapshotIsDirect) {
+                    downstreamSet.moveDirectUpstreamToIndirect(
+                        coroutineScope,
+                        scheduler,
+                        oldDirectDepth = snapshotDirectDepth,
+                        newIndirectDepth = dirty_indirectDepth,
+                        newIndirectSet =
+                            buildSet {
+                                addAll(dirty_indirectUpstreamRoots)
+                                if (dirty_isIndirectRoot) {
+                                    add(muxNode as MuxDeferredNode<*, *>)
+                                }
+                            },
+                    )
+                } else {
+                    downstreamSet.adjustIndirectUpstream(
+                        coroutineScope,
+                        scheduler,
+                        oldDepth = snapshotIndirectDepth,
+                        newDepth = dirty_indirectDepth,
+                        removals =
+                            buildSet {
+                                addAll(indirectRemovals)
+                                if (snapshotIsIndirectRoot && !dirty_isIndirectRoot) {
+                                    add(muxNode as MuxDeferredNode<*, *>)
+                                }
+                            },
+                        additions =
+                            buildSet {
+                                addAll(indirectAdditions)
+                                if (!snapshotIsIndirectRoot && dirty_isIndirectRoot) {
+                                    add(muxNode as MuxDeferredNode<*, *>)
+                                }
+                            },
+                    )
+                }
+            }
+
+            else -> {
+                // die
+                muxNode.lifecycle.lifecycleState = MuxLifecycleState.Dead
+
+                if (snapshotIsDirect) {
+                    downstreamSet.removeDirectUpstream(
+                        coroutineScope,
+                        scheduler,
+                        depth = snapshotDirectDepth,
+                    )
+                } else {
+                    downstreamSet.removeIndirectUpstream(
+                        coroutineScope,
+                        scheduler,
+                        depth = snapshotIndirectDepth,
+                        indirectSet =
+                            buildSet {
+                                addAll(snapshotIndirectRoots)
+                                if (snapshotIsIndirectRoot) {
+                                    add(muxNode as MuxDeferredNode<*, *>)
+                                }
+                            },
+                    )
+                }
+                downstreamSet.clear()
+            }
+        }
+        reset()
+    }
+
+    fun dirty_hasDirectUpstream(): Boolean = dirty_directUpstreamDepths.isNotEmpty()
+
+    private fun dirty_hasIndirectUpstream(): Boolean = dirty_indirectUpstreamRoots.isNotEmpty()
+
+    override fun toString(): String =
+        "DepthTracker(" +
+            "sIsDirect=$snapshotIsDirect, " +
+            "sDirectDepth=$snapshotDirectDepth, " +
+            "sIndirectDepth=$snapshotIndirectDepth, " +
+            "sIndirectRoots=$snapshotIndirectRoots, " +
+            "dIsIndirectRoot=$dirty_isIndirectRoot, " +
+            "dDirectDepths=$dirty_directUpstreamDepths, " +
+            "dIndirectDepths=$dirty_indirectUpstreamDepths, " +
+            "dIndirectRoots=$dirty_indirectUpstreamRoots" +
+            ")"
+
+    fun reset() {
+        snapshotIsDirect = dirty_hasDirectUpstream()
+        snapshotDirectDepth = dirty_directDepth
+        snapshotIndirectDepth = dirty_indirectDepth
+        snapshotIsIndirectRoot = dirty_isIndirectRoot
+        if (indirectAdditions.isNotEmpty() || indirectRemovals.isNotEmpty()) {
+            _snapshotIndirectRoots.clear()
+            _snapshotIndirectRoots.addAll(dirty_indirectUpstreamRoots)
+        }
+        indirectAdditions.clear()
+        indirectRemovals.clear()
+        //        check(!isDirty()) { "should not be dirty after a reset" }
+    }
+
+    fun isDirty(): Boolean =
+        when {
+            snapshotIsDirect -> !dirty_depthIsDirect || snapshotDirectDepth != dirty_directDepth
+            snapshotIsIndirectRoot -> dirty_depthIsDirect || !dirty_isIndirectRoot
+            else ->
+                dirty_depthIsDirect ||
+                    dirty_isIndirectRoot ||
+                    snapshotIndirectDepth != dirty_indirectDepth ||
+                    indirectAdditions.isNotEmpty() ||
+                    indirectRemovals.isNotEmpty()
+        }
+
+    fun dirty_depthIncreased(): Boolean =
+        snapshotDirectDepth < dirty_directDepth || snapshotIsIndirect && dirty_hasDirectUpstream()
+}
+
+/**
+ * Tracks downstream nodes to be scheduled when the owner of this DownstreamSet produces a value in
+ * a transaction.
+ */
+internal class DownstreamSet {
+
+    val outputs = HashSet<Output<*>>()
+    val stateWriters = mutableListOf<TStateSource<*>>()
+    val muxMovers = HashSet<MuxDeferredNode<*, *>>()
+    val nodes = HashSet<SchedulableNode>()
+
+    fun add(schedulable: Schedulable) {
+        when (schedulable) {
+            is Schedulable.S -> stateWriters.add(schedulable.state)
+            is Schedulable.M -> muxMovers.add(schedulable.muxMover)
+            is Schedulable.N -> nodes.add(schedulable.node)
+            is Schedulable.O -> outputs.add(schedulable.output)
+        }
+    }
+
+    fun remove(schedulable: Schedulable) {
+        when (schedulable) {
+            is Schedulable.S -> error("WTF: latches are never removed")
+            is Schedulable.M -> muxMovers.remove(schedulable.muxMover)
+            is Schedulable.N -> nodes.remove(schedulable.node)
+            is Schedulable.O -> outputs.remove(schedulable.output)
+        }
+    }
+
+    fun adjustDirectUpstream(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+    ) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch { node.adjustDirectUpstream(scheduler, oldDepth, newDepth) }
+            }
+        }
+
+    fun moveIndirectUpstreamToDirect(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+        newDirectDepth: Int,
+    ) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch {
+                    node.moveIndirectUpstreamToDirect(
+                        scheduler,
+                        oldIndirectDepth,
+                        oldIndirectSet,
+                        newDirectDepth,
+                    )
+                }
+            }
+            for (mover in muxMovers) {
+                launch {
+                    mover.moveIndirectPatchNodeToDirect(scheduler, oldIndirectDepth, oldIndirectSet)
+                }
+            }
+        }
+
+    fun adjustIndirectUpstream(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch {
+                    node.adjustIndirectUpstream(scheduler, oldDepth, newDepth, removals, additions)
+                }
+            }
+            for (mover in muxMovers) {
+                launch {
+                    mover.adjustIndirectPatchNode(
+                        scheduler,
+                        oldDepth,
+                        newDepth,
+                        removals,
+                        additions,
+                    )
+                }
+            }
+        }
+
+    fun moveDirectUpstreamToIndirect(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        oldDirectDepth: Int,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch {
+                    node.moveDirectUpstreamToIndirect(
+                        scheduler,
+                        oldDirectDepth,
+                        newIndirectDepth,
+                        newIndirectSet,
+                    )
+                }
+            }
+            for (mover in muxMovers) {
+                launch {
+                    mover.moveDirectPatchNodeToIndirect(scheduler, newIndirectDepth, newIndirectSet)
+                }
+            }
+        }
+
+    fun removeIndirectUpstream(
+        coroutineScope: CoroutineScope,
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch { node.removeIndirectUpstream(scheduler, depth, indirectSet) }
+            }
+            for (mover in muxMovers) {
+                launch { mover.removeIndirectPatchNode(scheduler, depth, indirectSet) }
+            }
+            for (output in outputs) {
+                launch { output.kill() }
+            }
+        }
+
+    fun removeDirectUpstream(coroutineScope: CoroutineScope, scheduler: Scheduler, depth: Int) =
+        coroutineScope.run {
+            for (node in nodes) {
+                launch { node.removeDirectUpstream(scheduler, depth) }
+            }
+            for (mover in muxMovers) {
+                launch { mover.removeDirectPatchNode(scheduler) }
+            }
+            for (output in outputs) {
+                launch { output.kill() }
+            }
+        }
+
+    fun clear() {
+        outputs.clear()
+        stateWriters.clear()
+        muxMovers.clear()
+        nodes.clear()
+    }
+}
+
+// TODO: remove this indirection
+internal sealed interface Schedulable {
+    data class S constructor(val state: TStateSource<*>) : Schedulable
+
+    data class M constructor(val muxMover: MuxDeferredNode<*, *>) : Schedulable
+
+    data class N constructor(val node: SchedulableNode) : Schedulable
+
+    data class O constructor(val output: Output<*>) : Schedulable
+}
+
+internal fun DownstreamSet.isEmpty() =
+    nodes.isEmpty() && outputs.isEmpty() && muxMovers.isEmpty() && stateWriters.isEmpty()
+
+@Suppress("NOTHING_TO_INLINE") internal inline fun DownstreamSet.isNotEmpty() = !isEmpty()
+
+internal fun CoroutineScope.scheduleAll(
+    downstreamSet: DownstreamSet,
+    evalScope: EvalScope,
+): Boolean {
+    downstreamSet.nodes.forEach { launch { it.schedule(evalScope) } }
+    downstreamSet.muxMovers.forEach { launch { it.scheduleMover(evalScope) } }
+    downstreamSet.outputs.forEach { launch { it.schedule(evalScope) } }
+    downstreamSet.stateWriters.forEach { evalScope.schedule(it) }
+    return downstreamSet.isNotEmpty()
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt
new file mode 100644
index 0000000..efb7a09
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Init.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.none
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Performs actions once, when the reactive component is first connected to the network. */
+internal class Init<out A>(val name: String?, private val block: suspend InitScope.() -> A) {
+
+    /** Has the initialization logic been evaluated yet? */
+    private val initialized = AtomicBoolean()
+
+    /**
+     * Stores the result after initialization, as well as the id of the [Network] it's been
+     * initialized with.
+     */
+    private val cache = CompletableDeferred<Pair<Any, A>>()
+
+    suspend fun connect(evalScope: InitScope): A =
+        if (initialized.getAndSet(true)) {
+            // Read from cache
+            val (networkId, result) = cache.await()
+            check(networkId == evalScope.networkId) { "Network mismatch" }
+            result
+        } else {
+            // Write to cache
+            block(evalScope).also { cache.complete(evalScope.networkId to it) }
+        }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun getUnsafe(): Maybe<A> =
+        if (cache.isCompleted) {
+            just(cache.getCompleted().second)
+        } else {
+            none
+        }
+}
+
+internal fun <A> init(name: String?, block: suspend InitScope.() -> A) = Init(name, block)
+
+internal fun <A> constInit(name: String?, value: A) = init(name) { value }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt
new file mode 100644
index 0000000..85c87fe
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Inputs.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+internal class InputNode<A>(
+    private val activate: suspend EvalScope.() -> Unit = {},
+    private val deactivate: () -> Unit = {},
+) : PushNode<A>, Key<A> {
+
+    internal val downstreamSet = DownstreamSet()
+    private val mutex = Mutex()
+    private val activated = AtomicBoolean(false)
+
+    override val depthTracker: DepthTracker = DepthTracker()
+
+    override suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean =
+        transactionStore.contains(this)
+
+    suspend fun visit(evalScope: EvalScope, value: A) {
+        evalScope.setResult(this, value)
+        coroutineScope {
+            if (!mutex.withLock { scheduleAll(downstreamSet, evalScope) }) {
+                evalScope.scheduleDeactivation(this@InputNode)
+            }
+        }
+    }
+
+    override suspend fun removeDownstream(downstream: Schedulable) {
+        mutex.withLock { downstreamSet.remove(downstream) }
+    }
+
+    override suspend fun deactivateIfNeeded() {
+        if (mutex.withLock { downstreamSet.isEmpty() && activated.getAndSet(false) }) {
+            deactivate()
+        }
+    }
+
+    override suspend fun scheduleDeactivationIfNeeded(evalScope: EvalScope) {
+        if (mutex.withLock { downstreamSet.isEmpty() }) {
+            evalScope.scheduleDeactivation(this)
+        }
+    }
+
+    override suspend fun addDownstream(downstream: Schedulable) {
+        mutex.withLock { downstreamSet.add(downstream) }
+    }
+
+    suspend fun addDownstreamAndActivateIfNeeded(downstream: Schedulable, evalScope: EvalScope) {
+        val needsActivation =
+            mutex.withLock {
+                val wasEmpty = downstreamSet.isEmpty()
+                downstreamSet.add(downstream)
+                wasEmpty && !activated.getAndSet(true)
+            }
+        if (needsActivation) {
+            activate(evalScope)
+        }
+    }
+
+    override suspend fun removeDownstreamAndDeactivateIfNeeded(downstream: Schedulable) {
+        val needsDeactivation =
+            mutex.withLock {
+                downstreamSet.remove(downstream)
+                downstreamSet.isEmpty() && activated.getAndSet(false)
+            }
+        if (needsDeactivation) {
+            deactivate()
+        }
+    }
+
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<A> =
+        evalScope.getCurrentValue(this)
+}
+
+internal fun <A> InputNode<A>.activated() = TFlowCheap { downstream ->
+    val input = this@activated
+    addDownstreamAndActivateIfNeeded(downstream, evalScope = this)
+    ActivationResult(connection = NodeConnection(input, input), needsEval = hasCurrentValue(input))
+}
+
+internal data object AlwaysNode : PushNode<Unit> {
+
+    override val depthTracker = DepthTracker()
+
+    override suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean = true
+
+    override suspend fun removeDownstream(downstream: Schedulable) {}
+
+    override suspend fun deactivateIfNeeded() {}
+
+    override suspend fun scheduleDeactivationIfNeeded(evalScope: EvalScope) {}
+
+    override suspend fun addDownstream(downstream: Schedulable) {}
+
+    override suspend fun removeDownstreamAndDeactivateIfNeeded(downstream: Schedulable) {}
+
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<Unit> = just(Unit)
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt
new file mode 100644
index 0000000..b6cd906
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/InternalScopes.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.FrpBuildScope
+import com.android.systemui.experimental.frp.FrpStateScope
+import com.android.systemui.experimental.frp.FrpTransactionScope
+import com.android.systemui.experimental.frp.TFlow
+import com.android.systemui.experimental.frp.internal.util.HeteroMap
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.util.Maybe
+
+internal interface InitScope {
+    val networkId: Any
+}
+
+internal interface EvalScope : NetworkScope, DeferScope {
+    val frpScope: FrpTransactionScope
+
+    suspend fun <R> runInTransactionScope(block: suspend FrpTransactionScope.() -> R): R
+}
+
+internal interface StateScope : EvalScope {
+    override val frpScope: FrpStateScope
+
+    suspend fun <R> runInStateScope(block: suspend FrpStateScope.() -> R): R
+
+    val endSignal: TFlow<Any>
+
+    fun childStateScope(newEnd: TFlow<Any>): StateScope
+}
+
+internal interface BuildScope : StateScope {
+    override val frpScope: FrpBuildScope
+
+    suspend fun <R> runInBuildScope(block: suspend FrpBuildScope.() -> R): R
+}
+
+internal interface NetworkScope : InitScope {
+
+    val epoch: Long
+    val network: Network
+
+    val compactor: Scheduler
+    val scheduler: Scheduler
+
+    val transactionStore: HeteroMap
+
+    fun scheduleOutput(output: Output<*>)
+
+    fun scheduleMuxMover(muxMover: MuxDeferredNode<*, *>)
+
+    fun schedule(state: TStateSource<*>)
+
+    suspend fun schedule(node: MuxNode<*, *, *>)
+
+    fun scheduleDeactivation(node: PushNode<*>)
+
+    fun scheduleDeactivation(output: Output<*>)
+}
+
+internal fun <A> NetworkScope.setResult(node: Key<A>, result: A) {
+    transactionStore[node] = result
+}
+
+internal fun <A> NetworkScope.getCurrentValue(key: Key<A>): Maybe<A> = transactionStore[key]
+
+internal fun NetworkScope.hasCurrentValue(key: Key<*>): Boolean = transactionStore.contains(key)
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt
new file mode 100644
index 0000000..e616d62
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Mux.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.ConcurrentNullableHashMap
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.util.Just
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/** Base class for muxing nodes, which have a potentially dynamic collection of upstream nodes. */
+internal sealed class MuxNode<K : Any, V, Output>(val lifecycle: MuxLifecycle<Output>) :
+    PushNode<Output> {
+
+    inline val mutex
+        get() = lifecycle.mutex
+
+    // TODO: preserve insertion order?
+    val upstreamData = ConcurrentNullableHashMap<K, V>()
+    val switchedIn = ConcurrentHashMap<K, MuxBranchNode<K, V>>()
+    val downstreamSet: DownstreamSet = DownstreamSet()
+
+    // TODO: inline DepthTracker? would need to be added to PushNode signature
+    final override val depthTracker = DepthTracker()
+
+    final override suspend fun addDownstream(downstream: Schedulable) {
+        mutex.withLock { addDownstreamLocked(downstream) }
+    }
+
+    /**
+     * Adds a downstream schedulable to this mux node, such that when this mux node emits a value,
+     * it will be scheduled for evaluation within this same transaction.
+     *
+     * Must only be called when [mutex] is acquired.
+     */
+    fun addDownstreamLocked(downstream: Schedulable) {
+        downstreamSet.add(downstream)
+    }
+
+    final override suspend fun removeDownstream(downstream: Schedulable) {
+        // TODO: return boolean?
+        mutex.withLock { downstreamSet.remove(downstream) }
+    }
+
+    final override suspend fun removeDownstreamAndDeactivateIfNeeded(downstream: Schedulable) {
+        val deactivate =
+            mutex.withLock {
+                downstreamSet.remove(downstream)
+                downstreamSet.isEmpty()
+            }
+        if (deactivate) {
+            doDeactivate()
+        }
+    }
+
+    final override suspend fun deactivateIfNeeded() {
+        if (mutex.withLock { downstreamSet.isEmpty() }) {
+            doDeactivate()
+        }
+    }
+
+    /** visit this node from the scheduler (push eval) */
+    abstract suspend fun visit(evalScope: EvalScope)
+
+    /** perform deactivation logic, propagating to all upstream nodes. */
+    protected abstract suspend fun doDeactivate()
+
+    final override suspend fun scheduleDeactivationIfNeeded(evalScope: EvalScope) {
+        if (mutex.withLock { downstreamSet.isEmpty() }) {
+            evalScope.scheduleDeactivation(this)
+        }
+    }
+
+    suspend fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int) {
+        mutex.withLock {
+            if (depthTracker.addDirectUpstream(oldDepth, newDepth)) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun moveIndirectUpstreamToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectRoots: Set<MuxDeferredNode<*, *>>,
+        newDepth: Int,
+    ) {
+        mutex.withLock {
+            if (
+                depthTracker.addDirectUpstream(oldDepth = null, newDepth) or
+                    depthTracker.removeIndirectUpstream(depth = oldIndirectDepth) or
+                    depthTracker.updateIndirectRoots(removals = oldIndirectRoots)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun adjustIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) {
+        mutex.withLock {
+            if (
+                depthTracker.addIndirectUpstream(oldDepth, newDepth) or
+                    depthTracker.updateIndirectRoots(
+                        additions,
+                        removals,
+                        butNot = this as? MuxDeferredNode<*, *>,
+                    )
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun moveDirectUpstreamToIndirect(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        mutex.withLock {
+            if (
+                depthTracker.addIndirectUpstream(oldDepth = null, newDepth) or
+                    depthTracker.removeDirectUpstream(oldDepth) or
+                    depthTracker.updateIndirectRoots(
+                        additions = newIndirectSet,
+                        butNot = this as? MuxDeferredNode<*, *>,
+                    )
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun removeDirectUpstream(scheduler: Scheduler, depth: Int, key: K) {
+        mutex.withLock {
+            switchedIn.remove(key)
+            if (depthTracker.removeDirectUpstream(depth)) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun removeIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+        key: K,
+    ) {
+        mutex.withLock {
+            switchedIn.remove(key)
+            if (
+                depthTracker.removeIndirectUpstream(oldDepth) or
+                    depthTracker.updateIndirectRoots(removals = indirectSet)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun visitCompact(scheduler: Scheduler) = coroutineScope {
+        if (depthTracker.isDirty()) {
+            depthTracker.applyChanges(coroutineScope = this, scheduler, downstreamSet, this@MuxNode)
+        }
+    }
+
+    abstract fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean
+}
+
+/** An input branch of a mux node, associated with a key. */
+internal class MuxBranchNode<K : Any, V>(private val muxNode: MuxNode<K, V, *>, val key: K) :
+    SchedulableNode {
+
+    val schedulable = Schedulable.N(this)
+
+    @Volatile lateinit var upstream: NodeConnection<V>
+
+    override suspend fun schedule(evalScope: EvalScope) {
+        val upstreamResult = upstream.getPushEvent(evalScope)
+        if (upstreamResult is Just) {
+            muxNode.upstreamData[key] = upstreamResult.value
+            evalScope.schedule(muxNode)
+        }
+    }
+
+    override suspend fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int) {
+        muxNode.adjustDirectUpstream(scheduler, oldDepth, newDepth)
+    }
+
+    override suspend fun moveIndirectUpstreamToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+        newDirectDepth: Int,
+    ) {
+        muxNode.moveIndirectUpstreamToDirect(
+            scheduler,
+            oldIndirectDepth,
+            oldIndirectSet,
+            newDirectDepth,
+        )
+    }
+
+    override suspend fun adjustIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.adjustIndirectUpstream(scheduler, oldDepth, newDepth, removals, additions)
+    }
+
+    override suspend fun moveDirectUpstreamToIndirect(
+        scheduler: Scheduler,
+        oldDirectDepth: Int,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.moveDirectUpstreamToIndirect(
+            scheduler,
+            oldDirectDepth,
+            newIndirectDepth,
+            newIndirectSet,
+        )
+    }
+
+    override suspend fun removeDirectUpstream(scheduler: Scheduler, depth: Int) {
+        muxNode.removeDirectUpstream(scheduler, depth, key)
+    }
+
+    override suspend fun removeIndirectUpstream(
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.removeIndirectUpstream(scheduler, depth, indirectSet, key)
+    }
+
+    override fun toString(): String = "MuxBranchNode(key=$key, mux=$muxNode)"
+}
+
+/** Tracks lifecycle of MuxNode in the network. Essentially a mutable ref for MuxLifecycleState. */
+internal class MuxLifecycle<A>(@Volatile var lifecycleState: MuxLifecycleState<A>) : TFlowImpl<A> {
+    val mutex = Mutex()
+
+    override fun toString(): String = "TFlowLifecycle[$hashString][$lifecycleState][$mutex]"
+
+    override suspend fun activate(
+        evalScope: EvalScope,
+        downstream: Schedulable,
+    ): ActivationResult<A>? =
+        mutex.withLock {
+            when (val state = lifecycleState) {
+                is MuxLifecycleState.Dead -> null
+                is MuxLifecycleState.Active -> {
+                    state.node.addDownstreamLocked(downstream)
+                    ActivationResult(
+                        connection = NodeConnection(state.node, state.node),
+                        needsEval = state.node.hasCurrentValueLocked(evalScope.transactionStore),
+                    )
+                }
+                is MuxLifecycleState.Inactive -> {
+                    state.spec
+                        .activate(evalScope, this@MuxLifecycle)
+                        .also { node ->
+                            lifecycleState =
+                                if (node == null) {
+                                    MuxLifecycleState.Dead
+                                } else {
+                                    MuxLifecycleState.Active(node)
+                                }
+                        }
+                        ?.let { node ->
+                            node.addDownstreamLocked(downstream)
+                            ActivationResult(
+                                connection = NodeConnection(node, node),
+                                needsEval = false,
+                            )
+                        }
+                }
+            }
+        }
+}
+
+internal sealed interface MuxLifecycleState<out A> {
+    class Inactive<A>(val spec: MuxActivator<A>) : MuxLifecycleState<A> {
+        override fun toString(): String = "Inactive"
+    }
+
+    class Active<A>(val node: MuxNode<*, *, A>) : MuxLifecycleState<A> {
+        override fun toString(): String = "Active(node=$node)"
+    }
+
+    data object Dead : MuxLifecycleState<Nothing>
+}
+
+internal interface MuxActivator<A> {
+    suspend fun activate(evalScope: EvalScope, lifecycle: MuxLifecycle<A>): MuxNode<*, *, A>?
+}
+
+internal inline fun <A> MuxLifecycle(onSubscribe: MuxActivator<A>): TFlowImpl<A> =
+    MuxLifecycle(MuxLifecycleState.Inactive(onSubscribe))
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt
new file mode 100644
index 0000000..6d43285
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxDeferred.kt
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.internal.util.associateByIndexTo
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.internal.util.mapParallel
+import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Left
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.Right
+import com.android.systemui.experimental.frp.util.These
+import com.android.systemui.experimental.frp.util.flatMap
+import com.android.systemui.experimental.frp.util.getMaybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.maybeThat
+import com.android.systemui.experimental.frp.util.maybeThis
+import com.android.systemui.experimental.frp.util.merge
+import com.android.systemui.experimental.frp.util.orElseGet
+import com.android.systemui.experimental.frp.util.partitionEithers
+import com.android.systemui.experimental.frp.util.these
+import java.util.TreeMap
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+
+internal class MuxDeferredNode<K : Any, V>(
+    lifecycle: MuxLifecycle<Map<K, V>>,
+    val spec: MuxActivator<Map<K, V>>,
+) : MuxNode<K, V, Map<K, V>>(lifecycle), Key<Map<K, V>> {
+
+    val schedulable = Schedulable.M(this)
+
+    @Volatile var patches: NodeConnection<Map<K, Maybe<TFlowImpl<V>>>>? = null
+    @Volatile var patchData: Map<K, Maybe<TFlowImpl<V>>>? = null
+
+    override fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean =
+        transactionStore.contains(this)
+
+    override suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean =
+        mutex.withLock { hasCurrentValueLocked(transactionStore) }
+
+    override suspend fun visit(evalScope: EvalScope) {
+        val result = upstreamData.toMap()
+        upstreamData.clear()
+        val scheduleDownstream = result.isNotEmpty()
+        val compactDownstream = depthTracker.isDirty()
+        if (scheduleDownstream || compactDownstream) {
+            coroutineScope {
+                mutex.withLock {
+                    if (compactDownstream) {
+                        depthTracker.applyChanges(
+                            coroutineScope = this,
+                            evalScope.scheduler,
+                            downstreamSet,
+                            muxNode = this@MuxDeferredNode,
+                        )
+                    }
+                    if (scheduleDownstream) {
+                        evalScope.setResult(this@MuxDeferredNode, result)
+                        if (!scheduleAll(downstreamSet, evalScope)) {
+                            evalScope.scheduleDeactivation(this@MuxDeferredNode)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<Map<K, V>> =
+        evalScope.getCurrentValue(key = this)
+
+    private suspend fun compactIfNeeded(evalScope: EvalScope) {
+        depthTracker.propagateChanges(evalScope.compactor, this)
+    }
+
+    override suspend fun doDeactivate() {
+        // Update lifecycle
+        lifecycle.mutex.withLock {
+            if (lifecycle.lifecycleState !is MuxLifecycleState.Active) return@doDeactivate
+            lifecycle.lifecycleState = MuxLifecycleState.Inactive(spec)
+        }
+        // Process branch nodes
+        coroutineScope {
+            switchedIn.values.forEach { branchNode ->
+                branchNode.upstream.let {
+                    launch { it.removeDownstreamAndDeactivateIfNeeded(branchNode.schedulable) }
+                }
+            }
+        }
+        // Process patch node
+        patches?.removeDownstreamAndDeactivateIfNeeded(schedulable)
+    }
+
+    // MOVE phase
+    //  - concurrent moves may be occurring, but no more evals. all depth recalculations are
+    //    deferred to the end of this phase.
+    suspend fun performMove(evalScope: EvalScope) {
+        val patch = patchData ?: return
+        patchData = null
+
+        // TODO: this logic is very similar to what's in MuxPromptMoving, maybe turn into an inline
+        //  fun?
+
+        // We have a patch, process additions/updates and removals
+        val (adds, removes) =
+            patch
+                .asSequence()
+                .map { (k, newUpstream: Maybe<TFlowImpl<V>>) ->
+                    when (newUpstream) {
+                        is Just -> Left(k to newUpstream.value)
+                        None -> Right(k)
+                    }
+                }
+                .partitionEithers()
+
+        val severed = mutableListOf<NodeConnection<*>>()
+
+        coroutineScope {
+            // remove and sever
+            removes.forEach { k ->
+                switchedIn.remove(k)?.let { branchNode: MuxBranchNode<K, V> ->
+                    val conn = branchNode.upstream
+                    severed.add(conn)
+                    launch { conn.removeDownstream(downstream = branchNode.schedulable) }
+                    depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                }
+            }
+
+            // add or replace
+            adds
+                .mapParallel { (k, newUpstream: TFlowImpl<V>) ->
+                    val branchNode = MuxBranchNode(this@MuxDeferredNode, k)
+                    k to
+                        newUpstream.activate(evalScope, branchNode.schedulable)?.let { (conn, _) ->
+                            branchNode.apply { upstream = conn }
+                        }
+                }
+                .forEach { (k, newBranch: MuxBranchNode<K, V>?) ->
+                    // remove old and sever, if present
+                    switchedIn.remove(k)?.let { branchNode ->
+                        val conn = branchNode.upstream
+                        severed.add(conn)
+                        launch { conn.removeDownstream(downstream = branchNode.schedulable) }
+                        depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                    }
+
+                    // add new
+                    newBranch?.let {
+                        switchedIn[k] = newBranch
+                        val branchDepthTracker = newBranch.upstream.depthTracker
+                        if (branchDepthTracker.snapshotIsDirect) {
+                            depthTracker.addDirectUpstream(
+                                oldDepth = null,
+                                newDepth = branchDepthTracker.snapshotDirectDepth,
+                            )
+                        } else {
+                            depthTracker.addIndirectUpstream(
+                                oldDepth = null,
+                                newDepth = branchDepthTracker.snapshotIndirectDepth,
+                            )
+                            depthTracker.updateIndirectRoots(
+                                additions = branchDepthTracker.snapshotIndirectRoots,
+                                butNot = this@MuxDeferredNode,
+                            )
+                        }
+                    }
+                }
+        }
+
+        coroutineScope {
+            for (severedNode in severed) {
+                launch { severedNode.scheduleDeactivationIfNeeded(evalScope) }
+            }
+        }
+
+        compactIfNeeded(evalScope)
+    }
+
+    suspend fun removeDirectPatchNode(scheduler: Scheduler) {
+        mutex.withLock {
+            if (
+                depthTracker.removeIndirectUpstream(depth = 0) or
+                    depthTracker.setIsIndirectRoot(false)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+            patches = null
+        }
+    }
+
+    suspend fun removeIndirectPatchNode(
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        // indirectly connected patches forward the indirectSet
+        mutex.withLock {
+            if (
+                depthTracker.updateIndirectRoots(removals = indirectSet) or
+                    depthTracker.removeIndirectUpstream(depth)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+            patches = null
+        }
+    }
+
+    suspend fun moveIndirectPatchNodeToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        // directly connected patches are stored as an indirect singleton set of the patchNode
+        mutex.withLock {
+            if (
+                depthTracker.updateIndirectRoots(removals = oldIndirectSet) or
+                    depthTracker.removeIndirectUpstream(oldIndirectDepth) or
+                    depthTracker.setIsIndirectRoot(true)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun moveDirectPatchNodeToIndirect(
+        scheduler: Scheduler,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        // indirectly connected patches forward the indirectSet
+        mutex.withLock {
+            if (
+                depthTracker.setIsIndirectRoot(false) or
+                    depthTracker.updateIndirectRoots(additions = newIndirectSet, butNot = this) or
+                    depthTracker.addIndirectUpstream(oldDepth = null, newDepth = newIndirectDepth)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun adjustIndirectPatchNode(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) {
+        // indirectly connected patches forward the indirectSet
+        mutex.withLock {
+            if (
+                depthTracker.updateIndirectRoots(
+                    additions = additions,
+                    removals = removals,
+                    butNot = this,
+                ) or depthTracker.addIndirectUpstream(oldDepth = oldDepth, newDepth = newDepth)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun scheduleMover(evalScope: EvalScope) {
+        patchData =
+            checkNotNull(patches) { "mux mover scheduled with unset patches upstream node" }
+                .getPushEvent(evalScope)
+                .orElseGet { null }
+        evalScope.scheduleMuxMover(this)
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+internal inline fun <A> switchDeferredImplSingle(
+    crossinline getStorage: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline getPatches: suspend EvalScope.() -> TFlowImpl<TFlowImpl<A>>,
+): TFlowImpl<A> =
+    mapImpl({
+        switchDeferredImpl(
+            getStorage = { mapOf(Unit to getStorage()) },
+            getPatches = { mapImpl(getPatches) { newFlow -> mapOf(Unit to just(newFlow)) } },
+        )
+    }) { map ->
+        map.getValue(Unit)
+    }
+
+internal fun <K : Any, A> switchDeferredImpl(
+    getStorage: suspend EvalScope.() -> Map<K, TFlowImpl<A>>,
+    getPatches: suspend EvalScope.() -> TFlowImpl<Map<K, Maybe<TFlowImpl<A>>>>,
+): TFlowImpl<Map<K, A>> =
+    MuxLifecycle(
+        object : MuxActivator<Map<K, A>> {
+            override suspend fun activate(
+                evalScope: EvalScope,
+                lifecycle: MuxLifecycle<Map<K, A>>,
+            ): MuxNode<*, *, Map<K, A>>? {
+                val storage: Map<K, TFlowImpl<A>> = getStorage(evalScope)
+                // Initialize mux node and switched-in connections.
+                val muxNode =
+                    MuxDeferredNode(lifecycle, this).apply {
+                        storage.mapValuesNotNullParallelTo(switchedIn) { (key, flow) ->
+                            val branchNode = MuxBranchNode(this@apply, key)
+                            flow.activate(evalScope, branchNode.schedulable)?.let {
+                                (conn, needsEval) ->
+                                branchNode
+                                    .apply { upstream = conn }
+                                    .also {
+                                        if (needsEval) {
+                                            val result = conn.getPushEvent(evalScope)
+                                            if (result is Just) {
+                                                upstreamData[key] = result.value
+                                            }
+                                        }
+                                    }
+                            }
+                        }
+                    }
+                // Update depth based on all initial switched-in nodes.
+                muxNode.switchedIn.values.forEach { branch ->
+                    val conn = branch.upstream
+                    if (conn.depthTracker.snapshotIsDirect) {
+                        muxNode.depthTracker.addDirectUpstream(
+                            oldDepth = null,
+                            newDepth = conn.depthTracker.snapshotDirectDepth,
+                        )
+                    } else {
+                        muxNode.depthTracker.addIndirectUpstream(
+                            oldDepth = null,
+                            newDepth = conn.depthTracker.snapshotIndirectDepth,
+                        )
+                        muxNode.depthTracker.updateIndirectRoots(
+                            additions = conn.depthTracker.snapshotIndirectRoots,
+                            butNot = muxNode,
+                        )
+                    }
+                }
+                // We don't have our patches connection established yet, so for now pretend we have
+                // a direct connection to patches. We will update downstream nodes later if this
+                // turns out to be a lie.
+                muxNode.depthTracker.setIsIndirectRoot(true)
+                muxNode.depthTracker.reset()
+
+                // Setup patches connection; deferring allows for a recursive connection, where
+                // muxNode is downstream of itself via patches.
+                var isIndirect = true
+                evalScope.deferAction {
+                    val (patchesConn, needsEval) =
+                        getPatches(evalScope).activate(evalScope, downstream = muxNode.schedulable)
+                            ?: run {
+                                isIndirect = false
+                                // Turns out we can't connect to patches, so update our depth and
+                                // propagate
+                                muxNode.mutex.withLock {
+                                    if (muxNode.depthTracker.setIsIndirectRoot(false)) {
+                                        muxNode.depthTracker.schedule(evalScope.scheduler, muxNode)
+                                    }
+                                }
+                                return@deferAction
+                            }
+                    muxNode.patches = patchesConn
+
+                    if (!patchesConn.schedulerUpstream.depthTracker.snapshotIsDirect) {
+                        // Turns out patches is indirect, so we are not a root. Update depth and
+                        // propagate.
+                        muxNode.mutex.withLock {
+                            if (
+                                muxNode.depthTracker.setIsIndirectRoot(false) or
+                                    muxNode.depthTracker.addIndirectUpstream(
+                                        oldDepth = null,
+                                        newDepth = patchesConn.depthTracker.snapshotIndirectDepth,
+                                    ) or
+                                    muxNode.depthTracker.updateIndirectRoots(
+                                        additions = patchesConn.depthTracker.snapshotIndirectRoots
+                                    )
+                            ) {
+                                muxNode.depthTracker.schedule(evalScope.scheduler, muxNode)
+                            }
+                        }
+                    }
+                    // Schedule mover to process patch emission at the end of this transaction, if
+                    // needed.
+                    if (needsEval) {
+                        val result = patchesConn.getPushEvent(evalScope)
+                        if (result is Just) {
+                            muxNode.patchData = result.value
+                            evalScope.scheduleMuxMover(muxNode)
+                        }
+                    }
+                }
+
+                // Schedule for evaluation if any switched-in nodes have already emitted within
+                // this transaction.
+                if (muxNode.upstreamData.isNotEmpty()) {
+                    evalScope.schedule(muxNode)
+                }
+                return muxNode.takeUnless { muxNode.switchedIn.isEmpty() && !isIndirect }
+            }
+        }
+    )
+
+internal inline fun <A> mergeNodes(
+    crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline getOther: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline f: suspend EvalScope.(A, A) -> A,
+): TFlowImpl<A> {
+    val merged =
+        mapImpl({ mergeNodes(getPulse, getOther) }) { these ->
+            these.merge { thiz, that -> f(thiz, that) }
+        }
+    return merged.cached()
+}
+
+internal inline fun <A, B> mergeNodes(
+    crossinline getPulse: suspend EvalScope.() -> TFlowImpl<A>,
+    crossinline getOther: suspend EvalScope.() -> TFlowImpl<B>,
+): TFlowImpl<These<A, B>> {
+    val storage =
+        mapOf(
+            0 to mapImpl(getPulse) { These.thiz<A, B>(it) },
+            1 to mapImpl(getOther) { These.that(it) },
+        )
+    val switchNode = switchDeferredImpl(getStorage = { storage }, getPatches = { neverImpl })
+    val merged =
+        mapImpl({ switchNode }) { mergeResults ->
+            val first = mergeResults.getMaybe(0).flatMap { it.maybeThis() }
+            val second = mergeResults.getMaybe(1).flatMap { it.maybeThat() }
+            these(first, second).orElseGet { error("unexpected missing merge result") }
+        }
+    return merged.cached()
+}
+
+internal inline fun <A> mergeNodes(
+    crossinline getPulses: suspend EvalScope.() -> Iterable<TFlowImpl<A>>
+): TFlowImpl<List<A>> {
+    val switchNode =
+        switchDeferredImpl(
+            getStorage = { getPulses().associateByIndexTo(TreeMap()) },
+            getPatches = { neverImpl },
+        )
+    val merged = mapImpl({ switchNode }) { mergeResults -> mergeResults.values.toList() }
+    return merged.cached()
+}
+
+internal inline fun <A> mergeNodesLeft(
+    crossinline getPulses: suspend EvalScope.() -> Iterable<TFlowImpl<A>>
+): TFlowImpl<A> {
+    val switchNode =
+        switchDeferredImpl(
+            getStorage = { getPulses().associateByIndexTo(TreeMap()) },
+            getPatches = { neverImpl },
+        )
+    val merged =
+        mapImpl({ switchNode }) { mergeResults: Map<Int, A> -> mergeResults.values.first() }
+    return merged.cached()
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt
new file mode 100644
index 0000000..ea0c150
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/MuxPrompt.kt
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.internal.util.launchImmediate
+import com.android.systemui.experimental.frp.internal.util.mapParallel
+import com.android.systemui.experimental.frp.internal.util.mapValuesNotNullParallelTo
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Left
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.Right
+import com.android.systemui.experimental.frp.util.filterJust
+import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.experimental.frp.util.partitionEithers
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.withLock
+
+internal class MuxPromptMovingNode<K : Any, V>(
+    lifecycle: MuxLifecycle<Pair<Map<K, V>, Map<K, PullNode<V>>?>>,
+    private val spec: MuxActivator<Pair<Map<K, V>, Map<K, PullNode<V>>?>>,
+) :
+    MuxNode<K, V, Pair<Map<K, V>, Map<K, PullNode<V>>?>>(lifecycle),
+    Key<Pair<Map<K, V>, Map<K, PullNode<V>>?>> {
+
+    @Volatile var patchData: Map<K, Maybe<TFlowImpl<V>>>? = null
+    @Volatile var patches: MuxPromptPatchNode<K, V>? = null
+
+    @Volatile private var reEval: Pair<Map<K, V>, Map<K, PullNode<V>>?>? = null
+
+    override fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean =
+        transactionStore.contains(this)
+
+    override suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean =
+        mutex.withLock { hasCurrentValueLocked(transactionStore) }
+
+    override suspend fun visit(evalScope: EvalScope) {
+        val preSwitchResults: Map<K, V> = upstreamData.toMap()
+        upstreamData.clear()
+
+        val patch: Map<K, Maybe<TFlowImpl<V>>>? = patchData
+        patchData = null
+
+        val (reschedule, evalResult) =
+            reEval?.let { false to it }
+                ?: if (preSwitchResults.isNotEmpty() || patch?.isNotEmpty() == true) {
+                    doEval(preSwitchResults, patch, evalScope)
+                } else {
+                    false to null
+                }
+        reEval = null
+
+        if (reschedule || depthTracker.dirty_depthIncreased()) {
+            reEval = evalResult
+            // Can't schedule downstream yet, need to compact first
+            if (depthTracker.dirty_depthIncreased()) {
+                depthTracker.schedule(evalScope.compactor, node = this)
+            }
+            evalScope.schedule(this)
+        } else {
+            val compactDownstream = depthTracker.isDirty()
+            if (evalResult != null || compactDownstream) {
+                coroutineScope {
+                    mutex.withLock {
+                        if (compactDownstream) {
+                            adjustDownstreamDepths(evalScope, coroutineScope = this)
+                        }
+                        if (evalResult != null) {
+                            evalScope.setResult(this@MuxPromptMovingNode, evalResult)
+                            if (!scheduleAll(downstreamSet, evalScope)) {
+                                evalScope.scheduleDeactivation(this@MuxPromptMovingNode)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private suspend fun doEval(
+        preSwitchResults: Map<K, V>,
+        patch: Map<K, Maybe<TFlowImpl<V>>>?,
+        evalScope: EvalScope,
+    ): Pair<Boolean, Pair<Map<K, V>, Map<K, PullNode<V>>?>?> {
+        val newlySwitchedIn: Map<K, PullNode<V>>? =
+            patch?.let {
+                // We have a patch, process additions/updates and removals
+                val (adds, removes) =
+                    patch
+                        .asSequence()
+                        .map { (k, newUpstream: Maybe<TFlowImpl<V>>) ->
+                            when (newUpstream) {
+                                is Just -> Left(k to newUpstream.value)
+                                None -> Right(k)
+                            }
+                        }
+                        .partitionEithers()
+
+                val additionsAndUpdates = mutableMapOf<K, PullNode<V>>()
+                val severed = mutableListOf<NodeConnection<*>>()
+
+                coroutineScope {
+                    // remove and sever
+                    removes.forEach { k ->
+                        switchedIn.remove(k)?.let { branchNode: MuxBranchNode<K, V> ->
+                            val conn: NodeConnection<V> = branchNode.upstream
+                            severed.add(conn)
+                            launchImmediate {
+                                conn.removeDownstream(downstream = branchNode.schedulable)
+                            }
+                            depthTracker.removeDirectUpstream(conn.depthTracker.snapshotDirectDepth)
+                        }
+                    }
+
+                    // add or replace
+                    adds
+                        .mapParallel { (k, newUpstream: TFlowImpl<V>) ->
+                            val branchNode = MuxBranchNode(this@MuxPromptMovingNode, k)
+                            k to
+                                newUpstream.activate(evalScope, branchNode.schedulable)?.let {
+                                    (conn, _) ->
+                                    branchNode.apply { upstream = conn }
+                                }
+                        }
+                        .forEach { (k, newBranch: MuxBranchNode<K, V>?) ->
+                            // remove old and sever, if present
+                            switchedIn.remove(k)?.let { oldBranch: MuxBranchNode<K, V> ->
+                                val conn: NodeConnection<V> = oldBranch.upstream
+                                severed.add(conn)
+                                launchImmediate {
+                                    conn.removeDownstream(downstream = oldBranch.schedulable)
+                                }
+                                depthTracker.removeDirectUpstream(
+                                    conn.depthTracker.snapshotDirectDepth
+                                )
+                            }
+
+                            // add new
+                            newBranch?.let {
+                                switchedIn[k] = newBranch
+                                additionsAndUpdates[k] = newBranch.upstream.directUpstream
+                                val branchDepthTracker = newBranch.upstream.depthTracker
+                                if (branchDepthTracker.snapshotIsDirect) {
+                                    depthTracker.addDirectUpstream(
+                                        oldDepth = null,
+                                        newDepth = branchDepthTracker.snapshotDirectDepth,
+                                    )
+                                } else {
+                                    depthTracker.addIndirectUpstream(
+                                        oldDepth = null,
+                                        newDepth = branchDepthTracker.snapshotIndirectDepth,
+                                    )
+                                    depthTracker.updateIndirectRoots(
+                                        additions = branchDepthTracker.snapshotIndirectRoots,
+                                        butNot = null,
+                                    )
+                                }
+                            }
+                        }
+                }
+
+                coroutineScope {
+                    for (severedNode in severed) {
+                        launch { severedNode.scheduleDeactivationIfNeeded(evalScope) }
+                    }
+                }
+
+                additionsAndUpdates.takeIf { it.isNotEmpty() }
+            }
+
+        return if (preSwitchResults.isNotEmpty() || newlySwitchedIn != null) {
+            (newlySwitchedIn != null) to (preSwitchResults to newlySwitchedIn)
+        } else {
+            false to null
+        }
+    }
+
+    private suspend fun adjustDownstreamDepths(
+        evalScope: EvalScope,
+        coroutineScope: CoroutineScope,
+    ) {
+        if (depthTracker.dirty_depthIncreased()) {
+            // schedule downstream nodes on the compaction scheduler; this scheduler is drained at
+            // the end of this eval depth, so that all depth increases are applied before we advance
+            // the eval step
+            depthTracker.schedule(evalScope.compactor, node = this@MuxPromptMovingNode)
+        } else if (depthTracker.isDirty()) {
+            // schedule downstream nodes on the eval scheduler; this is more efficient and is only
+            // safe if the depth hasn't increased
+            depthTracker.applyChanges(
+                coroutineScope,
+                evalScope.scheduler,
+                downstreamSet,
+                muxNode = this@MuxPromptMovingNode,
+            )
+        }
+    }
+
+    override suspend fun getPushEvent(
+        evalScope: EvalScope
+    ): Maybe<Pair<Map<K, V>, Map<K, PullNode<V>>?>> = evalScope.getCurrentValue(key = this)
+
+    override suspend fun doDeactivate() {
+        // Update lifecycle
+        lifecycle.mutex.withLock {
+            if (lifecycle.lifecycleState !is MuxLifecycleState.Active) return@doDeactivate
+            lifecycle.lifecycleState = MuxLifecycleState.Inactive(spec)
+        }
+        // Process branch nodes
+        switchedIn.values.forEach { branchNode ->
+            branchNode.upstream.removeDownstreamAndDeactivateIfNeeded(
+                downstream = branchNode.schedulable
+            )
+        }
+        // Process patch node
+        patches?.let { patches ->
+            patches.upstream.removeDownstreamAndDeactivateIfNeeded(downstream = patches.schedulable)
+        }
+    }
+
+    suspend fun removeIndirectPatchNode(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        mutex.withLock {
+            patches = null
+            if (
+                depthTracker.removeIndirectUpstream(oldDepth) or
+                    depthTracker.updateIndirectRoots(removals = indirectSet)
+            ) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+
+    suspend fun removeDirectPatchNode(scheduler: Scheduler, depth: Int) {
+        mutex.withLock {
+            patches = null
+            if (depthTracker.removeDirectUpstream(depth)) {
+                depthTracker.schedule(scheduler, this)
+            }
+        }
+    }
+}
+
+internal class MuxPromptEvalNode<K, V>(
+    private val movingNode: PullNode<Pair<Map<K, V>, Map<K, PullNode<V>>?>>
+) : PullNode<Map<K, V>> {
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<Map<K, V>> =
+        movingNode.getPushEvent(evalScope).map { (preSwitchResults, newlySwitchedIn) ->
+            coroutineScope {
+                newlySwitchedIn
+                    ?.map { (k, v) -> async { v.getPushEvent(evalScope).map { k to it } } }
+                    ?.awaitAll()
+                    ?.asSequence()
+                    ?.filterJust()
+                    ?.toMap(preSwitchResults.toMutableMap()) ?: preSwitchResults
+            }
+        }
+}
+
+// TODO: inner class?
+internal class MuxPromptPatchNode<K : Any, V>(private val muxNode: MuxPromptMovingNode<K, V>) :
+    SchedulableNode {
+
+    val schedulable = Schedulable.N(this)
+
+    lateinit var upstream: NodeConnection<Map<K, Maybe<TFlowImpl<V>>>>
+
+    override suspend fun schedule(evalScope: EvalScope) {
+        val upstreamResult = upstream.getPushEvent(evalScope)
+        if (upstreamResult is Just) {
+            muxNode.patchData = upstreamResult.value
+            evalScope.schedule(muxNode)
+        }
+    }
+
+    override suspend fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int) {
+        muxNode.adjustDirectUpstream(scheduler, oldDepth, newDepth)
+    }
+
+    override suspend fun moveIndirectUpstreamToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+        newDirectDepth: Int,
+    ) {
+        muxNode.moveIndirectUpstreamToDirect(
+            scheduler,
+            oldIndirectDepth,
+            oldIndirectSet,
+            newDirectDepth,
+        )
+    }
+
+    override suspend fun adjustIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.adjustIndirectUpstream(scheduler, oldDepth, newDepth, removals, additions)
+    }
+
+    override suspend fun moveDirectUpstreamToIndirect(
+        scheduler: Scheduler,
+        oldDirectDepth: Int,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.moveDirectUpstreamToIndirect(
+            scheduler,
+            oldDirectDepth,
+            newIndirectDepth,
+            newIndirectSet,
+        )
+    }
+
+    override suspend fun removeDirectUpstream(scheduler: Scheduler, depth: Int) {
+        muxNode.removeDirectPatchNode(scheduler, depth)
+    }
+
+    override suspend fun removeIndirectUpstream(
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    ) {
+        muxNode.removeIndirectPatchNode(scheduler, depth, indirectSet)
+    }
+}
+
+internal fun <K : Any, A> switchPromptImpl(
+    getStorage: suspend EvalScope.() -> Map<K, TFlowImpl<A>>,
+    getPatches: suspend EvalScope.() -> TFlowImpl<Map<K, Maybe<TFlowImpl<A>>>>,
+): TFlowImpl<Map<K, A>> {
+    val moving =
+        MuxLifecycle(
+            object : MuxActivator<Pair<Map<K, A>, Map<K, PullNode<A>>?>> {
+                override suspend fun activate(
+                    evalScope: EvalScope,
+                    lifecycle: MuxLifecycle<Pair<Map<K, A>, Map<K, PullNode<A>>?>>,
+                ): MuxNode<*, *, Pair<Map<K, A>, Map<K, PullNode<A>>?>>? {
+                    val storage: Map<K, TFlowImpl<A>> = getStorage(evalScope)
+                    // Initialize mux node and switched-in connections.
+                    val movingNode =
+                        MuxPromptMovingNode(lifecycle, this).apply {
+                            coroutineScope {
+                                launch {
+                                    storage.mapValuesNotNullParallelTo(switchedIn) { (key, flow) ->
+                                        val branchNode = MuxBranchNode(this@apply, key)
+                                        flow
+                                            .activate(
+                                                evalScope = evalScope,
+                                                downstream = branchNode.schedulable,
+                                            )
+                                            ?.let { (conn, needsEval) ->
+                                                branchNode
+                                                    .apply { upstream = conn }
+                                                    .also {
+                                                        if (needsEval) {
+                                                            val result =
+                                                                conn.getPushEvent(evalScope)
+                                                            if (result is Just) {
+                                                                upstreamData[key] = result.value
+                                                            }
+                                                        }
+                                                    }
+                                            }
+                                    }
+                                }
+                                // Setup patches connection
+                                val patchNode = MuxPromptPatchNode(this@apply)
+                                getPatches(evalScope)
+                                    .activate(
+                                        evalScope = evalScope,
+                                        downstream = patchNode.schedulable,
+                                    )
+                                    ?.let { (conn, needsEval) ->
+                                        patchNode.upstream = conn
+                                        patches = patchNode
+
+                                        if (needsEval) {
+                                            val result = conn.getPushEvent(evalScope)
+                                            if (result is Just) {
+                                                patchData = result.value
+                                            }
+                                        }
+                                    }
+                            }
+                        }
+                    // Update depth based on all initial switched-in nodes.
+                    movingNode.switchedIn.values.forEach { branch ->
+                        val conn = branch.upstream
+                        if (conn.depthTracker.snapshotIsDirect) {
+                            movingNode.depthTracker.addDirectUpstream(
+                                oldDepth = null,
+                                newDepth = conn.depthTracker.snapshotDirectDepth,
+                            )
+                        } else {
+                            movingNode.depthTracker.addIndirectUpstream(
+                                oldDepth = null,
+                                newDepth = conn.depthTracker.snapshotIndirectDepth,
+                            )
+                            movingNode.depthTracker.updateIndirectRoots(
+                                additions = conn.depthTracker.snapshotIndirectRoots,
+                                butNot = null,
+                            )
+                        }
+                    }
+                    // Update depth based on patches node.
+                    movingNode.patches?.upstream?.let { conn ->
+                        if (conn.depthTracker.snapshotIsDirect) {
+                            movingNode.depthTracker.addDirectUpstream(
+                                oldDepth = null,
+                                newDepth = conn.depthTracker.snapshotDirectDepth,
+                            )
+                        } else {
+                            movingNode.depthTracker.addIndirectUpstream(
+                                oldDepth = null,
+                                newDepth = conn.depthTracker.snapshotIndirectDepth,
+                            )
+                            movingNode.depthTracker.updateIndirectRoots(
+                                additions = conn.depthTracker.snapshotIndirectRoots,
+                                butNot = null,
+                            )
+                        }
+                    }
+                    movingNode.depthTracker.reset()
+
+                    // Schedule for evaluation if any switched-in nodes or the patches node have
+                    // already emitted within this transaction.
+                    if (movingNode.patchData != null || movingNode.upstreamData.isNotEmpty()) {
+                        evalScope.schedule(movingNode)
+                    }
+
+                    return movingNode.takeUnless { it.patches == null && it.switchedIn.isEmpty() }
+                }
+            }
+        )
+
+    val eval = TFlowCheap { downstream ->
+        moving.activate(evalScope = this, downstream)?.let { (connection, needsEval) ->
+            val evalNode = MuxPromptEvalNode(connection.directUpstream)
+            ActivationResult(
+                connection = NodeConnection(evalNode, connection.schedulerUpstream),
+                needsEval = needsEval,
+            )
+        }
+    }
+    return eval.cached()
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt
new file mode 100644
index 0000000..b5ffe75
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Network.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.TState
+import com.android.systemui.experimental.frp.internal.util.HeteroMap
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.none
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedDeque
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.atomic.AtomicLong
+import kotlin.coroutines.ContinuationInterceptor
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.yield
+
+private val nextNetworkId = AtomicLong()
+
+internal class Network(val coroutineScope: CoroutineScope) : NetworkScope {
+
+    override val networkId: Any = nextNetworkId.getAndIncrement()
+
+    @Volatile
+    override var epoch: Long = 0L
+        private set
+
+    override val network
+        get() = this
+
+    override val compactor = SchedulerImpl()
+    override val scheduler = SchedulerImpl()
+    override val transactionStore = HeteroMap()
+
+    private val stateWrites = ConcurrentLinkedQueue<TStateSource<*>>()
+    private val outputsByDispatcher =
+        ConcurrentHashMap<ContinuationInterceptor, ConcurrentLinkedQueue<Output<*>>>()
+    private val muxMovers = ConcurrentLinkedQueue<MuxDeferredNode<*, *>>()
+    private val deactivations = ConcurrentLinkedDeque<PushNode<*>>()
+    private val outputDeactivations = ConcurrentLinkedQueue<Output<*>>()
+    private val transactionMutex = Mutex()
+    private val inputScheduleChan = Channel<ScheduledAction<*>>()
+
+    override fun scheduleOutput(output: Output<*>) {
+        val continuationInterceptor =
+            output.context[ContinuationInterceptor] ?: Dispatchers.Unconfined
+        outputsByDispatcher
+            .computeIfAbsent(continuationInterceptor) { ConcurrentLinkedQueue() }
+            .add(output)
+    }
+
+    override fun scheduleMuxMover(muxMover: MuxDeferredNode<*, *>) {
+        muxMovers.add(muxMover)
+    }
+
+    override fun schedule(state: TStateSource<*>) {
+        stateWrites.add(state)
+    }
+
+    // TODO: weird that we have this *and* scheduler exposed
+    override suspend fun schedule(node: MuxNode<*, *, *>) {
+        scheduler.schedule(node.depthTracker.dirty_directDepth, node)
+    }
+
+    override fun scheduleDeactivation(node: PushNode<*>) {
+        deactivations.add(node)
+    }
+
+    override fun scheduleDeactivation(output: Output<*>) {
+        outputDeactivations.add(output)
+    }
+
+    /** Listens for external events and starts FRP transactions. Runs forever. */
+    suspend fun runInputScheduler() = coroutineScope {
+        launch { scheduler.activate() }
+        launch { compactor.activate() }
+        val actions = mutableListOf<ScheduledAction<*>>()
+        for (first in inputScheduleChan) {
+            // Drain and conflate all transaction requests into a single transaction
+            actions.add(first)
+            while (true) {
+                yield()
+                val func = inputScheduleChan.tryReceive().getOrNull() ?: break
+                actions.add(func)
+            }
+            transactionMutex.withLock {
+                // Run all actions
+                evalScope {
+                    for (action in actions) {
+                        launch { action.started(evalScope = this@evalScope) }
+                    }
+                }
+                // Step through the network
+                doTransaction()
+                // Signal completion
+                while (actions.isNotEmpty()) {
+                    actions.removeLast().completed()
+                }
+            }
+        }
+    }
+
+    /** Evaluates [block] inside of a new transaction when the network is ready. */
+    fun <R> transaction(block: suspend EvalScope.() -> R): Deferred<R> =
+        CompletableDeferred<R>(parent = coroutineScope.coroutineContext.job).also { onResult ->
+            val job =
+                coroutineScope.launch {
+                    inputScheduleChan.send(
+                        ScheduledAction(onStartTransaction = block, onResult = onResult)
+                    )
+                }
+            onResult.invokeOnCompletion { job.cancel() }
+        }
+
+    suspend fun <R> evalScope(block: suspend EvalScope.() -> R): R = deferScope {
+        block(EvalScopeImpl(this@Network, this))
+    }
+
+    /** Performs a transactional update of the FRP network. */
+    private suspend fun doTransaction() {
+        // Traverse network, then run outputs
+        do {
+            scheduler.drainEval(this)
+        } while (evalScope { evalOutputs(this) })
+        // Update states
+        evalScope { evalStateWriters(this) }
+        transactionStore.clear()
+        // Perform deferred switches
+        evalScope { evalMuxMovers(this) }
+        // Compact depths
+        scheduler.drainCompact()
+        compactor.drainCompact()
+        // Deactivate nodes with no downstream
+        evalDeactivations()
+        epoch++
+    }
+
+    /** Invokes all [Output]s that have received data within this transaction. */
+    private suspend fun evalOutputs(evalScope: EvalScope): Boolean {
+        // Outputs can enqueue other outputs, so we need two loops
+        if (outputsByDispatcher.isEmpty()) return false
+        while (outputsByDispatcher.isNotEmpty()) {
+            var launchedAny = false
+            coroutineScope {
+                for ((key, outputs) in outputsByDispatcher) {
+                    if (outputs.isNotEmpty()) {
+                        launchedAny = true
+                        launch(key) {
+                            while (outputs.isNotEmpty()) {
+                                val output = outputs.remove()
+                                launch { output.visit(evalScope) }
+                            }
+                        }
+                    }
+                }
+            }
+            if (!launchedAny) outputsByDispatcher.clear()
+        }
+        return true
+    }
+
+    private suspend fun evalMuxMovers(evalScope: EvalScope) {
+        while (muxMovers.isNotEmpty()) {
+            coroutineScope {
+                val toMove = muxMovers.remove()
+                launch { toMove.performMove(evalScope) }
+            }
+        }
+    }
+
+    /** Updates all [TState]es that have changed within this transaction. */
+    private suspend fun evalStateWriters(evalScope: EvalScope) {
+        coroutineScope {
+            while (stateWrites.isNotEmpty()) {
+                val latch = stateWrites.remove()
+                launch { latch.updateState(evalScope) }
+            }
+        }
+    }
+
+    private suspend fun evalDeactivations() {
+        coroutineScope {
+            launch {
+                while (deactivations.isNotEmpty()) {
+                    // traverse in reverse order
+                    //   - deactivations are added in depth-order during the node traversal phase
+                    //   - perform deactivations in reverse order, in case later ones propagate to
+                    //     earlier ones
+                    val toDeactivate = deactivations.removeLast()
+                    launch { toDeactivate.deactivateIfNeeded() }
+                }
+            }
+            while (outputDeactivations.isNotEmpty()) {
+                val toDeactivate = outputDeactivations.remove()
+                launch {
+                    toDeactivate.upstream?.removeDownstreamAndDeactivateIfNeeded(
+                        downstream = toDeactivate.schedulable
+                    )
+                }
+            }
+        }
+        check(deactivations.isEmpty()) { "unexpected lingering deactivations" }
+        check(outputDeactivations.isEmpty()) { "unexpected lingering output deactivations" }
+    }
+}
+
+internal class ScheduledAction<T>(
+    private val onResult: CompletableDeferred<T>? = null,
+    private val onStartTransaction: suspend EvalScope.() -> T,
+) {
+    private var result: Maybe<T> = none
+
+    suspend fun started(evalScope: EvalScope) {
+        result = just(onStartTransaction(evalScope))
+    }
+
+    fun completed() {
+        if (onResult != null) {
+            when (val result = result) {
+                is Just -> onResult.complete(result.value)
+                else -> {}
+            }
+        }
+        result = none
+    }
+}
+
+internal typealias TransactionStore = HeteroMap
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt
new file mode 100644
index 0000000..6375918
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NoScope.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.FrpScope
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.coroutines.startCoroutine
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.completeWith
+import kotlinx.coroutines.job
+
+internal object NoScope {
+    private object FrpScopeImpl : FrpScope
+
+    suspend fun <R> runInFrpScope(block: suspend FrpScope.() -> R): R {
+        val complete = CompletableDeferred<R>(coroutineContext.job)
+        block.startCoroutine(
+            FrpScopeImpl,
+            object : Continuation<R> {
+                override val context: CoroutineContext
+                    get() = EmptyCoroutineContext
+
+                override fun resumeWith(result: Result<R>) {
+                    complete.completeWith(result)
+                }
+            },
+        )
+        return complete.await()
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt
new file mode 100644
index 0000000..e7f76a0
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/NodeTypes.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.util.Maybe
+
+/*
+Dmux
+Muxes + Branch
+*/
+internal sealed interface SchedulableNode {
+    /** schedule this node w/ given NodeEvalScope */
+    suspend fun schedule(evalScope: EvalScope)
+
+    suspend fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int)
+
+    suspend fun moveIndirectUpstreamToDirect(
+        scheduler: Scheduler,
+        oldIndirectDepth: Int,
+        oldIndirectSet: Set<MuxDeferredNode<*, *>>,
+        newDirectDepth: Int,
+    )
+
+    suspend fun adjustIndirectUpstream(
+        scheduler: Scheduler,
+        oldDepth: Int,
+        newDepth: Int,
+        removals: Set<MuxDeferredNode<*, *>>,
+        additions: Set<MuxDeferredNode<*, *>>,
+    )
+
+    suspend fun moveDirectUpstreamToIndirect(
+        scheduler: Scheduler,
+        oldDirectDepth: Int,
+        newIndirectDepth: Int,
+        newIndirectSet: Set<MuxDeferredNode<*, *>>,
+    )
+
+    suspend fun removeIndirectUpstream(
+        scheduler: Scheduler,
+        depth: Int,
+        indirectSet: Set<MuxDeferredNode<*, *>>,
+    )
+
+    suspend fun removeDirectUpstream(scheduler: Scheduler, depth: Int)
+}
+
+/*
+All but Dmux
+ */
+internal sealed interface PullNode<out A> {
+    /**
+     * query the result of this node within the current transaction. if the node is cached, this
+     * will read from the cache, otherwise it will perform a full evaluation, even if invoked
+     * multiple times within a transaction.
+     */
+    suspend fun getPushEvent(evalScope: EvalScope): Maybe<A>
+}
+
+/*
+Muxes + DmuxBranch
+ */
+internal sealed interface PushNode<A> : PullNode<A> {
+
+    suspend fun hasCurrentValue(transactionStore: TransactionStore): Boolean
+
+    val depthTracker: DepthTracker
+
+    suspend fun removeDownstream(downstream: Schedulable)
+
+    /** called during cleanup phase */
+    suspend fun deactivateIfNeeded()
+
+    /** called from mux nodes after severs */
+    suspend fun scheduleDeactivationIfNeeded(evalScope: EvalScope)
+
+    suspend fun addDownstream(downstream: Schedulable)
+
+    suspend fun removeDownstreamAndDeactivateIfNeeded(downstream: Schedulable)
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt
new file mode 100644
index 0000000..e60dcca
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Output.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.util.Just
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+internal class Output<A>(
+    val context: CoroutineContext = EmptyCoroutineContext,
+    val onDeath: suspend () -> Unit = {},
+    val onEmit: suspend EvalScope.(A) -> Unit,
+) {
+
+    val schedulable = Schedulable.O(this)
+
+    @Volatile var upstream: NodeConnection<A>? = null
+    @Volatile var result: Any? = NoResult
+
+    private object NoResult
+
+    // invoked by network
+    suspend fun visit(evalScope: EvalScope) {
+        val upstreamResult = result
+        check(upstreamResult !== NoResult) { "output visited with null upstream result" }
+        result = null
+        @Suppress("UNCHECKED_CAST") evalScope.onEmit(upstreamResult as A)
+    }
+
+    suspend fun kill() {
+        onDeath()
+    }
+
+    suspend fun schedule(evalScope: EvalScope) {
+        val upstreamResult =
+            checkNotNull(upstream) { "output scheduled with null upstream" }.getPushEvent(evalScope)
+        if (upstreamResult is Just) {
+            result = upstreamResult.value
+            evalScope.scheduleOutput(this)
+        }
+    }
+}
+
+internal inline fun OneShot(crossinline onEmit: suspend EvalScope.() -> Unit): Output<Unit> =
+    Output<Unit>(onEmit = { onEmit() }).apply { result = Unit }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt
new file mode 100644
index 0000000..b4656e0
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/PullNodes.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.map
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+
+internal val neverImpl: TFlowImpl<Nothing> = TFlowCheap { null }
+
+internal class MapNode<A, B>(val upstream: PullNode<A>, val transform: suspend EvalScope.(A) -> B) :
+    PullNode<B> {
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<B> =
+        upstream.getPushEvent(evalScope).map { evalScope.transform(it) }
+}
+
+internal inline fun <A, B> mapImpl(
+    crossinline upstream: suspend EvalScope.() -> TFlowImpl<A>,
+    noinline transform: suspend EvalScope.(A) -> B,
+): TFlowImpl<B> = TFlowCheap { downstream ->
+    upstream().activate(evalScope = this, downstream)?.let { (connection, needsEval) ->
+        ActivationResult(
+            connection =
+                NodeConnection(
+                    directUpstream = MapNode(connection.directUpstream, transform),
+                    schedulerUpstream = connection.schedulerUpstream,
+                ),
+            needsEval = needsEval,
+        )
+    }
+}
+
+internal class CachedNode<A>(val key: Key<Deferred<Maybe<A>>>, val upstream: PullNode<A>) :
+    PullNode<A> {
+    override suspend fun getPushEvent(evalScope: EvalScope): Maybe<A> {
+        val deferred =
+            evalScope.transactionStore.getOrPut(key) {
+                evalScope.deferAsync(CoroutineStart.LAZY) { upstream.getPushEvent(evalScope) }
+            }
+        return deferred.await()
+    }
+}
+
+internal fun <A> TFlowImpl<A>.cached(): TFlowImpl<A> {
+    val key = object : Key<Deferred<Maybe<A>>> {}
+    return TFlowCheap {
+        activate(this, it)?.let { (connection, needsEval) ->
+            ActivationResult(
+                connection =
+                    NodeConnection(
+                        directUpstream = CachedNode(key, connection.directUpstream),
+                        schedulerUpstream = connection.schedulerUpstream,
+                    ),
+                needsEval = needsEval,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt
new file mode 100644
index 0000000..4fef865
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/Scheduler.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.experimental.frp.internal
+
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.PriorityBlockingQueue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+internal interface Scheduler {
+    suspend fun schedule(depth: Int, node: MuxNode<*, *, *>)
+
+    suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>)
+}
+
+internal class SchedulerImpl : Scheduler {
+    val enqueued = ConcurrentHashMap<MuxNode<*, *, *>, Any>()
+    val scheduledQ = PriorityBlockingQueue<Pair<Int, MuxNode<*, *, *>>>(16, compareBy { it.first })
+    val chan = Channel<Pair<Int, MuxNode<*, *, *>>>(Channel.UNLIMITED)
+
+    override suspend fun schedule(depth: Int, node: MuxNode<*, *, *>) {
+        if (enqueued.putIfAbsent(node, node) == null) {
+            chan.send(Pair(depth, node))
+        }
+    }
+
+    override suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) {
+        schedule(Int.MIN_VALUE + indirectDepth, node)
+    }
+
+    suspend fun activate() {
+        for (nodeSchedule in chan) {
+            scheduledQ.add(nodeSchedule)
+            drainChan()
+        }
+    }
+
+    internal suspend fun drainEval(network: Network) {
+        drain { runStep ->
+            runStep { muxNode -> network.evalScope { muxNode.visit(this) } }
+            // If any visited MuxPromptNodes had their depths increased, eagerly propagate those
+            // depth
+            // changes now before performing further network evaluation.
+            network.compactor.drainCompact()
+        }
+    }
+
+    internal suspend fun drainCompact() {
+        drain { runStep -> runStep { muxNode -> muxNode.visitCompact(scheduler = this) } }
+    }
+
+    private suspend inline fun drain(
+        crossinline onStep:
+            suspend (runStep: suspend (visit: suspend (MuxNode<*, *, *>) -> Unit) -> Unit) -> Unit
+    ): Unit = coroutineScope {
+        while (!chan.isEmpty || scheduledQ.isNotEmpty()) {
+            drainChan()
+            val maxDepth = scheduledQ.peek()?.first ?: error("Unexpected empty scheduler")
+            onStep { visit -> runStep(maxDepth, visit) }
+        }
+    }
+
+    private suspend fun drainChan() {
+        while (!chan.isEmpty) {
+            scheduledQ.add(chan.receive())
+        }
+    }
+
+    private suspend inline fun runStep(
+        maxDepth: Int,
+        crossinline visit: suspend (MuxNode<*, *, *>) -> Unit,
+    ) = coroutineScope {
+        while (scheduledQ.peek()?.first?.let { it <= maxDepth } == true) {
+            val (d, node) = scheduledQ.remove()
+            if (
+                node.depthTracker.dirty_hasDirectUpstream() &&
+                    d < node.depthTracker.dirty_directDepth
+            ) {
+                scheduledQ.add(node.depthTracker.dirty_directDepth to node)
+            } else {
+                launch {
+                    enqueued.remove(node)
+                    visit(node)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt
new file mode 100644
index 0000000..c1d1076
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/StateScopeImpl.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.FrpDeferredValue
+import com.android.systemui.experimental.frp.FrpStateScope
+import com.android.systemui.experimental.frp.FrpStateful
+import com.android.systemui.experimental.frp.FrpTransactionScope
+import com.android.systemui.experimental.frp.GroupedTFlow
+import com.android.systemui.experimental.frp.TFlow
+import com.android.systemui.experimental.frp.TFlowInit
+import com.android.systemui.experimental.frp.TFlowLoop
+import com.android.systemui.experimental.frp.TState
+import com.android.systemui.experimental.frp.TStateInit
+import com.android.systemui.experimental.frp.emptyTFlow
+import com.android.systemui.experimental.frp.groupByKey
+import com.android.systemui.experimental.frp.init
+import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
+import com.android.systemui.experimental.frp.mapCheap
+import com.android.systemui.experimental.frp.merge
+import com.android.systemui.experimental.frp.switch
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.map
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.startCoroutine
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.completeWith
+import kotlinx.coroutines.job
+
+internal class StateScopeImpl(val evalScope: EvalScope, override val endSignal: TFlow<Any>) :
+    StateScope, EvalScope by evalScope {
+
+    private val endSignalOnce: TFlow<Any> = endSignal.nextOnlyInternal("StateScope.endSignal")
+
+    private fun <A> TFlow<A>.truncateToScope(operatorName: String): TFlow<A> =
+        if (endSignalOnce === emptyTFlow) {
+            this
+        } else {
+            endSignalOnce.mapCheap { emptyTFlow }.toTStateInternal(operatorName, this).switch()
+        }
+
+    private fun <A> TFlow<A>.nextOnlyInternal(operatorName: String): TFlow<A> =
+        if (this === emptyTFlow) {
+            this
+        } else {
+            TFlowLoop<A>().apply {
+                loopback =
+                    mapCheap { emptyTFlow }
+                        .toTStateInternal(operatorName, this@nextOnlyInternal)
+                        .switch()
+            }
+        }
+
+    private fun <A> TFlow<A>.toTStateInternal(operatorName: String, init: A): TState<A> =
+        toTStateInternalDeferred(operatorName, CompletableDeferred(init))
+
+    private fun <A> TFlow<A>.toTStateInternalDeferred(
+        operatorName: String,
+        init: Deferred<A>,
+    ): TState<A> {
+        val changes = this@toTStateInternalDeferred
+        val name = operatorName
+        val impl =
+            mkState(name, operatorName, evalScope, { changes.init.connect(evalScope = this) }, init)
+        return TStateInit(constInit(name, impl))
+    }
+
+    private fun <R> deferredInternal(block: suspend FrpStateScope.() -> R): FrpDeferredValue<R> =
+        FrpDeferredValue(deferAsync { runInStateScope(block) })
+
+    private fun <A> TFlow<A>.toTStateDeferredInternal(
+        initialValue: FrpDeferredValue<A>
+    ): TState<A> {
+        val operatorName = "toTStateDeferred"
+        // Ensure state is only collected until the end of this scope
+        return truncateToScope(operatorName)
+            .toTStateInternalDeferred(operatorName, initialValue.unwrapped)
+    }
+
+    private fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyInternal(
+        storage: TState<Map<K, TFlow<V>>>
+    ): TFlow<Map<K, V>> {
+        val name = "mergeIncrementally"
+        return TFlowInit(
+            constInit(
+                name,
+                switchDeferredImpl(
+                    getStorage = {
+                        storage.init
+                            .connect(this)
+                            .getCurrentWithEpoch(this)
+                            .first
+                            .mapValuesParallel { (_, flow) -> flow.init.connect(this) }
+                    },
+                    getPatches = {
+                        mapImpl({ init.connect(this) }) { patch ->
+                            patch.mapValuesParallel { (_, m) ->
+                                m.map { flow -> flow.init.connect(this) }
+                            }
+                        }
+                    },
+                ),
+            )
+        )
+    }
+
+    private fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPromptInternal(
+        storage: TState<Map<K, TFlow<V>>>
+    ): TFlow<Map<K, V>> {
+        val name = "mergeIncrementallyPrompt"
+        return TFlowInit(
+            constInit(
+                name,
+                switchPromptImpl(
+                    getStorage = {
+                        storage.init
+                            .connect(this)
+                            .getCurrentWithEpoch(this)
+                            .first
+                            .mapValuesParallel { (_, flow) -> flow.init.connect(this) }
+                    },
+                    getPatches = {
+                        mapImpl({ init.connect(this) }) { patch ->
+                            patch.mapValuesParallel { (_, m) ->
+                                m.map { flow -> flow.init.connect(this) }
+                            }
+                        }
+                    },
+                ),
+            )
+        )
+    }
+
+    private fun <K, A, B> TFlow<Map<K, Maybe<FrpStateful<A>>>>.applyLatestStatefulForKeyInternal(
+        init: FrpDeferredValue<Map<K, FrpStateful<B>>>,
+        numKeys: Int?,
+    ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> {
+        val eventsByKey: GroupedTFlow<K, Maybe<FrpStateful<A>>> = groupByKey(numKeys)
+        val initOut: Deferred<Map<K, B>> = deferAsync {
+            init.unwrapped.await().mapValuesParallel { (k, stateful) ->
+                val newEnd = with(frpScope) { eventsByKey[k].skipNext() }
+                val newScope = childStateScope(newEnd)
+                newScope.runInStateScope(stateful)
+            }
+        }
+        val changesNode: TFlowImpl<Map<K, Maybe<A>>> =
+            mapImpl(
+                upstream = { this@applyLatestStatefulForKeyInternal.init.connect(evalScope = this) }
+            ) { upstreamMap ->
+                upstreamMap.mapValuesParallel { (k: K, ma: Maybe<FrpStateful<A>>) ->
+                    reenterStateScope(this@StateScopeImpl).run {
+                        ma.map { stateful ->
+                            val newEnd = with(frpScope) { eventsByKey[k].skipNext() }
+                            val newScope = childStateScope(newEnd)
+                            newScope.runInStateScope(stateful)
+                        }
+                    }
+                }
+            }
+        val operatorName = "applyLatestStatefulForKey"
+        val name = operatorName
+        val changes: TFlow<Map<K, Maybe<A>>> = TFlowInit(constInit(name, changesNode.cached()))
+        return changes to FrpDeferredValue(initOut)
+    }
+
+    private fun <A> TFlow<FrpStateful<A>>.observeStatefulsInternal(): TFlow<A> {
+        val operatorName = "observeStatefuls"
+        val name = operatorName
+        return TFlowInit(
+            constInit(
+                name,
+                mapImpl(
+                        upstream = { this@observeStatefulsInternal.init.connect(evalScope = this) }
+                    ) { stateful ->
+                        reenterStateScope(outerScope = this@StateScopeImpl)
+                            .runInStateScope(stateful)
+                    }
+                    .cached(),
+            )
+        )
+    }
+
+    override val frpScope: FrpStateScope = FrpStateScopeImpl()
+
+    private inner class FrpStateScopeImpl :
+        FrpStateScope, FrpTransactionScope by evalScope.frpScope {
+
+        override fun <A> deferredStateScope(
+            block: suspend FrpStateScope.() -> A
+        ): FrpDeferredValue<A> = deferredInternal(block)
+
+        override fun <A> TFlow<A>.holdDeferred(initialValue: FrpDeferredValue<A>): TState<A> =
+            toTStateDeferredInternal(initialValue)
+
+        override fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementally(
+            initialTFlows: FrpDeferredValue<Map<K, TFlow<V>>>
+        ): TFlow<Map<K, V>> {
+            val storage: TState<Map<K, TFlow<V>>> = foldMapIncrementally(initialTFlows)
+            return mergeIncrementallyInternal(storage)
+        }
+
+        override fun <K : Any, V> TFlow<Map<K, Maybe<TFlow<V>>>>.mergeIncrementallyPromptly(
+            initialTFlows: FrpDeferredValue<Map<K, TFlow<V>>>
+        ): TFlow<Map<K, V>> {
+            val storage: TState<Map<K, TFlow<V>>> = foldMapIncrementally(initialTFlows)
+            return mergeIncrementallyPromptInternal(storage)
+        }
+
+        override fun <K, A, B> TFlow<Map<K, Maybe<FrpStateful<A>>>>.applyLatestStatefulForKey(
+            init: FrpDeferredValue<Map<K, FrpStateful<B>>>,
+            numKeys: Int?,
+        ): Pair<TFlow<Map<K, Maybe<A>>>, FrpDeferredValue<Map<K, B>>> =
+            applyLatestStatefulForKeyInternal(init, numKeys)
+
+        override fun <A> TFlow<FrpStateful<A>>.applyStatefuls(): TFlow<A> =
+            observeStatefulsInternal()
+    }
+
+    override suspend fun <R> runInStateScope(block: suspend FrpStateScope.() -> R): R {
+        val complete = CompletableDeferred<R>(parent = coroutineContext.job)
+        block.startCoroutine(
+            frpScope,
+            object : Continuation<R> {
+                override val context: CoroutineContext
+                    get() = EmptyCoroutineContext
+
+                override fun resumeWith(result: Result<R>) {
+                    complete.completeWith(result)
+                }
+            },
+        )
+        return complete.await()
+    }
+
+    override fun childStateScope(newEnd: TFlow<Any>) =
+        StateScopeImpl(evalScope, merge(newEnd, endSignal))
+}
+
+private fun EvalScope.reenterStateScope(outerScope: StateScopeImpl) =
+    StateScopeImpl(evalScope = this, endSignal = outerScope.endSignal)
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt
new file mode 100644
index 0000000..7997864
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TFlowImpl.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.util.Maybe
+
+/* Initialized TFlow */
+internal fun interface TFlowImpl<out A> {
+    suspend fun activate(evalScope: EvalScope, downstream: Schedulable): ActivationResult<A>?
+}
+
+internal data class ActivationResult<out A>(
+    val connection: NodeConnection<A>,
+    val needsEval: Boolean,
+)
+
+internal inline fun <A> TFlowCheap(crossinline cheap: CheapNodeSubscribe<A>) =
+    TFlowImpl { scope, ds ->
+        scope.cheap(ds)
+    }
+
+internal typealias CheapNodeSubscribe<A> =
+    suspend EvalScope.(downstream: Schedulable) -> ActivationResult<A>?
+
+internal data class NodeConnection<out A>(
+    val directUpstream: PullNode<A>,
+    val schedulerUpstream: PushNode<*>,
+)
+
+internal suspend fun <A> NodeConnection<A>.hasCurrentValue(
+    transactionStore: TransactionStore
+): Boolean = schedulerUpstream.hasCurrentValue(transactionStore)
+
+internal suspend fun <A> NodeConnection<A>.removeDownstreamAndDeactivateIfNeeded(
+    downstream: Schedulable
+) = schedulerUpstream.removeDownstreamAndDeactivateIfNeeded(downstream)
+
+internal suspend fun <A> NodeConnection<A>.scheduleDeactivationIfNeeded(evalScope: EvalScope) =
+    schedulerUpstream.scheduleDeactivationIfNeeded(evalScope)
+
+internal suspend fun <A> NodeConnection<A>.removeDownstream(downstream: Schedulable) =
+    schedulerUpstream.removeDownstream(downstream)
+
+internal suspend fun <A> NodeConnection<A>.getPushEvent(evalScope: EvalScope): Maybe<A> =
+    directUpstream.getPushEvent(evalScope)
+
+internal val <A> NodeConnection<A>.depthTracker: DepthTracker
+    get() = schedulerUpstream.depthTracker
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt
new file mode 100644
index 0000000..d8b6dac
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TStateImpl.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.internal.util.associateByIndex
+import com.android.systemui.experimental.frp.internal.util.hashString
+import com.android.systemui.experimental.frp.internal.util.mapValuesParallel
+import com.android.systemui.experimental.frp.util.Just
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.none
+import java.util.concurrent.atomic.AtomicLong
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+internal sealed interface TStateImpl<out A> {
+    val name: String?
+    val operatorName: String
+    val changes: TFlowImpl<A>
+
+    suspend fun getCurrentWithEpoch(evalScope: EvalScope): Pair<A, Long>
+}
+
+internal sealed class TStateDerived<A>(override val changes: TFlowImpl<A>) :
+    TStateImpl<A>, Key<Deferred<Pair<A, Long>>> {
+
+    @Volatile
+    var invalidatedEpoch = Long.MIN_VALUE
+        private set
+
+    @Volatile
+    protected var cache: Any? = EmptyCache
+        private set
+
+    override suspend fun getCurrentWithEpoch(evalScope: EvalScope): Pair<A, Long> =
+        evalScope.transactionStore
+            .getOrPut(this) { evalScope.deferAsync(CoroutineStart.LAZY) { pull(evalScope) } }
+            .await()
+
+    suspend fun pull(evalScope: EvalScope): Pair<A, Long> {
+        @Suppress("UNCHECKED_CAST")
+        return recalc(evalScope)?.also { (a, epoch) -> setCache(a, epoch) }
+            ?: ((cache as A) to invalidatedEpoch)
+    }
+
+    fun setCache(value: A, epoch: Long) {
+        if (epoch > invalidatedEpoch) {
+            cache = value
+            invalidatedEpoch = epoch
+        }
+    }
+
+    fun getCachedUnsafe(): Maybe<A> {
+        @Suppress("UNCHECKED_CAST")
+        return if (cache == EmptyCache) none else just(cache as A)
+    }
+
+    protected abstract suspend fun recalc(evalScope: EvalScope): Pair<A, Long>?
+
+    private data object EmptyCache
+}
+
+internal class TStateSource<A>(
+    override val name: String?,
+    override val operatorName: String,
+    init: Deferred<A>,
+    override val changes: TFlowImpl<A>,
+) : TStateImpl<A> {
+    constructor(
+        name: String?,
+        operatorName: String,
+        init: A,
+        changes: TFlowImpl<A>,
+    ) : this(name, operatorName, CompletableDeferred(init), changes)
+
+    lateinit var upstreamConnection: NodeConnection<A>
+
+    // Note: Don't need to synchronize; we will never interleave reads and writes, since all writes
+    // are performed at the end of a network step, after any reads would have taken place.
+
+    @Volatile private var _current: Deferred<A> = init
+    @Volatile
+    var writeEpoch = 0L
+        private set
+
+    override suspend fun getCurrentWithEpoch(evalScope: EvalScope): Pair<A, Long> =
+        _current.await() to writeEpoch
+
+    /** called by network after eval phase has completed */
+    suspend fun updateState(evalScope: EvalScope) {
+        // write the latch
+        val eventResult = upstreamConnection.getPushEvent(evalScope)
+        if (eventResult is Just) {
+            _current = CompletableDeferred(eventResult.value)
+            writeEpoch = evalScope.epoch
+        }
+    }
+
+    override fun toString(): String = "TStateImpl(changes=$changes, current=$_current)"
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun getStorageUnsafe(): Maybe<A> =
+        if (_current.isCompleted) just(_current.getCompleted()) else none
+}
+
+internal fun <A> constS(name: String?, operatorName: String, init: A): TStateImpl<A> =
+    TStateSource(name, operatorName, init, neverImpl)
+
+internal inline fun <A> mkState(
+    name: String?,
+    operatorName: String,
+    evalScope: EvalScope,
+    crossinline getChanges: suspend EvalScope.() -> TFlowImpl<A>,
+    init: Deferred<A>,
+): TStateImpl<A> {
+    lateinit var state: TStateSource<A>
+    val calm: TFlowImpl<A> =
+        filterNode(getChanges) { new -> new != state.getCurrentWithEpoch(evalScope = this).first }
+            .cached()
+    return TStateSource(name, operatorName, init, calm).also {
+        state = it
+        evalScope.scheduleOutput(
+            OneShot {
+                calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
+                    (connection, needsEval) ->
+                    state.upstreamConnection = connection
+                    if (needsEval) {
+                        schedule(state)
+                    }
+                }
+            }
+        )
+    }
+}
+
+private inline fun <A> TFlowImpl<A>.calm(
+    crossinline getState: () -> TStateDerived<A>
+): TFlowImpl<A> =
+    filterNode({ this@calm }) { new ->
+            val state = getState()
+            val (current, _) = state.getCurrentWithEpoch(evalScope = this)
+            if (new != current) {
+                state.setCache(new, epoch)
+                true
+            } else {
+                false
+            }
+        }
+        .cached()
+
+internal fun <A, B> TStateImpl<A>.mapCheap(
+    name: String?,
+    operatorName: String,
+    transform: suspend EvalScope.(A) -> B,
+): TStateImpl<B> =
+    DerivedMapCheap(name, operatorName, this, mapImpl({ changes }) { transform(it) }, transform)
+
+internal class DerivedMapCheap<A, B>(
+    override val name: String?,
+    override val operatorName: String,
+    val upstream: TStateImpl<A>,
+    override val changes: TFlowImpl<B>,
+    private val transform: suspend EvalScope.(A) -> B,
+) : TStateImpl<B> {
+
+    override suspend fun getCurrentWithEpoch(evalScope: EvalScope): Pair<B, Long> {
+        val (a, epoch) = upstream.getCurrentWithEpoch(evalScope)
+        return evalScope.transform(a) to epoch
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+internal fun <A, B> TStateImpl<A>.map(
+    name: String?,
+    operatorName: String,
+    transform: suspend EvalScope.(A) -> B,
+): TStateImpl<B> {
+    lateinit var state: TStateDerived<B>
+    val mappedChanges = mapImpl({ changes }) { transform(it) }.cached().calm { state }
+    state = DerivedMap(name, operatorName, transform, this, mappedChanges)
+    return state
+}
+
+internal class DerivedMap<A, B>(
+    override val name: String?,
+    override val operatorName: String,
+    private val transform: suspend EvalScope.(A) -> B,
+    val upstream: TStateImpl<A>,
+    changes: TFlowImpl<B>,
+) : TStateDerived<B>(changes) {
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+
+    override suspend fun recalc(evalScope: EvalScope): Pair<B, Long>? {
+        val (a, epoch) = upstream.getCurrentWithEpoch(evalScope)
+        return if (epoch > invalidatedEpoch) {
+            evalScope.transform(a) to epoch
+        } else {
+            null
+        }
+    }
+}
+
+internal fun <A> TStateImpl<TStateImpl<A>>.flatten(name: String?, operator: String): TStateImpl<A> {
+    // emits the current value of the new inner state, when that state is emitted
+    val switchEvents = mapImpl({ changes }) { newInner -> newInner.getCurrentWithEpoch(this).first }
+    // emits the new value of the new inner state when that state is emitted, or
+    // falls back to the current value if a new state is *not* being emitted this
+    // transaction
+    val innerChanges =
+        mapImpl({ changes }) { newInner ->
+            mergeNodes({ switchEvents }, { newInner.changes }) { _, new -> new }
+        }
+    val switchedChanges: TFlowImpl<A> =
+        mapImpl({
+            switchPromptImpl(
+                getStorage = {
+                    mapOf(Unit to this@flatten.getCurrentWithEpoch(evalScope = this).first.changes)
+                },
+                getPatches = { mapImpl({ innerChanges }) { new -> mapOf(Unit to just(new)) } },
+            )
+        }) { map ->
+            map.getValue(Unit)
+        }
+    lateinit var state: DerivedFlatten<A>
+    state = DerivedFlatten(name, operator, this, switchedChanges.calm { state })
+    return state
+}
+
+internal class DerivedFlatten<A>(
+    override val name: String?,
+    override val operatorName: String,
+    val upstream: TStateImpl<TStateImpl<A>>,
+    changes: TFlowImpl<A>,
+) : TStateDerived<A>(changes) {
+    override suspend fun recalc(evalScope: EvalScope): Pair<A, Long> {
+        val (inner, epoch0) = upstream.getCurrentWithEpoch(evalScope)
+        val (a, epoch1) = inner.getCurrentWithEpoch(evalScope)
+        return a to maxOf(epoch0, epoch1)
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <A, B> TStateImpl<A>.flatMap(
+    name: String?,
+    operatorName: String,
+    noinline transform: suspend EvalScope.(A) -> TStateImpl<B>,
+): TStateImpl<B> = map(null, operatorName, transform).flatten(name, operatorName)
+
+internal fun <A, B, Z> zipStates(
+    name: String?,
+    operatorName: String,
+    l1: TStateImpl<A>,
+    l2: TStateImpl<B>,
+    transform: suspend EvalScope.(A, B) -> Z,
+): TStateImpl<Z> =
+    zipStates(null, operatorName, mapOf(0 to l1, 1 to l2)).map(name, operatorName) {
+        val a = it.getValue(0)
+        val b = it.getValue(1)
+        @Suppress("UNCHECKED_CAST") transform(a as A, b as B)
+    }
+
+internal fun <A, B, C, Z> zipStates(
+    name: String?,
+    operatorName: String,
+    l1: TStateImpl<A>,
+    l2: TStateImpl<B>,
+    l3: TStateImpl<C>,
+    transform: suspend EvalScope.(A, B, C) -> Z,
+): TStateImpl<Z> =
+    zipStates(null, operatorName, mapOf(0 to l1, 1 to l2, 2 to l3)).map(name, operatorName) {
+        val a = it.getValue(0)
+        val b = it.getValue(1)
+        val c = it.getValue(2)
+        @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C)
+    }
+
+internal fun <A, B, C, D, Z> zipStates(
+    name: String?,
+    operatorName: String,
+    l1: TStateImpl<A>,
+    l2: TStateImpl<B>,
+    l3: TStateImpl<C>,
+    l4: TStateImpl<D>,
+    transform: suspend EvalScope.(A, B, C, D) -> Z,
+): TStateImpl<Z> =
+    zipStates(null, operatorName, mapOf(0 to l1, 1 to l2, 2 to l3, 3 to l4)).map(
+        name,
+        operatorName,
+    ) {
+        val a = it.getValue(0)
+        val b = it.getValue(1)
+        val c = it.getValue(2)
+        val d = it.getValue(3)
+        @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C, d as D)
+    }
+
+internal fun <K : Any, A> zipStates(
+    name: String?,
+    operatorName: String,
+    states: Map<K, TStateImpl<A>>,
+): TStateImpl<Map<K, A>> {
+    if (states.isEmpty()) return constS(name, operatorName, emptyMap())
+    val stateChanges: Map<K, TFlowImpl<A>> = states.mapValues { it.value.changes }
+    lateinit var state: DerivedZipped<K, A>
+    // No need for calm; invariant ensures that changes will only emit when there's a difference
+    val changes: TFlowImpl<Map<K, A>> =
+        mapImpl({
+            switchDeferredImpl(getStorage = { stateChanges }, getPatches = { neverImpl })
+        }) { patch ->
+            states
+                .mapValues { (k, v) ->
+                    if (k in patch) {
+                        patch.getValue(k)
+                    } else {
+                        v.getCurrentWithEpoch(evalScope = this).first
+                    }
+                }
+                .also { state.setCache(it, epoch) }
+        }
+    state = DerivedZipped(name, operatorName, states, changes)
+    return state
+}
+
+internal class DerivedZipped<K : Any, A>(
+    override val name: String?,
+    override val operatorName: String,
+    val upstream: Map<K, TStateImpl<A>>,
+    changes: TFlowImpl<Map<K, A>>,
+) : TStateDerived<Map<K, A>>(changes) {
+    override suspend fun recalc(evalScope: EvalScope): Pair<Map<K, A>, Long> {
+        val newEpoch = AtomicLong()
+        return upstream.mapValuesParallel {
+            val (a, epoch) = it.value.getCurrentWithEpoch(evalScope)
+            newEpoch.accumulateAndGet(epoch, ::maxOf)
+            a
+        } to newEpoch.get()
+    }
+
+    override fun toString(): String = "${this::class.simpleName}@$hashString"
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <A> zipStates(
+    name: String?,
+    operatorName: String,
+    states: List<TStateImpl<A>>,
+): TStateImpl<List<A>> =
+    if (states.isEmpty()) {
+        constS(name, operatorName, emptyList())
+    } else {
+        zipStates(null, operatorName, states.asIterable().associateByIndex()).mapCheap(
+            name,
+            operatorName,
+        ) {
+            it.values.toList()
+        }
+    }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt
new file mode 100644
index 0000000..c3f80a1
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/TransactionalImpl.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal
+
+import com.android.systemui.experimental.frp.internal.util.Key
+import com.android.systemui.experimental.frp.internal.util.hashString
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+
+internal sealed class TransactionalImpl<out A> {
+    data class Const<out A>(val value: Deferred<A>) : TransactionalImpl<A>()
+
+    class Impl<A>(val block: suspend EvalScope.() -> A) : TransactionalImpl<A>(), Key<Deferred<A>> {
+        override fun toString(): String = "${this::class.simpleName}@$hashString"
+    }
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun <A> transactionalImpl(
+    noinline block: suspend EvalScope.() -> A
+): TransactionalImpl<A> = TransactionalImpl.Impl(block)
+
+internal fun <A> TransactionalImpl<A>.sample(evalScope: EvalScope): Deferred<A> =
+    when (this) {
+        is TransactionalImpl.Const -> value
+        is TransactionalImpl.Impl ->
+            evalScope.transactionStore
+                .getOrPut(this) {
+                    evalScope.deferAsync(start = CoroutineStart.LAZY) { evalScope.block() }
+                }
+                .also { it.start() }
+    }
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt
new file mode 100644
index 0000000..cc5538e
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Bag.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal.util
+
+internal class Bag<T> private constructor(private val intMap: MutableMap<T, Int>) :
+    Set<T> by intMap.keys {
+
+    constructor() : this(hashMapOf())
+
+    override fun toString(): String = intMap.toString()
+
+    fun add(element: T): Boolean {
+        val entry = intMap[element]
+        return if (entry != null) {
+            intMap[element] = entry + 1
+            false
+        } else {
+            intMap[element] = 1
+            true
+        }
+    }
+
+    fun remove(element: T): Boolean {
+        val entry = intMap[element]
+        return when {
+            entry == null -> {
+                false
+            }
+            entry <= 1 -> {
+                intMap.remove(element)
+                true
+            }
+            else -> {
+                intMap[element] = entry - 1
+                false
+            }
+        }
+    }
+
+    fun addAll(elements: Iterable<T>, butNot: T? = null): Set<T>? {
+        val newlyAdded = hashSetOf<T>()
+        for (value in elements) {
+            if (value != butNot) {
+                if (add(value)) {
+                    newlyAdded.add(value)
+                }
+            }
+        }
+        return newlyAdded.ifEmpty { null }
+    }
+
+    fun clear() {
+        intMap.clear()
+    }
+
+    fun removeAll(elements: Collection<T>): Set<T>? {
+        val result = hashSetOf<T>()
+        for (element in elements) {
+            if (remove(element)) {
+                result.add(element)
+            }
+        }
+        return result.ifEmpty { null }
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt
new file mode 100644
index 0000000..449aa19
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/ConcurrentNullableHashMap.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal.util
+
+import java.util.concurrent.ConcurrentHashMap
+
+internal class ConcurrentNullableHashMap<K : Any, V>
+private constructor(private val inner: ConcurrentHashMap<K, Any>) {
+    constructor() : this(ConcurrentHashMap())
+
+    @Suppress("UNCHECKED_CAST")
+    operator fun get(key: K): V? = inner[key]?.takeIf { it !== NullValue } as V?
+
+    @Suppress("UNCHECKED_CAST")
+    fun put(key: K, value: V?): V? =
+        inner.put(key, value ?: NullValue)?.takeIf { it !== NullValue } as V?
+
+    operator fun set(key: K, value: V?) {
+        put(key, value)
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    fun toMap(): Map<K, V> = inner.mapValues { (_, v) -> v.takeIf { it !== NullValue } as V }
+
+    fun clear() {
+        inner.clear()
+    }
+
+    fun isNotEmpty(): Boolean = inner.isNotEmpty()
+}
+
+private object NullValue
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt
new file mode 100644
index 0000000..14a567c
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/HeteroMap.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal.util
+
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.just
+import java.util.concurrent.ConcurrentHashMap
+
+internal interface Key<A>
+
+private object NULL
+
+internal class HeteroMap {
+
+    private val store = ConcurrentHashMap<Key<*>, Any>()
+
+    @Suppress("UNCHECKED_CAST")
+    operator fun <A> get(key: Key<A>): Maybe<A> =
+        store[key]?.let { just((if (it === NULL) null else it) as A) } ?: None
+
+    operator fun <A> set(key: Key<A>, value: A) {
+        store[key] = value ?: NULL
+    }
+
+    operator fun contains(key: Key<*>): Boolean = store.containsKey(key)
+
+    fun clear() {
+        store.clear()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    fun <A> remove(key: Key<A>): Maybe<A> =
+        store.remove(key)?.let { just((if (it === NULL) null else it) as A) } ?: None
+
+    @Suppress("UNCHECKED_CAST")
+    fun <A> getOrPut(key: Key<A>, defaultValue: () -> A): A =
+        store.compute(key) { _, value -> value ?: defaultValue() ?: NULL } as A
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt
new file mode 100644
index 0000000..6f19a76
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/MapUtils.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.internal.util
+
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.yield
+
+// TODO: It's possible that this is less efficient than having each coroutine directly insert into a
+//  ConcurrentHashMap, but then we would lose ordering
+internal suspend inline fun <K, A, B : Any, M : MutableMap<K, B>> Map<K, A>
+    .mapValuesNotNullParallelTo(
+    destination: M,
+    crossinline block: suspend (Map.Entry<K, A>) -> B?,
+): M =
+    destination.also {
+        coroutineScope {
+                mapValues {
+                    async {
+                        yield()
+                        block(it)
+                    }
+                }
+            }
+            .mapValuesNotNullTo(it) { (_, deferred) -> deferred.await() }
+    }
+
+internal inline fun <K, A, B : Any, M : MutableMap<K, B>> Map<K, A>.mapValuesNotNullTo(
+    destination: M,
+    block: (Map.Entry<K, A>) -> B?,
+): M =
+    destination.also {
+        for (entry in this@mapValuesNotNullTo) {
+            block(entry)?.let { destination.put(entry.key, it) }
+        }
+    }
+
+internal suspend fun <A, B> Iterable<A>.mapParallel(transform: suspend (A) -> B): List<B> =
+    coroutineScope {
+        map { async(start = CoroutineStart.LAZY) { transform(it) } }.awaitAll()
+    }
+
+internal suspend fun <K, A, B, M : MutableMap<K, B>> Map<K, A>.mapValuesParallelTo(
+    destination: M,
+    transform: suspend (Map.Entry<K, A>) -> B,
+): Map<K, B> = entries.mapParallel { it.key to transform(it) }.toMap(destination)
+
+internal suspend fun <K, A, B> Map<K, A>.mapValuesParallel(
+    transform: suspend (Map.Entry<K, A>) -> B
+): Map<K, B> = mapValuesParallelTo(mutableMapOf(), transform)
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt
new file mode 100644
index 0000000..0a47429
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/internal/util/Util.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.experimental.frp.internal.util
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.newCoroutineContext
+
+internal fun <A> CoroutineScope.asyncImmediate(
+    start: CoroutineStart = CoroutineStart.UNDISPATCHED,
+    context: CoroutineContext = EmptyCoroutineContext,
+    block: suspend CoroutineScope.() -> A,
+): Deferred<A> = async(start = start, context = Dispatchers.Unconfined + context, block = block)
+
+internal fun CoroutineScope.launchImmediate(
+    start: CoroutineStart = CoroutineStart.UNDISPATCHED,
+    context: CoroutineContext = EmptyCoroutineContext,
+    block: suspend CoroutineScope.() -> Unit,
+): Job = launch(start = start, context = Dispatchers.Unconfined + context, block = block)
+
+internal suspend fun awaitCancellationAndThen(block: suspend () -> Unit) {
+    try {
+        awaitCancellation()
+    } finally {
+        block()
+    }
+}
+
+internal fun CoroutineScope.launchOnCancel(
+    context: CoroutineContext = EmptyCoroutineContext,
+    block: () -> Unit,
+): Job =
+    launch(context = context, start = CoroutineStart.UNDISPATCHED) {
+        awaitCancellationAndThen(block)
+    }
+
+internal fun CoroutineScope.childScope(
+    context: CoroutineContext = EmptyCoroutineContext
+): CoroutineScope {
+    val newContext = newCoroutineContext(context)
+    val newJob = Job(parent = newContext[Job])
+    return CoroutineScope(newContext + newJob)
+}
+
+internal fun <A> Iterable<A>.associateByIndex(): Map<Int, A> = buildMap {
+    forEachIndexed { index, a -> put(index, a) }
+}
+
+internal fun <A, M : MutableMap<Int, A>> Iterable<A>.associateByIndexTo(destination: M): M =
+    destination.apply { forEachIndexed { index, a -> put(index, a) } }
+
+internal val Any.hashString: String
+    get() = Integer.toHexString(System.identityHashCode(this))
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt
new file mode 100644
index 0000000..dca8364
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Either.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package com.android.systemui.experimental.frp.util
+
+/**
+ * Contains a value of two possibilities: `Left<A>` or `Right<B>`
+ *
+ * [Either] generalizes sealed classes the same way that [Pair] generalizes data classes; if a
+ * [Pair] is effectively an anonymous grouping of two instances, then an [Either] is an anonymous
+ * set of two options.
+ */
+sealed class Either<out A, out B>
+
+/** An [Either] that contains a [Left] value. */
+data class Left<out A>(val value: A) : Either<A, Nothing>()
+
+/** An [Either] that contains a [Right] value. */
+data class Right<out B>(val value: B) : Either<Nothing, B>()
+
+/**
+ * Returns an [Either] containing the result of applying [transform] to the [Left] value, or the
+ * [Right] value unchanged.
+ */
+inline fun <A, B, C> Either<A, C>.mapLeft(transform: (A) -> B): Either<B, C> =
+    when (this) {
+        is Left -> Left(transform(value))
+        is Right -> this
+    }
+
+/**
+ * Returns an [Either] containing the result of applying [transform] to the [Right] value, or the
+ * [Left] value unchanged.
+ */
+inline fun <A, B, C> Either<A, B>.mapRight(transform: (B) -> C): Either<A, C> =
+    when (this) {
+        is Left -> this
+        is Right -> Right(transform(value))
+    }
+
+/** Returns a [Maybe] containing the [Left] value held by this [Either], if present. */
+inline fun <A> Either<A, *>.leftMaybe(): Maybe<A> =
+    when (this) {
+        is Left -> just(value)
+        else -> None
+    }
+
+/** Returns the [Left] value held by this [Either], or `null` if this is a [Right] value. */
+inline fun <A> Either<A, *>.leftOrNull(): A? =
+    when (this) {
+        is Left -> value
+        else -> null
+    }
+
+/** Returns a [Maybe] containing the [Right] value held by this [Either], if present. */
+inline fun <B> Either<*, B>.rightMaybe(): Maybe<B> =
+    when (this) {
+        is Right -> just(value)
+        else -> None
+    }
+
+/** Returns the [Right] value held by this [Either], or `null` if this is a [Left] value. */
+inline fun <B> Either<*, B>.rightOrNull(): B? =
+    when (this) {
+        is Right -> value
+        else -> null
+    }
+
+/**
+ * Partitions this sequence of [Either] into two lists; [Pair.first] contains all [Left] values, and
+ * [Pair.second] contains all [Right] values.
+ */
+fun <A, B> Sequence<Either<A, B>>.partitionEithers(): Pair<List<A>, List<B>> {
+    val lefts = mutableListOf<A>()
+    val rights = mutableListOf<B>()
+    for (either in this) {
+        when (either) {
+            is Left -> lefts.add(either.value)
+            is Right -> rights.add(either.value)
+        }
+    }
+    return lefts to rights
+}
+
+/**
+ * Partitions this map of [Either] values into two maps; [Pair.first] contains all [Left] values,
+ * and [Pair.second] contains all [Right] values.
+ */
+fun <K, A, B> Map<K, Either<A, B>>.partitionEithers(): Pair<Map<K, A>, Map<K, B>> {
+    val lefts = mutableMapOf<K, A>()
+    val rights = mutableMapOf<K, B>()
+    for ((k, e) in this) {
+        when (e) {
+            is Left -> lefts[k] = e.value
+            is Right -> rights[k] = e.value
+        }
+    }
+    return lefts to rights
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt
new file mode 100644
index 0000000..59c680e
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/Maybe.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE", "SuspendCoroutine")
+
+package com.android.systemui.experimental.frp.util
+
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.RestrictsSuspension
+import kotlin.coroutines.resume
+import kotlin.coroutines.startCoroutine
+import kotlin.coroutines.suspendCoroutine
+
+/** Represents a value that may or may not be present. */
+sealed class Maybe<out A>
+
+/** A [Maybe] value that is present. */
+data class Just<out A> internal constructor(val value: A) : Maybe<A>()
+
+/** A [Maybe] value that is not present. */
+data object None : Maybe<Nothing>()
+
+/** Utilities to query [Maybe] instances from within a [maybe] block. */
+@RestrictsSuspension
+object MaybeScope {
+    suspend operator fun <A> Maybe<A>.not(): A = suspendCoroutine { k ->
+        if (this is Just) k.resume(value)
+    }
+
+    suspend inline fun guard(crossinline block: () -> Boolean): Unit = suspendCoroutine { k ->
+        if (block()) k.resume(Unit)
+    }
+}
+
+/**
+ * Returns a [Maybe] value produced by evaluating [block].
+ *
+ * [block] can use its [MaybeScope] receiver to query other [Maybe] values, automatically cancelling
+ * execution of [block] and producing [None] when attempting to query a [Maybe] that is not present.
+ *
+ * This can be used instead of Kotlin's built-in nullability (`?.` and `?:`) operators when dealing
+ * with complex combinations of nullables:
+ * ``` kotlin
+ * val aMaybe: Maybe<Any> = ...
+ * val bMaybe: Maybe<Any> = ...
+ * val result: String = maybe {
+ *   val a = !aMaybe
+ *   val b = !bMaybe
+ *   "Got: $a and $b"
+ * }
+ * ```
+ */
+fun <A> maybe(block: suspend MaybeScope.() -> A): Maybe<A> {
+    var maybeResult: Maybe<A> = None
+    val k =
+        object : Continuation<A> {
+            override val context: CoroutineContext = EmptyCoroutineContext
+
+            override fun resumeWith(result: Result<A>) {
+                maybeResult = result.getOrNull()?.let { just(it) } ?: None
+            }
+        }
+    block.startCoroutine(MaybeScope, k)
+    return maybeResult
+}
+
+/** Returns a [Just] containing this value, or [None] if `null`. */
+inline fun <A> (A?).toMaybe(): Maybe<A> = maybe(this)
+
+/** Returns a [Just] containing a non-null [value], or [None] if `null`. */
+inline fun <A> maybe(value: A?): Maybe<A> = value?.let(::just) ?: None
+
+/** Returns a [Just] containing [value]. */
+fun <A> just(value: A): Maybe<A> = Just(value)
+
+/** A [Maybe] that is not present. */
+val none: Maybe<Nothing> = None
+
+/** A [Maybe] that is not present. */
+inline fun <A> none(): Maybe<A> = None
+
+/** Returns the value present in this [Maybe], or `null` if not present. */
+inline fun <A> Maybe<A>.orNull(): A? = orElse(null)
+
+/**
+ * Returns a [Maybe] holding the result of applying [transform] to the value in the original
+ * [Maybe].
+ */
+inline fun <A, B> Maybe<A>.map(transform: (A) -> B): Maybe<B> =
+    when (this) {
+        is Just -> just(transform(value))
+        is None -> None
+    }
+
+/** Returns the result of applying [transform] to the value in the original [Maybe]. */
+inline fun <A, B> Maybe<A>.flatMap(transform: (A) -> Maybe<B>): Maybe<B> =
+    when (this) {
+        is Just -> transform(value)
+        is None -> None
+    }
+
+/** Returns the value present in this [Maybe], or the result of [defaultValue] if not present. */
+inline fun <A> Maybe<A>.orElseGet(defaultValue: () -> A): A =
+    when (this) {
+        is Just -> value
+        is None -> defaultValue()
+    }
+
+/**
+ * Returns the value present in this [Maybe], or invokes [error] with the message returned from
+ * [getMessage].
+ */
+inline fun <A> Maybe<A>.orError(getMessage: () -> Any): A = orElseGet { error(getMessage()) }
+
+/** Returns the value present in this [Maybe], or [defaultValue] if not present. */
+inline fun <A> Maybe<A>.orElse(defaultValue: A): A =
+    when (this) {
+        is Just -> value
+        is None -> defaultValue
+    }
+
+/**
+ * Returns a [Maybe] that contains the present in the original [Maybe], only if it satisfies
+ * [predicate].
+ */
+inline fun <A> Maybe<A>.filter(predicate: (A) -> Boolean): Maybe<A> =
+    when (this) {
+        is Just -> if (predicate(value)) this else None
+        else -> this
+    }
+
+/** Returns a [List] containing all values that are present in this [Iterable]. */
+fun <A> Iterable<Maybe<A>>.filterJust(): List<A> = asSequence().filterJust().toList()
+
+/** Returns a [List] containing all values that are present in this [Sequence]. */
+fun <A> Sequence<Maybe<A>>.filterJust(): Sequence<A> = filterIsInstance<Just<A>>().map { it.value }
+
+// Align
+
+/**
+ * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
+ * and other, applied to [transform] as a [These].
+ */
+inline fun <A, B, C> Maybe<A>.alignWith(other: Maybe<B>, transform: (These<A, B>) -> C): Maybe<C> =
+    when (this) {
+        is Just -> {
+            val a = value
+            when (other) {
+                is Just -> {
+                    val b = other.value
+                    just(transform(These.both(a, b)))
+                }
+                None -> just(transform(These.thiz(a)))
+            }
+        }
+        None ->
+            when (other) {
+                is Just -> {
+                    val b = other.value
+                    just(transform(These.that(b)))
+                }
+                None -> none
+            }
+    }
+
+// Alt
+
+/** Returns a [Maybe] containing the value present in the original [Maybe], or [other]. */
+infix fun <A> Maybe<A>.orElseMaybe(other: Maybe<A>): Maybe<A> = orElseGetMaybe { other }
+
+/**
+ * Returns a [Maybe] containing the value present in the original [Maybe], or the result of [other].
+ */
+inline fun <A> Maybe<A>.orElseGetMaybe(other: () -> Maybe<A>): Maybe<A> =
+    when (this) {
+        is Just -> this
+        else -> other()
+    }
+
+// Apply
+
+/**
+ * Returns a [Maybe] containing the value present in [argMaybe] applied to the function present in
+ * the original [Maybe].
+ */
+fun <A, B> Maybe<(A) -> B>.apply(argMaybe: Maybe<A>): Maybe<B> = flatMap { f ->
+    argMaybe.map { a -> f(a) }
+}
+
+/**
+ * Returns a [Maybe] containing the result of applying [transform] to the values present in the
+ * original [Maybe] and [other].
+ */
+inline fun <A, B, C> Maybe<A>.zipWith(other: Maybe<B>, transform: (A, B) -> C) = flatMap { a ->
+    other.map { b -> transform(a, b) }
+}
+
+// Bind
+
+/**
+ * Returns a [Maybe] containing the value present in the [Maybe] present in the original [Maybe].
+ */
+fun <A> Maybe<Maybe<A>>.flatten(): Maybe<A> = flatMap { it }
+
+// Semigroup
+
+/**
+ * Returns a [Maybe] containing the result of applying the values present in the original [Maybe]
+ * and other, applied to [transform].
+ */
+fun <A> Maybe<A>.mergeWith(other: Maybe<A>, transform: (A, A) -> A): Maybe<A> =
+    alignWith(other) { it.merge(transform) }
+
+/**
+ * Returns a list containing only the present results of applying [transform] to each element in the
+ * original iterable.
+ */
+fun <A, B> Iterable<A>.mapMaybe(transform: (A) -> Maybe<B>): List<B> =
+    asSequence().mapMaybe(transform).toList()
+
+/**
+ * Returns a sequence containing only the present results of applying [transform] to each element in
+ * the original sequence.
+ */
+fun <A, B> Sequence<A>.mapMaybe(transform: (A) -> Maybe<B>): Sequence<B> =
+    map(transform).filterIsInstance<Just<B>>().map { it.value }
+
+/**
+ * Returns a map with values of only the present results of applying [transform] to each entry in
+ * the original map.
+ */
+inline fun <K, A, B> Map<K, A>.mapMaybeValues(
+    crossinline p: (Map.Entry<K, A>) -> Maybe<B>
+): Map<K, B> = asSequence().mapMaybe { entry -> p(entry).map { entry.key to it } }.toMap()
+
+/** Returns a map with all non-present values filtered out. */
+fun <K, A> Map<K, Maybe<A>>.filterJustValues(): Map<K, A> =
+    asSequence().mapMaybe { (key, mValue) -> mValue.map { key to it } }.toMap()
+
+/**
+ * Returns a pair of [Maybes][Maybe] that contain the [Pair.first] and [Pair.second] values present
+ * in the original [Maybe].
+ */
+fun <A, B> Maybe<Pair<A, B>>.splitPair(): Pair<Maybe<A>, Maybe<B>> =
+    map { it.first } to map { it.second }
+
+/** Returns the value associated with [key] in this map as a [Maybe]. */
+fun <K, V> Map<K, V>.getMaybe(key: K): Maybe<V> {
+    val value = get(key)
+    if (value == null && !containsKey(key)) {
+        return none
+    } else {
+        @Suppress("UNCHECKED_CAST")
+        return just(value as V)
+    }
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt
new file mode 100644
index 0000000..5404c07
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/These.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.util
+
+/** Contains at least one of two potential values. */
+sealed class These<A, B> {
+    /** Contains a single potential value. */
+    class This<A, B> internal constructor(val thiz: A) : These<A, B>()
+
+    /** Contains a single potential value. */
+    class That<A, B> internal constructor(val that: B) : These<A, B>()
+
+    /** Contains both potential values. */
+    class Both<A, B> internal constructor(val thiz: A, val that: B) : These<A, B>()
+
+    companion object {
+        /** Constructs a [These] containing only [thiz]. */
+        fun <A, B> thiz(thiz: A): These<A, B> = This(thiz)
+
+        /** Constructs a [These] containing only [that]. */
+        fun <A, B> that(that: B): These<A, B> = That(that)
+
+        /** Constructs a [These] containing both [thiz] and [that]. */
+        fun <A, B> both(thiz: A, that: B): These<A, B> = Both(thiz, that)
+    }
+}
+
+/**
+ * Returns a single value from this [These]; either the single value held within, or the result of
+ * applying [f] to both values.
+ */
+inline fun <A> These<A, A>.merge(f: (A, A) -> A): A =
+    when (this) {
+        is These.This -> thiz
+        is These.That -> that
+        is These.Both -> f(thiz, that)
+    }
+
+/** Returns the [These.This] [value][These.This.thiz] present in this [These] as a [Maybe]. */
+fun <A> These<A, *>.maybeThis(): Maybe<A> =
+    when (this) {
+        is These.Both -> just(thiz)
+        is These.That -> None
+        is These.This -> just(thiz)
+    }
+
+/**
+ * Returns the [These.This] [value][These.This.thiz] present in this [These], or `null` if not
+ * present.
+ */
+fun <A : Any> These<A, *>.thisOrNull(): A? =
+    when (this) {
+        is These.Both -> thiz
+        is These.That -> null
+        is These.This -> thiz
+    }
+
+/** Returns the [These.That] [value][These.That.that] present in this [These] as a [Maybe]. */
+fun <A> These<*, A>.maybeThat(): Maybe<A> =
+    when (this) {
+        is These.Both -> just(that)
+        is These.That -> just(that)
+        is These.This -> None
+    }
+
+/**
+ * Returns the [These.That] [value][These.That.that] present in this [These], or `null` if not
+ * present.
+ */
+fun <A : Any> These<*, A>.thatOrNull(): A? =
+    when (this) {
+        is These.Both -> that
+        is These.That -> that
+        is These.This -> null
+    }
+
+/** Returns [These.Both] values present in this [These] as a [Maybe]. */
+fun <A, B> These<A, B>.maybeBoth(): Maybe<Pair<A, B>> =
+    when (this) {
+        is These.Both -> just(thiz to that)
+        else -> None
+    }
+
+/** Returns a [These] containing [thiz] and/or [that] if they are present. */
+fun <A, B> these(thiz: Maybe<A>, that: Maybe<B>): Maybe<These<A, B>> =
+    when (thiz) {
+        is Just ->
+            just(
+                when (that) {
+                    is Just -> These.both(thiz.value, that.value)
+                    else -> These.thiz(thiz.value)
+                }
+            )
+        else ->
+            when (that) {
+                is Just -> just(These.that(that.value))
+                else -> none
+            }
+    }
+
+/**
+ * Returns a [These] containing [thiz] and/or [that] if they are non-null, or `null` if both are
+ * `null`.
+ */
+fun <A : Any, B : Any> theseNull(thiz: A?, that: B?): These<A, B>? =
+    thiz?.let { that?.let { These.both(thiz, that) } ?: These.thiz(thiz) }
+        ?: that?.let { These.that(that) }
+
+/**
+ * Returns two maps, with [Pair.first] containing all [These.This] values and [Pair.second]
+ * containing all [These.That] values.
+ *
+ * If the value is [These.Both], then the associated key with appear in both output maps, bound to
+ * [These.Both.thiz] and [These.Both.that] in each respective output.
+ */
+fun <K, A, B> Map<K, These<A, B>>.partitionThese(): Pair<Map<K, A>, Map<K, B>> {
+    val a = mutableMapOf<K, A>()
+    val b = mutableMapOf<K, B>()
+    for ((k, t) in this) {
+        when (t) {
+            is These.Both -> {
+                a[k] = t.thiz
+                b[k] = t.that
+            }
+            is These.That -> {
+                b[k] = t.that
+            }
+            is These.This -> {
+                a[k] = t.thiz
+            }
+        }
+    }
+    return a to b
+}
diff --git a/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
new file mode 100644
index 0000000..e52a6e1
--- /dev/null
+++ b/packages/SystemUI/frp/src/com/android/systemui/experimental/frp/util/WithPrev.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.experimental.frp.util
+
+/** Holds a [newValue] emitted from a `TFlow`, along with the [previousValue] emitted value. */
+data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
diff --git a/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt b/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt
new file mode 100644
index 0000000..a58f499
--- /dev/null
+++ b/packages/SystemUI/frp/test/com/android/systemui/experimental/frp/FrpTests.kt
@@ -0,0 +1,1370 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFrpApi::class)
+
+package com.android.systemui.experimental.frp
+
+import com.android.systemui.experimental.frp.util.Either
+import com.android.systemui.experimental.frp.util.Left
+import com.android.systemui.experimental.frp.util.Maybe
+import com.android.systemui.experimental.frp.util.None
+import com.android.systemui.experimental.frp.util.Right
+import com.android.systemui.experimental.frp.util.just
+import com.android.systemui.experimental.frp.util.map
+import com.android.systemui.experimental.frp.util.maybe
+import com.android.systemui.experimental.frp.util.none
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.DurationUnit
+import kotlin.time.measureTime
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class FrpTests {
+
+    @Test
+    fun basic() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        var result: Int? = null
+        activateSpec(network) { emitter.observe { result = it } }
+        runCurrent()
+        emitter.emit(3)
+        runCurrent()
+        assertEquals(3, result)
+        runCurrent()
+    }
+
+    @Test
+    fun basicTFlow() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        println("starting network")
+        val result = activateSpecWithResult(network) { emitter.nextDeferred() }
+        runCurrent()
+        println("emitting")
+        emitter.emit(3)
+        runCurrent()
+        println("awaiting")
+        assertEquals(3, result.await())
+        runCurrent()
+    }
+
+    @Test
+    fun basicTState() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        val result = activateSpecWithResult(network) { emitter.hold(0).stateChanges.nextDeferred() }
+        runCurrent()
+
+        emitter.emit(3)
+        runCurrent()
+
+        assertEquals(3, result.await())
+    }
+
+    @Test
+    fun basicEvent() = runFrpTest { network ->
+        val emitter = MutableSharedFlow<Int>()
+        val result = activateSpecWithResult(network) { async { emitter.first() } }
+        runCurrent()
+        emitter.emit(1)
+        runCurrent()
+        assertTrue("Result eventual has not completed.", result.isCompleted)
+        assertEquals(1, result.await())
+    }
+
+    @Test
+    fun basicTransactional() = runFrpTest { network ->
+        var value: Int? = null
+        var bSource = 1
+        val emitter = network.mutableTFlow<Unit>()
+        // Sampling this transactional will increment the source count.
+        val transactional = transactionally { bSource++ }
+        measureTime {
+                activateSpecWithResult(network) {
+                        // Two different flows that sample the same transactional.
+                        (0 until 2).map {
+                            val sampled = emitter.sample(transactional) { _, v -> v }
+                            sampled.toSharedFlow()
+                        }
+                    }
+                    .forEach { backgroundScope.launch { it.collect { value = it } } }
+                runCurrent()
+            }
+            .also { println("setup: ${it.toString(DurationUnit.MILLISECONDS, 2)}") }
+
+        measureTime {
+                emitter.emit(Unit)
+                runCurrent()
+            }
+            .also { println("emit 1: ${it.toString(DurationUnit.MILLISECONDS, 2)}") }
+
+        // Even though the transactional would be sampled twice, the first result is cached.
+        assertEquals(2, bSource)
+        assertEquals(1, value)
+
+        measureTime {
+                bSource = 10
+                emitter.emit(Unit)
+                runCurrent()
+            }
+            .also { println("emit 2: ${it.toString(DurationUnit.MILLISECONDS, 2)}") }
+
+        assertEquals(11, bSource)
+        assertEquals(10, value)
+    }
+
+    @Test
+    fun diamondGraph() = runFrpTest { network ->
+        val flow = network.mutableTFlow<Int>()
+        val outFlow =
+            activateSpecWithResult(network) {
+                // map TFlow like we map Flow
+                val left = flow.map { "left" to it }.onEach { println("left: $it") }
+                val right = flow.map { "right" to it }.onEach { println("right: $it") }
+
+                // convert TFlows to TStates so that they can be combined
+                val combined =
+                    left.hold("left" to 0).combineWith(right.hold("right" to 0)) { l, r -> l to r }
+                combined.stateChanges // get TState changes
+                    .onEach { println("merged: $it") }
+                    .toSharedFlow() // convert back to Flow
+            }
+        runCurrent()
+
+        val results = mutableListOf<Pair<Pair<String, Int>, Pair<String, Int>>>()
+        backgroundScope.launch { outFlow.toCollection(results) }
+        runCurrent()
+
+        flow.emit(1)
+        runCurrent()
+
+        flow.emit(2)
+        runCurrent()
+
+        assertEquals(
+            listOf(("left" to 1) to ("right" to 1), ("left" to 2) to ("right" to 2)),
+            results,
+        )
+    }
+
+    @Test
+    fun staticNetwork() = runFrpTest { network ->
+        var finalSum: Int? = null
+
+        val intEmitter = network.mutableTFlow<Int>()
+        val sampleEmitter = network.mutableTFlow<Unit>()
+
+        activateSpecWithResult(network) {
+                val updates = intEmitter.map { a -> { b: Int -> a + b } }
+
+                val sumD =
+                    TStateLoop<Int>().apply {
+                        loopback =
+                            updates
+                                .sample(this) { f, sum -> f(sum) }
+                                .onEach { println("sum update: $it") }
+                                .hold(0)
+                    }
+                sampleEmitter
+                    .onEach { println("sampleEmitter emitted") }
+                    .sample(sumD) { _, sum -> sum }
+                    .onEach { println("sampled: $it") }
+                    .nextDeferred()
+            }
+            .let { launch { finalSum = it.await() } }
+
+        runCurrent()
+
+        (1..5).forEach { i ->
+            println("emitting: $i")
+            intEmitter.emit(i)
+            runCurrent()
+        }
+        runCurrent()
+
+        sampleEmitter.emit(Unit)
+        runCurrent()
+
+        assertEquals(15, finalSum)
+    }
+
+    @Test
+    fun recursiveDefinition() = runFrpTest { network ->
+        var wasSold = false
+        var currentAmt: Int? = null
+
+        val coin = network.mutableTFlow<Unit>()
+        val price = 50
+        val frpSpec = frpSpec {
+            val eSold = TFlowLoop<Unit>()
+
+            val eInsert =
+                coin.map {
+                    { runningTotal: Int ->
+                        println("TEST: $runningTotal - 10 = ${runningTotal - 10}")
+                        runningTotal - 10
+                    }
+                }
+
+            val eReset =
+                eSold.map {
+                    { _: Int ->
+                        println("TEST: Resetting")
+                        price
+                    }
+                }
+
+            val eUpdate = eInsert.mergeWith(eReset) { f, g -> { a -> g(f(a)) } }
+
+            val dTotal = TStateLoop<Int>()
+            dTotal.loopback = eUpdate.sample(dTotal) { f, total -> f(total) }.hold(price)
+
+            val eAmt = dTotal.stateChanges
+            val bAmt = transactionally { dTotal.sample() }
+            eSold.loopback =
+                coin
+                    .sample(bAmt) { coin, total -> coin to total }
+                    .mapMaybe { (_, total) -> maybe { guard { total <= 10 } } }
+
+            val amts = eAmt.filter { amt -> amt >= 0 }
+
+            amts.observe { currentAmt = it }
+            eSold.observe { wasSold = true }
+
+            eSold.nextDeferred()
+        }
+
+        activateSpec(network) { frpSpec.applySpec() }
+
+        runCurrent()
+
+        println()
+        println()
+        coin.emit(Unit)
+        runCurrent()
+
+        assertEquals(40, currentAmt)
+
+        println()
+        println()
+        coin.emit(Unit)
+        runCurrent()
+
+        assertEquals(30, currentAmt)
+
+        println()
+        println()
+        coin.emit(Unit)
+        runCurrent()
+
+        assertEquals(20, currentAmt)
+
+        println()
+        println()
+        coin.emit(Unit)
+        runCurrent()
+
+        assertEquals(10, currentAmt)
+        assertEquals(false, wasSold)
+
+        println()
+        println()
+        coin.emit(Unit)
+        runCurrent()
+
+        assertEquals(true, wasSold)
+        assertEquals(50, currentAmt)
+    }
+
+    @Test
+    fun promptCleanup() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        val stopper = network.mutableTFlow<Unit>()
+
+        var result: Int? = null
+
+        val flow = activateSpecWithResult(network) { emitter.takeUntil(stopper).toSharedFlow() }
+        backgroundScope.launch { flow.collect { result = it } }
+        runCurrent()
+
+        emitter.emit(2)
+        runCurrent()
+
+        assertEquals(2, result)
+
+        stopper.emit(Unit)
+        runCurrent()
+    }
+
+    @Test
+    fun switchTFlow() = runFrpTest { network ->
+        var currentSum: Int? = null
+
+        val switchHandler = network.mutableTFlow<Pair<TFlow<Int>, String>>()
+        val aHandler = network.mutableTFlow<Int>()
+        val stopHandler = network.mutableTFlow<Unit>()
+        val bHandler = network.mutableTFlow<Int>()
+
+        val sumFlow =
+            activateSpecWithResult(network) {
+                val switchE = TFlowLoop<TFlow<Int>>()
+                switchE.loopback =
+                    switchHandler.mapStateful { (intFlow, name) ->
+                        println("[onEach] Switching to: $name")
+                        val nextSwitch =
+                            switchE.skipNext().onEach { println("[onEach] switched-out") }
+                        val stopEvent =
+                            stopHandler
+                                .onEach { println("[onEach] stopped") }
+                                .mergeWith(nextSwitch) { _, b -> b }
+                        intFlow.takeUntil(stopEvent)
+                    }
+
+                val adderE: TFlow<(Int) -> Int> =
+                    switchE.hold(emptyTFlow).switch().map { a ->
+                        println("[onEach] new number $a")
+                        ({ sum: Int ->
+                            println("$a+$sum=${a + sum}")
+                            sum + a
+                        })
+                    }
+
+                val sumD = TStateLoop<Int>()
+                sumD.loopback =
+                    adderE
+                        .sample(sumD) { f, sum -> f(sum) }
+                        .onEach { println("[onEach] writing sum: $it") }
+                        .hold(0)
+                val sumE = sumD.stateChanges
+
+                sumE.toSharedFlow()
+            }
+
+        runCurrent()
+
+        backgroundScope.launch { sumFlow.collect { currentSum = it } }
+
+        runCurrent()
+
+        switchHandler.emit(aHandler to "A")
+        runCurrent()
+
+        aHandler.emit(1)
+        runCurrent()
+
+        assertEquals(1, currentSum)
+
+        aHandler.emit(2)
+        runCurrent()
+
+        assertEquals(3, currentSum)
+
+        aHandler.emit(3)
+        runCurrent()
+
+        assertEquals(6, currentSum)
+
+        aHandler.emit(4)
+        runCurrent()
+
+        assertEquals(10, currentSum)
+
+        aHandler.emit(5)
+        runCurrent()
+
+        assertEquals(15, currentSum)
+
+        switchHandler.emit(bHandler to "B")
+        runCurrent()
+
+        aHandler.emit(6)
+        runCurrent()
+
+        assertEquals(15, currentSum)
+
+        bHandler.emit(6)
+        runCurrent()
+
+        assertEquals(21, currentSum)
+
+        bHandler.emit(7)
+        runCurrent()
+
+        assertEquals(28, currentSum)
+
+        bHandler.emit(8)
+        runCurrent()
+
+        assertEquals(36, currentSum)
+
+        bHandler.emit(9)
+        runCurrent()
+
+        assertEquals(45, currentSum)
+
+        bHandler.emit(10)
+        runCurrent()
+
+        assertEquals(55, currentSum)
+
+        println()
+        println("Stopping: B")
+        stopHandler.emit(Unit) // bHandler.complete()
+        runCurrent()
+
+        bHandler.emit(20)
+        runCurrent()
+
+        assertEquals(55, currentSum)
+
+        println()
+        println("Switching to: A2")
+        switchHandler.emit(aHandler to "A2")
+        runCurrent()
+
+        println("aHandler.emit(11)")
+        aHandler.emit(11)
+        runCurrent()
+
+        assertEquals(66, currentSum)
+
+        aHandler.emit(12)
+        runCurrent()
+
+        assertEquals(78, currentSum)
+
+        aHandler.emit(13)
+        runCurrent()
+
+        assertEquals(91, currentSum)
+
+        aHandler.emit(14)
+        runCurrent()
+
+        assertEquals(105, currentSum)
+
+        aHandler.emit(15)
+        runCurrent()
+
+        assertEquals(120, currentSum)
+
+        stopHandler.emit(Unit)
+        runCurrent()
+
+        aHandler.emit(100)
+        runCurrent()
+
+        assertEquals(120, currentSum)
+    }
+
+    @Test
+    fun switchIndirect() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Unit>()
+        activateSpec(network) {
+            emptyTFlow.map { emitter.map { 1 } }.flatten().map { "$it" }.observe()
+        }
+        runCurrent()
+    }
+
+    @Test
+    fun switchInWithResult() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Unit>()
+        val out =
+            activateSpecWithResult(network) {
+                emitter.map { emitter.map { 1 } }.flatten().toSharedFlow()
+            }
+        val result = out.stateIn(backgroundScope, SharingStarted.Eagerly, null)
+        runCurrent()
+        emitter.emit(Unit)
+        runCurrent()
+        assertEquals(null, result.value)
+    }
+
+    @Test
+    fun switchInCompleted() = runFrpTest { network ->
+        val outputs = mutableListOf<Int>()
+
+        val switchAH = network.mutableTFlow<Unit>()
+        val intAH = network.mutableTFlow<Int>()
+        val stopEmitter = network.mutableTFlow<Unit>()
+
+        val top = frpSpec {
+            val intS = intAH.takeUntil(stopEmitter)
+            val switched = switchAH.map { intS }.flatten()
+            switched.toSharedFlow()
+        }
+        val flow = activateSpecWithResult(network) { top.applySpec() }
+        backgroundScope.launch { flow.collect { outputs.add(it) } }
+        runCurrent()
+
+        switchAH.emit(Unit)
+        runCurrent()
+
+        stopEmitter.emit(Unit)
+        runCurrent()
+
+        // assertEquals(0, intAH.subscriptionCount.value)
+        intAH.emit(10)
+        runCurrent()
+
+        assertEquals(true, outputs.isEmpty())
+
+        switchAH.emit(Unit)
+        runCurrent()
+
+        // assertEquals(0, intAH.subscriptionCount.value)
+        intAH.emit(10)
+        runCurrent()
+
+        assertEquals(true, outputs.isEmpty())
+    }
+
+    @Test
+    fun switchTFlow_outerCompletesFirst() = runFrpTest { network ->
+        var stepResult: Int? = null
+
+        val switchAH = network.mutableTFlow<Unit>()
+        val switchStopEmitter = network.mutableTFlow<Unit>()
+        val intStopEmitter = network.mutableTFlow<Unit>()
+        val intAH = network.mutableTFlow<Int>()
+        val flow =
+            activateSpecWithResult(network) {
+                val intS = intAH.takeUntil(intStopEmitter)
+                val switchS = switchAH.takeUntil(switchStopEmitter)
+
+                val switched = switchS.map { intS }.flatten()
+                switched.toSharedFlow()
+            }
+        backgroundScope.launch { flow.collect { stepResult = it } }
+        runCurrent()
+
+        // assertEquals(0, intAH.subscriptionCount.value)
+        intAH.emit(100)
+        runCurrent()
+
+        assertEquals(null, stepResult)
+
+        switchAH.emit(Unit)
+        runCurrent()
+
+        //            assertEquals(1, intAH.subscriptionCount.value)
+
+        intAH.emit(5)
+        runCurrent()
+
+        assertEquals(5, stepResult)
+
+        println("stop outer")
+        switchStopEmitter.emit(Unit) // switchAH.complete()
+        runCurrent()
+
+        // assertEquals(1, intAH.subscriptionCount.value)
+        // assertEquals(0, switchAH.subscriptionCount.value)
+
+        intAH.emit(10)
+        runCurrent()
+
+        assertEquals(10, stepResult)
+
+        println("stop inner")
+        intStopEmitter.emit(Unit) // intAH.complete()
+        runCurrent()
+
+        // assertEquals(just(10), network.await())
+    }
+
+    @Test
+    fun mapTFlow() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        var stepResult: Int? = null
+
+        val flow =
+            activateSpecWithResult(network) {
+                val mappedS = emitter.map { it * it }
+                mappedS.toSharedFlow()
+            }
+
+        backgroundScope.launch { flow.collect { stepResult = it } }
+        runCurrent()
+
+        emitter.emit(1)
+        runCurrent()
+
+        assertEquals(1, stepResult)
+
+        emitter.emit(2)
+        runCurrent()
+
+        assertEquals(4, stepResult)
+
+        emitter.emit(10)
+        runCurrent()
+
+        assertEquals(100, stepResult)
+    }
+
+    @Test
+    fun mapTransactional() = runFrpTest { network ->
+        var doubledResult: Int? = null
+        var pullValue = 0
+        val a = transactionally { pullValue }
+        val b = transactionally { a.sample() * 2 }
+        val emitter = network.mutableTFlow<Unit>()
+        val flow =
+            activateSpecWithResult(network) {
+                val sampleB = emitter.sample(b) { _, b -> b }
+                sampleB.toSharedFlow()
+            }
+
+        backgroundScope.launch { flow.collect { doubledResult = it } }
+
+        runCurrent()
+
+        emitter.emit(Unit)
+        runCurrent()
+
+        assertEquals(0, doubledResult)
+
+        pullValue = 5
+        emitter.emit(Unit)
+        runCurrent()
+
+        assertEquals(10, doubledResult)
+    }
+
+    @Test
+    fun mapTState() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        var stepResult: Int? = null
+        val flow =
+            activateSpecWithResult(network) {
+                val state = emitter.hold(0).map { it + 2 }
+                val stateCurrent = transactionally { state.sample() }
+                val stateChanges = state.stateChanges
+                val sampleState = emitter.sample(stateCurrent) { _, b -> b }
+                val merge = stateChanges.mergeWith(sampleState) { a, b -> a + b }
+                merge.toSharedFlow()
+            }
+        backgroundScope.launch { flow.collect { stepResult = it } }
+        runCurrent()
+
+        emitter.emit(1)
+        runCurrent()
+
+        assertEquals(5, stepResult)
+
+        emitter.emit(10)
+        runCurrent()
+
+        assertEquals(15, stepResult)
+    }
+
+    @Test
+    fun partitionEither() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Either<Int, Int>>()
+        val result =
+            activateSpecWithResult(network) {
+                val (l, r) = emitter.partitionEither()
+                val pDiamond =
+                    l.map { it * 2 }
+                        .mergeWith(r.map { it * -1 }) { _, _ -> error("unexpected coincidence") }
+                pDiamond.hold(null).toStateFlow()
+            }
+        runCurrent()
+
+        emitter.emit(Left(10))
+        runCurrent()
+
+        assertEquals(20, result.value)
+
+        emitter.emit(Right(30))
+        runCurrent()
+
+        assertEquals(-30, result.value)
+    }
+
+    @Test
+    fun accumTState() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Int>()
+        val sampler = network.mutableTFlow<Unit>()
+        var stepResult: Int? = null
+        val flow =
+            activateSpecWithResult(network) {
+                val sumState = emitter.map { a -> { b: Int -> a + b } }.fold(0) { f, a -> f(a) }
+
+                sumState.stateChanges
+                    .mergeWith(sampler.sample(sumState) { _, sum -> sum }) { _, _ ->
+                        error("Unexpected coincidence")
+                    }
+                    .toSharedFlow()
+            }
+
+        backgroundScope.launch { flow.collect { stepResult = it } }
+        runCurrent()
+
+        emitter.emit(5)
+        runCurrent()
+        assertEquals(5, stepResult)
+
+        emitter.emit(10)
+        runCurrent()
+        assertEquals(15, stepResult)
+
+        sampler.emit(Unit)
+        runCurrent()
+        assertEquals(15, stepResult)
+    }
+
+    @Test
+    fun mergeTFlows() = runFrpTest { network ->
+        val first = network.mutableTFlow<Int>()
+        val stopFirst = network.mutableTFlow<Unit>()
+        val second = network.mutableTFlow<Int>()
+        val stopSecond = network.mutableTFlow<Unit>()
+        var stepResult: Int? = null
+
+        val flow: SharedFlow<Int>
+        val setupDuration = measureTime {
+            flow =
+                activateSpecWithResult(network) {
+                    val firstS = first.takeUntil(stopFirst)
+                    val secondS = second.takeUntil(stopSecond)
+                    val mergedS =
+                        firstS.mergeWith(secondS) { _, _ -> error("Unexpected coincidence") }
+                    mergedS.toSharedFlow()
+                    // mergedS.last("onComplete")
+                }
+            backgroundScope.launch { flow.collect { stepResult = it } }
+            runCurrent()
+        }
+
+        //            assertEquals(1, first.subscriptionCount.value)
+        //            assertEquals(1, second.subscriptionCount.value)
+
+        val firstEmitDuration = measureTime {
+            first.emit(1)
+            runCurrent()
+        }
+
+        assertEquals(1, stepResult)
+
+        val secondEmitDuration = measureTime {
+            second.emit(2)
+            runCurrent()
+        }
+
+        assertEquals(2, stepResult)
+
+        val stopFirstDuration = measureTime {
+            stopFirst.emit(Unit)
+            runCurrent()
+        }
+
+        // assertEquals(0, first.subscriptionCount.value)
+        val testDeadEmitFirstDuration = measureTime {
+            first.emit(10)
+            runCurrent()
+        }
+
+        assertEquals(2, stepResult)
+
+        //            assertEquals(1, second.subscriptionCount.value)
+
+        val secondEmitDuration2 = measureTime {
+            second.emit(3)
+            runCurrent()
+        }
+
+        assertEquals(3, stepResult)
+
+        val stopSecondDuration = measureTime {
+            stopSecond.emit(Unit)
+            runCurrent()
+        }
+
+        // assertEquals(0, second.subscriptionCount.value)
+        val testDeadEmitSecondDuration = measureTime {
+            second.emit(10)
+            runCurrent()
+        }
+
+        assertEquals(3, stepResult)
+
+        println(
+            """
+                setupDuration: ${setupDuration.toString(DurationUnit.MILLISECONDS, 2)}
+                firstEmitDuration: ${firstEmitDuration.toString(DurationUnit.MILLISECONDS, 2)}
+                secondEmitDuration: ${secondEmitDuration.toString(DurationUnit.MILLISECONDS, 2)}
+                stopFirstDuration: ${stopFirstDuration.toString(DurationUnit.MILLISECONDS, 2)}
+                testDeadEmitFirstDuration: ${
+                    testDeadEmitFirstDuration.toString(
+                        DurationUnit.MILLISECONDS,
+                        2,
+                    )
+                }
+                secondEmitDuration2: ${secondEmitDuration2.toString(DurationUnit.MILLISECONDS, 2)}
+                stopSecondDuration: ${stopSecondDuration.toString(DurationUnit.MILLISECONDS, 2)}
+                testDeadEmitSecondDuration: ${
+                    testDeadEmitSecondDuration.toString(
+                        DurationUnit.MILLISECONDS,
+                        2,
+                    )
+                }
+            """
+                .trimIndent()
+        )
+    }
+
+    @Test
+    fun sampleCancel() = runFrpTest { network ->
+        val updater = network.mutableTFlow<Int>()
+        val stopUpdater = network.mutableTFlow<Unit>()
+        val sampler = network.mutableTFlow<Unit>()
+        val stopSampler = network.mutableTFlow<Unit>()
+        var stepResult: Int? = null
+        val flow =
+            activateSpecWithResult(network) {
+                val stopSamplerFirst = stopSampler
+                val samplerS = sampler.takeUntil(stopSamplerFirst)
+                val stopUpdaterFirst = stopUpdater
+                val updaterS = updater.takeUntil(stopUpdaterFirst)
+                val sampledS = samplerS.sample(updaterS.hold(0)) { _, b -> b }
+                sampledS.toSharedFlow()
+            }
+
+        backgroundScope.launch { flow.collect { stepResult = it } }
+        runCurrent()
+
+        updater.emit(1)
+        runCurrent()
+
+        sampler.emit(Unit)
+        runCurrent()
+
+        assertEquals(1, stepResult)
+
+        stopSampler.emit(Unit)
+        runCurrent()
+
+        // assertEquals(0, updater.subscriptionCount.value)
+        // assertEquals(0, sampler.subscriptionCount.value)
+        updater.emit(10)
+        runCurrent()
+
+        sampler.emit(Unit)
+        runCurrent()
+
+        assertEquals(1, stepResult)
+    }
+
+    @Test
+    fun combineStates_differentUpstreams() = runFrpTest { network ->
+        val a = network.mutableTFlow<Int>()
+        val b = network.mutableTFlow<Int>()
+        var observed: Pair<Int, Int>? = null
+        val tState =
+            activateSpecWithResult(network) {
+                val state = combine(a.hold(0), b.hold(0)) { a, b -> Pair(a, b) }
+                state.stateChanges.observe { observed = it }
+                state
+            }
+        assertEquals(0 to 0, network.transact { tState.sample() })
+        assertEquals(null, observed)
+        a.emit(5)
+        assertEquals(5 to 0, observed)
+        assertEquals(5 to 0, network.transact { tState.sample() })
+        b.emit(3)
+        assertEquals(5 to 3, observed)
+        assertEquals(5 to 3, network.transact { tState.sample() })
+    }
+
+    @Test
+    fun sampleCombinedStates() = runFrpTest { network ->
+        val updater = network.mutableTFlow<Int>()
+        val emitter = network.mutableTFlow<Unit>()
+
+        val result =
+            activateSpecWithResult(network) {
+                val bA = updater.map { it * 2 }.hold(0)
+                val bB = updater.hold(0)
+                val combineD: TState<Pair<Int, Int>> = bA.combineWith(bB) { a, b -> a to b }
+                val sampleS = emitter.sample(combineD) { _, b -> b }
+                sampleS.nextDeferred()
+            }
+        println("launching")
+        runCurrent()
+
+        println("emitting update")
+        updater.emit(10)
+        runCurrent()
+
+        println("emitting sampler")
+        emitter.emit(Unit)
+        runCurrent()
+
+        println("asserting")
+        assertEquals(20 to 10, result.await())
+    }
+
+    @Test
+    fun switchMapPromptly() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Unit>()
+        val result =
+            activateSpecWithResult(network) {
+                emitter
+                    .map { emitter.map { 1 }.map { it + 1 }.map { it * 2 } }
+                    .hold(emptyTFlow)
+                    .switchPromptly()
+                    .nextDeferred()
+            }
+        runCurrent()
+
+        emitter.emit(Unit)
+        runCurrent()
+
+        assertTrue("Not complete", result.isCompleted)
+        assertEquals(4, result.await())
+    }
+
+    @Test
+    fun switchDeeper() = runFrpTest { network ->
+        val emitter = network.mutableTFlow<Unit>()
+        val e2 = network.mutableTFlow<Unit>()
+        val result =
+            activateSpecWithResult(network) {
+                val tres =
+                    merge(e2.map { 1 }, e2.map { 2 }, transformCoincidence = { a, b -> a + b })
+                tres.observeBuild()
+                val switch = emitter.map { tres }.flatten()
+                merge(switch, e2.map { null }, transformCoincidence = { a, _ -> a })
+                    .filterNotNull()
+                    .nextDeferred()
+            }
+        runCurrent()
+
+        emitter.emit(Unit)
+        runCurrent()
+
+        e2.emit(Unit)
+        runCurrent()
+
+        assertTrue("Not complete", result.isCompleted)
+        assertEquals(3, result.await())
+    }
+
+    @Test
+    fun recursionBasic() = runFrpTest { network ->
+        val add1 = network.mutableTFlow<Unit>()
+        val sub1 = network.mutableTFlow<Unit>()
+        val stepResult: StateFlow<Int> =
+            activateSpecWithResult(network) {
+                val dSum = TStateLoop<Int>()
+                val sAdd1 = add1.sample(dSum) { _, sum -> sum + 1 }
+                val sMinus1 = sub1.sample(dSum) { _, sum -> sum - 1 }
+                dSum.loopback = sAdd1.mergeWith(sMinus1) { a, _ -> a }.hold(0)
+                dSum.toStateFlow()
+            }
+        runCurrent()
+
+        add1.emit(Unit)
+        runCurrent()
+
+        assertEquals(1, stepResult.value)
+
+        add1.emit(Unit)
+        runCurrent()
+
+        assertEquals(2, stepResult.value)
+
+        sub1.emit(Unit)
+        runCurrent()
+
+        assertEquals(1, stepResult.value)
+    }
+
+    @Test
+    fun recursiveTState() = runFrpTest { network ->
+        val e = network.mutableTFlow<Unit>()
+        var changes = 0
+        val state =
+            activateSpecWithResult(network) {
+                val s = TFlowLoop<Unit>()
+                val deferred = s.map { tStateOf(null) }
+                val e3 = e.map { tStateOf(Unit) }
+                val flattened = e3.mergeWith(deferred) { a, _ -> a }.hold(tStateOf(null)).flatten()
+                s.loopback = emptyTFlow
+                flattened.toStateFlow()
+            }
+
+        backgroundScope.launch { state.collect { changes++ } }
+        runCurrent()
+    }
+
+    @Test
+    fun fanOut() = runFrpTest { network ->
+        val e = network.mutableTFlow<Map<String, Int>>()
+        val (fooFlow, barFlow) =
+            activateSpecWithResult(network) {
+                val selector = e.groupByKey()
+                val foos = selector.eventsForKey("foo")
+                val bars = selector.eventsForKey("bar")
+                foos.toSharedFlow() to bars.toSharedFlow()
+            }
+        val stateFlow = fooFlow.stateIn(backgroundScope, SharingStarted.Eagerly, null)
+        backgroundScope.launch { barFlow.collect { error("unexpected bar") } }
+        runCurrent()
+
+        assertEquals(null, stateFlow.value)
+
+        e.emit(mapOf("foo" to 1))
+        runCurrent()
+
+        assertEquals(1, stateFlow.value)
+    }
+
+    @Test
+    fun fanOutLateSubscribe() = runFrpTest { network ->
+        val e = network.mutableTFlow<Map<String, Int>>()
+        val barFlow =
+            activateSpecWithResult(network) {
+                val selector = e.groupByKey()
+                selector
+                    .eventsForKey("foo")
+                    .map { selector.eventsForKey("bar") }
+                    .hold(emptyTFlow)
+                    .switchPromptly()
+                    .toSharedFlow()
+            }
+        val stateFlow = barFlow.stateIn(backgroundScope, SharingStarted.Eagerly, null)
+        runCurrent()
+
+        assertEquals(null, stateFlow.value)
+
+        e.emit(mapOf("foo" to 0, "bar" to 1))
+        runCurrent()
+
+        assertEquals(1, stateFlow.value)
+    }
+
+    @Test
+    fun inputFlowCompleted() = runFrpTest { network ->
+        val results = mutableListOf<Int>()
+        val e = network.mutableTFlow<Int>()
+        activateSpec(network) { e.nextOnly().observe { results.add(it) } }
+        runCurrent()
+
+        e.emit(10)
+        runCurrent()
+
+        assertEquals(listOf(10), results)
+
+        e.emit(20)
+        runCurrent()
+        assertEquals(listOf(10), results)
+    }
+
+    @Test
+    fun fanOutThenMergeIncrementally() = runFrpTest { network ->
+        // A tflow of group updates, where a group is a tflow of child updates, where a child is a
+        // stateflow
+        val e = network.mutableTFlow<Map<Int, Maybe<TFlow<Map<Int, Maybe<StateFlow<String>>>>>>>()
+        println("fanOutMergeInc START")
+        val state =
+            activateSpecWithResult(network) {
+                // Convert nested Flows to nested TFlow/TState
+                val emitter: TFlow<Map<Int, Maybe<TFlow<Map<Int, Maybe<TState<String>>>>>>> =
+                    e.mapBuild { m ->
+                        m.mapValues { (_, mFlow) ->
+                            mFlow.map {
+                                it.mapBuild { m2 ->
+                                    m2.mapValues { (_, mState) ->
+                                        mState.map { stateFlow -> stateFlow.toTState() }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                // Accumulate all of our updates into a single TState
+                val accState: TState<Map<Int, Map<Int, String>>> =
+                    emitter
+                        .mapStateful {
+                            changeMap: Map<Int, Maybe<TFlow<Map<Int, Maybe<TState<String>>>>>> ->
+                            changeMap.mapValues { (groupId, mGroupChanges) ->
+                                mGroupChanges.map {
+                                    groupChanges: TFlow<Map<Int, Maybe<TState<String>>>> ->
+                                    // New group
+                                    val childChangeById = groupChanges.groupByKey()
+                                    val map: TFlow<Map<Int, Maybe<TFlow<Maybe<TState<String>>>>>> =
+                                        groupChanges.mapStateful {
+                                            gChangeMap: Map<Int, Maybe<TState<String>>> ->
+                                            gChangeMap.mapValues { (childId, mChild) ->
+                                                mChild.map { child: TState<String> ->
+                                                    println("new child $childId in the house")
+                                                    // New child
+                                                    val eRemoved =
+                                                        childChangeById
+                                                            .eventsForKey(childId)
+                                                            .filter { it === None }
+                                                            .nextOnly()
+
+                                                    val addChild: TFlow<Maybe<TState<String>>> =
+                                                        now.map { mChild }
+                                                            .onEach {
+                                                                println(
+                                                                    "addChild (groupId=$groupId, childId=$childId) ${child.sample()}"
+                                                                )
+                                                            }
+
+                                                    val removeChild: TFlow<Maybe<TState<String>>> =
+                                                        eRemoved
+                                                            .onEach {
+                                                                println(
+                                                                    "removeChild (groupId=$groupId, childId=$childId)"
+                                                                )
+                                                            }
+                                                            .map { none() }
+
+                                                    addChild.mergeWith(removeChild) { _, _ ->
+                                                        error("unexpected coincidence")
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    val mergeIncrementally: TFlow<Map<Int, Maybe<TState<String>>>> =
+                                        map.onEach { println("merge patch: $it") }
+                                            .mergeIncrementallyPromptly()
+                                    mergeIncrementally
+                                        .onEach { println("patch: $it") }
+                                        .foldMapIncrementally()
+                                        .flatMap { it.combineValues() }
+                                }
+                            }
+                        }
+                        .foldMapIncrementally()
+                        .flatMap { it.combineValues() }
+
+                accState.toStateFlow()
+            }
+        runCurrent()
+
+        assertEquals(emptyMap(), state.value)
+
+        val emitter2 = network.mutableTFlow<Map<Int, Maybe<StateFlow<String>>>>()
+        println()
+        println("init outer 0")
+        e.emit(mapOf(0 to just(emitter2.onEach { println("emitter2 emit: $it") })))
+        runCurrent()
+
+        assertEquals(mapOf(0 to emptyMap()), state.value)
+
+        println()
+        println("init inner 10")
+        emitter2.emit(mapOf(10 to just(MutableStateFlow("(0, 10)"))))
+        runCurrent()
+
+        assertEquals(mapOf(0 to mapOf(10 to "(0, 10)")), state.value)
+
+        // replace
+        println()
+        println("replace inner 10")
+        emitter2.emit(mapOf(10 to just(MutableStateFlow("(1, 10)"))))
+        runCurrent()
+
+        assertEquals(mapOf(0 to mapOf(10 to "(1, 10)")), state.value)
+
+        // remove
+        emitter2.emit(mapOf(10 to none()))
+        runCurrent()
+
+        assertEquals(mapOf(0 to emptyMap()), state.value)
+
+        // add again
+        emitter2.emit(mapOf(10 to just(MutableStateFlow("(2, 10)"))))
+        runCurrent()
+
+        assertEquals(mapOf(0 to mapOf(10 to "(2, 10)")), state.value)
+
+        // batch update
+        emitter2.emit(
+            mapOf(
+                10 to none(),
+                11 to just(MutableStateFlow("(0, 11)")),
+                12 to just(MutableStateFlow("(0, 12)")),
+            )
+        )
+        runCurrent()
+
+        assertEquals(mapOf(0 to mapOf(11 to "(0, 11)", 12 to "(0, 12)")), state.value)
+    }
+
+    @Test
+    fun applyLatestNetworkChanges() = runFrpTest { network ->
+        val newCount = network.mutableTFlow<FrpSpec<Flow<Int>>>()
+        val flowOfFlows: Flow<Flow<Int>> =
+            activateSpecWithResult(network) { newCount.applyLatestSpec().toSharedFlow() }
+        runCurrent()
+
+        val incCount = network.mutableTFlow<Unit>()
+        fun newFlow(): FrpSpec<SharedFlow<Int>> = frpSpec {
+            launchEffect {
+                try {
+                    println("new flow!")
+                    awaitCancellation()
+                } finally {
+                    println("cancelling old flow")
+                }
+            }
+            lateinit var count: TState<Int>
+            count =
+                incCount
+                    .onEach { println("incrementing ${count.sample()}") }
+                    .fold(0) { _, c -> c + 1 }
+            count.stateChanges.toSharedFlow()
+        }
+
+        var outerCount = 0
+        val lastFlows: StateFlow<Pair<StateFlow<Int?>, StateFlow<Int?>>> =
+            flowOfFlows
+                .map { it.stateIn(backgroundScope, SharingStarted.Eagerly, null) }
+                .pairwise(MutableStateFlow(null))
+                .onEach { outerCount++ }
+                .stateIn(
+                    backgroundScope,
+                    SharingStarted.Eagerly,
+                    MutableStateFlow(null) to MutableStateFlow(null),
+                )
+
+        runCurrent()
+
+        newCount.emit(newFlow())
+        runCurrent()
+
+        assertEquals(1, outerCount)
+        //        assertEquals(1, incCount.subscriptionCount)
+        assertNull(lastFlows.value.second.value)
+
+        incCount.emit(Unit)
+        runCurrent()
+
+        println("checking")
+        assertEquals(1, lastFlows.value.second.value)
+
+        incCount.emit(Unit)
+        runCurrent()
+
+        assertEquals(2, lastFlows.value.second.value)
+
+        newCount.emit(newFlow())
+        runCurrent()
+        incCount.emit(Unit)
+        runCurrent()
+
+        // verify old flow is not getting updates
+        assertEquals(2, lastFlows.value.first.value)
+        // but the new one is
+        assertEquals(1, lastFlows.value.second.value)
+    }
+
+    @Test
+    fun effect() = runFrpTest { network ->
+        val input = network.mutableTFlow<Unit>()
+        var effectRunning = false
+        var count = 0
+        activateSpec(network) {
+            val j = launchEffect {
+                effectRunning = true
+                try {
+                    awaitCancellation()
+                } finally {
+                    effectRunning = false
+                }
+            }
+            merge(emptyTFlow, input.nextOnly()).observe {
+                count++
+                j.cancel()
+            }
+        }
+        runCurrent()
+        assertEquals(true, effectRunning)
+        assertEquals(0, count)
+
+        println("1")
+        input.emit(Unit)
+        assertEquals(false, effectRunning)
+        assertEquals(1, count)
+
+        println("2")
+        input.emit(Unit)
+        assertEquals(1, count)
+        println("3")
+        input.emit(Unit)
+        assertEquals(1, count)
+    }
+
+    private fun runFrpTest(
+        timeout: Duration = 3.seconds,
+        block: suspend TestScope.(FrpNetwork) -> Unit,
+    ) {
+        runTest(timeout = timeout) {
+            val network = backgroundScope.newFrpNetwork()
+            runCurrent()
+            block(network)
+        }
+    }
+
+    private fun TestScope.activateSpec(network: FrpNetwork, spec: FrpSpec<*>) =
+        backgroundScope.launch { network.activateSpec(spec) }
+
+    private suspend fun <R> TestScope.activateSpecWithResult(
+        network: FrpNetwork,
+        spec: FrpSpec<R>,
+    ): R =
+        CompletableDeferred<R>()
+            .apply { activateSpec(network) { complete(spec.applySpec()) } }
+            .await()
+}
+
+private fun <T> assertEquals(expected: T, actual: T) =
+    org.junit.Assert.assertEquals(expected, actual)
+
+private fun <A> Flow<A>.pairwise(init: A): Flow<Pair<A, A>> = flow {
+    var prev = init
+    collect {
+        emit(prev to it)
+        prev = it
+    }
+}