Skip to content

Make crust's Mapbox Navigation dependency opt-in (default off)#3372

Draft
PhilippeFerreiraDeSousa wants to merge 1 commit into
devfrom
codex/crust-nav-optin
Draft

Make crust's Mapbox Navigation dependency opt-in (default off)#3372
PhilippeFerreiraDeSousa wants to merge 1 commit into
devfrom
codex/crust-nav-optin

Conversation

@PhilippeFerreiraDeSousa

@PhilippeFerreiraDeSousa PhilippeFerreiraDeSousa commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Why

Embedding crust (via island) forced MAPBOX_DOWNLOADS_TOKEN on every host, because crust unconditionally implementations the Mapbox Nav SDK, whose artifacts live only in Mapbox's private registry. A brand-new OEM that never navigates still couldn't build without obtaining a Mapbox credential — the follow-up teed up in #3367.

What

crust's config plugin gains a navigation prop, default false:

  • off (default): the Mapbox Downloads repo is not injected, and the Nav SDK is compileOnly — crust compiles, the classes are absent from the APK, and no credential is needed to build. The protobuf exclusion + desugaring still apply. This generalizes the condition crust's gradle already used for the China build.
  • on: repo injected, Nav SDK is implementation (in the APK). The Mentra app passes ["@mentra/crust", {navigation: true}].

The plugin writes the choice to a mentraCrustNavigation gradle property that android/build.gradle reads (isChinaBuild || !navigationEnabledcompileOnly).

Runtime safetyCrustModule gains a lazy navAvailable reflection check (Class.forName on MapboxNavigation, catching Throwable since a missing class is an Error, not an Exception). The seven nav AsyncFunctions (startNavigation, stopNavigation, requestNavigationPermission, resetNavigationPermission, simulateDeviation, setWrongSidewalkOffset, setSkipCrossings) return a clean {ok: false, error: "navigation not available in this build"} when nav is absent instead of crashing with NoClassDefFoundError. This also hardens the pre-existing China compileOnly path, which until now relied on the JS layer simply never calling nav.

Verification

  • Prebuild — Mentra app (nav on): Mapbox repo present, mentraCrustNavigation=true. Example OEM app (nav off, no arg): no Mapbox repo, mentraCrustNavigation=false, protobuf + desugaring still present.
  • check-android-compile.sh bluetooth-sdk (compiles the Mentra Android project incl. crust + the new guard, nav on): BUILD SUCCESSFUL.
  • Clean bun run prepare builds both the module and the plugin (the fresh-checkout CI path); mobile tsc clean; full jest 55 suites / 429; island tsc + 220; example OEM app typechecks.

Unverified — needs on-device

The nav-off runtime path (guard returning the clean error) is exercised only by an OEM-style build; the compile check covers nav-on. Worth a spot-check on the example OEM APK once built: calling a nav command should log "navigation not available" and return the error, not crash. The Mentra app (nav-on) behavior is unchanged and fully covered.

Follow-up

iOS: the Nav SDK wiring (mapbox-nav-ios.ts, mapbox-nav-crust-link.ts) still lives in mobile's local plugins; folding it into crust's plugin (and honoring the same navigation flag) is the iOS half, deferred until an iOS OEM consumer exists.


Note

Medium Risk
Changes Android embedding contract and native nav entry points; Mentra opts in explicitly, but wrong plugin config or missing on-device nav-off testing could surprise OEM integrators.

Overview
@mentra/crust now treats Mapbox Navigation as host opt-in (navigation, default false) instead of always pulling the Nav SDK and Mapbox Downloads Maven repo.

When navigation is off, the config plugin skips the authenticated Mapbox repo, sets mentraCrustNavigation=false, and crust’s Gradle uses compileOnly for the Nav artifacts (same idea as the existing China build). Protobuf-javalite exclusion and core-library desugaring still apply. The Mentra app explicitly passes ["@mentra/crust", { navigation: true }] so full nav builds are unchanged.

CrustModule adds a lazy navAvailable check (Class.forName on Mapbox classes) and all seven nav AsyncFunctions return navigation not available in this build instead of NoClassDefFoundError when the SDK isn’t in the APK.

package.json drops building the plugin from the default build / prepare scripts (plugin still has build:plugin).

Reviewed by Cursor Bugbot for commit 9f6602b. Bugbot is set up for automated code reviews on this repo. Configure here.

Embedding crust no longer forces a MAPBOX_DOWNLOADS_TOKEN on every host.
The config plugin gains a navigation prop (default false):

