mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
chore: uptick
This commit is contained in:
parent
bedbeebbc8
commit
6a37802476
1 changed files with 63 additions and 17 deletions
|
|
@ -241,6 +241,15 @@ export default class Ink {
|
|||
x: number
|
||||
y: number
|
||||
} | null = null
|
||||
// Burst of SIGWINCH (vscode panel drag) → one React commit per
|
||||
// microtask. Dims are captured sync in handleResize; only the
|
||||
// expensive tree rebuild defers.
|
||||
private pendingResizeRender = false
|
||||
|
||||
// Fold synchronous re-entry (selection fanout, onFrame callback)
|
||||
// into one follow-up microtask instead of stacking renders.
|
||||
private isRendering = false
|
||||
private immediateRerenderRequested = false
|
||||
constructor(private readonly options: Options) {
|
||||
autoBind(this)
|
||||
|
||||
|
|
@ -402,12 +411,10 @@ export default class Ink {
|
|||
this.displayCursor = null
|
||||
}
|
||||
|
||||
// NOT debounced. A debounce opens a window where stdout.columns is NEW
|
||||
// but this.terminalColumns/Yoga are OLD — any scheduleRender during that
|
||||
// window (spinner, clock) makes log-update detect a width change and
|
||||
// clear the screen, then the debounce fires and clears again (double
|
||||
// blank→paint flicker). useVirtualScroll's height scaling already bounds
|
||||
// the per-resize cost; synchronous handling keeps dimensions consistent.
|
||||
// Dims captured sync — closes the stale-dim window the original
|
||||
// debounce rejection warned about. Expensive React commit defers to
|
||||
// one microtask per burst: vscode fires many SIGWINCHes per panel
|
||||
// drag, each ~80ms uncoalesced = event loop visibly locks up.
|
||||
private handleResize = () => {
|
||||
const cols = this.options.stdout.columns || 80
|
||||
const rows = this.options.stdout.rows || 24
|
||||
|
|
@ -423,6 +430,15 @@ export default class Ink {
|
|||
this.terminalRows = rows
|
||||
this.altScreenParkPatch = makeAltScreenParkPatch(this.terminalRows)
|
||||
|
||||
// Pending throttled/drain work captured stale dims — cancel so
|
||||
// the upcoming microtask owns the next frame.
|
||||
this.scheduleRender.cancel?.()
|
||||
|
||||
if (this.drainTimer !== null) {
|
||||
clearTimeout(this.drainTimer)
|
||||
this.drainTimer = null
|
||||
}
|
||||
|
||||
// Alt screen: reset frame buffers so the next render repaints from
|
||||
// scratch (prevFrameContaminated → every cell written, wrapped in
|
||||
// BSU/ESU — old content stays visible until the new frame swaps
|
||||
|
|
@ -442,14 +458,24 @@ export default class Ink {
|
|||
this.needsEraseBeforePaint = true
|
||||
}
|
||||
|
||||
// Re-render the React tree with updated props so the context value changes.
|
||||
// React's commit phase will call onComputeLayout() to recalculate yoga layout
|
||||
// with the new dimensions, then call onRender() to render the updated frame.
|
||||
// We don't call scheduleRender() here because that would render before the
|
||||
// layout is updated, causing a mismatch between viewport and content dimensions.
|
||||
if (this.currentNode !== null) {
|
||||
this.render(this.currentNode)
|
||||
// Already queued: later events in this burst updated dims/alt-screen
|
||||
// prep above; the queued render picks up the latest values when it
|
||||
// fires (React commit → onComputeLayout → scheduleRender → onRender).
|
||||
if (this.pendingResizeRender) {
|
||||
return
|
||||
}
|
||||
|
||||
this.pendingResizeRender = true
|
||||
|
||||
queueMicrotask(() => {
|
||||
this.pendingResizeRender = false
|
||||
|
||||
if (this.isUnmounted || this.currentNode === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.render(this.currentNode)
|
||||
})
|
||||
}
|
||||
resolveExitPromise: () => void = () => {}
|
||||
rejectExitPromise: (reason?: Error) => void = () => {}
|
||||
|
|
@ -536,6 +562,17 @@ export default class Ink {
|
|||
return
|
||||
}
|
||||
|
||||
// Fold synchronous re-entry (selection fanout, onFrame callback)
|
||||
// into one follow-up microtask — back-to-back renders within one
|
||||
// macrotask were the freeze multiplier.
|
||||
if (this.isRendering) {
|
||||
this.immediateRerenderRequested = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.isRendering = true
|
||||
|
||||
// Entering a render cancels any pending drain tick — this render will
|
||||
// handle the drain (and re-schedule below if needed). Prevents a
|
||||
// wheel-event-triggered render AND a drain-timer render both firing.
|
||||
|
|
@ -906,10 +943,12 @@ export default class Ink {
|
|||
// are only ever true in alt-screen; in main-screen this is false→false.
|
||||
this.prevFrameContaminated = selActive || hlActive || !!frame.absoluteOverlayMoved
|
||||
|
||||
// Schedule corrective frame for scroll drain or absolute overlay resize.
|
||||
// Plain timeout instead of scheduleRender to avoid double-render from
|
||||
// lodash throttle's leadingEdge firing inside a trailing invocation.
|
||||
if (frame.scrollDrainPending || frame.absoluteOverlayMoved) {
|
||||
// Plain setTimeout (not scheduleRender) — lodash throttle's leading
|
||||
// edge would fire inside this trailing invocation and double-render.
|
||||
// Scroll drain only; absolute-overlay movement rides prevFrameContaminated
|
||||
// into the next natural render. Routing it here made caret re-layout a
|
||||
// 250fps self-oscillator that locked the event loop after resize.
|
||||
if (frame.scrollDrainPending) {
|
||||
this.drainTimer = setTimeout(() => this.onRender(), FRAME_INTERVAL_MS >> 2)
|
||||
}
|
||||
|
||||
|
|
@ -942,6 +981,13 @@ export default class Ink {
|
|||
},
|
||||
flickers
|
||||
})
|
||||
|
||||
this.isRendering = false
|
||||
|
||||
if (this.immediateRerenderRequested) {
|
||||
this.immediateRerenderRequested = false
|
||||
queueMicrotask(() => this.onRender())
|
||||
}
|
||||
}
|
||||
pause(): void {
|
||||
// Flush pending React updates and render before pausing.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue