diff --git a/Blacklight.sln.DotSettings.user b/Blacklight.sln.DotSettings.user
index 8bece1a237ef6b008da694102f6315c2fbec6d97..a4c636f05f3e18e362d327eec1ae5108a0198098 100644
--- a/Blacklight.sln.DotSettings.user
+++ b/Blacklight.sln.DotSettings.user
@@ -16,5 +16,9 @@
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALowerCaseStringEnumConverter_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1e29f6bc726b4d68bc914cbabf2575377600_003F27_003F3006d2c3_003FLowerCaseStringEnumConverter_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworkInformation_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b1b048ea0e94b28ad7cbac8ef6bc4c26e00_003Fb4_003Fff405095_003FNetworkInformation_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AProportionalDock_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F91c655b1795150937d9264ac1431c463ed5e5e8ad0286ad17e16f16175d94_003FProportionalDock_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2854ce6d56c18d0d837d3a3ef9c4f2c7c77691fa3528c8394986ac7ce7719_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6354a7b35d7821629924d3676acd7e67a6f7f94343e0e66ec439aa2bd6ed5_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeDataTemplate_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe9a5203b1cebd5ba4f745e6a4b3dd651886fe711fe1ad32dec118143c4fbd0_003FTreeDataTemplate_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeDataTemplate_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe9a5203b1cebd5ba4f745e6a4b3dd651886fe711fe1ad32dec118143c4fbd0_003FTreeDataTemplate_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeView_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F49b572a64c2c02980ffe97ff1673549ed9bdfbf9738ec66def83611762e85c_003FTreeView_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	</wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/Blacklight/App.axaml b/Blacklight/App.axaml
index 2cf90ee42b75875f01df58ca1f6859980bbc9296..d91b9e4df8dc6f44c2f41197659178002029b0e8 100644
--- a/Blacklight/App.axaml
+++ b/Blacklight/App.axaml
@@ -7,6 +7,7 @@
 	xmlns:core="clr-namespace:Dock.Model.Core;assembly=Dock.Model"
 	xmlns:documents="clr-namespace:Blacklight.ViewModels.Documents"
 	xmlns:util="clr-namespace:Blacklight.Util"
+	xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
 	RequestedThemeVariant="Default">
 	<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
 	<Application.Resources>
@@ -61,6 +62,7 @@
 	<Application.Styles>
 		<FluentTheme />
 		<DockFluentTheme />
+		<dialogHostAvalonia:DialogHostStyles />
 		<Style Selector="DockControl">
 			<Setter Property="(ControlRecyclingDataTemplate.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />
 		</Style>
diff --git a/Blacklight/Blacklight.csproj b/Blacklight/Blacklight.csproj
index a0cadd8e5e27383aa4b56174ba361c94fdf46a21..d7c3af54b36de8b14a2b4bc9b85a1dba1b1e49c3 100644
--- a/Blacklight/Blacklight.csproj
+++ b/Blacklight/Blacklight.csproj
@@ -22,6 +22,7 @@
         <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.5" />
         <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.2.0.9" />
         <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+        <PackageReference Include="DialogHost.Avalonia" Version="0.9.2" />
         <PackageReference Include="Dock.Avalonia" Version="11.2.0" />
         <PackageReference Include="Dock.Model.Mvvm" Version="11.2.0.1" />
         <PackageReference Include="Markdig" Version="0.40.0" />
@@ -45,4 +46,9 @@
     <ItemGroup>
       <ProjectReference Include="..\Lightquark.NET\Lightquark.NET.csproj" />
     </ItemGroup>
+
+
+    <ItemGroup>
+      <UpToDateCheckInput Remove="Views\Windows\CreateChannelView.axaml" />
+    </ItemGroup>
 </Project>
diff --git a/Blacklight/ViewModels/ClientViewModel.cs b/Blacklight/ViewModels/ClientViewModel.cs
index d852c107ad8cea6befe80b2e0d8a02846c5e1ecb..b1d4d229de762a2cd8efd1cf825389683db03c39 100644
--- a/Blacklight/ViewModels/ClientViewModel.cs
+++ b/Blacklight/ViewModels/ClientViewModel.cs
@@ -62,6 +62,7 @@ public class ClientViewModel : ViewModelBase
     public void TestCommand()
     {
         Log.Information("Current items in QuarkList: {ItemCount}", Client.QuarkList.Count);
+        _ = Client.UpdateResourceTree();
     }
     
     public void LogoutNow()
