From 9fb93c8ea2f6835cf73488cb46012e874433d3f7 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 12 Sep 2026 10:44:15 +0100 Subject: [PATCH] feat: default the new-identity wizard's profile prompt to skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare Enter on "Create a default configuration profile for ?" picked "create", since a select prompt with no explicit initial value defaults to whichever option is listed first. Most identities need no profile of their own at all — they're meant to fall through to whatever the global default (or a directory rule) already resolves to, and a profile only earns its keep once an identity genuinely needs to diverge from that. Defaulting to "create" nudged every new identity toward its own profile regardless. SelectParams gains an initialValue field, threaded straight into @clack/prompts' own select(), which already supports it — this moves the default answer without reordering the options themselves, so "skip" becomes what Enter confirms without becoming the first, most prominent line on screen. The wizard's own prompt sets it to "skip". --- src/configure.ts | 3 +++ src/identityManager.test.ts | 19 +++++++++++++++++++ src/identityManager.ts | 2 ++ 3 files changed, 24 insertions(+) diff --git a/src/configure.ts b/src/configure.ts index eddf9a3..312158b 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -47,6 +47,8 @@ interface PromptOption { export interface SelectParams { readonly message: string; readonly options: readonly PromptOption[]; + /** Which option the cursor starts on. Omit to let the prompt library fall back to its own default (the first option) — pass this explicitly whenever the safest or most common answer isn't the first one listed, so the on-screen reading order and the default-on-Enter answer can differ deliberately rather than being forced to match. */ + readonly initialValue?: Value; } /** Parameters for a multi-select prompt. */ @@ -103,6 +105,7 @@ export const realPromptsPort: PromptsPort = { .select({ message: params.message, options: toClackOptions(params.options), + ...(params.initialValue === undefined ? {} : { initialValue: params.initialValue }), }) .then((value): Value | symbol => { if (typeof value === "symbol" || isKnownOptionValue(value, params.options)) { diff --git a/src/identityManager.test.ts b/src/identityManager.test.ts index 3e974fe..0476edc 100644 --- a/src/identityManager.test.ts +++ b/src/identityManager.test.ts @@ -218,6 +218,25 @@ describe("identityManager", () => { expect(readIdentity(paths, "joseph.mearman@exadev.io")).toBeDefined(); expect(readActiveIdentity(paths)).toBe("joseph.mearman@exadev.io"); }); + + it("defaults the profile-creation prompt's cursor to skip, so a bare Enter creates no profile", async () => { + const selectCalls: SelectParams[] = []; + const base = scriptedIdentityPrompts(["create", "skip"]); + const spying: PromptsPort = { + ...base, + select: (params) => { + selectCalls.push(params); + return base.select(params); + }, + }; + + await runIdentityWizard(spying, paths, "work"); + + const profilePrompt = selectCalls.find((call) => call.message.includes("configuration profile")); + expect(profilePrompt?.initialValue).toBe("skip"); + // The option order stays create-then-skip regardless — initialValue moves the default answer without reordering what's read on screen. + expect(profilePrompt?.options.map((option) => option.value)).toEqual(["create", "skip"]); + }); }); describe("isIdentityDirectoryName", () => { diff --git a/src/identityManager.ts b/src/identityManager.ts index 9cdf6b4..8b63c1e 100644 --- a/src/identityManager.ts +++ b/src/identityManager.ts @@ -114,6 +114,8 @@ export async function runIdentityWizard(prompts: PromptsPort, paths: LayoutPaths { value: "create", label: "Create and configure a profile" }, { value: "skip", label: "Skip for now" }, ], + // Most identities need no profile of their own at all — they're meant to fall through to whatever the global default (or a directory rule) already resolves to, and a profile only earns its keep once an identity genuinely needs to diverge from that. Defaulting the cursor to "skip" makes the common, no-profile-needed case the one a bare Enter confirms, without reordering the options and making "skip" read as the first, most prominent choice on screen. + initialValue: "skip", }); if (!prompts.isCancel(profileChoice) && profileChoice === "create") { const result = await runProfileWizard(prompts, { paths, defaultNewName: name });