revert the chat tabs and panes work, back to one conversation

Andre asked for zero, not another fix on top. Reverts ec4f06a..7726c9f — the ten commits from
"tabs and panes" onward: the tab bar and pane splitting, tab renaming and its page title, the
per-server directory picker, the render-loop fix, pane transcript resolution, the send queue,
the two socket fixes from the other session, the pane-socket notes, and my own socket-set change
from tonight. He is rebuilding from here.

Deliberately KEPT: dc6b623, "talk to two officers at once from one browser". That was a separate
ask that predates the tabs one, and the multi-server client, the server chips and the connections
store stand on their own without panes. Reverting it too is one more command if that was the
intent.

Collateral, worth naming: cb7ab55 carried an unrelated MusicPlayerHost change alongside its
socket instrumentation, so that came out with it.

Reverts, not a reset — every one of these is pushed and a second session is live in this repo.

Typecheck clean. 600 pass, 2 fail — cliamp path-escape and the pty transport test, both failing
identically before this and unrelated to chat.

What is NOT explained by this revert: the browser symptoms tonight. The server was verified good
throughout — two real turns streamed back through the public URL on both models, and the full
2,281-message history came through nginx intact. Whatever the client fault is, it is still
unfound, and the pre-tabs code is where it now has to be looked for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 23:29:18 +01:00
co-authored by Claude Opus 5
parent 7726c9fc71
commit 0a4ff548b9
19 changed files with 77 additions and 749 deletions
+4 -14
View File
@@ -24,7 +24,7 @@ class SessionManager {
cwd,
model,
piProcess: null,
sockets: new Set(),
ws: null,
lastActivity: Date.now(),
idleTimer: null,
streamBuffer: '',
@@ -145,7 +145,7 @@ class SessionManager {
attachWs(sessionId: string, ws: any): void {
const session = this.sessions.get(sessionId);
if (session) {
session.sockets.add(ws);
session.ws = ws;
session.lastActivity = Date.now();
if (session.idleTimer) {
@@ -155,24 +155,14 @@ class SessionManager {
}
}
/**
* Removes one socket. The caller must say WHICH — a bare `detachWs(sessionId)` used to null the
* session's only socket field, so a stale client's close event silenced whichever client had attached
* after it. A close is only the end of the conversation when nothing else is still watching.
*/
detachWs(sessionId: string, ws: any): void {
detachWs(sessionId: string): void {
const session = this.sessions.get(sessionId);
if (session) {
session.sockets.delete(ws);
session.ws = null;
session.lastActivity = Date.now();
}
}
/** Whether anything is still watching — the idle GC must not start while another client is attached. */
hasSockets(sessionId: string): boolean {
return (this.sessions.get(sessionId)?.sockets.size ?? 0) > 0;
}
setIdleTimeout(sessionId: string, timeoutMs: number): void {
const session = this.sessions.get(sessionId);
if (!session) return;