mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
Preserve pending terminal restore on fast hide
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -72,7 +72,11 @@ import {
|
||||
} from '@/lib/pane-manager/mobile-driver-state'
|
||||
import { resolvePaneKeyForManager } from '@/lib/pane-manager/pane-key-resolution'
|
||||
import { safeFit } from '@/lib/pane-manager/pane-tree-ops'
|
||||
import { captureScrollState, restoreScrollStateAfterLayout } from '@/lib/pane-manager/pane-scroll'
|
||||
import {
|
||||
captureScrollState,
|
||||
getPendingScrollRestoreState,
|
||||
restoreScrollStateAfterLayout
|
||||
} from '@/lib/pane-manager/pane-scroll'
|
||||
import {
|
||||
logTerminalScrollRestore,
|
||||
terminalScrollStateForDebug,
|
||||
@@ -730,7 +734,8 @@ export default function TerminalPane({
|
||||
const scrollStatesByLeafId = isVisibleRef.current
|
||||
? Object.fromEntries(
|
||||
currentPanes.map((pane) => {
|
||||
const { bufferType, wasAtBottom, viewportY, baseY } = captureScrollState(pane.terminal)
|
||||
const { bufferType, wasAtBottom, viewportY, baseY } =
|
||||
getPendingScrollRestoreState(pane.terminal) ?? captureScrollState(pane.terminal)
|
||||
const current = { bufferType, wasAtBottom, viewportY, baseY }
|
||||
const previous = lastVisibleScrollStatesByLeafIdRef.current?.[pane.leafId]
|
||||
const stable =
|
||||
@@ -861,7 +866,8 @@ export default function TerminalPane({
|
||||
}
|
||||
return Object.fromEntries(
|
||||
manager.getPanes().map((pane) => {
|
||||
const { bufferType, wasAtBottom, viewportY, baseY } = captureScrollState(pane.terminal)
|
||||
const { bufferType, wasAtBottom, viewportY, baseY } =
|
||||
getPendingScrollRestoreState(pane.terminal) ?? captureScrollState(pane.terminal)
|
||||
const current = { bufferType, wasAtBottom, viewportY, baseY }
|
||||
const previous = lastVisibleScrollStatesByLeafIdRef.current?.[pane.leafId]
|
||||
const stable =
|
||||
|
||||
@@ -15,6 +15,7 @@ const mocks = vi.hoisted(() => ({
|
||||
fitPanes: vi.fn(),
|
||||
focusActivePane: vi.fn(),
|
||||
flushTerminalOutput: vi.fn(),
|
||||
getPendingScrollRestoreState: vi.fn(() => null),
|
||||
getTerminalOutputEpoch: vi.fn(() => 0),
|
||||
handleTerminalFileDrop: vi.fn(),
|
||||
pasteTerminalText: vi.fn(),
|
||||
@@ -70,6 +71,7 @@ vi.mock('@/lib/pane-manager/pane-terminal-output-scheduler', () => ({
|
||||
|
||||
vi.mock('@/lib/pane-manager/pane-scroll', () => ({
|
||||
captureScrollState: mocks.captureScrollState,
|
||||
getPendingScrollRestoreState: mocks.getPendingScrollRestoreState,
|
||||
getTerminalOutputEpoch: mocks.getTerminalOutputEpoch,
|
||||
restoreScrollState: mocks.restoreScrollState,
|
||||
restoreScrollStateAfterLayout: mocks.restoreScrollStateAfterLayout
|
||||
|
||||
+38
@@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({
|
||||
baseY: 0
|
||||
})),
|
||||
flushTerminalOutput: vi.fn(),
|
||||
getPendingScrollRestoreState: vi.fn((): unknown => null),
|
||||
getTerminalOutputEpoch: vi.fn(() => 1),
|
||||
isTerminalScrollRestoreInProgress: vi.fn(() => false)
|
||||
}))
|
||||
@@ -66,6 +67,7 @@ vi.mock('@/lib/pane-manager/pane-terminal-output-scheduler', () => ({
|
||||
vi.mock('@/lib/pane-manager/pane-scroll', () => ({
|
||||
cancelDeferredScrollRestore: mocks.cancelDeferredScrollRestore,
|
||||
captureScrollState: mocks.captureScrollState,
|
||||
getPendingScrollRestoreState: mocks.getPendingScrollRestoreState,
|
||||
getTerminalOutputEpoch: mocks.getTerminalOutputEpoch,
|
||||
isTerminalScrollRestoreInProgress: mocks.isTerminalScrollRestoreInProgress
|
||||
}))
|
||||
@@ -78,6 +80,7 @@ describe('useTerminalScrollVisibilityMemory', () => {
|
||||
resetHookRefs()
|
||||
vi.clearAllMocks()
|
||||
mocks.captureScrollState.mockReset()
|
||||
mocks.getPendingScrollRestoreState.mockReset()
|
||||
mocks.getTerminalOutputEpoch.mockReset()
|
||||
mocks.isTerminalScrollRestoreInProgress.mockReset()
|
||||
mocks.captureScrollState.mockImplementation(() => ({
|
||||
@@ -86,6 +89,7 @@ describe('useTerminalScrollVisibilityMemory', () => {
|
||||
viewportY: 0,
|
||||
baseY: 0
|
||||
}))
|
||||
mocks.getPendingScrollRestoreState.mockImplementation(() => null)
|
||||
mocks.getTerminalOutputEpoch.mockImplementation(() => 1)
|
||||
mocks.isTerminalScrollRestoreInProgress.mockImplementation(() => false)
|
||||
})
|
||||
@@ -256,6 +260,40 @@ describe('useTerminalScrollVisibilityMemory', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('captures the pending restore target while a visibility restore is settling', () => {
|
||||
const terminal = {
|
||||
onScroll: vi.fn(() => ({ dispose: vi.fn() }))
|
||||
}
|
||||
const manager = {
|
||||
getPanes: vi.fn(() => [{ id: 1, leafId: 'leaf-1', terminal }])
|
||||
}
|
||||
const pendingRestoreState = {
|
||||
bufferType: 'normal',
|
||||
wasAtBottom: false,
|
||||
viewportY: 150,
|
||||
baseY: 154
|
||||
}
|
||||
mocks.getPendingScrollRestoreState.mockReturnValue(pendingRestoreState)
|
||||
mocks.captureScrollState.mockReturnValue({
|
||||
bufferType: 'normal',
|
||||
wasAtBottom: false,
|
||||
viewportY: 56,
|
||||
baseY: 154
|
||||
})
|
||||
|
||||
beginHookRender()
|
||||
const visibilityMemory = useTerminalScrollVisibilityMemory({
|
||||
managerRef: { current: manager as never },
|
||||
isVisibleRef: { current: true },
|
||||
visibleResumeCompleteRef: { current: true },
|
||||
paneCount: 1
|
||||
})
|
||||
|
||||
expect(visibilityMemory.captureViewportPositions(false).get('leaf-1' as never)).toBe(
|
||||
pendingRestoreState
|
||||
)
|
||||
})
|
||||
|
||||
it('does not reuse a remembered snapshot when a pane id gets a new leaf', () => {
|
||||
const terminal = {
|
||||
onScroll: vi.fn(() => ({ dispose: vi.fn() }))
|
||||
|
||||
@@ -4,6 +4,7 @@ import { flushTerminalOutput } from '@/lib/pane-manager/pane-terminal-output-sch
|
||||
import {
|
||||
cancelDeferredScrollRestore,
|
||||
captureScrollState,
|
||||
getPendingScrollRestoreState,
|
||||
getTerminalOutputEpoch,
|
||||
isTerminalScrollRestoreInProgress
|
||||
} from '@/lib/pane-manager/pane-scroll'
|
||||
@@ -84,7 +85,8 @@ export function useTerminalScrollVisibilityMemory({
|
||||
if (useRememberedSnapshots && remembered) {
|
||||
return [pane.leafId, remembered.scrollState] as const
|
||||
}
|
||||
const state = captureScrollState(pane.terminal)
|
||||
const state =
|
||||
getPendingScrollRestoreState(pane.terminal) ?? captureScrollState(pane.terminal)
|
||||
const stableState =
|
||||
remembered && isTransientTerminalEdgeSnap(state, remembered.scrollState)
|
||||
? remembered.scrollState
|
||||
|
||||
@@ -37,6 +37,10 @@ export function isTerminalScrollRestoreInProgress(terminal: Terminal): boolean {
|
||||
return (terminalScrollRestoreDepths.get(terminal) ?? 0) > 0
|
||||
}
|
||||
|
||||
export function getPendingScrollRestoreState(terminal: Terminal): ScrollState | null {
|
||||
return deferredScrollRestores.get(terminal)?.state ?? null
|
||||
}
|
||||
|
||||
export function cancelDeferredScrollRestore(terminal: Terminal): void {
|
||||
const pending = deferredScrollRestores.get(terminal)
|
||||
if (!pending) {
|
||||
|
||||
Reference in New Issue
Block a user