Skip to content

[codex] Remove legacy cloud applet routes#3216

Draft
PhilippeFerreiraDeSousa wants to merge 88 commits into
aisraelov/island-namespace-wififrom
codex/remove-legacy-cloud-applet-routes
Draft

[codex] Remove legacy cloud applet routes#3216
PhilippeFerreiraDeSousa wants to merge 88 commits into
aisraelov/island-namespace-wififrom
codex/remove-legacy-cloud-applet-routes

Conversation

@PhilippeFerreiraDeSousa

Copy link
Copy Markdown
Contributor

Summary

  • remove the legacy /applet/settings and /applet/webview mobile routes and their error screen
  • drop cloud-v1 applet REST helpers from island RestComms
  • update home, deeplink, capsule menu, local miniapp, and RestComms test callsites away from those routes

Scope notes

  • No cloud/ backend changes
  • No mobile/modules/bluetooth-sdk changes
  • Cherry-picked cleanly from aea143b37db6015c69d44c690e359819e49bac05

Validation

  • cd mobile && bun install --frozen-lockfile to install dependencies in the fresh worktree
  • cd mobile && bun run compile passed

aisraelov added 30 commits June 16, 2026 08:30
Introduce the namespaced `island` object — the "(A) host toolkit API" from the
Phase-1 contract (OS-1622). Additive: it lives alongside the flat named exports
and grows one facade at a time.

First facade: `island.glasses.wifi` (scan / connect / forget) over the
bluetooth-sdk passthrough — the reference pattern the rest of the (A) domains
copy. The wifi screens (scan, connecting) now call `island.glasses.wifi.*` and
no longer import `@mentra/bluetooth-sdk` directly (the native-import boundary).

status()/onStatus() are deferred to the glasses-store migration — wifi status
derives from multiple sources in the glasses connection state, so the read-model
lands with that store, not here. connect() propagates bluetooth-sdk's coded
errors unchanged so callers keep their error mapping.

Jest suite for the facade (by-path import, real code under the CI runner); the
@mentra/island mock gains `island`. The bootstrap front door
(island.configure/start/stop) and remaining domains follow in later PRs.
Two more pieces of the (A) host API on the island namespace.

Bootstrap front door — island.configure({auth, config?, analytics?}) + start() +
stop(), backed by an island config singleton (runtime/bootstrap.ts). The host now
hands island its auth provider at boot (MantleManager.init). This is ADDITIVE: the
three transitional config seams (configureRuntime/Launcher/Island) still wire the
host adapters during the migration and collapse into this front door only as each
domain lands in island.

island.display.mirror — island's own read-model of the latest processed display
event (current() / onMirror(cb)), fed in PARALLEL with the legacy host
useDisplayStore from both display paths (LocalDisplayManager + the cloud path in
SocketComms). This is the safe "facade" half. The "callback" half — flipping the
preview UI onto onMirror, adding view tracking, deleting useDisplayStore + the
setDisplayEvent adapter, and migrating the display tests (which are coupled to v1
SocketComms + MantleManager) — is its own focused PR.

Jest: @mentra/island mock gains the new members; by-path suites cover the
bootstrap lifecycle + the mirror read-model.
…s facade)

The glasses store is the source of device state (connection, wifi, battery, OTA,
hotspot) that every device facade needs, so it moves into island — and the wifi
status read-model that was blocked on it now lands too.

- stores/glasses.ts -> modules/island/src/stores/glasses.ts. btsdk type imports
  switch to the relative _internal path (standalone-build-safe, like the
  passthrough); connection predicates come from island's GlassesReadiness.
- @/stores/glasses becomes a thin re-export shim — the ~46 existing importers are
  unchanged (the escape hatch from the Phase-1 plan). Also surfaced as
  island.glassesStore (the raw store) for the app to keep using directly.
- jest: the @mentra/island mock provides the REAL store via jest.requireActual so
  every consumer's tests keep their real setState/getState/subscribe behavior.
- island.glasses.wifi gains status() / onStatus() reading the island-owned store
  — the wifi facade is now complete.

