{"record":{"id":"39326f66b8b98b07","repo":"actualbudget/actual","slug":"unknown-display-type-string-visualtype-satisfie","errorCode":null,"errorMessage":"Unknown display type: ${String(visualType satisfies never)}","messagePattern":"Unknown display type: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/budget/goals/reducer.ts","lineNumber":235,"sourceCode":"          weight: 1,\n          priority: null,\n        },\n      };\n    case 'goal':\n      if (prevState.template.type === 'goal') {\n        return prevState;\n      }\n      return {\n        displayType: visualType,\n        template: {\n          directive: 'goal',\n          type: 'goal',\n          amount: 1000,\n        },\n      };\n    default:\n      // Make sure we're not missing any cases\n      throw new Error(\n        `Unknown display type: ${String(visualType satisfies never)}`,\n      );\n  }\n};\n\nfunction mapTemplateTypesForUpdate(\n  state: ReducerState,\n  template: Partial<Template> & Pick<Template, 'type'>,\n): ReducerState {\n  switch (state.template.type) {\n    case 'average':\n      switch (template.type) {\n        case 'copy':\n          return {\n            ...state,\n            displayType: 'historical',\n            template: {\n              ...template,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/budget/goals/reducer.ts#L217-L253","documentation":"changeType in the goals template reducer throws this when asked to convert the editor to a DisplayTemplateType ('limit', 'refill', 'fixed', 'percentage', 'schedule', 'by', 'remainder', 'historical', 'goal') that is not handled in its switch. The `visualType satisfies never` assertion documents that the default branch is unreachable for valid display types; hitting it means an out-of-range display type value reached the reducer. It is an exhaustiveness check so adding a new display type without a mapping branch fails loudly.","triggerScenarios":"Dispatching a change-type action through templateReducer with a visualType string that is not one of the DisplayTemplateType literals — e.g. a typo, a stale serialized state, or a new display type added to constants.ts without a matching case in changeType.","commonSituations":"Developers adding a new template display type to DisplayTemplateType but forgetting to extend changeType; UI code passing a display type derived from user input or persisted state that predates a rename.","solutions":["Log the visualType value to identify the unknown display type string.","Add a case for the missing display type in changeType, returning the appropriate default template shape for it.","Ensure the UI only dispatches display types that exist in DisplayTemplateType constants.","If restoring persisted editor state, validate/whitelist the stored displayType before dispatching."],"exampleFix":"// before\ndefault:\n  throw new Error(`Unknown display type: ${String(visualType satisfies never)}`);\n// after\ncase 'newDisplay':\n  return { displayType: visualType, template: defaultTemplateFor('newDisplay') };\ndefault:\n  throw new Error(`Unknown display type: ${String(visualType)}`);","handlingStrategy":"type-guard","validationCode":"const DISPLAY_TYPES = ['limit','refill','fixed','percentage','schedule','by','remainder','historical','goal'] as const;\nif (!DISPLAY_TYPES.includes(visualType as never)) return prevState;","typeGuard":"function isDisplayTemplateType(v: string): v is DisplayTemplateType {\n  return ['limit','refill','fixed','percentage','schedule','by','remainder','historical','goal'].includes(v);\n}","tryCatchPattern":"try {\n  dispatch({ type: 'change-type', displayType: visualType });\n} catch (err) {\n  logger.error('Unknown display type change', visualType, err);\n}","preventionTips":["Derive changeType's switch cases from the DisplayTemplateType union so tsc flags missing branches.","Whitelist displayType values read from persisted editor state before dispatching.","Never construct display-type strings by hand in UI code; import the constants."],"tags":["typescript","exhaustiveness","budget-templates","reducer"],"backgroundTag":"unhandled-switch-case","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}