diff --git a/docs/IRIS_EXTRACTION_HANDOFF.md b/docs/IRIS_EXTRACTION_HANDOFF.md index 319006d..52b2f56 100644 --- a/docs/IRIS_EXTRACTION_HANDOFF.md +++ b/docs/IRIS_EXTRACTION_HANDOFF.md @@ -3,106 +3,70 @@ 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 +## Where things stand -**#11 is merged** (`b234497` on canonical `main`, 2026-09-13). Open now are -#12 (pointer routing, never reviewed) and #13 (wgpu 30). Check for a review -on either before starting anything new: +Canonical `main` is **`00d2230`** (#13, wgpu 30). Eleven slices are in; two +pull requests are open and both are waiting on the owner, not on me: + +- **#12** `split/12-pointer-routing`, worktree `/home/bob/repos/iris-pr12`, + head `71ba372`. A widget takes only the input it answers to, and only where + the cursor is. Self-reviewed under the process below, which found two + defects: consumption decided once a frame rather than per input kind, and a + press firing on a widget the cursor had just left. Six tests across layers; + three of them fail on `main`. +- **#14** `split/14-setsize`, worktree `/home/bob/repos/iris-pr14`, head + `f312db6`. The `Sized` widget becomes `SetSize`, so the prelude stops + shadowing the marker trait and `?Sized` resolves again in any crate that + imports it. `tests/prelude_bounds.rs` fails to compile if that comes back. + +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/13/reviews -``` Check for one before doing anything -else: - -```sh -TOKEN=$(cat ~/.config/gitea/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/11/reviews + https://git.arirex.me/api/v1/repos/iris/iris/pulls/$N/reviews//comments 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 + https://git.arirex.me/api/v1/repos/iris/iris/issues/$N/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. +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 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). +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. -## Stop point +**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. -- 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`. -- Canonical `main` is `00d2230` (#13, wgpu 30). `/home/bob/repos/iris-pr13` - is a merged branch now; do not build new work on it. -- **#14** `split/14-setsize`, worktree `/home/bob/repos/iris-pr14`, head - `f312db6`, one commit on `00d2230`. The `Sized` widget becomes `SetSize`, - which stops the prelude shadowing the marker trait. Not reviewed yet. -- **#12** `split/12-pointer-routing`, worktree `/home/bob/repos/iris-pr12`, - head `71ba372`, current `main` merged in. A widget takes only the input it - answers to, and only where the cursor is. Self-reviewed under the new - process, which found two defects: consumption decided once a frame rather - than per kind, and a press firing on a widget the cursor had just left. - Six tests across layers; three fail on `main`. Waiting on the owner. -- `/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. -- **Each primitive records its own draws.** `Primitive::render` makes a - `PrimitiveRender` that states the layout its shader reads, uploads what 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. The renderer knows nothing about textures. - **Measured**, because dispatch per list was the objection: 6 instructions - per list, 0.1% of a frame at 256 and at 1024 layers, against the ~5,400 - wgpu spends recording one. `tests/draw_cost.rs` is that measurement. -- **`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 +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 @@ -111,28 +75,94 @@ shape: 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. +- **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. -## The review gate +## The next slice -`~/.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. +**The headless rig and the test harness.** `scripts/run-headless.sh`, +`headless.conf`, `rig-input` and `iris::harness` live in ai-app's iris +submodule and not in canonical Iris. Until they are extracted, Iris can only +be tested by what runs without a window, and every rendering claim is verified +by hand from another checkout. Extracting them is what lets later slices +arrive with tests instead of screenshots. Nothing else is claimed. -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. +Still in the target, roughly in dependency order: -`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. +- **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, and **the archived copy carries the same per-frame + consumption defect #12 removes** — extracting it as-is brings the bug back, + so `tests/pointer_routing.rs` is the acceptance criterion for that 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 @@ -157,11 +187,65 @@ complaint is verbose agent comments; the default verdict is delete. ```sh cd /home/bob/repos/iris && git fetch upstream -git worktree add -b split/13-name /home/bob/repos/iris-pr13 upstream/main +git worktree add -b split/15-name /home/bob/repos/iris-pr15 upstream/main ``` -The other `/home/bob/repos/iris-pr*` worktrees hold merged branches. They are -readable references; do not build new work on them. +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 +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 ` down|move|up ` 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 @@ -180,96 +264,3 @@ readable references; do not build new work on them. | #13 | Build on wgpu 30 (`00d2230`) | URLs are `https://git.arirex.me/iris/iris/pulls/{number}`. - -## Choosing the next slice - -**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 the order is by how much else depends on a thing, not by what is nearest -ready, and a slice lands with tests rather than with a script in `/tmp`. - -1. **wgpu 28.0.0 -> 30.0.1.** Done, open as #13. - `winit` stays on 0.30.12 until 0.31 is out of prerelease; `parley` 0.11.1 - is current. The API changes were: the instance takes the display handle it - presents on (GLES on Wayland needs it), `get_current_texture` returns a - status enum rather than a `Result`, presenting moved to the queue, and - bind group and vertex buffer layouts became sparse. -2. **Next: the headless rig and the test harness** -- `scripts/run-headless.sh`, - `headless.conf`, `rig-input` and `iris::harness` live in ai-app's iris - submodule, not in canonical Iris. Until they are extracted, canonical Iris - can only be tested by the four `tests/text_edit.rs` tests, and anything - about rendering is verified by hand from another checkout. Extracting them - is what lets every later slice arrive with tests. -3. Everything below, once those two are in. - -Still in the target and not yet claimed: - -- **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, and **the archived copy carries the same per-frame - consumption defect #12 removes** -- extracting it as-is brings the bug back, - so `tests/pointer_routing.rs` is the acceptance criterion for that 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. - -```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. - -## 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.