Cap what the transcript draws, and let a LazySpan clip itself
Four things Iris asked for on 2026-09-08.
**A LazySpan no longer cares about masks.** It asserted that something
around it had called `.masked()` and refused to draw otherwise, which is
why a plain full-screen list -- the benchmark, any simple app -- panicked.
It cared only because it draws a row straddling an edge in full and relied
on somebody else to cut off the overhang; it clips itself to the box it
was offered now. Strictly stronger than the assert, which a mask *larger*
than the list's box satisfied while letting the overhang through anyway --
the fault it was written for. The transcript's `.masked()` wrapper goes
with it, and `Painter::is_masked` with that.
**Everything on the transcript screen is capped.** One rule in one place,
`client_core::text_cap`, mirrored as `TextCap.kt` with the same numbers so
a bench comparing the apps compares renderers rather than policies:
a tool call's input 80 lines or 4 KiB -> "Show all N lines"
a tool call's output 80 lines or 4 KiB -> (already was, in iris)
a message 200 lines or 16 KiB -> "Show all N lines"
The input is what the edit-card report needed: an Edit's old_string and
new_string arrive whole and are routinely the biggest text on screen.
Messages are capped in both apps, user and agent alike.
Three rules that took a screenshot to get right. A message is cut on a
block boundary, never mid-block -- cut to its own opening line a fence
renders as an empty panel, which reads as a fault rather than as a cap --
except a message that is one enormous block, which is truncated, since
dropping it would leave the row blank. A reply still streaming is never
capped. And the input's two blocks share one "Show all", while input and
output have their own.
**Compose stops wrapping raw text**, per Iris's call: a tool's leftover
input fields and its output pan sideways like the command already did.
`on_tap` and hold-the-edge move to `transcript-ui/src/tap.rs`, since a
message's "Show all" needs exactly what a tool card's tap already had.
This commit is contained in:
1 parent
1318e149f5
commit
afbc2ad132
18 files changed
+1254
-272
No files matched your search
+58
-15
@@ -12,7 +12,56 @@ things still stay out.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-08 (newest): redrawing one widget cost O(its own primitives squared)
|
||||
## 2026-09-08 (newest): a `LazySpan` clips itself, and nothing is unbounded
|
||||
|
||||
Four things you asked for, in one change.
|
||||
|
||||
**A lazy span no longer cares about masks.** It used to assert that
|
||||
something around it had called `.masked()` and refuse to draw otherwise,
|
||||
which is why a plain full-screen list -- the benchmark, any simple app --
|
||||
panicked on its second line. Your question was the right one: it cared
|
||||
only because it draws a row straddling an edge *in full* (virtualisation
|
||||
decides which rows, never how much of one) and relied on somebody else to
|
||||
cut off the overhang. It clips itself to the box it was offered now, so a
|
||||
caller places it like any other widget. That is also strictly stronger
|
||||
than the assert was: a mask *larger* than the list's box satisfied
|
||||
`is_masked` and let the overhang through anyway, which is the fault the
|
||||
assert was written for. The transcript's own `.masked()` wrapper is gone
|
||||
with it.
|
||||
|
||||
**Everything on the transcript screen is capped now.** One rule in one
|
||||
place -- `client_core::text_cap`, mirrored as `TextCap.kt` with the same
|
||||
numbers, so a bench comparing the apps compares renderers and not
|
||||
policies:
|
||||
|
||||
a tool call's input 80 lines or 4 KiB -> "Show all N lines"
|
||||
a tool call's output 80 lines or 4 KiB -> (already was)
|
||||
a message 200 lines or 16 KiB -> "Show all N lines"
|
||||
|
||||
The input is what your edit card needed: an `Edit`'s `old_string` and
|
||||
`new_string` arrive whole and are routinely the biggest text on screen.
|
||||
Messages are capped for the reason you gave -- both user and agent, in
|
||||
both apps.
|
||||
|
||||
Three rules that took a screenshot to get right. A message is cut on a
|
||||
**block boundary**, not mid-block: cut to its own opening line a fence
|
||||
renders as an empty panel, which reads as a fault rather than as a cap
|
||||
(the exception is a message that is *one* enormous block, which is
|
||||
truncated, since dropping it would leave the row blank). A reply still
|
||||
streaming is **never** capped, because a row that stopped growing at two
|
||||
hundred lines while the model was plainly still writing reads as the
|
||||
stream having died. And the input's two blocks share **one** "Show all",
|
||||
since they are two halves of one answer -- while input and output have
|
||||
their own, since wanting the whole of a `new_string` says nothing about
|
||||
wanting the whole of the build log under it.
|
||||
|
||||
**The Compose app does not wrap raw text any more**, per your call: a
|
||||
tool's leftover input fields and its output pan sideways like the command
|
||||
already did. A wrapped log destroys the column alignment that carried its
|
||||
meaning, one line at a time and only on the long lines -- so iris was
|
||||
right and Compose was the one to change.
|
||||
|
||||
## 2026-09-08: redrawing one widget cost O(its own primitives squared)
|
||||
|
||||
Your report -- expanding a tool card with a long horizontally-scrolling
|
||||
edit in it lags -- is a framework defect, not a text-layout one, and the
|
||||
@@ -39,21 +88,15 @@ where that slot's handle sits in its owner's `ActiveData::primitives`
|
||||
|
||||
`benches/message_list.rs` grew scenario **(g)** for it, and the number to
|
||||
read is per-glyph: flat as N grows is the pass condition, and a total
|
||||
hides it. Two things about that file: it also caught the quadratic in
|
||||
`--phone`-sized text, and it had stopped running at all -- scenarios (a)
|
||||
and (e) built a `LazySpan` with no mask around it, which the span now
|
||||
asserts against, so the whole benchmark panicked on its second line.
|
||||
Fixed in the same change.
|
||||
hides it. That file had also stopped running at all -- scenarios (a) and
|
||||
(e) built a `LazySpan` with no mask around it, which the span asserted
|
||||
against, so the whole benchmark panicked on its second line. Worked around
|
||||
here and fixed properly in the entry above, which deletes the assert.
|
||||
|
||||
**What this does not fix, and what I would do next.** An open card still
|
||||
shapes, rasterises and submits *every* glyph of its input and output, not
|
||||
the screenful you can see -- iris does not cull within a widget, and a
|
||||
tool card is the one place that bites, because an `Edit`'s `old_string`
|
||||
and `new_string` go onto the card whole. The output half is already capped
|
||||
(`OUTPUT_LINES`/`OUTPUT_BYTES` in `tool.rs`, 80 lines or 4 KiB behind a
|
||||
"Show all"); the *input* half has no cap at all, and that is the
|
||||
asymmetry to close. Wrapping the block, which you asked for, changes the
|
||||
shape but not this cost: the same glyphs are laid out either way.
|
||||
**What this does not fix**, and what the entry below closes: an open card
|
||||
still shapes, rasterises and submits *every* glyph of its input and
|
||||
output, not the screenful you can see. iris does not cull within a widget,
|
||||
so the only bound available is a cap on what goes in.
|
||||
|
||||
## 2026-09-08: one `ScrollController`, a `Scrollable` trait, and `Pin`
|
||||
|
||||
|
||||
Reference in new issue
Block a user