mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(desktop): invalidate terminal overlay position on layout mutations
Signed-off-by: Ho Lim <subhoya@gmail.com>
This commit is contained in:
parent
7a5d534f5b
commit
5c8ac975e7
2 changed files with 65 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ vi.mock('./workspace', () => ({
|
|||
}))
|
||||
|
||||
let resizeObserverCallback: ResizeObserverCallback | null = null
|
||||
let mutationObserverCallback: MutationCallback | null = null
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
let windowStateCallback: ((payload: { isMinimized?: boolean; isVisible?: boolean }) => void) | null = null
|
||||
|
|
@ -125,6 +126,7 @@ describe('PersistentTerminal rect tracking', () => {
|
|||
setVisibility(false)
|
||||
installWindowStateBridge()
|
||||
resizeObserverCallback = null
|
||||
mutationObserverCallback = null
|
||||
vi.stubGlobal(
|
||||
'ResizeObserver',
|
||||
class {
|
||||
|
|
@ -137,6 +139,18 @@ describe('PersistentTerminal rect tracking', () => {
|
|||
unobserve = vi.fn()
|
||||
} as unknown as typeof ResizeObserver
|
||||
)
|
||||
vi.stubGlobal(
|
||||
'MutationObserver',
|
||||
class {
|
||||
constructor(callback: MutationCallback) {
|
||||
mutationObserverCallback = callback
|
||||
}
|
||||
|
||||
disconnect = vi.fn()
|
||||
observe = vi.fn()
|
||||
takeRecords = vi.fn(() => [])
|
||||
} as unknown as typeof MutationObserver
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -184,6 +198,43 @@ describe('PersistentTerminal rect tracking', () => {
|
|||
expect(raf.pending()).toBe(0)
|
||||
})
|
||||
|
||||
it('remeasures when layout moves the slot without resizing it', () => {
|
||||
const raf = installRaf()
|
||||
let currentRect = rect(10, 20, 200, 100)
|
||||
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(() => currentRect)
|
||||
|
||||
render(<Harness />)
|
||||
|
||||
act(() => {
|
||||
raf.runNext()
|
||||
})
|
||||
|
||||
const overlay = container!.lastElementChild as HTMLElement
|
||||
expect(overlay.style.top).toBe('10px')
|
||||
expect(overlay.style.left).toBe('20px')
|
||||
expect(raf.pending()).toBe(0)
|
||||
|
||||
currentRect = rect(32, 48, 200, 100)
|
||||
act(() => {
|
||||
mutationObserverCallback?.([], {} as MutationObserver)
|
||||
})
|
||||
|
||||
expect(raf.request).toHaveBeenCalledTimes(2)
|
||||
|
||||
act(() => {
|
||||
raf.runNext()
|
||||
})
|
||||
|
||||
expect(overlay.style.top).toBe('32px')
|
||||
expect(overlay.style.left).toBe('48px')
|
||||
|
||||
act(() => {
|
||||
raf.runNext()
|
||||
})
|
||||
|
||||
expect(raf.pending()).toBe(0)
|
||||
})
|
||||
|
||||
it('does not schedule rect RAFs while the Electron window is paused, then resumes when visible', () => {
|
||||
const raf = installRaf()
|
||||
vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue(rect(10, 20, 200, 100))
|
||||
|
|
|
|||
|
|
@ -153,11 +153,24 @@ export function PersistentTerminal({ onAddSelectionToChat }: PersistentTerminalP
|
|||
: new ResizeObserver(() => {
|
||||
scheduleMeasure()
|
||||
})
|
||||
const positionObserver =
|
||||
typeof MutationObserver === 'undefined'
|
||||
? null
|
||||
: new MutationObserver(() => {
|
||||
scheduleMeasure()
|
||||
})
|
||||
|
||||
if (measure()) {
|
||||
scheduleMeasure()
|
||||
}
|
||||
observer?.observe(slot)
|
||||
for (let node: HTMLElement | null = slot; node; node = node.parentElement) {
|
||||
positionObserver?.observe(node, {
|
||||
attributeFilter: ['class', 'style', 'hidden', 'aria-hidden', 'data-state'],
|
||||
attributes: true,
|
||||
childList: true
|
||||
})
|
||||
}
|
||||
window.addEventListener('resize', scheduleMeasure)
|
||||
window.addEventListener('scroll', scheduleMeasure, true)
|
||||
pauseController = createRendererLoopPauseController(handleVisibilityChange)
|
||||
|
|
@ -166,6 +179,7 @@ export function PersistentTerminal({ onAddSelectionToChat }: PersistentTerminalP
|
|||
stopped = true
|
||||
cancelFrame()
|
||||
observer?.disconnect()
|
||||
positionObserver?.disconnect()
|
||||
window.removeEventListener('resize', scheduleMeasure)
|
||||
window.removeEventListener('scroll', scheduleMeasure, true)
|
||||
pauseController?.dispose()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue