{"record":{"id":"eae784308a09ffc5","repo":"moeru-ai/airi","slug":"invalid-accelerator-input-multiple-non-modif","errorCode":null,"errorMessage":"Invalid accelerator \"${input}\": multiple non-modifier keys (\"${key}\", \"${token}\")","messagePattern":"Invalid accelerator \"(.+?)\": multiple non-modifier keys \\(\"(.+?)\", \"(.+?)\"\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-shared/src/global-shortcut/accelerators.ts","lineNumber":335,"sourceCode":"  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 }\n}\n\n/**\n * Tests whether `input` is a well-formed accelerator string.\n *\n * Use when:\n * - Gating user input without needing the parsed result\n *\n * Returns:\n * - `true` when `parseAccelerator` would succeed, `false` otherwise\n */","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-shared/src/global-shortcut/accelerators.ts#L317-L353","documentation":"The parser allows exactly one non-modifier key token. The first non-modifier token is stored in `key`; if a second non-modifier token is encountered (i.e. `key !== undefined`), it throws `Invalid accelerator \"<input>\": multiple non-modifier keys (\"<key>\", \"<token>\")`. Both offending tokens are included in the message.","triggerScenarios":"Inputs such as `\"Ctrl+A+B\"`, `\"KeyK+KeyL\"`, `\"Shift+F1+F2\"`, or any string where two tokens survive `lookupModifierToken` as non-modifiers. Also `\"A+B\"` with no modifiers.","commonSituations":"A chord/sequence UI that tried to express a multi-keystroke shortcut as one accelerator (accelerators are single combos, not sequences); user typing a second character into a single-key field; merging two shortcuts accidentally.","solutions":["If you need a key sequence, store two separate `ShortcutAccelerator` values, not one string.","Validate single-key intent in the recorder before serialization.","Run `isValidAccelerator(input)` on user input and surface a friendly message.","Inspect the two tokens in the error to find which key was extra."],"exampleFix":"// before\nparseAccelerator('Ctrl+A+B')  // throws\n\n// after\n// represent the sequence as two bindings\nconst combo1 = parseAccelerator('Ctrl+A')\nconst combo2 = parseAccelerator('Ctrl+B')","handlingStrategy":"validation","validationCode":"function countNonModifierTokens(input: string): number {\n  return input.split('+').map(t => t.trim()).filter(t => lookupModifierToken(t) === undefined && t.length > 0).length\n}\n\nif (countNonModifierTokens(raw) === 1) parseAccelerator(raw)","typeGuard":"function hasSingleKey(input: string): boolean {\n  return countNonModifierTokens(input) === 1\n}","tryCatchPattern":"try {\n  parseAccelerator(raw)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('multiple non-modifier keys')) {\n    // split into two separate bindings\n  } else throw e\n}","preventionTips":["Represent key sequences as multiple bindings, not one accelerator.","Recorder should close the combo after one non-modifier key.","Validate single-key intent before serialization."],"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"}