Files
ai-app/docs/IRIS_EXTRACTION_HANDOFF.md
T
2026-09-13 17:44:21 -04:00

11 KiB

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:

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/<id>/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:
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

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 rigscripts/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

cd <iris-worktree>
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:

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 <ms> down|move|up <x> <y> 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.