mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-13 03:52:00 +00:00
fix(tui): avoid main-screen scrollback reset loops
This commit is contained in:
parent
31f22890ea
commit
a494a614d0
2 changed files with 59 additions and 4 deletions
|
|
@ -30,10 +30,10 @@ const paint = (screen: Screen, y: number, text: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mkFrame = (screen: Screen, viewportW: number, viewportH: number): Frame => ({
|
const mkFrame = (screen: Screen, viewportW: number, viewportH: number, cursorY = 0): Frame => ({
|
||||||
screen,
|
screen,
|
||||||
viewport: { width: viewportW, height: viewportH },
|
viewport: { width: viewportW, height: viewportH },
|
||||||
cursor: { x: 0, y: 0, visible: true }
|
cursor: { x: 0, y: cursorY, visible: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
const stdoutOnly = (diff: ReturnType<LogUpdate['render']>) =>
|
const stdoutOnly = (diff: ReturnType<LogUpdate['render']>) =>
|
||||||
|
|
@ -112,4 +112,46 @@ describe('LogUpdate.render diff contract', () => {
|
||||||
expect(stdoutOnly(diff)).toBe('')
|
expect(stdoutOnly(diff)).toBe('')
|
||||||
expect(diff.some(p => p.type === 'clearTerminal')).toBe(false)
|
expect(diff.some(p => p.type === 'clearTerminal')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('ignores main-screen scrollback-only changes instead of resetting repeatedly', () => {
|
||||||
|
const w = 20
|
||||||
|
const viewportH = 5
|
||||||
|
const h = 8
|
||||||
|
|
||||||
|
const prev = mkScreen(w, h)
|
||||||
|
paint(prev, 0, 'timer 1s')
|
||||||
|
paint(prev, 6, 'visible prompt')
|
||||||
|
|
||||||
|
const next = mkScreen(w, h)
|
||||||
|
paint(next, 0, 'timer 2s')
|
||||||
|
paint(next, 6, 'visible prompt')
|
||||||
|
next.damage = { x: 0, y: 0, width: w, height: h }
|
||||||
|
|
||||||
|
const log = new LogUpdate({ isTTY: true, stylePool })
|
||||||
|
const diff = log.render(mkFrame(prev, w, viewportH, h), mkFrame(next, w, viewportH, h), false, false)
|
||||||
|
|
||||||
|
expect(diff.some(p => p.type === 'clearTerminal')).toBe(false)
|
||||||
|
expect(stdoutOnly(diff)).not.toContain('timer2s')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps alt-screen full reset for unreachable scrollback row changes', () => {
|
||||||
|
const w = 20
|
||||||
|
const viewportH = 5
|
||||||
|
const h = 8
|
||||||
|
|
||||||
|
const prev = mkScreen(w, h)
|
||||||
|
paint(prev, 0, 'timer 1s')
|
||||||
|
paint(prev, 6, 'visible prompt')
|
||||||
|
|
||||||
|
const next = mkScreen(w, h)
|
||||||
|
paint(next, 0, 'timer 2s')
|
||||||
|
paint(next, 6, 'visible prompt')
|
||||||
|
next.damage = { x: 0, y: 0, width: w, height: h }
|
||||||
|
|
||||||
|
const log = new LogUpdate({ isTTY: true, stylePool })
|
||||||
|
const diff = log.render(mkFrame(prev, w, viewportH, h), mkFrame(next, w, viewportH, h), true, false)
|
||||||
|
|
||||||
|
expect(diff.some(p => p.type === 'clearTerminal')).toBe(true)
|
||||||
|
expect(stdoutOnly(diff)).toContain('timer2s')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,13 @@ export class LogUpdate {
|
||||||
return fullResetSequence_CAUSES_FLICKER(next, 'offscreen', stylePool)
|
return fullResetSequence_CAUSES_FLICKER(next, 'offscreen', stylePool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prev.screen.height >= prev.viewport.height && prev.screen.height > 0 && cursorAtBottom && !isGrowing) {
|
if (
|
||||||
|
altScreen &&
|
||||||
|
prev.screen.height >= prev.viewport.height &&
|
||||||
|
prev.screen.height > 0 &&
|
||||||
|
cursorAtBottom &&
|
||||||
|
!isGrowing
|
||||||
|
) {
|
||||||
// viewportY = rows in scrollback from content overflow
|
// viewportY = rows in scrollback from content overflow
|
||||||
// +1 for the row pushed by cursor-restore scroll
|
// +1 for the row pushed by cursor-restore scroll
|
||||||
const viewportY = prev.screen.height - prev.viewport.height
|
const viewportY = prev.screen.height - prev.viewport.height
|
||||||
|
|
@ -330,8 +336,15 @@ export class LogUpdate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the cell outside the viewport range has changed, we need to reset
|
// If the cell outside the viewport range has changed, we need to reset
|
||||||
// because we can't move the cursor there to draw.
|
// because we can't move the cursor there to draw. In main-screen mode,
|
||||||
|
// those rows are already in terminal scrollback and invisible; resetting
|
||||||
|
// on every scrollback-only update can loop when a resize changes the
|
||||||
|
// physical buffer. Shrink-to-visible cases are handled above.
|
||||||
if (y < viewportY) {
|
if (y < viewportY) {
|
||||||
|
if (!altScreen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
needsFullReset = true
|
needsFullReset = true
|
||||||
resetTriggerY = y
|
resetTriggerY = y
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue