diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawBrowseModal.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawBrowseModal.razor
index b9a1f03f..433d5533 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawBrowseModal.razor
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Raw/RawBrowseModal.razor
@@ -683,13 +683,29 @@
_rebirthOutcome = null;
_rebirthError = null;
StateHasChanged();
+ var token = _token;
+ var target = _rebirthTarget;
try
{
- var published = await BrowserService.RequestRebirthAsync(_token, _rebirthTarget.Scope, default);
+ var published = await BrowserService.RequestRebirthAsync(token, target.Scope, default);
+ // The session's RequestRebirthAsync returns only once the births it asked for have arrived and
+ // gone quiet (bounded by the window), so the tree can be re-read now instead of telling the
+ // operator to re-expand it later (G2-F3). Skipped if the session or modal went away meanwhile.
+ var refreshed = token == _token && Visible && !_disposed;
+ if (refreshed)
+ {
+ // Re-key the tree against the SAME session (as the Refresh button does). The armed scope is
+ // dropped — its node may not be in the rebuilt tree — but the outcome line is kept.
+ _treeGeneration++;
+ _rebirthTarget = null;
+ }
+
_rebirthOutcome =
- $"Rebirth requested for {_rebirthTarget.Kind} — {published} command"
+ $"Rebirth requested for {target.Kind} — {published} command"
+ (published == 1 ? "" : "s")
- + " published. Re-expand the tree in a few seconds to see the republished metrics.";
+ + (refreshed
+ ? " published. The tree was refreshed with the births that arrived; press Refresh if more appear later."
+ : " published.");
_rebirthConfirming = false;
}
catch (Exception ex)
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawBrowseRebirthRefreshTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawBrowseRebirthRefreshTests.cs
new file mode 100644
index 00000000..4b38fbbe
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/RawBrowseRebirthRefreshTests.cs
@@ -0,0 +1,135 @@
+using System.Reflection;
+using System.Runtime.ExceptionServices;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.RenderTree;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.OtOpcUa.AdminUI.Browsing;
+using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw;
+using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
+
+namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw;
+
+// BL0006: the test renderer needs RenderTree types (same suppression as RawBrowseModalStaleOpenTests).
+#pragma warning disable BL0006
+
+///
+/// G2-F3: after a rebirth request the browse modal re-reads the tree itself (the session's
+/// RequestRebirthAsync now waits for the births), instead of telling the operator to re-expand it.
+///
+[Trait("Category", "Unit")]
+public sealed class RawBrowseRebirthRefreshTests : IDisposable
+{
+ private const BindingFlags Private = BindingFlags.NonPublic | BindingFlags.Instance;
+ private readonly List _renderers = new();
+
+ public void Dispose()
+ {
+ foreach (var r in _renderers) r.Dispose();
+ }
+
+ [Fact]
+ public async Task A_completed_rebirth_rekeys_the_tree_and_keeps_the_outcome()
+ {
+ var svc = new RebirthBrowserService { Result = Task.FromResult(2) };
+ var modal = NewOpenModal(svc);
+ var before = Get(modal, "_treeGeneration");
+
+ await ConfirmAsync(modal);
+
+ Get(modal, "_treeGeneration").ShouldBe(before + 1);
+ Get(modal, "_rebirthOutcome")!.ShouldContain("The tree was refreshed");
+ Get