Prune Iris TODO and prioritize color correctness
This commit is contained in:
1 parent
8c6e2ed9cf
commit
5428cd75c9
4 files changed
+123
-335
No files matched your search
@@ -1,15 +1,8 @@
|
||||
package dev.iris.android.demo;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -77,80 +70,4 @@ public final class IrisView extends RustView {
|
||||
activity.setContentView(scroll);
|
||||
}
|
||||
|
||||
private static final String DIAGNOSTICS_OVERLAY_TAG = "iris-diagnostics-overlay";
|
||||
|
||||
/**
|
||||
* The bench build's keyboard diagnostics capture
|
||||
* (`bench_client.rs`'s `on_insets_changed` /
|
||||
* `capture_keyboard_diagnostics`, via `bench_jni.rs`'s
|
||||
* `PlatformHandle::show_diagnostics_overlay`): unlike
|
||||
* `showRendererError` above, this adds a panel *over* this view
|
||||
* (`MainActivity`'s `FrameLayout` still holds `IrisView` underneath,
|
||||
* running) rather than replacing the activity's content, and gives it
|
||||
* a Copy button and a Close that removes the panel -- so it draws
|
||||
* (and can be read) whether or not iris itself is still putting
|
||||
* anything on screen, without abandoning the session that produced
|
||||
* it. Runs on the UI thread regardless of which thread calls it,
|
||||
* since the call comes from a background task (a delayed capture
|
||||
* after the keyboard opens), and touching the view tree off the UI
|
||||
* thread is undefined.
|
||||
*/
|
||||
void showDiagnosticsOverlay(String report) {
|
||||
Context context = getContext();
|
||||
if (!(context instanceof Activity)) {
|
||||
return;
|
||||
}
|
||||
Activity activity = (Activity) context;
|
||||
activity.runOnUiThread(() -> {
|
||||
ViewGroup parent = (ViewGroup) getParent();
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
View existing = parent.findViewWithTag(DIAGNOSTICS_OVERLAY_TAG);
|
||||
if (existing != null) {
|
||||
parent.removeView(existing);
|
||||
}
|
||||
|
||||
float density = activity.getResources().getDisplayMetrics().density;
|
||||
int pad = (int) (16 * density);
|
||||
|
||||
LinearLayout overlay = new LinearLayout(activity);
|
||||
overlay.setTag(DIAGNOSTICS_OVERLAY_TAG);
|
||||
overlay.setOrientation(LinearLayout.VERTICAL);
|
||||
overlay.setBackgroundColor(0xEE000000);
|
||||
overlay.setPadding(pad, pad, pad, pad);
|
||||
|
||||
TextView text = new TextView(activity);
|
||||
text.setText(report);
|
||||
text.setTextIsSelectable(true);
|
||||
text.setTextColor(0xFFFFFFFF);
|
||||
ScrollView scroll = new ScrollView(activity);
|
||||
scroll.addView(text);
|
||||
overlay.addView(scroll, new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
LinearLayout buttonRow = new LinearLayout(activity);
|
||||
buttonRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
buttonRow.setPadding(0, pad, 0, 0);
|
||||
|
||||
Button copy = new Button(activity);
|
||||
copy.setText("Copy");
|
||||
copy.setOnClickListener(v -> {
|
||||
ClipboardManager clipboard =
|
||||
(ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard != null) {
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("iris diagnostics", report));
|
||||
}
|
||||
});
|
||||
Button close = new Button(activity);
|
||||
close.setText("Close");
|
||||
close.setOnClickListener(v -> parent.removeView(overlay));
|
||||
buttonRow.addView(copy);
|
||||
buttonRow.addView(close);
|
||||
overlay.addView(buttonRow);
|
||||
|
||||
parent.addView(overlay, new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -222,31 +222,4 @@ impl PlatformHandle {
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows `report` in the shell's plain-view diagnostics overlay
|
||||
/// (`IrisView.showDiagnosticsOverlay`) -- a real `TextView` plus Copy
|
||||
/// and Close controls, added over whatever iris itself is drawing
|
||||
/// rather than replacing it (unlike `android::view::show_renderer_error`,
|
||||
/// which exists for the case the renderer can never recover from and
|
||||
/// intentionally never returns). Called from a background task after
|
||||
/// the keyboard-open delay (`bench_client.rs`'s `on_insets_changed`),
|
||||
/// so the Java side hops onto the UI thread itself before touching the
|
||||
/// view tree -- see that method's own comment.
|
||||
pub fn show_diagnostics_overlay(&self, report: &str) -> bool {
|
||||
self.try_show_diagnostics_overlay(report).is_some()
|
||||
}
|
||||
|
||||
fn try_show_diagnostics_overlay(&self, report: &str) -> Option<()> {
|
||||
let mut guard = self.vm.attach_current_thread().ok()?;
|
||||
let env: &mut JNIEnv = &mut guard;
|
||||
let jreport = env.new_string(report).ok()?;
|
||||
env.call_method(
|
||||
self.view.as_obj(),
|
||||
"showDiagnosticsOverlay",
|
||||
"(Ljava/lang/String;)V",
|
||||
&[JValue::Object(jreport.as_ref())],
|
||||
)
|
||||
.ok()?;
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
+67
-180
@@ -12,170 +12,63 @@ and six phone-report sections went on 2026-09-08 for that reason.
|
||||
|
||||
## Fix
|
||||
|
||||
- [ ] **Where the scroll *pin* lives.** The rest of "scrolling moves out
|
||||
of the list" landed on 2026-09-08 -- `List` is `LazySpan`, the physics
|
||||
and the gesture live in one `ScrollController`, `.scrollable()` is the
|
||||
only way anything scrolls, and **`docs/SCROLL.md` is the standing
|
||||
reference**; read that rather than reconstructing it here.
|
||||
What is left is one design question. The pin ("stay at the end as rows
|
||||
are appended") is still each widget's own: `Scroll` has `snap_end` for
|
||||
an ordinary child, `LazySpan` has one for itself, and the constructor
|
||||
argument sets each. Iris asked for `amt` and "other controls (iirc only
|
||||
at end for now)" to live in `Scroll` so a caller always edits the
|
||||
`Scroll`; that is done for `amt` and not for the pin, because a pin has
|
||||
to be *applied* when a row is appended -- between frames, with no
|
||||
painter in hand -- so moving it needs either a fourth `Widget` method or
|
||||
a parameter on `apply_scroll`. Nothing external edits a pin today (the
|
||||
transcript sets it once at construction and calls `jump_to_end` on the
|
||||
span for the rest), so this is a design question rather than a missing
|
||||
capability.
|
||||
- [ ] **A read-only text display has no widget of its own — P0's bench
|
||||
report area is a `TextEdit` standing in for one (2026-09-05).** The only
|
||||
way to get selectable text on screen today is `.editable(...)` plus
|
||||
`.attr::<Selectable>(())` (`Selectable` is only implemented for
|
||||
`TextEdit`, `iris/src/attr.rs`), which also makes the field focusable —
|
||||
tapping the bench report opens the soft keyboard over text nothing lets
|
||||
you type into. Harmless for a bench-only debug screen (not fixed this
|
||||
pass), but a real "selectable, not editable" text primitive would
|
||||
remove the keyboard side effect and is worth having before another
|
||||
screen wants the same thing (P1's own transcript rows already read
|
||||
their content from a `TextEdit` for the same reason).
|
||||
- [ ] **Colours are not in a defined colour space. Fix this before a
|
||||
styling pass.** Both render backends prefer an sRGB surface
|
||||
(`default/render.rs` and `android/render.rs`), while `fs_main` returns
|
||||
`unpack4x8unorm` palette and image bytes unchanged. An sRGB attachment
|
||||
treats those values as linear and encodes them again: Mocha Crust
|
||||
(17,17,27) became (73,73,91) in a desktop screenshot measured on
|
||||
2026-09-06.
|
||||
|
||||
## Build
|
||||
The earlier entry called this desktop-only because the Android picture
|
||||
looked right. That was not a measurement: neither the startup line nor
|
||||
the diagnostics report records the selected surface format, and the
|
||||
Android backend contains the identical preference and shader path. A
|
||||
device exposing only a non-sRGB surface can happen to hide the bug; it
|
||||
does not make the pipeline correct.
|
||||
|
||||
- [ ] **Positions as a single float per scroll.** Iris raised, and half
|
||||
rejected, letting a scroll update one float rather than positions:
|
||||
input handling cares about most elements in a list, so absolute
|
||||
positions must be computed on the CPU anyway. LAYOUT.md's design
|
||||
already lands here (GPU walks the chain, CPU resolves on demand for
|
||||
hit tests). Keep the CPU resolution lazy and per query; do not
|
||||
materialise every row's absolute position per frame.
|
||||
- [ ] **Animations, last.** Cosmetic, so after everything above. Must be
|
||||
**modular — a piece of the library rather than a core part forced into
|
||||
everything, the same way input is**. Whatever the mechanism, a widget
|
||||
that does not animate must pay nothing and import nothing for it.
|
||||
|
||||
## Found by P1a (2026-09-06)
|
||||
|
||||
- [ ] **Desktop colours are washed out: the winit surface is sRGB and
|
||||
the shader writes the palette's bytes as linear.** Mocha Crust
|
||||
(17,17,27) is drawn as (73,73,91), measured off
|
||||
`run-headless.sh --shot`. Android is correct, so this is the surface
|
||||
format rather than the palette -- but it makes the desktop build
|
||||
useless as a colour reference, which is exactly what P1a needed it for
|
||||
when the emulator could not draw glyphs.
|
||||
- [ ] **The bench report pane draws over the transcript rows instead of
|
||||
replacing them.** Visible on the emulator for the first time now that
|
||||
glyphs render there (`/tmp/emu-final.png`, 2026-09-06): after a bench
|
||||
run the report's lines and the transcript's occupy the same rows in the
|
||||
top third of the screen, both legible, neither on top. Pre-existing --
|
||||
the same overlap is in a screenshot taken before the move-slot fix -- so
|
||||
it is its own item, most likely the report pane not masking or not
|
||||
claiming its region.
|
||||
|
||||
## Found by P1b (2026-09-06), all with a headless repro
|
||||
|
||||
Each was found by looking at `iris/run-headless.sh transcript -- -p
|
||||
transcript-ui` rather than at a diff. docs/RUST.md's P1b box has the
|
||||
fuller account.
|
||||
|
||||
**No entry here is worked around any more** (Iris, 2026-09-08: "All of
|
||||
those should be fixed. There should never be workaround code. Do the
|
||||
same for those; fix them if they're trivial, diagnose and report if
|
||||
not."). Two are fixed and ticked; the two that are left are missing
|
||||
*capabilities* rather than defects being dodged, and each carries its
|
||||
diagnosis and what building it actually costs.
|
||||
|
||||
- [ ] **No overflow ellipsis.** `TextAttrs` can wrap or not wrap; there is
|
||||
no "one line, ellipsised" the way `maxLines = 1` + `TextOverflow.
|
||||
Ellipsis` gives Compose. A tool card's summary is clipped instead, so
|
||||
nothing on screen says it was cut. Whichever end is cut has to be a
|
||||
choice when this lands: a path is identified by its tail, a command by
|
||||
its head.
|
||||
|
||||
**Diagnosed 2026-09-08, and it is not trivial.** parley has no
|
||||
ellipsis of its own (checked: nothing in the vendored crates), so iris
|
||||
would build it, and the shape that looks easy is the one that breaks
|
||||
something. The easy half really is easy: shape at
|
||||
`max_advance = width - ellipsis_advance` with wrapping on, take line
|
||||
0's `text_range()`, and re-shape `text[..end].trim_end() + "…"` with
|
||||
wrapping off -- parley's own line breaker finds the cut, so nothing
|
||||
here counts glyph advances by hand. The hard half is that
|
||||
`TextBuffer` has exactly one string and everything addresses it by
|
||||
byte offset: the inline spans that carry a fence's colours and a
|
||||
link's range, `TextEditCtx::byte_at` (which turns a tap into a byte to
|
||||
match a link against), `Selection`'s `select`/`selected_text`, and
|
||||
`RowBlocks::apply_delta`. Truncating the buffer moves every one of
|
||||
those. So the real work is giving `TextBuffer` a **displayed** string
|
||||
distinct from its source, with one mapping from display byte to source
|
||||
byte that all of those go through -- worth doing, and not a
|
||||
by-the-way. Doing it only for text that is neither editable nor
|
||||
selectable would avoid all of that and is exactly the kind of
|
||||
exemption that comes back later.
|
||||
|
||||
It also wants an API change while it is open: `TextAttrs::wrap: bool`
|
||||
cannot say three states. Something like `Overflow::{Wrap, Clip,
|
||||
Ellipsis(End)}` replaces it, with `End::{Head, Tail}` making
|
||||
UI_RULES's "choose which end to truncate" a thing a caller must
|
||||
answer rather than a default nobody reads.
|
||||
- [ ] **A tool card's text is not selectable.** `Selection` is keyed
|
||||
`(RowKey, block index)` and a card has no markdown blocks, so nothing in
|
||||
a card registers. Compose's `SelectionContainer` covers tool output,
|
||||
which is the text people most want to copy.
|
||||
|
||||
**Diagnosed 2026-09-08: mechanical, but more than a sitting.** There
|
||||
is no key collision to design around, which was the open question:
|
||||
a `TranscriptRow::Tools` has *only* cards and no markdown blocks at
|
||||
all, so a card is free to number its own texts from 0 in reading
|
||||
order. What it costs is the registration lifecycle rather than the
|
||||
key. Each card's `TextEdit`s have to `Selection::register` as they are
|
||||
built and `unregister` when they are not -- and a card is rebuilt from
|
||||
several directions (`redraw_card` when a result arrives,
|
||||
`Shared::set_content` when the group is toggled or a call joins the
|
||||
run, and the per-card `WidgetPtr` swap), each of which frees widgets
|
||||
the map would otherwise still point at. That is the exact shape of the
|
||||
crash `Selection::clear`'s doc records from
|
||||
review, 2026-09-06: a handle in that map outliving the widget
|
||||
panics on the *next* long press, somewhere else entirely. So the work
|
||||
is a per-card base index with a stride (and a `debug_assert` that a
|
||||
card stays inside it), one register/unregister path that every rebuild
|
||||
route goes through, and a test per route that a rebuilt card leaves no
|
||||
stale handle behind.
|
||||
|
||||
## Warnings standing in the bench build (2026-09-08)
|
||||
|
||||
Seen while checking `cargo ndk -t arm64-v8a check --lib
|
||||
--no-default-features --features "transcript-screen bench"` from
|
||||
`app-rust/`, and left rather than silenced because it is a decision:
|
||||
|
||||
- [ ] **`PlatformHandle::show_diagnostics_overlay` has no caller.** It
|
||||
and the ~60 lines of `IrisView.showDiagnosticsOverlay` behind it are a
|
||||
plain-`TextView` overlay with Copy and Close, drawn over whatever iris
|
||||
is doing -- built so a report can be read *even if iris itself has
|
||||
stopped drawing*, which is the one case the in-iris diagnostics pane
|
||||
that replaced it cannot cover. So this is a live escape hatch nobody
|
||||
calls, not dead code: deleting both halves clears the warning and
|
||||
removes the fallback, and wiring it back to something is a product
|
||||
decision (Iris has no `logcat` on her phone). Ask before doing either.
|
||||
Done means defining one convention for palette bytes, decoded images,
|
||||
colour emoji and the clear colour, then converting exactly once for the
|
||||
selected target. Record the selected format in diagnostics, and add a GPU
|
||||
test that draws known non-black, non-white pixels into an sRGB target and
|
||||
reads the stored bytes back; screenshots from desktop and Android then
|
||||
confirm the same Catppuccin values rather than serving as the definition.
|
||||
## Build (for the port)
|
||||
|
||||
Widgets `RUST.md`'s "The port, in order (decided 2026-09-05)" needs and
|
||||
iris does not have yet, one entry per gap, named against the P-step that
|
||||
first needs it. Move an entry up to "Fix" or tick it in place once built;
|
||||
do not duplicate it there.
|
||||
first needs it. Move an entry up to "Fix" if it becomes a current defect;
|
||||
delete it once built rather than duplicating it there.
|
||||
|
||||
- [ ] **A history-paging cushion measured in on-screen viewports, not a
|
||||
row count.** (**P1**.) `iris::widget::List` has no equivalent of the
|
||||
Compose app's `HISTORY_SCREENS` — AGENTS.md's "Things that have
|
||||
bitten" is explicit that a fixed row count under-fills a screen on a
|
||||
tool-heavy transcript and over-fills one on a text-heavy one, so
|
||||
whatever loads the next page has to ask the list how many viewports
|
||||
are actually on screen, not assume a constant.
|
||||
- [ ] **A scaled thumbnail/image widget for an in-transcript image.**
|
||||
(**P1**.) `SessionImage.kt`'s bitmap decode-and-downscale has no iris
|
||||
counterpart; iris's own image widget (used by `bench_images.rs`) draws
|
||||
a loaded texture but does nothing about sourcing or scaling one from a
|
||||
server-produced attachment.
|
||||
- [ ] **Selectable, read-only text.** P0's report and P1's transcript rows
|
||||
use `TextEdit` because `Selectable` is implemented only for it. That
|
||||
makes prose focusable and opens the IME over text that cannot be edited.
|
||||
A display widget needs the same selection geometry and clipboard path
|
||||
without a text-input accessibility role or keyboard focus.
|
||||
- [ ] **Overflow ellipsis with an explicit retained end.** `TextAttrs` can
|
||||
only wrap or clip, so a tool summary is cut with no mark. Parley has no
|
||||
ellipsis primitive; use its line breaker to find the cut, but keep source
|
||||
and displayed strings distinct with one byte mapping shared by spans,
|
||||
links, selection and editing. Replace `wrap: bool` with an enum that can
|
||||
say wrap, clip, head ellipsis and tail ellipsis—the caller must choose
|
||||
because a command is identified by its head and a path by its tail.
|
||||
- [ ] **Expose the distance from a `LazySpan` viewport to its unloaded
|
||||
edge.** (**P1**.) `viewport_len` and the visible extents are already
|
||||
measured internally, but a paging caller cannot ask whether it is within
|
||||
the Compose app's six-viewport `HISTORY_SCREENS` cushion. The API should
|
||||
answer in pixels or viewport multiples, never rows: a row ranges from one
|
||||
line to a screen, so a fixed row count is not a distance.
|
||||
- [ ] **Let an image fit a bounded box while preserving its aspect ratio.**
|
||||
(**P1**.) `Image` currently always reports and draws the decoded texture's
|
||||
natural pixel size. Decoding and fetching a server-produced attachment
|
||||
belong in `app-rust`; iris only owes the generic fit/scale widget used to
|
||||
draw its thumbnail.
|
||||
- [ ] **Per-range backgrounds for rich text.** (**P1**.) Inline code is
|
||||
already monospace and coloured, but matching Compose's chip also needs
|
||||
the glyph run's boxes so a surface can be drawn behind exactly that byte
|
||||
range. `TextEdit` already computes the same geometry internally for its
|
||||
selection highlight; expose one shared primitive rather than giving the
|
||||
app a second text-layout path.
|
||||
- [ ] **A modal/dialog primitive.** (**P1**, reused by **P3** and
|
||||
**P5**.) Needed for the session settings dialog, `UsageDialog`'s
|
||||
equivalent, and the delete-with-`deleteForeign` confirmation with its
|
||||
@@ -184,23 +77,23 @@ do not duplicate it there.
|
||||
- [ ] **A horizontal gauge/bar widget.** (**P1**.) For
|
||||
`SessionUsageBar`'s equivalent — a bounded fill reflecting a fraction,
|
||||
nothing fancier.
|
||||
- [ ] **A `BusyItem` equivalent: a dimmed row carrying an operation
|
||||
label that does not block its list's own scroll/drag.** (**P3**.) The
|
||||
Compose version tried an overlay first and it swallowed the drag along
|
||||
with the tap (AGENTS.md's "Shared appearance") — worth not repeating
|
||||
that attempt in iris before building the row-level version directly.
|
||||
- [ ] **A toggle switch.** (**P3**.) For the delete dialog's
|
||||
`deleteForeign` control; iris has no switch/checkbox widget yet as far
|
||||
as this pass found.
|
||||
|
||||
## Reconsider
|
||||
## Later
|
||||
|
||||
- [ ] **`WidgetView`.** Iris is unsure of it: what she wants is an easy way
|
||||
to compose a widget from others (a button is the main case). With
|
||||
sizing folded into `draw`, composing may be easy enough that `View` is
|
||||
redundant. Decide after the layout change lands, by writing a button
|
||||
both ways and keeping the one that is shorter to explain; delete the
|
||||
other rather than keeping two ways.
|
||||
- [ ] **Property/content animations.** Cosmetic, so after correctness and
|
||||
parity. Keep them modular, like input; scrolling already animates through
|
||||
`Widget::tick` and `UiData::animate`. A widget that does not opt in must
|
||||
pay nothing and import nothing for them.
|
||||
|
||||
- [ ] **Remove `WidgetView` unless a real composite adopts it first.** The
|
||||
layout change this decision was waiting for has landed. Every composite
|
||||
in `app-rust/src/ui` now uses ordinary child handles plus a returned root;
|
||||
`WidgetView` and its derive are used only by `iris/examples/view.rs`.
|
||||
Today it demonstrates itself rather than shortening production code, so
|
||||
deletion is the concrete default—not another parallel composition style.
|
||||
|
||||
- [ ] **A `Stack` that chooses its mask the way it chooses its size
|
||||
(Iris, 2026-09-08).** She asked whether `masked_by` deserves to exist:
|
||||
@@ -215,14 +108,8 @@ do not duplicate it there.
|
||||
rounded panel and then cuts its content square. Both other call sites
|
||||
(`row.rs`'s fence, `tool.rs`'s raw output) are rounded, which is why
|
||||
the method stands for now.
|
||||
Her suggestion for removing it properly: **`Stack` already names where
|
||||
its size comes from (`StackSize::Child(n)`); let it name where its
|
||||
*mask* comes from the same way.** Then `.background(x)` is the one way
|
||||
to put a surface behind something, and clipping to that surface is a
|
||||
property of the stack rather than a second wrapper -- `masked_by` goes,
|
||||
and `Masked::shape` with it. Worth checking while designing it: what a
|
||||
stack with no mask child means (today's behaviour), whether the mask
|
||||
child must also have been *drawn* first (`set_mask_to_widget` requires
|
||||
it, and `Stack` draws in order, so naming child 0 is safe and naming a
|
||||
later one is not), and what happens when the named child is the same
|
||||
one the size comes from.
|
||||
Let `Stack` name the mask child the way `StackSize::Child(n)` names the
|
||||
sizing child. Then `.background(x)` remains the one way to add a surface
|
||||
and clipping to it is a stack property; the named mask child must have
|
||||
drawn before any child that uses it. Once that exists, delete
|
||||
`masked_by` and `Masked::shape` rather than retaining two APIs.
|
||||
+56
-45
@@ -44,7 +44,7 @@ the log of settling it. Currency means this file says where things *are*,
|
||||
not how they got here. What survives a prune is what cannot be cheaply
|
||||
re-derived: measurements, dead ends, invariants and their reasons.
|
||||
|
||||
## Where things stand (2026-09-08)
|
||||
## Where things stand (2026-09-09)
|
||||
|
||||
- **The framework is decided and built on.** iris draws the transcript
|
||||
screen on the desktop, on this checkout's emulator and on Iris's phone.
|
||||
@@ -56,8 +56,8 @@ re-derived: measurements, dead ends, invariants and their reasons.
|
||||
`app-rust/`, and `iris/` is the UI framework alone. See "One app crate"
|
||||
at the end -- it is the layout everything else here assumes.
|
||||
- **Open across the rest of the docs**: `docs/IRIS_TODO.md` is iris's own
|
||||
list (streaming re-layout is the live one), `docs/TODO.md` is the Compose
|
||||
app's.
|
||||
list (colour-space correctness is the live one), `docs/TODO.md` is the
|
||||
Compose app's.
|
||||
|
||||
## Desktop and phone share the code (Iris, 2026-09-07)
|
||||
|
||||
@@ -795,6 +795,13 @@ device.
|
||||
repository; adding the component here or pushing only `ai-app-2` is the
|
||||
wrong delivery path.
|
||||
|
||||
**Bench-only cleanup still open**: the diagnostics report pane draws
|
||||
over transcript rows. Reproduced on the emulator on 2026-09-09 by
|
||||
opening the named `Diagnostics` control. `REPORT_MAX_HEIGHT_DP`
|
||||
constrains its claimed height, but the pane is neither masked nor
|
||||
scrollable despite its construction comment saying it is both. This is
|
||||
an `app-rust` bench-screen defect, not an iris framework item.
|
||||
|
||||
- [ ] **P1 — session screen parity.** **Started 2026-09-06, on Iris's
|
||||
word**: "just continue with the plan for now; try to move towards
|
||||
feature parity for the transcript screen so that the test can be
|
||||
@@ -814,6 +821,13 @@ device.
|
||||
group, each card carries its state and summary, and the five
|
||||
`ToolState` values each have their own appearance.
|
||||
`tool.rs`'s module doc has what was chosen.
|
||||
- [ ] **Before the next parity slice — make iris's colour pipeline
|
||||
correct.** Raised by Iris on 2026-09-09 as something to settle
|
||||
sooner rather than later. Both backends currently prefer an
|
||||
sRGB surface while the shader returns palette/image bytes as
|
||||
linear values; `IRIS_TODO.md` has the measured mismatch and
|
||||
pass condition. Do this before judging or centralising the
|
||||
app's styling. It is correctness, not cosmetic polish.
|
||||
- [ ] **P1c — history paging and jump-to-latest.** Wire
|
||||
`client::transcript_source` into `src/ui`:
|
||||
the opening page, paging back on scroll with the cushion
|
||||
@@ -836,14 +850,12 @@ device.
|
||||
recomposition-equivalent per keyboard toggle (the
|
||||
`iris insets:` log line count).
|
||||
|
||||
History paging backward (with the
|
||||
page-boundary healing `app-rust`'s `client` does not have yet, below),
|
||||
`TranscriptSource`-backed cache/server stitching, jump-to-latest,
|
||||
tool-call cards and grouping, the session settings dialog, composer
|
||||
attachments, and the keyboard/insets behaviours AGENTS.md's "Things
|
||||
that have bitten" names (the floating-composer bug, `adjustResize`,
|
||||
the `imePadding`-vs-raw-inset rule). This is the highest-risk step:
|
||||
it is the screen the app is used for, every hour of the day.
|
||||
The remaining screen work is history paging and jump-to-latest, the
|
||||
session settings and usage dialogs, images and composer attachments,
|
||||
and the keyboard/insets behaviours AGENTS.md's "Things that have
|
||||
bitten" names (the floating-composer bug, `adjustResize`, the
|
||||
`imePadding`-vs-raw-inset rule). This is the highest-risk step: it is
|
||||
the screen the app is used for, every hour of the day.
|
||||
|
||||
**Kotlin it replaces**: `SessionScreen.kt`, `TranscriptList.kt`,
|
||||
`SessionSettingsDialog.kt`, `ToolInput.kt`, `ToolRows.kt`,
|
||||
@@ -856,29 +868,30 @@ device.
|
||||
already covers the row/markdown/selection/composer core these sit
|
||||
on top of or beside.)
|
||||
|
||||
**`app-rust`'s `client` needed, and what is not yet covered and must be
|
||||
ported first** (`CLIENT_CORE.md`): `TranscriptSource.kt` (deciding
|
||||
cache vs. server per page and stitching them — "not started"),
|
||||
`TranscriptItems.kt`'s `joinPages`/`healSplitMessage`/`adoptRun`
|
||||
(page-boundary healing — "not ported," and paging backward is
|
||||
exactly what exercises it), the markdown *block* model beyond
|
||||
syntax spans (headings/lists/tables/fences as distinct nodes —
|
||||
"not started," needed for `CodeFence`/`MarkdownPieces`' equivalents),
|
||||
and the attachments route (`/sessions/{id}/attachments` — "not
|
||||
covered" in `api.rs`, needed for `PendingAttachments`/`Attachment`).
|
||||
**`app-rust`'s `client` needed** (`CLIENT_CORE.md`): the paging half
|
||||
is ready — `transcript_source`, `join_pages` with seam healing, and
|
||||
`markdown_blocks` are all ported. The remaining client gap in P1 is
|
||||
the attachments route (`/sessions/{id}/attachments`), needed by P1d's
|
||||
`PendingAttachments`/`Attachment`.
|
||||
|
||||
**iris widgets missing, → `IRIS_TODO.md`'s new "Build (for the
|
||||
port)" section**: row-level accessibility names and the tappable
|
||||
link / background-chip primitive (both already listed under I5's
|
||||
leftovers — this step is what needs them, not a new ask); a
|
||||
history-paging cushion measured in on-screen viewports rather than
|
||||
a row count (the `HISTORY_SCREENS` lesson in "Things that have
|
||||
bitten," which iris's `List` has no equivalent of yet); a scaled
|
||||
thumbnail/image widget for `SessionImage`'s in-transcript images; a
|
||||
modal/dialog primitive for the session settings dialog and
|
||||
`UsageDialog` (iris has none today — check before building a second
|
||||
one for P3/P5); a horizontal gauge/bar widget for
|
||||
`SessionUsageBar`.
|
||||
**iris widgets missing, → `IRIS_TODO.md`'s "Build (for the port)"
|
||||
section**: the distance-to-unloaded-edge query P1c needs for its
|
||||
viewport-sized history cushion; per-range text backgrounds for inline
|
||||
code; a fitted image widget; one modal/dialog primitive reused by the
|
||||
settings and usage dialogs; and a horizontal gauge for
|
||||
`SessionUsageBar`. Tappable links already exist. Row accessibility
|
||||
names are app content applied through iris's existing `.label()` API,
|
||||
not a missing framework widget.
|
||||
|
||||
**`app-rust` UI defect still open**: tool-card text is not selectable.
|
||||
A `TranscriptRow::Tools` has no markdown blocks, so cards can number
|
||||
their own texts from zero without colliding with the row selection
|
||||
keys. The risk is lifecycle: every card rebuild path must unregister
|
||||
the old `TextEdit` handles before registering replacements, or the next
|
||||
long press can find a freed handle. Cover result arrival, group toggle,
|
||||
a call joining a run and the per-card widget swap with stale-handle
|
||||
tests. This uses iris's existing selection API; it is not a framework
|
||||
widget gap.
|
||||
|
||||
**Pass condition**: `app/ui-sandbox.sh`'s fixtures driven by
|
||||
`ui-trace record --do "tap '<label>'"` — a session with the big
|
||||
@@ -945,15 +958,12 @@ device.
|
||||
listed "not covered" in `api.rs`'s table and none started; each is
|
||||
real work, not a stub, per `CLIENT_CORE.md`'s own caveat.
|
||||
|
||||
**iris widgets missing**: a `BusyItem` equivalent — a row dimmed,
|
||||
drained of colour, labelled with the operation in progress, that
|
||||
does **not** block the list's own scroll/drag the way an overlay
|
||||
did on the Compose side (AGENTS.md's "Shared appearance"); a
|
||||
`uniqueItems` equivalent is logic, not a widget, and ports directly
|
||||
into `src/ui` itself; a confirmation dialog with a toggle switch,
|
||||
for the delete-with-`deleteForeign` flow, needs the same modal
|
||||
primitive P1 flagged — build it once, here or in P1, whichever
|
||||
lands first.
|
||||
**UI still needed**: `BusyItem` is app-specific appearance — a row
|
||||
dimmed, drained of colour and labelled with the operation in progress
|
||||
without blocking the list's scroll — so it belongs in `src/ui`, not
|
||||
iris. `uniqueItems` is app logic there too. Iris itself still needs
|
||||
the modal primitive P1 flagged and a toggle switch for the
|
||||
delete-with-`deleteForeign` flow.
|
||||
|
||||
**Pass condition**: `ui-trace` tap-by-name on all four tabs against
|
||||
`ui-sandbox.sh`'s fixtures; the two-copies-of-one-session-id
|
||||
@@ -1043,9 +1053,10 @@ push, in the session that picked the task up.
|
||||
`main` and not in `ai-app`. Nothing on this branch is production until
|
||||
Iris says so. Commit and push as you go.
|
||||
3. The E- and I-steps (the framework decision) are done — iris won,
|
||||
decided 2026-09-05. Take the next unchecked P-box in "## The
|
||||
port, in order (decided 2026-09-05)"; **P1 — session screen parity —
|
||||
is next.**
|
||||
decided 2026-09-05. **Fix iris's colour-space pipeline first**, as Iris
|
||||
requested on 2026-09-09; then take P1c, history paging and
|
||||
jump-to-latest. The client-side paging pieces it needs are already
|
||||
ported.
|
||||
4. Every step ends with its measurement written into this file beside the
|
||||
box, and the box ticked or the reason it could not be written in its
|
||||
place. A step that is blocked says by what, not "later". Write it as you
|
||||
|
||||
Reference in new issue
Block a user