From b351a81c8fa05891c8e15a6fcec2c59375563ce1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 29 May 2026 09:18:36 -0400 Subject: [PATCH] fix(adminui): preserve un-edited Modbus tag fields across edit (review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture the original ModbusTagDefinition as _source in ModbusTagRow and rewrite ToDefinition() to use 'with {}', so StringByteOrder, ArrayCount, Deadband, UnitId, and CoalesceProhibited survive a load→edit→save cycle. --- .../Clusters/Drivers/ModbusDriverPage.razor | 28 +++++++++++++++---- .../ModbusDriverPageFormSerializationTests.cs | 19 +++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/Drivers/ModbusDriverPage.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/Drivers/ModbusDriverPage.razor index 94e44026..5f58e258 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/Drivers/ModbusDriverPage.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/Drivers/ModbusDriverPage.razor @@ -490,20 +490,36 @@ else public int StringLength { get; set; } public bool WriteIdempotent { get; set; } - public ModbusTagRow Clone() => (ModbusTagRow)MemberwiseClone(); + // Original record (null for newly-added rows). Preserves fields the editor doesn't expose + // (StringByteOrder, ArrayCount, Deadband, UnitId, CoalesceProhibited) across a load→save. + private ModbusTagDefinition? _source; + + public ModbusTagRow Clone() => (ModbusTagRow)MemberwiseClone(); // _source is an immutable record ref — safe to share public static ModbusTagRow FromDefinition(ModbusTagDefinition d) => new() { Name = d.Name, Region = d.Region, Address = d.Address, DataType = d.DataType, Writable = d.Writable, ByteOrder = d.ByteOrder, BitIndex = d.BitIndex, StringLength = d.StringLength, WriteIdempotent = d.WriteIdempotent, + _source = d, }; - public ModbusTagDefinition ToDefinition() => new( - Name: Name.Trim(), Region: Region, Address: (ushort)Math.Clamp(Address, 0, 65535), - DataType: DataType, Writable: Writable, ByteOrder: ByteOrder, - BitIndex: (byte)Math.Clamp(BitIndex, 0, 255), StringLength: (ushort)Math.Clamp(StringLength, 0, 65535), - WriteIdempotent: WriteIdempotent); + public ModbusTagDefinition ToDefinition() + { + var baseDef = _source ?? new ModbusTagDefinition(Name.Trim(), Region, 0, DataType); + return baseDef with + { + Name = Name.Trim(), + Region = Region, + Address = (ushort)Math.Clamp(Address, 0, 65535), + DataType = DataType, + Writable = Writable, + ByteOrder = ByteOrder, + BitIndex = (byte)Math.Clamp(BitIndex, 0, 255), + StringLength = (ushort)Math.Clamp(StringLength, 0, 65535), + WriteIdempotent = WriteIdempotent, + }; + } public static string? ValidateRow(ModbusTagRow row, IReadOnlyList all, int? editIndex) { diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/ModbusDriverPageFormSerializationTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/ModbusDriverPageFormSerializationTests.cs index 542e6a6a..b0777da0 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/ModbusDriverPageFormSerializationTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/ModbusDriverPageFormSerializationTests.cs @@ -151,4 +151,23 @@ public sealed class ModbusDriverPageFormSerializationTests ModbusDriverPage.ModbusTagRow.ValidateRow(new() { Name = "A" }, rows, null) .ShouldNotBeNull(); } + + [Fact] + public void ToDefinition_preserves_unedited_fields() + { + var original = new ModbusTagDefinition( + "T", ModbusRegion.HoldingRegisters, 5, ModbusDataType.Int16, + StringByteOrder: ModbusStringByteOrder.LowByteFirst, + ArrayCount: 10, Deadband: 0.5, UnitId: 3, CoalesceProhibited: true); + var row = ModbusDriverPage.ModbusTagRow.FromDefinition(original); + row.Name = "Renamed"; + + var back = row.ToDefinition(); + back.Name.ShouldBe("Renamed"); + back.UnitId.ShouldBe((byte)3); + back.ArrayCount.ShouldBe(10); + back.Deadband.ShouldBe(0.5); + back.StringByteOrder.ShouldBe(ModbusStringByteOrder.LowByteFirst); + back.CoalesceProhibited.ShouldBeTrue(); + } }