diff --git a/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor b/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor
index cb7a2a2..6bbce9d 100644
--- a/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor
+++ b/src/ScadaLink.CentralUI/Components/Audit/AuditEventDetail.razor
@@ -28,6 +28,9 @@
SourceSiteId
@(Event.SourceSiteId ?? "—")
+ SourceNode
+ @(Event.SourceNode ?? "—")
+
SourceInstanceId
@(Event.SourceInstanceId ?? "—")
diff --git a/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs b/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs
index f4b48e9..9316c3e 100644
--- a/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs
+++ b/tests/ScadaLink.CentralUI.Tests/Components/Audit/AuditEventDetailTests.cs
@@ -81,6 +81,44 @@ public class AuditEventDetailTests : BunitContext
Assert.Contains("2026-05-20T12:30:45", cut.Markup);
}
+ [Fact]
+ public void RendersSourceNodeField_BetweenSiteAndInstance()
+ {
+ // SourceNode is rendered as a sibling row directly under SourceSiteId
+ // so the popup reads "site → node → instance → script" in provenance
+ // order. Populated case.
+ var ev = MakeEvent() with { SourceNode = "node-a" };
+
+ var cut = Render(p => p.Add(c => c.Event, ev));
+
+ Assert.Contains("data-test=\"field-SourceNode\"", cut.Markup);
+ Assert.Contains("node-a", cut.Markup);
+
+ // Ordering: SourceSiteId appears before SourceNode, which appears
+ // before SourceInstanceId.
+ var siteIdx = cut.Markup.IndexOf("data-test=\"field-SourceSiteId\"", StringComparison.Ordinal);
+ var nodeIdx = cut.Markup.IndexOf("data-test=\"field-SourceNode\"", StringComparison.Ordinal);
+ var instanceIdx = cut.Markup.IndexOf("data-test=\"field-SourceInstanceId\"", StringComparison.Ordinal);
+ Assert.True(siteIdx > 0 && nodeIdx > siteIdx && instanceIdx > nodeIdx,
+ $"Expected SourceSiteId < SourceNode < SourceInstanceId; got {siteIdx}, {nodeIdx}, {instanceIdx}");
+ }
+
+ [Fact]
+ public void RendersSourceNodeField_AsDashWhenNull()
+ {
+ // Null SourceNode (e.g. central direct-write row pre-feature, or a
+ // reconciled row from a retired node) renders as the em-dash, same
+ // convention as the sibling provenance fields.
+ var ev = MakeEvent(); // SourceNode left at default null
+ Assert.Null(ev.SourceNode);
+
+ var cut = Render(p => p.Add(c => c.Event, ev));
+
+ Assert.Contains("data-test=\"field-SourceNode\"", cut.Markup);
+ // The field is present and renders the em-dash placeholder.
+ Assert.Contains(">—<", cut.Markup);
+ }
+
[Fact]
public void ErrorSection_RendersWhenErrorPresent()
{