Skip to content

fix(ios): isolate rest timer liveRegion from 1Hz text semantics - #568

Closed
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/critical-bug-investigation-7495
Closed

fix(ios): isolate rest timer liveRegion from 1Hz text semantics#568
cursor[bot] wants to merge 2 commits into
mainfrom
cursor/critical-bug-investigation-7495

Conversation

@cursor

@cursor cursor Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Bug and impact

PR #567 (#565) moved liveRegion onto the visible rest countdown Text to avoid an iOS EXC_BAD_ACCESS from a zero-size accessibility node. That introduced a regression: Text still publishes a text semantic that updates every second, and live regions notify on text or contentDescription changes. During autoplay rest, VoiceOver/TalkBack users could receive announcements every tick (e.g. ~90 interruptions on a 90s rest), and iOS accessibility could be stressed again.

Root cause

Modifier.semantics { contentDescription = lastAnnouncedText } merges with Text's built-in text semantic. Throttling only contentDescription does not suppress live-region events driven by the changing text property.

Fix

Validation

  • ./gradlew :shared:compileCommonMainKotlinMetadata -Pskip.supabase.check=true
Open in Web View Automation 

Devil and others added 2 commits June 17, 2026 15:36
Remove the standalone 0.dp Box with liveRegion=Polite semantics in
RestTimerCard and attach liveRegion=Polite + contentDescription=
lastAnnouncedText onto the visible timer Text instead.

The zero-size liveRegion node increased accessibility-element disposal
churn during the 1Hz rest-timer recomposition and raced iOS
UIAccessibility's AX-tree poll, triggering EXC_BAD_ACCESS (SIGSEGV) in
Compose Multiplatform's AccessibilityElement.contentOffset/bounds path
(Accessibility.uikit.kt:725-726) when AXRuntime queried
accessibilityFrame on a disposed element. Relocating the semantics onto
a stable, visible, non-zero-size element that persists across ticks
eliminates the zero-size AX node while preserving the throttled
VoiceOver/TalkBack countdown announcements at 5s/10s/0s/paused
intervals (the LaunchedEffect/lastAnnouncedText logic is unchanged).

Bounded to RestTimerCard.kt only. Complements the CMP 1.10.3 -> 1.11.1
upgrade already on main (upstream JetBrains/compose-multiplatform#5543).

RCA: GPT-5.5 xhigh (issuecomment-4734439771).

Fixes #565
PR #567 moved liveRegion onto the countdown Text, but Text still exposes a
text semantic that updates every second. Live regions notify on text OR
contentDescription changes, so throttled announcements were broken and
VoiceOver/TalkBack could fire every tick during rest.

Restore a dedicated announcement node with clearAndSetSemantics (only
lastAnnouncedText) and use 1.dp instead of 0.dp to avoid the iOS
UIAccessibility bounds UAF from issue #565. Keep the visible timer Text
without liveRegion so users can still focus it to hear the current value.

Co-authored-by: Devil <9thLevelSoftware@users.noreply.github.com>
@9thLevelSoftware
9thLevelSoftware deleted the cursor/critical-bug-investigation-7495 branch June 17, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants