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;
|
package dev.iris.android.demo;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.Gravity;
|
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.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@@ -77,80 +70,4 @@ public final class IrisView extends RustView {
|
|||||||
activity.setContentView(scroll);
|
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()
|
.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
|
## Fix
|
||||||
|
|
||||||
- [ ] **Where the scroll *pin* lives.** The rest of "scrolling moves out
|
- [ ] **Colours are not in a defined colour space. Fix this before a
|
||||||
of the list" landed on 2026-09-08 -- `List` is `LazySpan`, the physics
|
styling pass.** Both render backends prefer an sRGB surface
|
||||||
and the gesture live in one `ScrollController`, `.scrollable()` is the
|
(`default/render.rs` and `android/render.rs`), while `fs_main` returns
|
||||||
only way anything scrolls, and **`docs/SCROLL.md` is the standing
|
`unpack4x8unorm` palette and image bytes unchanged. An sRGB attachment
|
||||||
reference**; read that rather than reconstructing it here.
|
treats those values as linear and encodes them again: Mocha Crust
|
||||||
What is left is one design question. The pin ("stay at the end as rows
|
(17,17,27) became (73,73,91) in a desktop screenshot measured on
|
||||||
are appended") is still each widget's own: `Scroll` has `snap_end` for
|
2026-09-06.
|
||||||
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).
|
|
||||||
|
|
||||||
## 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
|
Done means defining one convention for palette bytes, decoded images,
|
||||||
rejected, letting a scroll update one float rather than positions:
|
colour emoji and the clear colour, then converting exactly once for the
|
||||||
input handling cares about most elements in a list, so absolute
|
selected target. Record the selected format in diagnostics, and add a GPU
|
||||||
positions must be computed on the CPU anyway. LAYOUT.md's design
|
test that draws known non-black, non-white pixels into an sRGB target and
|
||||||
already lands here (GPU walks the chain, CPU resolves on demand for
|
reads the stored bytes back; screenshots from desktop and Android then
|
||||||
hit tests). Keep the CPU resolution lazy and per query; do not
|
confirm the same Catppuccin values rather than serving as the definition.
|
||||||
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.
|
|
||||||
## Build (for the port)
|
## Build (for the port)
|
||||||
|
|
||||||
Widgets `RUST.md`'s "The port, in order (decided 2026-09-05)" needs and
|
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
|
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;
|
first needs it. Move an entry up to "Fix" if it becomes a current defect;
|
||||||
do not duplicate it there.
|
delete it once built rather than duplicating it there.
|
||||||
|
|
||||||
- [ ] **A history-paging cushion measured in on-screen viewports, not a
|
- [ ] **Selectable, read-only text.** P0's report and P1's transcript rows
|
||||||
row count.** (**P1**.) `iris::widget::List` has no equivalent of the
|
use `TextEdit` because `Selectable` is implemented only for it. That
|
||||||
Compose app's `HISTORY_SCREENS` — AGENTS.md's "Things that have
|
makes prose focusable and opens the IME over text that cannot be edited.
|
||||||
bitten" is explicit that a fixed row count under-fills a screen on a
|
A display widget needs the same selection geometry and clipboard path
|
||||||
tool-heavy transcript and over-fills one on a text-heavy one, so
|
without a text-input accessibility role or keyboard focus.
|
||||||
whatever loads the next page has to ask the list how many viewports
|
- [ ] **Overflow ellipsis with an explicit retained end.** `TextAttrs` can
|
||||||
are actually on screen, not assume a constant.
|
only wrap or clip, so a tool summary is cut with no mark. Parley has no
|
||||||
- [ ] **A scaled thumbnail/image widget for an in-transcript image.**
|
ellipsis primitive; use its line breaker to find the cut, but keep source
|
||||||
(**P1**.) `SessionImage.kt`'s bitmap decode-and-downscale has no iris
|
and displayed strings distinct with one byte mapping shared by spans,
|
||||||
counterpart; iris's own image widget (used by `bench_images.rs`) draws
|
links, selection and editing. Replace `wrap: bool` with an enum that can
|
||||||
a loaded texture but does nothing about sourcing or scaling one from a
|
say wrap, clip, head ellipsis and tail ellipsis—the caller must choose
|
||||||
server-produced attachment.
|
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
|
- [ ] **A modal/dialog primitive.** (**P1**, reused by **P3** and
|
||||||
**P5**.) Needed for the session settings dialog, `UsageDialog`'s
|
**P5**.) Needed for the session settings dialog, `UsageDialog`'s
|
||||||
equivalent, and the delete-with-`deleteForeign` confirmation with its
|
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
|
- [ ] **A horizontal gauge/bar widget.** (**P1**.) For
|
||||||
`SessionUsageBar`'s equivalent — a bounded fill reflecting a fraction,
|
`SessionUsageBar`'s equivalent — a bounded fill reflecting a fraction,
|
||||||
nothing fancier.
|
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
|
- [ ] **A toggle switch.** (**P3**.) For the delete dialog's
|
||||||
`deleteForeign` control; iris has no switch/checkbox widget yet as far
|
`deleteForeign` control; iris has no switch/checkbox widget yet as far
|
||||||
as this pass found.
|
as this pass found.
|
||||||
|
|
||||||
## Reconsider
|
## Later
|
||||||
|
|
||||||
- [ ] **`WidgetView`.** Iris is unsure of it: what she wants is an easy way
|
- [ ] **Property/content animations.** Cosmetic, so after correctness and
|
||||||
to compose a widget from others (a button is the main case). With
|
parity. Keep them modular, like input; scrolling already animates through
|
||||||
sizing folded into `draw`, composing may be easy enough that `View` is
|
`Widget::tick` and `UiData::animate`. A widget that does not opt in must
|
||||||
redundant. Decide after the layout change lands, by writing a button
|
pay nothing and import nothing for them.
|
||||||
both ways and keeping the one that is shorter to explain; delete the
|
|
||||||
other rather than keeping two ways.
|
- [ ] **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
|
- [ ] **A `Stack` that chooses its mask the way it chooses its size
|
||||||
(Iris, 2026-09-08).** She asked whether `masked_by` deserves to exist:
|
(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
|
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
|
(`row.rs`'s fence, `tool.rs`'s raw output) are rounded, which is why
|
||||||
the method stands for now.
|
the method stands for now.
|
||||||
Her suggestion for removing it properly: **`Stack` already names where
|
Let `Stack` name the mask child the way `StackSize::Child(n)` names the
|
||||||
its size comes from (`StackSize::Child(n)`); let it name where its
|
sizing child. Then `.background(x)` remains the one way to add a surface
|
||||||
*mask* comes from the same way.** Then `.background(x)` is the one way
|
and clipping to it is a stack property; the named mask child must have
|
||||||
to put a surface behind something, and clipping to that surface is a
|
drawn before any child that uses it. Once that exists, delete
|
||||||
property of the stack rather than a second wrapper -- `masked_by` goes,
|
`masked_by` and `Masked::shape` rather than retaining two APIs.
|
||||||
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.
|
|
||||||
+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
|
not how they got here. What survives a prune is what cannot be cheaply
|
||||||
re-derived: measurements, dead ends, invariants and their reasons.
|
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
|
- **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.
|
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"
|
`app-rust/`, and `iris/` is the UI framework alone. See "One app crate"
|
||||||
at the end -- it is the layout everything else here assumes.
|
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
|
- **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
|
list (colour-space correctness is the live one), `docs/TODO.md` is the
|
||||||
app's.
|
Compose app's.
|
||||||
|
|
||||||
## Desktop and phone share the code (Iris, 2026-09-07)
|
## 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
|
repository; adding the component here or pushing only `ai-app-2` is the
|
||||||
wrong delivery path.
|
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
|
- [ ] **P1 — session screen parity.** **Started 2026-09-06, on Iris's
|
||||||
word**: "just continue with the plan for now; try to move towards
|
word**: "just continue with the plan for now; try to move towards
|
||||||
feature parity for the transcript screen so that the test can be
|
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
|
group, each card carries its state and summary, and the five
|
||||||
`ToolState` values each have their own appearance.
|
`ToolState` values each have their own appearance.
|
||||||
`tool.rs`'s module doc has what was chosen.
|
`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
|
- [ ] **P1c — history paging and jump-to-latest.** Wire
|
||||||
`client::transcript_source` into `src/ui`:
|
`client::transcript_source` into `src/ui`:
|
||||||
the opening page, paging back on scroll with the cushion
|
the opening page, paging back on scroll with the cushion
|
||||||
@@ -836,14 +850,12 @@ device.
|
|||||||
recomposition-equivalent per keyboard toggle (the
|
recomposition-equivalent per keyboard toggle (the
|
||||||
`iris insets:` log line count).
|
`iris insets:` log line count).
|
||||||
|
|
||||||
History paging backward (with the
|
The remaining screen work is history paging and jump-to-latest, the
|
||||||
page-boundary healing `app-rust`'s `client` does not have yet, below),
|
session settings and usage dialogs, images and composer attachments,
|
||||||
`TranscriptSource`-backed cache/server stitching, jump-to-latest,
|
and the keyboard/insets behaviours AGENTS.md's "Things that have
|
||||||
tool-call cards and grouping, the session settings dialog, composer
|
bitten" names (the floating-composer bug, `adjustResize`, the
|
||||||
attachments, and the keyboard/insets behaviours AGENTS.md's "Things
|
`imePadding`-vs-raw-inset rule). This is the highest-risk step: it is
|
||||||
that have bitten" names (the floating-composer bug, `adjustResize`,
|
the screen the app is used for, every hour of the day.
|
||||||
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`,
|
**Kotlin it replaces**: `SessionScreen.kt`, `TranscriptList.kt`,
|
||||||
`SessionSettingsDialog.kt`, `ToolInput.kt`, `ToolRows.kt`,
|
`SessionSettingsDialog.kt`, `ToolInput.kt`, `ToolRows.kt`,
|
||||||
@@ -856,29 +868,30 @@ device.
|
|||||||
already covers the row/markdown/selection/composer core these sit
|
already covers the row/markdown/selection/composer core these sit
|
||||||
on top of or beside.)
|
on top of or beside.)
|
||||||
|
|
||||||
**`app-rust`'s `client` needed, and what is not yet covered and must be
|
**`app-rust`'s `client` needed** (`CLIENT_CORE.md`): the paging half
|
||||||
ported first** (`CLIENT_CORE.md`): `TranscriptSource.kt` (deciding
|
is ready — `transcript_source`, `join_pages` with seam healing, and
|
||||||
cache vs. server per page and stitching them — "not started"),
|
`markdown_blocks` are all ported. The remaining client gap in P1 is
|
||||||
`TranscriptItems.kt`'s `joinPages`/`healSplitMessage`/`adoptRun`
|
the attachments route (`/sessions/{id}/attachments`), needed by P1d's
|
||||||
(page-boundary healing — "not ported," and paging backward is
|
`PendingAttachments`/`Attachment`.
|
||||||
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`).
|
|
||||||
|
|
||||||
**iris widgets missing, → `IRIS_TODO.md`'s new "Build (for the
|
**iris widgets missing, → `IRIS_TODO.md`'s "Build (for the port)"
|
||||||
port)" section**: row-level accessibility names and the tappable
|
section**: the distance-to-unloaded-edge query P1c needs for its
|
||||||
link / background-chip primitive (both already listed under I5's
|
viewport-sized history cushion; per-range text backgrounds for inline
|
||||||
leftovers — this step is what needs them, not a new ask); a
|
code; a fitted image widget; one modal/dialog primitive reused by the
|
||||||
history-paging cushion measured in on-screen viewports rather than
|
settings and usage dialogs; and a horizontal gauge for
|
||||||
a row count (the `HISTORY_SCREENS` lesson in "Things that have
|
`SessionUsageBar`. Tappable links already exist. Row accessibility
|
||||||
bitten," which iris's `List` has no equivalent of yet); a scaled
|
names are app content applied through iris's existing `.label()` API,
|
||||||
thumbnail/image widget for `SessionImage`'s in-transcript images; a
|
not a missing framework widget.
|
||||||
modal/dialog primitive for the session settings dialog and
|
|
||||||
`UsageDialog` (iris has none today — check before building a second
|
**`app-rust` UI defect still open**: tool-card text is not selectable.
|
||||||
one for P3/P5); a horizontal gauge/bar widget for
|
A `TranscriptRow::Tools` has no markdown blocks, so cards can number
|
||||||
`SessionUsageBar`.
|
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
|
**Pass condition**: `app/ui-sandbox.sh`'s fixtures driven by
|
||||||
`ui-trace record --do "tap '<label>'"` — a session with the big
|
`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
|
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.
|
real work, not a stub, per `CLIENT_CORE.md`'s own caveat.
|
||||||
|
|
||||||
**iris widgets missing**: a `BusyItem` equivalent — a row dimmed,
|
**UI still needed**: `BusyItem` is app-specific appearance — a row
|
||||||
drained of colour, labelled with the operation in progress, that
|
dimmed, drained of colour and labelled with the operation in progress
|
||||||
does **not** block the list's own scroll/drag the way an overlay
|
without blocking the list's scroll — so it belongs in `src/ui`, not
|
||||||
did on the Compose side (AGENTS.md's "Shared appearance"); a
|
iris. `uniqueItems` is app logic there too. Iris itself still needs
|
||||||
`uniqueItems` equivalent is logic, not a widget, and ports directly
|
the modal primitive P1 flagged and a toggle switch for the
|
||||||
into `src/ui` itself; a confirmation dialog with a toggle switch,
|
delete-with-`deleteForeign` flow.
|
||||||
for the delete-with-`deleteForeign` flow, needs the same modal
|
|
||||||
primitive P1 flagged — build it once, here or in P1, whichever
|
|
||||||
lands first.
|
|
||||||
|
|
||||||
**Pass condition**: `ui-trace` tap-by-name on all four tabs against
|
**Pass condition**: `ui-trace` tap-by-name on all four tabs against
|
||||||
`ui-sandbox.sh`'s fixtures; the two-copies-of-one-session-id
|
`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
|
`main` and not in `ai-app`. Nothing on this branch is production until
|
||||||
Iris says so. Commit and push as you go.
|
Iris says so. Commit and push as you go.
|
||||||
3. The E- and I-steps (the framework decision) are done — iris won,
|
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
|
decided 2026-09-05. **Fix iris's colour-space pipeline first**, as Iris
|
||||||
port, in order (decided 2026-09-05)"; **P1 — session screen parity —
|
requested on 2026-09-09; then take P1c, history paging and
|
||||||
is next.**
|
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
|
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
|
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
|
place. A step that is blocked says by what, not "later". Write it as you
|
||||||
|
|||||||
Reference in new issue
Block a user