Skip to content

[CP staging] follow-up: Screen Reader: Many Pages: There is no dialog role and title announced#87292

Merged
amyevans merged 10 commits intoExpensify:mainfrom
TaduJR:fix-Screen-Reader-Many-Pages-There-is-no-dialog-role-and-title-announced
Apr 8, 2026
Merged

[CP staging] follow-up: Screen Reader: Many Pages: There is no dialog role and title announced#87292
amyevans merged 10 commits intoExpensify:mainfrom
TaduJR:fix-Screen-Reader-Many-Pages-There-is-no-dialog-role-and-title-announced

Conversation

@TaduJR
Copy link
Copy Markdown
Contributor

@TaduJR TaduJR commented Apr 7, 2026

Explanation of Change

Follow-up to #85221. Fixes deploy blocker #87304 (Enter on confirm page activates Back button instead of submitting) and fixes focus ring showing on page load/refresh.

What's changed

  • DialogLabelContext - Removed initialFocusClaimedRef reset from pushLabel. Focus is now only claimed once per dialog lifecycle (when the RHP first opens), not on every inner screen transition. This prevents the Back button from stealing focus on screens like the expense confirm page.

  • useDialogContainerFocus - Extracted focus logic into focusFirstInteractiveElement() for unit testability. Added input-modality-aware focus ring handling:

    • Page load/refresh: Suppresses ring using data-programmatic-focus + outline: none. Registers onFirstTab handler that intercepts the first Tab key and re-focuses with a visible ring, preventing Tab from skipping the silently focused element. Suppression attributes survive browser tab-switch blur/re-focus cycles (no blur cleanup).
    • Keyboard navigation: Shows ring immediately (module-level keydown listener detects keyboard usage before the hook mounts).
    • Mouse click: mousedown listener resets keyboard flag so suppression is consistently restored after switching from keyboard to mouse.
    • onFirstTab listener registered without {once: true} so non-Tab keys don't consume it. Listener cleaned up via returned cleanup function called in useEffect teardown to prevent leaks on dialog unmount.
  • FOCUSABLE_SELECTOR - Added input, textarea, select for more robust first-focusable-element discovery.

  • Unit tests - 21 tests for focusFirstInteractiveElement (guards, suppression, onFirstTab, cleanup function, non-Tab persistence, focus-moved-away, keyboard path, mousedown reset, tab-switch resilience, aria-hidden, selectors). Each describe block sets its own modality explicitly to avoid test ordering dependencies. 10 tests for DialogLabelContext (updated focus claim test to verify pushLabel does not reset claim).

Fixed Issues

$ #76944
$ #87304
PROPOSAL: #76944 (comment)

Tests

Test 1: No focus ring on page load

  1. Navigate directly to any RHP page (e.g., paste staging.new.expensify.com/settings/about/app-download-links in the URL bar and press Enter) or go to App Download Links RHP and refresh the page
  2. Wait for the page to load
  3. Verify the Back button (< arrow) in the right panel does NOT show a blue focus ring

Test 2: Tab after page load lands on Back button

  1. Navigate directly to any RHP page (same as above)
  2. Wait for the page to load
  3. Press Tab
  4. Verify the Back button shows a blue focus ring (focus lands on it, not on the QR code)

Test 3: Keyboard navigation shows focus ring immediately

  1. Go to Settings > About
  2. Use Tab to navigate to "App download links"
  3. Press Enter to open it
  4. Verify the Back button in the right panel shows a blue focus ring immediately

Test 4: Mouse click then Tab lands on Back button

  1. Go to Settings > About
  2. Click "App download links" with the mouse
  3. Verify the Back button does NOT show a blue focus ring
  4. Press Tab
  5. Verify the Back button shows a blue focus ring (focus lands on it, not on the QR code)

Test 5: Repeated mouse clicks work consistently

  1. Go to Settings > About
  2. Click "App download links" with the mouse
  3. Press Tab, verify focus lands on Back button with ring
  4. Press the Back button to go back to About
  5. Click "App download links" again with the mouse
  6. Press Tab, verify focus lands on Back button with ring again (not on QR code)

Test 6: Auto-focused input is not stolen

  1. Go to Settings > Profile > Status (or any RHP page with an auto-focused input)
  2. Verify the text input has focus (cursor is blinking in the input field)
  3. Verify the Back button does NOT have focus

