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
@@ -8,36 +8,56 @@
<!--
── v5b's session shell status bar ───────────────────────────────────────────────────────────────────────
37px, DeepChrome, a 1px top border — the foot of the bordered container both the terminal and the SFTP
surface share. The design's own row also carries a negotiated cipher, a host-key algorithm and a
key/credential name; none of the three is reachable from this shell today — see the wave B report this
control's history carries, and design-notes/v5b-fidelity-notes.md's own decided deviation — so none of
them is drawn, omitted rather than faked, per this project's honesty rule.
surface share. The design's own row also carries a negotiated cipher and a host-key-plus-identity run
(`ed25519 · acme-deploy-key`, clickable to a pin-details modal); both are now real and bound, plumbed all
the way from SSH.NET through TerminalWorkspace.GetSessionFacts and TransfersViewModel to
MainWindowViewModel's own surface-aware properties — see SessionCipher, SessionHostKeyAlgorithm,
SessionIdentityLabel and the composed SessionIdentityText.
Three honest deviations from the mock. The algorithm prints exactly as negotiated — "ssh-ed25519", not
the design's shortened "ed25519" — because trimming it would be a cosmetic edit to a string this client
did not choose. The run is plain text, not a button: this application has no pin-details modal for a
session that is already open, and drawing a click target for a screen that does not exist would be the
fabrication this project's honesty rule forbids, not an honest omission of one. And a typed-password
session — nothing filed in the keychain to name — shows the algorithm alone, with no " · " after it,
because there is no item behind the dot.
What is real and bound: the CONNECTED word and dot, off IsSessionConnected, shown only while there is a
session/host context to report on at all — SessionAddress null means nothing here has anything to say,
the same state the header answers with its own empty-state sentence; the elapsed timer, off
SessionElapsedText, which is null and therefore absent whenever there is nothing timed; and, only on the
terminal surface — see <see cref="ShowsEncoding"/> — "UTF-8", which is a true fact about this client's own
renderer and write path (see TerminalWorkspace's terminal.js and SshShellSessionExtensions.WriteTextAsync)
rather than a negotiated session parameter, and is worded plainly rather than as a claim the remote agreed
to it.
the same state the header answers with its own empty-state sentence; the cipher and the host-key/identity
run, each collapsed rather than shown empty or fabricated when the fact behind it is not there — a bucket
connection has neither, and a session whose facts could not be read back at the instant they were asked
for has neither either; the elapsed timer, off SessionElapsedText, which is null and therefore absent
whenever there is nothing timed; and, only on the terminal surface — see <see cref="ShowsEncoding"/> —
"UTF-8", which is a true fact about this client's own renderer and write path (see TerminalWorkspace's
terminal.js and SshShellSessionExtensions.WriteTextAsync) rather than a negotiated session parameter, and
is worded plainly rather than as a claim the remote agreed to it.
-->
<Border Height="37" Background="{StaticResource DeepChrome}"
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0">
<Grid ColumnDefinitions="*,Auto" Margin="24,0">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="7" VerticalAlignment="Center"
IsVisible="{Binding SessionAddress, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse Classes="dot" Width="7" Height="7" Classes.live="{Binding IsSessionConnected}"
VerticalAlignment="Center" />
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="18" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center"
IsVisible="{Binding SessionAddress, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse Classes="dot" Width="7" Height="7" Classes.live="{Binding IsSessionConnected}"
VerticalAlignment="Center" />
<TextBlock FontFamily="Montserrat" FontWeight="SemiBold" FontSize="9.5" LetterSpacing="1.2"
VerticalAlignment="Center" Text="CONNECTED" Foreground="{StaticResource Live}"
IsVisible="{Binding IsSessionConnected}" />
<TextBlock FontFamily="Montserrat" FontWeight="SemiBold" FontSize="9.5" LetterSpacing="1.2"
VerticalAlignment="Center" Text="NOT CONNECTED" Foreground="{StaticResource TextFaint}"
IsVisible="{Binding !IsSessionConnected}" />
<TextBlock FontFamily="Montserrat" FontWeight="SemiBold" FontSize="9.5" LetterSpacing="1.2"
VerticalAlignment="Center" Text="CONNECTED" Foreground="{StaticResource Live}"
IsVisible="{Binding IsSessionConnected}" />
<TextBlock FontFamily="Montserrat" FontWeight="SemiBold" FontSize="9.5" LetterSpacing="1.2"
VerticalAlignment="Center" Text="NOT CONNECTED" Foreground="{StaticResource TextFaint}"
IsVisible="{Binding !IsSessionConnected}" />
</StackPanel>
<TextBlock Classes="mono" FontSize="11.5" Foreground="{StaticResource TextGhost}"
VerticalAlignment="Center" Text="{Binding SessionCipher}"
IsVisible="{Binding SessionCipher, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Classes="mono" FontSize="11.5" Foreground="{StaticResource TextGhost}"
VerticalAlignment="Center" Text="{Binding SessionIdentityText}"
IsVisible="{Binding SessionIdentityText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="18" VerticalAlignment="Center">
@@ -4,8 +4,9 @@ using Avalonia.Controls;
namespace DodoSSH.Client.App.Views;
/// <summary>
/// The v5b session shell's status bar: CONNECTED and its dot, the elapsed timer, and — only where it is true
/// — UTF-8. See the remark at the top of SessionStatusBar.axaml.
/// The v5b session shell's status bar: CONNECTED and its dot, the negotiated cipher, the host key's algorithm
/// and — where one authenticated — the identity that did, the elapsed timer, and — only where it is true —
/// UTF-8. See the remark at the top of SessionStatusBar.axaml.
/// </summary>
internal sealed partial class SessionStatusBar : UserControl
{