{"record":{"id":"821dc89609cefea9","repo":"farion1231/cc-switch","slug":"pi-form-selectpresetrequired","errorCode":null,"errorMessage":"pi.form.selectPresetRequired","messagePattern":"pi\\.form\\.selectPresetRequired","errorType":"validation","errorClass":"PiFormValidationError","httpStatus":null,"severity":"warning","filePath":"src/components/providers/forms/PiProviderForm.tsx","lineNumber":1068,"sourceCode":"      });\n      if (!applied) return;\n      includeCompatRef.current = includeCompat;\n      setProviderCompat(value);\n    },\n    [updateSettingsConfig],\n  );\n\n  const handleProviderKeyChange = useCallback((value: string) => {\n    const normalized = value.toLowerCase().replace(/[^a-z0-9-]/g, \"\");\n    setProviderKey(normalized);\n  }, []);\n\n  const submit = async (identity: ProviderFormData) => {\n    onSubmittingChange?.(true);\n    setFormError(null);\n    try {\n      if (!isEdit && selectedPresetId === null) {\n        throw new PiFormValidationError(t(\"pi.form.selectPresetRequired\"));\n      }\n      if (!parseJsonObject(identity.settingsConfig)) {\n        throw new PiFormValidationError(\n          t(\"jsonEditor.mustBeObject\"),\n          \"#pi-settings-config\",\n        );\n      }\n      const trimmedName = identity.name.trim();\n      const trimmedKey = providerKey.trim();\n      if (!trimmedName) {\n        throw new PiFormValidationError(\n          t(\"pi.form.nameRequired\"),\n          'input[name=\"name\"]',\n        );\n      }\n      if (!isEdit && !trimmedKey) {\n        throw new PiFormValidationError(\n          t(\"pi.form.providerKeyRequired\"),","sourceCodeStart":1050,"sourceCodeEnd":1086,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src/components/providers/forms/PiProviderForm.tsx#L1050-L1086","documentation":"Thrown by the Pi provider form's submit() in CC Switch (PiProviderForm.tsx:1067-1069) when saving a NEW Pi provider while selectedPresetId is still null. New Pi providers derive their transport defaults and template values from a built-in preset (or the explicit 'custom' entry), so creation without a selection is rejected. It is a PiFormValidationError with no fieldSelector: the catch block (lines 1254-1285) renders it as an inline form error plus a toast, and nothing is saved.","triggerScenarios":"Calling submit() with isEdit=false and selectedPresetId===null: the user opened the 'Add provider' dialog and clicked Save without ever touching ProviderPresetSelector, or the useState holding the selection was reset by a remount/HMR before submit.","commonSituations":"Hitting Save immediately after opening the create dialog; embedding PiProviderForm in a custom wrapper that skips the preset selector; React StrictMode double-mount losing uncommitted picker state; automated tests submitting the form programmatically without selecting a preset.","solutions":["Select a preset (or 'Custom') in the preset picker, then save again","Initialize selectedPresetId to a sensible default (first preset or 'custom') when the dialog opens in create mode","Disable the Save button while !isEdit && selectedPresetId === null, mirroring the form's own hasConfigurationSelection check (PiProviderForm.tsx:540)","In tests, drive the preset selector (or inject state) so selectedPresetId is non-null before submitting"],"exampleFix":"// before (dialog opens with no default)\nconst [selectedPresetId, setSelectedPresetId] = useState<string | null>(null);\n\n// after (create mode defaults to Custom, matching the guard's semantics)\nconst [selectedPresetId, setSelectedPresetId] = useState<string | null>(\n  isEdit ? null : \"custom\",\n);","handlingStrategy":"validation","validationCode":"// Before invoking submit for a new Pi provider:\nif (!isEdit && selectedPresetId === null) {\n  setFormError(t(\"pi.form.selectPresetRequired\"));\n  return; // don't call submit\n}","typeGuard":null,"tryCatchPattern":"try {\n  await submit(identity);\n} catch (error) {\n  if (error instanceof Error && error.name === \"PiFormValidationError\") {\n    // expected inline validation error; already surfaced by the form\n    return;\n  }\n  throw error;\n}","preventionTips":["Default selectedPresetId ('custom' or first preset) when the create dialog opens","Disable Save while !isEdit && selectedPresetId === null, reusing the form's hasConfigurationSelection logic","In tests, always drive the preset selector before submitting"],"tags":["form-validation","pi","preset","react","i18n"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}