mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
fix(native-chat): close picker reveal lifecycle races
This commit is contained in:
@@ -64,11 +64,23 @@ export function cancelNativeChatPtySends(ptyId: string): void {
|
||||
|
||||
/** Wait until every chat sequence on this PTY has finished or been cancelled. */
|
||||
export async function waitForNativeChatPtyIdle(ptyId: string): Promise<void> {
|
||||
const state = ptyQueues.get(ptyId)
|
||||
if (!state) {
|
||||
while (true) {
|
||||
const state = ptyQueues.get(ptyId)
|
||||
if (!state) {
|
||||
return
|
||||
}
|
||||
const tail = state.tail
|
||||
await tail
|
||||
const current = ptyQueues.get(ptyId)
|
||||
if (!current) {
|
||||
return
|
||||
}
|
||||
// A send may append while the captured tail drains; include its replacement.
|
||||
if (current !== state || current.tail !== tail) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
await state.tail
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,4 +39,25 @@ describe('waitForNativeChatSendQueueIdle', () => {
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
expect(resolved).toBe(true)
|
||||
})
|
||||
|
||||
it('waits for sends appended while an earlier successor is draining', async () => {
|
||||
const revealingSend = enqueueTimedSend(10)
|
||||
enqueueTimedSend(20)
|
||||
const queueIdle = waitForNativeChatSendQueueIdle(PTY_ID, revealingSend.settled)
|
||||
let resolved = false
|
||||
void queueIdle?.then(() => {
|
||||
resolved = true
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(15)
|
||||
enqueueTimedSend(30)
|
||||
await vi.advanceTimersByTimeAsync(15)
|
||||
expect(resolved).toBe(false)
|
||||
|
||||
await vi.advanceTimersByTimeAsync(29)
|
||||
expect(resolved).toBe(false)
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
expect(resolved).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
+15
@@ -84,6 +84,21 @@ describe('useNativeChatSlashCommandDispatched', () => {
|
||||
expect(onSwitchToTerminal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not reveal after the originating chat view unmounts', async () => {
|
||||
let settle = (): void => {}
|
||||
const settled = new Promise<void>((resolve) => {
|
||||
settle = resolve
|
||||
})
|
||||
const hook = render('claude')
|
||||
act(() => hook.result.current('/resume', settled))
|
||||
|
||||
hook.unmount()
|
||||
settle()
|
||||
await settled
|
||||
|
||||
expect(onSwitchToTerminal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('records the marker immediately, not on settle', async () => {
|
||||
// The `Ran /x` line is local feedback for a command with no transcript turn;
|
||||
// holding it until the writes finish would lag the user's own action.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, type Dispatch, type SetStateAction } from 'react'
|
||||
import { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } from 'react'
|
||||
import type { AgentType } from '../../../../shared/agent-status-types'
|
||||
import { getVerifiedNativeChatCommands } from '../../../../shared/native-chat-agent-profiles'
|
||||
import { nativeChatSlashCommandOpensAgentPicker } from '../../../../shared/native-chat-slash-commands'
|
||||
@@ -35,6 +35,13 @@ export function useNativeChatSlashCommandDispatched(args: {
|
||||
onSwitchToTerminal?: (agent: AgentType) => void
|
||||
}): (command: string, settled?: Promise<void>, cancelled?: () => boolean) => void {
|
||||
const { agent, commandMarkerScope, setCommandMarkers, onSwitchToTerminal } = args
|
||||
const mountedRef = useRef(true)
|
||||
useEffect(() => {
|
||||
mountedRef.current = true
|
||||
return () => {
|
||||
mountedRef.current = false
|
||||
}
|
||||
}, [])
|
||||
return useCallback(
|
||||
(command: string, settled?: Promise<void>, cancelled?: () => boolean) => {
|
||||
setCommandMarkers(appendCommandMarkerCache(commandMarkerScope, command))
|
||||
@@ -42,7 +49,7 @@ export function useNativeChatSlashCommandDispatched(args: {
|
||||
return
|
||||
}
|
||||
const reveal = (): void => {
|
||||
if (cancelled?.()) {
|
||||
if (!mountedRef.current || cancelled?.()) {
|
||||
return
|
||||
}
|
||||
onSwitchToTerminal?.(agent)
|
||||
|
||||
Reference in New Issue
Block a user