diff --git a/src/ReactiveUI.AndroidX/ReactiveUIBuilderAndroidXExtensions.cs b/src/ReactiveUI.AndroidX/ReactiveUIBuilderAndroidXExtensions.cs
new file mode 100644
index 000000000..4793d1938
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveUIBuilderAndroidXExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// AndroidX-specific extensions for ReactiveUIBuilder.
+///
+public static class ReactiveUIBuilderAndroidXExtensions
+{
+ ///
+ /// Registers AndroidX-specific services.
+ ///
+ /// The builder instance.
+ /// The builder instance for method chaining.
+#if NET6_0_OR_GREATER
+ [RequiresDynamicCode("WithAndroidX uses methods that require dynamic code generation")]
+ [RequiresUnreferencedCode("WithAndroidX uses methods that may require unreferenced code")]
+#endif
+ public static Builder.ReactiveUIBuilder WithAndroidX(this Builder.ReactiveUIBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.WithPlatformModule();
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/Registrations.cs b/src/ReactiveUI.AndroidX/Registrations.cs
new file mode 100644
index 000000000..938762679
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/Registrations.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using Android.OS;
+using Android.Runtime;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// AndroidX platform registrations.
+///
+///
+public class Registrations : IWantsToRegisterStuff
+{
+ ///
+#if NET6_0_OR_GREATER
+ [RequiresDynamicCode("Register uses methods that require dynamic code generation")]
+ [RequiresUnreferencedCode("Register uses methods that may require unreferenced code")]
+#endif
+ public void Register(Action, Type> registerFunction)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(registerFunction);
+#else
+ if (registerFunction is null)
+ {
+ throw new ArgumentNullException(nameof(registerFunction));
+ }
+#endif
+
+ // Leverage core Android platform registrations already present in ReactiveUI.Platforms android.
+ // This ensures IPlatformOperations, binding converters, and schedulers are configured.
+ new PlatformRegistrations().Register(registerFunction);
+
+ // AndroidX specific registrations could be added here if needed in the future.
+
+ // Ensure a SynchronizationContext exists on Android when not in unit tests.
+ if (!ModeDetector.InUnitTestRunner() && Looper.MyLooper() is null)
+ {
+ Looper.Prepare();
+ }
+ }
+}
diff --git a/src/ReactiveUI.Blazor/ReactiveUIBuilderBlazorExtensions.cs b/src/ReactiveUI.Blazor/ReactiveUIBuilderBlazorExtensions.cs
new file mode 100644
index 000000000..2ef3b38ce
--- /dev/null
+++ b/src/ReactiveUI.Blazor/ReactiveUIBuilderBlazorExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Blazor;
+
+///
+/// Blazor-specific extensions for ReactiveUIBuilder.
+///
+public static class ReactiveUIBuilderBlazorExtensions
+{
+ ///
+ /// Registers Blazor-specific services.
+ ///
+ /// The builder instance.
+ /// The builder instance for method chaining.
+#if NET6_0_OR_GREATER
+ [RequiresDynamicCode("WithBlazor uses methods that require dynamic code generation")]
+ [RequiresUnreferencedCode("WithBlazor uses methods that may require unreferenced code")]
+#endif
+ public static Builder.ReactiveUIBuilder WithBlazor(this Builder.ReactiveUIBuilder builder)
+ {
+ if (builder is null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ return builder.WithPlatformModule();
+ }
+}
diff --git a/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUI.Builder.AndroidX.Tests.csproj b/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUI.Builder.AndroidX.Tests.csproj
new file mode 100644
index 000000000..78892545b
--- /dev/null
+++ b/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUI.Builder.AndroidX.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+ net8.0-android
+ $(NoWarn);CS1591;SA1600
+ 34.0
+ false
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUIBuilderAndroidXTests.cs b/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUIBuilderAndroidXTests.cs
new file mode 100644
index 000000000..8973c4dcb
--- /dev/null
+++ b/src/ReactiveUI.Builder.AndroidX.Tests/ReactiveUIBuilderAndroidXTests.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.AndroidX;
+
+namespace ReactiveUI.Builder.AndroidX.Tests;
+
+public class ReactiveUIBuilderAndroidXTests
+{
+ [Fact]
+ public void WithAndroidX_Should_Register_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+
+ locator.CreateBuilder()
+ .WithAndroidX()
+ .Build();
+
+ var commandBinder = locator.GetService();
+ Assert.NotNull(commandBinder);
+
+ var observableForProperty = locator.GetService();
+ Assert.NotNull(observableForProperty);
+ }
+
+ [Fact]
+ public void WithCoreServices_AndAndroidX_Should_Register_All_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+
+ locator.CreateBuilder()
+ .WithCoreServices()
+ .WithAndroidX()
+ .Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var commandBinder = locator.GetService();
+ Assert.NotNull(commandBinder);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Maui.Tests/ReactiveUI.Builder.Maui.Tests.csproj b/src/ReactiveUI.Builder.Maui.Tests/ReactiveUI.Builder.Maui.Tests.csproj
new file mode 100644
index 000000000..e823efff5
--- /dev/null
+++ b/src/ReactiveUI.Builder.Maui.Tests/ReactiveUI.Builder.Maui.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+ net8.0;net9.0;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0
+ true
+ $(NoWarn);CS1591;SA1600
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs b/src/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs
new file mode 100644
index 000000000..0756c628d
--- /dev/null
+++ b/src/ReactiveUI.Builder.Maui.Tests/ReactiveUIBuilderMauiTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.Maui;
+
+namespace ReactiveUI.Builder.Maui.Tests;
+
+public class ReactiveUIBuilderMauiTests
+{
+ [Fact]
+ public void WithMaui_Should_Register_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+
+ locator.CreateBuilder()
+ .WithMaui()
+ .Build();
+
+ var typeConverters = locator.GetServices();
+ Assert.NotNull(typeConverters);
+ }
+
+ [Fact]
+ public void WithCoreServices_AndMaui_Should_Register_All_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+
+ locator.CreateBuilder()
+ .WithCoreServices()
+ .WithMaui()
+ .Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var typeConverters = locator.GetServices();
+ Assert.NotNull(typeConverters);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/Platforms/Blazor/ReactiveUIBuilderBlazorTests.cs b/src/ReactiveUI.Builder.Tests/Platforms/Blazor/ReactiveUIBuilderBlazorTests.cs
new file mode 100644
index 000000000..182d58eb3
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/Platforms/Blazor/ReactiveUIBuilderBlazorTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.Blazor;
+
+namespace ReactiveUI.Builder.Tests.Platforms.Blazor;
+
+public class ReactiveUIBuilderBlazorTests
+{
+ [Fact]
+ public void WithBlazor_Should_Register_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithBlazor().Build();
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+
+ var typeConverters = locator.GetServices();
+ Assert.NotEmpty(typeConverters);
+ }
+
+ [Fact]
+ public void WithCoreServices_AndBlazor_Should_Register_All_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithCoreServices().WithBlazor().Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/Platforms/Drawing/ReactiveUIBuilderDrawingTests.cs b/src/ReactiveUI.Builder.Tests/Platforms/Drawing/ReactiveUIBuilderDrawingTests.cs
new file mode 100644
index 000000000..867b76813
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/Platforms/Drawing/ReactiveUIBuilderDrawingTests.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.Drawing;
+
+namespace ReactiveUI.Builder.Tests.Platforms.Drawing;
+
+public class ReactiveUIBuilderDrawingTests
+{
+ [Fact]
+ public void WithDrawing_Should_Register_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithDrawing().Build();
+
+ // Drawing registers bitmap loader in non-NETSTANDARD contexts; we can still assert no exception and core services with chaining
+ locator.CreateBuilder().WithCoreServices().WithDrawing().Build();
+ var bindingConverters = locator.GetServices();
+ Assert.NotNull(bindingConverters);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/Platforms/WinForms/ReactiveUIBuilderWinFormsTests.cs b/src/ReactiveUI.Builder.Tests/Platforms/WinForms/ReactiveUIBuilderWinFormsTests.cs
new file mode 100644
index 000000000..8c90b8f44
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/Platforms/WinForms/ReactiveUIBuilderWinFormsTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.Winforms;
+
+namespace ReactiveUI.Builder.Tests.Platforms.WinForms;
+
+public class ReactiveUIBuilderWinFormsTests
+{
+ [Fact]
+ public void WithWinForms_Should_Register_WinForms_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithWinForms().Build();
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+
+ var activationFetcher = locator.GetService();
+ Assert.NotNull(activationFetcher);
+ }
+
+ [Fact]
+ public void WithCoreServices_AndWinForms_Should_Register_All_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithCoreServices().WithWinForms().Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/Platforms/Wpf/ReactiveUIBuilderWpfTests.cs b/src/ReactiveUI.Builder.Tests/Platforms/Wpf/ReactiveUIBuilderWpfTests.cs
new file mode 100644
index 000000000..f058fbd73
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/Platforms/Wpf/ReactiveUIBuilderWpfTests.cs
@@ -0,0 +1,41 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using ReactiveUI.Wpf;
+
+namespace ReactiveUI.Builder.Tests.Platforms.Wpf;
+
+public class ReactiveUIBuilderWpfTests
+{
+ [Fact]
+ public void WithWpf_Should_Register_Wpf_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithWpf().Build();
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+
+ var activationFetcher = locator.GetService();
+ Assert.NotNull(activationFetcher);
+ }
+
+ [Fact]
+ public void WithCoreServices_AndWpf_Should_Register_All_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithCoreServices().WithWpf().Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var platformOperations = locator.GetService();
+ Assert.NotNull(platformOperations);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj b/src/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj
new file mode 100644
index 000000000..f17d6c292
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/ReactiveUI.Builder.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0;net9.0
+ ;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0
+ $(NoWarn);CS1591
+ enable
+ enable
+ false
+ $(NoWarn);SA1600
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs b/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs
new file mode 100644
index 000000000..7554f401a
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderBlockingTests.cs
@@ -0,0 +1,30 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Builder.Tests;
+
+///
+/// Tests ensuring the builder blocks reflection-based initialization.
+///
+public class ReactiveUIBuilderBlockingTests
+{
+ [Fact]
+ public void Build_SetsFlag_AndBlocks_InitializeReactiveUI()
+ {
+ using var locator = new ModernDependencyResolver();
+
+ RxApp.HasBeenBuiltUsingBuilder = false;
+
+ var builder = locator.CreateBuilder();
+ builder.WithCoreServices().Build();
+
+ Assert.True(RxApp.HasBeenBuiltUsingBuilder);
+
+ locator.InitializeReactiveUI();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+ }
+}
diff --git a/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs b/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs
new file mode 100644
index 000000000..dad8b769c
--- /dev/null
+++ b/src/ReactiveUI.Builder.Tests/ReactiveUIBuilderCoreTests.cs
@@ -0,0 +1,141 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Builder.Tests;
+
+///
+/// Tests for the ReactiveUIBuilder core functionality.
+///
+public class ReactiveUIBuilderCoreTests
+{
+ [Fact]
+ public void CreateBuilder_Should_Return_Builder_Instance()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ Assert.NotNull(builder);
+ Assert.IsType(builder);
+ }
+
+ [Fact]
+ public void WithCoreServices_Should_Register_Core_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ builder.WithCoreServices().Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+
+ var typeConverter = locator.GetService();
+ Assert.NotNull(typeConverter);
+ }
+
+ [Fact]
+ public void WithPlatformServices_Should_Register_Platform_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ builder.WithPlatformServices().Build();
+
+ var services = locator.GetServices();
+ Assert.NotNull(services);
+ Assert.True(services.Any());
+ }
+
+ [Fact]
+ public void WithCustomRegistration_Should_Execute_Custom_Action()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ var customServiceRegistered = false;
+
+ builder.WithCustomRegistration(r =>
+ {
+ r.RegisterConstant("TestValue", typeof(string));
+ customServiceRegistered = true;
+ }).Build();
+
+ Assert.True(customServiceRegistered);
+ var service = locator.GetService();
+ Assert.Equal("TestValue", service);
+ }
+
+ [Fact]
+ public void Build_Should_Always_Register_Core_Services()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.Build();
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+ }
+
+ [Fact]
+ public void WithCustomRegistration_With_Null_Action_Should_Throw()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ Assert.Throws(() => builder.WithCustomRegistration(null!));
+ }
+
+ [Fact]
+ public void WithViewsFromAssembly_Should_Register_Views()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ var assembly = typeof(ReactiveUIBuilderCoreTests).Assembly;
+
+ builder.WithViewsFromAssembly(assembly).Build();
+ Assert.NotNull(builder);
+ }
+
+ [Fact]
+ public void WithViewsFromAssembly_With_Null_Assembly_Should_Throw()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+ Assert.Throws(() => builder.WithViewsFromAssembly(null!));
+ }
+
+ [Fact]
+ public void WithCoreServices_Called_Multiple_Times_Should_Not_Register_Twice()
+ {
+ using var locator = new ModernDependencyResolver();
+ var builder = locator.CreateBuilder();
+
+ builder.WithCoreServices().WithCoreServices().Build();
+
+ var services = locator.GetServices();
+ Assert.NotNull(services);
+ Assert.True(services.Any());
+ }
+
+ [Fact]
+ public void Builder_Should_Support_Fluent_Chaining()
+ {
+ using var locator = new ModernDependencyResolver();
+ var customServiceRegistered = false;
+
+ locator.CreateBuilder()
+ .WithCoreServices()
+ .WithPlatformServices()
+ .WithCustomRegistration(r =>
+ {
+ r.RegisterConstant("Test", typeof(string));
+ customServiceRegistered = true;
+ })
+ .Build();
+
+ Assert.True(customServiceRegistered);
+ var service = locator.GetService();
+ Assert.Equal("Test", service);
+
+ var observableProperty = locator.GetService();
+ Assert.NotNull(observableProperty);
+ }
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/App.xaml b/src/ReactiveUI.Builder.WpfApp/App.xaml
new file mode 100644
index 000000000..79a2684d9
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/App.xaml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/ReactiveUI.Builder.WpfApp/App.xaml.cs b/src/ReactiveUI.Builder.WpfApp/App.xaml.cs
new file mode 100644
index 000000000..49e1d113e
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/App.xaml.cs
@@ -0,0 +1,95 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reactive.Linq;
+using System.Windows;
+using ReactiveUI.Wpf;
+using Splat;
+
+namespace ReactiveUI.Builder.WpfApp;
+
+///
+/// Interaction logic for App.xaml.
+///
+[SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "Disposed on application exit in OnExit")]
+public partial class App : Application
+{
+ private Services.WpfAutoSuspendHelper? _autoSuspend;
+ private Services.FileJsonSuspensionDriver? _driver;
+ private Services.ChatNetworkService? _networkService;
+
+ ///
+ /// Raises the event.
+ ///
+ /// A that contains the event data.
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ // Initialize ReactiveUI via the Builder
+ var locator = Locator.CurrentMutable;
+ var builder = locator.CreateBuilder();
+ builder
+ .WithCoreServices()
+ .WithWpf()
+ .WithViewsFromAssembly(typeof(App).Assembly)
+ .WithCustomRegistration(r =>
+ {
+ // Register IScreen implementation as a factory so creation happens after state is loaded
+ r.Register(() => new ViewModels.AppBootstrapper());
+
+ // Register MessageBus as a singleton if not already
+ if (Locator.Current.GetService() is null)
+ {
+ r.RegisterConstant(MessageBus.Current);
+ }
+ })
+ .Build();
+
+ // Setup Suspension
+ RxApp.SuspensionHost.CreateNewAppState = () => new ViewModels.ChatState();
+
+ var statePath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "ReactiveUI.Builder.WpfApp",
+ "state.json");
+ Directory.CreateDirectory(Path.GetDirectoryName(statePath)!);
+
+ _driver = new Services.FileJsonSuspensionDriver(statePath);
+ _autoSuspend = new Services.WpfAutoSuspendHelper(this, _driver);
+ _autoSuspend.OnStartup();
+
+ // Load state from disk (or create new)
+ var loaded = _driver.LoadState().Wait();
+ RxApp.SuspensionHost.AppState = loaded;
+
+ // Start network service
+ _networkService = new Services.ChatNetworkService();
+ _networkService.Start();
+
+ // Create and show the shell
+ var mainWindow = new MainWindow();
+ MainWindow = mainWindow;
+ mainWindow.Show();
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// An that contains the event data.
+ protected override void OnExit(ExitEventArgs e)
+ {
+ _networkService?.Dispose();
+ if (_driver is not null && RxApp.SuspensionHost.AppState is not null)
+ {
+ _driver.SaveState(RxApp.SuspensionHost.AppState).Wait();
+ }
+
+ _autoSuspend?.OnExit();
+ base.OnExit(e);
+ }
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/AssemblyInfo.cs b/src/ReactiveUI.Builder.WpfApp/AssemblyInfo.cs
new file mode 100644
index 000000000..d6bbbfdbe
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Windows;
+
+[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
diff --git a/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml b/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml
new file mode 100644
index 000000000..ce51432bb
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs b/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs
new file mode 100644
index 000000000..47e2b54eb
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/MainWindow.xaml.cs
@@ -0,0 +1,59 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Windows;
+using Splat;
+
+namespace ReactiveUI.Builder.WpfApp;
+
+///
+/// Interaction logic for MainWindow.xaml.
+///
+public partial class MainWindow : Window, IViewFor
+{
+ ///
+ /// The view model property.
+ ///
+ public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
+ nameof(ViewModel), typeof(ViewModels.AppBootstrapper), typeof(MainWindow), new PropertyMetadata(null));
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ // Set up content host with routing
+ var host = new RoutedViewHost
+ {
+ Router = Locator.Current.GetService()!.Router,
+ DefaultContent = new System.Windows.Controls.TextBlock { Text = "Loading...", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center },
+ };
+
+ Content = host;
+ ViewModel = (ViewModels.AppBootstrapper)Locator.Current.GetService()!;
+ }
+
+ ///
+ /// Gets or sets the ViewModel corresponding to this specific View. This should be
+ /// a DependencyProperty if you're using XAML.
+ ///
+ public ViewModels.AppBootstrapper? ViewModel
+ {
+ get => (ViewModels.AppBootstrapper?)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ ///
+ /// Gets or sets the ViewModel corresponding to this specific View. This should be
+ /// a DependencyProperty if you're using XAML.
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (ViewModels.AppBootstrapper?)value;
+ }
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/ReactiveUI.Builder.WpfApp.csproj b/src/ReactiveUI.Builder.WpfApp/ReactiveUI.Builder.WpfApp.csproj
new file mode 100644
index 000000000..c755cf719
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/ReactiveUI.Builder.WpfApp.csproj
@@ -0,0 +1,17 @@
+
+
+
+ WinExe
+ net9.0-windows10.0.19041.0
+ enable
+ enable
+ true
+ false
+ A sample WPF application using ReactiveUI.
+
+
+
+
+
+
+
diff --git a/src/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs b/src/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs
new file mode 100644
index 000000000..0af0f7ad4
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/Services/AppInstance.cs
@@ -0,0 +1,11 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Builder.WpfApp.Services;
+
+internal static class AppInstance
+{
+ public static readonly Guid Id = Guid.NewGuid();
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkMessage.cs b/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkMessage.cs
new file mode 100644
index 000000000..6900759b6
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkMessage.cs
@@ -0,0 +1,66 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Builder.WpfApp.Services;
+
+///
+/// Network message payload used to broadcast chat messages.
+///
+public sealed class ChatNetworkMessage
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ChatNetworkMessage()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with values.
+ ///
+ /// The unique identifier for the room.
+ /// The human-readable room name used as the MessageBus contract.
+ /// The sender name.
+ /// The message text.
+ /// The message timestamp.
+ public ChatNetworkMessage(string roomId, string roomName, string sender, string text, DateTimeOffset timestamp)
+ {
+ RoomId = roomId;
+ RoomName = roomName;
+ Sender = sender;
+ Text = text;
+ Timestamp = timestamp;
+ }
+
+ ///
+ /// Gets or sets the room ID.
+ ///
+ public string RoomId { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the room name.
+ ///
+ public string RoomName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the sender.
+ ///
+ public string Sender { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the message text.
+ ///
+ public string Text { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the timestamp.
+ ///
+ public DateTimeOffset Timestamp { get; set; }
+
+ ///
+ /// Gets or sets the originating app instance id.
+ ///
+ public Guid InstanceId { get; set; }
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs b/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs
new file mode 100644
index 000000000..eb4bd221d
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/Services/ChatNetworkService.cs
@@ -0,0 +1,139 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.Net;
+using System.Net.Sockets;
+using System.Text.Json;
+
+namespace ReactiveUI.Builder.WpfApp.Services;
+
+///
+/// A simple UDP-based network relay to share chat messages and room events between app instances.
+///
+public sealed class ChatNetworkService : IDisposable
+{
+ private const string RoomsContract = "__rooms__";
+ private const int Port = 54545;
+
+ // IPv4 local multicast address
+ private static readonly IPAddress MulticastAddress = IPAddress.Parse("239.255.0.1");
+
+ private readonly UdpClient _udp; // sender
+ private readonly IPEndPoint _sendEndpoint;
+ private readonly CancellationTokenSource _cts = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ChatNetworkService()
+ {
+ _sendEndpoint = new IPEndPoint(MulticastAddress, Port);
+ _udp = new UdpClient(AddressFamily.InterNetwork);
+
+ try
+ {
+ // Enable multicast loopback so we can also receive our messages (we filter locally using InstanceId)
+ _udp.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 1);
+ _udp.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastLoopback, true);
+ }
+ catch
+ {
+ // ignore
+ }
+
+ // Outgoing chat messages (default contract)
+ MessageBus.Current.Listen()
+ .Subscribe(Send);
+
+ // Outgoing room events
+ MessageBus.Current.Listen(contract: RoomsContract)
+ .Subscribe(Send);
+ }
+
+ ///
+ /// Starts the background receive loop.
+ ///
+ public void Start() => Task.Run(ReceiveLoop, _cts.Token);
+
+ ///
+ public void Dispose()
+ {
+ _cts.Cancel();
+ _udp.Dispose();
+ _cts.Dispose();
+ }
+
+ private async Task ReceiveLoop()
+ {
+ using var listener = new UdpClient(AddressFamily.InterNetwork);
+ try
+ {
+ // Allow multiple processes to bind the same UDP port
+ listener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ listener.ExclusiveAddressUse = false;
+ listener.Client.Bind(new IPEndPoint(IPAddress.Any, Port));
+
+ // Join multicast group on default interface
+ listener.JoinMulticastGroup(MulticastAddress);
+ }
+ catch
+ {
+ return;
+ }
+
+ while (!_cts.IsCancellationRequested)
+ {
+ try
+ {
+ var result = await listener.ReceiveAsync(_cts.Token).ConfigureAwait(false);
+ var buffer = result.Buffer;
+
+ // Inspect JSON for known properties to determine message type
+ using var doc = JsonDocument.Parse(buffer);
+ var root = doc.RootElement;
+ var isRoomEvent = root.TryGetProperty("Kind", out _) || root.TryGetProperty("Snapshot", out _);
+
+ if (isRoomEvent)
+ {
+ var evt = JsonSerializer.Deserialize(buffer);
+ if (evt is not null)
+ {
+ MessageBus.Current.SendMessage(evt, contract: RoomsContract);
+ }
+
+ continue;
+ }
+
+ // Otherwise treat as chat message
+ var chat = JsonSerializer.Deserialize(buffer);
+ if (chat is not null)
+ {
+ MessageBus.Current.SendMessage(chat, contract: chat.RoomName);
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch
+ {
+ // ignore malformed input
+ }
+ }
+ }
+
+ private void Send(object message)
+ {
+ try
+ {
+ var bytes = JsonSerializer.SerializeToUtf8Bytes(message, message.GetType());
+ _udp.Send(bytes, bytes.Length, _sendEndpoint);
+ }
+ catch
+ {
+ // ignore
+ }
+ }
+}
diff --git a/src/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs b/src/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs
new file mode 100644
index 000000000..656fa6326
--- /dev/null
+++ b/src/ReactiveUI.Builder.WpfApp/Services/FileJsonSuspensionDriver.cs
@@ -0,0 +1,74 @@
+// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.IO;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Text.Json;
+
+namespace ReactiveUI.Builder.WpfApp.Services;
+
+///
+/// FileJsonSuspensionDriver.
+///
+///
+///
+/// Initializes a new instance of the class.
+///
+/// The path.
+public sealed class FileJsonSuspensionDriver(string path) : ISuspensionDriver
+{
+ private readonly JsonSerializerOptions _options = new() { WriteIndented = true };
+
+ ///
+ /// Invalidates the application state (i.e. deletes it from disk).
+ ///
+ ///
+ /// A completed observable.
+ ///
+ public IObservable InvalidateState() => Observable.Start(
+ () =>
+ {
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ },
+ RxApp.TaskpoolScheduler);
+
+ ///
+ /// Loads the application state from persistent storage.
+ ///
+ ///
+ /// An object observable.
+ ///
+ public IObservable