Test 7: Enter on confirm page does not go back (deploy blocker #87304)

  1. Open FAB > Create expense > Manual
  2. Enter an amount (e.g., 10)
  3. Press Enter to proceed to the confirm page
  4. Press Enter again on the confirm page
  5. Verify the page does NOT navigate back to the amount input page
  6. Verify the app either submits the expense or shows a validation error (e.g., merchant required)

Test 8: Tab switch does not show focus ring

  1. Navigate directly to any RHP page (e.g., paste staging.new.expensify.com/settings/about/app-download-links in the URL bar)
  2. Wait for the page to load
  3. Switch to another browser tab
  4. Switch back to the app tab
  5. Verify the Back button does NOT show a blue focus ring

Test 9: Inner navigation does not re-focus Back button

  1. Open FAB > Create expense > Manual
  2. Enter an amount and press Enter
  3. On the confirm page, verify the Back button does NOT have a blue focus ring
  4. Press Tab, verify focus moves to the first interactive element on the confirm page (not the Back button)
  • Verify that no errors appear in the JS console

Offline tests

Same as tests

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Mac-Chrome.mp4

@TaduJR TaduJR requested review from a team as code owners April 7, 2026 22:26
@melvin-bot melvin-bot bot requested review from joekaufmanexpensify and mkhutornyi and removed request for a team and mkhutornyi April 7, 2026 22:26
@melvin-bot
Copy link
Copy Markdown

melvin-bot bot commented Apr 7, 2026

@mkhutornyi Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot melvin-bot bot requested review from a team, amyevans and mkhutornyi and removed request for a team April 7, 2026 22:26
@melvin-bot
Copy link
Copy Markdown

melvin-bot bot commented Apr 7, 2026

@mkhutornyi @amyevans One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9df8884c29

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/components/DialogLabelContext.tsx 88.88% <ø> (ø)
src/hooks/useDialogContainerFocus/index.ts 81.81% <94.44%> (+81.81%) ⬆️
... and 75 files with indirect coverage changes

@TaduJR
Copy link
Copy Markdown
Contributor Author

TaduJR commented Apr 8, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@MelvinBot
Copy link
Copy Markdown
Contributor

Code Review — PR #87292

Overall the approach is solid: extracting focusFirstInteractiveElement with input-modality awareness is the right pattern, and the test coverage is thorough. A few things worth addressing:


1. Test ordering dependency (medium)

The test file relies on module-level hadKeyboardEvent state leaking between describe blocks (e.g. "after keyboard interaction" depends on a prior test's keydown dispatch). If Jest ever runs these blocks in a different order or in isolation, they'll fail. Consider explicitly setting the modality at the top of each describe block via a mousedown/keydown dispatch rather than depending on execution order — the last describe block already does this correctly.

2. Module-level listeners never removed (low)

The keydown and mousedown listeners registered at module scope (index.ts:8-21) persist for the lifetime of the page. This is fine for production but can cause state leakage across tests. Since the test file already works around this by dispatching events, this is mostly a note — but if test flakiness appears later, this is the likely culprit.

3. pushLabel behavior change needs verification (medium)

Removing initialFocusClaimedRef.current = false from pushLabel in DialogLabelContext.tsx means that when navigating within an already-open dialog (e.g. pushing a new screen onto the RHP stack), focus will NOT be re-claimed. The test was updated to match. This is intentional per the PR description, but please confirm this doesn't break screen reader announcement when navigating between screens inside the RHP — the original reset was presumably added for that reason.


Everything else looks clean. The onFirstTab interception pattern with blur cleanup is well-handled, and the aria-hidden filtering is correct.

@mkhutornyi
Copy link
Copy Markdown
Contributor

Bug? After refresh page, go to another tab and back, back button is focused

Screen.Recording.2026-04-08.at.9.04.31.am.mov

@mkhutornyi
Copy link
Copy Markdown
Contributor

Please check last 2 comments. Otherwise looks good

@MelvinBot
Copy link
Copy Markdown
Contributor

This PR prevents #87304 because of the pushLabel behavior change in DialogLabelContext.tsx — the line initialFocusClaimedRef.current = false was removed from pushLabel.

Before this PR: Every time a new screen pushed a label (inner navigation like amount → confirm), the focus claim was reset, so claimInitialFocus() returned true again and useDialogContainerFocus focused the Back button on the confirm page. Enter then activated it.

After this PR: pushLabel no longer resets the flag. Only the first screen mounted in the dialog gets programmatic focus. Inner navigation (amount → confirm) calls claimInitialFocus() which returns false, so the hook bails out at index.ts:78 without focusing the Back button. The confirm page's Back button never receives focus, so Enter can't activate it.

The updated test at DialogLabelContextTest.tsx:126 confirms this: "pushLabel does not reset focus claim (only first screen in dialog gets focus)".

@TaduJR TaduJR changed the title fix: suppress focus ring on programmatic dialog focus and add unit tests follow-up: Screen Reader: Many Pages: There is no dialog role and title announced Apr 8, 2026
@mkhutornyi
Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari
87292.mov

@MelvinBot
Copy link
Copy Markdown
Contributor

🤖 Code Review

The core fix (removing initialFocusClaimedRef reset from pushLabel) is correct and well-motivated — it prevents the Back button from stealing focus on inner screen transitions. The extracted focusFirstInteractiveElement is clean and testable. A few items to address:


1. Listener leak in onFirstTab (bug)
src/hooks/useDialogContainerFocus/index.ts:52 — Each call to focusFirstInteractiveElement in mouse-modality adds an onFirstTab listener to document. This listener only self-removes when the user presses Tab while focus is on the target element. If the user closes the dialog without pressing Tab, the listener persists. Opening and closing dialogs repeatedly accumulates stale listeners on document — each one fires on every keydown event (returning early, but still running).

Suggested fix: Track the listener in the useEffect cleanup, or store it in a module-level variable that gets replaced on each call (removing the previous one before adding a new one).


2. PR description mentions BaseOverlay aria-hidden change, but it's not in the diff
The "What's changed" section says "BaseOverlay — Added aria-hidden to hide overlay Close buttons from screen readers", but no overlay file is modified in this PR. Either the change was dropped or the description needs updating.


3. Minor: named exports only consumed by tests
focusFirstInteractiveElement and FOCUSABLE_SELECTOR are exported at line 89 but not imported by any production code — only by the test file. This is fine if intentional (test-only exports), but worth noting.


Everything else — the modality detection (hadKeyboardEvent), the onFirstTab Tab-interception UX, the data-programmatic-focus suppression surviving blur cycles, and the test coverage — looks solid.

@mkhutornyi
Copy link
Copy Markdown
Contributor

@TaduJR please address #87292 (comment)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b22a3df1a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@joekaufmanexpensify joekaufmanexpensify left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing DB

@MelvinBot
Copy link
Copy Markdown
Contributor

🤖 Code Review

The PR is well-structured — the extraction of focusFirstInteractiveElement, the cleanup lifecycle, and the onFirstTab mechanism are all correct. Tests are thorough. A few minor items:


Minor: FOCUSABLE_SELECTOR doesn't exclude :disabled or input[type="hidden"] (src/hooks/useDialogContainerFocus/index.ts:5)

The selector button, [href], input, textarea, select, ... will match disabled buttons/inputs and <input type="hidden">. Browsers silently no-op .focus() on disabled elements, but the function would still set data-programmatic-focus and outline: none on an element that never actually receives focus — leaving stale attributes until cleanup runs. Consider:

const FOCUSABLE_SELECTOR = 'button:not(:disabled), [href], input:not(:disabled):not([type="hidden"]), textarea:not(:disabled), select:not(:disabled), [role="button"], [role="link"], [tabindex]:not([tabindex="-1"])';

Nit: Missing test for disabled-element edge case (tests/unit/focusFirstInteractiveElementTest.ts)

A test verifying that a disabled first element is skipped (or that stale attributes don't persist) would strengthen the suite.


Everything else looks good — the onFirstTab listener is properly cleaned up on unmount via the returned cleanup function, the typeof document SSR guard is in place, the activeElement check is equivalent to the old code, and the DialogLabelContext change + updated test correctly reflects the new "focus once per dialog lifecycle" behavior.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5b8132d4c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@mkhutornyi mkhutornyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check minor comments above.
Otherwise looks good

@TaduJR
Copy link
Copy Markdown
Contributor Author

TaduJR commented Apr 8, 2026

Please check minor comments above.

Done.

Copy link
Copy Markdown
Contributor

@amyevans amyevans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test coverage!

@amyevans amyevans changed the title follow-up: Screen Reader: Many Pages: There is no dialog role and title announced [CP staging] follow-up: Screen Reader: Many Pages: There is no dialog role and title announced Apr 8, 2026
@amyevans amyevans merged commit 9e3c1b5 into Expensify:main Apr 8, 2026
34 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

🚧 @amyevans has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify
Copy link
Copy Markdown
Contributor

OSBotify commented Apr 8, 2026

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

OSBotify pushed a commit that referenced this pull request Apr 8, 2026
…ere-is-no-dialog-role-and-title-announced

[CP staging] follow-up: Screen Reader: Many Pages: There is no dialog role and title announced

(cherry picked from commit 9e3c1b5)

(cherry-picked to staging by roryabraham)
@OSBotify OSBotify added the CP Staging marks PRs that have been CP'd to staging label Apr 8, 2026
@OSBotify
Copy link
Copy Markdown
Contributor

OSBotify commented Apr 8, 2026

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.3.54-4 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 cancelled 🔪
🍎 iOS 🍎 cancelled 🔪

@MelvinBot
Copy link
Copy Markdown
Contributor

No help site changes required.

This PR modifies internal focus management logic (DialogLabelContext, useDialogContainerFocus) — specifically fixing focus ring suppression on page load and preventing the Back button from stealing focus on inner navigation (deploy blocker #87304). These are accessibility/focus behavior implementation details, not user-facing feature or workflow changes.

The help site articles under docs/articles document product features, settings, and workflows. None of the changes in this PR affect anything documented there.

@OSBotify
Copy link
Copy Markdown
Contributor

OSBotify commented Apr 9, 2026

🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.54-7 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@OSBotify
Copy link
Copy Markdown
Contributor

OSBotify commented Apr 9, 2026

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.3.55-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@MelvinBot
Copy link
Copy Markdown
Contributor

No help site changes are required for this PR.

The changes are purely internal accessibility/focus-management fixes (focus ring suppression, keyboard navigation, deploy blocker fix for Enter key on confirm pages). They don't affect any documented user flows, feature names, settings labels, or UI workflows covered by help site articles under docs/articles.

@jponikarchuk
Copy link
Copy Markdown

Test 9 of this PR failing because of the issue #87469
This issue is reproducible in: Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CP Staging marks PRs that have been CP'd to staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants