OnNodeActivated.InvokeAsync(node.ExecutionId)">
}
diff --git a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs
index c415d48..fc773f7 100644
--- a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs
+++ b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.cs
@@ -78,6 +78,15 @@ public partial class ExecutionTree
///
[Parameter] public int Depth { get; set; }
+ ///
+ /// Raised when a node is double-clicked, carrying that node's
+ /// . The same callback is
+ /// threaded unchanged into every recursive child instance, so a
+ /// double-click on a node at any depth invokes the root-supplied handler
+ /// (used to open the node detail modal).
+ ///
+ [Parameter] public EventCallback
OnNodeActivated { get; set; }
+
// The subtrees this instance renders: assembled from Nodes on the root,
// or taken straight from PreBuiltRoots on a nested instance.
private IReadOnlyList _rootsToRender = Array.Empty();
diff --git a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.css b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.css
index 8f483a7..db6acab 100644
--- a/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.css
+++ b/src/ScadaLink.CentralUI/Components/Audit/ExecutionTree.razor.css
@@ -25,7 +25,10 @@
position: relative;
}
-/* The node card: a flex row of [toggle][body]. */
+/* The node card: a flex row of [toggle][body].
+ user-select: none — the body is double-clickable (opens the node detail
+ modal), so suppress the text selection a double-click would otherwise
+ leave behind. */
.execution-tree-node {
display: flex;
align-items: flex-start;
@@ -35,6 +38,7 @@
border: 1px solid var(--bs-border-color);
border-radius: 0.375rem;
background-color: var(--bs-body-bg);
+ user-select: none;
}
/* The execution the user drilled in from — a left accent rule + tinted
diff --git a/tests/ScadaLink.CentralUI.Tests/Components/Audit/ExecutionTreeTests.cs b/tests/ScadaLink.CentralUI.Tests/Components/Audit/ExecutionTreeTests.cs
index 43b5825..88d31a8 100644
--- a/tests/ScadaLink.CentralUI.Tests/Components/Audit/ExecutionTreeTests.cs
+++ b/tests/ScadaLink.CentralUI.Tests/Components/Audit/ExecutionTreeTests.cs
@@ -227,6 +227,63 @@ public class ExecutionTreeTests : BunitContext
cut.Find($"[data-test=\"tree-toggle-{root}\"]").GetAttribute("aria-expanded"));
}
+ [Fact]
+ public void DoubleClickingNode_RaisesOnNodeActivated_WithExecutionId()
+ {
+ // Double-clicking a node's body raises OnNodeActivated carrying that
+ // node's ExecutionId — the affordance a later task uses to open the
+ // node detail modal.
+ var root = Guid.Parse("aaaaaaaa-4444-4444-4444-444444444444");
+ var child = Guid.Parse("bbbbbbbb-4444-4444-4444-444444444444");
+ var nodes = new List
+ {
+ Node(root, null),
+ Node(child, root),
+ };
+
+ Guid? activated = null;
+ var cut = Render(p => p
+ .Add(c => c.Nodes, nodes)
+ .Add(c => c.ArrivedFromExecutionId, root)
+ .Add(c => c.OnNodeActivated, (Guid id) => activated = id));
+
+ var rootBody = cut.Find($"[data-test=\"tree-node-{root}\"] .execution-tree-body");
+ rootBody.DoubleClick();
+
+ Assert.Equal(root, activated);
+ }
+
+ [Fact]
+ public void DoubleClickingNestedNode_BubblesOnNodeActivated_ToRoot()
+ {
+ // root → child → grandchild. Double-clicking a deeply nested node's
+ // body invokes the SAME root-supplied callback — the EventCallback is
+ // threaded unchanged down every recursive ExecutionTree instance.
+ var root = Guid.Parse("aaaaaaaa-5555-5555-5555-555555555555");
+ var child = Guid.Parse("bbbbbbbb-5555-5555-5555-555555555555");
+ var grandchild = Guid.Parse("cccccccc-5555-5555-5555-555555555555");
+ var nodes = new List
+ {
+ Node(root, null),
+ Node(child, root),
+ Node(grandchild, child),
+ };
+
+ Guid? activated = null;
+ var cut = Render(p => p
+ .Add(c => c.Nodes, nodes)
+ .Add(c => c.ArrivedFromExecutionId, root)
+ .Add(c => c.OnNodeActivated, (Guid id) => activated = id));
+
+ // Double-click the grandchild (two recursion levels deep).
+ cut.Find($"[data-test=\"tree-node-{grandchild}\"] .execution-tree-body").DoubleClick();
+ Assert.Equal(grandchild, activated);
+
+ // And the child (one level deep) — both reach the root's callback.
+ cut.Find($"[data-test=\"tree-node-{child}\"] .execution-tree-body").DoubleClick();
+ Assert.Equal(child, activated);
+ }
+
private static int CountOccurrences(string haystack, string needle)
{
int count = 0, idx = 0;