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
-74
View File
@@ -1,74 +0,0 @@
# Multi-pane chat: where it stands, and the one open bug
Written 2026-08-10 at the end of a long session, so the next one starts from evidence instead of
re-deriving it.
## The open bug
**A pane pointed at a REMOTE server shows `Disconnected` and never gets a reply.** The pane opens, lists
and reads that server's conversations fine (HTTP with the API key works). Sending produces nothing.
Console, repeatedly:
```
WebSocket connection to 'wss://officer.pastilhas.dev/api/chat/ws?token=ofk_…' failed:
WebSocket is closed before the connection is established.
```
The same message also appears for the LOCAL pane's `ws://localhost:9010/...`, yet the local pane works
and shows connected. So the message alone is not the bug — something closes the remote one and it never
comes back.
## Ruled out, each by direct test — do not re-test these
- **The server.** `wss://officer.pastilhas.dev/api/chat/ws?token=<the same ofk_ key>` opens on the first
try from outside the browser. Tested twice: with no `Origin` header, and with
`Origin: http://localhost:9010` (what the browser sends). Both `OPEN`.
- **The key.** Same key, works over HTTP for `/chat/sessions`, and opens the socket above.
- **Websocket auth not understanding API keys.** `upgradeWs` in `server.tsx` uses `resolveAuthToken`, the
same resolver as the HTTP doors, and comments say so explicitly. `chat` is an `execution` capability
and the owner passes `isWsProviderAllowed`.
- **Officer being stale on alpha.** Pulled and restarted; the failure persists.
- **Send being dropped.** Fixed in `243bd04``useChatWebSocket.send` used to `return` silently when the
socket was not `OPEN`. It now queues and flushes on open. That fix is real and worth keeping, but it
did not resolve this: a socket that never opens never flushes.
## Where to look next
`src/workspaces/hooks/src/useChatWebSocket.ts`, and specifically what happens with SEVERAL instances
mounted at once — one per pane.
The handlers look individually correct: `close` returns early when `socketRef.current !== socket`, the
effect depends on `[url]` alone, and `isCleaningUpRef` is reset on mount. What has NOT been established
is how those interact across three simultaneous instances plus React's dev StrictMode double-invoke,
which creates, closes and recreates every socket on mount.
Concrete things to try, cheapest first:
1. **Instrument before theorising.** Log `url`, `readyState` and instance identity on every create,
open, close and retry. The console message says a close arrived during CONNECTING; it does not say
who called it. That is the whole question and it is one log line away.
2. **Check whether the effect re-runs.** `url` is a string, so it should be stable — but
`chatSocketUrl(serverId, localStorage.getItem('BEARER_TOKEN'))` is recomputed every render, and if
anything makes `serverId` flip (say, a pane re-resolving its target) the URL changes and the socket
is torn down mid-handshake, forever.
3. **Suspect the transcript resolver in `ChatPane`.** It calls `onTargetChange` on success, which
rewrites the tab state, which produces a new `target` object for every pane in that tab. If that ever
loops, every pane's socket is rebuilt on every pass and none survives the handshake.
4. **Try one pane pointed at the remote server, alone.** If it connects, the bug is about multiplicity
rather than about the remote server, which would be the single most useful fact to have.
## What works, so it is not re-litigated
- Adding a second server by URL + `ofk_` key, verified against `/api/auth/me` before storing.
- Per-pane server chips; the list, the directory picker and the transcript all follow the pane's server.
- Tabs, splitting to three panes, closing, renaming a tab, and the tab name winning the page title.
- Reading a remote conversation end to end.
## Setup facts worth not rediscovering
- Andre's Mac key lives in agent memory. Alpha needs its own; the one in the console above is alpha's.
- `bun dev` on the Mac serves a STALE bundle whenever a new file is added to a workspace package — the
symptom is `X is not a function` for an export that plainly exists. Restart it; do not debug it.
- The Mac is `https://macbook.pastilhas.dev` through NPM on alpha (`100.64.0.1` → Mac `100.64.0.9:9010`),
and the cert is valid under strict TLS.