Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dashboard error states

A screen that cannot load its data has to say why. Before #962 every screen rendered the same sentence — Failed to load X. — for causes needing entirely different responses, so it pointed at none of them.

That is not a hypothetical cost. During the #924 dogfooding pass the Keys screen showed “Failed to load your keys.” while the real cause was every /api/v1/me/* route returning 401 (#942). The message sent the operator to check their key configuration; the actual cause was found afterwards by reading traces. An error that cannot separate you are not signed in from the server is down costs more time than no error at all, because it invites a wrong hypothesis and the operator spends their attention there first.

The rule

Never render a load failure by hand. Use LoadError:

{keys.error && (
  <LoadError
    error={keys.error}
    resource={t("errors.resources.virtualKeys")}
    onRetry={() => keys.refetch()}
  />
)}

resource is the translated noun for what failed — it is interpolated into the title, so it reads as a sentence in every locale. Pass onRetry whenever the caller holds a query handle; the component decides whether offering it is honest.

What it distinguishes

classifyLoadError in ui/src/lib/load-error.ts maps a thrown value to one of six kinds. ApiError already carries status and the control plane’s code, so no screen has to parse a message to find out what happened.

kindcauserecovery offered
unauthenticated401sign in again
forbidden403none — ask an administrator
openMode401 with code open_mode_no_sessionnone — set ROLTER_ADMIN_TOKEN
unreachablethe thrown value is not an ApiError, so fetch never connectedretry
server5xxretry
unknownany other non-ok statusretry

Two of these are easy to collapse and must not be. A plain 401 is fixed by signing in; open_mode_no_session is a control plane running with no admin token, which has no accounts to sign into at all — signing in again is exactly the wrong advice. And a retry button on a 403 suggests the failure was transient when it was a permission, so isRetryable withholds it.

Two things that are not this component

An empty result is not a failure. A successful request returning zero rows renders an empty state. Routing it here would tell an operator something is broken when nothing is.

The control plane’s own message is never swallowed. LoadError prints it beneath the summary. The dashboard’s classification is a helpful gloss, not a replacement — #962 happened because the gloss was the only thing on screen and it was wrong.

Adding a screen

Add the resource noun to errors.resources.* in every catalog under ui/src/lib/i18n/locales/ (see i18n) and use it as above. The six errors.load.* kinds already exist; a new screen needs no new error copy.