Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions webview-ui/src/components/prompts/PromptsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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}
Expand All @@ -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",
Expand All @@ -522,7 +532,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
onClick={() => {
vscode.postMessage({
type: "deleteCustomMode",
slug: mode,
slug: visualMode,
})
}}>
<span className="codicon codicon-trash"></span>
Expand All @@ -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"
Expand All @@ -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,
})
}
Expand Down Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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 ??
Expand All @@ -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(),
})
Expand Down