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).
|
transcript, app screens).
|
||||||
|
|
||||||
The second one, for the Rust port on the `rustify` branch: **the phone app
|
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,
|
and a planned desktop app share almost all of their code.** Screens,
|
||||||
folding, paging, config and the network client live in the shared crates
|
widgets, folding, paging, config and the network client live in
|
||||||
(`iris`, `client-core`, `transcript-ui`, `tabs-ui`); `android-app` and
|
`app-rust/`'s `client` and `ui` modules, drawn with `iris`; `src/android`
|
||||||
`desktop-app` are thin entry points that own only what the platform forces
|
and `src/desktop` are thin entry points that own only what the platform
|
||||||
(JNI and the IME on one side, winit and argv on the other). The two
|
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
|
*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
|
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,
|
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
|
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
|
work on both goes in `ui` the first time it is written, and a platform
|
||||||
platform crate growing a widget or a colour is a defect to move, not a
|
module growing a widget or a colour is a defect to move, not a convenience
|
||||||
convenience to keep. Iris said this on 2026-09-07; docs/RUST.md carries the
|
to keep. Iris said this on 2026-09-07; docs/RUST.md carries the details.
|
||||||
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
|
## 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
|
- `server/` — the Rust backend (`ai-server`). `routes.rs`'s module doc
|
||||||
comment is the HTTP table and the surface's source of truth.
|
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".
|
- `app/` — the Compose app, package `com.example.aiapp`, label "AI Sessions".
|
||||||
`AppRoot.kt` is the navigation `when`; `MainScreen.kt` the root's four tabs
|
`AppRoot.kt` is the navigation `when`; `MainScreen.kt` the root's four tabs
|
||||||
(sessions, import, models, setups); `Api.kt`/`EventStream.kt` the REST + SSE
|
(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
|
Read it before touching `TranscriptCache.kt`, `TranscriptSource.kt`, or
|
||||||
the opening and stream effects in `SessionScreen.kt`.
|
the opening and stream effects in `SessionScreen.kt`.
|
||||||
- `docs/TODO.md` — the working list.
|
- `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`
|
- `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
|
branch of the `ai-app-2` clone): what has to be reproduced, the
|
||||||
framework decision, and the ordered experiments with their pass
|
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
|
`docs/LAYOUT.md`, `docs/TEXTURES.md`, `docs/CLIENT_CORE.md` — iris's
|
||||||
own build log (**any major addition or design decision, not only
|
own build log (**any major addition or design decision, not only
|
||||||
public API** -- Iris, 2026-09-08), working list, decisions log,
|
public API** -- Iris, 2026-09-08), working list, decisions log,
|
||||||
layout/render design, and texture-atlas design, and the client-core
|
layout/render design, texture-atlas design, and the design of
|
||||||
crate's design, respectively.
|
`app-rust`'s `client` module, respectively.
|
||||||
- `docs/SCROLL.md` — how anything in iris scrolls: one
|
- `docs/SCROLL.md` — how anything in iris scrolls: one
|
||||||
`ScrollController` holds the position, the gesture, the fling and the
|
`ScrollController` holds the position, the gesture, the fling and the
|
||||||
pin, and the two widgets that scroll (`ScrollArea`, `LazySpan`) own
|
pin, and the two widgets that scroll (`ScrollArea`, `LazySpan`) own
|
||||||
@@ -121,10 +147,15 @@ guaranteed to have.
|
|||||||
|
|
||||||
## Checking your work
|
## Checking your work
|
||||||
|
|
||||||
- **Server**: `./run-tests.sh` from the repo root (or `cargo test` from
|
- **Rust**: `./run-tests.sh` from the repo root runs `event-model`,
|
||||||
`server/`), plus `cargo clippy --all-targets` and `cargo fmt`. The build
|
`server/` and `app-rust/`; `cd iris && cargo test` runs the framework's
|
||||||
stays warning-clean and rustfmt-clean at the defaults — there is no
|
own suite, which is slower and not about this product. Each workspace
|
||||||
`rustfmt.toml` and there should not be one.
|
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/`,
|
- **App**: from `app/`,
|
||||||
`. ./android-env.sh && ./gradlew :androidApp:ktfmtFormat
|
`. ./android-env.sh && ./gradlew :androidApp:ktfmtFormat
|
||||||
:androidApp:compileDebugKotlin :androidApp:lintDebug
|
: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
|
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
|
how the cost of a layout node per link was attributed to the framework
|
||||||
rather than guessed at.
|
rather than guessed at.
|
||||||
- **`iris/android-app/build-apk.sh [debug|release] [--abi ...] [--features
|
- **`app-rust/build-apk.sh [debug|release] [--abi ...] [--features
|
||||||
...]`** builds iris-android-app's cdylib (`cargo ndk`) and its APK
|
...]`** builds the Rust app's cdylib (`cargo ndk` from `app-rust/`,
|
||||||
(Gradle) in one step and verifies the result (`aapt2`/`apksigner`), and
|
straight into `android-project/app/src/main/jniLibs/`) and its APK
|
||||||
**`iris/android-app/run-bench.sh [--apk PATH]`** installs it on this
|
(Gradle, from `android-project/`) in one step and verifies the result
|
||||||
checkout's own emulator, taps "Run benchmark" by label, and prints the
|
(`aapt2`/`apksigner`), and **`app-rust/run-bench.sh [--apk PATH]`**
|
||||||
report -- written so the P0 build/install/tap/read-report cycle stops
|
installs it on this checkout's own emulator, taps "Run benchmark" by
|
||||||
being retyped by hand each time (docs/RUST.md's P0 box).
|
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
|
- **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
|
the commands and what each cannot answer): test at the cheapest one
|
||||||
that can answer the question. `cargo test -p transcript-fixture` runs
|
that can answer the question. `cd app-rust && cargo test` runs the real
|
||||||
the real transcript screen over the bench fixture with **no window, no
|
transcript screen over the bench fixture with **no window, no
|
||||||
compositor and no GPU** (`iris::harness`), on a clock the test owns and
|
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
|
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`
|
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
|
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
|
--dir ../app-rust --shot …` opens the same screen in a window at the
|
||||||
density for looking at, and `--replay FILE` drives the same recording
|
phone's own size and density for looking at, and `--replay FILE` drives
|
||||||
into it. The emulator is for JNI, the IME, insets, the surface
|
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 --
|
lifecycle and one verification run before a build goes to the phone --
|
||||||
not for iterating on layout.
|
not for iterating on layout.
|
||||||
- **The emulator is a GLES rig, deliberately** (Iris, 2026-09-08;
|
- **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",
|
"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]]
|
[[package]]
|
||||||
name = "aligned"
|
name = "aligned"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -743,19 +765,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "client-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"event-model",
|
|
||||||
"log",
|
|
||||||
"pulldown-cmark",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"ureq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
@@ -881,9 +890,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
@@ -891,18 +900,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-epoch"
|
name = "crossbeam-epoch"
|
||||||
version = "0.9.20"
|
version = "0.9.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.22"
|
version = "0.8.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
@@ -1055,7 +1064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1167,9 +1176,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.12.4"
|
version = "0.12.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23"
|
checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
@@ -1718,24 +1727,6 @@ dependencies = [
|
|||||||
"winit",
|
"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]]
|
[[package]]
|
||||||
name = "iris-core"
|
name = "iris-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1943,7 +1934,7 @@ dependencies = [
|
|||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"libc",
|
"libc",
|
||||||
"plain",
|
"plain",
|
||||||
"redox_syscall 0.9.3",
|
"redox_syscall 0.9.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2599,7 +2590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3173,9 +3164,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.9.3"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d678d17679829e73d371e96880897e98fee2ded7acc0a50bdf8af2affa4b2fe5"
|
checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
]
|
]
|
||||||
@@ -3282,14 +3273,14 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.43"
|
version = "0.23.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -3638,7 +3629,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3840,28 +3831,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
name = "tree_magic_mini"
|
name = "tree_magic_mini"
|
||||||
version = "3.2.2"
|
version = "3.2.2"
|
||||||
@@ -3887,7 +3856,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3932,9 +3901,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ureq"
|
name = "ureq"
|
||||||
version = "3.4.0"
|
version = "3.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
checksum = "af5546be8f5378d5414f83733f5c9a2526f4645829edbc1c41790aeef1b38e8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"cookie_store",
|
"cookie_store",
|
||||||
@@ -3952,9 +3921,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ureq-proto"
|
name = "ureq-proto"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
checksum = "fabc3e92916c89c95b20eef7b06b00b066bc217ef9ea3a4ac9bf1a7e35261e10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"http",
|
"http",
|
||||||
@@ -4417,7 +4386,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5075,18 +5044,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.56"
|
version = "0.8.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.56"
|
version = "0.8.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"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" />
|
<data android:scheme="aiapp" android:host="enroll" />
|
||||||
</intent-filter>
|
</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>
|
</activity>
|
||||||
|
|
||||||
<!-- This app's own recent log, for Dev Updater to read on the
|
<!-- 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 {
|
static {
|
||||||
// The provider is created before any activity, so it cannot rely
|
// The provider is created before any activity, so it cannot rely
|
||||||
// on MainActivity's own load. Loading twice is a no-op.
|
// 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. */
|
/** 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 {
|
public final class MainActivity extends Activity {
|
||||||
static {
|
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
|
#!/bin/sh
|
||||||
# Builds iris-android-app end to end: the cdylib (cargo ndk, straight into
|
# Builds the Android app end to end: the cdylib (cargo ndk from this
|
||||||
# app/src/main/jniLibs/) then the APK (Gradle). Written to stop re-typing
|
# 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
|
# the same incantation by hand every time (ANDROID_HOME/NDK exports, the
|
||||||
# cargo ndk invocation, the keystore env for a release build, apksigner/
|
# cargo ndk invocation, the keystore env for a release build, apksigner/
|
||||||
# aapt2 verification) -- see docs/RUST.md's P0 box. Same shape as `app/
|
# 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
|
# whatever earlier builds left here, and Gradle packages every directory it
|
||||||
# finds -- a debug x86_64 emulator build left behind made an arm64 "release"
|
# finds -- a debug x86_64 emulator build left behind made an arm64 "release"
|
||||||
# 339 MB on 2026-09-06.
|
# 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.
|
# ...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
|
# `mergeReleaseNativeLibs` is *up to date* against its cached inputs, so a
|
||||||
# build that switches ABI packages the previous ABI: an `--abi x86_64`
|
# 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.
|
# 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
|
# 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.
|
# an ABI change costs the native merge and not the whole Gradle build.
|
||||||
rm -rf app/build/intermediates/merged_native_libs \
|
rm -rf android-project/app/build/intermediates/merged_native_libs \
|
||||||
app/build/intermediates/stripped_native_libs \
|
android-project/app/build/intermediates/stripped_native_libs \
|
||||||
app/build/intermediates/merged_jni_libs
|
android-project/app/build/intermediates/merged_jni_libs
|
||||||
echo "build-apk.sh: cargo ndk -t $ABI build ${BUILD_TYPE:+(${BUILD_TYPE})} --features \"$FEATURES\""
|
echo "build-apk.sh: cargo ndk -t $ABI build ${BUILD_TYPE:+(${BUILD_TYPE})} --features \"$FEATURES\""
|
||||||
if [ "$BUILD_TYPE" = "release" ]; then
|
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
|
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
|
fi
|
||||||
|
|
||||||
GRADLE_TASK="assembleDebug"
|
GRADLE_TASK="assembleDebug"
|
||||||
APK_DIR="app/build/outputs/apk/debug"
|
APK_DIR="android-project/app/build/outputs/apk/debug"
|
||||||
APK_NAME="app-debug.apk"
|
APK_NAME="app-debug.apk"
|
||||||
if [ "$BUILD_TYPE" = "release" ]; then
|
if [ "$BUILD_TYPE" = "release" ]; then
|
||||||
GRADLE_TASK="assembleRelease"
|
GRADLE_TASK="assembleRelease"
|
||||||
APK_DIR="app/build/outputs/apk/release"
|
APK_DIR="android-project/app/build/outputs/apk/release"
|
||||||
APK_NAME="app-release.apk"
|
APK_NAME="app-release.apk"
|
||||||
# Same key `app/build-apk.sh` (the Compose app) generates once under
|
# Same key `app/build-apk.sh` (the Compose app) generates once under
|
||||||
# ~/.config/ai-app/release.jks -- see AGENTS.md's "Checking your work".
|
# ~/.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")
|
AI_APP_KEYSTORE_PASSWORD=$(cat "$AI_APP_KEYSTORE.password")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
gradle ":app:$GRADLE_TASK" --console=plain
|
(cd android-project && gradle ":app:$GRADLE_TASK" --console=plain)
|
||||||
|
|
||||||
APK_PATH="$(pwd)/$APK_DIR/$APK_NAME"
|
APK_PATH="$(pwd)/$APK_DIR/$APK_NAME"
|
||||||
BUILD_TOOLS=$(ls -d "$SDK_ROOT"/build-tools/*/ | sort -V | tail -1)
|
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
|
//! `--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
|
//! 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
|
//! 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
|
//! the Android bench and the headless tests, so what differs between a
|
||||||
//! screenshot here and one from the phone is the renderer, never the
|
//! screenshot here and one from the phone is the renderer, never the
|
||||||
@@ -80,7 +80,7 @@ fn main() {
|
|||||||
pub struct Client {
|
pub struct Client {
|
||||||
ui_state: DefaultUiState,
|
ui_state: DefaultUiState,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
screen: Option<transcript_ui::TranscriptScreen>,
|
screen: Option<ai_app::ui::TranscriptScreen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultAppState for Client {
|
impl DefaultAppState for Client {
|
||||||
@@ -88,8 +88,8 @@ impl DefaultAppState for Client {
|
|||||||
WindowAttributes::default()
|
WindowAttributes::default()
|
||||||
.with_title("iris transcript (bench fixture)")
|
.with_title("iris transcript (bench fixture)")
|
||||||
.with_inner_size(PhysicalSize::new(
|
.with_inner_size(PhysicalSize::new(
|
||||||
transcript_fixture::PHONE_WIDTH,
|
ai_app::ui::fixture::PHONE_WIDTH,
|
||||||
transcript_fixture::PHONE_HEIGHT,
|
ai_app::ui::fixture::PHONE_HEIGHT,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ impl DefaultAppState for Client {
|
|||||||
rsc: &mut DefaultRsc<Self>,
|
rsc: &mut DefaultRsc<Self>,
|
||||||
_: Proxy<Self::Event>,
|
_: Proxy<Self::Event>,
|
||||||
) -> Self {
|
) -> 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) => {
|
Ok(opened) => {
|
||||||
if let Some(message) = message_argv() {
|
if let Some(message) = message_argv() {
|
||||||
opened.screen.composer.field.edit(rsc).set(&message);
|
opened.screen.composer.field.edit(rsc).set(&message);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
//! I5's desktop proof: the transcript screen built from synthetic
|
//! 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),
|
//! `lib.rs`'s doc for why `transcript-ui` itself never fetches anything),
|
||||||
//! run via `iris/run-headless.sh transcript -- -p transcript-ui` for a
|
//! run via `iris/run-headless.sh transcript -- -p transcript-ui` for a
|
||||||
//! screenshot on the winit backend, or `cargo run --example transcript -p
|
//! 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
|
//! with `ui-trace record --do "tap 'Tools'"` on Android, to prove
|
||||||
//! hold-the-edge expand).
|
//! hold-the-edge expand).
|
||||||
|
|
||||||
use client_core::QuestionOption;
|
use ai_app::client::QuestionOption;
|
||||||
use client_core::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
use ai_app::client::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -25,7 +25,7 @@ fn main() {
|
|||||||
pub struct Client {
|
pub struct Client {
|
||||||
ui_state: DefaultUiState,
|
ui_state: DefaultUiState,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
screen: transcript_ui::TranscriptScreen,
|
screen: ai_app::ui::TranscriptScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn msg(seq: u64, from_user: bool, text: &str) -> FoldedRow {
|
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.
|
/// screen in the expanded shot.
|
||||||
fn long_output() -> String {
|
fn long_output() -> String {
|
||||||
(0..200)
|
(0..200)
|
||||||
.map(|i| format!("test transcript_ui::case_{i} ... ok"))
|
.map(|i| format!("test ai_app::ui::case_{i} ... ok"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ fn synthetic_rows() -> Vec<FoldedRow> {
|
|||||||
Some((&long_output(), false)),
|
Some((&long_output(), false)),
|
||||||
)),
|
)),
|
||||||
msg(6, true, "Looks good, thanks!"),
|
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
|
// 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,
|
// app's without a server (docs/RUST.md's P1a box). The heading,
|
||||||
// paragraph, fence and table are the *same source* the bench
|
// paragraph, fence and table are the *same source* the bench
|
||||||
@@ -207,7 +207,7 @@ impl DefaultAppState for Client {
|
|||||||
rsc: &mut DefaultRsc<Self>,
|
rsc: &mut DefaultRsc<Self>,
|
||||||
_: Proxy<Self::Event>,
|
_: Proxy<Self::Event>,
|
||||||
) -> Self {
|
) -> 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
|
// Exercises `push_row`/`ItemKey` beyond construction time, matching
|
||||||
// how a live SSE loop appends -- a row arriving after the screen
|
// how a live SSE loop appends -- a row arriving after the screen
|
||||||
// already exists must land at the bottom without disturbing what's
|
// already exists must land at the bottom without disturbing what's
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
#
|
#
|
||||||
# Usage: ./run-bench.sh [--apk PATH]
|
# Usage: ./run-bench.sh [--apk PATH]
|
||||||
# Defaults to this checkout's own release APK
|
# 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.
|
# debug one -- build one first with ./build-apk.sh.
|
||||||
set -eu
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
@@ -20,10 +21,10 @@ while [ $# -gt 0 ]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
if [ -z "$APK" ]; then
|
if [ -z "$APK" ]; then
|
||||||
if [ -f app/build/outputs/apk/release/app-release.apk ]; then
|
if [ -f android-project/app/build/outputs/apk/release/app-release.apk ]; then
|
||||||
APK=app/build/outputs/apk/release/app-release.apk
|
APK=android-project/app/build/outputs/apk/release/app-release.apk
|
||||||
else
|
else
|
||||||
APK=app/build/outputs/apk/debug/app-debug.apk
|
APK=android-project/app/build/outputs/apk/debug/app-debug.apk
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ ! -f "$APK" ]; then
|
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
|
//! 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.
|
//! `android_logger` as the logger to forward to and nothing else.
|
||||||
//!
|
//!
|
||||||
//! Everything general -- the ring, its bounds, the `log::Log` backend --
|
//! Everything general -- the ring, its bounds, the `log::Log` backend --
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
//! the same phone through `devlog`'s `ContentProvider`. See
|
//! the same phone through `devlog`'s `ContentProvider`. See
|
||||||
//! `docs/DECISIONS.md`, 2026-09-07.
|
//! `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
|
/// Installs the ring in front of `android_logger`, so `logcat` still sees
|
||||||
/// exactly what it saw before and the ring sees it too.
|
/// 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
|
/// contract is live and which package's log it is -- the bench build and
|
||||||
/// the ordinary one have different ones.
|
/// the ordinary one have different ones.
|
||||||
pub fn diagnostics_line() -> String {
|
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}"),
|
Some(authority) => format!("devlog provider: content://{authority}"),
|
||||||
// Not "off": Android creates a provider lazily, so this is what
|
// Not "off": Android creates a provider lazily, so this is what
|
||||||
// "nobody has asked for it yet" looks like, and it is a different
|
// "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
|
//! this file exists to measure -- see docs/RUST.md's P0 box for the
|
||||||
//! before/after report.
|
//! 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 android_view::jni::{JavaVM, objects::GlobalRef};
|
||||||
use client_core::transcript_fold::{TranscriptItem, fold_event};
|
|
||||||
use event_model::SeqEvent;
|
use event_model::SeqEvent;
|
||||||
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
@@ -98,7 +98,7 @@ pub struct BenchClient {
|
|||||||
/// P0 box, "the status-bar inset is not applied," found the row sitting
|
/// P0 box, "the status-bar inset is not applied," found the row sitting
|
||||||
/// directly under it because nothing here read `insets().top` at all.
|
/// directly under it because nothing here read `insets().top` at all.
|
||||||
top_bar: WeakWidget<WidgetPtr>,
|
top_bar: WeakWidget<WidgetPtr>,
|
||||||
screen: Option<transcript_ui::TranscriptScreen>,
|
screen: Option<crate::ui::TranscriptScreen>,
|
||||||
items: Vec<TranscriptItem>,
|
items: Vec<TranscriptItem>,
|
||||||
/// The events not yet streamed -- consumed by `start_benchmark`'s own
|
/// The events not yet streamed -- consumed by `start_benchmark`'s own
|
||||||
/// clone, kept here only as the source a second run would need (the
|
/// 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,
|
last_top_pad: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
match transcript_fixture::build_screen(rsc) {
|
match crate::ui::fixture::build_screen(rsc) {
|
||||||
Ok((opened, tree)) => {
|
Ok((opened, tree)) => {
|
||||||
client.items = opened.items;
|
client.items = opened.items;
|
||||||
client.stream_tail = opened.stream_tail;
|
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
|
// The composer bar sits directly on whichever of the IME or the
|
||||||
// navigation bar is currently the bottom of usable space -- see
|
// 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
|
// `ime_bottom` already exceeds the plain nav-bar inset whenever the
|
||||||
// keyboard covers it, so the larger of the two is always the right
|
// keyboard covers it, so the larger of the two is always the right
|
||||||
// answer without needing to know which is currently showing.
|
// answer without needing to know which is currently showing.
|
||||||
@@ -602,7 +602,7 @@ impl BenchClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild_transcript(&mut self, rsc: &mut Rsc) {
|
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.content)(rsc).set(tree);
|
||||||
self.screen = Some(screen);
|
self.screen = Some(screen);
|
||||||
}
|
}
|
||||||
@@ -667,8 +667,8 @@ impl BenchClient {
|
|||||||
// answer is "none" -- the bench itself opens a checked-in
|
// answer is "none" -- the bench itself opens a checked-in
|
||||||
// fixture and needs no server, so this pane is the only place
|
// fixture and needs no server, so this pane is the only place
|
||||||
// an enrolment can be seen to have taken.
|
// an enrolment can be seen to have taken.
|
||||||
crate::enrollment::status_line(),
|
crate::android::enrollment::status_line(),
|
||||||
crate::app_log::diagnostics_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
|
/// 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
|
/// `Released` arm) -- `Scrollable::fling` sets a velocity and drives
|
||||||
/// nothing by itself.
|
/// nothing by itself.
|
||||||
fn animate_scroll(scroll: iris::prelude::WeakWidget<iris::prelude::LazySpan>, rsc: &mut Rsc) {
|
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
|
//! **Why**: Iris runs these builds on a phone with no `adb`, and Android
|
||||||
//! forbids one app reading another's `logcat`, so nothing outside this
|
//! forbids one app reading another's `logcat`, so nothing outside this
|
||||||
//! process can recover what it wrote. The app already keeps a bounded copy
|
//! 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
|
//! 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
|
//! 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
|
//! 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.
|
// line announcing this one rather than buried under it.
|
||||||
#[cfg(feature = "transcript-screen")]
|
#[cfg(feature = "transcript-screen")]
|
||||||
if let Some(dir) = string_arg(&mut env, &files_dir) {
|
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"))]
|
#[cfg(not(feature = "transcript-screen"))]
|
||||||
let _ = &files_dir;
|
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
|
/// `DevLogProvider.nativeLinesSince` -- every held line with a sequence at
|
||||||
/// or after `since`, oldest first, [`FIELDS_PER_LINE`] strings each.
|
/// 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
|
/// is, and one definition of the cursor is what keeps the app's own
|
||||||
/// uploaded report and this provider describing the same lines.
|
/// 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.
|
/// The three status numbers, as the provider's row.
|
||||||
#[cfg(feature = "transcript-screen")]
|
#[cfg(feature = "transcript-screen")]
|
||||||
fn status_fields() -> Vec<String> {
|
fn status_fields() -> Vec<String> {
|
||||||
let ring = client_core::log_ring::process_ring();
|
let ring = crate::client::log_ring::process_ring();
|
||||||
vec![
|
vec![
|
||||||
ring.len().to_string(),
|
ring.len().to_string(),
|
||||||
ring.dropped().to_string(),
|
ring.dropped().to_string(),
|
||||||
@@ -164,7 +164,7 @@ fn status_fields() -> Vec<String> {
|
|||||||
|
|
||||||
#[cfg(feature = "transcript-screen")]
|
#[cfg(feature = "transcript-screen")]
|
||||||
fn line_fields(since: u64) -> Vec<String> {
|
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);
|
let mut fields = Vec::with_capacity(lines.len() * FIELDS_PER_LINE);
|
||||||
for line in lines {
|
for line in lines {
|
||||||
fields.push(line.seq.to_string());
|
fields.push(line.seq.to_string());
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Which `ai-server` this app talks to, and how it was told.
|
//! Which `ai-server` this app talks to, and how it was told.
|
||||||
//!
|
//!
|
||||||
//! The parsing, the file and its owner-only mode are
|
//! 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
|
//! the desktop app. What is genuinely this platform's, and all that is
|
||||||
//! here, is the intent plumbing: Android hands an `aiapp://enroll?...`
|
//! here, is the intent plumbing: Android hands an `aiapp://enroll?...`
|
||||||
//! link to `MainActivity`, which passes it and the app's private files
|
//! 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
|
//! avoids: it arrives from the activity, and `AndroidAppState::new` -- the
|
||||||
//! first thing that wants the enrollment -- has no parameter it could come
|
//! first thing that wants the enrollment -- has no parameter it could come
|
||||||
//! in through. Same shape, and the same reason, as
|
//! 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"))]
|
#[cfg(not(feature = "bench"))]
|
||||||
use client_core::api::UreqTransport;
|
use crate::client::api::UreqTransport;
|
||||||
use client_core::config::{EnrolledServer, EnrollmentStore};
|
use crate::client::config::{EnrolledServer, EnrollmentStore};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
//! `IrisView`/`MainActivity` Java, and the JNI registration I2 built and
|
//! `IrisView`/`MainActivity` Java, and the JNI registration I2 built and
|
||||||
//! measured against, and the only thing a transcript screen needs on top
|
//! measured against, and the only thing a transcript screen needs on top
|
||||||
//! is a different `AndroidAppState` -- the same axis `tabs_ui::build` vs.
|
//! 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`).
|
//! `iris/examples/tabs.rs` and `iris/transcript-ui/examples/transcript.rs`).
|
||||||
//! A build picks one screen or the other, never both, so `Client` and
|
//! A build picks one screen or the other, never both, so `Client` and
|
||||||
//! `TranscriptClient` are cfg-gated apart rather than switched at runtime --
|
//! `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
|
//! **`bench` feature (P0's iris half, docs/RUST.md):** a third
|
||||||
//! `AndroidAppState`, `bench_client::BenchClient`, on the same axis --
|
//! `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
|
//! fixture (`app/bench-fixture/assets/transcript.jsonl`) instead of a real
|
||||||
//! server, with a "Run benchmark" control that drives the same scroll loop
|
//! server, with a "Run benchmark" control that drives the same scroll loop
|
||||||
//! and streaming phase the Compose `bench` build type's `BenchRun.kt`
|
//! and streaming phase the Compose `bench` build type's `BenchRun.kt`
|
||||||
+11
-11
@@ -13,13 +13,13 @@
|
|||||||
//! straight on the screen under test.
|
//! straight on the screen under test.
|
||||||
//!
|
//!
|
||||||
//! Which server it opens it against is no longer baked in: it is the
|
//! 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
|
//! and `desktop-app`'s identical `--link`), because an APK
|
||||||
//! cross-compiled here cannot pin the CA of a server on the host.
|
//! cross-compiled here cannot pin the CA of a server on the host.
|
||||||
//!
|
//!
|
||||||
//! **Reuses `iris/desktop-app`'s `app.rs` shape almost exactly** --
|
//! **Reuses `iris/desktop-app`'s `app.rs` shape almost exactly** --
|
||||||
//! `fold_event`/`group_tool_runs`/`fold_page`/`raw_seq` from
|
//! `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
|
//! a stale background response. What differs is only the redraw
|
||||||
//! mechanism: android-view has no `winit::EventLoopProxy`, so this uses
|
//! mechanism: android-view has no `winit::EventLoopProxy`, so this uses
|
||||||
//! `iris::task::Tasks::redraw_handle` (new, added alongside this box) to
|
//! `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
|
//! **Streaming no longer costs a full rebuild** (fixed after the P0 gate
|
||||||
//! showed why it mattered -- 20 events/second means 20 rebuilds/second of
|
//! showed why it mattered -- 20 events/second means 20 rebuilds/second of
|
||||||
//! a ~3,200-row transcript otherwise): `apply_event` calls
|
//! 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
|
//! after `fold_event`, which updates only the row(s) that actually
|
||||||
//! changed (almost always just the one open assistant message) instead of
|
//! changed (almost always just the one open assistant message) instead of
|
||||||
//! refolding and rebuilding every row. `rebuild_transcript` still runs
|
//! refolding and rebuilding every row. `rebuild_transcript` still runs
|
||||||
//! the whole widget tree once, for the opening page and for `apply`'s own
|
//! the whole widget tree once, for the opening page and for `apply`'s own
|
||||||
//! rare regroup fallback.
|
//! rare regroup fallback.
|
||||||
|
|
||||||
use client_core::api::{ApiClient, UreqTransport};
|
use crate::client::api::{ApiClient, UreqTransport};
|
||||||
use client_core::event_stream::{StreamItem, follow_session_events};
|
use crate::client::event_stream::{StreamItem, follow_session_events};
|
||||||
use client_core::transcript_fold::{TranscriptItem, fold_event, fold_page, group_tool_runs};
|
use crate::client::transcript_fold::{TranscriptItem, fold_event, fold_page, group_tool_runs};
|
||||||
use event_model::SeqEvent;
|
use event_model::SeqEvent;
|
||||||
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
use iris::android::{AndroidAppState, AndroidRsc, AndroidUiState, HasAndroidUiState};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
@@ -54,7 +54,7 @@ pub struct TranscriptClient {
|
|||||||
/// `set` calls the way `desktop-app`'s `transcript_ptr` isn't touched
|
/// `set` calls the way `desktop-app`'s `transcript_ptr` isn't touched
|
||||||
/// by rebuilding the session list beside it.
|
/// by rebuilding the session list beside it.
|
||||||
content: WeakWidget<WidgetPtr>,
|
content: WeakWidget<WidgetPtr>,
|
||||||
screen: Option<transcript_ui::TranscriptScreen>,
|
screen: Option<crate::ui::TranscriptScreen>,
|
||||||
/// The folded transcript as of the last rebuild -- kept here (not
|
/// The folded transcript as of the last rebuild -- kept here (not
|
||||||
/// re-derived) for the same reason `desktop-app`'s `Client::items`
|
/// re-derived) for the same reason `desktop-app`'s `Client::items`
|
||||||
/// exists: a live `StreamEvent` only carries one new wire event, and
|
/// 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,
|
/// while the app is running is how somebody points it at another server,
|
||||||
/// and a cached transport would keep talking to the old one.
|
/// and a cached transport would keep talking to the old one.
|
||||||
fn build_transport() -> Result<UreqTransport, String> {
|
fn build_transport() -> Result<UreqTransport, String> {
|
||||||
crate::enrollment::transport()
|
crate::android::enrollment::transport()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn placeholder<Rsc: HasEvents>(rsc: &mut Rsc, message: &str) -> StrongWidget {
|
fn placeholder<Rsc: HasEvents>(rsc: &mut Rsc, message: &str) -> StrongWidget {
|
||||||
@@ -262,14 +262,14 @@ impl TranscriptClient {
|
|||||||
.fetch_transcript_page(&session_id, None, 200, true)
|
.fetch_transcript_page(&session_id, None, 200, true)
|
||||||
.map_err(|e| e.to_string());
|
.map_err(|e| e.to_string());
|
||||||
// The wire `seq` of the last line, not a folded item's `seq()`
|
// 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
|
// resuming from the latter re-delivers deltas already folded
|
||||||
// into an in-progress reply.
|
// into an in-progress reply.
|
||||||
let after = page
|
let after = page
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|values| values.last())
|
.and_then(|values| values.last())
|
||||||
.and_then(client_core::transcript_fold::raw_seq)
|
.and_then(crate::client::transcript_fold::raw_seq)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let result = page.and_then(|values| fold_page(&values));
|
let result = page.and_then(|values| fold_page(&values));
|
||||||
|
|
||||||
@@ -341,7 +341,7 @@ impl TranscriptClient {
|
|||||||
.filter(|t| !t.is_empty());
|
.filter(|t| !t.is_empty());
|
||||||
|
|
||||||
let rows = group_tool_runs(&self.items);
|
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 {
|
if let Some(text) = in_progress {
|
||||||
screen.composer.field.edit(rsc).set(&text);
|
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
|
//! 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
|
//! 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
|
//! `CLIENT_CORE.md` for exactly which routes have a typed method here and
|
||||||
//! which do not.
|
//! which do not.
|
||||||
@@ -64,7 +64,7 @@ pub trait Transport: Send + Sync {
|
|||||||
|
|
||||||
/// Opens `path` and answers a reader over the response body, for a
|
/// 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
|
/// 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.
|
/// [`Transport::request`] does for a non-2xx response.
|
||||||
fn stream(&self, path: &str) -> Result<Box<dyn Read + Send>, ApiError>;
|
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
|
/// A page of transcript history. `before` is the newest-first cursor
|
||||||
/// (server default is "the newest page" when absent, which a caller
|
/// (server default is "the newest page" when absent, which a caller
|
||||||
/// gets by passing `None`); the events themselves are handed back as
|
/// 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
|
/// 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.
|
/// lines (for the transcript cache) is not forced to parse them.
|
||||||
pub fn fetch_transcript_page(
|
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
|
/// A page of transcript history, each line handed back paired with the
|
||||||
/// exact text it came from, and bounded below by `after` -- the shape
|
/// 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
|
/// fetched in the transcript cache without a second round trip to fetch
|
||||||
/// the raw text separately. Ported from `Api.kt`'s `fetchTranscript`.
|
/// 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 {
|
pub fn base_url(&self) -> String {
|
||||||
format!("https://{}:{}", self.host, self.port)
|
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,
|
//! 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.
|
//! replaying the transcript after a cursor and then following it live.
|
||||||
//! Ported from `app/.../EventStream.kt`; the framing itself is
|
//! Ported from `app/.../EventStream.kt`; the framing itself is
|
||||||
//! [`crate::sse`].
|
//! [`crate::client::sse`].
|
||||||
|
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
|
|
||||||
use event_model::SeqEvent;
|
use event_model::SeqEvent;
|
||||||
|
|
||||||
use crate::api::{ApiError, Transport};
|
use crate::client::api::{ApiError, Transport};
|
||||||
use crate::sse::SseReader;
|
use crate::client::sse::SseReader;
|
||||||
|
|
||||||
/// The frame name the server uses to say a cursor was too far behind to
|
/// 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`.
|
/// continue from. Must match `send_backlog` in `server/src/routes.rs`.
|
||||||
@@ -83,7 +83,7 @@ pub fn follow_session_events(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::{Body, RawResponse};
|
use crate::client::api::{Body, RawResponse};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
struct FixtureTransport {
|
struct FixtureTransport {
|
||||||
File renamed without changes.
@@ -107,8 +107,8 @@ impl MarkdownScanner {
|
|||||||
fn table_delimiter(&self, start: usize, end: usize) -> bool {
|
fn table_delimiter(&self, start: usize, end: usize) -> bool {
|
||||||
let mut dashes = false;
|
let mut dashes = false;
|
||||||
let mut pipes = false;
|
let mut pipes = false;
|
||||||
for at in self.indented(start, end)..end {
|
for c in &self.code[self.indented(start, end)..end] {
|
||||||
match self.code[at] {
|
match c {
|
||||||
'-' => dashes = true,
|
'-' => dashes = true,
|
||||||
'|' => pipes = true,
|
'|' => pipes = true,
|
||||||
':' | ' ' | '\t' => {}
|
':' | ' ' | '\t' => {}
|
||||||
@@ -263,8 +263,7 @@ impl MarkdownScanner {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let mut seen = 0usize;
|
let mut seen = 0usize;
|
||||||
for at in start..end {
|
for &c in &self.code[start..end] {
|
||||||
let c = self.code[at];
|
|
||||||
if c == marker {
|
if c == marker {
|
||||||
seen += 1;
|
seen += 1;
|
||||||
} else if !c.is_whitespace() {
|
} 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
|
/// Whether a target belongs to this app or to `iris` rather than to a
|
||||||
/// `client_core`) rather than a dependency's -- `starts_with` guarded by an
|
/// dependency -- `starts_with` guarded by an
|
||||||
/// exact match or a `::` so an unrelated crate that merely begins with the
|
/// 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
|
/// same letters (there is no such crate today, but the check should not
|
||||||
/// rely on that) is never mistaken for one of ours.
|
/// rely on that) is never mistaken for one of ours.
|
||||||
fn is_own_target(target: &str) -> bool {
|
fn is_own_target(target: &str) -> bool {
|
||||||
target == "iris"
|
target == "iris"
|
||||||
|| target.starts_with("iris::")
|
|| target.starts_with("iris::")
|
||||||
|| target == "client_core"
|
|| target == "ai_app"
|
||||||
|| target.starts_with("client_core::")
|
|| target.starts_with("ai_app::")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a line at `level` from `target` belongs in the ring, given
|
/// Whether a line at `level` from `target` belongs in the ring, given
|
||||||
@@ -733,7 +733,7 @@ mod tests {
|
|||||||
"own Debug, tracing on: included"
|
"own Debug, tracing on: included"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
ring_accepts(Level::Trace, "client_core::api", true),
|
ring_accepts(Level::Trace, "ai_app::api", true),
|
||||||
"own Trace, tracing on: included"
|
"own Trace, tracing on: included"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -742,8 +742,8 @@ mod tests {
|
|||||||
fn is_own_target_matches_the_crate_or_its_modules_only() {
|
fn is_own_target_matches_the_crate_or_its_modules_only() {
|
||||||
assert!(is_own_target("iris"));
|
assert!(is_own_target("iris"));
|
||||||
assert!(is_own_target("iris::sense"));
|
assert!(is_own_target("iris::sense"));
|
||||||
assert!(is_own_target("client_core"));
|
assert!(is_own_target("ai_app"));
|
||||||
assert!(is_own_target("client_core::log_ring"));
|
assert!(is_own_target("ai_app::log_ring"));
|
||||||
assert!(!is_own_target("iris_something_else"));
|
assert!(!is_own_target("iris_something_else"));
|
||||||
assert!(!is_own_target("naga::front"));
|
assert!(!is_own_target("naga::front"));
|
||||||
assert!(!is_own_target("jni"));
|
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
|
//! `GET /notifications`, the attention stream PLAN.md's "Notifications: two
|
||||||
//! places, never both" describes. Ported from the parsing half of
|
//! places, never both" describes. Ported from the parsing half of
|
||||||
//! `app/.../Notifications.kt`'s `NotificationService` -- the framing
|
//! `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
|
//! [`NotificationKind`], mirroring `server/src/session/mod.rs`'s
|
||||||
//! `Notification`/`NotificationKind`).
|
//! `Notification`/`NotificationKind`).
|
||||||
//!
|
//!
|
||||||
@@ -17,8 +17,8 @@ use std::io::{BufRead, BufReader};
|
|||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::api::{ApiError, Transport};
|
use crate::client::api::{ApiError, Transport};
|
||||||
use crate::sse::SseReader;
|
use crate::client::sse::SseReader;
|
||||||
|
|
||||||
/// One frame of `GET /notifications`, matching `server/src/session/mod.rs`'s
|
/// One frame of `GET /notifications`, matching `server/src/session/mod.rs`'s
|
||||||
/// `Notification` field for field.
|
/// `Notification` field for field.
|
||||||
@@ -93,7 +93,7 @@ pub fn follow_notifications(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::{Body, RawResponse};
|
use crate::client::api::{Body, RawResponse};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
struct FixtureTransport {
|
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
|
//! of this crate exists: the derivation is the same on a phone and on a
|
||||||
//! desktop, and it is testable without a renderer.
|
//! desktop, and it is testable without a renderer.
|
||||||
|
|
||||||
use crate::durations::format_millis_text;
|
use crate::client::durations::format_millis_text;
|
||||||
use crate::highlight::Language;
|
use crate::client::highlight::Language;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
/// A tool call's input, split into the parts a card draws separately.
|
/// 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 event_model::SeqEvent;
|
||||||
|
|
||||||
use crate::api::{ApiClient, ApiError, Transport};
|
use crate::client::api::{ApiClient, ApiError, Transport};
|
||||||
use crate::event_stream::{self, StreamItem};
|
use crate::client::event_stream::{self, StreamItem};
|
||||||
use crate::transcript_cache::SessionCache;
|
use crate::client::transcript_cache::SessionCache;
|
||||||
|
|
||||||
/// How many events a session screen opens with, cached or fetched.
|
/// How many events a session screen opens with, cached or fetched.
|
||||||
///
|
///
|
||||||
@@ -284,7 +284,7 @@ impl<T: Transport> TranscriptSource<T> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::{Body, RawResponse};
|
use crate::client::api::{Body, RawResponse};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@@ -352,7 +352,7 @@ mod tests {
|
|||||||
cache_root: &std::path::Path,
|
cache_root: &std::path::Path,
|
||||||
) -> TranscriptSource<ScriptedTransport> {
|
) -> TranscriptSource<ScriptedTransport> {
|
||||||
let api = ApiClient::new(transport);
|
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)
|
TranscriptSource::new(api, "s1", cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +385,7 @@ mod tests {
|
|||||||
|
|
||||||
let transport2 = ScriptedTransport::default();
|
let transport2 = ScriptedTransport::default();
|
||||||
transport2.respond(200, format!("[{}]", status_line(1)));
|
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);
|
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||||
assert!(source2.probe().unwrap());
|
assert!(source2.probe().unwrap());
|
||||||
assert!(source2.cache.tail().is_some());
|
assert!(source2.cache.tail().is_some());
|
||||||
@@ -404,7 +404,7 @@ mod tests {
|
|||||||
let transport2 = ScriptedTransport::default();
|
let transport2 = ScriptedTransport::default();
|
||||||
let different = r#"{"seq":1,"ts":1.0,"type":"status","state":"running"}"#.to_string();
|
let different = r#"{"seq":1,"ts":1.0,"type":"status","state":"running"}"#.to_string();
|
||||||
transport2.respond(200, format!("[{different}]"));
|
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);
|
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||||
assert!(!source2.probe().unwrap());
|
assert!(!source2.probe().unwrap());
|
||||||
assert!(source2.cache.tail().is_none());
|
assert!(source2.cache.tail().is_none());
|
||||||
@@ -420,7 +420,7 @@ mod tests {
|
|||||||
|
|
||||||
let transport2 = ScriptedTransport::default();
|
let transport2 = ScriptedTransport::default();
|
||||||
transport2.respond(500, "server on fire");
|
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);
|
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||||
assert!(source2.probe().is_err());
|
assert!(source2.probe().is_err());
|
||||||
assert!(
|
assert!(
|
||||||
@@ -475,7 +475,7 @@ mod tests {
|
|||||||
|
|
||||||
let transport2 = ScriptedTransport::default();
|
let transport2 = ScriptedTransport::default();
|
||||||
transport2.respond(200, format!("[{}]", status_line(3)));
|
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);
|
let source2 = TranscriptSource::new(ApiClient::new(transport2), "s1", cache);
|
||||||
source2.page(5, 10, true).unwrap();
|
source2.page(5, 10, true).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -491,7 +491,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_server_page_is_floored_at_the_end_of_the_cached_run() {
|
fn a_server_page_is_floored_at_the_end_of_the_cached_run() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
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
|
// 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
|
// 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.
|
// cache reads the directory as damaged and discards it.
|
||||||
@@ -529,7 +529,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn an_unreadable_cached_page_is_a_parse_error_rather_than_an_empty_one() {
|
fn an_unreadable_cached_page_is_a_parse_error_rather_than_an_empty_one() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
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(
|
cache.store_page(
|
||||||
&[r#"{"seq":3,"but":"not an event"}"#.to_string()],
|
&[r#"{"seq":3,"but":"not an event"}"#.to_string()],
|
||||||
3,
|
3,
|
||||||
@@ -551,7 +551,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_bad_cached_opening_line_purges_rather_than_panicking() {
|
fn a_bad_cached_opening_line_purges_rather_than_panicking() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
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.append("not json at all", 1);
|
||||||
cache.flush();
|
cache.flush();
|
||||||
let transport = ScriptedTransport::default();
|
let transport = ScriptedTransport::default();
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```text
|
||||||
//! +-----------+--------------------------------------+
|
//! +-----------+--------------------------------------+
|
||||||
//! | session | transcript_ui::TranscriptScreen |
|
//! | session | crate::ui::TranscriptScreen |
|
||||||
//! | list | (List of folded rows + composer) |
|
//! | list | (List of folded rows + composer) |
|
||||||
//! | (WidgetPtr| |
|
//! | (WidgetPtr| |
|
||||||
//! | swapped | (WidgetPtr swapped whole on session |
|
//! | swapped | (WidgetPtr swapped whole on session |
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! **Incoming SSE events go through `TranscriptScreen::apply`**, not a
|
//! **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
|
//! item list as before, then `apply` updates only the row(s) that actually
|
||||||
//! changed (almost always the one still-open assistant message a delta
|
//! changed (almost always the one still-open assistant message a delta
|
||||||
//! landed in) instead of rebuilding the whole right-hand widget tree from
|
//! 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
|
//! would otherwise lose data on -- `apply`'s own path never touches the
|
||||||
//! composer at all, so this only matters on the fallback.
|
//! 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
|
//! blocking by design -- see `client-core`'s `Cargo.toml`) runs on plain
|
||||||
//! `std::thread`s that report back through `winit`'s `EventLoopProxy`
|
//! `std::thread`s that report back through `winit`'s `EventLoopProxy`
|
||||||
//! (`Proxy<AppEvent>`), rather than through iris's own `Tasks`/`task_on`:
|
//! (`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
|
//! `Proxy::send_event` wakes the window's event loop immediately, once per
|
||||||
//! event, which is what a stream wants.
|
//! event, which is what a stream wants.
|
||||||
|
|
||||||
use client_core::api::{ApiClient, SessionSummary, UreqTransport};
|
use crate::client::api::{ApiClient, SessionSummary, UreqTransport};
|
||||||
use client_core::event_stream::{StreamItem, follow_session_events};
|
use crate::client::event_stream::{StreamItem, follow_session_events};
|
||||||
use client_core::transcript_fold::{
|
use crate::client::transcript_fold::{
|
||||||
TranscriptItem, fold_event, fold_page, group_tool_runs, raw_seq,
|
TranscriptItem, fold_event, fold_page, group_tool_runs, raw_seq,
|
||||||
};
|
};
|
||||||
use event_model::SeqEvent;
|
use event_model::SeqEvent;
|
||||||
@@ -103,7 +103,7 @@ struct Client {
|
|||||||
items: Vec<TranscriptItem>,
|
items: Vec<TranscriptItem>,
|
||||||
list_ptr: WeakWidget<WidgetPtr>,
|
list_ptr: WeakWidget<WidgetPtr>,
|
||||||
transcript_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
|
/// Bumped every time the selected session changes; see `AppEvent`'s
|
||||||
/// doc for what it guards against.
|
/// doc for what it guards against.
|
||||||
generation: Arc<AtomicU64>,
|
generation: Arc<AtomicU64>,
|
||||||
@@ -123,7 +123,7 @@ impl DefaultAppState for Client {
|
|||||||
// already called this once to fail fast before a window opens;
|
// already called this once to fail fast before a window opens;
|
||||||
// this call only fails if the filesystem changed underneath the
|
// this call only fails if the filesystem changed underneath the
|
||||||
// process in between, which is not a case worth a nicer message.
|
// 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}");
|
eprintln!("desktop-app: {e}");
|
||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
});
|
});
|
||||||
@@ -364,7 +364,7 @@ impl Client {
|
|||||||
.filter(|t| !t.is_empty());
|
.filter(|t| !t.is_empty());
|
||||||
|
|
||||||
let rows = group_tool_runs(&self.items);
|
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 {
|
if let Some(text) = in_progress {
|
||||||
screen.composer.field.edit(rsc).set(&text);
|
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`.
|
//! [`EnrollmentStore`] pointed at `$XDG_CONFIG_HOME/ai-app-desktop`.
|
||||||
//!
|
//!
|
||||||
//! Only the directory is this app's -- the file's name, its JSON, and its
|
//! 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
|
//! are the store's, shared with the Android client so the two cannot come
|
||||||
//! to disagree about them.
|
//! to disagree about them.
|
||||||
|
|
||||||
use client_core::config::EnrollmentStore;
|
use crate::client::config::EnrollmentStore;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// `$XDG_CONFIG_HOME/ai-app-desktop`, falling back to `~/.config` the way
|
/// `$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
|
//! The desktop binary's command line and the enrolment it resolves --
|
||||||
//! winit window on the desktop, with a session list beside it, talking to
|
//! `--link`/`--ca`, parsed once at startup and again from `app.rs`'s
|
||||||
//! a real `ai-server` over `client-core`'s REST + SSE clients. See
|
//! `Client::new`. Here rather than in `src/bin_desktop.rs` because both
|
||||||
//! `app.rs`'s module doc for the widget tree and the event flow.
|
//! callers are in the library; the binary is only `fn main`.
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
mod app;
|
use crate::client::config::EnrolledServer;
|
||||||
mod config;
|
|
||||||
|
|
||||||
use client_core::config::EnrolledServer;
|
use super::config;
|
||||||
|
|
||||||
struct Args {
|
struct Args {
|
||||||
ca_path: Option<std::path::PathBuf>,
|
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` --
|
/// 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
|
/// see that call site's comment for why it is not threaded through some
|
||||||
/// other way (`DefaultApp::run()` takes no payload).
|
/// 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 args = parse_args()?;
|
||||||
let store = config::store();
|
let store = config::store();
|
||||||
let server = match args.link {
|
let server = match args.link {
|
||||||
@@ -92,15 +74,3 @@ fn load_startup_config() -> Result<(EnrolledServer, Vec<u8>), String> {
|
|||||||
};
|
};
|
||||||
Ok((server, ca_pem))
|
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::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use client_core::api::UreqTransport;
|
use crate::client::api::UreqTransport;
|
||||||
use client_core::notifications::{SessionNotification, follow_notifications};
|
use crate::client::notifications::{SessionNotification, follow_notifications};
|
||||||
use jni::Env;
|
use jni::Env;
|
||||||
use jni::errors::Result;
|
use jni::errors::Result;
|
||||||
use jni::objects::{JObject, JValue};
|
use jni::objects::{JObject, JValue};
|
||||||
use jni::sys::{JNI_TRUE, jint};
|
use jni::sys::{JNI_TRUE, jint};
|
||||||
|
|
||||||
use crate::settings::{self, ServerSettings};
|
use crate::shell::settings::{self, ServerSettings};
|
||||||
|
|
||||||
const ALERT_CHANNEL: &str = "sessions";
|
const ALERT_CHANNEL: &str = "sessions";
|
||||||
const ONGOING_CHANNEL: &str = "connection";
|
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`
|
/// 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
|
/// 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
|
/// 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
|
/// which is a decision affecting every caller of that trait, not just this
|
||||||
/// one -- left for whoever next depends on prompt shutdown.
|
/// one -- left for whoever next depends on prompt shutdown.
|
||||||
static STOPPING: AtomicBool = AtomicBool::new(false);
|
static STOPPING: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
fn static_int(env: &mut Env, class: &str, field: &str) -> Result<i32> {
|
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>> {
|
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,
|
env,
|
||||||
"androidx/core/app/NotificationManagerCompat",
|
"androidx/core/app/NotificationManagerCompat",
|
||||||
"from",
|
"from",
|
||||||
@@ -80,22 +80,22 @@ fn create_channel(
|
|||||||
name: &str,
|
name: &str,
|
||||||
importance: i32,
|
importance: i32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let id_j = crate::jcall::jstr_obj(env, id)?;
|
let id_j = crate::shell::jcall::jstr_obj(env, id)?;
|
||||||
let builder = crate::jcall::new_object(
|
let builder = crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
"androidx/core/app/NotificationChannelCompat$Builder",
|
"androidx/core/app/NotificationChannelCompat$Builder",
|
||||||
"(Ljava/lang/String;I)V",
|
"(Ljava/lang/String;I)V",
|
||||||
&[JValue::Object(&id_j), JValue::Int(importance)],
|
&[JValue::Object(&id_j), JValue::Int(importance)],
|
||||||
)?;
|
)?;
|
||||||
let name_j = crate::jcall::jstr_obj(env, name)?;
|
let name_j = crate::shell::jcall::jstr_obj(env, name)?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
"setName",
|
"setName",
|
||||||
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationChannelCompat$Builder;",
|
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationChannelCompat$Builder;",
|
||||||
&[JValue::Object(&name_j)],
|
&[JValue::Object(&name_j)],
|
||||||
)?;
|
)?;
|
||||||
let channel = crate::jcall::call_method(
|
let channel = crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
"build",
|
"build",
|
||||||
@@ -103,7 +103,7 @@ fn create_channel(
|
|||||||
&[],
|
&[],
|
||||||
)?
|
)?
|
||||||
.l()?;
|
.l()?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
manager,
|
manager,
|
||||||
"createNotificationChannel",
|
"createNotificationChannel",
|
||||||
@@ -146,8 +146,8 @@ fn new_intent_for<'l>(
|
|||||||
context: &JObject,
|
context: &JObject,
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
) -> Result<JObject<'l>> {
|
) -> Result<JObject<'l>> {
|
||||||
let target_class = crate::jcall::find_class(env, class_name)?;
|
let target_class = crate::shell::jcall::find_class(env, class_name)?;
|
||||||
crate::jcall::new_object(
|
crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
"android/content/Intent",
|
"android/content/Intent",
|
||||||
"(Landroid/content/Context;Ljava/lang/Class;)V",
|
"(Landroid/content/Context;Ljava/lang/Class;)V",
|
||||||
@@ -165,41 +165,42 @@ fn session_intent<'l>(
|
|||||||
session_id: &str,
|
session_id: &str,
|
||||||
) -> Result<JObject<'l>> {
|
) -> Result<JObject<'l>> {
|
||||||
let intent = new_intent_for(env, context, "com/example/aiapp/shell/MainActivity")?;
|
let intent = new_intent_for(env, context, "com/example/aiapp/shell/MainActivity")?;
|
||||||
let action_view = crate::jcall::jstr_obj(env, "android.intent.action.VIEW")?;
|
let action_view = crate::shell::jcall::jstr_obj(env, "android.intent.action.VIEW")?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&intent,
|
&intent,
|
||||||
"setAction",
|
"setAction",
|
||||||
"(Ljava/lang/String;)Landroid/content/Intent;",
|
"(Ljava/lang/String;)Landroid/content/Intent;",
|
||||||
&[JValue::Object(&action_view)],
|
&[JValue::Object(&action_view)],
|
||||||
)?;
|
)?;
|
||||||
let builder = crate::jcall::new_object(env, "android/net/Uri$Builder", "()V", &[])?;
|
let builder = crate::shell::jcall::new_object(env, "android/net/Uri$Builder", "()V", &[])?;
|
||||||
let scheme = crate::jcall::jstr_obj(env, settings::SCHEME)?;
|
let scheme = crate::shell::jcall::jstr_obj(env, settings::SCHEME)?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
"scheme",
|
"scheme",
|
||||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||||
&[JValue::Object(&scheme)],
|
&[JValue::Object(&scheme)],
|
||||||
)?;
|
)?;
|
||||||
let authority = crate::jcall::jstr_obj(env, "session")?;
|
let authority = crate::shell::jcall::jstr_obj(env, "session")?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
"authority",
|
"authority",
|
||||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||||
&[JValue::Object(&authority)],
|
&[JValue::Object(&authority)],
|
||||||
)?;
|
)?;
|
||||||
let path = crate::jcall::jstr_obj(env, session_id)?;
|
let path = crate::shell::jcall::jstr_obj(env, session_id)?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
"appendPath",
|
"appendPath",
|
||||||
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
"(Ljava/lang/String;)Landroid/net/Uri$Builder;",
|
||||||
&[JValue::Object(&path)],
|
&[JValue::Object(&path)],
|
||||||
)?;
|
)?;
|
||||||
let uri = crate::jcall::call_method(env, &builder, "build", "()Landroid/net/Uri;", &[])?.l()?;
|
let uri = crate::shell::jcall::call_method(env, &builder, "build", "()Landroid/net/Uri;", &[])?
|
||||||
crate::jcall::call_method(
|
.l()?;
|
||||||
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&intent,
|
&intent,
|
||||||
"setData",
|
"setData",
|
||||||
@@ -216,7 +217,7 @@ fn pending_activity<'l>(
|
|||||||
) -> Result<JObject<'l>> {
|
) -> Result<JObject<'l>> {
|
||||||
let update_current = static_int(env, "android/app/PendingIntent", "FLAG_UPDATE_CURRENT")?;
|
let update_current = static_int(env, "android/app/PendingIntent", "FLAG_UPDATE_CURRENT")?;
|
||||||
let immutable = static_int(env, "android/app/PendingIntent", "FLAG_IMMUTABLE")?;
|
let immutable = static_int(env, "android/app/PendingIntent", "FLAG_IMMUTABLE")?;
|
||||||
crate::jcall::call_static_method(
|
crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"android/app/PendingIntent",
|
"android/app/PendingIntent",
|
||||||
"getActivity",
|
"getActivity",
|
||||||
@@ -238,7 +239,7 @@ fn builder_call<'l>(
|
|||||||
sig: &str,
|
sig: &str,
|
||||||
args: &[JValue],
|
args: &[JValue],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
crate::jcall::call_method(env, builder, method, sig, args)?;
|
crate::shell::jcall::call_method(env, builder, method, sig, args)?;
|
||||||
Ok(())
|
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>> {
|
fn ongoing_notification<'l>(env: &mut Env<'l>, context: &JObject) -> Result<JObject<'l>> {
|
||||||
let channel = crate::jcall::jstr_obj(env, ONGOING_CHANNEL)?;
|
let channel = crate::shell::jcall::jstr_obj(env, ONGOING_CHANNEL)?;
|
||||||
let builder = crate::jcall::new_object(
|
let builder = crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
"androidx/core/app/NotificationCompat$Builder",
|
"androidx/core/app/NotificationCompat$Builder",
|
||||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||||
&[JValue::Object(context), JValue::Object(&channel)],
|
&[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(
|
builder_call(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
@@ -297,7 +298,8 @@ fn ongoing_notification<'l>(env: &mut Env<'l>, context: &JObject) -> Result<JObj
|
|||||||
"(I)Landroidx/core/app/NotificationCompat$Builder;",
|
"(I)Landroidx/core/app/NotificationCompat$Builder;",
|
||||||
&[JValue::Int(priority_min)],
|
&[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
|
/// 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 =
|
let service_intent =
|
||||||
new_intent_for(env, context, "com/example/aiapp/shell/NotificationService")?;
|
new_intent_for(env, context, "com/example/aiapp/shell/NotificationService")?;
|
||||||
if settings::load(env, context)?.is_none() {
|
if settings::load(env, context)?.is_none() {
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
context,
|
context,
|
||||||
"stopService",
|
"stopService",
|
||||||
@@ -316,7 +318,7 @@ pub fn sync(env: &mut Env, context: &JObject) -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
create_channels(env, context)?;
|
create_channels(env, context)?;
|
||||||
crate::jcall::call_static_method(
|
crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"androidx/core/content/ContextCompat",
|
"androidx/core/content/ContextCompat",
|
||||||
"startForegroundService",
|
"startForegroundService",
|
||||||
@@ -335,7 +337,7 @@ pub fn on_start_command(env: &mut Env, service: JObject) -> jint {
|
|||||||
match try_start(env, &service) {
|
match try_start(env, &service) {
|
||||||
Ok(true) => static_int(env, "android/app/Service", "START_STICKY").unwrap_or(1),
|
Ok(true) => static_int(env, "android/app/Service", "START_STICKY").unwrap_or(1),
|
||||||
Ok(false) => {
|
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)
|
static_int(env, "android/app/Service", "START_NOT_STICKY").unwrap_or(2)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -352,7 +354,7 @@ fn try_start(env: &mut Env, service: &JObject) -> Result<bool> {
|
|||||||
let ca = settings::load_pinned_ca(env)?;
|
let ca = settings::load_pinned_ca(env)?;
|
||||||
let notification = ongoing_notification(env, service)?;
|
let notification = ongoing_notification(env, service)?;
|
||||||
let fg_type = foreground_type(env)?;
|
let fg_type = foreground_type(env)?;
|
||||||
crate::jcall::call_static_method(
|
crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"androidx/core/app/ServiceCompat",
|
"androidx/core/app/ServiceCompat",
|
||||||
"startForeground",
|
"startForeground",
|
||||||
@@ -424,13 +426,14 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
|||||||
let allowed = if sdk < tiramisu {
|
let allowed = if sdk < tiramisu {
|
||||||
true
|
true
|
||||||
} else {
|
} 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(
|
let granted = static_int(
|
||||||
env,
|
env,
|
||||||
"android/content/pm/PackageManager",
|
"android/content/pm/PackageManager",
|
||||||
"PERMISSION_GRANTED",
|
"PERMISSION_GRANTED",
|
||||||
)?;
|
)?;
|
||||||
let result = crate::jcall::call_static_method(
|
let result = crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"androidx/core/content/ContextCompat",
|
"androidx/core/content/ContextCompat",
|
||||||
"checkSelfPermission",
|
"checkSelfPermission",
|
||||||
@@ -441,20 +444,21 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
|||||||
result == granted
|
result == granted
|
||||||
};
|
};
|
||||||
let enabled =
|
let enabled =
|
||||||
crate::jcall::call_method(env, &manager, "areNotificationsEnabled", "()Z", &[])?.z()?;
|
crate::shell::jcall::call_method(env, &manager, "areNotificationsEnabled", "()Z", &[])?
|
||||||
|
.z()?;
|
||||||
if !allowed || !enabled {
|
if !allowed || !enabled {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let intent = session_intent(env, context, ¬ification.session_id)?;
|
let intent = session_intent(env, context, ¬ification.session_id)?;
|
||||||
let pending = pending_activity(env, context, &intent)?;
|
let pending = pending_activity(env, context, &intent)?;
|
||||||
let channel = crate::jcall::jstr_obj(env, ALERT_CHANNEL)?;
|
let channel = crate::shell::jcall::jstr_obj(env, ALERT_CHANNEL)?;
|
||||||
let builder = crate::jcall::new_object(
|
let builder = crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
"androidx/core/app/NotificationCompat$Builder",
|
"androidx/core/app/NotificationCompat$Builder",
|
||||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||||
&[JValue::Object(context), JValue::Object(&channel)],
|
&[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(
|
builder_call(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
@@ -462,7 +466,7 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
|||||||
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationCompat$Builder;",
|
"(Ljava/lang/CharSequence;)Landroidx/core/app/NotificationCompat$Builder;",
|
||||||
&[JValue::Object(&title)],
|
&[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(
|
builder_call(
|
||||||
env,
|
env,
|
||||||
&builder,
|
&builder,
|
||||||
@@ -507,11 +511,16 @@ fn show(env: &mut Env, context: &JObject, notification: &SessionNotification) ->
|
|||||||
"(Z)Landroidx/core/app/NotificationCompat$Builder;",
|
"(Z)Landroidx/core/app/NotificationCompat$Builder;",
|
||||||
&[JValue::Bool(JNI_TRUE)],
|
&[JValue::Bool(JNI_TRUE)],
|
||||||
)?;
|
)?;
|
||||||
let built =
|
let built = crate::shell::jcall::call_method(
|
||||||
crate::jcall::call_method(env, &builder, "build", "()Landroid/app/Notification;", &[])?
|
env,
|
||||||
|
&builder,
|
||||||
|
"build",
|
||||||
|
"()Landroid/app/Notification;",
|
||||||
|
&[],
|
||||||
|
)?
|
||||||
.l()?;
|
.l()?;
|
||||||
let tag = crate::jcall::jstr_obj(env, ¬ification.session_id)?;
|
let tag = crate::shell::jcall::jstr_obj(env, ¬ification.session_id)?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&manager,
|
&manager,
|
||||||
"notify",
|
"notify",
|
||||||
@@ -542,9 +551,9 @@ pub fn on_destroy() {
|
|||||||
pub fn log_error(env: &mut Env, where_: &str, error: &jni::errors::Error) {
|
pub fn log_error(env: &mut Env, where_: &str, error: &jni::errors::Error) {
|
||||||
let message = format!("android-shell: {where_}: {error}");
|
let message = format!("android-shell: {where_}: {error}");
|
||||||
let _ = (|| -> Result<()> {
|
let _ = (|| -> Result<()> {
|
||||||
let tag = crate::jcall::jstr_obj(env, "android-shell")?;
|
let tag = crate::shell::jcall::jstr_obj(env, "android-shell")?;
|
||||||
let msg = crate::jcall::jstr_obj(env, &message)?;
|
let msg = crate::shell::jcall::jstr_obj(env, &message)?;
|
||||||
crate::jcall::call_static_method(
|
crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"android/util/Log",
|
"android/util/Log",
|
||||||
"e",
|
"e",
|
||||||
@@ -39,9 +39,9 @@ const STORE_CLASS: &str = "com/example/wgapplink/ServerStore";
|
|||||||
const SETTINGS_CLASS: &str = "com/example/wgapplink/ServerSettings";
|
const SETTINGS_CLASS: &str = "com/example/wgapplink/ServerSettings";
|
||||||
|
|
||||||
fn new_store<'l>(env: &mut Env<'l>) -> Result<JObject<'l>> {
|
fn new_store<'l>(env: &mut Env<'l>) -> Result<JObject<'l>> {
|
||||||
let scheme = crate::jcall::jstr_obj(env, SCHEME)?;
|
let scheme = crate::shell::jcall::jstr_obj(env, SCHEME)?;
|
||||||
let alias = crate::jcall::jstr_obj(env, KEY_ALIAS)?;
|
let alias = crate::shell::jcall::jstr_obj(env, KEY_ALIAS)?;
|
||||||
crate::jcall::new_object(
|
crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
STORE_CLASS,
|
STORE_CLASS,
|
||||||
"(Ljava/lang/String;Ljava/lang/String;)V",
|
"(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> {
|
fn read_settings(env: &mut Env, settings_obj: &JObject) -> Result<ServerSettings> {
|
||||||
let host = get_string(env, settings_obj, "getHost")?;
|
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")?;
|
let token = get_string(env, settings_obj, "getToken")?;
|
||||||
Ok(ServerSettings { host, port, token })
|
Ok(ServerSettings { host, port, token })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_string(env: &mut Env, obj: &JObject, getter: &str) -> Result<String> {
|
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)?;
|
let jstr: JString = env.cast_local::<JString>(value)?;
|
||||||
jstr.try_to_string(env)
|
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`.
|
/// `ServerConfig.kt`'s `loadServerSettings`.
|
||||||
pub fn load(env: &mut Env, context: &JObject) -> Result<Option<ServerSettings>> {
|
pub fn load(env: &mut Env, context: &JObject) -> Result<Option<ServerSettings>> {
|
||||||
let store = new_store(env)?;
|
let store = new_store(env)?;
|
||||||
let settings_obj = crate::jcall::call_method(
|
let settings_obj = crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&store,
|
&store,
|
||||||
"load",
|
"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`.
|
/// Seals and stores `settings` -- mirrors `ServerConfig.kt`'s `saveServerSettings`.
|
||||||
pub fn save(env: &mut Env, context: &JObject, settings: &ServerSettings) -> Result<()> {
|
pub fn save(env: &mut Env, context: &JObject, settings: &ServerSettings) -> Result<()> {
|
||||||
let store = new_store(env)?;
|
let store = new_store(env)?;
|
||||||
let host = crate::jcall::jstr_obj(env, &settings.host)?;
|
let host = crate::shell::jcall::jstr_obj(env, &settings.host)?;
|
||||||
let token = crate::jcall::jstr_obj(env, &settings.token)?;
|
let token = crate::shell::jcall::jstr_obj(env, &settings.token)?;
|
||||||
let settings_obj = crate::jcall::new_object(
|
let settings_obj = crate::shell::jcall::new_object(
|
||||||
env,
|
env,
|
||||||
SETTINGS_CLASS,
|
SETTINGS_CLASS,
|
||||||
"(Ljava/lang/String;ILjava/lang/String;)V",
|
"(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),
|
JValue::Object(&token),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
crate::jcall::call_method(
|
crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&store,
|
&store,
|
||||||
"save",
|
"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.
|
/// query-parameter rules rather than re-deriving them here.
|
||||||
pub fn parse_enrollment_uri(env: &mut Env, uri: &JObject) -> Result<Option<ServerSettings>> {
|
pub fn parse_enrollment_uri(env: &mut Env, uri: &JObject) -> Result<Option<ServerSettings>> {
|
||||||
let store = new_store(env)?;
|
let store = new_store(env)?;
|
||||||
let settings_obj = crate::jcall::call_method(
|
let settings_obj = crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
&store,
|
&store,
|
||||||
"parseEnrollmentUri",
|
"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
|
/// but into a plain Java constant, since this module has no Kotlin of its
|
||||||
/// own to generate into.
|
/// own to generate into.
|
||||||
pub fn load_pinned_ca(env: &mut Env) -> Result<Vec<u8>> {
|
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,
|
env,
|
||||||
"com/example/aiapp/shell/PinnedCa",
|
"com/example/aiapp/shell/PinnedCa",
|
||||||
"PINNED_CA_PEM",
|
"PINNED_CA_PEM",
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
//! likely to be what somebody meant. Worth revisiting once a real screen
|
//! likely to be what somebody meant. Worth revisiting once a real screen
|
||||||
//! exists to ask instead of guessing.
|
//! exists to ask instead of guessing.
|
||||||
|
|
||||||
use client_core::api::{ApiClient, UreqTransport};
|
use crate::client::api::{ApiClient, UreqTransport};
|
||||||
use jni::Env;
|
use jni::Env;
|
||||||
use jni::errors::Result;
|
use jni::errors::Result;
|
||||||
use jni::objects::{JObject, JString, JValue};
|
use jni::objects::{JObject, JString, JValue};
|
||||||
|
|
||||||
use crate::notify;
|
use crate::shell::notify;
|
||||||
use crate::settings;
|
use crate::shell::settings;
|
||||||
|
|
||||||
const ACTION_SEND: &str = "android.intent.action.SEND";
|
const ACTION_SEND: &str = "android.intent.action.SEND";
|
||||||
const ACTION_SEND_MULTIPLE: &str = "android.intent.action.SEND_MULTIPLE";
|
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";
|
const EXTRA_TEXT: &str = "android.intent.extra.TEXT";
|
||||||
|
|
||||||
fn get_string_method(env: &mut Env, obj: &JObject, method: &str) -> Result<Option<String>> {
|
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() {
|
if value.is_null() {
|
||||||
return Ok(None);
|
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<()> {
|
fn toast(env: &mut Env, context: &JObject, message: &str) -> Result<()> {
|
||||||
let message = crate::jcall::jstr_obj(env, message)?;
|
let message = crate::shell::jcall::jstr_obj(env, message)?;
|
||||||
crate::jcall::call_static_method(
|
crate::shell::jcall::call_static_method(
|
||||||
env,
|
env,
|
||||||
"com/example/aiapp/shell/MainActivity",
|
"com/example/aiapp/shell/MainActivity",
|
||||||
"toast",
|
"toast",
|
||||||
@@ -65,7 +66,8 @@ pub fn handle_intent(env: &mut Env, activity: &JObject, intent: &JObject) -> Res
|
|||||||
if action.as_deref() != Some(ACTION_VIEW) {
|
if action.as_deref() != Some(ACTION_VIEW) {
|
||||||
return Ok(());
|
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() {
|
if uri.is_null() {
|
||||||
return Ok(());
|
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
|
/// as a share, and `AttachmentButton`'s upload-then-message pattern for
|
||||||
/// what happens to it, minus attachments per this module's doc comment.
|
/// what happens to it, minus attachments per this module's doc comment.
|
||||||
fn handle_share(env: &mut Env, activity: &JObject, intent: &JObject) -> Result<()> {
|
fn handle_share(env: &mut Env, activity: &JObject, intent: &JObject) -> Result<()> {
|
||||||
let extra_text = crate::jcall::jstr_obj(env, EXTRA_TEXT)?;
|
let extra_text = crate::shell::jcall::jstr_obj(env, EXTRA_TEXT)?;
|
||||||
let text = crate::jcall::call_method(
|
let text = crate::shell::jcall::call_method(
|
||||||
env,
|
env,
|
||||||
intent,
|
intent,
|
||||||
"getStringExtra",
|
"getStringExtra",
|
||||||
File renamed without changes.
@@ -5,7 +5,7 @@
|
|||||||
//! bytes, generated by `app/bench-fixture/generate.py`, never a real
|
//! bytes, generated by `app/bench-fixture/generate.py`, never a real
|
||||||
//! transcript -- that file's own README), embedded with `include_str!`.
|
//! transcript -- that file's own README), embedded with `include_str!`.
|
||||||
//! The first [`BACKLOG_COUNT`] non-blank lines are the opening window,
|
//! 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
|
//! 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
|
//! tail, replayed one at a time through `fold_event` the way a live SSE
|
||||||
//! frame arrives.
|
//! frame arrives.
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
//! UI-shaped in a platform crate). What stayed there is the JNI half --
|
//! UI-shaped in a platform crate). What stayed there is the JNI half --
|
||||||
//! the clipboard, the battery sampler, the IME calls and the report.
|
//! 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 event_model::SeqEvent;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ pub fn rows(items: &[TranscriptItem]) -> Vec<TranscriptRow> {
|
|||||||
/// streamed. The tree itself comes back separately from
|
/// streamed. The tree itself comes back separately from
|
||||||
/// [`build_screen`], since whoever takes it owns it.
|
/// [`build_screen`], since whoever takes it owns it.
|
||||||
pub struct Opened {
|
pub struct Opened {
|
||||||
pub screen: transcript_ui::TranscriptScreen,
|
pub screen: crate::ui::TranscriptScreen,
|
||||||
pub items: Vec<TranscriptItem>,
|
pub items: Vec<TranscriptItem>,
|
||||||
/// The tail, for a caller that goes on replaying it one event at a
|
/// The tail, for a caller that goes on replaying it one event at a
|
||||||
/// time through `fold_event`/`TranscriptScreen::apply` -- the
|
/// 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
|
/// 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
|
/// for a caller (the Android bench) that puts the screen inside a shell
|
||||||
/// of its own.
|
/// of its own.
|
||||||
pub fn build_screen<Rsc: HasEvents>(rsc: &mut Rsc) -> Result<(Opened, StrongWidget), String>
|
pub fn build_screen<Rsc: HasEvents>(rsc: &mut Rsc) -> Result<(Opened, StrongWidget), String>
|
||||||
@@ -123,7 +123,7 @@ where
|
|||||||
{
|
{
|
||||||
let fixture = Fixture::parse();
|
let fixture = Fixture::parse();
|
||||||
let items = fixture.backlog_items()?;
|
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((
|
Ok((
|
||||||
Opened {
|
Opened {
|
||||||
screen,
|
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,
|
//! 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
|
//! the links inside it, and the [`BlockFrame`] the row builder puts around
|
||||||
//! it.
|
//! it.
|
||||||
@@ -44,8 +44,8 @@
|
|||||||
//! paragraph's -- visible, not incorrect, and not fixed here since it
|
//! paragraph's -- visible, not incorrect, and not fixed here since it
|
||||||
//! needs `SpanStyle` to carry line-height too.
|
//! needs `SpanStyle` to carry line-height too.
|
||||||
|
|
||||||
use client_core::highlight::{self, Kind, Language};
|
use crate::client::highlight::{self, Kind, Language};
|
||||||
use client_core::markdown_blocks::{Block, BlockKind};
|
use crate::client::markdown_blocks::{Block, BlockKind};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd};
|
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd};
|
||||||
use std::ops::Range;
|
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
|
/// block boundary there and the styling here cannot disagree about what
|
||||||
/// the source means.
|
/// the source means.
|
||||||
fn options() -> Options {
|
fn options() -> Options {
|
||||||
Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES | Options::ENABLE_TASKLISTS
|
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`.
|
/// appended to `spans`.
|
||||||
///
|
///
|
||||||
/// The highlighter indexes **chars** and `SpanStyle` indexes **bytes**
|
/// The highlighter indexes **chars** and `SpanStyle` indexes **bytes**
|
||||||
@@ -567,7 +567,7 @@ fn wrap_cell(text: &str, width: usize) -> Vec<String> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client_core::markdown_blocks::split_blocks;
|
use crate::client::markdown_blocks::split_blocks;
|
||||||
|
|
||||||
fn block(src: &str) -> Rendered {
|
fn block(src: &str) -> Rendered {
|
||||||
let blocks = split_blocks(src);
|
let blocks = split_blocks(src);
|
||||||
@@ -7,22 +7,22 @@
|
|||||||
//!
|
//!
|
||||||
//! ```text
|
//! ```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 1: sender label + one TextEdit |
|
||||||
//! | row 2: sender label + one TextEdit |
|
//! | row 2: sender label + one TextEdit |
|
||||||
//! | row 3 (Tools): collapsed/expanded |
|
//! | 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
|
//! **What this crate does not do itself**: fetch anything over the network
|
||||||
//! or read the transcript cache. [`build`] takes an already-folded
|
//! 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
|
//! [`TranscriptScreen::push_row`] takes one more as it arrives -- the
|
||||||
//! caller (an app's own `main`, or a future `iris-android-app`-shaped
|
//! 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
|
//! `event_stream::follow_session_events` and the transcript cache, per the
|
||||||
//! code rules' "ask for the least you need": a widget-tree builder that
|
//! 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
|
//! also knew how to make an HTTPS request would be untestable without a
|
||||||
@@ -44,13 +44,19 @@
|
|||||||
//! row's own text too.
|
//! row's own text too.
|
||||||
|
|
||||||
pub mod composer;
|
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 markdown;
|
||||||
pub mod row;
|
pub mod row;
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
pub(crate) mod tap;
|
pub(crate) mod tap;
|
||||||
pub mod tool;
|
pub mod tool;
|
||||||
|
|
||||||
use client_core::transcript_fold::TranscriptRow as FoldedRow;
|
use crate::client::transcript_fold::TranscriptRow as FoldedRow;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use selection::Selection;
|
use selection::Selection;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
@@ -111,7 +117,7 @@ impl TranscriptScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the session this transcript belongs to is still doing
|
/// 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
|
/// 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
|
/// no result is *running* while the session works and *never came
|
||||||
@@ -189,7 +195,7 @@ impl TranscriptScreen {
|
|||||||
// row -- rebuild it.
|
// row -- rebuild it.
|
||||||
if matches!(
|
if matches!(
|
||||||
item,
|
item,
|
||||||
client_core::transcript_fold::TranscriptItem::ToolRun { .. }
|
crate::client::transcript_fold::TranscriptItem::ToolRun { .. }
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -214,7 +220,7 @@ impl TranscriptScreen {
|
|||||||
|
|
||||||
/// Apply the effect of one more folded event without rebuilding the
|
/// Apply the effect of one more folded event without rebuilding the
|
||||||
/// whole screen -- RUST.md's "streaming still costs a full rebuild"
|
/// 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
|
/// own before/after item lists (never grouped into rows -- that
|
||||||
/// happens here, over both, so the common tail cases can be told
|
/// happens here, over both, so the common tail cases can be told
|
||||||
/// apart; `group_tool_runs` is pure bookkeeping over already-folded
|
/// apart; `group_tool_runs` is pure bookkeeping over already-folded
|
||||||
@@ -245,12 +251,12 @@ impl TranscriptScreen {
|
|||||||
pub fn apply<Rsc: HasEvents>(
|
pub fn apply<Rsc: HasEvents>(
|
||||||
&self,
|
&self,
|
||||||
rsc: &mut Rsc,
|
rsc: &mut Rsc,
|
||||||
old: &[client_core::transcript_fold::TranscriptItem],
|
old: &[crate::client::transcript_fold::TranscriptItem],
|
||||||
new: &[client_core::transcript_fold::TranscriptItem],
|
new: &[crate::client::transcript_fold::TranscriptItem],
|
||||||
) where
|
) where
|
||||||
Rsc::State: FocusHost + OpenUrl,
|
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 old_rows = group_tool_runs(old);
|
||||||
let new_rows = group_tool_runs(new);
|
let new_rows = group_tool_runs(new);
|
||||||
@@ -521,7 +527,7 @@ fn diff_rows(old: &[FoldedRow], new: &[FoldedRow]) -> RowDiff {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod diff_tests {
|
mod diff_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client_core::transcript_fold::TranscriptItem;
|
use crate::client::transcript_fold::TranscriptItem;
|
||||||
|
|
||||||
fn user(seq: u64, text: &str) -> FoldedRow {
|
fn user(seq: u64, text: &str) -> FoldedRow {
|
||||||
FoldedRow::Single(TranscriptItem::UserMsg {
|
FoldedRow::Single(TranscriptItem::UserMsg {
|
||||||
@@ -627,7 +633,7 @@ mod diff_tests {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod apply_tests {
|
mod apply_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client_core::transcript_fold::TranscriptItem;
|
use crate::client::transcript_fold::TranscriptItem;
|
||||||
|
|
||||||
struct TestFocus {
|
struct TestFocus {
|
||||||
focus: Option<WeakWidget<TextEdit>>,
|
focus: Option<WeakWidget<TextEdit>>,
|
||||||
@@ -744,7 +750,7 @@ mod apply_tests {
|
|||||||
)];
|
)];
|
||||||
let (screen, tree) = build_tree(
|
let (screen, tree) = build_tree(
|
||||||
&mut rsc,
|
&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();
|
let mut render = UiRenderState::new();
|
||||||
render.resize((1080.0, 20000.0));
|
render.resize((1080.0, 20000.0));
|
||||||
@@ -799,7 +805,7 @@ mod apply_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_row_dropped_by_a_regroup_does_not_outlive_itself_in_selection() {
|
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 {
|
let mut rsc = TestRsc {
|
||||||
ui: UiData::default(),
|
ui: UiData::default(),
|
||||||
@@ -824,7 +830,7 @@ mod apply_tests {
|
|||||||
// land on; `begin` deselects every *other* registered row first,
|
// land on; `begin` deselects every *other* registered row first,
|
||||||
// which is exactly what used to resolve a stale `WeakWidget` left
|
// which is exactly what used to resolve a stale `WeakWidget` left
|
||||||
// by the regrouped-away rows and panic.
|
// 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(
|
screen.selection.borrow_mut().begin(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
(surviving_key, 0),
|
(surviving_key, 0),
|
||||||
@@ -844,7 +850,7 @@ mod apply_tests {
|
|||||||
/// `if new_key != old_key` guard this replaced could not see it.
|
/// `if new_key != old_key` guard this replaced could not see it.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_tail_rebuilt_with_fewer_blocks_leaves_none_of_them_in_selection() {
|
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 {
|
let mut rsc = TestRsc {
|
||||||
ui: UiData::default(),
|
ui: UiData::default(),
|
||||||
@@ -862,7 +868,7 @@ mod apply_tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let (screen, _tree) = build_tree(&mut rsc, group_tool_runs(&old_items));
|
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!(
|
assert_eq!(
|
||||||
screen
|
screen
|
||||||
.selection
|
.selection
|
||||||
@@ -886,7 +892,7 @@ mod apply_tests {
|
|||||||
|
|
||||||
// What a reader does next: press the row that survived. `begin`
|
// What a reader does next: press the row that survived. `begin`
|
||||||
// resolves every registered handle, so a stale one panics here.
|
// 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(
|
screen.selection.borrow_mut().begin(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
(surviving_key, 0),
|
(surviving_key, 0),
|
||||||
@@ -924,7 +930,8 @@ mod apply_tests {
|
|||||||
rsc: &mut TestRsc,
|
rsc: &mut TestRsc,
|
||||||
items: &[TranscriptItem],
|
items: &[TranscriptItem],
|
||||||
) -> (TranscriptScreen, StrongWidget, UiRenderState) {
|
) -> (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();
|
let mut render = UiRenderState::new();
|
||||||
render.resize((1080.0, 20000.0));
|
render.resize((1080.0, 20000.0));
|
||||||
render.update(&tree, rsc);
|
render.update(&tree, rsc);
|
||||||
@@ -948,7 +955,7 @@ mod apply_tests {
|
|||||||
let items = run_of(3, output, true);
|
let items = run_of(3, output, true);
|
||||||
let (screen, tree) = build_tree(
|
let (screen, tree) = build_tree(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
client_core::transcript_fold::group_tool_runs(&items),
|
crate::client::transcript_fold::group_tool_runs(&items),
|
||||||
);
|
);
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
render.resize((1080.0, 20000.0));
|
render.resize((1080.0, 20000.0));
|
||||||
@@ -1017,7 +1024,7 @@ mod apply_tests {
|
|||||||
];
|
];
|
||||||
let (_screen, tree) = build_tree(
|
let (_screen, tree) = build_tree(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
client_core::transcript_fold::group_tool_runs(&items),
|
crate::client::transcript_fold::group_tool_runs(&items),
|
||||||
);
|
);
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
render.resize((1080.0, 20000.0));
|
render.resize((1080.0, 20000.0));
|
||||||
@@ -1038,8 +1045,8 @@ mod apply_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_long_message_is_drawn_only_as_far_as_the_cap() {
|
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 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 capped = shapes_for_message(¶graphs(crate::client::text_cap::MESSAGE_LINES * 4));
|
||||||
let bigger = shapes_for_message(¶graphs(client_core::text_cap::MESSAGE_LINES * 40));
|
let bigger = shapes_for_message(¶graphs(crate::client::text_cap::MESSAGE_LINES * 40));
|
||||||
assert!(
|
assert!(
|
||||||
capped > 0,
|
capped > 0,
|
||||||
"the screen shaped nothing, so this compares zeroes"
|
"the screen shaped nothing, so this compares zeroes"
|
||||||
@@ -1112,7 +1119,7 @@ mod apply_tests {
|
|||||||
|
|
||||||
let (screen, tree) = build_tree(
|
let (screen, tree) = build_tree(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
client_core::transcript_fold::group_tool_runs(&before),
|
crate::client::transcript_fold::group_tool_runs(&before),
|
||||||
);
|
);
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
render.resize((1080.0, 20000.0));
|
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 after = vec![user(1, "stable"), user(2, "not a tool call at all")];
|
||||||
let (screen, _tree) = build_tree(
|
let (screen, _tree) = build_tree(
|
||||||
&mut rsc,
|
&mut rsc,
|
||||||
client_core::transcript_fold::group_tool_runs(&before),
|
crate::client::transcript_fold::group_tool_runs(&before),
|
||||||
);
|
);
|
||||||
screen.apply(&mut rsc, &before, &after);
|
screen.apply(&mut rsc, &before, &after);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! One `iris::widget::list::LazyItem` per folded transcript row
|
//! 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,
|
//! 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"
|
//! 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 2 (rich inline text) still holds within a block and
|
||||||
//! behaviour 1 (selection) runs across blocks and rows alike through
|
//! behaviour 1 (selection) runs across blocks and rows alike through
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
//! were and why this one.
|
//! were and why this one.
|
||||||
//!
|
//!
|
||||||
//! A `TranscriptRow::Tools` (a run of adjacent tool calls, grouped by
|
//! 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
|
//! 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
|
//! 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
|
//! (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
|
//! collapsed summary and the full detail -- the same two-step contract
|
||||||
//! `lazy_span.rs`'s module doc describes for `AGENTS.md`'s `holdTopEdge`.
|
//! `lazy_span.rs`'s module doc describes for `AGENTS.md`'s `holdTopEdge`.
|
||||||
|
|
||||||
use crate::markdown::{BlockFrame, Link, frame_of, render_block};
|
use crate::client::markdown_blocks::{Block, BlockKind, common_prefix, split_blocks};
|
||||||
use crate::selection::{SelKey, Selection};
|
use crate::client::text_cap::{MESSAGE_BYTES, MESSAGE_LINES, cut, show_all_label};
|
||||||
use crate::tap::{hold_edge, on_tap};
|
use crate::client::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
||||||
use crate::tool::ToolRow;
|
use crate::ui::markdown::{BlockFrame, Link, frame_of, render_block};
|
||||||
use client_core::markdown_blocks::{Block, BlockKind, common_prefix, split_blocks};
|
use crate::ui::selection::{SelKey, Selection};
|
||||||
use client_core::text_cap::{MESSAGE_BYTES, MESSAGE_LINES, cut, show_all_label};
|
use crate::ui::tap::{hold_edge, on_tap};
|
||||||
use client_core::transcript_fold::{QuestionCard, TranscriptItem, TranscriptRow as FoldedRow};
|
use crate::ui::tool::ToolRow;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
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
|
/// 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
|
/// forced on so a hashed key can never collide with a real sequence number
|
||||||
/// (this build never produces 2^63 events).
|
/// (this build never produces 2^63 events).
|
||||||
pub fn row_key(key: &client_core::transcript_fold::ItemKey) -> RowKey {
|
pub fn row_key(key: &crate::client::transcript_fold::ItemKey) -> RowKey {
|
||||||
use client_core::transcript_fold::ItemKey;
|
use crate::client::transcript_fold::ItemKey;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
match key {
|
match key {
|
||||||
ItemKey::Seq(seq) => *seq,
|
ItemKey::Seq(seq) => *seq,
|
||||||
@@ -132,7 +132,7 @@ fn tool_call_markdown(tool: &str, input: &str, output: &str) -> String {
|
|||||||
pub struct RowBlocks {
|
pub struct RowBlocks {
|
||||||
/// What each field was built from, in order -- compared against a
|
/// What each field was built from, in order -- compared against a
|
||||||
/// fresh split to decide what may be kept. See
|
/// 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.
|
/// comparison and not an assumption.
|
||||||
blocks: Vec<Block>,
|
blocks: Vec<Block>,
|
||||||
fields: Vec<WeakWidget<TextEdit>>,
|
fields: Vec<WeakWidget<TextEdit>>,
|
||||||
@@ -291,8 +291,8 @@ where
|
|||||||
})
|
})
|
||||||
.size(BASE_SIZE)
|
.size(BASE_SIZE)
|
||||||
.color(match frame {
|
.color(match frame {
|
||||||
BlockFrame::Quote => crate::markdown::QUOTE_TEXT_COLOR,
|
BlockFrame::Quote => crate::ui::markdown::QUOTE_TEXT_COLOR,
|
||||||
_ => crate::markdown::TEXT_COLOR,
|
_ => crate::ui::markdown::TEXT_COLOR,
|
||||||
})
|
})
|
||||||
.add(rsc);
|
.add(rsc);
|
||||||
selection.borrow_mut().register(key, field);
|
selection.borrow_mut().register(key, field);
|
||||||
@@ -378,7 +378,7 @@ where
|
|||||||
left: dp(QUOTE_BAR_DP + FRAME_PAD_DP),
|
left: dp(QUOTE_BAR_DP + FRAME_PAD_DP),
|
||||||
..Padding::ZERO
|
..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))
|
.width(rest(1))
|
||||||
.add_strong(rsc)
|
.add_strong(rsc)
|
||||||
.any(),
|
.any(),
|
||||||
@@ -392,7 +392,7 @@ where
|
|||||||
/// One widget per block rather than one per message is what makes a
|
/// 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
|
/// streamed delta cost the last block instead of the whole reply -- see
|
||||||
/// [`RowBlocks::apply_delta`] for the other half, and
|
/// [`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
|
/// across the whole transcript; the unit it steps in is a block now rather
|
||||||
/// than a row (`selection::SelKey`).
|
/// than a row (`selection::SelKey`).
|
||||||
///
|
///
|
||||||
@@ -700,7 +700,7 @@ where
|
|||||||
if let Some(calls) = calls {
|
if let Some(calls) = calls {
|
||||||
let key = row_key(&calls[0].key());
|
let key = row_key(&calls[0].key());
|
||||||
let (widget, tools) =
|
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)));
|
return (key, widget, Some(TailRow::Tools(tools)));
|
||||||
}
|
}
|
||||||
let FoldedRow::Single(item) = row else {
|
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
|
/// 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
|
/// 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 --
|
/// docs/DECISIONS.md for why), so the block, not the row, is the unit --
|
||||||
/// `(row, block)` compares lexicographically, which is reading order for
|
/// `(row, block)` compares lexicographically, which is reading order for
|
||||||
/// both levels, so every range query below is unchanged.
|
/// both levels, so every range query below is unchanged.
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
//! while what they tapped changes height.
|
//! while what they tapped changes height.
|
||||||
//!
|
//!
|
||||||
//! Both halves are shared by everything in the transcript that opens: a
|
//! 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"
|
//! tool card and its group ([`crate::ui::tool`]), and a message's "Show all"
|
||||||
//! ([`crate::row`]). They are here rather than in either of those because
|
//! ([`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
|
//! 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
|
//! copies of it would eventually disagree -- which on a scrolling screen
|
||||||
//! means one of them firing at the end of a pan.
|
//! means one of them firing at the end of a pan.
|
||||||
|
|
||||||
use crate::selection::Selection;
|
use crate::ui::selection::Selection;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
//! `ToolRows.kt`/`ToolInput.kt` (RUST.md's P1b).
|
//! `ToolRows.kt`/`ToolInput.kt` (RUST.md's P1b).
|
||||||
//!
|
//!
|
||||||
//! One card per call. Closed, it is a single line: the tool's name and
|
//! 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
|
//! `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
|
//! turns one row into four and a run of them into a wall. Open, it shows
|
||||||
//! the description, the input and the output.
|
//! the description, the input and the output.
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
//! `a_delta_into_a_long_reply_...` counts it for a streamed delta.
|
//! `a_delta_into_a_long_reply_...` counts it for a streamed delta.
|
||||||
//!
|
//!
|
||||||
//! **Two or more adjacent calls are one group** -- decided in
|
//! **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
|
//! 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
|
//! 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,
|
//! 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
|
//! rule is written once, in the gesture machine, and this file only reads
|
||||||
//! its answer.
|
//! its answer.
|
||||||
|
|
||||||
use crate::markdown::{TEXT_COLOR, VERBATIM_BACKGROUND, highlight_into};
|
use crate::client::text_cap::{VERBATIM_BYTES, VERBATIM_LINES, cut, show_all_label};
|
||||||
use crate::selection::Selection;
|
use crate::client::tool_summary::{ToolInput, parse_tool_input};
|
||||||
use crate::tap::{hold_edge, on_tap};
|
use crate::client::transcript_fold::{ToolState, TranscriptItem};
|
||||||
use client_core::text_cap::{VERBATIM_BYTES, VERBATIM_LINES, cut, show_all_label};
|
use crate::ui::markdown::{TEXT_COLOR, VERBATIM_BACKGROUND, highlight_into};
|
||||||
use client_core::tool_summary::{ToolInput, parse_tool_input};
|
use crate::ui::selection::Selection;
|
||||||
use client_core::transcript_fold::{ToolState, TranscriptItem};
|
use crate::ui::tap::{hold_edge, on_tap};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use std::{cell::Cell, cell::RefCell, collections::HashMap, rc::Rc};
|
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
|
/// 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
|
/// 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.
|
/// rather than a row.
|
||||||
pub struct ToolRow {
|
pub struct ToolRow {
|
||||||
shared: Rc<Shared>,
|
shared: Rc<Shared>,
|
||||||
@@ -11,17 +11,17 @@
|
|||||||
//! down. Compose does not do that: a down while `isScrollInProgress`
|
//! down. Compose does not do that: a down while `isScrollInProgress`
|
||||||
//! starts the drag immediately (`scrollable`'s `startDragImmediately`).
|
//! 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::harness::{Harness, TouchAction, TouchScript};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use iris::sense::DRAG_SLOP;
|
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
|
/// 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
|
/// `LazySpan::repair_anchor` to resolve the opening `snap_end` into a real
|
||||||
/// anchor, which is what every assertion about scroll position reads.
|
/// 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 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(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
h.frame(PHONE_FRAME_MS);
|
||||||
(h, opened.screen)
|
(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
|
/// 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
|
/// 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.
|
/// 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 middle = phone_size().y / 2.0;
|
||||||
let list = (screen.list)(&mut h.rsc);
|
let list = (screen.list)(&mut h.rsc);
|
||||||
let key = list.key_at(middle).expect("a row under the viewport");
|
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)
|
(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)
|
(screen.list)(&mut h.rsc)
|
||||||
.extent(key)
|
.extent(key)
|
||||||
.expect("the tracked row is still loaded")
|
.expect("the tracked row is still loaded")
|
||||||
@@ -59,7 +59,7 @@ const CATCH_X: f32 = 540.0;
|
|||||||
/// delta. Returns the release time.
|
/// delta. Returns the release time.
|
||||||
fn drag_from(
|
fn drag_from(
|
||||||
h: &mut Harness,
|
h: &mut Harness,
|
||||||
screen: &transcript_ui::TranscriptScreen,
|
screen: &ai_app::ui::TranscriptScreen,
|
||||||
key: RowKey,
|
key: RowKey,
|
||||||
y0: f32,
|
y0: f32,
|
||||||
t0: u64,
|
t0: u64,
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
//! handle to it from the outside, and a coordinate would only prove that
|
//! handle to it from the outside, and a coordinate would only prove that
|
||||||
//! *something* moved.
|
//! *something* moved.
|
||||||
|
|
||||||
|
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||||
use iris::harness::{Harness, TouchAction};
|
use iris::harness::{Harness, TouchAction};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use transcript_fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
|
||||||
|
|
||||||
/// The horizontal scroll area drawn inside `top..bottom`, with the box
|
/// 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
|
/// 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]
|
#[test]
|
||||||
fn a_flick_across_a_code_fence_keeps_moving_after_the_finger_leaves() {
|
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 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;
|
let screen = opened.screen;
|
||||||
h.frame(0);
|
h.frame(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
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(100);
|
||||||
h.frame(108);
|
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)
|
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||||
.extent(key)
|
.extent(key)
|
||||||
.expect("the fence row is on screen");
|
.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.
|
/// list, while the fence carries on coasting untouched.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_drag_away_from_a_coasting_fence_scrolls_the_list_and_leaves_it_coasting() {
|
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 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;
|
let screen = opened.screen;
|
||||||
h.frame(0);
|
h.frame(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
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(100);
|
||||||
h.frame(108);
|
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)
|
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||||
.extent(key)
|
.extent(key)
|
||||||
.expect("the fence row is on screen");
|
.expect("the fence row is on screen");
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
//! or the next press anywhere on screen is measured from the origin the
|
//! or the next press anywhere on screen is measured from the origin the
|
||||||
//! abandoned one left behind.
|
//! abandoned one left behind.
|
||||||
|
|
||||||
|
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||||
use iris::harness::{Harness, TouchAction, TouchScript};
|
use iris::harness::{Harness, TouchAction, TouchScript};
|
||||||
use iris::prelude::*;
|
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 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(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
h.frame(PHONE_FRAME_MS);
|
||||||
(h, opened.screen)
|
(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
|
/// 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`
|
/// "nothing moved" reads a row's own extent, the way `catch_a_fling.rs`
|
||||||
/// does.
|
/// 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 middle = phone_size().y / 2.0;
|
||||||
let list = (screen.list)(&mut h.rsc);
|
let list = (screen.list)(&mut h.rsc);
|
||||||
let key = list.key_at(middle).expect("a row under the viewport");
|
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)
|
(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)
|
(screen.list)(&mut h.rsc)
|
||||||
.extent(key)
|
.extent(key)
|
||||||
.expect("the tracked row is still loaded")
|
.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
|
/// pointer capture the moment it commits. Everything else that was handed
|
||||||
/// a frame of that press is told so with `CursorSense::Cancel` -- and the
|
/// 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,
|
/// 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
|
/// 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
|
/// stays open with the fence's touch-down as its origin and the next
|
||||||
/// press anywhere is measured from there.
|
/// 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 knows exactly which row it is pressing and where.
|
||||||
#[test]
|
#[test]
|
||||||
fn panning_a_code_fence_then_tapping_elsewhere_moves_nothing() {
|
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 (mut h, screen) = opened();
|
||||||
let fence = TranscriptRow::Single(TranscriptItem::AssistantMsg {
|
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
|
// 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.
|
// 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)
|
let (top, bottom) = (screen.list)(&mut h.rsc)
|
||||||
.extent(key)
|
.extent(key)
|
||||||
.expect("the fence row is on screen");
|
.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
|
// A tap on the paragraph, a long way down the screen from where that
|
||||||
// pan started.
|
// 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)
|
let (ptop, pbottom) = (screen.list)(&mut h.rsc)
|
||||||
.extent(para_key)
|
.extent(para_key)
|
||||||
.expect("the paragraph row is on screen");
|
.expect("the paragraph row is on screen");
|
||||||
+7
-4
@@ -27,8 +27,8 @@
|
|||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
|
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||||
use iris::harness::{Harness, TouchScript};
|
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
|
/// 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
|
/// 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())
|
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 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(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
h.frame(PHONE_FRAME_MS);
|
||||||
(h, opened.screen)
|
(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
|
// 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.
|
// the bare message is the same as handing it a real ring line.
|
||||||
let report = input_lines.join("\n");
|
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")
|
let mut child = Command::new("python3")
|
||||||
.arg(script_path)
|
.arg(script_path)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
@@ -8,16 +8,16 @@
|
|||||||
//! many evenly-spaced events; a finger at 120Hz is five samples in
|
//! many evenly-spaced events; a finger at 120Hz is five samples in
|
||||||
//! 20ms).
|
//! 20ms).
|
||||||
|
|
||||||
|
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||||
use iris::harness::{Harness, TouchScript};
|
use iris::harness::{Harness, TouchScript};
|
||||||
use iris::prelude::*;
|
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
|
/// 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
|
/// `LazySpan::repair_anchor` to resolve the opening `snap_end` into a real
|
||||||
/// anchor, which is what every assertion about scroll position reads.
|
/// 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 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(0);
|
||||||
h.frame(PHONE_FRAME_MS);
|
h.frame(PHONE_FRAME_MS);
|
||||||
(h, opened.screen)
|
(h, opened.screen)
|
||||||
@@ -27,7 +27,7 @@ fn script(name: &str, text: &str) -> TouchScript {
|
|||||||
TouchScript::parse(text).unwrap_or_else(|e| panic!("{name}: {e}"))
|
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()
|
(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
|
//! it is inside the list's own box, and nothing outside that box reaches
|
||||||
//! the screen.
|
//! the screen.
|
||||||
|
|
||||||
|
use ai_app::ui::fixture::{PHONE_FRAME_MS, PHONE_SCALE, phone_size};
|
||||||
use iris::harness::Harness;
|
use iris::harness::Harness;
|
||||||
use iris::prelude::*;
|
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 --
|
/// 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
|
/// 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_H: f32 = 300.0;
|
||||||
const HEADER: UiColor = UiColor::new(28, 28, 34, 255);
|
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 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);
|
let content = WidgetPtr::new().add(&mut h.rsc);
|
||||||
content(&mut h.rsc).set(tree);
|
content(&mut h.rsc).set(tree);
|
||||||
let root = (rect(HEADER).height(abs(HEADER_H)), content.height(rest(1)))
|
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.
|
/// 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
|
h.render
|
||||||
.window_region(&screen.list.id(), &h.rsc)
|
.window_region(&screen.list.id(), &h.rsc)
|
||||||
.expect("the list is on screen")
|
.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
|
/// 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
|
/// `draw_inner`'s old-children diffing means a row it did not place this
|
||||||
/// frame is not among them.
|
/// 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
|
let mut rows: Vec<(f32, f32)> = h
|
||||||
.render
|
.render
|
||||||
.active
|
.active
|
||||||
@@ -69,7 +69,7 @@ fn drawn_rows(h: &Harness, screen: &transcript_ui::TranscriptScreen) -> Vec<(f32
|
|||||||
/// position lives in the `LazySpan`'s own `ScrollController`.
|
/// position lives in the `LazySpan`'s own `ScrollController`.
|
||||||
/// It used to be the opposite here, because a `LazySpan`'s anchor offset
|
/// It used to be the opposite here, because a `LazySpan`'s anchor offset
|
||||||
/// ran the other way.
|
/// 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);
|
(screen.list)(&mut h.rsc).scroll(amount);
|
||||||
h.frame(t);
|
h.frame(t);
|
||||||
t + PHONE_FRAME_MS
|
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.
|
* 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
|
* 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", "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.
|
* 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
|
* 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.
|
* 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)
|
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
|
* 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
|
* 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.
|
* 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
|
* The single mechanism [fetchTranscript], [EventStream], [TranscriptSource] and
|
||||||
* [TranscriptCache.session] all take, rather than each growing its own branch between a session 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.
|
* builds one with the one-argument constructor; a subagent's screen supplies both ids.
|
||||||
*/
|
*/
|
||||||
data class TranscriptAddress(val sessionId: String, val subagentId: String? = null) {
|
data class TranscriptAddress(val sessionId: String, val subagentId: String? = null) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
plugins { alias(libs.plugins.androidApplication) }
|
plugins { alias(libs.plugins.androidApplication) }
|
||||||
|
|
||||||
// E3 (RUST.md): the Kotlin/Java shell being replaced by a thin JNI bridge
|
// 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
|
// 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
|
// of working Compose UI this experiment does not touch, and the two can be
|
||||||
// installed side by side on the same development device (see
|
// 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
|
// and Keystore alias are not the production app's). No Compose plugin, no
|
||||||
// Kotlin source of its own: `MainActivity`/`NotificationService` are plain
|
// Kotlin source of its own: `MainActivity`/`NotificationService` are plain
|
||||||
// Java, and the CA constant below is generated as Java too.
|
// 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 --
|
// 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
|
// see that module's `build.gradle.kts` comment for the reasoning (the
|
||||||
// trust boundary follows the machine that builds, never a pasted copy).
|
// 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`).
|
// `settings::load_pinned_ca` lookup (`com/example/aiapp/shell/PinnedCa`).
|
||||||
val pinnedCaPath: String =
|
val pinnedCaPath: String =
|
||||||
System.getenv("AI_APP_CA")
|
System.getenv("AI_APP_CA")
|
||||||
@@ -150,12 +150,12 @@ androidComponents {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// The Keystore-sealed enrollment (ServerStore/ServerSettings) --
|
// 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
|
// over JNI rather than re-sealing the token in Rust; see that file's
|
||||||
// module doc.
|
// module doc.
|
||||||
implementation(project(":link"))
|
implementation(project(":link"))
|
||||||
// NotificationCompat/NotificationManagerCompat/NotificationChannelCompat/
|
// 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
|
// 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
|
// the library that already has it, rather than being re-derived as a
|
||||||
// set of Build.VERSION.SDK_INT branches in Rust.
|
// set of Build.VERSION.SDK_INT branches in Rust.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import android.widget.Toast;
|
|||||||
*/
|
*/
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("android_shell");
|
System.loadLibrary("ai_app");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import android.os.IBinder;
|
|||||||
*/
|
*/
|
||||||
public class NotificationService extends Service {
|
public class NotificationService extends Service {
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("android_shell");
|
System.loadLibrary("ai_app");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
**Renamed 2026-09-08.** This was the `client-core` crate; it is now
|
||||||
RUST.md's recommendation item 1. It is a plain Rust library crate with no UI
|
`app-rust/src/client/`, a module of the one app crate rather than a crate
|
||||||
framework dependency of any kind, so it can outlive whichever one the app
|
of its own (docs/RUST.md's "One app crate"). Nothing about what it *is*
|
||||||
ends up drawing with (Masonry, iris, or something else -- see RUST.md).
|
changed: it is still the app's pure logic held once instead of twice, per
|
||||||
`event-model/` is its sibling: the wire shape both this crate and `server/`
|
RUST.md's recommendation item 1, and still has **no UI framework
|
||||||
share, extracted from `server/src/session/driver.rs` and
|
dependency of any kind** -- the `iris` dependency sits behind the `screens`
|
||||||
`session/transcript.rs` on 2026-09-04.
|
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`
|
`event-model/` stayed a crate, and is the one split in the port that was
|
||||||
so its own behaviour is unchanged (`./run-tests.sh` covers it); `client-core`
|
never optional: it is the wire shape both this app and `server/` depend on,
|
||||||
has no caller -- it exists for whichever experiment in RUST.md picks it up
|
extracted from `server/src/session/driver.rs` and `session/transcript.rs`
|
||||||
next (a Masonry or iris transcript screen, most likely).
|
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
|
## 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 |
|
| `event-model/src/lib.rs` (shared crate) | `Events.kt` (the enum mirror) | Done |
|
||||||
| `ansi.rs` | `Ansi.kt` | Done, ported test-for-test |
|
| `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
|
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
|
that line rather than degrading to a placeholder row. Closing this means
|
||||||
deciding how `event_model` itself represents "a shape I don't recognise"
|
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.
|
fix, so it is recorded here rather than silently worked around.
|
||||||
|
|
||||||
## `config.rs`: `EnrolledServer`
|
## `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
|
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
|
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
|
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
|
`$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-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
|
caller today is `src/desktop`; the Android entry point is a second one, not
|
||||||
be a second one, not a reason to move the type.
|
a reason to move the type.
|
||||||
|
|
||||||
## What `transcript_source.rs` covers, and what it does not
|
## 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
|
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
|
Kotlin coroutine scope), not to this pure logic -- `follow` is the same
|
||||||
"write to the cache, then hand the frame to the caller" decorator
|
"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
|
already hand-wrote around `event_stream::follow_session_events` before this
|
||||||
existed; the cache write moved into one shared place so a third caller
|
existed; the cache write moved into one shared place so a third caller
|
||||||
does not repeat it again by hand.
|
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
|
block instead of the message (docs/RUST.md's Task B). What it
|
||||||
deliberately does **not** build is the tree below that: nested list
|
deliberately does **not** build is the tree below that: nested list
|
||||||
items, table cells, inline spans. Inline styling is still the renderer's
|
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`
|
has needed the rest yet. `CodeFence.kt`'s use of `org.intellij.markdown`
|
||||||
for a full CommonMark AST is Compose rendering plumbing, not something
|
for a full CommonMark AST is Compose rendering plumbing, not something
|
||||||
to port as-is.
|
to port as-is.
|
||||||
@@ -209,8 +214,8 @@ does not repeat it again by hand.
|
|||||||
|
|
||||||
## Verifying
|
## Verifying
|
||||||
|
|
||||||
`./run-tests.sh` from the repo root now runs `event-model`, `client-core`
|
`./run-tests.sh` from the repo root runs `event-model`, `server` and
|
||||||
and `server` in that order (each `cargo test`, forwarding arguments the
|
`app-rust` in that order (each `cargo test`, forwarding arguments the same
|
||||||
same way it always has). From `client-core/` directly: `cargo test`
|
way it always has). From `app-rust/` directly: `cargo test`, `cargo clippy
|
||||||
(119 tests), `cargo clippy --all-targets`, `cargo fmt` -- all clean as of
|
--all-targets`, `cargo fmt` -- all clean as of 2026-09-08, 229 tests across
|
||||||
this writing (2026-09-06).
|
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
|
for iris API changes); this file is only the summary. Newest first. Items
|
||||||
marked **DEFERRED** are ones the agent chose not to decide alone.
|
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)
|
## 2026-09-08 (last: scrolling moves out of the list)
|
||||||
|
|
||||||
Agreed with Iris in the exchange that followed, so most of this is her
|
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,
|
`Tasks::redraw_handle`'s design had anticipated) -- both in I5's own box,
|
||||||
both in `IRIS.md`.
|
both in `IRIS.md`.
|
||||||
- **Design choices for the two pieces before this are summarised in
|
- **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.
|
choices made without her.
|
||||||
- **E4 done, 2026-09-05.** `iris/desktop-app`: a winit window with a
|
- **E4 done, 2026-09-05.** `iris/desktop-app`: a winit window with a
|
||||||
session list beside `transcript-ui`'s screen (`build_tree`), against 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
|
`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
|
full fling/stream/type/keyboard cycle on the emulator's GLES adapter with
|
||||||
no crash.
|
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
|
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
|
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
|
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
|
## What a subagent is here
|
||||||
|
|
||||||
File renamed without changes.
Generated
-390
@@ -532,12 +532,6 @@ dependencies = [
|
|||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.23.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -717,19 +711,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "client-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"event-model",
|
|
||||||
"log",
|
|
||||||
"pulldown-cmark",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"ureq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
@@ -775,35 +756,6 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -890,25 +842,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
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]]
|
[[package]]
|
||||||
name = "dispatch"
|
name = "dispatch"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1061,14 +994,6 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "event-model"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exr"
|
name = "exr"
|
||||||
version = "1.74.2"
|
version = "1.74.2"
|
||||||
@@ -1200,15 +1125,6 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
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]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.34"
|
version = "0.3.34"
|
||||||
@@ -1283,26 +1199,6 @@ dependencies = [
|
|||||||
"windows-link",
|
"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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -1447,22 +1343,6 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
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]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -1591,27 +1471,6 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad"
|
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]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.10"
|
version = "0.25.10"
|
||||||
@@ -1729,12 +1588,6 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -2143,12 +1996,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-conv"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -2814,12 +2661,6 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "powerfmt"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -2872,25 +2713,6 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"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]]
|
[[package]]
|
||||||
name = "pulp"
|
name = "pulp"
|
||||||
version = "0.22.3"
|
version = "0.22.3"
|
||||||
@@ -3162,20 +2984,6 @@ dependencies = [
|
|||||||
"wayland-protocols-wlr",
|
"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]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -3226,41 +3034,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"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]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
@@ -3343,19 +3116,6 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"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]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "serde_repr"
|
||||||
version = "0.1.21"
|
version = "0.1.21"
|
||||||
@@ -3512,12 +3272,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "subtle"
|
|
||||||
version = "2.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swash"
|
name = "swash"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@@ -3645,36 +3399,6 @@ dependencies = [
|
|||||||
"zune-jpeg",
|
"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]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@@ -3781,31 +3505,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
name = "tree_magic_mini"
|
name = "tree_magic_mini"
|
||||||
version = "3.2.2"
|
version = "3.2.2"
|
||||||
@@ -3844,12 +3543,6 @@ dependencies = [
|
|||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicase"
|
|
||||||
version = "2.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -3868,62 +3561,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
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]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -3968,12 +3605,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.4+wasi-0.2.12"
|
version = "1.0.4+wasi-0.2.12"
|
||||||
@@ -4167,15 +3798,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
@@ -5058,12 +4680,6 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize"
|
|
||||||
version = "1.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -5105,12 +4721,6 @@ version = "0.6.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|||||||
@@ -101,18 +101,8 @@ members = [
|
|||||||
"core",
|
"core",
|
||||||
"macro",
|
"macro",
|
||||||
"tabs-ui",
|
"tabs-ui",
|
||||||
"transcript-ui",
|
|
||||||
"transcript-fixture",
|
|
||||||
"rig-input",
|
"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]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -161,8 +151,3 @@ accesskit = "0.25.0"
|
|||||||
iris-core = { path = "core" }
|
iris-core = { path = "core" }
|
||||||
iris-macro = { path = "macro" }
|
iris-macro = { path = "macro" }
|
||||||
tokio = "1.53.1"
|
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 [-- cargo args]
|
||||||
# ./run-headless.sh tabs --shot /tmp/tabs.png --seconds 4
|
# ./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 --dir ../app-rust --shot /tmp/p.png
|
||||||
# ./run-headless.sh phone --phone --replay transcript-fixture/touch/flick-120hz.touch \
|
# ./run-headless.sh phone --phone --dir ../app-rust \
|
||||||
# --shot /tmp/p.png -- -p transcript-fixture
|
# --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
|
# `--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
|
# 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,
|
# `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
|
# density to iris the way `DisplayMetrics.density` does on Android
|
||||||
# (`iris::default::content_scale`). So a screenshot from here and one
|
# (`iris::default::content_scale`). So a screenshot from here and one
|
||||||
# from the phone are the same layout at the same density, and what
|
# 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.
|
# shaped, which is what every other example wants.
|
||||||
#
|
#
|
||||||
# `--replay FILE` drives one of the `.touch` recordings the headless
|
# `--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
|
# `rig-input`'s `replay-touch` -- one recording, both layers. With
|
||||||
# `--shot` it also writes `<shot>-before.png` from just before the
|
# `--shot` it also writes `<shot>-before.png` from just before the
|
||||||
# gesture, since "the list moved" is a claim about two pictures.
|
# gesture, since "the list moved" is a claim about two pictures.
|
||||||
#
|
#
|
||||||
# `--bin` runs a real crate binary instead of an example (E4's
|
# `--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
|
# `cargo build --bin NAME` instead of `--example NAME`, and
|
||||||
# `target/debug/NAME` instead of `target/debug/examples/NAME`. Its own
|
# `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
|
# argv (the CLI flags a real binary takes, as opposed to `cargo build`'s
|
||||||
@@ -45,6 +51,8 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
here=$(cd "$(dirname "$0")" && pwd)
|
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"
|
run="${XDG_RUNTIME_DIR:-/tmp}/iris-headless"
|
||||||
seconds=3
|
seconds=3
|
||||||
shot=""
|
shot=""
|
||||||
@@ -54,7 +62,7 @@ kind=example
|
|||||||
phone=no
|
phone=no
|
||||||
|
|
||||||
# The phone Iris runs the bench on. Not typed from memory: these are
|
# 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.
|
# in turn come from her own reports -- keep the three in step.
|
||||||
PHONE_MODE=1080x2424@120Hz
|
PHONE_MODE=1080x2424@120Hz
|
||||||
PHONE_SCALE=2.55
|
PHONE_SCALE=2.55
|
||||||
@@ -67,11 +75,12 @@ while [ $# -gt 0 ]; do
|
|||||||
--bin) kind=bin; shift ;;
|
--bin) kind=bin; shift ;;
|
||||||
--phone) phone=yes; shift ;;
|
--phone) phone=yes; shift ;;
|
||||||
--replay) replay=$2; shift 2 ;;
|
--replay) replay=$2; shift 2 ;;
|
||||||
|
--dir) workdir=$(cd "$2" && pwd); shift 2 ;;
|
||||||
--) shift; break ;;
|
--) shift; break ;;
|
||||||
*) example=$1; shift ;;
|
*) example=$1; shift ;;
|
||||||
esac
|
esac
|
||||||
done
|
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; }
|
[ -z "$replay" ] || [ -f "$replay" ] || { echo "run-headless: no touch script at $replay" >&2; exit 2; }
|
||||||
|
|
||||||
mkdir -p "$run"
|
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
|
# Built before the app starts, so a compile error is not reported as a
|
||||||
# window that failed to move.
|
# 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
|
if [ "$kind" = bin ]; then
|
||||||
cargo build --bin "$example" "$@" >&2
|
cargo build --bin "$example" "$@" >&2
|
||||||
bin="$here/target/debug/$example"
|
bin="$workdir/target/debug/$example"
|
||||||
else
|
else
|
||||||
cargo build --example "$example" "$@" >&2
|
cargo build --example "$example" "$@" >&2
|
||||||
bin="$here/target/debug/examples/$example"
|
bin="$workdir/target/debug/examples/$example"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2086 -- deliberately word-split: this is the
|
# 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
|
#!/bin/sh
|
||||||
# Runs this repo's tests. Extra arguments are forwarded to each `cargo test`,
|
# Runs this repo's Rust tests. Extra arguments are forwarded to each
|
||||||
# e.g. `./run-tests.sh transcript` to run just the transcript tests in all
|
# `cargo test`, e.g. `./run-tests.sh transcript` to run just the transcript
|
||||||
# three crates.
|
# tests in every workspace.
|
||||||
#
|
#
|
||||||
# server/ holds the backend's own logic (event normalization, transcript
|
# Three workspaces, in dependency order:
|
||||||
# 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
|
# event-model the wire shape server/ and app-rust/ share, so the two
|
||||||
# once instead of twice (see CLIENT_CORE.md) -- the syntax highlighter, the
|
# agree by construction rather than by review
|
||||||
# ANSI parser, the transcript cache and fold, the REST and SSE clients. It
|
# server/ the backend's own logic (event normalization, transcript
|
||||||
# is not wired into the Android app yet (RUST.md), which still carries its
|
# cursors, config persistence, token auth)
|
||||||
# own Kotlin copies and has no tests of its own to run here; verifying it
|
# app-rust/ the app itself: `client` (highlighter, ANSI parser,
|
||||||
# means running it -- see AGENTS.md.
|
# 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
|
set -eu
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
for crate in event-model client-core server; do
|
for workspace in event-model server app-rust; do
|
||||||
(cd "$crate" && cargo test "$@")
|
(cd "$workspace" && cargo test "$@")
|
||||||
done
|
done
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
//! ?limit=N, ?coalesce=true to count rows not deltas,
|
//! ?limit=N, ?coalesce=true to count rows not deltas,
|
||||||
//! ?after=N to floor it at what the caller already holds
|
//! ?after=N to floor it at what the caller already holds
|
||||||
//! GET /sessions/{id}/subagents [{id, title, status, created, lastActivity}], oldest
|
//! 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,
|
//! GET /sessions/{id}/subagents/{sub}/transcript exactly the transcript route above,
|
||||||
//! against that subagent's own transcript
|
//! against that subagent's own transcript
|
||||||
//! GET /sessions/{id}/subagents/{sub}/events?after=N exactly the events route above,
|
//! 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
|
/// 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(
|
async fn subagent_transcript(
|
||||||
State(manager): State<Arc<SessionManager>>,
|
State(manager): State<Arc<SessionManager>>,
|
||||||
UrlPath((id, sub)): UrlPath<(String, String)>,
|
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
|
/// 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(
|
async fn subagent_events(
|
||||||
State(manager): State<Arc<SessionManager>>,
|
State(manager): State<Arc<SessionManager>>,
|
||||||
UrlPath((id, sub)): UrlPath<(String, String)>,
|
UrlPath((id, sub)): UrlPath<(String, String)>,
|
||||||
@@ -1884,7 +1884,7 @@ fn sse_stream(
|
|||||||
|
|
||||||
/// `GET /sessions/{id}/subagents`: every subagent this session has started,
|
/// `GET /sessions/{id}/subagents`: every subagent this session has started,
|
||||||
/// oldest first, with a status read from its own transcript -- see
|
/// 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
|
/// 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
|
/// process was the session's, and a session with none has nothing left to
|
||||||
/// ask.
|
/// ask.
|
||||||
@@ -1895,7 +1895,7 @@ async fn list_subagents(
|
|||||||
let session = lookup(&manager, &id)?;
|
let session = lookup(&manager, &id)?;
|
||||||
// Anything but `Exited` or `Unknown` has a process behind it, which is
|
// Anything but `Exited` or `Unknown` has a process behind it, which is
|
||||||
// what decides whether a subagent still reading `Running` from its own
|
// 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!(
|
let running = !matches!(
|
||||||
session.status(),
|
session.status(),
|
||||||
crate::session::driver::SessionStatus::Exited
|
crate::session::driver::SessionStatus::Exited
|
||||||
|
|||||||
+16
-6
@@ -8,7 +8,7 @@
|
|||||||
//! the Keystore-sealed enrollment, RUST.md's E3 entry explains why that
|
//! the Keystore-sealed enrollment, RUST.md's E3 entry explains why that
|
||||||
//! code is reused rather than re-derived in Rust) and on
|
//! code is reused rather than re-derived in Rust) and on
|
||||||
//! `androidx.core:core-ktx` (used at runtime through JNI by
|
//! `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
|
//! Both are ordinary Maven/AAR dependency graphs, and reimplementing a
|
||||||
//! dependency resolver to avoid one Gradle invocation was not a good trade
|
//! dependency resolver to avoid one Gradle invocation was not a good trade
|
||||||
//! against "smallest honest route" (RUST.md's E5 box) -- especially since
|
//! 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 repo_root = repo_root()?;
|
||||||
let app_dir = repo_root.join("app");
|
let app_dir = repo_root.join("app");
|
||||||
let shell_app_dir = app_dir.join("shellApp");
|
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()?;
|
let sdk = sdk::find()?;
|
||||||
sdk::require_ndk_installed(&sdk.root)?;
|
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(", "));
|
println!("==> Building the shell JNI bridge for {}", abis.join(", "));
|
||||||
build_native_libs(&android_shell_dir, &shell_app_dir, &sdk, abis)?;
|
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)");
|
println!("==> Resolving the runtime classpath (one Gradle call -- see apk.rs's module doc)");
|
||||||
let classpath_jars = runtime_classpath_jars(&app_dir, &sdk)?;
|
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
|
// 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
|
// no reason for this crate's debug build to be bigger or slower for a
|
||||||
// signing choice that has nothing to do with it.
|
// 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_HOME", &sdk.root);
|
||||||
cmd.env("ANDROID_SDK_ROOT", &sdk.root);
|
cmd.env("ANDROID_SDK_ROOT", &sdk.root);
|
||||||
run_checked(
|
run_checked(
|
||||||
@@ -395,7 +405,7 @@ fn merge(
|
|||||||
std::fs::remove_dir_all(&stage).ok();
|
std::fs::remove_dir_all(&stage).ok();
|
||||||
}
|
}
|
||||||
for abi in abis {
|
for abi in abis {
|
||||||
let so_name = "libandroid_shell.so";
|
let so_name = "libai_app.so";
|
||||||
let src = shell_app_dir
|
let src = shell_app_dir
|
||||||
.join("src/main/jniLibs")
|
.join("src/main/jniLibs")
|
||||||
.join(abi)
|
.join(abi)
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
//! `cargo xtask apk` -- E5 (RUST.md): packages `app/shellApp` into a
|
//! `cargo xtask apk` -- E5 (RUST.md): packages `app/shellApp` into a
|
||||||
//! signed, installable APK with no Gradle in the packaging step itself.
|
//! 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;
|
//! Java stub classes (plus the generated pinned-CA constant) into dex;
|
||||||
//! `aapt2` compiles the manifest into `resources.arsc`; the dex and native
|
//! `aapt2` compiles the manifest into `resources.arsc`; the dex and native
|
||||||
//! libraries are merged into that base APK with `jar`; `zipalign` and
|
//! libraries are merged into that base APK with `jar`; `zipalign` and
|
||||||
|
|||||||
Reference in new issue
Block a user