{"record":{"id":"55ad84aece9a8bb5","repo":"Hmbown/CodeWhale","slug":"custom-theme-requires-custom-theme-name","errorCode":null,"errorMessage":"custom theme requires custom_theme_name","messagePattern":"custom theme requires custom_theme_name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config_ui.rs","lineNumber":868,"sourceCode":"        validate_and_normalize_model(config, identity.provider, model)\n            .map_err(|err| anyhow::anyhow!(\"invalid provider_models.{provider_id} value: {err}\"))?;\n    }\n    if doc.config.mcp_config_path.trim().is_empty() {\n        bail!(\"mcp_config_path cannot be empty\");\n    }\n    let _ = theme_setting_for_document(doc)?;\n    Ok(())\n}\n\nfn theme_setting_for_document(doc: &ConfigUiDocument) -> Result<String> {\n    let setting = if doc.settings.theme == UiThemeValue::Custom {\n        let name = doc\n            .settings\n            .custom_theme_name\n            .as_deref()\n            .map(str::trim)\n            .filter(|name| !name.is_empty())\n            .ok_or_else(|| anyhow::anyhow!(\"custom theme requires custom_theme_name\"))?;\n        format!(\"{}{}\", crate::palette::USER_THEME_PREFIX, name)\n    } else {\n        doc.settings.theme.as_setting().to_string()\n    };\n    crate::palette::resolve_theme_setting(&setting, None)\n        .map(|(normalized, _, _)| normalized)\n        .map_err(anyhow::Error::msg)\n}\n\n/// Validate and normalize a model against the provider's *effective* route.\n///\n/// A custom DeepSeek base URL owns its own model namespace, so non-DeepSeek ids\n/// must be accepted here the same way request-time\n/// `wire_model_for_provider_route` preserves them. The previous provider-only\n/// gate rejected those saves even though the live session could use them.\nfn validate_and_normalize_model(\n    config: &Config,\n    provider: crate::config::ApiProvider,","sourceCodeStart":850,"sourceCodeEnd":886,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/config_ui.rs#L850-L886","documentation":"theme_setting_for_document (crates/tui/src/config_ui.rs:868) builds the theme setting string when /config saves. UiThemeValue::Custom is only a marker: it needs custom_theme_name to compose <USER_THEME_PREFIX><name> and resolve it through palette::resolve_theme_setting. A missing, None, or whitespace-only name fails the save with this message.","triggerScenarios":"Saving /config with doc.settings.theme == UiThemeValue::Custom while custom_theme_name is None or trims to empty.","commonSituations":"Selecting \"custom\" in the theme picker without typing a name; a document default that pre-selects Custom; clearing the name field but leaving the selector on Custom.","solutions":["Enter a non-empty custom theme name in the adjacent field before saving","Switch the theme selector to a built-in theme instead of Custom","Make the UI clear the Custom selection automatically when the name field is emptied"],"exampleFix":"# before\ntheme = \"custom\"\ncustom_theme_name = \"\"\n\n# after\ntheme = \"custom\"\ncustom_theme_name = \"nord-dark\"","handlingStrategy":"validation","validationCode":"fn custom_theme_is_complete(doc: &ConfigUiDocument) -> bool {\n    doc.settings.theme != UiThemeValue::Custom\n        || doc.settings\n            .custom_theme_name\n            .as_deref()\n            .is_some_and(|name| !name.trim().is_empty())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make the theme form clear the Custom selection when the name field is emptied","Disable Save while theme == Custom and the name field is blank","Offer a picker of existing user themes instead of free-text entry where possible"],"tags":["config-ui","theme","validation","required-field"],"backgroundTag":"missing-required-argument","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}