diff --git a/job-tracker-ui/src/confirm.test.tsx b/job-tracker-ui/src/confirm.test.tsx
index bd69952d..b3a4fd8f 100644
--- a/job-tracker-ui/src/confirm.test.tsx
+++ b/job-tracker-ui/src/confirm.test.tsx
@@ -67,3 +67,19 @@ test('finishing the opening transition preserves focus already moved inside the
jest.useRealTimers();
}
});
+
+test('finishing the opening transition recovers Cancel from the nested-modal container fallback', async () => {
+ jest.useFakeTimers();
+ try {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /open confirm/i }));
+ const container = screen.getByRole('dialog', { name: 'Delete job' }).parentElement!;
+ expect(container).toHaveAttribute('role', 'presentation');
+ act(() => container.focus());
+ expect(container).toHaveFocus();
+ await act(async () => { jest.advanceTimersByTime(1000); });
+ expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus();
+ } finally {
+ jest.useRealTimers();
+ }
+});
diff --git a/job-tracker-ui/src/confirm.tsx b/job-tracker-ui/src/confirm.tsx
index a9c27504..2b48aecd 100644
--- a/job-tracker-ui/src/confirm.tsx
+++ b/job-tracker-ui/src/confirm.tsx
@@ -24,6 +24,7 @@ export function ConfirmProvider({ children }: { children: React.ReactNode }) {
const i18n = useOptionalI18n();
const messageId = useId();
const titleId = useId();
+ const cancelRef = useRef(null);
const resolverRef = useRef<((value: boolean) => void) | null>(null);
const [state, setState] = useState({
open: false,
@@ -63,6 +64,10 @@ export function ConfirmProvider({ children }: { children: React.ReactNode }) {