From ee66a4b38197948a04ecd54cacc4a9d0e0679ba8 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 29 Oct 2026 05:27:41 +0200 Subject: [PATCH] ui: polish custom endpoint authentication and model controls --- components/settings/keys-section.tsx | 42 +++++++++++------------ tests/custom-provider-ui.test.tsx | 51 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 tests/custom-provider-ui.test.tsx diff --git a/components/settings/keys-section.tsx b/components/settings/keys-section.tsx index 219c3c5..b64f07e 100644 --- a/components/settings/keys-section.tsx +++ b/components/settings/keys-section.tsx @@ -909,7 +909,7 @@ function CustomEndpointEditor({ {authMode === "apiKey" && keySource === "env" &&

Environment supplies the effective key. Change the environment to replace it.

} {authMode === "apiKey" && keyPresent && (changedTarget || keyReuseRequired) && }
- { @@ -917,12 +917,12 @@ function CustomEndpointEditor({ setKeyTouched(true); if (!e.target.value.trim()) setAuthMode("keyless"); }} - placeholder={authMode === "keyless" ? "Keyless — saved/environment keys are not used" : keyPresent ? "Leave blank to keep effective key" : "API key"} + placeholder={keyPresent ? "Leave blank to keep effective key" : "API key"} aria-label="Custom endpoint API key" data-testid="custom-key-input" - disabled={busy || pinsBusy || authMode === "keyless" || keySource === "env"} + disabled={busy || pinsBusy || keySource === "env"} className="h-7 min-w-0 flex-1 rounded-sm border border-border bg-background px-1.5 font-mono text-xs text-foreground outline-none placeholder:text-muted-foreground/60 focus:border-muted-foreground/50" - /> + />}
{!setup && models.length > 0 && (
- - Budget pins do not resize backend context. Blank uses reported limits, then fallback (8192 context / 4096 output). Custom pricing is unknown. -
{models.map((m) => { const d = draftOf(m); @@ -995,38 +992,41 @@ function CustomEndpointEditor({
); })}
- {pinsError && (

{pinsError}

)} +
)} -

Reasoning support is a capability declaration. The effort picker’s Off may not disable a custom backend’s default thinking.

+
+ {!setup && models.length > 0 &&

Budget pins do not resize backend context. Blank uses reported limits, then fallback (8192 context / 4096 output). Custom pricing is unknown.

} +

Reasoning support is a capability declaration. The effort picker’s Off may not disable a custom backend’s default thinking.

+
{(error || pinsError) && } {error && (

diff --git a/tests/custom-provider-ui.test.tsx b/tests/custom-provider-ui.test.tsx new file mode 100644 index 0000000..98c2992 --- /dev/null +++ b/tests/custom-provider-ui.test.tsx @@ -0,0 +1,51 @@ +import React, { act } from "react"; +(globalThis as Record).React = React; +import assert from "node:assert/strict"; +import { afterEach, test } from "node:test"; +import { createRoot, type Root } from "react-dom/client"; +import { parseHTML } from "linkedom"; +import { KeysSection, CustomEndpointSetup } from "../components/settings/keys-section.tsx"; + +const dom = parseHTML(""); +Object.assign(globalThis, { window: dom.window, document: dom.document, Element: dom.window.Element, HTMLElement: dom.window.HTMLElement, HTMLInputElement: dom.window.HTMLInputElement, CustomEvent: dom.window.CustomEvent, IS_REACT_ACT_ENVIRONMENT: true }); +const originalFetch = globalThis.fetch; +let root: Root | undefined; +let host: HTMLElement | undefined; +async function mount(setup = false) { + globalThis.fetch = async (input, init) => { + assert.equal(init?.method ?? "GET", "GET", "polish interactions must not save credentials/settings"); + if (String(input).startsWith("/api/custom-provider")) return Response.json({ baseUrl: "http://fixture.invalid/v1", authMode: "keyless", keySource: "none", keyPresent: false, keyReuseRequired: false, models: [{ id: "fixture-model" }], overrides: {}, manualModels: [] }, { headers: { etag: '"fixture"' } }); + return Response.json({ keys: [], providers: [] }); + }; + host = document.createElement("div"); document.body.appendChild(host); root = createRoot(host); + await act(async () => root!.render(setup ? assert.fail("unexpected save")} /> : )); + if (!setup) await click("custom-endpoint-row-btn"); +} +function element(id: string) { const node = document.querySelector(`[data-testid="${id}"]`); assert.ok(node, id); return node; } +async function click(id: string) { await act(async () => element(id).dispatchEvent(new dom.window.Event("click", { bubbles: true }))); } +async function selectMode(value: string) { + const select = element("custom-auth-mode"); + Object.defineProperty(select, "value", { value, configurable: true }); + await act(async () => select.dispatchEvent(new dom.window.Event("change", { bubbles: true }))); +} +afterEach(async () => { await act(async () => root?.unmount()); host?.remove(); root = undefined; host = undefined; globalThis.fetch = originalFetch; }); +for (const setup of [false, true]) { + test(`${setup ? "Setup" : "Settings"} hides key input in Keyless and reveals it only in API key mode`, async () => { + await mount(setup); + assert.equal(document.querySelector('[data-testid="custom-key-input"]'), null); + await selectMode("apiKey"); assert.equal(element("custom-key-input").getAttribute("type"), "password"); + await selectMode("keyless"); assert.equal(document.querySelector('[data-testid="custom-key-input"]'), null); + assert.ok(element("custom-save")); + }); +} +test("model controls use approved effort labels, right-align Save limits, and group help below", async () => { + await mount(); + const select = element("custom-pin-reasoning-fixture-model"); + assert.equal(select.getAttribute("aria-label"), "fixture-model reasoning effort support"); + assert.deepEqual(Array.from(select.children).map(option => option.textContent), ["Reasoning effort: Use reported", "Reasoning effort: Supported", "Reasoning effort: Not supported"]); + assert.deepEqual(Array.from(select.children).map(option => option.getAttribute("value")), ["", "on", "off"], "capability values unchanged"); + const save = element("custom-pins-save"); assert.ok(save.classList.contains("ml-auto")); assert.equal(save.parentElement!.lastElementChild, save); + const help = element("custom-help"); assert.equal(element("custom-budget-help").parentElement, help); assert.equal(element("custom-reasoning-help").parentElement, help); + const children = Array.from(element("custom-endpoint-editor").children); assert.ok(children.indexOf(help) > children.indexOf(element("custom-pins"))); + assert.match(help.textContent ?? "", /do not resize backend context/); assert.match(help.textContent ?? "", /Off may not disable/); +});