From 7673a68678737787498e90931aaec86abf3e0ef2 Mon Sep 17 00:00:00 2001
From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com>
Date: Wed, 27 May 2026 00:16:37 +0200
Subject: [PATCH] refactor(frontend): modify onboarding for rivet compute
---
.../data-providers/cloud-data-provider.tsx | 21 +++++++++++++++++
frontend/src/app/getting-started.tsx | 23 ++++++++++++++++++-
frontend/src/lib/data.ts | 19 ++++++++++++---
3 files changed, 59 insertions(+), 4 deletions(-)
diff --git a/frontend/src/app/data-providers/cloud-data-provider.tsx b/frontend/src/app/data-providers/cloud-data-provider.tsx
index e3aecdc1b8..4ce52f0171 100644
--- a/frontend/src/app/data-providers/cloud-data-provider.tsx
+++ b/frontend/src/app/data-providers/cloud-data-provider.tsx
@@ -1010,6 +1010,27 @@ export const createProjectContext = ({
limit: opts?.limit,
});
},
+ // Polls the project image registry. Used by the onboarding frontend
+ // step to hold back the "Waiting for an Actor" caption until the user
+ // has pushed their first image to Rivet Compute.
+ currentProjectFirstImagePresentQueryOptions() {
+ return queryOptions({
+ queryKey: [
+ organization,
+ project,
+ "images",
+ "first-present",
+ ] as QueryKey,
+ queryFn: async () => {
+ const response = await client.docker.listImages(project, {
+ limit: 1,
+ org: organization,
+ });
+ return response.images.length > 0;
+ },
+ refetchInterval: 5000,
+ });
+ },
upsertCurrentProjectManagedPoolMutationOptions() {
return mutationOptions({
mutationKey: [organization, project, "managed-pool", "upsert"],
diff --git a/frontend/src/app/getting-started.tsx b/frontend/src/app/getting-started.tsx
index 3c3ed60104..f10ef90ee8 100644
--- a/frontend/src/app/getting-started.tsx
+++ b/frontend/src/app/getting-started.tsx
@@ -1487,6 +1487,24 @@ function useServerfullEndpoint() {
return data?.url || engineEnv().VITE_APP_API_URL;
}
+// Cloud + Rivet compute: poll the project image registry. Until the user
+// pushes their first image, no runner config exists and no actor can be
+// scheduled, so the caller hides the "Waiting for an Actor" affordance and
+// shows a deployment-pending caption instead. In non-cloud builds
+// `useCloudNamespaceDataProvider` returns undefined and this is a no-op.
+function useWaitingForFirstImage(provider: string | undefined): boolean {
+ const nsDataProvider = useCloudNamespaceDataProvider();
+ const enabled =
+ features.compute && provider === "rivet" && nsDataProvider != null;
+ const { data: hasImage } = useQuery({
+ ...(nsDataProvider
+ ? nsDataProvider.currentProjectFirstImagePresentQueryOptions()
+ : { queryKey: ["frontend-setup", "first-image-noop"] as const, queryFn: () => false }),
+ enabled,
+ });
+ return enabled && hasImage !== true;
+}
+
function FrontendSetup() {
const dataProvider = useDataProvider();
@@ -1521,6 +1539,7 @@ function FrontendSetup() {
const { data: nsData } = useQuery(
nsDataProvider.currentProjectNamespaceQueryOptions({ namespace: namespaceParam }),
);
+ const waitingForFirstImage = useWaitingForFirstImage(provider);
const deploymentUrl = useMemo(() => {
if (provider === "rivet" && nsData?.access?.engineNamespaceName) {
@@ -1571,7 +1590,9 @@ function FrontendSetup() {
- Waiting for an Actor to be created... + {waitingForFirstImage + ? "Waiting for your first deployment..." + : "Waiting for an Actor to be created..."}
diff --git a/frontend/src/lib/data.ts b/frontend/src/lib/data.ts index 99b93a7bcc..d0024db21b 100644 --- a/frontend/src/lib/data.ts +++ b/frontend/src/lib/data.ts @@ -52,10 +52,23 @@ export function deriveOnboardingState(opts: { .find((p) => p !== undefined); const hasRunnerNames = (runnerNames?.pages[0]?.names.length ?? 0) > 0; - const hasRunnerConfigs = - Object.keys(runnerConfigs?.pages[0]?.runnerConfigs ?? {}).length > 0; + // A runner-config datacenter counts as "user-onboarded" only when + // `metadata.provider` is set. Both onboarding signals write it: the + // serverless `upsertRunnerConfig` call here in the frontend, and the + // engine-ee compute-pool actor once GCP Cloud Run services come up. A + // bare runner config without provider metadata (e.g. compute pre-enabled + // out-of-band, or partial backend state) doesn't count, so the user + // keeps seeing the provider/backend onboarding step instead of being + // pushed straight to "Waiting for an Actor". + const hasConfiguredDatacenter = (runnerConfigs?.pages ?? []).some((page) => + Object.values(page.runnerConfigs).some((config) => + Object.values(config.datacenters).some( + (dc) => deriveProviderFromMetadata(dc.metadata) !== undefined, + ), + ), + ); const hasActors = actorCount > 0; - const hasBackendConfigured = hasRunnerNames || hasRunnerConfigs; + const hasBackendConfigured = hasRunnerNames || hasConfiguredDatacenter; return { displayOnboarding: !hasBackendConfigured && !hasActors,