fix(tui): preserve compaction indicator ownership
This commit is contained in:
@@ -1524,11 +1524,13 @@ export function createTuiChat(
|
|||||||
// live durable bracket can announce its in-flight state without mistaking
|
// live durable bracket can announce its in-flight state without mistaking
|
||||||
// a stale resumed orphan for current work.
|
// a stale resumed orphan for current work.
|
||||||
if (event.type === 'compact/start' && event.data.turn === null) {
|
if (event.type === 'compact/start' && event.data.turn === null) {
|
||||||
compacting = {
|
if (compacting === undefined) {
|
||||||
startedAt: now(),
|
compacting = {
|
||||||
timer: setInterval(renderStatus, STATUS_ANIMATION_INTERVAL_MS),
|
startedAt: now(),
|
||||||
|
timer: setInterval(renderStatus, STATUS_ANIMATION_INTERVAL_MS),
|
||||||
|
}
|
||||||
|
runtime.terminal.setProgress(true)
|
||||||
}
|
}
|
||||||
runtime.terminal.setProgress(true)
|
|
||||||
requestRender()
|
requestRender()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1538,7 +1540,9 @@ export function createTuiChat(
|
|||||||
if (event.data.error !== undefined) {
|
if (event.data.error !== undefined) {
|
||||||
appendNotice(`Compaction failed: ${event.data.error}`, 'warning')
|
appendNotice(`Compaction failed: ${event.data.error}`, 'warning')
|
||||||
}
|
}
|
||||||
beginFadeOut(COMPACTING_GLYPH)
|
// A concurrently running turn owns the indicator. Keep its timer and
|
||||||
|
// progress bit instead of letting the compaction fade clear that state.
|
||||||
|
if (runningStatus === undefined) beginFadeOut(COMPACTING_GLYPH)
|
||||||
requestRender()
|
requestRender()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2075,9 +2075,52 @@ describe('pi-tui chat lifecycle and transcript', () => {
|
|||||||
|
|
||||||
expect(result.terminal.output).toContain('dsh ◍ ')
|
expect(result.terminal.output).toContain('dsh ◍ ')
|
||||||
expect(result.terminal.output).not.toContain('dsh ⊙ ')
|
expect(result.terminal.output).not.toContain('dsh ⊙ ')
|
||||||
|
result.session.append('compact/end', { turn: null })
|
||||||
|
await tick()
|
||||||
|
result.terminal.output = ''
|
||||||
|
result.terminal.resize(result.terminal.columns + 1)
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
expect(result.terminal.output).toContain('dsh ◍ ')
|
||||||
|
expect(result.terminal.output).not.toContain('dsh ⊙ ')
|
||||||
|
expect(result.terminal.progress.at(-1)).toBe(true)
|
||||||
await dispose(result)
|
await dispose(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('treats duplicate live compaction starts as one owned bracket', async () => {
|
||||||
|
const intervalSpy = vi.spyOn(globalThis, 'setInterval')
|
||||||
|
const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval')
|
||||||
|
let result: Awaited<ReturnType<typeof setup>> | undefined
|
||||||
|
let didDispose = false
|
||||||
|
let clock = 0
|
||||||
|
try {
|
||||||
|
result = await setup({ omitInitialLifecycle: true, now: () => clock })
|
||||||
|
intervalSpy.mockClear()
|
||||||
|
clearIntervalSpy.mockClear()
|
||||||
|
result.session.append('compact/start', { turn: null })
|
||||||
|
clock = 1_000
|
||||||
|
result.terminal.output = ''
|
||||||
|
result.session.append('compact/start', { turn: null })
|
||||||
|
await tick()
|
||||||
|
|
||||||
|
expect(intervalSpy).toHaveBeenCalledOnce()
|
||||||
|
expect(result.terminal.output).toContain('dsh ⊙ ')
|
||||||
|
expect(result.terminal.progress.at(-1)).toBe(true)
|
||||||
|
|
||||||
|
result.session.append('compact/end', { turn: null })
|
||||||
|
await tick()
|
||||||
|
expect(clearIntervalSpy).toHaveBeenCalledOnce()
|
||||||
|
expect(result.terminal.progress.at(-1)).toBe(false)
|
||||||
|
|
||||||
|
await dispose(result)
|
||||||
|
didDispose = true
|
||||||
|
} finally {
|
||||||
|
if (result !== undefined && !didDispose) await dispose(result)
|
||||||
|
intervalSpy.mockRestore()
|
||||||
|
clearIntervalSpy.mockRestore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('does not show compaction progress for a resumed orphaned start', async () => {
|
it('does not show compaction progress for a resumed orphaned start', async () => {
|
||||||
const result = await setup({
|
const result = await setup({
|
||||||
omitInitialLifecycle: true,
|
omitInitialLifecycle: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user