fix(ui): show a banner when a ConPTY session dies after startup

InteractiveTerminalViewModel.HasExitedAfterStartup now covers every
post-startup death that ExitedAfterStartup alone missed (crash, clean
exit, quiet exit inside the grace window) -- previously the tile just
stayed looking alive/thinking with zero signal. ConPtyPaneView binds a
new banner to it (message + Retry + Close); CanRetry simplifies to
"any HasExited state" since the same descriptor factory can relaunch
either kind of death. HandlerRestartCoordinator's worker-side
auto-restart path is untouched.
This commit is contained in:
mika kuns
2026-09-10 13:32:19 +02:00
parent 2b0562b2ed
commit 256bea5ffa
7 changed files with 104 additions and 5 deletions
@@ -151,6 +151,30 @@ public class ConPtyPaneViewModelTests
Assert.True(pane.RetryCommand.CanExecute(null));
}
// ── HasExitedAfterStartup banner (fix: a mid-run death used to leave a silent dead tile — no
// StartError, no signal at all) ─────────────────────────────────────────────────────────
[Fact]
public void RetryCommand_Enabled_AfterDeathAfterStartup()
{
using var pane = NewTaskPane(NeverCompletes);
DieAfterRunning(pane, exitCode: 1);
Assert.True(pane.Terminal.HasExitedAfterStartup);
Assert.True(pane.RetryCommand.CanExecute(null));
}
[Fact]
public void ExitedBannerText_IncludesExitCode()
{
using var pane = NewTaskPane(NeverCompletes);
DieAfterRunning(pane, exitCode: 42);
Assert.Contains("42", pane.ExitedBannerText);
}
// ── SessionDiedAfterStartup (feeds the worker's usage-limit auto-restart) ────────────────────
[Fact]
@@ -36,6 +36,7 @@ public class InteractiveTerminalViewModelTests
Assert.True(terminal.HasExited);
Assert.Null(terminal.StartError);
Assert.True(terminal.HasExitedAfterStartup); // no StartError banner, so the host needs this signal instead
}
[Fact]
@@ -48,6 +49,30 @@ public class InteractiveTerminalViewModelTests
Assert.True(terminal.HasExited);
Assert.Null(terminal.StartError);
Assert.True(terminal.HasExitedAfterStartup);
}
// ── HasExitedAfterStartup (drives the host's "session ended" banner — every death that
// ISN'T already covered by StartError) ─────────────────────────────────────────────────
[Fact]
public void FreshTerminal_HasExitedAfterStartupIsFalse()
{
using var terminal = new InteractiveTerminalViewModel(() => LaunchedAt);
Assert.False(terminal.HasExitedAfterStartup);
}
[Fact]
public void ProcessExitsNonzero_WithinGraceWindow_HasExitedAfterStartupIsFalse()
{
using var terminal = new InteractiveTerminalViewModel(() => LaunchedAt);
terminal.IsRunning = true;
terminal.OnSessionProcessExited(null, 1); // startup failure — StartError covers it instead
Assert.NotNull(terminal.StartError);
Assert.False(terminal.HasExitedAfterStartup);
}
// ── ExitedAfterStartup (a session that ran, then broke -- used to be a silent dead tile;