perf(tui): stabilize long-session scrolling

This commit is contained in:
Brooklyn Nicholson 2026-04-26 01:47:05 -05:00
parent 59b56d445c
commit db4e4acca0
10 changed files with 195 additions and 105 deletions

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import { cursorLayout, offsetFromPosition } from '../components/textInput.js'
import { offsetFromPosition } from '../components/textInput.js'
import { cursorLayout, inputVisualHeight, stableComposerColumns } from '../lib/inputMetrics.js'
describe('cursorLayout — char-wrap parity with wrap-ansi', () => {
it('places cursor mid-line at its column', () => {
@ -35,6 +36,18 @@ describe('cursorLayout — char-wrap parity with wrap-ansi', () => {
})
})
describe('input metrics helpers', () => {
it('computes visual height from the wrapped cursor line', () => {
expect(inputVisualHeight('abcdefgh', 8)).toBe(2)
expect(inputVisualHeight('one\ntwo', 40)).toBe(2)
})
it('reserves a stable transcript scrollbar gutter for composer width', () => {
expect(stableComposerColumns(100, 3)).toBe(93)
expect(stableComposerColumns(10, 3)).toBe(20)
})
})
describe('offsetFromPosition — char-wrap inverse of cursorLayout', () => {
it('returns 0 for empty input', () => {
expect(offsetFromPosition('', 0, 0, 10)).toBe(0)

View file

@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest'
import { getViewportSnapshot, viewportSnapshotKey } from '../lib/viewportStore.js'
describe('viewportStore', () => {
it('normalizes absent scroll handles', () => {
expect(getViewportSnapshot(null)).toEqual({
atBottom: true,
bottom: 0,
pending: 0,
scrollHeight: 0,
top: 0,
viewportHeight: 0
})
})
it('includes pending scroll delta in snapshot math and keying', () => {
const handle = {
getPendingDelta: () => 3,
getScrollHeight: () => 40,
getScrollTop: () => 10,
getViewportHeight: () => 5,
isSticky: () => false
}
const snap = getViewportSnapshot(handle as any)
expect(snap).toMatchObject({ atBottom: false, bottom: 18, pending: 3, scrollHeight: 40, top: 13, viewportHeight: 5 })
expect(viewportSnapshotKey(snap)).toBe('0:13:5:40:3')
})
})