fix(native-chat): handle reordering with timestampless transcripts (#10050)
Grok transcripts carry no timestamps. Previous logic excluded these rows from matching, leaving pending sends and launch prompts unmatched and causing the seeded bubble to appear rank-pinned at the list tail — which reads as conversation reordering. Now pending sends, launch prompts, and their pruning rules treat null timestamps as matching-eligible, allowing echo suppression and proper cleanup of delivered messages.
This commit is contained in:
parent
099003188d
commit
bae29fd1e2
|
|
@ -134,6 +134,16 @@ describe('prunePendingSends', () => {
|
|||
expect(prunePendingSends(pending, [oldUser, oldAnswer])).toEqual(pending)
|
||||
})
|
||||
|
||||
it('prunes a first send against a timestampless transcript turn (grok)', () => {
|
||||
const pending = [{ ...pendingOf('p1', 'rename it'), afterMessageId: null }]
|
||||
const transcript = [
|
||||
{ ...userMessage('u1', 'rename it'), timestamp: null },
|
||||
{ ...assistantMessage('a1', 'done'), timestamp: null }
|
||||
]
|
||||
|
||||
expect(prunePendingSends(pending, transcript)).toEqual([])
|
||||
})
|
||||
|
||||
it('prunes only one of two identical pending sends for one completed turn', () => {
|
||||
const pending = [pendingOf('p1', 'repeat'), pendingOf('p2', 'repeat')]
|
||||
expect(
|
||||
|
|
@ -246,6 +256,14 @@ describe('pendingSendsAsMessages', () => {
|
|||
expect(prunePendingSends(pending, remoteTranscript)).toEqual([])
|
||||
})
|
||||
|
||||
it('hides a first send while its timestampless transcript turn is visible (grok)', () => {
|
||||
const pending = [{ ...pendingOf('p1', 'rename it'), afterMessageId: null }]
|
||||
|
||||
expect(
|
||||
pendingSendsAsMessages(pending, [{ ...userMessage('u1', 'rename it'), timestamp: null }])
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('hides only one of two identical pending sends for one real user turn', () => {
|
||||
const pending = [pendingOf('p1', 'repeat'), pendingOf('p2', 'repeat')]
|
||||
expect(pendingSendsAsMessages(pending, [userMessage('u1', 'repeat')]).map((m) => m.id)).toEqual(
|
||||
|
|
@ -340,6 +358,20 @@ describe('launchPromptAsMessage', () => {
|
|||
).toBe(true)
|
||||
})
|
||||
|
||||
// Grok transcripts carry no timestamps; before the null-matchable rule the
|
||||
// seeded bubble was never hidden or pruned and sat rank-pinned at the list
|
||||
// tail forever, reading as the conversation reordering.
|
||||
it('hides and prunes the launch prompt against a timestampless transcript (grok)', () => {
|
||||
const entry = { tabId: 'tab-1', agent: 'grok' as const, text: 'rename it', createdAt: 42 }
|
||||
const transcript = [
|
||||
{ ...userMessage('u1', 'rename it'), timestamp: null },
|
||||
{ ...assistantMessage('a1', 'done'), timestamp: null }
|
||||
]
|
||||
|
||||
expect(launchPromptAsMessage(entry, transcript)).toBeNull()
|
||||
expect(shouldPruneLaunchPrompt(entry, transcript)).toBe(true)
|
||||
})
|
||||
|
||||
it('does not bind a launch prompt to an older identical completed turn', () => {
|
||||
const entry = {
|
||||
tabId: 'tab-1',
|
||||
|
|
|
|||
|
|
@ -111,8 +111,11 @@ function messageIsAfterPendingTimestamp(
|
|||
message: NativeChatMessage,
|
||||
pending: NativeChatPendingSend
|
||||
): boolean {
|
||||
// Why: some transcripts (e.g. Grok) never carry timestamps. Excluding their
|
||||
// rows would make the echo unmatchable forever, stranding a rank-pinned
|
||||
// bubble at the list tail — which reads as the conversation reordering.
|
||||
if (message.timestamp === null) {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
const boundary = nativeChatPendingMatchingAfter(pending)
|
||||
// A transcript-clock boundary describes an existing message, so exclude ties.
|
||||
|
|
@ -231,9 +234,11 @@ export function launchPromptAsMessage(
|
|||
if (!entry) {
|
||||
return null
|
||||
}
|
||||
// Why: a launch prompt seeds a brand-new session, so a matching user turn
|
||||
// with no timestamp (e.g. Grok transcripts) can only be its own delivery.
|
||||
const represented = matchingNativeChatUserContentCounts(
|
||||
existingMessages.filter(
|
||||
(message) => message.timestamp !== null && message.timestamp >= entry.createdAt
|
||||
(message) => message.timestamp === null || message.timestamp >= entry.createdAt
|
||||
)
|
||||
)
|
||||
if ((represented.get(nativeChatPendingContentKey(entry)) ?? 0) > 0) {
|
||||
|
|
@ -256,7 +261,7 @@ export function shouldPruneLaunchPrompt(
|
|||
messages: NativeChatMessage[]
|
||||
): boolean {
|
||||
const relevant = messages.filter(
|
||||
(message) => message.timestamp !== null && message.timestamp >= entry.createdAt
|
||||
(message) => message.timestamp === null || message.timestamp >= entry.createdAt
|
||||
)
|
||||
return (
|
||||
(advancedNativeChatUserContentCounts(relevant).get(nativeChatPendingContentKey(entry)) ?? 0) > 0
|
||||
|
|
|
|||
Loading…
Reference in New Issue