Draw what authenticated, and over what, on the session status bar
ci / build and test (push) Failing after 2m20s
ci / desktop nightly (push) Skipped
ci / api image (push) Skipped
ci / android head (push) Successful in 3m27s

The v5b design's own row: the negotiated cipher, then the host key's algorithm
and the name of the key or credential that authenticated, as one mono run
beside CONNECTED — on both surfaces, off MainWindowViewModel's surface-aware
SessionCipher and SessionIdentityText, the same shape SessionAddress set.

The identity's name comes out of TryBuildAuthentication, the one resolution
point that always had it in scope and always threw it away; it rides
HostAuthentication to the tab and to the SFTP connect alike. Three deviations,
recorded in the gaps doc: the algorithm prints as negotiated rather than
shortened, the run is plain text because no pin-details modal exists for an
open session, and a typed password shows the algorithm alone — there is no
item behind the dot. A dead terminal tab keeps its facts for the scrollback
still on screen; an SFTP disconnect, with no scrollback, clears them.
This commit is contained in:
2026-08-08 20:54:56 +02:00
parent 8209f15741
commit 9d5ff9f23a
8 changed files with 330 additions and 47 deletions
@@ -6221,7 +6221,9 @@ public sealed class ShellFlowTests : IAsyncLifetime
/// <remarks>
/// The status bar's facts, read off the selected terminal tab. <see cref="MainWindowViewModel.SessionElapsedText"/>
/// is real, not fabricated: <c>StartedAt</c> is set from the shell's own clock at the moment the session
/// opens, and this reads it back through the same clock.
/// opens, and this reads it back through the same clock. The cipher and host-key algorithm are the
/// fake's own, and the identity text is the bound key's label — <c>ConnectedHostWithAPinAsync</c> binds
/// "deploy" — composed with the real (unshortened) algorithm string.
/// </remarks>
[Fact]
public async Task SessionFacts_ReflectTheSelectedTerminalTab()
@@ -6231,6 +6233,9 @@ public sealed class ShellFlowTests : IAsyncLifetime
shell.IsSessionConnected.ShouldBeTrue();
shell.SessionAddress.ShouldBe(shell.Tabs[0].Address);
shell.SessionElapsedText.ShouldNotBeNull().ShouldStartWith("session ");
shell.SessionCipher.ShouldBe("aes256-gcm@openssh.com");
shell.SessionHostKeyAlgorithm.ShouldBe("ssh-ed25519");
shell.SessionIdentityText.ShouldBe("ssh-ed25519 · deploy");
}
/// <remarks>
@@ -6245,6 +6250,55 @@ public sealed class ShellFlowTests : IAsyncLifetime
shell.IsSessionConnected.ShouldBeFalse();
shell.SessionAddress.ShouldBeNull();
shell.SessionElapsedText.ShouldBeNull();
shell.SessionCipher.ShouldBeNull();
shell.SessionHostKeyAlgorithm.ShouldBeNull();
shell.SessionIdentityText.ShouldBeNull();
}
/// <remarks>
/// A typed password has nothing filed in the keychain to name, so the status bar's identity run is the
/// host-key algorithm alone — no " · " and nothing after it, which is what
/// <see cref="MainWindowViewModel.SessionIdentityText"/>'s own remark promises rather than a placeholder
/// standing in for the item that was never there.
/// </remarks>
[Fact]
public async Task SessionFacts_ATypedPasswordSession_ShowsTheHostKeyAlgorithmAloneWithNoIdentity()
{
var vault = await ReadyToConnectAsync();
await using var renderer = await FakeRenderer.AttachAsync(workspace, Token);
vault.ManualTarget = "deploy@build.internal:2222";
vault.ManualPassword = "hunter2";
await vault.ConnectManuallyCommand.ExecuteAsync(null);
shell.IsSessionConnected.ShouldBeTrue();
shell.SessionHostKeyAlgorithm.ShouldBe("ssh-ed25519");
shell.SessionIdentityText.ShouldBe("ssh-ed25519", "a typed password names no keychain item");
}
/// <remarks>
/// The same facts on the other surface, and one asymmetry worth pinning: the SFTP session is its own
/// login through the same resolution ladder, so the status bar names that session's cipher, host key and
/// identity — and stops naming them on disconnect, where a terminal tab keeps its facts for the
/// scrollback still on screen (see <c>TerminalTabViewModel.Cipher</c>'s remark).
/// </remarks>
[Fact]
public async Task SessionFacts_FollowTheSftpSurfacesOwnSession()
{
await ConnectedHostWithAPinAsync();
await shell.OpenPinnedPathCommand.ExecuteAsync("/var/www/app");
shell.IsTransfersShowing.ShouldBeTrue();
shell.SessionCipher.ShouldBe("aes256-gcm@openssh.com");
shell.SessionIdentityText.ShouldBe("ssh-ed25519 · deploy");
await shell.Transfers.DisconnectCommand.ExecuteAsync(null);
shell.SessionCipher.ShouldBeNull("the SFTP surface has no scrollback for a dead session's facts to describe");
shell.SessionIdentityText.ShouldBeNull();
}
[Fact]