diff --git a/web/src/attach.ts b/web/src/attach.ts index e019e57..ef7c4b5 100644 --- a/web/src/attach.ts +++ b/web/src/attach.ts @@ -30,6 +30,10 @@ export interface AttachConnection { export interface AttachOptions { /** Invoked when the user clicks the view's explicit "disconnect" control. */ onRequestClose?: (sessionId: Uuid) => void; + /** Invoked when the socket closes on its own — the session ended or the link + * dropped — but NOT on an intentional `dispose()` (eviction / user close). The + * shell uses this to auto-close the pane rather than leave a dead one. */ + onClosed?: (sessionId: Uuid) => void; } /** What the header shows about the session (name / command / host). Optional — @@ -242,6 +246,9 @@ export function createAttachConnection( ws.onclose = (ev) => { setState(`closed (${ev.code})`, "closed"); + // Only bubble up server/network closes — an intentional dispose has already + // set `disposed`, and eviction/user-close handle their own pane teardown. + if (!disposed) opts.onClosed?.(sessionId); }; // PTY input: every keystroke goes out as a binary frame. A pending key-bar diff --git a/web/src/catalog.ts b/web/src/catalog.ts index 07ef274..3adc557 100644 --- a/web/src/catalog.ts +++ b/web/src/catalog.ts @@ -337,12 +337,24 @@ export function renderTopbar(store: CatalogStore): HTMLElement { return header; } +/** Two-tier sidebar highlight state (issue #33): which sessions are open in some + * pane (quiet), which one is focused (loud), and a stable per-session colour so + * a row maps to its pane. */ +export interface SidebarHighlights { + open: Set; + focused: string | null; + colors: Map; +} + /** Live handle to the persistent sidebar (built once, survives navigation). */ export interface SidebarHandle { /** The `