- off: the Mapbox Downloads repo is NOT injected and the Nav SDK is
  compileOnly (generalizing the existing China-build condition) — crust
  compiles, the classes are absent from the APK, and no credential is
  needed to build. protobuf exclusion + desugaring still apply.
- on: repo injected, Nav SDK is implementation (in the APK). The Mentra
  app passes {navigation: true}.

The plugin writes the choice to the mentraCrustNavigation gradle
property that android/build.gradle reads. CrustModule gains a lazy
navAvailable reflection check (Class.forName on MapboxNavigation,
catching Throwable since a missing class is an Error); the seven nav
AsyncFunctions return a clean 'navigation not available in this build'
result when it's absent instead of crashing with NoClassDefFoundError.
This also hardens the pre-existing China compileOnly path, which relied
on JS never calling nav.

Verified by prebuild: Mentra app -> mapbox repo present,
mentraCrustNavigation=true; example OEM app -> no mapbox repo,
mentraCrustNavigation=false, protobuf+desugar still applied. Android
compile check (nav on) BUILD SUCCESSFUL.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📋 PR Review Helper

📱 Mobile App Build

Ready to test! (commit 9f6602b)

📥 Download APK

🕶️ ASG Client Build

Waiting for build...


🔀 Test Locally

gh pr checkout 3372

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

PR Agent Orchestrator State

{
  "cycle": 1,
  "fixRound": 0,
  "totalReviewerRuns": 1,
  "consecutiveNoNewReviews": 1,
  "openFindings": [],
  "resolvedFindings": [],
  "nitFindings": [],
  "phase": "discovery",
  "status": "in_progress",
  "stagnationFixRounds": 0,
  "fingerprintReopenCounts": {},
  "mutedFingerprints": [],
  "lastPair": [],
  "lastOpenCount": 0
}

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Agent Review — cycle 1

✅ No blocking findings · 0 blocking · 0 nits
Reviewers this cycle: none

No model reviews ran this cycle.

Updated automatically by the PR Agent Orchestrator each review cycle. Nits do not block merge.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9f6602b. Configure here.

// compiles against the SDK — the classes are just absent at runtime, and
// CrustModule's navAvailable guard returns a clean error if nav is invoked.
def navigationEnabled = (findProperty("mentraCrustNavigation") ?: "false").toString() == "true"
if (isChinaBuild || !navigationEnabled) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nav-off compile needs Mapbox repo

High Severity

When navigation is off, the crust plugin stops injecting the Mapbox Downloads Maven repo, but :mentra-crust still declares compileOnly Mapbox Navigation SDK dependencies. Gradle must still resolve those artifacts to compile NavigationManager.kt, which only exists in Mapbox’s private registry—so credential-free OEM builds fail on a clean cache.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9f6602b. Configure here.

AsyncFunction("requestNavigationPermission") { promise: expo.modules.kotlin.Promise ->
if (!navAvailable) {
promise.resolve(navUnavailableResult)
return@AsyncFunction

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Permission response missing accepted

Low Severity

When navigation is unavailable, requestNavigationPermission resolves navUnavailableResult, which only has ok and error. Other failure paths include accepted: false, and NavigationService.requestPermission types the result as {ok, accepted, error?}—callers may treat accepted as undefined instead of explicitly false.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9f6602b. Configure here.

@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: 9f6602b613

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 108 to 109
compileOnly 'com.mapbox.navigationcore:navigation:3.25.0'
compileOnly 'com.mapbox.navigationcore:tripdata:3.25.0'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid resolving Mapbox in nav-off builds

When navigation is omitted or false, the config plugin no longer injects the Mapbox Downloads Maven repo, but this branch still puts com.mapbox.navigationcore:* on crust's compileOnly classpath. compileOnly is still resolved when compiling NavigationManager.kt; it only keeps the artifacts out of the APK. In a clean OEM host with no Mapbox repo/token and no cached artifacts, Gradle fails dependency resolution instead of producing the intended credential-free build.

Useful? React with 👍 / 👎.

Comment on lines +9 to +13
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare && expo-module build plugin",
"prepare": "expo-module prepare",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep building the config plugin on prepare

app.plugin.js requires ./plugin/build, and that directory is not tracked in the repo; it is produced by expo-module build plugin. Dropping that command from the normal build/prepare path means a clean install or published package can leave the plugin output missing, so Expo fails to load @mentra/crust before prebuild reaches any of the new Gradle logic.

Useful? React with 👍 / 👎.

@PhilippeFerreiraDeSousa PhilippeFerreiraDeSousa marked this pull request as draft July 8, 2026 17:26
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