RUST.md, IRIS.md, IRIS_TODO.md: record I5 -- transcript-ui built and
partial, the recommendation's numbers still missing I5's own box: the seven "hard to get back" behaviours each shown or given a sourced reason, the exact verification commands and results, and what remains (Android integration, touch-drag-vs-selection arbitration, row accessibility names, a tappable link, code-span chip, selection's anchor-row shortcut, code-fence syntax highlighting) -- each also a dated IRIS_TODO.md item so it is not silently dropped. Ticked [~] rather than [x]: the widget-tree half is built and tested, the emulator half is not. "Where things stand" and the Recommendation's item 3 updated in place to say plainly that neither Masonry (E2) nor iris (I5) has produced a render number yet, and why -- not a bad measurement, no measurement obtainable yet on either side -- with the structural findings that do exist (iris now does cross-row selection and per-span inline rich text, neither of which exists in masonry/masonry_core/xilem today) recorded as what currently favours iris absent a number. IRIS.md gets SpanStyle's own entry: what changed, why, and the one thing a future TextBuilderOutput impl must remember (both TextOutput and TextEditOutput apply .spans() -- this box shipped the bug of missing one of the pair once already). Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This commit is contained in:
1 parent
3f25e7ebca
commit
b063fbd7f9
3 files changed
+347
-4
No files matched your search
@@ -42,7 +42,31 @@ session spending an afternoon on them again.
|
||||
below), **E3 (the Kotlin/Java shell over a JNI bridge into Rust, both
|
||||
pass conditions proved on the emulator — see its own box)**, I0a, I0b
|
||||
(iris builds on a pinned nightly and runs), I1 (parley + glyph atlas),
|
||||
I2 (iris on android-view), I3 (`iris::widget::List`).
|
||||
I2 (iris on android-view), I3 (`iris::widget::List`), I4 (host half).
|
||||
- **I5 — the transcript screen in iris: partial, 2026-09-05 (ticked `[~]`
|
||||
in its own box, not `[x]`).** `iris/transcript-ui/` builds a real
|
||||
transcript screen — markdown-folded rows in `iris::widget::List`,
|
||||
cross-row selection, a growing composer, tool-row expand-hold — on top
|
||||
of a new, genuinely useful iris capability this box added:
|
||||
**`SpanStyle`**, per-range text styling (`core/src/primitive/text.rs`),
|
||||
which is what lets one wrapped, selectable `TextEdit` carry a heading,
|
||||
bold, italic, inline code and a link all inside the same paragraph —
|
||||
exactly the inline-rich-text ceiling E2 found Masonry structurally
|
||||
unable to cross. Screenshotted via `run-headless.sh` (real inline
|
||||
styling visible, not just block-level). 9 new tests, all passing;
|
||||
`cargo build/clippy/fmt/test --workspace` and `cargo ndk` (both `iris`
|
||||
and `transcript-ui`) all clean. **What did not happen this pass**: any
|
||||
Android integration for this specific screen (no cdylib/Gradle shell
|
||||
exists for it yet, unlike `tabs-ui`'s `iris-android-app`), and therefore
|
||||
the emulator-side pass condition (`transcript-bench.sh` against the
|
||||
Compose baseline, `ui-trace` tap-by-name on a row) — `emu list` showed
|
||||
the one emulator here held by another session, but the real blocker is
|
||||
that the integration work itself is unbuilt, not the emulator being
|
||||
busy. Touch-drag panning over a row's own rendered text is also not yet
|
||||
reachable, for a specific, diagnosed reason (it competes with this box's
|
||||
own row-level drag-select for the same gesture) rather than an absent
|
||||
primitive. Full accounting, every citation, and the dated
|
||||
IRIS_TODO.md items are in I5's own box below.
|
||||
- **E3 done, 2026-09-05, and unlike E1/E2 it is committed to this repo**
|
||||
(`android-shell/` — a JNI-bridge crate on `client-core` — plus a new
|
||||
Gradle module `app/shellApp/`, left deliberately separate from
|
||||
@@ -591,6 +615,25 @@ light" has a knob inside the same stack.
|
||||
carries the screen within the Compose baseline, it is the app's
|
||||
framework and Masonry was the calibration. If it does not, the
|
||||
measurement says which parts of Masonry to adopt underneath it.
|
||||
|
||||
**Not decidable yet, 2026-09-05 — what's missing, named rather than
|
||||
guessed at.** Neither side of this comparison has a render number:
|
||||
E2 found Masonry's own scroll gesture path absent on Android
|
||||
entirely (its box, "measurable frames"), and I5 built the iris side of
|
||||
the screen (`iris/transcript-ui/`) but not the Android integration
|
||||
around it — no cdylib/Gradle shell exists for this screen yet (unlike
|
||||
`tabs-ui`'s `iris-android-app`, I2), so there is nothing installed on a
|
||||
device for `transcript-bench.sh` to measure against the Compose
|
||||
baseline. What would close this: build that integration (real
|
||||
`client-core` networking against `app/ui-sandbox.sh --delay`, a cdylib
|
||||
+ Gradle module the way I2 did for `tabs-ui`), then run
|
||||
`transcript-bench.sh`'s gesture on both. Until then, the decision rests
|
||||
on the structural findings both sides *did* produce: Masonry cannot do
|
||||
cross-row selection or per-span inline rich text at all today (E2's
|
||||
`grep -rln`, zero hits, cited in its own box), and iris now does both
|
||||
(I5's `SpanStyle` and `selection.rs`) as well as programmatic
|
||||
touch-scroll (I3) — three structural points in iris's favour with no
|
||||
opposing measurement yet on either side.
|
||||
4. Then the shell (E3), the desktop window (E4) and the packaging (E5),
|
||||
which do not depend on the choice.
|
||||
|
||||
@@ -1924,9 +1967,220 @@ silently on real hardware.
|
||||
immediately after the first (attach, detach, attach again) and
|
||||
confirm the process is still alive afterward -- the detach-abort
|
||||
this box's mitigation exists for.
|
||||
- [ ] **I5 — the transcript screen in iris.** E2's pass conditions, all
|
||||
seven behaviours, against the sandbox with `--delay`. This is the
|
||||
point the decision in the recommendation is made at.
|
||||
- [~] **I5 — the transcript screen in iris (2026-09-05). The widget-tree
|
||||
half is built, tested and screenshotted; the emulator half (real
|
||||
device numbers against the Compose baseline) is not -- ticked
|
||||
partial rather than done, see "What remains" at the end of this box.**
|
||||
|
||||
**Where it lives.** `iris/transcript-ui/` (new workspace member,
|
||||
`[lib]`), the same shape as `iris/tabs-ui`: generic over `Rsc:
|
||||
HasEvents` + `Rsc::State: FocusHost` so the same `build()` can run
|
||||
under winit (`transcript-ui/examples/transcript.rs`) or an
|
||||
android-view cdylib later. Depends on `client-core`/`event-model` by
|
||||
path (real code, matching E2's precedent) and `pulldown-cmark`
|
||||
(0.13.4, current stable). Four modules: `markdown.rs` (CommonMark ->
|
||||
plain text + `Vec<SpanStyle>`), `row.rs` (one `iris::widget::List`
|
||||
row per folded `TranscriptRow`), `selection.rs` (cross-row
|
||||
selection), `composer.rs` (the growing input field). `lib.rs`'s own
|
||||
module doc has the screen's shape and the one gap it documents up
|
||||
front (below).
|
||||
|
||||
**New iris API, added in this box and recorded in `IRIS.md`:
|
||||
`SpanStyle`, per-range text styling.** This is the actual answer to
|
||||
RUST.md's E2 finding against Masonry ("rich inline text -- block-level
|
||||
yes, inline no, and both for the same reason":
|
||||
`masonry/src/widgets/text_area.rs:43-44`'s `TextArea::edit_styles()`
|
||||
returns one `StyleSet` for the whole editor, with `// TODO:
|
||||
RichTextInput` beside it). `core/src/primitive/text.rs`'s
|
||||
`TextBuffer` gained `spans: Vec<SpanStyle>` and `set_spans`;
|
||||
`SpanStyle{range, color, family, font_size, bold, italic,
|
||||
underline}` pushes into parley's `RangedBuilder` via `.push(property,
|
||||
range)` instead of only `.push_default(...)`, so one `TextEdit` can
|
||||
carry a heading's bigger bold font, an inline-code span's monospace
|
||||
colour, a link's colour+underline and an ordinary paragraph's base
|
||||
style all in the *same* wrapped, selectable buffer.
|
||||
`core/src/render/atlas.rs`'s `PlacedGlyph` gained a `color: UiColor`
|
||||
field (read from parley's own per-run `Style::brush`,
|
||||
`core/src/primitive/text.rs`'s `TextData::place`) and
|
||||
`core/src/ui/painter.rs`'s `glyphs()` now colours each glyph from
|
||||
that field instead of one colour for the whole `RenderedText` --
|
||||
the change that actually makes a span's colour reach the screen.
|
||||
**Real bug found and fixed while wiring this in**: `TextBuilder`'s
|
||||
`.spans(...)` was only threaded through `TextOutput::run` (the
|
||||
read-only `Text` widget), not the sibling `TextEditOutput::run` (the
|
||||
`TextEdit` every transcript row actually uses) -- a "rule that
|
||||
governs a set belongs to the set, not one member" miss, per
|
||||
CODE_RULES.md; found because `run-headless.sh`'s screenshot showed
|
||||
*no* styling at all despite `markdown.rs`'s own unit tests passing
|
||||
(they only check the string/range logic, not the render path -- see
|
||||
`iris/src/widget/text/build.rs`'s `TextEditOutput::run`, now fixed).
|
||||
|
||||
**The seven behaviours, each shown or given a sourced reason, same
|
||||
structure as E2's own accounting:**
|
||||
|
||||
1. **Selection spanning rows -- shown, with a scoped shortcut
|
||||
recorded rather than hidden.** `selection.rs`'s `Selection`
|
||||
coordinates each visible row's own `TextEditCtx::select`/
|
||||
`select_all`/`deselect` (already built for one field, I2) from a
|
||||
single drag that crosses row boundaries: rows between the anchor
|
||||
and the pointer get `select_all()`, the row under the pointer gets
|
||||
a true partial selection from whichever edge faces the anchor,
|
||||
and `selected_text()` concatenates the result in row order. The
|
||||
one shortcut: the *anchor* row is selected in full once the drag
|
||||
leaves it, rather than "from the click point to its far edge",
|
||||
because that needs the row's own laid-out size and
|
||||
`TextEditCtx`'s `layout()` helper is private
|
||||
(`iris/src/widget/text/edit.rs`) -- see `selection.rs`'s module
|
||||
doc. Pure range-membership logic (`in_range`, mirroring
|
||||
`begin`/`extend`'s row-selection arithmetic) is unit-tested
|
||||
without any render harness; the widget-level wiring is not
|
||||
independently screenshotted this pass (would need a synthetic
|
||||
drag injected into the winit example -- not attempted, time).
|
||||
2. **Rich inline text -- shown, genuinely inline this time.**
|
||||
`markdown::render_markdown` folds one row's whole markdown (not
|
||||
one block at a time) into one string plus spans, so a heading, a
|
||||
**bold** word, *italic* text, `inline code`, and a
|
||||
[link](url) inside the same paragraph render in one `TextEdit`
|
||||
that still wraps and selects as a single buffer --
|
||||
screenshotted, see below. Deliberately not attempted, each
|
||||
recorded at the point it would have gone in `markdown.rs`'s own
|
||||
doc: a background chip behind inline code (needs glyph-run
|
||||
geometry `TextEdit`-internal and not exposed, the same primitive
|
||||
`TextEdit::draw`'s selection highlight uses,
|
||||
`iris/src/widget/text/edit.rs:99`), a tappable link (same missing
|
||||
primitive), a real table layout, and per-token syntax colour
|
||||
inside a fence.
|
||||
3. **Bottom-anchored virtualised list, hold-the-edge on expand --
|
||||
shown**, reusing I3's `List` unmodified. A `TranscriptRow::Tools`
|
||||
row collapses to "N tool calls" and expands to every call's own
|
||||
tool/input/output on tap; `row.rs`'s click handler calls
|
||||
`List::extent(key)` to convert the tap's row-local position into
|
||||
the viewport-relative position `List::note_tap` wants, exactly
|
||||
the two-step contract `list.rs`'s module doc describes for
|
||||
`holdTopEdge`. Not independently screenshotted mid-expand this
|
||||
pass (no input-injection into the desktop example was built) --
|
||||
the mechanism is the same one I3 already benchmarked
|
||||
(`expand-hold`, flat at 0.10-0.11ms across N), applied to real
|
||||
content instead of a synthetic row.
|
||||
4. **The soft keyboard -- inherited from I2, not re-investigated.**
|
||||
The composer (`composer.rs`) is an ordinary `TextEdit` with the
|
||||
same `InputConnection` bridge I2 built and measured (Gboard
|
||||
suggestions over real buffer content); nothing new to add here,
|
||||
and no Android shell exists yet for this screen specifically to
|
||||
re-verify it against (see "What remains").
|
||||
5. **Platform integration -- out of scope by design**, same as E2:
|
||||
E3's list, not this box's.
|
||||
6. **Accessibility names -- shown for the composer, not yet for
|
||||
rows.** The composer field carries `.label("Message")` (I4). Rows
|
||||
do not yet carry per-row labels (a row's own text *is* its
|
||||
accessible content via `TextEdit`'s `access_role`, I4, but
|
||||
nothing calls `.label()` on it, so `Widgets::named()` does not
|
||||
include it) -- a small, real gap, recorded as an IRIS_TODO.md
|
||||
item rather than silently left, since AGENTS.md's bench scripts
|
||||
depend on exactly this for driving a screen by name.
|
||||
7. **Measurable frames / the render-number pass condition -- not
|
||||
attempted, and unlike E2 the reason is not an absent gesture
|
||||
path.** `List` demonstrably scrolls (I3's flat draws/moves,
|
||||
programmatic `scroll()`) and mouse-wheel scrolling is wired here
|
||||
(`lib.rs`'s `CursorSense::Scroll` on `list`). What is *not*
|
||||
reachable yet is a **touch-drag pan starting on a row's own
|
||||
text**: `row.rs` registers `CursorSense::click_or_drag()` on each
|
||||
row's `TextEdit` for selection, and `TextEdit::draw` calls
|
||||
`painter.child_layer()` (`iris/src/widget/text/edit.rs:87`), so
|
||||
`core/src/sense.rs`'s `run_sensors` (which stops at the first
|
||||
layer, checked innermost-first, that consumed the gesture) gives
|
||||
that row first refusal on *every* frame it is pressed, not just
|
||||
the frame the press started -- a row's drag-select wins the same
|
||||
gesture a list-level pan would want. `lib.rs`'s own module doc
|
||||
states this precisely, with the fix named (a press distance/time
|
||||
arbiter deciding pan vs. select before either commits, or gating
|
||||
text-drag-selection behind a long-press). This is a genuine,
|
||||
diagnosed architecture gap this box's *own* two features created
|
||||
by both wanting the same gesture -- not a missing primitive the
|
||||
way Masonry's absent `on_pointer_event` drag handling was.
|
||||
|
||||
**Verification, exact commands and results (2026-09-05, this VM):**
|
||||
|
||||
- `cargo fmt --all -- --check`: clean.
|
||||
- `cargo build --workspace --all-targets`: clean, all six workspace
|
||||
members (`iris`, `iris-core`, `iris-macro`, `tabs-ui`,
|
||||
`transcript-ui`, plus the excluded `android-app`).
|
||||
- `cargo clippy --all-targets` and `cargo clippy -p transcript-ui
|
||||
--all-targets`: zero warnings.
|
||||
- `cargo test --workspace`: 28 tests in `iris`/`iris-core` (all
|
||||
pre-existing, unaffected) + **9 new in `transcript-ui`** -- 5 pure
|
||||
markdown tests (`bold_and_italic_produce_spans_over_the_right_range`,
|
||||
`heading_gets_a_bigger_font_size_span`,
|
||||
`link_is_styled_and_keeps_its_visible_text`,
|
||||
`fenced_code_block_is_monospaced`, a plain-text baseline) and 4
|
||||
selection tests (forward/backward/single-row range arithmetic,
|
||||
plus `unregister_forgets_the_row_and_clears_a_matching_anchor`
|
||||
against a real minimal `TextEdit` in the arena, no window needed --
|
||||
same harness style as `list.rs`'s own tests).
|
||||
- `cargo ndk -t x86_64 -P 26 build -p transcript-ui` and `... clippy
|
||||
-p transcript-ui --lib`: clean (`--lib` only -- the example uses
|
||||
`iris::default`, winit-only by design, same as `iris/examples/
|
||||
tabs`'s own example never having an Android build of itself; the
|
||||
Android-facing entry point is a separate cdylib, not built this
|
||||
pass, see below). `cargo ndk ... build -p iris` / `clippy -p iris`
|
||||
also re-checked clean, since this box touched `iris-core`'s text
|
||||
pipeline.
|
||||
- `run-headless.sh transcript --shot ... -- -p transcript-ui`:
|
||||
renders. Cropped for legibility (this VM has no image viewer --
|
||||
see I3's own note on the same limitation and the throwaway crop
|
||||
tool used here, not committed): a full conversation with a
|
||||
**bold** word, *italic* text, `inline code` in its own colour, a
|
||||
`# Sure` heading rendered visibly larger and bold, a coloured link,
|
||||
a monospaced fenced code block, a collapsed "▸ 3 tool calls" row,
|
||||
and the composer bar at the bottom -- every one of E2's markdown
|
||||
screenshot's features, now inline within single paragraphs rather
|
||||
than block-per-widget. Screenshots at `/tmp/iris_i5_transcript2.png`
|
||||
(full) and crops there, not committed per the standing rule against
|
||||
screenshots of real content leaving this repo -- these are
|
||||
synthetic rows, but the rule is kept uniform regardless.
|
||||
|
||||
**What remains, named rather than silently dropped (also in
|
||||
IRIS_TODO.md, dated 2026-09-05):**
|
||||
|
||||
- **The emulator half of the pass condition was not attempted.**
|
||||
`emu list` shows `emulator-5554` (AVD `ai-app`, a different
|
||||
checkout) held by another session during this pass, but even with
|
||||
a free emulator this needs real Android integration that does not
|
||||
exist yet for this screen: a cdylib + Gradle shell the way
|
||||
`iris-android-app` wraps `tabs-ui` (I2), real
|
||||
`client-core::ApiClient`/`event_stream::follow_session_events`
|
||||
wiring against `app/ui-sandbox.sh` with `--delay` (this crate
|
||||
deliberately does not fetch anything itself, see `lib.rs`'s doc),
|
||||
and then `transcript-bench.sh`'s gesture compared against the
|
||||
Compose baseline. That is real, multi-part follow-on work in its
|
||||
own right -- closer in size to E2/E3 than to "run one more
|
||||
script" -- not something this pass's remaining time could
|
||||
responsibly rush and still report honestly.
|
||||
- **Touch-drag panning over a row's own text** -- behaviour 7 above.
|
||||
- **Row-level accessibility names** -- behaviour 6 above.
|
||||
- **A tappable link and a code-span background chip** -- behaviour 2.
|
||||
- **`Selection`'s anchor-row shortcut** -- behaviour 1.
|
||||
- **No syntax highlighting inside a fenced code block** -- `markdown.rs`
|
||||
notes `client_core::highlight` exists and could feed this.
|
||||
- **`row.rs`'s tool-row expand and `selection.rs`'s cross-row drag
|
||||
are not independently screenshotted/driven** -- covered by reading
|
||||
and by the primitives they reuse (I3's `List` tests, this box's
|
||||
own unit tests), not by a dedicated repro this pass.
|
||||
|
||||
**Net for the recommendation.** Item 3 ("decide when the transcript
|
||||
screen exists in both, from the measurements") still cannot be
|
||||
decided by a number -- E2 could not produce one for Masonry, and I5
|
||||
has not yet produced one for iris either, for an unrelated reason
|
||||
(no Android harness built yet, not an absent capability). What *can*
|
||||
be said structurally, updating E2's own conclusion: iris now also
|
||||
demonstrates the two things E2 found Masonry structurally unable to
|
||||
do at all -- cross-row selection and true per-span inline rich text
|
||||
inside one wrapped, selectable buffer -- neither of which exists
|
||||
anywhere in `masonry`/`masonry_core`/`xilem` today (E2's own
|
||||
`grep -rln` finding). That is a second structural point in iris's
|
||||
favour, alongside I2's working touch-scroll-vs-Masonry's-absent one,
|
||||
still short of the render-number comparison the recommendation
|
||||
ultimately wants.
|
||||
|
||||
## For the next agent
|
||||
|
||||
|
||||
Reference in new issue
Block a user