{"record":{"id":"297f842b53681a52","repo":"actualbudget/actual","slug":"unknown-item-type","errorCode":null,"errorMessage":"Unknown item type: ","messagePattern":"Unknown item type: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/budget/BudgetCategories.tsx","lineNumber":376,"sourceCode":"              content = (\n                <IncomeCategory\n                  cat={item.value}\n                  editingCell={editingCell}\n                  isLast={idx === items.length - 1}\n                  onEditName={onEditName}\n                  onEditMonth={onEditMonth}\n                  onSave={_onSaveCategory}\n                  onDelete={onDeleteCategory}\n                  onDragChange={onDragChange}\n                  onReorder={onReorderCategory}\n                  onBudgetAction={onBudgetAction}\n                  onShowActivity={onShowActivity}\n                />\n              );\n              break;\n            default:\n              // @ts-expect-error Error is expected here because \"item.type\" is \"never\"\n              throw new Error('Unknown item type: ' + item.type);\n          }\n\n          const pos =\n            idx === 0 ? 'first' : idx === items.length - 1 ? 'last' : null;\n\n          return (\n            <DropHighlightPosContext.Provider\n              key={\n                'value' in item\n                  ? item.value.id\n                  : item.type === 'income-separator'\n                    ? 'separator'\n                    : idx\n              }\n              value={pos}\n            >\n              <View\n                style={","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/budget/BudgetCategories.tsx#L358-L394","documentation":"BudgetCategories' item renderer switches on a discriminated union of budget item types; the default branch is unreachable for well-typed input (hence `item.type` is `never` and the @ts-expect-error). Throwing here guards against data that violates the union at runtime, e.g. a type string produced outside the known set.","triggerScenarios":"Rendering a budget row whose item.type is not one of the handled kinds (e.g. 'income', 'total-spend', etc.) — typically after a new item type is added to the data layer without extending this switch, or corrupted/generated rows carrying an unexpected type.","commonSituations":"A contributor adds a new budget summary row type and forgets the BudgetCategories switch; plugin/template code injecting rows with a custom type; version skew between the bundled app and data produced by a newer release.","solutions":["Add a case for the unexpected item.type to the switch so the union is fully handled (TypeScript exhaustiveness will then flag future additions).","Render a neutral fallback (e.g. return null or a placeholder row) in the default branch instead of crashing the whole budget view.","Log the offending type (via logger) before handling, to diagnose where the unknown row originates."],"exampleFix":"// before\ndefault:\n  // @ts-expect-error\n  throw new Error('Unknown item type: ' + item.type);\n\n// after\ndefault:\n  console.warn('Unknown budget item type:', item.type);\n  return null;","handlingStrategy":"type-guard","validationCode":"const KNOWN_TYPES = ['income', 'total-spend', 'total-income', 'total-leftover'] as const;\ntype KnownType = typeof KNOWN_TYPES[number];\nif (!KNOWN_TYPES.includes(item.type as KnownType)) return null; // skip unknown rows","typeGuard":"function isKnownBudgetItem(item: BudgetItem): item is BudgetItem & { type: KnownType } {\n  return (KNOWN_TYPES as readonly string[]).includes(item.type);\n}","tryCatchPattern":"try {\n  rows.push(renderItem(item));\n} catch (e) {\n  if (String(e.message).startsWith('Unknown item type')) {\n    logger.warn('skipping unknown budget item', item);\n    return null;\n  } else throw e;\n}","preventionTips":["Extend the switch whenever a new budget item type is added to the union.","Rely on the `never` exhaustiveness check instead of bypassing it silently.","Render a fallback row instead of throwing so one bad row can't break the budget view."],"tags":["typescript","exhaustiveness","discriminated-union","budget"],"backgroundTag":"unhandled-enum-case","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}