diff --git a/.gitignore b/.gitignore
index 53a6d75..9b47fee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@
 /src/NodeDev.Blazor.MAUI/bin/Debug/net9.0-windows10.0.19041.0/win10-x64
 /src/NodeDev.Blazor.MAUI/obj
 /src/NodeDev.Blazor.MAUI/bin/Release/net9.0-windows10.0.19041.0/win-x64
+/src/NodeDev.Blazor.Server/AppOptions.json
\ No newline at end of file
diff --git a/src/Blazor.Diagrams b/src/Blazor.Diagrams
new file mode 160000
index 0000000..ac4eef3
--- /dev/null
+++ b/src/Blazor.Diagrams
@@ -0,0 +1 @@
+Subproject commit ac4eef3d8e4ef0cc5a7caabfda33864d154890be
diff --git a/src/Dis2Msil b/src/Dis2Msil
new file mode 160000
index 0000000..780f509
--- /dev/null
+++ b/src/Dis2Msil
@@ -0,0 +1 @@
+Subproject commit 780f509a50a91dfb5bdd4c18e34c386bfde087cb
diff --git a/src/NodeDev.Blazor.Server/Program.cs b/src/NodeDev.Blazor.Server/Program.cs
index 6dc07cf..16895fc 100644
--- a/src/NodeDev.Blazor.Server/Program.cs
+++ b/src/NodeDev.Blazor.Server/Program.cs
@@ -14,9 +14,9 @@
 // Configure the HTTP request pipeline.
 if (!app.Environment.IsDevelopment())
 {
-    app.UseExceptionHandler("/Error");
-    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
-    app.UseHsts();
+	app.UseExceptionHandler("/Error");
+	// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+	app.UseHsts();
 }
 
 app.UseAntiforgery();
