From 1beaa0118bd6bc1e399824c7f0d078878a7ca8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Wed, 14 Jan 2026 12:21:40 +0100 Subject: [PATCH] feat(cli): restore IDE grouping using groupMultiselect Replace flat multiselect with native @clack/prompts groupMultiselect component to restore visual grouping of IDE/tool options: - "Previously Configured" - pre-selected IDEs from existing install - "Recommended Tools" - starred preferred options - "Additional Tools" - other available options This restores the grouped UX that was lost during the Inquirer.js to @clack/prompts migration. Co-Authored-By: Claude Opus 4.5 --- tools/cli/lib/prompts.js | 25 +++++++++++++++++++++ tools/cli/lib/ui.js | 47 ++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/tools/cli/lib/prompts.js b/tools/cli/lib/prompts.js index 51deb2c0..96b80ba1 100644 --- a/tools/cli/lib/prompts.js +++ b/tools/cli/lib/prompts.js @@ -166,6 +166,30 @@ async function multiselect(options) { return result; } +/** + * Grouped multi-select prompt for categorized options + * @param {Object} options - Prompt options + * @param {string} options.message - The question to ask + * @param {Object} options.options - Object mapping group names to arrays of choices + * @param {Array} [options.initialValues] - Array of initially selected values + * @param {boolean} [options.required=false] - Whether at least one must be selected + * @param {boolean} [options.selectableGroups=false] - Whether groups can be selected as a whole + * @returns {Promise} Array of selected values + */ +async function groupMultiselect(options) { + const clack = await getClack(); + + const result = await clack.groupMultiselect({ + message: options.message, + options: options.options, + initialValues: options.initialValues, + required: options.required || false, + }); + + await handleCancel(result); + return result; +} + /** * Confirm prompt (replaces Inquirer 'confirm' type) * @param {Object} options - Prompt options @@ -397,6 +421,7 @@ module.exports = { spinner, select, multiselect, + groupMultiselect, confirm, text, password, diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index 02b42ab1..a78cfec9 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -442,13 +442,14 @@ class UI { const preferredIdes = ideManager.getPreferredIdes(); const otherIdes = ideManager.getOtherIdes(); - // Build IDE choices array with separators - const ideChoices = []; + // Build grouped options object for groupMultiselect + const groupedOptions = {}; const processedIdes = new Set(); + const initialValues = []; // First, add previously configured IDEs at the top, marked with ✅ if (configuredIdes.length > 0) { - ideChoices.push(new choiceUtils.Separator('── Previously Configured ──')); + const configuredGroup = []; for (const ideValue of configuredIdes) { // Skip empty or invalid IDE values if (!ideValue || typeof ideValue !== 'string') { @@ -461,44 +462,41 @@ class UI { const ide = preferredIde || otherIde; if (ide) { - ideChoices.push({ - name: `${ide.name} ✅`, + configuredGroup.push({ + label: `${ide.name} ✅`, value: ide.value, - checked: true, // Previously configured IDEs are checked by default }); processedIdes.add(ide.value); + initialValues.push(ide.value); // Pre-select configured IDEs } else { // Warn about unrecognized IDE (but don't fail) console.log(chalk.yellow(`⚠️ Previously configured IDE '${ideValue}' is no longer available`)); } } + if (configuredGroup.length > 0) { + groupedOptions['Previously Configured'] = configuredGroup; + } } // Add preferred tools (excluding already processed) const remainingPreferred = preferredIdes.filter((ide) => !processedIdes.has(ide.value)); if (remainingPreferred.length > 0) { - ideChoices.push(new choiceUtils.Separator('── Recommended Tools ──')); - for (const ide of remainingPreferred) { - ideChoices.push({ - name: `${ide.name} ⭐`, - value: ide.value, - checked: false, - }); + groupedOptions['Recommended Tools'] = remainingPreferred.map((ide) => { processedIdes.add(ide.value); - } + return { + label: `${ide.name} ⭐`, + value: ide.value, + }; + }); } // Add other tools (excluding already processed) const remainingOther = otherIdes.filter((ide) => !processedIdes.has(ide.value)); if (remainingOther.length > 0) { - ideChoices.push(new choiceUtils.Separator('── Additional Tools ──')); - for (const ide of remainingOther) { - ideChoices.push({ - name: ide.name, - value: ide.value, - checked: false, - }); - } + groupedOptions['Additional Tools'] = remainingOther.map((ide) => ({ + label: ide.name, + value: ide.value, + })); } let selectedIdes = []; @@ -506,9 +504,10 @@ class UI { // Loop until user selects at least one tool OR explicitly confirms no tools while (!userConfirmedNoTools) { - selectedIdes = await prompts.multiselect({ + selectedIdes = await prompts.groupMultiselect({ message: `Select tools to configure ${chalk.dim('(↑/↓ navigate, SPACE select, ENTER confirm)')}:`, - choices: ideChoices, + options: groupedOptions, + initialValues: initialValues.length > 0 ? initialValues : undefined, required: false, });