diff --git a/Blacklight/ViewModels/Dialogs/CreateChannelViewModel.cs b/Blacklight/ViewModels/Dialogs/CreateChannelViewModel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b181e04683e3f83e16cbeda133579696edf0eb53
--- /dev/null
+++ b/Blacklight/ViewModels/Dialogs/CreateChannelViewModel.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.Input;
+using DialogHostAvalonia;
+using Lightquark.NET;
+using Lightquark.NET.Objects;
+using Lightquark.NET.Objects.Reply;
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+
+namespace Blacklight.ViewModels.Dialogs;
+
+public class CreateChannelViewModel : ViewModelBase
+{
+    private Quark _meow;
+    private string _errorMessage = "";
+    private string? _channelName;
+    private string? _channelDescription;
+    public ICommand CreateChannelCommand => new RelayCommand(CreateChannel);
+
+    private async void CreateChannel()
+    {
+        try
+        {
+            if (ChannelName == null)
+            {
+                ErrorMessage = "Name is required";
+                return;
+            }
+            if (ChannelName.Length is < 1 or > 64)
+            {
+                ErrorMessage = "Name should be between 1 and 64 characters";
+                return;
+            }
+
+            var client = App.Services!.GetRequiredService<Client>();
+            var res = await client.CreateChannel(Quark.Id, ChannelName, ChannelDescription);
+            if (res.Request.Success)
+            {
+                DialogHost.GetDialogSession("DialogHost")?.Close(false);
+                return;
+            }
+
+            ErrorMessage = res.Response.Message;
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "Failed channel creation");
+            ErrorMessage = "Failed to create channel.";
+        }
+    }
+
+    public Quark Quark
+    {
+        get => _meow;
+        set => SetProperty(ref _meow, value);
+    }
+
+    public string ErrorMessage
+    {
+        get => _errorMessage;
+        set => SetProperty(ref _errorMessage, value);
+    }
+
+    public string? ChannelName
+    {
+        get => _channelName;
+        set => SetProperty(ref _channelName, value);
+    }
+
+    public string? ChannelDescription
+    {
+        get => _channelDescription;
+        set => SetProperty(ref _channelDescription, value);
+    }
+}
+
diff --git a/Blacklight/ViewModels/DockFactory.cs b/Blacklight/ViewModels/DockFactory.cs
index 414f3124cfbe01608e75482eb5f31d93e8115fdd..ec3a31137fff4054e7a9780c7814cae6078af01d 100644
--- a/Blacklight/ViewModels/DockFactory.cs
+++ b/Blacklight/ViewModels/DockFactory.cs
@@ -11,6 +11,7 @@ using Dock.Model.Core;
 using Dock.Model.Mvvm;
 using Dock.Model.Mvvm.Controls;
 using Lightquark.NET.Objects;
+using MongoDB.Bson;
 
 namespace Blacklight.ViewModels;
 
@@ -166,6 +167,21 @@ public class DockContext
         parent.ActiveDockable = document;
     }
 
+    // TODO
+    // public void RemoveDocument(ObjectId channelId)
+    // {
+    //     var docks = GetDocumentDocks(_mainLayoutDock);
+    //     foreach (var dockWithParent in docks)
+    //     {
+    //         if (dockWithParent.DocumentDock.VisibleDockables == null) throw new Exception("wtf? _documentDock.VisibleDockables is null");
+    //         var toRemove = dockWithParent.DocumentDock.VisibleDockables.OfType<DocumentViewModel>().Where(d => d.Channel.Id == channelId);
+    //         foreach (var removable in toRemove)
+    //         {
+    //             dockWithParent.DocumentDock.VisibleDockables.Remove(removable);
+    //         }
+    //     }
+    // }
+    
     public void RemoveDocument(DocumentViewModel document)
     {
         var docks = GetDocumentDocks(_mainLayoutDock);
diff --git a/Blacklight/ViewModels/Tools/ResourceExplorerViewModel.cs b/Blacklight/ViewModels/Tools/ResourceExplorerViewModel.cs
index 59890ce769fd6787828d34c87785e6c04b9a5e64..11998447dcb10c927951df1b329147fcb61e3527 100644
--- a/Blacklight/ViewModels/Tools/ResourceExplorerViewModel.cs
+++ b/Blacklight/ViewModels/Tools/ResourceExplorerViewModel.cs
@@ -1,5 +1,13 @@
 using System;
+using System.Diagnostics;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Blacklight.ViewModels.Dialogs;
+using DialogHostAvalonia;
 using Dock.Model.Mvvm.Controls;
+using Lightquark.NET;
+using Microsoft.Extensions.DependencyInjection;
 using MongoDB.Bson;
 using Serilog;
 
@@ -15,14 +23,18 @@ public class ResourceExplorerViewModel : Tool
             return;
         }
         Log.Information("Cats go meow {QuarkId}", quarkId);
-        
+        var client = App.Services!.GetRequiredService<Client>();
+        DialogHost.Show(new CreateChannelViewModel
+        {
+            Quark = client.Quarks.First(q => q.Id == quarkId)
+        });
     }
 
     
     // ReSharper disable once UnusedMember.Global
     public bool CanCreateChannel(object obj)
     {
-        return obj is ObjectId;
+        return obj is ObjectId; // TODO: Check permission
     } 
     
     public void LeaveQuark(object obj)
