diff --git a/.gitignore b/.gitignore
index c0c35c1..fbf55e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -423,6 +423,7 @@ FodyWeavers.xsd
!/build
!/docs
!/examples
+!/dependencies
!/src
!/tests
!/tools
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..6f752c3
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "dependencies/Connected"]
+ path = dependencies/Connected
+ url = https://git.tompit.com/Connected/Connected.git
diff --git a/Connected.UI.sln b/Connected.UI.sln
new file mode 100644
index 0000000..3a6e345
--- /dev/null
+++ b/Connected.UI.sln
@@ -0,0 +1,48 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.32916.344
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected", "src\Connected\Connected.csproj", "{26AD2745-4DB4-4603-A944-C54D1A374032}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Client", "src\Connected.Client\Connected.Client.csproj", "{F0E0F91D-DFCB-44B7-8BA1-5E74C5E008F5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.UI", "src\Connected.UI\Connected.UI.csproj", "{52D89174-140B-4017-8B52-B83FA936D809}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dependencies", "Dependencies", "{0A1EBC0F-0567-41FB-9187-7DE1722FAB4E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connected.Components", "..\connected.components\src\Connected.Components\Connected.Components.csproj", "{7A6A8DFF-E5B4-482D-98F9-B2AE76235093}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {26AD2745-4DB4-4603-A944-C54D1A374032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {26AD2745-4DB4-4603-A944-C54D1A374032}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {26AD2745-4DB4-4603-A944-C54D1A374032}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {26AD2745-4DB4-4603-A944-C54D1A374032}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0E0F91D-DFCB-44B7-8BA1-5E74C5E008F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0E0F91D-DFCB-44B7-8BA1-5E74C5E008F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0E0F91D-DFCB-44B7-8BA1-5E74C5E008F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0E0F91D-DFCB-44B7-8BA1-5E74C5E008F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {52D89174-140B-4017-8B52-B83FA936D809}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {52D89174-140B-4017-8B52-B83FA936D809}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {52D89174-140B-4017-8B52-B83FA936D809}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {52D89174-140B-4017-8B52-B83FA936D809}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A6A8DFF-E5B4-482D-98F9-B2AE76235093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A6A8DFF-E5B4-482D-98F9-B2AE76235093}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A6A8DFF-E5B4-482D-98F9-B2AE76235093}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A6A8DFF-E5B4-482D-98F9-B2AE76235093}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {7A6A8DFF-E5B4-482D-98F9-B2AE76235093} = {0A1EBC0F-0567-41FB-9187-7DE1722FAB4E}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4BE29712-DF74-4798-8F2E-92D7446D2465}
+ EndGlobalSection
+EndGlobal
diff --git a/dependencies/Connected b/dependencies/Connected
new file mode 160000
index 0000000..5721864
--- /dev/null
+++ b/dependencies/Connected
@@ -0,0 +1 @@
+Subproject commit 5721864f19815bd8f906ce52ae9f74301db69657
diff --git a/src/Connected.UI/Connected.UI.csproj b/src/Connected.UI/Connected.UI.csproj
new file mode 100644
index 0000000..e99c91c
--- /dev/null
+++ b/src/Connected.UI/Connected.UI.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Connected.UI/Instance.cs b/src/Connected.UI/Instance.cs
new file mode 100644
index 0000000..21f2f9e
--- /dev/null
+++ b/src/Connected.UI/Instance.cs
@@ -0,0 +1,63 @@
+using Connected.Middleware;
+using Connected.Startup;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Connected;
+
+public static class Instance
+{
+ public static WebAssemblyHost Host { get; private set; } = default!;
+ private static IServiceProvider ServiceProvider { get; set; } = default!;
+
+ internal static T? GetService()
+ {
+ return ServiceProvider.GetService();
+ }
+ public static async Task Start(List startups, Dictionary args) where TApp : IComponent
+ {
+ var builder = WebAssemblyHostBuilder.CreateDefault(UnpackArguments(args));
+
+ builder.RootComponents.Add("#app");
+ builder.RootComponents.Add("head::after");
+
+ builder.Services.AddHttpClient();
+
+ builder.Services.AddSingleton(typeof(IComponentMiddlewareService), typeof(ComponentMiddlewareService));
+ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+ foreach (var startup in startups)
+ await startup.ConfigureServices(builder.Services);
+
+ Host = builder.Build();
+
+ foreach (var startup in startups)
+ await startup.Configure(Host);
+
+ await Host.RunAsync();
+ }
+
+ private static string[] UnpackArguments(Dictionary args)
+ {
+ var result = new string[args.Count];
+
+ for (var i = 0; i < args.Count; i++)
+ {
+ var arg = args.ElementAt(i);
+
+ result[i] = string.IsNullOrWhiteSpace(arg.Value) ? arg.Key : $"{arg.Key}={arg.Value}";
+ }
+
+ return result;
+ }
+
+ public static void UseMiddlewareComponent(this WebAssemblyHost app)
+ {
+ if (app.Services.GetService() is not IComponentMiddlewareService service)
+ throw new NullReferenceException(nameof(IComponentMiddlewareService));
+
+ service.Add();
+ }
+}
\ No newline at end of file
diff --git a/src/Connected.UI/Layouts/DefaultLayout.razor b/src/Connected.UI/Layouts/DefaultLayout.razor
new file mode 100644
index 0000000..2fce8ce
--- /dev/null
+++ b/src/Connected.UI/Layouts/DefaultLayout.razor
@@ -0,0 +1,9 @@
+@inherits LayoutComponentBase
+
+
+
+
+
+
+
+@Body
\ No newline at end of file
diff --git a/src/Connected.UI/Middleware/ComponentMiddlewareService.cs b/src/Connected.UI/Middleware/ComponentMiddlewareService.cs
new file mode 100644
index 0000000..902d30a
--- /dev/null
+++ b/src/Connected.UI/Middleware/ComponentMiddlewareService.cs
@@ -0,0 +1,42 @@
+using System.Collections.Concurrent;
+
+namespace Connected.Middleware;
+
+internal class ComponentMiddlewareService : IComponentMiddlewareService
+{
+ public ComponentMiddlewareService()
+ {
+ Middleware = new();
+ }
+
+ private ConcurrentDictionary> Middleware { get; }
+
+ public void Add()
+ {
+ var componentName = typeof(TComponent).FullName;
+
+ if (string.IsNullOrEmpty(componentName))
+ throw new ArgumentException(null, nameof(TMiddleware));
+
+ if (Middleware.TryGetValue(componentName, out List? items))
+ {
+ //TODO: sort by priority so multiple inheritance would work
+ items.Add(typeof(TMiddleware));
+ }
+ else
+ Middleware.TryAdd(componentName, new List { typeof(TMiddleware) });
+ }
+
+ public Type? Select()
+ {
+ var typeName = typeof(TComponent).FullName;
+
+ if (string.IsNullOrEmpty(typeName))
+ throw new ArgumentException(null, nameof(TComponent));
+
+ if (Middleware.TryGetValue(typeName, out List? items) && items is not null && items.Any())
+ return items[0];
+
+ return null;
+ }
+}
diff --git a/src/Connected.UI/Middleware/IComponentMiddlewareService.cs b/src/Connected.UI/Middleware/IComponentMiddlewareService.cs
new file mode 100644
index 0000000..4b347b3
--- /dev/null
+++ b/src/Connected.UI/Middleware/IComponentMiddlewareService.cs
@@ -0,0 +1,8 @@
+namespace Connected.Middleware;
+
+public interface IComponentMiddlewareService
+{
+ void Add();
+
+ Type? Select();
+}
diff --git a/src/Connected.UI/UIComposition.cs b/src/Connected.UI/UIComposition.cs
new file mode 100644
index 0000000..a48d635
--- /dev/null
+++ b/src/Connected.UI/UIComposition.cs
@@ -0,0 +1,29 @@
+using Connected.Middleware;
+using Microsoft.AspNetCore.Components;
+using Microsoft.Extensions.Logging;
+
+namespace Connected.Components;
+
+public abstract class UIComposition : ComponentBase
+{
+ private ILogger _logger;
+
+ [Inject]
+ private ILoggerFactory LoggerFactory { get; set; } = default!;
+
+ [Inject]
+ private IComponentMiddlewareService? MiddlewareService { get; set; }
+
+ protected ILogger Logger => _logger ??= LoggerFactory.CreateLogger(GetType());
+
+ protected Type ResolveComponent()
+ {
+ if (MiddlewareService is null)
+ return typeof(TComponent);
+
+ if (MiddlewareService.Select() is Type type)
+ return type;
+
+ return typeof(TComponent);
+ }
+}