[PR #1845] [MERGED] fix(staffml): trap focus inside the AskInterviewer waitlist modal #30948

Closed
opened 2026-06-21 20:35:30 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/harvard-edge/cs249r_book/pull/1845
Author: @farhan523
Created: 6/6/2026
Status: Merged
Merged: 6/10/2026
Merged by: @profvjreddi

Base: devHead: fix/staffml-a11y-polish


📝 Commits (1)

  • 3ad380f fix(staffml): trap focus inside the AskInterviewer waitlist modal

📊 Changes

2 files changed (+139 additions, -1 deletions)

View changed files

interviews/staffml/src/__tests__/waitlist-modal.test.tsx (+96 -0)
📝 interviews/staffml/src/components/AskInterviewer.tsx (+43 -1)

📄 Description

Summary

The waitlist modal at the bottom of AskInterviewer carried role=\"dialog\" aria-modal=\"true\" but didn't enforce the contract — Tab and Shift+Tab escaped into background controls behind the backdrop, and the email input wasn't auto-focused on open. Keyboard users landed in the page body and had to hunt for the input.

Changes

  • Auto-focus the email input on open via the same setTimeout(0) pattern used by CommandPalette and KeyboardShortcutsOverlay. Keyboard users land in the right place.
  • Focus trap: Tab from the last focusable cycles back to the first, Shift+Tab from the first cycles to the last. Middle-of-trap presses fall through to the browser's native handling — we only block at the edges.
  • Focus restoration: capture document.activeElement on mount, restore it on unmount so focus returns to the trigger that opened the modal.
  • Export WaitlistModal as a named export so the regression test can render it directly without spinning up the full AskInterviewer parent and triggering its open state.

Mirrors the focus-trap pattern already used in CommandPalette (lines 238-252), so the two modals behave identically for keyboard users.

Test plan

  • npx vitest run → 62/62 individual cases (was 57 on dev; +5 new for autofocus, restoration, forward cycle, backward cycle, middle-trap fall-through).
  • npx tsc --noEmit → unchanged from dev baseline (the 2 pre-existing katex / js-quantities errors are env-local).
  • Manual: open Mock Interview, click "Join waitlist" (or whatever triggers WaitlistModal), confirm the email input has focus, Tab repeatedly and verify focus stays inside the dialog, close and verify focus returns to the trigger.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/harvard-edge/cs249r_book/pull/1845 **Author:** [@farhan523](https://github.com/farhan523) **Created:** 6/6/2026 **Status:** ✅ Merged **Merged:** 6/10/2026 **Merged by:** [@profvjreddi](https://github.com/profvjreddi) **Base:** `dev` ← **Head:** `fix/staffml-a11y-polish` --- ### 📝 Commits (1) - [`3ad380f`](https://github.com/harvard-edge/cs249r_book/commit/3ad380f5634c61c6536d9562c8ce60444afd60f8) fix(staffml): trap focus inside the AskInterviewer waitlist modal ### 📊 Changes **2 files changed** (+139 additions, -1 deletions) <details> <summary>View changed files</summary> ➕ `interviews/staffml/src/__tests__/waitlist-modal.test.tsx` (+96 -0) 📝 `interviews/staffml/src/components/AskInterviewer.tsx` (+43 -1) </details> ### 📄 Description ## Summary The waitlist modal at the bottom of `AskInterviewer` carried `role=\"dialog\" aria-modal=\"true\"` but didn't enforce the contract — Tab and Shift+Tab escaped into background controls behind the backdrop, and the email input wasn't auto-focused on open. Keyboard users landed in the page body and had to hunt for the input. ### Changes - **Auto-focus the email input on open** via the same `setTimeout(0)` pattern used by `CommandPalette` and `KeyboardShortcutsOverlay`. Keyboard users land in the right place. - **Focus trap**: `Tab` from the last focusable cycles back to the first, `Shift+Tab` from the first cycles to the last. Middle-of-trap presses fall through to the browser's native handling — we only block at the edges. - **Focus restoration**: capture `document.activeElement` on mount, restore it on unmount so focus returns to the trigger that opened the modal. - **Export `WaitlistModal`** as a named export so the regression test can render it directly without spinning up the full `AskInterviewer` parent and triggering its open state. Mirrors the focus-trap pattern already used in `CommandPalette` (lines 238-252), so the two modals behave identically for keyboard users. ## Test plan - [x] `npx vitest run` → 62/62 individual cases (was 57 on dev; +5 new for autofocus, restoration, forward cycle, backward cycle, middle-trap fall-through). - [x] `npx tsc --noEmit` → unchanged from dev baseline (the 2 pre-existing `katex` / `js-quantities` errors are env-local). - [ ] Manual: open Mock Interview, click \"Join waitlist\" (or whatever triggers `WaitlistModal`), confirm the email input has focus, Tab repeatedly and verify focus stays inside the dialog, close and verify focus returns to the trigger. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-06-21 20:35:30 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/cs249r_book#30948