Repository navigation
[Mac] Fix VoiceOver does not announce initial ComboBox state in Task and Project Details - #38595
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 38595Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 38595" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
| SelectedIndex="{Binding CategoryIndex}" | ||
| IsOpen="{Binding IsCategoryPickerExpanded, Mode=TwoWay}" | ||
| SemanticProperties.Hint="ComboBox to select a category. Activate to browse and select a category"/> | ||
| SemanticProperties.Hint="ComboBox to select a category. Activate to browse and select a category. State Collapsed"/> |
There was a problem hiding this comment.
🔍 AI-Generated Review (multi-model)
[major] Accessibility — This hard-codes State Collapsed into SemanticProperties.Hint even when IsOpen is true. Concrete scenario: open the picker, then return focus to it while it remains expanded; assistive tech can read a stale collapsed state from the semantic node. The same stale-state pattern is also introduced in TaskDetailPage.xaml:47.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@praveenkumarkarunanithi — new AI review results are available based on commit
6156b76.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ⚠️ SKIPPED
No tests were detected in this PR.
Recommendation: Add tests to verify the fix using the write-tests-agent.
📋 Pre-Flight — Context & Validation
Issue: #36471 - Voice over is not announcing role as combo box and state as collapsed for "Project" combobox:A11y_.NET maui_user can creat a tak_Screen reader
PR: #38595 - [Mac] Fix VoiceOver does not announce initial ComboBox state in Task and Project Details
Platforms Affected: Mac Catalyst (primary); the template is cross-platform and the author also reports manual Android and Windows validation
Files Changed: 2 implementation, 0 test
Key Findings
- The reported failure is the initial VoiceOver focus announcement for the Project and Category pickers: the role and collapsed state are not spoken before
Picker.IsOpenchanges. - The PR appends
State Collapsedto each picker's staticSemanticProperties.Hint; existingIsOpenbindings and page-model announcements continue to announce later expanded/collapsed transitions. - The gate was already skipped because the PR contains no detected tests. The PR says this behavior was validated manually because spoken screen-reader output is not reliably covered by existing automation.
- Issue discussion contains conflicting reproduction evidence: one tester could not reproduce on two Macs, and current-source trusted-tester verification remains pending.
- There are no inline review comments, submitted reviews, or technical PR discussion to incorporate.
Code Review Summary
Verdict: SKIPPED
Confidence: N/A
Errors: 0 | Warnings: 0 | Suggestions: 0
Key code review findings:
- Dedicated expert review is intentionally deferred to STEP 5b by the execution contract; this pre-flight is context-only.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #38595 | Append a static collapsed-state phrase to both native Picker hints while retaining dynamic IsOpen announcements |
ProjectDetailPage.xaml, TaskDetailPage.xaml |
Original PR; manual accessibility validation claimed |
🔬 Code Review — Deep Analysis
Code Review — PR #38595
Independent Assessment
What this changes:
Adds . State Collapsed to static SemanticProperties.Hint strings on two Picker controls:
src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xamlsrc/Templates/src/templates/maui-mobile/Pages/TaskDetailPage.xaml
Both pickers already bind IsOpen two-way and both corresponding page models (ProjectDetailPageModel, TaskDetailPageModel) already announce expanded/collapsed transitions via SemanticScreenReader.Announce(...).
Inferred motivation:
Provide initial focus-time spoken state for VoiceOver (collapsed) before any IsOpen transition occurs.
Reconciliation with PR Narrative
Author claims:
PR restores initial ComboBox state announcement on Mac VoiceOver by appending static collapsed text while preserving dynamic expanded/collapsed announcements.
Agreement/disagreement:
Agree on the initial-announcement gap and intent.
Disagree that the current implementation is fully correct: hard-coding State Collapsed in static semantics can become stale while the picker is expanded, conflicting with actual current state.
Prior Review Reconciliation
No prior ❌ Error findings found across:
- top-level PR reviews
- inline PR review comments
- PR issue comments
Blast Radius Assessment
- Runs for all instances: Yes, for these two template pickers.
- Startup impact: No direct startup-path change.
- Static/shared state: No.
CI Status
- Required-check result: undetermined (
gh pr checks --requiredunavailable in this environment due unauthenticated GH CLI). - Classification: undetermined CI coverage.
- Action taken: confidence capped to low.
- Additional evidence: supplied gate status is
Gate SKIPPED — no tests detected.
Findings
❌ Error — Static semantic state can be incorrect while picker is open
src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xaml:45 (same issue pattern in TaskDetailPage.xaml:47):
The new static hint always says State Collapsed, but picker state is mutable via IsOpen.
Concrete scenario: user opens picker (expanded), then focus returns to picker while still open; assistive tech can read stale collapsed state from the semantic node. Dynamic SemanticScreenReader.Announce calls are event-based and do not keep the node's static hint synchronized.
Failure-Mode Probing
- Pages that do not use these pickers: no impact.
- Expanded-state revisit: stale
State Collapsedhint can contradict runtime state. - Handler reconnect/null lifecycle: not introduced by this XAML-only diff.
External Output Contract
Not applicable.
Verdict: NEEDS_CHANGES
Confidence: low
Summary:
The change improves initial announcement behavior but introduces a correctness issue in ongoing accessibility state representation. Because the semantic hint now embeds a mutable state as static text, it can become incorrect during normal interaction. With CI/test evidence undetermined (and gate skipped), the fix should be revised before merge.
🛠️ Try-Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | gpt-5.3-codex try-fix |
Runtime semantic-state metadata bound to Picker.IsOpen, rather than a fixed collapsed-state hint |
None | Baseline establishment rejected the pre-existing dirty workspace, so the candidate could not be implemented. The permitted template build also failed with NETSDK1004 because project.assets.json was absent. |
|
| 2 | gpt-5.6-sol try-fix |
Remove authored Picker hints and rely on native Mac Catalyst accessibility to expose role and state | None | Baseline establishment again produced no safe edit allow-list. XML parsing passed on unchanged files but did not validate VoiceOver behavior. | |
| PR | PR #38595 | Append State Collapsed to both static Picker hints while retaining dynamic IsOpen announcements |
2 XAML files | No tests detected; author reports manual accessibility validation. |
Attempt Details
Candidate 1 — Runtime semantic-state binding
The candidate planned to derive accessibility metadata from each Picker's live IsOpen state. Unlike the PR's fixed prose, the semantic state would remain synchronized after initial focus and later expansion/collapse transitions.
Result: Blocked before implementation. EstablishBrokenBaseline.ps1 refused to create .github/.baseline-state.json because the workspace already contained broad unrelated changes; without a RevertedFiles allow-list, the try-fix contract prohibited edits. The exact permitted command, dotnet build src/Templates/src/Microsoft.Maui.Templates.csproj --no-restore, ran once and failed with NETSDK1004 because artifacts/obj/Microsoft.Maui.Templates/project.assets.json did not exist.
Diff: None.
Failure analysis: This result does not validate or invalidate the runtime-state approach. It establishes only that candidate implementation was unsafe in the current baseline and that compilation could not proceed without an unrequested restore.
Candidate 2 — Native Picker semantics
The candidate planned to remove authored Picker accessibility hints and rely on the native Mac Catalyst Picker accessibility element to expose its role and collapsed/expanded state. This delegates semantic truth to the native control, unlike both the PR's fixed state phrase and candidate 1's XAML state binding.
Result: Blocked before implementation. EstablishBrokenBaseline.ps1 again rejected the pre-existing dirty workspace and created no RevertedFiles allow-list, so no production edit was permitted. The exact allowed command parsed both unchanged XAML files successfully.
Diff: None.
Failure analysis: The successful XML parse establishes only that the unchanged PR files are well-formed. It provides no evidence that native Mac Catalyst semantics expose the missing role/state, so this approach remains unvalidated.
Cross-Pollination
Skipped by execution contract.
Exhausted: Yes — both mandated models were invoked once; no cross-pollination or follow-up review was run.
Selected Fix: PR's fix provisionally — neither alternative could be implemented or behaviorally tested, and the Gate supplied no automated validation. STEP 5b should treat the selection as low-confidence and rely on its dedicated expert review plus manual VoiceOver evidence.
🏁 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Winner: pr-plus-reviewer
pr-plus-reviewer is the only implemented candidate that addresses both halves of the accessibility contract: an initial collapsed state and semantic metadata that remains accurate after Picker.IsOpen changes. The submitted PR improves initial VoiceOver output but hard-codes State Collapsed; the expert review confirmed that this can contradict the live expanded state when focus returns while the picker remains open.
Comparative analysis
| Rank | Candidate | Implementation | Validation evidence | Assessment |
|---|---|---|---|---|
| 1 | pr-plus-reviewer |
Binds each picker hint to a computed state-aware page-model property and raises dependent-property notifications from IsOpen; preserves existing transition announcements |
diff --check passed; both XAML files parsed; template build was blocked by missing restore assets; no Mac Catalyst VoiceOver run |
Winner. Resolves the expert finding with a focused four-file change. Compilation and spoken behavior remain uncertain. |
| 2 | pr |
Appends fixed State Collapsed text to both picker hints |
Gate skipped because no tests were detected; author reports manual platform validation | Improves first-focus output, but leaves the semantic node stale while expanded. Expert verdict: NEEDS_CHANGES. |
| 3 | try-fix-1 |
Proposed live semantic-state metadata bound to Picker.IsOpen |
Blocked before edits; template build failed with missing project.assets.json |
Sound direction and conceptually aligned with the winner, but there is no candidate diff to evaluate or adopt. |
| 4 | try-fix-2 |
Proposed removing authored picker hints and relying on native semantics | Blocked before edits; only unchanged PR XAML parsed | No implementation or VoiceOver evidence. It also risks losing the explicit descriptive hint that currently compensates for the reported native announcement gap. |
No candidate passed or failed a regression test: the trusted Gate was skipped, both STEP 5a attempts were blocked before implementation, and pr-plus-reviewer could only complete structural XAML validation. The ordering therefore rests on demonstrated code correctness rather than regression-test success; no failed-regression candidate was ranked above a passing candidate.
Required PR change
Adopt pr-plus-reviewer/reviewer.patch. It replaces the fixed hints with CategoryPickerSemanticHint and ProjectPickerSemanticHint, each derived from the corresponding observable IsOpen property and kept synchronized through NotifyPropertyChangedFor.
Remaining validation gap
The targeted template build ran exactly once and was blocked by NETSDK1004 because restore assets were absent. Both modified XAML files are well formed, but the winning patch still needs a restored template/sample build and manual Mac Catalyst VoiceOver verification before merge.
🧭 Next Steps — reviewer changes required
The reviewer-enhanced candidate identified changes that are not yet in the submitted PR.
Why: The reviewer-enhanced PR is the only implemented candidate that supplies the initial collapsed announcement without leaving static semantic metadata stale while the picker is expanded. Structural XAML validation passed, but compilation and Mac Catalyst VoiceOver behavior remain unverified because the targeted build was blocked by missing restore assets.
Address the actionable findings in this review before merging.
|
/backport to release/10.0.1xx-sr11 |
|
Started backporting to |
… ComboBox state in Task and Project Details (#38600) Backport of #38595 to release/10.0.1xx-sr11 /cc @kubaflo @praveenkumarkarunanithi Co-authored-by: praveenkumarkarunanithi <100338903+praveenkumarkarunanithi@users.noreply.github.com>
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
VoiceOver on macOS does not announce the ComboBox control type or its initial "Collapsed" state when initially navigating to the Project and Category pickers in the .NET MAUI mobile project template (DeveloperBalance).
Unknown
On initial linear navigation (
Control + Option + Right Arrow), VoiceOver reads only the control's descriptive label without indicating that it is an expandable ComboBox or that its current state is collapsed.Root Cause
In earlier versions, static semantic properties (
SemanticProperties.DescriptionandSemanticProperties.Hint="State Collapsed") were assigned to the parentSfTextInputLayoutcontainer. The regression was introduced in PR #32286 , which attempted to make state announcements dynamic by removing the static"State Collapsed"hint and relying exclusively onSemanticScreenReader.Announcetriggered viaPicker.IsOpenproperty changes.Because synthetic announcements only fire on value changes, no state announcement was produced when the control initially received focus.
Description of Change
Appended State Collapsed directly to the
SemanticProperties.Hintof each native Picker on both the Project Details and Task Details pages.Kept
SfTextInputLayoutresponsible solely for visual presentation and floating labels.Preserved existing bindings, commands, and dynamic expansion announcements from PR #32286 while ensuring the initial resting state is exposed to platform accessibility APIs.
Why Tests Were Not Added
This issue requires manual accessibility validation using platform screen readers (macOS VoiceOver and Android TalkBack) to verify focus traversal and spoken announcements. Because these accessibility announcements cannot be reliably validated via automated UI/unit tests, manual verification was performed across macOS, Android, and Windows.
Issues Fixed
Fixes #36471
Screenshots
BeforeFix.mov
AfterFix.mov