From 4395268cc175b3c58673d045b24739db8b9be0a2 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Thu, 30 Jul 2026 19:29:30 +0800 Subject: [PATCH] fix(ui-models): contain the card's credential probe rejection The review named this call site with the other two, and the previous pass missed it: the editor card's mount-time `credentials.describe` had only a fulfillment handler, so a transport failure reached the browser as an unhandled rejection. The probe is a placeholder hint ("already configured"), never a precondition for editing, so it now renders without the hint rather than failing. Covered by a test that fails without the handler. --- .../ui-models/src/client/ProviderEditor.tsx | 15 ++++++++--- .../ui-models/tests/components.spec.tsx | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) 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.