diff --git a/packages/client/ui-models/src/client/ProviderEditor.tsx b/packages/client/ui-models/src/client/ProviderEditor.tsx
index 47bf6a9286..ec8abe45ad 100644
--- a/packages/client/ui-models/src/client/ProviderEditor.tsx
+++ b/packages/client/ui-models/src/client/ProviderEditor.tsx
@@ -141,10 +141,17 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
useEffect(() => {
let stale = false
setKeyState(undefined)
- void api.credentials.describe({ refs: [keyRef] }).then((response) => {
- if (stale || !response.result.ok) return
- setKeyState(response.result.value.credentials[keyRef])
- })
+ // The key state is a placeholder hint, not a precondition for editing:
+ // neither a business rejection nor a transport failure may reach the
+ // browser as an unhandled rejection, so the card simply renders without
+ // the "already configured" hint.
+ void api.credentials.describe({ refs: [keyRef] }).then(
+ (response) => {
+ if (stale || !response.result.ok) return
+ setKeyState(response.result.value.credentials[keyRef])
+ },
+ () => undefined,
+ )
return () => { stale = true }
}, [api.credentials, keyRef])
diff --git a/packages/client/ui-models/tests/components.spec.tsx b/packages/client/ui-models/tests/components.spec.tsx
index 974f400999..786dcc63db 100644
--- a/packages/client/ui-models/tests/components.spec.tsx
+++ b/packages/client/ui-models/tests/components.spec.tsx
@@ -371,6 +371,31 @@ describe('ModelsSection', () => {
expect(set).not.toHaveBeenCalled()
})
+ it('renders the card without the stored-key hint when the credential probe rejects', async () => {
+ // The probe is a placeholder hint, not a precondition: an escaping
+ // rejection would surface in the browser as an unhandled rejection.
+ const { face } = scriptedFace()
+ face.credentials.describe = vi.fn(() => Promise.reject(new Error('connection lost')))
+ const unhandled = vi.fn()
+ process.on('unhandledRejection', unhandled)
+ try {
+ const controller = new ModelsSettingsStore(face as unknown as WireFace)
+ await controller.load()
+ render()
+ const key = await screen.findByLabelText(en.keyInput)
+ expect(key.placeholder).toBe(en.keyPlaceholder)
+ await new Promise(resolve => setTimeout(resolve, 10))
+ expect(unhandled).not.toHaveBeenCalled()
+ } finally {
+ process.off('unhandledRejection', unhandled)
+ }
+ })
+
it('tells the user to reopen when another writer moved the namespace first', async () => {
// The stale-draft overwrite: two tabs open the same card, the other saves,
// and this one must be refused rather than replay its opening snapshot.