iris: a Rect is not size-independent, and P1a's block appearance verified
The defect P1a's screenshots found, and the one that mattered: `Rect::is_size_independent()` answered `true`. A `Rect` fills whatever region it is handed, so its content *is* the region -- and `draw_inner`'s fast path, which rewrites a widget's primitives with `r.outside(&from).within(®ion)` instead of redrawing it, cannot reproduce that once a region carries both `rel` and `abs`. What it looked like: a fenced code block's background kept the height of the provisional full-region draw `Span` does in its first phase, so one fence's panel covered every block below it and every row below that, with the text underneath laid out correctly. Likely the same cause as RUST.md's older "the composer bar's grey background is not drawn". Also here: a quote's bar is a `Stack` background behind padded text rather than a two-child `Span(Dir::RIGHT)` (one widget fewer and no provisional pass), and `transcript-ui`'s `transcript` example gains a row holding one of every block kind -- the fixture's own heading, paragraph, fence and table source, plus a list and a quote, which the fixture has neither of. docs/bench/p1a-2026-09-06/ has the pairs and docs/RUST.md's P1a box names what still differs. The iris half is from the desktop backend because this emulator cannot draw iris's glyphs at all (solid boxes, reproduced on the previous commit, with Compose drawing text correctly on the same AVD); both routes to Vulkan on this AVD were tried and both fail. Bench stream phase, assertions live, no abort: p50 53.0ms p90 108.6ms p99 132.0ms against 52.8/108.1/137.3 before -- unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
64f64b54e5
commit
69525bd131
11 files changed
+355
-34
No files matched your search
+135
-11
@@ -5465,17 +5465,141 @@ device.
|
||||
comparison fair. **Sub-order, decided by the design agent, by what
|
||||
the bench fixture exercises and Compose already draws** (each is
|
||||
one agent; tick and date in place):
|
||||
- [ ] **P1a — markdown block rendering parity.** Now that a row is
|
||||
a column of per-block widgets (`e1030d6`), draw each block
|
||||
the way Compose does: headings at their sizes, fences in a
|
||||
mono face with `client-core`'s `highlight` spans and a
|
||||
distinct background, bullet/numbered lists with indent,
|
||||
tables as aligned columns, block quotes, links as tappable
|
||||
spans (opening through the platform — the "tappable link"
|
||||
primitive in IRIS_TODO's "Build (for the port)"). Against
|
||||
`app/bench-fixture/`, screenshot beside the Compose bench
|
||||
build on the same emulator. Pure parts (block → style
|
||||
mapping) tested in `client-core`/`transcript-ui`.
|
||||
- [x] **P1a — markdown block rendering parity.** Done 2026-09-06.
|
||||
Each top-level block is drawn in one of **three frames**
|
||||
(`transcript-ui::markdown::BlockFrame`, mapped from
|
||||
`BlockKind` by the pure `frame_of`): `Plain` (a paragraph,
|
||||
heading, list or rule -- text and spans, no extra widget),
|
||||
`Verbatim { fill }` (a fence or a table -- a rounded panel
|
||||
that does not wrap and pans sideways, `CodeFence.kt`'s
|
||||
`horizontalScroll`), and `Quote` (a bar behind text padded
|
||||
past it). Everything else markdown can say is expressed in
|
||||
`SpanStyle`s, which cost no widgets.
|
||||
**What each block looks like now, against `Markdown.kt`:**
|
||||
- *Headings* -- Material's own ladder, the six sizes
|
||||
`markdownTypography` picks (24/22/16/14/12/11 at a 16pt
|
||||
body), bold. Was a three-step 28/24/21/19.
|
||||
- *Fences* -- monospace on Mocha Crust, rounded, with
|
||||
`client_core::highlight`'s spans by language in the same
|
||||
Catppuccin palette `Theme.kt`'s `catppuccinSyntax()` uses.
|
||||
An unknown language is plain rather than coloured by the
|
||||
nearest one. A fence being streamed into re-renders only
|
||||
the last block (`RowBlocks::apply_delta`), so earlier
|
||||
fences are never re-scanned.
|
||||
- *Lists* -- the bullet ladder `MarkdownPieces.kt` draws
|
||||
(disc/ring/square by depth) and ordered lists counting from
|
||||
the number written, markers in Lavender.
|
||||
- *Tables* -- padded monospace columns measured from the
|
||||
cells, header bold, a rule under it, on Surface 0. A real
|
||||
grid was rejected; docs/DECISIONS.md, 2026-09-06, has why.
|
||||
- *Quotes* -- a Surface 2 bar down the left, text one shade
|
||||
back from body.
|
||||
- *Links* -- coloured and underlined as before, and now
|
||||
**tappable**: `GestureOutcome::Tapped` (a press that
|
||||
committed to neither a pan nor a selection),
|
||||
`TextEditCtx::byte_at` for which byte, and
|
||||
`iris::platform::OpenUrl` for the platform (`xdg-open`/
|
||||
`open`/`start`; on Android an `ACTION_VIEW` intent deferred
|
||||
to `after_input`, the shape `pending_show_keyboard` uses).
|
||||
**Screenshots: `docs/bench/p1a-2026-09-06/`.**
|
||||
`compose-heading-fence-table.png` and
|
||||
`compose-fence-table.png` are the Compose `bench` build on
|
||||
this checkout's AVD against `app/bench-fixture/`;
|
||||
`iris-blocks.png` is iris rendering the same heading,
|
||||
paragraph, link, fence and table source (plus a list and a
|
||||
quote, which the fixture has neither of) from
|
||||
`transcript-ui`'s own `transcript` example.
|
||||
**Why the iris half is not from the emulator**, which the
|
||||
pass condition asked for: **the emulator cannot draw iris's
|
||||
glyphs at all.** Every character comes out as a solid filled
|
||||
box of the right width -- `iris-emulator-gles-glyphs.png`.
|
||||
Established as *not* this change's doing and not the app's:
|
||||
the previous commit (`20303e0`) draws the same boxes, and the
|
||||
Compose bench build on the same AVD in the same minute draws
|
||||
text perfectly. The atlas sample's alpha reads as 1 under
|
||||
`-gpu host` + `-feature -Vulkan` (Mesa 26.2.2 / virgl), which
|
||||
is what an *incomplete* GL texture returns (0,0,0,1). Both
|
||||
ways out were tried and both fail: `GPU_HOST_FEATURES=" "`
|
||||
still dies at boot with gfxstream's documented "Format
|
||||
VK_FORMAT_R8G8B8A8_UNORM is not supported ... Failed to find
|
||||
memory type for ColorBuffers", and `EMU_GPU=software` does
|
||||
give the guest SwiftShader Vulkan but iris **SIGSEGVs inside
|
||||
`surface_changed`** on it. So the appearance half of this box
|
||||
is taken on the desktop/winit backend, which renders on the
|
||||
host's real GPU through `iris/run-headless.sh`.
|
||||
**What still differs, pair by pair:**
|
||||
1. *Colour, on the desktop shot only.* The winit surface is
|
||||
sRGB and the shader writes the palette's bytes as linear,
|
||||
so every fill reads ~4x lighter: Crust (17,17,27) comes out
|
||||
(73,73,91), measured. Not a palette error and not present
|
||||
on Android, where the previous pass measured the composer
|
||||
bar at rgb(41,40,49) for a declared (40,40,46). Worth its
|
||||
own item; it makes the desktop build a poor colour
|
||||
reference until fixed.
|
||||
2. *A list's wrapped line.* Compose lays an item out as a
|
||||
marker column beside a text column, so a second line stays
|
||||
indented; iris writes the marker into the same buffer, so
|
||||
a wrapped line returns to the left margin. Needs per-line
|
||||
indent in `TextAttrs`.
|
||||
3. *A table.* Compose draws a real grid, cells wrapping at a
|
||||
136dp floor; iris draws padded monospace columns. Same
|
||||
information, different picture.
|
||||
4. *Inline code.* Compose draws a chip behind it; iris gives
|
||||
the range a monospace face and the code colour. Unchanged
|
||||
by this box -- still blocked on per-range glyph geometry
|
||||
(IRIS_TODO).
|
||||
5. *A user message.* Compose draws it in a rounded card;
|
||||
iris draws a sender label above plain text. That is the
|
||||
row's own styling, P1's rather than P1a's.
|
||||
**One real defect found and fixed on the way**, and it is
|
||||
not a small one: **`Rect::is_size_independent()` answered
|
||||
`true`.** A `Rect` fills whatever region it is given, so its
|
||||
content *is* the region -- and `draw_inner`'s
|
||||
size-independent fast path, which rewrites a widget's
|
||||
primitives with `r.outside(&from).within(®ion)` instead of
|
||||
redrawing, cannot reproduce that remap once a region carries
|
||||
both `rel` and `abs`. The visible result: a fenced block's
|
||||
background kept the height of the **provisional full-region
|
||||
draw** `Span` does in its first phase, so one fence's panel
|
||||
covered every block below it *and every row below that*,
|
||||
while the text underneath was laid out correctly. It answers
|
||||
`false` now (`iris/src/widget/rect.rs`, with the account at
|
||||
the definition). This is very likely the same family as this
|
||||
file's older "the composer bar's grey background is not
|
||||
drawn" note and any other `.background(rect(..))` tint.
|
||||
**One defect found and left open**, with its repro:
|
||||
`UiRenderState::reposition`'s debug assert -- *"widget ... is
|
||||
both moved by its parent's own layout (`mov`) and
|
||||
repositioned within it"* -- fires from `List::place` when a
|
||||
transcript row's blocks **wrap**. Reproduce in one line:
|
||||
change `.wrap(!verbatim)` to `.wrap(true)` in
|
||||
`transcript-ui/src/row.rs`'s `build_block` and run
|
||||
`iris/run-headless.sh transcript --shot /tmp/x.png -- -p
|
||||
transcript-ui`. It is *not* caused by the `Rect` fix above
|
||||
(it survives it) and not by any one block kind (bisected: it
|
||||
appears once the row is tall enough). The shipping
|
||||
configuration does not reach it -- verbatim blocks do not
|
||||
wrap -- and neither does the Android bench, which ran clean
|
||||
with the assertions live. It should be the next thing looked
|
||||
at under P1, because it is a real inconsistency about who
|
||||
owns a widget's move slot, not a false alarm.
|
||||
**Bench, stream phase, this checkout's AVD, debug x86_64
|
||||
`force-gles`, assertions live, no abort:**
|
||||
`stream: 294 frames over 21.0s, late 283 (96.3%), p50 53.0ms
|
||||
p90 108.6ms p99 132.0ms` against the pre-P1a
|
||||
`p50 52.8ms p90 108.1ms p99 137.3ms` -- unchanged, which is
|
||||
the point: block styling is span work, not layout work. The
|
||||
`worst` figure is the one number that moved and it does not
|
||||
reproduce: 567.3ms, 140.1ms and 664.5ms across three runs of
|
||||
the same build, against 148.9ms before. Unexplained; it is a
|
||||
single frame in 294 and the percentiles are flat, so it reads
|
||||
as an emulator hiccup rather than a cost, but it is written
|
||||
down rather than rounded off.
|
||||
**Checks**: `cargo fmt --all --check` clean in both
|
||||
workspaces; `cargo clippy -p iris -p iris-core -p
|
||||
transcript-ui -p desktop-app -p tabs-ui --all-targets`
|
||||
warning-free; `cargo test` 85 (iris, +4) + 13 (iris-core) +
|
||||
31 (transcript-ui, +11) + 123 (client-core).
|
||||
- [ ] **P1b — tool-call cards and grouping.** `ToolRows.kt`/
|
||||
`ToolInput.kt`'s cards: a collapsed row per call with name
|
||||
and a one-line summary, expand to input and output, runs of
|
||||
|
||||
Reference in new issue
Block a user