@@ -32,7 +44,7 @@ public class ResourceExplorerViewModel : Tool
     // ReSharper disable once UnusedMember.Global
     public bool CanLeaveQuark(object obj)
     {
-        return true;
+        return false;
     } 
     public void EditQuark(object obj)
     {
@@ -41,15 +53,63 @@ public class ResourceExplorerViewModel : Tool
     // ReSharper disable once UnusedMember.Global
     public bool CanEditQuark(object obj)
     {
-        return true;
+        return false;
     } 
     public void DeleteQuark(object obj)
     {
         throw new NotImplementedException();
     }
     // ReSharper disable once UnusedMember.Global
-    public bool CanDeletQuark(object obj)
+    public bool CanDeleteQuark(object obj)
+    {
+        return false;
+    } 
+    public void EditChannel(object obj)
+    {
+        throw new NotImplementedException();
+    }
+    // ReSharper disable once UnusedMember.Global
+    public bool CanEditChannel(object obj)
+    {
+        return false;
+    } 
+    public async void DeleteChannel(object obj)
+    {
+        try
+        {
+            if (obj is not ObjectId channelId)
+            {
+                Log.Information("obj is {Obj}", obj.GetType().FullName);
+                return;
+            }
+            var client = App.Services!.GetRequiredService<Client>();
+            var res = await client.DeleteChannel(channelId);
+            if (res.Request.Success) return;
+            
+            // App.DockContext.RemoveDocument(channelId); TODO
+            
+            await DialogHost.Show(new TextBlock
+            {
+                Text = $"Failed to delete channel: {res.Response.Message}",
+                Foreground = Brushes.Red,
+                FontWeight = FontWeight.Bold
+            });
+        }
+        catch (Exception ex)
+        {
+            Log.Error(ex, "Failed to delete channel");
+            
+            await DialogHost.Show(new TextBlock
+            {
+                Text = "Failed to delete channel",
+                Foreground = Brushes.Red,
+                FontWeight = FontWeight.Bold
+            });
+        }
+    }
+    // ReSharper disable once UnusedMember.Global
+    public bool CanDeleteChannel(object obj)
     {
-        return true;
+        return obj is ObjectId; // TODO: Check permission
     } 
 }
\ No newline at end of file
diff --git a/Blacklight/ViewModels/Windows/CreateChannelViewModel.cs b/Blacklight/ViewModels/Windows/CreateChannelViewModel.cs
deleted file mode 100644
index 436893ad2badf75b1597dc1859f57b66f3676d19..0000000000000000000000000000000000000000
--- a/Blacklight/ViewModels/Windows/CreateChannelViewModel.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Blacklight.ViewModels.Windows;
-
-public class CreateChannelViewModel : ViewModelBase
-{
-    
-}
\ No newline at end of file
diff --git a/Blacklight/Views/Dialogs/CreateChannelView.axaml b/Blacklight/Views/Dialogs/CreateChannelView.axaml
new file mode 100644
index 0000000000000000000000000000000000000000..d886551e1828799f10b9f9e41a1f5a5337cf426f
--- /dev/null
+++ b/Blacklight/Views/Dialogs/CreateChannelView.axaml
@@ -0,0 +1,65 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:dialogs="clr-namespace:Blacklight.ViewModels.Dialogs"
+             xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="Blacklight.Views.Dialogs.CreateChannelView"
+             x:DataType="dialogs:CreateChannelViewModel">
+	<Grid>
+		<Grid.RowDefinitions>
+			*,*,*,*,*
+		</Grid.RowDefinitions>
+		<Grid.ColumnDefinitions>
+			Auto,*,Auto
+		</Grid.ColumnDefinitions>
+		
+		<TextBlock Grid.Row="0" 
+		           Grid.Column="0" 
+		           Grid.ColumnSpan="3" 
+		           FontWeight="Bold" HorizontalAlignment="Center" Text="{Binding Quark.Name, StringFormat=Create channel in {0}}" />
+		
+		<Grid Margin="0,4" Grid.Row="1" 
+		      Grid.Column="0" 
+		      Grid.ColumnSpan="3">
+			<Grid.ColumnDefinitions>
+				Auto,*,Auto
+			</Grid.ColumnDefinitions>
+			<TextBlock VerticalAlignment="Center" Grid.Column="0" Margin="0, 0, 10, 0">Name:</TextBlock>
+			<TextBox MinWidth="196" MaxLength="64" Grid.Column="2" Watermark="Name" Text="{Binding ChannelName}"/>
+		</Grid>
+		
+		<Grid Margin="0,4" Grid.Row="2" 
+		      Grid.Column="0" 
+		      Grid.ColumnSpan="3">
+			<Grid.ColumnDefinitions>
+				Auto,*,Auto
+			</Grid.ColumnDefinitions>
+			<TextBlock VerticalAlignment="Center" Grid.Column="0" Margin="0, 0, 10, 0">Description:</TextBlock>
+			<TextBox MinWidth="196" MaxLength="512" Grid.Column="2" Watermark="Description (optional)" Text="{Binding ChannelDescription}"/>
+		</Grid>
+		
+		<TextBlock MaxWidth="256"
+		           Grid.Row="3"
+		           Grid.Column="0"
+		           Foreground="Red"
+		           FontWeight="Bold"
+		           TextWrapping="Wrap"
+		           HorizontalAlignment="Center"
+		           Grid.ColumnSpan="3"
+		           Text="{Binding ErrorMessage}"/>
+		
+		<Grid Margin="0,4" Grid.Row="4" 
+		      Grid.Column="0" 
+		      Grid.ColumnSpan="3"
+		      HorizontalAlignment="Stretch">
+			<Grid.ColumnDefinitions>
+				Auto,*,Auto
+			</Grid.ColumnDefinitions>
+			<Button Grid.Column="0" Command="{Binding CreateChannelCommand}">Create</Button>
+			<Button Grid.Column="2"
+			        Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHostAvalonia:DialogHost}, Path=CloseDialogCommand}">Cancel</Button>
+		</Grid>
+	</Grid>
+</UserControl>
diff --git a/Blacklight/Views/Windows/CreateChannelView.axaml.cs b/Blacklight/Views/Dialogs/CreateChannelView.axaml.cs
similarity index 62%
rename from Blacklight/Views/Windows/CreateChannelView.axaml.cs
rename to Blacklight/Views/Dialogs/CreateChannelView.axaml.cs
index f24ee5675eb3ece6f819f3decb4ab84e70eb4899..d8fe34c80f23c46b57e6c23a440493f8487f21b8 100644
--- a/Blacklight/Views/Windows/CreateChannelView.axaml.cs
+++ b/Blacklight/Views/Dialogs/CreateChannelView.axaml.cs
@@ -2,9 +2,9 @@
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 
-namespace Blacklight.Views.Windows;
+namespace Blacklight.Views.Dialogs;
 
-public partial class CreateChannelView : Window
+public partial class CreateChannelView : UserControl
 {
     public CreateChannelView()
     {
diff --git a/Blacklight/Views/MainWindow.axaml b/Blacklight/Views/MainWindow.axaml
index 6b12f563ea5e99e9079331d649fa3f6b27dfb341..894fb5402cc3048b8909550a9db58a84b0afb84b 100644
--- a/Blacklight/Views/MainWindow.axaml
+++ b/Blacklight/Views/MainWindow.axaml
@@ -6,7 +6,7 @@
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:login="clr-namespace:Blacklight.Views.Login"
-        xmlns:util="clr-namespace:Blacklight.Util"
+        xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
         mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="700"
         x:Class="Blacklight.Views.MainWindow"
         x:DataType="vm:MainWindowViewModel"
@@ -34,18 +34,24 @@
 			<views:ClientView />
 		</DataTemplate>
 	</Window.DataTemplates>
-
-	<Panel>
+	
+	<dialogHostAvalonia:DialogHost 
+		CloseOnClickAway="True"
+		BlurBackground="True"
+		BlurBackgroundRadius="5"
+		Identifier="DialogHost">
 		<Panel>
 			<Panel>
-				<TransitioningContentControl Content="{Binding CurrentViewModel}">
-					<TransitioningContentControl.PageTransition>
-						<CrossFade Duration="0.500" />
-					</TransitioningContentControl.PageTransition>
-				</TransitioningContentControl>
+				<Panel>
+					<TransitioningContentControl Content="{Binding CurrentViewModel}">
+						<TransitioningContentControl.PageTransition>
+							<CrossFade Duration="0.500" />
+						</TransitioningContentControl.PageTransition>
+					</TransitioningContentControl>
+				</Panel>
 			</Panel>
 		</Panel>
-	</Panel>
+	</dialogHostAvalonia:DialogHost>
 
 
 </Window>
\ No newline at end of file
diff --git a/Blacklight/Views/Tools/ResourceExplorerView.axaml b/Blacklight/Views/Tools/ResourceExplorerView.axaml
index 45bd4d82e7e14fd71303d17bd77492ae52b48006..d35dfa4ecffe5b4cdfee3fe14e479a616f111788 100644
--- a/Blacklight/Views/Tools/ResourceExplorerView.axaml
+++ b/Blacklight/Views/Tools/ResourceExplorerView.axaml
@@ -11,7 +11,12 @@
              x:Class="Blacklight.Views.Tools.ResourceExplorerView">
 	<Grid ColumnDefinitions="*" RowDefinitions="*" Background="{DynamicResource ResourceExplorerColor}">
 		<TreeView Grid.Row="0" ItemsSource="{Binding $parent[views:ClientView].((viewModels:ClientViewModel)DataContext).Client.ResourceTree}" SelectionChanged="ItemSelected">
-	    <TreeView.DataTemplates>
+			<TreeView.Styles>
+				<Style Selector="TreeViewItem" x:DataType="objects:QuarkListItem">
+					<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
+				</Style>
+			</TreeView.Styles>
+			<TreeView.DataTemplates>
 		    <util:ResourceExplorerTemplateSelector>
 			    <TreeDataTemplate x:Key="QuarkTemplate" x:DataType="objects:QuarkListItem" ItemsSource="{Binding Children}">
 				    <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
@@ -43,6 +48,17 @@
 				    <StackPanel Orientation="Horizontal">
 					    <TextBlock FontWeight="Bold" Foreground="{DynamicResource ChannelHashtagColor}" Margin="0,0,2,0">#</TextBlock>
 					    <TextBlock Text="{Binding Channel.Name, FallbackValue='No Channel Information'}"></TextBlock>
+					    					    
+					    <StackPanel.ContextMenu>
+						    <ContextMenu>
+							    <MenuItem Header="Edit channel"
+							              Command="{Binding $parent[TreeView].((tools:ResourceExplorerViewModel)DataContext).EditChannel}"
+							              CommandParameter="{Binding Channel.Id}"/>
+							    <MenuItem Header="Delete channel"
+							              Command="{Binding $parent[TreeView].((tools:ResourceExplorerViewModel)DataContext).DeleteChannel}"
+							              CommandParameter="{Binding Channel.Id}"/>
+						    </ContextMenu>
+					    </StackPanel.ContextMenu>
 				    </StackPanel>
 			    </TreeDataTemplate>
 			    <TreeDataTemplate x:Key="FolderTemplate" x:DataType="objects:QuarkListItem" ItemsSource="{Binding Children}">
diff --git a/Blacklight/Views/Windows/CreateChannelView.axaml b/Blacklight/Views/Windows/CreateChannelView.axaml
deleted file mode 100644
index 91d5363aca908c2cdc81cb85976325fdfd626825..0000000000000000000000000000000000000000
--- a/Blacklight/Views/Windows/CreateChannelView.axaml
+++ /dev/null
@@ -1,23 +0,0 @@
-<Window xmlns="https://github.com/avaloniaui"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-        x:Class="Blacklight.Views.Windows.CreateChannelView"
-        WindowStartupLocation="CenterScreen"
-        Title="CreateChannelView">
-	<Panel>
-		<ExperimentalAcrylicBorder IsHitTestVisible="False">
-			<ExperimentalAcrylicBorder.Material>
-				<ExperimentalAcrylicMaterial
-					BackgroundSource="Digger"
-					TintColor="Black"
-					TintOpacity="1"
-					MaterialOpacity="0.65" />
-			</ExperimentalAcrylicBorder.Material>
-		</ExperimentalAcrylicBorder>
-		<TextBlock>
-			Welcome to Avalonia!
-		</TextBlock>
-	</Panel>
-</Window>
\ No newline at end of file
diff --git a/Lightquark.NET/Client.cs b/Lightquark.NET/Client.cs
index 86990912885fee07f71c046bf69ba47495fb51e5..01bb2545c4827fa412be92eba232ed9a5ac84c63 100644
--- a/Lightquark.NET/Client.cs
+++ b/Lightquark.NET/Client.cs
@@ -73,7 +73,7 @@ public partial class Client : ObservableObject
         {
             Formatting = Formatting.Indented,
             NullValueHandling = NullValueHandling.Ignore,
-            Converters = new List<JsonConverter> { new AttachmentCreationConverter(), new StatusCreationConverter(), new ChannelCreationConverter() },
+            Converters = new List<JsonConverter> { new AttachmentCreationConverter(), new StatusCreationConverter() },
             ContractResolver = new CamelCasePropertyNamesContractResolver()
         };
         var mapperConfig = new MapperConfiguration(cfg =>
diff --git a/Lightquark.NET/ClientMethods/Channel.cs b/Lightquark.NET/ClientMethods/Channel.cs
index 25ab170a3023b22905e9aab76da68b40f7be28f5..9268f2cdc879bcd3465a6c408caac332125d9acc 100644
--- a/Lightquark.NET/ClientMethods/Channel.cs
+++ b/Lightquark.NET/ClientMethods/Channel.cs
@@ -1,21 +1,89 @@
-using Lightquark.NET.Objects;
+using Avalonia.Threading;
+using Lightquark.NET.Objects;
+using Lightquark.NET.Objects.Reply;
+using MongoDB.Bson;
+using Serilog;
 
 namespace Lightquark.NET;
 
 public partial class Client
 {
-    private void AddOrUpdateChannel(Channel channel)
+    private async void AddOrUpdateChannel(Channel channel)
     {
-        var existingChannel = Channels.FirstOrDefault(q => q.Id == channel.Id);
-        if (existingChannel != null)
+        try
         {
-            // Update
-            var index = Channels.IndexOf(existingChannel);
-            Channels[index] = _mapper.Map(channel, existingChannel);
+            var existingChannel = Channels.FirstOrDefault(q => q.Id == channel.Id);
+            if (existingChannel != null)
+            {
+                // Update
+                var index = Channels.IndexOf(existingChannel);
+                channel = _mapper.Map(channel, existingChannel);
+                Channels[index] = channel;
+            }
+            else
+            {
+                Channels.Add(channel);
+            }
+
+            var parentQuark = Quarks.FirstOrDefault(q => q.Id == channel.QuarkId);
+            if (parentQuark != null)
+            {
+                Log.Information("Channel {ChannelId} ({ChannelName}) has parent quark found in cache: {QuarkId} ({QuarkName})", 
+                    channel.Id, 
+                    channel.Name, 
+                    parentQuark.Id, 
+                    parentQuark.Name);
+                var c = parentQuark.Channels.ToList();
+                c.Add(channel);
+                parentQuark.Channels = c.ToArray();
+            }
+            else
+            {
+                Log.Warning("Channel {ChannelId} ({ChannelName}) has no parent quark in cache: {QuarkId}", channel.Id, channel.Name, channel.QuarkId);
+            }
         }
-        else
+        catch (Exception ex)
         {
-            Channels.Add(channel);
+            Log.Error(ex, "Failed to AddOrUpdateChannel");
         }
     }
+
+    private void RemoveChannel(ObjectId channelId)
+    {
+        var existingChannel = Channels.FirstOrDefault(c => c.Id == channelId);
+        if (existingChannel == null) return;
+        Channels.Remove(existingChannel);
+        var parentQuark = Quarks.FirstOrDefault(q => q.Channels.Contains(existingChannel));
+        if (parentQuark == null) return;
+        var channels = parentQuark.Channels.ToList();
+        parentQuark.Channels = channels.Where(c => c.Id != channelId).ToArray();
+    }
+
+    public async Task<Reply<ChannelCreateReplyResponse>> CreateChannel(ObjectId quarkId, string name, string? description)
+    {
+        var res = await CallRpc<ChannelCreateReplyResponse>("POST", $"{Version}/channel", new
+        {
+            quark = quarkId.ToString(),
+            name,
+            description
+        });
+
+        return res;
+    }
+
+    public async Task<Reply<ChannelDeleteReplyResponse>> DeleteChannel(ObjectId channelId)
+    {
+        var res = await CallRpc<ChannelDeleteReplyResponse>("DELETE", $"{Version}/channel/{channelId}", null);
+        return res;
+    }
+}
+
+public record ChannelCreateReplyResponse : BaseReplyResponse
+{
+    
+}
+
+public record ChannelDeleteReplyResponse : BaseReplyResponse
+{
+    
 }
\ No newline at end of file
diff --git a/Lightquark.NET/ClientMethods/Gateway.cs b/Lightquark.NET/ClientMethods/Gateway.cs
index a3e95ded3a7adda8e73acc877c8746ea18d09a05..8125502155a429c52cb42a3a5e72c38369cfc35f 100644
--- a/Lightquark.NET/ClientMethods/Gateway.cs
+++ b/Lightquark.NET/ClientMethods/Gateway.cs
@@ -1,4 +1,5 @@
 using System.Diagnostics;
+using Avalonia.Threading;
 using Lightquark.NET.Objects;
 using Lightquark.NET.Objects.Reply;
 using Lightquark.NET.Util;
@@ -120,6 +121,19 @@ public partial class Client
                         var selectionMessage = ParseGateway<SelectionGatewayMessage>(msg.Text!);
                         GatekeeperSelection(selectionMessage);
                         break;
+                    case "channelCreate":
+                    case "channelUpdate":
+                        var channelCreateMessage = ParseGateway<ChannelCreateGatewayMessage>(msg.Text!);
+                        channelCreateMessage.Channel.QuarkId = channelCreateMessage.Quark.Id;
+                        channelCreateMessage.Channel.Quark = null;
+                        AddOrUpdateChannel(channelCreateMessage.Channel);
+                        _ = Dispatcher.UIThread.Invoke(UpdateResourceTree);
+                        break;
+                    case "channelDelete":
+                        var channelDeleteMessage = ParseGateway<ChannelCreateGatewayMessage>(msg.Text!);
+                        RemoveChannel(channelDeleteMessage.Channel.Id);
+                        _ = Dispatcher.UIThread.Invoke(UpdateResourceTree);
+                        break;
                 }
             }
             catch (Exception ex)
@@ -244,6 +258,14 @@ public record MessageCreateGatewayMessage : BaseGatewayMessage
     public required ObjectId ChannelId { get; init; }
 }
 
