diff --git a/docs/configure/plugins/bar.md b/docs/configure/plugins/bar.md
index 9e4b3c84c..245d5809d 100644
--- a/docs/configure/plugins/bar.md
+++ b/docs/configure/plugins/bar.md
@@ -79,7 +79,10 @@ The `FocusedWindowWidget` displays the title of the focused window.
 
 ### Workspace Widget
 
-The `WorkspaceWidget` displays the name of the current workspace.
+The `WorkspaceWidget` displays:
+
+- the active workspace on the bar's monitor
+- the workspaces which can be opened on the monitor - i.e., [sticky workspaces for the monitor](../core/workspaces.md#sticky-workspaces) and non-sticky workspaces
 
 ### Tree Layout Widget
 
diff --git a/docs/script/plugins/bar.md b/docs/script/plugins/bar.md
index da21ec30b..96b90282b 100644
--- a/docs/script/plugins/bar.md
+++ b/docs/script/plugins/bar.md
@@ -34,6 +34,8 @@ List<BarComponent> rightComponents = new()
 - [FocusedWindowWidget](<xref:Whim.Bar.FocusedWindowWidget.CreateComponent(System.Func{Whim.IWindow,System.String})>)
 - [WorkspaceWidget](xref:Whim.Bar.WorkspaceWidget.CreateComponent)
 
+More information about each widget can be found [here](../../configure/plugins/bar.md#widgets).
+
 ## Example Config
 
 ```csharp
diff --git a/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs b/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs
index 17d024808..fdd20d771 100644
--- a/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs
+++ b/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs
@@ -1,4 +1,3 @@
-using AutoFixture;
 using NSubstitute;
 using Whim.TestUtils;
 using Windows.Win32.Graphics.Gdi;
@@ -6,282 +5,224 @@
 
 namespace Whim.Bar.Tests;
 
-public class WorkspaceWidgetViewModelCustomization : ICustomization
-{
-	public void Customize(IFixture fixture)
-	{
-		IContext context = fixture.Freeze<IContext>();
-
-		// The workspace manager should have a single workspace
-		using IWorkspace workspace = fixture.Create<IWorkspace>();
-		workspace.Id.Returns(Guid.NewGuid());
-		context.WorkspaceManager.GetEnumerator().Returns((_) => new List<IWorkspace>() { workspace }.GetEnumerator());
-
-		fixture.Inject(context);
-	}
-}
-
 [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
 public class WorkspaceWidgetViewModelTests
 {
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceAdded_AlreadyExists(IContext context, IMonitor monitor)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void WorkspaceWidgetViewModel_Ctor(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		IWorkspace workspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
+
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2);
 
 		// When
-		context.WorkspaceManager.WorkspaceAdded += Raise.Event<EventHandler<WorkspaceAddedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceAddedEventArgs() { Workspace = workspace }
-		);
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
 
 		// Then
-		Assert.Single(viewModel.Workspaces);
-		Assert.Equal(workspace, viewModel.Workspaces[0].Workspace);
-		context.Butler.Pantry.Received(1).GetMonitorForWorkspace(workspace);
+		Assert.Equal(2, sut.Workspaces.Count);
+		Assert.Same(workspace1, sut.Workspaces[0].Workspace);
+		Assert.Same(workspace2, sut.Workspaces[1].Workspace);
+		Assert.False(sut.Workspaces[0].ActiveOnMonitor);
+		Assert.True(sut.Workspaces[1].ActiveOnMonitor);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceAdded(IContext context, IMonitor monitor, IWorkspace addedWorkspace)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void WorkspaceAdded(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		IWorkspace workspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
 
-		// When
-		context.WorkspaceManager.WorkspaceAdded += Raise.Event<EventHandler<WorkspaceAddedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceAddedEventArgs() { Workspace = addedWorkspace }
-		);
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2);
 
-		// Then
-		Assert.Equal(2, viewModel.Workspaces.Count);
-		context.Butler.Pantry.Received(1).GetMonitorForWorkspace(workspace);
-		context.Butler.Pantry.Received(1).GetMonitorForWorkspace(addedWorkspace);
-	}
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceRemoved(IContext context, IMonitor monitor)
-	{
-		// Given
-		IWorkspace workspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		Workspace workspace3 = StoreTestUtils.CreateWorkspace(ctx);
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace3);
 
 		// When
-		context.WorkspaceManager.WorkspaceRemoved += Raise.Event<EventHandler<WorkspaceRemovedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceRemovedEventArgs() { Workspace = workspace }
-		);
+		root.WorkspaceSector.QueueEvent(new WorkspaceAddedEventArgs() { Workspace = workspace3 });
+		root.DispatchEvents();
 
 		// Then
-		Assert.Empty(viewModel.Workspaces);
+		Assert.Equal(3, sut.Workspaces.Count);
+		Assert.Same(workspace1, sut.Workspaces[0].Workspace);
+		Assert.Same(workspace2, sut.Workspaces[1].Workspace);
+		Assert.Same(workspace3, sut.Workspaces[2].Workspace);
+		Assert.False(sut.Workspaces[0].ActiveOnMonitor);
+		Assert.True(sut.Workspaces[1].ActiveOnMonitor);
+		Assert.False(sut.Workspaces[2].ActiveOnMonitor);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceRemoved_DoesNotExist(
-		IContext context,
-		IMonitor monitor,
-		IWorkspace removedWorkspace
-	)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void WorkspaceRemoved(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		IWorkspace workspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace3 = StoreTestUtils.CreateWorkspace(ctx);
 
-		// When
-		context.WorkspaceManager.WorkspaceRemoved += Raise.Event<EventHandler<WorkspaceRemovedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceRemovedEventArgs() { Workspace = removedWorkspace }
-		);
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2);
 
-		// Then
-		Assert.Single(viewModel.Workspaces);
-		Assert.Equal(workspace, viewModel.Workspaces[0].Workspace);
-	}
-
-	#region WorkspaceManager_MonitorWorkspaceChanged
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_MonitorWorkspaceChanged_Deactivate(
-		IContext context,
-		IMonitor monitor,
-		IWorkspace currentWorkspace
-	)
-	{
-		// Given
-		IWorkspace previousWorkspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
 
 		// When
-		context.Butler.MonitorWorkspaceChanged += Raise.Event<EventHandler<MonitorWorkspaceChangedEventArgs>>(
-			context.WorkspaceManager,
-			new MonitorWorkspaceChangedEventArgs()
-			{
-				Monitor = monitor,
-				PreviousWorkspace = previousWorkspace,
-				CurrentWorkspace = currentWorkspace,
-			}
-		);
+		root.WorkspaceSector.Workspaces = root.WorkspaceSector.Workspaces.Remove(workspace1.Id);
+		root.WorkspaceSector.QueueEvent(new WorkspaceRemovedEventArgs() { Workspace = workspace1 });
+		root.DispatchEvents();
 
 		// Then
-		WorkspaceModel model = viewModel.Workspaces[0];
-		Assert.False(model.ActiveOnMonitor);
+		Assert.Equal(2, sut.Workspaces.Count);
+		Assert.Same(workspace2, sut.Workspaces[0].Workspace);
+		Assert.Same(workspace3, sut.Workspaces[1].Workspace);
+		Assert.True(sut.Workspaces[0].ActiveOnMonitor);
+		Assert.False(sut.Workspaces[1].ActiveOnMonitor);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_MonitorWorkspaceChanged_Activate(
-		IContext context,
-		IMonitor monitor,
-		IWorkspace addedWorkspace
-	)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void MonitorWorkspaceChanged_WrongMonitor(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		monitor.Handle.Returns((HMONITOR)100);
+		IMonitor monitor1 = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		IMonitor monitor2 = StoreTestUtils.CreateMonitor((HMONITOR)200);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
 
-		IWorkspace workspace = context.WorkspaceManager.First();
-		context.Butler.Pantry.GetMonitorForWorkspace(workspace).Returns(monitor);
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor1, workspace1);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor2, workspace2);
 
-		// Add workspace
-		context.WorkspaceManager.WorkspaceAdded += Raise.Event<EventHandler<WorkspaceAddedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceAddedEventArgs() { Workspace = addedWorkspace }
-		);
-
-		// Verify that the correct workspace is active on the monitor
-		WorkspaceModel existingModel = viewModel.Workspaces[0];
-		WorkspaceModel addedWorkspaceModel = viewModel.Workspaces[1];
-		Assert.True(existingModel.ActiveOnMonitor);
-		Assert.False(addedWorkspaceModel.ActiveOnMonitor);
+		WorkspaceWidgetViewModel sut = new(ctx, monitor1);
 
 		// When
-		context.Butler.MonitorWorkspaceChanged += Raise.Event<EventHandler<MonitorWorkspaceChangedEventArgs>>(
-			context.WorkspaceManager,
-			new MonitorWorkspaceChangedEventArgs()
-			{
-				Monitor = monitor,
-				PreviousWorkspace = existingModel.Workspace,
-				CurrentWorkspace = addedWorkspaceModel.Workspace,
-			}
+		root.MapSector.QueueEvent(
+			new MonitorWorkspaceChangedEventArgs() { Monitor = monitor2, CurrentWorkspace = workspace2 }
 		);
+		root.DispatchEvents();
 
 		// Then
-		Assert.False(existingModel.ActiveOnMonitor);
-		Assert.True(addedWorkspaceModel.ActiveOnMonitor);
+		Assert.Equal(2, sut.Workspaces.Count);
+		Assert.Same(workspace1, sut.Workspaces[0].Workspace);
+		Assert.Same(workspace2, sut.Workspaces[1].Workspace);
+		Assert.True(sut.Workspaces[0].ActiveOnMonitor);
+		Assert.False(sut.Workspaces[1].ActiveOnMonitor);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_MonitorWorkspaceChanged_DifferentMonitor(
-		IContext context,
-		IMonitor monitor,
-		IWorkspace addedWorkspace,
-		IMonitor otherMonitor
-	)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void MonitorWorkspaceChanged_CorrectMonitor(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		monitor.Handle.Returns((HMONITOR)100);
-
-		IWorkspace workspace = context.WorkspaceManager.First();
-		context.Butler.Pantry.GetMonitorForWorkspace(workspace).Returns(monitor);
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
 
-		// Add workspace
-		context.WorkspaceManager.WorkspaceAdded += Raise.Event<EventHandler<WorkspaceAddedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceAddedEventArgs() { Workspace = addedWorkspace }
-		);
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace1);
 
-		// Verify that the correct workspace is active on the monitor
-		WorkspaceModel existingModel = viewModel.Workspaces[0];
-		WorkspaceModel addedWorkspaceModel = viewModel.Workspaces[1];
-		Assert.True(existingModel.ActiveOnMonitor);
-		Assert.False(addedWorkspaceModel.ActiveOnMonitor);
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
 
 		// When
-		context.Butler.MonitorWorkspaceChanged += Raise.Event<EventHandler<MonitorWorkspaceChangedEventArgs>>(
-			context.WorkspaceManager,
-			new MonitorWorkspaceChangedEventArgs()
-			{
-				Monitor = otherMonitor,
-				PreviousWorkspace = existingModel.Workspace,
-				CurrentWorkspace = addedWorkspaceModel.Workspace,
-			}
+		root.MapSector.QueueEvent(
+			new MonitorWorkspaceChangedEventArgs() { Monitor = monitor, CurrentWorkspace = workspace2 }
 		);
+		root.DispatchEvents();
 
 		// Then
-		Assert.True(existingModel.ActiveOnMonitor);
-		Assert.False(addedWorkspaceModel.ActiveOnMonitor);
+		Assert.Equal(2, sut.Workspaces.Count);
+		Assert.Same(workspace1, sut.Workspaces[0].Workspace);
+		Assert.Same(workspace2, sut.Workspaces[1].Workspace);
+		Assert.False(sut.Workspaces[0].ActiveOnMonitor);
+		Assert.True(sut.Workspaces[1].ActiveOnMonitor);
 	}
-	#endregion
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceRenamed_ExistingWorkspace(IContext context, IMonitor monitor)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void WorkspaceRenamed_WrongMonitor(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		IWorkspace workspace = context.WorkspaceManager.First();
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor1 = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		IMonitor monitor2 = StoreTestUtils.CreateMonitor((HMONITOR)200);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
+
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor1, workspace1);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor2, workspace2);
+
+		WorkspaceWidgetViewModel sut = new(ctx, monitor1);
+		WorkspaceModel workspaceModel = sut.Workspaces[0];
 
 		// When
-		// Then
-		Assert.PropertyChanged(
-			viewModel.Workspaces[0],
-			nameof(WorkspaceModel.Name),
+		CustomAssert.DoesNotPropertyChange(
+			h => workspaceModel.PropertyChanged += h,
+			h => workspaceModel.PropertyChanged -= h,
 			() =>
 			{
-				context.WorkspaceManager.WorkspaceRenamed += Raise.Event<EventHandler<WorkspaceRenamedEventArgs>>(
-					context.WorkspaceManager,
-					new WorkspaceRenamedEventArgs() { Workspace = workspace, PreviousName = "Old Name" }
+				root.WorkspaceSector.QueueEvent(
+					new WorkspaceRenamedEventArgs() { Workspace = workspace2, PreviousName = "Old Name" }
 				);
+				root.DispatchEvents();
 			}
 		);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	public void WorkspaceManager_WorkspaceRenamed_NonExistingWorkspace(
-		IContext context,
-		IMonitor monitor,
-		IWorkspace renamedWorkspace
-	)
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void WorkspaceRenamed_CorrectMonitor(IContext ctx, MutableRootSector root)
 	{
 		// Given
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100);
+		Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx);
+		Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx);
 
-		// Verify that property changed is not raised
+		StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2);
+		StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace1);
 
-		bool propertyChangedRaised = false;
-		WorkspaceModel model = viewModel.Workspaces[0];
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
+		WorkspaceModel workspaceModel = sut.Workspaces[0];
 
 		// When
-		model.PropertyChanged += (sender, args) => propertyChangedRaised = true;
-
-		context.WorkspaceManager.WorkspaceRenamed += Raise.Event<EventHandler<WorkspaceRenamedEventArgs>>(
-			context.WorkspaceManager,
-			new WorkspaceRenamedEventArgs() { Workspace = renamedWorkspace, PreviousName = "Old Name" }
+		Assert.PropertyChanged(
+			sut.Workspaces[0],
+			nameof(workspaceModel.Name),
+			() =>
+			{
+				root.WorkspaceSector.QueueEvent(
+					new WorkspaceRenamedEventArgs() { Workspace = workspace1, PreviousName = "Old Name" }
+				);
+				root.DispatchEvents();
+			}
 		);
-
-		// Then
-		Assert.False(propertyChangedRaised);
 	}
 
-	[Theory, AutoSubstituteData<WorkspaceWidgetViewModelCustomization>]
-	[System.Diagnostics.CodeAnalysis.SuppressMessage(
-		"Usage",
-		"NS5000:Received check.",
-		Justification = "The analyzer is wrong"
-	)]
-	public void Dispose(IContext context, IMonitor monitor)
+	[Theory, AutoSubstituteData]
+	public void Dispose(IContext ctx, IMonitor monitor)
 	{
 		// Given
-		WorkspaceWidgetViewModel viewModel = new(context, monitor);
+		WorkspaceWidgetViewModel sut = new(ctx, monitor);
 
 		// When
-		viewModel.Dispose();
+		sut.Dispose();
 
 		// Then
-		context.WorkspaceManager.Received(1).WorkspaceAdded -= Arg.Any<EventHandler<WorkspaceAddedEventArgs>>();
-		context.WorkspaceManager.Received(1).WorkspaceRemoved -= Arg.Any<EventHandler<WorkspaceRemovedEventArgs>>();
-		context.Butler.Received(1).MonitorWorkspaceChanged -= Arg.Any<EventHandler<MonitorWorkspaceChangedEventArgs>>();
-		context.WorkspaceManager.Received(1).WorkspaceRenamed -= Arg.Any<EventHandler<WorkspaceRenamedEventArgs>>();
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceAdded += Arg.Any<EventHandler<WorkspaceAddedEventArgs>>();
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceRemoved += Arg.Any<EventHandler<WorkspaceRemovedEventArgs>>();
+		ctx.Store.MapEvents.Received(1).MonitorWorkspaceChanged += Arg.Any<
+			EventHandler<MonitorWorkspaceChangedEventArgs>
+		>();
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceRenamed += Arg.Any<EventHandler<WorkspaceRenamedEventArgs>>();
+
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceAdded -= Arg.Any<EventHandler<WorkspaceAddedEventArgs>>();
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceRemoved -= Arg.Any<EventHandler<WorkspaceRemovedEventArgs>>();
+		ctx.Store.MapEvents.Received(1).MonitorWorkspaceChanged -= Arg.Any<
+			EventHandler<MonitorWorkspaceChangedEventArgs>
+		>();
+		ctx.Store.WorkspaceEvents.Received(1).WorkspaceRenamed -= Arg.Any<EventHandler<WorkspaceRenamedEventArgs>>();
 	}
 }
diff --git a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs
index 1088ed66d..512874f63 100644
--- a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs
+++ b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace Whim.Bar;
@@ -31,44 +32,40 @@ public WorkspaceWidgetViewModel(IContext context, IMonitor monitor)
 		_context = context;
 		Monitor = monitor;
 
-		_context.WorkspaceManager.WorkspaceAdded += WorkspaceManager_WorkspaceAdded;
-		_context.WorkspaceManager.WorkspaceRemoved += WorkspaceManager_WorkspaceRemoved;
-		_context.Butler.MonitorWorkspaceChanged += Butler_MonitorWorkspaceChanged;
-		_context.WorkspaceManager.WorkspaceRenamed += WorkspaceManager_WorkspaceRenamed;
+		_context.Store.WorkspaceEvents.WorkspaceAdded += WorkspaceEvents_WorkspaceAdded;
+		_context.Store.WorkspaceEvents.WorkspaceRemoved += WorkspaceEvents_WorkspaceRemoved;
+		_context.Store.MapEvents.MonitorWorkspaceChanged += MapEvents_MonitorWorkspaceChanged;
+		_context.Store.WorkspaceEvents.WorkspaceRenamed += WorkspaceEvents_WorkspaceRenamed;
 
-		// Populate the list of workspaces
-		foreach (IWorkspace workspace in _context.WorkspaceManager)
-		{
-			IMonitor? monitorForWorkspace = _context.Butler.Pantry.GetMonitorForWorkspace(workspace);
-			Workspaces.Add(new WorkspaceModel(context, this, workspace, Monitor.Handle == monitorForWorkspace?.Handle));
-		}
+		UpdateWorkspacesCollection();
 	}
 
-	private void WorkspaceManager_WorkspaceAdded(object? sender, WorkspaceEventArgs args)
+	private void UpdateWorkspacesCollection()
 	{
-		if (Workspaces.Any(model => model.Workspace.Id == args.Workspace.Id))
-		{
-			return;
-		}
+		Workspaces.Clear();
 
-		IMonitor? monitorForWorkspace = _context.Butler.Pantry.GetMonitorForWorkspace(args.Workspace);
-		Workspaces.Add(
-			new WorkspaceModel(_context, this, args.Workspace, Monitor.Handle == monitorForWorkspace?.Handle)
-		);
-	}
+		IReadOnlyList<IWorkspace> workspaces =
+			_context.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(Monitor.Handle)).ValueOrDefault ?? [];
 
-	private void WorkspaceManager_WorkspaceRemoved(object? sender, WorkspaceEventArgs args)
-	{
-		WorkspaceModel? workspaceModel = Workspaces.FirstOrDefault(model => model.Workspace.Id == args.Workspace.Id);
-		if (workspaceModel == null)
+		foreach (IWorkspace workspace in workspaces)
 		{
-			return;
-		}
+			IMonitor? monitorForWorkspace = _context
+				.Store.Pick(Pickers.PickMonitorByWorkspace(workspace.Id))
+				.ValueOrDefault;
 
-		Workspaces.Remove(workspaceModel);
+			Workspaces.Add(
+				new WorkspaceModel(_context, this, workspace, Monitor.Handle == monitorForWorkspace?.Handle)
+			);
+		}
 	}
 
-	private void Butler_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChangedEventArgs args)
+	private void WorkspaceEvents_WorkspaceAdded(object? sender, WorkspaceEventArgs args) =>
+		UpdateWorkspacesCollection();
+
+	private void WorkspaceEvents_WorkspaceRemoved(object? sender, WorkspaceEventArgs args) =>
+		UpdateWorkspacesCollection();
+
+	private void MapEvents_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChangedEventArgs args)
 	{
 		if (args.Monitor.Handle != Monitor.Handle)
 		{
@@ -81,7 +78,7 @@ private void Butler_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChan
 		}
 	}
 
-	private void WorkspaceManager_WorkspaceRenamed(object? sender, WorkspaceRenamedEventArgs e)
+	private void WorkspaceEvents_WorkspaceRenamed(object? sender, WorkspaceRenamedEventArgs e)
 	{
 		WorkspaceModel? workspace = Workspaces.FirstOrDefault(m => m.Workspace.Id == e.Workspace.Id);
 		if (workspace == null)
@@ -100,10 +97,10 @@ protected virtual void Dispose(bool disposing)
 			if (disposing)
 			{
 				// dispose managed state (managed objects)
-				_context.WorkspaceManager.WorkspaceAdded -= WorkspaceManager_WorkspaceAdded;
-				_context.WorkspaceManager.WorkspaceRemoved -= WorkspaceManager_WorkspaceRemoved;
-				_context.Butler.MonitorWorkspaceChanged -= Butler_MonitorWorkspaceChanged;
-				_context.WorkspaceManager.WorkspaceRenamed -= WorkspaceManager_WorkspaceRenamed;
+				_context.Store.WorkspaceEvents.WorkspaceAdded -= WorkspaceEvents_WorkspaceAdded;
+				_context.Store.WorkspaceEvents.WorkspaceRemoved -= WorkspaceEvents_WorkspaceRemoved;
+				_context.Store.MapEvents.MonitorWorkspaceChanged -= MapEvents_MonitorWorkspaceChanged;
+				_context.Store.WorkspaceEvents.WorkspaceRenamed -= WorkspaceEvents_WorkspaceRenamed;
 			}
 
 			// free unmanaged resources (unmanaged objects) and override finalizer
diff --git a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs
index fadbd6c76..ccef7cb0d 100644
--- a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs
+++ b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs
@@ -422,7 +422,7 @@ internal void PickExplicitStickyMonitorIndicesByWorkspace_Success(IContext ctx,
 	}
 }
 
-public class PickValidMonitorForWorkspaceTests
+public class PickValidMonitorByWorkspaceTests
 {
 	[Theory, AutoSubstituteData<StoreCustomization>]
 	internal void TargetMonitorIsValid(IContext ctx, MutableRootSector root)
@@ -438,7 +438,7 @@ internal void TargetMonitorIsValid(IContext ctx, MutableRootSector root)
 		);
 
 		// When we get the monitor
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)1));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)1));
 
 		// Then we get the target monitor
 		Assert.True(result.IsSuccessful);
@@ -464,7 +464,7 @@ internal void FallbackToLastMonitor(IContext ctx, MutableRootSector root)
 		);
 
 		// When we try to use monitor 1
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)1));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)1));
 
 		// Then we get monitor 2 since it's the last valid monitor used
 		Assert.True(result.IsSuccessful);
@@ -490,7 +490,7 @@ internal void FallbackToFirstAvailableMonitor(IContext ctx, MutableRootSector ro
 		);
 
 		// When we try to use monitor 2
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)2));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)2));
 
 		// Then we get monitor 1 as it's the first valid monitor
 		Assert.True(result.IsSuccessful);
@@ -513,7 +513,7 @@ internal void NoValidMonitors(IContext ctx, MutableRootSector root)
 		);
 
 		// When we try to get a valid monitor
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id));
 
 		// Then we get an error since there are no valid monitors and no fallback monitors
 		Assert.False(result.IsSuccessful);
@@ -531,7 +531,7 @@ internal void UseActiveMonitorWhenNoTargetSpecified(IContext ctx, MutableRootSec
 		root.MonitorSector.ActiveMonitorHandle = (HMONITOR)2;
 
 		// When we don't specify a target monitor
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id));
 
 		// Then we get the active monitor
 		Assert.True(result.IsSuccessful);
@@ -545,10 +545,110 @@ internal void WorkspaceDoesNotExist(IContext ctx)
 		Guid nonExistentWorkspaceId = Guid.NewGuid();
 
 		// When we try to get a valid monitor
-		var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(nonExistentWorkspaceId));
+		var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(nonExistentWorkspaceId));
 
 		// Then we get an error
 		Assert.False(result.IsSuccessful);
 		Assert.Contains("not found", result.Error!.Message);
 	}
 }
+
+public class PickStickyWorkspacesByMonitorTests
+{
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void MonitorNotFound(IContext ctx)
+	{
+		// Given we have a monitor that doesn't exist
+		HMONITOR nonExistentHandle = (HMONITOR)999;
+
+		// When we try to get workspaces for the monitor
+		var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(nonExistentHandle));
+
+		// Then we get an error
+		Assert.False(result.IsSuccessful);
+		Assert.Contains("not found", result.Error!.Message);
+	}
+
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void GetStickyWorkspaces(IContext ctx, MutableRootSector root)
+	{
+		// Given we have three workspaces, two sticky to monitor 1
+		var workspace1 = CreateWorkspace(ctx);
+		var workspace2 = CreateWorkspace(ctx);
+		var workspace3 = CreateWorkspace(ctx);
+		AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3);
+
+		IMonitor monitor = CreateMonitor((HMONITOR)1);
+		AddMonitorsToManager(ctx, root, monitor);
+
+		root.MapSector.StickyWorkspaceMonitorIndexMap = root
+			.MapSector.StickyWorkspaceMonitorIndexMap.SetItem(workspace1.Id, [0, 1])
+			.SetItem(workspace2.Id, [0, 4]);
+
+		// When we get the workspaces for the monitor
+		var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle));
+
+		// Then we get the sticky workspaces in workspace order
+		Assert.True(result.IsSuccessful);
+		Assert.Equal(3, result.Value.Count); // All workspaces (2 sticky + 1 non-sticky)
+		Assert.Equal(workspace1, result.Value[0]); // First in workspace order
+		Assert.Equal(workspace2, result.Value[1]); // Second in workspace order
+		Assert.Equal(workspace3, result.Value[2]); // Non-sticky workspace
+	}
+
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void GetAllWorkspacesWhenNoneSticky(IContext ctx, MutableRootSector root)
+	{
+		// Given we have three workspaces but none are sticky
+		var workspace1 = CreateWorkspace(ctx);
+		var workspace2 = CreateWorkspace(ctx);
+		var workspace3 = CreateWorkspace(ctx);
+		AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3);
+
+		IMonitor monitor = CreateMonitor((HMONITOR)1);
+		AddMonitorsToManager(ctx, root, monitor);
+
+		// When we get the workspaces for the monitor
+		var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle));
+
+		// Then we get all workspaces in workspace order
+		Assert.True(result.IsSuccessful);
+		Assert.Equal(3, result.Value.Count);
+		Assert.Equal(workspace1, result.Value[0]);
+		Assert.Equal(workspace2, result.Value[1]);
+		Assert.Equal(workspace3, result.Value[2]);
+	}
+
+	[Theory, AutoSubstituteData<StoreCustomization>]
+	internal void InvalidMonitorIndicesAreIgnored(IContext ctx, MutableRootSector root)
+	{
+		// Given we have a workspace with both valid and invalid monitor indices
+		var workspace = CreateWorkspace(ctx);
+		AddWorkspacesToManager(ctx, root, workspace);
+
+		IMonitor monitor1 = CreateMonitor((HMONITOR)1);
+		IMonitor monitor2 = CreateMonitor((HMONITOR)2);
+		AddMonitorsToManager(ctx, root, monitor1, monitor2); // Only indices 0 and 1 are valid
+
+		root.MapSector.StickyWorkspaceMonitorIndexMap = root.MapSector.StickyWorkspaceMonitorIndexMap.SetItem(
+			workspace.Id,
+			[-1, 0, 1, 2] // Invalid indices: -1 and 2
+		);
+
+		// When we get the workspaces for monitor1 (index 0)
+		var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor1.Handle));
+
+		// Then the workspace is included because index 0 is valid, ignoring invalid indices
+		Assert.True(result.IsSuccessful);
+		Assert.Single(result.Value);
+		Assert.Equal(workspace, result.Value[0]);
+
+		// And when we get workspaces for monitor2 (index 1)
+		result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor2.Handle));
+
+		// Then the workspace is included because index 1 is valid, ignoring invalid indices
+		Assert.True(result.IsSuccessful);
+		Assert.Single(result.Value);
+		Assert.Equal(workspace, result.Value[0]);
+	}
+}
diff --git a/src/Whim/Store/MapSector/MapPickers.cs b/src/Whim/Store/MapSector/MapPickers.cs
index f82fe7c61..bf9392cf4 100644
--- a/src/Whim/Store/MapSector/MapPickers.cs
+++ b/src/Whim/Store/MapSector/MapPickers.cs
@@ -300,10 +300,17 @@ out ImmutableArray<int> monitorIndices
 	/// </item>
 	/// </list>
 	/// </summary>
-	/// <param name="workspaceId"></param>
-	/// <param name="monitorHandle"></param>
-	/// <returns></returns>
-	public static PurePicker<Result<HMONITOR>> PickValidMonitorForWorkspace(
+	/// <param name="workspaceId">
+	/// The ID of the workspace to get the monitor for.
+	/// </param>
+	/// <param name="monitorHandle">
+	/// The preferred monitor to use. If not provided, the last monitor the workspace was activated on will next be tried.
+	/// </param>
+	/// <returns>
+	/// The first valid monitor for the workspace, when passed to <see cref="IStore.Pick{TResult}(PurePicker{TResult})"/>.
+	/// If the workspace can't be found, then an error is returned.
+	/// </returns>
+	public static PurePicker<Result<HMONITOR>> PickValidMonitorByWorkspace(
 		WorkspaceId workspaceId,
 		HMONITOR monitorHandle = default
 	) =>
@@ -359,4 +366,80 @@ public static PurePicker<Result<HMONITOR>> PickValidMonitorForWorkspace(
 
 			return Result.FromException<HMONITOR>(StoreExceptions.NoValidMonitorForWorkspace(workspaceId));
 		};
+
+	/// <summary>
+	/// Retrieves the workspaces which can be shown on the given monitor.
+	/// </summary>
+	/// <param name="monitorHandle">
+	/// The handle of the monitor to get the workspaces for.
+	/// </param>
+	/// <returns>
+	/// The workspaces which can be shown on the monitor, when passed to <see cref="IStore.Pick{TResult}(PurePicker{TResult})"/>.
+	/// If the monitor can't be found, then an error is returned.
+	/// </returns>
+	public static PurePicker<Result<IReadOnlyList<IWorkspace>>> PickStickyWorkspacesByMonitor(HMONITOR monitorHandle) =>
+		rootSector =>
+		{
+			IMapSector mapSector = rootSector.MapSector;
+			IWorkspaceSector workspaceSector = rootSector.WorkspaceSector;
+			IMonitorSector monitorSector = rootSector.MonitorSector;
+
+			// Verify the monitor exists.
+			Result<IMonitor> monitorResult = PickMonitorByHandle(monitorHandle)(rootSector);
+			if (!monitorResult.IsSuccessful)
+			{
+				return Result.FromException<IReadOnlyList<IWorkspace>>(monitorResult.Error!);
+			}
+
+			// Get the index of the monitor.
+			IMonitor monitor = monitorResult.Value;
+			ImmutableArray<IMonitor> monitors = monitorSector.Monitors;
+			int monitorIndex = monitors.IndexOf(monitor);
+
+			// Get the workspaces which can be shown on the monitor.
+			List<WorkspaceId> processedWorkspaces = [];
+			List<WorkspaceId> unsortedWorkspaces = [];
+
+			foreach (
+				(
+					WorkspaceId workspaceId,
+					ImmutableArray<int> monitorIndices
+				) in mapSector.StickyWorkspaceMonitorIndexMap
+			)
+			{
+				// If the workspace is sticky to the monitor, or it's orphaned.
+				if (
+					monitorIndices.Contains(monitorIndex)
+					|| monitorIndices.All(index => index < 0 || index >= monitors.Length)
+				)
+				{
+					unsortedWorkspaces.Add(workspaceId);
+				}
+
+				processedWorkspaces.Add(workspaceId);
+			}
+
+			// Get the workspaces which can be shown on any monitor.
+			foreach (WorkspaceId workspaceId in workspaceSector.Workspaces.Keys)
+			{
+				if (processedWorkspaces.Contains(workspaceId))
+				{
+					continue;
+				}
+
+				unsortedWorkspaces.Add(workspaceId);
+			}
+
+			// Crude sorting.
+			List<IWorkspace> sortedWorkspaces = [];
+			foreach (WorkspaceId workspaceId in workspaceSector.WorkspaceOrder)
+			{
+				if (unsortedWorkspaces.Contains(workspaceId))
+				{
+					sortedWorkspaces.Add(workspaceSector.Workspaces[workspaceId]);
+				}
+			}
+
+			return sortedWorkspaces;
+		};
 }
diff --git a/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs b/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs
index 00b98978a..747f16445 100644
--- a/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs
+++ b/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs
@@ -31,7 +31,7 @@ internal override Result<Unit> Execute(IContext ctx, IInternalContext internalCt
 		}
 
 		Result<HMONITOR> targetMonitorHandleResult = ctx.Store.Pick(
-			PickValidMonitorForWorkspace(workspace.Id, MonitorHandle)
+			PickValidMonitorByWorkspace(workspace.Id, MonitorHandle)
 		);
 		if (!targetMonitorHandleResult.TryGet(out HMONITOR targetMonitorHandle))
 		{