fix(chat): complete initial placement immediately after history refresh settles (#3918)

This commit is contained in:
Carlos Florêncio
2026-09-25 10:28:12 +01:00
committed by GitHub
parent b88aea31ad
commit e58807d5a9
2 changed files with 40 additions and 20 deletions
@@ -1543,6 +1543,21 @@ type InitialScrollPositionEffectParams = Omit<InitialScrollPositionParams, "plac
activationPendingRef: React.RefObject<boolean>;
};
function handleEmptyTranscriptPlacement(params: {
hasEmptyTranscript: boolean;
envSwitchPlacementToken: number | null;
historyRefreshPending: boolean;
didInitialScroll: React.RefObject<boolean>;
activationPendingRef: React.RefObject<boolean>;
}): boolean {
if (!params.hasEmptyTranscript) return false;
if (params.envSwitchPlacementToken !== null && !params.historyRefreshPending) {
markInitialScrollConsumed(params.didInitialScroll, params.activationPendingRef);
completeEnvSwitchPlacement(params.envSwitchPlacementToken);
}
return true;
}
function runInitialScrollPositionEffect(
params: InitialScrollPositionEffectParams,
): void | (() => void) {
@@ -1565,13 +1580,13 @@ function runInitialScrollPositionEffect(
activationPendingRef,
} = params;
if (!isVisible || (envSwitchPlacementToken !== null && isRestoringLayout)) return;
const hasEmptyTranscript = itemCount === 0;
if (envSwitchPlacementToken !== null && hasEmptyTranscript) {
if (historyRefreshPending) return;
markInitialScrollConsumed(didInitialScroll, activationPendingRef);
completeEnvSwitchPlacement(envSwitchPlacementToken);
return;
}
const hasEmptyTranscript = handleEmptyTranscriptPlacement({
hasEmptyTranscript: itemCount === 0,
envSwitchPlacementToken,
historyRefreshPending,
didInitialScroll,
activationPendingRef,
});
if (didInitialScroll.current || hasEmptyTranscript) return;
const element = scrollRef.current;
if (!element) return;
@@ -1605,7 +1620,11 @@ function runInitialScrollPositionEffect(
if (phase === "provisional") provisionalTokenRef.current = envSwitchPlacementToken;
};
if (activationPendingRef.current) return scheduleAfterPanelRestore(applyInitialScroll);
const isEnvSwitchSettling =
envSwitchPlacementToken !== null && provisionalTokenRef.current === envSwitchPlacementToken;
if (activationPendingRef.current && !isEnvSwitchSettling) {
return scheduleAfterPanelRestore(applyInitialScroll);
}
applyInitialScroll();
}
@@ -86,6 +86,7 @@ const HARNESS_RENDER_ERROR = "harness did not render";
const NATIVE_SCROLL_MANAGEMENT_TEST_ID = "native-scroll-management-container";
const AUTO_SCROLL_CONTAINER_TEST_ID = "auto-scroll-container";
const CACHED_MESSAGE_ID = "cached-message";
const SETTLED_MESSAGE_ID = "settled-message";
const TEST_MESSAGES = [{} as Message];
/** Always returns false: the harness never locks programmatic scrolling. */
const NEVER_LOCKED = () => false;
@@ -412,18 +413,18 @@ describe("useNativeScrollManagement transcript pagination", () => {
metrics.scrollHeight = 1400;
rerender(
<NativeScrollManagementHarness
items={[transcriptMessage("settled-message")]}
items={[transcriptMessage(SETTLED_MESSAGE_ID)]}
metrics={metrics}
sessionId="session-b"
enabled
/>,
);
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
expect(scrollContainer.scrollTop).toBe(1400);
expect(mockDockviewState.pendingChatInitialPlacement).toBeNull();
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
} finally {
vi.unstubAllGlobals();
}
@@ -476,17 +477,17 @@ describe("useNativeScrollManagement transcript pagination", () => {
metrics.scrollTop = 915;
rerender(
<NativeScrollManagementHarness
items={[transcriptMessage("settled-message")]}
items={[transcriptMessage(SETTLED_MESSAGE_ID)]}
metrics={metrics}
sessionId="session-b"
/>,
);
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
expect(scrollContainer.scrollTop).toBe(320);
expect(scrollContainer.scrollTop).not.toBe(810);
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
} finally {
vi.unstubAllGlobals();
}
@@ -556,19 +557,19 @@ describe("useNativeScrollManagement transcript pagination", () => {
metrics.scrollTop = 480;
rerender(
<NativeScrollManagementHarness
items={[transcriptMessage("settled-message")]}
items={[transcriptMessage(SETTLED_MESSAGE_ID)]}
metrics={metrics}
sessionId="session-b"
enabled
hasUnreadDivider
/>,
);
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
expect(metrics.scrollTop).toBe(480);
expect(mockDockviewState.pendingChatInitialPlacement).toBeNull();
act(() => {
for (let frame = frames.shift(); frame; frame = frames.shift()) frame(0);
});
} finally {
vi.unstubAllGlobals();
}