Verified: typecheck 0 new errors across all 46 consumers; jest 265 pass (the real
store runs in-test via requireActual). The island standalone build uses the proven
relative-_internal pattern; CI confirms it (local build is blocked only by the
unrelated cloud-v2 zod dep not being installed in this checkout).
The OEM-facing API object is now `toolkit` (toolkit.glasses.wifi, toolkit.configure
/ start / stop, toolkit.display.mirror, toolkit.glassesStore). The code module stays
@mentra/island — only the public surface is renamed, since the product is the OEM
Integration Toolkit. Done now while the namespace has only a handful of consumers.

All consumers updated: the wifi screens, SocketComms, MantleManager, the jest mock,
and the runtime-visible bootstrap error string. The flat exports (useGlassesStore,
BluetoothSdk, …) are unchanged.
…rror reads it)

The glasses-screen mirror read-model now lives in island, so the mirror UI is on
island-owned state too (the device store moved last commit).

- stores/display.ts -> modules/island/src/stores/display.ts. The two pure
  @/utils/e2eMetrics helpers (extractDisplayText, logE2EMetric) are inlined so the
  store is self-contained inside island; the host e2eMetrics util is untouched.
  Added subscribeWithSelector for the facade's selector subscription.
- @/stores/display becomes a re-export shim (consumers unchanged). The raw store is
  toolkit.displayStore — a Mentra-app escape hatch, NOT the OEM contract.
- toolkit.display.mirror is now the typed READ facade over the island-owned store
  (current() / onMirror()), replacing the earlier parallel read-model. Reverted the
  parallel ingest feeds in LocalDisplayManager + SocketComms.
- jest: the @mentra/island mock serves the real display store via requireActual.

Verified: typecheck 0 new errors; jest 265 pass (GlassesDisplayMirror + the
SocketComms display path stay green).
…der toolkit.stores

Continues the device-store consolidation.

