fix(tui): restart dashboard chat on idle exit hotkeys

This commit is contained in:
Shannon Sands 2026-06-19 16:11:55 +10:00 committed by kshitij
parent a64fc490fe
commit 12dfcfdf73
13 changed files with 207 additions and 18 deletions

View file

@ -74,9 +74,15 @@ interface ChatSidebarProps {
/** Management profile from the dashboard switcher — scopes session.create. */
profile?: string;
className?: string;
onDashboardNewSessionRequest?: () => void;
}
export function ChatSidebar({ channel, profile, className }: ChatSidebarProps) {
export function ChatSidebar({
channel,
profile,
className,
onDashboardNewSessionRequest,
}: ChatSidebarProps) {
// `version` bumps on reconnect; gw is derived so we never call setState
// for it inside an effect (React 19's set-state-in-effect rule). The
// counter is the dependency on purpose — it's not read in the memo body,
@ -112,9 +118,12 @@ export function ChatSidebar({ channel, profile, className }: ChatSidebarProps) {
useEffect(() => {
let cancelled = false;
setSessionId(null);
setInfo({});
setError(null);
queueMicrotask(() => {
if (cancelled) return;
setSessionId(null);
setInfo({});
setError(null);
});
const offState = gw.onState(setState);
const offSessionInfo = gw.on<SessionInfo>("session.info", (ev) => {
@ -233,7 +242,9 @@ export function ChatSidebar({ channel, profile, className }: ChatSidebarProps) {
const { type, payload } = frame.params;
if (type === "tool.start") {
if (type === "dashboard.new_session_requested") {
onDashboardNewSessionRequest?.();
} else if (type === "tool.start") {
const p = payload as
| { tool_id?: string; name?: string; context?: string }
| undefined;
@ -309,7 +320,7 @@ export function ChatSidebar({ channel, profile, className }: ChatSidebarProps) {
unmounting = true;
ws?.close();
};
}, [channel, version]);
}, [channel, onDashboardNewSessionRequest, version]);
const reconnect = useCallback(() => {
setError(null);

View file

@ -153,6 +153,15 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
setBanner(null);
setReconnectNonce((n) => n + 1);
}, []);
const startFreshDashboardChat = useCallback(() => {
const next = new URLSearchParams(searchParams);
next.delete("resume");
setSearchParams(next, { replace: true });
setSessionEnded(false);
setBanner(null);
setReconnectNonce((n) => n + 1);
}, [searchParams, setSearchParams]);
// Raw state for the mobile side-sheet + a derived value that force-
// closes whenever the chat tab isn't active. The *derived* value is
// what side-effects (body-scroll lock, keydown listener, portal render)
@ -881,7 +890,11 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
"border-t border-current/10",
)}
>
<ChatSidebar channel={channel} profile={scopedProfile} />
<ChatSidebar
channel={channel}
profile={scopedProfile}
onDashboardNewSessionRequest={startFreshDashboardChat}
/>
</div>
</div>
</>,
@ -967,7 +980,11 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
className="flex min-h-0 shrink-0 flex-col overflow-hidden lg:h-full lg:w-80"
>
<div className="min-h-0 flex-1 overflow-hidden">
<ChatSidebar channel={channel} profile={scopedProfile} />
<ChatSidebar
channel={channel}
profile={scopedProfile}
onDashboardNewSessionRequest={startFreshDashboardChat}
/>
</div>
</div>
)}