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:
@@ -463,11 +463,8 @@ export default class Ink {
|
|||||||
this.resetFramesForAltScreen()
|
this.resetFramesForAltScreen()
|
||||||
this.needsEraseBeforePaint = true
|
this.needsEraseBeforePaint = true
|
||||||
|
|
||||||
// Post-resize drift healer: 160ms after the last resize, force one full
|
// One last repaint after the resize burst settles closes any host-side
|
||||||
// reconcile so Yoga/React catch up to the final viewport and any stale
|
// reflow drift the normal diff path can't see.
|
||||||
// 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.
|
|
||||||
this.resizeSettleTimer = setTimeout(() => {
|
this.resizeSettleTimer = setTimeout(() => {
|
||||||
this.resizeSettleTimer = null
|
this.resizeSettleTimer = null
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('stickyPromptFromViewport', () => {
|
|||||||
|
|
||||||
const offsets = [0, 2, 10, 12, 20]
|
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', () => {
|
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]
|
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 { createGatewayEventHandler } from './createGatewayEventHandler.js'
|
||||||
import { createSlashHandler } from './createSlashHandler.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 { $overlayState, patchOverlayState } from './overlayStore.js'
|
||||||
import { turnController } from './turnController.js'
|
import { turnController } from './turnController.js'
|
||||||
import { $turnState, patchTurnState } from './turnStore.js'
|
import { $turnState, patchTurnState } from './turnStore.js'
|
||||||
@@ -672,16 +672,11 @@ export function useMainApp(gw: GatewayClient) {
|
|||||||
return top + vp >= total - 3
|
return top + vp >= total - 3
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const liveProgress = useMemo<AppLayoutProgressProps>(
|
const liveProgress = useMemo(() => ({ ...turn, showProgressArea, showStreamingArea: Boolean(turn.streaming) }), [turn, showProgressArea])
|
||||||
() => ({ ...turn, showProgressArea, showStreamingArea: Boolean(turn.streaming) }),
|
|
||||||
[turn, showProgressArea]
|
|
||||||
)
|
|
||||||
|
|
||||||
const frozenProgressRef = useRef(liveProgress)
|
const frozenProgressRef = useRef(liveProgress)
|
||||||
|
|
||||||
// When the live tail is offscreen, freeze its snapshot so scroll work doesn't
|
// Freeze the offscreen live tail so scroll doesn't rebuild unseen streaming UI.
|
||||||
// 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) {
|
if (liveTailVisible || !ui.busy) {
|
||||||
frozenProgressRef.current = liveProgress
|
frozenProgressRef.current = liveProgress
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,28 +249,15 @@ export function StickyPromptTracker({ messages, offsets, scrollRef, onChange }:
|
|||||||
useSyncExternalStore(
|
useSyncExternalStore(
|
||||||
useCallback((cb: () => void) => scrollRef.current?.subscribe(cb) ?? (() => {}), [scrollRef]),
|
useCallback((cb: () => void) => scrollRef.current?.subscribe(cb) ?? (() => {}), [scrollRef]),
|
||||||
() => {
|
() => {
|
||||||
const s = scrollRef.current
|
const { atBottom, top } = getStickyViewport(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
|
|
||||||
|
|
||||||
return atBottom ? -1 - top : top
|
return atBottom ? -1 - top : top
|
||||||
},
|
},
|
||||||
() => NaN
|
() => NaN
|
||||||
)
|
)
|
||||||
|
|
||||||
const s = scrollRef.current
|
const { atBottom, bottom, top } = getStickyViewport(scrollRef.current)
|
||||||
const top = Math.max(0, (s?.getScrollTop() ?? 0) + (s?.getPendingDelta() ?? 0))
|
const text = stickyPromptFromViewport(messages, offsets, top, bottom, atBottom)
|
||||||
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)
|
|
||||||
|
|
||||||
useEffect(() => onChange(text), [onChange, text])
|
useEffect(() => onChange(text), [onChange, text])
|
||||||
|
|
||||||
@@ -395,3 +382,15 @@ interface TranscriptScrollbarProps {
|
|||||||
scrollRef: RefObject<ScrollBoxHandle | null>
|
scrollRef: RefObject<ScrollBoxHandle | null>
|
||||||
t: Theme
|
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 = (
|
export const stickyPromptFromViewport = (
|
||||||
messages: readonly Msg[],
|
messages: readonly Msg[],
|
||||||
offsets: ArrayLike<number>,
|
offsets: ArrayLike<number>,
|
||||||
bottom: number,
|
|
||||||
top: number,
|
top: number,
|
||||||
|
bottom: number,
|
||||||
sticky: boolean
|
sticky: boolean
|
||||||
) => {
|
) => {
|
||||||
if (sticky || !messages.length) {
|
if (sticky || !messages.length) {
|
||||||
|
|||||||
Reference in New Issue
Block a user