a new chat gets its name without a refresh — this time with the import
Re-lands f9a36996, which I reverted because it broke chat rendering outright:
`ReferenceError: useQueryClient is not defined`.
The cause was mine and it is embarrassing in a specific way. The edit that added the import searched for
`import { useCallback, useEffect, useMemo, useRef, useState } from 'react'` and the file's first line is
`import { useState, useEffect, useRef, useCallback } from 'react'` — different order, and useMemo is not in
it. So the search matched nothing, and I had used replace WITHOUT an assertion, so nothing said so. That is
the identical failure I caught and wrote up hours earlier in the CommandHQ layout edit, where two edits
silently did nothing for the same reason.
Every edit in this commit asserts. Which then caught a second, subtler fault: `commitStreaming()` appears
ELEVEN times, so anchoring the turn-end call on it put announceSession inside `assistant:text` rather than
`result`, with mangled indentation. An assert proves an anchor EXISTS; it does not prove it is unique. The
result handler is now anchored on a three-line sequence that occurs once, and the count is asserted rather
than assumed.
Verified in the BUILT BUNDLE rather than by typecheck, because a ReferenceError is a bundle fact: the name
appears zero times as a free identifier (the bundler resolved and renamed it), and both invalidation keys are
present — CLAUDE_SESSIONS went from one occurrence to two, the second being this.
Worth recording about the tooling, since I doubted it: tsgo DOES catch this class of error. I probed it three
times with a deliberate type error in this exact file and it reported every time. So the check is sound and
the miss was mine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useChatWebSocket } from 'hooks/useChatWebSocket';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useSettings } from 'state/useSettings';
|
||||
@@ -200,6 +201,26 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
const [claudeSessionId, setClaudeSessionId] = useState<string | null>(resumeSessionId ?? null);
|
||||
|
||||
const client = useClient();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
/*
|
||||
Tell the session LISTS that this conversation exists.
|
||||
|
||||
The chat header resolves its own title by finding this session in the cached list —
|
||||
`sessions.find((s) => s.id === sessionId)`. A newborn conversation is not in that list: it has
|
||||
`staleTime: 30s` and nothing invalidated it, so for up to half a minute the header read "New chat" and
|
||||
the rename control was absent, because rename is a per-row action on a list the session was not in yet.
|
||||
Navigating away and back fixed it, which is what made it look like a rendering bug rather than a stale
|
||||
cache.
|
||||
|
||||
Fired when the transcript id arrives — the START of the turn, not the end — because that is the moment
|
||||
the session becomes real on disk and the server can already name it from the first user message. Fired
|
||||
again when a turn finishes, because the harness writes its own `ai-title` while the turn runs.
|
||||
*/
|
||||
const announceSession = useCallback(() => {
|
||||
void queryClient.invalidateQueries({ queryKey: ['CLAUDE_SESSIONS'] });
|
||||
void queryClient.invalidateQueries({ queryKey: ['CHAT_PWDS'] });
|
||||
}, [queryClient]);
|
||||
|
||||
// Fetch the next older window and prepend it. The scroll container restores its position from the
|
||||
// height delta so the view stays put. Guarded against overlap and against running once fully paged in.
|
||||
@@ -343,6 +364,8 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
claudeSessionIdRef.current = msg.claudeSessionId;
|
||||
setClaudeSessionId(msg.claudeSessionId);
|
||||
if (replaceUrl) writePermalink(msg.claudeSessionId);
|
||||
// The lists do not know this conversation exists yet — see `announceSession`.
|
||||
announceSession();
|
||||
break;
|
||||
|
||||
case 'assistant:delta':
|
||||
@@ -420,6 +443,9 @@ export function useChat(initialSessionId?: string, initialModel?: string | null,
|
||||
const hadTools = toolCallsInTurnRef.current;
|
||||
toolCallsInTurnRef.current = false;
|
||||
onTurnCompleteRef.current?.(hadTools);
|
||||
// Every turn end: the harness writes its own `ai-title` while a turn runs, so the name the lists are
|
||||
// showing may now be bettered by the one on disk.
|
||||
announceSession();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user