Skip to content

Releases: GenericJam/mob

0.7.0

12 Jun 18:08

Choose a tag to compare

Added

  • Pure-Elixir composite components (Mob.Composite): UI kits register tag-name expanders (the manifest ui_components expand: form, or Mob.Composite.register/2) and <MyTag …/> expands to built-in widget trees in a new FIRST render pass — fixpoint with a depth guard, crash-isolated. on_* props written as bare strings/atoms are auto-injected as {screen_pid, tag} (no more threading self()). Hot-pushable. See decisions/2026-06-11-composite-expansion-pass.md.
  • Route-bound navigation params (Mob.Nav.Registry.register/3 + lookup_route/1): a registered route can carry a params map merged under push params into mount/3 — the enabler for data-driven plugins (mob_ash registers /ash/post as {MobAsh.ListScreen, %{resource: …}}). Screen-manifest entries take an optional :params.
  • Style packages, tokens-only tier (MOB_STYLES.md implemented in part): the runtime manifest carries styles/default_style; boot applies the default style's theme (Mob.Plugins.apply_default_style/0). The five preset themes ship in the mob_themes package.
  • Boot-time plugin NIF loading (mob_notify_set_screen_pid seam, host_requirements printing, composites boot registration) — the plugin-system core wiring landed across this cycle; see MOB_PLUGINS.md.

Removed (BREAKING — each capability moves to its plugin package)

  • Mob.Cameramob_camera (the camera_preview node stays in core)
  • Mob.Locationmob_location
  • Mob.Notifymob_notify (delivery plumbing — delegate, push-token forward, launch handoff — stays in core; pairs with the server-side mob_push)
  • Mob.Photosmob_photos
  • Mob.Biometricmob_biometric
  • Mob.Scannermob_scanner (requires mob_camera for the :camera permission)
  • Mob.Btmob_bluetooth (Wave 1)
  • Themes Obsidian/ObsidianGlass/Citrus/Birch/Material3mob_themes (light/dark/adaptive remain the neutral baseline)
    No deprecation shims (see plugin_extraction_plan.md for the policy rationale). Migration: add the package dep + activate in mob.exs; module names change (Mob.CameraMobCamera, Mob.Theme.CitrusMobThemes.Citrus, …).

0.6.26

01 Jun 04:15

Choose a tag to compare

Fixed

  • iOS: stop capping the literal super-carrier at 10 MB. mob_beam.m appended a hardcoded -MIscs 10 after the configured flags; since allocator flags are last-wins, it silently overrode the 0.6.24 -MIscs 128 default (and any mob_beam_flags override), so the literal area was always 10 MB. A large app (e.g. embedded Livebook) plus a notebook's Mix.install filled it and the VM aborted with literal_alloc: Cannot allocate .... Removed the hardcoded cap; the -MIscs 128 default now takes effect (iOS accepts a 128 MB reservation). Verified on a physical iPhone: emu_args shows a single -MIscs 128 and Mix.install returns :ok.

0.6.25

31 May 19:36

Choose a tag to compare

Added

  • "Open with" — receive a file another app opens into yours. New Mob.Files.take_opened_document/0 returns %{path, name, mime, size} (or :none) for a file handed to the app (e.g. a notebook emailed and tapped), parallel to Mob.Files.pick/2's {:files, :picked, …}. Call it from your root screen's mount/3; a file opened while already running arrives as {:files, :opened, item} (iOS). New NIF take_opened_document plus C-export mob_set_opened_document on both platforms (iOS application:openURL:options:mob_handle_opened_url; Android MainActivity reads the ACTION_VIEW/SEND intent → MobBridge.setOpenedDocument). The app declares the document type (iOS CFBundleDocumentTypes, Android <intent-filter>) and forwards the open. Verified end-to-end: a .livemd opened into the embedded-Livebook app opens as a notebook on a physical iPhone and a physical Android (Moto G).

0.6.24

31 May 06:22

Choose a tag to compare

