From f8655fb9058cf831072a9bef860eedc629331445 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Wed, 15 Oct 2025 15:28:31 +0200 Subject: [PATCH 01/13] refactor: onboarding --- .../src/app/dialogs/connect-railway-frame.tsx | 10 --- .../src/app/dialogs/edit-runner-config.tsx | 5 +- frontend/src/app/runners-table.tsx | 10 +-- .../ns.$namespace/connect.tsx | 72 +++++++++++++++++++ 4 files changed, 78 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 71ca84a483..2b5b445530 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -353,13 +353,3 @@ function DeployToRailwayButton() { ); } - -const useSelectedDatacenter = () => { - const datacenter = useWatch({ name: "datacenter" }); - - const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), - ); - - return data?.url || engineEnv().VITE_APP_API_URL; -}; diff --git a/frontend/src/app/dialogs/edit-runner-config.tsx b/frontend/src/app/dialogs/edit-runner-config.tsx index 08474b446d..e3bf87ea98 100644 --- a/frontend/src/app/dialogs/edit-runner-config.tsx +++ b/frontend/src/app/dialogs/edit-runner-config.tsx @@ -49,7 +49,10 @@ export default function EditRunnerConfigFrameContent({ }, }; - const otherDcs = Object.entries(datacenters).filter(([k]) => k !== dc).filter(([k, v]) => v.serverless).map(([k, v]) => [k, config]); + const otherDcs = Object.entries(datacenters) + .filter(([k]) => k !== dc) + .filter(([k, v]) => v.serverless) + .map(([k, v]) => [k, config]); console.log(otherDcs, [dc, config]); diff --git a/frontend/src/app/runners-table.tsx b/frontend/src/app/runners-table.tsx index dffc18ad7f..efbfdcbaa6 100644 --- a/frontend/src/app/runners-table.tsx +++ b/frontend/src/app/runners-table.tsx @@ -1,24 +1,18 @@ import { - faHourglassClock, faPlus, - faSignal4, - faSignal5, faSignalAlt, faSignalAlt2, faSignalAlt3, faSignalAlt4, - faSignalGood, Icon, } from "@rivet-gg/icons"; import type { Rivet } from "@rivetkit/engine-api-full"; -import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; +import { useInfiniteQuery } from "@tanstack/react-query"; import { Link } from "@tanstack/react-router"; -import { formatDistance, formatRelative } from "date-fns"; -import { useInterval } from "usehooks-ts"; +import { formatDistance } from "date-fns"; import { Button, DiscreteCopyButton, - Ping, Skeleton, Table, TableBody, diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx index 4f1a239d79..ea56d416c8 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx @@ -356,6 +356,78 @@ export function RouteComponent() { +
+
+

Connect New Project

+
+

+ Start a new RivetKit project with Rivet Cloud. Use one + of our templates to get started quickly. +

+ +
+
+

1-Click Deploy From Template

+
+ + +
+
+
+

Quickstart Guides

+
+ + + + + + + + + +
+
+
); From ecb2be84cc565a0b161e24caab56387475497948 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:01:05 +0200 Subject: [PATCH 02/13] refactor: manual provider configuration --- .../app/dialogs/connect-manual-serverless-frame.tsx | 4 +++- frontend/src/app/dialogs/connect-railway-frame.tsx | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx index 72cd3b3b5c..492a38339d 100644 --- a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx @@ -117,7 +117,9 @@ function FormStepper({ let existing: Record = {}; try { const runnerConfig = await queryClient.fetchQuery( - provider.runnerConfigQueryOptions({name: values.runnerName}), + provider.runnerConfigQueryOptions({ + name: values.runnerName, + }), ); existing = runnerConfig?.datacenters || {}; } catch { diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 2b5b445530..71ca84a483 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -353,3 +353,13 @@ function DeployToRailwayButton() { ); } + +const useSelectedDatacenter = () => { + const datacenter = useWatch({ name: "datacenter" }); + + const { data } = useQuery( + useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), + ); + + return data?.url || engineEnv().VITE_APP_API_URL; +}; From d11bf5065d1b349eb0a32030efbe872062c14f27 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:34:33 +0200 Subject: [PATCH 03/13] fix: bolts, rivets and nits --- .../ns.$namespace/connect.tsx | 76 ++----------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx index ea56d416c8..f7b9275d52 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx @@ -356,77 +356,13 @@ export function RouteComponent() { -
-
-

Connect New Project

-
-

- Start a new RivetKit project with Rivet Cloud. Use one - of our templates to get started quickly. -

-
-
-

1-Click Deploy From Template

-
- - -
-
-
-

Quickstart Guides

-
- - - - - - - - - -
-
+
+ +
+ + +
From c2e2918a6106e57984e85573bcb696ab0a71eca0 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 17 Oct 2025 05:39:13 -0700 Subject: [PATCH 04/13] chore: update url --- .../projects.$project/ns.$namespace/connect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx index f7b9275d52..3d7d18e3c0 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx @@ -263,7 +263,7 @@ export function RouteComponent() { asChild > From d1c87002f4e9aebce419097cfa8cb8ef2ad02537 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 17 Oct 2025 18:04:35 -0700 Subject: [PATCH 05/13] chore: enable fluid on vercel --- .../connect-manual-serverless-frame.tsx | 4 +- .../dialogs/connect-quick-vercel-frame.tsx | 4 +- .../src/app/dialogs/connect-vercel-frame.tsx | 10 +---- .../src/app/forms/connect-vercel-form.tsx | 40 ++----------------- 4 files changed, 9 insertions(+), 49 deletions(-) diff --git a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx index 492a38339d..71e32cb2f1 100644 --- a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx @@ -160,8 +160,8 @@ function FormStepper({ }} defaultValues={{ runnerName: "default", - slotsPerRunner: 25, - maxRunners: 1000, + slotsPerRunner: 1, + maxRunners: 10000, minRunners: 1, runnerMargin: 0, headers: [], diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index e5b5a74499..25be5487cc 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -123,9 +123,9 @@ function FormStepper({ defaultValues={{ plan: "hobby", runnerName: "default", - slotsPerRunner: 25, + slotsPerRunner: 1, minRunners: 1, - maxRunners: 1000, + maxRunners: 10_000, runnerMargin: 0, headers: [], success: false, diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx index 44c4490d58..3f5ec4f27f 100644 --- a/frontend/src/app/dialogs/connect-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx @@ -88,7 +88,6 @@ function FormStepper({ content={{ "initial-info": () => , "api-route": () => , - "vercel-settings": () => , deploy: () => , }} onSubmit={async ({ values }) => { @@ -127,9 +126,9 @@ function FormStepper({ defaultValues={{ plan: "hobby", runnerName: "default", - slotsPerRunner: 25, + slotsPerRunner: 1, minRunners: 1, - maxRunners: 1000, + maxRunners: 10_000, runnerMargin: 0, headers: [], success: false, @@ -170,11 +169,6 @@ function StepApiRoute() { return ; } -function StepVercelSettings() { - const plan = useWatch({ name: "plan" as const }); - return ; -} - function StepDeploy() { return ( <> diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx index 07ce38db09..be5f636e03 100644 --- a/frontend/src/app/forms/connect-vercel-form.tsx +++ b/frontend/src/app/forms/connect-vercel-form.tsx @@ -54,13 +54,6 @@ export const stepper = defineStepper( schema: z.object({}), next: "Next", }, - { - id: "vercel-settings", - title: "Configure Vercel settings", - assist: false, - next: "Next", - schema: z.object({}), - }, { id: "deploy", title: "Deploy to Vercel", @@ -125,36 +118,9 @@ export const RunnerMargin = ConnectManualServerlessForm.RunnerMargin; export const Headers = ConnectManualServerlessForm.Headers; export const PLAN_TO_MAX_DURATION: Record = { - hobby: 60, - pro: 300, - enterprise: 900, -}; - -const code = ({ plan }: { plan: string }) => - `{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "fluid": false, // [!code highlight] -}`; - -export const Json = ({ plan }: { plan: string }) => { - return ( -
- - code({ plan }).replaceAll(" // [!code highlight]", "") - } - > - - -

Rivet provides its own intelligent load balancing mechanism.

-
- ); + hobby: 300, + pro: 800, + enterprise: 800, }; const integrationCode = ({ plan }: { plan: string }) => From 73b2042d270f5f169ad9231f6ac1008780ed11ff Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Fri, 17 Oct 2025 23:44:40 -0700 Subject: [PATCH 06/13] chore(frontend): fix vercel 1-click on oss, improve vercel onboarding flow --- .../connect-manual-serverfull-frame.tsx | 2 +- .../dialogs/connect-quick-railway-frame.tsx | 186 +++++++++ .../dialogs/connect-quick-vercel-frame.tsx | 82 ++-- .../src/app/dialogs/connect-railway-frame.tsx | 13 +- .../src/app/dialogs/connect-vercel-frame.tsx | 159 ++++++-- .../forms/connect-manual-serverless-form.tsx | 7 +- .../app/forms/connect-quick-vercel-form.tsx | 13 +- .../src/app/forms/connect-vercel-form.tsx | 172 ++++++-- frontend/src/app/layout.tsx | 5 +- frontend/src/app/use-dialog.tsx | 3 + .../components/code-preview/code-preview.tsx | 1 + frontend/src/components/code.tsx | 19 +- frontend/src/components/copy-area.tsx | 2 +- .../src/components/external-link-card.tsx | 39 ++ frontend/src/components/index.ts | 1 + frontend/src/components/ui/accordion.tsx | 2 +- frontend/src/routes/_context/_cloud.tsx | 21 + .../ns.$namespace/connect.tsx | 373 ++++++++++-------- frontend/src/routes/_context/_engine.tsx | 42 ++ 19 files changed, 828 insertions(+), 314 deletions(-) create mode 100644 frontend/src/app/dialogs/connect-quick-railway-frame.tsx create mode 100644 frontend/src/components/external-link-card.tsx diff --git a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx index a7c83e5a66..eb0065ab32 100644 --- a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx @@ -185,8 +185,8 @@ export function EnvVariablesStep() {

Value

- +
diff --git a/frontend/src/app/dialogs/connect-quick-railway-frame.tsx b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx new file mode 100644 index 0000000000..074491890e --- /dev/null +++ b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx @@ -0,0 +1,186 @@ +import { faRailway, Icon } from "@rivet-gg/icons"; +import { + useMutation, + usePrefetchInfiniteQuery, + useSuspenseInfiniteQuery, +} from "@tanstack/react-query"; +import confetti from "canvas-confetti"; +import z from "zod"; +import * as ConnectRailwayForm from "@/app/forms/connect-railway-form"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + type DialogContentProps, + ExternalLinkCard, + Frame, +} from "@/components"; +import { useEngineCompatDataProvider } from "@/components/actors"; +import { defineStepper } from "@/components/ui/stepper"; +import { queryClient } from "@/queries/global"; +import { useRailwayTemplateLink } from "@/utils/use-railway-template-link"; +import { StepperForm } from "../forms/stepper-form"; + +const stepper = defineStepper( + { + id: "step-1", + title: "Deploy to Railway", + assist: false, + next: "Next", + schema: z.object({ + runnerName: z.string().min(1, "Runner name is required"), + datacenter: z.string().min(1, "Please select a region"), + }), + }, + { + id: "step-2", + title: "Wait for the Runner to connect", + assist: true, + schema: z.object({ + success: z.boolean().refine((v) => v === true, { + message: "Runner must be connected to proceed", + }), + }), + next: "Add", + }, +); + +interface ConnectQuickRailwayFrameContentProps extends DialogContentProps {} + +export default function ConnectQuickRailwayFrameContent({ + onClose, +}: ConnectQuickRailwayFrameContentProps) { + usePrefetchInfiniteQuery({ + ...useEngineCompatDataProvider().regionsQueryOptions(), + pages: Infinity, + }); + const { data } = useSuspenseInfiniteQuery( + useEngineCompatDataProvider().regionsQueryOptions(), + ); + + const prefferedRegionForRailway = + data.find((region) => region.name.toLowerCase().includes("us-west")) + ?.id || + data.find((region) => region.name.toLowerCase().includes("us-east")) + ?.id || + data.find((region) => region.name.toLowerCase().includes("ore"))?.id || + "auto"; + + return ( + <> + + +
+ Add Railway +
+
+
+ + + + + ); +} + +function FormStepper({ + onClose, + defaultDatacenter, +}: { + onClose?: () => void; + defaultDatacenter: string; +}) { + const provider = useEngineCompatDataProvider(); + const { mutateAsync } = useMutation({ + ...provider.upsertRunnerConfigMutationOptions(), + onSuccess: async () => { + confetti({ + angle: 60, + spread: 55, + origin: { x: 0 }, + }); + confetti({ + angle: 120, + spread: 55, + origin: { x: 1 }, + }); + + await queryClient.invalidateQueries( + provider.runnerConfigsQueryOptions(), + ); + onClose?.(); + }, + }); + return ( + { + await mutateAsync({ + name: values.runnerName, + config: { + [values.datacenter]: { + normal: {}, + metadata: { provider: "railway" }, + }, + }, + }); + }} + defaultValues={{ + runnerName: "default", + success: true, + datacenter: defaultDatacenter, + }} + content={{ + "step-1": () => , + "step-2": () => , + }} + /> + ); +} + +function Step1({ datacenter }: { datacenter: string }) { + return ( + <> +
+

+ Deploy the Rivet Railway template to get started quickly. +

+ +
+ + + + Advanced Options + + + + + + + + + ); +} + +function DeployToRailwayButton({ datacenter }: { datacenter: string }) { + const runnerName = "default"; + const url = useRailwayTemplateLink({ + runnerName, + datacenter, + }); + + return ( + + ); +} + +function Step2() { + return ; +} diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index 25be5487cc..9ccdb1af94 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -12,6 +12,7 @@ import { AccordionItem, AccordionTrigger, type DialogContentProps, + ExternalLinkCard, Frame, } from "@/components"; import { type Region, useEngineCompatDataProvider } from "@/components/actors"; @@ -84,7 +85,6 @@ function FormStepper({ {...stepper} content={{ "initial-info": () => , - "env-vars": () => , deploy: () => , }} onSubmit={async ({ values }) => { @@ -98,10 +98,7 @@ function FormStepper({ maxRunners: values.maxRunners, slotsPerRunner: values.slotsPerRunner, runnersMargin: values.runnerMargin, - requestLifespan: - ConnectVercelForm.PLAN_TO_MAX_DURATION[ - values.plan - ] - 5, // Subtract 5s to ensure we don't hit Vercel's timeout + requestLifespan: 295, // 5 minutes minus buffer headers: Object.fromEntries( values.headers.map(([key, value]) => [key, value]), ), @@ -121,7 +118,6 @@ function FormStepper({ }); }} defaultValues={{ - plan: "hobby", runnerName: "default", slotsPerRunner: 1, minRunners: 1, @@ -140,35 +136,22 @@ function FormStepper({ function StepInitialInfo() { return ( <> - - - - - Advanced options - - - - - - - - - - - - - - ); -} - -function StepEnvVars() { - return ( - <> -

- Set the following environment variables in your Vercel project - settings. -

- +
+

+ Deploy the Rivet Vercel template to get started quickly. +

+ +
+
+

+ Set the following environment variables: +

+ +
); } @@ -176,21 +159,26 @@ function StepEnvVars() { function StepDeploy() { return ( <> -

- - Deploy your project to Vercel using your preferred method - - . After deployment, return here to add the endpoint. -

- + + + + Advanced + + + + + + + + + + + +
+ ); } diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 71ca84a483..717aa9e3df 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -177,8 +177,8 @@ export function EnvVariablesStep() {

Value

- +
@@ -220,7 +220,7 @@ function Step1() { - Advanced options + Advanced @@ -259,7 +259,7 @@ function RivetRunnerEnv() { <> {isLoading ? ( @@ -288,6 +288,7 @@ function RivetTokenEnv() { )} @@ -300,7 +301,7 @@ function RivetEndpointEnv() { <> >; -interface CreateProjectFrameContentProps extends DialogContentProps {} +interface CreateProjectFrameContentProps extends DialogContentProps { } + +function usePublishableToken() { + return match(__APP_TYPE__) + .with("cloud", () => { + const routeContext = useRouteContext({ + from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace/connect", + select: (ctx) => ctx.dataProvider, + }); + return useSuspenseQuery( + routeContext.publishableTokenQueryOptions(), + ).data; + }) + .with("engine", () => { + return useSuspenseQuery( + useEngineCompatDataProvider().engineAdminTokenQueryOptions(), + ).data; + }) + .otherwise(() => { + throw new Error("Not in a valid context"); + }); +} + +const useEndpoint = () => { + return match(__APP_TYPE__) + .with("cloud", () => { + return cloudEnv().VITE_APP_API_URL; + }) + .with("engine", () => { + return getConfig().apiUrl; + }) + .otherwise(() => { + throw new Error("Not in a valid context"); + }); +}; + +const useNamespace = () => { + return match(__APP_TYPE__) + .with("cloud", () => { + const routeContext = useRouteContext({ + from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace/connect", + select: (ctx) => ctx.dataProvider.engineNamespace, + }); + return routeContext; + }) + .with("engine", () => { + return "default"; + }) + .otherwise(() => { + throw new Error("Not in a valid context"); + }); +}; export default function CreateProjectFrameContent({ onClose, @@ -63,6 +119,10 @@ function FormStepper({ datacenters: Region[]; }) { const provider = useEngineCompatDataProvider(); + const token = usePublishableToken(); + const endpoint = useEndpoint(); + const namespace = useNamespace(); + const { mutateAsync } = useMutation({ ...provider.upsertRunnerConfigMutationOptions(), onSuccess: async () => { @@ -86,8 +146,9 @@ function FormStepper({ , + // "initial-info": () => , "api-route": () => , + frontend: () => , deploy: () => , }} onSubmit={async ({ values }) => { @@ -103,7 +164,7 @@ function FormStepper({ runnersMargin: values.runnerMargin, requestLifespan: ConnectVercelForm.PLAN_TO_MAX_DURATION[ - values.plan + values.plan ] - 5, // Subtract 5s to ensure we don't hit Vercel's timeout headers: Object.fromEntries( values.headers.map(([key, value]) => [key, value]), @@ -140,53 +201,85 @@ function FormStepper({ ); } -function StepInitialInfo() { - return ( - <> - - - - - Advanced options - - - - - - - - - - - - - - ); -} +// function StepInitialInfo() { +// return ( +// <> +// +// +// +// +// Advanced +// +// +// +// +// +// +// +// +// +// +// +// +// +// ); +// } function StepApiRoute() { const plan = useWatch({ name: "plan" as const }); return ; } +function StepFrontend({ + token, + endpoint, + namespace, +}: { + token: string; + endpoint: string; + namespace: string; +}) { + return ; +} + function StepDeploy() { return ( <>

+ Deploy your code to Vercel and paste your deployment's endpoint: +

+
+ + + + + Advanced + + + + + + + + + + + + +
+ +

+ Need help deploying? See{" "} - Deploy your project to Vercel using your preferred method + Vercel's deployment documentation - . After deployment, return here to add the endpoint. + .

-
- - -
); } diff --git a/frontend/src/app/forms/connect-manual-serverless-form.tsx b/frontend/src/app/forms/connect-manual-serverless-form.tsx index b4667d2b45..3111fa5382 100644 --- a/frontend/src/app/forms/connect-manual-serverless-form.tsx +++ b/frontend/src/app/forms/connect-manual-serverless-form.tsx @@ -440,14 +440,15 @@ export function ConnectionCheck({ provider }: { provider: string }) { const enabled = !!endpoint && endpointSchema.safeParse(endpoint).success; - const [debounced] = useDebounceValue(endpoint, 300); + const [debouncedEndpoint] = useDebounceValue(endpoint, 300); + const [debouncedHeaders] = useDebounceValue(headers, 300); const { isSuccess, data, isError, isRefetchError, isLoadingError, error } = useQuery({ ...dataProvider.runnerHealthCheckQueryOptions({ - runnerUrl: debounced, + runnerUrl: debouncedEndpoint, headers: Object.fromEntries( - headers.filter(([k, v]) => k && v).map(([k, v]) => [k, v]), + debouncedHeaders.filter(([k, v]) => k && v).map(([k, v]) => [k, v]), ), }), enabled, diff --git a/frontend/src/app/forms/connect-quick-vercel-form.tsx b/frontend/src/app/forms/connect-quick-vercel-form.tsx index 207b04362b..949d3430e8 100644 --- a/frontend/src/app/forms/connect-quick-vercel-form.tsx +++ b/frontend/src/app/forms/connect-quick-vercel-form.tsx @@ -17,7 +17,6 @@ export const stepper = defineStepper( assist: false, next: "Next", schema: z.object({ - plan: z.string().min(1, "Please select a Vercel plan"), runnerName: z.string().min(1, "Runner name is required"), datacenters: z .record(z.boolean()) @@ -32,16 +31,9 @@ export const stepper = defineStepper( runnerMargin: z.coerce.number().min(0, "Must be 0 or greater"), }), }, - { - id: "env-vars", - title: "Configure Environment Variables", - assist: false, - next: "Next", - schema: z.object({}), - }, { id: "deploy", - title: "Deploy to Vercel", + title: "Configure Vercel endpoint", assist: true, next: "Done", schema: z.object({ @@ -51,9 +43,6 @@ export const stepper = defineStepper( }, ); -export const PLAN_TO_MAX_DURATION = ConnectVercelForm.PLAN_TO_MAX_DURATION; - -export const Plan = ConnectVercelForm.Plan; export const RunnerName = ConnectVercelForm.RunnerName; export const Datacenters = ConnectVercelForm.Datacenters; diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx index be5f636e03..417fc3c9b0 100644 --- a/frontend/src/app/forms/connect-vercel-form.tsx +++ b/frontend/src/app/forms/connect-vercel-form.tsx @@ -4,6 +4,7 @@ import * as ConnectManualServerlessForm from "@/app/forms/connect-manual-serverl import { Code, CodeFrame, + CodeGroup, CodePreview, FormControl, FormDescription, @@ -26,33 +27,41 @@ const endpointSchema = z .endsWith("/api/rivet", "Endpoint must end with /api/rivet"); export const stepper = defineStepper( + // { + // id: "initial-info", + // title: "Configure", + // assist: false, + // next: "Next", + // schema: z.object({ + // plan: z.string().min(1, "Please select a Vercel plan"), + // runnerName: z.string().min(1, "Runner name is required"), + // datacenters: z + // .record(z.boolean()) + // .refine( + // (data) => Object.values(data).some(Boolean), + // "At least one datacenter must be selected", + // ), + // headers: z.array(z.tuple([z.string(), z.string()])).default([]), + // slotsPerRunner: z.coerce.number().min(1, "Must be at least 1"), + // maxRunners: z.coerce.number().min(1, "Must be at least 1"), + // minRunners: z.coerce.number().min(0, "Must be 0 or greater"), + // runnerMargin: z.coerce.number().min(0, "Must be 0 or greater"), + // }), + // }, { - id: "initial-info", - title: "Configure", + id: "api-route", + title: "Add API Route", assist: false, + schema: z.object({}), next: "Next", - schema: z.object({ - plan: z.string().min(1, "Please select a Vercel plan"), - runnerName: z.string().min(1, "Runner name is required"), - datacenters: z - .record(z.boolean()) - .refine( - (data) => Object.values(data).some(Boolean), - "At least one datacenter must be selected", - ), - headers: z.array(z.tuple([z.string(), z.string()])).default([]), - slotsPerRunner: z.coerce.number().min(1, "Must be at least 1"), - maxRunners: z.coerce.number().min(1, "Must be at least 1"), - minRunners: z.coerce.number().min(0, "Must be 0 or greater"), - runnerMargin: z.coerce.number().min(0, "Must be 0 or greater"), - }), }, { - id: "api-route", - title: "Configure maxDuration in API route handler", + id: "frontend", + title: "Connect Frontend", assist: false, - schema: z.object({}), next: "Next", + optional: true, + schema: z.object({}), }, { id: "deploy", @@ -117,27 +126,89 @@ export const RunnerMargin = ConnectManualServerlessForm.RunnerMargin; export const Headers = ConnectManualServerlessForm.Headers; -export const PLAN_TO_MAX_DURATION: Record = { - hobby: 300, - pro: 800, - enterprise: 800, -}; +// export const PLAN_TO_MAX_DURATION: Record = { +// hobby: 300, +// pro: 800, +// enterprise: 800, +// }; +// +// const integrationCode = ({ plan }: { plan: string }) => +// `import { toNextHandler } from "@rivetkit/next-js"; +// import { registry } from "@/rivet/registry"; +// +// export const maxDuration = ${PLAN_TO_MAX_DURATION[plan] || 60}; // [!code highlight] +// +// export const { GET, POST, PUT, PATCH, HEAD, OPTIONS } = toNextHandler(registry);`; const integrationCode = ({ plan }: { plan: string }) => `import { toNextHandler } from "@rivetkit/next-js"; import { registry } from "@/rivet/registry"; -export const maxDuration = ${PLAN_TO_MAX_DURATION[plan] || 60}; // [!code highlight] - export const { GET, POST, PUT, PATCH, HEAD, OPTIONS } = toNextHandler(registry);`; export const IntegrationCode = ({ plan }: { plan: string }) => { return ( -
+

- Update your Rivet API route handler to export the{" "} - maxDuration configuration. + If you have not created a project, see the{" "} + + Next.js quickstart guide + + .

+

First, install the Rivet Next.js package:

+ + "npm install @rivetkit/next-js"} + > + + + "pnpm add @rivetkit/next-js"} + > + + + "yarn add @rivetkit/next-js"} + > + + + "bun add @rivetkit/next-js"} + > + + + +

Then, add your Rivet route handler for Rivet:

{ export const Endpoint = ConnectManualServerlessForm.Endpoint; export const ConnectionCheck = ConnectManualServerlessForm.ConnectionCheck; + +export const FrontendIntegrationCode = ({ + token, + endpoint, + namespace, +}: { + token: string; + endpoint: string; + namespace: string; +}) => { + const clientCode = `"use client"; +import { createRivetKit } from "@rivetkit/next-js/client"; +import type { registry } from "@/rivet/registry"; + +export const { useActor } = createRivetKit({ + endpoint: "${endpoint}", + namespace: "${namespace}", + token: "${token}", +}); +`; + + return ( +
+

Connect your Next.js frontend to Rivet:

+ clientCode} + > + + +

+ This token is safe to publish on your frontend. +

+
+ ); +}; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 6a30bda8bb..1e54377467 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -392,13 +392,14 @@ const Subnav = () => { } : { to: "/", fuzzy: true }, ); - const hasDataProvider = useDataProviderCheck(); - const hasQuery = !!useDataProvider().buildsQueryOptions; if (nsMatch === false) { return null; } + const hasDataProvider = useDataProviderCheck(); + const hasQuery = hasDataProvider && !!useDataProvider().buildsQueryOptions; + return (
{__APP_TYPE__ === "engine" ? ( diff --git a/frontend/src/app/use-dialog.tsx b/frontend/src/app/use-dialog.tsx index 761125dac1..9918da25bc 100644 --- a/frontend/src/app/use-dialog.tsx +++ b/frontend/src/app/use-dialog.tsx @@ -17,6 +17,9 @@ export const useDialog = { ConnectRailway: createDialogHook( () => import("@/app/dialogs/connect-railway-frame"), ), + ConnectQuickRailway: createDialogHook( + () => import("@/app/dialogs/connect-quick-railway-frame"), + ), ConnectManual: createDialogHook( () => import("@/app/dialogs/connect-manual-frame"), ), diff --git a/frontend/src/components/code-preview/code-preview.tsx b/frontend/src/components/code-preview/code-preview.tsx index 4809ad2f4f..30a98497a0 100644 --- a/frontend/src/components/code-preview/code-preview.tsx +++ b/frontend/src/components/code-preview/code-preview.tsx @@ -12,6 +12,7 @@ import theme from "./theme.json"; const langs = { typescript: () => import("@shikijs/langs/typescript"), json: () => import("@shikijs/langs/json"), + bash: () => import("@shikijs/langs/bash") }; interface CodePreviewProps { diff --git a/frontend/src/components/code.tsx b/frontend/src/components/code.tsx index c31cad87e0..25692af5ce 100644 --- a/frontend/src/components/code.tsx +++ b/frontend/src/components/code.tsx @@ -1,4 +1,4 @@ -import { faCopy, faFile, Icon } from "@rivet-gg/icons"; +import { faCopy, faFile, Icon, type IconProp } from "@rivet-gg/icons"; import { Children, cloneElement, @@ -40,6 +40,7 @@ interface CodeGroupProps { children: ReactElement<{ language?: keyof typeof languageNames; title?: string; + icon?: IconProp; isInGroup?: boolean; }>[]; } @@ -69,11 +70,22 @@ export function CodeGroup({ children, className }: CodeGroupProps) { value={idx} className="data-[state=active]:!text-white" > - {child.props.title || + {child.props.icon ? ( + <> + + {child.props.title || + languageNames[ + child.props.language || "bash" + ] || + "Code"} + + ) : ( + child.props.title || languageNames[ child.props.language || "bash" ] || - "Code"} + "Code" + )} ); })} @@ -98,6 +110,7 @@ export function CodeGroup({ children, className }: CodeGroupProps) { interface CodeFrameProps { file?: string; title?: string; + icon?: IconProp; language: keyof typeof languageNames; isInGroup?: boolean; code?: () => string | string; diff --git a/frontend/src/components/copy-area.tsx b/frontend/src/components/copy-area.tsx index 643b774f06..5f297cadf7 100644 --- a/frontend/src/components/copy-area.tsx +++ b/frontend/src/components/copy-area.tsx @@ -189,7 +189,7 @@ export function DiscreteInput({ type={finalShow ? "text" : "password"} readOnly value={value} - className={cn("font-mono", !show ? "pr-16" : "pr-8")} + className={cn("font-mono truncate", !show ? "pr-16" : "pr-8")} />
diff --git a/frontend/src/components/external-link-card.tsx b/frontend/src/components/external-link-card.tsx new file mode 100644 index 0000000000..23c35922f2 --- /dev/null +++ b/frontend/src/components/external-link-card.tsx @@ -0,0 +1,39 @@ +import { faChevronRight, Icon, type IconDefinition } from "@rivet-gg/icons"; + +interface ExternalLinkCardProps { + href: string; + icon: IconDefinition; + title: string; + description?: string; +} + +export function ExternalLinkCard({ + href, + icon, + title, + description = "Opens in a new tab", +}: ExternalLinkCardProps) { + return ( + +
+
+
+ +
+
{title}
+
+ {description} +
+
+
+ +
+
+
+ ); +} diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index e69740e807..af2fd6dd30 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -12,6 +12,7 @@ export * from "./datepicker"; export * from "./docs-card"; export * from "./docs-sheet"; export * from "./external-card"; +export * from "./external-link-card"; export * from "./fullscreen-loading"; export * from "./hooks"; export * from "./lib/config"; diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx index 6f764a97c6..e4a2feada4 100644 --- a/frontend/src/components/ui/accordion.tsx +++ b/frontend/src/components/ui/accordion.tsx @@ -13,7 +13,7 @@ const AccordionItem = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/frontend/src/routes/_context/_cloud.tsx b/frontend/src/routes/_context/_cloud.tsx index 91dd9e775e..b833a21878 100644 --- a/frontend/src/routes/_context/_cloud.tsx +++ b/frontend/src/routes/_context/_cloud.tsx @@ -40,6 +40,7 @@ function CloudModals() { const ConnectVercelDialog = useDialog.ConnectVercel.Dialog; const ConnectQuickVercelDialog = useDialog.ConnectQuickVercel.Dialog; const ConnectRailwayDialog = useDialog.ConnectRailway.Dialog; + const ConnectQuickRailwayDialog = useDialog.ConnectQuickRailway.Dialog; const ConnectManualDialog = useDialog.ConnectManual.Dialog; const ConnectAwsDialog = useDialog.ConnectAws.Dialog; const ConnectGcpDialog = useDialog.ConnectGcp.Dialog; @@ -124,6 +125,26 @@ function CloudModals() { }, }} /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> +
-
+
+
+

Create New Project

+
+

+ Start a new RivetKit project with Rivet Cloud. Use + one of our templates to get started quickly. +

+ +
+
+

1-Click Deploy From Template

+
+ + +
+
+
+

Quickstart Guides

+
+ + + + + + + + + +
+
+
+

Connect Existing Project

@@ -234,90 +309,6 @@ export function RouteComponent() {
-
-
-

Connect New Project

-
-

- Start a new RivetKit project with Rivet Cloud. Use - one of our templates to get started quickly. -

- -
-
-

1-Click Deploy From Template

-
- - -
-
-
-

Quickstart Guides

-
- - - - - - - - - -
-
-
); @@ -382,7 +373,7 @@ function Providers() { }); return ( -
+

Providers

@@ -428,7 +419,7 @@ function Runners() { }); return ( -
+

Runners

@@ -490,8 +481,105 @@ function ConnectYourFrontend() { select: (ctx) => ctx.dataProvider.engineNamespace, }); + const { data: configs } = useInfiniteQuery({ + ...useEngineCompatDataProvider().runnerConfigsQueryOptions(), + refetchInterval: 5000, + }); + + // Check if Vercel is connected + const hasVercel = configs?.pages.some((page) => + Object.values(page.runnerConfigs).some( + (config) => config.metadata?.provider === "vercel" + ) + ); + + const nextJsTab = ( + + + See Next.js Documentation{" "} + + + + } + > + + + ); + + const reactTab = ( + + + See React Documentation{" "} + + + + } + > + + + ); + + const javascriptTab = ( + + + See JavaScript Documentation{" "} + + + + } + > + + + ); + return ( -
+

Connect Your Frontend

@@ -500,81 +588,19 @@ function ConnectYourFrontend() {

- - - See JavaScript Documentation{" "} - - - - } - > - - - - - - See React Documentation{" "} - - - - } - > - - - - - - See Next.js Documentation{" "} - - - - } - > - - + {hasVercel ? ( + <> + {nextJsTab} + {reactTab} + {javascriptTab} + + ) : ( + <> + {javascriptTab} + {reactTab} + {nextJsTab} + + )}
@@ -624,16 +650,15 @@ const nextJsCode = ({ endpoint: string; namespace: string; }) => `"use client"; -import { createClient, createRivetKit } from "@rivetkit/next-js/client"; +import { createRivetKit } from "@rivetkit/next-js/client"; import type { registry } from "@/rivet/registry"; -const client = createClient({ +export const { useActor } = createRivetKit({ endpoint: "${engineEnv().VITE_APP_API_URL}", namespace: "${namespace}", token: "${token}", }); - -export const { useActor } = createRivetKit(client);`; +`; function ProviderDropdown({ children }: { children: React.ReactNode }) { const navigate = Route.useNavigate(); @@ -748,11 +773,6 @@ function DataLoadingPlaceholder() { } function OneClickDeployRailwayButton() { - const url = useRailwayTemplateLink({ - runnerName: "rivet-cloud-starter", - datacenter: "us-east-1", - }); - return ( ); } diff --git a/frontend/src/routes/_context/_engine.tsx b/frontend/src/routes/_context/_engine.tsx index 2ccffa1bf5..0ee2e8cde2 100644 --- a/frontend/src/routes/_context/_engine.tsx +++ b/frontend/src/routes/_context/_engine.tsx @@ -35,7 +35,9 @@ function EngineModals() { const CreateNamespaceDialog = useDialog.CreateNamespace.Dialog; const ConnectVercelDialog = useDialog.ConnectVercel.Dialog; + const ConnectQuickVercelDialog = useDialog.ConnectQuickVercel.Dialog; const ConnectRailwayDialog = useDialog.ConnectRailway.Dialog; + const ConnectQuickRailwayDialog = useDialog.ConnectQuickRailway.Dialog; const ConnectManualDialog = useDialog.ConnectManual.Dialog; const ConnectAwsDialog = useDialog.ConnectAws.Dialog; const ConnectGcpDialog = useDialog.ConnectGcp.Dialog; @@ -82,6 +84,46 @@ function EngineModals() { }, }} /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> Date: Sat, 18 Oct 2025 00:36:21 -0700 Subject: [PATCH 07/13] chore: fix handler timeout --- frontend/src/app/forms/connect-vercel-form.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx index 417fc3c9b0..da87417575 100644 --- a/frontend/src/app/forms/connect-vercel-form.tsx +++ b/frontend/src/app/forms/connect-vercel-form.tsx @@ -144,6 +144,8 @@ const integrationCode = ({ plan }: { plan: string }) => `import { toNextHandler } from "@rivetkit/next-js"; import { registry } from "@/rivet/registry"; +export const maxDuration = 300; + export const { GET, POST, PUT, PATCH, HEAD, OPTIONS } = toNextHandler(registry);`; export const IntegrationCode = ({ plan }: { plan: string }) => { From c75052ee24037e99eab36a02f33b83eac3ab5c13 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 02:08:44 -0700 Subject: [PATCH 08/13] chore(frontend): add next-js provider icon --- frontend/src/app/runner-config-table.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/app/runner-config-table.tsx b/frontend/src/app/runner-config-table.tsx index 5123d1ca2d..22ce42c45d 100644 --- a/frontend/src/app/runner-config-table.tsx +++ b/frontend/src/app/runner-config-table.tsx @@ -1,6 +1,7 @@ import { faCog, faCogs, + faNextjs, faRailway, faTrash, faVercel, @@ -219,6 +220,13 @@ function Provider({ metadata }: { metadata: unknown }) {
); } + if (metadata.provider === "next-js") { + return ( +
+ Next.js +
+ ); + } if (metadata.provider === "railway") { return (
From f597cfbabc825d92a1489dce40dd3fb79cea8d07 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 02:21:03 -0700 Subject: [PATCH 09/13] fix(frontend): routing bug --- .../projects.$project/ns.$namespace/connect.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx index b8b9e903d3..b327758763 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx @@ -443,17 +443,16 @@ function Runners() { } function usePublishableToken() { + const dataProvider = useEngineCompatDataProvider(); return match(__APP_TYPE__) .with("cloud", () => { return useSuspenseQuery( - Route.useRouteContext({ - select: (ctx) => ctx.dataProvider, - }).publishableTokenQueryOptions(), + dataProvider.publishableTokenQueryOptions(), ).data; }) .with("engine", () => { return useSuspenseQuery( - useEngineCompatDataProvider().engineAdminTokenQueryOptions(), + dataProvider.engineAdminTokenQueryOptions(), ).data; }) .otherwise(() => { @@ -477,12 +476,11 @@ const useEndpoint = () => { function ConnectYourFrontend() { const token = usePublishableToken(); const endpoint = useEndpoint(); - const namespace = Route.useRouteContext({ - select: (ctx) => ctx.dataProvider.engineNamespace, - }); + const dataProvider = useEngineCompatDataProvider(); + const namespace = dataProvider.engineNamespace; const { data: configs } = useInfiniteQuery({ - ...useEngineCompatDataProvider().runnerConfigsQueryOptions(), + ...dataProvider.runnerConfigsQueryOptions(), refetchInterval: 5000, }); From 6de0a3b39036dc7ca9e5316c28c195524d768660 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 02:32:09 -0700 Subject: [PATCH 10/13] fix(frontend): fix routing --- .../ns.$namespace/connect.tsx | 73 +++++++++---------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx index b327758763..1c02a2348a 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/connect.tsx @@ -446,14 +446,12 @@ function usePublishableToken() { const dataProvider = useEngineCompatDataProvider(); return match(__APP_TYPE__) .with("cloud", () => { - return useSuspenseQuery( - dataProvider.publishableTokenQueryOptions(), - ).data; + return useSuspenseQuery(dataProvider.publishableTokenQueryOptions()) + .data; }) .with("engine", () => { - return useSuspenseQuery( - dataProvider.engineAdminTokenQueryOptions(), - ).data; + return useSuspenseQuery(dataProvider.engineAdminTokenQueryOptions()) + .data; }) .otherwise(() => { throw new Error("Not in a valid context"); @@ -485,11 +483,7 @@ function ConnectYourFrontend() { }); // Check if Vercel is connected - const hasVercel = configs?.pages.some((page) => - Object.values(page.runnerConfigs).some( - (config) => config.metadata?.provider === "vercel" - ) - ); + const hasVercel = hasProvider(configs, ["vercel", "next-js"]); const nextJsTab = ( See Next.js Documentation{" "} - + } @@ -530,10 +521,7 @@ function ConnectYourFrontend() { > See React Documentation{" "} - + } @@ -557,10 +545,7 @@ function ConnectYourFrontend() { > See JavaScript Documentation{" "} - + } @@ -586,25 +571,36 @@ function ConnectYourFrontend() {

- {hasVercel ? ( - <> - {nextJsTab} - {reactTab} - {javascriptTab} - - ) : ( - <> - {javascriptTab} - {reactTab} - {nextJsTab} - - )} + {hasVercel + ? [nextJsTab, reactTab, javascriptTab] + : [javascriptTab, reactTab, nextJsTab]}
); } +type RunnerConfig = [ + string, + { + datacenters: Record; + }, +]; + +function hasProvider( + configs: RunnerConfig[] | undefined, + providers: string[], +): boolean { + if (!configs) return false; + return configs.some(([, config]) => + Object.values(config.datacenters).some( + (datacenter) => + datacenter.metadata?.provider && + providers.includes(datacenter.metadata.provider), + ), + ); +} + const javascriptCode = ({ token, endpoint, @@ -779,10 +775,7 @@ function OneClickDeployRailwayButton() { startIcon={} asChild > - + Railway From ad73dfd8b399d6c90909106bbda0ba676f6d4133 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 03:03:01 -0700 Subject: [PATCH 11/13] fix(frontend): fix create vercel --- .../app/data-providers/engine-data-provider.tsx | 14 ++++++++++++-- .../src/app/dialogs/connect-quick-vercel-frame.tsx | 3 ++- frontend/src/app/dialogs/connect-vercel-frame.tsx | 7 +++---- frontend/src/app/forms/connect-vercel-form.tsx | 13 +++++++++++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index df1ab78d51..6d74e1e3f9 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -578,7 +578,12 @@ export const createNamespaceContext = ({ variant?: Rivet.RunnerConfigVariant; }) { return infiniteQueryOptions({ - queryKey: [{ namespace }, "runners", "configs", opts], + queryKey: [ + { namespace }, + "runners", + "configs", + { variant: opts?.variant }, + ], initialPageParam: undefined as string | undefined, queryFn: async ({ signal: abortSignal, pageParam }) => { const response = await client.runnerConfigs.list( @@ -621,7 +626,12 @@ export const createNamespaceContext = ({ variant?: Rivet.RunnerConfigVariant; }) { return queryOptions({ - queryKey: [{ namespace }, "runners", "config", opts], + queryKey: [ + { namespace }, + "runners", + "config", + { name: opts.name, variant: opts.variant }, + ], enabled: !!opts.name, queryFn: async ({ signal: abortSignal }) => { const response = await client.runnerConfigs.list( diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index 9ccdb1af94..09128d5a0e 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -18,6 +18,7 @@ import { import { type Region, useEngineCompatDataProvider } from "@/components/actors"; import { queryClient } from "@/queries/global"; import { StepperForm } from "../forms/stepper-form"; +import { VERCEL_SERVERLESS_MAX_DURATION } from "./connect-vercel-frame"; import { EnvVariablesStep } from "./connect-railway-frame"; const { stepper } = ConnectVercelForm; @@ -98,7 +99,7 @@ function FormStepper({ maxRunners: values.maxRunners, slotsPerRunner: values.slotsPerRunner, runnersMargin: values.runnerMargin, - requestLifespan: 295, // 5 minutes minus buffer + requestLifespan: VERCEL_SERVERLESS_MAX_DURATION - 5, // Subtract 5s to ensure we don't hit Vercel's timeout headers: Object.fromEntries( values.headers.map(([key, value]) => [key, value]), ), diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx index ba65a2fbba..8a84fd3706 100644 --- a/frontend/src/app/dialogs/connect-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx @@ -29,6 +29,8 @@ const { stepper } = ConnectVercelForm; type FormValues = z.infer>; +export const VERCEL_SERVERLESS_MAX_DURATION = 300; + interface CreateProjectFrameContentProps extends DialogContentProps { } function usePublishableToken() { @@ -162,10 +164,7 @@ function FormStepper({ maxRunners: values.maxRunners, slotsPerRunner: values.slotsPerRunner, runnersMargin: values.runnerMargin, - requestLifespan: - ConnectVercelForm.PLAN_TO_MAX_DURATION[ - values.plan - ] - 5, // Subtract 5s to ensure we don't hit Vercel's timeout + requestLifespan: VERCEL_SERVERLESS_MAX_DURATION - 5, // Subtract 5s to ensure we don't hit Vercel's timeout headers: Object.fromEntries( values.headers.map(([key, value]) => [key, value]), ), diff --git a/frontend/src/app/forms/connect-vercel-form.tsx b/frontend/src/app/forms/connect-vercel-form.tsx index da87417575..6593882c1a 100644 --- a/frontend/src/app/forms/connect-vercel-form.tsx +++ b/frontend/src/app/forms/connect-vercel-form.tsx @@ -71,6 +71,19 @@ export const stepper = defineStepper( schema: z.object({ success: z.boolean().refine((val) => val, "Connection failed"), endpoint: endpointSchema, + runnerName: z.string().min(1, "Runner name is required"), + datacenters: z + .record(z.boolean()) + .refine( + (data) => Object.values(data).some(Boolean), + "At least one datacenter must be selected", + ), + headers: z.array(z.tuple([z.string(), z.string()])).default([]), + slotsPerRunner: z.coerce.number().min(1, "Must be at least 1"), + maxRunners: z.coerce.number().min(1, "Must be at least 1"), + minRunners: z.coerce.number().min(0, "Must be 0 or greater"), + runnerMargin: z.coerce.number().min(0, "Must be 0 or greater"), + plan: z.string().min(1, "Please select a Vercel plan"), }), }, ); From 5cf2b53f31b7ca2e30a9b8a7f689e4e1fba35471 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 03:12:07 -0700 Subject: [PATCH 12/13] fix(frontend): fix template url --- frontend/src/app/dialogs/connect-quick-vercel-frame.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index 09128d5a0e..c78b44459b 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -142,7 +142,7 @@ function StepInitialInfo() { Deploy the Rivet Vercel template to get started quickly.

From 725df553de6e187f0a579d89e8ac23053b5385c8 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 18 Oct 2025 03:20:07 -0700 Subject: [PATCH 13/13] chore: fix inspector --- frontend/src/app/connect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/connect.tsx b/frontend/src/app/connect.tsx index 80ebdd5f46..eaa0a68509 100644 --- a/frontend/src/app/connect.tsx +++ b/frontend/src/app/connect.tsx @@ -31,7 +31,7 @@ export function Connect({