refactor(ui-tui): clean touched resize and sticky prompt paths
Trim comment noise, remove redundant typing, normalize sticky prompt viewport args to top→bottom order, and reuse one sticky viewport helper instead of duplicating the math.
This commit is contained in:
parent
9a885fba31
commit
9bf6e1cd6e
@ -463,11 +463,8 @@ export default class Ink {
|
||||
this.resetFramesForAltScreen()
|
||||
this.needsEraseBeforePaint = true
|
||||
|
||||
// Post-resize drift healer: 160ms after the last resize, force one full
|
||||
// reconcile so Yoga/React catch up to the final viewport and any stale
|
||||
// terminal cells from host-side reflow get repainted away. Ink upstream
|
||||
// and ConPTY/xterm reports point to this as a general resize/reflow
|
||||
// desync class, not an xterm.js-only quirk.
|
||||
// One last repaint after the resize burst settles closes any host-side
|
||||
// reflow drift the normal diff path can't see.
|
||||
this.resizeSettleTimer = setTimeout(() => {
|
||||
this.resizeSettleTimer = null
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ describe('stickyPromptFromViewport', () => {
|
||||
|
||||
const offsets = [0, 2, 10, 12, 20]
|
||||
|
||||
expect(stickyPromptFromViewport(messages, offsets, 16, 8, false)).toBe('')
|
||||
expect(stickyPromptFromViewport(messages, offsets, 8, 16, false)).toBe('')
|
||||
})
|
||||
|
||||
it('shows the latest user message above the viewport when no user message is visible', () => {
|
||||
@ -26,6 +26,6 @@ describe('stickyPromptFromViewport', () => {
|
||||
|
||||
const offsets = [0, 2, 10, 12, 20]
|
||||
|
||||
expect(stickyPromptFromViewport(messages, offsets, 20, 16, false)).toBe('current prompt')
|
||||
expect(stickyPromptFromViewport(messages, offsets, 16, 20, false)).toBe('current prompt')
|
||||
})
|
||||
})
|
||||
|
||||
@ -22,7 +22,7 @@ import type { Msg, PanelSection, SlashCatalog } from '../types.js'
|
||||
|
||||
import { createGatewayEventHandler } from './createGatewayEventHandler.js'
|
||||
import { createSlashHandler } from './createSlashHandler.js'
|
||||
import { type AppLayoutProgressProps, type GatewayRpc, type TranscriptRow } from './interfaces.js'
|
||||
import { type GatewayRpc, type TranscriptRow } from './interfaces.js'
|
||||
import { $overlayState, patchOverlayState } from './overlayStore.js'
|
||||
import { turnController } from './turnController.js'
|
||||
import { $turnState, patchTurnState } from './turnStore.js'
|
||||
@ -672,16 +672,11 @@ export function useMainApp(gw: GatewayClient) {
|
||||
return top + vp >= total - 3
|
||||
})()
|
||||
|
||||
const liveProgress = useMemo<AppLayoutProgressProps>(
|
||||
() => ({ ...turn, showProgressArea, showStreamingArea: Boolean(turn.streaming) }),
|
||||
[turn, showProgressArea]
|
||||
)
|
||||
const liveProgress = useMemo(() => ({ ...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.
|
||||
// Freeze the offscreen live tail so scroll doesn't rebuild unseen streaming UI.
|
||||
if (liveTailVisible || !ui.busy) {
|
||||
frozenProgressRef.current = liveProgress
|
||||
}
|
||||
|
||||
@ -249,28 +249,15 @@ export function StickyPromptTracker({ messages, offsets, scrollRef, onChange }:
|
||||
useSyncExternalStore(
|
||||
useCallback((cb: () => void) => scrollRef.current?.subscribe(cb) ?? (() => {}), [scrollRef]),
|
||||
() => {
|
||||
const s = scrollRef.current
|
||||
|
||||
if (!s) {
|
||||
return NaN
|
||||
}
|
||||
|
||||
const top = Math.max(0, s.getScrollTop() + s.getPendingDelta())
|
||||
const vp = Math.max(0, s.getViewportHeight())
|
||||
const total = Math.max(vp, s.getScrollHeight())
|
||||
const atBottom = s.isSticky() || top + vp >= total - 2
|
||||
const { atBottom, top } = getStickyViewport(scrollRef.current)
|
||||
|
||||
return atBottom ? -1 - top : top
|
||||
},
|
||||
() => NaN
|
||||
)
|
||||
|
||||
const s = scrollRef.current
|
||||
const top = Math.max(0, (s?.getScrollTop() ?? 0) + (s?.getPendingDelta() ?? 0))
|
||||
const vp = Math.max(0, s?.getViewportHeight() ?? 0)
|
||||
const total = Math.max(vp, s?.getScrollHeight() ?? vp)
|
||||
const atBottom = (s?.isSticky() ?? true) || top + vp >= total - 2
|
||||
const text = stickyPromptFromViewport(messages, offsets, top + vp, top, atBottom)
|
||||
const { atBottom, bottom, top } = getStickyViewport(scrollRef.current)
|
||||
const text = stickyPromptFromViewport(messages, offsets, top, bottom, atBottom)
|
||||
|
||||
useEffect(() => onChange(text), [onChange, text])
|
||||
|
||||
@ -395,3 +382,15 @@ interface TranscriptScrollbarProps {
|
||||
scrollRef: RefObject<ScrollBoxHandle | null>
|
||||
t: Theme
|
||||
}
|
||||
|
||||
function getStickyViewport(s?: ScrollBoxHandle | null) {
|
||||
const top = Math.max(0, (s?.getScrollTop() ?? 0) + (s?.getPendingDelta() ?? 0))
|
||||
const vp = Math.max(0, s?.getViewportHeight() ?? 0)
|
||||
const total = Math.max(vp, s?.getScrollHeight() ?? vp)
|
||||
|
||||
return {
|
||||
atBottom: (s?.isSticky() ?? true) || top + vp >= total - 2,
|
||||
bottom: top + vp,
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@ const upperBound = (offsets: ArrayLike<number>, target: number) => {
|
||||
export const stickyPromptFromViewport = (
|
||||
messages: readonly Msg[],
|
||||
offsets: ArrayLike<number>,
|
||||
bottom: number,
|
||||
top: number,
|
||||
bottom: number,
|
||||
sticky: boolean
|
||||
) => {
|
||||
if (sticky || !messages.length) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user