Skip to content

jvm: fix macOS accessibility crash and update Compose to 1.10.3#451

Merged
rainxchzed merged 1 commit intomainfrom
macos-npe
Apr 23, 2026
Merged

jvm: fix macOS accessibility crash and update Compose to 1.10.3#451
rainxchzed merged 1 commit intomainfrom
macos-npe

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented Apr 23, 2026

  • Introduce A11yCrashGuard to prevent app freezes on macOS caused by a known NullPointerException in the Compose Multiplatform 1.10.x accessibility bridge.
  • Initialize A11yCrashGuard in DesktopApp.kt to catch and suppress the specific NPE within the AWT EventDispatchThread.
  • Upgrade Compose Multiplatform and related JetBrains dependencies from 1.10.1 to 1.10.3.
  • Expand allowed capabilities in .claude/settings.local.json to include web search and specific JetBrains/GitHub domains.

Summary by CodeRabbit

  • Bug Fixes

    • Resolved macOS-specific accessibility-related crashes.
  • Chores

    • Updated Compose Multiplatform dependency to version 1.10.3.

- Introduce `A11yCrashGuard` to prevent app freezes on macOS caused by a known `NullPointerException` in the Compose Multiplatform 1.10.x accessibility bridge.
- Initialize `A11yCrashGuard` in `DesktopApp.kt` to catch and suppress the specific NPE within the AWT EventDispatchThread.
- Upgrade Compose Multiplatform and related JetBrains dependencies from 1.10.1 to 1.10.3.
- Expand allowed capabilities in `.claude/settings.local.json` to include web search and specific JetBrains/GitHub domains.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Walkthrough

The PR introduces a macOS-specific crash guard to handle Compose accessibility-related NullPointerExceptions in the AWT event dispatch thread, updates the Compose Multiplatform version from 1.10.1 to 1.10.3, integrates the crash guard into application startup, and extends AI configuration permissions for web search and GitHub/JetBrains domain fetching.

Changes

Cohort / File(s) Summary
Configuration & Permissions
.claude/settings.local.json
Extended allowed permissions to include general web search and constrained web fetching from JetBrains/GitHub-related domains.
Crash Guard Implementation
composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt
New crash guard for macOS that intercepts and suppresses NullPointerExceptions originating from Compose accessibility layer by inspecting stack frames and exception causes, emitting a one-time diagnostic message.
App Initialization
composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
Installs A11yCrashGuard during application startup before continuing normal initialization.
Dependency Management
gradle/libs.versions.toml
Upgraded Compose Multiplatform versions: compose-multiplatform and compose-jetbrains from 1.10.1 to 1.10.3.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Hops through the code with a vigilant eye,
Catching those crashes that make programs cry—
A guard in the macOS night stands so tall,
While versions do leap from one point to another call.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the two main changes: fixing a macOS accessibility crash and updating Compose to 1.10.3, matching the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch macos-npe

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt (1)

35-50: Suppressing the NPE leaves the current event partially dispatched.

When super.dispatchEvent(event) throws, any listeners registered after the failing one for the same event are skipped. That's the intended trade-off per the docstring (VoiceOver may miss updates), but it also means non-a11y listeners on the same event are silently dropped. Worth calling out explicitly in the KDoc so future readers don't widen the catch.

