Files
ai-app/docs/IRIS_TODO.md
T

5.4 KiB

iris: known problems and things still to build

Only open Iris framework work lives here. Delete an item when it lands.

Fix

  • 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.

    Neither diagnostics nor startup logging records Android's selected surface format. A device exposing only a non-sRGB surface can hide the shared bug.

    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)

Framework capabilities needed by RUST.md's port plan:

  • 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 toggle switch. Build once, wherever it is first needed, rather than once per screen that wants one.
  • A horizontal gauge/bar widget. (P1.) For SessionUsageBar's equivalent — a bounded fill reflecting a fraction, nothing fancier.
  • A toggle switch. (P3.) For the delete dialog's deleteForeign control; iris has no switch/checkbox widget yet as far as this pass found.

Later

  • 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. Every composite in app-rust/src/ui uses ordinary child handles plus a root; WidgetView and its derive are used only by iris/examples/view.rs. It currently demonstrates itself rather than shortening production code.

  • A Stack that chooses its mask the way it chooses its size should replace masked_by. For a square-cornered surface, .background(rect(BAR_FILL)).masked() was measured against .masked_by(rect(BAR_FILL)) on the composer at the phone's own size and density and the two are identical to the pixel. What the pair cannot express is a clip that is not a box: Painter::set_mask writes a RectPrimitive::color(Color::NONE) at the widget's own region, with no radius, so .background(rect(fill).radius(r)).masked() draws a 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. 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.