Files
orca/tests/tools/omp-transcript-rendered
Brennan Benson 5e3effc32f fix(native-chat): show every user message on the message rail, not just loaded ones (#22558)
* fix(native-chat): show every user message on the message rail, not just loaded ones

The message rail was built only from transcript rows the renderer had
loaded, so any prompt above the loaded page had no tick, and the rail lost
ticks when a long live session trimmed its retained window.

The host now answers `agentSession.conversationOutline`: every user message
in a structured session's journal (item id, creation sequence, a preview
cut to 200 characters, image count) plus the journal position it is current
through. It is derived from the reduced journal on each request with the
same projection the transcript runs, so an entry's id and preview are what
the loaded row shows. The reply is bounded like a history page: previews
shorten, then drop, and only then do the oldest entries go.

The renderer asks only while the pane is visible and older history is
unloaded, uses outline entries only for messages older than its loaded
window (the window is authoritative for the rest), and falls back to loaded
messages while the outline is stale (epoch change, or the window trimmed
past what it covers). Selecting a tick with no row pages older history in
until the row exists, then uses the existing rail jump.

The method is negotiated with `agent-session.conversation-outline.v1`; a
client never calls a host that does not advertise it, and any failure
leaves the rail on loaded messages.

* fix(native-chat): keep a rail jump from the bottom from re-arming follow and cancelling itself

A rail jump started by a reader following the end stopped a few pixels
above the bottom instead of reaching the message. Paging older history in
for a jump always leaves the reader following at the very end, so jumps to
unloaded messages hit it every time; a jump to a loaded message from the
end did too.

The jump scrolls smoothly, and only its landing is marked as the
application's own scroll. Its first frames move a pixel or two, still
inside the band where a reader event re-arms follow, so the list read the
jump leaving the end as the reader arriving at it. The next frame, just
outside the band, then read as the reader taking over and rebased the view
with an instant write, which cancels the smooth scroll.

Re-arming follow now needs the reader to be arriving at the end: an
unmarked event that moved the view up never reattaches a detached reader.

* test(native-chat): check a rail jump left the end before reading where it landed

* fix(native-chat): keep the rail's message list still while a press selects an item

With the whole conversation in the rail, its hover list overflows and opens
scrolled to the message being read. Clicking an older item did nothing:
pressing it focuses it, which turns the hover preview interactive, and that
switch re-ran the effect that scrolls the lit row into view and focuses it.
The list moved under the pointer between press and release, so the click
landed on the list instead of the item, and focus jumped to the lit row.

Revealing the lit row now follows the list opening (and its rows shifting),
not the switch between hover and interactive. Entering interactive moves
focus into the list only when focus is not already on one of its items.

* perf(native-chat): keep the rail's outline entries stable while a trimmed window slides

A long live session holds a head-trimmed window, so every new row moved the
oldest-loaded edge and rebuilt the outline view even when no user message
crossed it. The rail then re-merged, re-rendered and re-read the scroll
geometry on each new row. The view is now reused while the set of entries
older than the edge is unchanged.

* fix(native-chat): let a rail jump wait out an older page already loading

Scrolling to the top of the loaded window asks for the next older page. A rail
jump made while that page was in flight asked again, got the lane's immediate
no-op return, read it as a page with no progress, and dropped the click. The
jump now waits for the in-flight page to land before deciding.

* fix(native-chat): reattach follow when content shrinking clamps a reader onto the end

The rule that stops a smooth scroll leaving the end from re-arming follow
compared offsets, so it also refused a reader whose offset dropped because
settled content folded away beneath them and the browser clamped them onto
the end. They sat at the bottom without following, and the next reply grew
out of view. Re-arming now requires closing on the end rather than moving
down, which still rejects a scroll leaving it.

* fix(native-chat): keep the rail hooks' ref writes out of render

Both hooks wrote a ref while rendering, which React may replay or discard.
The rail's structural-sharing baseline is now recorded after commit, and the
history jump calls the lane's page loader from its effect instead of through
a render-updated ref.

* fix(native-chat): let the latest rail pick win over a jump still paging

A jump to an unloaded message keeps paging older history until it lands. A
pick made meanwhile lost to it: a loaded message scrolled into view, then the
earlier jump finished and pulled the reader away; another unloaded message was
ignored. Picking a loaded message now cancels the paging jump, and picking an
unloaded one retargets it without asking for a second page.

