@@ -235,6 +235,10 @@ export function useAgent() {
235235 const [ pendingSteers , setPendingSteers ] = useState < string [ ] > ( [ ] ) ;
236236 const sessionIdRef = useRef < string | null > ( null ) ;
237237 const abortRef = useRef < AbortController | null > ( null ) ;
238+ // send() claims the tab synchronously BEFORE its first await: a loadSession
239+ // resolving mid-run must not replace the transcript, because the run's
240+ // plain-value setMessages(transcript) writes would clobber it.
241+ const sendClaimRef = useRef ( false ) ;
238242 const messageCounter = useRef ( 0 ) ;
239243
240244 const nextId = ( ) => String ( ++ messageCounter . current ) ;
@@ -246,17 +250,19 @@ export function useAgent() {
246250 * emits, so the restored transcript renders identically.
247251 */
248252 const loadSession = useCallback ( async ( sessionId : string ) : Promise < boolean > => {
249- // Never swap the session out from under a tab that already has one.
250- if ( sessionIdRef . current ) return false ;
253+ // Never swap the session out from under a tab that already has one, or
254+ // one where a send has already claimed the transcript.
255+ if ( sessionIdRef . current || sendClaimRef . current ) return false ;
251256 try {
252257 const res = await apiFetch (
253258 `/sessions/${ encodeURIComponent ( sessionId ) } /history` ,
254259 ) ;
255260 if ( ! res . ok ) return false ;
256261 const data = ( await res . json ( ) ) as { messages ?: HistoryItem [ ] } ;
257262 // Re-check after the awaits: a message sent while the history fetch was
258- // in flight binds the tab to a fresh session, which must win.
259- if ( sessionIdRef . current ) return false ;
263+ // in flight claims the tab (and will bind a fresh session), which must
264+ // win over hydration.
265+ if ( sessionIdRef . current || sendClaimRef . current ) return false ;
260266 const restored : ChatMessage [ ] = [ ] ;
261267 const fallbackTs = Date . now ( ) ;
262268 for ( const item of data . messages ?? [ ] ) {
@@ -347,6 +353,7 @@ export function useAgent() {
347353 computeTarget ?: string ,
348354 ) : Promise < string | undefined > => {
349355 if ( ! text . trim ( ) || status === "submitted" || status === "streaming" ) return ;
356+ sendClaimRef . current = true ;
350357
351358 const userMsgId = nextId ( ) ;
352359 const assistantId = nextId ( ) ;
@@ -453,6 +460,7 @@ export function useAgent() {
453460 setPendingSteers ( [ ] ) ;
454461 setStatus ( aborted ? "ready" : "error" ) ;
455462 } finally {
463+ sendClaimRef . current = false ;
456464 abortRef . current = null ;
457465 }
458466
0 commit comments