{"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/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-shared/src/global-shortcut/accelerators.ts#L297-L333","documentation":"`parseAccelerator(input)` parses a shortcut string like `\"Mod+Shift+K\"` into a structured `ShortcutAccelerator`. It throws `Invalid accelerator: empty string` when `input.trim()` has length 0, i.e. the caller passed `\"\"`, `\"   \"`, or a value that becomes empty after trimming. Parsing happens before any tokenization, so no other validation runs. Whitespace-only strings are treated identically to the empty string.","triggerScenarios":"Calling `parseAccelerator('')`, `parseAccelerator('   ')`, or passing a variable that resolved to an empty/whitespace string (e.g. a settings field that was never filled in, a form input before the user typed anything, or a destructured config value that defaulted to `''`).","commonSituations":"Loading a global-shortcut binding from persisted settings where the field was saved blank; binding a keybind editor to a reactive ref before the user enters a value; reading a YAML/JSON config key that is present but empty; passing `undefined`-derived defaults that coerce to `''`.","solutions":["Guard the input before parsing: `if (!input?.trim()) return` or use the provided `isValidAccelerator(input)` / `tryParseAccelerator` helper if available.","Treat an empty binding as 'no shortcut' in your settings model (store `null` rather than `''`).","Validate in the settings UI: disable the save/apply action while the field is empty.","If parsing user input live, skip the `parseAccelerator` call until the string is non-empty."],"exampleFix":"// before\nconst acc = parseAccelerator(settings.globalShortcut ?? '')\n\n// after\nconst raw = settings.globalShortcut\nif (!raw || !raw.trim()) return  // treat as unbound\nconst acc = parseAccelerator(raw)","handlingStrategy":"validation","validationCode":"function shouldParseAccelerator(input: unknown): input is string {\n  return typeof input === 'string' && input.trim().length > 0\n}\n\nif (shouldParseAccelerator(raw)) parseAccelerator(raw)","typeGuard":"function isNonEmptyAcceleratorString(input: unknown): input is string {\n  return typeof input === 'string' && input.trim().length > 0\n}","tryCatchPattern":"try {\n  const acc = parseAccelerator(raw)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid accelerator')) {\n    // treat as unbound; do not register\n  } else throw e\n}","preventionTips":["Store unbound shortcuts as null, not empty string.","Run isValidAccelerator(input) on any user-supplied keybind before parse.","Disable the apply/save action in the UI while the field is empty."],"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"}