the settings form, and the branch that never ran

the plugin could not be configured. /_config had GET, PUT, DELETE and a test
that validates without storing, and nothing rendered any of it — the
connection had to be inserted by hand.

ConnectionPanel is both the wizard and the settings page, one component, the
same choice jellyfin's ConnectionSection makes and for the same reason: a
connection edited months later goes through exactly the path that stored the
first one. HostsPanel shows it when nothing is configured and on ?config=1,
which is in the URL so it survives a refresh. test comes before save and
stores nothing, so a wrong password cannot replace a working one. the secret
field starts empty even when one is stored, and empty means keep — that is
what lets a typo in the URL be fixed without going to find the password.

and while wiring the unconfigured case, it turned out that case never fired.

both panels read `(error as { body?: Failure }).body` to decide whether a
failure was unconfigured, unauthenticated or unreachable. useClient throws
ApiError(status, message) and has no `.body` at all, so that was always
undefined: every failure fell through to the generic message, and the careful
explanation of NPM's 404-means-credential quirk was written, shipped and
unreachable. asFailure now reads the STATUS, which the router already chooses
deliberately — 409 nothing stored, 503 no answer, 502 answered and refused.

owner-only is explained rather than reported. a member hitting 403 here reads
why: it is an admin login and NPM has no read-only role.

still not installed, still no db:push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-25 16:51:31 +00:00
co-authored by Claude Opus 5
parent ca77a4507d
commit ee8edd3709
3 changed files with 379 additions and 9 deletions
+3 -2
View File
@@ -36,8 +36,9 @@ export const CertificatesPanel = () => {
setCertificates(await clientRef.current.get<Certificate[]>("/proxy-manager/certificates"));
setError(null);
} catch (caught) {
const body = (caught as { body?: { error?: string } })?.body;
setError(body?.error ?? "Could not load certificates.");
// Not `.body` — `useClient` throws `ApiError(status, message)` and has no body. See `asFailure` in
// HostsPanel.tsx for the full account; this panel had the same dead read.
setError(caught instanceof Error ? caught.message : "Could not load certificates.");
setCertificates(null);
}
}, []);