Fixed

  • iOS: enlarge the BEAM literal super-carrier to 128 MB (-MIscs 128 default flag). iOS can't reserve the OTP default 1 GB literal virtual area and falls back to ~10 MB. A large app such as an embedded Livebook plus a notebook's Mix.install fills that 10 MB and the VM aborts with literal_alloc: Cannot allocate N bytes (of type "literal"). The iOS native launcher's default flags now request a 128 MB literal carrier — a virtual MAP_NORESERVE reservation (commits physical only on use) that iOS accepts where 1 GB fails. Apps no longer need a per-app beam_flags: override for this. iOS-only; Android keeps its normal large carrier. A runtime mob_beam_flags override still wins. Verified on a physical iPhone: embedded Livebook serves and Mix.install([{:short_uuid, "~> 0.1"}]) returns :ok.

0.6.23

29 May 23:37

Choose a tag to compare

Added

  • Element positions without a screenshot. element_frames/0 NIF surfaced as Mob.Test.element_frames/1 (%{id => {x,y,w,h}}), frame/2, and tap_id/2 (drive by id at real coordinates). Any rendered node given an :id reports its live on-screen frame (logical points iOS / dp Android) to a registry the agent reads over dist — a compact structured map instead of image bytes, with no accessibility activation. The renderer also sets the :id as the element's accessibility identifier (iOS accessibilityIdentifier, Android Compose testTag), so the same tags are visible to XCUITest/Espresso. Opt-in per element: untagged nodes cost nothing (the tracking modifier only attaches when an :id is present). iOS records the full element frame via a GeometryReader background; Android via Modifier.onGloballyPositioned. Verified on iOS sim, Android device, and a physical iPhone. The Android Kotlin side lives in the mob_new MobBridge.kt.eex template.
  • In-process screenshot + scroll control over dist (no adb/xcrun). Three test-harness NIFs (screenshot/3, scroll_info/1, scroll_to/3) surfaced as Mob.Test.screenshot/2, scroll_info/2, scroll_to/4, and screenshot_tour/3. A remotely-connected agent gets pixels and deterministic scroll entirely over Erlang distribution — the capability Sloppy Joe and WireTap need to drive a device an agent can only reach over dist. Capture is in-process (iOS UIGraphicsImageRenderer + drawViewHierarchy; Android PixelCopy against the activity window). Scroll views are addressed by their :id prop; scroll_info reports kind: :pixel (iOS UIScrollView, Android verticalScroll) or :index (Android LazyColumn, where y is an item index and viewport is the visible-item count). Captures the app's own surface only — FLAG_SECURE/secure fields render blank, and a backgrounded app returns {:error, :no_window}. The Android Kotlin side (screenshot/scrollInfo/scrollTo) lives in the mob_new MobBridge.kt.eex template; existing apps pick it up on regeneration. Debug-only (iOS #if !MOB_RELEASE). See decisions/2026-05-29-bridge-nif-screenshot-scroll.md.

Changed

  • Mob.Bt extracted to standalone mob_bluetooth plugin. See plugin_extraction_plan.md Wave 1. Session A moved the Elixir wrappers (Mob.Bt, Mob.Bt.Hfp, Mob.Bt.Hid, Mob.Bt.Spp) out of core into a separate repo as MobBluetooth.*; the Zig NIF (android/jni/mob_nif.zig) and the iOS stubs (ios/mob_nif.m) stay here until Session B promotes the plugin to tier-1. Apps that used Mob.Bt.* should add {:mob_bluetooth, path: "..."} and rename their references to MobBluetooth.* — there is intentionally no compatibility shim.

OTP pre-built runtime 7d46fdd4

29 May 08:49

Choose a tag to compare

Pre-built OTP for Android (aarch64 + arm32), iOS simulator (aarch64-apple-iossimulator), and iOS device (aarch64-apple-ios). OTP source commit: 7d46fdd4.

0.6.22

28 May 19:40

Choose a tag to compare

