From 9ae01f76838ba5fbab825fc4eec95f6d5ec2fa56 Mon Sep 17 00:00:00 2001 From: Rafael Thayto Tani Date: Thu, 30 Jul 2026 12:15:59 -0300 Subject: [PATCH] fix(link): pin the create-application option to the top of the app picker The "+ Create a new application" choice in the shared app picker was appended after all applications, so with a long app list it was hidden below the truncation fold in `clerk link`, `clerk init`, and the `clerk users` fallback picker. Render it as the first row, both in the unfiltered list and while a search term is typed. --- .changeset/commands-update-order-explore.md | 5 ++++ .../cli-core/src/commands/link/index.test.ts | 23 +++++++++++-------- packages/cli-core/src/lib/app-picker.ts | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 .changeset/commands-update-order-explore.md diff --git a/.changeset/commands-update-order-explore.md b/.changeset/commands-update-order-explore.md new file mode 100644 index 000000000..78fcc2062 --- /dev/null +++ b/.changeset/commands-update-order-explore.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Show the "+ Create a new application" option first in the interactive application picker used by `clerk link` and `clerk users`. diff --git a/packages/cli-core/src/commands/link/index.test.ts b/packages/cli-core/src/commands/link/index.test.ts index 96819075d..044cb81df 100644 --- a/packages/cli-core/src/commands/link/index.test.ts +++ b/packages/cli-core/src/commands/link/index.test.ts @@ -434,7 +434,7 @@ describe("link", () => { expect(mockFetchApplication).not.toHaveBeenCalled(); }); - test("source returns all choices plus create option when term is empty", async () => { + test("source returns create option first, then all choices, when term is empty", async () => { mockIsAgent.mockReturnValue(false); mockGetToken.mockResolvedValue("token"); mockListApplications.mockResolvedValue([ @@ -454,9 +454,14 @@ describe("link", () => { }, ]); mockSearch.mockImplementation( - async (config: { source: (term: string | undefined) => unknown[] }) => { + async (config: { + source: (term: string | undefined) => { name: string; value: string }[]; + }) => { const results = config.source(undefined); - expect(results).toHaveLength(3); // 2 apps + create option + expect(results).toHaveLength(3); // create option + 2 apps + expect(results[0]!.value).toBe("__create_new__"); + expect(results[1]!.value).toBe("app_a"); + expect(results[2]!.value).toBe("app_b"); return "app_a"; }, ); @@ -489,9 +494,9 @@ describe("link", () => { source: (term: string | undefined) => { name: string; value: string }[]; }) => { const results = config.source("my"); - expect(results).toHaveLength(2); // 1 match + create option - expect(results[0]!.value).toBe("app_a"); - expect(results[1]!.value).toBe("__create_new__"); + expect(results).toHaveLength(2); // create option + 1 match + expect(results[0]!.value).toBe("__create_new__"); + expect(results[1]!.value).toBe("app_a"); const noMatch = config.source("zzz"); expect(noMatch).toHaveLength(1); // only create option @@ -528,9 +533,9 @@ describe("link", () => { source: (term: string | undefined) => { name: string; value: string }[]; }) => { const results = config.source("abc"); - expect(results).toHaveLength(2); // 1 match + create option - expect(results[0]!.value).toBe("app_abc"); - expect(results[1]!.value).toBe("__create_new__"); + expect(results).toHaveLength(2); // create option + 1 match + expect(results[0]!.value).toBe("__create_new__"); + expect(results[1]!.value).toBe("app_abc"); return "app_abc"; }, ); diff --git a/packages/cli-core/src/lib/app-picker.ts b/packages/cli-core/src/lib/app-picker.ts index abd846bfc..6a230424a 100644 --- a/packages/cli-core/src/lib/app-picker.ts +++ b/packages/cli-core/src/lib/app-picker.ts @@ -57,7 +57,7 @@ export async function pickOrCreateApp(opts: { const filtered = term ? appChoices.filter((c) => c.name.toLowerCase().includes(term.toLowerCase())) : appChoices; - return [...filtered, createChoice]; + return [createChoice, ...filtered]; }, });