From fc2c8869442c5f1107b787da5d644ce078fcc94e Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Wed, 16 Jul 2025 16:05:21 -0500 Subject: [PATCH] fix: prevent empty mode names from being saved (#5766) - Add local state management for mode name input field to allow visual emptying - Implement onBlur validation to only save non-empty names - Add backend validation using modeConfigSchema.safeParse() - Ensure empty names are never written to YAML files This allows users to clear the name field visually while preventing invalid empty names from being persisted to configuration files. --- src/core/config/CustomModesManager.ts | 8 +++++ webview-ui/src/components/modes/ModesView.tsx | 36 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index b4bcfa62d6..3da22d1be8 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -401,6 +401,14 @@ export class CustomModesManager { public async updateCustomMode(slug: string, config: ModeConfig): Promise { try { + // Validate the mode configuration before saving + const validationResult = modeConfigSchema.safeParse(config) + if (!validationResult.success) { + const errors = validationResult.error.errors.map((e) => e.message).join(", ") + logger.error(`Invalid mode configuration for ${slug}`, { errors: validationResult.error.errors }) + throw new Error(`Invalid mode configuration: ${errors}`) + } + const isProjectMode = config.source === "project" let targetPath: string diff --git a/webview-ui/src/components/modes/ModesView.tsx b/webview-ui/src/components/modes/ModesView.tsx index 620797290d..170d03b0e4 100644 --- a/webview-ui/src/components/modes/ModesView.tsx +++ b/webview-ui/src/components/modes/ModesView.tsx @@ -110,6 +110,10 @@ const ModesView = ({ onDone }: ModesViewProps) => { const [searchValue, setSearchValue] = useState("") const searchInputRef = useRef(null) + // Local state for mode name input to allow visual emptying + const [localModeName, setLocalModeName] = useState("") + const [currentEditingModeSlug, setCurrentEditingModeSlug] = useState(null) + // Direct update functions const updateAgentPrompt = useCallback( (mode: Mode, promptData: PromptComponent) => { @@ -218,6 +222,14 @@ const ModesView = ({ onDone }: ModesViewProps) => { } }, [getCurrentMode, checkRulesDirectory, hasRulesToExport]) + // Reset local name state when mode changes + useEffect(() => { + if (currentEditingModeSlug && currentEditingModeSlug !== visualMode) { + setCurrentEditingModeSlug(null) + setLocalModeName("") + } + }, [visualMode, currentEditingModeSlug]) + // Helper function to safely access mode properties const getModeProperty = ( mode: ModeConfig | undefined, @@ -725,16 +737,34 @@ const ModesView = ({ onDone }: ModesViewProps) => {
{ + value={ + currentEditingModeSlug === visualMode + ? localModeName + : (getModeProperty(findModeBySlug(visualMode, customModes), "name") ?? + "") + } + onFocus={() => { const customMode = findModeBySlug(visualMode, customModes) if (customMode) { + setCurrentEditingModeSlug(visualMode) + setLocalModeName(customMode.name) + } + }} + onChange={(e) => { + setLocalModeName(e.target.value) + }} + onBlur={() => { + const customMode = findModeBySlug(visualMode, customModes) + if (customMode && localModeName.trim()) { + // Only update if the name is not empty updateCustomMode(visualMode, { ...customMode, - name: e.target.value, + name: localModeName, source: customMode.source || "global", }) } + // Clear the editing state + setCurrentEditingModeSlug(null) }} className="w-full" />