Added

  • Mob.Certs — load CA certificates from a PEM bundle into Erlang's :public_key cacert store. Android's system trust store lives behind a Java API that :public_key.cacerts_load/0 (no-arg) can't reach, so the first TLS call from Req / Mint / Finch crashes with no_cacerts_found (or FunctionClauseError in some OTP versions). Apps bundle a PEM (conventional source: copy castore's cacerts.pem into priv/ at build time) and call Mob.Certs.load_cacerts!(Application.app_dir(:my_app, "priv/cacerts.pem")) once at boot. iOS and the Android emulator aren't affected; calling unconditionally is harmless there. Verified end-to-end on a Moto G Power 5G 2024 (Android 14): Mix.install([{:req, "~> 0.5"}]) then Req.get!("https://geocoding-api.open-meteo.com/v1/search?name=Vancouver") returns 200.
  • mob_beam.zig exports MOB_NATIVE_LIB_DIR before BEAM start — the absolute path of the app's nativeLibraryDir, which the APK install hash makes unpredictable at compile time. Apps that bundle runtime binaries (escript, rebar3, etc.) as lib*.so need this to set MIX_REBAR3 and locate the bundled escripts.
  • Optional ERTS-extras symlinks (escript / erlexec / erl / beam.smp) in mob_beam.zig. Silent-skips when the lib isn't in nativeLibDir, so non-opting-in apps see no behaviour change. Apps that drop lib<name>.so into android/app/src/main/jniLibs/<abi>/ get a working BINDIR/<name> — enough for runtime Mix.install of rebar3-built deps (telemetry, jose, jiffy, …) to bootstrap a fresh VM. erl and erlexec both target the same liberlexec.so because they are the same binary (erlexec doesn't switch on argv[0]).

Changed

  • extra_applications: [:logger, :public_key] — Elixir 1.19+ strips unused OTP applications from the code path; Mob.Certs calls :public_key.cacerts_load/1 at runtime, so its .beam must be in the path even though mob doesn't start :public_key itself.

Fixed

  • mix.exs — collapsed duplicate before_closing_body_tag/1 clauses introduced in 0.6.20. The mermaid clause's _ catchall shadowed an older language-elixir highlighter clause, leaving it as dead code (and emitting compile warnings). The unified clause emits both scripts; the duplicate docs/0 keyword entry was removed.

Docs

  • common_fixes.md — new section documenting the Android cacerts symptom (no_cacerts_found / FunctionClauseError) and the load-PEM-at-boot fix; also the bundled-OTP-extras pattern (wrapper script, rebar3 module-name derivation, $ROOTDIR/bin/*.boot materialization) for apps that opt into runtime rebar3.

0.6.21

28 May 15:01

Choose a tag to compare

Added

  • Mob.DNS.resolve/1 now works on Android. nif_resolve_ipv4 (android/jni/mob_nif.zig) calls Bionic's getaddrinfo in-process and seeds :inet_db's :file table, mirroring the iOS NIF added in #32. Physical Android devices return :nxdomain from BEAM's default DNS path (forking inet_gethost as a port program) even when the same app's in-process HTTPS stack resolves the hostname fine — the emulator masks this. Verified end-to-end on a Moto G Power 5G 2024 (Android 14): Mob.DNS.resolve("repo.hex.pm") returns the right IP, :inet.getaddr/2 then succeeds via the seeded entry, and Mix.install([{:dep, "~> ..."}]) from a notebook setup cell resolves, fetches, and compiles on-device. Bionic addrinfo / sockaddr_in / getaddrinfo / freeaddrinfo / EAI_* bindings added to android/jni/mob_zig.zig. Suspected root cause is libnetd_client.so's netd routing not surviving execve; the NIF sidesteps it by running in the app's own process.

Changed

  • Mob.DNS moduledoc — dropped the "Android isn't affected" claim. Added a background-app caveat: Android App Standby blocks all outbound network from a backgrounded mob app (TCP-by-IP, not just DNS — surfaces as :closed / :timeout on any socket attempt). Fix is a foreground service or keep the app foregrounded; not a mob bug.

Docs

  • common_fixes.md — new section documenting the :nxdomain symptom on physical Android, the foreground-app caveat, and the fix.

0.6.20

27 May 15:17

Choose a tag to compare

Full Changelog: 0.6.19...0.6.20

0.6.19

27 May 13:57

Choose a tag to compare

What's Changed

New Contributors

Full Changelog: 0.6.18...0.6.19