{"record":{"id":"a286d957d61aeb4f","repo":"midudev/autoskills","slug":"initialselected-length-initialselected-length-must-match","errorCode":null,"errorMessage":"initialSelected length (${initialSelected.length}) must match items length (${items.length})","messagePattern":"initialSelected length \\((.+?)\\) must match items length \\((.+?)\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/autoskills/ui.ts","lineNumber":102,"sourceCode":"  write(dim(`   ${subtitle}`) + \"\\n\");\n  write(SHOW_CURSOR);\n  log();\n}\n\ninterface MultiSelectOptions<T> {\n  labelFn: (item: T, i: number) => string;\n  hintFn?: (item: T, i: number) => string;\n  groupFn?: (item: T) => string;\n  initialSelected?: boolean[];\n  shortcuts?: { key: string; label: string; fn: (items: T[]) => boolean[] }[];\n}\n\nexport function multiSelect<T>(\n  items: T[],\n  { labelFn, hintFn, groupFn, initialSelected, shortcuts = [] }: MultiSelectOptions<T>,\n): Promise<T[]> {\n  if (initialSelected && initialSelected.length !== items.length) {\n    throw new Error(\n      `initialSelected length (${initialSelected.length}) must match items length (${items.length})`,\n    );\n  }\n\n  if (!process.stdin.isTTY) return Promise.resolve(items);\n\n  return new Promise((resolve) => {\n    const selected = initialSelected\n      ? initialSelected.slice()\n      : Array.from({ length: items.length }, () => true);\n    let cursor = 0;\n    let rendered = false;\n\n    let groupCount = 0;\n    if (groupFn) {\n      let last: string | null = null;\n      for (const item of items) {\n        const g = groupFn(item);","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/midudev/autoskills/blob/0ec725320d2137253ab2e68e7ba8a072148e741a/packages/autoskills/ui.ts#L84-L120","documentation":"The multiSelect UI helper requires initialSelected to be a per-item boolean-selection map expressed positionally: it must have exactly one entry per item in items. Passing an array of different length is a programming error because selections cannot be meaningfully aligned to items, so the function fails fast.","triggerScenarios":"Calling multiSelect(items, { initialSelected }) where initialSelected.length !== items.length — e.g. preselecting by filtering 'selected item objects' instead of building a boolean array aligned to the items list, or reusing a stale selection array after the items list changed.","commonSituations":"Persisting user selections and passing them back after the item list was re-filtered or re-sorted; confusing 'list of selected values' with the per-item selection mask the API expects; dynamic skill lists changing length between runs.","solutions":["Build initialSelected as items.map(item => <whether this item should start selected>) so lengths always match.","If you store selected values, convert them to the aligned array: items.map(i => savedSelections.includes(valueOf(i))).","Pass undefined instead of an array when you have no prior selection (the parameter is optional).","Recompute the selection array at call time from the current items rather than caching it."],"exampleFix":"// before\nconst initialSelected = previouslyChosenSkills; // array of chosen items, wrong length\nawait multiSelect(allSkills, { initialSelected });\n\n// after\nconst initialSelected = allSkills.map(s => previouslyChosenSkills.includes(s));\nawait multiSelect(allSkills, { initialSelected });","handlingStrategy":"validation","validationCode":"function alignedSelection(items, chosenValues, valueOf) {\n  const set = new Set(chosenValues);\n  return items.map(i => set.has(valueOf(i)));\n}\n// call-site guard\nconst initialSelected = alignedSelection(allSkills, savedSkillIds, s => s.id);\nif (initialSelected.length !== allSkills.length) throw new Error(\"selection misaligned\");","typeGuard":"const isAlignedSelection = <T,>(items: T[], sel?: unknown[]): sel is boolean[] =>\n  Array.isArray(sel) && sel.every(v => typeof v === \"boolean\") && sel.length === items.length;","tryCatchPattern":"try {\n  return await multiSelect(items, { initialSelected });\n} catch (e) {\n  if (e.message.includes(\"must match items length\")) {\n    return multiSelect(items, {}); // degrade to no preselection instead of crashing\n  } else throw e;\n}","preventionTips":["Always derive initialSelected from the current items array via items.map(...).","Never cache selection masks across runs where the item list can change.","Omit initialSelected entirely when there is no prior state.","Add a unit test asserting selection array length equals items length for each list state."],"tags":["ui","api-misuse","array-length","typescript"],"backgroundTag":"shape-mismatch","analyzedSha":"0ec725320d2137253ab2e68e7ba8a072148e741a","analyzedAt":"2026-09-15T14:12:31.090Z","contentChangedAt":"2026-09-15T14:12:31.090Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}