diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Dashboard.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Dashboard.razor
index a4516794..d76ad07a 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Dashboard.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Dashboard.razor
@@ -107,7 +107,7 @@
{
_siteCount = (await SiteRepository.GetAllSitesAsync()).Count;
_dataConnectionCount = (await SiteRepository.GetAllDataConnectionsAsync()).Count;
- _templateCount = (await TemplateEngineRepository.GetAllTemplatesAsync()).Count;
+ _templateCount = (await TemplateEngineRepository.GetTemplateStubsAsync()).Count;
_apiKeyCount = (await ApiKeyAdmin.ListAsync()).Count;
}
catch
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceCreate.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceCreate.razor
index d7bf9769..4852d677 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceCreate.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceCreate.razor
@@ -267,7 +267,7 @@
{
try
{
- _templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
+ _templates = (await TemplateEngineRepository.GetTemplateStubsAsync()).ToShellTemplates();
_folders = (await TemplateEngineRepository.GetAllFoldersAsync()).ToList();
_foldersById = _folders.ToDictionary(f => f.Id);
if (TemplateId is int tid)
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateCreate.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateCreate.razor
index b04a9283..0c7d0152 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateCreate.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateCreate.razor
@@ -1,4 +1,5 @@
@page "/design/templates/create"
+@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
@using ZB.MOM.WW.ScadaBridge.Security
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories
@@ -73,7 +74,7 @@
{
try
{
- _templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
+ _templates = (await TemplateEngineRepository.GetTemplateStubsAsync()).ToShellTemplates();
if (ParentId is int pid && _templates.Any(t => t.Id == pid))
{
_createParentId = pid;
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor
index 21d9cccb..c27daeb9 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor
@@ -125,7 +125,7 @@
_loadError = null;
try
{
- _templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
+ _templates = (await TemplateEngineRepository.GetAllTemplatesForAnalysisAsync()).ToList();
_selectedTemplate = await TemplateEngineRepository.GetTemplateWithChildrenAsync(Id)
?? _templates.FirstOrDefault(t => t.Id == Id);
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor
index c964772e..ee3904c9 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor
@@ -148,7 +148,7 @@
_loading = true;
try
{
- _templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
+ _templates = (await TemplateEngineRepository.GetAllTemplatesForAnalysisAsync()).ToList();
_folders = (await TemplateEngineRepository.GetAllFoldersAsync()).ToList();
_templatesById = _templates.ToDictionary(t => t.Id);
_compositionsById = _templates
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs
index 2baf01d8..517543dc 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs
@@ -1,3 +1,4 @@
+using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
@@ -136,7 +137,7 @@ public partial class TransportExport : ComponentBase
_errorMessage = null;
try
{
- _templates = (await TemplateRepo.GetAllTemplatesAsync()).ToList();
+ _templates = (await TemplateRepo.GetTemplateStubsAsync()).ToShellTemplates();
_folders = (await TemplateRepo.GetAllFoldersAsync()).ToList();
_sharedScripts = (await TemplateRepo.GetAllSharedScriptsAsync()).ToList();
_externalSystems = (await ExternalRepo.GetAllExternalSystemsAsync()).ToList();
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplateStubExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplateStubExtensions.cs
new file mode 100644
index 00000000..5ab4ff67
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplateStubExtensions.cs
@@ -0,0 +1,25 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
+
+///
+/// Adapts the untracked, body-free projection to childless
+/// shells for pickers and trees that only read identity/edge fields
+/// (id, name, parent, folder, derived flag) — review 2026-10-21 07/P-1.
+///
+internal static class TemplateStubExtensions
+{
+ /// Builds childless shells carrying only the stub's identity and edges.
+ /// The stubs to adapt.
+ /// One shell template per stub, in input order.
+ public static List ToShellTemplates(this IEnumerable stubs) =>
+ stubs.Select(s => new Template(s.Name)
+ {
+ Id = s.Id,
+ ParentTemplateId = s.ParentTemplateId,
+ FolderId = s.FolderId,
+ IsDerived = s.IsDerived,
+ OwnerCompositionId = s.OwnerCompositionId,
+ }).ToList();
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceCreateTemplateFieldTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceCreateTemplateFieldTests.cs
index e4478fa8..aa64de0c 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceCreateTemplateFieldTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceCreateTemplateFieldTests.cs
@@ -1,3 +1,4 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
using System.Security.Claims;
using Bunit;
using Bunit.TestDoubles;
@@ -108,8 +109,9 @@ public class InstanceCreateTemplateFieldTests : BunitContext
new("Elsewhere") { Id = 8, FolderId = 3 },
};
- _repo.GetAllTemplatesAsync(Arg.Any())
- .Returns(Task.FromResult>(templates));
+ _repo.GetTemplateStubsAsync(Arg.Any())
+ .Returns(Task.FromResult>(
+ (templates).Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId)).ToList()));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(folders));
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateEditInheritedSetTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateEditInheritedSetTests.cs
index 2bb21b45..954a4ccb 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateEditInheritedSetTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateEditInheritedSetTests.cs
@@ -90,7 +90,7 @@ public class TemplateEditInheritedSetTests : BunitContext
IsDerived = true,
};
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(
new List { grandparent, baseTemplate, derived }));
_repo.GetTemplateWithChildrenAsync(DerivedId, Arg.Any())
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/Design/TransportExportPageTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/Design/TransportExportPageTests.cs
index b07b6260..5481d3be 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/Design/TransportExportPageTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/Design/TransportExportPageTests.cs
@@ -1,3 +1,4 @@
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Authorization;
@@ -61,8 +62,9 @@ public class TransportExportPageTests : BunitContext
// Default empty repos so OnInitializedAsync doesn't throw — individual
// tests override the bits they care about.
- _templateRepo.GetAllTemplatesAsync(Arg.Any())
- .Returns(Task.FromResult>(new List()));
+ _templateRepo.GetTemplateStubsAsync(Arg.Any())
+ .Returns(Task.FromResult>(
+ (new List()).Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId)).ToList()));
_templateRepo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
_templateRepo.GetAllSharedScriptsAsync(Arg.Any())
@@ -139,8 +141,9 @@ public class TransportExportPageTests : BunitContext
// export page no longer offers a keys selection list, only API methods.
var apiMethod = new ApiMethod("CreateOrder", "// noop") { Id = 70 };
- _templateRepo.GetAllTemplatesAsync(Arg.Any())
- .Returns(Task.FromResult>(new List { template }));
+ _templateRepo.GetTemplateStubsAsync(Arg.Any())
+ .Returns(Task.FromResult>(
+ (new List { template }).Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId)).ToList()));
_templateRepo.GetAllSharedScriptsAsync(Arg.Any())
.Returns(Task.FromResult>(new List { script }));
_externalRepo.GetAllExternalSystemsAsync(Arg.Any())
@@ -218,8 +221,9 @@ public class TransportExportPageTests : BunitContext
});
var motor = new Template("Motor") { Id = 2 };
- _templateRepo.GetAllTemplatesAsync(Arg.Any())
- .Returns(Task.FromResult>(new List { pump, motor }));
+ _templateRepo.GetTemplateStubsAsync(Arg.Any())
+ .Returns(Task.FromResult>(
+ (new List { pump, motor }).Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId)).ToList()));
_templateRepo.GetTemplateWithChildrenAsync(1, Arg.Any())
.Returns(Task.FromResult(pump));
_templateRepo.GetTemplateWithChildrenAsync(2, Arg.Any())
@@ -262,8 +266,9 @@ public class TransportExportPageTests : BunitContext
public async Task Step4_triggers_ExportAsync_with_selected_artifacts_and_user_identity()
{
var template = new Template("Pump") { Id = 1 };
- _templateRepo.GetAllTemplatesAsync(Arg.Any())
- .Returns(Task.FromResult>(new List { template }));
+ _templateRepo.GetTemplateStubsAsync(Arg.Any())
+ .Returns(Task.FromResult>(
+ (new List { template }).Select(t => new TemplateStub(t.Id, t.Name, t.ParentTemplateId, t.FolderId, t.IsDerived, t.OwnerCompositionId)).ToList()));
_templateRepo.GetTemplateWithChildrenAsync(1, Arg.Any())
.Returns(Task.FromResult(template));
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/TemplateListLoadTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/TemplateListLoadTests.cs
new file mode 100644
index 00000000..a49ede33
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/TemplateListLoadTests.cs
@@ -0,0 +1,41 @@
+using System.Security.Claims;
+using Bunit;
+using Microsoft.AspNetCore.Components.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using NSubstitute;
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
+using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
+using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
+using Dashboard = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Dashboard;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages;
+
+/// Review 2026-10-21 07/P-1: the landing page must not load the full tracked template graph just to count.
+public class TemplateListLoadTests : BunitContext
+{
+ /// The Dashboard counts templates from stubs, never the full graph.
+ /// A task that represents the asynchronous operation.
+ [Fact]
+ public async Task Dashboard_CountsTemplatesFromStubs_NotFullGraph()
+ {
+ var repo = Substitute.For();
+ repo.GetTemplateStubsAsync(Arg.Any()).Returns(Task.FromResult>(
+ new List { new(1, "A", null, null, false, null), new(2, "B", null, null, false, null) }));
+ var sites = Substitute.For();
+ sites.GetAllSitesAsync().Returns(new List());
+ sites.GetAllDataConnectionsAsync().Returns(new List());
+ var keys = Substitute.For();
+ Services.AddSingleton(repo);
+ Services.AddSingleton(sites);
+ Services.AddSingleton(keys);
+ Services.AddSingleton(Substitute.For());
+ AddAuthorization();
+
+ var cut = Render();
+ await cut.WaitForStateAsync(() => cut.Markup.Contains("Templates"));
+
+ await repo.Received(1).GetTemplateStubsAsync(Arg.Any());
+ await repo.DidNotReceive().GetAllTemplatesAsync(Arg.Any());
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TemplatesPageTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TemplatesPageTests.cs
index 75ba74bc..ef5ea4d6 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TemplatesPageTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TemplatesPageTests.cs
@@ -69,7 +69,7 @@ public class TemplatesPageTests : BunitContext
[Fact]
public void Renders_EmptyState_WhenNoTemplatesOrFolders()
{
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
@@ -85,7 +85,7 @@ public class TemplatesPageTests : BunitContext
{
var folder = new TemplateFolder("Dev") { Id = 1 };
var template = new Template("TestMachine") { Id = 5, FolderId = 1 };
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List { template }));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List { folder }));
@@ -114,7 +114,7 @@ public class TemplatesPageTests : BunitContext
new TemplateComposition("DelmiaReceiver") { Id = 10, ComposedTemplateId = 99 });
var composed = new Template("Other") { Id = 99 };
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List { template, composed }));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
@@ -153,7 +153,7 @@ public class TemplatesPageTests : BunitContext
cvdReactor.Compositions.Add(
new TemplateComposition("LeftReactorSide") { Id = 200, TemplateId = 1, ComposedTemplateId = 8 });
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(
new List { cvdReactor, reactorSide, leftReactorSide, leakTest }));
_repo.GetAllFoldersAsync(Arg.Any())
@@ -191,7 +191,7 @@ public class TemplatesPageTests : BunitContext
var alpha = new Template("AlphaDevice") { Id = 10, FolderId = 1 };
var beta = new Template("BetaDevice") { Id = 20, FolderId = 1 };
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List { alpha, beta }));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List { folder }));
@@ -243,7 +243,7 @@ public class TemplatesPageTests : BunitContext
var beta = new TemplateFolder("Beta") { Id = 2, ParentFolderId = null, SortOrder = 1 };
var all = new List { alpha, beta };
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(_ => Task.FromResult>(all));
@@ -307,7 +307,7 @@ public class TemplatesPageTests : BunitContext
// Tree reloaded after the mutation: LoadTemplatesAsync re-fetched (initial
// load + post-reorder reload). GetAllTemplatesAsync is only touched by the
// page's load path, never by ReorderFolderAsync, so 2 calls == one reload.
- _repo.Received(2).GetAllTemplatesAsync(Arg.Any());
+ _repo.Received(2).GetAllTemplatesForAnalysisAsync(Arg.Any());
}
/// Verifies that choosing move up dispatches a reorder up and reloads the tree.
@@ -333,7 +333,7 @@ public class TemplatesPageTests : BunitContext
Assert.Equal(1, alpha.SortOrder);
// Tree reloaded after the mutation (one reload == GetAllTemplatesAsync twice).
- _repo.Received(2).GetAllTemplatesAsync(Arg.Any());
+ _repo.Received(2).GetAllTemplatesForAnalysisAsync(Arg.Any());
}
/// Verifies that move up is disabled for the first sibling folder.
@@ -372,7 +372,7 @@ public class TemplatesPageTests : BunitContext
[Fact]
public void RootContextMenu_OffersNewFolderAndNewTemplate()
{
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
@@ -392,7 +392,7 @@ public class TemplatesPageTests : BunitContext
[Fact]
public void RootContextMenu_NewTemplate_NavigatesToRootCreate()
{
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
@@ -417,7 +417,7 @@ public class TemplatesPageTests : BunitContext
private void SeedOneTemplate(int id, string name)
{
- _repo.GetAllTemplatesAsync(Arg.Any())
+ _repo.GetAllTemplatesForAnalysisAsync(Arg.Any())
.Returns(Task.FromResult>(new List { new(name) { Id = id } }));
_repo.GetAllFoldersAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));