RUST.md, DECISIONS.md, IRIS_TODO.md: the port plan, P1-P7, after iris-over-Masonry
Adds "The port, in order (decided 2026-09-05)" to RUST.md: seven ordered steps building the app on iris now that the framework is decided, each naming the Kotlin files it replaces, the client-core pieces it needs (and which are not yet covered and must be ported first), the missing iris widgets it needs (recorded in IRIS_TODO.md's new "Build (for the port)" section), and a pass condition a later agent can run. Ordered by risk to the daily-use path: session screen parity, then the shell merge and a real phone install, then root tabs, the file explorer, settings/enrolment, desktop parity, and the cutover itself. Crate-shape decision recorded in DECISIONS.md: one UI crate, app-ui, grown out of transcript-ui rather than started beside it, with desktop-app/android-app as thin entry points over it and platform-only code staying in the E3/E5 Java shell. Updates RUST.md's "Where things stand" and "For the next agent" to point at P1 rather than the now-closed framework decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
621f08d725
commit
5b7dc0e4e2
3 files changed
+298
-4
No files matched your search
+240
-4
@@ -40,6 +40,12 @@ session spending an afternoon on them again.
|
||||
numbers in I5's box and E1/E2's findings. See the Recommendation's item
|
||||
3 and `DECISIONS.md`. Next: the remaining screens and the app on iris —
|
||||
a new ordered list is the next thing to write into this file.
|
||||
- **The port plan exists, 2026-09-05: "## The port, in order (decided
|
||||
2026-09-05)"**, seven steps (P1–P7) below "Experiments, in order,"
|
||||
ordered by risk to the daily-use path rather than by screen count. **P1
|
||||
— session screen parity — is next.** One crate decision made there:
|
||||
screens grow out of `iris/transcript-ui` into `iris/app-ui`, with
|
||||
`desktop-app`/`android-app` as thin entry points over it.
|
||||
- **I5 is now `[x]`: a clean, single-session, like-for-like 24-swipe
|
||||
scroll comparison between Compose and iris exists, 2026-09-05.** Same
|
||||
sandbox session content for both apps, same emulator, `EMU_GPU=software`
|
||||
@@ -3195,6 +3201,236 @@ silently on real hardware.
|
||||
`e2a1fad`'s own message. `docs/DECISIONS.md`'s DEFERRED item is
|
||||
updated with this section's host-GPU table below.
|
||||
|
||||
## The port, in order (decided 2026-09-05)
|
||||
|
||||
Iris decided iris over Masonry (`DECISIONS.md`). This is the ordered plan
|
||||
for the rest of the app, decided by the design agent per the standing
|
||||
"decide technical questions yourself" instruction — a serious
|
||||
user-facing tradeoff is not in play in the ordering itself, so it is not
|
||||
deferred to her. **Crate shape, decided here**: the screens live in one
|
||||
crate, **`iris/app-ui`**, grown from `iris/transcript-ui` rather than
|
||||
started beside it — `transcript-ui` already has the right generic shape
|
||||
(`Rsc: HasEvents` + `Rsc::State: FocusHost`, the same axis `tabs-ui`
|
||||
varies along) and the same `client-core`/`event-model` path
|
||||
dependencies every later screen needs, so growing it in place is a
|
||||
rename plus new modules rather than a second crate re-declaring
|
||||
dependencies the first already has. It holds a `Screen` enum and a back
|
||||
stack — the direct equivalent of `AppRoot.kt`'s `when` and `MainScreen.kt`'s
|
||||
tab `enum` — with each Compose screen becoming one `iris::widget`
|
||||
subtree module. `iris/desktop-app` (E4) and `iris/android-app` (I2/I5)
|
||||
become thin entry points that call into `app-ui`, the way `AppRoot`/
|
||||
`MainActivity` today call into Compose screens they don't otherwise own.
|
||||
Platform-only code (the notification foreground service, the share
|
||||
target, the QR scanner, the Keystore-sealed token, deep-link enrolment)
|
||||
stays exactly where E3/E5 already put it — `android-shell/` and
|
||||
`app/shellApp` — since none of it is a screen `app-ui` could draw.
|
||||
|
||||
Order is by **risk to the daily-use path**, not by screen count: the
|
||||
session screen is what the app is for and where every hard behaviour
|
||||
(paging, cache, keyboard insets, selection) already lives, so it goes
|
||||
first and on the phone as reachable code as soon as possible, before the
|
||||
lower-risk screens.
|
||||
|
||||
Every step below assumes the `app/ui-sandbox.sh` fixtures (AGENTS.md's
|
||||
"The rigs") and the `this-machine-android` skill's facts (per-checkout
|
||||
AVD, `ui-trace` by accessibility name, GrapheneOS phone quirks, the
|
||||
`adb shell` quoting traps) apply unchanged — read that skill before
|
||||
running any pass condition below that touches an emulator or a real
|
||||
device.
|
||||
|
||||
- [ ] **P1 — session screen parity.** History paging backward (with the
|
||||
page-boundary healing `client-core` 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.
|
||||
|
||||
**Kotlin it replaces**: `SessionScreen.kt`, `TranscriptList.kt`,
|
||||
`SessionSettingsDialog.kt`, `ToolInput.kt`, `ToolRows.kt`,
|
||||
`AskQuestion.kt`, `Compaction.kt`, `SessionUsageBar.kt`,
|
||||
`PendingAttachments.kt`, `Attachment.kt`, `Attachments.kt`,
|
||||
`SessionImage.kt`, `MemoryNote.kt`, `PeerMessage.kt`, `RawBlock.kt`,
|
||||
`CodeFence.kt`, `MarkdownLinks.kt`, `MarkdownPieces.kt`,
|
||||
`Markdown.kt`, `Bubble.kt`, `ScrollAnchor.kt`, `Drafts.kt`,
|
||||
`UsageDialog.kt`, `Chevron.kt`, `Dividers.kt`. (`transcript-ui`
|
||||
already covers the row/markdown/selection/composer core these sit
|
||||
on top of or beside.)
|
||||
|
||||
**`client-core` 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`).
|
||||
|
||||
**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`.
|
||||
|
||||
**Pass condition**: `app/ui-sandbox.sh`'s fixtures driven by
|
||||
`ui-trace record --do "tap '<label>'"` — a session with the big
|
||||
transcript (`AI_SANDBOX_BIG_MB`), a paused/slow-spawning one
|
||||
(`AI_SANDBOX_SPAWN_DELAY`), and `--delay` on the server — exercising
|
||||
the four states UI_RULES.md says to design first: unknown (a page
|
||||
that hasn't loaded), empty (a session with no messages yet), error
|
||||
(a failed send/interrupt), and too-long (the big transcript,
|
||||
paged). Re-take the I5 `FrameReport` (`iris frame report` in
|
||||
logcat, same as I5's box) once this screen has real paging and
|
||||
compare it against I5's own numbers, not against Compose's — the
|
||||
three measurement sources still are not comparable per
|
||||
`DECISIONS.md`'s DEFERRED item.
|
||||
|
||||
- [ ] **P2 — the shell merge and a real phone install.** Merge this
|
||||
screen's cdylib into the E3/E5 shell (`android-shell` +
|
||||
`app/shellApp`) behind the same feature-flag pattern I5 used to
|
||||
extend `iris-android-app` (`DECISIONS.md`, 2026-09-05), so there is
|
||||
one app — notification service, share target and the real screen —
|
||||
rather than a demo shell and a service shell side by side. Package
|
||||
with `cargo xtask apk` (E5) and get it onto the real GrapheneOS
|
||||
phone, not just the emulator: `arm64-v8a` is the ABI that matters
|
||||
there (the emulator here is x86_64), and the `this-machine-android`
|
||||
skill's facts apply for the first time in this port — no System
|
||||
Tracing on that phone (frame numbers have to come from `FrameReport`
|
||||
itself), the local-network permission is required there even though
|
||||
AOSP's docs say VPN traffic is excluded, and `ui-trace`/`adb`
|
||||
target this checkout's own emulator by default so a real-device
|
||||
command needs `-s <serial>` explicitly.
|
||||
|
||||
**Kotlin it replaces**: nothing further than E3 already did
|
||||
(`Notifications.kt` → `notifications.rs`, `Share.kt` → `share.rs`,
|
||||
`ServerConfig.kt`'s Keystore half → JNI calls into `wg-app-link`) —
|
||||
this step is wiring P1's screen in as the shell's real content
|
||||
instead of E3's placeholder, plus getting a signed APK onto a
|
||||
physical device for the first time in this port.
|
||||
|
||||
**`client-core` needed**: none new; E3 already covers what the
|
||||
shell itself needs. Attachments (P1's gap) matter here too if a
|
||||
real photo share is exercised.
|
||||
|
||||
**iris widgets missing**: none — this step is integration, not new
|
||||
widgets.
|
||||
|
||||
**Pass condition**: `cargo xtask apk`, install on the real phone
|
||||
over adb, enroll via the deep link, background the app and get a
|
||||
real notification, share a text snippet into a session, and
|
||||
confirm `ui-trace` can still find controls by name on real
|
||||
hardware (accessibility names are not guaranteed to survive a real
|
||||
device's TalkBack/AccessKit wiring the way they do in the
|
||||
emulator — this is the first time that gets checked for real).
|
||||
|
||||
- [ ] **P3 — root tabs.** `Screen`/`MainTab` in `app-ui`: the sessions
|
||||
list, import, models and setups tabs, plus spawn and the app's one
|
||||
level of back-stack navigation (`AppRoot.kt`'s `when`).
|
||||
|
||||
**Kotlin it replaces**: `AppRoot.kt`, `MainScreen.kt`,
|
||||
`SessionListScreen.kt`, `ImportScreen.kt`, `ModelsScreen.kt`,
|
||||
`SetupsScreen.kt`, `SpawnScreen.kt`, `BusyItem.kt`,
|
||||
`UniqueItems.kt`, `SessionAlerts.kt`.
|
||||
|
||||
**`client-core` needed, not yet covered**: setups/machine/provider
|
||||
discovery, the models routes (`/models*`, HuggingFace browsing and
|
||||
downloads), and importing (`/setups/{id}/importable*`) — all three
|
||||
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 `app-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.
|
||||
|
||||
**Pass condition**: `ui-trace` tap-by-name on all four tabs against
|
||||
`ui-sandbox.sh`'s fixtures; the two-copies-of-one-session-id
|
||||
fixture (AGENTS.md's "Importing") does not crash the list — this is
|
||||
the regression `uniqueItems` exists for and it must be exercised
|
||||
here, not assumed; the delete dialog's paragraph reads correctly
|
||||
both with and without `deleteForeign` toggled (its own text, not
|
||||
appended, per AGENTS.md).
|
||||
|
||||
- [ ] **P4 — file explorer.** The viewer, the editor with its
|
||||
`EDIT_LIMIT`, and the 409 conflict.
|
||||
|
||||
**Kotlin it replaces**: `FilesScreen.kt`, `FileViewer.kt`,
|
||||
`FileEditor.kt`, `FileLines.kt`.
|
||||
|
||||
**`client-core` needed, not yet covered**: `/setups/{id}/dir|file`
|
||||
— not in `api.rs`'s covered list, real work, port first.
|
||||
|
||||
**iris widgets missing**: nothing beyond what P1 needs (a
|
||||
virtualised line-numbered text view is `iris::widget::List` reused,
|
||||
per I3's box) — the open question is whether the editor's
|
||||
`BasicTextField`-equivalent cost (`docs/EXPLORER.md`'s "what the
|
||||
measurements said") reproduces in iris's `TextEdit` at the same
|
||||
`EDIT_LIMIT`, which this step has to re-measure rather than assume.
|
||||
|
||||
**Pass condition**: `app/ui-sandbox.sh`'s `~/files` fixture tree
|
||||
(empty dir, tab/apostrophe names, binary, over `FILE_LIMIT`,
|
||||
`chmod 000`, symlinks good and broken, one source file per
|
||||
language, `edit-32k.rs`/`edit-128k.rs`/`big-source.rs`) driven by
|
||||
name; the 409 reproduced by editing the file on the machine between
|
||||
opening it and saving, per AGENTS.md's own recipe.
|
||||
|
||||
- [ ] **P5 — settings, enrollment, notifications permission.**
|
||||
|
||||
**Kotlin it replaces**: `SettingsScreen.kt`, `ServerConfig.kt`'s
|
||||
remaining non-Keystore parts, `DebugStats.kt`, `FrameStats.kt`,
|
||||
`CrashLog.kt`. The QR scanner (`EnrollmentScanActivity`, in
|
||||
`wg-app-link`) is platform-only and is **not** replaced — it stays
|
||||
a Java/Kotlin activity per decision 1 above, called into from
|
||||
`app-ui` the way it is called into from Compose today.
|
||||
|
||||
**`client-core` needed**: none new — `config.rs`'s
|
||||
`EnrolledServer`/`parse_link` already cover the deep-link half; the
|
||||
Keystore half stays the JNI call E3 already wired.
|
||||
|
||||
**iris widgets missing**: none identified yet — a plain form screen.
|
||||
|
||||
**Pass condition**: enroll via the same `aiappshell://enroll?...`
|
||||
link `ui-sandbox.sh`'s banner prints; the local-network-not-allowed
|
||||
banner (AGENTS.md's standing-condition text) reads by name when the
|
||||
permission is off; `POST_NOTIFICATIONS` request flow checked on the
|
||||
real phone from P2, not just the emulator.
|
||||
|
||||
- [ ] **P6 — desktop parity.** Root tabs, explorer and settings on
|
||||
`iris/desktop-app`, matching P3–P5 there. Not a Kotlin replacement
|
||||
(the desktop app has no Compose original) — this is closing the
|
||||
gap `E4` deliberately left (session list + transcript only).
|
||||
|
||||
**`client-core` needed**: the same P3/P4 gaps, once closed there.
|
||||
|
||||
**Pass condition**: `run-headless.sh` screenshots of each tab and
|
||||
the explorer against `app/ui-sandbox.sh`, the same way E4's did.
|
||||
|
||||
- [ ] **P7 — the switch of `ai-app`'s main.** Point `ai-app`'s production
|
||||
Android build at `app-ui`/`android-shell` instead of
|
||||
`app/androidApp`; decide then whether `app/androidApp` stays as a
|
||||
reference or is retired — a load-bearing decision (AGENTS.md's
|
||||
"ask before changing load-bearing decisions") to bring to Iris
|
||||
rather than make here.
|
||||
|
||||
**Pass condition**: the full set of pass conditions above, re-run
|
||||
once more against a real `ai-server` (not the sandbox) on a real
|
||||
phone, side by side with the Compose build until it holds.
|
||||
|
||||
## For the next agent
|
||||
|
||||
What to do when you pick this up, in order, so nothing here has to be
|
||||
@@ -3206,10 +3442,10 @@ re-derived:
|
||||
2. Work on the **`rustify`** branch of this clone (`ai-app-2`), not on
|
||||
`main` and not in `ai-app`. Nothing on this branch is production until
|
||||
Iris says so. Commit and push as you go.
|
||||
3. Take the next unchecked box above, in order. E1 has proved android-view
|
||||
on this emulator, so the E-steps and the I-steps can now proceed in
|
||||
parallel in separate sessions; see "Where things stand" at the top for
|
||||
which is next.
|
||||
3. The E- and I-steps (the framework decision) are done — iris won,
|
||||
`DECISIONS.md` 2026-09-05. Take the next unchecked P-box in "## The
|
||||
port, in order (decided 2026-09-05)"; **P1 — session screen parity —
|
||||
is next.**
|
||||
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