{"record":{"id":"7ee5c3c6278d3542","repo":"actualbudget/actual","slug":"unknown-modal","errorCode":null,"errorMessage":"Unknown modal","messagePattern":"Unknown modal","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/components/Modals.tsx","lineNumber":435,"sourceCode":"          return <OutOfSyncMigrationsModal key={key} />;\n\n        case 'edit-access':\n          return <EditUserAccess key={key} {...modal.options} />;\n\n        case 'edit-user':\n          return <EditUserFinanceApp key={key} {...modal.options} />;\n\n        case 'transfer-ownership':\n          return <TransferOwnership key={key} {...modal.options} />;\n\n        case 'enable-openid':\n          return <OpenIDEnableModal key={key} {...modal.options} />;\n\n        case 'enable-password-auth':\n          return <PasswordEnableModal key={key} {...modal.options} />;\n\n        default:\n          throw new Error('Unknown modal');\n      }\n    })\n    .map((modal, idx) => (\n      <Fragment key={`${modalStack[idx].name}-${idx}`}>{modal}</Fragment>\n    ));\n\n  // fragment needed per TS types\n  // oxlint-disable-next-line react/jsx-no-useless-fragment\n  return <>{modals}</>;\n}\n","sourceCodeStart":417,"sourceCodeEnd":446,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/components/Modals.tsx#L417-L446","documentation":"Modals maps the current modalStack entries to components via a switch on the modal name. If a modal in the stack has a name without a registered case (and no default fallback component), it throws 'Unknown modal'. This indicates the modal stack contains an entry the renderer doesn't know — usually a newly added modal or a renamed one missing its case here.","triggerScenarios":"Pushing a modal with a name that has no case in Modals.tsx (e.g. dispatch(pushModal({ modal: { name: 'my-modal' } })) before adding the mapping); renaming a modal in one place but not the Modals switch; a typo in the modal name string.","commonSituations":"Adding a new modal feature and forgetting the Modals.tsx case; persisted client state re-pushing a removed modal name; version upgrade where a modal was renamed; typo in a pushModal call.","solutions":["Add a case for the missing modal name in Modals.tsx returning its component with modal.options.","Verify the exact name being pushed matches the case string (check for typos/renames).","Clear the stale modal state (reload the app / reset the modal stack) if an outdated name was persisted.","Grep for pushModal calls with that name to find the source dispatching it.","Add a type for allowed modal names so mismatches are caught at compile time."],"exampleFix":"// before\ncase 'enable-password-auth':\n  return <PasswordEnableModal key={key} {...modal.options} />;\ndefault:\n  throw new Error('Unknown modal');\n// after\ncase 'enable-password-auth':\n  return <PasswordEnableModal key={key} {...modal.options} />;\ncase 'my-new-modal':\n  return <MyNewModal key={key} {...modal.options} />;\ndefault:\n  throw new Error('Unknown modal');","handlingStrategy":"type-guard","validationCode":"const KNOWN_MODALS = ['openid-enable','enable-password-auth','add-account','schedule-edit'] as const;\ntype ModalName = typeof KNOWN_MODALS[number];\nfunction isKnownModal(name) {\n  return KNOWN_MODALS.includes(name);\n}\n// guard before pushModal:\nif (!isKnownModal(modal.name)) return;","typeGuard":"function isModalName(value) {\n  return typeof value === 'string' && KNOWN_MODALS.includes(value);\n}","tryCatchPattern":"// Render-time throws can't be caught inside the component; guard at dispatch time:\ntry {\n  dispatch(pushModal({ modal: { name: modalName, options } }));\n} catch (e) {\n  if (e.message === 'Unknown modal') {\n    logger.warn('Attempted to open unregistered modal', modalName);\n    return;\n  }\n  throw e;\n}","preventionTips":["Centralize modal names in a shared union type and use it for both pushModal calls and the Modals switch.","Always add the Modals.tsx case in the same PR that introduces a new modal.","Add an ErrorBoundary around <Modals /> so an unknown modal doesn't blank the whole app.","Grep pushModal call sites when renaming a modal."],"tags":["react","ui","modal","developer-error"],"backgroundTag":"unknown-modal-name","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}