- core / connection / gallerySync stores -> modules/island/src/stores/* via the
  shim pattern (the @/stores/* files become 1-line re-exports; consumers unchanged).
- Two coupled types relocated into island so the stores are standalone-build-safe:
  WebSocketStatus (was @/services/ws-types) and PhotoInfo (was @/types/asg). Both old
  homes now re-export from island, so their other importers are unchanged.
- Grouped the raw-store escape hatches under toolkit.stores.{glasses,display,core,
  connection,gallerySync} (was toolkit.glassesStore/displayStore — nothing consumed
  those yet, so zero-risk). Reminder: stores are the Mentra-app escape hatch, NOT the
  OEM contract; OEMs use the typed facades.
- Deferred cloudClientStatus: it imports cloud-v2 (@mentra/cloud-client) types, so it
  belongs with the cloud-client / session work, not this batch.
- jest: the @mentra/island mock serves the real stores via requireActual.

Verified: typecheck 0 new errors across all consumers; jest 265 pass.
toolkit.glasses now carries the core surface (the central OEM glasses API):
- connection: connectDefault / disconnect / forget (bluetooth-sdk passthrough)
- read-model: status() / onStatus() / info() — a curated projection of the
  island-owned glasses store, so the store's field layout doesn't leak into UIs
- capabilities() from the model table; requestVersionInfo()
- input events: onButtonPress / onTouchGesture (passthrough listeners)
- wifi nested under it

Also adds agents/island-facade-buildout.md — the per-domain tracking spec for the
rest of the buildout (two move-patterns, sequence, the settings keystone, the
git-merge-dev gate for session).

Verified: typecheck 0 new errors; jest 269 pass (new glassesFacade suite green).
toolkit.speech.{stt,tts} — currentLanguage / languages / languageInfo / download /
activate / cancelDownload / deleteModel, wrapping the in-island STT+TTS model
managers via type-safe Parameters<> forwarding. STT also exposes status() /
onStatusChanged() over the offline-model service. Logic was already in island, so
this is a thin facade. Tested (managers mocked to avoid native deps).

Verified: typecheck 0 new errors; jest 272 pass.
…atal island build)

Mobile CI (and dev's, since the cloud-v2 merge) failed install on
`@mentra/cloud-client - 404`. Two install-time causes, both fixed:

1) mobile/modules/island/package.json declared `"@mentra/cloud-client": "*"`, but
   cloud-client is resolved via metro + tsconfig PATH aliases (per the cloud-v2
   setup), not node_modules — the `*` made bun try npm and 404. Removed it; the app
   still resolves cloud-client via the alias (island only type-imports it).

2) postinstall builds island standalone (expo-module), whose isolated tsconfig has
   no cloud-v2 aliases, so it can't resolve `@mentra/cloud-client` /
   `@mentra/cloud-runtime/protocol` and failed the whole install. But island's
   build/ is NOT consumed: metro (metro.config.js) and tsconfig both resolve
   `@mentra/island` to src/. Made the build non-fatal (warn) — same precedent as
   the root @mentra/miniapp postinstall.

Unblocks mobile CI (mine + dev's). App typecheck + jest green.
…ypes

- Restore the cloud-v2 path aliases in island/tsconfig.json (a local build:module
  regenerated and stripped them in the prior commit, and I accidentally committed
  that) — they let island's standalone build resolve cloud-client/cloud-runtime.
- Export the 5 store state interfaces (CoreState, ConnectionState, DisplayStore,
  GlassesState, GallerySyncState) so toolkit.stores' declaration emit can name them
  (fixes the TS4023 the standalone build hit).
…resolves cloud-v2 source

After the install 404 was fixed, the mobile typecheck failed: it follows the
metro/tsconfig aliases into cloud-v2 SOURCE (manila/cloud-v2/packages/*), which
import zod/tweetnacl. Module resolution is file-relative, so those resolve against
cloud-v2/node_modules — never installed, because cloud-v2 is a SEPARATE bun
workspace the mobile install doesn't cover. Install cloud-v2's deps in the mobile
postinstall (non-fatal). Third and final piece of un-redding the cloud-v2 mobile CI
(broken on dev since the cloud-v2 merge): drop spurious cloud-client dep →
non-fatal island standalone build → install cloud-v2 deps.
…hotoSettings

MentraBluetoothSDK.swift failed to compile, blocking the native iOS build:
- :493 used `settings.resetCaptureTuning` (Bool?) directly in an `if`.
- :504 used `if let size = settings.size`, but `size` is non-optional
  (ButtonPhotoSize, always defaulted to .medium in `from(params:)`).
Unwrap the Bool? with `== true`; set `size` unconditionally to match its
non-optional design. Both pre-existing on dev (camera/button-photo work) —
the last red on the mobile CI native build, only reachable once the cloud-v2
install fixes let the build get past install to the Swift compile.
…rface

Documents the live OEM-facing API as we build it: lifecycle (configure/start/stop),
glasses (connection/status/info/capabilities/version/events + wifi), speech (stt/tts),
display.mirror, the stores escape hatch, and the host adapter seam. Page lives under
docs/glasses-oems/ but is intentionally NOT nav-linked yet — Phase-1 preview, gated
from the live OEM site until release.
…store into island

Leaf moves ahead of relocating the CloudClient singleton itself:
- cloudClientStatus store -> island/src/stores (host @/stores/cloudClientStatus
  becomes a re-export shim); surfaced as toolkit.stores.cloudClientStatus.
- cloud secure store (MMKV KeyValueStore for the v2 refresh token) ->
  island/src/utils/cloudClient (host @/utils/cloudClient/MmkvSecureStore shim).
  react-native-mmkv is already an island dep, so this is adapter-free.
jest @mentra/island mock gains the store via requireActual (pure zustand).
…nd + toolkit.session

The cloud-client/auth keystone — pulls backend comms ownership into island.

- island/src/services/CloudClientService.ts owns the @mentra/cloud-client
  singleton. It builds the client from island-owned pieces (the native UDP
  socket, the MMKV secure store, the cloud-status store) + the two things the
  host provides through the toolkit FRONT DOOR (not a configureRuntime adapter):
  auth.getSubjectToken (the one permanent seam) and the resolved cloud endpoints
  + LC3 frame size via toolkit.configure({config}). On init it SELF-WIRES the
  runtime cloud/cloudConnection hooks, so the host no longer injects a
  CloudRuntimeAdapter.
- toolkit.session exposes the live-session status read-model (status +
  audioTransport) + isConnected. Account ops (delete/export) stay host RestComms
  for now (cloud-client core has none) — they land with the account domain.
- Host @/services/cloudClient becomes a thin wrapper: keeps the dev/settings
  endpoint resolution (resolvedEndpoints/lc3FrameSizeBytes/cloudConfigValues,
  host-coupled) and delegates managed-photo/stream/connection to island, so
  PhonePhotoCoordinator / cloudStreamApi / the dev Cloud-URL switcher are
  untouched.
- MantleManager passes config via configure() and drops the host-injected
  cloud/cloudConnection hooks.
- bootstrap IslandConfigValues gains audioFrameSizeBytes; jest @mentra/island
  mock gains cloudClientService + toolkit.session.

Per OS-1622 this is the adapter-free path: storage/status/client all owned by
island; the configureRuntime cloud hook is now island-self-wired (deleted in the
#8 sweep). Docs (toolkit.session) + buildout spec updated.

Typecheck + full jest (281 tests) green. Device-level audio/transcription/photo
verification is still required on hardware.
…o island storage

Increment 1 of the settings+RestComms co-move. GlobalEventEmitter -> island (one
shared instance, host shim); ports storage.loadSubKeys() into island's MMKV util
(settings.ts needs it). jest mock gains the shared emitter.
…-coupled v1-comms pair)

The biggest single migration — settings (964 LOC) + RestComms (731 LOC) move
together because they're mutually coupled (settings calls restComms.writeUserSettings
for cloud-sync; RestComms reads the settings store for the backend URL). Moving the
pair into island resolves BOTH the circular dependency AND the early-auth timing trap
(RestComms reads the backend URL at exchangeToken, before island's config/adapters are
wired — only the module-load-available store works there; now it's island's own store).

- settings.ts -> island/src/stores; RestComms.ts -> island/src/services (git mv,
  history preserved). Host @/stores/settings + @/services/RestComms are re-export shims
  (96 + 24 consumers unchanged). Exposed as toolkit.stores.settings.
- Import rewrites: settings -> island RestComms + island storage; RestComms -> island
  settings/connection stores, island GlobalEventEmitter, island types, BgTimer, and the
  internal btsdk surface via the relative build/_internal path (the
  @mentra/bluetooth-sdk-internal alias doesn't resolve in island's standalone build).
- GlobalEventEmitter moved into island (one shared instance + host shim); ported
  storage.loadSubKeys into island's MMKV util; dropped two dead imports (Platform/Device).
- jest: requireActual settings + RestComms (tests used the real host store before the
  move); moduleNameMapper maps the relative build/_internal path to the mockable source
  so requireActual doesn't load the real native btsdk; the mock exposes the REAL island
  GlobalEventEmitter so emit/listen share one instance.

v1-transitional: RestComms is deleted in place when v1 retires; the point is the host
no longer owns comms during the (multi-week) transition. SocketComms is a separate
4-6 week refactor and moves last.

mobile typecheck + island standalone build + full jest (281 tests) all green.
…tions (fix TS4023)

toolkit.stores.settings = useSettingsStore is typed with SettingsState, which was
not exported; island's standalone build EMITS .d.ts (expo-module-tsc) and can't name
an un-exported type -> TS4023. Passed local 'tsc --noEmit' (no emit) but failed CI's
build:module. Exporting the interface fixes it (same pattern as the other moved-store
State interfaces). Verified with 'bun run build:module' (the emit build) now green.
…facades

Typed (A) facades over logic just moved into island:
- toolkit.settings — keyed get/set/onChanged/descriptor/keys over the island settings
  store (the OEM contract; toolkit.stores.settings stays the raw escape hatch).
- toolkit.session.account.{delete,confirmDelete} — adapter-free now that RestComms is in
  island (corrects the earlier deferral). requestExport omitted (host UI flow, no backend).
- toolkit.dev — backend/cloud URL overrides (island settings store), reconnect the
  island cloud client, minimumClientVersion (island RestComms).
Exported Setting + SettingsState interfaces (TS4023: descriptor()/stores.settings emit
declarations). Verified with the EMIT build (build:module), not just tsc --noEmit.
jest mock extended; full suite (281) green.
…incidents

toolkit.incidents — typed passthrough over the now-island RestComms incident
submission (create/uploadLogs/uploadAttachments/sendFeedback). The single-call
file() that bundles diagnostics island-side is a follow-up (needs the host's
native diagnostics-gathering + console logBuffer moved in). OEM docs updated for
the new facades (settings, session.account, dev, incidents). Emit build + 281 jest green.
- glasses.onStatus + session.onStatus: dedupe — fire only when the projected
  snapshot actually changes (were firing on every store update).
- CloudClientService: read LC3 frame size LIVE from the island settings store so a
  reconnect after a glasses swap (G1=20 <-> G2=40) isn't stale on the configure() snapshot.
- toolkit.dev: setCloudUrls/reconnectCloud now reconnect onto the merged current
  cloud-URL overrides (a single-URL update used to set the setting but not apply it).
- docs/glasses-oems/toolkit.mdx: remove stray </content></invoke> markup (broke MDX).

Deferred (pre-existing, flagged): the ButtonPhotoSettings.size overwrite (proper fix =
optional size, ripples into the BLE encoder — camera-domain) and the !client-vs-connected
guard message in CloudClientService (carried over from the host, behavior-preserving).
…niapps lifecycle facade

- display.mirror: expose view()/setView('main'|'dashboard') — the store already
  backed it; was just unexposed. (sendButtonPress simulated-input is a separate dev aid.)
- toolkit.miniapps: lifecycle facade over the island apps store —
  list/onChanged(deduped)/refresh/start/stop/setForeground/clearForeground/stopAll/
  install/uninstall. The WebView bridge primitives stay exported separately (host mounts
  the <WebView>). jest mock extended; typecheck + emit build + 281 jest green.
file({feedbackData, phoneState, logs?, screenshots?}) -> {incidentId} orchestrates
the full flow island-side: createIncident -> uploadLogs -> sendIncidentId to glasses
-> uploadAttachments. The OEM gathers diagnostics (native-coupled: NetInfo/Constants/
Location/ImagePicker + console logBuffer) and passes them in; island owns the
submission. Lower-level primitives still exposed. Doc + jest mock updated; emit build +
281 jest green. Finishes the last of the 3 partial facades (with display.mirror.setView
+ toolkit.miniapps).
- Stale cloud endpoint override: reconnect(null) now CLEARS endpointsOverride and
  falls back to the boot config; toolkit.dev's empty-settings branch passes null, so
  cleared/default cloud URLs stop reconnecting to a stale override. Added
  cloudClientService.stop() for lifecycle symmetry.
- start() skips cloud init: toolkit.start() now constructs/connects the cloud client
  (and toolkit.stop() tears it down), so the documented configure()+start() flow yields
  a live toolkit.session for OEMs. Idempotent — the Mentra app's explicit cloudClient.init()
  becomes a no-op. Composed in island.ts to avoid a bootstrap<->service import cycle; safe
  now that init() reads the settings STORE (module-load-available), not the adapter.

Emit build + 281 jest green.
The iOS build intermittently failed at 'pod install' with
'React-Core-prebuilt/React.xcframework/Info.plist doesn't exist' — the cached Pods
(keyed by lockfile) can be valid-by-key yet missing the prebuilt React xcframework
(a known RN-0.83 prebuilt + CocoaPods cache flake on the self-hosted runners). pod
install was a bare command with no fallback (only the later build step retried with
clean caches). Make pod install self-heal: on failure, clear project Pods + the global
pod cache and reinstall with --repo-update. Happy path unchanged.

Not a TS issue — one of the two iOS build jobs passed (6m39s) on the same commit; only
the runner with the corrupted cache failed.
- toolkit.glasses.settings: keyed DEVICE-settings facade (get/set/onChanged/descriptor/
  available) scoped to BLUETOOTH_SETTING_KEYS (exported), minus the internal sync keys
  (core_token/auth_email). set() persists + auto-syncs to the glasses via the store.
- toolkit.pairing: first-time discovery + pair — scan/scanning/searchResults/onFound(deduped)
  over the core store + BluetoothSdk startScan/connect/setDefaultDevice + the pair_failure/
  glasses_not_ready events. (connectDefault for the already-paired default stays on glasses.)

Doc + jest mock updated; mobile typecheck + island emit build + 281 jest green.
permissions deferred — it's the host-coupled one (PermissionsUtils: i18n/theme/AlertUtils),
needs the adapter pattern; recommended as a focused follow-up off this now-green PR.
…roller) + toolkit.phoneNotifications

- toolkit.glasses: + connect(device), connectSimulated(), setDefault(device), and a
  controller sub-facade (connectDefault/disconnect/forget). Switched glasses.ts to the
  internal btsdk surface (these live there, not the public entry). Dropped reconnect()
  (no btsdk backing) and controller.connect() (only connectDefaultController exists).
- toolkit.phoneNotifications: enabled/setEnabled + blocklist/setBlocklist (island settings),
  installedApps + has/requestListenerPermission (CrustModule). Android-only; iOS defaults.
  Emit build confirms crust resolves in island's standalone build.

Emit build + 281 jest green.
aisraelov and others added 28 commits June 18, 2026 11:05
…ost hooks

Three runtime-blocking passthrough hooks an OEM had to inject become direct btsdk
calls so a bare OEM works without them:
- sendDisplayEvent -> LocalDisplayManager calls BluetoothSdk.displayEvent (the display
  output path — nothing renders on the glasses without it).
- setMicRequirements -> MicStateCoordinator calls BluetoothSdk.updateBluetoothSettings
  (the mic control plane — the glasses never stream audio without it).
- restartTranscriber -> LocalSttFallbackCoordinator calls BluetoothSdk.restartTranscriber.

Removed the three RuntimeHooks fields + MantleManager bindings. setDisplayEvent (host
glasses-mirror UI) + subscribeGlassesStatus stay host hooks for now.

Verified: app tsc clean, island expo-module build clean, jest 40 suites / 289 tests.
…mapping

- MantleManager registered audio_pairing_needed / audio_connected / audio_disconnected /
  save_setting / head_up TWICE in setupSubscriptions, so each handler fired twice per
  event. Remove the redundant first-block registrations (the surviving head_up copy is
  the superset that also forwards to miniapps).
- PhoneLocationService.getLocationAccuracy ignored the aggregate miniapp tiers
  ("passive"/"low"/"high") — they fell through to Lowest, so a miniapp requesting
  high-rate GPS got the coarsest accuracy. Map passive→Lowest, low→Low, high→High
  (realtime→BestForNavigation already handled); the accuracy-string vocabulary stays.

Verified: app tsc clean, island expo-module build clean, jest green.
…ata flow)

island owned the stores, coordinators, miniapp runtime, and facades, but never
subscribed to the device for most events — MantleManager was still the event router
the whole runtime depended on. So a bare OEM that imported island + called
toolkit.start() got a connected runtime with almost no device data flowing in.

New self-starting DeviceEventRouter (started by toolkit.start()) subscribes to the
inbound native events and routes the NON-v1 legs:
- wifi_status_change / hotspot_status_change / hotspot_error / gallery_status -> island
  stores + the process event bus. NOTE: island's own gallerySyncService listens on the
  bus for hotspot/gallery events, so without this bridge island's gallery sync was DEAD
  for a bare OEM.
- photo_response -> phonePhotoCoordinator (owned) / restComms (cloud-app); stream_status
  + keep_alive_ack -> phoneStreamCoordinator (owned).
- button_press / touch_event / accel_event / head_up -> local miniapps (forwardEvent).
- save_setting -> settings store (the inbound complement to GlassesSettingsSync).
- miniapp_selected -> the glasses swipe-menu app launcher.

MantleManager keeps only its v1 SocketComms legs (touch/stream/keepalive cloud forwards,
etc., deleted at v1 retirement) + host-UI legs; the moved legs are removed there to avoid
double-handling. mic_pcm->audio_chunk + phone_notification stay host for now (Buffer dep /
v1 REST). Added a by-path deviceEventRouter.test.ts; moved the photo_response assertion
out of MantleManager.test.

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
… hooks)

DisplayProcessor (battery/connection display placeholders + device-model tracking) and
LocalMiniappRuntime (initial glasses_battery snapshot) read the host glassesStatus +
subscribeGlassesStatus hooks. island already owns this data in useGlassesStore (fed by
GlassesStatusProjection), so point them at the store / GlassesReadiness directly and
drop both hooks + the MantleManager bindings. A bare OEM gets glasses status without
wiring them.

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
Resolve conflicts:
- config.ts: keep the heading/locationTier/cameraSettings removals (moved into island
  on this branch); restore the CloudConnectionAdapter interface dev's tree dropped (this
  branch's cloudClientService self-wires cloudConnection + LocalSttFallbackCoordinator
  reads it).
- MantleManager.ts: take dev's import line (the `crust` → `@mentra/crust` package rename
  + new BluetoothStatus/OtaStatus types); keep the cloudConnection self-wire comment.
- cloudClient.ts: keep this branch's thin delegating wrapper (the cloud client lives in
  island's CloudClientService). Port dev's NEW local-dev runtime-token feature
  (getLocalDevRuntimeToken / shouldUseLocalDevRuntimeToken) into CloudClientService so a
  localhost dev cloud still authenticates without a Supabase exchange.
- Update this branch's island `crust` imports + the gallery test mock to `@mentra/crust`.

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
… host hook)

Miniapp speaker / TTS playback was a host-injected audioPlayback hook, even though the
service is pure expo-audio + btsdk volume control with zero host coupling — so an OEM
had to implement it. Move AudioPlaybackService into island and call BluetoothSdk
directly for the glasses media-volume + own-app-playing hints (was toolkit.glasses.audio).

- LocalMiniappRuntime calls audioPlaybackService directly (dropped the host-not-configured
  guard); the v1 SocketComms audio_play_request handler imports it from @mentra/island.
- Removed the audioPlayback hook + AudioPlaybackAdapter/AudioPlayRequest from RuntimeHooks
  + the MantleManager binding. Added expo-audio to island deps. Repointed the
  AudioPlaybackService test by-path; the @mentra/island jest mock supplies it for host
  consumers. This completes the 5 passthrough-hook eliminations — an OEM gets miniapp
  audio with no wiring.

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
Resolve conflicts (same shape as prior merges — keep this branch's island moves, port
dev's new cloud feature into island):
- dev added a `miniappAuth` hook (package-scoped local-miniapp backend tokens via
  cloudClient.getMiniappAuthToken). Kept the hook; ported getMiniappAuthToken +
  normalizeExpiresAt into island's CloudClientService and delegated through the thin
  cloudClient wrapper. Dropped dev's re-added audioPlayback/glassesStatus hooks (moved
  into island) and the `cloud` configureRuntime arg (self-wired by cloudClientService).
- config.ts / index.ts: kept MiniappAuthAdapter/MiniappAuthToken; dropped the
  AudioPlaybackAdapter/AudioPlayRequest + glassesStatus surfaces.
- cloudClient.ts: kept the thin delegating wrapper.
- jest.config.js: union (this branch's btsdk build/_internal map + dev's expo/rn/crust maps).
- Removed a dead `setCaptureLedEnabled` btsdk binding (no native method, no public type,
  no consumers — leftover from dev's camera-LED removal; it failed tsc post-merge).

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
…OEM-readiness)

Two of the "a bare OEM still has to inject this" gaps from the e2e trace:

- settings hook eliminated. The runtime read user/device settings (coreToken,
  defaultWearable, localSttFallbackActive, cameraFov) through getRuntimeHooks().settings,
  but the host injector just re-wrapped island's own useSettingsStore. Point the 8 call
  sites (LocalMiniappRuntime / LocalSttFallbackCoordinator / DisplayProcessor) at
  useSettingsStore directly (same move as glassesStatus); remove the SettingsAccessor
  hook + the MantleManager binding. DisplayProcessor.attachToRuntime no longer early-
  returns when the hook is absent — a bare OEM now gets the right device profile +
  userId/coreToken instead of silent defaults.
- Declared the 11 runtime deps island imports but didn't list in package.json
  (@mentra/crust, @mentra/cloud-client, @mentra/cloud-runtime, axios, expo-notifications,
  expo-modules-core, react-native-localize/-permissions/-wifi-reborn,
  @react-native-community/netinfo, @craftzdog/react-native-buffer) so a clean install
  resolves them.

Verified: app tsc clean, island expo-module build clean, jest 41 suites / 295 tests.
Move the MentraJS engine construction out of the host bootstrap into island's
new MiniappEngine service: ensureMiniappEngine() builds the crash controller,
UI router, and JS router, binds them to the native Crust module + the launcher,
wires the dev-server background-respawn signal, and starts the message pump.
Idempotent — getMiniappEngine() returns the singleton.

toolkit.start() now brings up the local-miniapp engine (LocalMiniappRuntime +
the router + DisplayProcessor + gallery sync) so a bare OEM gets working
miniapps with no host construction step. All four bringup calls are idempotent.

The host mentraJsBootstrap shrinks to a thin shim that attaches only the
genuinely host-owned concerns over the island engine: the inter-miniapp interop
policy (system-app set, app-store start/stop, audit) and crashloop telemetry
(Sentry, automatic incident filing, the user alert). getMentraJS() now delegates
to the island engine singleton.
…jest mock

Two OEM-readiness/test-fidelity gaps surfaced by an end-to-end review:

- island's timers util require()s react-native-nitro-modules (the nitro
  bg-timer runtime probe) but the dep was undeclared. Harmless in the Mentra
  app (the host declares it directly), but a standalone OEM consumer would
  miss it. Declared in peerDependencies (* — host-provided), matching the
  other native peer deps.

- the @mentra/island jest mock omitted ensureMiniappEngine/getMiniappEngine/
  configureLauncher/miniappLauncher/sttModelManager — all public exports the
  host imports. Most notably ensureMiniappEngine is reached transitively by
  MantleManager.test via mantle.init() -> bootstrapMentraJS(), where the
  missing mock made the bootstrap throw-and-swallow instead of running. The
  mock now returns a minimal functional engine so the gate exercises the real
  host attach path.
…pace-wifi

# Conflicts:
#	mobile/src/stores/settings.ts
…pace-wifi

# Conflicts:
#	mobile/bun.lock
#	mobile/modules/island/src/index.ts
#	mobile/src/effects/OtaUpdateChecker.tsx
#	mobile/src/services/MantleManager.ts
#	mobile/src/stores/display.ts
…ettings seed)

- incidents.file() referenced this.notifyGlasses, so a detached call
  (const {file} = toolkit.incidents; file(input)) dropped `this` and threw.
  Reference the incidents object directly so destructured use still notifies.

- connectDefault()/reconnect() called pushAllBluetoothSettings() without
  awaiting it; the native settings write is async, so the connect handshake
  could race ahead and replay stale native settings. pushAllBluetoothSettings
  now returns the write promise and both connect paths await the seed first.
…tically

The wifi_off path set waitingForWifiRetry + wifiSettingsOpenedAt at notice-emit
time, before the user picked Open Settings vs Cancel. A user who cancelled was
left armed: a phantom "WiFi initializing" cooldown on the next tap, and an
unsolicited auto-sync on a later foreground if WiFi happened to come up.

Use the GalleryNotice.ack mechanism that already exists (the wifi-join notice
uses it): the island attaches the arming as the notice's ack closure and stops
setting the state optimistically; the host calls notice.ack() only on Open
Settings. Cancel arms nothing. Runtime policy (what arming means) stays in the
island; the host only reports which button the user pressed.
…rop pseudo offline-captions renderer

Local on-device STT transcripts now reach local miniapps for ANY host (not just
the Mentra app): DeviceEventRouter (started by toolkit.start()) listens for
local_transcription and forwards transcription:<lang> to localMiniappRuntime,
gated by localSttFallbackCoordinator.isActive() so cloud STT keeps owning
delivery when the WS is up (no double-routing).

With the pseudo offline-captions miniapp gone, MantleManager's
handle_local_transcription — and the host-side caption rendering it drove
(TranscriptProcessor, sendPendingTranscript, displayTextMain, resetDisplayTimeout)
— are removed. Added DeviceEventRouter tests for the active/inactive paths;
dropped the now-obsolete MantleManager offline-render test.
Route cloud, display, launcher, and interop runtime access through island-owned services and the toolkit front door instead of configureRuntime/configureLauncher/configureIsland.

Keep the legacy Mentra app catalog behind a transitional app-store hook until cloud v1 app surfaces can be deleted.
Remove the legacy Mentra app catalog bridge that fetched and managed cloud-v1 applets through RestComms.

Keep built-in/offline app registration in a smaller catalog and let the island app store own local-only app state without host hooks.
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

📋 PR Review Helper

📱 Mobile App Build

Ready to test! (commit 1af6587)

📥 Download APK

🕶️ ASG Client Build

Waiting for build...


🔀 Test Locally

gh pr checkout 3216

@PhilippeFerreiraDeSousa PhilippeFerreiraDeSousa force-pushed the aisraelov/island-namespace-wifi branch from 559daf1 to 0138b73 Compare June 30, 2026 23:50
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