Skip to content

fix: break VoiceOver grouping on iOS so task checkbox is independently focusable#91530

Merged
cristipaval merged 48 commits into
Expensify:mainfrom
Krishna2323:fix/77574-task-checkbox-accessibility
Jun 19, 2026
Merged

fix: break VoiceOver grouping on iOS so task checkbox is independently focusable#91530
cristipaval merged 48 commits into
Expensify:mainfrom
Krishna2323:fix/77574-task-checkbox-accessibility

Conversation

@Krishna2323

@Krishna2323 Krishna2323 commented May 24, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Fixed Issues

$ #77574
PROPOSAL:

Tests

Here are the test steps:

Prerequisites:

  • Have a task created in a chat (e.g., "Take a test drive")
  • Enable VoiceOver on iOS

iOS (Primary fix):

  1. Open a chat containing a task
  2. Enable VoiceOver
  3. Swipe to navigate to the task action
  4. Verify the checkbox is independently focusable and announced as "Not checked, Task: <title>, checkbox, double tap to toggle"
  5. Swipe again to the title area
  6. Verify it's announced as "Task: <title>, button, double tap to activate"
  7. Double tap the checkbox to toggle completion
  8. Verify the state updates correctly

Android:

  1. Open a chat containing a task
  2. Enable TalkBack
  3. Navigate to the task action
  4. Verify the checkbox and title are still individually focusable (no regression)
  5. Verify the accessibility label reads "Task: <title>" instead of raw HTML

Web:

  1. Open a chat containing a task
  2. Tab through the chat messages
  3. Verify the task row is still focusable as a single element (no change from before)
  4. Verify long press / right-click context menu still works on the task row
  • Verify that no errors appear in the JS console

Offline tests

  • Same as tests

QA Steps

  • 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_hybrid.mp4
Android: mWeb Chrome
android_mWeb.mp4
iOS: Native
ios_hybrid.mp4
iOS: mWeb Safari
ios_mWeb.mp4
MacOS: Chrome / Safari
web_chrome.mp4

…y focusable

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
@codecov

codecov Bot commented May 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
...nchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx 94.59% <100.00%> (+0.47%) ⬆️
src/components/Checkbox.tsx 100.00% <ø> (ø)
...TMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx 73.43% <ø> (ø)
src/components/SelectionButton.tsx 84.21% <ø> (ø)
...libs/shouldBreakAccessibilityGrouping/index.ios.ts 100.00% <100.00%> (ø)
src/pages/inbox/report/ReportActionItem.tsx 90.64% <100.00%> (+0.16%) ⬆️
src/libs/shouldBreakAccessibilityGrouping/index.ts 0.00% <0.00%> (ø)
src/hooks/useTaskCheckboxAccessibility.ts 73.91% <73.91%> (ø)
src/components/ReportActionItem/TaskPreview.tsx 80.26% <56.25%> (-6.31%) ⬇️
src/components/ReportActionItem/TaskView.tsx 0.00% <0.00%> (ø)
... and 9 files with indirect coverage changes

…sions on other platforms

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
@Krishna2323

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copy link
Copy Markdown

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: 37a3806a51

ℹ️ 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".

Comment thread src/components/ReportActionItem/TaskPreview.tsx Outdated
@mkhutornyi

Copy link
Copy Markdown
Contributor

Mostly looks good.

Just one issue found.

After toggle checkbox, old checked state is announced. This happens on both pages.

  1. Checkbox is in unchecked state
  2. Screen reader announces "..., unchecked"
  3. Toggle checkbox by double tap. Now checkbox is in checked state
  4. Screen reader still announces "..., unchecked" instead of "..., checked"
  5. Toggle checkbox again. Now checkbox is in unchecked state
  6. Screen reader announces "..., checked" instead of "..., unchecked"

…fter toggle

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
@Krishna2323

Copy link
Copy Markdown
Contributor Author

