{"record":{"id":"0f0f6b04a9b17d9d","repo":"actualbudget/actual","slug":"unrecognized-menu-option-string-name","errorCode":null,"errorMessage":"Unrecognized menu option: ${String(name)}","messagePattern":"Unrecognized menu option: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx","lineNumber":47,"sourceCode":"\n  return (\n    <span>\n      <Menu\n        onMenuSelect={name => {\n          switch (name) {\n            case 'view':\n              onShowActivity(categoryId, month);\n              break;\n            case 'carryover':\n              if (!carryover) onBudgetAction(month, 'reset-hold');\n              onBudgetAction(month, 'carryover', {\n                category: categoryId,\n                flag: !carryover,\n              });\n              onClose();\n              break;\n            default:\n              throw new Error(`Unrecognized menu option: ${String(name)}`);\n          }\n        }}\n        items={[\n          {\n            name: 'carryover',\n            text: carryover ? t('Disable auto hold') : t('Enable auto hold'),\n          },\n          {\n            name: 'view',\n            text: t('View transactions'),\n          },\n        ]}\n      />\n    </span>\n  );\n}\n","sourceCodeStart":29,"sourceCodeEnd":64,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/budget/envelope/IncomeMenu.tsx#L29-L64","documentation":"IncomeMenu's onSelect switch handles its menu options (e.g. 'carryover') and throws on any other name via `Unrecognized menu option: ${String(name)}`. Like BalanceMenu/BudgetMenu, it is an exhaustiveness guard assuming items and switch cases stay synchronized.","triggerScenarios":"Selecting an income-category menu item whose name has no case in the switch — after adding a new item to the items array without a matching case, or dispatching a stale/renamed option name programmatically.","commonSituations":"Contributors adding an income menu action but only editing items; renamed option ids surviving in saved/UI state; tests clicking items that no longer have a handler.","solutions":["Add the missing case to IncomeMenu's onSelect switch for the option name in question.","Share a string-literal union between item definitions and onSelect so TypeScript reports unmatched names.","Replace the throw with a logged warning + early return if a no-op option is acceptable in some contexts."],"exampleFix":"// before\ndefault:\n  throw new Error(`Unrecognized menu option: ${String(name)}`);\n\n// after\ncase 'reset-income':\n  onResetIncome?.();\n  break;\ndefault:\n  console.warn('Unknown income menu option', name);","handlingStrategy":"type-guard","validationCode":"type IncomeMenuOption = 'carryover' | 'report-budget';\nconst INCOME_MENU_OPTIONS: readonly IncomeMenuOption[] = ['carryover', 'report-budget'];\nif (!INCOME_MENU_OPTIONS.includes(name as IncomeMenuOption)) return;","typeGuard":"function isIncomeMenuOption(name: string): name is IncomeMenuOption {\n  return (INCOME_MENU_OPTIONS as readonly string[]).includes(name);\n}","tryCatchPattern":"onSelect={name => {\n  try {\n    handleIncomeOption(name);\n  } catch (e) {\n    if (String(e.message).startsWith('Unrecognized menu option')) console.warn('unhandled income option', name);\n    else throw e;\n  }\n}}","preventionTips":["Share a literal-union type between IncomeMenu items and its onSelect switch.","Add the switch case whenever a new income menu item is introduced.","Add a test that exercises each item name against onSelect to keep cases in sync."],"tags":["typescript","exhaustiveness","menu","budget"],"backgroundTag":"unhandled-enum-case","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}