diff --git a/frontend/src/app/runner-config-table.tsx b/frontend/src/app/runner-config-table.tsx index f7179c077b..15f147bcdf 100644 --- a/frontend/src/app/runner-config-table.tsx +++ b/frontend/src/app/runner-config-table.tsx @@ -87,7 +87,7 @@ export function RunnerConfigsTable({ - There's no providers matching criteria. + No providers added yet. diff --git a/frontend/src/app/runners-table.tsx b/frontend/src/app/runners-table.tsx index 59bacab7f5..1490e55034 100644 --- a/frontend/src/app/runners-table.tsx +++ b/frontend/src/app/runners-table.tsx @@ -412,8 +412,8 @@ function EmptyState() { ) : ( - There are no runners connected. You will not be able to - run actors until a runner appears here. + No runners connected yet. Actors can't run until a + runner appears here. )} diff --git a/frontend/src/app/settings-drawer.tsx b/frontend/src/app/settings-drawer.tsx index ff9417d795..380dee2d8a 100644 --- a/frontend/src/app/settings-drawer.tsx +++ b/frontend/src/app/settings-drawer.tsx @@ -92,8 +92,9 @@ const TAB_META: Record = { }, settings: { title: "Settings", - description: - "Connect your RivetKit application to Rivet Cloud. Use your cloud of choice to run Rivet Actors.", + description: features.platform + ? "Connect your RivetKit application to Rivet Cloud. Use your cloud of choice to run Rivet Actors." + : "Connect providers and runners to this namespace. Use your cloud of choice to run Rivet Actors.", }, compute: { title: "Compute", diff --git a/frontend/src/app/settings-pages/namespace-settings.tsx b/frontend/src/app/settings-pages/namespace-settings.tsx index a6451a1f80..ae1a3c31e0 100644 --- a/frontend/src/app/settings-pages/namespace-settings.tsx +++ b/frontend/src/app/settings-pages/namespace-settings.tsx @@ -109,7 +109,7 @@ function Providers() { return ( + } + /> + ); +} + function DocsButton() { return ( ) - ) : ( + ) : count > RECORDS_PER_PAGE ? ( + // Only worth saying once the user has actually scrolled through + // more than one page; for short lists it is just noise. {copy.noMoreActors} - )} + ) : null} ); } diff --git a/frontend/src/components/actors/inspector-tab-registry.tsx b/frontend/src/components/actors/inspector-tab-registry.tsx index 1e560710ab..fd1804bea5 100644 --- a/frontend/src/components/actors/inspector-tab-registry.tsx +++ b/frontend/src/components/actors/inspector-tab-registry.tsx @@ -75,25 +75,29 @@ interface TabRegistration { render: (actorId: ActorId) => ReactNode; } -// Tab list — preserved order matches the dashboard tab strip. Adding a new -// inspector tab here automatically advertises it to the dashboard (in the -// iframe path) and renders it inline (in the legacy path). +// Tab list — preserved order matches the dashboard tab strip, and the first +// available tab is the default the dashboard opens. State sits before +// Database on purpose: every actor has a SQLite database (so Database would +// otherwise always win), but the state object is what the actor's code +// actually defines. Adding a new inspector tab here automatically advertises +// it to the dashboard (in the iframe path) and renders it inline (in the +// legacy path). export const INSPECTOR_TAB_REGISTRATIONS: readonly TabRegistration[] = [ { descriptor: { id: "workflow", label: "Workflow", icon: "workflow" }, available: (caps) => caps.isWorkflowEnabled, render: (actorId) => , }, - { - descriptor: { id: "database", label: "Database", icon: "database" }, - available: (caps) => caps.isDatabaseEnabled, - render: (actorId) => , - }, { descriptor: { id: "state", label: "State", icon: "state" }, available: (caps) => caps.isStateEnabled, render: (actorId) => , }, + { + descriptor: { id: "database", label: "Database", icon: "database" }, + available: (caps) => caps.isDatabaseEnabled, + render: (actorId) => , + }, { descriptor: { id: "queue", label: "Queue", icon: "queue" }, available: (caps) => caps.isQueueSupported, diff --git a/frontend/src/components/actors/no-providers-alert.tsx b/frontend/src/components/actors/no-providers-alert.tsx index 01d6402238..9dd8c80d36 100644 --- a/frontend/src/components/actors/no-providers-alert.tsx +++ b/frontend/src/components/actors/no-providers-alert.tsx @@ -1,4 +1,4 @@ -import { faBook, faExclamationTriangle, faPlus, Icon } from "@rivet-gg/icons"; +import { faBook, faPlug, faPlus, Icon } from "@rivet-gg/icons"; import { Link } from "@tanstack/react-router"; import { ProviderDropdown } from "@/app/provider-dropdown"; import { docsLinks } from "@/content/data"; @@ -16,14 +16,14 @@ export function NoProvidersAlert({

- No Providers Connected + No providers connected

- You can't run any Actors yet. Use provider of your choice to - connect and start deploying and running Rivet Actors. + Actors need somewhere to run. Add a provider to connect a + cloud of your choice and start running Rivet Actors.

@@ -65,7 +65,7 @@ export function NoProvidersAlert({ size="sm" className="w-full" > - Connect Provider + Add Provider )} diff --git a/frontend/src/components/live-badge.tsx b/frontend/src/components/live-badge.tsx index 45e1c1f324..6b201ad084 100644 --- a/frontend/src/components/live-badge.tsx +++ b/frontend/src/components/live-badge.tsx @@ -11,7 +11,8 @@ export function LiveBadge({ className }: LiveBadgeProps) { className={cn(className, "flex justify-center items-center")} variant="outline" > -
+ {/* Accent, not destructive: red reads as an error elsewhere in the app. */} +
Live ); diff --git a/frontend/src/routes/_context.tsx b/frontend/src/routes/_context.tsx index 2bab960caa..15feffba23 100644 --- a/frontend/src/routes/_context.tsx +++ b/frontend/src/routes/_context.tsx @@ -44,7 +44,16 @@ const searchSchema = z export const Route = createFileRoute("/_context")({ component: RouteComponent, validateSearch: (search) => { - const validated = searchSchema.parse(search); + // Hand-typed and shared links often carry `?n=counter` instead of the + // serialized array form. Accept both so a bare string never surfaces + // as a raw validation error page. Normalized before parsing because + // the schema is an intersection, and a per-field transform would + // conflict with the untouched value from the `z.record` half. + const normalized = + typeof search.n === "string" + ? { ...search, n: [search.n] } + : search; + const validated = searchSchema.parse(normalized); // `pool` is scoped to the pages that actually use it: the Logs route // re-declares it in its own validateSearch, and the compute settings tab // needs it while open. Drop it everywhere else so the selected pool does