Give hosts and terminals their own screen, and the rest of the vault another
ci / build and test (ubuntu) (pull_request) Canceled after 0s
ci / build (windows) (pull_request) Canceled after 0s

Rebuilds the client's shell from an imported design: a titlebar and nav rail
it draws itself, real multi-session tabs over the one WebView, a Ctrl+K host
search, and a vault screen that merges keys, passwords and pinned host keys
into one table. Hosts left the vault column for their own screen beside the
terminal, which is what the design asks for and turned out to be the better
split anyway.

Two screens the design shows have nothing behind them yet — file transfer
and teams — and say so plainly rather than rendering invented data; every
other gap between the design and this build is recorded in
docs/design-import-gaps.md.
This commit is contained in:
2026-07-31 08:39:37 +02:00
parent d162271a45
commit 9a76eced14
37 changed files with 4672 additions and 1347 deletions
@@ -16,6 +16,43 @@ public enum TerminalServerOpcode : byte
/// <summary>A session has ended. Payload is a UTF-8 reason for the user.</summary>
SessionClosed = 3,
/// <summary>
/// This session's pane is the one to show. No payload.
/// </summary>
/// <remarks>
/// <para>
/// Every session gets a pane and all but one are hidden, so something has to say which. The page picked
/// the newest on its own, which is right exactly once — at the moment a session opens — and wrong for
/// every tab switch afterwards, because the page has no idea a tab strip exists. It is the host that
/// knows which tab is selected, so it is the host that says.
/// </para>
/// <para>
/// An opcode rather than script evaluation through the WebView bridge, which would have been the smaller
/// change. The bridge is UI-thread-bound and unordered with respect to this socket: a switch sent that
/// way could arrive before the <see cref="SessionOpened"/> frame for the pane it names, and the page
/// would activate a session that does not exist yet. On this socket the two are ordered by construction.
/// </para>
/// </remarks>
SessionActivated = 4,
/// <summary>
/// This session is gone for good; the renderer should destroy its pane. No payload.
/// </summary>
/// <remarks>
/// <para>
/// Deliberately not the same thing as <see cref="SessionClosed"/>. That one means the shell ended and
/// the pane must <em>stay</em> — the user was probably reading the last thing the remote said, and that
/// is usually why it ended. This one means the user closed the tab, so there is nothing left to read.
/// </para>
/// <para>
/// It exists because a pane is expensive and the page never reclaimed one. Every closed tab used to
/// leave an xterm instance, its scrollback and a WebGL context behind — and WebGL contexts are a hard
/// browser limit of about sixteen, so a working day of opening and closing terminals ends with panes
/// that cannot get a renderer. Nothing surfaced that, because the leak is inside the WebView.
/// </para>
/// </remarks>
SessionRemoved = 5,
}
/// <summary>Frames the renderer sends to the host.</summary>