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:
@@ -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 { View, Text, Platform, Pressable, Keyboard } from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
@@ -848,7 +848,11 @@ function ControlledStatusBar({
|
|||||||
|
|
||||||
const EMPTY_MODES: AgentMode[] = [];
|
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 { preferences, updatePreferences } = useFormPreferences();
|
||||||
const agent = useSessionStore(
|
const agent = useSessionStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
@@ -1048,7 +1052,7 @@ export function AgentStatusBar({ agentId, serverId, onDropdownClose }: AgentStat
|
|||||||
disabled={!client}
|
disabled={!client}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
export function DraftAgentStatusBar({
|
export function DraftAgentStatusBar({
|
||||||
providerDefinitions,
|
providerDefinitions,
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { Pressable, Text, View } from "react-native";
|
import { Pressable, Text, View } from "react-native";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { ChevronDown, GitBranch } from "lucide-react-native";
|
import { ChevronDown, GitBranch } from "lucide-react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
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 {
|
interface BranchSwitcherProps {
|
||||||
currentBranchName: string | null;
|
currentBranchName: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
branchOptions: ComboboxOption[];
|
serverId: string;
|
||||||
isOpen: boolean;
|
workspaceId: string;
|
||||||
onOpenChange: (open: boolean) => void;
|
isGitCheckout: boolean;
|
||||||
onBranchSelect: (branchId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BranchSwitcher({
|
export function BranchSwitcher({
|
||||||
currentBranchName,
|
currentBranchName,
|
||||||
title,
|
title,
|
||||||
branchOptions,
|
serverId,
|
||||||
isOpen,
|
workspaceId,
|
||||||
onOpenChange,
|
isGitCheckout,
|
||||||
onBranchSelect,
|
|
||||||
}: BranchSwitcherProps) {
|
}: BranchSwitcherProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const anchorRef = useRef<View>(null);
|
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) {
|
if (!currentBranchName) {
|
||||||
return (
|
return (
|
||||||
@@ -36,7 +53,7 @@ export function BranchSwitcher({
|
|||||||
<View ref={anchorRef} collapsable={false}>
|
<View ref={anchorRef} collapsable={false}>
|
||||||
<Pressable
|
<Pressable
|
||||||
testID="workspace-header-branch-switcher"
|
testID="workspace-header-branch-switcher"
|
||||||
onPress={() => onOpenChange(true)}
|
onPress={() => setIsOpen(true)}
|
||||||
style={({ hovered, pressed }) => [
|
style={({ hovered, pressed }) => [
|
||||||
styles.branchSwitcherTrigger,
|
styles.branchSwitcherTrigger,
|
||||||
(hovered || pressed) && styles.branchSwitcherTriggerHovered,
|
(hovered || pressed) && styles.branchSwitcherTriggerHovered,
|
||||||
@@ -53,14 +70,14 @@ export function BranchSwitcher({
|
|||||||
<Combobox
|
<Combobox
|
||||||
options={branchOptions}
|
options={branchOptions}
|
||||||
value={currentBranchName}
|
value={currentBranchName}
|
||||||
onSelect={onBranchSelect}
|
onSelect={handleBranchSelect}
|
||||||
searchable
|
searchable
|
||||||
placeholder="Switch branch..."
|
placeholder="Switch branch..."
|
||||||
searchPlaceholder="Filter branches..."
|
searchPlaceholder="Filter branches..."
|
||||||
emptyText="No branches found."
|
emptyText="No branches found."
|
||||||
title="Switch branch"
|
title="Switch branch"
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={setIsOpen}
|
||||||
anchorRef={anchorRef}
|
anchorRef={anchorRef}
|
||||||
desktopPlacement="bottom-start"
|
desktopPlacement="bottom-start"
|
||||||
desktopPreventInitialFlash
|
desktopPreventInitialFlash
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ import type { ListTerminalsResponse } from "@server/shared/messages";
|
|||||||
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
||||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
import { useBranchSwitcher } from "@/hooks/use-branch-switcher";
|
|
||||||
import { useStableEvent } from "@/hooks/use-stable-event";
|
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||||
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
||||||
import { generateDraftId } from "@/stores/draft-keys";
|
import { generateDraftId } from "@/stores/draft-keys";
|
||||||
@@ -762,23 +761,6 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
? trimNonEmpty(checkoutQuery.data.currentBranch)
|
? trimNonEmpty(checkoutQuery.data.currentBranch)
|
||||||
: null;
|
: 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 mobileView = usePanelStore((state) => state.mobileView);
|
||||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
||||||
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
|
||||||
@@ -1982,10 +1964,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
<BranchSwitcher
|
<BranchSwitcher
|
||||||
currentBranchName={currentBranchName}
|
currentBranchName={currentBranchName}
|
||||||
title={workspaceHeader.title}
|
title={workspaceHeader.title}
|
||||||
branchOptions={branchOptions}
|
serverId={normalizedServerId}
|
||||||
isOpen={isBranchSwitcherOpen}
|
workspaceId={normalizedWorkspaceId}
|
||||||
onOpenChange={setIsBranchSwitcherOpen}
|
isGitCheckout={isGitCheckout}
|
||||||
onBranchSelect={handleBranchSelect}
|
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
testID="workspace-header-subtitle"
|
testID="workspace-header-subtitle"
|
||||||
|
|||||||
@@ -5876,7 +5876,11 @@ export class Session {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const lastEmitted = subscription.lastEmittedByWorkspaceId.get(workspaceId);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user