+public record ChannelCreateGatewayMessage : BaseGatewayMessage
+{
+    [JsonProperty("channel")]
+    public required Channel Channel { get; init; }
+    [JsonProperty("quark")]
+    public required Quark Quark { get; init; }
+}
+
 public record GatewayBusEvent
 {
     public required BaseGatewayMessage BaseMessage { get; init; }
diff --git a/Lightquark.NET/ClientMethods/Quark.cs b/Lightquark.NET/ClientMethods/Quark.cs
index c834c384eacb10d68f84997aa720d127876570d6..5722be5a61943a99b9bbf7a719cf67c21d8ff15f 100644
--- a/Lightquark.NET/ClientMethods/Quark.cs
+++ b/Lightquark.NET/ClientMethods/Quark.cs
@@ -1,5 +1,7 @@
 using System.Collections.ObjectModel;
+using System.Diagnostics;
 using Avalonia.Media.Imaging;
+using Avalonia.Threading;
 using Lightquark.NET.Assets;
 using Lightquark.NET.Objects;
 using Lightquark.NET.Objects.Reply;
@@ -104,7 +106,7 @@ public partial class Client
         }
         
         await SaveQuarkList();
-        await UpdateResourceTree();
+        _ = Dispatcher.UIThread.Invoke(UpdateResourceTree);
     }
     
     private Func<QuarkListItem, bool> IdFinder(ObjectId id)
