Isolate branch switcher query from WorkspaceScreenContent to prevent 20ms re-renders

Move useBranchSwitcher into BranchSwitcher component so query state
changes only re-render the small header widget instead of the entire
workspace screen. Also memo-wrap AgentStatusBar to skip re-renders
on keystroke.
This commit is contained in:
Mohamed Boudra
2026-04-12 12:38:46 +07:00
parent cde10485d4
commit acb294b9eb
4 changed files with 43 additions and 37 deletions
@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef, useState } from "react";
import { memo, useCallback, useMemo, useRef, useState } from "react";
import { View, Text, Platform, Pressable, Keyboard } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useShallow } from "zustand/shallow";
@@ -848,7 +848,11 @@ function ControlledStatusBar({
const EMPTY_MODES: AgentMode[] = [];
export function AgentStatusBar({ agentId, serverId, onDropdownClose }: AgentStatusBarProps) {
export const AgentStatusBar = memo(function AgentStatusBar({
agentId,
serverId,
onDropdownClose,
}: AgentStatusBarProps) {
const { preferences, updatePreferences } = useFormPreferences();
const agent = useSessionStore(
useShallow((state) => {
@@ -1048,7 +1052,7 @@ export function AgentStatusBar({ agentId, serverId, onDropdownClose }: AgentStat
disabled={!client}
/>
);
}
});
export function DraftAgentStatusBar({
providerDefinitions,
+28 -11
View File
@@ -1,28 +1,45 @@
import { useRef } from "react";
import { Pressable, Text, View } from "react-native";
import { useQueryClient } from "@tanstack/react-query";
import { ChevronDown, GitBranch } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { useToast } from "@/contexts/toast-context";
import { useBranchSwitcher } from "@/hooks/use-branch-switcher";
interface BranchSwitcherProps {
currentBranchName: string | null;
title: string;
branchOptions: ComboboxOption[];
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onBranchSelect: (branchId: string) => void;
serverId: string;
workspaceId: string;
isGitCheckout: boolean;
}
export function BranchSwitcher({
currentBranchName,
title,
branchOptions,
isOpen,
onOpenChange,
onBranchSelect,
serverId,
workspaceId,
isGitCheckout,
}: BranchSwitcherProps) {
const { theme } = useUnistyles();
const anchorRef = useRef<View>(null);
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const toast = useToast();
const queryClient = useQueryClient();
const { branchOptions, isOpen, setIsOpen, handleBranchSelect } = useBranchSwitcher({
client,
normalizedServerId: serverId,
normalizedWorkspaceId: workspaceId,
currentBranchName,
isGitCheckout,
isConnected,
toast,
queryClient,
});
if (!currentBranchName) {
return (
@@ -36,7 +53,7 @@ export function BranchSwitcher({
<View ref={anchorRef} collapsable={false}>
<Pressable
testID="workspace-header-branch-switcher"
onPress={() => onOpenChange(true)}
onPress={() => setIsOpen(true)}
style={({ hovered, pressed }) => [
styles.branchSwitcherTrigger,
(hovered || pressed) && styles.branchSwitcherTriggerHovered,
@@ -53,14 +70,14 @@ export function BranchSwitcher({
<Combobox
options={branchOptions}
value={currentBranchName}
onSelect={onBranchSelect}
onSelect={handleBranchSelect}
searchable
placeholder="Switch branch..."
searchPlaceholder="Filter branches..."
emptyText="No branches found."
title="Switch branch"
open={isOpen}
onOpenChange={onOpenChange}
onOpenChange={setIsOpen}
anchorRef={anchorRef}
desktopPlacement="bottom-start"
desktopPreventInitialFlash
@@ -81,7 +81,6 @@ import type { ListTerminalsResponse } from "@server/shared/messages";
import { upsertTerminalListEntry } from "@/utils/terminal-list";
import { confirmDialog } from "@/utils/confirm-dialog";
import { useArchiveAgent } from "@/hooks/use-archive-agent";
import { useBranchSwitcher } from "@/hooks/use-branch-switcher";
import { useStableEvent } from "@/hooks/use-stable-event";
import { buildProviderCommand } from "@/utils/provider-command-templates";
import { generateDraftId } from "@/stores/draft-keys";
@@ -762,23 +761,6 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
? trimNonEmpty(checkoutQuery.data.currentBranch)
: null;
const {
branchOptions,
isOpen: isBranchSwitcherOpen,
setIsOpen: setIsBranchSwitcherOpen,
handleBranchSelect,
invalidateStashAndCheckout,
} = useBranchSwitcher({
client,
normalizedServerId,
normalizedWorkspaceId,
currentBranchName,
isGitCheckout,
isConnected,
toast,
queryClient,
});
const mobileView = usePanelStore((state) => state.mobileView);
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
@@ -1982,10 +1964,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
<BranchSwitcher
currentBranchName={currentBranchName}
title={workspaceHeader.title}
branchOptions={branchOptions}
isOpen={isBranchSwitcherOpen}
onOpenChange={setIsBranchSwitcherOpen}
onBranchSelect={handleBranchSelect}
serverId={normalizedServerId}
workspaceId={normalizedWorkspaceId}
isGitCheckout={isGitCheckout}
/>
<Text
testID="workspace-header-subtitle"
+5 -1
View File
@@ -5876,7 +5876,11 @@ export class Session {
};
const lastEmitted = subscription.lastEmittedByWorkspaceId.get(workspaceId);
if (lastEmitted && lastEmitted.kind === "upsert" && equal(lastEmitted.workspace, nextWorkspace)) {
if (
lastEmitted &&
lastEmitted.kind === "upsert" &&
equal(lastEmitted.workspace, nextWorkspace)
) {
continue;
}