Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions web/src/components/ChatSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
* `ChatPage.tsx` for where the id is generated.
*
* Best-effort throughout: WS failures show in the badge / banner, the
* terminal pane keeps working unimpaired.
* terminal pane keeps working unimpaired. When `showTools=false`, the sidebar
* is a model/session rail only and does not open sidecar sockets.
*/

import { Button } from "@nous-research/ui/ui/components/button";
Expand Down Expand Up @@ -169,6 +170,18 @@ export function ChatSidebar({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main no longer declares or passes showTools: ChatSidebarProps is at current web/src/components/ChatSidebar.tsx:73-80, and both ChatPage call sites omit it. This branch must be reworked against the current sidebar API before salvage; a direct cherry-pick will not typecheck.

useEffect(() => {
let cancelled = false;
if (!showTools) {
queueMicrotask(() => {
if (cancelled) return;
setState("idle");
setInfo({});
setError(null);
});
return () => {
cancelled = true;
};
}

queueMicrotask(() => {
if (cancelled) return;
setInfo({});
Expand Down Expand Up @@ -221,7 +234,7 @@ export function ChatSidebar({
gw.close();
};
// `profile` is read from render; scope changes bump `version` → new `gw`.
}, [gw]);
}, [gw, showTools]);

// Event subscriber WebSocket — receives the rebroadcast of every
// dispatcher emit from the PTY child's gateway. See /api/pub +
Expand All @@ -232,7 +245,7 @@ export function ChatSidebar({
// JSON-RPC sidecar so the sidebar matches its documented best-effort
// UX and the user always has a reconnect affordance.
useEffect(() => {
if (!channel) {
if (!showTools || !channel) {
return;
}
// In loopback mode the legacy ?token=<session> path is fine; in gated
Expand Down Expand Up @@ -367,7 +380,7 @@ export function ChatSidebar({
unmounting = true;
ws?.close();
};
}, [channel, onDashboardNewSessionRequest, onSessionTitleChange, version]);
}, [channel, onDashboardNewSessionRequest, onSessionTitleChange, showTools, version]);

// Seed the badge on mount and re-read it whenever the sockets are rebuilt
// (a profile/channel switch bumps `version`).
Expand Down
59 changes: 15 additions & 44 deletions web/src/pages/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import { FitAddon } from "@xterm/addon-fit";
import { Unicode11Addon } from "@xterm/addon-unicode11";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { WebglAddon } from "@xterm/addon-webgl";
import { Terminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import { Button } from "@nous-research/ui/ui/components/button";
Expand Down Expand Up @@ -219,7 +218,8 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
() => generateChannelId(`${resumeParam ?? ""}\0${scopedProfile}`),
[resumeParam, scopedProfile],
);
const titleScope = `${channel}\0${reconnectNonce}`;
const ptyIdentity = `${channel}\0${reconnectNonce}`;
const titleScope = ptyIdentity;
const sessionTitle =
sessionTitleState.scope === titleScope ? sessionTitleState.title : null;
const handleSessionTitleChange = useCallback(
Expand Down Expand Up @@ -257,30 +257,12 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
};
}, [resumeParam, scopedProfile, handleSessionTitleChange]);

useEffect(() => {
if (!resumeParam) return;

let cancelled = false;

api
.getSessionLatestDescendant(resumeParam)
.then((res) => {
if (cancelled || !res.session_id || res.session_id === resumeParam) {
return;
}

const next = new URLSearchParams(searchParams);
next.set("resume", res.session_id);
setSearchParams(next, { replace: true });
})
.catch(() => {
// Best-effort: old servers or missing sessions should not block chat.
});

return () => {
cancelled = true;
};
}, [resumeParam, searchParams, setSearchParams]);
// Do not rewrite `?resume=` after the PTY has already started. The
// dashboard chat mounts the terminal immediately from `resumeParam`; doing
// an async latest-descendant lookup here can complete shortly after the
// resumed output paints, mutate the URL, and force the PTY/xterm session to
// tear down. If callers want latest-descendant canonicalisation, resolve it
// before navigating to /chat rather than from inside the live terminal.

useEffect(() => {
const mql = window.matchMedia("(max-width: 1023px)");
Expand Down Expand Up @@ -529,24 +511,10 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {

term.open(host);

// WebGL draws from a texture atlas sized with device pixels. On phones and
// in DevTools device mode that often produces *visually* much larger cells
// than `fontSize` suggests — users see "huge" text even at 7–9px settings.
// The canvas/DOM renderer tracks `fontSize` faithfully; use it for narrow
// hosts. Wide layouts still get WebGL for crisp box-drawing.
const useWebgl = terminalTierWidthPx(host) >= 768;
if (useWebgl) {
try {
const webgl = new WebglAddon();
webgl.onContextLoss(() => webgl.dispose());
term.loadAddon(webgl);
} catch (err) {
console.warn(
"[hermes-chat] WebGL renderer unavailable; falling back to default",
err,
);
}
}
// Do not use xterm WebGL in dashboard chat. It can paint resumed
// transcript output correctly at first, then blank a large rectangle after
// the texture atlas/layout settles. The default renderer is less fancy but
// stable for long resumed Hermes turns.

// Initial fit + resize observer. fit.fit() reads the container's
// current bounding box and resizes the terminal grid to match.
Expand Down Expand Up @@ -795,6 +763,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
wsRef.current?.close();
wsRef.current = null;
term.dispose();
host.replaceChildren();
termRef.current = null;
fitRef.current = null;
if (copyResetRef.current) {
Expand Down Expand Up @@ -983,7 +952,9 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
}}
>
<div
key={ptyIdentity}
ref={hostRef}
data-pty-identity={ptyIdentity}
className="hermes-chat-xterm-host min-h-0 min-w-0 flex-1"
/>

Expand Down