diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor
index d4180e38..177e358c 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor
@@ -1,4 +1,5 @@
-@page "/dev/context-menu-demo"
+@* The /dev/context-menu-demo route lives in ContextMenuDemo.razor.cs behind #if DEBUG (R04 S7), so a
+ Release build ships the component but no route to it. *@
@* Dev-only host page to live-verify the reusable ContextMenu component. *@
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode InteractiveServer
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor.cs
new file mode 100644
index 00000000..8e5c60ec
--- /dev/null
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Dev/ContextMenuDemo.razor.cs
@@ -0,0 +1,16 @@
+namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Dev;
+
+///
+/// Dev-only host page that live-verifies the reusable ContextMenu component. The route is
+/// compiled only into DEBUG builds (R04 S7): a Release (production) build keeps the component type —
+/// so nothing that references it breaks — but registers no /dev/* route, and the router never
+/// serves it.
+///
+#if DEBUG
+[Microsoft.AspNetCore.Components.Route(RoutePath)]
+#endif
+public partial class ContextMenuDemo
+{
+ /// The DEBUG-only route.
+ public const string RoutePath = "/dev/context-menu-demo";
+}
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/DevRouteGatingTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/DevRouteGatingTests.cs
new file mode 100644
index 00000000..4eed93b6
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/DevRouteGatingTests.cs
@@ -0,0 +1,43 @@
+using System.Reflection;
+using Microsoft.AspNetCore.Components;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.OtOpcUa.AdminUI.Components;
+
+namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Authorization;
+
+///
+/// R04 S7: /dev/* routes exist only in DEBUG builds. The test project builds in the same
+/// configuration as the AdminUI assembly, so each configuration asserts its own half — run
+/// dotnet test -c Release to exercise the production half.
+///
+[Trait("Category", "Unit")]
+public sealed class DevRouteGatingTests
+{
+ private static IReadOnlyList DevRoutes() =>
+ typeof(Routes).Assembly.GetTypes()
+ .SelectMany(t => t.GetCustomAttributes(inherit: false))
+ .Select(r => r.Template)
+ .Where(t => t.StartsWith("/dev/", StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ [Fact]
+ public void Dev_routes_follow_the_build_configuration()
+ {
+#if DEBUG
+ DevRoutes().ShouldBe([ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Dev.ContextMenuDemo.RoutePath]);
+#else
+ DevRoutes().ShouldBeEmpty();
+#endif
+ }
+
+ [Fact]
+ public void Production_routes_are_present_in_every_configuration()
+ {
+ // Positive control: the scan sees routes at all (an empty assembly scan would pass the Release half).
+ typeof(Routes).Assembly.GetTypes()
+ .SelectMany(t => t.GetCustomAttributes(inherit: false))
+ .Select(r => r.Template)
+ .ShouldContain("/hosts");
+ }
+}
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/PageAuthorizationGuardTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/PageAuthorizationGuardTests.cs
index 231c61cc..60ff5323 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/PageAuthorizationGuardTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Authorization/PageAuthorizationGuardTests.cs
@@ -59,7 +59,10 @@ public class PageAuthorizationGuardTests
// ── AuthenticatedRead (17): dashboards, lists, live tails (read-only) + the dev ContextMenu demo ──
[typeof(Home)] = AdminUiPolicies.AuthenticatedRead,
+#if DEBUG
+ // Routable only in DEBUG builds (R04 S7) — a Release build registers no /dev/* route.
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Dev.ContextMenuDemo)] = AdminUiPolicies.AuthenticatedRead,
+#endif
[typeof(Fleet)] = AdminUiPolicies.AuthenticatedRead,
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.FleetDiagnostics)] = AdminUiPolicies.AuthenticatedRead,
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Hosts)] = AdminUiPolicies.AuthenticatedRead,