From a5af16aee17472edf29350ba69d8baabe22426cb Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 29 Sep 2026 20:52:48 -0700 Subject: [PATCH 1/2] feat(web): the provider step preselects no provider (CL-9506) --- .../src/onboarding/provider-connect-step.tsx | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/apps/web/src/onboarding/provider-connect-step.tsx b/apps/web/src/onboarding/provider-connect-step.tsx index c74908ece1..23ee22190e 100644 --- a/apps/web/src/onboarding/provider-connect-step.tsx +++ b/apps/web/src/onboarding/provider-connect-step.tsx @@ -1,7 +1,13 @@ // Connects one provider credential through the stock catalog routes and // derives the single offering it mints; a tenant that already resolves an // offering skips this step (see `resolveExistingOffering`). -import { Button, Input, RadioGroup, RadioOption, Select } from "@corbits/react-ui"; +import { + Button, + Input, + RadioGroup, + RadioOption, + Select, +} from "@corbits/react-ui"; import { cancelProviderLogin, credentialNameFor, @@ -140,7 +146,10 @@ export async function resolveExistingOffering( const models = await getResolvedCatalog(tenantId, fetchImpl); const offerings = models .flatMap((model) => - model.offerings.map((offering) => ({ ...offering, model: model.canonicalName })), + model.offerings.map((offering) => ({ + ...offering, + model: model.canonicalName, + })), ) .sort((a, b) => a.priority - b.priority); const defaultSourceOfferingId = offerings[0]?.offeringId; @@ -156,7 +165,11 @@ export async function resolveExistingOffering( } /** An offering minted from exactly one option: one credential, one model. */ -function offeringFromOption(option: ProviderOption, canonicalName: string, id: string) { +function offeringFromOption( + option: ProviderOption, + canonicalName: string, + id: string, +) { return { sourceOfferingIds: [id], defaultSourceOfferingId: id, @@ -173,15 +186,17 @@ export function ProviderConnectStep({ readonly onConnected: (offering: ExistingOffering) => void; readonly onError: (message: string) => void; }) { - const [selected, setSelected] = useState(PROVIDER_OPTIONS[0]?.id ?? "anthropic"); + const [selected, setSelected] = useState(null); const [apiKey, setApiKey] = useState(""); const [baseURL, setBaseURL] = useState(""); const [modelName, setModelName] = useState(""); const [submitting, setSubmitting] = useState(false); const [loginId, setLoginId] = useState(null); - const option = - PROVIDER_OPTIONS.find((candidate) => candidate.id === selected) ?? PROVIDER_OPTIONS[0]; + // No provider is preselected: the person chooses. + const option = PROVIDER_OPTIONS.find( + (candidate) => candidate.id === selected, + ); const isLocal = option?.local === true; const oauthProvider = option?.oauthProvider; @@ -200,14 +215,17 @@ export function ProviderConnectStep({ const modelKnown = !isLocal || tags.includes(modelName); const ready = - oauthProvider !== undefined || - (isLocal - ? baseURL.trim().length > 0 && modelName.trim().length > 0 && modelKnown - : apiKey.trim().length > 0); + option !== undefined && + (oauthProvider !== undefined || + (isLocal + ? baseURL.trim().length > 0 && modelName.trim().length > 0 && modelKnown + : apiKey.trim().length > 0)); function fail(cause: unknown, operation: string) { const refId = reportError(cause, { operation, tenantId }); - onError(`${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`); + onError( + `${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`, + ); } function selectOption(id: string) { @@ -215,7 +233,10 @@ export function ProviderConnectStep({ if (loginId !== null) { // Abandoning a login must free the fixed loopback port it holds. void cancelProviderLogin(tenantId, loginId).catch((cause: unknown) => { - reportError(cause, { operation: "onboarding.cancel-provider-login", tenantId }); + reportError(cause, { + operation: "onboarding.cancel-provider-login", + tenantId, + }); }); setLoginId(null); } @@ -227,7 +248,10 @@ export function ProviderConnectStep({ // Starting a login is the hub's job end to end: it runs the loopback PKCE // flow and stores the tokens, and hands back only a URL to open. const startLogin = useMutation({ - mutationFn: async (target: { option: ProviderOption; provider: string }) => { + mutationFn: async (target: { + option: ProviderOption; + provider: string; + }) => { const providerId = await ensureProviderRow(tenantId, { providerName: target.option.label, plugin: target.option.plugin, @@ -255,7 +279,8 @@ export function ProviderConnectStep({ queryKey: ["onboarding", "oauth-login", tenantId, loginId], enabled: loginId !== null && option !== undefined, queryFn: async () => { - if (loginId === null || option === undefined) throw new Error("no login in flight"); + if (loginId === null || option === undefined) + throw new Error("no login in flight"); const state = await readProviderLogin(tenantId, loginId); if (state.status !== "completed") return state; const created = await shadowOffering(tenantId, { @@ -272,7 +297,8 @@ export function ProviderConnectStep({ offering: offeringFromOption(option, option.canonicalName, created.id), }; }, - refetchInterval: (query) => (query.state.data?.status === "pending" ? 2000 : false), + refetchInterval: (query) => + query.state.data?.status === "pending" ? 2000 : false, }); const loginState = login.data; @@ -330,11 +356,14 @@ export function ProviderConnectStep({ : "Connect"; return ( -
void handleSubmit(event)}> + void handleSubmit(event)} + > {PROVIDER_OPTIONS.map((candidate) => ( @@ -346,7 +375,7 @@ export function ProviderConnectStep({ /> ))} - {oauthProvider !== undefined ? ( + {option === undefined ? null : oauthProvider !== undefined ? (

{waiting ? "Finish signing in on the tab that opened, then come back here." @@ -388,12 +417,15 @@ export function ProviderConnectStep({ {tagsQuery.isError ? (

- {tagsQuery.error instanceof Error ? tagsQuery.error.message : String(tagsQuery.error)} + {tagsQuery.error instanceof Error + ? tagsQuery.error.message + : String(tagsQuery.error)}

) : null} {!tagsQuery.isError && modelName !== "" && !modelKnown ? (

- {modelName} is not one of the models Ollama reports at this base URL. + {modelName} is not one of the models Ollama reports at this base + URL.

) : null} From 09fabae2223d8b78db7ddacf381100276ab8766e Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 29 Sep 2026 20:54:30 -0700 Subject: [PATCH 2/2] style(web): format the provider step (CL-9506) --- .../src/onboarding/provider-connect-step.tsx | 45 +++++-------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/apps/web/src/onboarding/provider-connect-step.tsx b/apps/web/src/onboarding/provider-connect-step.tsx index 23ee22190e..ba26f48c1d 100644 --- a/apps/web/src/onboarding/provider-connect-step.tsx +++ b/apps/web/src/onboarding/provider-connect-step.tsx @@ -1,13 +1,7 @@ // Connects one provider credential through the stock catalog routes and // derives the single offering it mints; a tenant that already resolves an // offering skips this step (see `resolveExistingOffering`). -import { - Button, - Input, - RadioGroup, - RadioOption, - Select, -} from "@corbits/react-ui"; +import { Button, Input, RadioGroup, RadioOption, Select } from "@corbits/react-ui"; import { cancelProviderLogin, credentialNameFor, @@ -165,11 +159,7 @@ export async function resolveExistingOffering( } /** An offering minted from exactly one option: one credential, one model. */ -function offeringFromOption( - option: ProviderOption, - canonicalName: string, - id: string, -) { +function offeringFromOption(option: ProviderOption, canonicalName: string, id: string) { return { sourceOfferingIds: [id], defaultSourceOfferingId: id, @@ -194,9 +184,7 @@ export function ProviderConnectStep({ const [loginId, setLoginId] = useState(null); // No provider is preselected: the person chooses. - const option = PROVIDER_OPTIONS.find( - (candidate) => candidate.id === selected, - ); + const option = PROVIDER_OPTIONS.find((candidate) => candidate.id === selected); const isLocal = option?.local === true; const oauthProvider = option?.oauthProvider; @@ -223,9 +211,7 @@ export function ProviderConnectStep({ function fail(cause: unknown, operation: string) { const refId = reportError(cause, { operation, tenantId }); - onError( - `${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`, - ); + onError(`${cause instanceof Error ? cause.message : String(cause)} (ref ${refId})`); } function selectOption(id: string) { @@ -248,10 +234,7 @@ export function ProviderConnectStep({ // Starting a login is the hub's job end to end: it runs the loopback PKCE // flow and stores the tokens, and hands back only a URL to open. const startLogin = useMutation({ - mutationFn: async (target: { - option: ProviderOption; - provider: string; - }) => { + mutationFn: async (target: { option: ProviderOption; provider: string }) => { const providerId = await ensureProviderRow(tenantId, { providerName: target.option.label, plugin: target.option.plugin, @@ -279,8 +262,7 @@ export function ProviderConnectStep({ queryKey: ["onboarding", "oauth-login", tenantId, loginId], enabled: loginId !== null && option !== undefined, queryFn: async () => { - if (loginId === null || option === undefined) - throw new Error("no login in flight"); + if (loginId === null || option === undefined) throw new Error("no login in flight"); const state = await readProviderLogin(tenantId, loginId); if (state.status !== "completed") return state; const created = await shadowOffering(tenantId, { @@ -297,8 +279,7 @@ export function ProviderConnectStep({ offering: offeringFromOption(option, option.canonicalName, created.id), }; }, - refetchInterval: (query) => - query.state.data?.status === "pending" ? 2000 : false, + refetchInterval: (query) => (query.state.data?.status === "pending" ? 2000 : false), }); const loginState = login.data; @@ -356,10 +337,7 @@ export function ProviderConnectStep({ : "Connect"; return ( - void handleSubmit(event)} - > + void handleSubmit(event)}> {tagsQuery.isError ? (

- {tagsQuery.error instanceof Error - ? tagsQuery.error.message - : String(tagsQuery.error)} + {tagsQuery.error instanceof Error ? tagsQuery.error.message : String(tagsQuery.error)}

) : null} {!tagsQuery.isError && modelName !== "" && !modelKnown ? (

- {modelName} is not one of the models Ollama reports at this base - URL. + {modelName} is not one of the models Ollama reports at this base URL.

) : null}