Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 5 additions & 5 deletions .github/workflows/oem-app-apk.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: OEM Example App APK

# Publishes an installable APK of sdk/example-oem-app — the reference second
# host built against the public @mentra/island entry — as a workflow artifact
# host built against the public @mentra/engine entry — as a workflow artifact
# on every dev push that touches its inputs (mirroring how the MentraOS app
# APK is produced on dev pushes by "Mobile App Android Build").
#
Expand All @@ -20,7 +20,7 @@ on:
branches:
- dev
paths:
- "mobile/modules/island/**"
- "mobile/modules/engine/**"
- "mobile/modules/bluetooth-sdk/**"
- "mobile/modules/crust/**"
- "mobile/modules/miniapp/**"
Expand Down Expand Up @@ -117,8 +117,8 @@ jobs:
working-directory: mobile/modules/crust
run: bun run build

- name: Build island
working-directory: mobile/modules/island
- name: Build engine
working-directory: mobile/modules/engine
run: bun run build

- name: Run Expo prebuild
Expand Down Expand Up @@ -180,7 +180,7 @@ jobs:
if (e.status !== 404) throw e;
release = (await github.rest.repos.createRelease({
owner, repo, tag_name: tag, name: 'Example OEM app builds (rolling)',
body: 'Rolling dev builds of sdk/example-oem-app — the reference second host built against the public @mentra/island entry. Debug-keystore-signed release APKs, one per dev commit that touches the OEM stack. Assets older than 14 days are swept automatically.',
body: 'Rolling dev builds of sdk/example-oem-app — the reference second host built against the public @mentra/engine entry. Debug-keystore-signed release APKs, one per dev commit that touches the OEM stack. Assets older than 14 days are swept automatically.',
prerelease: true,
})).data;
}
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/oem-host-gate.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: OEM Host Boundary Gate

# Proves the toolkit/host boundary from the CONSUMER side: the example OEM app
# must typecheck against @mentra/island's BUILT public types (build/*.d.ts via
# Proves the engine/host boundary from the CONSUMER side: the example OEM app
# must typecheck against @mentra/engine's BUILT public types (build/*.d.ts via
# the exports map) with zero access to the /internal migration surface. The
# in-repo Mentra app enforces the boundary on itself with
# scripts/check-mobile-runtime-boundary.sh; this gate is the half that check
Expand All @@ -15,7 +15,7 @@ name: OEM Host Boundary Gate
on:
pull_request:
paths:
- "mobile/modules/island/**"
- "mobile/modules/engine/**"
- "mobile/modules/bluetooth-sdk/**"
- "mobile/modules/crust/**"
- "mobile/modules/miniapp/**"
Expand All @@ -28,7 +28,7 @@ on:
- main
- dev
paths:
- "mobile/modules/island/**"
- "mobile/modules/engine/**"
- "mobile/modules/bluetooth-sdk/**"
- "mobile/modules/crust/**"
- "mobile/modules/miniapp/**"
Expand Down Expand Up @@ -63,24 +63,24 @@ jobs:

- name: Install SDK workspace
working-directory: sdk
# The sdk postinstall builds @mentra/miniapp, whose types island's
# The sdk postinstall builds @mentra/miniapp, whose types engine's
# build imports — let it run.
run: bun install --frozen-lockfile

- name: Build bluetooth-sdk types
# island's public entry re-exports event types from
# engine's public entry re-exports event types from
# @mentra/bluetooth-sdk, so its build/*.d.ts must exist first.
working-directory: mobile/modules/bluetooth-sdk
run: bun run build

- name: Build crust types
# island's services import @mentra/crust, whose types point at its
# engine's services import @mentra/crust, whose types point at its
# build output.
working-directory: mobile/modules/crust
run: bun run build

- name: Build island public types
working-directory: mobile/modules/island
- name: Build engine public types
working-directory: mobile/modules/engine
run: bun run build

- name: Typecheck example OEM app against the built public entry
Expand All @@ -96,7 +96,7 @@ jobs:
set -euo pipefail
violations=$(grep -RIn --include='*.ts' --include='*.tsx' \
--exclude-dir=node_modules \
-e '@mentra/island/internal' \
-e '@mentra/engine/internal' \
-e '@mentra/bluetooth-sdk/internal' \
-e '@mentra/bluetooth-sdk-internal' \
sdk/example-oem-app || true)
Expand Down
22 changes: 11 additions & 11 deletions agents/island-facade-buildout.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Island facade buildout — tracking spec

