diff --git a/AGENTS.md b/AGENTS.md
index 87292fc..57972a5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -20,19 +20,26 @@ is a new driver — never a session-type branch in shared code (routes,
transcript, app screens).
The second one, for the Rust port on the `rustify` branch: **the phone app
-and a planned desktop app share almost all of their code.** Screens, widgets,
-folding, paging, config and the network client live in the shared crates
-(`iris`, `client-core`, `transcript-ui`, `tabs-ui`); `android-app` and
-`desktop-app` are thin entry points that own only what the platform forces
-(JNI and the IME on one side, winit and argv on the other). The two
+and a planned desktop app share almost all of their code.** Screens,
+widgets, folding, paging, config and the network client live in
+`app-rust/`'s `client` and `ui` modules, drawn with `iris`; `src/android`
+and `src/desktop` are thin entry points that own only what the platform
+forces (JNI and the IME on one side, winit and argv on the other). The two
*layouts* will differ, to suit a phone's screen and a finger against a
desktop's screen and a mouse -- but the widgets a layout is made of (a
button, a text field, a list, a card) and the styling (colours, spacing,
type) are one implementation with no per-platform copy. Anything that could
-work on both goes in a shared crate the first time it is written, and a
-platform crate growing a widget or a colour is a defect to move, not a
-convenience to keep. Iris said this on 2026-09-07; docs/RUST.md carries the
-details.
+work on both goes in `ui` the first time it is written, and a platform
+module growing a widget or a colour is a defect to move, not a convenience
+to keep. Iris said this on 2026-09-07; docs/RUST.md carries the details.
+
+The third, from Iris on 2026-09-08: **`iris/` is the UI framework and
+nothing else.** Nothing in it may know about a session, a transcript, a
+setup or a server; anything that does belongs in `app-rust/`, and the
+dependency runs one way only. The port's project code is **one crate**
+(`ai-app`) rather than the six it was scattered across — see docs/RUST.md's
+"One app crate" for what forced each of the splits that were removed and
+the two that remain.
## Layout
@@ -44,6 +51,22 @@ Module-by-module intent is in `docs/PLAN.md`'s "Backend layout".
- `server/` — the Rust backend (`ai-server`). `routes.rs`'s module doc
comment is the HTTP table and the surface's source of truth.
+- `event-model/` — the wire shape `server/` and `app-rust/` both depend on,
+ which is the whole reason it is a crate of its own rather than part of
+ either: it is the contract between them, so the two agree by construction.
+- `app-rust/` — the Rust app, one crate (`ai-app`) with three faces. `src/
+ client` is everything with no UI in it (the REST and SSE clients, the
+ transcript cache and fold, the highlighter, the ANSI parser, config and
+ the enrolment link); `src/ui` is the screens as iris widget trees;
+ `src/desktop` + `src/bin_desktop.rs` is the winit binary; `src/android`
+ is the `android-view` entry point and `android-project/` its Gradle app;
+ `src/shell` is the separate JNI bridge the Kotlin `app/shellApp` calls.
+ Features pick which face a build is: `screens` (default) for anything
+ that draws, `shell` for the Compose app's bridge, `bench` for P0's
+ fixture build. See its `Cargo.toml` header.
+- `iris/` — the UI framework, and **only** the UI framework: `core`,
+ `macro`, the `iris` crate itself, `tabs-ui` (its own demo widget tree)
+ and `rig-input`. It must not mention anything this product is about.
- `app/` — the Compose app, package `com.example.aiapp`, label "AI Sessions".
`AppRoot.kt` is the navigation `when`; `MainScreen.kt` the root's four tabs
(sessions, import, models, setups); `Api.kt`/`EventStream.kt` the REST + SSE
@@ -66,6 +89,9 @@ Module-by-module intent is in `docs/PLAN.md`'s "Backend layout".
Read it before touching `TranscriptCache.kt`, `TranscriptSource.kt`, or
the opening and stream effects in `SessionScreen.kt`.
- `docs/TODO.md` — the working list.
+ - `docs/SUBAGENTS.md` and `docs/SUBAGENTS_DECISIONS.md` — a session's
+ subagents (the wire shape, the phone's view) and the choices behind
+ them still awaiting review.
- `docs/RUST.md` — the plan for moving the app to Rust (on the `rustify`
branch of the `ai-app-2` clone): what has to be reproduced, the
framework decision, and the ordered experiments with their pass
@@ -74,8 +100,8 @@ Module-by-module intent is in `docs/PLAN.md`'s "Backend layout".
`docs/LAYOUT.md`, `docs/TEXTURES.md`, `docs/CLIENT_CORE.md` — iris's
own build log (**any major addition or design decision, not only
public API** -- Iris, 2026-09-08), working list, decisions log,
- layout/render design, and texture-atlas design, and the client-core
- crate's design, respectively.
+ layout/render design, texture-atlas design, and the design of
+ `app-rust`'s `client` module, respectively.
- `docs/SCROLL.md` — how anything in iris scrolls: one
`ScrollController` holds the position, the gesture, the fling and the
pin, and the two widgets that scroll (`ScrollArea`, `LazySpan`) own
@@ -121,10 +147,15 @@ guaranteed to have.
## Checking your work
-- **Server**: `./run-tests.sh` from the repo root (or `cargo test` from
- `server/`), plus `cargo clippy --all-targets` and `cargo fmt`. The build
- stays warning-clean and rustfmt-clean at the defaults — there is no
- `rustfmt.toml` and there should not be one.
+- **Rust**: `./run-tests.sh` from the repo root runs `event-model`,
+ `server/` and `app-rust/`; `cd iris && cargo test` runs the framework's
+ own suite, which is slower and not about this product. Each workspace
+ also gets `cargo clippy --all-targets` and `cargo fmt`. The build stays
+ warning-clean and rustfmt-clean at the defaults — there is no
+ `rustfmt.toml` and there should not be one. `app-rust/` and `iris/` are
+ pinned to the same dated nightly (`rust-toolchain.toml`, one copy each,
+ because a pin applies per directory); `server/` and `event-model/` are
+ stable.
- **App**: from `app/`,
`. ./android-env.sh && ./gradlew :androidApp:ktfmtFormat
:androidApp:compileDebugKotlin :androidApp:lintDebug
@@ -295,25 +326,30 @@ Each exists because something was invisible without it.
framework, from `atrace` text output with no trace processor needed. It is
how the cost of a layout node per link was attributed to the framework
rather than guessed at.
-- **`iris/android-app/build-apk.sh [debug|release] [--abi ...] [--features
- ...]`** builds iris-android-app's cdylib (`cargo ndk`) and its APK
- (Gradle) in one step and verifies the result (`aapt2`/`apksigner`), and
- **`iris/android-app/run-bench.sh [--apk PATH]`** installs it on this
- checkout's own emulator, taps "Run benchmark" by label, and prints the
- report -- written so the P0 build/install/tap/read-report cycle stops
- being retyped by hand each time (docs/RUST.md's P0 box).
+- **`app-rust/build-apk.sh [debug|release] [--abi ...] [--features
+ ...]`** builds the Rust app's cdylib (`cargo ndk` from `app-rust/`,
+ straight into `android-project/app/src/main/jniLibs/`) and its APK
+ (Gradle, from `android-project/`) in one step and verifies the result
+ (`aapt2`/`apksigner`), and **`app-rust/run-bench.sh [--apk PATH]`**
+ installs it on this checkout's own emulator, taps "Run benchmark" by
+ label, and prints the report -- written so the P0
+ build/install/tap/read-report cycle stops being retyped by hand each
+ time (docs/RUST.md's P0 box). It passes `--no-default-features`, so
+ `--features` alone decides what is in the `.so`; that is what keeps the
+ 1.9 MB bench fixture out of a build that did not ask for `bench`.
- **iris's three test layers** (docs/RUST.md's "Three test layers" has
the commands and what each cannot answer): test at the cheapest one
- that can answer the question. `cargo test -p transcript-fixture` runs
- the real transcript screen over the bench fixture with **no window, no
+ that can answer the question. `cd app-rust && cargo test` runs the real
+ transcript screen over the bench fixture with **no window, no
compositor and no GPU** (`iris::harness`), on a clock the test owns and
a gesture replayed from a `t_ms action x y` file under
- `iris/transcript-fixture/touch/` -- which is how the batched 120Hz
+ `app-rust/touch/` -- which is how the batched 120Hz
flick a finger actually makes is testable at all, since a `ui-trace`
swipe is many evenly-spaced events. `iris/run-headless.sh phone --phone
- --shot …` opens the same screen in a window at the phone's own size and
- density for looking at, and `--replay FILE` drives the same recording
- into it. The emulator is for JNI, the IME, insets, the surface
+ --dir ../app-rust --shot …` opens the same screen in a window at the
+ phone's own size and density for looking at, and `--replay FILE` drives
+ the same recording into it (`--dir` names the workspace to build in,
+ since the rig lives in iris and the app's examples do not). The emulator is for JNI, the IME, insets, the surface
lifecycle and one verification run before a build goes to the phone --
not for iterating on layout.
- **The emulator is a GLES rig, deliberately** (Iris, 2026-09-08;
diff --git a/android-shell/Cargo.lock b/android-shell/Cargo.lock
deleted file mode 100644
index 8d98ec4..0000000
--- a/android-shell/Cargo.lock
+++ /dev/null
@@ -1,1130 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
-name = "aho-corasick"
-version = "1.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "android-shell"
-version = "0.1.0"
-dependencies = [
- "android_logger",
- "client-core",
- "jni",
- "log",
-]
-
-[[package]]
-name = "android_log-sys"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
-
-[[package]]
-name = "android_logger"
-version = "0.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3"
-dependencies = [
- "android_log-sys",
- "env_filter",
- "log",
-]
-
-[[package]]
-name = "base64"
-version = "0.23.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
-
-[[package]]
-name = "bitflags"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
-
-[[package]]
-name = "bytes"
-version = "1.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
-
-[[package]]
-name = "cc"
-version = "1.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-
-[[package]]
-name = "client-core"
-version = "0.1.0"
-dependencies = [
- "base64",
- "event-model",
- "log",
- "pulldown-cmark",
- "serde",
- "serde_json",
- "ureq",
-]
-
-[[package]]
-name = "combine"
-version = "4.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
-dependencies = [
- "bytes",
- "memchr",
-]
-
-[[package]]
-name = "cookie"
-version = "0.18.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
-dependencies = [
- "percent-encoding",
- "time",
- "version_check",
-]
-
-[[package]]
-name = "cookie_store"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
-dependencies = [
- "cookie",
- "document-features",
- "idna",
- "indexmap",
- "log",
- "serde",
- "serde_derive",
- "serde_json",
- "time",
- "url",
-]
-
-[[package]]
-name = "crc32fast"
-version = "1.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "deranged"
-version = "0.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
-
-[[package]]
-name = "displaydoc"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.5",
-]
-
-[[package]]
-name = "document-features"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
-dependencies = [
- "litrs",
-]
-
-[[package]]
-name = "env_filter"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
-dependencies = [
- "log",
- "regex",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "event-model"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "find-msvc-tools"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
-
-[[package]]
-name = "flate2"
-version = "1.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
-dependencies = [
- "crc32fast",
- "miniz_oxide",
- "zlib-rs",
-]
-
-[[package]]
-name = "form_urlencoded"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "getopts"
-version = "0.2.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
-dependencies = [
- "unicode-width",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-
-[[package]]
-name = "http"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
-dependencies = [
- "bytes",
- "itoa",
-]
-
-[[package]]
-name = "httparse"
-version = "1.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
-
-[[package]]
-name = "icu_collections"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
-dependencies = [
- "displaydoc",
- "potential_utf",
- "utf8_iter",
- "yoke",
- "zerofrom",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locale_core"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
-dependencies = [
- "displaydoc",
- "litemap",
- "tinystr",
- "writeable",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
-dependencies = [
- "icu_collections",
- "icu_normalizer_data",
- "icu_properties",
- "icu_provider",
- "smallvec",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer_data"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
-
-[[package]]
-name = "icu_properties"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
-dependencies = [
- "displaydoc",
- "icu_collections",
- "icu_locale_core",
- "icu_properties_data",
- "icu_provider",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "icu_properties_data"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
-
-[[package]]
-name = "icu_provider"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
-dependencies = [
- "displaydoc",
- "icu_locale_core",
- "writeable",
- "yoke",
- "zerofrom",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "idna"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
-dependencies = [
- "idna_adapter",
- "smallvec",
- "utf8_iter",
-]
-
-[[package]]
-name = "idna_adapter"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
-dependencies = [
- "icu_normalizer",
- "icu_properties",
-]
-
-[[package]]
-name = "indexmap"
-version = "2.14.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
-
-[[package]]
-name = "jni"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
-dependencies = [
- "cfg-if",
- "combine",
- "jni-macros",
- "jni-sys",
- "log",
- "simd_cesu8",
- "thiserror",
- "walkdir",
- "windows-link",
-]
-
-[[package]]
-name = "jni-macros"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
-dependencies = [
- "proc-macro2",
- "quote",
- "rustc_version",
- "simd_cesu8",
- "syn 2.0.119",
-]
-
-[[package]]
-name = "jni-sys"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
-dependencies = [
- "jni-sys-macros",
-]
-
-[[package]]
-name = "jni-sys-macros"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
-dependencies = [
- "quote",
- "syn 2.0.119",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.189"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
-
-[[package]]
-name = "litemap"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
-
-[[package]]
-name = "litrs"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
-
-[[package]]
-name = "log"
-version = "0.4.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
-
-[[package]]
-name = "memchr"
-version = "2.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
-
-[[package]]
-name = "miniz_oxide"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
-dependencies = [
- "adler2",
- "simd-adler32",
-]
-
-[[package]]
-name = "num-conv"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
-
-[[package]]
-name = "once_cell"
-version = "1.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
-
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
-[[package]]
-name = "potential_utf"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
-dependencies = [
- "zerovec",
-]
-
-[[package]]
-name = "powerfmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.107"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "pulldown-cmark"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
-dependencies = [
- "bitflags",
- "getopts",
- "memchr",
- "pulldown-cmark-escape",
- "unicase",
-]
-
-[[package]]
-name = "pulldown-cmark-escape"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
-
-[[package]]
-name = "quote"
-version = "1.0.47"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "regex"
-version = "1.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.8.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
-
-[[package]]
-name = "ring"
-version = "0.17.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
-dependencies = [
- "cc",
- "cfg-if",
- "getrandom",
- "libc",
- "untrusted",
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "rustc_version"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
-dependencies = [
- "semver",
-]
-
-[[package]]
-name = "rustls"
-version = "0.23.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
-dependencies = [
- "log",
- "once_cell",
- "ring",
- "rustls-pki-types",
- "rustls-webpki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls-pki-types"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
-dependencies = [
- "zeroize",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.103.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
-]
-
-[[package]]
-name = "same-file"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "semver"
-version = "1.0.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
-
-[[package]]
-name = "serde"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
-dependencies = [
- "serde_core",
- "serde_derive",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.229"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.5",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.151"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
-dependencies = [
- "itoa",
- "memchr",
- "serde",
- "serde_core",
- "zmij",
-]
-
-[[package]]
-name = "shlex"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
-
-[[package]]
-name = "simd-adler32"
-version = "0.3.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
-
-[[package]]
-name = "simd_cesu8"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
-dependencies = [
- "rustc_version",
- "simdutf8",
-]
-
-[[package]]
-name = "simdutf8"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
-
-[[package]]
-name = "smallvec"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
-
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-
-[[package]]
-name = "subtle"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-
-[[package]]
-name = "syn"
-version = "2.0.119"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "3.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "synstructure"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
-]
-
-[[package]]
-name = "thiserror"
-version = "2.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "2.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.5",
-]
-
-[[package]]
-name = "time"
-version = "0.3.55"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
-dependencies = [
- "deranged",
- "num-conv",
- "powerfmt",
- "serde_core",
- "time-core",
- "time-macros",
-]
-
-[[package]]
-name = "time-core"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
-
-[[package]]
-name = "time-macros"
-version = "0.2.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
-dependencies = [
- "num-conv",
- "time-core",
-]
-
-[[package]]
-name = "tinystr"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
-dependencies = [
- "displaydoc",
- "zerovec",
-]
-
-[[package]]
-name = "unicase"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "unicode-width"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
-
-[[package]]
-name = "untrusted"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
-
-[[package]]
-name = "ureq"
-version = "3.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af5546be8f5378d5414f83733f5c9a2526f4645829edbc1c41790aeef1b38e8b"
-dependencies = [
- "base64",
- "cookie_store",
- "flate2",
- "log",
- "percent-encoding",
- "rustls",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "ureq-proto",
- "utf8-zero",
- "webpki-roots",
-]
-
-[[package]]
-name = "ureq-proto"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fabc3e92916c89c95b20eef7b06b00b066bc217ef9ea3a4ac9bf1a7e35261e10"
-dependencies = [
- "base64",
- "http",
- "httparse",
- "log",
-]
-
-[[package]]
-name = "url"
-version = "2.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
- "serde",
-]
-
-[[package]]
-name = "utf8-zero"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
-
-[[package]]
-name = "utf8_iter"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
-
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[package]]
-name = "walkdir"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
-dependencies = [
- "same-file",
- "winapi-util",
-]
-
-[[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "webpki-roots"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
-dependencies = [
- "rustls-pki-types",
-]
-
-[[package]]
-name = "winapi-util"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "writeable"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
-
-[[package]]
-name = "yoke"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
-dependencies = [
- "stable_deref_trait",
- "yoke-derive",
- "zerofrom",
-]
-
-[[package]]
-name = "yoke-derive"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
- "synstructure",
-]
-
-[[package]]
-name = "zerofrom"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
-dependencies = [
- "zerofrom-derive",
-]
-
-[[package]]
-name = "zerofrom-derive"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.119",
- "synstructure",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
-
-[[package]]
-name = "zerotrie"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
-dependencies = [
- "displaydoc",
- "yoke",
- "zerofrom",
-]
-
-[[package]]
-name = "zerovec"
-version = "0.11.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
-dependencies = [
- "yoke",
- "zerofrom",
- "zerovec-derive",
-]
-
-[[package]]
-name = "zerovec-derive"
-version = "0.11.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 3.0.5",
-]
-
-[[package]]
-name = "zlib-rs"
-version = "0.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
-
-[[package]]
-name = "zmij"
-version = "1.0.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/android-shell/Cargo.toml b/android-shell/Cargo.toml
deleted file mode 100644
index 3758acd..0000000
--- a/android-shell/Cargo.toml
+++ /dev/null
@@ -1,36 +0,0 @@
-[package]
-name = "android-shell"
-version = "0.1.0"
-edition = "2024"
-
-# The JNI bridge behind E3's two Java stub classes (`MainActivity`,
-# `NotificationService` -- see RUST.md's "How much Java is unavoidable" for
-# why those two classes cannot be anything but Java/Kotlin, registered from
-# the manifest by name). Everything they would otherwise have done in
-# Kotlin -- the SSE follow loop, deciding where a notification is shown,
-# picking a session for a share -- is here instead, built on `client-core`
-# so the networking and parsing are not duplicated a third time next to the
-# server and the Kotlin app.
-#
-# `cdylib` for `System.loadLibrary`; `lib` too so `cargo test`/`clippy` run
-# on a normal host target without an Android NDK toolchain, the same
-# posture `client-core` and `server` already have.
-
-[lib]
-name = "android_shell"
-crate-type = ["cdylib", "lib"]
-
-[dependencies]
-client-core = { path = "../client-core" }
-jni = "0.22"
-log = "0.4"
-
-# `LogErrorAndDefault` (the `native_method!` error policy this crate uses
-# throughout, see lib.rs) logs through the `log` facade, which is a no-op
-# without a backend installed -- so without this, every recoverable error
-# at a native entry point would be silently dropped rather than reaching
-# logcat. Android-only: nothing else here needs it, and it does not build
-# off-device (see `notify::ensure_logger`'s call site, the only place this
-# is used).
-[target.'cfg(target_os = "android")'.dependencies]
-android_logger = "0.15"
diff --git a/app-rust/.gitignore b/app-rust/.gitignore
new file mode 100644
index 0000000..ff375e7
--- /dev/null
+++ b/app-rust/.gitignore
@@ -0,0 +1,10 @@
+android-project/.gradle/
+android-project/build/
+android-project/app/build/
+
+
+# Rebuilt by `cargo ndk -o app/src/main/jniLibs/ build` before every
+# Gradle build -- see RUST.md's I2 for the exact command.
+android-project/app/src/main/jniLibs/
+target/
+Cargo.lock.orig
diff --git a/iris/android-app/Cargo.lock b/app-rust/Cargo.lock
similarity index 98%
rename from iris/android-app/Cargo.lock
rename to app-rust/Cargo.lock
index ed982b9..7d3161f 100644
--- a/iris/android-app/Cargo.lock
+++ b/app-rust/Cargo.lock
@@ -166,6 +166,28 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "ai-app"
+version = "0.1.0"
+dependencies = [
+ "android-view",
+ "android_logger",
+ "base64",
+ "event-model",
+ "iris",
+ "jni 0.22.4",
+ "libc",
+ "log",
+ "pulldown-cmark",
+ "serde",
+ "serde_json",
+ "tabs-ui",
+ "tempfile",
+ "tokio",
+ "ureq",
+ "winit",
+]
+
[[package]]
name = "aligned"
version = "0.4.3"
@@ -743,19 +765,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
-[[package]]
-name = "client-core"
-version = "0.1.0"
-dependencies = [
- "base64",
- "event-model",
- "log",
- "pulldown-cmark",
- "serde",
- "serde_json",
- "ureq",
-]
-
[[package]]
name = "clipboard-win"
version = "5.4.1"
@@ -881,9 +890,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
-version = "0.8.7"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
+checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@@ -891,18 +900,18 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.20"
+version = "0.9.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
+checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.22"
+version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
[[package]]
name = "crunchy"
@@ -1055,7 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1167,9 +1176,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "font-types"
-version = "0.12.4"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23"
+checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6"
dependencies = [
"bytemuck",
]
@@ -1718,24 +1727,6 @@ dependencies = [
"winit",
]
-[[package]]
-name = "iris-android-app"
-version = "0.1.0"
-dependencies = [
- "android-view",
- "android_logger",
- "client-core",
- "event-model",
- "iris",
- "libc",
- "log",
- "serde_json",
- "tabs-ui",
- "tokio",
- "transcript-fixture",
- "transcript-ui",
-]
-
[[package]]
name = "iris-core"
version = "0.1.0"
@@ -1943,7 +1934,7 @@ dependencies = [
"bitflags 2.13.1",
"libc",
"plain",
- "redox_syscall 0.9.3",
+ "redox_syscall 0.9.4",
]
[[package]]
@@ -2599,7 +2590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3173,9 +3164,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.9.3"
+version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5"
+checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
dependencies = [
"bitflags 2.13.1",
]
@@ -3282,14 +3273,14 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "rustls"
-version = "0.23.43"
+version = "0.23.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
dependencies = [
"log",
"once_cell",
@@ -3638,7 +3629,7 @@ dependencies = [
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3840,28 +3831,6 @@ dependencies = [
"once_cell",
]
-[[package]]
-name = "transcript-fixture"
-version = "0.1.0"
-dependencies = [
- "client-core",
- "event-model",
- "iris",
- "serde_json",
- "transcript-ui",
-]
-
-[[package]]
-name = "transcript-ui"
-version = "0.1.0"
-dependencies = [
- "client-core",
- "event-model",
- "iris",
- "log",
- "pulldown-cmark",
-]
-
[[package]]
name = "tree_magic_mini"
version = "3.2.2"
@@ -3887,7 +3856,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
dependencies = [
"memoffset",
"tempfile",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3932,9 +3901,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
-version = "3.4.0"
+version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
+checksum = "af5546be8f5378d5414f83733f5c9a2526f4645829edbc1c41790aeef1b38e8b"
dependencies = [
"base64",
"cookie_store",
@@ -3952,9 +3921,9 @@ dependencies = [
[[package]]
name = "ureq-proto"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
+checksum = "fabc3e92916c89c95b20eef7b06b00b066bc217ef9ea3a4ac9bf1a7e35261e10"
dependencies = [
"base64",
"http",
@@ -4417,7 +4386,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5075,18 +5044,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
[[package]]
name = "zerocopy"
-version = "0.8.56"
+version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
+checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.56"
+version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
+checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
dependencies = [
"proc-macro2",
"quote",
diff --git a/app-rust/Cargo.toml b/app-rust/Cargo.toml
new file mode 100644
index 0000000..a759027
--- /dev/null
+++ b/app-rust/Cargo.toml
@@ -0,0 +1,154 @@
+# 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
+opt-level = "s"
+
+[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"]
+
diff --git a/iris/android-app/app/build.gradle b/app-rust/android-project/app/build.gradle
similarity index 100%
rename from iris/android-app/app/build.gradle
rename to app-rust/android-project/app/build.gradle
diff --git a/iris/android-app/app/src/main/AndroidManifest.xml b/app-rust/android-project/app/src/main/AndroidManifest.xml
similarity index 99%
rename from iris/android-app/app/src/main/AndroidManifest.xml
rename to app-rust/android-project/app/src/main/AndroidManifest.xml
index e950e43..833f9f0 100644
--- a/iris/android-app/app/src/main/AndroidManifest.xml
+++ b/app-rust/android-project/app/src/main/AndroidManifest.xml
@@ -39,7 +39,7 @@
-
+