iris is the framework alone; the app is one crate in app-rust/
Iris: "the organization of the rust rewrite is a mess right now... there shouldn't be anything related to the app inside of iris. Iris is supposed to be the UI framework alone." And, on the crate count: "I'm confused why the app only code needs more than one crate though." Nine cargo workspaces become three, and the port's project code -- which sat in five places, four of them inside the framework -- becomes one crate, `ai-app`, in `app-rust/`: client-core -> app-rust/src/client iris/transcript-ui -> app-rust/src/ui iris/transcript-fixture -> app-rust/src/ui/fixture.rs + tests/ + touch/ iris/desktop-app -> app-rust/src/desktop + src/bin_desktop.rs iris/android-app -> app-rust/src/android + android-project/ android-shell -> app-rust/src/shell iris/ keeps core, macro, the iris crate, tabs-ui and rig-input, and now mentions no session, transcript, setup or server anywhere. Only two of the old splits had a reason that survived reading. event-model stays a crate at the repo root because server/ depends on it too, so a crate is what makes the backend and the app agree by construction. The two Android .so names looked like a hard constraint -- a package produces one library artifact -- until P2 turned out to already plan merging those two Android apps into one; both faces now come out of libai_app.so, picked apart by features so `--no-default-features --features shell` keeps wgpu, parley and iris out of the Compose app's APK. docs/RUST.md's "One app crate" has the rest, including what each remaining feature is for. DECISIONS.md and SUBAGENTS.md move into docs/ with everything else. Verified: ./run-tests.sh and `cd iris && cargo test` green, clippy and fmt clean in all five workspaces, `cargo ndk -t x86_64` links libai_app.so, build-apk.sh produces an APK that installs and launches on this checkout's emulator (Gl ... virgl, as expected), and the phone-sized headless screenshot renders the transcript unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e9a6562dc6
commit
6d5a231f5c
100 files changed
+923
-3294
No files matched your search
@@ -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;
|
||||
|
||||
Generated
-1130
File diff suppressed because it is too large.
Load diff
@@ -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"
|
||||
@@ -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
|
||||
+49
-80
@@ -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",
|
||||
@@ -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"]
|
||||
|
||||
File renamed without changes.
+1
-1
@@ -39,7 +39,7 @@
|
||||
<data android:scheme="aiapp" android:host="enroll" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="main" />
|
||||
<meta-data android:name="android.app.lib_name" android:value="ai_app" />
|
||||
</activity>
|
||||
|
||||
<!-- This app's own recent log, for Dev Updater to read on the
|
||||
+1
-1
@@ -49,7 +49,7 @@ public final class DevLogProvider extends ContentProvider {
|
||||
static {
|
||||
// The provider is created before any activity, so it cannot rely
|
||||
// on MainActivity's own load. Loading twice is a no-op.
|
||||
System.loadLibrary("main");
|
||||
System.loadLibrary("ai_app");
|
||||
}
|
||||
|
||||
/** Matches {@link #nativeLinesSince}'s flat answer. Both sides say it once. */
|
||||
File renamed without changes.
+1
-1
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class MainActivity extends Activity {
|
||||
static {
|
||||
System.loadLibrary("main");
|
||||
System.loadLibrary("ai_app");
|
||||
}
|
||||
|
||||
/**
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
# Builds iris-android-app end to end: the cdylib (cargo ndk, straight into
|
||||
# app/src/main/jniLibs/) then the APK (Gradle). Written to stop re-typing
|
||||
# Builds the Android app end to end: the cdylib (cargo ndk from this
|
||||
# directory, straight into android-project/app/src/main/jniLibs/) then the
|
||||
# APK (Gradle, from android-project/). Written to stop re-typing
|
||||
# the same incantation by hand every time (ANDROID_HOME/NDK exports, the
|
||||
# cargo ndk invocation, the keystore env for a release build, apksigner/
|
||||
# aapt2 verification) -- see docs/RUST.md's P0 box. Same shape as `app/
|
||||
@@ -66,7 +67,7 @@ export ANDROID_NDK_HOME="$NDK_DIR"
|
||||
# whatever earlier builds left here, and Gradle packages every directory it
|
||||
# finds -- a debug x86_64 emulator build left behind made an arm64 "release"
|
||||
# 339 MB on 2026-09-06.
|
||||
rm -rf app/src/main/jniLibs
|
||||
rm -rf android-project/app/src/main/jniLibs
|
||||
# ...and Gradle's own copy of them, which `rm -rf jniLibs` does not reach.
|
||||
# `mergeReleaseNativeLibs` is *up to date* against its cached inputs, so a
|
||||
# build that switches ABI packages the previous ABI: an `--abi x86_64`
|
||||
@@ -76,22 +77,24 @@ rm -rf app/src/main/jniLibs
|
||||
# exactly like the phone's own Vulkan problem and is nothing of the kind.
|
||||
# Scoped to the merge task's directory rather than all of `app/build`, so
|
||||
# an ABI change costs the native merge and not the whole Gradle build.
|
||||
rm -rf app/build/intermediates/merged_native_libs \
|
||||
app/build/intermediates/stripped_native_libs \
|
||||
app/build/intermediates/merged_jni_libs
|
||||
rm -rf android-project/app/build/intermediates/merged_native_libs \
|
||||
android-project/app/build/intermediates/stripped_native_libs \
|
||||
android-project/app/build/intermediates/merged_jni_libs
|
||||
echo "build-apk.sh: cargo ndk -t $ABI build ${BUILD_TYPE:+(${BUILD_TYPE})} --features \"$FEATURES\""
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
cargo ndk -t "$ABI" -P 29 -o app/src/main/jniLibs/ build --release --features "$FEATURES"
|
||||
cargo ndk -t "$ABI" -P 29 -o android-project/app/src/main/jniLibs/ build --lib \
|
||||
--profile android-release --no-default-features --features "$FEATURES"
|
||||
else
|
||||
cargo ndk -t "$ABI" -P 29 -o app/src/main/jniLibs/ build --features "$FEATURES"
|
||||
cargo ndk -t "$ABI" -P 29 -o android-project/app/src/main/jniLibs/ build --lib \
|
||||
--profile android-dev --no-default-features --features "$FEATURES"
|
||||
fi
|
||||
|
||||
GRADLE_TASK="assembleDebug"
|
||||
APK_DIR="app/build/outputs/apk/debug"
|
||||
APK_DIR="android-project/app/build/outputs/apk/debug"
|
||||
APK_NAME="app-debug.apk"
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
GRADLE_TASK="assembleRelease"
|
||||
APK_DIR="app/build/outputs/apk/release"
|
||||
APK_DIR="android-project/app/build/outputs/apk/release"
|
||||
APK_NAME="app-release.apk"
|
||||
# Same key `app/build-apk.sh` (the Compose app) generates once under
|
||||
# ~/.config/ai-app/release.jks -- see AGENTS.md's "Checking your work".
|
||||
@@ -104,7 +107,7 @@ if [ "$BUILD_TYPE" = "release" ]; then
|
||||
AI_APP_KEYSTORE_PASSWORD=$(cat "$AI_APP_KEYSTORE.password")
|
||||
fi
|
||||
|
||||
gradle ":app:$GRADLE_TASK" --console=plain
|
||||
(cd android-project && gradle ":app:$GRADLE_TASK" --console=plain)
|
||||
|
||||
APK_PATH="$(pwd)/$APK_DIR/$APK_NAME"
|
||||
BUILD_TOOLS=$(ls -d "$SDK_ROOT"/build-tools/*/ | sort -V | tail -1)
|
||||
@@ -5,7 +5,7 @@
|
||||
//!
|
||||
//! `--phone` sets the headless sway output to the phone's own 1080x2424
|
||||
//! and exports `IRIS_SCALE=2.55`, so this draws at the density Iris's
|
||||
//! phone reports (`transcript_fixture::PHONE_SCALE`) rather than the
|
||||
//! phone reports (`ai_app::ui::fixture::PHONE_SCALE`) rather than the
|
||||
//! desktop's 1.0 -- same screen, same fixture and the same folding as
|
||||
//! the Android bench and the headless tests, so what differs between a
|
||||
//! screenshot here and one from the phone is the renderer, never the
|
||||
@@ -80,7 +80,7 @@ fn main() {
|
||||
pub struct Client {
|
||||
ui_state: DefaultUiState,
|
||||
#[allow(dead_code)]
|
||||
screen: Option<transcript_ui::TranscriptScreen>,
|
||||
screen: Option<ai_app::ui::TranscriptScreen>,
|
||||
}
|
||||
|
||||
impl DefaultAppState for Client {
|
||||
@@ -88,8 +88,8 @@ impl DefaultAppState for Client {
|
||||
WindowAttributes::default()
|
||||
.with_title("iris transcript (bench fixture)")
|
||||
.with_inner_size(PhysicalSize::new(
|
||||
transcript_fixture::PHONE_WIDTH,
|
||||
transcript_fixture::PHONE_HEIGHT,
|
||||
ai_app::ui::fixture::PHONE_WIDTH,
|
||||
ai_app::ui::fixture::PHONE_HEIGHT,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ impl DefaultAppState for Client {
|
||||
rsc: &mut DefaultRsc<Self>,
|
||||
_: Proxy<Self::Event>,
|
||||
) -> Self {
|
||||
let screen = match transcript_fixture::open(rsc, &mut ui_state) {
|
||||
let screen = match ai_app::ui::fixture::open(rsc, &mut ui_state) {
|
||||
Ok(opened) => {
|
||||
if let Some(message) = message_argv() {
|
||||
opened.screen.composer.field.edit(rsc).set(&message);
|
||||
@@ -1,5 +1,5 @@
|
||||
//! I5's desktop proof: the transcript screen built from synthetic
|
||||
//! `client_core::transcript_fold` rows (no network, no server -- see
|
||||
//! `ai_app::client::transcript_fold` rows (no network, no server -- see
|
||||
//! `lib.rs`'s doc for why `transcript-ui` itself never fetches anything),
|
||||
//! run via `iris/run-headless.sh transcript -- -p transcript-ui` for a
|
||||
//! screenshot on the winit backend, or `cargo run --example transcript -p
|
||||
@@ -13,8 +13,8 @@
|
||||
//! with `ui-trace record --do "tap 'Tools'"` on Android, to prove
|
||||
//! hold-the-edge expand).
|
||||
|
||||
use client_core::QuestionOption;
|
||||
use client_core::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||
use ai_app::client::QuestionOption;
|
||||
use ai_app::client::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||
use iris::prelude::*;
|
||||
|
||||
fn main() {
|
||||
@@ -25,7 +25,7 @@ fn main() {
|
||||
pub struct Client {
|
||||
ui_state: DefaultUiState,
|
||||
#[allow(dead_code)]
|
||||
screen: transcript_ui::TranscriptScreen,
|
||||
screen: ai_app::ui::TranscriptScreen,
|
||||
}
|
||||
|
||||
fn msg(seq: u64, from_user: bool, text: &str) -> FoldedRow {
|
||||
@@ -108,7 +108,7 @@ fn asking(id: &str, tool: &str, input: &str) -> TranscriptItem {
|
||||
/// screen in the expanded shot.
|
||||
fn long_output() -> String {
|
||||
(0..200)
|
||||
.map(|i| format!("test transcript_ui::case_{i} ... ok"))
|
||||
.map(|i| format!("test ai_app::ui::case_{i} ... ok"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
@@ -159,7 +159,7 @@ fn synthetic_rows() -> Vec<FoldedRow> {
|
||||
Some((&long_output(), false)),
|
||||
)),
|
||||
msg(6, true, "Looks good, thanks!"),
|
||||
// Every block kind `client_core::markdown_blocks` names, in one
|
||||
// Every block kind `ai_app::client::markdown_blocks` names, in one
|
||||
// row, so P1a's appearance can be looked at against the Compose
|
||||
// app's without a server (docs/RUST.md's P1a box). The heading,
|
||||
// paragraph, fence and table are the *same source* the bench
|
||||
@@ -207,7 +207,7 @@ impl DefaultAppState for Client {
|
||||
rsc: &mut DefaultRsc<Self>,
|
||||
_: Proxy<Self::Event>,
|
||||
) -> Self {
|
||||
let screen = transcript_ui::build(rsc, &mut ui_state, synthetic_rows());
|
||||
let screen = ai_app::ui::build(rsc, &mut ui_state, synthetic_rows());
|
||||
// Exercises `push_row`/`ItemKey` beyond construction time, matching
|
||||
// how a live SSE loop appends -- a row arriving after the screen
|
||||
// already exists must land at the bottom without disturbing what's
|
||||
@@ -7,7 +7,8 @@
|
||||
#
|
||||
# Usage: ./run-bench.sh [--apk PATH]
|
||||
# Defaults to this checkout's own release APK
|
||||
# (app/build/outputs/apk/release/app-release.apk) if it exists, else the
|
||||
# (android-project/app/build/outputs/apk/release/app-release.apk) if it
|
||||
# exists, else the
|
||||
# debug one -- build one first with ./build-apk.sh.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
@@ -20,10 +21,10 @@ while [ $# -gt 0 ]; do
|
||||
esac
|
||||
done
|
||||
if [ -z "$APK" ]; then
|
||||
if [ -f app/build/outputs/apk/release/app-release.apk ]; then
|
||||
APK=app/build/outputs/apk/release/app-release.apk
|
||||
if [ -f android-project/app/build/outputs/apk/release/app-release.apk ]; then
|
||||
APK=android-project/app/build/outputs/apk/release/app-release.apk
|
||||
else
|
||||
APK=app/build/outputs/apk/debug/app-debug.apk
|
||||
APK=android-project/app/build/outputs/apk/debug/app-debug.apk
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$APK" ]; then
|
||||
@@ -0,0 +1,11 @@
|
||||
# iris needs nightly (see the #![feature] list in core/src/lib.rs and src/lib.rs).
|
||||
# The pin is dated rather than "nightly" because the const-traits feature set
|
||||
# changes shape between nightlies: on 2026-09-04 the vendored January tree would
|
||||
# not parse at all, because `impl const Trait for T` had become
|
||||
# `const impl Trait for T`. A rolling channel turns that into a build that
|
||||
# breaks unattended on whatever machine Dev Updater happens to build on.
|
||||
# Advance this deliberately, with the feature list in RUST.md's I0b.
|
||||
[toolchain]
|
||||
channel = "nightly-2026-09-03"
|
||||
components = ["clippy", "rustfmt"]
|
||||
targets = ["aarch64-linux-android", "x86_64-linux-android"]
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The platform half of this app's logging: what
|
||||
//! `client_core::log_ring` needs that only Android can supply, which is
|
||||
//! `crate::client::log_ring` needs that only Android can supply, which is
|
||||
//! `android_logger` as the logger to forward to and nothing else.
|
||||
//!
|
||||
//! Everything general -- the ring, its bounds, the `log::Log` backend --
|
||||
@@ -13,7 +13,7 @@
|
||||
//! the same phone through `devlog`'s `ContentProvider`. See
|
||||
//! `docs/DECISIONS.md`, 2026-09-07.
|
||||
|
||||
use client_core::log_ring::{self, LogRing};
|
||||
use crate::client::log_ring::{self, LogRing};
|
||||
|
||||
/// Installs the ring in front of `android_logger`, so `logcat` still sees
|
||||
/// exactly what it saw before and the ring sees it too.
|
||||
@@ -61,7 +61,7 @@ pub fn ring() -> &'static LogRing {
|
||||
/// contract is live and which package's log it is -- the bench build and
|
||||
/// the ordinary one have different ones.
|
||||
pub fn diagnostics_line() -> String {
|
||||
let where_to_read = match crate::devlog::authority() {
|
||||
let where_to_read = match crate::android::devlog::authority() {
|
||||
Some(authority) => format!("devlog provider: content://{authority}"),
|
||||
// Not "off": Android creates a provider lazily, so this is what
|
||||
// "nobody has asked for it yet" looks like, and it is a different
|
||||
@@ -17,9 +17,9 @@
|
||||
//! this file exists to measure -- see docs/RUST.md's P0 box for the
|
||||
//! before/after report.
|
||||
|
||||
use crate::bench_jni::PlatformHandle;
|
||||
use crate::android::bench_jni::PlatformHandle;
|
||||
use crate::client::transcript_fold::{TranscriptItem, fold_event};
|
||||
use android_view::jni::{JavaVM, objects::GlobalRef};
|
||||
use client_core::transcript_fold::{TranscriptItem, fold_event};
|
||||
use event_model::SeqEvent;
|
||||
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||
use iris::prelude::*;
|
||||
@@ -98,7 +98,7 @@ pub struct BenchClient {
|
||||
/// P0 box, "the status-bar inset is not applied," found the row sitting
|
||||
/// directly under it because nothing here read `insets().top` at all.
|
||||
top_bar: WeakWidget<WidgetPtr>,
|
||||
screen: Option<transcript_ui::TranscriptScreen>,
|
||||
screen: Option<crate::ui::TranscriptScreen>,
|
||||
items: Vec<TranscriptItem>,
|
||||
/// The events not yet streamed -- consumed by `start_benchmark`'s own
|
||||
/// clone, kept here only as the source a second run would need (the
|
||||
@@ -288,7 +288,7 @@ impl AndroidAppState for BenchClient {
|
||||
last_top_pad: 0.0,
|
||||
};
|
||||
|
||||
match transcript_fixture::build_screen(rsc) {
|
||||
match crate::ui::fixture::build_screen(rsc) {
|
||||
Ok((opened, tree)) => {
|
||||
client.items = opened.items;
|
||||
client.stream_tail = opened.stream_tail;
|
||||
@@ -364,7 +364,7 @@ impl AndroidAppState for BenchClient {
|
||||
|
||||
// The composer bar sits directly on whichever of the IME or the
|
||||
// navigation bar is currently the bottom of usable space -- see
|
||||
// `transcript_ui::composer::Composer::set_bottom_inset`'s doc.
|
||||
// `crate::ui::composer::Composer::set_bottom_inset`'s doc.
|
||||
// `ime_bottom` already exceeds the plain nav-bar inset whenever the
|
||||
// keyboard covers it, so the larger of the two is always the right
|
||||
// answer without needing to know which is currently showing.
|
||||
@@ -602,7 +602,7 @@ impl BenchClient {
|
||||
}
|
||||
|
||||
fn rebuild_transcript(&mut self, rsc: &mut Rsc) {
|
||||
let (screen, tree) = transcript_ui::build_tree(rsc, transcript_fixture::rows(&self.items));
|
||||
let (screen, tree) = crate::ui::build_tree(rsc, crate::ui::fixture::rows(&self.items));
|
||||
(self.content)(rsc).set(tree);
|
||||
self.screen = Some(screen);
|
||||
}
|
||||
@@ -667,8 +667,8 @@ impl BenchClient {
|
||||
// answer is "none" -- the bench itself opens a checked-in
|
||||
// fixture and needs no server, so this pane is the only place
|
||||
// an enrolment can be seen to have taken.
|
||||
crate::enrollment::status_line(),
|
||||
crate::app_log::diagnostics_line()
|
||||
crate::android::enrollment::status_line(),
|
||||
crate::android::app_log::diagnostics_line()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -974,7 +974,7 @@ async fn read_anchor_position(
|
||||
}
|
||||
|
||||
/// Register the scrolling widget with the frame loop, exactly as a
|
||||
/// finger's own release does (`transcript_ui::Selection::drag`'s
|
||||
/// finger's own release does (`crate::ui::Selection::drag`'s
|
||||
/// `Released` arm) -- `Scrollable::fling` sets a velocity and drives
|
||||
/// nothing by itself.
|
||||
fn animate_scroll(scroll: iris::prelude::WeakWidget<iris::prelude::LazySpan>, rsc: &mut Rsc) {
|
||||
File renamed without changes.
@@ -4,7 +4,7 @@
|
||||
//! **Why**: Iris runs these builds on a phone with no `adb`, and Android
|
||||
//! forbids one app reading another's `logcat`, so nothing outside this
|
||||
//! process can recover what it wrote. The app already keeps a bounded copy
|
||||
//! (`client_core::log_ring`); this is how the copy leaves the process. Dev
|
||||
//! (`crate::client::log_ring`); this is how the copy leaves the process. Dev
|
||||
//! Updater is on the same phone, so handing it over needs no tunnel, no
|
||||
//! token and no second enrolment -- and it is Dev Updater's own contract
|
||||
//! rather than something invented here, so any app it delivers can do the
|
||||
@@ -80,7 +80,7 @@ pub extern "system" fn Java_dev_iris_android_demo_DevLogProvider_nativeReady(
|
||||
// line announcing this one rather than buried under it.
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
if let Some(dir) = string_arg(&mut env, &files_dir) {
|
||||
crate::app_log::set_crash_dir(std::path::Path::new(&dir));
|
||||
crate::android::app_log::set_crash_dir(std::path::Path::new(&dir));
|
||||
}
|
||||
#[cfg(not(feature = "transcript-screen"))]
|
||||
let _ = &files_dir;
|
||||
@@ -124,7 +124,7 @@ pub extern "system" fn Java_dev_iris_android_demo_DevLogProvider_nativeStatus(
|
||||
/// `DevLogProvider.nativeLinesSince` -- every held line with a sequence at
|
||||
/// or after `since`, oldest first, [`FIELDS_PER_LINE`] strings each.
|
||||
///
|
||||
/// Inclusive of `since` because [`client_core::log_ring::LogRing::since`]
|
||||
/// Inclusive of `since` because [`crate::client::log_ring::LogRing::since`]
|
||||
/// is, and one definition of the cursor is what keeps the app's own
|
||||
/// uploaded report and this provider describing the same lines.
|
||||
///
|
||||
@@ -144,7 +144,7 @@ pub extern "system" fn Java_dev_iris_android_demo_DevLogProvider_nativeLinesSinc
|
||||
/// The three status numbers, as the provider's row.
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
fn status_fields() -> Vec<String> {
|
||||
let ring = client_core::log_ring::process_ring();
|
||||
let ring = crate::client::log_ring::process_ring();
|
||||
vec![
|
||||
ring.len().to_string(),
|
||||
ring.dropped().to_string(),
|
||||
@@ -164,7 +164,7 @@ fn status_fields() -> Vec<String> {
|
||||
|
||||
#[cfg(feature = "transcript-screen")]
|
||||
fn line_fields(since: u64) -> Vec<String> {
|
||||
let (lines, _next) = client_core::log_ring::process_ring().since(since);
|
||||
let (lines, _next) = crate::client::log_ring::process_ring().since(since);
|
||||
let mut fields = Vec::with_capacity(lines.len() * FIELDS_PER_LINE);
|
||||
for line in lines {
|
||||
fields.push(line.seq.to_string());
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Which `ai-server` this app talks to, and how it was told.
|
||||
//!
|
||||
//! The parsing, the file and its owner-only mode are
|
||||
//! `client_core::config` (`EnrolledServer`/`EnrollmentStore`), shared with
|
||||
//! `crate::client::config` (`EnrolledServer`/`EnrollmentStore`), shared with
|
||||
//! the desktop app. What is genuinely this platform's, and all that is
|
||||
//! here, is the intent plumbing: Android hands an `aiapp://enroll?...`
|
||||
//! link to `MainActivity`, which passes it and the app's private files
|
||||
@@ -19,11 +19,11 @@
|
||||
//! avoids: it arrives from the activity, and `AndroidAppState::new` -- the
|
||||
//! first thing that wants the enrollment -- has no parameter it could come
|
||||
//! in through. Same shape, and the same reason, as
|
||||
//! `client_core::log_ring`'s process ring.
|
||||
//! `crate::client::log_ring`'s process ring.
|
||||
|
||||
#[cfg(not(feature = "bench"))]
|
||||
use client_core::api::UreqTransport;
|
||||
use client_core::config::{EnrolledServer, EnrollmentStore};
|
||||
use crate::client::api::UreqTransport;
|
||||
use crate::client::config::{EnrolledServer, EnrollmentStore};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! `IrisView`/`MainActivity` Java, and the JNI registration I2 built and
|
||||
//! measured against, and the only thing a transcript screen needs on top
|
||||
//! is a different `AndroidAppState` -- the same axis `tabs_ui::build` vs.
|
||||
//! `transcript_ui::build` already varies along on the winit side (compare
|
||||
//! `crate::ui::build` already varies along on the winit side (compare
|
||||
//! `iris/examples/tabs.rs` and `iris/transcript-ui/examples/transcript.rs`).
|
||||
//! A build picks one screen or the other, never both, so `Client` and
|
||||
//! `TranscriptClient` are cfg-gated apart rather than switched at runtime --
|
||||
@@ -26,7 +26,7 @@
|
||||
//!
|
||||
//! **`bench` feature (P0's iris half, docs/RUST.md):** a third
|
||||
//! `AndroidAppState`, `bench_client::BenchClient`, on the same axis --
|
||||
//! `transcript_ui::build_tree` again, this time against the checked-in
|
||||
//! `crate::ui::build_tree` again, this time against the checked-in
|
||||
//! fixture (`app/bench-fixture/assets/transcript.jsonl`) instead of a real
|
||||
//! server, with a "Run benchmark" control that drives the same scroll loop
|
||||
//! and streaming phase the Compose `bench` build type's `BenchRun.kt`
|
||||
+11
-11
@@ -13,13 +13,13 @@
|
||||
//! straight on the screen under test.
|
||||
//!
|
||||
//! Which server it opens it against is no longer baked in: it is the
|
||||
//! enrollment an `aiapp://enroll` link left behind (`crate::enrollment`,
|
||||
//! enrollment an `aiapp://enroll` link left behind (`crate::android::enrollment`,
|
||||
//! and `desktop-app`'s identical `--link`), because an APK
|
||||
//! cross-compiled here cannot pin the CA of a server on the host.
|
||||
//!
|
||||
//! **Reuses `iris/desktop-app`'s `app.rs` shape almost exactly** --
|
||||
//! `fold_event`/`group_tool_runs`/`fold_page`/`raw_seq` from
|
||||
//! `client_core::transcript_fold`, a `generation` counter guarding against
|
||||
//! `crate::client::transcript_fold`, a `generation` counter guarding against
|
||||
//! a stale background response. What differs is only the redraw
|
||||
//! mechanism: android-view has no `winit::EventLoopProxy`, so this uses
|
||||
//! `iris::task::Tasks::redraw_handle` (new, added alongside this box) to
|
||||
@@ -30,16 +30,16 @@
|
||||
//! **Streaming no longer costs a full rebuild** (fixed after the P0 gate
|
||||
//! showed why it mattered -- 20 events/second means 20 rebuilds/second of
|
||||
//! a ~3,200-row transcript otherwise): `apply_event` calls
|
||||
//! `transcript_ui::TranscriptScreen::apply` with the item list before and
|
||||
//! `crate::ui::TranscriptScreen::apply` with the item list before and
|
||||
//! after `fold_event`, which updates only the row(s) that actually
|
||||
//! changed (almost always just the one open assistant message) instead of
|
||||
//! refolding and rebuilding every row. `rebuild_transcript` still runs
|
||||
//! the whole widget tree once, for the opening page and for `apply`'s own
|
||||
//! rare regroup fallback.
|
||||
|
||||
use client_core::api::{ApiClient, UreqTransport};
|
||||
use client_core::event_stream::{StreamItem, follow_session_events};
|
||||
use client_core::transcript_fold::{TranscriptItem, fold_event, fold_page, group_tool_runs};
|
||||
use crate::client::api::{ApiClient, UreqTransport};
|
||||
use crate::client::event_stream::{StreamItem, follow_session_events};
|
||||
use crate::client::transcript_fold::{TranscriptItem, fold_event, fold_page, group_tool_runs};
|
||||
use event_model::SeqEvent;
|
||||
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||
use iris::prelude::*;
|
||||
@@ -54,7 +54,7 @@ pub struct TranscriptClient {
|
||||
/// `set` calls the way `desktop-app`'s `transcript_ptr` isn't touched
|
||||
/// by rebuilding the session list beside it.
|
||||
content: WeakWidget<WidgetPtr>,
|
||||
screen: Option<transcript_ui::TranscriptScreen>,
|
||||
screen: Option<crate::ui::TranscriptScreen>,
|
||||
/// The folded transcript as of the last rebuild -- kept here (not
|
||||
/// re-derived) for the same reason `desktop-app`'s `Client::items`
|
||||
/// exists: a live `StreamEvent` only carries one new wire event, and
|
||||
@@ -90,7 +90,7 @@ impl HasAndroidUiState for TranscriptClient {
|
||||
/// while the app is running is how somebody points it at another server,
|
||||
/// and a cached transport would keep talking to the old one.
|
||||
fn build_transport() -> Result<UreqTransport, String> {
|
||||
crate::enrollment::transport()
|
||||
crate::android::enrollment::transport()
|
||||
}
|
||||
|
||||
fn placeholder<Rsc: HasEvents>(rsc: &mut Rsc, message: &str) -> StrongWidget {
|
||||
@@ -262,14 +262,14 @@ impl TranscriptClient {
|
||||
.fetch_transcript_page(&session_id, None, 200, true)
|
||||
.map_err(|e| e.to_string());
|
||||
// The wire `seq` of the last line, not a folded item's `seq()`
|
||||
// -- see `client_core::transcript_fold::raw_seq`'s doc for why
|
||||
// -- see `crate::client::transcript_fold::raw_seq`'s doc for why
|
||||
// resuming from the latter re-delivers deltas already folded
|
||||
// into an in-progress reply.
|
||||
let after = page
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|values| values.last())
|
||||
.and_then(client_core::transcript_fold::raw_seq)
|
||||
.and_then(crate::client::transcript_fold::raw_seq)
|
||||
.unwrap_or(0);
|
||||
let result = page.and_then(|values| fold_page(&values));
|
||||
|
||||
@@ -341,7 +341,7 @@ impl TranscriptClient {
|
||||
.filter(|t| !t.is_empty());
|
||||
|
||||
let rows = group_tool_runs(&self.items);
|
||||
let (screen, tree) = transcript_ui::build_tree(rsc, rows);
|
||||
let (screen, tree) = crate::ui::build_tree(rsc, rows);
|
||||
|
||||
if let Some(text) = in_progress {
|
||||
screen.composer.field.edit(rsc).set(&text);
|
||||
@@ -0,0 +1,35 @@
|
||||
//! RUST.md's E4: the transcript screen (`transcript-ui`, I5) in a real
|
||||
//! winit window on the desktop, with a session list beside it, talking to
|
||||
//! a real `ai-server` over `client-core`'s REST + SSE clients. See
|
||||
//! `app.rs`'s module doc for the widget tree and the event flow.
|
||||
//!
|
||||
//! Usage:
|
||||
//!
|
||||
//! desktop-app --link 'aiapp://enroll?host=H&port=P&token=T&ca=B'
|
||||
//! desktop-app # after the first run above
|
||||
//! desktop-app --ca /path/to/ca.pem # a link that carries no CA
|
||||
//!
|
||||
//! `--link` is the same text `app/ui-sandbox.sh`'s banner prints and a
|
||||
//! phone would scan as a QR (DECISIONS.md, 2026-09-05) -- pasted rather
|
||||
//! than scanned, since a desktop has no camera to assume. It is parsed and
|
||||
//! saved once; later runs read it back and `--link` is only needed again
|
||||
//! to enrol against a different server.
|
||||
//!
|
||||
//! The CA comes with the link (`wg_app_link::enroll::ca_param`, which
|
||||
//! `ai-server` now always includes) and is saved with it. `--ca` is the
|
||||
//! override for a link that carries none, and names the same
|
||||
//! `certs/ca.pem` a `curl --cacert` call uses.
|
||||
|
||||
use ai_app::desktop::{app, startup};
|
||||
|
||||
fn main() {
|
||||
// Validated once here so a bad `--ca`/`--link` is reported on stderr
|
||||
// before any window opens; `Client::new` calls this same function
|
||||
// again once the window exists, so this first call is a fast-fail
|
||||
// rather than the only place the values come from.
|
||||
if let Err(e) = startup::load_startup_config() {
|
||||
eprintln!("desktop-app: {e}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
app::run();
|
||||
}
|
||||
File renamed without changes.
@@ -1,5 +1,5 @@
|
||||
//! The REST half of the backend's surface (see `server/src/routes.rs`'s
|
||||
//! module doc for the table); the SSE half is [`crate::event_stream`].
|
||||
//! module doc for the table); the SSE half is [`crate::client::event_stream`].
|
||||
//! Ported from `app/.../Api.kt`, but **not at full parity yet** -- see
|
||||
//! `CLIENT_CORE.md` for exactly which routes have a typed method here and
|
||||
//! which do not.
|
||||
@@ -64,7 +64,7 @@ pub trait Transport: Send + Sync {
|
||||
|
||||
/// Opens `path` and answers a reader over the response body, for a
|
||||
/// caller that reads it as a stream rather than all at once (the SSE
|
||||
/// connections in [`crate::event_stream`]). Fails the same way
|
||||
/// connections in [`crate::client::event_stream`]). Fails the same way
|
||||
/// [`Transport::request`] does for a non-2xx response.
|
||||
fn stream(&self, path: &str) -> Result<Box<dyn Read + Send>, ApiError>;
|
||||
}
|
||||
@@ -265,7 +265,7 @@ impl<T: Transport> ApiClient<T> {
|
||||
/// A page of transcript history. `before` is the newest-first cursor
|
||||
/// (server default is "the newest page" when absent, which a caller
|
||||
/// gets by passing `None`); the events themselves are handed back as
|
||||
/// [`event_model::SeqEvent`] via `crate::event_stream`'s parsing, kept
|
||||
/// [`event_model::SeqEvent`] via `crate::client::event_stream`'s parsing, kept
|
||||
/// out of this method's signature so a caller that only wants the raw
|
||||
/// lines (for the transcript cache) is not forced to parse them.
|
||||
pub fn fetch_transcript_page(
|
||||
@@ -284,7 +284,7 @@ impl<T: Transport> ApiClient<T> {
|
||||
|
||||
/// A page of transcript history, each line handed back paired with the
|
||||
/// exact text it came from, and bounded below by `after` -- the shape
|
||||
/// `crate::transcript_source::TranscriptSource` needs to store what it
|
||||
/// `crate::client::transcript_source::TranscriptSource` needs to store what it
|
||||
/// fetched in the transcript cache without a second round trip to fetch
|
||||
/// the raw text separately. Ported from `Api.kt`'s `fetchTranscript`.
|
||||
///
|
||||
@@ -99,7 +99,7 @@ impl EnrolledServer {
|
||||
})
|
||||
}
|
||||
|
||||
/// Where a `client_core::api::UreqTransport` reaches this server.
|
||||
/// Where a `crate::client::api::UreqTransport` reaches this server.
|
||||
pub fn base_url(&self) -> String {
|
||||
format!("https://{}:{}", self.host, self.port)
|
||||
}
|
||||
File renamed without changes.
@@ -1,14 +1,14 @@
|
||||
//! The SSE half of the API: one long-lived GET per open session screen,
|
||||
//! replaying the transcript after a cursor and then following it live.
|
||||
//! Ported from `app/.../EventStream.kt`; the framing itself is
|
||||
//! [`crate::sse`].
|
||||
//! [`crate::client::sse`].
|
||||
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
||||
use event_model::SeqEvent;
|
||||
|
||||
use crate::api::{ApiError, Transport};
|
||||
use crate::sse::SseReader;
|
||||
use crate::client::api::{ApiError, Transport};
|
||||
use crate::client::sse::SseReader;
|
||||
|
||||
/// The frame name the server uses to say a cursor was too far behind to
|
||||
/// continue from. Must match `send_backlog` in `server/src/routes.rs`.
|
||||
@@ -83,7 +83,7 @@ pub fn follow_session_events(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::{Body, RawResponse};
|
||||
use crate::client::api::{Body, RawResponse};
|
||||
use std::io::Cursor;
|
||||
|
||||
struct FixtureTransport {
|
||||
File renamed without changes.
@@ -107,8 +107,8 @@ impl MarkdownScanner {
|
||||
fn table_delimiter(&self, start: usize, end: usize) -> bool {
|
||||
let mut dashes = false;
|
||||
let mut pipes = false;
|
||||
for at in self.indented(start, end)..end {
|
||||
match self.code[at] {
|
||||
for c in &self.code[self.indented(start, end)..end] {
|
||||
match c {
|
||||
'-' => dashes = true,
|
||||
'|' => pipes = true,
|
||||
':' | ' ' | '\t' => {}
|
||||
@@ -263,8 +263,7 @@ impl MarkdownScanner {
|
||||
return false;
|
||||
}
|
||||
let mut seen = 0usize;
|
||||
for at in start..end {
|
||||
let c = self.code[at];
|
||||
for &c in &self.code[start..end] {
|
||||
if c == marker {
|
||||
seen += 1;
|
||||
} else if !c.is_whitespace() {
|
||||
File renamed without changes.
@@ -305,16 +305,16 @@ impl LogRing {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a target belongs to this app's own crates (`iris` or
|
||||
/// `client_core`) rather than a dependency's -- `starts_with` guarded by an
|
||||
/// Whether a target belongs to this app or to `iris` rather than to a
|
||||
/// dependency -- `starts_with` guarded by an
|
||||
/// exact match or a `::` so an unrelated crate that merely begins with the
|
||||
/// same letters (there is no such crate today, but the check should not
|
||||
/// rely on that) is never mistaken for one of ours.
|
||||
fn is_own_target(target: &str) -> bool {
|
||||
target == "iris"
|
||||
|| target.starts_with("iris::")
|
||||
|| target == "client_core"
|
||||
|| target.starts_with("client_core::")
|
||||
|| target == "ai_app"
|
||||
|| target.starts_with("ai_app::")
|
||||
}
|
||||
|
||||
/// Whether a line at `level` from `target` belongs in the ring, given
|
||||
@@ -733,7 +733,7 @@ mod tests {
|
||||
"own Debug, tracing on: included"
|
||||
);
|
||||
assert!(
|
||||
ring_accepts(Level::Trace, "client_core::api", true),
|
||||
ring_accepts(Level::Trace, "ai_app::api", true),
|
||||
"own Trace, tracing on: included"
|
||||
);
|
||||
}
|
||||
@@ -742,8 +742,8 @@ mod tests {
|
||||
fn is_own_target_matches_the_crate_or_its_modules_only() {
|
||||
assert!(is_own_target("iris"));
|
||||
assert!(is_own_target("iris::sense"));
|
||||
assert!(is_own_target("client_core"));
|
||||
assert!(is_own_target("client_core::log_ring"));
|
||||
assert!(is_own_target("ai_app"));
|
||||
assert!(is_own_target("ai_app::log_ring"));
|
||||
assert!(!is_own_target("iris_something_else"));
|
||||
assert!(!is_own_target("naga::front"));
|
||||
assert!(!is_own_target("jni"));
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
//! `GET /notifications`, the attention stream PLAN.md's "Notifications: two
|
||||
//! places, never both" describes. Ported from the parsing half of
|
||||
//! `app/.../Notifications.kt`'s `NotificationService` -- the framing
|
||||
//! ([`crate::sse`]) and the wire shape ([`SessionNotification`],
|
||||
//! ([`crate::client::sse`]) and the wire shape ([`SessionNotification`],
|
||||
//! [`NotificationKind`], mirroring `server/src/session/mod.rs`'s
|
||||
//! `Notification`/`NotificationKind`).
|
||||
//!
|
||||
@@ -17,8 +17,8 @@ use std::io::{BufRead, BufReader};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::api::{ApiError, Transport};
|
||||
use crate::sse::SseReader;
|
||||
use crate::client::api::{ApiError, Transport};
|
||||
use crate::client::sse::SseReader;
|
||||
|
||||
/// One frame of `GET /notifications`, matching `server/src/session/mod.rs`'s
|
||||
/// `Notification` field for field.
|
||||
@@ -93,7 +93,7 @@ pub fn follow_notifications(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::{Body, RawResponse};
|
||||
use crate::client::api::{Body, RawResponse};
|
||||
use std::io::Cursor;
|
||||
|
||||
struct FixtureTransport {
|
||||
File renamed without changes.
File renamed without changes.
@@ -13,8 +13,8 @@
|
||||
//! of this crate exists: the derivation is the same on a phone and on a
|
||||
//! desktop, and it is testable without a renderer.
|
||||
|
||||
use crate::durations::format_millis_text;
|
||||
use crate::highlight::Language;
|
||||
use crate::client::durations::format_millis_text;
|
||||
use crate::client::highlight::Language;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A tool call's input, split into the parts a card draws separately.
|
||||
File renamed without changes.
File renamed without changes.
@@ -22,9 +22,9 @@
|
||||
|
||||
use event_model::SeqEvent;
|
||||
|
||||
use crate::api::{ApiClient, ApiError, Transport};
|
||||
use crate::event_stream::{self, StreamItem};
|
||||
use crate::transcript_cache::SessionCache;
|
||||
use crate::client::api::{ApiClient, ApiError, Transport};
|
||||
use crate::client::event_stream::{self, StreamItem};
|
||||
use crate::client::transcript_cache::SessionCache;
|
||||
|
||||
/// How many events a session screen opens with, cached or fetched.
|
||||
///
|
||||
@@ -284,7 +284,7 @@ impl<T: Transport> TranscriptSource<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::{Body, RawResponse};
|
||||
use crate::client::api::{Body, RawResponse};
|
||||
use std::collections::VecDeque;
|
||||
use std::io::Read;
|
||||
use std::sync::Mutex;
|
||||
@@ -352,7 +352,7 @@ mod tests {
|
||||
cache_root: &std::path::Path,
|
||||
) -> TranscriptSource<ScriptedTransport> {
|
||||
let api = ApiClient::new(transport);
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(cache_root).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(cache_root).session("s1");
|
||||
TranscriptSource::new(api, "s1", cache)
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ mod tests {
|
||||
|
||||
let transport2 = ScriptedTransport::default();
|
||||
transport2.respond(200, format!("[{}]", status_line(1)));
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||
assert!(source2.probe().unwrap());
|
||||
assert!(source2.cache.tail().is_some());
|
||||
@@ -404,7 +404,7 @@ mod tests {
|
||||
let transport2 = ScriptedTransport::default();
|
||||
let different = r#"{"seq":1,"ts":1.0,"type":"status","state":"running"}"#.to_string();
|
||||
transport2.respond(200, format!("[{different}]"));
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||
assert!(!source2.probe().unwrap());
|
||||
assert!(source2.cache.tail().is_none());
|
||||
@@ -420,7 +420,7 @@ mod tests {
|
||||
|
||||
let transport2 = ScriptedTransport::default();
|
||||
transport2.respond(500, "server on fire");
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||
assert!(source2.probe().is_err());
|
||||
assert!(
|
||||
@@ -475,7 +475,7 @@ mod tests {
|
||||
|
||||
let transport2 = ScriptedTransport::default();
|
||||
transport2.respond(200, format!("[{}]", status_line(3)));
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||
source2.page(5, 10, true).unwrap();
|
||||
assert_eq!(
|
||||
@@ -491,7 +491,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_server_page_is_floored_at_the_end_of_the_cached_run() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
// A stored page covering [3, 6) and two live events above it, so the run this
|
||||
// phone holds is [3, 8) -- the newest chunk has to be an appended one, or the
|
||||
// cache reads the directory as damaged and discards it.
|
||||
@@ -529,7 +529,7 @@ mod tests {
|
||||
#[test]
|
||||
fn an_unreadable_cached_page_is_a_parse_error_rather_than_an_empty_one() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
cache.store_page(
|
||||
&[r#"{"seq":3,"but":"not an event"}"#.to_string()],
|
||||
3,
|
||||
@@ -551,7 +551,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_bad_cached_opening_line_purges_rather_than_panicking() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let cache = crate::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
let cache = crate::client::transcript_cache::TranscriptCache::new(dir.path()).session("s1");
|
||||
cache.append("not json at all", 1);
|
||||
cache.flush();
|
||||
let transport = ScriptedTransport::default();
|
||||
@@ -7,7 +7,7 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! +-----------+--------------------------------------+
|
||||
//! | session | transcript_ui::TranscriptScreen |
|
||||
//! | session | crate::ui::TranscriptScreen |
|
||||
//! | list | (List of folded rows + composer) |
|
||||
//! | (WidgetPtr| |
|
||||
//! | swapped | (WidgetPtr swapped whole on session |
|
||||
@@ -16,7 +16,7 @@
|
||||
//! ```
|
||||
//!
|
||||
//! **Incoming SSE events go through `TranscriptScreen::apply`**, not a
|
||||
//! full rebuild: `client_core::transcript_fold::fold_event` folds the new
|
||||
//! full rebuild: `crate::client::transcript_fold::fold_event` folds the new
|
||||
//! item list as before, then `apply` updates only the row(s) that actually
|
||||
//! changed (almost always the one still-open assistant message a delta
|
||||
//! landed in) instead of rebuilding the whole right-hand widget tree from
|
||||
@@ -29,7 +29,7 @@
|
||||
//! would otherwise lose data on -- `apply`'s own path never touches the
|
||||
//! composer at all, so this only matters on the fallback.
|
||||
//!
|
||||
//! Background network I/O (`client_core::api`/`event_stream`, both
|
||||
//! Background network I/O (`crate::client::api`/`event_stream`, both
|
||||
//! blocking by design -- see `client-core`'s `Cargo.toml`) runs on plain
|
||||
//! `std::thread`s that report back through `winit`'s `EventLoopProxy`
|
||||
//! (`Proxy<AppEvent>`), rather than through iris's own `Tasks`/`task_on`:
|
||||
@@ -39,9 +39,9 @@
|
||||
//! `Proxy::send_event` wakes the window's event loop immediately, once per
|
||||
//! event, which is what a stream wants.
|
||||
|
||||
use client_core::api::{ApiClient, SessionSummary, UreqTransport};
|
||||
use client_core::event_stream::{StreamItem, follow_session_events};
|
||||
use client_core::transcript_fold::{
|
||||
use crate::client::api::{ApiClient, SessionSummary, UreqTransport};
|
||||
use crate::client::event_stream::{StreamItem, follow_session_events};
|
||||
use crate::client::transcript_fold::{
|
||||
TranscriptItem, fold_event, fold_page, group_tool_runs, raw_seq,
|
||||
};
|
||||
use event_model::SeqEvent;
|
||||
@@ -103,7 +103,7 @@ struct Client {
|
||||
items: Vec<TranscriptItem>,
|
||||
list_ptr: WeakWidget<WidgetPtr>,
|
||||
transcript_ptr: WeakWidget<WidgetPtr>,
|
||||
screen: Option<transcript_ui::TranscriptScreen>,
|
||||
screen: Option<crate::ui::TranscriptScreen>,
|
||||
/// Bumped every time the selected session changes; see `AppEvent`'s
|
||||
/// doc for what it guards against.
|
||||
generation: Arc<AtomicU64>,
|
||||
@@ -123,7 +123,7 @@ impl DefaultAppState for Client {
|
||||
// already called this once to fail fast before a window opens;
|
||||
// this call only fails if the filesystem changed underneath the
|
||||
// process in between, which is not a case worth a nicer message.
|
||||
let (server, ca_pem) = crate::load_startup_config().unwrap_or_else(|e| {
|
||||
let (server, ca_pem) = super::startup::load_startup_config().unwrap_or_else(|e| {
|
||||
eprintln!("desktop-app: {e}");
|
||||
std::process::exit(2);
|
||||
});
|
||||
@@ -364,7 +364,7 @@ impl Client {
|
||||
.filter(|t| !t.is_empty());
|
||||
|
||||
let rows = group_tool_runs(&self.items);
|
||||
let (screen, tree) = transcript_ui::build_tree(rsc, rows);
|
||||
let (screen, tree) = crate::ui::build_tree(rsc, rows);
|
||||
|
||||
if let Some(text) = in_progress {
|
||||
screen.composer.field.edit(rsc).set(&text);
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Where the desktop app keeps its enrollment: `client_core::config`'s
|
||||
//! Where the desktop app keeps its enrollment: `crate::client::config`'s
|
||||
//! [`EnrollmentStore`] pointed at `$XDG_CONFIG_HOME/ai-app-desktop`.
|
||||
//!
|
||||
//! Only the directory is this app's -- the file's name, its JSON, and its
|
||||
@@ -6,7 +6,7 @@
|
||||
//! are the store's, shared with the Android client so the two cannot come
|
||||
//! to disagree about them.
|
||||
|
||||
use client_core::config::EnrollmentStore;
|
||||
use crate::client::config::EnrollmentStore;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// `$XDG_CONFIG_HOME/ai-app-desktop`, falling back to `~/.config` the way
|
||||
@@ -0,0 +1,7 @@
|
||||
//! The desktop entry point's state: the widget tree, the event flow and
|
||||
//! the saved enrolment. `src/bin_desktop.rs` is the binary that drives it
|
||||
//! -- see that file's doc comment for the command line.
|
||||
|
||||
pub mod app;
|
||||
pub mod config;
|
||||
pub mod startup;
|
||||
@@ -1,29 +1,11 @@
|
||||
//! RUST.md's E4: the transcript screen (`transcript-ui`, I5) in a real
|
||||
//! winit window on the desktop, with a session list beside it, talking to
|
||||
//! a real `ai-server` over `client-core`'s REST + SSE clients. See
|
||||
//! `app.rs`'s module doc for the widget tree and the event flow.
|
||||
//!
|
||||
//! Usage:
|
||||
//!
|
||||
//! desktop-app --link 'aiapp://enroll?host=H&port=P&token=T&ca=B'
|
||||
//! desktop-app # after the first run above
|
||||
//! desktop-app --ca /path/to/ca.pem # a link that carries no CA
|
||||
//!
|
||||
//! `--link` is the same text `app/ui-sandbox.sh`'s banner prints and a
|
||||
//! phone would scan as a QR (DECISIONS.md, 2026-09-05) -- pasted rather
|
||||
//! than scanned, since a desktop has no camera to assume. It is parsed and
|
||||
//! saved once; later runs read it back and `--link` is only needed again
|
||||
//! to enrol against a different server.
|
||||
//!
|
||||
//! The CA comes with the link (`wg_app_link::enroll::ca_param`, which
|
||||
//! `ai-server` now always includes) and is saved with it. `--ca` is the
|
||||
//! override for a link that carries none, and names the same
|
||||
//! `certs/ca.pem` a `curl --cacert` call uses.
|
||||
//! The desktop binary's command line and the enrolment it resolves --
|
||||
//! `--link`/`--ca`, parsed once at startup and again from `app.rs`'s
|
||||
//! `Client::new`. Here rather than in `src/bin_desktop.rs` because both
|
||||
//! callers are in the library; the binary is only `fn main`.
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
use crate::client::config::EnrolledServer;
|
||||
|
||||
use client_core::config::EnrolledServer;
|
||||
use super::config;
|
||||
|
||||
struct Args {
|
||||
ca_path: Option<std::path::PathBuf>,
|
||||
@@ -54,7 +36,7 @@ fn parse_args() -> Result<Args, String> {
|
||||
/// argv and config file, so it is safe to call again from `Client::new` --
|
||||
/// see that call site's comment for why it is not threaded through some
|
||||
/// other way (`DefaultApp::run()` takes no payload).
|
||||
fn load_startup_config() -> Result<(EnrolledServer, Vec<u8>), String> {
|
||||
pub fn load_startup_config() -> Result<(EnrolledServer, Vec<u8>), String> {
|
||||
let args = parse_args()?;
|
||||
let store = config::store();
|
||||
let server = match args.link {
|
||||
@@ -92,15 +74,3 @@ fn load_startup_config() -> Result<(EnrolledServer, Vec<u8>), String> {
|
||||
};
|
||||
Ok((server, ca_pem))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Validated once here so a bad `--ca`/`--link` is reported on stderr
|
||||
// before any window opens; `Client::new` calls this same function
|
||||
// again once the window exists, so this first call is a fast-fail
|
||||
// rather than the only place the values come from.
|
||||
if let Err(e) = load_startup_config() {
|
||||
eprintln!("desktop-app: {e}");
|
||||
std::process::exit(2);
|
||||
}
|
||||
app::run();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//! The app -- a phone and desktop interface to AI coding sessions, drawn
|
||||
//! with `iris` (the UI framework next door, which knows nothing about any
|
||||
//! of this) and talking to `ai-server` over the model in `event-model`.
|
||||
//!
|
||||
//! Four modules, and the three entry points that combine them:
|
||||
//!
|
||||
//! - [`client`] -- no UI at all: the REST and SSE clients, the transcript
|
||||
//! cache and fold, the highlighter, the ANSI parser, config and the
|
||||
//! enrolment link. `docs/CLIENT_CORE.md` is its design.
|
||||
//! - [`ui`] -- the screens, as iris widget trees. `docs/RUST.md`'s I5.
|
||||
//! - [`desktop`] -- the winit entry point's app state (`src/bin_desktop.rs`
|
||||
//! is the binary itself).
|
||||
//! - [`android`] -- the `android-view` entry point: JNI registration, the
|
||||
//! view peer, the enrolment deep link and the on-device log.
|
||||
//! - [`shell`] -- a second, separate JNI surface: the bridge the *Kotlin*
|
||||
//! app in `app/shellApp` calls for notifications, sharing and settings.
|
||||
//! It draws nothing, which is why it is behind its own feature.
|
||||
|
||||
pub mod client;
|
||||
|
||||
#[cfg(feature = "screens")]
|
||||
pub mod ui;
|
||||
|
||||
#[cfg(all(feature = "screens", not(target_os = "android")))]
|
||||
pub mod desktop;
|
||||
|
||||
#[cfg(all(feature = "screens", target_os = "android"))]
|
||||
pub mod android;
|
||||
|
||||
// `jni` 0.22's `native_method!` expands to `AtomicBool::fetch_update`,
|
||||
// which this toolchain deprecates in favour of `try_update`. The call is
|
||||
// inside the macro, so there is nothing here to migrate -- the fix is a
|
||||
// `jni` release, and this allow comes out when one lands. Scoped to the
|
||||
// module the macro is used in rather than the crate, so a deprecation in
|
||||
// our own code is still a warning.
|
||||
#[cfg(feature = "shell")]
|
||||
#[allow(deprecated)]
|
||||
pub mod shell;
|
||||
File renamed without changes.
File renamed without changes.
@@ -12,14 +12,14 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
use client_core::api::UreqTransport;
|
||||
use client_core::notifications::{SessionNotification, follow_notifications};
|
||||
use crate::client::api::UreqTransport;
|
||||
use crate::client::notifications::{SessionNotification, follow_notifications};
|
||||
use jni::Env;
|
||||
use jni::errors::Result;
|
||||
use jni::objects::{JObject, JValue};
|
||||
use jni::sys::{JNI_TRUE, jint};
|
||||
|
||||
use crate::settings::{self, ServerSettings};
|
||||
use crate::shell::settings::{self, ServerSettings};
|
||||
|
||||
const ALERT_CHANNEL: &str = "sessions";
|
||||
const ONGOING_CHANNEL: &str = "connection";
|
||||
@@ -53,17 +53,17 @@ static RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
/// handle. So a stop lands at the next reconnect, not mid-read. `/notifications`
|
||||
/// is idle between events (a keep-alive, per `server/src/routes.rs`), so in
|
||||
/// practice this is a bounded wait rather than a hang; closing that gap
|
||||
/// for real means adding a cancellation point to `client_core::Transport`,
|
||||
/// for real means adding a cancellation point to `crate::client::Transport`,
|
||||
/// which is a decision affecting every caller of that trait, not just this
|
||||
/// one -- left for whoever next depends on prompt shutdown.
|
||||
static STOPPING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn static_int(env: &mut Env, class: &str, field: &str) -> Result<i32> {
|
||||
crate::jcall::get_static_field(env, class, field, "I")?.i()
|
||||
crate::shell::jcall::get_static_field(env, class, field, "I")?.i()
|
||||
}
|
||||
|
||||
fn notification_manager<'l>(env: &mut Env<'l>, context: &JObject) -> Result<JObject<'l>> {
|
||||
crate::jcall::call_static_method(
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"androidx/core/app/NotificationManagerCompat",
|
||||
"from",
|
||||
@@ -80,22 +80,22 @@ fn create_channel(
|
||||
name: &str,
|
||||
importance: i32,
|
||||
) -> Result<()> {
|
||||
let id_j = crate::jcall::jstr_obj(env, id)?;
|
||||
let builder = crate::jcall::new_object(
|
||||
let id_j = crate::shell::jcall::jstr_obj(env, id)?;
|
||||
let builder = crate::shell::jcall::new_object(
|
||||
env,
|
||||
"androidx/core/app/NotificationChannelCompat$Builder",
|
||||
"(Ljava/lang/String;I)V",
|
||||
&[JValue::Object(&id_j), JValue::Int(importance)],
|
||||
)?;
|
||||
let name_j = crate::jcall::jstr_obj(env, name)?;
|
||||
crate::jcall::call_method(
|
||||
let name_j = crate::shell::jcall::jstr_obj(env, name)?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"setName",
|
||||
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationChannelCompat$Builder;",
|
||||
&[JValue::Object(&name_j)],
|
||||
)?;
|
||||
let channel = crate::jcall::call_method(
|
||||
let channel = crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"build",
|
||||
@@ -103,7 +103,7 @@ fn create_channel(
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
crate::jcall::call_method(
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
manager,
|
||||
"createNotificationChannel",
|
||||
@@ -146,8 +146,8 @@ fn new_intent_for<'l>(
|
||||
context: &JObject,
|
||||
class_name: &str,
|
||||
) -> Result<JObject<'l>> {
|
||||
let target_class = crate::jcall::find_class(env, class_name)?;
|
||||
crate::jcall::new_object(
|
||||
let target_class = crate::shell::jcall::find_class(env, class_name)?;
|
||||
crate::shell::jcall::new_object(
|
||||
env,
|
||||
"android/content/Intent",
|
||||
"(Landroid/content/Context;Ljava/lang/Class;)V",
|
||||
@@ -165,41 +165,42 @@ fn session_intent<'l>(
|
||||
session_id: &str,
|
||||
) -> Result<JObject<'l>> {
|
||||
let intent = new_intent_for(env, context, "com/example/aiapp/shell/MainActivity")?;
|
||||
let action_view = crate::jcall::jstr_obj(env, "android.intent.action.VIEW")?;
|
||||
crate::jcall::call_method(
|
||||
let action_view = crate::shell::jcall::jstr_obj(env, "android.intent.action.VIEW")?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&intent,
|
||||
"setAction",
|
||||
"(Ljava/lang/String;)Landroid/content/Intent;",
|
||||
&[JValue::Object(&action_view)],
|
||||
)?;
|
||||
let builder = crate::jcall::new_object(env, "android/net/Uri$Builder", "()V", &[])?;
|
||||
let scheme = crate::jcall::jstr_obj(env, settings::SCHEME)?;
|
||||
crate::jcall::call_method(
|
||||
let builder = crate::shell::jcall::new_object(env, "android/net/Uri$Builder", "()V", &[])?;
|
||||
let scheme = crate::shell::jcall::jstr_obj(env, settings::SCHEME)?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"scheme",
|
||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||
&[JValue::Object(&scheme)],
|
||||
)?;
|
||||
let authority = crate::jcall::jstr_obj(env, "session")?;
|
||||
crate::jcall::call_method(
|
||||
let authority = crate::shell::jcall::jstr_obj(env, "session")?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"authority",
|
||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||
&[JValue::Object(&authority)],
|
||||
)?;
|
||||
let path = crate::jcall::jstr_obj(env, session_id)?;
|
||||
crate::jcall::call_method(
|
||||
let path = crate::shell::jcall::jstr_obj(env, session_id)?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"appendPath",
|
||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||
&[JValue::Object(&path)],
|
||||
)?;
|
||||
let uri = crate::jcall::call_method(env, &builder, "build", "()Landroid/net/Uri;", &[])?.l()?;
|
||||
crate::jcall::call_method(
|
||||
let uri = crate::shell::jcall::call_method(env, &builder, "build", "()Landroid/net/Uri;", &[])?
|
||||
.l()?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&intent,
|
||||
"setData",
|
||||
@@ -216,7 +217,7 @@ fn pending_activity<'l>(
|
||||
) -> Result<JObject<'l>> {
|
||||
let update_current = static_int(env, "android/app/PendingIntent", "FLAG_UPDATE_CURRENT")?;
|
||||
let immutable = static_int(env, "android/app/PendingIntent", "FLAG_IMMUTABLE")?;
|
||||
crate::jcall::call_static_method(
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"android/app/PendingIntent",
|
||||
"getActivity",
|
||||
@@ -238,7 +239,7 @@ fn builder_call<'l>(
|
||||
sig: &str,
|
||||
args: &[JValue],
|
||||
) -> Result<()> {
|
||||
crate::jcall::call_method(env, builder, method, sig, args)?;
|
||||
crate::shell::jcall::call_method(env, builder, method, sig, args)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -259,14 +260,14 @@ fn foreground_type(env: &mut Env) -> Result<i32> {
|
||||
}
|
||||
|
||||
fn ongoing_notification<'l>(env: &mut Env<'l>, context: &JObject) -> Result<JObject<'l>> {
|
||||
let channel = crate::jcall::jstr_obj(env, ONGOING_CHANNEL)?;
|
||||
let builder = crate::jcall::new_object(
|
||||
let channel = crate::shell::jcall::jstr_obj(env, ONGOING_CHANNEL)?;
|
||||
let builder = crate::shell::jcall::new_object(
|
||||
env,
|
||||
"androidx/core/app/NotificationCompat$Builder",
|
||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||
&[JValue::Object(context), JValue::Object(&channel)],
|
||||
)?;
|
||||
let title = crate::jcall::jstr_obj(env, "Watching for sessions that need you")?;
|
||||
let title = crate::shell::jcall::jstr_obj(env, "Watching for sessions that need you")?;
|
||||
builder_call(
|
||||
env,
|
||||
&builder,
|
||||
@@ -297,7 +298,8 @@ fn ongoing_notification<'l>(env: &mut Env<'l>, context: &JObject) -> Result<JObj
|
||||
"(I)Landroidx/core/app/NotificationCompat$Builder;",
|
||||
&[JValue::Int(priority_min)],
|
||||
)?;
|
||||
crate::jcall::call_method(env, &builder, "build", "()Landroid/app/Notification;", &[])?.l()
|
||||
crate::shell::jcall::call_method(env, &builder, "build", "()Landroid/app/Notification;", &[])?
|
||||
.l()
|
||||
}
|
||||
|
||||
/// Starts the service if there is a server to connect to, and stops it
|
||||
@@ -306,7 +308,7 @@ pub fn sync(env: &mut Env, context: &JObject) -> Result<()> {
|
||||
let service_intent =
|
||||
new_intent_for(env, context, "com/example/aiapp/shell/NotificationService")?;
|
||||
if settings::load(env, context)?.is_none() {
|
||||
crate::jcall::call_method(
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
context,
|
||||
"stopService",
|
||||
@@ -316,7 +318,7 @@ pub fn sync(env: &mut Env, context: &JObject) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
create_channels(env, context)?;
|
||||
crate::jcall::call_static_method(
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"androidx/core/content/ContextCompat",
|
||||
"startForegroundService",
|
||||
@@ -335,7 +337,7 @@ pub fn on_start_command(env: &mut Env, service: JObject) -> jint {
|
||||
match try_start(env, &service) {
|
||||
Ok(true) => static_int(env, "android/app/Service", "START_STICKY").unwrap_or(1),
|
||||
Ok(false) => {
|
||||
let _ = crate::jcall::call_method(env, &service, "stopSelf", "()V", &[]);
|
||||
let _ = crate::shell::jcall::call_method(env, &service, "stopSelf", "()V", &[]);
|
||||
static_int(env, "android/app/Service", "START_NOT_STICKY").unwrap_or(2)
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -352,7 +354,7 @@ fn try_start(env: &mut Env, service: &JObject) -> Result<bool> {
|
||||
let ca = settings::load_pinned_ca(env)?;
|
||||
let notification = ongoing_notification(env, service)?;
|
||||
let fg_type = foreground_type(env)?;
|
||||
crate::jcall::call_static_method(
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"androidx/core/app/ServiceCompat",
|
||||
"startForeground",
|
||||
@@ -424,13 +426,14 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
||||
let allowed = if sdk < tiramisu {
|
||||
true
|
||||
} else {
|
||||
let permission = crate::jcall::jstr_obj(env, "android.permission.POST_NOTIFICATIONS")?;
|
||||
let permission =
|
||||
crate::shell::jcall::jstr_obj(env, "android.permission.POST_NOTIFICATIONS")?;
|
||||
let granted = static_int(
|
||||
env,
|
||||
"android/content/pm/PackageManager",
|
||||
"PERMISSION_GRANTED",
|
||||
)?;
|
||||
let result = crate::jcall::call_static_method(
|
||||
let result = crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"androidx/core/content/ContextCompat",
|
||||
"checkSelfPermission",
|
||||
@@ -441,20 +444,21 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
||||
result == granted
|
||||
};
|
||||
let enabled =
|
||||
crate::jcall::call_method(env, &manager, "areNotificationsEnabled", "()Z", &[])?.z()?;
|
||||
crate::shell::jcall::call_method(env, &manager, "areNotificationsEnabled", "()Z", &[])?
|
||||
.z()?;
|
||||
if !allowed || !enabled {
|
||||
return Ok(());
|
||||
}
|
||||
let intent = session_intent(env, context, ¬ification.session_id)?;
|
||||
let pending = pending_activity(env, context, &intent)?;
|
||||
let channel = crate::jcall::jstr_obj(env, ALERT_CHANNEL)?;
|
||||
let builder = crate::jcall::new_object(
|
||||
let channel = crate::shell::jcall::jstr_obj(env, ALERT_CHANNEL)?;
|
||||
let builder = crate::shell::jcall::new_object(
|
||||
env,
|
||||
"androidx/core/app/NotificationCompat$Builder",
|
||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||
&[JValue::Object(context), JValue::Object(&channel)],
|
||||
)?;
|
||||
let title = crate::jcall::jstr_obj(env, ¬ification.title)?;
|
||||
let title = crate::shell::jcall::jstr_obj(env, ¬ification.title)?;
|
||||
builder_call(
|
||||
env,
|
||||
&builder,
|
||||
@@ -462,7 +466,7 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
||||
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationCompat$Builder;",
|
||||
&[JValue::Object(&title)],
|
||||
)?;
|
||||
let text = crate::jcall::jstr_obj(env, notification.kind.attention_line())?;
|
||||
let text = crate::shell::jcall::jstr_obj(env, notification.kind.attention_line())?;
|
||||
builder_call(
|
||||
env,
|
||||
&builder,
|
||||
@@ -507,11 +511,16 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
||||
"(Z)Landroidx/core/app/NotificationCompat$Builder;",
|
||||
&[JValue::Bool(JNI_TRUE)],
|
||||
)?;
|
||||
let built =
|
||||
crate::jcall::call_method(env, &builder, "build", "()Landroid/app/Notification;", &[])?
|
||||
let built = crate::shell::jcall::call_method(
|
||||
env,
|
||||
&builder,
|
||||
"build",
|
||||
"()Landroid/app/Notification;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
let tag = crate::jcall::jstr_obj(env, ¬ification.session_id)?;
|
||||
crate::jcall::call_method(
|
||||
let tag = crate::shell::jcall::jstr_obj(env, ¬ification.session_id)?;
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&manager,
|
||||
"notify",
|
||||
@@ -542,9 +551,9 @@ pub fn on_destroy() {
|
||||
pub fn log_error(env: &mut Env, where_: &str, error: &jni::errors::Error) {
|
||||
let message = format!("android-shell: {where_}: {error}");
|
||||
let _ = (|| -> Result<()> {
|
||||
let tag = crate::jcall::jstr_obj(env, "android-shell")?;
|
||||
let msg = crate::jcall::jstr_obj(env, &message)?;
|
||||
crate::jcall::call_static_method(
|
||||
let tag = crate::shell::jcall::jstr_obj(env, "android-shell")?;
|
||||
let msg = crate::shell::jcall::jstr_obj(env, &message)?;
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"android/util/Log",
|
||||
"e",
|
||||
@@ -39,9 +39,9 @@ const STORE_CLASS: &str = "com/example/wgapplink/ServerStore";
|
||||
const SETTINGS_CLASS: &str = "com/example/wgapplink/ServerSettings";
|
||||
|
||||
fn new_store<'l>(env: &mut Env<'l>) -> Result<JObject<'l>> {
|
||||
let scheme = crate::jcall::jstr_obj(env, SCHEME)?;
|
||||
let alias = crate::jcall::jstr_obj(env, KEY_ALIAS)?;
|
||||
crate::jcall::new_object(
|
||||
let scheme = crate::shell::jcall::jstr_obj(env, SCHEME)?;
|
||||
let alias = crate::shell::jcall::jstr_obj(env, KEY_ALIAS)?;
|
||||
crate::shell::jcall::new_object(
|
||||
env,
|
||||
STORE_CLASS,
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
@@ -51,13 +51,14 @@ fn new_store<'l>(env: &mut Env<'l>) -> Result<JObject<'l>> {
|
||||
|
||||
fn read_settings(env: &mut Env, settings_obj: &JObject) -> Result<ServerSettings> {
|
||||
let host = get_string(env, settings_obj, "getHost")?;
|
||||
let port = crate::jcall::call_method(env, settings_obj, "getPort", "()I", &[])?.i()?;
|
||||
let port = crate::shell::jcall::call_method(env, settings_obj, "getPort", "()I", &[])?.i()?;
|
||||
let token = get_string(env, settings_obj, "getToken")?;
|
||||
Ok(ServerSettings { host, port, token })
|
||||
}
|
||||
|
||||
fn get_string(env: &mut Env, obj: &JObject, getter: &str) -> Result<String> {
|
||||
let value = crate::jcall::call_method(env, obj, getter, "()Ljava/lang/String;", &[])?.l()?;
|
||||
let value =
|
||||
crate::shell::jcall::call_method(env, obj, getter, "()Ljava/lang/String;", &[])?.l()?;
|
||||
let jstr: JString = env.cast_local::<JString>(value)?;
|
||||
jstr.try_to_string(env)
|
||||
}
|
||||
@@ -66,7 +67,7 @@ fn get_string(env: &mut Env, obj: &JObject, getter: &str) -> Result<String> {
|
||||
/// `ServerConfig.kt`'s `loadServerSettings`.
|
||||
pub fn load(env: &mut Env, context: &JObject) -> Result<Option<ServerSettings>> {
|
||||
let store = new_store(env)?;
|
||||
let settings_obj = crate::jcall::call_method(
|
||||
let settings_obj = crate::shell::jcall::call_method(
|
||||
env,
|
||||
&store,
|
||||
"load",
|
||||
@@ -83,9 +84,9 @@ pub fn load(env: &mut Env, context: &JObject) -> Result<Option<ServerSettings>>
|
||||
/// Seals and stores `settings` -- mirrors `ServerConfig.kt`'s `saveServerSettings`.
|
||||
pub fn save(env: &mut Env, context: &JObject, settings: &ServerSettings) -> Result<()> {
|
||||
let store = new_store(env)?;
|
||||
let host = crate::jcall::jstr_obj(env, &settings.host)?;
|
||||
let token = crate::jcall::jstr_obj(env, &settings.token)?;
|
||||
let settings_obj = crate::jcall::new_object(
|
||||
let host = crate::shell::jcall::jstr_obj(env, &settings.host)?;
|
||||
let token = crate::shell::jcall::jstr_obj(env, &settings.token)?;
|
||||
let settings_obj = crate::shell::jcall::new_object(
|
||||
env,
|
||||
SETTINGS_CLASS,
|
||||
"(Ljava/lang/String;ILjava/lang/String;)V",
|
||||
@@ -95,7 +96,7 @@ pub fn save(env: &mut Env, context: &JObject, settings: &ServerSettings) -> Resu
|
||||
JValue::Object(&token),
|
||||
],
|
||||
)?;
|
||||
crate::jcall::call_method(
|
||||
crate::shell::jcall::call_method(
|
||||
env,
|
||||
&store,
|
||||
"save",
|
||||
@@ -110,7 +111,7 @@ pub fn save(env: &mut Env, context: &JObject, settings: &ServerSettings) -> Resu
|
||||
/// query-parameter rules rather than re-deriving them here.
|
||||
pub fn parse_enrollment_uri(env: &mut Env, uri: &JObject) -> Result<Option<ServerSettings>> {
|
||||
let store = new_store(env)?;
|
||||
let settings_obj = crate::jcall::call_method(
|
||||
let settings_obj = crate::shell::jcall::call_method(
|
||||
env,
|
||||
&store,
|
||||
"parseEnrollmentUri",
|
||||
@@ -129,7 +130,7 @@ pub fn parse_enrollment_uri(env: &mut Env, uri: &JObject) -> Result<Option<Serve
|
||||
/// but into a plain Java constant, since this module has no Kotlin of its
|
||||
/// own to generate into.
|
||||
pub fn load_pinned_ca(env: &mut Env) -> Result<Vec<u8>> {
|
||||
let value = crate::jcall::get_static_field(
|
||||
let value = crate::shell::jcall::get_static_field(
|
||||
env,
|
||||
"com/example/aiapp/shell/PinnedCa",
|
||||
"PINNED_CA_PEM",
|
||||
@@ -18,13 +18,13 @@
|
||||
//! likely to be what somebody meant. Worth revisiting once a real screen
|
||||
//! exists to ask instead of guessing.
|
||||
|
||||
use client_core::api::{ApiClient, UreqTransport};
|
||||
use crate::client::api::{ApiClient, UreqTransport};
|
||||
use jni::Env;
|
||||
use jni::errors::Result;
|
||||
use jni::objects::{JObject, JString, JValue};
|
||||
|
||||
use crate::notify;
|
||||
use crate::settings;
|
||||
use crate::shell::notify;
|
||||
use crate::shell::settings;
|
||||
|
||||
const ACTION_SEND: &str = "android.intent.action.SEND";
|
||||
const ACTION_SEND_MULTIPLE: &str = "android.intent.action.SEND_MULTIPLE";
|
||||
@@ -32,7 +32,8 @@ const ACTION_VIEW: &str = "android.intent.action.VIEW";
|
||||
const EXTRA_TEXT: &str = "android.intent.extra.TEXT";
|
||||
|
||||
fn get_string_method(env: &mut Env, obj: &JObject, method: &str) -> Result<Option<String>> {
|
||||
let value = crate::jcall::call_method(env, obj, method, "()Ljava/lang/String;", &[])?.l()?;
|
||||
let value =
|
||||
crate::shell::jcall::call_method(env, obj, method, "()Ljava/lang/String;", &[])?.l()?;
|
||||
if value.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -41,8 +42,8 @@ fn get_string_method(env: &mut Env, obj: &JObject, method: &str) -> Result<Optio
|
||||
}
|
||||
|
||||
fn toast(env: &mut Env, context: &JObject, message: &str) -> Result<()> {
|
||||
let message = crate::jcall::jstr_obj(env, message)?;
|
||||
crate::jcall::call_static_method(
|
||||
let message = crate::shell::jcall::jstr_obj(env, message)?;
|
||||
crate::shell::jcall::call_static_method(
|
||||
env,
|
||||
"com/example/aiapp/shell/MainActivity",
|
||||
"toast",
|
||||
@@ -65,7 +66,8 @@ pub fn handle_intent(env: &mut Env, activity: &JObject, intent: &JObject) -> Res
|
||||
if action.as_deref() != Some(ACTION_VIEW) {
|
||||
return Ok(());
|
||||
}
|
||||
let uri = crate::jcall::call_method(env, intent, "getData", "()Landroid/net/Uri;", &[])?.l()?;
|
||||
let uri = crate::shell::jcall::call_method(env, intent, "getData", "()Landroid/net/Uri;", &[])?
|
||||
.l()?;
|
||||
if uri.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -109,8 +111,8 @@ fn handle_enrollment(env: &mut Env, activity: &JObject, uri: &JObject) -> Result
|
||||
/// as a share, and `AttachmentButton`'s upload-then-message pattern for
|
||||
/// what happens to it, minus attachments per this module's doc comment.
|
||||
fn handle_share(env: &mut Env, activity: &JObject, intent: &JObject) -> Result<()> {
|
||||
let extra_text = crate::jcall::jstr_obj(env, EXTRA_TEXT)?;
|
||||
let text = crate::jcall::call_method(
|
||||
let extra_text = crate::shell::jcall::jstr_obj(env, EXTRA_TEXT)?;
|
||||
let text = crate::shell::jcall::call_method(
|
||||
env,
|
||||
intent,
|
||||
"getStringExtra",
|
||||
File renamed without changes.
@@ -5,7 +5,7 @@
|
||||
//! bytes, generated by `app/bench-fixture/generate.py`, never a real
|
||||
//! transcript -- that file's own README), embedded with `include_str!`.
|
||||
//! The first [`BACKLOG_COUNT`] non-blank lines are the opening window,
|
||||
//! folded once through `client_core::transcript_fold::fold_page` exactly
|
||||
//! folded once through `crate::client::transcript_fold::fold_page` exactly
|
||||
//! as a real `/transcript` page would be; the rest are the streaming
|
||||
//! tail, replayed one at a time through `fold_event` the way a live SSE
|
||||
//! frame arrives.
|
||||
@@ -16,7 +16,7 @@
|
||||
//! UI-shaped in a platform crate). What stayed there is the JNI half --
|
||||
//! the clipboard, the battery sampler, the IME calls and the report.
|
||||
|
||||
use client_core::transcript_fold::{TranscriptItem, TranscriptRow, fold_page, group_tool_runs};
|
||||
use crate::client::transcript_fold::{TranscriptItem, TranscriptRow, fold_page, group_tool_runs};
|
||||
use event_model::SeqEvent;
|
||||
use iris::prelude::*;
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn rows(items: &[TranscriptItem]) -> Vec<TranscriptRow> {
|
||||
/// streamed. The tree itself comes back separately from
|
||||
/// [`build_screen`], since whoever takes it owns it.
|
||||
pub struct Opened {
|
||||
pub screen: transcript_ui::TranscriptScreen,
|
||||
pub screen: crate::ui::TranscriptScreen,
|
||||
pub items: Vec<TranscriptItem>,
|
||||
/// The tail, for a caller that goes on replaying it one event at a
|
||||
/// time through `fold_event`/`TranscriptScreen::apply` -- the
|
||||
@@ -114,7 +114,7 @@ pub struct Opened {
|
||||
}
|
||||
|
||||
/// Build the transcript screen over the fixture's opening page, without
|
||||
/// claiming the window's root -- `transcript_ui::build_tree`'s own split,
|
||||
/// claiming the window's root -- `crate::ui::build_tree`'s own split,
|
||||
/// for a caller (the Android bench) that puts the screen inside a shell
|
||||
/// of its own.
|
||||
pub fn build_screen<Rsc: HasEvents>(rsc: &mut Rsc) -> Result<(Opened, StrongWidget), String>
|
||||
@@ -123,7 +123,7 @@ where
|
||||
{
|
||||
let fixture = Fixture::parse();
|
||||
let items = fixture.backlog_items()?;
|
||||
let (screen, tree) = transcript_ui::build_tree(rsc, rows(&items));
|
||||
let (screen, tree) = crate::ui::build_tree(rsc, rows(&items));
|
||||
Ok((
|
||||
Opened {
|
||||
screen,
|
||||
@@ -1,4 +1,4 @@
|
||||
//! One markdown **block** (`client_core::markdown_blocks::Block`) rendered
|
||||
//! One markdown **block** (`crate::client::markdown_blocks::Block`) rendered
|
||||
//! for display: the plain text to draw, the [`SpanStyle`]s that style it,
|
||||
//! the links inside it, and the [`BlockFrame`] the row builder puts around
|
||||
//! it.
|
||||
@@ -44,8 +44,8 @@
|
||||
//! paragraph's -- visible, not incorrect, and not fixed here since it
|
||||
//! needs `SpanStyle` to carry line-height too.
|
||||
|
||||
use client_core::highlight::{self, Kind, Language};
|
||||
use client_core::markdown_blocks::{Block, BlockKind};
|
||||
use crate::client::highlight::{self, Kind, Language};
|
||||
use crate::client::markdown_blocks::{Block, BlockKind};
|
||||
use iris::prelude::*;
|
||||
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd};
|
||||
use std::ops::Range;
|
||||
@@ -385,14 +385,14 @@ pub fn render_markdown(src: &str, base_size: f32) -> Rendered {
|
||||
}
|
||||
}
|
||||
|
||||
/// The same option set `client_core::markdown_blocks` splits with, so a
|
||||
/// The same option set `crate::client::markdown_blocks` splits with, so a
|
||||
/// block boundary there and the styling here cannot disagree about what
|
||||
/// the source means.
|
||||
fn options() -> Options {
|
||||
Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES | Options::ENABLE_TASKLISTS
|
||||
}
|
||||
|
||||
/// `client_core::highlight`'s spans for the code at `range` inside `text`,
|
||||
/// `crate::client::highlight`'s spans for the code at `range` inside `text`,
|
||||
/// appended to `spans`.
|
||||
///
|
||||
/// The highlighter indexes **chars** and `SpanStyle` indexes **bytes**
|
||||
@@ -567,7 +567,7 @@ fn wrap_cell(text: &str, width: usize) -> Vec<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use client_core::markdown_blocks::split_blocks;
|
||||
use crate::client::markdown_blocks::split_blocks;
|
||||
|
||||
fn block(src: &str) -> Rendered {
|
||||
let blocks = split_blocks(src);
|
||||
@@ -7,22 +7,22 @@
|
||||
//!
|
||||
//! ```text
|
||||
//! +------------------------------------------+
|
||||
//! | iris::widget::LazySpan (transcript_ui::row) | <- .height(rest(1))
|
||||
//! | iris::widget::LazySpan (crate::ui::row) | <- .height(rest(1))
|
||||
//! | row 1: sender label + one TextEdit |
|
||||
//! | row 2: sender label + one TextEdit |
|
||||
//! | row 3 (Tools): collapsed/expanded |
|
||||
//! | ... |
|
||||
//! +------------------------------------------+
|
||||
//! | composer bar (transcript_ui::composer) | <- natural height
|
||||
//! | composer bar (crate::ui::composer) | <- natural height
|
||||
//! +------------------------------------------+
|
||||
//! ```
|
||||
//!
|
||||
//! **What this crate does not do itself**: fetch anything over the network
|
||||
//! or read the transcript cache. [`build`] takes an already-folded
|
||||
//! `Vec<client_core::transcript_fold::TranscriptRow>` and
|
||||
//! `Vec<crate::client::transcript_fold::TranscriptRow>` and
|
||||
//! [`TranscriptScreen::push_row`] takes one more as it arrives -- the
|
||||
//! caller (an app's own `main`, or a future `iris-android-app`-shaped
|
||||
//! cdylib) owns `client_core::ApiClient`/
|
||||
//! cdylib) owns `crate::client::ApiClient`/
|
||||
//! `event_stream::follow_session_events` and the transcript cache, per the
|
||||
//! code rules' "ask for the least you need": a widget-tree builder that
|
||||
//! also knew how to make an HTTPS request would be untestable without a
|
||||
@@ -44,13 +44,19 @@
|
||||
//! row's own text too.
|
||||
|
||||
pub mod composer;
|
||||
// The checked-in bench fixture opened as a real screen -- 1.9 MB of
|
||||
// `include_str!`, so it is a feature rather than always present: a build
|
||||
// meant for a phone must not carry it. `bench` turns it on; so does the
|
||||
// default, which is what makes `cargo test` here run the harness tests.
|
||||
#[cfg(feature = "fixture")]
|
||||
pub mod fixture;
|
||||
pub mod markdown;
|
||||
pub mod row;
|
||||
pub mod selection;
|
||||
pub(crate) mod tap;
|
||||
pub mod tool;
|
||||
|
||||
use client_core::transcript_fold::TranscriptRow as FoldedRow;
|
||||
use crate::client::transcript_fold::TranscriptRow as FoldedRow;
|
||||
use iris::prelude::*;
|
||||
use selection::Selection;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -111,7 +117,7 @@ impl TranscriptScreen {
|
||||
}
|
||||
|
||||
/// Whether the session this transcript belongs to is still doing
|
||||
/// something (`client_core::transcript_fold::session_working`).
|
||||
/// something (`crate::client::transcript_fold::session_working`).
|
||||
///
|
||||
/// The one thing a tool card cannot read off its own call: a call with
|
||||
/// no result is *running* while the session works and *never came
|
||||
@@ -189,7 +195,7 @@ impl TranscriptScreen {
|
||||
// row -- rebuild it.
|
||||
if matches!(
|
||||
item,
|
||||
client_core::transcript_fold::TranscriptItem::ToolRun { .. }
|
||||
crate::client::transcript_fold::TranscriptItem::ToolRun { .. }
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -214,7 +220,7 @@ impl TranscriptScreen {
|
||||
|
||||
/// Apply the effect of one more folded event without rebuilding the
|
||||
/// whole screen -- RUST.md's "streaming still costs a full rebuild"
|
||||
/// fix. `old`/`new` are `client_core::transcript_fold::fold_event`'s
|
||||
/// fix. `old`/`new` are `crate::client::transcript_fold::fold_event`'s
|
||||
/// own before/after item lists (never grouped into rows -- that
|
||||
/// happens here, over both, so the common tail cases can be told
|
||||
/// apart; `group_tool_runs` is pure bookkeeping over already-folded
|
||||
@@ -245,12 +251,12 @@ impl TranscriptScreen {
|
||||
pub fn apply<Rsc: HasEvents>(
|
||||
&self,
|
||||
rsc: &mut Rsc,
|
||||
old: &[client_core::transcript_fold::TranscriptItem],
|
||||
new: &[client_core::transcript_fold::TranscriptItem],
|
||||
old: &[crate::client::transcript_fold::TranscriptItem],
|
||||
new: &[crate::client::transcript_fold::TranscriptItem],
|
||||
) where
|
||||
Rsc::State: FocusHost + OpenUrl,
|
||||
{
|
||||
use client_core::transcript_fold::group_tool_runs;
|
||||
use crate::client::transcript_fold::group_tool_runs;
|
||||
|
||||
let old_rows = group_tool_runs(old);
|
||||
let new_rows = group_tool_runs(new);
|
||||
@@ -521,7 +527,7 @@ fn diff_rows(old: &[FoldedRow], new: &[FoldedRow]) -> RowDiff {
|
||||
#[cfg(test)]
|
||||
mod diff_tests {
|
||||
use super::*;
|
||||
use client_core::transcript_fold::TranscriptItem;
|
||||
use crate::client::transcript_fold::TranscriptItem;
|
||||
|
||||
fn user(seq: u64, text: &str) -> FoldedRow {
|
||||
FoldedRow::Single(TranscriptItem::UserMsg {
|
||||
@@ -627,7 +633,7 @@ mod diff_tests {
|
||||
#[cfg(test)]
|
||||
mod apply_tests {
|
||||
use super::*;
|
||||
use client_core::transcript_fold::TranscriptItem;
|
||||
use crate::client::transcript_fold::TranscriptItem;
|
||||
|
||||
struct TestFocus {
|
||||
focus: Option<WeakWidget<TextEdit>>,
|
||||
@@ -744,7 +750,7 @@ mod apply_tests {
|
||||
)];
|
||||
let (screen, tree) = build_tree(
|
||||
&mut rsc,
|
||||
client_core::transcript_fold::group_tool_runs(&old_items),
|
||||
crate::client::transcript_fold::group_tool_runs(&old_items),
|
||||
);
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((1080.0, 20000.0));
|
||||
@@ -799,7 +805,7 @@ mod apply_tests {
|
||||
|
||||
#[test]
|
||||
fn a_row_dropped_by_a_regroup_does_not_outlive_itself_in_selection() {
|
||||
use client_core::transcript_fold::group_tool_runs;
|
||||
use crate::client::transcript_fold::group_tool_runs;
|
||||
|
||||
let mut rsc = TestRsc {
|
||||
ui: UiData::default(),
|
||||
@@ -824,7 +830,7 @@ mod apply_tests {
|
||||
// land on; `begin` deselects every *other* registered row first,
|
||||
// which is exactly what used to resolve a stale `WeakWidget` left
|
||||
// by the regrouped-away rows and panic.
|
||||
let surviving_key = row::row_key(&client_core::transcript_fold::ItemKey::Seq(4));
|
||||
let surviving_key = row::row_key(&crate::client::transcript_fold::ItemKey::Seq(4));
|
||||
screen.selection.borrow_mut().begin(
|
||||
&mut rsc,
|
||||
(surviving_key, 0),
|
||||
@@ -844,7 +850,7 @@ mod apply_tests {
|
||||
/// `if new_key != old_key` guard this replaced could not see it.
|
||||
#[test]
|
||||
fn a_tail_rebuilt_with_fewer_blocks_leaves_none_of_them_in_selection() {
|
||||
use client_core::transcript_fold::group_tool_runs;
|
||||
use crate::client::transcript_fold::group_tool_runs;
|
||||
|
||||
let mut rsc = TestRsc {
|
||||
ui: UiData::default(),
|
||||
@@ -862,7 +868,7 @@ mod apply_tests {
|
||||
);
|
||||
|
||||
let (screen, _tree) = build_tree(&mut rsc, group_tool_runs(&old_items));
|
||||
let tail_key = row::row_key(&client_core::transcript_fold::ItemKey::Seq(2));
|
||||
let tail_key = row::row_key(&crate::client::transcript_fold::ItemKey::Seq(2));
|
||||
assert_eq!(
|
||||
screen
|
||||
.selection
|
||||
@@ -886,7 +892,7 @@ mod apply_tests {
|
||||
|
||||
// What a reader does next: press the row that survived. `begin`
|
||||
// resolves every registered handle, so a stale one panics here.
|
||||
let surviving_key = row::row_key(&client_core::transcript_fold::ItemKey::Seq(1));
|
||||
let surviving_key = row::row_key(&crate::client::transcript_fold::ItemKey::Seq(1));
|
||||
screen.selection.borrow_mut().begin(
|
||||
&mut rsc,
|
||||
(surviving_key, 0),
|
||||
@@ -924,7 +930,8 @@ mod apply_tests {
|
||||
rsc: &mut TestRsc,
|
||||
items: &[TranscriptItem],
|
||||
) -> (TranscriptScreen, StrongWidget, UiRenderState) {
|
||||
let (screen, tree) = build_tree(rsc, client_core::transcript_fold::group_tool_runs(items));
|
||||
let (screen, tree) =
|
||||
build_tree(rsc, crate::client::transcript_fold::group_tool_runs(items));
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((1080.0, 20000.0));
|
||||
render.update(&tree, rsc);
|
||||
@@ -948,7 +955,7 @@ mod apply_tests {
|
||||
let items = run_of(3, output, true);
|
||||
let (screen, tree) = build_tree(
|
||||
&mut rsc,
|
||||
client_core::transcript_fold::group_tool_runs(&items),
|
||||
crate::client::transcript_fold::group_tool_runs(&items),
|
||||
);
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((1080.0, 20000.0));
|
||||
@@ -1017,7 +1024,7 @@ mod apply_tests {
|
||||
];
|
||||
let (_screen, tree) = build_tree(
|
||||
&mut rsc,
|
||||
client_core::transcript_fold::group_tool_runs(&items),
|
||||
crate::client::transcript_fold::group_tool_runs(&items),
|
||||
);
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((1080.0, 20000.0));
|
||||
@@ -1038,8 +1045,8 @@ mod apply_tests {
|
||||
#[test]
|
||||
fn a_long_message_is_drawn_only_as_far_as_the_cap() {
|
||||
let paragraphs = |n: usize| "a paragraph of a reply\n\n".repeat(n);
|
||||
let capped = shapes_for_message(¶graphs(client_core::text_cap::MESSAGE_LINES * 4));
|
||||
let bigger = shapes_for_message(¶graphs(client_core::text_cap::MESSAGE_LINES * 40));
|
||||
let capped = shapes_for_message(¶graphs(crate::client::text_cap::MESSAGE_LINES * 4));
|
||||
let bigger = shapes_for_message(¶graphs(crate::client::text_cap::MESSAGE_LINES * 40));
|
||||
assert!(
|
||||
capped > 0,
|
||||
"the screen shaped nothing, so this compares zeroes"
|
||||
@@ -1112,7 +1119,7 @@ mod apply_tests {
|
||||
|
||||
let (screen, tree) = build_tree(
|
||||
&mut rsc,
|
||||
client_core::transcript_fold::group_tool_runs(&before),
|
||||
crate::client::transcript_fold::group_tool_runs(&before),
|
||||
);
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((1080.0, 20000.0));
|
||||
@@ -1180,7 +1187,7 @@ mod apply_tests {
|
||||
let after = vec![user(1, "stable"), user(2, "not a tool call at all")];
|
||||
let (screen, _tree) = build_tree(
|
||||
&mut rsc,
|
||||
client_core::transcript_fold::group_tool_runs(&before),
|
||||
crate::client::transcript_fold::group_tool_runs(&before),
|
||||
);
|
||||
screen.apply(&mut rsc, &before, &after);
|
||||
assert_eq!(
|
||||
@@ -1,7 +1,7 @@
|
||||
//! One `iris::widget::list::LazyItem` per folded transcript row
|
||||
//! (`client_core::transcript_fold::TranscriptRow`). A row is a **column of
|
||||
//! (`crate::client::transcript_fold::TranscriptRow`). A row is a **column of
|
||||
//! one `TextEdit` per top-level markdown block** (paragraph, heading,
|
||||
//! fence, list, table -- `client_core::markdown_blocks`), each rendered
|
||||
//! fence, list, table -- `crate::client::markdown_blocks`), each rendered
|
||||
//! with `markdown`'s inline spans, so that RUST.md's "hard to get back"
|
||||
//! behaviour 2 (rich inline text) still holds within a block and
|
||||
//! behaviour 1 (selection) runs across blocks and rows alike through
|
||||
@@ -15,7 +15,7 @@
|
||||
//! were and why this one.
|
||||
//!
|
||||
//! A `TranscriptRow::Tools` (a run of adjacent tool calls, grouped by
|
||||
//! `client_core::transcript_fold::group_tool_runs`) is the row that proves
|
||||
//! `crate::client::transcript_fold::group_tool_runs`) is the row that proves
|
||||
//! behaviour 3's "hold the edge nearest the tap" on expand: tapping its
|
||||
//! header calls `LazySpan::note_tap` at the row's own on-screen position
|
||||
//! (read back from `LazySpan::extent`, since the tap event only knows its
|
||||
@@ -23,13 +23,13 @@
|
||||
//! collapsed summary and the full detail -- the same two-step contract
|
||||
//! `lazy_span.rs`'s module doc describes for `AGENTS.md`'s `holdTopEdge`.
|
||||
|
||||
use crate::markdown::{BlockFrame, Link, frame_of, render_block};
|
||||
use crate::selection::{SelKey, Selection};
|
||||
use crate::tap::{hold_edge, on_tap};
|
||||
use crate::tool::ToolRow;
|
||||
use client_core::markdown_blocks::{Block, BlockKind, common_prefix, split_blocks};
|
||||
use client_core::text_cap::{MESSAGE_BYTES, MESSAGE_LINES, cut, show_all_label};
|
||||
use client_core::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||
use crate::client::markdown_blocks::{Block, BlockKind, common_prefix, split_blocks};
|
||||
use crate::client::text_cap::{MESSAGE_BYTES, MESSAGE_LINES, cut, show_all_label};
|
||||
use crate::client::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||
use crate::ui::markdown::{BlockFrame, Link, frame_of, render_block};
|
||||
use crate::ui::selection::{SelKey, Selection};
|
||||
use crate::ui::tap::{hold_edge, on_tap};
|
||||
use crate::ui::tool::ToolRow;
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
@@ -53,8 +53,8 @@ pub const BASE_SIZE: f32 = 16.0;
|
||||
/// tool-call rows sharing a list slot, not data loss), and the high bit is
|
||||
/// forced on so a hashed key can never collide with a real sequence number
|
||||
/// (this build never produces 2^63 events).
|
||||
pub fn row_key(key: &client_core::transcript_fold::ItemKey) -> RowKey {
|
||||
use client_core::transcript_fold::ItemKey;
|
||||
pub fn row_key(key: &crate::client::transcript_fold::ItemKey) -> RowKey {
|
||||
use crate::client::transcript_fold::ItemKey;
|
||||
use std::hash::{Hash, Hasher};
|
||||
match key {
|
||||
ItemKey::Seq(seq) => *seq,
|
||||
@@ -132,7 +132,7 @@ fn tool_call_markdown(tool: &str, input: &str, output: &str) -> String {
|
||||
pub struct RowBlocks {
|
||||
/// What each field was built from, in order -- compared against a
|
||||
/// fresh split to decide what may be kept. See
|
||||
/// `client_core::markdown_blocks`' module doc for why this is a
|
||||
/// `crate::client::markdown_blocks`' module doc for why this is a
|
||||
/// comparison and not an assumption.
|
||||
blocks: Vec<Block>,
|
||||
fields: Vec<WeakWidget<TextEdit>>,
|
||||
@@ -291,8 +291,8 @@ where
|
||||
})
|
||||
.size(BASE_SIZE)
|
||||
.color(match frame {
|
||||
BlockFrame::Quote => crate::markdown::QUOTE_TEXT_COLOR,
|
||||
_ => crate::markdown::TEXT_COLOR,
|
||||
BlockFrame::Quote => crate::ui::markdown::QUOTE_TEXT_COLOR,
|
||||
_ => crate::ui::markdown::TEXT_COLOR,
|
||||
})
|
||||
.add(rsc);
|
||||
selection.borrow_mut().register(key, field);
|
||||
@@ -378,7 +378,7 @@ where
|
||||
left: dp(QUOTE_BAR_DP + FRAME_PAD_DP),
|
||||
..Padding::ZERO
|
||||
})
|
||||
.background(rect(crate::markdown::QUOTE_BAR_COLOR).width(dp(QUOTE_BAR_DP)))
|
||||
.background(rect(crate::ui::markdown::QUOTE_BAR_COLOR).width(dp(QUOTE_BAR_DP)))
|
||||
.width(rest(1))
|
||||
.add_strong(rsc)
|
||||
.any(),
|
||||
@@ -392,7 +392,7 @@ where
|
||||
/// One widget per block rather than one per message is what makes a
|
||||
/// streamed delta cost the last block instead of the whole reply -- see
|
||||
/// [`RowBlocks::apply_delta`] for the other half, and
|
||||
/// `client_core::markdown_blocks` for the split. Selection still runs
|
||||
/// `crate::client::markdown_blocks` for the split. Selection still runs
|
||||
/// across the whole transcript; the unit it steps in is a block now rather
|
||||
/// than a row (`selection::SelKey`).
|
||||
///
|
||||
@@ -700,7 +700,7 @@ where
|
||||
if let Some(calls) = calls {
|
||||
let key = row_key(&calls[0].key());
|
||||
let (widget, tools) =
|
||||
crate::tool::build_tool_row(rsc, list, selection, key, calls.to_vec(), working);
|
||||
crate::ui::tool::build_tool_row(rsc, list, selection, key, calls.to_vec(), working);
|
||||
return (key, widget, Some(TailRow::Tools(tools)));
|
||||
}
|
||||
let FoldedRow::Single(item) = row else {
|
||||
@@ -35,7 +35,7 @@ use std::{collections::BTreeMap, time::Instant};
|
||||
|
||||
/// What this selects between: a row's `RowKey` and the index of one
|
||||
/// markdown **block** inside it. A row is a column of one text widget per
|
||||
/// block since 2026-09-06 (`client_core::markdown_blocks`, and
|
||||
/// block since 2026-09-06 (`crate::client::markdown_blocks`, and
|
||||
/// docs/DECISIONS.md for why), so the block, not the row, is the unit --
|
||||
/// `(row, block)` compares lexicographically, which is reading order for
|
||||
/// both levels, so every range query below is unchanged.
|
||||
@@ -2,13 +2,13 @@
|
||||
//! while what they tapped changes height.
|
||||
//!
|
||||
//! Both halves are shared by everything in the transcript that opens: a
|
||||
//! tool card and its group ([`crate::tool`]), and a message's "Show all"
|
||||
//! ([`crate::row`]). They are here rather than in either of those because
|
||||
//! tool card and its group ([`crate::ui::tool`]), and a message's "Show all"
|
||||
//! ([`crate::ui::row`]). They are here rather than in either of those because
|
||||
//! there is one right answer to "was that a tap?" on this screen, and two
|
||||
//! copies of it would eventually disagree -- which on a scrolling screen
|
||||
//! means one of them firing at the end of a pan.
|
||||
|
||||
use crate::selection::Selection;
|
||||
use crate::ui::selection::Selection;
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! `ToolRows.kt`/`ToolInput.kt` (RUST.md's P1b).
|
||||
//!
|
||||
//! One card per call. Closed, it is a single line: the tool's name and
|
||||
//! what the call is for ([`client_core::tool_summary::parse_tool_input`]'s
|
||||
//! what the call is for ([`crate::client::tool_summary::parse_tool_input`]'s
|
||||
//! `title`). The command itself is not on it, because a wrapped command
|
||||
//! turns one row into four and a run of them into a wall. Open, it shows
|
||||
//! the description, the input and the output.
|
||||
@@ -16,7 +16,7 @@
|
||||
//! `a_delta_into_a_long_reply_...` counts it for a streamed delta.
|
||||
//!
|
||||
//! **Two or more adjacent calls are one group** -- decided in
|
||||
//! `client_core::transcript_fold::group_tool_runs`/`adopt_run` and never
|
||||
//! `crate::client::transcript_fold::group_tool_runs`/`adopt_run` and never
|
||||
//! re-derived here. A group is a header, a column of cards on its own
|
||||
//! surface, and a bar at its foot: it closes from either end, because a
|
||||
//! long group's header scrolls off while its last call is still on screen,
|
||||
@@ -32,12 +32,12 @@
|
||||
//! rule is written once, in the gesture machine, and this file only reads
|
||||
//! its answer.
|
||||
|
||||
use crate::markdown::{TEXT_COLOR, VERBATIM_BACKGROUND, highlight_into};
|
||||
use crate::selection::Selection;
|
||||
use crate::tap::{hold_edge, on_tap};
|
||||
use client_core::text_cap::{VERBATIM_BYTES, VERBATIM_LINES, cut, show_all_label};
|
||||
use client_core::tool_summary::{ToolInput, parse_tool_input};
|
||||
use client_core::transcript_fold::{ToolState, TranscriptItem};
|
||||
use crate::client::text_cap::{VERBATIM_BYTES, VERBATIM_LINES, cut, show_all_label};
|
||||
use crate::client::tool_summary::{ToolInput, parse_tool_input};
|
||||
use crate::client::transcript_fold::{ToolState, TranscriptItem};
|
||||
use crate::ui::markdown::{TEXT_COLOR, VERBATIM_BACKGROUND, highlight_into};
|
||||
use crate::ui::selection::Selection;
|
||||
use crate::ui::tap::{hold_edge, on_tap};
|
||||
use iris::prelude::*;
|
||||
use std::{cell::Cell, cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
@@ -163,7 +163,7 @@ struct Shared {
|
||||
|
||||
/// One transcript row's worth of tool calls, kept by the caller for the
|
||||
/// row a result can still land in -- the tool-call counterpart of
|
||||
/// [`crate::row::RowBlocks`], and the reason a `ToolEnd` costs one card
|
||||
/// [`crate::ui::row::RowBlocks`], and the reason a `ToolEnd` costs one card
|
||||
/// rather than a row.
|
||||
pub struct ToolRow {
|
||||
shared: Rc<Shared>,
|
||||
@@ -11,17 +11,17 @@
|
||||
//! down. Compose does not do that: a down while `isScrollInProgress`
|
||||
//! starts the drag immediately (`scrollable`'s `startDragImmediately`).
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchAction, TouchScript};
|
||||
use iris::prelude::*;
|
||||
use iris::sense::DRAG_SLOP;
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
/// The screen open on the fixture, framed twice -- once to draw, once for
|
||||
/// `LazySpan::repair_anchor` to resolve the opening `snap_end` into a real
|
||||
/// anchor, which is what every assertion about scroll position reads.
|
||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
fn opened() -> (Harness, ai_app::ui::TranscriptScreen) {
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
(h, opened.screen)
|
||||
@@ -31,7 +31,7 @@ fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
/// under the middle of the viewport. `LazySpan` has no travel accessor and
|
||||
/// this needs none -- a row's own extent moves exactly as far as the
|
||||
/// content does, and the row is picked once so the two readings compare.
|
||||
fn tracked_row(h: &mut Harness, screen: &transcript_ui::TranscriptScreen) -> (RowKey, f32) {
|
||||
fn tracked_row(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen) -> (RowKey, f32) {
|
||||
let middle = phone_size().y / 2.0;
|
||||
let list = (screen.list)(&mut h.rsc);
|
||||
let key = list.key_at(middle).expect("a row under the viewport");
|
||||
@@ -39,7 +39,7 @@ fn tracked_row(h: &mut Harness, screen: &transcript_ui::TranscriptScreen) -> (Ro
|
||||
(key, top)
|
||||
}
|
||||
|
||||
fn row_top(h: &mut Harness, screen: &transcript_ui::TranscriptScreen, key: RowKey) -> f32 {
|
||||
fn row_top(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen, key: RowKey) -> f32 {
|
||||
(screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the tracked row is still loaded")
|
||||
@@ -59,7 +59,7 @@ const CATCH_X: f32 = 540.0;
|
||||
/// delta. Returns the release time.
|
||||
fn drag_from(
|
||||
h: &mut Harness,
|
||||
screen: &transcript_ui::TranscriptScreen,
|
||||
screen: &ai_app::ui::TranscriptScreen,
|
||||
key: RowKey,
|
||||
y0: f32,
|
||||
t0: u64,
|
||||
@@ -8,9 +8,9 @@
|
||||
//! handle to it from the outside, and a coordinate would only prove that
|
||||
//! *something* moved.
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchAction};
|
||||
use iris::prelude::*;
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
/// The horizontal scroll area drawn inside `top..bottom`, with the box
|
||||
/// it was drawn at -- a fence is the only thing in a transcript that pans
|
||||
@@ -58,10 +58,10 @@ fn amt(h: &Harness, id: WidgetId) -> f32 {
|
||||
|
||||
#[test]
|
||||
fn a_flick_across_a_code_fence_keeps_moving_after_the_finger_leaves() {
|
||||
use client_core::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
use ai_app::client::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let screen = opened.screen;
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
@@ -78,7 +78,7 @@ fn a_flick_across_a_code_fence_keeps_moving_after_the_finger_leaves() {
|
||||
h.frame(100);
|
||||
h.frame(108);
|
||||
|
||||
let key = transcript_ui::row::row_key(&fence.key());
|
||||
let key = ai_app::ui::row::row_key(&fence.key());
|
||||
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the fence row is on screen");
|
||||
@@ -147,10 +147,10 @@ fn a_flick_across_a_code_fence_keeps_moving_after_the_finger_leaves() {
|
||||
/// list, while the fence carries on coasting untouched.
|
||||
#[test]
|
||||
fn a_drag_away_from_a_coasting_fence_scrolls_the_list_and_leaves_it_coasting() {
|
||||
use client_core::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
use ai_app::client::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let screen = opened.screen;
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
@@ -171,7 +171,7 @@ fn a_drag_away_from_a_coasting_fence_scrolls_the_list_and_leaves_it_coasting() {
|
||||
h.frame(100);
|
||||
h.frame(108);
|
||||
|
||||
let key = transcript_ui::row::row_key(&fence.key());
|
||||
let key = ai_app::ui::row::row_key(&fence.key());
|
||||
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the fence row is on screen");
|
||||
@@ -8,13 +8,13 @@
|
||||
//! or the next press anywhere on screen is measured from the origin the
|
||||
//! abandoned one left behind.
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchAction, TouchScript};
|
||||
use iris::prelude::*;
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
fn opened() -> (Harness, ai_app::ui::TranscriptScreen) {
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
(h, opened.screen)
|
||||
@@ -30,7 +30,7 @@ fn script(name: &str, text: &str) -> TouchScript {
|
||||
/// different row without the content moving at all -- so a test asserting
|
||||
/// "nothing moved" reads a row's own extent, the way `catch_a_fling.rs`
|
||||
/// does.
|
||||
fn tracked_row(h: &mut Harness, screen: &transcript_ui::TranscriptScreen) -> (RowKey, f32) {
|
||||
fn tracked_row(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen) -> (RowKey, f32) {
|
||||
let middle = phone_size().y / 2.0;
|
||||
let list = (screen.list)(&mut h.rsc);
|
||||
let key = list.key_at(middle).expect("a row under the viewport");
|
||||
@@ -38,7 +38,7 @@ fn tracked_row(h: &mut Harness, screen: &transcript_ui::TranscriptScreen) -> (Ro
|
||||
(key, top)
|
||||
}
|
||||
|
||||
fn row_top(h: &mut Harness, screen: &transcript_ui::TranscriptScreen, key: RowKey) -> f32 {
|
||||
fn row_top(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen, key: RowKey) -> f32 {
|
||||
(screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the tracked row is still loaded")
|
||||
@@ -123,7 +123,7 @@ fn a_press_ended_by_a_cancel_leaves_no_origin_for_the_next_one() {
|
||||
/// pointer capture the moment it commits. Everything else that was handed
|
||||
/// a frame of that press is told so with `CursorSense::Cancel` -- and the
|
||||
/// widget the press actually landed on is the fence's own text block,
|
||||
/// which drives `transcript_ui::Selection`'s shared `DragGesture`. A
|
||||
/// which drives `ai_app::ui::Selection`'s shared `DragGesture`. A
|
||||
/// block that does not register `Cancel` never hears it, so the gesture
|
||||
/// stays open with the fence's touch-down as its origin and the next
|
||||
/// press anywhere is measured from there.
|
||||
@@ -132,7 +132,7 @@ fn a_press_ended_by_a_cancel_leaves_no_origin_for_the_next_one() {
|
||||
/// test knows exactly which row it is pressing and where.
|
||||
#[test]
|
||||
fn panning_a_code_fence_then_tapping_elsewhere_moves_nothing() {
|
||||
use client_core::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
use ai_app::client::transcript_fold::{TranscriptItem, TranscriptRow};
|
||||
|
||||
let (mut h, screen) = opened();
|
||||
let fence = TranscriptRow::Single(TranscriptItem::AssistantMsg {
|
||||
@@ -161,7 +161,7 @@ fn panning_a_code_fence_then_tapping_elsewhere_moves_nothing() {
|
||||
|
||||
// Press in the middle of the fence's own row, so the gesture starts on
|
||||
// the text block inside the scroll area rather than in a gap.
|
||||
let key = transcript_ui::row::row_key(&fence.key());
|
||||
let key = ai_app::ui::row::row_key(&fence.key());
|
||||
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(key)
|
||||
.expect("the fence row is on screen");
|
||||
@@ -182,7 +182,7 @@ fn panning_a_code_fence_then_tapping_elsewhere_moves_nothing() {
|
||||
|
||||
// A tap on the paragraph, a long way down the screen from where that
|
||||
// pan started.
|
||||
let para_key = transcript_ui::row::row_key(¶.key());
|
||||
let para_key = ai_app::ui::row::row_key(¶.key());
|
||||
let (ptop, pbottom) = (screen.list)(&mut h.rsc)
|
||||
.extent(para_key)
|
||||
.expect("the paragraph row is on screen");
|
||||
+7
-4
@@ -27,8 +27,8 @@
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchScript};
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
/// Records every line's level and formatted message -- enough to answer
|
||||
/// both "is the ring quiet" (no line at `Debug` or below) and "what did
|
||||
@@ -72,9 +72,9 @@ fn drain(logger: &CaptureLogger) -> Vec<(log::Level, String)> {
|
||||
std::mem::take(&mut *logger.lines.lock().unwrap())
|
||||
}
|
||||
|
||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
fn opened() -> (Harness, ai_app::ui::TranscriptScreen) {
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
(h, opened.screen)
|
||||
@@ -149,7 +149,10 @@ fn tracing_is_silent_off_and_round_trips_the_flick_on() {
|
||||
// is prefix-agnostic (it `search`es for the marker), so handing it
|
||||
// the bare message is the same as handing it a real ring line.
|
||||
let report = input_lines.join("\n");
|
||||
let script_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../benches/report_to_touch.py");
|
||||
let script_path = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../iris/benches/report_to_touch.py"
|
||||
);
|
||||
let mut child = Command::new("python3")
|
||||
.arg(script_path)
|
||||
.stdin(Stdio::piped())
|
||||
@@ -8,16 +8,16 @@
|
||||
//! many evenly-spaced events; a finger at 120Hz is five samples in
|
||||
//! 20ms).
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::{Harness, TouchScript};
|
||||
use iris::prelude::*;
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
/// The screen open on the fixture, framed twice: once to draw, once for
|
||||
/// `LazySpan::repair_anchor` to resolve the opening `snap_end` into a real
|
||||
/// anchor, which is what every assertion about scroll position reads.
|
||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
fn opened() -> (Harness, ai_app::ui::TranscriptScreen) {
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let opened = transcript_fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
let opened = ai_app::ui::fixture::open(&mut h.rsc, &mut h.state).expect("the fixture folds");
|
||||
h.frame(0);
|
||||
h.frame(PHONE_FRAME_MS);
|
||||
(h, opened.screen)
|
||||
@@ -27,7 +27,7 @@ fn script(name: &str, text: &str) -> TouchScript {
|
||||
TouchScript::parse(text).unwrap_or_else(|e| panic!("{name}: {e}"))
|
||||
}
|
||||
|
||||
fn offset(h: &mut Harness, screen: &transcript_ui::TranscriptScreen) -> String {
|
||||
fn offset(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen) -> String {
|
||||
(screen.list)(&mut h.rsc).anchor_position_display()
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
//! it is inside the list's own box, and nothing outside that box reaches
|
||||
//! the screen.
|
||||
|
||||
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
use iris::harness::Harness;
|
||||
use iris::prelude::*;
|
||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
|
||||
/// A header band above the transcript, as `bench_client.rs` puts one --
|
||||
/// the surface the rows were drawing over on the phone. Its exact height
|
||||
@@ -21,9 +21,9 @@ use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||
const HEADER_H: f32 = 300.0;
|
||||
const HEADER: UiColor = UiColor::new(28, 28, 34, 255);
|
||||
|
||||
fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
fn opened() -> (Harness, ai_app::ui::TranscriptScreen) {
|
||||
let mut h = Harness::new(phone_size(), PHONE_SCALE);
|
||||
let (opened, tree) = transcript_fixture::build_screen(&mut h.rsc).expect("the fixture folds");
|
||||
let (opened, tree) = ai_app::ui::fixture::build_screen(&mut h.rsc).expect("the fixture folds");
|
||||
let content = WidgetPtr::new().add(&mut h.rsc);
|
||||
content(&mut h.rsc).set(tree);
|
||||
let root = (rect(HEADER).height(abs(HEADER_H)), content.height(rest(1)))
|
||||
@@ -37,7 +37,7 @@ fn opened() -> (Harness, transcript_ui::TranscriptScreen) {
|
||||
}
|
||||
|
||||
/// The list's own on-screen box, in window pixels.
|
||||
fn list_box(h: &Harness, screen: &transcript_ui::TranscriptScreen) -> PixelRegion {
|
||||
fn list_box(h: &Harness, screen: &ai_app::ui::TranscriptScreen) -> PixelRegion {
|
||||
h.render
|
||||
.window_region(&screen.list.id(), &h.rsc)
|
||||
.expect("the list is on screen")
|
||||
@@ -47,7 +47,7 @@ fn list_box(h: &Harness, screen: &transcript_ui::TranscriptScreen) -> PixelRegio
|
||||
/// topmost first. A `LazySpan`'s direct children are exactly its rows, and
|
||||
/// `draw_inner`'s old-children diffing means a row it did not place this
|
||||
/// frame is not among them.
|
||||
fn drawn_rows(h: &Harness, screen: &transcript_ui::TranscriptScreen) -> Vec<(f32, f32)> {
|
||||
fn drawn_rows(h: &Harness, screen: &ai_app::ui::TranscriptScreen) -> Vec<(f32, f32)> {
|
||||
let mut rows: Vec<(f32, f32)> = h
|
||||
.render
|
||||
.active
|
||||
@@ -69,7 +69,7 @@ fn drawn_rows(h: &Harness, screen: &transcript_ui::TranscriptScreen) -> Vec<(f32
|
||||
/// position lives in the `LazySpan`'s own `ScrollController`.
|
||||
/// It used to be the opposite here, because a `LazySpan`'s anchor offset
|
||||
/// ran the other way.
|
||||
fn scrolled(h: &mut Harness, screen: &transcript_ui::TranscriptScreen, amount: f32, t: u64) -> u64 {
|
||||
fn scrolled(h: &mut Harness, screen: &ai_app::ui::TranscriptScreen, amount: f32, t: u64) -> u64 {
|
||||
(screen.list)(&mut h.rsc).scroll(amount);
|
||||
h.frame(t);
|
||||
t + PHONE_FRAME_MS
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -280,7 +280,7 @@ fun fetchSession(settings: ServerSettings, sessionId: String): SessionSummary =
|
||||
* One row of `GET /sessions/{id}/subagents`, oldest first.
|
||||
*
|
||||
* A subagent is a second transcript owned by a session -- no process, no controls of its own -- so
|
||||
* this carries only what a card needs to draw and to open it; see SUBAGENTS.md. [status] is
|
||||
* this carries only what a card needs to draw and to open it; see docs/SUBAGENTS.md. [status] is
|
||||
* "running", "exited" or "unknown": a subagent whose session is not itself running cannot be
|
||||
* running, and the list says so rather than reporting a state that cannot hold.
|
||||
*/
|
||||
|
||||
@@ -49,7 +49,7 @@ private sealed class Screen {
|
||||
|
||||
/**
|
||||
* One subagent's own transcript, read-only. See [SessionScreen]'s `subagent` parameter and
|
||||
* SUBAGENTS.md's "Phone". Closing it returns to [Main] under it, not to [Session]: a subagent
|
||||
* docs/SUBAGENTS.md's "Phone". Closing it returns to [Main] under it, not to [Session]: a subagent
|
||||
* is opened from the session list's card rather than from inside the session it belongs to.
|
||||
*/
|
||||
data class SubagentTarget(val summary: SessionSummary, val subagent: SubagentSummary)
|
||||
|
||||
@@ -478,7 +478,7 @@ private fun SubagentCard(subagent: SubagentSummary, onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The subcard's word for a subagent's status -- see SUBAGENTS.md's "Wire shape". Its own function
|
||||
* The subcard's word for a subagent's status -- see docs/SUBAGENTS.md's "Wire shape". Its own function
|
||||
* rather than a branch inside [StatusText], because a subagent's three states are not that
|
||||
* composable's five: "exited" reads as "finished" here, since its process was always its parent's
|
||||
* and never something of its own to have merely stopped.
|
||||
|
||||
@@ -5,7 +5,7 @@ package com.example.aiapp
|
||||
*
|
||||
* The single mechanism [fetchTranscript], [EventStream], [TranscriptSource] and
|
||||
* [TranscriptCache.session] all take, rather than each growing its own branch between a session and
|
||||
* a subagent -- see SUBAGENTS.md's "Phone" and "Wire shape". A caller that has only a session id
|
||||
* a subagent -- see docs/SUBAGENTS.md's "Phone" and "Wire shape". A caller that has only a session id
|
||||
* builds one with the one-argument constructor; a subagent's screen supplies both ids.
|
||||
*/
|
||||
data class TranscriptAddress(val sessionId: String, val subagentId: String? = null) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
plugins { alias(libs.plugins.androidApplication) }
|
||||
|
||||
// E3 (RUST.md): the Kotlin/Java shell being replaced by a thin JNI bridge
|
||||
// into Rust (`../../android-shell`). Deliberately its own module rather
|
||||
// into Rust (`../../app-rust`, the `shell` feature). Deliberately its own module rather
|
||||
// than a rewrite of `:androidApp` in place -- that module is ~13,000 lines
|
||||
// of working Compose UI this experiment does not touch, and the two can be
|
||||
// installed side by side on the same development device (see
|
||||
// `settings.SCHEME`'s doc in `android-shell` for why the deep-link scheme
|
||||
// `settings.SCHEME`'s doc in `app-rust/src/shell` for why the deep-link scheme
|
||||
// and Keystore alias are not the production app's). No Compose plugin, no
|
||||
// Kotlin source of its own: `MainActivity`/`NotificationService` are plain
|
||||
// Java, and the CA constant below is generated as Java too.
|
||||
@@ -13,7 +13,7 @@ plugins { alias(libs.plugins.androidApplication) }
|
||||
// The CA this build pins is baked in the same way `androidApp`'s does --
|
||||
// see that module's `build.gradle.kts` comment for the reasoning (the
|
||||
// trust boundary follows the machine that builds, never a pasted copy).
|
||||
// `PinnedCa.java`'s package must match `android-shell`'s
|
||||
// `PinnedCa.java`'s package must match `app-rust/src/shell`'s
|
||||
// `settings::load_pinned_ca` lookup (`com/example/aiapp/shell/PinnedCa`).
|
||||
val pinnedCaPath: String =
|
||||
System.getenv("AI_APP_CA")
|
||||
@@ -150,12 +150,12 @@ androidComponents {
|
||||
|
||||
dependencies {
|
||||
// The Keystore-sealed enrollment (ServerStore/ServerSettings) --
|
||||
// android-shell's settings.rs calls into this Kotlin class directly
|
||||
// the shell bridge's settings.rs calls into this Kotlin class directly
|
||||
// over JNI rather than re-sealing the token in Rust; see that file's
|
||||
// module doc.
|
||||
implementation(project(":link"))
|
||||
// NotificationCompat/NotificationManagerCompat/NotificationChannelCompat/
|
||||
// ServiceCompat -- android-shell's notify.rs calls these classes over
|
||||
// ServiceCompat -- the shell bridge's notify.rs calls these classes over
|
||||
// JNI so the pre-26 fallback behaviour (no channels) lives once, in
|
||||
// the library that already has it, rather than being re-derived as a
|
||||
// set of Build.VERSION.SDK_INT branches in Rust.
|
||||
|
||||
@@ -17,7 +17,7 @@ import android.widget.Toast;
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
static {
|
||||
System.loadLibrary("android_shell");
|
||||
System.loadLibrary("ai_app");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,7 @@ import android.os.IBinder;
|
||||
*/
|
||||
public class NotificationService extends Service {
|
||||
static {
|
||||
System.loadLibrary("android_shell");
|
||||
System.loadLibrary("ai_app");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Generated
-983
@@ -1,983 +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 = "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",
|
||||
"tempfile",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[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 = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||
|
||||
[[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 = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[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 = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[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 = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[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 = "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 = "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 = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[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 = "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 = "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"
|
||||
@@ -1,52 +0,0 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# The app's pure logic, held once instead of twice: the event model (shared
|
||||
# with `server/` via `event-model`), the REST + SSE clients for its HTTP
|
||||
# surface (see `server/src/routes.rs`'s module doc for the table), the
|
||||
# transcript fold and cache, the markdown block model, the syntax
|
||||
# highlighter and the ANSI parser. See `docs/CLIENT_CORE.md` for
|
||||
# what this holds today, what it does not yet, and how it corresponds to
|
||||
# the Kotlin it replaces.
|
||||
#
|
||||
# No UI framework dependency of any kind -- this crate is meant to outlive
|
||||
# whichever one the app ends up drawing with (see RUST.md).
|
||||
|
||||
[dependencies]
|
||||
event-model = { path = "../event-model" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
# "raw_value" is `fetch_transcript_lines`'s reason -- it needs the exact
|
||||
# bytes the server sent, not this crate's own re-serialization of a parsed
|
||||
# `Value`, so a cached line and a live SSE frame for the same event agree
|
||||
# byte-for-byte (see that method's doc). "float_roundtrip" is why they
|
||||
# agree on a `ts` at all -- see server/Cargo.toml's identical comment.
|
||||
serde_json = { version = "1", features = ["float_roundtrip", "raw_value"] }
|
||||
# The blocking HTTP client for the REST calls and the long-lived SSE GETs.
|
||||
# `server/` already depends on ureq for its own outbound HTTPS (the usage
|
||||
# poll in usage.rs) and it is rustls-backed like the rest of this project's
|
||||
# TLS, so this reuses that choice rather than pulling in reqwest's async
|
||||
# stack -- a client that runs one blocking request at a time, the way
|
||||
# Api.kt's `HttpURLConnection` calls and Sse.kt's blocking read loop do, has
|
||||
# no need of an async runtime, and RUST.md's brief for this port is
|
||||
# "lightweight" throughout.
|
||||
ureq = { version = "3", features = ["json"] }
|
||||
# The markdown block split (`markdown_blocks`), which has to agree with the
|
||||
# renderer in `iris/transcript-ui` about where a block begins -- so it is
|
||||
# the same parser at the same version, rather than a hand-written splitter
|
||||
# that would drift from it.
|
||||
pulldown-cmark = "0.13.4"
|
||||
# The enrollment link's `ca` parameter is base64url of the CA's DER
|
||||
# (`config::parse_link`). Same version `wg-app-link` already pins for the
|
||||
# minting half, so a workspace that has both resolves one copy.
|
||||
base64 = "0.23"
|
||||
# The logging facade only -- `log_ring` implements a `log::Log` backend and
|
||||
# wraps whichever real one the platform installed (`android_logger` on the
|
||||
# phone, `env_logger` on the desktop), which is why neither of those is a
|
||||
# dependency here. See `log_ring`'s module doc.
|
||||
log = { version = "0.4.34", features = ["std"] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
+29
-24
@@ -1,21 +1,26 @@
|
||||
# client-core
|
||||
# `app-rust`'s `client` module
|
||||
|
||||
`client-core/` is the app's pure logic held once instead of twice, per
|
||||
RUST.md's recommendation item 1. It is a plain Rust library crate with no UI
|
||||
framework dependency of any kind, so it can outlive whichever one the app
|
||||
ends up drawing with (Masonry, iris, or something else -- see RUST.md).
|
||||
`event-model/` is its sibling: the wire shape both this crate and `server/`
|
||||
share, extracted from `server/src/session/driver.rs` and
|
||||
`session/transcript.rs` on 2026-09-04.
|
||||
**Renamed 2026-09-08.** This was the `client-core` crate; it is now
|
||||
`app-rust/src/client/`, a module of the one app crate rather than a crate
|
||||
of its own (docs/RUST.md's "One app crate"). Nothing about what it *is*
|
||||
changed: it is still the app's pure logic held once instead of twice, per
|
||||
RUST.md's recommendation item 1, and still has **no UI framework
|
||||
dependency of any kind** -- the `iris` dependency sits behind the `screens`
|
||||
feature and nothing under `src/client` may reach it. That independence is
|
||||
what lets it outlive whichever framework the app draws with, and it is now
|
||||
an invariant of a module rather than of a manifest, so it is worth stating
|
||||
plainly: a `use iris::` under `src/client/` is a defect.
|
||||
|
||||
Neither crate is wired into anything yet. `server/` re-exports `event-model`
|
||||
so its own behaviour is unchanged (`./run-tests.sh` covers it); `client-core`
|
||||
has no caller -- it exists for whichever experiment in RUST.md picks it up
|
||||
next (a Masonry or iris transcript screen, most likely).
|
||||
`event-model/` stayed a crate, and is the one split in the port that was
|
||||
never optional: it is the wire shape both this app and `server/` depend on,
|
||||
extracted from `server/src/session/driver.rs` and `session/transcript.rs`
|
||||
on 2026-09-04, so a crate is what makes the two agree by construction.
|
||||
|
||||
Paths below are written as `src/client/…`, relative to `app-rust/`.
|
||||
|
||||
## What's here, and what Kotlin file it replaces
|
||||
|
||||
| `client-core/src/…` | Kotlin original | Status |
|
||||
| `src/client/…` | Kotlin original | Status |
|
||||
|------------------------------------------|-------------------------------------------|--------|
|
||||
| `event-model/src/lib.rs` (shared crate) | `Events.kt` (the enum mirror) | Done |
|
||||
| `ansi.rs` | `Ansi.kt` | Done, ported test-for-test |
|
||||
@@ -118,7 +123,7 @@ has no `Unknown`/catch-all variant, unlike `Events.kt`'s hand-kept mirror.
|
||||
A server newer than this build that adds an event type will fail to parse
|
||||
that line rather than degrading to a placeholder row. Closing this means
|
||||
deciding how `event_model` itself represents "a shape I don't recognise"
|
||||
-- a shared-model decision affecting `server/` too, not a `client-core`-only
|
||||
-- a shared-model decision affecting `server/` too, not a `client`-only
|
||||
fix, so it is recorded here rather than silently worked around.
|
||||
|
||||
## `config.rs`: `EnrolledServer`
|
||||
@@ -130,11 +135,11 @@ parses on the phone -- so any Rust client enrols from the same text a
|
||||
phone would scan as a QR, with no second format invented for it (RUST.md's
|
||||
E4, DECISIONS.md 2026-09-05). Deliberately does not decide where it is
|
||||
persisted or under what file permissions -- a phone seals its token in the
|
||||
Android Keystore, `iris/desktop-app/src/config.rs` writes it to
|
||||
Android Keystore, `src/desktop/config.rs` writes it to
|
||||
`$XDG_CONFIG_HOME/ai-app-desktop/enrollment.json` at 0600 -- since that is
|
||||
caller-specific (the code rules' "ask for the least you need"). Its only
|
||||
caller today is `desktop-app`; a future Android build of this crate would
|
||||
be a second one, not a reason to move the type.
|
||||
caller today is `src/desktop`; the Android entry point is a second one, not
|
||||
a reason to move the type.
|
||||
|
||||
## What `transcript_source.rs` covers, and what it does not
|
||||
|
||||
@@ -184,7 +189,7 @@ thread. Both are wall-clock/thread-lifetime policy that belongs to
|
||||
whichever runtime embeds this crate (iris's own timers, a Tokio task, a
|
||||
Kotlin coroutine scope), not to this pure logic -- `follow` is the same
|
||||
"write to the cache, then hand the frame to the caller" decorator
|
||||
`iris/desktop-app/src/app.rs` and `iris/android-app/src/transcript_client.rs`
|
||||
`src/desktop/app.rs` and `src/android/transcript_client.rs`
|
||||
already hand-wrote around `event_stream::follow_session_events` before this
|
||||
existed; the cache write moved into one shared place so a third caller
|
||||
does not repeat it again by hand.
|
||||
@@ -198,7 +203,7 @@ does not repeat it again by hand.
|
||||
block instead of the message (docs/RUST.md's Task B). What it
|
||||
deliberately does **not** build is the tree below that: nested list
|
||||
items, table cells, inline spans. Inline styling is still the renderer's
|
||||
own job per block (`iris/transcript-ui/src/markdown.rs`), and nothing
|
||||
own job per block (`src/ui/markdown.rs`), and nothing
|
||||
has needed the rest yet. `CodeFence.kt`'s use of `org.intellij.markdown`
|
||||
for a full CommonMark AST is Compose rendering plumbing, not something
|
||||
to port as-is.
|
||||
@@ -209,8 +214,8 @@ does not repeat it again by hand.
|
||||
|
||||
## Verifying
|
||||
|
||||
`./run-tests.sh` from the repo root now runs `event-model`, `client-core`
|
||||
and `server` in that order (each `cargo test`, forwarding arguments the
|
||||
same way it always has). From `client-core/` directly: `cargo test`
|
||||
(119 tests), `cargo clippy --all-targets`, `cargo fmt` -- all clean as of
|
||||
this writing (2026-09-06).
|
||||
`./run-tests.sh` from the repo root runs `event-model`, `server` and
|
||||
`app-rust` in that order (each `cargo test`, forwarding arguments the same
|
||||
way it always has). From `app-rust/` directly: `cargo test`, `cargo clippy
|
||||
--all-targets`, `cargo fmt` -- all clean as of 2026-09-08, 229 tests across
|
||||
the crate and its headless harness suites.
|
||||
@@ -5,6 +5,30 @@ they can be judged and reversed later. Detail lives in RUST.md (and IRIS.md
|
||||
for iris API changes); this file is only the summary. Newest first. Items
|
||||
marked **DEFERRED** are ones the agent chose not to decide alone.
|
||||
|
||||
## 2026-09-08 (the port is one crate, and iris is framework-only)
|
||||
|
||||
Asked for by Iris directly, so the shape rather than the fact is what is
|
||||
open to review. `app-rust/` is one crate, `ai-app`, with `client`, `ui`,
|
||||
`desktop`, `android` and `shell` as modules; `iris/` holds only `core`,
|
||||
`macro`, `iris`, `tabs-ui` and `rig-input`. docs/RUST.md's "One app crate"
|
||||
has the table and the reason each old split did or did not survive.
|
||||
|
||||
Three calls made inside that, none of which she named:
|
||||
|
||||
1. **`event-model` stayed at the repo root** rather than moving into
|
||||
`app-rust/` -- her choice when asked, since `server/` depends on it too.
|
||||
2. **The Android application id and Java package were left alone**
|
||||
(`dev.iris.android.demo`, label "iris android-view demo"), though both
|
||||
now name the wrong thing. Renaming them makes the next install
|
||||
side-by-side rather than an upgrade on her phone, and changes the
|
||||
`DevLogProvider` authority Dev Updater reads. Say the word and it is a
|
||||
small change.
|
||||
3. **The `bench` fixture is behind a `fixture` feature that is on by
|
||||
default**, with `build-apk.sh` passing `--no-default-features` so an APK
|
||||
carries the 1.9 MB fixture only when it asked for `bench`. The
|
||||
alternative -- default off -- would have made `cargo test` silently skip
|
||||
the six harness suites, which is the worse failure.
|
||||
|
||||
## 2026-09-08 (last: scrolling moves out of the list)
|
||||
|
||||
Agreed with Iris in the exchange that followed, so most of this is her
|
||||
|
||||
+108
-1
@@ -2089,7 +2089,7 @@ column above.
|
||||
`Tasks::redraw_handle`'s design had anticipated) -- both in I5's own box,
|
||||
both in `IRIS.md`.
|
||||
- **Design choices for the two pieces before this are summarised in
|
||||
`DECISIONS.md`** at the repo root, which is the file Iris reads for
|
||||
`DECISIONS.md`** in `docs/`, which is the file Iris reads for
|
||||
choices made without her.
|
||||
- **E4 done, 2026-09-05.** `iris/desktop-app`: a winit window with a
|
||||
session list beside `transcript-ui`'s screen (`build_tree`), against a
|
||||
@@ -8394,3 +8394,110 @@ again `--features iris/force-gles` on virgl, identical output. Layer 3:
|
||||
`build-apk.sh release` (arm64) builds, and the x86_64 debug bench ran a
|
||||
full fling/stream/type/keyboard cycle on the emulator's GLES adapter with
|
||||
no crash.
|
||||
|
||||
## One app crate, 2026-09-08 (the repository reorganised)
|
||||
|
||||
Iris, reading the tree: *"the organization of the rust rewrite is a mess
|
||||
right now… there shouldn't be anything related to the app inside of iris.
|
||||
Iris is supposed to be the UI framework alone."* Then, on the crate count:
|
||||
*"I'm confused why the app only code needs more than one crate though."*
|
||||
|
||||
### What it was
|
||||
|
||||
Nine cargo workspaces, each with its own `Cargo.lock` and `target/`, and
|
||||
the port's project code in five places — `iris/transcript-ui`,
|
||||
`iris/transcript-fixture`, `iris/desktop-app`, `iris/android-app` (all
|
||||
*inside* the framework), plus `client-core` and `android-shell` at the
|
||||
root. Two root markdown files (`DECISIONS.md`, `SUBAGENTS.md`) sat outside
|
||||
`docs/`.
|
||||
|
||||
### What it is
|
||||
|
||||
**One crate, `ai-app`, in `app-rust/`.** Modules, not crates:
|
||||
|
||||
| was | is |
|
||||
|----------------------------------------|-----------------------------------|
|
||||
| `client-core` | `src/client` |
|
||||
| `iris/transcript-ui` | `src/ui` |
|
||||
| `iris/transcript-fixture` | `src/ui/fixture.rs` + `tests/`, `touch/` |
|
||||
| `iris/desktop-app` | `src/desktop` + `src/bin_desktop.rs` |
|
||||
| `iris/android-app` | `src/android` + `android-project/` |
|
||||
| `android-shell` | `src/shell` |
|
||||
|
||||
`iris/` now holds `core`, `macro`, the `iris` crate, `tabs-ui` and
|
||||
`rig-input` — framework only, with no mention of a session, a transcript,
|
||||
a setup or a server anywhere in it.
|
||||
|
||||
### Why one crate really is enough
|
||||
|
||||
Each split had a stated reason at the time; on inspection only two
|
||||
survived, and one of those is not in `app-rust` at all.
|
||||
|
||||
- **`client-core` separate from the UI** was "pure logic with no framework
|
||||
dependency". That property is worth keeping and does not need a crate:
|
||||
`iris` is behind the `screens` feature and `src/client/` may not reach
|
||||
it. An invariant on a module instead of on a manifest, stated in
|
||||
docs/CLIENT_CORE.md.
|
||||
- **`transcript-fixture` separate from `transcript-ui`** was so the
|
||||
headless harness and a desktop window opened the same bytes. Both are
|
||||
now the same crate, so it is `src/ui/fixture.rs` behind a `fixture`
|
||||
feature (1.9 MB of `include_str!` must not reach a phone build) with the
|
||||
six harness suites in `tests/`.
|
||||
- **Two Android `.so` names**, `libmain.so` for the iris app and
|
||||
`libandroid_shell.so` for the Kotlin shell's JNI bridge, looked like the
|
||||
one hard constraint: a package produces exactly one library artifact.
|
||||
It dissolves because **P2 already plans to merge those two Android apps
|
||||
into one**. So both faces come out of one package as `libai_app.so`,
|
||||
picked apart by features (`--no-default-features --features shell` keeps
|
||||
wgpu, parley and iris out of the Compose app's APK), which is the
|
||||
direction of travel rather than a workaround. `xtask apk` and
|
||||
`app/shellApp`'s `System.loadLibrary` were updated to match.
|
||||
- **A desktop binary and an Android cdylib in one package** is not a
|
||||
problem: `iris` itself already target-gates winit against android-view
|
||||
in one manifest, and the same table does it here. `build-apk.sh` passes
|
||||
`--lib` so `cargo ndk` never tries to build the desktop binary.
|
||||
- **`event-model` stays a crate**, and is the one split that was never
|
||||
optional: `server/` depends on it too, so a crate is what makes the
|
||||
backend and the app agree by construction. Iris chose to leave it at the
|
||||
repo root rather than inside `app-rust/`, since it is the contract
|
||||
between the two rather than app code.
|
||||
|
||||
So: three workspaces where there were nine — `event-model`, `server`,
|
||||
`app-rust` — plus `iris` and `xtask`.
|
||||
|
||||
### Things that moved with it, worth knowing
|
||||
|
||||
- **The toolchain pin is per directory.** `app-rust/rust-toolchain.toml` is
|
||||
a copy of `iris/`'s, because `client-core` used to build on stable and
|
||||
now shares iris's dated nightly. Two consequences appeared immediately:
|
||||
two `needless_range_loop` warnings in the markdown highlighter (fixed),
|
||||
and four `AtomicBool::fetch_update` deprecations from inside `jni`
|
||||
0.22's `native_method!` macro. The last are not ours to migrate — the
|
||||
fix is a `jni` release — so `src/lib.rs` carries an `#[allow(deprecated)]`
|
||||
scoped to `mod shell` with that reason written at it.
|
||||
- **The Android release profile is `android-release`, not `release`.** The
|
||||
aggressive settings `iris/android-app` had (`panic = "abort"`,
|
||||
`opt-level = "s"`, fat LTO) would otherwise apply to the desktop build
|
||||
too, which is a testing surface. `build-apk.sh` passes
|
||||
`--profile android-release` / `--profile android-dev`.
|
||||
- **`iris/run-headless.sh` grew `--dir DIR`**, defaulting to `iris/`. The
|
||||
rig belongs to the framework; the examples it usually runs no longer do.
|
||||
`replay-touch` is still built from `iris/`.
|
||||
- **The log target changed** from `client_core` to `ai_app`
|
||||
(`src/client/log_ring.rs`'s `is_own_target`).
|
||||
- **Not renamed, deliberately:** the Android application id and Java
|
||||
package are still `dev.iris.android.demo` and the label is still "iris
|
||||
android-view demo", both now misleading. Changing them changes the app's
|
||||
identity on Iris's phone (a side-by-side install rather than an upgrade)
|
||||
and the `DevLogProvider` authority Dev Updater reads, so it is hers to
|
||||
decide rather than a tidy-up to make quietly.
|
||||
|
||||
### Verified
|
||||
|
||||
`./run-tests.sh` (event-model, server, app-rust) and `cd iris && cargo
|
||||
test` green; `cargo clippy --all-targets` and `cargo fmt` clean in every
|
||||
workspace. `cargo ndk -t x86_64` links `libai_app.so`; `./build-apk.sh
|
||||
debug --abi x86_64` produces an installable APK; installed and launched on
|
||||
this checkout's emulator, drawing through `Gl … virgl` as expected. The
|
||||
phone-sized headless screenshot (`run-headless.sh phone --phone --dir
|
||||
../app-rust --shot …`) renders the transcript unchanged.
|
||||
@@ -3,7 +3,7 @@
|
||||
A session's subagents -- the helpers a Claude Code session starts through its
|
||||
Task tool -- each get a transcript of their own, listed under the session's
|
||||
card and readable in the same transcript view the session has. Designed
|
||||
2026-09-05; the decisions Bryan has not yet reviewed are in `DECISIONS.md`.
|
||||
2026-09-05; the decisions Bryan has not yet reviewed are in `SUBAGENTS_DECISIONS.md`.
|
||||
|
||||
## What a subagent is here
|
||||
|
||||
File renamed without changes.
Generated
-390
@@ -532,12 +532,6 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.10.0"
|
||||
@@ -717,19 +711,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"
|
||||
@@ -775,35 +756,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[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 = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -890,25 +842,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
|
||||
[[package]]
|
||||
name = "desktop-app"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client-core",
|
||||
"event-model",
|
||||
"iris",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"transcript-ui",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@@ -1061,14 +994,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.74.2"
|
||||
@@ -1200,15 +1125,6 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[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 = "futures-core"
|
||||
version = "0.3.34"
|
||||
@@ -1283,26 +1199,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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 = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -1447,22 +1343,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[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"
|
||||
@@ -1591,27 +1471,6 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad"
|
||||
|
||||
[[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 = "image"
|
||||
version = "0.25.10"
|
||||
@@ -1729,12 +1588,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -2143,12 +1996,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
@@ -2814,12 +2661,6 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -2872,25 +2713,6 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"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 = "pulp"
|
||||
version = "0.22.3"
|
||||
@@ -3162,20 +2984,6 @@ dependencies = [
|
||||
"wayland-protocols-wlr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.21.1"
|
||||
@@ -3226,41 +3034,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
||||
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 = "rustversion"
|
||||
version = "1.0.23"
|
||||
@@ -3343,19 +3116,6 @@ dependencies = [
|
||||
"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 = "serde_repr"
|
||||
version = "0.1.21"
|
||||
@@ -3512,12 +3272,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.2.10"
|
||||
@@ -3645,36 +3399,6 @@ dependencies = [
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[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 = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
@@ -3781,31 +3505,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "transcript-fixture"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client-core",
|
||||
"event-model",
|
||||
"iris",
|
||||
"log",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"transcript-ui",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -3844,12 +3543,6 @@ dependencies = [
|
||||
"keyboard-types",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -3868,62 +3561,6 @@ 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.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
||||
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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
||||
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"
|
||||
@@ -3968,12 +3605,6 @@ dependencies = [
|
||||
"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 = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
@@ -4167,15 +3798,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[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 = "weezl"
|
||||
version = "0.1.12"
|
||||
@@ -5058,12 +4680,6 @@ dependencies = [
|
||||
"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"
|
||||
@@ -5105,12 +4721,6 @@ 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"
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
|
||||
@@ -101,18 +101,8 @@ members = [
|
||||
"core",
|
||||
"macro",
|
||||
"tabs-ui",
|
||||
"transcript-ui",
|
||||
"transcript-fixture",
|
||||
"rig-input",
|
||||
"desktop-app",
|
||||
]
|
||||
# android-app pulls in android-view, which needs the NDK sysroot to link
|
||||
# -- excluded so `cargo build --workspace --all-targets` on the host stays
|
||||
# buildable. Cross-compile it from its own directory (its own single-crate
|
||||
# workspace, since it has no `[workspace]` table of its own and this
|
||||
# exclusion stops it inheriting this one): `cd android-app && cargo ndk
|
||||
# -t x86_64 -P 29 build`.
|
||||
exclude = ["android-app"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
@@ -161,8 +151,3 @@ accesskit = "0.25.0"
|
||||
iris-core = { path = "core" }
|
||||
iris-macro = { path = "macro" }
|
||||
tokio = "1.53.1"
|
||||
# Current stable as of 2026-09-05 (`cargo search`) -- I5's markdown block
|
||||
# model, the same crate E2's uncommitted `e2-transcript` experiment used for
|
||||
# the identical job (RUST.md), rather than reimplementing a CommonMark
|
||||
# parser.
|
||||
pulldown-cmark = "0.13.4"
|
||||
@@ -1,6 +0,0 @@
|
||||
.gradle/
|
||||
build/
|
||||
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.
|
||||
app/src/main/jniLibs/
|
||||
@@ -1,90 +0,0 @@
|
||||
[package]
|
||||
name = "iris-android-app"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# Deliberately outside the `iris` workspace (see that Cargo.toml's
|
||||
# `[workspace] exclude`): this crate exists only to be cross-compiled with
|
||||
# `cargo ndk` for the emulator/a phone, and pulls in android-view, which
|
||||
# needs the NDK sysroot to link. Folding it into the main workspace would
|
||||
# make `cargo build --workspace --all-targets` -- the host command RUST.md
|
||||
# and AGENTS.md both require to stay clean -- try to link a cdylib against
|
||||
# libraries that do not exist on this machine. See RUST.md's I2.
|
||||
|
||||
[lib]
|
||||
name = "main"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
iris = { path = "../" }
|
||||
android-view = { git = "https://github.com/rust-mobile/android-view.git", rev = "bec6c62a96cef8239b0fd7fedeef9b184d02e3a1" }
|
||||
android_logger = "0.15.1"
|
||||
log = "0.4.34"
|
||||
# `tabs-screen` (default, I2/I4's demo) and `transcript-screen` (I5's
|
||||
# Android integration) are mutually exclusive -- one `ActiveClient` type is
|
||||
# compiled in, never both (`lib.rs`'s doc comment) -- so both sets of deps
|
||||
# are optional and each screen's feature pulls in only its own. Without
|
||||
# this, building `--features transcript-screen` alone (default features
|
||||
# still on) left `tabs-ui` linked but never referenced under that cfg,
|
||||
# which Cargo's `unused_dependencies` lint (on by default) correctly flags.
|
||||
tabs-ui = { path = "../tabs-ui", optional = true }
|
||||
transcript-ui = { path = "../transcript-ui", optional = true }
|
||||
# P0's bench build only: the fixture and the folded screen both bench
|
||||
# clients open, shared with the headless harness and the desktop window
|
||||
# (docs/RUST.md's "Three test layers").
|
||||
transcript-fixture = { path = "../transcript-fixture", optional = true }
|
||||
client-core = { path = "../../client-core", optional = true }
|
||||
event-model = { path = "../../event-model", optional = true }
|
||||
serde_json = { version = "1", features = ["float_roundtrip"], optional = true }
|
||||
# P0's bench build only (docs/RUST.md): `getrusage(RUSAGE_SELF)` for
|
||||
# process CPU time, matching `libc::getrusage`'s mention in that box over
|
||||
# parsing `/proc/self/stat` by hand and assuming `USER_HZ`. Already in the
|
||||
# workspace's own dependency tree transitively (`iris/Cargo.lock`, pinned
|
||||
# at 0.2.179) -- this makes it a direct dependency at the same version
|
||||
# rather than a second, possibly-drifting resolution.
|
||||
libc = { version = "0.2.189", optional = true }
|
||||
# P0's bench build only: the scroll animation and the streaming phase are
|
||||
# both a sequence of `sleep`s inside the async task `rsc.spawn_task` already
|
||||
# runs on iris's own tokio runtime (`iris/src/task.rs`'s `Tasks::init`), and
|
||||
# the battery sampler is a second, concurrent task on that same runtime
|
||||
# (`tokio::spawn`) -- so this crate needs `tokio` directly rather than only
|
||||
# through `iris`. `rt`+`time` only: no I/O, no macros, nothing this crate
|
||||
# doesn't call. Version matches the one `iris`'s own dependency tree already
|
||||
# resolves to (`iris/Cargo.lock`), so there is one copy of the runtime, not
|
||||
# two.
|
||||
tokio = { version = "1.53.1", features = ["rt", "time"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["tabs-screen"]
|
||||
tabs-screen = ["dep:tabs-ui"]
|
||||
transcript-screen = ["dep:transcript-ui", "dep:client-core", "dep:event-model", "dep:serde_json"]
|
||||
# RUST.md's I5 "Where iris's frame time goes": forces the GLES backend
|
||||
# instead of SwiftShader's software Vulkan. See `iris/Cargo.toml`'s own doc
|
||||
# on the feature this forwards to.
|
||||
force-gles = ["iris/force-gles"]
|
||||
# P0's iris half (docs/RUST.md, docs/AGENTS.md's "The rigs"): the same
|
||||
# checked-in fixture, scroll loop and streaming phase the Compose `bench`
|
||||
# build type drives, run here against `transcript-ui`'s real screen with no
|
||||
# server. Depends on `transcript-screen` for `transcript-ui`/`client-core`/
|
||||
# `event-model` -- `lib.rs`'s `ActiveClient` selection gives this feature
|
||||
# priority over `transcript-screen`'s own `TranscriptClient` when both are
|
||||
# listed, which is how this crate's build command names both explicitly.
|
||||
bench = ["transcript-screen", "dep:transcript-fixture", "dep:libc", "dep:tokio"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
# Measured 2026-09-07 (docs/RUST.md's "APK size" subsection): together these
|
||||
# take libmain.so from 18,546,488 to 11,193,608 bytes (-39.7%) and the APK
|
||||
# from 20,678,956 to 13,326,076 bytes (-35.5%), arm64-v8a release. `strip`
|
||||
# also works around AGP's own stripReleaseDebugSymbols failing silently on
|
||||
# this .so ("packaging them as they are"). `opt-level = "s"` over `"z"`:
|
||||
# `z` measured another ~800 KB smaller but was not checked against iris's
|
||||
# own frame-time bench, so it is not worth the unmeasured risk -- see the
|
||||
# doc for the number and the follow-up this leaves.
|
||||
strip = true
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
opt-level = "s"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
@@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "desktop-app"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# RUST.md's E4: the same transcript-ui screen (I5) in a winit window on the
|
||||
# desktop, beside a session list, talking to a real `ai-server` through
|
||||
# `client-core`'s REST + SSE clients. Enrolment reuses the phone's own
|
||||
# `aiapp://enroll?...` link (`client-core::config`) rather than inventing a
|
||||
# second format -- see DECISIONS.md's 2026-09-05 entry. An ordinary
|
||||
# workspace member (unlike `android-app`): nothing here needs the NDK, so
|
||||
# `cargo build --workspace --all-targets` at the host stays clean with it
|
||||
# included.
|
||||
|
||||
[dependencies]
|
||||
iris = { path = ".." }
|
||||
transcript-ui = { path = "../transcript-ui" }
|
||||
client-core = { path = "../../client-core" }
|
||||
event-model = { path = "../../event-model" }
|
||||
# Already pulled in transitively through client-core; used directly here
|
||||
# only to persist `EnrolledServer` as the app's own tiny config file (see
|
||||
# `config.rs`) -- no new dependency.
|
||||
serde_json = { version = "1", features = ["float_roundtrip"] }
|
||||
winit = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
+21
-12
@@ -3,14 +3,20 @@
|
||||
#
|
||||
# ./run-headless.sh tabs [-- cargo args]
|
||||
# ./run-headless.sh tabs --shot /tmp/tabs.png --seconds 4
|
||||
# ./run-headless.sh phone --phone --shot /tmp/p.png -- -p transcript-fixture
|
||||
# ./run-headless.sh phone --phone --replay transcript-fixture/touch/flick-120hz.touch \
|
||||
# --shot /tmp/p.png -- -p transcript-fixture
|
||||
# ./run-headless.sh phone --phone --dir ../app-rust --shot /tmp/p.png
|
||||
# ./run-headless.sh phone --phone --dir ../app-rust \
|
||||
# --replay ../app-rust/touch/flick-120hz.touch --shot /tmp/p.png
|
||||
#
|
||||
# `--dir DIR` names the workspace to build in, defaulting to `iris/` (this
|
||||
# script's own directory). The app's examples -- the phone-sized transcript
|
||||
# screen and everything else that is about *this product* -- live in
|
||||
# `app-rust/`, which is a workspace of its own; `replay-touch` is still
|
||||
# built from iris, since it is part of the rig rather than of either app.
|
||||
#
|
||||
# `--phone` is layer 2 of docs/RUST.md's "Three test layers": the output
|
||||
# and the window take Iris's phone's own size and density (1080x2424 at
|
||||
# `content_scale` 2.55, from docs/bench/iris-phone-v2-2026-09-06.md,
|
||||
# carried in `transcript_fixture::PHONE_*`), and `IRIS_SCALE` hands that
|
||||
# carried in `ai_app::ui::fixture::PHONE_*`), and `IRIS_SCALE` hands that
|
||||
# density to iris the way `DisplayMetrics.density` does on Android
|
||||
# (`iris::default::content_scale`). So a screenshot from here and one
|
||||
# from the phone are the same layout at the same density, and what
|
||||
@@ -18,13 +24,13 @@
|
||||
# shaped, which is what every other example wants.
|
||||
#
|
||||
# `--replay FILE` drives one of the `.touch` recordings the headless
|
||||
# tests use (`iris/transcript-fixture/touch/`) into the window through
|
||||
# tests use (`app-rust/touch/`) into the window through
|
||||
# `rig-input`'s `replay-touch` -- one recording, both layers. With
|
||||
# `--shot` it also writes `<shot>-before.png` from just before the
|
||||
# gesture, since "the list moved" is a claim about two pictures.
|
||||
#
|
||||
# `--bin` runs a real crate binary instead of an example (E4's
|
||||
# `desktop-app`, which is a window a person runs, not a demo) --
|
||||
# `ai-app-desktop`, which is a window a person runs, not a demo) --
|
||||
# `cargo build --bin NAME` instead of `--example NAME`, and
|
||||
# `target/debug/NAME` instead of `target/debug/examples/NAME`. Its own
|
||||
# argv (the CLI flags a real binary takes, as opposed to `cargo build`'s
|
||||
@@ -45,6 +51,8 @@
|
||||
set -eu
|
||||
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
# The workspace `--dir` selects; see the header. `$here` is iris itself.
|
||||
workdir="$here"
|
||||
run="${XDG_RUNTIME_DIR:-/tmp}/iris-headless"
|
||||
seconds=3
|
||||
shot=""
|
||||
@@ -54,7 +62,7 @@ kind=example
|
||||
phone=no
|
||||
|
||||
# The phone Iris runs the bench on. Not typed from memory: these are
|
||||
# `transcript_fixture::PHONE_WIDTH`/`PHONE_HEIGHT`/`PHONE_SCALE`, which
|
||||
# `ai_app::ui::fixture::PHONE_WIDTH`/`PHONE_HEIGHT`/`PHONE_SCALE`, which
|
||||
# in turn come from her own reports -- keep the three in step.
|
||||
PHONE_MODE=1080x2424@120Hz
|
||||
PHONE_SCALE=2.55
|
||||
@@ -67,11 +75,12 @@ while [ $# -gt 0 ]; do
|
||||
--bin) kind=bin; shift ;;
|
||||
--phone) phone=yes; shift ;;
|
||||
--replay) replay=$2; shift 2 ;;
|
||||
--dir) workdir=$(cd "$2" && pwd); shift 2 ;;
|
||||
--) shift; break ;;
|
||||
*) example=$1; shift ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$example" ] || { echo "usage: $0 NAME [--bin] [--phone] [--replay TOUCH] [--shot PNG] [--seconds N] [-- cargo args]" >&2; exit 2; }
|
||||
[ -n "$example" ] || { echo "usage: $0 NAME [--bin] [--phone] [--dir DIR] [--replay TOUCH] [--shot PNG] [--seconds N] [-- cargo args]" >&2; exit 2; }
|
||||
[ -z "$replay" ] || [ -f "$replay" ] || { echo "run-headless: no touch script at $replay" >&2; exit 2; }
|
||||
|
||||
mkdir -p "$run"
|
||||
@@ -128,15 +137,15 @@ out_h=${mode#*x}; out_h=${out_h%@*}
|
||||
|
||||
# Built before the app starts, so a compile error is not reported as a
|
||||
# window that failed to move.
|
||||
[ -z "$replay" ] || cargo build --bin replay-touch -p rig-input >&2
|
||||
[ -z "$replay" ] || (cd "$here" && cargo build --bin replay-touch -p rig-input) >&2
|
||||
|
||||
cd "$here"
|
||||
cd "$workdir"
|
||||
if [ "$kind" = bin ]; then
|
||||
cargo build --bin "$example" "$@" >&2
|
||||
bin="$here/target/debug/$example"
|
||||
bin="$workdir/target/debug/$example"
|
||||
else
|
||||
cargo build --example "$example" "$@" >&2
|
||||
bin="$here/target/debug/examples/$example"
|
||||
bin="$workdir/target/debug/examples/$example"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086 -- deliberately word-split: this is the
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "transcript-fixture"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# The bench fixture, opened as a real transcript screen with no server --
|
||||
# docs/RUST.md's "Three test layers". It was `iris-android-app`'s
|
||||
# `bench_client.rs` alone until 2026-09-07; the fixture-loading and
|
||||
# fold-driving half moved here so the headless harness (layer 1), the
|
||||
# phone-shaped desktop window (layer 2) and the Android bench (layer 3)
|
||||
# all open the *same* screen from the same bytes, per AGENTS.md's rule
|
||||
# that nothing UI-shaped lives in a platform crate.
|
||||
|
||||
[dependencies]
|
||||
iris = { path = ".." }
|
||||
transcript-ui = { path = "../transcript-ui" }
|
||||
client-core = { path = "../../client-core" }
|
||||
event-model = { path = "../../event-model" }
|
||||
# `float_roundtrip` for the same reason `server/Cargo.toml` has it: a `ts`
|
||||
# read back must be the one that was written (AGENTS.md).
|
||||
serde_json = { version = "1", features = ["float_roundtrip"] }
|
||||
|
||||
[dev-dependencies]
|
||||
winit = { workspace = true }
|
||||
# `examples/phone.rs`'s `--typed` only: it spaces its insertions out over
|
||||
# real frames, and a task spawned through `iris`'s own runtime
|
||||
# (`iris/src/task.rs`) is where the sleep has to happen. Same version the
|
||||
# workspace already pins for `iris-android-app`.
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
# For the `iris::input`/`iris::frame` round-trip test: a capturing `log::Log`
|
||||
# to read back what `iris::diagnostics::log_frame`/`sense::log_input_event`
|
||||
# wrote, pinned to the same version `iris/Cargo.toml` already carries.
|
||||
log = "0.4.34"
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "transcript-ui"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# I5 (RUST.md): the transcript screen's widget tree, built the same way
|
||||
# `tabs-ui` is -- its own crate, generic over `Rsc: HasEvents` +
|
||||
# `Rsc::State: FocusHost`, so the winit example and an eventual
|
||||
# `iris-android-app`-style cdylib call the same `build`. See its own module
|
||||
# doc for the design and RUST.md's I5 box for what is and is not proved yet.
|
||||
#
|
||||
# `client-core`/`event-model` by path, real code and not a reimplementation
|
||||
# -- the same dependency shape E2's uncommitted Masonry experiment used for
|
||||
# the identical job.
|
||||
|
||||
[dependencies]
|
||||
iris = { path = ".." }
|
||||
client-core = { path = "../../client-core" }
|
||||
event-model = { path = "../../event-model" }
|
||||
pulldown-cmark = { workspace = true }
|
||||
# Selection has no accessibility label of its own yet (IRIS_TODO.md's
|
||||
# "Row-level accessibility names"), so a logcat line at
|
||||
# begin/extend is the smallest way to confirm a real long-press-then-drag
|
||||
# reached `Selection` on-device (RUST.md's I5 box, "Measurements taken"
|
||||
# (c)) -- version pinned to match `iris-android-app`'s own dependency.
|
||||
log = "0.4.34"
|
||||
+20
-13
@@ -1,18 +1,25 @@
|
||||
#!/bin/sh
|
||||
# Runs this repo's tests. Extra arguments are forwarded to each `cargo test`,
|
||||
# e.g. `./run-tests.sh transcript` to run just the transcript tests in all
|
||||
# three crates.
|
||||
# Runs this repo's Rust tests. Extra arguments are forwarded to each
|
||||
# `cargo test`, e.g. `./run-tests.sh transcript` to run just the transcript
|
||||
# tests in every workspace.
|
||||
#
|
||||
# server/ holds the backend's own logic (event normalization, transcript
|
||||
# cursors, config persistence, token auth); event-model is the wire shape
|
||||
# server/ and client-core share; client-core is the app's pure logic held
|
||||
# once instead of twice (see CLIENT_CORE.md) -- the syntax highlighter, the
|
||||
# ANSI parser, the transcript cache and fold, the REST and SSE clients. It
|
||||
# is not wired into the Android app yet (RUST.md), which still carries its
|
||||
# own Kotlin copies and has no tests of its own to run here; verifying it
|
||||
# means running it -- see AGENTS.md.
|
||||
# Three workspaces, in dependency order:
|
||||
#
|
||||
# event-model the wire shape server/ and app-rust/ share, so the two
|
||||
# agree by construction rather than by review
|
||||
# server/ the backend's own logic (event normalization, transcript
|
||||
# cursors, config persistence, token auth)
|
||||
# app-rust/ the app itself: `client` (highlighter, ANSI parser,
|
||||
# transcript cache and fold, REST and SSE clients -- see
|
||||
# docs/CLIENT_CORE.md) and `ui` (the screens, drawn with
|
||||
# iris), plus the headless harness tests over the bench
|
||||
# fixture that need no window and no GPU.
|
||||
#
|
||||
# `iris/` is the UI framework and has its own tests; run them from there
|
||||
# (`cd iris && cargo test`). They are not in this loop because iris is not
|
||||
# about this product and its suite is the slower of the two.
|
||||
set -eu
|
||||
cd "$(dirname "$0")"
|
||||
for crate in event-model client-core server; do
|
||||
(cd "$crate" && cargo test "$@")
|
||||
for workspace in event-model server app-rust; do
|
||||
(cd "$workspace" && cargo test "$@")
|
||||
done
|
||||
@@ -30,7 +30,7 @@
|
||||
//! ?limit=N, ?coalesce=true to count rows not deltas,
|
||||
//! ?after=N to floor it at what the caller already holds
|
||||
//! GET /sessions/{id}/subagents [{id, title, status, created, lastActivity}], oldest
|
||||
//! first -- see SUBAGENTS.md
|
||||
//! first -- see docs/SUBAGENTS.md
|
||||
//! GET /sessions/{id}/subagents/{sub}/transcript exactly the transcript route above,
|
||||
//! against that subagent's own transcript
|
||||
//! GET /sessions/{id}/subagents/{sub}/events?after=N exactly the events route above,
|
||||
@@ -1779,7 +1779,7 @@ async fn transcript(
|
||||
}
|
||||
|
||||
/// Exactly [`transcript`]'s route and answer, against one subagent's own
|
||||
/// transcript instead of its session's -- see `SUBAGENTS.md`'s wire shape.
|
||||
/// transcript instead of its session's -- see `docs/SUBAGENTS.md`'s wire shape.
|
||||
async fn subagent_transcript(
|
||||
State(manager): State<Arc<SessionManager>>,
|
||||
UrlPath((id, sub)): UrlPath<(String, String)>,
|
||||
@@ -1846,7 +1846,7 @@ async fn events(
|
||||
}
|
||||
|
||||
/// Exactly [`events`]'s route and answer, against one subagent's own stream
|
||||
/// instead of its session's -- see `SUBAGENTS.md`'s wire shape.
|
||||
/// instead of its session's -- see `docs/SUBAGENTS.md`'s wire shape.
|
||||
async fn subagent_events(
|
||||
State(manager): State<Arc<SessionManager>>,
|
||||
UrlPath((id, sub)): UrlPath<(String, String)>,
|
||||
@@ -1884,7 +1884,7 @@ fn sse_stream(
|
||||
|
||||
/// `GET /sessions/{id}/subagents`: every subagent this session has started,
|
||||
/// oldest first, with a status read from its own transcript -- see
|
||||
/// `SUBAGENTS.md`'s wire shape. A subagent whose last status is `Running` is
|
||||
/// `docs/SUBAGENTS.md`'s wire shape. A subagent whose last status is `Running` is
|
||||
/// reported `Unknown` instead when the session itself is not running: its
|
||||
/// process was the session's, and a session with none has nothing left to
|
||||
/// ask.
|
||||
@@ -1895,7 +1895,7 @@ async fn list_subagents(
|
||||
let session = lookup(&manager, &id)?;
|
||||
// Anything but `Exited` or `Unknown` has a process behind it, which is
|
||||
// what decides whether a subagent still reading `Running` from its own
|
||||
// transcript can be believed -- see `SUBAGENTS.md`'s wire shape.
|
||||
// transcript can be believed -- see `docs/SUBAGENTS.md`'s wire shape.
|
||||
let running = !matches!(
|
||||
session.status(),
|
||||
crate::session::driver::SessionStatus::Exited
|
||||
|
||||
+16
-6
@@ -8,7 +8,7 @@
|
||||
//! the Keystore-sealed enrollment, RUST.md's E3 entry explains why that
|
||||
//! code is reused rather than re-derived in Rust) and on
|
||||
//! `androidx.core:core-ktx` (used at runtime through JNI by
|
||||
//! `android-shell`'s `notify.rs`, for `NotificationCompat` and friends).
|
||||
//! the shell bridge's `notify.rs`, for `NotificationCompat` and friends).
|
||||
//! Both are ordinary Maven/AAR dependency graphs, and reimplementing a
|
||||
//! dependency resolver to avoid one Gradle invocation was not a good trade
|
||||
//! against "smallest honest route" (RUST.md's E5 box) -- especially since
|
||||
@@ -34,7 +34,10 @@ pub fn build(variant: Variant, abis: &[String]) -> Result<PathBuf, Fail> {
|
||||
let repo_root = repo_root()?;
|
||||
let app_dir = repo_root.join("app");
|
||||
let shell_app_dir = app_dir.join("shellApp");
|
||||
let android_shell_dir = repo_root.join("android-shell");
|
||||
// The JNI bridge is the `shell` feature of the one app crate now, not
|
||||
// a crate of its own -- `--no-default-features` is what keeps iris,
|
||||
// wgpu and parley out of a `.so` for an app that draws with Compose.
|
||||
let app_crate_dir = repo_root.join("app-rust");
|
||||
|
||||
let sdk = sdk::find()?;
|
||||
sdk::require_ndk_installed(&sdk.root)?;
|
||||
@@ -49,8 +52,8 @@ pub fn build(variant: Variant, abis: &[String]) -> Result<PathBuf, Fail> {
|
||||
)
|
||||
})?;
|
||||
|
||||
println!("==> Building android-shell for {}", abis.join(", "));
|
||||
build_native_libs(&android_shell_dir, &shell_app_dir, &sdk, abis)?;
|
||||
println!("==> Building the shell JNI bridge for {}", abis.join(", "));
|
||||
build_native_libs(&app_crate_dir, &shell_app_dir, &sdk, abis)?;
|
||||
|
||||
println!("==> Resolving the runtime classpath (one Gradle call -- see apk.rs's module doc)");
|
||||
let classpath_jars = runtime_classpath_jars(&app_dir, &sdk)?;
|
||||
@@ -159,7 +162,14 @@ fn build_native_libs(
|
||||
// segfaults this emulator's driver (RUST.md's E1 entry), and there is
|
||||
// no reason for this crate's debug build to be bigger or slower for a
|
||||
// signing choice that has nothing to do with it.
|
||||
cmd.args(["build", "--release", "-p", "android-shell"]);
|
||||
cmd.args([
|
||||
"build",
|
||||
"--release",
|
||||
"--lib",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
"shell",
|
||||
]);
|
||||
cmd.env("ANDROID_HOME", &sdk.root);
|
||||
cmd.env("ANDROID_SDK_ROOT", &sdk.root);
|
||||
run_checked(
|
||||
@@ -395,7 +405,7 @@ fn merge(
|
||||
std::fs::remove_dir_all(&stage).ok();
|
||||
}
|
||||
for abi in abis {
|
||||
let so_name = "libandroid_shell.so";
|
||||
let so_name = "libai_app.so";
|
||||
let src = shell_app_dir
|
||||
.join("src/main/jniLibs")
|
||||
.join(abi)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
//! `cargo xtask apk` -- E5 (RUST.md): packages `app/shellApp` into a
|
||||
//! signed, installable APK with no Gradle in the packaging step itself.
|
||||
//! `cargo ndk` cross-compiles `android-shell`; `javac`/`d8` turn its two
|
||||
//! `cargo ndk` cross-compiles the app crate's `shell` feature; `javac`/`d8` turn its two
|
||||
//! Java stub classes (plus the generated pinned-CA constant) into dex;
|
||||
//! `aapt2` compiles the manifest into `resources.arsc`; the dex and native
|
||||
//! libraries are merged into that base APK with `jar`; `zipalign` and
|
||||
|
||||
Reference in new issue
Block a user