Goal: build the OEM-facing `toolkit.*` typed facades by moving the backing logic
into `@mentra/island`, domain by domain, on branch `aisraelov/island-namespace-wifi`
Goal: build the OEM-facing `engine.*` typed facades by moving the backing logic
into `@mentra/engine`, domain by domain, on branch `aisraelov/island-namespace-wifi`
(PR #3167). One branch, one commit per domain, green at every commit.

## Two move-patterns
Expand All @@ -18,19 +18,19 @@ into `@mentra/island`, domain by domain, on branch `aisraelov/island-namespace-w
`configureRuntime` bridge is a temporary means, not the destination; aim
adapter-free. The one permanent seam is `auth.getSubjectToken`.

Rule: stores are the Mentra-app escape hatch (`toolkit.stores.*`), NOT the OEM
Rule: stores are the Mentra-app escape hatch (`engine.stores.*`), NOT the OEM
contract. OEMs use the typed facade functions.

## cloud-v2 mobile-CI integration (was fully broken on dev)
The cloud-v2 merge left the mobile CI red on dev (install died on a 404, so the
typecheck never even ran). Three fixes, all on this branch (they un-red dev too):
1. **Spurious dep** — `mobile/modules/island/package.json` declared
1. **Spurious dep** — `mobile/modules/engine/package.json` declared
`"@mentra/cloud-client": "*"`; cloud-client is resolved via metro+tsconfig path
aliases, not npm, so the `*` 404'd. Removed it.
2. **island standalone build** — `postinstall` builds island via `expo-module`
(`build:module`), whose isolated tsconfig lacks the cloud-v2 aliases → fails on
cloud-v2 imports. But island's `build/` is unused (metro + tsconfig resolve
`@mentra/island` → src). Made it non-fatal in `mobile/scripts/postinstall.mjs`.
`@mentra/engine` → src). Made it non-fatal in `mobile/scripts/postinstall.mjs`.
3. **cloud-v2 deps** — the mobile typecheck follows the aliases into cloud-v2
SOURCE (`../cloud-v2/packages/*`), which import `zod`/`tweetnacl`; resolution is
file-relative so they must be in `cloud-v2/node_modules`, never installed (cloud-v2
Expand All @@ -52,14 +52,14 @@ adapter-injection pattern (#2), not a trivial move:
These are real per-domain efforts, each its own careful commit.

## Docs (keep current as we go)
Each shipped domain is documented in the OEM docs at `docs/glasses-oems/toolkit.mdx`
Each shipped domain is documented in the OEM docs at `docs/glasses-oems/engine.mdx`
(Mintlify). When a facade lands, add its surface there in the same commit. The page is
written but **not yet nav-linked in `docs/docs.json`** — it's an unreleased Phase-1
API, so publishing to the live OEM site is gated until release (one-line nav add when
greenlit).

## Verification per commit
`npx tsc --noEmit -p .` (resolves `@mentra/island`→src, validates the real code) +
`npx tsc --noEmit -p .` (resolves `@mentra/engine`→src, validates the real code) +
`bun run test`. The island standalone build can't run locally (cloud-v2 `zod` not
installed in this checkout) — CI confirms it; use the proven relative-`_internal`
pattern for btsdk types.
Expand All @@ -77,14 +77,14 @@ pattern for btsdk types.
| dev | `utils/cloudClient/devHost.ts` + core store | 1 | todo |
| miniapps | apps store + LocalMiniappRuntime (island) + MiniappCatalog | 1/hard (WebView) | todo |
| pairing | pairing screens state machine (readiness primitive already island) | 1 (extract) | todo |
| **settings** | `stores/settings.ts` (964 LOC) + `RestComms` + `storage` | keystone | **DONE (#3167)** — settings store moved into island, `toolkit.stores.settings`. Moved **together with RestComms** (mutually coupled: settings→RestComms cloud-sync, RestComms→settings backend URL). Storage uses island's MMKV (ported `loadSubKeys`). Unblocks glasses.settings + phoneNotifications. The typed `toolkit.settings` keyed facade (get/set/onChanged) is still TODO on top of the moved store. |
| **settings** | `stores/settings.ts` (964 LOC) + `RestComms` + `storage` | keystone | **DONE (#3167)** — settings store moved into island, `engine.stores.settings`. Moved **together with RestComms** (mutually coupled: settings→RestComms cloud-sync, RestComms→settings backend URL). Storage uses island's MMKV (ported `loadSubKeys`). Unblocks glasses.settings + phoneNotifications. The typed `engine.settings` keyed facade (get/set/onChanged) is still TODO on top of the moved store. |
| **RestComms** (v1 REST) | `services/RestComms.ts` (731 LOC) | move-with-settings | **DONE (#3167)** — moved into island with settings (the coupled pair). v1-transitional: deleted in place when v1 retires. Reads backend URL from the now-island settings store directly (no early-auth timing hack). Host `@/services/RestComms` is a shim. GlobalEventEmitter also moved in (one shared instance). |
| glasses.settings | settings store + btsdk | 2 (after settings) | blocked on settings |
| phoneNotifications | settings store + crust + permissions | 2 (after settings) | blocked on settings |
| gallery | `services/asg/gallerySyncService.ts` (~1000 LOC, hotspot) | hard | todo |
| notifications | scattered detectors → new event bus | hard (new) | todo |
| session | `cloud-client` (cloud-v2) | keystone | **DONE (#3167)** — `CloudClientService` owns the CloudClient in island (built from island UDP + MMKV secure store + `getAuth()` + endpoints via `getConfigValues()`); self-wires the `cloud`/`cloudConnection` runtime hooks; `toolkit.session` exposes status. Account ops (delete/export) deferred (still host RestComms). Host `@/services/cloudClient` is a thin wrapper keeping dev/settings endpoint resolution. |
| cloudClientStatus (store) | cloud-client types | — | **DONE (#3167)** — moved into island, `toolkit.stores.cloudClientStatus`. |
| session | `cloud-client` (cloud-v2) | keystone | **DONE (#3167)** — `CloudClientService` owns the CloudClient in island (built from island UDP + MMKV secure store + `getAuth()` + endpoints via `getConfigValues()`); self-wires the `cloud`/`cloudConnection` runtime hooks; `engine.session` exposes status. Account ops (delete/export) deferred (still host RestComms). Host `@/services/cloudClient` is a thin wrapper keeping dev/settings endpoint resolution. |
| cloudClientStatus (store) | cloud-client types | — | **DONE (#3167)** — moved into island, `engine.stores.cloudClientStatus`. |
| cloud secure store (MMKV) | cloud-client KeyValueStore | — | **DONE (#3167)** — moved into island (react-native-mmkv already an island dep; adapter-free). |

## Sequence
Expand All @@ -102,5 +102,5 @@ glasses.settings + phoneNotifications) → pairing → gallery → miniapps WebV
notifications. Last: `git merge dev`, then session + cloudClientStatus.

This PR (#3167) is a clean, landable foundation at the cheap-tier boundary: the
core `toolkit.*` facade surface + store escape hatches, green. Land it, then
core `engine.*` facade surface + store escape hatches, green. Land it, then
sequence the host-coupled tier deliberately.
28 changes: 14 additions & 14 deletions agents/island-ui-logic-extraction-plan.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Island — the MentraOS OEM Integration Toolkit
# Island — the MentraOS OEM Integration Engine

> **Status:** design / build plan. **Base branch:** `dev`. The cloud-client move (#5)
> sequences last, once `cloud-v2` has merged into `dev` (days out).
Expand All @@ -7,10 +7,10 @@

## Why

We are turning `@mentra/island` into the **MentraOS OEM Integration Toolkit**: a single
We are turning `@mentra/engine` into the **MentraOS OEM Integration Engine**: a single
library a glasses OEM drops into their app to get *all* of MentraOS — glasses connection,
the miniapp runtime, backend services, OTA, pairing, settings, sensors — while writing
their own UI. The Mentra app becomes the first consumer of that same toolkit: a thin UI
their own UI. The Mentra app becomes the first consumer of that same engine: a thin UI
layer over island, with no privileged backdoor.

Today the logic is inverted — business logic lives in React screens and the app reaches
Expand All @@ -21,7 +21,7 @@ makes island the single boundary the UI talks to.
Mentra's *own* glasses (the SGCs already in `@mentra/bluetooth-sdk`: G1/G2/Live/Nex/Mach1/
Simulated) and the app becomes a UI layer. **Phase 2** (separate, later) makes the glasses
layer *injectable* — an OEM registers its own SGC(s) at runtime instead of PRing them into
`@mentra/bluetooth-sdk` — turning island into a true third-party toolkit. Phase 1 is the
`@mentra/bluetooth-sdk` — turning island into a true third-party engine. Phase 1 is the
prerequisite; Phase 2's adapter dynamics are TBD and out of scope here.

## What
Expand All @@ -35,7 +35,7 @@ prerequisite; Phase 2's adapter dynamics are TBD and out of scope here.
the host needs, it *reads from* or *calls on* island.

> **Governing principle — if the Mentra app's UI uses it, island exposes it.** The Mentra
> app is OEM #1 and the reference consumer; the toolkit surface is the *union* of
> app is OEM #1 and the reference consumer; the engine surface is the *union* of
> everything the Mentra app needs. A third-party OEM consumes whatever subset it wants.

The complete host→island injection:
Expand Down Expand Up @@ -183,7 +183,7 @@ The re-exported `@mentra/bluetooth-sdk` command + event surface, namespaced unde
`on(event, cb)` (e.g. `ota_status`, `version_info`). It exists so the host can drive
device-specific things island does **not** model — chiefly **OTA**, the Mentra-Live firmware
flow (APK→MTK→BES), which lives in the Mentra app (its only consumer). Because the call routes
through island, the app's OTA code imports only `@mentra/island`.
through island, the app's OTA code imports only `@mentra/engine`.

> **Facades vs passthrough.** Model *shared* capabilities (connection, settings, wifi, pairing)
> as typed facades; reach for `glasses.btsdk` only for the *long tail* no facade covers. If a
Expand Down Expand Up @@ -291,7 +291,7 @@ reimplement any of it.**
Therefore the contract is a single mountable component:

```tsx
import { MiniappView } from "@mentra/island"
import { MiniappView } from "@mentra/engine"
<MiniappView packageName={pkg} onExit={...} /> // island owns spawn, shim, routing, handshake, respawn, the capsule menu
```

Expand Down Expand Up @@ -363,13 +363,13 @@ and its comms route their btsdk imports through island in the sweep; island cons

## The native dependency boundary

End-state: the app imports `@mentra/island` only; island imports `crust` + `@mentra/bluetooth-sdk`
End-state: the app imports `@mentra/engine` only; island imports `crust` + `@mentra/bluetooth-sdk`
and exposes their surface. Today the app reaches past island in **80 files** (67 btsdk + 13
crust). Most die for free — when a coordinator lands, its screens stop importing btsdk because the
device port owns the calls. The sweep (#8) mops up the orphans: re-export btsdk through
`island/index.ts`; wrap `crust`'s capabilities (heading, nav, media, notifications) as island
services. An ESLint `no-restricted-imports` rule banning `crust`/`@mentra/bluetooth-sdk` outside
`mobile/modules/island/**` ratchets the boundary shut per migrated surface.
`mobile/modules/engine/**` ratchets the boundary shut per migrated surface.

Two special cases: `stores/glasses.ts` + `stores/core.ts` move **into** island (they *are* the
device-state store the coordinators read); the `photo`/`video`/`streaming` coordinators collapse
Expand All @@ -388,7 +388,7 @@ The embedded RN runtime lives as long as its host **process** does:
runtime: `connectedDevice`, `+microphone`/`+dataSync` while streaming), started/stopped from
`DeviceManager` on connect/disconnect.

The toolkit's Android packaging has **two layers that merge differently**: a library module's
The engine's Android packaging has **two layers that merge differently**: a library module's
AndroidManifest **auto-merges** into the consuming app; **Gradle build config does not.**
`mobile/plugins/android.ts` is the *Mentra app's* Expo config plugin (runs only in Mentra's
prebuild; an OEM never runs it), so anything an island module needs that currently lives there
Expand Down Expand Up @@ -419,7 +419,7 @@ overrides by shadowing the resource — no code fork.
### Layer 2 — Gradle (does **not** merge)
Two different things hide here; separate them.

**(a) Config the consuming app genuinely must set → ship as `@mentra/island/plugin`** (an Expo
**(a) Config the consuming app genuinely must set → ship as `@mentra/engine/plugin`** (an Expo
config plugin in the island package; native OEMs follow the same snippets):
- enable `coreLibraryDesugaring` (`+ desugar_jdk_libs`) — AGP requires the *app* to enable it
because crust's Nav SDK uses Java 8+ APIs; crust enables it module-side, the app must too.
Expand All @@ -435,10 +435,10 @@ app's `settings.gradle` must register `:lc3Lib`. Fix at the source — btsdk dep
consumer `settings.gradle` entry exists.

Mentra's app-only glue (signing, versionName, Sentry, deep-link scheme, heap/node-path) stays in
`mobile/plugins/android.ts` and is not part of the toolkit.
`mobile/plugins/android.ts` and is not part of the engine.

### OEM residual obligations (irreducible — policy / branding / keys)
1. Apply `@mentra/island/plugin` (Expo) or the documented Gradle steps (native).
1. Apply `@mentra/engine/plugin` (Expo) or the documented Gradle steps (native).
2. Supply a Google Nav API key value (or use Mentra's).
3. Play Console: justify the FGS types (`connectedDevice`, `microphone`) and `QUERY_ALL_PACKAGES`.
4. iOS: add the `bluetooth-central` background mode to `Info.plist`.
Expand All @@ -463,7 +463,7 @@ Mentra's app-only glue (signing, versionName, Sentry, deep-link scheme, heap/nod

Regenerate: `grep -rl "@mentra/bluetooth-sdk" mobile/src | grep -v __tests__` (67) ·
`grep -rlE "from ['\"]crust['\"]" mobile/src` (13). Bucketed by the PR that removes the import
(✅ a coordinator owns the calls so the import vanishes; 🔁 re-point to `@mentra/island` in the
(✅ a coordinator owns the calls so the import vanishes; 🔁 re-point to `@mentra/engine` in the
sweep):

- **OTA** 🔁 (stays in the app; re-point to `island.glasses.btsdk`) `app/ota/{check-for-updates,progress,progress-legacy,deriveOtaDisplayState}.tsx`,
Expand Down
Loading
Loading