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.