From a3992a90faa564e37db4d840a2fc50df0444f66b Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 4 Nov 2026 15:37:10 +0100 Subject: [PATCH] fix(dashboard): focus Close, not Cancel Purchase, in the details dialog openModal focuses the first control after the header X, which in the upcoming purchase details dialog was the destructive Cancel Purchase. Pass initialFocus so Close takes focus instead. Closes #1653 Co-Authored-By: Claude Opus 5.5 --- frontend/src/__tests__/dashboard.test.ts | 7 +++++++ frontend/src/dashboard.ts | 3 ++- frontend/tests-e2e/dialog-focus.spec.ts | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/__tests__/dashboard.test.ts b/frontend/src/__tests__/dashboard.test.ts index 6167608a8..9130d35f4 100644 --- a/frontend/src/__tests__/dashboard.test.ts +++ b/frontend/src/__tests__/dashboard.test.ts @@ -742,6 +742,13 @@ describe('Dashboard Module', () => { expect(document.activeElement).toBe(viewBtn); }); + // #1653: opening the dialog must not put focus on the destructive action. + test('initial focus lands on Close, not Cancel Purchase', async () => { + const { modal } = await openDetails(); + expect(modal.querySelector('#cancel-purchase-detail-btn')).not.toBeNull(); + expect(document.activeElement?.id).toBe('close-purchase-details-btn'); + }); + test('Cancel Purchase uses the card confirm copy and sends one DELETE while in flight', async () => { const { modal } = await openDetails(); let finishDelete: () => void = () => {}; diff --git a/frontend/src/dashboard.ts b/frontend/src/dashboard.ts index 12fe45c1d..b069ddaa3 100644 --- a/frontend/src/dashboard.ts +++ b/frontend/src/dashboard.ts @@ -688,7 +688,8 @@ function viewPurchaseDetails(executionId: string): void { document.body.appendChild(modal); // Focus moves into the dialog, Tab is trapped, Escape closes it and focus // returns to View Details (#731). Every close path removes the element. - openModal(modal, { onClose: () => modal.remove() }); + // Initial focus goes to Close, not the destructive Cancel Purchase (#1653). + openModal(modal, { initialFocus: '#close-purchase-details-btn', onClose: () => modal.remove() }); } // buildUpcomingDetailsModal constructs the dialog via DOM-API, not diff --git a/frontend/tests-e2e/dialog-focus.spec.ts b/frontend/tests-e2e/dialog-focus.spec.ts index a4265d504..845fec162 100644 --- a/frontend/tests-e2e/dialog-focus.spec.ts +++ b/frontend/tests-e2e/dialog-focus.spec.ts @@ -50,7 +50,9 @@ test('upcoming purchase details dialog takes focus, closes on Escape and restore const dialog = page.getByRole('dialog', { name: 'Upcoming Purchase Details' }); await expect(dialog).toBeVisible(); - expect(await page.evaluate(() => !!document.getElementById('purchase-details-modal')?.contains(document.activeElement))).toBe(true); + // #1653: the safe Close button takes focus, not the destructive Cancel Purchase. + await expect(dialog.locator('#cancel-purchase-detail-btn')).toBeVisible(); + await expect(dialog.locator('#close-purchase-details-btn')).toBeFocused(); expect(await tabStaysInside(page, '#purchase-details-modal', 5)).toBe(true); await page.keyboard.press('Escape');