@mkhutornyi could you please check again?

)}
<PressableWithSecondaryInteraction
ref={popoverAnchorRef}
accessible={shouldBreakAccessibilityGrouping() && isCreatedTaskReportAction(action) ? false : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As setting false here, no pressed feedback. I'm afraid QA will mark this as regression

Screen.Recording.2026-05-25.at.3.50.00.AM.mov

production:

Screen.Recording.2026-05-25.at.3.51.11.AM.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tried to fix that, could you please check?

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
Comment on lines +98 to +99
const [prevIsTaskCompletedFromOnyx, setPrevIsTaskCompletedFromOnyx] = useState(isTaskCompletedFromOnyx);
const [isTaskCompleted, setIsTaskCompleted] = useState(isTaskCompletedFromOnyx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Double states for the same state variable seems workaround to me

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now local state is only used when shouldBreakGrouping && isScreenReaderActive (iOS + VoiceOver).

Comment thread src/components/ReportActionItem/TaskView.tsx Outdated
Comment on lines +98 to +104
const [prevIsTaskCompletedFromOnyx, setPrevIsTaskCompletedFromOnyx] = useState(isTaskCompletedFromOnyx);
const [isTaskCompleted, setIsTaskCompleted] = useState(isTaskCompletedFromOnyx);

if (prevIsTaskCompletedFromOnyx !== isTaskCompletedFromOnyx) {
setPrevIsTaskCompletedFromOnyx(isTaskCompletedFromOnyx);
setIsTaskCompleted(isTaskCompletedFromOnyx);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This local state update logic now runs on every platform, not just iOS.
If completeTask/reopenTask ever fails or is queued offline and the underlying Onyx state doesn't flip, the local state might read "checked" briefly until the next re-render syncs back. Please verify this case.
And consider scoping this to shouldBreakGrouping only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Scoped. setLocalIsTaskCompleted is only called on iOS + VoiceOver. On all other platforms, isTaskCompleted resolves directly to the Onyx value.

…le click check

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
…edback for title area

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
@Krishna2323 Krishna2323 marked this pull request as ready for review May 28, 2026 05:22
@Krishna2323 Krishna2323 requested review from a team as code owners May 28, 2026 05:22
@melvin-bot melvin-bot Bot requested review from joekaufmanexpensify and mkhutornyi and removed request for a team May 28, 2026 05:22
@melvin-bot

melvin-bot Bot commented May 28, 2026

Copy link
Copy Markdown

@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 removed the request for review from a team May 28, 2026 05:22
@Krishna2323

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copy link
Copy Markdown

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: 9523a108e8

ℹ️ 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".

Comment thread src/components/ReportActionItem/TaskView.tsx Outdated
Signed-off-by: krishna2323 <belivethatkg@gmail.com>
<View style={[styles.flexRow, styles.flex1]}>
<Checkbox
onPress={callFunctionIfActionIsAllowed(() => {
// If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please restore this comment

const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined);

const shouldShowGreenDotIndicator = isOpenTaskReport(taskContextReport, action) && isReportManager(taskContextReport);
const taskAccessibilityLabel = taskTitleWithoutImage ? `${translate('task.task')}: ${taskTitleWithoutImage}` : translate('task.task');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Raw html is announced here.

Image

@mkhutornyi

mkhutornyi commented May 28, 2026

Copy link
Copy Markdown
Contributor

When double tap "Take a test drive", it opens test drive modal instead of navigating to detail page.

@Krishna2323

Copy link
Copy Markdown
Contributor Author

@mkhutornyi could you please test again? Now it should only announce the checkbox state when toggled.

@mkhutornyi

Copy link
Copy Markdown
Contributor

@Krishna2323
When toggle on, "task, checkbox, unchecked"
When toggle off, "t, marked as incomplete" (maybe "t" is first letter of "task")

@mkhutornyi

Copy link
Copy Markdown
Contributor

The above bug is flaky. Mostly works well, so I won't block on this.

const shouldShowGreenDotIndicator = isOpenTaskReport(taskContextReport, action) && isReportManager(taskContextReport);
const taskTitlePlainText = Parser.htmlToText(taskTitle);
const taskAccessibilityLabel = taskTitlePlainText ? `${translate('task.task')}: ${taskTitlePlainText}` : translate('task.task');
const shouldUseSplitTaskCheckboxAccessibility = shouldBreakGrouping && isScreenReaderActive;

@mkhutornyi mkhutornyi Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NAB: Should we use this flag everywhere? shouldBreakGrouping && isScreenReaderActive is duplicated in many places.

@MelvinBot

Copy link
Copy Markdown
Contributor

Review

The core fix is sound: gating a shouldBreakAccessibilityGrouping() platform split behind screen-reader detection, setting accessible={false} on the iOS parent so the checkbox and title become individually focusable, and giving the checkbox a proper role/label/state. The optimistic local-state pattern (if (prev !== current) setState(...)) is the React-recommended derived-state approach and is React-Compiler safe. Translation keys (task.messages.completed / reopened) and the useAccessibilityAnnouncement announcementKey option all check out.

A few things worth confirming before merge — mostly because this PR touches three shared components that are used well beyond the task flow.

1. Removed blur-on-click in TaskView changes web behavior for all users, not just screen readers

TaskView.tsx:167 dropped the e parameter and this block that previously ran on every web click:

if (e?.type === 'click') {
    (e.currentTarget as HTMLElement).blur();
}

This removal is not gated on isScreenReaderActive, so it affects every sighted web user too. That blur was presumably intentional (clearing the lingering focus outline / pressed-state highlight after a mouse click). Was dropping it intended, or a side effect of the refactor? If unintended, consider keeping the blur on the non-screen-reader path.

2. BaseAnchorForCommentsOnly changes affect every comment link app-wide

BaseAnchorForCommentsOnly.tsx:84 changes the outer wrapper role from always LINK to linkProps.onPress ? CONST.ROLE.LINK : undefined, and adds a screen-reader onPress to the inner Text. This component renders every link in chat — external URLs, email links, @mentions, and room mentions — not just task titles. The test plan only covers the task row. Please verify across those other link types that (a) external/email links still announce as links and open correctly under VoiceOver/TalkBack, and (b) web click + context-menu behavior is unchanged. This is the change most likely to cause an unrelated regression.

3. SelectionButton event.preventDefault() applies to every checkbox/radio

SelectionButton.tsx:145 now calls preventDefault() on Space/Enter for all selection buttons. Preventing Space-scroll is a reasonable default, but this is a global keyboard-behavior change. Low risk, but worth a sanity check that no form relies on Enter-on-a-checkbox doing something else.

4. Duplication (optional)

The local-state mirror + announcement + shouldBreakGrouping && isScreenReaderActive guard is repeated nearly verbatim in both TaskPreview.tsx and TaskView.tsx, and the compound guard appears ~6× per file. Extracting a small hook (e.g. useTaskCheckboxAccessibility) would DRY this up, but it's a judgment call — not blocking.


Nothing here looks like a correctness bug in the task flow itself; the open questions are about scope (items 1–3 touch shared components beyond what the issue asked for). I can't run VoiceOver/TalkBack from here, so the cross-platform accessibility behavior in items 2 and 3 needs manual verification on device.

🤖 Generated by MelvinBot — reviewed at 4a111df. This is an automated review; treat findings as suggestions for the human reviewer.

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 4a111df4f6

ℹ️ 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".

@mkhutornyi

Copy link
Copy Markdown
Contributor

Android TalkBack: toggle checkbox doesn't announce anything.
Not blocking on this as already broken on production.

android.mov

@mkhutornyi mkhutornyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The targeted fixes are

  • focusable checkbox on iOS
  • raw HTML announcement on all platforms

@melvin-bot

melvin-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

We did not find an internal engineer to review this PR, trying to assign a random engineer to #77574 as well as to this PR... Please reach out for help on Slack if no one gets assigned!

@melvin-bot melvin-bot Bot requested a review from cristipaval June 16, 2026 14:13
Comment thread src/components/ReportActionItem/TaskView.tsx Outdated
Comment thread src/components/ReportActionItem/TaskView.tsx Outdated

@cristipaval cristipaval left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Posted 2 comments

…blur

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
@Krishna2323

Copy link
Copy Markdown
Contributor Author

@cristipaval ready again.

@melvin-bot melvin-bot Bot requested a review from cristipaval June 18, 2026 15:40
Comment thread src/hooks/useTaskCheckboxAccessibility.ts Outdated
@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Code Review

The approach is sound — breaking VoiceOver's accessibility grouping on iOS via accessible={false} on the parent and splitting the checkbox/title into independent focus targets is the right pattern, and the cross-platform label upgrade to Task: <title> directly addresses the reported "announced as a variable" symptom. A few things to confirm before this is good to go, mostly around the changes that touch shared components.

🔴 Worth confirming

1. BaseAnchorForCommentsOnly role change affects every chat link, not just tasks.
BaseAnchorForCommentsOnly.tsx:84 changes role={CONST.ROLE.LINK}role={linkProps.onPress ? CONST.ROLE.LINK : undefined}. This component renders every anchor in chat. Public/whitelisted external links are passed onPress={undefined} from AnchorRenderer.tsx:149, so those links now lose role="link". On web the underlying <a href> still carries link semantics, but on native VoiceOver/TalkBack this could regress how a normal external link is announced. Can you confirm regular (non-task) links were tested with a screen reader, and ideally scope this to task-title children only?

2. The screen-reader onPress handler is also a shared-component change. The stopPropagation()/preventDefault() branch at BaseAnchorForCommentsOnly.tsx:104 fires for any anchor when a screen reader is active. Please confirm tapping a plain link in a comment with VoiceOver/TalkBack still activates it correctly.

3. No unit tests for the new logic. useTaskCheckboxAccessibility.ts adds branching + optimistic local state + announcements, and Codecov reports a coverage decrease, but no test file is in the diff (the checklist says unit tests were added). Worth adding coverage for the split decision and the completed/reopened announcement.

🟡 Minor / cleanup

  • Android scope: the linked issue reproduces on both iOS and Android native, but shouldBreakAccessibilityGrouping only returns true on iOS (index.ios.ts vs index.ts). The Task: <title> label upgrade does reach Android, but Android keeps the grouped target. Is Android intentionally out of scope here, or relying on TalkBack already exposing children individually?
  • SelectionButton global change: SelectionButton.tsx:145 adds event.preventDefault() to handleSpaceOrEnterKey for every checkbox/radio in the app. Likely benign (suppresses space-scroll), but it's a shared change — confirm Enter-to-submit forms that contain a checkbox aren't affected.
  • Redundant alias: useTaskCheckboxAccessibility.ts:30 shouldUseSplitTaskCheckboxAccessibility is just an alias of shouldSplitTaskAccessibilityTargets — two exported names for the same boolean. Collapse to one.
  • Empty-string hint: accessibilityHint={shouldUseSplitTaskCheckboxAccessibility ? '' : undefined} on the split title pressable — the intent of '' vs undefined isn't obvious. A short comment (or just undefined) would help.
Things that look correct
  • Hook is called unconditionally (no conditional-hook violation); split is correctly gated on shouldBreakGrouping && isScreenReaderActive, so sighted/web users see no change (accessible={undefined}, not false).
  • Optimistic localIsCompleted can momentarily drift from Onyx if a toggle fails server-side, but the prevIsCompletedFromOnyx resync pattern re-aligns it on the next Onyx change (and the toggle optimistically updates Onyx anyway), so this is fine.
  • Translation keys task.messages.completed / task.messages.reopened and Parser.htmlToText all exist.
  • updateTaskCheckboxStateForAccessibility is invoked after the isActiveTaskEditRoute early-return and inside callFunctionIfActionIsAllowed, so it won't announce a state change that didn't happen.

I focused on correctness and the blast radius of the shared-component edits; I did not run the app with a screen reader. Reply with @MelvinBot <feedback> to refine.

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

Copy link
Copy Markdown

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: 00c16401bb

ℹ️ 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".

Comment thread src/hooks/useTaskCheckboxAccessibility.ts Outdated
@Krishna2323

Copy link
Copy Markdown
Contributor Author

@MelvinBot review again

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Code Review (re-review)

Re-reviewed the delta since my last pass (commit 90ce9671). The two 🔴 shared-component concerns are now properly scoped, and the cleanup items are resolved. Nothing new blocking — the remaining items are the same confirm-only points from before.

✅ Addressed

  • Anchor role regression (was 🔴 Some initial fixes and code style updates #1): role is now linkProps.onPress || !isChildOfTaskTitle ? CONST.ROLE.LINK : undefined at BaseAnchorForCommentsOnly.tsx:84. role only drops to undefined when !linkProps.onPress && isChildOfTaskTitle — i.e. a non-pressable anchor inside a task title. Every normal chat link has !isChildOfTaskTitle === true, so it keeps role="link" exactly as before. Regression resolved.
  • Screen-reader onPress override (was 🔴 Fix spaces #2): now gated on isChildOfTaskTitle && isScreenReaderActive && linkProps.onPress (:103). Plain comment links no longer get the stopPropagation/preventDefault branch. The isChildOfTaskTitle flag is derived from the existing HTMLEngineUtils.isChildOfTaskTitle(tnode), so the scoping is consistent with how task titles are already detected.
  • Redundant alias (was 🟡): shouldUseSplitTaskCheckboxAccessibility is gone; call sites use shouldSplitTaskAccessibilityTargets directly.
  • Empty-string hint (was 🟡): extracted to a named titlePressableAccessibilityHint with an explanatory comment about the NativeGenericPressable label fallback. Clear now.

🟡 Still open (non-blocking — same as last review)

  • No unit tests for useTaskCheckboxAccessibility (split decision + completed/reopened announcement). Codecov still flags the coverage drop. Worth adding given the optimistic-state + announcement branching.
  • Android grouping still relies only on the Task: <title> label upgrade — shouldBreakAccessibilityGrouping returns true on iOS only. mkhutornyi already noted TalkBack doesn't announce the toggle (pre-existing, not a blocker). Just confirm Android is intentionally label-only here.
  • SelectionButton event.preventDefault() at SelectionButton.tsx:145 is still a global change for every checkbox/radio. Likely benign (suppresses space-scroll), but confirm Enter-to-submit forms containing a checkbox aren't affected.

🟢 Minor (optional)

An external URL pasted inside a task title (anchor with no onPress) will now lose role="link" under a screen reader, since it's treated as part of the single task button. That's consistent with the "title is one target" intent, so probably fine — just flagging the edge case.

Verification notes
  • Diffed 00c16401 (last reviewed) → 7f76633b (current head). Substantive change is commit 90ce9671 ("address review feedback"); the rest is a main merge.
  • Confirmed no remaining references to the removed shouldUseSplitTaskCheckboxAccessibility.
  • Hook is still called unconditionally; split still gated on shouldBreakGrouping && isScreenReaderActive, so sighted/web users are unaffected.
  • I reviewed for correctness and blast radius; I did not run the app with a screen reader.

Reply with @MelvinBot <feedback> to refine.

@Krishna2323

Copy link
Copy Markdown
Contributor Author

@cristipaval @mkhutornyi please check again 🙇🏻

@cristipaval cristipaval merged commit 5b46d9a into Expensify:main Jun 19, 2026
33 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

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

@OSBotify

Copy link
Copy Markdown
Contributor

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

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants