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

230 lines
11 KiB
Markdown

# 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 six times ("very close now"); the sixth
is answered in `444a2cd` and a seventh 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/<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 `444a2cd`, twelve commits on top of `0f6a28b`. Reviewed six 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**: window, masks, the glyph
atlas array, and the one sampler. A mask texture would go here too. A
primitive that samples an image of its own declares `Primitive::TEXTURE`,
which gives it an image bind group, a draw call per instance, and the slot
recorded at write time; no other pipeline mentions textures at all.
- **`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 records the group 0 binding numbers; the atlas
and sampler are declared by the shaders that read them.
- **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.
## Verifying a framework slice
```sh
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:
```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 `<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.