mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
Merge pull request #3194 from deepseek-harness/perf/turn-navigator-memo
fix(client): stop rebuilding the turn rail on every chat render
This commit is contained in:
@@ -498,7 +498,8 @@ export function ChatView({
|
||||
loadOlder()
|
||||
}
|
||||
|
||||
const navigateToTurn = (item: TurnNavigationItem): void => {
|
||||
// Identity feeds the memoized rail; a fresh closure per render would defeat it.
|
||||
const navigateToTurn = useCallback((item: TurnNavigationItem): void => {
|
||||
const local = listRef.current
|
||||
if (local === null) return
|
||||
const row = anchorElement(local, item.anchorKey)
|
||||
@@ -519,7 +520,7 @@ export function ChatView({
|
||||
const position = isAtBottom ? null : scrollPosition(local, el)
|
||||
if (isAtBottom) chatScroll.save(null)
|
||||
else if (position !== null) chatScroll.save(position)
|
||||
}
|
||||
}, [loadingOlder, chatScroll])
|
||||
|
||||
return (
|
||||
<div className={css.root}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
useId, useState, type CSSProperties, type MouseEvent, type PointerEvent,
|
||||
memo, useId, useState, type CSSProperties, type MouseEvent, type PointerEvent,
|
||||
} from 'react'
|
||||
import type { ChatViewSlotProps } from '../contract/slots.ts'
|
||||
import type { TurnNavigationItem } from '../contract/snapshot.ts'
|
||||
@@ -53,8 +53,7 @@ function itemAtPointer(
|
||||
return items[Math.round(ratio * (items.length - 1))]
|
||||
}
|
||||
|
||||
/** Compact rail of the currently loaded Turns with hover and focus previews. */
|
||||
export function TurnNavigator({ items, activeTurn, onNavigate, t }: TurnNavigatorProps) {
|
||||
function TurnNavigatorRail({ items, activeTurn, onNavigate, t }: TurnNavigatorProps) {
|
||||
const [previewTurn, setPreviewTurn] = useState<number | null>(null)
|
||||
const previewId = useId()
|
||||
if (items.length < 2) return null
|
||||
@@ -116,3 +115,14 @@ export function TurnNavigator({ items, activeTurn, onNavigate, t }: TurnNavigato
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact rail of the currently loaded Turns with hover and focus previews.
|
||||
*
|
||||
* Memoized because it renders two host elements per loaded Turn while the
|
||||
* enclosing view re-renders on every streaming delta: without the guard a long
|
||||
* session rebuilds hundreds of marks per commit for a rail that only changes
|
||||
* when a Turn is added, removed, or becomes active. Its props must therefore
|
||||
* stay referentially stable across those commits.
|
||||
*/
|
||||
export const TurnNavigator = memo(TurnNavigatorRail)
|
||||
|
||||
@@ -412,6 +412,34 @@ describe('Chat node rendering', () => {
|
||||
})
|
||||
|
||||
describe('ChatView', () => {
|
||||
it('leaves the turn rail unrendered when an unrelated Chat update commits', () => {
|
||||
const snapshot = chatSnapshotFixture({
|
||||
nodes: [
|
||||
userInTurn(1, 'first prompt', 1),
|
||||
assistant(2, 'first response', 1),
|
||||
userInTurn(4, 'second prompt', 2),
|
||||
assistant(5, 'second response', 2),
|
||||
],
|
||||
turnEnds: new Map([[1, 3], [2, 6]]),
|
||||
})
|
||||
const h = makeHarness({}, {}, snapshot)
|
||||
// The rail asks for its own accessible name once per render, so counting
|
||||
// that key counts renders without reaching into the component.
|
||||
let railRenders = 0
|
||||
const translate = h.props.t
|
||||
const counting = ((key: string, vars?: Record<string, unknown>) => {
|
||||
if (key === 'chat.turnNavigation.label') railRenders += 1
|
||||
return (translate as (k: string, v?: Record<string, unknown>) => string)(key, vars)
|
||||
}) as ChatViewSlotProps['t']
|
||||
render(<h.ChatView {...h.props} t={counting} />)
|
||||
const afterMount = railRenders
|
||||
expect(afterMount).toBeGreaterThan(0)
|
||||
|
||||
act(() => { h.setSelection({ turnSeq: 3, callId: 'a', toolName: 'bash' }) })
|
||||
|
||||
expect(railRenders).toBe(afterMount)
|
||||
})
|
||||
|
||||
it('projects loaded turns into prompt and response navigation previews', () => {
|
||||
const snapshot = chatSnapshotFixture({
|
||||
nodes: [
|
||||
|
||||
Reference in New Issue
Block a user