perf(ui-tui): freeze offscreen live tail during scroll

When the viewport is away from the bottom, keep the last visible progress snapshot instead of rebuilding the streaming/thinking subtree on every turn-store update. This cuts scroll-time churn while preserving live updates near the tail and on turn completion.
This commit is contained in:
Brooklyn Nicholson 2026-04-23 13:16:18 -05:00
parent 1e445b2547
commit c8ff70fe03

View file

@ -22,7 +22,7 @@ import type { Msg, PanelSection, SlashCatalog } from '../types.js'
import { createGatewayEventHandler } from './createGatewayEventHandler.js'
import { createSlashHandler } from './createSlashHandler.js'
import { type GatewayRpc, type TranscriptRow } from './interfaces.js'
import { type AppLayoutProgressProps, type GatewayRpc, type TranscriptRow } from './interfaces.js'
import { $overlayState, patchOverlayState } from './overlayStore.js'
import { turnController } from './turnController.js'
import { $turnState, patchTurnState } from './turnStore.js'
@ -658,11 +658,36 @@ export function useMainApp(gw: GatewayClient) {
[cols, composerActions, composerState, empty, pagerPageSize, submit]
)
const appProgress = useMemo(
const liveTailVisible = (() => {
const s = scrollRef.current
if (!s) {
return true
}
const top = Math.max(0, s.getScrollTop() + s.getPendingDelta())
const vp = Math.max(0, s.getViewportHeight())
const total = Math.max(vp, s.getScrollHeight())
return top + vp >= total - 3
})()
const liveProgress = useMemo<AppLayoutProgressProps>(
() => ({ ...turn, showProgressArea, showStreamingArea: Boolean(turn.streaming) }),
[turn, showProgressArea]
)
const frozenProgressRef = useRef(liveProgress)
// When the live tail is offscreen, freeze its snapshot so scroll work doesn't
// keep rebuilding the streaming/thinking subtree the user can't see. Thaw as
// soon as the viewport comes back near the bottom or the turn finishes.
if (liveTailVisible || !ui.busy) {
frozenProgressRef.current = liveProgress
}
const appProgress = liveTailVisible || !ui.busy ? liveProgress : frozenProgressRef.current
const cwd = ui.info?.cwd || process.env.HERMES_CWD || process.cwd()
const gitBranch = useGitBranch(cwd)