# Iris extraction handoff Operational handoff for pulling Iris out of ai-app into a standalone framework. Not a decisions log; delete it when the extraction is done. ## Read this first **Pull request #11 has been reviewed seven times; the seventh is answered in `79dcc15` and an eighth has not arrived.** Check for one before doing anything else: ```sh TOKEN=$(cat ~/.config/gitea/token) curl -s -H "Authorization: token $TOKEN" \ https://git.arirex.me/api/v1/repos/iris/iris/pulls/11/reviews curl -s -H "Authorization: token $TOKEN" \ https://git.arirex.me/api/v1/repos/iris/iris/pulls/11/reviews//comments curl -s -H "Authorization: token $TOKEN" \ https://git.arirex.me/api/v1/repos/iris/iris/issues/11/comments ``` Check the newest `submitted_at` rather than assuming the first result is current. My replies are ordinary issue comments on the same PR and say what each change was for. **Agree a design before sending another variation of it.** The owner stopped the fourth round with *"we should probably agree on the design here rather than you keep submitting variations that I review"*. When a review comes back about the shape of something rather than a defect in it, put the options and a recommendation in front of her and implement what she picks. She chose the per-image bind group for textures on 2026-09-13; the alternatives were packing images into arrays like atlas pages, and a bindless `binding_array` (ruled out by Android support). ## Stop point - Canonical Iris: `git@git.arirex.me:iris/iris.git`, `main` at `0f6a28b` (the squash merge of #10). - Agent fork: `git@git.arirex.me:iris-ai/iris.git`. The bot cannot push canonical; work in the fork and open PRs into `iris/iris:main`. - **#11** `split/11-texture-bindings`, worktree `/home/bob/repos/iris-pr11`, head `79dcc15`, fourteen commits on top of `0f6a28b`. Reviewed seven times, all answered. - **#12** `split/12-pointer-routing`, worktree `/home/bob/repos/iris-pr12`, head `028521b`. A scroll falling through a hovered button. Not reviewed as of the last check. Independent of #11 and can merge in either order. - `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean. ## What #11 became It started as "draw the glyph atlas as an array texture" and four rounds of review turned it into a rework of how primitives are drawn. Worth knowing before reading a new review, because the earlier rounds are what produced this shape: - **Atlas pages are their own thing.** `GlyphAtlas` owns its page images and emits dirty rectangles; `GpuPages` owns the array texture they upload to. `Textures` holds standalone images only. - **A primitive registers itself by being drawn.** The type carries its own WGSL and `PrimitiveRegistry` keys ids by `TypeId`, so the kind comes from the type and there are no `RECT`/`GLYPH`/`TEXTURE` constants. Nothing is seeded, so an id depends on what a ui drew first and a ui pays only for what it uses. - **Nothing dispatches dynamically.** Push, free, renumber, upload and draw are identical for every primitive; only the entry size and the pipeline differ, and those are data. `InstanceList` carries a runtime stride and its data as bytes. Measured: 0.2ns per write over a statically typed list, against 1.6ns for a trait object. - **The shared group is what every pipeline gets**: the window and the masks. A mask texture would go here too. - **`Primitive::SAMPLES` says what a primitive samples and how often it binds** -- `Atlas` once for a list, `Image(fn)` for one instance alone, defaulted to nothing. That is group 2, so a rect's pipeline has no texture or sampler in its layout. Both cases share one layout builder and one group builder. - **`shader.wgsl` became `shader/prelude.wgsl` plus one file per primitive**, because one module cannot declare two types at the same binding. The prelude carries only what every primitive uses -- window, masks, vertex shader, `masked()` -- and its header is where binding numbers are written down; what a shader samples is declared by that shader. - **A texture handle is drawn like anything else.** `Painter::primitive` takes `impl PrimitiveLike`: a primitive, or something that yields one and does what else drawing it needs -- a `&TextureHandle` retains its share on the way through. The blanket impl and the one for `&TextureHandle` do not collide, because `Primitive` is ours and no crate can add the overlapping impl. - **Order within a layer means nothing**, and the widgets do not rely on it: `Stack` gives each child its own layer and `TextEdit` draws its view in a child layer above the selection rectangles. Verified with a ui that registers glyph before rect. ## Standing instructions from the owner - Pull Iris out even if the Rust application switchover is not accepted. No app, session, transcript, setup or server concepts in Iris; the dependency runs one way from `app/` to Iris. - **Small, coherent PRs.** The original extraction PR was too large to review. A slice may be redone rather than transplanted, and need not remove every old feature. Independent PRs may be open at once so work continues during review. - Do not recreate an `ai` branch in canonical Iris; the fork is the boundary. - **Never rewrite a pushed branch.** Follow review with additive commits. - Respond to each review finding with a fix or a concise explanation. Do not add a ceremonial comment when the changed code already answers it. ## The review gate `~/.claude/MACHINE.md` now carries a machine-wide rule, and the procedure is the installed **`pre-submit-review`** skill. It is not optional and not project-specific: build clean, review the code, review the comments separately once the code has settled, then verify the claim by running it. The fixes a review produces are themselves unreviewed code, so the passes repeat until a round finds nothing. Running it on #11 found four defects that format, clippy, tests and five headless renders had all passed — including one hidden *by* the tests passing, where an image alone in a layer failed GPU validation because every other case happened to have a rectangle in the same layer leaving a bind group set. That is the shape to hunt for. `audit.sh` in the skill directory prints every comment line a branch adds against a base ref, for an explicit verdict each. The owner's standing complaint is verbose agent comments; the default verdict is delete. ## Repository topology ### ai-app checkout - `/home/bob/repos/ai-app-2`, `origin = git@git.arirex.me:iris/ai-app.git`, branch `rustify`. - `iris/` is a submodule pinned at `32f6ad8`, the complete extracted snapshot, and `.gitmodules` points at the **bot fork**, not canonical Iris. - Do not change either casually: ai-app needs the complete snapshot while canonical Iris is only partly caught up. Reconcile when canonical contains what ai-app needs, or when the owner accepts a temporarily non-building pin. ### standalone Iris checkout - `/home/bob/repos/iris`, `origin` = fork, `upstream` = canonical. - Fork `main` and `origin/archive/full-extraction` both name `32f6ad8`, the target snapshot. `history/full` names the source-history result `a615bcd`. - **Do not reset, overwrite or force-push fork `main`**: it is both the target reference and the commit ai-app pins. - Start each new branch from current `upstream/main` in its own worktree: ```sh cd /home/bob/repos/iris && git fetch upstream git worktree add -b split/13-name /home/bob/repos/iris-pr13 upstream/main ``` The other `/home/bob/repos/iris-pr*` worktrees hold merged branches. They are readable references; do not build new work on them. ## Merged so far | PR | On canonical `main` | | --- | --- | | #2 | Build on the current nightly (`4275314`) | | #3 | Request a frame after resize (`936fbdd`) | | #4 | Decouple `iris-core` from winit (`465e430`) | | #5 | Use vsync by default (`ec2b5d4`) | | #6 | Notify winit before presenting (`db9b0f2`) | | #7 | Keep unsafe reference helpers internal (`0191f20`) | | #8 | Initialize the window uniform from the surface (`6e271e8`) | | #9 | Preserve primitive-count recursion (`b90c855`) | | #10 | Text layout and rendering on Parley (`0f6a28b`) | URLs are `https://git.arirex.me/iris/iris/pulls/{number}`. ## Choosing the next slice ```sh cd /home/bob/repos/iris && git fetch upstream git diff --stat upstream/main..archive/full-extraction ``` The archive is a reference, not a patch to apply. Recreate a change on top of canonical `main`, leave app-specific behaviour out, and verify it independently. Pick disjoint path sets when two PRs are open, and branch each from the latest `upstream/main` rather than stacking. Still in the target and not yet claimed: - Widget draw size and measurement cleanup (source commit `6671194`). - Retained span, scrolling and layout placement. - Retained paints, selection, overlays and shared UI runtime state. - Generic desktop/Android framework hosts and reusable example/APK tooling. - Application-owned fonts and application-named font families. - Shared resource-handle bookkeeping and replaceable glyph-atlas buckets. - Positioned text overflow and cluster-safe ellipsis. - **The headless rig** — `scripts/run-headless.sh`, `headless.conf` and `rig-input` live in ai-app's iris submodule and not in canonical Iris. Everything below depends on running them from there; extracting them is a slice of its own. The three sampling paths each need a real render, and the examples for two of them are not in the repo: `/tmp/pr11-checks/run.sh` copies them in, renders rect+glyph+image together, an image alone in a layer, and a grown atlas, then removes them again. ## Verifying a framework slice ```sh cd cargo fmt --all --check cargo clippy --all-targets -- -D warnings cargo test ``` `--workspace` when the slice crosses workspace crates. A rendering claim needs a real run, which on this machine means the headless rig in ai-app's submodule: ```sh cd /home/bob/repos/ai-app-2/iris ./scripts/run-headless.sh tabs --dir /home/bob/repos/iris-prNN \ --shot /tmp/out.png --seconds 3 ./scripts/run-headless.sh tabs --dir ... --replay /tmp/taps.touch --shot ... ``` A `.touch` file is ` down|move|up ` in the output's own pixels; the script replays it through a virtual pointer. The `tabs` example's five tabs sit at x = 192, 576, 960, 1344 and 1728 on a 1920x1200 output. Read the `this-machine-graphics` skill before trusting any of it — the Vulkan device here comes and goes, and everything silently falls back to software when it does. ## Cautions - Read `/home/bob/repos/ai-app-2/AGENTS.md` and the machine-wide rules first. - Keep Iris generic: session drivers, transcripts, setup and server concepts, app icons and product fonts stay in ai-app. Android and desktop code is Iris work only when it is a generic host or platform integration. - Preserve the dirty-worktree rule. All worktrees were clean at handoff; anything found later may be the owner's or another agent's. - Do not delete the archived snapshot or the fork `main` ai-app pins. - A complete target branch is not permission to recreate the giant PR. - Another agent was freeing disk on this VM and removed `target/` from the `iris-pr*` worktrees once. Sources and git state were untouched. Tell peers before changing shared machine tooling, and expect a cold rebuild sometimes.