201 lines
8.9 KiB
Markdown
201 lines
8.9 KiB
Markdown
# Rust app and Iris
|
|
|
|
The production app is one Rust crate under `app/`, with shared Android and
|
|
desktop UI drawn by the in-tree `iris` framework. This document keeps the
|
|
open work and measured constraints that are expensive to rediscover.
|
|
|
|
## Boundaries
|
|
|
|
- `app/src/client` contains transport, config, caching, folding, highlighting,
|
|
and other platform/UI-independent logic. It must not depend on Iris.
|
|
- `app/src/ui` contains product screens and reusable product widgets.
|
|
- `app/src/android` and `app/src/desktop` contain only platform integration.
|
|
Platform services are exposed to shared UI through small traits.
|
|
- `iris/` is only a UI framework. Session, transcript, server, setup, and
|
|
product styling concepts never belong there.
|
|
- `iris/src/rsc` groups resources carried by or installed through `StdRsc`:
|
|
attributes, events, overlays, pointer sensing/input, tasks, and per-widget
|
|
state. It is the standard bundle used by Iris's hosts, not a requirement;
|
|
shared widgets depend on its narrow resource traits so a custom bundle can
|
|
implement only what it needs. `widget/` is reserved for concrete retained
|
|
widgets. Public paths such as `iris::sense` remain aliases for readability.
|
|
- Phone and desktop layouts may differ. Their component implementations,
|
|
colors, spacing, text styles, gestures, folding, and network behavior do
|
|
not.
|
|
|
|
The application crate has an rlib for tests and desktop and a cdylib for
|
|
Android. Features select optional surfaces: `screens` links Iris, `fixture`
|
|
embeds the synthetic transcript, and `bench` adds the benchmark runner. An
|
|
ordinary APK uses `transcript-screen`, not `bench`.
|
|
|
|
## Current state
|
|
|
|
The transcript screen runs in the headless harness, a desktop window, the
|
|
Android emulator, and a real phone. The phone performance gate passed; the
|
|
preserved reports are under `docs/bench/`.
|
|
|
|
Open session-screen work:
|
|
|
|
- History paging, viewport-sized prefetch, distinct unknown/empty/error
|
|
states, and jump-to-latest.
|
|
- Images, attachments, session settings, usage display, and dialogs.
|
|
- Re-verification of IME/inset behavior on the real phone.
|
|
|
|
Later product work:
|
|
|
|
- Root navigation and the sessions/import/models/setups surfaces.
|
|
- File explorer and editor, including conflict handling.
|
|
- Settings, notification permission, sharing, and notification service.
|
|
- Desktop layouts for those surfaces.
|
|
|
|
Client route coverage and gaps live in `docs/CLIENT_CORE.md`; framework work
|
|
lives in `docs/IRIS_TODO.md`.
|
|
|
|
## Test layers
|
|
|
|
Use the cheapest layer that can answer the question.
|
|
|
|
### 1. In-process harness
|
|
|
|
`iris::harness` builds the real widget tree with no window, compositor, or
|
|
GPU. Its clock is controlled by the test. `TouchScript` replays the same
|
|
historical samples Android supplies. Recordings live under `app/touch/`.
|
|
|
|
cd app && cargo test
|
|
|
|
The suite covers fling velocity and duration, catching a moving list,
|
|
cancellation, taps, long-press selection, list bounds, and composer insets.
|
|
It cannot answer pixel, atlas, GPU upload, surface lifecycle, JNI, clipboard,
|
|
or real-inset questions.
|
|
|
|
`iris/tests/mask_sdf.rs` is the narrow exception: it uses a headless GPU
|
|
compute pass to prove the shader and CPU hit-test arithmetic agree.
|
|
|
|
### 2. Phone-shaped desktop window
|
|
|
|
cd iris
|
|
./run-headless.sh phone --phone --dir ../app --shot /tmp/phone.png
|
|
./run-headless.sh phone --phone --dir ../app \
|
|
--replay ../app/touch/flick-120hz.touch --shot /tmp/replay.png
|
|
|
|
This layer is for visual output and desktop input integration. Debug frame
|
|
times on the VM's GPU are not phone measurements.
|
|
|
|
### 3. Android
|
|
|
|
cd app
|
|
./build-apk.sh debug --abi x86_64
|
|
|
|
Android is required for JNI, lifecycle, IME, insets, clipboard, accessibility,
|
|
and renderer fallback. Read the `this-machine-android` skill first. Use a
|
|
release arm64 build for real-phone measurements.
|
|
|
|
The emulator is deliberately a GLES rig: Vulkan is SwiftShader, while GLES
|
|
is host-accelerated through virgl. Do not compile with `force-gles`; Iris's
|
|
runtime fallback must make the same decision the shipped binary makes. Read
|
|
the `iris renderer:` log before interpreting any result.
|
|
|
|
## Performance rigs and established results
|
|
|
|
`scripts/rigs/ui-profile/tests/frame_profile.rs` measures the CPU half of a
|
|
frame. Run it only in release mode:
|
|
|
|
cd scripts/rigs/ui-profile
|
|
cargo test --release --test frame_profile -- --ignored --nocapture
|
|
|
|
A fling is not CPU-bound after the first pass over content: only roughly one
|
|
frame in six performs layout, and later passes measured p99 0.26 ms. Initial
|
|
multi-millisecond spikes are text shaping for newly visited transcript rows.
|
|
|
|
A streamed event originally measured 3.86 ms in the host rig and 9.5 ms on
|
|
the phone. Folding was 0.35 ms and applying the widget diff 0.41 ms; shaping
|
|
dominated because the old fixture grew one 14,888-character markdown block.
|
|
Real replies measured a largest block of 1,580 characters over 7,706 blocks.
|
|
The fixture now inserts blank lines and retains the run-on block only as a
|
|
one-shot stress case.
|
|
|
|
`arena_churn.rs` measures GPU-array upload by reporting the byte-diff floor,
|
|
the bytes Iris uploads, and the former whole-array cost. The rig established:
|
|
|
|
- Whole-array writes cost 758 MB over one fling and 1.2 GB over 401 streamed
|
|
deltas.
|
|
- `ArrBuf` must retain allocation across length changes.
|
|
- Redraw must reuse primitive slots within the frame rather than freeing and
|
|
appending them.
|
|
- Dirty ranges must track changed entries and cancel provisional writes that
|
|
are restored before upload.
|
|
- `Painter::set_child_offset` and stable `LazySpan` row boxes reduce pinned
|
|
growth to a 1.1% instance upload, matching its 1.1% floor. Median instance
|
|
upload fell from 176,496 bytes to 1,488 bytes.
|
|
|
|
Do not optimize folding again without new evidence. The measurements ruled it
|
|
out as the streamed-frame bottleneck.
|
|
|
|
## Text and fonts
|
|
|
|
Body and monospace text come from platform fonts. Icons use the bundled Nerd
|
|
Font subset because their codepoints are a small closed set no platform must
|
|
provide. Android font enumeration previously rendered some bold spans as
|
|
blank gaps, so every real-phone build must visually verify bold text and the
|
|
triangle disclosure glyphs; desktop and emulator font sets are not evidence
|
|
for the phone.
|
|
|
|
Markdown is split into blocks once and retained. A delta reshapes only the
|
|
block it changes. Tables wrap cells and horizontally scroll only after their
|
|
minimum useful column width. Links share a text widget and hit regions; they
|
|
must not become one layout node per link.
|
|
|
|
## Scrolling and retained rendering
|
|
|
|
`docs/SCROLL.md` defines scrolling. One `ScrollController` owns position,
|
|
gesture, fling, and pin state. `ScrollArea` and `LazySpan` each own one through
|
|
`Scrollable`.
|
|
|
|
The transcript is bottom-anchored. Appending while pinned keeps the newest
|
|
edge fixed; loading history above the anchor must not move visible content.
|
|
Expanding a row holds the edge nearest the tap. Retained child offsets move a
|
|
subtree without rewriting every primitive.
|
|
|
|
A page is a number of events, not a number of visible rows: many deltas fold
|
|
into one reply and many tool calls can group into one row. Prefetch therefore
|
|
uses measured viewports, never a fixed row count.
|
|
|
|
## Android host
|
|
|
|
The Java activity owns the Android lifecycle, the `View`, enrollment intent,
|
|
and insets. `iris::android` owns surface lifecycle, touch history, generic
|
|
motion, hover, mouse buttons, wheel input, IME plumbing, and accessibility.
|
|
The product client consumes the same Iris pointer state as desktop.
|
|
|
|
Static inset dispatch supplies settled values; `WindowInsetsAnimation`
|
|
supplies intermediate IME heights and re-reads settled insets at animation
|
|
end. Visibility and height are separate signals during animation. targetSdk
|
|
35+ is required for reliable IME overlap under enforced edge-to-edge.
|
|
|
|
The application id is `com.example.aiapp`, preserving the retired app's
|
|
identity. The Java package remains `dev.iris.android.demo`, an internal JNI
|
|
name. Explicit benchmark builds add `.bench` to the application id.
|
|
|
|
## Build constraints
|
|
|
|
- `app`, `iris`, and `scripts/rigs/ui-profile` use synchronized rolling
|
|
nightly toolchains.
|
|
- Android uses the `android-release`/`android-dev` profiles so phone settings
|
|
do not affect desktop builds.
|
|
- `app/build-apk.sh` removes prior ABI native outputs before packaging; Gradle
|
|
otherwise may reuse a stale merged library from another ABI.
|
|
- Release builds are signed by the machine-local
|
|
`~/.config/ai-app/release.jks`. No key or enrollment secret belongs in the
|
|
repository.
|
|
- `iris/run-headless.sh --dir DIR` selects the workspace containing the
|
|
example and defaults to `iris/`.
|
|
|
|
## Session-screen pass condition
|
|
|
|
Use `app/ui-sandbox.sh` with artificial delay, a large transcript, and slow
|
|
spawn. Exercise unknown, empty, error, and too-long states. Drive controls by
|
|
accessibility label. Verify paging seams, jump-to-latest, selection, tool-card
|
|
state, message steering, and IME closure. Then re-run the frame report on the
|
|
real phone and compare only with the same Iris reporter—not historical Compose
|
|
timings, whose clocks and accounting differ.
|