From c9971799b19bb1b8eeaff53738a80951b64f3c07 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:09:41 +0100 Subject: [PATCH] fix(inspector): prevent error flickering --- frontend/src/app/layout.tsx | 158 +++++++++--------- .../components/actors/actor-status-label.tsx | 144 ++++++++++------ .../actors/dialogs/create-actor-dialog.tsx | 4 +- frontend/src/queries/global.ts | 15 +- .../projects.$project/ns.$namespace/index.tsx | 4 +- 5 files changed, 190 insertions(+), 135 deletions(-) diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 29eee7d5d5..6b9d4d97fd 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -196,13 +196,13 @@ const Sidebar = ({ Settings ( <> - - - - - - - + + GitHub + + + ))} diff --git a/frontend/src/components/actors/actor-status-label.tsx b/frontend/src/components/actors/actor-status-label.tsx index fa3e7bf792..9bbd8b7bb6 100644 --- a/frontend/src/components/actors/actor-status-label.tsx +++ b/frontend/src/components/actors/actor-status-label.tsx @@ -95,27 +95,47 @@ export function ActorError({ error }: { error: object | string }) { )) .otherwise(() =>

Unknown error: {errMsg}

), ) - .with(P.shape({ runner_pool_error: P.shape({ runner_id: P.string }) }), (err) => ( - - )) - .with(P.shape({ runner_no_response: P.shape({ runner_id: P.string }) }), (err) => ( -

- Runner ({err.runner_no_response.runner_id}) was allocated but Actor - did not respond. -

- )) - .with(P.shape({ runner_connection_lost: P.shape({ runner_id: P.string }) }), (err) => ( -

- Runner ({err.runner_connection_lost.runner_id}) connection was lost - (no recent ping, network issue, or crash). -

- )) - .with(P.shape({ runner_draining_timeout: P.shape({ runner_id: P.string }) }), (err) => ( -

- Runner ({err.runner_draining_timeout.runner_id}) was draining but - Actor didn't stop in time. -

- )) + .with( + P.shape({ + runner_pool_error: P.shape({ runner_id: P.string }) + .or(P.shape({ serverless_http_error: P.any })) + .or(P.string) + .or(P.shape({ serverless_connection_error: P.any })) + .or(P.shape({ serverless_invalid_sse_payload: P.any })), + }), + (err) => , + ) + .with( + P.shape({ runner_no_response: P.shape({ runner_id: P.string }) }), + (err) => ( +

+ Runner ({err.runner_no_response.runner_id}) was allocated + but Actor did not respond. +

+ ), + ) + .with( + P.shape({ + runner_connection_lost: P.shape({ runner_id: P.string }), + }), + (err) => ( +

+ Runner ({err.runner_connection_lost.runner_id}) connection + was lost (no recent ping, network issue, or crash). +

+ ), + ) + .with( + P.shape({ + runner_draining_timeout: P.shape({ runner_id: P.string }), + }), + (err) => ( +

+ Runner ({err.runner_draining_timeout.runner_id}) was + draining but Actor didn't stop in time. +

+ ), + ) .with(P.shape({ crashed: P.shape({ message: P.string }) }), (err) => (

Actor crashed. {err.crashed.message}

)) @@ -139,7 +159,13 @@ export function QueriedActorError({ actorId }: { actorId: ActorId }) { export function RunnerPoolError({ error, }: { - error: object | string | undefined; + error: + | string + | null + | { runner_id: string } + | { serverless_http_error: unknown } + | { serverless_connection_error: unknown } + | { serverless_invalid_sse_payload: unknown }; }) { return match(error) .with(P.nullish, () => null) @@ -153,34 +179,52 @@ export function RunnerPoolError({ )) .otherwise(() =>

Unknown runner pool error

), ) - .with(P.shape({ serverless_http_error: P.shape({ status_code: P.number, body: P.string }) }), (errObj) => { - const { status_code, body } = errObj.serverless_http_error; - const code = status_code ?? "unknown"; - return ( - <> -

Serverless HTTP error with status code {code}

- {body ? : null} - - ); - }) - .with(P.shape({ serverless_connection_error: P.shape({ message: P.string }) }), (errObj) => { - const message = errObj.serverless_connection_error?.message; - return ( - <> -

Unable to connect to serverless endpoint

- {message ? : null} - - ); - }) - .with(P.shape({ serverless_invalid_sse_payload: P.shape({ message: P.string }) }), (errObj) => { - const message = errObj.serverless_invalid_sse_payload?.message; - return ( - <> -

Request payload validation failed

- {message ? : null} - - ); - }) + .with( + P.shape({ + serverless_http_error: P.shape({ + status_code: P.number, + body: P.string, + }), + }), + (errObj) => { + const { status_code, body } = errObj.serverless_http_error; + const code = status_code ?? "unknown"; + return ( + <> +

Serverless HTTP error with status code {code}

+ {body ? : null} + + ); + }, + ) + .with( + P.shape({ + serverless_connection_error: P.shape({ message: P.string }), + }), + (errObj) => { + const message = errObj.serverless_connection_error?.message; + return ( + <> +

Unable to connect to serverless endpoint

+ {message ? : null} + + ); + }, + ) + .with( + P.shape({ + serverless_invalid_sse_payload: P.shape({ message: P.string }), + }), + (errObj) => { + const message = errObj.serverless_invalid_sse_payload?.message; + return ( + <> +

Request payload validation failed

+ {message ? : null} + + ); + }, + ) .otherwise(() => { return

Unknown runner pool error.

; }); diff --git a/frontend/src/components/actors/dialogs/create-actor-dialog.tsx b/frontend/src/components/actors/dialogs/create-actor-dialog.tsx index 8d5a8dea3c..5f15cdb810 100644 --- a/frontend/src/components/actors/dialogs/create-actor-dialog.tsx +++ b/frontend/src/components/actors/dialogs/create-actor-dialog.tsx @@ -41,7 +41,9 @@ export default function CreateActorDialog({ onClose }: ContentProps) { input: values.input ? JSON.parse(values.input) : undefined, key: values.key, datacenter: - __APP_TYPE__ === "inspector" ? "" : values.datacenter, + __APP_TYPE__ === "inspector" + ? undefined + : values.datacenter, crashPolicy: values.crashPolicy || Rivet.CrashPolicy.Sleep, runnerNameSelector: values.runnerNameSelector || "default", }); diff --git a/frontend/src/queries/global.ts b/frontend/src/queries/global.ts index 7a9a1845e7..677410bdf3 100644 --- a/frontend/src/queries/global.ts +++ b/frontend/src/queries/global.ts @@ -11,6 +11,8 @@ import { isRivetApiError } from "@/lib/errors"; import { modal } from "@/utils/modal-utils"; import { Changelog } from "./types"; +const previousQueryCache = new QueryCache(); + const queryCache = new QueryCache({ onError(error, query) { // Silently ignore CancelledError - these are expected during navigation/unmount @@ -34,12 +36,19 @@ const queryCache = new QueryCache({ queryKey: query.queryKey, }); } + + if (query.meta?.statusCheck) { + previousQueryCache.remove(query); + } }, onSuccess(data, query) { if (query.meta?.statusCheck) { - queryClient.invalidateQueries({ - predicate: (q) => q.state.error !== null, - }); + if (!previousQueryCache.find(query)) { + previousQueryCache.add(query); + queryClient.invalidateQueries({ + predicate: (q) => q.state.error !== null, + }); + } } }, }); diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx index 6373e586b9..210e415332 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace/index.tsx @@ -5,7 +5,7 @@ import { } from "@tanstack/react-router"; import { Actors } from "@/app/actors"; import { BuildPrefiller } from "@/app/build-prefiller"; -import { PendingRouteLayout } from "@/app/route-layout"; +import { FullscreenLoading } from "@/components"; export const Route = createFileRoute( "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace/", @@ -16,7 +16,7 @@ export const Route = createFileRoute( throw notFound(); } }, - pendingComponent: PendingRouteLayout, + pendingComponent: FullscreenLoading, }); export function RouteComponent() {