diff --git a/.changeset/fn-7436-auth-anthropic-order.md b/.changeset/fn-7436-auth-anthropic-order.md
new file mode 100644
index 0000000000..ec76f11867
--- /dev/null
+++ b/.changeset/fn-7436-auth-anthropic-order.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Keep Anthropic authentication cards grouped near the top in Settings.
+category: fix
+dev: Sorts Claude CLI, Anthropic Subscription, and Anthropic API Key before other auth cards within each auth group.
diff --git a/packages/dashboard/app/components/__tests__/AuthenticationSection.test.tsx b/packages/dashboard/app/components/__tests__/AuthenticationSection.test.tsx
index f6a45817d1..c682369b16 100644
--- a/packages/dashboard/app/components/__tests__/AuthenticationSection.test.tsx
+++ b/packages/dashboard/app/components/__tests__/AuthenticationSection.test.tsx
@@ -28,9 +28,35 @@ vi.mock("../CustomProvidersSection", () => ({
CustomProvidersSection: () =>
,
}));
-vi.mock("../ClaudeCliProviderCard", () => ({ ClaudeCliProviderCard: () => }));
-vi.mock("../CursorCliProviderCard", () => ({ CursorCliProviderCard: () => }));
-vi.mock("../LlamaCppProviderCard", () => ({ LlamaCppProviderCard: () => }));
+vi.mock("../ClaudeCliProviderCard", () => ({
+ ClaudeCliProviderCard: ({ authenticated }: { authenticated: boolean }) => (
+
+ ),
+}));
+vi.mock("../CursorCliProviderCard", () => ({
+ CursorCliProviderCard: ({ authenticated }: { authenticated: boolean }) => (
+
+ ),
+}));
+vi.mock("../LlamaCppProviderCard", () => ({
+ LlamaCppProviderCard: ({ authenticated }: { authenticated: boolean }) => (
+
+ ),
+}));
+
+function authCardOrder(groupLabel: "Authenticated" | "Available") {
+ const group = screen.getByText(groupLabel).closest(".auth-provider-group") as HTMLElement;
+ return Array.from(group.children)
+ .map((child) => {
+ const element = child as HTMLElement;
+ if (element.dataset.testid === "claude-cli-provider-card") return "claude-cli";
+ if (element.dataset.testid === "cursor-cli-provider-card") return "cursor-cli";
+ if (element.dataset.testid === "llama-cpp-provider-card") return "llama-cpp";
+ const icon = element.querySelector("[data-testid^='auth-provider-icon-']");
+ return icon?.dataset.testid?.replace("auth-provider-icon-", "") ?? null;
+ })
+ .filter((providerId): providerId is string => Boolean(providerId));
+}
function renderAuthSection(providers: AuthProvider[], overrides: Partial = {}) {
const handleLogin = vi.fn();
@@ -77,6 +103,77 @@ describe("AuthenticationSection", () => {
vi.clearAllMocks();
});
+ it("sorts visible Anthropic standard providers together near the top", () => {
+ renderAuthSection([
+ { id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
+ { id: "anthropic-api-key", name: "Anthropic API Key", authenticated: false, type: "api_key" },
+ { id: "github-copilot", name: "GitHub Copilot", authenticated: false, type: "oauth" },
+ { id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: false, type: "oauth" },
+ { id: "openrouter", name: "OpenRouter", authenticated: false, type: "api_key" },
+ ]);
+
+ expect(authCardOrder("Available")).toEqual([
+ "anthropic-subscription",
+ "anthropic-api-key",
+ "github-copilot",
+ "openai",
+ "openrouter",
+ ]);
+ });
+
+ it("prioritizes Anthropic cards within each auth state without crossing group boundaries", () => {
+ renderAuthSection([
+ { id: "openai", name: "OpenAI", authenticated: true, type: "api_key" },
+ { id: "anthropic-api-key", name: "Anthropic API Key", authenticated: false, type: "api_key" },
+ { id: "github-copilot", name: "GitHub Copilot", authenticated: false, type: "oauth" },
+ { id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: true, type: "oauth" },
+ { id: "openrouter", name: "OpenRouter", authenticated: true, type: "api_key" },
+ ]);
+
+ expect(authCardOrder("Authenticated")).toEqual(["anthropic-subscription", "openai", "openrouter"]);
+ expect(authCardOrder("Available")).toEqual(["anthropic-api-key", "github-copilot"]);
+ });
+
+ it("hides legacy Anthropic when separated cards are present without breaking priority order", () => {
+ renderAuthSection([
+ { id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
+ { id: "anthropic-api-key", name: "Anthropic API Key", authenticated: false, type: "api_key" },
+ { id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
+ { id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: false, type: "oauth" },
+ ]);
+
+ expect(screen.queryByTestId("auth-provider-icon-anthropic")).not.toBeInTheDocument();
+ expect(authCardOrder("Available")).toEqual(["anthropic-subscription", "anthropic-api-key", "openai"]);
+ });
+
+ it("keeps Claude CLI first among CLI-backed authentication cards", () => {
+ renderAuthSection([
+ { id: "llama-cpp", name: "Llama.cpp", authenticated: false, type: "cli" },
+ { id: "cursor-cli", name: "Cursor CLI", authenticated: false, type: "cli" },
+ { id: "claude-cli", name: "Anthropic — via Claude CLI", authenticated: false, type: "cli" },
+ ]);
+
+ expect(authCardOrder("Available")).toEqual(["claude-cli", "cursor-cli", "llama-cpp"]);
+ });
+
+ it("groups Anthropic CLI, subscription, and API-key cards before other CLI providers", () => {
+ renderAuthSection([
+ { id: "cursor-cli", name: "Cursor CLI", authenticated: false, type: "cli" },
+ { id: "anthropic-api-key", name: "Anthropic API Key", authenticated: false, type: "api_key" },
+ { id: "llama-cpp", name: "Llama.cpp", authenticated: false, type: "cli" },
+ { id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: false, type: "oauth" },
+ { id: "claude-cli", name: "Anthropic — via Claude CLI", authenticated: false, type: "cli" },
+ ]);
+
+ expect(authCardOrder("Available")).toEqual([
+ "claude-cli",
+ "anthropic-subscription",
+ "anthropic-api-key",
+ "cursor-cli",
+ "llama-cpp",
+ ]);
+ });
+
it("renders separate Anthropic subscription OAuth and API-key cards", () => {
const { handleLogin, handleSaveApiKey } = renderAuthSection([
{ id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: false, type: "oauth" },
diff --git a/packages/dashboard/app/components/settings/sections/AuthenticationSection.tsx b/packages/dashboard/app/components/settings/sections/AuthenticationSection.tsx
index d657201158..08e01136d4 100644
--- a/packages/dashboard/app/components/settings/sections/AuthenticationSection.tsx
+++ b/packages/dashboard/app/components/settings/sections/AuthenticationSection.tsx
@@ -44,6 +44,32 @@ export interface AuthenticationSectionData {
export interface AuthenticationSectionProps {
auth: AuthenticationSectionData;
}
+const ANTHROPIC_AUTH_PROVIDER_PRIORITY: Record = {
+ "claude-cli": 0,
+ "anthropic-subscription": 1,
+ "anthropic-api-key": 2,
+ anthropic: 3,
+};
+const getAuthProviderPriority = (provider: AuthProvider) => ANTHROPIC_AUTH_PROVIDER_PRIORITY[provider.id] ?? Number.POSITIVE_INFINITY;
+/*
+FNXC:ProviderAuth 2026-07-02-11:26:
+Settings groups Anthropic-family auth surfaces near the top so the Claude CLI, subscription OAuth, and API-key paths stay discoverable after the provider split while each Authenticated/Available group keeps its own boundary.
+*/
+const compareAuthProviderDisplayOrder = (a: AuthProvider, b: AuthProvider) => {
+ if (a.authenticated !== b.authenticated) {
+ return a.authenticated ? -1 : 1;
+ }
+ const aPriority = getAuthProviderPriority(a);
+ const bPriority = getAuthProviderPriority(b);
+ if (aPriority !== bPriority) {
+ return aPriority - bPriority;
+ }
+ const nameDelta = a.name.localeCompare(b.name);
+ if (nameDelta !== 0) {
+ return nameDelta;
+ }
+ return a.id.localeCompare(b.id);
+};
export function AuthenticationSection({ auth }: AuthenticationSectionProps) {
const { t } = useTranslation("app");
const { projectId, addToast, authProviders, authLoading, authActionInProgress, apiKeyInputs, setApiKeyInputs, apiKeyErrors, opencodeApiKeyRefreshStatus, deviceCodes, loginInstructions, manualCodeConfigs, manualCodeInputs, setManualCodeInputs, manualCodeSubmitInProgress, loadAuthStatus, handleLogin, handleLogout, handleCancelLogin, handleSaveApiKey, handleClearApiKey, handleSubmitManualCode, onReopenOnboarding, } = auth;
@@ -55,38 +81,33 @@ export function AuthenticationSection({ auth }: AuthenticationSectionProps) {
const visibleAuthProviders = hasSeparatedAnthropicProvider
? authProviders.filter((p) => p.id !== "anthropic")
: authProviders;
- // CLI-backed providers render their own compact card; filter them out of the
- // standard OAuth/API-key sort and render alongside.
- const cliAuthProviders = visibleAuthProviders.filter((p) => p.type === "cli");
- const nonCliProviders = visibleAuthProviders.filter((p) => p.type !== "cli");
- const sortedProviders = [...nonCliProviders].sort((a, b) => {
- if (a.authenticated !== b.authenticated) {
- return a.authenticated ? -1 : 1;
- }
- return a.name.localeCompare(b.name);
- });
+ const isSupportedCliProvider = (provider: AuthProvider) => provider.id === "claude-cli" || provider.id === "cursor-cli" || provider.id === "llama-cpp";
+ /*
+ FNXC:ProviderAuth 2026-07-02-12:20:
+ Authentication ordering must sort supported CLI and non-CLI provider cards in one list so Cursor CLI or llama.cpp cannot split Claude CLI from Anthropic subscription/API-key entries.
+ */
+ const sortedProviders = [...visibleAuthProviders]
+ .filter((p) => p.type !== "cli" || isSupportedCliProvider(p))
+ .sort(compareAuthProviderDisplayOrder);
const authenticatedProviders = sortedProviders.filter((p) => p.authenticated);
const unauthenticatedProviders = sortedProviders.filter((p) => !p.authenticated);
- const claudeCliProvider = cliAuthProviders.find((p) => p.id === "claude-cli");
- const cursorCliProvider = cliAuthProviders.find((p) => p.id === "cursor-cli");
- const llamaCppProvider = cliAuthProviders.find((p) => p.id === "llama-cpp");
- const claudeCliCard = claudeCliProvider ? ( {
- void loadAuthStatus();
- }}/>) : null;
- const cursorCliCard = cursorCliProvider ? ( {
- void loadAuthStatus();
- }}/>) : null;
- const llamaCppCard = llamaCppProvider ? ( {
- void loadAuthStatus();
- }}/>) : null;
- const showAuthenticatedGroup = authenticatedProviders.length > 0 ||
- (claudeCliProvider?.authenticated ?? false) ||
- (cursorCliProvider?.authenticated ?? false) ||
- (llamaCppProvider?.authenticated ?? false);
- const showAvailableGroup = unauthenticatedProviders.length > 0 ||
- (claudeCliProvider && !claudeCliProvider.authenticated) ||
- (cursorCliProvider && !cursorCliProvider.authenticated) ||
- (llamaCppProvider && !llamaCppProvider.authenticated);
+ const renderCliProviderCard = (provider: AuthProvider) => {
+ if (provider.id === "claude-cli") {
+ return ( {
+ void loadAuthStatus();
+ }}/>);
+ }
+ if (provider.id === "cursor-cli") {
+ return ( {
+ void loadAuthStatus();
+ }}/>);
+ }
+ return ( {
+ void loadAuthStatus();
+ }}/>);
+ };
+ const showAuthenticatedGroup = authenticatedProviders.length > 0;
+ const showAvailableGroup = unauthenticatedProviders.length > 0;
const providerSupportsApiKey = (provider: AuthProvider) => provider.type === "api_key";
const renderApiKeySection = (provider: AuthProvider) => (
@@ -170,10 +191,7 @@ export function AuthenticationSection({ auth }: AuthenticationSectionProps) {
)}
{showAuthenticatedGroup && (
{t("settings.auth.groupAuthenticated", "Authenticated")}
- {claudeCliProvider?.authenticated && claudeCliCard}
- {cursorCliProvider?.authenticated && cursorCliCard}
- {llamaCppProvider?.authenticated && llamaCppCard}
- {authenticatedProviders.map((provider) => (
+ {authenticatedProviders.map((provider) => provider.type === "cli" ? renderCliProviderCard(provider) : (
{/* Stable icon wrapper contract for auth card tests: auth-provider-icon-
*/}
@@ -193,10 +211,7 @@ export function AuthenticationSection({ auth }: AuthenticationSectionProps) {
)}
{showAvailableGroup && (
{t("settings.auth.groupAvailable", "Available")}
- {claudeCliProvider && !claudeCliProvider.authenticated && claudeCliCard}
- {cursorCliProvider && !cursorCliProvider.authenticated && cursorCliCard}
- {llamaCppProvider && !llamaCppProvider.authenticated && llamaCppCard}
- {unauthenticatedProviders.map((provider) => (
+ {unauthenticatedProviders.map((provider) => provider.type === "cli" ? renderCliProviderCard(provider) : (
{/* Stable icon wrapper contract for auth card tests: auth-provider-icon-
*/}