actualbudget/actual · error

Unknown display type: ${String(visualType satisfies never)}

Error message

Unknown display type: ${String(visualType satisfies never)}

What it means

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.

Source

Thrown at packages/desktop-client/src/components/budget/goals/reducer.ts:235

          weight: 1,
          priority: null,
        },
      };
    case 'goal':
      if (prevState.template.type === 'goal') {
        return prevState;
      }
      return {
        displayType: visualType,
        template: {
          directive: 'goal',
          type: 'goal',
          amount: 1000,
        },
      };
    default:
      // Make sure we're not missing any cases
      throw new Error(
        `Unknown display type: ${String(visualType satisfies never)}`,
      );
  }
};

function mapTemplateTypesForUpdate(
  state: ReducerState,
  template: Partial<Template> & Pick<Template, 'type'>,
): ReducerState {
  switch (state.template.type) {
    case 'average':
      switch (template.type) {
        case 'copy':
          return {
            ...state,
            displayType: 'historical',
            template: {
              ...template,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Log the visualType value to identify the unknown display type string.
  2. Add a case for the missing display type in changeType, returning the appropriate default template shape for it.
  3. Ensure the UI only dispatches display types that exist in DisplayTemplateType constants.
  4. If restoring persisted editor state, validate/whitelist the stored displayType before dispatching.

Example fix

// before
default:
  throw new Error(`Unknown display type: ${String(visualType satisfies never)}`);
// after
case 'newDisplay':
  return { displayType: visualType, template: defaultTemplateFor('newDisplay') };
default:
  throw new Error(`Unknown display type: ${String(visualType)}`);
Defensive patterns

Strategy: type-guard

Validate before calling

const DISPLAY_TYPES = ['limit','refill','fixed','percentage','schedule','by','remainder','historical','goal'] as const;
if (!DISPLAY_TYPES.includes(visualType as never)) return prevState;

Type guard

function isDisplayTemplateType(v: string): v is DisplayTemplateType {
  return ['limit','refill','fixed','percentage','schedule','by','remainder','historical','goal'].includes(v);
}

Try / catch

try {
  dispatch({ type: 'change-type', displayType: visualType });
} catch (err) {
  logger.error('Unknown display type change', visualType, err);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/39326f66b8b98b07. Report an issue: GitHub.