about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-05-14 17:49:09 +0200
committerRory& <root@rory.gay>2024-05-14 17:49:09 +0200
commit41c5a84dacfd036b8d8f01f72226ac5a519995e3 (patch)
treea4bfc76541692cbbb0fc18f34463cf31a57440f5
parentImprove the heatmap layout (diff)
downloadMatrixUtils-41c5a84dacfd036b8d8f01f72226ac5a519995e3.tar.xz
Organise tools somewhat, set proper icons for nav menu
m---------LibMatrix0
-rw-r--r--MatrixRoomUtils.sln6
-rw-r--r--MatrixUtils.Web/MatrixUtils.Web.csproj21
-rw-r--r--MatrixUtils.Web/Pages/Dev/ModalTest.razor88
-rw-r--r--MatrixUtils.Web/Pages/Index.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor (renamed from MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor)2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor (renamed from MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor)2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs (renamed from MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor (renamed from MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor)2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/Index.razor (renamed from MatrixUtils.Web/Pages/Client/Index.razor)2
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor (renamed from MatrixUtils.Web/Pages/User/DMSpace.razor)6
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor (renamed from MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor (renamed from MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor (renamed from MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor (renamed from MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Index.razor11
-rw-r--r--MatrixUtils.Web/Pages/Labs/Index.razor.css (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2.razor)4
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor)6
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css0
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor (renamed from MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor)1
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor (renamed from MatrixUtils.Web/Pages/Tools/LeaveRoom.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor (renamed from MatrixUtils.Web/Pages/Tools/MediaLocator.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor (renamed from MatrixUtils.Web/Pages/Tools/MigrateRoom.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor (renamed from MatrixUtils.Web/Pages/Tools/SpaceDebug.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Index.razor39
-rw-r--r--MatrixUtils.Web/Pages/Tools/Index.razor.css6
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor (renamed from MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor (renamed from MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css (renamed from MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor (renamed from MatrixUtils.Web/Pages/Tools/SessionCount.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor (renamed from MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor)3
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor (renamed from MatrixUtils.Web/Pages/Tools/InviteCounter.razor)9
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor (renamed from MatrixUtils.Web/Pages/Tools/MassCMEBan.razor)8
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor276
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor (renamed from MatrixUtils.Web/Pages/Tools/RoomIntersections.razor)2
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor (renamed from MatrixUtils.Web/Pages/Tools/UserTrace.razor)6
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor (renamed from MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor (renamed from MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor)0
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor (renamed from MatrixUtils.Web/Pages/Tools/ViewAccountData.razor)0
-rw-r--r--MatrixUtils.Web/Shared/NavMenu.razor20
-rw-r--r--MatrixUtils.Web/Shared/NavMenu.razor.css5
46 files changed, 381 insertions, 146 deletions
diff --git a/LibMatrix b/LibMatrix
-Subproject 896ee7f099f817e8cc9aba96a9db00fcce67163
+Subproject b5860ce2011b96a2919d5306445b0e8bd8408b3
diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln
index 08f236d..f52a446 100644
--- a/MatrixRoomUtils.sln
+++ b/MatrixRoomUtils.sln
@@ -60,8 +60,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "Lib
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.DmSpaced", "MatrixUtils.DmSpaced\MatrixUtils.DmSpaced.csproj", "{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorApp1", "tmp\BlazorApp1\BlazorApp1.csproj", "{2057E543-84D9-483A-9A14-6399E7062419}"
-EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -164,10 +162,6 @@ Global
 		{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.Build.0 = Release|Any CPU
-		{2057E543-84D9-483A-9A14-6399E7062419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{2057E543-84D9-483A-9A14-6399E7062419}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{2057E543-84D9-483A-9A14-6399E7062419}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{2057E543-84D9-483A-9A14-6399E7062419}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{F4E241C3-0300-4B87-8707-BCBDEF1F0185} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33}
diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index c2732de..f27ae40 100644
--- a/MatrixUtils.Web/MatrixUtils.Web.csproj
+++ b/MatrixUtils.Web/MatrixUtils.Web.csproj
@@ -48,7 +48,26 @@
     </ItemGroup>
 
     <ItemGroup>
-      <Folder Include="Pages\Tools\Moderation\Draupnir\" />
+      <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\ClientRoomList.razor" />
+      <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\ClientStatusList.razor" />
+      <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\MatrixClient.razor" />
+      <_ContentIncludedByDefault Remove="Pages\Client\Index.razor" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\ClientRoomList.razor" />
+      <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\ClientStatusList.razor" />
+      <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\MatrixClient.razor" />
+      <AdditionalFiles Include="Pages\Labs\Client\Index.razor" />
+      <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage0.razor" />
+      <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage1.razor" />
+      <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage2.razor" />
+      <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage3.razor" />
+      <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\MainTabComponents\MainTabSpaceItem.razor" />
+      <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2ByRoomTypeTab.razor" />
+      <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2DMsTab.razor" />
+      <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2MainTab.razor" />
+      <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2SyncContainer.razor" />
     </ItemGroup>
     
 </Project>
diff --git a/MatrixUtils.Web/Pages/Dev/ModalTest.razor b/MatrixUtils.Web/Pages/Dev/ModalTest.razor
deleted file mode 100644
index 4a0487f..0000000
--- a/MatrixUtils.Web/Pages/Dev/ModalTest.razor
+++ /dev/null
@@ -1,88 +0,0 @@
-@page "/Dev/ModalTest"
-@inject IJSRuntime JsRuntime
-<h3>ModalTest</h3>
-
-@foreach (var (key, value) in _windowInfos) {
-   @* <ModalWindow X="@value.X" Y="@value.Y" Title="@value.Title">@value.Content</ModalWindow> *@
-}
-@for (var i = 0; i < 5; i++) {
-     var i1 = i;
-     <ModalWindow X="@Random.Shared.Next(1400)" Y="@Random.Shared.Next(1000)" Title="@("Window " + i1)" OnCloseClicked="() => OnCloseClicked(i1)">
-          @for (var j = 0; j < i1; j++) {
-              <h1>@j</h1>
-          }
-     </ModalWindow>
-}
-
-@code {
-
-    private Dictionary<int, WindowInfo> _windowInfos = new();
-
-    private class WindowInfo {
-        public double X;
-        public double Y;
-        public string Title;
-        public RenderFragment Content;
-    }
-
-    protected override async Task OnInitializedAsync() {
-        double _x = 2;
-        double _xv = 20;
-        double _y = 0;
-        double multiplier = 1;
-
-        for (var i = 0; i < 200; i++) {
-            var i1 = i;
-            _windowInfos.Add(_windowInfos.Count, new WindowInfo {
-                X = _x,
-                Y = _y,
-                Title = "Win" + i1,
-                Content = builder => {
-                    builder.OpenComponent<ModalWindow>(0);
-                    builder.AddAttribute(1, "X", _x);
-                    builder.AddAttribute(2, "Y", _y);
-                    builder.AddAttribute(3, "Title", "Win" + i1);
-                    builder.AddAttribute(4, "ChildContent", (RenderFragment)(builder2 => {
-                        builder2.OpenElement(0, "h1");
-                        builder2.AddContent(1, "Hello " + i1);
-                        builder2.CloseElement();
-                    }));
-                    builder.CloseComponent();
-                }
-            });
-            //_x += _xv /= 1000/System.Math.Sqrt((double)_windowInfos.Count)*_windowInfos.Count.ToString().Length*multiplier;
-            _y += 20;
-	    _x += 20;
-            var dimension = await JsRuntime.InvokeAsync<WindowDimension>("getWindowDimensions");
-            if (_x > dimension.Width - 100) _x %= dimension.Width - 100;
-            if (_y > dimension.Height - 50) {
-                _y %= dimension.Height - 50;
-                _xv = 20;
-            }
-            if (
-                (_windowInfos.Count < 10 && _windowInfos.Count % 2 == 0) ||
-                (_windowInfos.Count < 100 && _windowInfos.Count % 10 == 0) ||
-                (_windowInfos.Count < 1000 && _windowInfos.Count % 50 == 0) ||
-                (_windowInfos.Count < 10000 && _windowInfos.Count % 100 == 0)
-                ) {
-                StateHasChanged();
-                await Task.Delay(25);
-            }
-            if(_windowInfos.Count > 750) multiplier = 2;
-            if(_windowInfos.Count > 1500) multiplier = 3;
-
-        }
-
-        await base.OnInitializedAsync();
-    }
-
-    private void OnCloseClicked(int i1) {
-        Console.WriteLine("Close clicked on " + i1);
-    }
-
-    public class WindowDimension {
-        public int Width { get; set; }
-        public int Height { get; set; }
-    }
-
-}
diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor
index b3dc7d4..8dfd1be 100644
--- a/MatrixUtils.Web/Pages/Index.razor
+++ b/MatrixUtils.Web/Pages/Index.razor
@@ -186,7 +186,7 @@ Small collection of tools to do not-so-everyday things.
                     ServerVersion = await (serverVersionTask ?? Task.FromResult<ServerVersionResponse?>(null)!),
                     Homeserver = hs
                 });
-                if (updateSw.ElapsedMilliseconds > 250) {
+                if (updateSw.ElapsedMilliseconds > 25) {
                     updateSw.Restart();
                     StateHasChanged();
                 }
diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
index 845f30d..b370080 100644
--- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientRoomList.razor
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
@@ -1,4 +1,4 @@
-@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext
+@using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext
 @* user header and room list *@
 @foreach (var room in Data.SyncWrapper.Rooms) {
     <LinkButton OnClick="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")">
diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor
index 1100c98..c680c13 100644
--- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientStatusList.razor
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor
@@ -1,4 +1,4 @@
-@using ClientContext = MatrixUtils.Web.Pages.Client.Index.ClientContext;
+@using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext;
 @using System.Collections.ObjectModel
 
 @foreach (var ctx in Data) {
diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs
index 16051b8..16051b8 100644
--- a/MatrixUtils.Web/Pages/Client/ClientComponents/ClientSyncWrapper.cs
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientSyncWrapper.cs
diff --git a/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor
index b4a81f7..7d3e52a 100644
--- a/MatrixUtils.Web/Pages/Client/ClientComponents/MatrixClient.razor
+++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor
@@ -1,4 +1,4 @@
-@using Index = MatrixUtils.Web.Pages.Client.Index
+@using Index = MatrixUtils.Web.Pages.Labs.Client.Index
 @using MatrixUtils.Web.Pages.Client.ClientComponents
 
 <div class="container-fluid">
diff --git a/MatrixUtils.Web/Pages/Client/Index.razor b/MatrixUtils.Web/Pages/Labs/Client/Index.razor
index 2a9a327..5b489b0 100644
--- a/MatrixUtils.Web/Pages/Client/Index.razor
+++ b/MatrixUtils.Web/Pages/Labs/Client/Index.razor
@@ -1,4 +1,4 @@
-@page "/Client"
+@page "/Labs/Client"
 @using LibMatrix
 @using MatrixUtils.Abstractions
 @using MatrixUtils.Web.Pages.Client.ClientComponents
diff --git a/MatrixUtils.Web/Pages/User/DMSpace.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor
index e3dba30..c0dc8a6 100644
--- a/MatrixUtils.Web/Pages/User/DMSpace.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor
@@ -1,10 +1,10 @@
-@page "/User/DMSpace/Setup"
+@page "/Labs/DMSpace/Setup"
 @using LibMatrix
 @using LibMatrix.Responses
 @using MatrixUtils.Abstractions
 @using MatrixUtils.LibDMSpace
 @using MatrixUtils.LibDMSpace.StateEvents
-@using MatrixUtils.Web.Pages.User.DMSpaceStages
+@using MatrixUtils.Web.Pages.Labs.DMSpace.DMSpaceStages
 @using System.Text.Json.Serialization
 <h3>DM Space Management</h3>
 <hr/>
@@ -49,7 +49,7 @@
 
     protected override async Task OnInitializedAsync() {
         if (NavigationManager.Uri.Contains("?stage=")) {
-            NavigationManager.NavigateTo("/User/DMSpace/Setup", true);
+            NavigationManager.NavigateTo(NavigationManager.Uri.Replace("stage=", ""), true); //"/User/DMSpace/Setup"
         }
         DMSpaceRootPage = this;
         SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate();
diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor
index 5f6508c..5f6508c 100644
--- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage0.razor
diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
index 2176467..2176467 100644
--- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor
diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
index a70e9c5..a70e9c5 100644
--- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor
diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
index 865e956..865e956 100644
--- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor
+++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor
diff --git a/MatrixUtils.Web/Pages/Labs/Index.razor b/MatrixUtils.Web/Pages/Labs/Index.razor
new file mode 100644
index 0000000..fbe4b62
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Labs/Index.razor
@@ -0,0 +1,11 @@
+@page "/Labs"
+<h3>Index of /Labs</h3>
+<p>Welcome to RMU Laboratories! We wish you a safe and informative time!</p>
+<p>These pages are a work in progress, and may not work <b>or cause permanent account changes!</b></p>
+<p>We do not claim responsibility in case something goes wrong here!</p>
+<p><b style="color: red;">Here be dragons!!</b></p>
+<br/>
+
+<a href="/Labs/Rooms2">Room List v3</a><br/>
+<a href="/Labs/Client">Client implementation attempt</a><br/>
+<a href="/Labs/DMSpace/Setup">DM Space setup</a><br/>
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Labs/Index.razor.css
index e69de29..e69de29 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css
+++ b/MatrixUtils.Web/Pages/Labs/Index.razor.css
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor
index 98b8a1d..3392960 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor
@@ -1,9 +1,9 @@
-@page "/Rooms2"
+@page "/Labs/Rooms2"
 @using LibMatrix.Responses
 @using System.Collections.ObjectModel
 @using System.ComponentModel
 @using MatrixUtils.Abstractions
-@using MatrixUtils.Web.Pages.Rooms.Index2Components
+@using MatrixUtils.Web.Pages.Labs.Rooms2.Index2Components
 @inject ILogger<Index> logger
 <h3>Room list</h3>
 
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor
index 6483f01..6483f01 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css
index d6e413f..d6e413f 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
index f4cf849..f4cf849 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2ByRoomTypeTab.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor
index f4cf849..f4cf849 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor
index b163a52..7ccfae2 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor
@@ -1,11 +1,7 @@
 @using MatrixUtils.Abstractions
-@using System.Security.Cryptography
-@using ArcaneLibs.Extensions
 @using System.ComponentModel
-@using System.Diagnostics
 @using LibMatrix.EventTypes.Spec.State
-@using MatrixUtils.Web.Pages.Rooms.Index2Components.MainTabComponents
-@using Microsoft.AspNetCore.Components.Rendering
+@using MatrixUtils.Web.Pages.Labs.Rooms2.Index2Components.MainTabComponents
 <h3>RoomsIndex2MainTab</h3>
 
 @* <div> *@
diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor.css
diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor
index 418ee02..91f228d 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor
+++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor
@@ -2,7 +2,6 @@
 @using LibMatrix.Responses
 @using MatrixUtils.Abstractions
 @using System.Diagnostics
-@using System.Diagnostics.CodeAnalysis
 @using LibMatrix.EventTypes.Spec.State
 @using LibMatrix.Extensions
 @using LibMatrix.Utilities
diff --git a/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
index 841552e..841552e 100644
--- a/MatrixUtils.Web/Pages/Tools/LeaveRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
diff --git a/MatrixUtils.Web/Pages/Tools/MediaLocator.razor b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor
index 6e87926..6e87926 100644
--- a/MatrixUtils.Web/Pages/Tools/MediaLocator.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor
diff --git a/MatrixUtils.Web/Pages/Tools/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
index 11d35f1..11d35f1 100644
--- a/MatrixUtils.Web/Pages/Tools/MigrateRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
diff --git a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor
index 263879b..263879b 100644
--- a/MatrixUtils.Web/Pages/Tools/SpaceDebug.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor
index f1e04a3..3aec2e3 100644
--- a/MatrixUtils.Web/Pages/Tools/Index.razor
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor
@@ -1,10 +1,31 @@
 @page "/Tools"
-<h3>Other tools</h3>
-
-<a href="/Tools/CopyPowerlevel">Copy highest powerlevel across all session</a><br/>
-<a href="/Tools/KnownHomeserverList">Find all homeservers you share a room with</a><br/>
-<a href="/Tools/MassRoomJoin">Join room across all session</a><br/>
-<a href="/Tools/MediaLocator">Locate lost media</a><br/>
-<a href="/Tools/SpaceDebug">Debug space relationships</a><br/>
-<a href="/Tools/MigrateRoom">Migrate users from a split room to a new room</a><br/>
-<a href="/Tools/LeaveRoom">Leave room by ID</a><br/>
+<h3>Index of /Tools</h3>
+
+<h4 class="tool-category">Information tools</h4>
+<hr/>
+<a href="/Tools/Info/PolicyListActivity">View policy list activity</a><br/>
+<a href="/Tools/Info/KnownHomeserverList">Find all homeservers you share a room with</a><br/>
+<a href="/Tools/Info/SessionCount">Show session counts for users in a given room</a><br/>
+
+<h4 class="tool-category">User tools</h4>
+<hr/>
+<a href="/Tools/User/MassRoomJoin">Join room across all session</a><br/>
+<a href="/Tools/User/CopyPowerlevel">Copy highest powerlevel across all session</a><br/>
+<a href="/Tools/User/ViewAccountData">View account data</a><br/>
+
+<h4 class="tool-category">Moderation tools</h4>
+<hr/>
+<a href="/Tools/Moderation/InviteCounter">Count invites by inviter</a><br/>
+<a href="/Tools/Moderation/MembershipHistory">View membership history</a><br/>
+<a href="/Tools/Moderation/UserTrace">Trace user across rooms</a><br/>
+<a href="/tools/Moderation/MassCMEBan">Mass write policies to Community Moderation Effort</a><br/>
+<a href="/tools/Moderation/RoomIntersections">Find rooms with common users</a><br/>
+<a href="/tools/Moderation/DraupnirProtectedRoomsEditor">Edit Draupnir protected rooms set</a><br/>
+
+
+<h4 class="tool-category">Debugging tools</h4>
+<hr/>
+<a href="/Tools/Debug/SpaceDebug">Debug space relationships</a><br/>
+<a href="/Tools/Debug/LeaveRoom">Leave room by ID</a><br/>
+<a href="/Tools/Debug/MediaLocator">Locate lost media</a><br/>
+<a href="/Tools/Debug/MigrateRoom">Migrate users from a split room to a new room</a><br/>
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor.css b/MatrixUtils.Web/Pages/Tools/Index.razor.css
new file mode 100644
index 0000000..c9bd995
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor.css
@@ -0,0 +1,6 @@
+.tool-category {
+    margin-top: 20px;
+}
+hr{
+    margin: unset;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
index ddd7b15..ddd7b15 100644
--- a/MatrixUtils.Web/Pages/Tools/KnownHomeserverList.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
diff --git a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
index c94d0b0..c94d0b0 100644
--- a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
diff --git a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css
index 443fdb5..443fdb5 100644
--- a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor.css
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor.css
diff --git a/MatrixUtils.Web/Pages/Tools/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
index 3b68bfa..3b68bfa 100644
--- a/MatrixUtils.Web/Pages/Tools/SessionCount.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
diff --git a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor
index f9cbfa2..805bd40 100644
--- a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor
@@ -1,9 +1,12 @@
 @page "/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/DraupnirProtectedRoomsEditor"
 @using System.Text.Json.Serialization
 @using LibMatrix.EventTypes.Spec.State
 @using LibMatrix.RoomTypes
 <h3>Edit Draupnir protected rooms</h3>
 <hr/>
+<p><b>Note:</b> You will need to restart Draupnir after applying changes!</p>
+<p>Minor note: This <i>should</i> also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.</p>
 
 @if (data is not null) {
     <div class="row">
diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
index 8f4b4dd..2123d4d 100644
--- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
@@ -1,12 +1,7 @@
-@page "/Tools/InviteCounter"
-@using ArcaneLibs.Extensions
-@using LibMatrix.RoomTypes
+@page "/Tools/Moderation/InviteCounter"
 @using System.Collections.ObjectModel
-@using LibMatrix
-@using System.Collections.Frozen
 @using LibMatrix.EventTypes.Spec.State
-@using MatrixUtils.Abstractions
-<h3>User Trace</h3>
+<h3>Invite counter</h3>
 <hr/>
 
 <br/>
diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
index cbbca9e..ea1e5f6 100644
--- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
@@ -1,12 +1,6 @@
-@page "/Tools/MassCMEBan"
-@using ArcaneLibs.Extensions
-@using LibMatrix.RoomTypes
+@page "/Tools/Moderation/MassCMEBan"
 @using System.Collections.ObjectModel
-@using LibMatrix
-@using System.Collections.Frozen
-@using LibMatrix.EventTypes.Spec.State
 @using LibMatrix.EventTypes.Spec.State.Policy
-@using MatrixUtils.Abstractions
 <h3>User Trace</h3>
 <hr/>
 
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
new file mode 100644
index 0000000..e5ba004
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -0,0 +1,276 @@
+@page "/Tools/Moderation/MembershipHistory"
+@using System.Collections.ObjectModel
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+<h3>Membership history viewer</h3>
+<hr/>
+
+<br/>
+<span>Room ID: </span>
+<InputText @bind-Value="@roomId"></InputText>
+<LinkButton OnClick="@Execute">Execute</LinkButton>
+<p><InputCheckbox @bind-Value="ChronologicalOrder"/> Chronological order</p>
+<p>
+    <span>Show </span>
+    <InputCheckbox @bind-Value="ShowJoins"/> joins
+    <InputCheckbox @bind-Value="ShowLeaves"/> leaves
+    <InputCheckbox @bind-Value="ShowUpdates"/> profile updates
+    <InputCheckbox @bind-Value="ShowKnocks"/> knocks
+    <InputCheckbox @bind-Value="ShowInvites"/> invites
+    <InputCheckbox @bind-Value="ShowKicks"/> kicks
+    <InputCheckbox @bind-Value="ShowBans"/> bans
+</p>
+<p>
+    <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = false; })">Hide all</LinkButton>
+    <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = true; })">Show all</LinkButton>
+    <LinkButton OnClick="@(async () => { ShowJoins ^= true; ShowLeaves ^= true; ShowUpdates ^= true; ShowKnocks ^= true; ShowInvites ^= true; ShowKicks ^= true; ShowBans ^= true; })">Toggle all</LinkButton>
+</p>
+<p>
+    <span>Sender: </span>
+    <InputSelect @bind-Value="Sender">
+        <option value="">All</option>
+        @foreach (var sender in Memberships.Select(x => x.Sender).Distinct()) {
+            <option value="@sender">@sender</option>
+        }
+    </InputSelect>
+</p>
+<p>
+    <span>User: </span>
+    <InputSelect @bind-Value="User">
+        <option value="">All</option>
+        @foreach (var user in Memberships.Select(x => x.StateKey).Distinct()) {
+            <option value="@user">@user</option>
+        }
+    </InputSelect>
+</p>
+
+
+<br/>
+
+<details>
+    <summary>Results</summary>
+    @{
+        Dictionary<string, StateEventResponse> previousMemberships = [];
+        var filteredMemberships = Memberships.AsEnumerable();
+        if (ChronologicalOrder) {
+            filteredMemberships = filteredMemberships.Reverse();
+        }
+        if(!string.IsNullOrWhiteSpace(Sender)) {
+            filteredMemberships = filteredMemberships.Where(x => x.Sender == Sender);
+        }
+        if(!string.IsNullOrWhiteSpace(User)) {
+            filteredMemberships = filteredMemberships.Where(x => x.StateKey == User);
+        }
+
+        @foreach (var membership in filteredMemberships) {
+            RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent;
+            @switch (content.Membership) {
+                case RoomMemberEventContent.MembershipTypes.Invite: {
+                    if (_showInvites) {
+                        <p style="color: green;">@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+                    }
+
+                    break;
+                }
+                case RoomMemberEventContent.MembershipTypes.Ban: {
+                    if (_showBans) {
+                        <p style="color: red;">@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+                    }
+
+                    break;
+                }
+                case RoomMemberEventContent.MembershipTypes.Leave: {
+                    if (membership.Sender == membership.StateKey) {
+                        if (_showLeaves) {
+                            <p style="color: #C66;">@membership.Sender left the room</p>
+                        }
+                    }
+                    else {
+                        if (_showKicks) {
+                            <p style="color: darkorange;">@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+                        }
+                    }
+
+                    break;
+                }
+                case RoomMemberEventContent.MembershipTypes.Knock: {
+                    if (_showKnocks) {
+                        <p>@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+                    }
+
+                    break;
+                }
+                case RoomMemberEventContent.MembershipTypes.Join: {
+                    if (previousMemberships.TryGetValue(membership.StateKey, out var previous)
+                        && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) {
+                        if (_showUpdates) {
+                            <p style="color: #777;">@membership.Sender changed their profile</p>
+                        }
+                    }
+                    else {
+                        if (_showJoins) {
+                            <p style="color: #6C6;">@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+                        }
+                    }
+
+                    break;
+                }
+                default: {
+                    <b>Unknown membership @content.Membership!</b>
+                    break;
+                }
+            }
+
+            previousMemberships[membership.StateKey] = membership;
+        }
+    }
+</details>
+
+<br/>
+<details open>
+    <summary>Log</summary>
+    @foreach (var line in log.Reverse()) {
+        <pre>@line</pre>
+    }
+</details>
+
+@code {
+
+#region Filter bindings
+
+    private bool _chronologicalOrder = false;
+
+    private bool ChronologicalOrder {
+        get => _chronologicalOrder;
+        set {
+            _chronologicalOrder = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showJoins = true;
+
+    private bool ShowJoins {
+        get => _showJoins;
+        set {
+            _showJoins = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showLeaves = true;
+
+    private bool ShowLeaves {
+        get => _showLeaves;
+        set {
+            _showLeaves = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showUpdates = true;
+
+    private bool ShowUpdates {
+        get => _showUpdates;
+        set {
+            _showUpdates = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showKnocks = true;
+
+    private bool ShowKnocks {
+        get => _showKnocks;
+        set {
+            _showKnocks = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showInvites = true;
+
+    private bool ShowInvites {
+        get => _showInvites;
+        set {
+            _showInvites = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showKicks = true;
+
+    private bool ShowKicks {
+        get => _showKicks;
+        set {
+            _showKicks = value;
+            StateHasChanged();
+        }
+    }
+
+    private bool _showBans = true;
+
+    private bool ShowBans {
+        get => _showBans;
+        set {
+            _showBans = value;
+            StateHasChanged();
+        }
+    }
+    
+    private string sender = "";
+    
+    private string Sender {
+        get => sender;
+        set {
+            sender = value;
+            StateHasChanged();
+        }
+    }
+    
+    private string user = "";
+    
+    private string User {
+        get => user;
+        set {
+            user = value;
+            StateHasChanged();
+        }
+    }
+
+#endregion
+
+    private ObservableCollection<string> log { get; set; } = new();
+    private List<StateEventResponse> Memberships { get; set; } = [];
+    private AuthenticatedHomeserverGeneric hs { get; set; }
+
+    [Parameter, SupplyParameterFromQuery(Name = "room")]
+    public string roomId { get; set; }
+
+    protected override async Task OnInitializedAsync() {
+        log.CollectionChanged += (sender, args) => StateHasChanged();
+        hs = await RMUStorage.GetCurrentSessionOrNavigate();
+        if (hs is null) return;
+
+        StateHasChanged();
+        Console.WriteLine("Rerendered!");
+        await base.OnInitializedAsync();
+        if (!string.IsNullOrWhiteSpace(roomId))
+            await Execute();
+    }
+
+    private async Task Execute() {
+        Memberships.Clear();
+        var room = hs.GetRoom(roomId);
+        var events = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000);
+        await foreach (var resp in events) {
+            var all = resp.State.Concat(resp.Chunk);
+            Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId));
+
+            log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline");
+        }
+
+        StateHasChanged();
+    }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
index 173ff01..b8baeb8 100644
--- a/MatrixUtils.Web/Pages/Tools/RoomIntersections.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
@@ -1,4 +1,4 @@
-@page "/Tools/RoomIntersections"
+@page "/Tools/Moderation/RoomIntersections"
 @using LibMatrix.RoomTypes
 @using System.Collections.ObjectModel
 @using LibMatrix
diff --git a/MatrixUtils.Web/Pages/Tools/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
index 95fe02b..915f8dc 100644
--- a/MatrixUtils.Web/Pages/Tools/UserTrace.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
@@ -1,12 +1,8 @@
-@page "/Tools/UserTrace"
+@page "/Tools/Moderation/UserTrace"
 @using ArcaneLibs.Extensions
 @using LibMatrix.RoomTypes
 @using System.Collections.ObjectModel
 @using LibMatrix
-@using System.Collections.Frozen
-@using LibMatrix.EventTypes.Spec.State
-@using LibMatrix.Filters
-@using MatrixUtils.Abstractions
 <h3>User Trace</h3>
 <hr/>
 
diff --git a/MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
index 667b518..667b518 100644
--- a/MatrixUtils.Web/Pages/Tools/CopyPowerlevel.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
diff --git a/MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
index a2ad388..a2ad388 100644
--- a/MatrixUtils.Web/Pages/Tools/MassJoinRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
diff --git a/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
index d8b02bb..d8b02bb 100644
--- a/MatrixUtils.Web/Pages/Tools/ViewAccountData.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
diff --git a/MatrixUtils.Web/Shared/NavMenu.razor b/MatrixUtils.Web/Shared/NavMenu.razor
index 43e2237..770a246 100644
--- a/MatrixUtils.Web/Shared/NavMenu.razor
+++ b/MatrixUtils.Web/Shared/NavMenu.razor
@@ -7,6 +7,8 @@
     </div>
 </div>
 
+@* icons: https://www.appstudio.dev/app/OpenIconic.html *@
+
 <div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
     <nav class="flex-column">
         <div class="nav-item px-3">
@@ -30,19 +32,19 @@
 
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="Rooms">
-                <span class="oi oi-plus" aria-hidden="true"></span> Room list
+                <span class="oi oi-list-rich" aria-hidden="true"></span> Room list
             </NavLink>
         </div>
 
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="User/Profile">
-                <span class="oi oi-plus" aria-hidden="true"></span> Manage profile
+                <span class="oi oi-person" aria-hidden="true"></span> Manage profile
             </NavLink>
         </div>
         
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="User/DirectMessages">
-                <span class="oi oi-plus" aria-hidden="true"></span> Manage DMs
+                <span class="oi oi-people" aria-hidden="true"></span> Manage DMs
             </NavLink>
         </div>
 
@@ -55,7 +57,7 @@
 
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="HSAdmin">
-                <span class="oi oi-plus" aria-hidden="true"></span> Homeserver admin
+                <span class="oi oi-hard-drive" aria-hidden="true"></span> Homeserver admin
             </NavLink>
         </div>
 
@@ -65,6 +67,12 @@
             </NavLink>
         </div>
 
+        <div class="nav-item px-3">
+            <NavLink class="nav-link" href="Labs">
+                <span class="oi oi-beaker" aria-hidden="true"></span> Labs
+            </NavLink>
+        </div>
+
         <!-- RMU -->
         
         <div class="nav-item px-3">
@@ -74,13 +82,13 @@
         
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="Dev/Options">
-                <span class="oi oi-plus" aria-hidden="true"></span> Developer options
+                <span class="oi oi-wrench" aria-hidden="true"></span> Developer options
             </NavLink>
         </div>
         
         <div class="nav-item px-3">
             <NavLink class="nav-link" href="Dev/Utilities">
-                <span class="oi oi-plus" aria-hidden="true"></span> Developer utilities
+                <span class="oi oi-code" aria-hidden="true"></span> Developer utilities
             </NavLink>
         </div>
     </nav>
diff --git a/MatrixUtils.Web/Shared/NavMenu.razor.css b/MatrixUtils.Web/Shared/NavMenu.razor.css
index 447f2df..afaa292 100644
--- a/MatrixUtils.Web/Shared/NavMenu.razor.css
+++ b/MatrixUtils.Web/Shared/NavMenu.razor.css
@@ -66,3 +66,8 @@
         overflow-y: auto;
     }
 }
+
+
+hr {
+    margin: 0;
+}
\ No newline at end of file