{"record":{"id":"9e9e3342de7d4715","repo":"moeru-ai/airi","slug":"invalid-accelerator-input-empty-token","errorCode":null,"errorMessage":"Invalid accelerator \"${input}\": empty token","messagePattern":"Invalid accelerator \"(.+?)\": empty token","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/global-shortcut/accelerators.ts","lineNumber":324,"sourceCode":" *   // => { 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\n    if (key !== undefined)\n      throw new Error(`Invalid accelerator \"${input}\": multiple non-modifier keys (\"${key}\", \"${token}\")`)\n    key = normalizeKeyToken(token)\n  }\n\n  if (key === undefined)\n    throw new Error(`Invalid accelerator \"${input}\": no key token`)\n\n  return { modifiers, key }","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-shared/src/global-shortcut/accelerators.ts#L306-L342","documentation":"`parseAccelerator` splits the trimmed input on `+` and trims each token. If any resulting token has length 0, it throws `Invalid accelerator \"<input>\": empty token`. This means the string contains a `+` with nothing (or only whitespace) on one side of it. The original `input` is echoed in the message so the malformed region is visible.","triggerScenarios":"Inputs such as `\"Ctrl++K\"`, `\"+K\"`, `\"K+\"`, `\"Ctrl+ +K\"` (whitespace-only segment), `\"Mod+\"`, or any value where `trimmed.split('+')` yields an empty/whitespace element after per-token `.trim()`.","commonSituations":"Typos in hand-authored keybind config (`\"Mod + + K\"`); concatenating modifiers with a stray separator (`[mod, '', key].join('+')`); copy-paste from docs that used an em-dash or special plus character; a keystroke recorder that emits an empty segment between two key events.","solutions":["Sanitize the string before parsing: collapse repeated `+` and strip leading/trailing `+` (`input.replace(/\\+{2,}/g, '+').replace(^\\+|\\+$/g, '').trim()`).","Use `isValidAccelerator(input)` to gate the parse on user-supplied values.","Reconstruct the accelerator programmatically from a structured `{ modifiers, key }` via `formatAccelerator` instead of hand-concatenating strings.","Inspect the echoed `<input>` in the message to locate the stray separator."],"exampleFix":"// before\nconst acc = parseAccelerator(rawUserInput)\n\n// after\nconst clean = rawUserInput.replace(/\\+{2,}/g, '+').replace(/^\\+|\\+$/g, '').trim()\nif (!clean) return\nconst acc = parseAccelerator(clean)","handlingStrategy":"validation","validationCode":"function sanitizeAccelerator(input: string): string {\n  return input.replace(/\\+{2,}/g, '+').replace(/^\\+|\\+$/g, '').trim()\n}\n\nconst clean = sanitizeAccelerator(raw)\nif (clean) parseAccelerator(clean)","typeGuard":"function isWellFormedAccelerator(input: string): boolean {\n  const trimmed = input.trim()\n  if (!trimmed) return false\n  return trimmed.split('+').every(tok => tok.trim().length > 0)\n}","tryCatchPattern":"try {\n  parseAccelerator(raw)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('empty token')) {\n    // re-prompt the user for the keybind\n  } else throw e\n}","preventionTips":["Do not hand-concatenate accelerator strings; build them from structured modifiers+key.","Strip stray separators and collapse repeated '+' before parsing.","Validate with the library's isValidAccelerator helper on user input."],"tags":["input-validation","global-shortcut","accelerator"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}