diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs
index b9fb2efd..6f468538 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs
@@ -78,6 +78,17 @@ public sealed record ResolvedAttribute
///
public string? DataSourceReference { get; init; }
+ ///
+ /// The data-source reference as resolved from the template chain BEFORE any
+ /// per-instance InstanceConnectionBinding.DataSourceReferenceOverride was
+ /// applied — i.e. the template/composed default the Configure page shows as the
+ /// placeholder. Equals when no override is set;
+ /// null for attributes that are not data-sourced.
+ /// Central-side presentation data only: deliberately excluded from the revision
+ /// hash (RevisionHashService projects through HashableAttribute).
+ ///
+ public string? DefaultDataSourceReference { get; init; }
+
///
/// If data-sourced: the resolved connection ID from the instance binding.
///
diff --git a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening/FlatteningService.cs b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening/FlatteningService.cs
index bf4fca32..c01e5be7 100644
--- a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening/FlatteningService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening/FlatteningService.cs
@@ -369,6 +369,15 @@ public class FlatteningService
Dictionary attributes,
IReadOnlyDictionary dataConnections)
{
+ // Capture the pre-binding default for EVERY data-sourced attribute (bound or
+ // not) so the Configure page can show the template default as a placeholder.
+ foreach (var key in attributes.Keys.ToList())
+ {
+ var attr = attributes[key];
+ if (attr.DataSourceReference is not null && attr.DefaultDataSourceReference is null)
+ attributes[key] = attr with { DefaultDataSourceReference = attr.DataSourceReference };
+ }
+
foreach (var binding in bindings)
{
if (!attributes.TryGetValue(binding.AttributeName, out var existing))
@@ -385,7 +394,8 @@ public class FlatteningService
BoundDataConnectionId = connection.Id,
BoundDataConnectionName = connection.Name,
BoundDataConnectionProtocol = connection.Protocol,
- DataSourceReference = binding.DataSourceReferenceOverride ?? existing.DataSourceReference
+ DataSourceReference = binding.DataSourceReferenceOverride ?? existing.DataSourceReference,
+ DefaultDataSourceReference = existing.DefaultDataSourceReference ?? existing.DataSourceReference
};
}
}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Flattening/DefaultDataSourceReferenceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Flattening/DefaultDataSourceReferenceTests.cs
new file mode 100644
index 00000000..1daf2b2d
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Flattening/DefaultDataSourceReferenceTests.cs
@@ -0,0 +1,81 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
+using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening;
+
+namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Flattening;
+
+///
+/// Design 2026-08-20 (grouped connection bindings): the flattened attribute
+/// carries the data-source reference as it was BEFORE the instance binding
+/// override, so the Configure page can show the template default next to the
+/// effective reference. The field is deliberately NOT part of the revision hash.
+///
+public class DefaultDataSourceReferenceTests
+{
+ [Fact]
+ public void RevisionHash_IgnoresDefaultDataSourceReference()
+ {
+ var svc = new RevisionHashService();
+ var a = new ResolvedAttribute
+ {
+ CanonicalName = "LeftMESReceiver.MoveInFlag",
+ DataType = "Boolean",
+ DataSourceReference = "MESReceiver_022.MoveInFlag",
+ BoundDataConnectionId = 1
+ };
+ var without = new FlattenedConfiguration { InstanceUniqueName = "Z28061", Attributes = [a] };
+ var with = without with { Attributes = [a with { DefaultDataSourceReference = "MoveInFlag" }] };
+
+ Assert.Equal(svc.ComputeHash(without), svc.ComputeHash(with));
+ }
+
+ [Fact]
+ public void ResolvedAttribute_DefaultDataSourceReference_IsNullByDefault()
+ {
+ var a = new ResolvedAttribute { CanonicalName = "X" };
+ Assert.Null(a.DefaultDataSourceReference);
+ }
+
+ [Fact]
+ public void Flatten_PopulatesDefault_ForBoundAndUnboundDataSourcedAttributes()
+ {
+ var template = new Template("Base") { Id = 1 };
+ template.Attributes.Add(new TemplateAttribute("MoveInFlag") { DataType = DataType.Boolean, DataSourceReference = "MoveInFlag" });
+ template.Attributes.Add(new TemplateAttribute("Unbound") { DataType = DataType.Boolean, DataSourceReference = "UnboundRef" });
+ template.Attributes.Add(new TemplateAttribute("Static") { DataType = DataType.Boolean, Value = "false" });
+
+ var instance = new Instance("Z28061") { Id = 1, TemplateId = 1, SiteId = 1 };
+ instance.ConnectionBindings.Add(new InstanceConnectionBinding("MoveInFlag")
+ {
+ DataConnectionId = 1,
+ DataSourceReferenceOverride = "MESReceiver_022.MoveInFlag"
+ });
+
+ var connections = new Dictionary
+ {
+ [1] = new("GW", "MxGateway", 1) { Id = 1, PrimaryConfiguration = "http://localhost" }
+ };
+
+ var result = new FlatteningService().Flatten(
+ instance,
+ [template],
+ new Dictionary>(),
+ new Dictionary>(),
+ connections);
+
+ Assert.True(result.IsSuccess, result.IsFailure ? result.Error : null);
+ var bound = result.Value.Attributes.Single(a => a.CanonicalName == "MoveInFlag");
+ Assert.Equal("MESReceiver_022.MoveInFlag", bound.DataSourceReference);
+ Assert.Equal("MoveInFlag", bound.DefaultDataSourceReference);
+
+ var unbound = result.Value.Attributes.Single(a => a.CanonicalName == "Unbound");
+ Assert.Equal("UnboundRef", unbound.DataSourceReference);
+ Assert.Equal("UnboundRef", unbound.DefaultDataSourceReference);
+
+ var stat = result.Value.Attributes.Single(a => a.CanonicalName == "Static");
+ Assert.Null(stat.DefaultDataSourceReference);
+ }
+}