Browse Source

fix UI/UX: Mode switch performance from 1 sec -> instant (#2997)

* Fix mode switching performance in PromptsView by using local state for instant UI updates

* Update webview-ui/src/components/prompts/PromptsView.tsx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: Matt Rubens <[email protected]>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
dlab-anton 10 months ago
parent
commit
ecc7473a02
1 changed files with 39 additions and 29 deletions
  1. 39 29
      webview-ui/src/components/prompts/PromptsView.tsx

+ 39 - 29
webview-ui/src/components/prompts/PromptsView.tsx

@@ -60,6 +60,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 		customModes,
 	} = useExtensionState()
 
+	// Use a local state to track the visually active mode
+	// This prevents flickering when switching modes rapidly by:
+	// 1. Updating the UI immediately when a mode is clicked
+	// 2. Not syncing with the backend mode state (which would cause flickering)
+	// 3. Still sending the mode change to the backend for persistence
+	const [visualMode, setVisualMode] = useState(mode)
+
 	// Memoize modes to preserve array order
 	const modes = useMemo(() => getAllModes(customModes), [customModes])
 
@@ -126,22 +133,25 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 	// Handle mode switching with explicit state initialization
 	const handleModeSwitch = useCallback(
 		(modeConfig: ModeConfig) => {
-			if (modeConfig.slug === mode) return // Prevent unnecessary updates
+			if (modeConfig.slug === visualMode) return // Prevent unnecessary updates
+
+			// Immediately update visual state for instant feedback
+			setVisualMode(modeConfig.slug)
 
-			// First switch the mode
+			// Then send the mode change message to the backend
 			switchMode(modeConfig.slug)
 
 			// Exit tools edit mode when switching modes
 			setIsToolsEditMode(false)
 		},
-		[mode, switchMode, setIsToolsEditMode],
+		[visualMode, switchMode, setIsToolsEditMode],
 	)
 
 	// Helper function to get current mode's config
 	const getCurrentMode = useCallback((): ModeConfig | undefined => {
-		const findMode = (m: ModeConfig): boolean => m.slug === mode
+		const findMode = (m: ModeConfig): boolean => m.slug === visualMode
 		return customModes?.find(findMode) || modes.find(findMode)
-	}, [mode, customModes, modes])
+	}, [visualMode, customModes, modes])
 
 	// Helper function to safely access mode properties
 	const getModeProperty = <T extends keyof ModeConfig>(
@@ -472,7 +482,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 
 					<div className="flex gap-2 items-center mb-3 flex-wrap py-1">
 						{modes.map((modeConfig) => {
-							const isActive = mode === modeConfig.slug
+							const isActive = visualMode === modeConfig.slug
 							return (
 								<button
 									key={modeConfig.slug}
@@ -493,20 +503,20 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 
 				<div style={{ marginBottom: "20px" }}>
 					{/* Only show name and delete for custom modes */}
-					{mode && findModeBySlug(mode, customModes) && (
+					{visualMode && findModeBySlug(visualMode, customModes) && (
 						<div className="flex gap-3 mb-4">
 							<div className="flex-1">
 								<div className="font-bold mb-1">{t("prompts:createModeDialog.name.label")}</div>
 								<div className="flex gap-2">
 									<VSCodeTextField
-										value={getModeProperty(findModeBySlug(mode, customModes), "name") ?? ""}
+										value={getModeProperty(findModeBySlug(visualMode, customModes), "name") ?? ""}
 										onChange={(e: Event | React.FormEvent<HTMLElement>) => {
 											const target =
 												(e as CustomEvent)?.detail?.target ||
 												((e as any).target as HTMLInputElement)
-											const customMode = findModeBySlug(mode, customModes)
+											const customMode = findModeBySlug(visualMode, customModes)
 											if (customMode) {
-												updateCustomMode(mode, {
+												updateCustomMode(visualMode, {
 													...customMode,
 													name: target.value,
 													source: customMode.source || "global",
@@ -522,7 +532,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 										onClick={() => {
 											vscode.postMessage({
 												type: "deleteCustomMode",
-												slug: mode,
+												slug: visualMode,
 											})
 										}}>
 										<span className="codicon codicon-trash"></span>
@@ -534,7 +544,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 					<div style={{ marginBottom: "16px" }}>
 						<div className="flex justify-between items-center mb-1">
 							<div className="font-bold">{t("prompts:roleDefinition.title")}</div>
-							{!findModeBySlug(mode, customModes) && (
+							{!findModeBySlug(visualMode, customModes) && (
 								<Button
 									variant="ghost"
 									size="icon"
@@ -555,25 +565,25 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						</div>
 						<VSCodeTextArea
 							value={(() => {
-								const customMode = findModeBySlug(mode, customModes)
-								const prompt = customModePrompts?.[mode] as PromptComponent
-								return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode)
+								const customMode = findModeBySlug(visualMode, customModes)
+								const prompt = customModePrompts?.[visualMode] as PromptComponent
+								return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(visualMode)
 							})()}
 							onChange={(e) => {
 								const value =
 									(e as CustomEvent)?.detail?.target?.value ||
 									((e as any).target as HTMLTextAreaElement).value
-								const customMode = findModeBySlug(mode, customModes)
+								const customMode = findModeBySlug(visualMode, customModes)
 								if (customMode) {
 									// For custom modes, update the JSON file
-									updateCustomMode(mode, {
+									updateCustomMode(visualMode, {
 										...customMode,
 										roleDefinition: value.trim() || "",
 										source: customMode.source || "global",
 									})
 								} else {
 									// For built-in modes, update the prompts
-									updateAgentPrompt(mode, {
+									updateAgentPrompt(visualMode, {
 										roleDefinition: value.trim() || undefined,
 									})
 								}
@@ -617,7 +627,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						<div className="mb-4">
 							<div className="flex justify-between items-center mb-1">
 								<div className="font-bold">{t("prompts:tools.title")}</div>
-								{findModeBySlug(mode, customModes) && (
+								{findModeBySlug(visualMode, customModes) && (
 									<Button
 										variant="ghost"
 										size="icon"
@@ -632,16 +642,16 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 									</Button>
 								)}
 							</div>
-							{!findModeBySlug(mode, customModes) && (
+							{!findModeBySlug(visualMode, customModes) && (
 								<div className="text-sm text-vscode-descriptionForeground mb-2">
 									{t("prompts:tools.builtInModesText")}
 								</div>
 							)}
-							{isToolsEditMode && findModeBySlug(mode, customModes) ? (
+							{isToolsEditMode && findModeBySlug(visualMode, customModes) ? (
 								<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
 									{availableGroups.map((group) => {
 										const currentMode = getCurrentMode()
-										const isCustomMode = findModeBySlug(mode, customModes)
+										const isCustomMode = findModeBySlug(visualMode, customModes)
 										const customMode = isCustomMode
 										const isGroupEnabled = isCustomMode
 											? customMode?.groups?.some((g) => getGroupName(g) === group)
@@ -710,7 +720,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 								marginBottom: "4px",
 							}}>
 							<div style={{ fontWeight: "bold" }}>{t("prompts:customInstructions.title")}</div>
-							{!findModeBySlug(mode, customModes) && (
+							{!findModeBySlug(visualMode, customModes) && (
 								<Button
 									variant="ghost"
 									size="icon"
@@ -738,8 +748,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 						</div>
 						<VSCodeTextArea
 							value={(() => {
-								const customMode = findModeBySlug(mode, customModes)
-								const prompt = customModePrompts?.[mode] as PromptComponent
+								const customMode = findModeBySlug(visualMode, customModes)
+								const prompt = customModePrompts?.[visualMode] as PromptComponent
 								return (
 									customMode?.customInstructions ??
 									prompt?.customInstructions ??
@@ -750,18 +760,18 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
 								const value =
 									(e as CustomEvent)?.detail?.target?.value ||
 									((e as any).target as HTMLTextAreaElement).value
-								const customMode = findModeBySlug(mode, customModes)
+								const customMode = findModeBySlug(visualMode, customModes)
 								if (customMode) {
 									// For custom modes, update the JSON file
-									updateCustomMode(mode, {
+									updateCustomMode(visualMode, {
 										...customMode,
 										customInstructions: value.trim() || undefined,
 										source: customMode.source || "global",
 									})
 								} else {
 									// For built-in modes, update the prompts
-									const existingPrompt = customModePrompts?.[mode] as PromptComponent
-									updateAgentPrompt(mode, {
+									const existingPrompt = customModePrompts?.[visualMode] as PromptComponent
+									updateAgentPrompt(visualMode, {
 										...existingPrompt,
 										customInstructions: value.trim(),
 									})