# The app: everything that is about *this product* rather than about the UI # framework it draws with. One package, because splitting it was buying # nothing -- see `docs/RUST.md`'s "One app crate" for the account. In short: # `client` (the REST/SSE clients, the transcript cache and fold, the # highlighter) and `ui` (the screens, in iris widgets) only ever ship # together, and the three entry points below are three faces of one binary # rather than three programs. # # `iris/` is the framework and knows nothing about any of this; the # dependency runs one way, and a widget or a colour appearing here that is # not about a session, a transcript or a setup belongs there instead # (AGENTS.md). [package] name = "ai-app" version = "0.1.0" edition = "2024" # `cdylib` is the Android face -- both the iris app (`android-project/`, # `System.loadLibrary("ai_app")`) and the JNI bridge the Kotlin shell in # `app/shellApp` calls (the `shell` feature). `rlib` is what the desktop # binary, the examples and `tests/` link against. One package produces one # library artifact, so the two Android apps share a `.so` name and pick # what goes in it with features rather than with a second crate. [lib] name = "ai_app" crate-type = ["cdylib", "rlib"] [[bin]] name = "ai-app-desktop" path = "src/bin_desktop.rs" required-features = ["screens"] [[example]] name = "transcript" required-features = ["screens"] [[example]] name = "phone" required-features = ["fixture"] [dependencies] # The event model, shared with `server/` so the two agree by construction. # It stays a crate of its own at the repo root for exactly that reason: # it is the contract between this app and the backend, not app code. event-model = { path = "../event-model" } serde = { version = "1", features = ["derive"] } # `float_roundtrip` for the same reason `server/` sets it -- AGENTS.md's # "Things that have bitten". `raw_value` for the transcript cache. serde_json = { version = "1", features = ["float_roundtrip", "raw_value"] } ureq = { version = "3", features = ["json"] } pulldown-cmark = "0.13.4" base64 = "0.23" log = { version = "0.4.34", features = ["std"] } # The UI framework. Optional so `--no-default-features --features shell` # builds the Kotlin shell's JNI bridge without linking wgpu, parley and # the rest of a renderer into an APK that draws with Compose. iris = { path = "../iris", optional = true } # iris's own tabs demo, kept runnable on Android through this project's # Gradle app (the `tabs-screen` feature). The dependency direction is the # right way round: the app may reach into the framework's example widget # tree, never the reverse. tabs-ui = { path = "../iris/tabs-ui", optional = true } jni = { version = "0.22", optional = true } # `bench` only: `libc` for the process CPU-time and RSS samples, `tokio` # for the run's own timer. libc = { version = "0.2.189", optional = true } tokio = { version = "1.53.1", features = ["rt", "time"], optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] winit = "0.30.13" # Pinned to the exact commit RUST.md's E1 measured on this emulator; see # `iris/Cargo.toml`'s copy of this pin for what advancing it costs. [target.'cfg(target_os = "android")'.dependencies] android-view = { git = "https://github.com/rust-mobile/android-view.git", rev = "bec6c62a96cef8239b0fd7fedeef9b184d02e3a1" } android_logger = "0.15.1" [features] default = ["screens", "fixture"] # The iris half: `src/ui` and everything that draws. Off for the Kotlin # shell's bridge, which is JNI and `src/client` only. screens = ["dep:iris"] # `src/ui/fixture.rs` and the harness tests that drive it. Default-on so # `cargo test` covers them; `build-apk.sh` passes `--no-default-features` # so an APK carries the 1.9 MB fixture only when it asked for `bench`. fixture = ["screens"] # The two Android widget trees, on the same axis: a build picks one. transcript-screen = ["screens"] tabs-screen = ["screens", "dep:tabs-ui"] # P0's iris half (docs/RUST.md): the fixture screen with a "Run benchmark" # control, driving the same scroll loop and streaming phase the Compose # bench build type does. bench = ["transcript-screen", "fixture", "dep:libc", "dep:tokio"] # The JNI bridge `app/shellApp` calls -- notifications, the share target # and the Keystore-sealed settings. shell = ["dep:jni"] # See `iris/Cargo.toml`'s feature of the same name. Never for a phone. force-gles = ["screens", "iris/force-gles"] [dev-dependencies] tempfile = "3" tokio = { version = "1.53.1", features = ["rt", "time"] } # The Android builds, kept off `release`/`dev` so a desktop build is not # also optimised for size and unwinding is not also turned off for the # tests. `build-apk.sh` passes `--profile android-release`. [profile.android-release] inherits = "release" panic = "abort" strip = true lto = "fat" codegen-units = 1 # **Speed, not size** (2026-09-09). This was `"s"`, chosen when the # question was why the APK was double the Compose one -- but that was # measured in bytes only, and `"s"` costs the loop vectorisation and # inlining a renderer runs on. Measured with # `app-rust/tests/fling_profile.rs`, the same warm fling eight times over: # iris's own per-frame work is p90 0.15ms / p99 0.42ms at `"s"` and # p90 0.09ms / p99 0.26ms at `3`, so about a third of the CPU half of a # scrolling frame was being paid for 1.9 MB of download. The same # argument the table in docs/RUST.md gives for refusing `"z"`, applied one # level further up. opt-level = 3 [profile.android-dev] inherits = "dev" panic = "abort" # Same reasoning as `iris/Cargo.toml`'s copy: full DWARF in every test # binary is what made `cargo test` here write tens of gigabytes. [profile.dev] debug = "line-tables-only" [profile.test] debug = "line-tables-only" # The headless harness tests (`iris::harness`, no window and no GPU) all # open the bench fixture, so they say so rather than failing to compile # when it is off. [[test]] name = "catch_a_fling" required-features = ["fixture"] [[test]] name = "fence_fling" required-features = ["fixture"] [[test]] name = "gesture_cancel" required-features = ["fixture"] [[test]] name = "input_log_roundtrip" required-features = ["fixture"] [[test]] name = "phone_screen" required-features = ["fixture"] [[test]] name = "top_edge" required-features = ["fixture"]