diff --git a/src/NodeDev.Blazor/Components/OpenProjectDialog.razor b/src/NodeDev.Blazor/Components/OpenProjectDialog.razor
new file mode 100644
index 0000000..05860dd
--- /dev/null
+++ b/src/NodeDev.Blazor/Components/OpenProjectDialog.razor
@@ -0,0 +1,75 @@
+@using System.Text.Json
+@using NodeDev.Blazor.Services
+@using NodeDev.Core
+@inject AppOptionsContainer AppOptionsContainer
+@inject IDialogService DialogService
+@inject ProjectService ProjectService
+@inject ISnackbar Snackbar
+
+<MudDialog>
+	<DialogContent>
+		<MudStack>
+			<MudPaper Class="full-width">
+				<MudList T="string" @bind-SelectedValue="ProjectName" ReadOnly="false">
+					@foreach (var item in RecentProjects)
+					{
+						<MudListItem Text="@item"></MudListItem>
+					}
+				</MudList>
+			</MudPaper>
+		</MudStack>
+	</DialogContent>
+	<DialogActions>
+		<MudButton Color="Color.Primary" OnClick="LoadProject" Disabled="ProjectName is null">Open</MudButton>
+		<MudButton Color="Color.Primary" OnClick="Close">Cancel</MudButton>
+	</DialogActions>
+</MudDialog>
+
+@code {
+	[CascadingParameter]
+	private MudDialogInstance MudDialog { get; set; } = null!;
+
+	private string? ProjectName { get; set; }
+	private List<string> RecentProjects { get; set; } = new List<string>();
+
+	private void Close() => MudDialog.Close(DialogResult.Ok(true));
+
+	protected override void OnInitialized()
+	{
+		base.OnInitialized();
+		RecentProjects = ListRecentProjects();
+	}
+
+	private List<string> ListRecentProjects()
+	{
+		if (AppOptionsContainer.AppOptions.ProjectsDirectory is null)
+		{
+			return [];
+		}
+		if (!Directory.Exists(AppOptionsContainer.AppOptions.ProjectsDirectory))
+		{
+			return [];
+		}
+		return Directory.EnumerateFiles(AppOptionsContainer.AppOptions.ProjectsDirectory, "*.ndproj").Select(Path.GetFileNameWithoutExtension).ToList()!;
+	}
+
+	private async Task LoadProject()
+	{
+		if (string.IsNullOrWhiteSpace(ProjectName))
+		{
+			return;
+		}
+		try
+		{
+			var projectPath = Path.Combine(AppOptionsContainer.AppOptions.ProjectsDirectory!, $"{ProjectName}.ndproj");
+			await ProjectService.LoadProjectFromFileAsync(projectPath);
+		}
+		catch (Exception ex)
+		{
+			Snackbar.Configuration.VisibleStateDuration = 10000;
+			Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter;
+			Snackbar.Add(ex.Message, Severity.Error);
+		}
+		MudDialog.Close(DialogResult.Ok(true));
+	}
+}
diff --git a/src/NodeDev.Blazor/Components/OptionsDialog.razor b/src/NodeDev.Blazor/Components/OptionsDialog.razor
new file mode 100644
index 0000000..66314c8
--- /dev/null
+++ b/src/NodeDev.Blazor/Components/OptionsDialog.razor
@@ -0,0 +1,38 @@
+@using NodeDev.Blazor.Services
+@inject IDialogService DialogService
+@inject Services.AppOptionsContainer AppOptionsContainer
+
+<MudDialog>
+    <TitleContent>
+        Options
+    </TitleContent>
+    <DialogContent>
+        <MudTextField @bind-Value="AppOptions.ProjectsDirectory" Label="Projects Directory" Variant="Variant.Text" AutoGrow data-test-id="optionsProjectDirectory"></MudTextField>
+    </DialogContent>
+    <DialogActions>
+        <MudButton Color="Color.Primary" OnClick="Close" data-test-id="optionsCancel">Cancel</MudButton>
+        <MudButton Color="Color.Primary" OnClick="Accept" data-test-id="optionsAccept">Ok</MudButton>
+    </DialogActions>
+</MudDialog>
+
+@code {
+    [CascadingParameter]
+    private MudDialogInstance MudDialog { get; set; } = null!;
+
+    private AppOptions AppOptions { get; set; } = null!;
+
+    protected override void OnInitialized()
+    {
+        base.OnInitialized();
+        AppOptions = AppOptionsContainer.AppOptions with { };
+    }
+
+    private void Accept()
+    {
+        AppOptionsContainer.AppOptions = AppOptions;
+        MudDialog.Close();
+    }
+
+    private void Close() => MudDialog.Close();
+
+}
\ No newline at end of file
diff --git a/src/NodeDev.Blazor/Components/ProjectToolbar.razor b/src/NodeDev.Blazor/Components/ProjectToolbar.razor
new file mode 100644
index 0000000..76b5c4d
--- /dev/null
+++ b/src/NodeDev.Blazor/Components/ProjectToolbar.razor
@@ -0,0 +1,88 @@
+@using Microsoft.AspNetCore.Components.Forms
+@using NodeDev.Blazor.Services
+@using NodeDev.Core
+@inject ProjectService ProjectService
+@inject ISnackbar Snackbar
+@inject AppOptionsContainer AppOptionsContainer
+@inject IDialogService DialogService
+
+
+<MudButton OnClick="Open" Class="ml-3" Disabled=@Project.IsLiveDebuggingEnabled data-test-id="openProject">Open</MudButton>
+<MudButton OnClick="NewProject" Class="ml-3" Disabled=@Project.IsLiveDebuggingEnabled data-test-id="newProject">New Project</MudButton>
+<MudButton OnClick="Save" Class="ml-3" data-test-id="save">Save</MudButton>
+<MudButton OnClick="SaveAs" Class="ml-3" data-test-id="saveAs">Save As</MudButton>
+<MudButton OnClick="Add" Class="ml-3">Add node</MudButton>
+<MudButton OnClick="Run" Class="ml-3">Run</MudButton>
+<MudButton OnClick="SwitchLiveDebugging">@(Project.IsLiveDebuggingEnabled ? "Stop Live Debugging" : "Start Live Debugging")</MudButton>
+<MudSpacer />
+<MudButton OnClick="OpenOptionsDialogAsync" Class="ml-3" data-test-id="options">Options</MudButton>
+<MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
+
+
+@code {
+
+    private Project Project => ProjectService.Project;
+
+	private DialogOptions DialogOptions => new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true };
+
+    private Task Open()
+    {
+        return DialogService.ShowAsync<OpenProjectDialog>("Open Project", DialogOptions);
+    }
+
+    private Task Save()
+    {
+        if (string.IsNullOrWhiteSpace(Project.Settings.ProjectName))
+        {
+            return SaveAs();
+        }
+
+        try
+        {
+            ProjectService.SaveProjectToFile();
+            Snackbar.Add("Project saved", Severity.Success);
+        }
+        catch (Exception ex)
+        {
+            Snackbar.Add(ex.Message, Severity.Error);
+        }
+        
+		return Task.CompletedTask;
+    }
+
+	private Task SaveAs()
+	{
+        return DialogService.ShowAsync<SaveAsProjectDialog>("Save As Project", DialogOptions);
+	}
+
+    private void NewProject()
+    {
+        ProjectService.ChangeProject(Core.Project.CreateNewDefaultProject());
+    }
+
+    private void Add()
+    {
+        //GraphCanvas?.ShowAddNode();
+    }
+
+    public void Run()
+    {
+        new Thread(() =>
+        {
+            Project.Run(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release);
+        }).Start();
+    }
+
+    private void SwitchLiveDebugging()
+    {
+        if (Project.IsLiveDebuggingEnabled)
+            Project.StopLiveDebugging();
+        else
+            Project.StartLiveDebugging();
+    }
+
+    private Task OpenOptionsDialogAsync()
+    {
+        return DialogService.ShowAsync<OptionsDialog>("Options", DialogOptions);
+    }
+}
diff --git a/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor b/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor
new file mode 100644
index 0000000..70de910
--- /dev/null
+++ b/src/NodeDev.Blazor/Components/SaveAsProjectDialog.razor
@@ -0,0 +1,48 @@
+@using System.Text.Json
+@using NodeDev.Blazor.Services
+@using NodeDev.Core
+@inject AppOptionsContainer AppOptionsContainer
+@inject IDialogService DialogService
+@inject ProjectService ProjectService
+@inject ISnackbar Snackbar
+
+<MudDialog>
+	<DialogContent>
+		<MudTextField @bind-Value="ProjectName" Label="File Name" Variant="Variant.Text" data-test-id="saveAsProjectName"></MudTextField>
+	</DialogContent>
+	<DialogActions>
+		<MudButton Color="Color.Primary" OnClick="SaveProject" Disabled="ProjectName is null" data-test-id="saveAsSave">Save</MudButton>
+		<MudButton Color="Color.Primary" OnClick="Close" data-test-id="saveAsCancel">Cancel</MudButton>
+	</DialogActions>
+</MudDialog>
+
+@code {
+	[CascadingParameter]
+	private MudDialogInstance MudDialog { get; set; } = null!;
+
+	public string? ProjectName { get; set; }
+
+	private void Close() => MudDialog.Close(DialogResult.Ok(true));
+
+	protected override void OnInitialized()
+	{
+		base.OnInitialized();
+		ProjectName = ProjectService.Project.Settings.ProjectName;
+	}
+
+	private void SaveProject()
+	{
+		try
+		{
+			ProjectService.Project.Settings.ProjectName = ProjectName!;
+			ProjectService.SaveProjectToFile();
+			Snackbar.Add("Project saved", Severity.Success);
+			MudDialog.Close(DialogResult.Ok(ProjectName));
+		}
+		catch (Exception ex)
+		{
+			Snackbar.Add(ex.Message, Severity.Error);
+		}
+	}
+
+}
diff --git a/src/NodeDev.Blazor/Index.razor b/src/NodeDev.Blazor/Index.razor
index 0f1b210..038194e 100644
--- a/src/NodeDev.Blazor/Index.razor
+++ b/src/NodeDev.Blazor/Index.razor
@@ -1,6 +1,9 @@
-@inject Services.DebuggedPathService DebuggedPathService
+@using NodeDev.Blazor.Services
+@inject Services.DebuggedPathService DebuggedPathService
 @inject NavigationManager NavigationManager
 @inject ISnackbar Snackbar
