Compare commits

...

1 Commits

Author SHA1 Message Date
Willem Jiang 001f6efcff fix(chat): prevent first user message from being swallowed in new conversations
The optimistic message clearing effect cleared too eagerly — any stream
  message (including AI messages from messages-tuple events) triggered the
  clear before the server's human message had arrived via values events.
  For new threads this caused the user's first prompt to disappear permanently.

  Only clear optimistic messages once the server's human message has been
  confirmed to arrive in thread.messages, not just when any message arrives.

  Fixes #2730
2026-05-05 20:24:03 +08:00
+25 -5
View File
@@ -286,6 +286,13 @@ export function useThreadStream({
const summarizedRef = useRef<Set<string>>(null);
// Track message count before sending so we know when server has responded
const prevMsgCountRef = useRef(thread.messages.length);
// Track human message count before sending to prevent clearing optimistic
// messages before the server's human message arrives (e.g. when AI messages
// from "messages-tuple" events arrive before the input human message from
// "values" events).
const prevHumanMsgCountRef = useRef(
thread.messages.filter((m) => m.type === "human").length,
);
summarizedRef.current ??= new Set<string>();
@@ -296,12 +303,22 @@ export function useThreadStream({
sendInFlightRef.current = false;
}, [threadId]);
// Clear optimistic when server messages arrive (count increases)
// Clear optimistic when server messages arrive.
// For messages with a human optimistic message, wait until the server's
// human message has arrived to avoid clearing before the input message
// appears in the stream (the input message may arrive via "values" events
// after individual "messages-tuple" events for AI messages).
useEffect(() => {
if (
optimisticMessages.length > 0 &&
thread.messages.length > prevMsgCountRef.current
) {
if (optimisticMessages.length === 0) return;
const hasHumanOptimistic = optimisticMessages.some(
(m) => m.type === "human",
);
const newHumanMsgArrived =
thread.messages.filter((m) => m.type === "human").length >
prevHumanMsgCountRef.current;
if (!hasHumanOptimistic || newHumanMsgArrived) {
setOptimisticMessages([]);
}
}, [thread.messages.length, optimisticMessages.length]);
@@ -322,6 +339,9 @@ export function useThreadStream({
// Capture current count before showing optimistic messages
prevMsgCountRef.current = thread.messages.length;
prevHumanMsgCountRef.current = thread.messages.filter(
(m) => m.type === "human",
).length;
// Build optimistic files list with uploading status
const optimisticFiles: FileInMessage[] = (message.files ?? []).map(