Also consider narrowing the match further — e.g., require SemanticsOwnerAccessibility in the class name — to reduce the chance of swallowing unrelated a11y NPEs introduced by future Compose releases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt`
around lines 35 - 50, The catch in dispatchEvent currently suppresses Compose
accessibility NPEs (detected by isComposeA11yNpe) which leaves the current
AWTEvent partially dispatched and silently drops subsequent non-a11y listeners;
update the KDoc for dispatchEvent/A11yCrashGuard to explicitly state this
trade‑off (that suppressing the exception skips later listeners for the same
event) and narrow the isComposeA11yNpe matcher to be more specific (e.g.,
require "SemanticsOwnerAccessibility" in the exception stack/class name check)
to avoid swallowing unrelated NPEs; keep the warned AtomicBoolean behavior
unchanged but document the rationale and narrowed matching in the KDoc.
gradle/libs.versions.toml (1)

36-48: Consider consolidating resources with compose-jetbrains.

The resources key (line 48) now holds the same value as compose-jetbrains (line 36) and is used by a single Compose UI tooling artifact (line 131). Having two separately named keys pinned to the same version makes it easy for them to drift on the next bump. Consider reusing compose-jetbrains for ui-tooling-preview unless there is a specific reason the Compose tooling artifact needs an independent version axis.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gradle/libs.versions.toml` around lines 36 - 48, The TOML defines both
resources and compose-jetbrains with the same version; remove the duplicate
resources key and update any dependency that uses resources (notably the Compose
UI tooling artifact/ui-tooling-preview) to reference compose-jetbrains instead
so version bumps stay unified; touch the libs.versions.toml entries (remove
resources) and the build files where ui-tooling-preview is declared to point to
the compose-jetbrains version key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt`:
- Around line 26-30: install() currently pushes a new FilteringEventQueue every
call which stacks handlers; make it idempotent by adding a private AtomicBoolean
(e.g., "installed") checked at the start of install(): if already true return,
otherwise set it to true and proceed to push the FilteringEventQueue; reference
the existing install() function and the FilteringEventQueue class so future
calls become no-ops and avoid stacking multiple event queues.

---

Nitpick comments:
In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt`:
- Around line 35-50: The catch in dispatchEvent currently suppresses Compose
accessibility NPEs (detected by isComposeA11yNpe) which leaves the current
AWTEvent partially dispatched and silently drops subsequent non-a11y listeners;
update the KDoc for dispatchEvent/A11yCrashGuard to explicitly state this
trade‑off (that suppressing the exception skips later listeners for the same
event) and narrow the isComposeA11yNpe matcher to be more specific (e.g.,
require "SemanticsOwnerAccessibility" in the exception stack/class name check)
to avoid swallowing unrelated NPEs; keep the warned AtomicBoolean behavior
unchanged but document the rationale and narrowed matching in the KDoc.

In `@gradle/libs.versions.toml`:
- Around line 36-48: The TOML defines both resources and compose-jetbrains with
the same version; remove the duplicate resources key and update any dependency
that uses resources (notably the Compose UI tooling artifact/ui-tooling-preview)
to reference compose-jetbrains instead so version bumps stay unified; touch the
libs.versions.toml entries (remove resources) and the build files where
ui-tooling-preview is declared to point to the compose-jetbrains version key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bada8158-27f1-4185-b165-f955abb80728

📥 Commits

Reviewing files that changed from the base of the PR and between 11ad1fd and 246fb5e.

📒 Files selected for processing (4)
  • .claude/settings.local.json
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt
  • composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/DesktopApp.kt
  • gradle/libs.versions.toml

Comment on lines +26 to +30
fun install() {
val osName = System.getProperty("os.name")?.lowercase().orEmpty()
if (!osName.contains("mac")) return
Toolkit.getDefaultToolkit().systemEventQueue.push(FilteringEventQueue())
}
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.

⚠️ Potential issue | 🟡 Minor

install() is not idempotent.

Every call pushes another FilteringEventQueue on top of the current system event queue. If startup ever invokes this twice (tests, hot-reload, restart paths), you'll stack filtering queues and each event will traverse multiple dispatchEvent frames. Consider guarding with an AtomicBoolean so subsequent calls are no-ops.

Proposed fix
 object A11yCrashGuard {
+    private val installed = java.util.concurrent.atomic.AtomicBoolean(false)
+
     fun install() {
         val osName = System.getProperty("os.name")?.lowercase().orEmpty()
         if (!osName.contains("mac")) return
+        if (!installed.compareAndSet(false, true)) return
         Toolkit.getDefaultToolkit().systemEventQueue.push(FilteringEventQueue())
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@composeApp/src/jvmMain/kotlin/zed/rainxch/githubstore/A11yCrashGuard.kt`
around lines 26 - 30, install() currently pushes a new FilteringEventQueue every
call which stacks handlers; make it idempotent by adding a private AtomicBoolean
(e.g., "installed") checked at the start of install(): if already true return,
otherwise set it to true and proceed to push the FilteringEventQueue; reference
the existing install() function and the FilteringEventQueue class so future
calls become no-ops and avoid stacking multiple event queues.

@rainxchzed rainxchzed merged commit 6929946 into main Apr 23, 2026
1 check passed
@rainxchzed rainxchzed deleted the macos-npe branch April 23, 2026 16:12
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.

1 participant