+@inject ProjectService ProjectService
+@implements IDisposable
 
 <MudThemeProvider />
 <MudPopoverProvider />
@@ -9,14 +12,7 @@
 
 <MudLayout Style="height: 100vh; width: 100%; overflow: hidden">
     <MudAppBar Elevation="1" data-test-id="appBar">
-        <MudButton OnClick="NewProject" Class="ml-3" data-test-id="newProject">New Project</MudButton>
-        <MudButton OnClick="Save" Class="ml-3" data-test-id="Save">Save</MudButton>
-        <MudButton OnClick="Build" Class="ml-3" data-test-id="Build">Build</MudButton>
-        <MudButton OnClick="Add" Class="ml-3">Add node</MudButton>
-        <MudButton OnClick="Run" Class="ml-3">Run</MudButton>
-        <MudButton OnClick="SwitchLiveDebugging">@(Project.IsLiveDebuggingEnabled ? "Stop Live Debugging" : "Start Live Debugging")</MudButton>
-        <MudSpacer />
-        <MudIconButton Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Edge="Edge.End" />
+        <ProjectToolbar />
     </MudAppBar>
     <MudMainContent Style="width: 100%; height: 100%; overflow-y: hidden">
 
@@ -24,7 +20,7 @@
             <StartContent>
                 <MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-2 wh100" KeepPanelsAlive="true" Class="wh100" data-test-id="ProjectExplorerSection">
                     <MudTabPanel Text="Project">
-                        <ProjectExplorer Project="Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer>
+                        <ProjectExplorer Project="ProjectService.Project" @bind-SelectedClass="SelectedClass"></ProjectExplorer>
                     </MudTabPanel>
                     <MudTabPanel Text="Class">
                         @if (SelectedClass != null)
@@ -33,7 +29,6 @@
                         }
                     </MudTabPanel>
                 </MudTabs>
-
             </StartContent>
 
             <EndContent>
@@ -61,7 +56,7 @@
                                     </MudTabs>
                                 </CascadingValue>
 
-                                <DebuggerConsolePanel Project="Project" />
+                                <DebuggerConsolePanel Project="ProjectService.Project" />
 
                             </MudStack>
 
@@ -85,8 +80,6 @@
 
 @code {
 
-    private Core.Project Project { get; set; } = null!;
-
     private Core.Class.NodeClass? SelectedClass { get; set; }
 
     private List<Core.Class.NodeClassMethod> OpenedMethods { get; } = new();
@@ -105,14 +98,20 @@
 
     protected override void OnInitialized()
     {
-        if (File.Exists("project.json"))
-            Project = Core.Project.Deserialize(File.ReadAllText("project.json"));
-        else
-            Project = Core.Project.CreateNewDefaultProject();
+        ProjectService.ProjectChanged += OnProjectChanged;
 
-        DebuggedPathService.ChangeProject(Project);
+        DebuggedPathService.ChangeProject(ProjectService.Project);
     }
 
+    private void OnProjectChanged()
+	{
+		_ = InvokeAsync(() =>
+		{
+            DebuggedPathService.ChangeProject(ProjectService.Project);
+            NavigationManager.Refresh(true);
+		});
+	}
+
     private void SwitchSourceViewerGraph()
     {
         if (SourceViewerGraphPercentage <= SourceViewer_OpenedGraphPercentage)
@@ -144,7 +143,7 @@
         if (method == null)
         {
             // Find the main method
-            var program = Project.Classes.FirstOrDefault(x => x.Name == "Program");
+            var program = ProjectService.Project.Classes.FirstOrDefault(x => x.Name == "Program");
 
             // Find the main method in the program class
             method = program?.Methods.FirstOrDefault(x => x.Name == "Main");
@@ -164,46 +163,8 @@
         StateHasChanged();
     }
 
-    private void Save()
-    {
-        var content = Project.Serialize();
-        File.WriteAllText("project.json", content);
-
-        Snackbar.Add("Project saved", Severity.Success);
-    }
-
-    private void NewProject()
-    {
-        if (File.Exists("project.json"))
-            File.Move("project.json", "project_backup.json", true);
-
-
-        NavigationManager.Refresh(true);
-    }
-
-    private void Add()
-    {
-        //GraphCanvas?.ShowAddNode();
-    }
-
-    public void Build()
-    {
-        Project.Build(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release);
-    }
-
-    public void Run()
-    {
-        new Thread(() =>
-        {
-            Project.Run(Project.IsLiveDebuggingEnabled ? Core.BuildOptions.Debug : Core.BuildOptions.Release);
-        }).Start();
-    }
-
-    private void SwitchLiveDebugging()
+    public void Dispose()
     {
-        if (Project.IsLiveDebuggingEnabled)
-            Project.StopLiveDebugging();
-        else
-            Project.StartLiveDebugging();
+        ProjectService.ProjectChanged -= OnProjectChanged;
     }
 }
\ No newline at end of file
diff --git a/src/NodeDev.Blazor/Services/AppOptions.cs b/src/NodeDev.Blazor/Services/AppOptions.cs
new file mode 100644
index 0000000..046ba50
--- /dev/null
+++ b/src/NodeDev.Blazor/Services/AppOptions.cs
@@ -0,0 +1,6 @@
+namespace NodeDev.Blazor.Services;
+
+public record class AppOptions
+{
+	public string ProjectsDirectory { get; set; } = string.Empty;
+}
diff --git a/src/NodeDev.Blazor/Services/AppOptionsContainer.cs b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs
new file mode 100644
index 0000000..426b514
--- /dev/null
+++ b/src/NodeDev.Blazor/Services/AppOptionsContainer.cs
@@ -0,0 +1,44 @@
+using System.Text.Json;
+
+namespace NodeDev.Blazor.Services;
+
+public class AppOptionsContainer
+{
+	private readonly string OptionsFileName;
+
+	private AppOptions appOptions = new AppOptions();
+	public AppOptions AppOptions
+	{
+		get => appOptions;
+		set
+		{
+			appOptions = value;
+			SaveOptions();
+		}
+	}
+
+	public AppOptionsContainer(string optionFileName)
+	{
+		OptionsFileName = optionFileName;
+		if (!string.IsNullOrWhiteSpace(OptionsFileName))
+		{
+			LoadOptions();
+		}
+	}
+
+	public void SaveOptions()
+	{
+		File.WriteAllText(OptionsFileName, JsonSerializer.Serialize(appOptions, new JsonSerializerOptions()
+		{
+			WriteIndented = true
+		}));
+	}
+
+	public void LoadOptions()
+	{
+		if (File.Exists(OptionsFileName))
+		{
+			appOptions = JsonSerializer.Deserialize<AppOptions>(File.ReadAllText(OptionsFileName)) ?? new AppOptions();
+		}
+	}
+}
diff --git a/src/NodeDev.Blazor/Services/ProjectService.cs b/src/NodeDev.Blazor/Services/ProjectService.cs
new file mode 100644
index 0000000..4bfa531
--- /dev/null
+++ b/src/NodeDev.Blazor/Services/ProjectService.cs
@@ -0,0 +1,58 @@
+using NodeDev.Core;
+
+namespace NodeDev.Blazor.Services
+{
+	/// <summary>
+	/// Service used to keep a singleton of the project throughout the application.
+	/// </summary>
+	public class ProjectService
+	{
+		public Project Project { get; private set; }
+
+		private readonly AppOptionsContainer AppOptionsContainer;
+
+		public delegate void ProjectChangedHandler();
+		/// <summary>
+		/// Event used to notify subscribers when the current project has changed.
+		/// </summary>
+		public event ProjectChangedHandler? ProjectChanged;
+
+		/// <summary>
+		/// Instanciates a default project as the current project.
+		/// </summary>
+		public ProjectService(AppOptionsContainer appOptionsContainer)
+		{
+			Project = Project.CreateNewDefaultProject();
+			AppOptionsContainer = appOptionsContainer;
+		}
+
+		/// <summary>
+		/// Changes the current project and notifies all subscribers of <see cref="ProjectChanged" />.
+		/// </summary>
+		/// <param name="project"></param>
+		public void ChangeProject(Project project)
+		{
+			Project = project;
+			ProjectChanged?.Invoke();
+		}
+
+		public async Task LoadProjectFromFileAsync(string file)
+		{
+			ArgumentNullException.ThrowIfNullOrWhiteSpace(file);
+
+			var json = await File.ReadAllTextAsync(file);
+			var project = Project.Deserialize(json);
+			project.Settings.ProjectName = Path.GetFileNameWithoutExtension(file);
+			ChangeProject(project);
+		}
+
+		public void SaveProjectToFile()
+		{
+			ArgumentNullException.ThrowIfNullOrWhiteSpace(Project.Settings.ProjectName);
+			var projectPath = Path.Combine(AppOptionsContainer.AppOptions.ProjectsDirectory!, $"{Project.Settings.ProjectName}.ndproj");
+			string content = Project.Serialize();
+			File.WriteAllText(projectPath, content);
+
+		}
+	}
+}
diff --git a/src/NodeDev.Blazor/Services/ServicesExtension.cs b/src/NodeDev.Blazor/Services/ServicesExtension.cs
index 407d087..e08d725 100644
--- a/src/NodeDev.Blazor/Services/ServicesExtension.cs
+++ b/src/NodeDev.Blazor/Services/ServicesExtension.cs
@@ -10,7 +10,9 @@ public static IServiceCollection AddNodeDev(this IServiceCollection services)
 		{
 			services
 				.AddMudServices()
-				.AddScoped<DebuggedPathService>();
+				.AddScoped<DebuggedPathService>()
+				.AddSingleton<ProjectService>()
+				.AddSingleton(new AppOptionsContainer("AppOptions.json"));
 
 			return services;
 		}
diff --git a/src/NodeDev.Core/ProjectSettings.cs b/src/NodeDev.Core/ProjectSettings.cs
index a5e25a0..ce2c687 100644
--- a/src/NodeDev.Core/ProjectSettings.cs
+++ b/src/NodeDev.Core/ProjectSettings.cs
@@ -2,5 +2,6 @@
 
 public record class ProjectSettings()
 {
+	public string ProjectName { get; set; } = string.Empty;
 	public static ProjectSettings Default { get; } = new();
 }
diff --git a/src/NodeDev.EndToEndTests/Features/SaveProject.feature b/src/NodeDev.EndToEndTests/Features/SaveProject.feature
index be229fc..110d219 100644
--- a/src/NodeDev.EndToEndTests/Features/SaveProject.feature
+++ b/src/NodeDev.EndToEndTests/Features/SaveProject.feature
@@ -4,5 +4,5 @@ Scenario: Save empty project
 	Given I load the default project
 	Then The 'Main' method in the 'Program' class should exist
     
-	Given I save the current project
+	Given I save the current project as 'EmptyProject'
 	Then Snackbar should contain 'Project saved'
\ No newline at end of file
diff --git a/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs b/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs
index 916f927..b2aa478 100644
--- a/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs
+++ b/src/NodeDev.EndToEndTests/Features/SaveProject.feature.cs
@@ -105,7 +105,7 @@ public async System.Threading.Tasks.Task SaveEmptyProject()
  await testRunner.ThenAsync("The \'Main\' method in the \'Program\' class should exist", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then ");
 #line hidden
 #line 7
- await testRunner.GivenAsync("I save the current project", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given ");
+ await testRunner.GivenAsync("I save the current project as \'EmptyProject\'", ((string)(null)), ((global::Reqnroll.Table)(null)), "Given ");
 #line hidden
 #line 8
  await testRunner.ThenAsync("Snackbar should contain \'Project saved\'", ((string)(null)), ((global::Reqnroll.Table)(null)), "Then ");
diff --git a/src/NodeDev.EndToEndTests/Pages/HomePage.cs b/src/NodeDev.EndToEndTests/Pages/HomePage.cs
index a5f0801..bdb248c 100644
--- a/src/NodeDev.EndToEndTests/Pages/HomePage.cs
+++ b/src/NodeDev.EndToEndTests/Pages/HomePage.cs
@@ -18,6 +18,10 @@ public HomePage(Hooks.Hooks hooks)
 	private ILocator SearchProjectExplorerTabsHeader => _user.Locator("[data-test-id='ProjectExplorerSection'] .mud-tabs-tabbar");
 	private ILocator SearchClassExplorer => _user.Locator("[data-test-id='classExplorer']");
 	private ILocator SearchSnackBarContainer => _user.Locator("#mud-snackbar-container");
+	private ILocator SearchOptionsButton => SearchAppBar.Locator("[data-test-id='options']");
+	private ILocator SearchSaveButton => SearchAppBar.Locator("[data-test-id='save']");
+	private ILocator SearchSaveAsButton => SearchAppBar.Locator("[data-test-id='saveAs']");
+
 
 	public async Task CreateNewProject()
 	{
@@ -69,11 +73,48 @@ public async Task HasMethodByName(string name)
 
 	public async Task SaveProject()
 	{
-		var saveBtn = SearchAppBar.Locator("[data-test-id='Save']");
+		await SearchSaveButton.WaitForVisible();
+		await SearchSaveButton.ClickAsync();
+	}
 
-		await saveBtn.WaitForVisible();
+	public async Task OpenOptionsDialog()
+	{
+		await SearchOptionsButton.WaitForVisible();
+		await SearchOptionsButton.ClickAsync();
+	}
 
-		await saveBtn.ClickAsync();
+	public async Task SetProjectsDirectory(string directory)
+	{
+		var projectsDirectoryInput = _user.Locator("[data-test-id='optionsProjectDirectory']");
+		await projectsDirectoryInput.WaitForVisible();
+		await projectsDirectoryInput.FillAsync(directory);
+	}
+
+	public async Task AcceptOptions()
+	{
+		var acceptButton = _user.Locator("[data-test-id='optionsAccept']");
+		await acceptButton.WaitForVisible();
+		await acceptButton.ClickAsync();
+	}
+
+	public async Task OpenSaveAsDialog()
+	{
+		await SearchSaveAsButton.WaitForVisible();
+		await SearchSaveAsButton.ClickAsync();
+	}
+
+	public async Task SetProjectNameAs(string projectName)
+	{
+		var projectNameInput = _user.Locator("[data-test-id='saveAsProjectName']");
+		await projectNameInput.WaitForVisible();
+		await projectNameInput.FillAsync(projectName);
+	}
+
+	public async Task AcceptSaveAs()
+	{
+		var saveButton = _user.Locator("[data-test-id='saveAsSave']");
+		await saveButton.WaitForVisible();
+		await saveButton.ClickAsync();
 	}
 
 	public async Task SnackBarHasByText(string text)
diff --git a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs
index 5258535..9099f7b 100644
--- a/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs
+++ b/src/NodeDev.EndToEndTests/StepDefinitions/MainPageStepDefinitions.cs
@@ -46,4 +46,21 @@ public async Task ThenSnackbarShouldContain(string text)
 	{
 		await HomePage.SnackBarHasByText(text);
 	}
+
+	[Given("I set the projects directory to {string}")]
+	public async Task GivenISetTheProjectsDirectoryTo(string directory)
+	{
+		await HomePage.OpenOptionsDialog();
+		await HomePage.SetProjectsDirectory(directory);
+		await HomePage.AcceptOptions();
+	}
+
+	[Given("I save the current project as {string}")]
+	public async Task GivenISaveTheCurrentProjectAs(string projectName)
+	{
+		await HomePage.OpenSaveAsDialog();
+		await HomePage.SetProjectNameAs(projectName);
+		await HomePage.AcceptSaveAs();
+	}
+
 }
diff --git a/src/NodeDev.Tests/ProjectServiceTest.cs b/src/NodeDev.Tests/ProjectServiceTest.cs
new file mode 100644
index 0000000..a9b46f0
--- /dev/null
+++ b/src/NodeDev.Tests/ProjectServiceTest.cs
@@ -0,0 +1,32 @@
+using NodeDev.Blazor.Services;
+using NodeDev.Core;
+
+namespace NodeDev.Tests;
+
+public class ProjectServiceTest
+{
+	[Fact]
+	public void TestsChangeProject()
+	{
+		var project = new Project(Guid.NewGuid());
+		var projectService = new ProjectService(new AppOptionsContainer(""));
+
+		projectService.ChangeProject(project);
+
+		Assert.Equal(project, projectService.Project);
+	}
+
+	[Fact]
+	public void TestsProjectChangedEvent()
+	{
+		var project = new Project(Guid.NewGuid());
+		var projectService = new ProjectService(new AppOptionsContainer(""));
+		bool isEventTriggered = false;
+
+		projectService.ProjectChanged += () => isEventTriggered = true;
+		projectService.ChangeProject(project);
+
+		Assert.True(isEventTriggered);
+	}
+}
+