Public Access
Merge branch 'claude/gallant-brahmagupta-1f8244'
Writes down that locking the vault leaves shells running, and shows the count on the unlock screen rather than leaving it to be inferred. Conflict resolution: - ShellFlowTests' fixture keeps main's FakeSshConnectionFactory. The branch added an IdleSshConnectionFactory for exactly what main's fake already does — a shell that is open, silent and never closes on its own — so FakeSshConnections.cs is dropped rather than merged, leaving one fake SSH stack in the suite instead of two that would drift apart. - MainWindowViewModel and TerminalWorkspace: both sides added their own members, so both are kept. - TerminalWorkspaceTests was added by both branches, with the renderer gate on one side and session lifetime on the other. Merged into one class over one set of helpers; the gate tests now use FakeConnectionFactory rather than an NSubstitute stub, since the suite already has the fake. gallant's polling Timeout constant is PollTimeout, which no longer reads as the renderer's. - platform-flags.md keeps main's measured focus section and drops the short "nothing hands the terminal keyboard focus" entry the branch still carried, which that section supersedes. One genuine disagreement between the branches, left visible rather than flattened: this branch measured that a collapsed WebView cannot be typed into and attributed it to a hidden WS_CHILD window being ineligible for keyboard focus, while main's focus work measured Win32 focus still held by that hidden window and added a lock path that moves the keyboard off it. Both results stand; the mechanism sentence now defers to the focus entry, which makes the input barrier something the lock path maintains rather than something the platform guarantees. Full suite green, including the container-backed SSH tests.
This commit is contained in:
@@ -31,10 +31,19 @@ public sealed class TerminalWorkspaceOptions
|
||||
/// Owns the loopback data plane and every live terminal session.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// One data plane and one renderer page for the whole application, with a session id per terminal.
|
||||
/// Not one WebView per tab: each WebView2 is a separate browser process, so twenty tabs would mean
|
||||
/// twenty renderer processes and several hundred megabytes for a working set a user would call
|
||||
/// ordinary. Splits and tabs are layout inside the single page.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>A session's lifetime is the application's, not the vault's.</b> This object is composed once at
|
||||
/// startup and outlives every lock, deliberately: locking the vault zeroes keys, and a shell needs no
|
||||
/// vault key to keep running, so a job started before the lock keeps running through it. That is a
|
||||
/// policy rather than an oversight — <c>MainWindowViewModel.LockAsync</c> says why, and the shell shows
|
||||
/// <see cref="LiveSessionCount"/> on the unlock screen so it is not a hidden state.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class TerminalWorkspace : IAsyncDisposable
|
||||
{
|
||||
@@ -69,6 +78,25 @@ public sealed class TerminalWorkspace : IAsyncDisposable
|
||||
/// <summary>Where the WebView should navigate.</summary>
|
||||
public Uri PageUrl => dataPlane.PageUrl;
|
||||
|
||||
/// <summary>
|
||||
/// How many terminals still have a live shell behind them.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Not <c>sessions.Count</c>, which over-reports. Nothing removes an entry when the remote closes
|
||||
/// the channel on its own — <see cref="RunSessionAsync"/> only drops the renderer registration — so
|
||||
/// a session whose shell exited half an hour ago is still in the dictionary. A completed
|
||||
/// <c>Run</c> task is what "the shell is gone" actually looks like: the pump's loops have finished
|
||||
/// and it has already sent <c>SessionClosed</c> to the renderer.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This exists because the shell shows the number on the unlock screen, and a lock screen that
|
||||
/// claims a shell is still running when it is not would be the same class of dishonesty the number
|
||||
/// is there to prevent.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int LiveSessionCount => sessions.Values.Count(session => !session.Run.IsCompleted);
|
||||
|
||||
/// <summary>Starts the loopback listener.</summary>
|
||||
public void Start() => server = dataPlane.RunAsync(lifetime.Token);
|
||||
|
||||
@@ -130,7 +158,15 @@ public sealed class TerminalWorkspace : IAsyncDisposable
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/// <summary>Closes one terminal.</summary>
|
||||
/// <summary>
|
||||
/// Closes one terminal.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reached only from <see cref="DisposeAsync"/> today, which is a consequence of the lifetime policy
|
||||
/// above rather than an accident: nothing else in the application ends a session, because locking
|
||||
/// deliberately does not and there is no per-tab close in the interface yet. It is here, and tested,
|
||||
/// because closing one terminal without taking the process down is what a tab close needs.
|
||||
/// </remarks>
|
||||
public async Task CloseSessionAsync(uint sessionId)
|
||||
{
|
||||
if (!sessions.Remove(sessionId, out var session))
|
||||
|
||||
Reference in New Issue
Block a user