perf: reduce UI render fanout and scroll jitter
- Cut broad render fanout across the app by replacing shared-store whole-object subscriptions with leaf selectors, memoizing hot chrome boundaries, and isolating disabled global providers from live session/message state. This keeps header controls, composer toolbars, side panels, and other non-hot UI surfaces from repainting on every assistant update or keystroke. - Rework sidebar session ordering so recent, project groups, and worktree groups derive from one ordering source while avoiding streaming-time thrash. The sidebar now uses a stabilized session snapshot, preserves structural identity for unchanged rows, reads live row status/details per session, and applies a one-shot sort bump on idle->busy instead of continuously resorting during activity. - Fix chat/input scroll instability by separating viewport-resize handling from message-growth handling, disabling conflicting native scroll anchoring, and stopping textarea autosize from collapsing on every growth keystroke. This removes the multiline typing jiggle during streaming and reduces unnecessary composer rerenders. - Also gate voice context wiring behind voice-mode enablement and codify the learned render/scroll/order anti-patterns in AGENTS.md so future changes avoid the same classes of regressions.
This commit is contained in:
@@ -66,6 +66,7 @@ type Props = {
|
||||
setRenameFolderDraft: React.Dispatch<React.SetStateAction<string>>;
|
||||
setRenamingFolderId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
pinnedSessionIds: Set<string>;
|
||||
sessionOrderIndex: Map<string, number>;
|
||||
prVisualStateByDirectoryBranch: Map<string, {
|
||||
visualState: 'draft' | 'open' | 'blocked' | 'merged' | 'closed';
|
||||
number: number;
|
||||
@@ -130,12 +131,24 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
setRenameFolderDraft,
|
||||
setRenamingFolderId,
|
||||
pinnedSessionIds,
|
||||
sessionOrderIndex,
|
||||
prVisualStateByDirectoryBranch,
|
||||
onToggleCollapsedGroup,
|
||||
dragHandleProps,
|
||||
compactBodyPadding = false,
|
||||
} = props;
|
||||
|
||||
const compareSessionNodes = React.useCallback((a: SessionNode, b: SessionNode) => {
|
||||
const aIndex = sessionOrderIndex.get(a.session.id);
|
||||
const bIndex = sessionOrderIndex.get(b.session.id);
|
||||
if (aIndex !== undefined || bIndex !== undefined) {
|
||||
if (aIndex === undefined) return 1;
|
||||
if (bIndex === undefined) return -1;
|
||||
if (aIndex !== bIndex) return aIndex - bIndex;
|
||||
}
|
||||
return compareSessionsByPinnedAndTime(a.session, b.session, pinnedSessionIds);
|
||||
}, [pinnedSessionIds, sessionOrderIndex]);
|
||||
|
||||
const searchData = hasSessionSearchQuery ? groupSearchDataByGroup.get(group) : null;
|
||||
const displayMode = useSessionDisplayStore((state) => state.displayMode);
|
||||
const isMinimalMode = displayMode === 'minimal';
|
||||
@@ -144,7 +157,11 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
const maxVisible = hideDirectoryControls ? 10 : 5;
|
||||
const groupMatchesSearch = hasSessionSearchQuery ? searchData?.groupMatches === true : false;
|
||||
const shouldFilterGroupContents = hasSessionSearchQuery;
|
||||
const sourceGroupNodes = shouldFilterGroupContents ? (searchData?.filteredNodes ?? []) : group.sessions;
|
||||
const sourceGroupNodes = React.useMemo(
|
||||
() => [...(shouldFilterGroupContents ? (searchData?.filteredNodes ?? []) : group.sessions)]
|
||||
.sort(compareSessionNodes),
|
||||
[compareSessionNodes, group.sessions, searchData?.filteredNodes, shouldFilterGroupContents],
|
||||
);
|
||||
const folderScopeKey = group.folderScopeKey ?? normalizePath(group.directory ?? null);
|
||||
const scopeFolders = folderScopeKey ? getFoldersForScope(folderScopeKey) : [];
|
||||
|
||||
@@ -163,7 +180,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
|
||||
const nodes = folder.sessionIds
|
||||
.map((sid) => nodeBySessionId.get(sid))
|
||||
.filter((n): n is SessionNode => Boolean(n))
|
||||
.sort((a, b) => compareSessionsByPinnedAndTime(a.session, b.session, pinnedSessionIds));
|
||||
.sort(compareSessionNodes);
|
||||
return { folder, nodes };
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user