@@ -149,8 +151,13 @@ public partial class Client
         });
     }
 
-    private async Task UpdateResourceTree()
+    private int _g;
+    private readonly SemaphoreSlim _g2 = new(1); 
+    public async Task UpdateResourceTree()
     {
+        Log.Information("[{G}] UpdateResourceTree called", _g++);
+        await _g2.WaitAsync();
+        Log.Information("[{G}] UpdateResourceTree obtained semaphore", _g);
         var quarkList = QuarkList.ToList();
         foreach (var quarkListItem in quarkList.ToList())
         {
@@ -174,6 +181,7 @@ public partial class Client
                 var populatedQuark = await PopulateQuarkListQuark(quarkListItem);
                 if (populatedQuark != null)
                 {
+                    populatedQuark.IsExpanded = quarkListItem.IsExpanded;
                     quarkList[index] = populatedQuark;
                 }
             }
@@ -185,19 +193,22 @@ public partial class Client
         {
             ResourceTree.Add(quarkListItem);
         }
+
+        _g2.Release();
+        Log.Information("[{G}] UpdateResourceTree ends, {A} items", _g, ResourceTree.Count);
     }
 
     private async Task<QuarkListItem?> PopulateQuarkListQuark(QuarkListItem quarkListItem)
     {
         if (quarkListItem.Type != QuarkListItemType.Quark)
             throw new Exception("Quark list parse failure? Non-quark passed to PopulateQuarkListQuark");
-        Log.Information("Quark {@Quark}", quarkListItem);
+        // Log.Information("Quark {@Quark}", quarkListItem);
         if (quarkListItem.QuarkId == null || quarkListItem.QuarkId == ObjectId.Empty)
             throw new Exception("Quark list parse failure? Empty or null quark id");
         var quark = Quarks.FirstOrDefault(q => q.Id == quarkListItem.QuarkId);
         if (quark == null)
         {
-            Log.Information("Missing Quark in cache, getting it from API");
+            // Log.Information("Missing Quark in cache, getting it from API");
             var rpcQuark = await CallRpc<QuarkReplyResponse>("GET", $"/{Version}/quark/{quarkListItem.QuarkId}", null);
             if (!rpcQuark.Request.Success) return quarkListItem;
             AddOrUpdateQuark(rpcQuark.Response.Quark!);
@@ -206,11 +217,12 @@ public partial class Client
 
         quarkListItem.Quark = quark;
         quarkListItem.Children = [];
-        Log.Information("Quark has {Count} channels", quark.Channels?.Length);
+        // Log.Information("Quark has {Count} channels", quark.Channels?.Length);
         foreach (var channel in quark.Channels ?? [])
         {
-            Log.Information("Channel {Ch}", channel);
-            AddOrUpdateChannel((Channel)channel);
+            // Log.Information("Channel {Ch}", channel);
+            channel.QuarkId = quark.Id;
+            AddOrUpdateChannel(channel);
             var cacheChannel = Channels.FirstOrDefault(c => c.Id == channel.Id);
             quarkListItem.Children.Add(new QuarkListItem
             {
diff --git a/Lightquark.NET/Objects/Channel.cs b/Lightquark.NET/Objects/Channel.cs
index 9385ec0653a198172c333489819e4c65e012ec3c..663a1cf1b1e91726f605cfc133d345a23c5d4093 100644
--- a/Lightquark.NET/Objects/Channel.cs
+++ b/Lightquark.NET/Objects/Channel.cs
@@ -6,7 +6,7 @@ using Newtonsoft.Json;
 
 namespace Lightquark.NET.Objects;
 
-public class Channel : ObservableObject, IChannel
+public class Channel : ObservableObject
 {
     private ObjectId _id;
     private string _name;
@@ -14,6 +14,7 @@ public class Channel : ObservableObject, IChannel
     private int? _index;
     private ObjectId _quarkId;
     private Quark[]? _virtualQuarks;
+    private Quark? _quark;
 
     [JsonProperty("_id")]
     [JsonConverter(typeof(ObjectIdConverter))]
@@ -51,18 +52,15 @@ public class Channel : ObservableObject, IChannel
         set => SetProperty(ref _quarkId, value);
     }
 
-    // Virtuals
 
-    [JsonIgnore]
-    public Quark[]? VirtualQuarks
+    [JsonProperty("quark")]
+    public Quark? Quark
     {
-        get => _virtualQuarks;
+        get => _quark;
         set
         {
-            if (SetProperty(ref _virtualQuarks, value)) OnPropertyChanged(nameof(Quark));
+            SetProperty(ref _quark, value);
+            if (value != null) QuarkId = value.Id;
         }
     }
-
-    [JsonProperty("quark")]
-    public IQuark? Quark => VirtualQuarks?.First();
 }
\ No newline at end of file
diff --git a/Lightquark.NET/Objects/Quark.cs b/Lightquark.NET/Objects/Quark.cs
index 451574cc0c2021f0e8a225612934c2fc3ffd7695..1e6b137fb51d8016526df0d524fb8a9ac2a8abe0 100644
--- a/Lightquark.NET/Objects/Quark.cs
+++ b/Lightquark.NET/Objects/Quark.cs
@@ -7,7 +7,7 @@ using Newtonsoft.Json;
 
 namespace Lightquark.NET.Objects;
 
-public class Quark : ObservableObject, IQuark
+public class Quark : ObservableObject
 {
     [JsonIgnore]
     private Bitmap? _icon;
@@ -15,6 +15,8 @@ public class Quark : ObservableObject, IQuark
     [JsonIgnore]
     public string? FetchedIcon = null;
 
+    private Channel[] _channels;
+
     [JsonIgnore]
     public Bitmap? Icon
     {
@@ -41,8 +43,12 @@ public class Quark : ObservableObject, IQuark
     public required ObjectId[] Owners { get; set; }
 
     [JsonProperty("channels")]
-    public IChannel[] Channels { get; set; }
-    
+    public Channel[] Channels
+    {
+        get => _channels;
+        set => _channels = value.DistinctBy(c => c.Id).ToArray();
+    }
+
     [JsonProperty("inviteEnabled")]
     public bool InviteEnabled { get; set; } = true;
 
diff --git a/Lightquark.NET/Objects/QuarkListItem.cs b/Lightquark.NET/Objects/QuarkListItem.cs
index 5608dc436bf265d7b970661d66b8ecbbde93529b..46c691b085e1a4ff0b73ca28c09c0fe101a58f0d 100644
--- a/Lightquark.NET/Objects/QuarkListItem.cs
+++ b/Lightquark.NET/Objects/QuarkListItem.cs
@@ -1,13 +1,21 @@
 using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
 using Lightquark.NET.Util.Converters;
 using Lightquark.Types.Mongo;
 using MongoDB.Bson;
 using Newtonsoft.Json;
+using Serilog;
 
 namespace Lightquark.NET.Objects;
 
-public class QuarkListItem
+public class QuarkListItem : ObservableObject
 {
+    private bool _isExpanded;
+    private string? _folderName;
+    private ObservableCollection<QuarkListItem>? _children;
+    private Channel? _channel;
+    private Quark? _quark;
+
     [JsonProperty("type")]
     [JsonConverter(typeof(LowerCaseStringEnumConverter))]
     public required QuarkListItemType Type { get; init; }
@@ -15,18 +23,41 @@ public class QuarkListItem
     [JsonProperty("quarkId")]
     [JsonConverter(typeof(ObjectIdConverter))]
     public ObjectId? QuarkId { get; init; }
-    
+
     [JsonIgnore]
-    public Quark? Quark { get; set; }
-    
+    public Quark? Quark
+    {
+        get => _quark;
+        set => SetProperty(ref _quark, value);
+    }
+
     [JsonIgnore]
-    public Channel? Channel { get; set; }
-    
+    public Channel? Channel
+    {
+        get => _channel;
+        set => SetProperty(ref _channel, value);
+    }
+
     [JsonProperty("children")]
-    public ObservableCollection<QuarkListItem>? Children { get; set; }
+    public ObservableCollection<QuarkListItem>? Children
+    {
+        get => _children;
+        set => SetProperty(ref _children, value);
+    }
 
     [JsonProperty("name")]
-    public string? FolderName { get; set; }
+    public string? FolderName
+    {
+        get => _folderName;
+        set => SetProperty(ref _folderName, value);
+    }
+
+    [JsonIgnore]
+    public bool IsExpanded
+    {
+        get => _isExpanded;
+        set => SetProperty(ref _isExpanded, value);
+    }
 }
 
 public enum QuarkListItemType
diff --git a/Lightquark.NET/Util/Converters/ChannelCreationConverter.cs b/Lightquark.NET/Util/Converters/ChannelCreationConverter.cs
deleted file mode 100644
index 67c89994a7931e0a18440accfa4ee84a7fc31e31..0000000000000000000000000000000000000000
--- a/Lightquark.NET/Util/Converters/ChannelCreationConverter.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Lightquark.NET.Objects;
-using Lightquark.Types.Mongo;
-using Newtonsoft.Json.Converters;
-
-namespace Lightquark.NET.Util.Converters;
-
-public class ChannelCreationConverter : CustomCreationConverter<IChannel>
-{
-    public override IChannel Create(Type objectType)
-    {
-        return new Channel();
-    }
-}
\ No newline at end of file