Repository: cashapp/redwood
Pull request #464 (2022-10-18 16:20:10)

Use a value class to encapsulate a widget ID in the protocol
Refs #418 

JakeWharton: The other ones are a bit harder, but also want to rename some shit in the protocol first as it's currently not super symmetric 

Patch:
diff --git a/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolApplier.kt b/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolApplier.kt
index eb22af0b9..bf3117065 100644
--- a/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolApplier.kt
+++ b/redwood-protocol-compose/src/commonMain/kotlin/app/cash/redwood/protocol/compose/ProtocolApplier.kt
@@ -23,8 +23,8 @@ import app.cash.redwood.LayoutModifier
 import app.cash.redwood.compose.RedwoodApplier
 import app.cash.redwood.protocol.ChildrenDiff
 import app.cash.redwood.protocol.ChildrenDiff.Companion.RootChildrenTag
-import app.cash.redwood.protocol.ChildrenDiff.Companion.RootId
 import app.cash.redwood.protocol.Event
+import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.LayoutModifiers
 import app.cash.redwood.protocol.PropertyDiff
 
@@ -67,7 +67,7 @@ private sealed class DiffProducingChildrenWidget(
   class Intermediate(tag: UInt) : DiffProducingChildrenWidget(tag)
   class Root : DiffProducingChildrenWidget(RootChildrenTag) {
     init {
-      id = RootId
+      id = Id.Root
     }
   }
 
@@ -90,7 +90,7 @@ public abstract class AbstractDiffProducingWidget(
   override val value: Nothing
     get() = throw AssertionError()
 
-  public var id: ULong = ULong.MAX_VALUE
+  public var id: Id = Id(ULong.MAX_VALUE)
     internal set
 
   @Suppress("PropertyName") // Avoiding potential collision with subtype properties.
@@ -149,7 +149,7 @@ internal class ProtocolApplier(
   private val diffAppender: DiffAppender,
 ) : AbstractApplier<AbstractDiffProducingWidget>(DiffProducingChildrenWidget.Root()),
   RedwoodApplier<DiffProducingWidget.Factory> {
-  private var nextId = RootId + 1U
+  private var nextId = Id.Root.next()
   internal val nodes = mutableMapOf(root.id to root)
 
   override fun onEndChanges() {
@@ -169,7 +169,9 @@ internal class ProtocolApplier(
     } else {
       val current = current as DiffProducingChildrenWidget
 
-      val id = nextId++
+      val id = nextId
+      nextId = id.next()
+
       instance.id = id
       instance._diffAppender = diffAppender
 
@@ -190,7 +192,7 @@ internal class ProtocolApplier(
     // TODO We should not have to track this and send it as part of the protocol.
     //  Ideally this would be entirely encapsulated on the display-side with additional bookkeeping.
     //  For now, we track it here and send it in the protocol as a simple solution.
-    val removedIds = ArrayList<ULong>(count)
+    val removedIds = ArrayList<Id>(count)
     for (i in index until index + count) {
       removedIds.add(children[i].id)
     }
@@ -215,3 +217,5 @@ internal class ProtocolApplier(
     diffAppender.append(ChildrenDiff.Clear)
   }
 }
+
+private fun Id.next(): Id = Id(value + 1UL)
diff --git a/redwood-protocol-widget/src/commonMain/kotlin/app/cash/redwood/protocol/widget/ProtocolDisplay.kt b/redwood-protocol-widget/src/commonMain/kotlin/app/cash/redwood/protocol/widget/ProtocolDisplay.kt
index 707bbdbdf..35bf40ef4 100644
--- a/redwood-protocol-widget/src/commonMain/kotlin/app/cash/redwood/protocol/widget/ProtocolDisplay.kt
+++ b/redwood-protocol-widget/src/commonMain/kotlin/app/cash/redwood/protocol/widget/ProtocolDisplay.kt
@@ -18,10 +18,10 @@ package app.cash.redwood.protocol.widget
 import app.cash.redwood.LayoutModifier
 import app.cash.redwood.protocol.ChildrenDiff
 import app.cash.redwood.protocol.ChildrenDiff.Companion.RootChildrenTag
-import app.cash.redwood.protocol.ChildrenDiff.Companion.RootId
 import app.cash.redwood.protocol.Diff
 import app.cash.redwood.protocol.DiffSink
 import app.cash.redwood.protocol.EventSink
+import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.PropertyDiff
 import app.cash.redwood.widget.Widget
 import kotlinx.serialization.json.JsonArray
@@ -32,7 +32,7 @@ public class ProtocolDisplay<T : Any>(
   private val eventSink: EventSink,
 ) : DiffSink {
   private val root: DiffConsumingWidget<T> = ProtocolDisplayRoot(container)
-  private val widgets = mutableMapOf(RootId to root)
+  private val widgets = mutableMapOf(Id.Root to root)
 
   override fun sendDiff(diff: Diff) {
     for (childrenDiff in diff.childrenDiffs) {
@@ -59,7 +59,7 @@ public class ProtocolDisplay<T : Any>(
         ChildrenDiff.Clear -> {
           children.clear()
           widgets.clear()
-          widgets[RootId] = root
+          widgets[Id.Root] = root
         }
       }
     }
diff --git a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
index 44cb8d33f..b9360ded1 100644
--- a/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
+++ b/redwood-protocol/src/commonMain/kotlin/app/cash/redwood/protocol/protocol.kt
@@ -15,6 +15,7 @@
  */
 package app.cash.redwood.protocol
 
+import kotlin.jvm.JvmInline
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.JsonArray
@@ -29,10 +30,19 @@ public fun interface DiffSink {
   public fun sendDiff(diff: Diff)
 }
 
+/** Identifies a widget instance. */
+@JvmInline
+@Serializable
+public value class Id(public val value: ULong) {
+  public companion object {
+    public val Root: Id = Id(0UL)
+  }
+}
+
 @Serializable
 public data class Event(
   /** Identifier for the widget from which this event originated. */
-  val id: ULong,
+  val id: Id,
   /** Identifies which event occurred on the widget with [id]. */
   val tag: UInt,
   val value: JsonElement = JsonNull,
@@ -48,7 +58,7 @@ public data class Diff(
 @Serializable
 public data class PropertyDiff(
   /** Identifier for the widget whose property has changed. */
-  val id: ULong,
+  val id: Id,
   /** Identifies which property changed on the widget with [id]. */
   val tag: UInt,
   val value: JsonElement = JsonNull,
@@ -57,7 +67,7 @@ public data class PropertyDiff(
 @Serializable
 public data class LayoutModifiers(
   /** Identifier for the widget whose layout modifier has changed. */
-  val id: ULong,
+  val id: Id,
   /**
    * Array of layout modifiers. Each element of this array is itself a two-element array of the
    * layout modifier tag and then serialized value.
@@ -68,7 +78,7 @@ public data class LayoutModifiers(
 @Serializable
 public sealed class ChildrenDiff {
   /** Identifier for the widget whose children have changed. */
-  public abstract val id: ULong
+  public abstract val id: Id
 
   /** Identifies which group of children changed on the widget with [id]. */
   public abstract val tag: UInt
@@ -76,16 +86,16 @@ public sealed class ChildrenDiff {
   @Serializable
   @SerialName("clear")
   public object Clear : ChildrenDiff() {
-    override val id: ULong get() = RootId
+    override val id: Id get() = Id.Root
     override val tag: UInt get() = RootChildrenTag
   }
 
   @Serializable
   @SerialName("insert")
   public data class Insert(
-    override val id: ULong,
+    override val id: Id,
     override val tag: UInt,
-    val childId: ULong,
+    val childId: Id,
     val kind: Int,
     val index: Int,
   ) : ChildrenDiff()
@@ -93,7 +103,7 @@ public sealed class ChildrenDiff {
   @Serializable
   @SerialName("move")
   public data class Move(
-    override val id: ULong,
+    override val id: Id,
     override val tag: UInt,
     val fromIndex: Int,
     val toIndex: Int,
@@ -103,11 +113,11 @@ public sealed class ChildrenDiff {
   @Serializable
   @SerialName("remove")
   public data class Remove(
-    override val id: ULong,
+    override val id: Id,
     override val tag: UInt,
     val index: Int,
     val count: Int,
-    val removedIds: List<ULong>,
+    val removedIds: List<Id>,
   ) : ChildrenDiff() {
     init {
       require(count == removedIds.size) {
@@ -117,7 +127,6 @@ public sealed class ChildrenDiff {
   }
 
   public companion object {
-    public const val RootId: ULong = 0UL
     public const val RootChildrenTag: UInt = 1U
   }
 }


Test patch:
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
index 43625c8f8..f2bee8d66 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/DiffProducingWidgetFactoryTest.kt
@@ -18,6 +18,7 @@ package app.cash.redwood.protocol.compose
 import app.cash.redwood.LayoutModifier
 import app.cash.redwood.protocol.Diff
 import app.cash.redwood.protocol.Event
+import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.LayoutModifiers
 import app.cash.redwood.protocol.PropertyDiff
 import example.redwood.compose.DiffProducingExampleSchemaWidgetFactory
@@ -47,7 +48,7 @@ class DiffProducingWidgetFactoryTest {
     val diffProducingWidget = textInput as AbstractDiffProducingWidget
     val diffSink = RecordingDiffSink()
     val diffAppender = DiffAppender(diffSink)
-    diffProducingWidget.id = 1U
+    diffProducingWidget.id = Id(1U)
     diffProducingWidget._diffAppender = diffAppender
 
     textInput.customType(10.seconds)
@@ -55,7 +56,7 @@ class DiffProducingWidgetFactoryTest {
 
     val expected = Diff(
       propertyDiffs = listOf(
-        PropertyDiff(1U, 2U, JsonPrimitive("PT10S")),
+        PropertyDiff(Id(1U), 2U, JsonPrimitive("PT10S")),
       ),
     )
     assertEquals(expected, diffSink.diffs.single())
@@ -73,7 +74,7 @@ class DiffProducingWidgetFactoryTest {
     val diffProducingWidget = button as AbstractDiffProducingWidget
     val diffSink = RecordingDiffSink()
     val diffAppender = DiffAppender(diffSink)
-    diffProducingWidget.id = 1U
+    diffProducingWidget.id = Id(1U)
     diffProducingWidget._diffAppender = diffAppender
 
     button.layoutModifiers = LayoutModifier.customType(10.seconds)
@@ -82,7 +83,7 @@ class DiffProducingWidgetFactoryTest {
     val expected = Diff(
       layoutModifiers = listOf(
         LayoutModifiers(
-          1U,
+          Id(1U),
           buildJsonArray {
             add(
               buildJsonArray {
@@ -113,7 +114,7 @@ class DiffProducingWidgetFactoryTest {
     val diffProducingWidget = button as AbstractDiffProducingWidget
     val diffSink = RecordingDiffSink()
     val diffAppender = DiffAppender(diffSink)
-    diffProducingWidget.id = 1U
+    diffProducingWidget.id = Id(1U)
     diffProducingWidget._diffAppender = diffAppender
 
     button.layoutModifiers = LayoutModifier.customTypeWithDefault(10.seconds, "sup")
@@ -122,7 +123,7 @@ class DiffProducingWidgetFactoryTest {
     val expected = Diff(
       layoutModifiers = listOf(
         LayoutModifiers(
-          1U,
+          Id(1U),
           buildJsonArray {
             add(
               buildJsonArray {
@@ -158,7 +159,7 @@ class DiffProducingWidgetFactoryTest {
       argument = it
     }
 
-    diffProducingWidget.sendEvent(Event(1U, 4U, JsonPrimitive("PT10S")))
+    diffProducingWidget.sendEvent(Event(Id(1U), 4U, JsonPrimitive("PT10S")))
 
     assertEquals(10.seconds, argument)
   }
@@ -167,7 +168,7 @@ class DiffProducingWidgetFactoryTest {
     val factory = DiffProducingExampleSchemaWidgetFactory()
     val button = factory.Button() as AbstractDiffProducingWidget
 
-    val event = Event(1U, 3456543U)
+    val event = Event(Id(1U), 3456543U)
     val t = assertFailsWith<IllegalArgumentException> {
       button.sendEvent(event)
     }
@@ -180,7 +181,7 @@ class DiffProducingWidgetFactoryTest {
     val factory = DiffProducingExampleSchemaWidgetFactory(mismatchHandler = handler)
     val button = factory.Button() as AbstractDiffProducingWidget
 
-    button.sendEvent(Event(1U, 3456543U))
+    button.sendEvent(Event(Id(1U), 3456543U))
 
     assertEquals("Unknown event 3456543 for 4", handler.events.single())
   }
diff --git a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
index 2903cc87b..5dba15ec6 100644
--- a/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
+++ b/redwood-protocol-compose/src/commonTest/kotlin/app/cash/redwood/protocol/compose/ProtocolTest.kt
@@ -22,9 +22,9 @@ import androidx.compose.runtime.setValue
 import app.cash.redwood.compose.WidgetVersion
 import app.cash.redwood.protocol.ChildrenDiff
 import app.cash.redwood.protocol.ChildrenDiff.Companion.RootChildrenTag
-import app.cash.redwood.protocol.ChildrenDiff.Companion.RootId
 import app.cash.redwood.protocol.Diff
 import app.cash.redwood.protocol.Event
+import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.LayoutModifiers
 import app.cash.redwood.protocol.PropertyDiff
 import example.redwood.compose.Button
@@ -84,20 +84,20 @@ class ProtocolTest {
     assertEquals(
       Diff(
         childrenDiffs = listOf(
-          ChildrenDiff.Insert(RootId, RootChildrenTag, 1U, 1 /* row */, 0),
-          ChildrenDiff.Insert(1U, 1U, 2U, 3 /* text */, 0),
-          ChildrenDiff.Insert(1U, 1U, 3U, 1 /* row */, 1),
-          ChildrenDiff.Insert(3U, 1U, 4U, 3 /* text */, 0),
+          ChildrenDiff.Insert(Id.Root, RootChildrenTag, Id(1U), 1 /* row */, 0),
+          ChildrenDiff.Insert(Id(1U), 1U, Id(2U), 3 /* text */, 0),
+          ChildrenDiff.Insert(Id(1U), 1U, Id(3U), 1 /* row */, 1),
+          ChildrenDiff.Insert(Id(3U), 1U, Id(4U), 3 /* text */, 0),
         ),
         layoutModifiers = listOf(
-          LayoutModifiers(1U, JsonArray(listOf())),
-          LayoutModifiers(2U, JsonArray(listOf())),
-          LayoutModifiers(3U, JsonArray(listOf())),
-          LayoutModifiers(4U, JsonArray(listOf())),
+          LayoutModifiers(Id(1U), JsonArray(listOf())),
+          LayoutModifiers(Id(2U), JsonArray(listOf())),
+          LayoutModifiers(Id(3U), JsonArray(listOf())),
+          LayoutModifiers(Id(4U), JsonArray(listOf())),
         ),
         propertyDiffs = listOf(
-          PropertyDiff(2U, 1U /* text */, JsonPrimitive("hey")),
-          PropertyDiff(4U, 1U /* text */, JsonPrimitive("hello")),
+          PropertyDiff(Id(2U), 1U /* text */, JsonPrimitive("hey")),
+          PropertyDiff(Id(4U), 1U /* text */, JsonPrimitive("hello")),
         ),
       ),
       diffs.removeFirst(),
@@ -134,43 +134,43 @@ class ProtocolTest {
     assertEquals(
       Diff(
         childrenDiffs = listOf(
-          ChildrenDiff.Insert(RootId, RootChildrenTag, 1U, 4 /* button */, 0),
+          ChildrenDiff.Insert(Id.Root, RootChildrenTag, Id(1U), 4 /* button */, 0),
         ),
         layoutModifiers = listOf(
-          LayoutModifiers(1U, JsonArray(listOf())),
+          LayoutModifiers(Id(1U), JsonArray(listOf())),
         ),
         propertyDiffs = listOf(
-          PropertyDiff(1U, 1U /* text */, JsonPrimitive("state: 0")),
-          PropertyDiff(1U, 2U /* onClick */, JsonPrimitive(true)),
+          PropertyDiff(Id(1U), 1U /* text */, JsonPrimitive("state: 0")),
+          PropertyDiff(Id(1U), 2U /* onClick */, JsonPrimitive(true)),
         ),
       ),
       diffs.removeFirst(),
     )
 
     // Invoke the onClick lambda to move the state from 0 to 1.
-    composition.sendEvent(Event(1U, 2U))
+    composition.sendEvent(Event(Id(1U), 2U))
     yield() // Allow state change to be handled.
 
     clock.awaitFrame()
     assertEquals(
       Diff(
         propertyDiffs = listOf(
-          PropertyDiff(1U, 1U /* text */, JsonPrimitive("state: 1")),
+          PropertyDiff(Id(1U), 1U /* text */, JsonPrimitive("state: 1")),
         ),
       ),
       diffs.removeFirst(),
     )
 
     // Invoke the onClick lambda to move the state from 1 to 2.
-    composition.sendEvent(Event(1U, 2U))
+    composition.sendEvent(Event(Id(1U), 2U))
     yield() // Allow state change to be handled.
 
     clock.awaitFrame()
     assertEquals(
       Diff(
         propertyDiffs = listOf(
-          PropertyDiff(1U, 1U /* text */, JsonPrimitive("state: 2")),
-          PropertyDiff(1U, 2U /* text */, JsonPrimitive(false)),
+          PropertyDiff(Id(1U), 1U /* text */, JsonPrimitive("state: 2")),
+          PropertyDiff(Id(1U), 2U /* text */, JsonPrimitive(false)),
         ),
       ),
       diffs.removeFirst(),
@@ -184,7 +184,7 @@ class ProtocolTest {
     assertEquals(
       Diff(
         propertyDiffs = listOf(
-          PropertyDiff(1U, 1U /* text */, JsonPrimitive("state: 3")),
+          PropertyDiff(Id(1U), 1U /* text */, JsonPrimitive("state: 3")),
         ),
       ),
       diffs.removeFirst(),
diff --git a/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt b/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
index fe2dd9467..65f383687 100644
--- a/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
+++ b/redwood-protocol-widget/src/commonTest/kotlin/app/cash/redwood/protocol/widget/DiffConsumingWidgetFactoryTest.kt
@@ -19,6 +19,7 @@ import app.cash.redwood.LayoutModifier
 import app.cash.redwood.layout.widget.RedwoodLayoutWidgetFactory
 import app.cash.redwood.protocol.Event
 import app.cash.redwood.protocol.EventSink
+import app.cash.redwood.protocol.Id
 import app.cash.redwood.protocol.PropertyDiff
 import example.redwood.compose.accessibilityDescription
 import example.redwood.compose.customType
@@ -231,7 +232,7 @@ class DiffConsumingWidgetFactoryTest {
     val textInput = factory.create(5)!!
 
     val throwingEventSink = EventSink { error(it) }
-    textInput.apply(PropertyDiff(1U, 2U, JsonPrimitive("PT10S")), throwingEventSink)
+    textInput.apply(PropertyDiff(Id(1U), 2U, JsonPrimitive("PT10S")), throwingEventSink)
 
     assertEquals(10.seconds, recordingTextInput.customType)
   }
@@ -240,7 +241,7 @@ class DiffConsumingWidgetFactoryTest {
     val factory = DiffConsumingExampleSchemaWidgetFactory(EmptyExampleSchemaWidgetFactory())
     val button = factory.create(4) as DiffConsumingWidget<*>
 
-    val diff = PropertyDiff(1U, 345432U)
+    val diff = PropertyDiff(Id(1U), 345432U)
     val eventSink = EventSink { throw UnsupportedOperationException() }
     val t = assertFailsWith<IllegalArgumentException> {
       button.apply(diff, eventSink)
@@ -256,7 +257,7 @@ class DiffConsumingWidgetFactoryTest {
     )
     val button = factory.create(4) as DiffConsumingWidget<*>
 
-    button.apply(PropertyDiff(1U, 345432U)) { throw UnsupportedOperationException() }
+    button.apply(PropertyDiff(Id(1U), 345432U)) { throw UnsupportedOperationException() }
 
     assertEquals("Unknown property 345432 for 4", handler.events.single())
   }
@@ -277,11 +278,11 @@ class DiffConsumingWidgetFactoryTest {
     val textInput = factory.create(5)!!
 
     val eventSink = RecordingEventSink()
-    textInput.apply(PropertyDiff(1U, 4U, JsonPrimitive(true)), eventSink)
+    textInput.apply(PropertyDiff(Id(1U), 4U, JsonPrimitive(true)), eventSink)
 
     recordingTextInput.onChangeCustomType!!.invoke(10.seconds)
 
-    assertEquals(Event(1U, 4U, JsonPrimitive("PT10S")), eventSink.events.single())
+    assertEquals(Event(Id(1U), 4U, JsonPrimitive("PT10S")), eventSink.events.single())
   }
 
   open class EmptyExampleSchemaWidgetFactory : ExampleSchemaWidgetFactory<Nothing> {
diff --git a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
index 84d1b2d4e..7992242ed 100644
--- a/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
+++ b/redwood-protocol/src/commonTest/kotlin/app/cash/redwood/protocol/ProtocolTest.kt
@@ -31,13 +31,13 @@ class ProtocolTest {
   }
 
   @Test fun eventNonNullValue() {
-    val model = Event(1U, 2U, JsonPrimitive("Hello"))
+    val model = Event(Id(1U), 2U, JsonPrimitive("Hello"))
     val json = """{"id":1,"tag":2,"value":"Hello"}"""
     assertJsonRoundtrip(Event.serializer(), model, json)
   }
 
   @Test fun eventNullValue() {
-    val model = Event(1U, 2U)
+    val model = Event(Id(1U), 2U)
     val json = """{"id":1,"tag":2}"""
     assertJsonRoundtrip(Event.serializer(), model, json)
   }
@@ -46,13 +46,13 @@ class ProtocolTest {
     val model = Diff(
       childrenDiffs = listOf(
         ChildrenDiff.Clear,
-        ChildrenDiff.Insert(1U, 2U, 3U, 4, 5),
-        ChildrenDiff.Move(1U, 2U, 3, 4, 5),
-        ChildrenDiff.Remove(1U, 2U, 3, 4, listOf(5U, 6U, 7U, 8U)),
+        ChildrenDiff.Insert(Id(1U), 2U, Id(3U), 4, 5),
+        ChildrenDiff.Move(Id(1U), 2U, 3, 4, 5),
+        ChildrenDiff.Remove(Id(1U), 2U, 3, 4, listOf(Id(5U), Id(6U), Id(7U), Id(8U))),
       ),
       layoutModifiers = listOf(
         LayoutModifiers(
-          1U,
+          Id(1U),
           buildJsonArray {
             add(
               buildJsonArray {
@@ -64,8 +64,8 @@ class ProtocolTest {
         ),
       ),
       propertyDiffs = listOf(
-        PropertyDiff(1U, 2U, JsonPrimitive("Hello")),
-        PropertyDiff(1U, 2U, JsonNull),
+        PropertyDiff(Id(1U), 2U, JsonPrimitive("Hello")),
+        PropertyDiff(Id(1U), 2U, JsonNull),
       ),
     )
     val json = "" +
@@ -95,7 +95,7 @@ class ProtocolTest {
 
   @Test fun removeCountMustMatchListSize() {
     assertFailsWith<IllegalArgumentException>("Count 4 != Removed ID list size 3") {
-      ChildrenDiff.Remove(1U, 2U, 3, 4, listOf(5U, 6U, 7U))
+      ChildrenDiff.Remove(Id(1U), 2U, 3, 4, listOf(Id(5U), Id(6U), Id(7U)))
     }
   }