diff --git a/SongTidy.Tests/MSTestSettings.cs b/SongTidy.Tests/MSTestSettings.cs
new file mode 100644
index 0000000..aaf278c
--- /dev/null
+++ b/SongTidy.Tests/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/SongTidy.Tests/ProjectSmokeTests.cs b/SongTidy.Tests/ProjectSmokeTests.cs
new file mode 100644
index 0000000..8ef7c1e
--- /dev/null
+++ b/SongTidy.Tests/ProjectSmokeTests.cs
@@ -0,0 +1,17 @@
+namespace SongTidy.Tests;
+
+///
+/// Contains baseline tests that verify the test project is wired to the application assembly.
+///
+[TestClass]
+public sealed class ProjectSmokeTests {
+ ///
+ /// Verifies that the main application assembly can be loaded from the test project.
+ ///
+ [TestMethod]
+ public void MainAssemblyIsReferenced() {
+ var assemblyName = typeof(global::SongTidy.ViewModels.MainWindowViewModel).Assembly.GetName().Name;
+
+ Assert.AreEqual("SongTidy", assemblyName);
+ }
+}
diff --git a/SongTidy.Tests/SongTidy.Tests.csproj b/SongTidy.Tests/SongTidy.Tests.csproj
new file mode 100644
index 0000000..9250254
--- /dev/null
+++ b/SongTidy.Tests/SongTidy.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net10.0
+ latest
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SongTidy.slnx b/SongTidy.slnx
new file mode 100644
index 0000000..4b21ef0
--- /dev/null
+++ b/SongTidy.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/SongTidy/App.axaml b/SongTidy/App.axaml
new file mode 100644
index 0000000..8988053
--- /dev/null
+++ b/SongTidy/App.axaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SongTidy/App.axaml.cs b/SongTidy/App.axaml.cs
new file mode 100644
index 0000000..40af2d6
--- /dev/null
+++ b/SongTidy/App.axaml.cs
@@ -0,0 +1,25 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+using SongTidy.ViewModels;
+using SongTidy.Views;
+
+namespace SongTidy;
+
+public partial class App : Application {
+ public override void Initialize() {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted() {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
+ desktop.MainWindow = new MainWindow {
+ DataContext = new MainWindowViewModel(),
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+
+}
diff --git a/SongTidy/Assets/avalonia-logo.ico b/SongTidy/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..f7da8bb
Binary files /dev/null and b/SongTidy/Assets/avalonia-logo.ico differ
diff --git a/SongTidy/Program.cs b/SongTidy/Program.cs
new file mode 100644
index 0000000..30b034b
--- /dev/null
+++ b/SongTidy/Program.cs
@@ -0,0 +1,32 @@
+using System;
+
+using Avalonia;
+using Avalonia.ReactiveUI;
+
+namespace SongTidy;
+
+///
+/// Provides the application entry point and Avalonia configuration.
+///
+sealed class Program {
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ ///
+ /// Builds the Avalonia application configuration.
+ ///
+ ///
+ /// This method is also discovered by the Avalonia visual designer, so it must remain public.
+ ///
+ /// The configured application builder.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace()
+ .UseReactiveUI();
+}
diff --git a/SongTidy/SongTidy.csproj b/SongTidy/SongTidy.csproj
new file mode 100644
index 0000000..f6d7433
--- /dev/null
+++ b/SongTidy/SongTidy.csproj
@@ -0,0 +1,32 @@
+
+
+ WinExe
+ net10.0
+ enable
+ true
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
+
+
diff --git a/SongTidy/ViewLocator.cs b/SongTidy/ViewLocator.cs
new file mode 100644
index 0000000..2401014
--- /dev/null
+++ b/SongTidy/ViewLocator.cs
@@ -0,0 +1,46 @@
+using System;
+
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+
+using SongTidy.ViewModels;
+
+namespace SongTidy;
+
+///
+/// Resolves a view for a view model by replacing the "ViewModel" suffix
+/// with "View" in the full type name.
+///
+public class ViewLocator : IDataTemplate {
+ ///
+ /// Creates the view that corresponds to the supplied view model.
+ ///
+ /// The view model instance to resolve a view for.
+ ///
+ /// The resolved view, or a diagnostic text block when no matching view exists.
+ ///
+ public Control? Build(object? param) {
+ if (param is null)
+ return null;
+
+ // Convention-based lookup: SongTidy.ViewModels.FooViewModel -> SongTidy.Views.FooView.
+ var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+ var type = Type.GetType(name);
+
+ if (type != null) {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+
+ // Show the unresolved type name instead of failing silently during development.
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ ///
+ /// Determines whether the supplied data object is a view model handled by this locator.
+ ///
+ /// The data object to inspect.
+ /// when the object derives from .
+ public bool Match(object? data) {
+ return data is ViewModelBase;
+ }
+}
diff --git a/SongTidy/ViewModels/MainWindowViewModel.cs b/SongTidy/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..34283cf
--- /dev/null
+++ b/SongTidy/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,11 @@
+namespace SongTidy.ViewModels;
+
+///
+/// Represents the state and commands of the main application window.
+///
+public class MainWindowViewModel : ViewModelBase {
+ ///
+ /// Gets a temporary greeting displayed by the template view.
+ ///
+ public string Greeting { get; } = "Welcome to Avalonia!";
+}
diff --git a/SongTidy/ViewModels/ViewModelBase.cs b/SongTidy/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..2248153
--- /dev/null
+++ b/SongTidy/ViewModels/ViewModelBase.cs
@@ -0,0 +1,10 @@
+using ReactiveUI;
+
+namespace SongTidy.ViewModels;
+
+///
+/// Serves as the common base class for all view models in the application,
+/// providing ReactiveUI change notification support.
+///
+public class ViewModelBase : ReactiveObject {
+}
diff --git a/SongTidy/Views/MainWindow.axaml b/SongTidy/Views/MainWindow.axaml
new file mode 100644
index 0000000..c4d4169
--- /dev/null
+++ b/SongTidy/Views/MainWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/SongTidy/Views/MainWindow.axaml.cs b/SongTidy/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..83cc4ba
--- /dev/null
+++ b/SongTidy/Views/MainWindow.axaml.cs
@@ -0,0 +1,9 @@
+using Avalonia.Controls;
+
+namespace SongTidy.Views;
+
+public partial class MainWindow : Window {
+ public MainWindow() {
+ InitializeComponent();
+ }
+}
diff --git a/SongTidy/app.manifest b/SongTidy/app.manifest
new file mode 100644
index 0000000..ff23578
--- /dev/null
+++ b/SongTidy/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+