Pressing "New chat" while a brand-new chat's first turn was still streaming
only removed the role badge — the thread, session and stream were kept. The
thread remount is driven by a render-phase reconciler in useChatSession that
fires only when activeChatId !== thread.chatId; on a not-yet-adopted new chat
both are null, so startNewChat's setActiveChatId(null) was a no-op and nothing
remounted.
- useChatSession: add startFreshThread(), which unconditionally dispatches a
reconcile to a fresh mount key so ChatThread remounts even when activeChatId
stays null. startNewChat now calls it.
- Guard against the abandoned thread's late finish: ai@6's useChat does not
abort on unmount and proxies its callbacks, so onFinish/onError of the chat
the user just left still fire and would adopt it back. onTurnFinished now
takes the finishing thread's key and, when it no longer matches the mounted
thread, only refreshes the chat list — no adoption, no fallback arming, no
per-chat message invalidation. ChatThread forwards its threadKey in both
onFinish and onError. An omitted key (legacy callers/tests) counts as the
current thread, preserving the #137 adoption behavior.
Tests: 3 new useChatSession cases (fresh-thread remount with activeChatId null,
abandoned-thread finish rejected, current-thread finish still adopts). Full
ai-chat suite green (183).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>