276 lines
14 KiB
Markdown
276 lines
14 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.
|
|
|
|
## Where things stand
|
|
|
|
Canonical `main` is **`32b1038`** (#14, SetSize). Twelve slices are in. One
|
|
pull request is open and one branch is pushed but unopened:
|
|
|
|
- **#12** `split/12-pointer-routing`, worktree `/home/bob/repos/iris-pr12`,
|
|
head `f3fd941`. Reworked after the owner's review rejected the design: a
|
|
layer, not a widget, is what consumes input. `CursorSenses::consumes`
|
|
decides only whether a layer stops the input reaching the one below, taking
|
|
nothing out of the cursor, and senses on one layer never block each other.
|
|
Five tests; two fail on `main`. One question is out to her in the reply:
|
|
whether a widget the cursor has left should still be handed a press.
|
|
- **`split/15-harness`**, worktree `/home/bob/repos/iris-pr15`, head
|
|
`d5efdd2`, pushed with no pull request opened. `Tasks` takes an
|
|
`Arc<dyn WakeTaskQueue>` instead of an `Arc<Window>`, so `DefaultRsc` builds
|
|
without one, and `iris::harness` drives a ui with no surface. Six tests in
|
|
`tests/harness.rs`. Open it when #12 clears.
|
|
|
|
Check for a review before starting anything, and read the newest
|
|
`submitted_at` rather than the first result:
|
|
|
|
```sh
|
|
TOKEN=$(cat ~/.config/gitea/token)
|
|
N=12
|
|
curl -s -H "Authorization: token $TOKEN" \
|
|
https://git.arirex.me/api/v1/repos/iris/iris/pulls/$N/reviews
|
|
curl -s -H "Authorization: token $TOKEN" \
|
|
https://git.arirex.me/api/v1/repos/iris/iris/pulls/$N/reviews/<id>/comments
|
|
curl -s -H "Authorization: token $TOKEN" \
|
|
https://git.arirex.me/api/v1/repos/iris/iris/issues/$N/comments
|
|
```
|
|
|
|
My replies are ordinary issue comments on the same PR and say what each change
|
|
was for. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean.
|
|
|
|
## How the work is sequenced
|
|
|
|
**Most fundamental first**, from the owner on 2026-09-13: *"please do more
|
|
fundamental changes first, such as library updates and core framework changes,
|
|
so that code only has to be written once"*, and *"should probably start adding
|
|
tests early on rather than later, so you don't have to make separate test
|
|
scripts and stuff. This might involve making the harness eventually, depends
|
|
on what needs tested."*
|
|
|
|
So slices are ordered by how much depends on them, not by what is nearest
|
|
ready, and a slice arrives with tests rather than with a script in `/tmp`.
|
|
|
|
**Agree a design before sending another variation of it.** The owner stopped
|
|
the fourth round of #11 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.
|
|
|
|
**Nothing is submitted without a separate review pass** — the installed
|
|
`pre-submit-review` skill: build clean, review the code, review the comments on
|
|
their own 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. It has earned its place repeatedly: four defects
|
|
on #11 that format, clippy, tests and five headless renders had all passed, and
|
|
on #12 a regression introduced by the review's own first draft. `audit.sh` in
|
|
the skill directory prints every comment line a branch adds against a base ref;
|
|
the owner's standing complaint is verbose agent comments, and the default
|
|
verdict is delete. **Machine-specific notes do not belong in the repository** —
|
|
they live in `~/.claude/MACHINE.md` or a `this-machine-*` skill.
|
|
|
|
Other 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, one at a time.** The original extraction PR was too
|
|
large to review. A slice may be redone rather than transplanted, and need not
|
|
remove every old feature. Do not open several at once to stay busy during a
|
|
review: on 2026-09-13 the owner asked for the work to go serially, most
|
|
fundamental first, so that code is written once against the version of the
|
|
framework that will actually exist.
|
|
- Do not recreate an `ai` branch in canonical Iris; the fork is the boundary.
|
|
- **Never rewrite a pushed branch.** Follow review with additive commits, and
|
|
merge `upstream/main` in rather than rebasing when a branch falls behind.
|
|
- 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.
|
|
- **A test has to guard something that could break again.** The owner deleted
|
|
#14's test as pointless: the rename it guarded cannot regress. When a fix is
|
|
structural, the structure is the test.
|
|
|
|
## The next slice
|
|
|
|
**The headless rig.** `scripts/run-headless.sh`, `headless.conf` and
|
|
`rig-input` are still only in ai-app's iris submodule, so every rendering
|
|
claim in canonical Iris is verified by hand from another checkout. The harness
|
|
half of this is done in `split/15-harness`; the rig is shell and a Wayland
|
|
replay binary and transplants nearly as-is, minus the `--phone` and `--dir`
|
|
flags' ai-app specifics.
|
|
|
|
Still in the target, roughly in dependency order:
|
|
|
|
- **The input restructure** — `src/default/sense.rs` becomes `src/rsc/sense.rs`
|
|
(308 lines to 2313), plus `core/src/event/controller.rs`, `desktop/input.rs`,
|
|
`android/input.rs` and `sense_tests.rs`: pointer capture, drag slop and axis,
|
|
platform cancellation, mask-aware hit testing, event timestamps. It replaces
|
|
the file #12 fixes. The archive's own `consumes` is what #12 now implements,
|
|
so that part transplants; `tests/pointer_routing.rs` is the acceptance
|
|
criterion for the slice.
|
|
- 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.
|
|
|
|
Dependencies are current apart from `winit`, which stays on 0.30.12 until
|
|
0.31 leaves prerelease. `parley` 0.11.1 and `image` 0.25.10 are latest.
|
|
|
|
```sh
|
|
cd /home/bob/repos/iris && git fetch upstream
|
|
git diff --stat upstream/main..origin/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 — unless the slice fixes
|
|
code another open branch replaces, in which case say so and stack deliberately.
|
|
|
|
## How the renderer works now
|
|
|
|
Current invariants, not history. Worth reading before touching `core/render`.
|
|
|
|
- **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 the
|
|
pipelines it uses.
|
|
- **Each primitive records its own draws.** `Primitive::render` makes a
|
|
`PrimitiveRender` that states the layout its shader reads, uploads whatever
|
|
it owns, and records its draws. `GlyphRender` owns the atlas and binds it
|
|
once per list; `ImageRender` owns the images and binds one per instance; the
|
|
default owns nothing and draws every instance in one call. The renderer sets
|
|
the pipeline, the shared group, the list's data and its vertex buffer, and
|
|
knows nothing else. Dispatch per list was measured at 6 instructions, 0.1% of
|
|
a frame at 256 and at 1024 layers, against the ~5,400 wgpu spends recording
|
|
one list; `tests/draw_cost.rs` is that measurement.
|
|
- **The shared bind group is the window and the masks**, given to every draw.
|
|
A mask texture would go here too. What a primitive samples is its own group,
|
|
and a primitive that samples nothing has no such group in its pipeline.
|
|
- **Every binding size is stated.** A `None` minimum puts the binding on
|
|
wgpu-core's late-sized list, which `is_ready` scans on every draw.
|
|
- **`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
|
|
whatever else drawing it needs — a `&TextureHandle` retains its share on the
|
|
way through, which a `Pod` primitive cannot.
|
|
- **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.
|
|
- **Images are one texture and one bind group each**, so each drawn image is a
|
|
draw call. The owner chose that on 2026-09-13 over packing images into
|
|
arrays like atlas pages; a bindless `binding_array` was ruled out by Android
|
|
support. Revisit only with her.
|
|
- **Layers are never freed** (`TODO` in `primitive/layer.rs`), so every layer a
|
|
session creates is walked every frame thereafter. Measured at ~2ns per empty
|
|
layer per frame, which is why it is the TODO's problem and not a bug of its
|
|
own.
|
|
|
|
## 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/15-name /home/bob/repos/iris-pr15 upstream/main
|
|
```
|
|
|
|
Every other `/home/bob/repos/iris-pr*` worktree holds a merged branch. They
|
|
are readable references; do not build new work on them.
|
|
|
|
## Verifying a 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 until the rig is extracted means driving it from 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
|
|
`tabs` example's five tabs sit at x = 192, 576, 960, 1344 and 1728 on a
|
|
1920x1200 output; tapping the third and then the bottom-right button twice
|
|
adds two images.
|
|
|
|
Three renders cover the drawing paths, and each needs a different ui, so two
|
|
of them are throwaway examples written into the worktree and deleted after:
|
|
|
|
1. `tabs` with that replay — rects, glyphs and images together.
|
|
2. An image alone in a layer, which is the case that failed GPU validation
|
|
when every other test happened to have a rectangle in the same layer.
|
|
3. Six lines of 400px text, which forces the atlas to four pages and proves
|
|
the array grew and its group was rebuilt.
|
|
|
|
Those three become ordinary tests with the harness slice, which is the
|
|
argument for doing it next.
|
|
|
|
## Cautions
|
|
|
|
- Read `/home/bob/repos/ai-app-2/AGENTS.md` and the machine-wide rules first.
|
|
Anything about this machine — the GPU that comes and goes, measuring a small
|
|
performance difference, the emulator — is in `~/.claude/MACHINE.md` and the
|
|
`this-machine-*` skills, and belongs there rather than here.
|
|
- 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.
|
|
|
|
## 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`) |
|
|
| #11 | Atlas as an array texture, and the primitive rendering overhaul (`b234497`) |
|
|
| #13 | Build on wgpu 30 (`00d2230`) |
|
|
| #14 | Rename the `Sized` widget to `SetSize` (`32b1038`) |
|
|
|
|
URLs are `https://git.arirex.me/iris/iris/pulls/{number}`.
|