perf: isolate chat render caches by pane

This commit is contained in:
Shakker
2026-07-15 02:27:06 +01:00
committed by Shakker
parent 784c33be43
commit e8498d546d
3 changed files with 42 additions and 4 deletions
+26
View File
@@ -23,6 +23,7 @@ const SENDER_METADATA_BLOCK =
function createProps(overrides: Partial<CachedChatItemsProps> = {}): CachedChatItemsProps {
return {
paneId: "pane-a",
sessionKey: "main",
runId: null,
messages: [],
@@ -2454,6 +2455,31 @@ describe("thread item cache", () => {
}),
).not.toBe(first);
});
it("keeps same-session render caches isolated between panes", () => {
resetChatThreadState();
const messages = [
{ role: "assistant", content: "needle" },
{ role: "user", content: "other" },
];
const paneA = createProps({
paneId: "pane-a",
messages,
searchOpen: true,
searchQuery: "needle",
});
const paneB = createProps({ paneId: "pane-b", messages });
const paneAItems = buildCachedChatItems(paneA);
const paneBItems = buildCachedChatItems(paneB);
expect(buildCachedChatItems({ ...paneA })).toBe(paneAItems);
expect(buildCachedChatItems({ ...paneB })).toBe(paneBItems);
resetChatThreadState("pane-a");
expect(buildCachedChatItems({ ...paneA })).not.toBe(paneAItems);
expect(buildCachedChatItems({ ...paneB })).toBe(paneBItems);
});
});
function canvasBlocksIn(group: MessageGroup): unknown[] {
+14 -4
View File
@@ -46,6 +46,7 @@ import { getOrCreateSessionCacheValue } from "./session-cache.ts";
import { buildUserChatMessageContentBlocks } from "./user-message-content.ts";
type BuildChatItemsProps = {
paneId: string;
sessionKey: string;
runId?: string | null;
/** Invalidates cached display copy when the active UI language changes. */
@@ -77,13 +78,17 @@ type StreamRunRenderItem = {
parts: Array<Extract<ChatItem, { kind: "stream" } | { kind: "reading-indicator" }>>;
};
const chatItemsBySession = new Map<string, CachedChatItems>();
const chatItemsByPane = new Map<string, Map<string, CachedChatItems>>();
const expandedToolCardsBySession = new Map<string, Map<string, boolean>>();
const initializedToolCardsBySession = new Map<string, Set<string>>();
const lastAutoExpandPrefBySession = new Map<string, boolean>();
export function resetChatThreadState(): void {
chatItemsBySession.clear();
export function resetChatThreadState(paneId?: string): void {
if (paneId) {
chatItemsByPane.delete(paneId);
return;
}
chatItemsByPane.clear();
resetWorkingStartedAt();
expandedToolCardsBySession.clear();
initializedToolCardsBySession.clear();
@@ -1546,7 +1551,12 @@ function sameChatItemsInput(previous: BuildChatItemsProps, next: BuildChatItemsP
export function buildCachedChatItems(
input: BuildChatItemsProps,
): ReturnType<typeof buildChatItems> {
const cached = getOrCreateSessionCacheValue(chatItemsBySession, input.sessionKey, () => ({
let paneCache = chatItemsByPane.get(input.paneId);
if (!paneCache) {
paneCache = new Map();
chatItemsByPane.set(input.paneId, paneCache);
}
const cached = getOrCreateSessionCacheValue(paneCache, input.sessionKey, () => ({
input: null,
items: [],
}));
@@ -189,6 +189,7 @@ export function resetChatThreadPresentationState(paneId?: string) {
removeChatSelectionPopup();
if (paneId) {
threadStates.delete(paneId);
resetChatThreadState(paneId);
} else {
threadStates.clear();
resetChatThreadState();
@@ -598,6 +599,7 @@ export function renderChatThread(props: ChatThreadProps) {
const deleted = getDeletedMessages(props.sessionKey);
const locale = i18n.getLocale();
const chatItems = buildCachedChatItems({
paneId: props.paneId,
sessionKey: props.sessionKey,
runId:
props.sessions?.sessions.find((row) => areUiSessionKeysEquivalent(row.key, props.sessionKey))