fix(desktop): keep the session loader up while known history is empty
Brand-new drafts are empty on purpose. A routed session the list already knows has messages must not drop the loader just because a runtime id is bound — that is the blank frame during an unproven warm hold and a cold switch.
This commit is contained in:
committed by
brooklyn!
parent
b6eb17d01c
commit
d229648511
@@ -7,6 +7,7 @@ import { memo, Suspense, useCallback, useEffect, useId, useMemo, useRef, useStat
|
||||
import { useLocation } from 'react-router'
|
||||
|
||||
import type { SubmitTextOptions } from '@/app/session/hooks/use-prompt-actions/utils'
|
||||
import { sessionShouldHaveTranscript } from '@/app/session/hooks/use-session-actions/utils'
|
||||
import { Thread } from '@/components/assistant-ui/thread'
|
||||
import { TranscriptWindowProvider } from '@/components/assistant-ui/thread/transcript-window'
|
||||
import { Backdrop } from '@/components/Backdrop'
|
||||
@@ -71,7 +72,7 @@ import { useRuntimeMessageRepository } from './runtime-repository'
|
||||
import { ScrollToBottomButton } from './scroll-to-bottom-button'
|
||||
import { useSessionView } from './session-view'
|
||||
import { SessionActionsMenu } from './sidebar/session-actions-menu'
|
||||
import { threadLoadingState } from './thread-loading'
|
||||
import { routedSessionIsLoading, threadLoadingState } from './thread-loading'
|
||||
import {
|
||||
backfillOlderTranscriptPage,
|
||||
mergeOlderTranscriptPage,
|
||||
@@ -502,10 +503,11 @@ const ChatViewContent = memo(function ChatViewContent({
|
||||
})
|
||||
|
||||
// Session is still loading if the route references a session we haven't
|
||||
// resumed yet. Once `activeSessionId` is set (runtime has resumed), the
|
||||
// session exists — even if it has zero messages (a brand-new routed
|
||||
// session). The flicker where `busy` flips true briefly during hydrate
|
||||
// is handled by `threadLoadingState`'s last-visible-user gate.
|
||||
// resumed yet. Brand-new routed drafts are empty on purpose once a runtime
|
||||
// is bound. A session the list already knows has history must keep the
|
||||
// loader up until a display-authoritative transcript arrives — including
|
||||
// the unproven warm-cache hold, where the runtime is bound but messages
|
||||
// are still suppressed.
|
||||
//
|
||||
// resumeExhausted: the bounded auto-retry in use-route-resume gave up on this
|
||||
// routed session (gateway RPC + REST fallback failed through every attempt).
|
||||
@@ -513,9 +515,21 @@ const ChatViewContent = memo(function ChatViewContent({
|
||||
// spinning forever. Gated on the route matching so a stale latch from another
|
||||
// session can't blank the current one.
|
||||
const resumeExhausted = isPrimary && isRoutedSessionView && resumeExhaustedSessionId === routedSessionId
|
||||
const routedHasHistory = Boolean(
|
||||
routedSessionId &&
|
||||
sessions.some(
|
||||
session => sessionMatchesStoredId(session, routedSessionId) && sessionShouldHaveTranscript(session)
|
||||
)
|
||||
)
|
||||
|
||||
const loadingSession =
|
||||
!resumeExhausted && isRoutedSessionView && (routeSessionMismatch || (messagesEmpty && !activeSessionId))
|
||||
const loadingSession = routedSessionIsLoading({
|
||||
activeSessionId,
|
||||
knownHistory: routedHasHistory,
|
||||
messagesEmpty,
|
||||
resumeExhausted,
|
||||
routeSessionMismatch,
|
||||
routedSessionView: isRoutedSessionView
|
||||
})
|
||||
|
||||
const threadLoading = threadLoadingState(loadingSession, busy, awaitingResponse, lastVisibleIsUser)
|
||||
// Hide the composer in the exhausted error state too: there's no live runtime
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ChatMessage } from '@/lib/chat-messages'
|
||||
|
||||
import { lastVisibleMessageIsUser, threadLoadingState } from './thread-loading'
|
||||
import { lastVisibleMessageIsUser, routedSessionIsLoading, threadLoadingState } from './thread-loading'
|
||||
|
||||
function message(id: string, role: ChatMessage['role'], hidden = false): ChatMessage {
|
||||
return {
|
||||
@@ -32,3 +32,33 @@ describe('thread loading state', () => {
|
||||
expect(threadLoadingState(false, true, true, lastVisibleMessageIsUser(messages))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('routedSessionIsLoading', () => {
|
||||
const base = {
|
||||
activeSessionId: 'runtime-1' as string | null,
|
||||
knownHistory: false,
|
||||
messagesEmpty: false,
|
||||
resumeExhausted: false,
|
||||
routeSessionMismatch: false,
|
||||
routedSessionView: true
|
||||
}
|
||||
|
||||
it('keeps the session loader up when known history is held off the view', () => {
|
||||
expect(
|
||||
routedSessionIsLoading({
|
||||
...base,
|
||||
knownHistory: true,
|
||||
messagesEmpty: true
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('does not treat a brand-new empty routed draft as still loading', () => {
|
||||
expect(
|
||||
routedSessionIsLoading({
|
||||
...base,
|
||||
messagesEmpty: true
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,3 +29,38 @@ export function threadLoadingState(
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function routedSessionIsLoading({
|
||||
activeSessionId,
|
||||
knownHistory,
|
||||
messagesEmpty,
|
||||
resumeExhausted,
|
||||
routeSessionMismatch,
|
||||
routedSessionView
|
||||
}: {
|
||||
activeSessionId: string | null
|
||||
knownHistory: boolean
|
||||
messagesEmpty: boolean
|
||||
resumeExhausted: boolean
|
||||
routeSessionMismatch: boolean
|
||||
routedSessionView: boolean
|
||||
}): boolean {
|
||||
if (resumeExhausted || !routedSessionView) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (routeSessionMismatch) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!messagesEmpty) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Brand-new routed drafts are empty on purpose. A session the list already
|
||||
// knows has history must keep the loader up until a display-authoritative
|
||||
// transcript arrives — including the unproven warm-cache hold, where the
|
||||
// runtime is bound but messages are still suppressed.
|
||||
return !activeSessionId || knownHistory
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user