fix(ai-chat): prevent duplicate chat row on first-turn error; add adoption tests
Addresses the PR #138 review. Blocker 1 — duplicate chat row: a brand-new chat whose first turn errors BEFORE the SSE 'start' chunk never receives the authoritative chatId, so metadata adoption can't run; a retry then sent chatId:null and the server inserted a SECOND chat row, orphaning the first turn. Keep metadata adoption as the primary path (resolveAdoptedChatId) and add a bounded, unambiguous fallback: on a new-chat finish with no server id, snapshot the known chat ids and, once the list refetch lands, adopt the SINGLE newly-appeared id (pickNewlyCreatedChatId). Zero or >1 new ids (e.g. two tabs racing) → no adoption — no items[0] guessing, so #137 stays fixed. The wait-for-refetch guard compares set membership (robust to a concurrent delete), and the diff dedupes so a repeated id from a paginated list never reads as ambiguous. Blocker 2 — tests: new adopt-chat-id.test.ts covers both pure helpers (adopt decision + newly-created-id diff incl. dedupe/reorder); the server messageMetadata callback is extracted to chatStreamStartMetadata and unit-tested (start -> {chatId}, otherwise undefined). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -269,9 +269,11 @@ export default function ChatThread({
|
||||
},
|
||||
// `onError` runs in addition to `onFinish` (which ai@6 also calls on error).
|
||||
// Log the raw failure here for devtools; the UI shows a friendly classified
|
||||
// banner via `error` below. We still call `onTurnFinished()` (idempotent with
|
||||
// the onFinish call) so a brand-new chat that fails its first turn is adopted
|
||||
// and the chat list refreshes immediately rather than after a manual refresh.
|
||||
// banner via `error` below. We still call `onTurnFinished()` with NO server id
|
||||
// (idempotent with the onFinish call): for a brand-new chat that ARMS the
|
||||
// bounded list-refetch fallback (adopt the single newly-appeared chat once the
|
||||
// refetch lands); for an existing chat it just refreshes the chat list
|
||||
// immediately rather than after a manual refresh.
|
||||
onError: (streamError) => {
|
||||
// Surface the raw failure in the browser console (devtools) for debugging;
|
||||
// the UI separately shows a friendly classified banner (see errorView).
|
||||
|
||||
Reference in New Issue
Block a user