All notable changes to mob are documented here.
Format: Keep a Changelog. Versioning: SemVer.
Full module documentation: hexdocs.pm/mob.
[0.6.26]
Fixed
- iOS: stop capping the literal super-carrier at 10 MB.
mob_beam.mappended a hardcoded-MIscs 10after the configured flags; since allocator flags are last-wins, it silently overrode the 0.6.24-MIscs 128default (and anymob_beam_flagsoverride), so the literal area was always 10 MB. A large app (e.g. embedded Livebook) plus a notebook'sMix.installfilled it and the VM aborted withliteral_alloc: Cannot allocate .... Removed the hardcoded cap; the-MIscs 128default now takes effect (iOS accepts a 128 MB reservation). Verified on a physical iPhone:emu_argsshows a single-MIscs 128andMix.installreturns:ok.
[0.6.25]
Added
- "Open with" — receive a file another app opens into yours. New
Mob.Files.take_opened_document/0returns%{path, name, mime, size}(or:none) for a file handed to the app (e.g. a notebook emailed and tapped), parallel toMob.Files.pick/2's{:files, :picked, …}. Call it from your root screen'smount/3; a file opened while already running arrives as{:files, :opened, item}(iOS). New NIFtake_opened_documentplus C-exportmob_set_opened_documenton both platforms (iOSapplication:openURL:options:→mob_handle_opened_url; AndroidMainActivityreads the ACTION_VIEW/SEND intent →MobBridge.setOpenedDocument). The app declares the document type (iOSCFBundleDocumentTypes, Android<intent-filter>) and forwards the open. Verified end-to-end: a.livemdopened into the embedded-Livebook app opens as a notebook on a physical iPhone and a physical Android (Moto G).
[0.6.24]
Fixed
- iOS: enlarge the BEAM literal super-carrier to 128 MB (
-MIscs 128default 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'sMix.installfills that 10 MB and the VM aborts withliteral_alloc: Cannot allocate N bytes (of type "literal"). The iOS native launcher's default flags now request a 128 MB literal carrier — a virtualMAP_NORESERVEreservation (commits physical only on use) that iOS accepts where 1 GB fails. Apps no longer need a per-appbeam_flags:override for this. iOS-only; Android keeps its normal large carrier. A runtimemob_beam_flagsoverride still wins. Verified on a physical iPhone: embedded Livebook serves andMix.install([{:short_uuid, "~> 0.1"}])returns:ok.
[0.6.23]
Added
- Element positions without a screenshot.
element_frames/0NIF surfaced asMob.Test.element_frames/1(%{id => {x,y,w,h}}),frame/2, andtap_id/2(drive by id at real coordinates). Any rendered node given an:idreports 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:idas the element's accessibility identifier (iOSaccessibilityIdentifier, Android ComposetestTag), so the same tags are visible to XCUITest/Espresso. Opt-in per element: untagged nodes cost nothing (the tracking modifier only attaches when an:idis present). iOS records the full element frame via aGeometryReaderbackground; Android viaModifier.onGloballyPositioned. Verified on iOS sim, Android device, and a physical iPhone. The Android Kotlin side lives in themob_newMobBridge.kt.eextemplate. - In-process screenshot + scroll control over dist (no adb/xcrun). Three test-harness NIFs (
screenshot/3,scroll_info/1,scroll_to/3) surfaced asMob.Test.screenshot/2,scroll_info/2,scroll_to/4, andscreenshot_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 (iOSUIGraphicsImageRenderer+drawViewHierarchy; AndroidPixelCopyagainst the activity window). Scroll views are addressed by their:idprop;scroll_inforeportskind: :pixel(iOSUIScrollView, AndroidverticalScroll) or:index(AndroidLazyColumn, 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 themob_newMobBridge.kt.eextemplate; existing apps pick it up on regeneration. Debug-only (iOS#if !MOB_RELEASE). Seedecisions/2026-05-29-bridge-nif-screenshot-scroll.md.
Changed
Mob.Btextracted to standalonemob_bluetoothplugin. Seeplugin_extraction_plan.mdWave 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 asMobBluetooth.*; 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 usedMob.Bt.*should add{:mob_bluetooth, path: "..."}and rename their references toMobBluetooth.*— there is intentionally no compatibility shim.
[0.6.22]
Added
Mob.Certs— load CA certificates from a PEM bundle into Erlang's:public_keycacert 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 withno_cacerts_found(orFunctionClauseErrorin some OTP versions). Apps bundle a PEM (conventional source: copycastore'scacerts.pemintopriv/at build time) and callMob.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"}])thenReq.get!("https://geocoding-api.open-meteo.com/v1/search?name=Vancouver")returns200.mob_beam.zigexportsMOB_NATIVE_LIB_DIRbefore 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.) aslib*.soneed this to setMIX_REBAR3and locate the bundled escripts.- Optional ERTS-extras symlinks (
escript/erlexec/erl/beam.smp) inmob_beam.zig. Silent-skips when the lib isn't in nativeLibDir, so non-opting-in apps see no behaviour change. Apps that droplib<name>.sointoandroid/app/src/main/jniLibs/<abi>/get a workingBINDIR/<name>— enough for runtimeMix.installof rebar3-built deps (telemetry, jose, jiffy, …) to bootstrap a fresh VM.erlanderlexecboth target the sameliberlexec.sobecause they are the same binary (erlexec doesn't switch onargv[0]).
Changed
extra_applications: [:logger, :public_key]— Elixir 1.19+ strips unused OTP applications from the code path;Mob.Certscalls:public_key.cacerts_load/1at runtime, so its.beammust be in the path even though mob doesn't start:public_keyitself.
Fixed
mix.exs— collapsed duplicatebefore_closing_body_tag/1clauses 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 duplicatedocs/0keyword 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/*.bootmaterialization) for apps that opt into runtime rebar3.
[0.6.21]
Added
Mob.DNS.resolve/1now works on Android.nif_resolve_ipv4(android/jni/mob_nif.zig) calls Bionic'sgetaddrinfoin-process and seeds:inet_db's:filetable, mirroring the iOS NIF added in #32. Physical Android devices return:nxdomainfrom BEAM's default DNS path (forkinginet_gethostas 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/2then succeeds via the seeded entry, andMix.install([{:dep, "~> ..."}])from a notebook setup cell resolves, fetches, and compiles on-device. Bionicaddrinfo/sockaddr_in/getaddrinfo/freeaddrinfo/EAI_*bindings added toandroid/jni/mob_zig.zig. Suspected root cause islibnetd_client.so's netd routing not surviving execve; the NIF sidesteps it by running in the app's own process.
Changed
Mob.DNSmoduledoc — 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/:timeouton 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:nxdomainsymptom on physical Android, the foreground-app caveat, and the fix.
[0.6.18]
Changed
RUSTLER_NIF_LIB_PATH→RUSTLER_BEAM_LIBRARY_PATHinmob_beam.zig's host setenv block. Matches the env var name filmor chose for the alternative upstream rustler PR (rusterlium/rustler#733), which is what'll land upstream instead of our #726. End-to-end tested on physical arm64 Android with filmor's branch: Mob sets the env var → rustler reads it → Rust NIF resolves and executes. Mob users on rustler 0.37 Hex release (no patch) see no change; users on the GenericJam fork OR on whatever rustler version eventually ships #733 get matching behaviour.
[0.6.17]
Added
Mob.Audio.play_at/4— sample-accurate scheduled audio playback. Takes an absolute local wall-clock target (System.system_time(:millisecond)ms-since-epoch) and hands it to the audio hardware clock for firing, rather than waking the BEAM viaProcess.send_after. The hardware-clock path eliminates timer-wheel + scheduler jitter from the end-to-end sync error, leaving per-device first-sample latency (~30–80 ms, calibratable) as the dominant remaining term. iOS only in this release; Android still falls through to the existingMediaPlayerpath (port to AAudio is pending).- iOS:
nif_audio_play_at(Path, OptsJson, AtWallMs)backed by a dedicatedAVAudioEngine+AVAudioPlayerNode. The wall-time target is converted to anAVAudioTimehostTimeviamach_absolute_time+mach_timebase_info, then handed to-[AVAudioPlayerNode scheduleBuffer:atTime:options:completionHandler:]. Past targets schedule ASAP. Multipleplay_atcalls accumulate on the player's timeline — useaudio_stop_playbackto flush. audio_set_volumeandaudio_stop_playbacknow also reach the scheduled-engine player so cross-API mixing behaves sanely.
Use case
- Distributed orchestra / multi-device musical performance where every phone must start the same sample at the same wall-clock instant. Pair with an NTP-style server-clock-sync helper on the caller side; this API takes the converted local-clock target.
[0.6.16]
Added
mob_beam.zigexportsRUSTLER_NIF_LIB_PATHbefore BEAM start. Callsdladdr(&mob_start_beam)to discover the absolute path of the host.so(e.g.lib<app>.so) andsetenv()s it asRUSTLER_NIF_LIB_PATH. Pairs with the matching upstream rustler change (rusterlium/rustler#726): rustler'sDlsymNifFiller::new()on Android reads the env var first, falls back to its existing dladdr-self probe when unset. End result: rustler-based Rust NIFs statically linked into Mob's main.sonow resolveenif_*symbols correctly on Bionic without any per-app patching. Existing rustler users on Android who don't run inside Mob see no change — the dladdr fallback covers them.mob_zig.zigexposesdladdr+DlInfoto other Zig consumers underjni.dladdr/jni.DlInfo. Hand-declared to match the libc/Bionic surface; same hand-declared FFI policy as the rest ofmob_zig.zig(we don't use@cImporthere).
Notes
- The setenv runs unconditionally — even apps that don't ship a rustler NIF get the env var set. Harmless. The env var only affects rustler's own startup logic when a rustler-built NIF loads.
Verified end-to-end on a physical arm64 Android device (moto g power 2021): host sets path → rustler reads env var →
dlopen(path, RTLD_NOW | RTLD_NOLOAD)→dlsymallenif_*exports → Rust NIFgreet/0executes and returns"Hello from Rust!"to BEAM.
[0.6.15]
Added
text_fieldnow accepts asecure: trueprop. iOS renders the field as a SwiftUISecureField(masked input) instead of the plainTextField. The prop flows through the existing renderer passthrough; cleartext still reaches the BEAM viaon_changeso apps can hash/store the value as normal. Android consumes the same prop viaPasswordVisualTransformationoncemob_new'sMobBridge.kt.eextemplate is updated in a companion PR — until then the prop is a graceful no-op on Android (renders as a regular field), no breakage.Reveal-toggle ("eye" button) is intentionally deferred — its interaction with SwiftUI focus retention requires a
ZStack-and-opacity rebuild ofMobTextFieldand warrants its own change.
Fixed
- iOS:
Mob.App.start/0now switches:inet_dbto file-only lookup and seedslocalhostbefore any user code runs — BEAM's default:nativelookup tries toexecvetheinet_gethostport program, which the iOS sandbox refuses, crashing the firstNode.connect/:erpc.call/gen_tcp.connect/3with:badarg. Apps no longer need to set the lookup chain themselves;Mob.DNS.configure_pure_beam/1still composes on top for outbound DNS. Seeguides/dns_on_ios.md. - iOS:
Columnnow honoursfill_height: true. The.columncase inMobRootViewonly setmaxWidth, so aColumnwithfill_height: truewould collapse to its children's natural height — breaking the canonical<Column fill_width fill_height>header/flex/footer pattern. Now setsmaxHeight: .infinitywhen the prop is set and switches alignment to.topLeadingso children anchor at the top when the column flexes. Default (nofill_height) behavior is unchanged.
Docs
- Plugin system design corpus:
MOB_PLUGINS.md(capability-plugin manifest, tiers 0-4, spec-v2 code-generated plugins),MOB_STYLES.md(style preset system, namespaced cherry-pick, stable per-primitive prop contract),MOB_PLUGIN_SECURITY.md(three-layer trust model, dev-mode escape hatches,:acknowledge_unsafe_plugins),plugin_extraction_plan.md(Phase 0 → Phase 3 + risk register + kickoff checklist). Locks scope to Elixir-first, BEAM-native, Gen-AI-enabled; parks full-language non-BEAM frontends at speculativeplugin_spec_version: 3. Companionagent_briefs/rustler_env_var_test.mdcovers filmor's env-var-based fix inrusterlium/rustler#726.
[0.6.14]
Added
:mob_nif.set_theme/1— push resolved theme palette to native. Lets a ComposeMaterialThemewrapper follow runtimeMob.Theme.set(...)calls instead of being baked into MainActivity at compile time. Otherwise Material 3 system chrome (NavigationBar, Button, etc.) stays at the default light scheme while the BEAM-side primitives switch to whatever theme is active — a visible mismatch when an app uses Obsidian / ObsidianGlass.Mob.Theme.resolved_palette/1— exposes the "semantic token → theme map → palette → ARGB int" resolution path that the renderer uses internally. The native side gets concrete integers it can hand toColor(...)directly.
Notes
- iOS implements the NIF as a no-op for symmetry — SwiftUI in
MobRootView.swiftrenders every surface via mob primitives with explicit color props, so there's no system chrome that needs the push. - The Android
MobBridge.setTheme(String)Java hook is looked up viacacheOptional, so older templates that predate this load fine; the NIF just returns:okwithout dispatching when the method isn't on the bridge. - The mob_new generator templates that wire
MaterialTheme↔setThemein newly-generated apps will follow in a separate release; existing apps adopt manually (aMutableStatein MobBridge.kt +MaterialTheme(colorScheme = …)wrap in MainActivity.kt).
[0.6.13]
Changed
- Liquid Glass uses
Glass.clearinstead ofGlass.regular. On dark surfaces with little behind a card to refract,.regularreads as a frosted plate rather than glass..clearis the right variant for the floating-card look the theme is meant to evoke — what's beneath shows through, the card looks like it's hovering. Only affects iOS 26+ (the.ultraThinMaterialfallback for older iOS is unchanged).
[0.6.12]
Added
Mob.Theme—glassflag for translucent surfaces. Newglass: falsefield on the theme struct. When set,Mob.Renderertags everyBoxnode that has abackground:withglass: true, and the iOS side swaps the solid fill for.glassEffect(.regular, in: shape)on iOS 26+ (real Liquid Glass) or.ultraThinMaterialon iOS 17–25 (closest fallback that ships in older SDKs). Other nodes pass through untouched. Opt in via a preset or by passingglass: truetoMob.Theme.build/1.Mob.Theme.ObsidianGlass— Obsidian palette +glass: truefor the common "make the whole app glassy" case. Switch at runtime withMob.Theme.set(Mob.Theme.ObsidianGlass); revert withMob.Theme.set(Mob.Theme.Obsidian).Mob.Theme.flags_map/1— companion tocolor_map/1/spacing_map/1/radius_map/1. Returns%{glass: bool}for now; future flag-style toggles will land here.
Notes
- Android receives the flag but ignores it for now — Compose Material 3 doesn't ship a first-class glassy surface yet; boxes fall back to solid. Compose-side support is a follow-up.
[0.6.11]
Fixed
~MOBsigil no longer double-encodes non-ASCII bytes in template source. The NimbleParsec parser usedascii_string/2for string attribute values (text="...") and brace content (text={...}); itsinteger-typed body re-encoded each source byte ≥128 as a Latin-1 codepoint then UTF-8. Net effect:–(E2 80 93) emerged asÂ+pad+O(C3 A2 C2 80 C2 93) — mojibake on screen. Swapped both call sites toutf8_string/2, which matches by codepoint and round-trips multi-byte sequences (em-dash, en-dash, middle dot, smart quotes, accents, emoji) byte-for-byte. Workaround that's now unnecessary: binding the non-ASCII string to a variable outside the sigil and referencing it viatext={var}.
[0.6.10]
Added
- iOS BEAM startup honours
MOB_NODE_SUFFIXenv var. The simulator branch already auto-derived a unique node-name suffix fromSIMULATOR_UDIDso concurrent sims didn't collide in Mac's EPMD, but there was no manual override path — the Android-sideMOB_NODE_SUFFIXconvention was iOS-blind. Now both branches (simulator + physical device) readMOB_NODE_SUFFIXwith priority: explicit env → SIMULATORUDID-derived (sim only) → none. Pairs withmob_dev 0.5.10'smix mob.deploy --node-suffix Xflag (forwarded to simctl via the `SIMCTL_CHILD*` mechanism). - Resolves the
Protocol 'inet_tcp': register/listen error: no_reg_reply_from_epmdsymptom seen when running multiple iOS sims of the same app concurrently for visual-comparison work (e.g. cross-platform theme parity).
[0.6.9]
Fixed
- CI pipeline unblocked. The 0.6.8 push failed two CI gates and never
reached Hex; this release ships the same code with the gates green:
android/jni/mob_beam.hreformatted to satisfyxcrun clang-format --dry-run -Werror(the camera-frame delivery declaration was split across three lines in a style clang-format wanted on two).decimalbumped 2.4.0 → 3.1.0 (transitive viaecto_sqlite3/jason) to clear advisory GHSA-rhv4-8758-jx7v — unbounded exponent inDecimal.new/1enables an unauthenticated DoS, affects< 3.0.0.jasonbumped 1.4.4 → 1.4.5 since older Jason cappeddecimalto~> 1.0 or ~> 2.0.
No source-level changes since 0.6.8 — same Mob.Camera.start_frame_stream/2
Android implementation and Mob.Canvas viewport docs, now actually on Hex.
[0.6.8]
Added
Mob.Camera.start_frame_stream/2now works on Android. The Camera2 + CameraXImageAnalysisuse case is wired through to BEAM as{:camera, :frame, %{bytes, width, height, format, timestamp_ms, dropped}}messages. Previously this NIF returned:unsupportedon Android — iOS-only. The Android implementation supports the sameformat: :rgb_f32the iOS side does (:bgra_u8planned for a follow-up).Mob.Canvasmoduledoc documents the viewport-scaling contract: thewidth/heightprops are logical viewport units, NOT pixels. The renderer scales draw-op coordinates against the actual on-screen pixel size. New tests intest/mob/canvas_test.exspin the contract so future readers don't regress to interpreting them as raw pixels.
Notes
- Combined with
mob_dev 0.5.9'smix mob.enable tfliteand thenx_tflite_mob 0.0.3Hex package, the cross-platform live YOLO demo (mob_yolo_demo) now runs end-to-end with only Hex deps. Measured perf: 24 ms iPhone SE A15 via Core ML → ANE; 75–117 ms Moto G Power 5G (Dimensity / BXM-8-256) via NNAPI /mtk-gpu_shim.
[0.6.7]
Added
guides/mobile_surface_matrix.md— comprehensive audit of mob's mobile capability surface vs. React Native + Expo SDK reference. Tables across UI components, gestures/input, device/system, storage, camera/audio, connectivity, sensors, location, notifications, background tasks, auth/payment, ML/Vision, maps, accessibility, iOS-only, Android-only, plus an "architecturally not present" section. Per-row status (✅ / 🟡 / ❌ / ⛔) with iOS + Android indicators. Hand-maintained from inspection oflib/mob/andsrc/mob_nif.erl. Sets realistic expectations and surfaces plugin candidates.- README link + hexdocs entry so the matrix is discoverable for new users.
RELEASE.md"Tests + docs for new functionality" section now includes amix docspreview step and clarifies that hexdocs publishing is automatic viamix hex.publish(rides along from the previously-unreleased doc improvement).MOB_PLUGINS.md— plugin manifest schema spec covering five plugin tiers (pure Elixir helper through embedded sub-app), worked examples per tier, install + activation flow, schema reference, validation rules, hot-push compatibility table, plugin_spec_version forward-compat. References from the matrix's ❌ rows as plugin candidates.
[0.6.6]
Added
RELEASE.md— canonical release-process documentation covering the mix.exs-driven trigger model, the patch-bump-default-with-mandatory- permission rule, CHANGELOG conventions, when a bump is warranted (new functionality, bug fixes, doc improvements, dep bumps) vs. when it isn't (CI tweaks, hook changes, internal refactors), the tests-and-docs-with-new-functionality non-negotiables, and the per-step idempotency ofrelease.yml. Linked frommob_devandmob_newCLAUDE.md by URL so the canonical process is one file..githooks/pre-push— committed pre-push hook that runs the cheap preflight (format + credo + warnings-as-errors) on every push and the full release preflight (test suite +mob.security_scanwhere present) only whenmix.exschanged. Activate per-clone withgit config core.hooksPath .githooks.CLAUDE.md"Release flow" section linking to the new docs.
[0.6.5]
Fixed
- HexDocs source links pointed at the non-existent
mainbranch — corrected tomasterso each</>glyph next to a heading now opens the actual source file in the GitHub repo. mob_nif.zigcalled the variadicenif_make_list/2(not exposed inmob_erts.zig) from the BT paired-list finisher; the Android arm64 build failed at link. Switched to the non-variadicenif_make_list_from_array(env, &empty, 0).
Added
.github/workflows/test.yml— runsmix test,mix format --check-formatted,mix credo --strict,mix erlfmt --check src/,xcrun clang-format,swiftlint, andmix deps.auditon push to master and on every PR..github/workflows/release.yml— on tag push, creates a GitHub Release whose body is the matching## [X.Y.Z]section from this changelog (falls back to auto-generated commit notes if the tag has no section).PLAN.md— three-layer CI + integration-test plan covering the gap between unit tests and on-device verification.
[0.6.4]
Added
Mob.GpuView/Mob.UI.gpu_view/1— Metal fragment-shader surface on iOS. Host owns the vertex shader (full-screen quad withv_uv); user supplies an MSL fragment shader plus a list of uniforms packed at natural alignment into fragment-buffer slot 0. SwiftUIMobGpuViewwraps anMTKViewwith a hash-keyed shader cache and a translucent red overlay for compile errors. iOS-only in this release; the Android GLES 3.0 backend ships in mob_new 0.3.1.<GpuView>tag whitelisted for bothpriv/tags/ios.txtandpriv/tags/android.txt.
[0.6.3]
Fixed
- iOS camera sensor delivered frames in landscape-right by default —
Mob.Camera.start_frame_stream/2was feeding 90°-rotated pixels to ML models, dropping classification accuracy enough that a jar appeared as "laptop 24%" instead of "cup 96%".AVCaptureConnection.videoRotationAngle = 90(iOS 17+) /videoOrientation = .portrait(older) is now set on both the preview layer and the data-output connection, so what the user sees and what the model sees are the same upright frame.
[0.6.2]
Added
Mob.Camera.start_frame_stream/2andstop_frame_stream/1— push-driven per-frame delivery as{:camera, :frame, %{bytes, width, height, format, timestamp_ms, dropped}}. Defaults to 640×640rgb_f32for direct Nx hand-off; caller-overridable width/height/format/facing and a softwarethrottle_msgate.
Changed
- iOS camera now uses a single shared
AVCaptureSessionfor preview and frame stream. The previous two-session design silently dropped frames because iOS allows only one active session per physical camera.
[0.6.1] and earlier
Earlier releases predate this changelog; consult the tag list and the per-tag commit messages for history.