{"record":{"id":"3c8f32e385e7f192","repo":"actualbudget/actual","slug":"unrecognized-menu-item-name","errorCode":null,"errorMessage":"Unrecognized menu item: ${name}","messagePattern":"Unrecognized menu item: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/budget/envelope/BudgetMenu.tsx","lineNumber":44,"sourceCode":"  const onMenuSelect = (name: string) => {\n    switch (name) {\n      case 'copy-single-last':\n        onCopyLastMonthAverage?.();\n        break;\n      case 'set-single-3-avg':\n        onSetMonthsAverage?.(3);\n        break;\n      case 'set-single-6-avg':\n        onSetMonthsAverage?.(6);\n        break;\n      case 'set-single-12-avg':\n        onSetMonthsAverage?.(12);\n        break;\n      case 'apply-single-category-template':\n        onApplyBudgetTemplate?.();\n        break;\n      default:\n        throw new Error(`Unrecognized menu item: ${name}`);\n    }\n  };\n\n  return (\n    <Menu\n      {...props}\n      onMenuSelect={onMenuSelect}\n      items={[\n        {\n          name: 'copy-single-last',\n          text: t(\"Copy last month's budget\"),\n        },\n        {\n          name: 'set-single-3-avg',\n          text: t('Set to 3 month average'),\n        },\n        {\n          name: 'set-single-6-avg',","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/budget/envelope/BudgetMenu.tsx#L26-L62","documentation":"BudgetMenu's onMenuSelect maps each menu item name to an optional callback prop (onSetSpread, onApplyBudgetTemplate, etc.) and throws in the default branch for unknown names. The default branch is only reachable when the items array and this handler drift out of sync.","triggerScenarios":"Selecting (or programmatically dispatching) a menu item whose name has no case in onMenuSelect — typically after adding a new item like 'apply-single-category-template' without a matching case, or passing extra items via Menu props spread ({...props}).","commonSituations":"Contributors extending the budget tool menu but only editing the items list; callers overriding/adding items through props; tests simulating clicks on renamed items.","solutions":["Add the missing case to onMenuSelect for the item name being selected.","Define a shared union type for menu item names used by both the items array and onMenuSelect, gaining compile-time exhaustiveness checking.","When spreading extra props into Menu, ensure any added items also have onSelect handling or are filtered out."],"exampleFix":"// before\ndefault:\n  throw new Error(`Unrecognized menu item: ${name}`);\n\n// after\ncase 'copy-last-month-budget':\n  onCopyLastMonthBudget?.();\n  break;\ndefault:\n  console.warn('Unrecognized budget menu item', name);","handlingStrategy":"type-guard","validationCode":"type BudgetMenuItem = 'set-spread' | 'check-templates' | 'apply-single-category-template' | 'copy-last-month-budget' | 'set-months-average';\nconst BUDGET_MENU_ITEMS: readonly BudgetMenuItem[] = ['set-spread', 'check-templates', 'apply-single-category-template', 'copy-last-month-budget', 'set-months-average'];\nif (!BUDGET_MENU_ITEMS.includes(name as BudgetMenuItem)) return;","typeGuard":"function isBudgetMenuItem(name: string): name is BudgetMenuItem {\n  return (BUDGET_MENU_ITEMS as readonly string[]).includes(name);\n}","tryCatchPattern":"onMenuSelect={name => {\n  try {\n    dispatch(name);\n  } catch (e) {\n    if (String(e.message).startsWith('Unrecognized menu item')) console.warn('unhandled item', name);\n    else throw e;\n  }\n}}","preventionTips":["Define menu item names in one shared literal-union type used by items and onSelect.","Audit {...props} spreads that can inject unhandled items into Menu.","Write a test that clicks every rendered menu item to catch missing cases in CI."],"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"}