* fix(native-chat): step a rail history jump with a functional update

The step that requests the next page wrote the pending jump from the value
its effect closed over, so a pick or cancel queued since that commit would
be overwritten.

* refactor(native-chat): run a rail history jump as one abortable awaited loop

The jump through unloaded history was an effect-driven state machine that
guessed "no progress" from the message list's identity and could not be
cancelled by anything but another rail pick. A diff reveal, "Jump to latest"
or the reader scrolling left it paging, and when its page landed it pulled
the reader away; a history read that kept failing during a live turn could
repeat back to back.

Loading an older page now reports how it ended, and a second request while
a page is in flight joins it instead of being refused. The jump is an
awaited loop that reads the rail from a commit after each page, stops on
anything but a page that moved the window, and is aborted by any other
navigation, reader input (wheel, touch, scroll keys, scrollbar), a session
switch or unmount. The latest pick wins.

* fix(native-chat): derive the rail outline from the transcript's own projection

The host built the outline from user items alone, while the transcript
orders every message by when it was observed, folds tool results into the
turn above and then drops harness turns. An imported user row carrying a
tool result beside harness text therefore got a rail tick previewing the
harness text, and clicking it paged history for a row that never draws.

The transcript's order-fold-strip projection now lives in one shared
function. The renderer's list projection wraps it with its own tail-row
order, and the host runs it over the whole journal and keeps the user rows
that draw content, so the outline lists the same messages in the same order.

* fix(native-chat): retry a failed rail outline read a few times

A failed outline read left the rail on loaded messages until a new gap
opened, the pane was shown again or the epoch changed. The client now
rejects a failed read (a host without the outline still resolves to
nothing, without being called), and the rail retries up to three times
with doubling backoff.

* test(native-chat): give the rendered-transcript fixture the older-page result contract

* perf(native-chat): sort the shared transcript projection without a spread copy

* fix(native-chat): let a wheel over the rail cancel a rail jump still paging

The rail forwards its wheel to the transcript, so a reader scrolling there is
scrolling the transcript. That wheel never reached the scroller's reader-input
handlers, so the jump kept paging and later pulled the reader to its target.

* fix(native-chat): keep the rail's list open while a picked message pages in

Picking a message that is not loaded yet can take several pages of older
history. The list closed on the pick, so its busy item was never seen and
the click looked ignored. The list now stays open with that item pulsing
until the jump lands or is abandoned, and stops revealing the lit row
meanwhile so the rows do not move under the pointer.

* fix(native-chat): keep the shared transcript projection loadable on mobile

The projection moved to src/shared, which mobile's Hermes engine also loads,
and switching its sort to toSorted broke the Hermes compatibility guard.
Sort a copy made with Array.from instead, as other shared code does.
2026-09-23 20:26:54 -07:00
..

OMP transcript rendering proof

From the repository root, choose a disposable output file and run:

ORCA_BACKGROUND_LAUNCH=1 bun tests/tools/omp-transcript-runtime-smoke.mjs /path/to/read-only/oh-my-pi /tmp/omp-transcript-reader-proof.json
ORCA_BACKGROUND_LAUNCH=1 node tests/tools/omp-transcript-rendered/run.mjs /tmp/omp-transcript-reader-proof.json

The first command loads Orca's generated extensions with the actual OMP loader, creates persistent sessions through OMP's SessionManager in disposable directories, receives HTTP metadata, and reads the resulting files with Orca's transcript reader. Its optional third argument exports those exact reader messages. It covers both dedicated and Pi-routed OMP, initial and new sessions, lazy file creation, custom session directories, and child suppression. No model is called.

The second command renders the exported messages using the production desktop message list and styles, checks all four messages are visible, and captures CDP screenshots. It reuses the existing hidden Electron fixture host and writes its disposable profile, screenshots, and report under .bench-fixtures/omp-transcript-*. The test asserts all native windows remain hidden and unfocused.

This is a component rendering proof with actual reader output, not a full Orca shell or a live agent conversation. It does not exercise native Windows, WSL, or remote transport at runtime; separate resolver tests cover WSL path authority.