diff --git a/Tools/OutWit.Database.Studio.Tests/ViewModels/TheStatusLineTakesBackWhatIsNoLongerTrueTests.cs b/Tools/OutWit.Database.Studio.Tests/ViewModels/TheStatusLineTakesBackWhatIsNoLongerTrueTests.cs
new file mode 100644
index 0000000..974f10b
--- /dev/null
+++ b/Tools/OutWit.Database.Studio.Tests/ViewModels/TheStatusLineTakesBackWhatIsNoLongerTrueTests.cs
@@ -0,0 +1,141 @@
+using OutWit.Database.Studio.Tests.Helpers;
+using OutWit.Database.Studio.ViewModels.Tabs;
+
+namespace OutWit.Database.Studio.Tests.ViewModels;
+
+///
+/// The status line may keep saying what HAPPENED; it may not keep saying what IS.
+///
+///
+///
+/// Measured in the running application on 2026-08-19, with no editor open anywhere: the line
+/// read Editing table: Products. The tab had been opened and closed again, and the sentence
+/// stayed behind it.
+///
+///
+/// Most of what this line carries is an event - «executed in 9 ms», «loaded 28 rows» - and an event
+/// stays true after it happens, which is why the line is not simply cleared when anything changes.
+/// «Editing table X» is not an event. It is a state, it belongs to the tab, and it ends when the tab
+/// does.
+///
+///
+[TestFixture]
+public class TheStatusLineTakesBackWhatIsNoLongerTrueTests
+{
+ #region Fields
+
+ private StudioFixture m_studio = null!;
+
+ #endregion
+
+ #region Setup
+
+ [SetUp]
+ public async Task SetUp()
+ {
+ m_studio = await StudioFixture.CreateAsync();
+
+ await m_studio.Explorer.RefreshAsync();
+ }
+
+ [TearDown]
+ public async Task TearDown()
+ {
+ await m_studio.DisposeAsync();
+ }
+
+ #endregion
+
+ #region The rule
+
+ [Test]
+ public async Task ClosingTheEditorTakesEditingOffTheLineTest()
+ {
+ var ready = m_studio.App.Localization["Status.Ready"];
+
+ var tab = await OpenTheEditorAsync();
+
+ Assume.That(m_studio.MainWindow.StatusText, Does.Contain("Customers"),
+ "the line says which table is being edited");
+
+ await CloseAsync(tab);
+
+ Assert.That(m_studio.MainWindow.StatusText, Is.EqualTo(ready),
+ "nothing is being edited, so the line does not say that anything is - and what it "
+ + "falls back to is the same sentence the window starts with, not whatever happened "
+ + "to be there before the editor opened");
+ }
+
+ ///
+ /// The other direction, and the reason the owner is remembered rather than the line being
+ /// cleared: a message that arrived AFTER the editor opened belongs to whoever sent it, and
+ /// closing the editor must not take it away.
+ ///
+ [Test]
+ public async Task ClosingTheEditorLeavesSomebodyElsesMessageAloneTest()
+ {
+ var tab = await OpenTheEditorAsync();
+
+ const string SINCE_THEN = "Something else happened";
+
+ m_studio.MainWindow.StatusText = SINCE_THEN;
+
+ await CloseAsync(tab);
+
+ Assert.That(m_studio.MainWindow.StatusText, Is.EqualTo(SINCE_THEN),
+ "the editor only takes back what is still its own sentence");
+ }
+
+ ///
+ /// And an EVENT stays. This is the case that stops the rule above from becoming "clear the line
+ /// whenever a tab closes", which would throw away the answer to the last thing the user did.
+ ///
+ [Test]
+ public async Task ClosingAQueryTabLeavesWhatItReportedOnTheLineTest()
+ {
+ var query = m_studio.Workspace.OpenQueryTab("SELECT 1", "one", m_studio.Database);
+
+ const string HAPPENED = "Query executed successfully in 9 ms";
+
+ m_studio.MainWindow.StatusText = HAPPENED;
+
+ await CloseAsync(query);
+
+ Assert.That(m_studio.MainWindow.StatusText, Is.EqualTo(HAPPENED),
+ "what happened stays true after the tab that did it has gone");
+ }
+
+ #endregion
+
+ #region Tools
+
+ private async Task OpenTheEditorAsync()
+ {
+ var tab = await m_studio.Workspace.OpenTableEditTabAsync(m_studio.Database, "Customers");
+
+ // Through the explorer, because that is where the sentence is written.
+ m_studio.Explorer.SelectedNode = m_studio.Explorer.Nodes
+ .SelectMany(Flatten)
+ .First(node => node.NodeType == Studio.Models.DatabaseNodeType.Table
+ && node.Name == "Customers");
+
+ await m_studio.Explorer.OpenWhatItIsAsync();
+
+ return tab;
+ }
+
+ private async Task CloseAsync(WorkspaceTabViewModel tab)
+ {
+ await StudioFixture.PressAsync(m_studio.Workspace.CloseTabCommand, tab);
+ }
+
+ private static IEnumerable Flatten(Studio.Models.DatabaseNode node)
+ {
+ yield return node;
+
+ foreach (var child in node.Children.SelectMany(Flatten))
+ yield return child;
+ }
+
+ #endregion
+}
diff --git a/Tools/OutWit.Database.Studio/ViewModels/DatabaseExplorerViewModel.cs b/Tools/OutWit.Database.Studio/ViewModels/DatabaseExplorerViewModel.cs
index a4d6e12..1594d04 100644
--- a/Tools/OutWit.Database.Studio/ViewModels/DatabaseExplorerViewModel.cs
+++ b/Tools/OutWit.Database.Studio/ViewModels/DatabaseExplorerViewModel.cs
@@ -249,8 +249,10 @@ private async Task EditDataAsync()
// The flag is the tab's own IsReadOnly, which is what CanAddRow, CanDeleteRow and CanCommit
// are computed from; the connection's read-only mode is a DIFFERENT thing that WorkspaceTab
// holds under the same name.
- ApplicationVm.MainWindowVm.StatusText = Localization.Format(
- tab.IsReadOnly ? "Explorer.Viewing" : "Explorer.Editing", tableName);
+ // The TAB owns this line: «editing Products» is true while the editor is open and false the
+ // moment it is closed, which is not something an event-shaped message has to think about.
+ ApplicationVm.MainWindowVm.SayThisWhile(tab, Localization.Format(
+ tab.IsReadOnly ? "Explorer.Viewing" : "Explorer.Editing", tableName));
Logger.LogInformation("Edit data for table {TableName} in {Connection}", tableName, session.DisplayName);
}
diff --git a/Tools/OutWit.Database.Studio/ViewModels/MainWindowViewModel.cs b/Tools/OutWit.Database.Studio/ViewModels/MainWindowViewModel.cs
index def8079..e78612b 100644
--- a/Tools/OutWit.Database.Studio/ViewModels/MainWindowViewModel.cs
+++ b/Tools/OutWit.Database.Studio/ViewModels/MainWindowViewModel.cs
@@ -16,6 +16,14 @@ namespace OutWit.Database.Studio.ViewModels;
///
public sealed class MainWindowViewModel : ViewModelBase
{
+ #region Fields
+
+ /// Who said what is on the status line, when what it says is a state rather than an event.
+ private object? m_statusOwner;
+ private string? m_statusSaid;
+
+ #endregion
+
#region Constructors
public MainWindowViewModel(ApplicationViewModel applicationVm)
@@ -624,6 +632,43 @@ private void OnNotificationsChanged(object? sender, EventArgs e)
[Notify]
public string StatusText { get; set; } = null!;
+ ///
+ /// Says something that is true only WHILE is there.
+ ///
+ ///
+ ///
+ /// Most of what this line carries is an EVENT - «executed in 9 ms», «loaded 28 rows» - and an
+ /// event stays true after it happens. «Editing table Products» is not an event, it is a state,
+ /// and it was still on the line after the tab it described had been closed: measured on
+ /// 2026-08-19 with no editor open at all.
+ ///
+ ///
+ /// The owner is compared by reference AND the text is compared to what was set, so a later
+ /// message from anywhere else is never taken away by the owner closing afterwards.
+ ///
+ ///
+ public void SayThisWhile(object owner, string text)
+ {
+ StatusText = text;
+
+ m_statusOwner = owner;
+ m_statusSaid = text;
+ }
+
+ ///
+ /// Takes back what said, if it is still on the line.
+ ///
+ public void ForgetWhatWasSaidBy(object owner)
+ {
+ if (!ReferenceEquals(m_statusOwner, owner) || StatusText != m_statusSaid)
+ return;
+
+ m_statusOwner = null;
+ m_statusSaid = null;
+
+ StatusText = Localization["Status.Ready"];
+ }
+
[Notify]
public bool IsLoading { get; set; }
diff --git a/Tools/OutWit.Database.Studio/ViewModels/WorkspaceTabsViewModel.cs b/Tools/OutWit.Database.Studio/ViewModels/WorkspaceTabsViewModel.cs
index cf673d1..73ace7d 100644
--- a/Tools/OutWit.Database.Studio/ViewModels/WorkspaceTabsViewModel.cs
+++ b/Tools/OutWit.Database.Studio/ViewModels/WorkspaceTabsViewModel.cs
@@ -339,6 +339,9 @@ private async Task CloseTabAsync(WorkspaceTabViewModel? tab)
SelectedTab = Tabs[index];
}
+ // A tab that said something about itself on the status line takes it back with it.
+ ApplicationVm.MainWindowVm.ForgetWhatWasSaidBy(tab);
+
Logger.LogInformation("Closed tab: {Title}", tab.Title);
}
@@ -363,6 +366,8 @@ private async Task CloseAllTabsAsync()
tab.PropertyChanged -= OnTabPropertyChanged;
tab.OnClosed();
Tabs.Remove(tab);
+
+ ApplicationVm.MainWindowVm.ForgetWhatWasSaidBy(tab);
}
SelectedTab ??= Tabs.LastOrDefault();
@@ -385,6 +390,8 @@ private async Task CloseOtherTabsAsync(WorkspaceTabViewModel? keepTab)
tab.PropertyChanged -= OnTabPropertyChanged;
tab.OnClosed();
Tabs.Remove(tab);
+
+ ApplicationVm.MainWindowVm.ForgetWhatWasSaidBy(tab);
}
SelectedTab = keepTab;