diff --git a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs index 184fa0fc52..5a4053ec35 100644 --- a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs +++ b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs @@ -313,7 +313,9 @@ public async Task CreateComplexFormComponent(ComplexFormCo return await repo.FindComplexFormComponent(addEntryComponentChange.EntityId); } - if (between is not null) + // The orderable diff sends (null, null) for singletons; skip the move so + // revisits in one sync don't bump Order via PickOrder. + if (between is { Previous: not null } or { Next: not null }) { await MoveComplexFormComponent(existing, between); } diff --git a/backend/FwLite/MiniLcm.Tests/ComplexFormComponentTestsBase.cs b/backend/FwLite/MiniLcm.Tests/ComplexFormComponentTestsBase.cs index dd123d0f34..8c32f57a7f 100644 --- a/backend/FwLite/MiniLcm.Tests/ComplexFormComponentTestsBase.cs +++ b/backend/FwLite/MiniLcm.Tests/ComplexFormComponentTestsBase.cs @@ -95,6 +95,22 @@ public async Task CreateComplexFormComponent_ReplayingReturnedObject_IsIdempoten again.Should().BeEquivalentTo(created); } + [Fact] + public async Task CreateComplexFormComponent_BetweenWithNoAnchors_DoesNotReorderExisting() + { + // Sync can revisit the same CFC with (null, null) — once via SyncComplexForms, + // again via SyncComplexFormComponents. The second call must not reorder. + var first = await Api.CreateComplexFormComponent( + ComplexFormComponent.FromEntries(_complexFormEntry, _componentEntry)); + var second = await Api.CreateComplexFormComponent( + ComplexFormComponent.FromEntries(_complexFormEntry, _componentEntry), + new BetweenPosition(null, null)); + + second.Order.Should().Be(first.Order); + var entry = await Api.GetEntry(_complexFormEntryId); + entry!.Components.Should().ContainSingle().Which.Order.Should().Be(first.Order); + } + [Fact] public async Task CreateComplexFormComponent_UsingTheSameComponentWithSenseDoesNothing() {