diff --git a/.changeset/fn-7468-onboarding-quick-providers.md b/.changeset/fn-7468-onboarding-quick-providers.md
new file mode 100644
index 0000000000..5fec19580c
--- /dev/null
+++ b/.changeset/fn-7468-onboarding-quick-providers.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": minor
+---
+
+summary: Expand first-run AI provider quick-start choices beyond Anthropic.
+category: feature
+dev: Moves advanced/all-provider onboarding controls under the quick-start provider section.
diff --git a/packages/dashboard/app/components/ModelOnboardingModal.tsx b/packages/dashboard/app/components/ModelOnboardingModal.tsx
index 53756029d7..837b099adf 100644
--- a/packages/dashboard/app/components/ModelOnboardingModal.tsx
+++ b/packages/dashboard/app/components/ModelOnboardingModal.tsx
@@ -245,10 +245,11 @@ function getProviderDisplayName(providerId: string): string {
}
/*
-FNXC:ProviderAuth 2026-06-29-23:58:
-Onboarding quick start must show Anthropic subscription OAuth and raw Anthropic API-key auth as separate first-class cards; keep the legacy `anthropic` id as a fallback only for older status payloads.
+FNXC:Onboarding 2026-07-03-00:00:
+Onboarding quick start must show practical first-run choices beyond Anthropic, including OpenAI/ChatGPT, Google/Gemini, OpenRouter, and Ollama when those visible providers are configured.
+Keep Anthropic subscription OAuth and raw Anthropic API-key auth as separate first-class cards; use the legacy `anthropic` id only as a fallback for older status payloads.
*/
-const QUICK_START_PROVIDER_IDS = ["anthropic-subscription", "anthropic-api-key", "anthropic", "openai", "google", "gemini", "ollama"] as const;
+const QUICK_START_PROVIDER_IDS = ["anthropic-subscription", "anthropic-api-key", "anthropic", "openai", "google", "gemini", "openrouter", "ollama"] as const;
const ONBOARDING_CURATED_PROVIDER_FAMILY_ORDER = [
"anthropic",
@@ -257,6 +258,7 @@ const ONBOARDING_CURATED_PROVIDER_FAMILY_ORDER = [
"cursor-cli",
"llama-cpp",
"openai-codex",
+ "openrouter",
"gemini",
"minimax",
"kimi",
@@ -2539,6 +2541,48 @@ export function ModelOnboardingModal({
{t("setup.noQuickStartProviders", "No quick-start providers are available in this environment.")}
)}
+
+ {/*
+ FNXC:Onboarding 2026-07-03-00:00:
+ Expanded/all-provider controls belong directly under quick-start providers so the advanced provider list, custom-provider list, and add-custom-provider form stay visually tied to the first provider decision.
+ */}
+
+
+ {advancedProviders.length > 0 ? (
+
+ {advancedProviders.map((provider) => renderAiProviderCard(provider))}
+
+ ) : (
+
+ {t("setup.allProvidersShown", "All currently available providers are already shown above.")}
+
+ )}
+
+ {customProviders.length > 0 ? (
+
+ {customProviders.map((provider) => (
+
+
+
{provider.name || provider.id}
+
+ ))}
+
+ ) : null}
+
+ {!showCustomProviderForm ? (
+
setShowCustomProviderForm(true)}>
+ {t("setup.addCustomProvider", "Add custom provider")}
+
+ ) : (
+
{ setShowCustomProviderForm(false); setCustomProviderError(undefined); }}
+ saving={customProviderSaving}
+ error={customProviderError}
+ />
+ )}
+
+
{connectedNonQuickStartProviders.length > 0 && (
@@ -2592,44 +2636,6 @@ export function ModelOnboardingModal({
)}
-
-
- {advancedProviders.length > 0 ? (
-
- {advancedProviders.map((provider) => renderAiProviderCard(provider))}
-
- ) : (
-
- {t("setup.allProvidersShown", "All currently available providers are already shown above.")}
-
- )}
-
- {customProviders.length > 0 ? (
-
- {customProviders.map((provider) => (
-
-
-
{provider.name || provider.id}
-
- ))}
-
- ) : null}
-
- {!showCustomProviderForm ? (
-
setShowCustomProviderForm(true)}>
- {t("setup.addCustomProvider", "Add custom provider")}
-
- ) : (
-
{ setShowCustomProviderForm(false); setCustomProviderError(undefined); }}
- saving={customProviderSaving}
- error={customProviderError}
- />
- )}
-
-
-
{/* OAuth login disclosure */}
{hasOauthProviders && (
diff --git a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx
index 734c2fffa4..3a53534d93 100644
--- a/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ModelOnboardingModal.test.tsx
@@ -225,6 +225,12 @@ async function navigateToFirstTaskStep() {
});
}
+function getProviderOrderInSection(container: HTMLElement): string[] {
+ return Array.from(container.querySelectorAll("[data-testid^='onboarding-provider-card-']"))
+ .map((card) => card.dataset.testid?.replace("onboarding-provider-card-", "") ?? "")
+ .filter(Boolean);
+}
+
beforeEach(() => {
vi.clearAllMocks();
clearAuthToken();
@@ -603,6 +609,43 @@ describe("ModelOnboardingModal", () => {
expect(screen.getByRole("button", { name: /Advanced provider settings/ })).toBeTruthy();
});
+ it("orders practical quick-start providers without showing filtered providers", async () => {
+ mockFetchAuthStatus.mockResolvedValueOnce({
+ providers: [
+ { id: "minimax", name: "MiniMax", authenticated: false, type: "api_key" },
+ { id: "ollama", name: "Ollama", authenticated: false, type: "api_key" },
+ { id: "openrouter", name: "OpenRouter", authenticated: false, type: "api_key" },
+ { id: "google-antigravity", name: "Google Antigravity", authenticated: false, type: "oauth" },
+ { id: "google", name: "Google", authenticated: false, type: "api_key" },
+ { id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
+ { id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
+ ],
+ });
+
+ render( );
+
+ const quickStartSection = await screen.findByTestId("onboarding-quick-start-providers");
+ expect(getProviderOrderInSection(quickStartSection)).toEqual(["anthropic", "openai", "google", "openrouter", "ollama"]);
+ expect(screen.queryByTestId("onboarding-provider-card-google-antigravity")).toBeNull();
+ });
+
+ it("preserves split Anthropic quick-start cards and suppresses legacy Anthropic fallback", async () => {
+ mockFetchAuthStatus.mockResolvedValueOnce({
+ providers: [
+ { id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
+ { id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
+ { id: "anthropic-api-key", name: "Anthropic API Key", authenticated: false, type: "api_key" },
+ { id: "anthropic-subscription", name: "Anthropic Subscription", authenticated: false, type: "oauth" },
+ ],
+ });
+
+ render( );
+
+ const quickStartSection = await screen.findByTestId("onboarding-quick-start-providers");
+ expect(getProviderOrderInSection(quickStartSection)).toEqual(["anthropic-subscription", "anthropic-api-key", "openai"]);
+ expect(within(quickStartSection).queryByTestId("onboarding-provider-card-anthropic")).toBeNull();
+ });
+
it("keeps advanced providers hidden by default until advanced settings is expanded", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
@@ -629,7 +672,7 @@ describe("ModelOnboardingModal", () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
- { id: "openrouter", name: "OpenRouter", authenticated: true, type: "api_key" },
+ { id: "minimax", name: "MiniMax", authenticated: true, type: "api_key" },
],
});
@@ -640,8 +683,8 @@ describe("ModelOnboardingModal", () => {
});
const connectedSection = screen.getByTestId("onboarding-connected-providers");
- expect(within(connectedSection).getByText("OpenRouter")).toBeTruthy();
- expect(screen.queryByTestId("onboarding-provider-card-openrouter")).toBeTruthy();
+ expect(within(connectedSection).getByText("MiniMax")).toBeTruthy();
+ expect(screen.queryByTestId("onboarding-provider-card-minimax")).toBeTruthy();
expect(screen.queryByTestId("onboarding-advanced-provider-settings")).toBeNull();
});
@@ -649,14 +692,75 @@ describe("ModelOnboardingModal", () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
- { id: "openrouter", name: "OpenRouter", authenticated: true, type: "api_key" },
+ { id: "minimax", name: "MiniMax", authenticated: true, type: "api_key" },
],
});
render( );
- const openRouterWrapper = await screen.findByTestId("onboarding-provider-icon-openrouter");
- expect(within(openRouterWrapper).getByTestId("provider-icon")).toHaveAttribute("data-provider", "openrouter");
+ const minimaxWrapper = await screen.findByTestId("onboarding-provider-icon-minimax");
+ expect(within(minimaxWrapper).getByTestId("provider-icon")).toHaveAttribute("data-provider", "minimax");
+ });
+
+ it("places the single advanced-provider disclosure inside quick start before default model selection", async () => {
+ render( );
+
+ const quickStartSection = await screen.findByTestId("onboarding-quick-start-providers");
+ const advancedButtons = screen.getAllByRole("button", { name: /Advanced provider settings/ });
+ expect(advancedButtons).toHaveLength(1);
+ expect(quickStartSection).toContainElement(advancedButtons[0]);
+
+ const defaultModelHeading = screen.getByText("Default Model (Optional)");
+ expect(
+ quickStartSection.compareDocumentPosition(defaultModelHeading) & Node.DOCUMENT_POSITION_FOLLOWING,
+ ).toBeTruthy();
+ });
+
+ it("shows empty advanced state when every visible provider is already quick-start", async () => {
+ mockFetchAuthStatus.mockResolvedValueOnce({
+ providers: [
+ { id: "anthropic", name: "Anthropic", authenticated: false, type: "oauth" },
+ { id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
+ ],
+ });
+
+ render( );
+
+ expect(await screen.findByTestId("onboarding-quick-start-providers")).toBeTruthy();
+ expect(screen.queryByTestId("onboarding-advanced-provider-settings")).toBeNull();
+
+ fireEvent.click(screen.getByRole("button", { name: /Advanced provider settings/ }));
+
+ expect(await screen.findByTestId("onboarding-advanced-provider-settings")).toHaveTextContent(
+ "All currently available providers are already shown above.",
+ );
+ });
+
+ it("keeps existing custom providers and add-custom controls in the quick-start advanced area", async () => {
+ mockFetchCustomProviders.mockResolvedValueOnce({
+ providers: [
+ { id: "custom-openai", name: "Custom OpenAI", baseUrl: "https://example.com", api: "openai-responses", models: [] },
+ ],
+ });
+
+ render( );
+
+ const quickStartSection = await screen.findByTestId("onboarding-quick-start-providers");
+ fireEvent.click(within(quickStartSection).getByRole("button", { name: /Advanced provider settings/ }));
+
+ const advancedPanel = await screen.findByTestId("onboarding-advanced-provider-settings");
+ expect(within(advancedPanel).getByText("Custom OpenAI")).toBeTruthy();
+ expect(within(advancedPanel).getByRole("button", { name: /Add custom provider/ })).toBeTruthy();
+ });
+
+ it("shows the no-provider empty state without rendering leftover advanced-provider controls", async () => {
+ mockFetchAuthStatus.mockResolvedValueOnce({ providers: [] });
+
+ render( );
+
+ expect(await screen.findByText("No AI providers are configured. Please check your Fusion configuration.")).toBeTruthy();
+ expect(screen.queryByTestId("onboarding-quick-start-providers")).toBeNull();
+ expect(screen.queryByRole("button", { name: /Advanced provider settings/ })).toBeNull();
});
it("shows model dropdown in AI Setup step", async () => {
diff --git a/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx b/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx
index 0465444098..80ea8348d5 100644
--- a/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx
+++ b/packages/dashboard/app/components/__tests__/onboarding-flow.test.tsx
@@ -433,7 +433,8 @@ describe("onboarding flow integration", () => {
{ id: "anthropic", name: "Anthropic", authenticated: true, type: "oauth" },
{ id: "google", name: "Google", authenticated: false, type: "oauth" },
{ id: "minimax", name: "MiniMax", authenticated: false, type: "api_key" },
- { id: "openrouter", name: "OpenRouter", authenticated: true, type: "api_key" },
+ { id: "openrouter", name: "OpenRouter", authenticated: false, type: "api_key" },
+ { id: "zai", name: "Zhipu AI", authenticated: true, type: "api_key" },
{ id: "github", name: "GitHub", authenticated: true, type: "oauth" },
],
});
@@ -448,10 +449,10 @@ describe("onboarding flow integration", () => {
expect(screen.getByRole("button", { name: /Advanced provider settings/ })).toHaveAttribute("aria-expanded", "false");
const quickStartSection = screen.getByTestId("onboarding-quick-start-providers");
- expect(getProviderOrderInSection(quickStartSection)).toEqual(["anthropic", "openai", "google"]);
+ expect(getProviderOrderInSection(quickStartSection)).toEqual(["anthropic", "openai", "google", "openrouter"]);
const connectedSection = screen.getByTestId("onboarding-connected-providers");
- expect(getProviderOrderInSection(connectedSection)).toEqual(["openrouter"]);
+ expect(getProviderOrderInSection(connectedSection)).toEqual(["zai"]);
expect(screen.queryByTestId("onboarding-provider-card-minimax")).not.toBeInTheDocument();
expect(screen.queryByTestId("onboarding-provider-card-moonshot")).not.toBeInTheDocument();
@@ -474,7 +475,7 @@ describe("onboarding flow integration", () => {
mockFetchAuthStatus.mockResolvedValue({
providers: [
{ id: "openai", name: "OpenAI", authenticated: false, type: "api_key" },
- { id: "openrouter", name: "OpenRouter", authenticated: true, type: "api_key" },
+ { id: "minimax", name: "MiniMax", authenticated: true, type: "api_key" },
{ id: "openai-codex", name: "OpenAI Codex", authenticated: false, type: "oauth" },
{ id: "claude-cli", name: "Anthropic — via Claude CLI", authenticated: false, type: "cli" },
],
@@ -489,7 +490,7 @@ describe("onboarding flow integration", () => {
expect(screen.getByTestId("onboarding-apikey-save-openai")).toBeInTheDocument();
const connectedSection = screen.getByTestId("onboarding-connected-providers");
- expect(within(connectedSection).getByText("OpenRouter")).toBeInTheDocument();
+ expect(within(connectedSection).getByText("MiniMax")).toBeInTheDocument();
expect(within(connectedSection).getByRole("button", { name: "Remove Key" })).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: /Advanced provider settings/ }));
@@ -608,7 +609,7 @@ describe("onboarding flow integration", () => {
expect(hasSavedStateCall("github")).toBe(true);
});
- expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ modelOnboardingComplete: true });
+ expect(mockUpdateGlobalSettings).toHaveBeenCalledWith(expect.objectContaining({ modelOnboardingComplete: true }));
expect(renderResult.onComplete).toHaveBeenCalledTimes(1);
expect(mockMarkOnboardingCompleted).not.toHaveBeenCalled();
});
@@ -977,7 +978,7 @@ describe("onboarding flow integration", () => {
});
expect(mockMarkOnboardingCompleted).toHaveBeenCalled();
- expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ modelOnboardingComplete: true });
+ expect(mockUpdateGlobalSettings).toHaveBeenCalledWith(expect.objectContaining({ modelOnboardingComplete: true }));
await waitFor(() => {
expect(screen.getByText("Your first task is ready!")).toBeInTheDocument();
});
@@ -1090,7 +1091,7 @@ describe("onboarding flow integration", () => {
});
expect(renderResult.onOpenNewTask).toHaveBeenCalled();
- expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ modelOnboardingComplete: true });
+ expect(mockUpdateGlobalSettings).toHaveBeenCalledWith(expect.objectContaining({ modelOnboardingComplete: true }));
});
it("task creation flow: Import from GitHub CTA completes onboarding and triggers import", async () => {