diff --git a/.github/workflows/ci-hotreload.yml b/.github/workflows/ci-hotreload.yml
new file mode 100644
index 000000000..f2cfa452d
--- /dev/null
+++ b/.github/workflows/ci-hotreload.yml
@@ -0,0 +1,34 @@
+name: CI - HotReload
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'src/HotReload/**'
+ - 'eng/**'
+ - 'Directory.Build.props'
+ - 'Directory.Build.targets'
+ - 'Directory.Packages.props'
+ - 'global.json'
+ - 'NuGet.config'
+ pull_request:
+ types: [opened, synchronize, reopened, edited]
+ branches: [main]
+ paths:
+ - 'src/HotReload/**'
+ - 'eng/**'
+ - 'Directory.Build.props'
+ - 'Directory.Build.targets'
+ - 'Directory.Packages.props'
+ - 'global.json'
+ - 'NuGet.config'
+
+jobs:
+ build:
+ uses: ./.github/workflows/_build.yml
+ with:
+ project-path: src/HotReload/HotReload.slnf
+ project-name: hotreload
+ run-tests: false
+ pack: true
+ install-workloads: false
diff --git a/AGENTS.md b/AGENTS.md
index 58ef251cd..642f4618d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,6 +13,7 @@ This repository hosts experimental .NET MAUI packages. It is a **multi-product m
| **Cli** | `Microsoft.Maui.Cli` (global tool: `maui`) | Unified MAUI command-line tool: environment diagnostics (`maui doctor`), Android SDK/JDK/emulator management, Apple platform management, device listing, `maui go` for rapid prototyping, `maui profile startup` for performance tracing, and the `maui devflow` automation surface. |
| **DevFlow** | `Microsoft.Maui.DevFlow.*` packages plus the unified `maui devflow` CLI surface | Runtime MAUI automation toolkit. In-app agent with HTTP API, visual tree inspection, CDP bridge for Blazor WebViews, MCP server for AI agents, cross-platform driver library. |
| **Comet** | `Comet`, `Comet.SourceGenerator`, `Comet.Layout.Yoga` | Experimental MVU UI framework for .NET MAUI — C# fluent UI, signals/reactive state, Yoga layout. |
+| **HotReload** | `Microsoft.Maui.HotReload` | MetadataUpdateHandler for .NET MAUI hot reload — `IHotReloadable` interface with per-instance `OnHotReload()` callbacks, backed by a Roslyn source generator. |
| **Go** | `Microsoft.Maui.Go.Server` + Comet Go companion app | Single-file Comet apps server and companion app for rapid prototyping (alpha; sister to Comet). |
| **Essentials.AI** | `Microsoft.Maui.Essentials.AI` | On-device AI for .NET MAUI — semantic search, chat completion, embeddings, and tool use against local models. |
| **AIExtensions** | `Microsoft.Maui.AI.Attributes` | Source-generated AI tool bindings — turns decorated C# methods into `Microsoft.Extensions.AI`-callable tools using Roslyn, with DI parameter binding and AOT support. |
@@ -128,6 +129,11 @@ maui-labs/
│ ├── Linux.Gtk4/ # Linux GTK4 platform backend
│ ├── MacOS/ # macOS AppKit platform backend
│ └── Windows.WPF/ # WPF platform backend
+├── src/
+│ └── HotReload/ # HotReload product
+│ ├── Microsoft.Maui.HotReload/ # Shipping package (IHotReloadable, registry, handler)
+│ ├── Microsoft.Maui.HotReload.SourceGen/ # Roslyn source generator (bundled as analyzer)
+│ └── HotReload.slnf # Solution filter
├── samples/ # Sample MAUI apps (not shipped)
├── playground/ # Manual test/scratch apps
├── eng/ # Shared build infrastructure
diff --git a/MauiLabs.slnx b/MauiLabs.slnx
index a4ceebe82..007094080 100644
--- a/MauiLabs.slnx
+++ b/MauiLabs.slnx
@@ -17,6 +17,10 @@
+
+
+
+
diff --git a/README.md b/README.md
index b9a7f8fda..33ae3df20 100644
--- a/README.md
+++ b/README.md
@@ -140,6 +140,14 @@ Built artifacts are exposed as `@(MauiAppArtifact)` items with `ArtifactType`, `
|---------|-------------|
| `Microsoft.Maui.Build.AppProjectReference` | Build-time app project reference with artifact discovery |
+### HotReload
+
+A `MetadataUpdateHandler` for .NET MAUI that delivers per-instance hot reload callbacks via the `IHotReloadable` interface, backed by a Roslyn source generator.
+
+| Package | Description |
+|---------|-------------|
+| `Microsoft.Maui.HotReload` | MetadataUpdateHandler for .NET MAUI hot reload with `IHotReloadable` callbacks |
+
## Agent Skills
This repository is also a marketplace for distributable agent skills for .NET MAUI development. Skills are organized as plugins compatible with Copilot CLI, Claude Code, and VS Code.
diff --git a/eng/pipelines/devflow-official.yml b/eng/pipelines/devflow-official.yml
index f49a5e9f8..5ee56df9a 100644
--- a/eng/pipelines/devflow-official.yml
+++ b/eng/pipelines/devflow-official.yml
@@ -44,6 +44,10 @@ parameters:
displayName: 'Publish EssentialsAI packages to NuGet.org'
type: boolean
default: false
+- name: publishHotReloadNuget
+ displayName: 'Publish HotReload packages to NuGet.org'
+ type: boolean
+ default: false
variables:
- template: /eng/pipelines/common-variables.yml@self
@@ -387,7 +391,33 @@ extends:
-projects $(Build.SourcesDirectory)\platforms\MacOS\MacOS-libs.slnf
$(_OfficialBuildArgs)
displayName: Build, Test and Pack macOS AppKit
-
+
+ # HotReload targets netstandard2.0 + net10.0 with no MAUI workloads.
+ # Builds and packs on Windows for MicroBuild/ESRP signing.
+ - job: HotReload
+ displayName: HotReload - Windows
+ pool:
+ name: NetCore1ESPool-Internal
+ demands: ImageOverride -equals windows.vs2026preview.scout.amd64
+ strategy:
+ matrix:
+ Release:
+ _BuildConfig: Release
+ _OfficialBuildArgs: /p:DotNetSignType=$(_SignType)
+ /p:TeamName=$(_TeamName)
+ /p:OfficialBuildId=$(BUILD.BUILDNUMBER)
+ steps:
+ - task: UseDotNet@2
+ displayName: Install .NET SDK
+ inputs:
+ version: 10.0.105
+ - script: eng\common\cibuild.cmd
+ -configuration $(_BuildConfig)
+ -prepareMachine
+ -projects $(Build.SourcesDirectory)\src\HotReload\HotReload.slnf
+ $(_OfficialBuildArgs)
+ displayName: Build and Pack HotReload
+
- template: /eng/common/templates-official/post-build/post-build.yml@self
parameters:
enableSourceLinkValidation: false
@@ -834,3 +864,58 @@ extends:
packageParentPath: '$(Pipeline.Workspace)/MacOSPackages'
nuGetFeedType: external
publishFeedCredentials: 'nuget.org (dotnetframework)'
+
+ # Publish HotReload packages to NuGet.org
+ - ${{ if eq(parameters.publishHotReloadNuget, true) }}:
+ - stage: publish_hotreload_nuget
+ displayName: 'Publish HotReload to NuGet.org'
+ dependsOn:
+ - Validate
+ - publish_using_darc
+ jobs:
+ - job: PrepareArtifacts
+ displayName: 'Prepare HotReload Artifacts'
+ timeoutInMinutes: 15
+ pool:
+ name: NetCore1ESPool-Internal
+ image: windows.vs2026preview.scout.amd64
+ os: windows
+ templateContext:
+ outputs:
+ - output: pipelineArtifact
+ displayName: Publish HotReload Packages
+ targetPath: '$(Pipeline.Workspace)/HotReloadPackages'
+ artifactName: HotReloadPackagesForNuGet
+ steps:
+ - download: current
+ artifact: PackageArtifacts
+ displayName: Download PackageArtifacts
+ - powershell: |
+ New-Item -ItemType Directory -Force -Path '$(Pipeline.Workspace)/HotReloadPackages'
+ Copy-Item '$(Pipeline.Workspace)/PackageArtifacts/Microsoft.Maui.HotReload.*.nupkg' '$(Pipeline.Workspace)/HotReloadPackages/' -Verbose
+ displayName: Filter HotReload packages
+
+ - job: PublishNuGet
+ displayName: 'Push HotReload to NuGet.org'
+ dependsOn: PrepareArtifacts
+ timeoutInMinutes: 30
+ pool:
+ name: NetCore1ESPool-Internal
+ image: windows.vs2026preview.scout.amd64
+ os: windows
+ templateContext:
+ type: releaseJob
+ isProduction: true
+ inputs:
+ - input: pipelineArtifact
+ artifactName: HotReloadPackagesForNuGet
+ targetPath: '$(Pipeline.Workspace)/HotReloadPackages'
+ steps:
+ - task: 1ES.PublishNuget@1
+ displayName: 'Push HotReload to NuGet.org'
+ inputs:
+ useDotNetTask: false
+ packagesToPush: '$(Pipeline.Workspace)/HotReloadPackages/*.nupkg'
+ packageParentPath: '$(Pipeline.Workspace)/HotReloadPackages'
+ nuGetFeedType: external
+ publishFeedCredentials: 'nuget.org (dotnetframework)'
diff --git a/src/HotReload/HotReload.slnf b/src/HotReload/HotReload.slnf
new file mode 100644
index 000000000..121a4ecc7
--- /dev/null
+++ b/src/HotReload/HotReload.slnf
@@ -0,0 +1,9 @@
+{
+ "solution": {
+ "path": "../../MauiLabs.slnx",
+ "projects": [
+ "src\\HotReload\\Microsoft.Maui.HotReload\\Microsoft.Maui.HotReload.csproj",
+ "src\\HotReload\\Microsoft.Maui.HotReload.SourceGen\\Microsoft.Maui.HotReload.SourceGen.csproj"
+ ]
+ }
+}
diff --git a/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Shipped.md b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Shipped.md
new file mode 100644
index 000000000..7519a8a8d
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Shipped.md
@@ -0,0 +1,3 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
diff --git a/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Unshipped.md b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Unshipped.md
new file mode 100644
index 000000000..5e79268c7
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,8 @@
+; Unshipped analyzer release
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+MUH0001 | HotReload | Info | HotReloadInitialize not called in constructor
diff --git a/src/HotReload/Microsoft.Maui.HotReload.SourceGen/HotReloadAwareGenerator.cs b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/HotReloadAwareGenerator.cs
new file mode 100644
index 000000000..59f1dd1b0
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/HotReloadAwareGenerator.cs
@@ -0,0 +1,205 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.Maui.Labs.HotReload.SourceGen
+{
+ ///
+ /// Generates a HotReloadInitialize() method for every partial class that
+ /// implements Microsoft.Maui.Labs.HotReload.IHotReloadAware. Also emits an informational
+ /// diagnostic if no constructor appears to call it.
+ ///
+ [Generator]
+ public sealed class HotReloadAwareGenerator : IIncrementalGenerator
+ {
+ const string IHotReloadAwareFullName = "Microsoft.Maui.Labs.HotReload.IHotReloadAware";
+
+ static readonly DiagnosticDescriptor MissingInitCallDescriptor = new(
+ id: "MUH0001",
+ title: "HotReloadInitialize not called",
+ messageFormat: "'{0}' implements IHotReloadAware but no constructor calls HotReloadInitialize(). Add a call to HotReloadInitialize() in your constructor to enable hot reload notifications.",
+ category: "HotReload",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Types implementing IHotReloadAware must call the generated HotReloadInitialize() in their constructor for hot reload registration to take effect.");
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var candidates = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ predicate: IsPartialClassDeclaration,
+ transform: GetHotReloadableClassInfo)
+ .Where(static info => info is not null)
+ .Select(static (info, _) => info!);
+
+ // Deduplicate: a class split across multiple partial declarations will produce multiple
+ // candidates with the same HintName. Only the first one should emit source.
+ context.RegisterSourceOutput(candidates.Collect(), EmitAllSources);
+ }
+
+ static bool IsPartialClassDeclaration(SyntaxNode node, System.Threading.CancellationToken _) =>
+ node is ClassDeclarationSyntax c &&
+ c.Modifiers.Any(SyntaxKind.PartialKeyword) &&
+ c.BaseList is not null;
+
+ static HotReloadableClassInfo? GetHotReloadableClassInfo(GeneratorSyntaxContext ctx, System.Threading.CancellationToken ct)
+ {
+ var classDecl = (ClassDeclarationSyntax)ctx.Node;
+ if (ctx.SemanticModel.GetDeclaredSymbol(classDecl, ct) is not INamedTypeSymbol classSymbol)
+ return null;
+
+ // Skip generic types and nested types: the simple partial class codegen doesn't
+ // handle type parameters or containing-type wrappers. Users of those shapes must
+ // call HotReloadRegistry.Register manually.
+ if (classSymbol.IsGenericType || classSymbol.ContainingType != null)
+ return null;
+
+ // Check interfaces: prefer semantic model, but fall back to syntax name matching
+ // so the generator works even when the referenced assembly isn't fully loaded
+ // in the compilation (e.g. net10.0 lib consumed by a net11.0-* project).
+ bool implementsIHotReloadAware = false;
+
+ var iface = ctx.SemanticModel.Compilation.GetTypeByMetadataName(IHotReloadAwareFullName);
+ if (iface is not null)
+ {
+ // Semantic path: exact type identity check
+ foreach (var i in classSymbol.AllInterfaces)
+ {
+ if (SymbolEqualityComparer.Default.Equals(i, iface))
+ {
+ implementsIHotReloadAware = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Syntax fallback: match by short or fully-qualified name in the base list
+ foreach (var baseType in classDecl.BaseList?.Types ?? default)
+ {
+ var typeName = baseType.Type switch
+ {
+ IdentifierNameSyntax id => id.Identifier.Text,
+ QualifiedNameSyntax q => q.Right.Identifier.Text,
+ _ => null
+ };
+ if (typeName == "IHotReloadAware")
+ {
+ implementsIHotReloadAware = true;
+ break;
+ }
+ }
+ }
+
+ if (!implementsIHotReloadAware)
+ return null;
+
+ // Check whether any constructor in this partial declaration calls HotReloadInitialize.
+ // Walk identifier tokens (skipping trivia/comments) so we don't get false positives
+ // from documentation or comments mentioning "HotReloadInitialize".
+ bool hasInitCall = false;
+ foreach (var member in classDecl.Members)
+ {
+ if (member is not ConstructorDeclarationSyntax ctor)
+ continue;
+
+ SyntaxNode? body = (SyntaxNode?)ctor.Body ?? ctor.ExpressionBody;
+ if (body is null)
+ continue;
+
+ foreach (var id in body.DescendantNodes().OfType())
+ {
+ if (id.Identifier.ValueText == "HotReloadInitialize")
+ {
+ hasInitCall = true;
+ break;
+ }
+ }
+
+ if (hasInitCall)
+ break;
+ }
+
+ return new HotReloadableClassInfo(
+ ns: classSymbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : classSymbol.ContainingNamespace.ToDisplayString(),
+ className: classSymbol.Name,
+ hintName: classSymbol.ToDisplayString().Replace('.', '_').Replace('<', '_').Replace('>', '_'),
+ location: classDecl.GetLocation(),
+ hasInitCall: hasInitCall);
+ }
+
+ static void EmitAllSources(SourceProductionContext ctx, System.Collections.Immutable.ImmutableArray allCandidates)
+ {
+ var seen = new System.Collections.Generic.HashSet(StringComparer.Ordinal);
+ foreach (var info in allCandidates)
+ {
+ // Only emit once per unique class (multiple partial declarations yield the same HintName).
+ if (!seen.Add(info.HintName))
+ continue;
+
+ EmitSource(ctx, info);
+ }
+ }
+
+ static void EmitSource(SourceProductionContext ctx, HotReloadableClassInfo info)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine();
+
+ if (info.Namespace is not null)
+ {
+ sb.AppendLine($"namespace {info.Namespace}");
+ sb.AppendLine("{");
+ }
+
+ string indent = info.Namespace is not null ? " " : string.Empty;
+ sb.AppendLine($"{indent}partial class {info.ClassName}");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} /// Auto-generated. Call from your constructor to register this instance for hot reload notifications.");
+ sb.AppendLine($"{indent} private void HotReloadInitialize()");
+ sb.AppendLine($"{indent} => global::Microsoft.Maui.Labs.HotReload.HotReloadRegistry.Register(this);");
+ sb.AppendLine($"{indent}}}");
+
+ if (info.Namespace is not null)
+ sb.AppendLine("}");
+
+ ctx.AddSource($"{info.HintName}.HotReload.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
+
+ if (!info.HasInitCall)
+ {
+ ctx.ReportDiagnostic(Diagnostic.Create(
+ MissingInitCallDescriptor,
+ info.Location,
+ info.ClassName));
+ }
+ }
+
+ sealed class HotReloadableClassInfo
+ {
+ public string? Namespace { get; }
+ public string ClassName { get; }
+ public string HintName { get; }
+ public Location Location { get; }
+ public bool HasInitCall { get; }
+
+ public HotReloadableClassInfo(string? ns, string className, string hintName, Location location, bool hasInitCall)
+ {
+ Namespace = ns;
+ ClassName = className;
+ HintName = hintName;
+ Location = location;
+ HasInitCall = hasInitCall;
+ }
+ }
+ }
+}
diff --git a/src/HotReload/Microsoft.Maui.HotReload.SourceGen/Microsoft.Maui.HotReload.SourceGen.csproj b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/Microsoft.Maui.HotReload.SourceGen.csproj
new file mode 100644
index 000000000..2da57d100
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload.SourceGen/Microsoft.Maui.HotReload.SourceGen.csproj
@@ -0,0 +1,28 @@
+
+
+
+ netstandard2.0
+ Microsoft.Maui.Labs.HotReload.SourceGen
+ Microsoft.Maui.Labs.HotReload.SourceGen
+ true
+ true
+ false
+ enable
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/HotReload/Microsoft.Maui.HotReload/AssemblyInfo.cs b/src/HotReload/Microsoft.Maui.HotReload/AssemblyInfo.cs
new file mode 100644
index 000000000..2d59ff52f
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+#if !NETSTANDARD
+using System.Reflection.Metadata;
+
+[assembly: MetadataUpdateHandler(typeof(Microsoft.Maui.Labs.HotReload.MauiMetadataUpdateHandler))]
+#endif
diff --git a/src/HotReload/Microsoft.Maui.HotReload/HotReloadRegistry.cs b/src/HotReload/Microsoft.Maui.HotReload/HotReloadRegistry.cs
new file mode 100644
index 000000000..b41f7101c
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload/HotReloadRegistry.cs
@@ -0,0 +1,129 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Maui.Labs.HotReload
+{
+ ///
+ /// Registry that tracks live instances so they can be notified
+ /// when their type is updated via .NET Hot Reload.
+ ///
+ ///
+ /// Instances are held via so registration does not prevent garbage collection.
+ ///
+ public static class HotReloadRegistry
+ {
+ static readonly object _lock = new();
+ static readonly Dictionary>> _registry = new();
+
+ ///
+ /// Registers so it receives
+ /// callbacks when its type (or any base type) is updated at runtime.
+ ///
+ public static void Register(IHotReloadAware instance)
+ {
+ if (instance is null)
+ return;
+
+ var type = instance.GetType();
+ lock (_lock)
+ {
+ if (!_registry.TryGetValue(type, out var list))
+ _registry[type] = list = new List>();
+
+ // Compact dead references before adding to keep the list small.
+ list.RemoveAll(static w => !w.TryGetTarget(out _));
+ list.Add(new WeakReference(instance));
+ }
+ }
+
+ ///
+ /// Removes from the registry.
+ /// Safe to call even if the instance was never registered.
+ ///
+ public static void Unregister(IHotReloadAware instance)
+ {
+ if (instance is null)
+ return;
+
+ var type = instance.GetType();
+ lock (_lock)
+ {
+ if (!_registry.TryGetValue(type, out var list))
+ return;
+
+ for (int i = list.Count - 1; i >= 0; i--)
+ {
+ if (!list[i].TryGetTarget(out var target))
+ {
+ // Remove dead references opportunistically.
+ list.RemoveAt(i);
+ }
+ else if (ReferenceEquals(target, instance))
+ {
+ list.RemoveAt(i);
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Notifies all registered instances whose runtime type is, or derives from, any of the
+ /// . Called by .
+ ///
+ internal static void NotifyInstances(Type[] updatedTypes)
+ {
+ List? toNotify = null;
+
+ lock (_lock)
+ {
+ foreach (var updatedType in updatedTypes)
+ {
+ foreach (var kvp in _registry)
+ {
+ // Notify instances whose concrete type is updatedType or a subtype of it.
+ if (!updatedType.IsAssignableFrom(kvp.Key))
+ continue;
+
+ foreach (var weakRef in kvp.Value)
+ {
+ if (!weakRef.TryGetTarget(out var instance))
+ continue;
+
+ // Deduplicate by reference to avoid double-notifying when both a base
+ // type and derived type appear in updatedTypes simultaneously.
+ toNotify ??= new List();
+ bool alreadyQueued = false;
+ for (int i = 0; i < toNotify.Count; i++)
+ {
+ if (ReferenceEquals(toNotify[i], instance))
+ {
+ alreadyQueued = true;
+ break;
+ }
+ }
+ if (!alreadyQueued)
+ toNotify.Add(instance);
+ }
+ }
+ }
+ }
+
+ if (toNotify is null)
+ return;
+
+ foreach (var instance in toNotify)
+ {
+ try
+ {
+ instance.OnHotReload(updatedTypes);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[MauiHotReload] Error in OnHotReload for {instance.GetType()}: {ex}");
+ }
+ }
+ }
+ }
+}
diff --git a/src/HotReload/Microsoft.Maui.HotReload/IHotReloadAware.cs b/src/HotReload/Microsoft.Maui.HotReload/IHotReloadAware.cs
new file mode 100644
index 000000000..7d2cdf606
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload/IHotReloadAware.cs
@@ -0,0 +1,25 @@
+//
+#nullable enable
+using System;
+
+namespace Microsoft.Maui.Labs.HotReload
+{
+ ///
+ /// Implemented by types that want to be notified when their code is updated via .NET Hot Reload.
+ ///
+ ///
+ /// To register for notifications, call HotReloadInitialize() in your constructor
+ /// (generated automatically by Microsoft.Maui.Labs.HotReload.SourceGen for partial classes).
+ ///
+ public interface IHotReloadAware
+ {
+ ///
+ /// Called when this instance's type (or a base type) has been updated via hot reload.
+ ///
+ ///
+ /// The set of types that were updated, as reported by the .NET Hot Reload host.
+ /// May be if the host did not provide type information.
+ ///
+ void OnHotReload(Type[]? updatedTypes);
+ }
+}
diff --git a/src/HotReload/Microsoft.Maui.HotReload/MauiMetadataUpdateHandler.cs b/src/HotReload/Microsoft.Maui.HotReload/MauiMetadataUpdateHandler.cs
new file mode 100644
index 000000000..aaaca86b0
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload/MauiMetadataUpdateHandler.cs
@@ -0,0 +1,46 @@
+#nullable enable
+using System;
+#if !NETSTANDARD
+using Microsoft.Maui.HotReload;
+#endif
+
+namespace Microsoft.Maui.Labs.HotReload
+{
+ ///
+ /// Handles .NET Hot Reload metadata update notifications, forwarding them to registered
+ /// instances and the MAUI view-level hot reload infrastructure.
+ ///
+ public static class MauiMetadataUpdateHandler
+ {
+ ///
+ /// Called by the .NET Hot Reload host after metadata has been applied.
+ /// Notifies all registered instances whose types were updated,
+ /// then forwards to MAUI's MauiHotReloadHelper for view-level reload.
+ ///
+ ///
+ /// This method is invoked only during hot reload sessions (debug/development builds).
+ /// Trimming and hot reload are mutually exclusive, so trim-compatibility warnings are suppressed.
+ ///
+#if !NETSTANDARD
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Hot Reload is not compatible with trimming.")]
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Hot Reload is not compatible with AOT.")]
+#endif
+ public static void UpdateApplication(Type[] types)
+ {
+ HotReloadRegistry.NotifyInstances(types);
+#if !NETSTANDARD
+ MauiHotReloadHelper.UpdateApplication(types);
+#endif
+ }
+
+ ///
+ /// Called by the .NET Hot Reload host before metadata is applied, to clear any caches.
+ ///
+ public static void ClearCache(Type[] types)
+ {
+#if !NETSTANDARD
+ MauiHotReloadHelper.ClearCache(types);
+#endif
+ }
+ }
+}
diff --git a/src/HotReload/Microsoft.Maui.HotReload/Microsoft.Maui.HotReload.csproj b/src/HotReload/Microsoft.Maui.HotReload/Microsoft.Maui.HotReload.csproj
new file mode 100644
index 000000000..2eacc81a4
--- /dev/null
+++ b/src/HotReload/Microsoft.Maui.HotReload/Microsoft.Maui.HotReload.csproj
@@ -0,0 +1,53 @@
+
+
+
+ netstandard2.0;net10.0
+ Microsoft.Maui.Labs.HotReload
+ Microsoft.Maui.Labs.HotReload
+ true
+ enable
+ latest
+
+
+
+
+
+
+
+
+ true
+ true
+ Microsoft.Maui.Labs.HotReload
+ Provides a MetadataUpdateHandler for .NET MAUI hot reload. Register any IHotReloadable instance to receive OnHotReload() callbacks when their type is hot-reloaded.
+ $(DefaultPackageTags);hotreload;developer-tools
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/HotReload/README.md b/src/HotReload/README.md
new file mode 100644
index 000000000..7f6199857
--- /dev/null
+++ b/src/HotReload/README.md
@@ -0,0 +1,107 @@
+# Microsoft.Maui.HotReload
+
+A `MetadataUpdateHandler` for .NET MAUI that delivers per-instance hot reload callbacks. Implement `IHotReloadable` on any class, call the generated `HotReloadInitialize()` in its constructor, and receive `OnHotReload()` notifications whenever your type is modified during a hot reload session.
+
+> ⚠️ **Experimental** — APIs may change between releases. This package is not covered by the [.NET MAUI Support Policy](https://dotnet.microsoft.com/platform/support/policy/maui) and is provided as-is.
+
+## How It Works
+
+The package has two components:
+
+| Assembly | Role |
+|----------|------|
+| `Microsoft.Maui.HotReload` | Ships the `IHotReloadable` interface, `HotReloadRegistry`, and `MauiMetadataUpdateHandler` |
+| `Microsoft.Maui.HotReload.SourceGen` | Roslyn incremental source generator bundled as an analyzer inside the NuGet package |
+
+When the .NET runtime applies a metadata update (hot reload), the `[assembly: MetadataUpdateHandler]` attribute routes the notification to `MauiMetadataUpdateHandler.UpdateApplication(Type[])`. This method:
+
+1. Walks the `HotReloadRegistry` to find all live `IHotReloadable` instances whose type (or a base type) was updated.
+2. Calls `OnHotReload()` on each matched instance.
+3. Forwards to MAUI's built-in `MauiHotReloadHelper` for view-level reload (on `net10.0`+).
+
+Instances are held via `WeakReference` so registration does not prevent garbage collection.
+
+## Quick Start
+
+Install the NuGet package:
+
+```bash
+dotnet add package Microsoft.Maui.HotReload --prerelease
+```
+
+Implement `IHotReloadable` on a `partial` class and call `HotReloadInitialize()` in the constructor:
+
+```csharp
+using Microsoft.Maui.HotReload;
+
+public partial class MainPage : ContentPage, IHotReloadable
+{
+ public MainPage()
+ {
+ HotReloadInitialize(); // Generated by the source generator
+ InitializeComponent();
+ }
+
+ public void OnHotReload()
+ {
+ // Re-apply dynamic state, refresh bindings, etc.
+ System.Diagnostics.Debug.WriteLine("MainPage was hot-reloaded!");
+ }
+}
+```
+
+That's it — during a hot reload session, `OnHotReload()` fires whenever `MainPage` (or any of its base types) is updated.
+
+## Source Generator
+
+The `Microsoft.Maui.HotReload.SourceGen` generator runs at compile time and, for every `partial` class implementing `IHotReloadable`:
+
+1. **Emits a `HotReloadInitialize()` method** that calls `HotReloadRegistry.Register(this)`.
+2. **Reports diagnostic `MUH0001`** (Info severity) if no constructor in the class appears to call `HotReloadInitialize()`.
+
+### Limitations
+
+- **Generic types and nested types** are not supported by the generator. For those, call `HotReloadRegistry.Register(this)` manually in the constructor.
+- The class **must be `partial`** for the generator to emit code into it.
+
+## Manual Registration
+
+For classes that cannot be `partial` or are generic/nested, register directly:
+
+```csharp
+public class MyService : IHotReloadable
+{
+ public MyService()
+ {
+ HotReloadRegistry.Register(this);
+ }
+
+ public void OnHotReload()
+ {
+ // Handle hot reload
+ }
+}
+```
+
+Call `HotReloadRegistry.Unregister(this)` if you need to stop receiving notifications before the instance is garbage-collected.
+
+## Target Frameworks
+
+| TFM | Behavior |
+|-----|----------|
+| `net10.0` | Full integration — `MetadataUpdateHandler` registered, forwards to MAUI's `MauiHotReloadHelper` |
+| `netstandard2.0` | `IHotReloadable` + `HotReloadRegistry` available for library authors; `MetadataUpdateHandler` not active |
+
+## Package
+
+| Package | Description |
+|---------|-------------|
+| `Microsoft.Maui.HotReload` | MetadataUpdateHandler for .NET MAUI hot reload with `IHotReloadable` callbacks |
+
+## Building
+
+```bash
+dotnet build src/HotReload/HotReload.slnf
+```
+
+No MAUI workload install is required — the project targets `netstandard2.0` and `net10.0` with a conditional `Microsoft.Maui.Core` reference.