diff --git a/Source/Cli/Commands/Chronicle/Workbench/views/ReadModelsView.cs b/Source/Cli/Commands/Chronicle/Workbench/views/ReadModelsView.cs
index b304829..2a14b83 100644
--- a/Source/Cli/Commands/Chronicle/Workbench/views/ReadModelsView.cs
+++ b/Source/Cli/Commands/Chronicle/Workbench/views/ReadModelsView.cs
@@ -81,17 +81,42 @@ public void OpenDetailOverlay(WorkbenchReadModel rm)
$"[{mut}]Queryable[/] [{queryableColor}]{(rm.IsQueryable ? "Yes" : "No")}[/]",
$"[{mut}]Identifier[/] {rm.Identifier}");
- var instancesContent = BuildInstancesContent(rm);
+ // Open immediately with a placeholder for the Instances tab, then fetch off the UI thread so
+ // activating a row never blocks (or deadlocks) the render loop. The fetched content is pushed
+ // back into the tab editor on the UI thread once it arrives.
+ const string instancesTab = "Instances";
+ var loadingContent = OnFetchInstances is null
+ ? $"[{mut}](No instance loader configured)[/]"
+ : $"[{mut}]Loading instances…[/]";
List<(string TabName, string Content)> tabs =
[
("Info", infoContent),
- ("Instances", instancesContent)
+ (instancesTab, loadingContent)
];
var overlay = new DetailOverlayWindow();
var window = overlay.Build(_windowSystem, $" {rm.ContainerName} ", tabs, []);
_windowSystem.AddWindow(window, activateWindow: true);
+
+ if (OnFetchInstances is null)
+ {
+ return;
+ }
+
+ var windowSystem = _windowSystem;
+ _ = Task.Run(async () =>
+ {
+ var content = await FetchInstancesContentAsync(rm).ConfigureAwait(false);
+ windowSystem.EnqueueOnUIThread(() =>
+ {
+ if (overlay.TabEditors.TryGetValue(instancesTab, out var editor))
+ {
+ // The overlay strips markup to plain text for its read-only editors.
+ editor.SetContent(Markup.Remove(content));
+ }
+ });
+ });
}
///
@@ -154,7 +179,7 @@ protected override IEnumerable GetCompletions(string input) =>
///
protected override void OnRowActivated(WorkbenchReadModel item) => OpenDetailOverlay(item);
- string BuildInstancesContent(WorkbenchReadModel rm)
+ async Task FetchInstancesContentAsync(WorkbenchReadModel rm)
{
var mut = WorkbenchColors.Muted.ToMarkup();
var dan = WorkbenchColors.Danger.ToMarkup();
@@ -166,8 +191,8 @@ string BuildInstancesContent(WorkbenchReadModel rm)
try
{
- var data = OnFetchInstances(rm.ContainerName, CancellationToken.None)
- .GetAwaiter().GetResult();
+ var data = await OnFetchInstances(rm.ContainerName, CancellationToken.None)
+ .ConfigureAwait(false);
if (data.ReadModelInstancesError is not null)
{
diff --git a/Source/Cli/Commands/Chronicle/Workbench/windows/DetailOverlayWindow.cs b/Source/Cli/Commands/Chronicle/Workbench/windows/DetailOverlayWindow.cs
index f0c7610..4c09edb 100644
--- a/Source/Cli/Commands/Chronicle/Workbench/windows/DetailOverlayWindow.cs
+++ b/Source/Cli/Commands/Chronicle/Workbench/windows/DetailOverlayWindow.cs
@@ -12,8 +12,15 @@ namespace Cratis.Cli.Commands.Chronicle.Workbench;
///
public class DetailOverlayWindow
{
+ readonly Dictionary _tabEditors = [];
Window? _window;
+ ///
+ /// Gets the read-only editors backing each tab, keyed by tab name. Lets callers update a tab's
+ /// content after (for example, when a tab is populated by an async fetch).
+ ///
+ public IReadOnlyDictionary TabEditors => _tabEditors;
+
///
/// Builds a detail overlay window with the specified title, tabbed content, and action buttons.
///
@@ -49,6 +56,7 @@ public Window Build(
.Build();
tabBuilder.AddTab(tabName, editor);
+ _tabEditors[tabName] = editor;
}
var tabControl = tabBuilder.Fill().Build();