From ddd3cbd04c78a3840ca362772111953e881b5a4b Mon Sep 17 00:00:00 2001 From: Astrid Gealer Date: Fri, 23 Oct 2026 22:12:11 +0000 Subject: [PATCH 1/3] fix(frontend): report API key copy failures inline instead of alert() When the clipboard write fails on the one-time "API Key Created" modal, the app popped a browser-native alert(). Native dialogs cannot be styled, are blocked in some embedded contexts, and sit outside the modal's focus handling. The modal now shows an inline role="alert" message under the key and selects the key text, so Ctrl+C / Cmd+C copies it without leaving the modal. A later successful copy hides the message again. Refs #705 Co-Authored-By: Claude Opus 5.5 --- frontend/src/__tests__/apikeys.test.ts | 20 +++++-- frontend/src/apikeys.ts | 12 ++++- .../tests-e2e/apikey-copy-failure.spec.ts | 54 +++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 frontend/tests-e2e/apikey-copy-failure.spec.ts diff --git a/frontend/src/__tests__/apikeys.test.ts b/frontend/src/__tests__/apikeys.test.ts index ecf634f1..475939ec 100644 --- a/frontend/src/__tests__/apikeys.test.ts +++ b/frontend/src/__tests__/apikeys.test.ts @@ -874,7 +874,7 @@ describe('API Keys Module', () => { jest.useRealTimers(); }); - test('copy button shows alert on clipboard error', async () => { + test('copy failure shows an inline error and selects the key, not a native alert (#705)', async () => { const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); const writeTextMock = jest.fn().mockRejectedValue(new Error('Clipboard error')); Object.defineProperty(navigator, 'clipboard', { @@ -885,12 +885,24 @@ describe('API Keys Module', () => { showKeyCreatedModal('test-api-key'); - const copyBtn = document.getElementById('copy-apikey-btn'); - copyBtn?.click(); + const copyError = document.getElementById('apikey-copy-error') as HTMLElement; + expect(copyError.getAttribute('role')).toBe('alert'); + expect(copyError.classList.contains('hidden')).toBe(true); + + document.getElementById('copy-apikey-btn')?.click(); await new Promise(resolve => setTimeout(resolve, 0)); - expect(window.alert).toHaveBeenCalledWith('Failed to copy to clipboard. Please copy manually.'); + expect(window.alert).not.toHaveBeenCalled(); + expect(copyError.classList.contains('hidden')).toBe(false); + expect(copyError.textContent).toContain('The key is selected'); + expect(window.getSelection()?.toString()).toBe('test-api-key'); + + // A later successful copy clears the error again. + writeTextMock.mockResolvedValueOnce(undefined); + document.getElementById('copy-apikey-btn')?.click(); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(copyError.classList.contains('hidden')).toBe(true); consoleError.mockRestore(); }); diff --git a/frontend/src/apikeys.ts b/frontend/src/apikeys.ts index 31cd7126..ce3d3789 100644 --- a/frontend/src/apikeys.ts +++ b/frontend/src/apikeys.ts @@ -431,6 +431,7 @@ export function showKeyCreatedModal(apiKey: string): void { ${escapeHtml(apiKey)} +