fix: keep the session-detail page live, instead of freezing on a snapshot
Nightly Build / build (push) Successful in 7m49s

Leaving `#session/{id}` closes its watch socket, but coming back never
reopened it: the loader bailed out on an unchanged id, so the page showed
the transcript as it was when you left, with nothing streaming into it.
Reload whenever the socket is down, not only when the id changes.

The socket also had no keepalive, unlike the chat one — and a watched
session can go minutes without an event, which is exactly what an idle
proxy drops. Ping every 25s, and resync from the API on reconnect, since
the bus is a broadcast with no replay and everything sent during the gap
is gone.

Also: remove the duplicate `disconnectedCallback` that shadowed the first
and leaked the locale listener, handle `tool_cancelled`/`tool_rejected`
(a stopped or denied call stayed on "pending" forever), and follow the
tail only when the reader is already at the bottom.
This commit is contained in:
2026-08-04 19:55:22 +01:00
parent efb5b1dc33
commit 3f74dc26f2
2 changed files with 103 additions and 26 deletions
+14
View File
@@ -35,6 +35,14 @@ async fn handle_socket(mut socket: WebSocket, skald: Arc<Skald>, session_id: i64
};
let mut rx = ctx.chat_hub.events("session-watch");
// Keepalive, for the same reason the chat socket has one: a session being
// watched can go minutes without an event (a slow `execute_cmd`), and a
// silent socket is what an idle proxy or the browser drops — leaving the
// page frozen on a snapshot with no `onclose` to trigger its reconnect.
let mut keepalive = tokio::time::interval(std::time::Duration::from_secs(25));
keepalive.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
keepalive.tick().await; // consume the immediate first tick (don't ping on connect)
loop {
tokio::select! {
// Detect client disconnect.
@@ -63,6 +71,12 @@ async fn handle_socket(mut socket: WebSocket, skald: Arc<Skald>, session_id: i64
Err(broadcast::error::RecvError::Closed) => break,
}
}
_ = keepalive.tick() => {
if socket.send(Message::Ping(Default::default())).await.is_err() {
break;
}
}
}
}