{"record":{"id":"227b0398962d7693","repo":"moeru-ai/airi","slug":"invalid-accelerator-empty-string","errorCode":null,"errorMessage":"Invalid accelerator: empty string","messagePattern":"Invalid accelerator: empty string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/global-shortcut/accelerators.ts","lineNumber":315,"sourceCode":" *   canonical key\n *\n * Throws:\n * - `Error` when the input is empty, has empty tokens, names an\n *   unknown key, repeats a modifier, or contains multiple non-modifier\n *   tokens\n *\n * @example\n *   parseAccelerator('Mod+Shift+K')\n *   // => { modifiers: ['cmd-or-ctrl', 'shift'], key: 'KeyK' }\n *\n * @example\n *   parseAccelerator(' CmdOrCtrl + Shift + KeyK ')\n *   // => same as above; whitespace tolerated\n */\nexport function parseAccelerator(input: string): ShortcutAccelerator {\n  const trimmed = input.trim()\n  if (trimmed.length === 0)\n    throw new Error('Invalid accelerator: empty string')\n\n  const rawTokens = trimmed.split('+')\n  const modifiers: ShortcutModifier[] = []\n  let key: ShortcutKey | undefined\n\n  for (const raw of rawTokens) {\n    const token = raw.trim()\n    if (token.length === 0)\n      throw new Error(`Invalid accelerator \"${input}\": empty token`)\n\n    const modifier = lookupModifierToken(token)\n    if (modifier !== undefined) {\n      if (modifiers.includes(modifier))\n        throw new Error(`Invalid accelerator \"${input}\": duplicate modifier \"${modifier}\"`)\n      modifiers.push(modifier)\n      continue\n    }\n","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-shared/src/global-shortcut/accelerators.ts#L297-L333","documentation":"parseAccelerator (packages/stage-shared/src/global-shortcut/accelerators.ts:315) trims its input and immediately throws this error when the result has length 0. An accelerator must contain at least one token (modifier(s) + key), so an empty or whitespace-only string is rejected up front before any tokenization.","triggerScenarios":"Calling parseAccelerator('') or parseAccelerator('   '), most often because a config value, cleared user binding, or null coerced to an empty string was passed through without validation.","commonSituations":"Settings UIs that let users clear a shortcut and then persist '', YAML/JSON configs with an empty hotkey field, or defaults that were never populated.","solutions":["Skip shortcut registration when the trimmed string is empty (treat it as 'unset')","Substitute a sensible default accelerator when the configured value is blank","Validate shortcut strings at settings-load time and drop invalid entries with a warning"],"exampleFix":"// before\nparseAccelerator(shortcutFromConfig) // throws when config value is ''\n// after\nconst trimmed = shortcutFromConfig.trim()\nif (trimmed.length > 0)\n  parseAccelerator(trimmed) // empty string means 'no shortcut configured'","handlingStrategy":"validation","validationCode":"const trimmed = configuredShortcut.trim()\nif (trimmed.length === 0) {\n  // empty means 'no shortcut'; skip registration instead of parsing\n  unregisterAll()\n}\nelse {\n  registerGlobalShortcut(parseAccelerator(trimmed), handler)\n}","typeGuard":"const isNonEmptyAccelerator = (input: string): boolean => input.trim().length > 0","tryCatchPattern":"try {\n  const accel = parseAccelerator(config.hotkey)\n}\ncatch (error) {\n  if (error instanceof Error && error.message === 'Invalid accelerator: empty string') {\n    config.hotkey = 'Mod+Shift+K' // documented default\n  }\n  else throw error\n}","preventionTips":["Treat empty shortcut strings as 'unset' and skip registration","Validate shortcut fields when loading settings files","Never coerce null/undefined hotkeys to '' before parsing"],"tags":["keyboard","shortcut","accelerator","validation","empty-input"],"backgroundTag":"invalid-keyboard-accelerator","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}