Iris: "remove both decisions and iris.md. I've decided to instead make decisions when planning with agents rather than after they do things, and they're both too long for me to wanna read, + don't cover all the decisions I'll wanna make about the code anyways. I'll just naturally run into things for now. Todo is important though." So docs/DECISIONS.md (850 lines) and docs/IRIS.md (1,986) are gone, and AGENTS.md now says not to start another: raise a choice while planning it with her, otherwise decide it and put the reasoning at the code it governs. The TODO lists stay. docs/SUBAGENTS_DECISIONS.md went with them -- same artefact, same reasoning, and she did not name it, so its six decisions were folded into docs/SUBAGENTS.md rather than deleted. Deleting the logs left ~30 citations dangling in code comments and docs. Each states its reason inline and cited the file only for provenance, so they now read "decided 2026-09-07" or name the module doc that carries the reasoning. The root had six things that were not a program or a document. Moved, per "I only meant top level sh files": run-tests.sh, test-wg-tunnel.sh, wg-setup-host.sh -> scripts/ rigs/ -> scripts/rigs/ xtask/ -> scripts/xtask/ A project's own scripts stayed with the project: app/*.sh, app-rust/*.sh, iris/*.sh and server/enroll-link.sh did not move. `target/` at the root is deleted and cannot come back: there was never a workspace there, and the 29 MB was only xtask's scratch space, now in scripts/xtask/target/. `cargo xtask apk` still runs from the repo root and now publishes to scripts/build/outputs/apk/<mode>/ -- one directory deep, because that is what Dev Updater's `*/build/outputs/apk/*/*.apk` discovery pattern needs, and scripts/xtask/build would have been two. Verified: ./scripts/run-tests.sh and `cd iris && cargo test` green, clippy and fmt clean everywhere, `cargo xtask apk debug --abi x86_64` builds and signs an APK carrying lib/x86_64/libai_app.so at the new publish path, and the repo root is now eleven entries with no build output among them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
147 lines
5.7 KiB
Rust
147 lines
5.7 KiB
Rust
//! Layer 2 of docs/RUST.md's "Three test layers": the fixture-backed
|
|
//! transcript screen in a phone-shaped window, for looking at.
|
|
//!
|
|
//! iris/run-headless.sh phone --phone --shot /tmp/phone.png -- -p transcript-fixture
|
|
//!
|
|
//! `--phone` sets the headless sway output to the phone's own 1080x2424
|
|
//! and exports `IRIS_SCALE=2.55`, so this draws at the density Iris's
|
|
//! phone reports (`ai_app::ui::fixture::PHONE_SCALE`) rather than the
|
|
//! desktop's 1.0 -- same screen, same fixture and the same folding as
|
|
//! the Android bench and the headless tests, so what differs between a
|
|
//! screenshot here and one from the phone is the renderer, never the
|
|
//! data.
|
|
//!
|
|
//! `--message TEXT` (through `RUN_HEADLESS_ARGS`) starts with that text
|
|
//! already in the composer, `\n` for a newline -- the composer's grown
|
|
//! and overflowing states are otherwise unreachable here, since this
|
|
//! window has no keyboard to type into (UI_RULES.md's "check the states
|
|
//! you can't see by default"). `--typed TEXT` *enters* the same text
|
|
//! instead, one character per 100ms: laying the composer out from
|
|
//! scratch and growing one already on screen are different cases, and
|
|
//! only the second reproduced the caret landing in the bar's padding
|
|
//! (decided 2026-09-08).
|
|
//!
|
|
//! No server: `transcript-fixture` embeds the transcript. Colour,
|
|
//! spacing, type and anything a person has to *see* is answered here;
|
|
//! anything with an assertion behind it belongs in `tests/
|
|
//! phone_screen.rs` one layer down.
|
|
|
|
use iris::prelude::*;
|
|
use winit::{dpi::PhysicalSize, window::WindowAttributes};
|
|
|
|
/// The `--ime PX` argument: the bottom inset a keyboard would report,
|
|
/// applied after the first frame the way Android's `on_insets_changed`
|
|
/// does. The composer's keyboard-open layout is otherwise unreachable
|
|
/// here, and it is where its mask went wrong before (see
|
|
/// `ActiveData::own_mask`).
|
|
fn ime_argv() -> Option<f32> {
|
|
let mut args = std::env::args().skip(1);
|
|
while let Some(arg) = args.next() {
|
|
if arg == "--ime" {
|
|
return args.next()?.parse().ok();
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// The `--message TEXT` argument, with `\n` taken as a newline so a
|
|
/// multi-line message survives one shell word.
|
|
fn message_argv() -> Option<String> {
|
|
let mut args = std::env::args().skip(1);
|
|
while let Some(arg) = args.next() {
|
|
if arg == "--message" {
|
|
return Some(args.next()?.replace("\\n", "\n"));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// The `--typed TEXT` argument: the same text as `--message`, but
|
|
/// *entered* rather than preloaded -- one insertion per 100ms, into a
|
|
/// focused field, the way a person types. The two are different cases
|
|
/// for layout: `--message` is laid out from scratch on the first frame,
|
|
/// while this grows an already-drawn composer, which is the path
|
|
/// Iris's 2026-09-08 phone report is about.
|
|
fn typed_argv() -> Option<String> {
|
|
let mut args = std::env::args().skip(1);
|
|
while let Some(arg) = args.next() {
|
|
if arg == "--typed" {
|
|
return Some(args.next()?.replace("\\n", "\n"));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn main() {
|
|
DefaultApp::<Client>::run();
|
|
}
|
|
|
|
#[derive(DefaultUiState)]
|
|
pub struct Client {
|
|
ui_state: DefaultUiState,
|
|
#[allow(dead_code)]
|
|
screen: Option<ai_app::ui::TranscriptScreen>,
|
|
}
|
|
|
|
impl DefaultAppState for Client {
|
|
fn window_attributes() -> WindowAttributes {
|
|
WindowAttributes::default()
|
|
.with_title("iris transcript (bench fixture)")
|
|
.with_inner_size(PhysicalSize::new(
|
|
ai_app::ui::fixture::PHONE_WIDTH,
|
|
ai_app::ui::fixture::PHONE_HEIGHT,
|
|
))
|
|
}
|
|
|
|
fn new(
|
|
mut ui_state: DefaultUiState,
|
|
rsc: &mut DefaultRsc<Self>,
|
|
_: Proxy<Self::Event>,
|
|
) -> Self {
|
|
let screen = match ai_app::ui::fixture::open(rsc, &mut ui_state) {
|
|
Ok(opened) => {
|
|
if let Some(message) = message_argv() {
|
|
opened.screen.composer.field.edit(rsc).set(&message);
|
|
}
|
|
if let Some(text) = typed_argv() {
|
|
let field = opened.screen.composer.field;
|
|
let redraw = rsc.tasks.redraw_handle();
|
|
rsc.spawn_task(async move |mut ctx| {
|
|
for ch in text.chars() {
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
ctx.update(move |state: &mut Client, rsc| {
|
|
state.set_focus(Some(field));
|
|
let end = rsc[field].text().len();
|
|
let mut edit = field.edit(rsc);
|
|
if edit.text.caret().is_none() {
|
|
edit.set_cursor_byte(end);
|
|
}
|
|
edit.insert(&ch.to_string());
|
|
});
|
|
redraw.request_redraw();
|
|
}
|
|
});
|
|
}
|
|
if let Some(inset) = ime_argv() {
|
|
opened.screen.composer.set_bottom_inset(rsc, inset);
|
|
}
|
|
Some(opened.screen)
|
|
}
|
|
// On screen rather than a panic: this window exists to be
|
|
// looked at, and "the fixture stopped folding" is something
|
|
// to read, not a process that vanished (UI_RULES.md).
|
|
Err(message) => {
|
|
let text = wtext(format!("Couldn't fold the bench fixture: {message}"))
|
|
.color(Color::WHITE)
|
|
.wrap(true)
|
|
.pad(dp(16))
|
|
.add_strong(rsc)
|
|
.any();
|
|
ui_state.set_root(text);
|
|
None
|
|
}
|
|
};
|
|
Self { ui_state, screen }
|
|
}
|
|
}
|