The other half of EXPLORER.md: a folder button on the session header opens the machine's filesystem, starting where the session works. It draws **over** the session in the same `Box`, so the session under it stays composed -- its event stream keeps flowing, its draft and scroll position stay where they were, and coming back from a file costs nothing. Back steps one level inside it (editor, viewer, directory, parent) and only closes from where it opened; the platform gesture, the button and the swipe all go through the one function, so they cannot mean different things. The viewer is a `LazyColumn` of lines rather than one `Text`, because text layout is linear in the text and a twenty-thousand-line file in a single `Text` measures all of it to draw a screenful. Lines do not wrap and share one horizontal scroll, so a logical line is a visual line and the gutter cannot come to number the wrong text; the gutter's width is measured from the digit count of the line count in the style it is drawn in. The editor is a `BasicTextField` with a `VisualTransformation` carrying the scanner's spans, which is the one Compose API that colours a field's own text rather than replacing the field. `fileLanguage` reads the same table `fenceLanguage` does, so a language added for fences is a language added for files. A file that changed on the machine while it was open here refuses to be overwritten and asks, with what each of the three answers costs. That is the ordinary case, not the exotic one: an agent editing the file somebody is reading is what this whole feature is for. The speedometer moves off the header into the session settings dialog, where the session's other about-the-session controls are, and the folder takes a place between the usage chart and the cog -- widest scope to narrowest, cog at the end, as Iris asked. Both benchmark scripts move onto `ui-trace`'s new tap-by-label action in the same change, so the render report is never unavailable and never pressed at a coordinate that has stopped meaning anything; `app/bench-lib.sh` is what they share, and `grep -n "tap [0-9]" app/*.sh` is the check. Exercised on the emulator against the sandbox's new fixture tree, with a screenshot or a ui-trace for each: the listing (dotfiles, directories first, a symlink to a directory sorted with them, a name with a tab in it), a highlighted file, binary, too big, a permission error, editing and saving, the 409 and its Overwrite, back with unsaved edits, creating a name that exists, creating one that does not and landing in the editor, an empty directory, and `..` above the directory the session opened in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
3.7 KiB
Bash
Executable File
95 lines
3.7 KiB
Bash
Executable File
#!/bin/sh
|
|
# Streams a message into the sandbox session on screen and prints the app's
|
|
# render report for the time it took -- the standard measurement for "is a
|
|
# reply that is still arriving cheap", the way transcript-bench.sh is for
|
|
# scrolling one that has settled.
|
|
#
|
|
# ./stream-bench.sh FILE restart the app, open the session the
|
|
# sandbox lists first, stream FILE into
|
|
# it, report
|
|
# ./stream-bench.sh -k FILE keep whatever session is open now
|
|
#
|
|
# The session is the first one the sandbox lists (ui-sandbox.sh spawn makes
|
|
# one), and the file is echoed back a word at a time, which is the shape a
|
|
# real reply arrives in. The numbers to read are `markdown reparsed while
|
|
# streaming` -- how many times, and how long each -- and the worst
|
|
# `record: one block`; both are proportional to how much of the reply the
|
|
# tail reparse has to cover, which is what the live parse exists to bound.
|
|
set -eu
|
|
cd "$(dirname "$0")"
|
|
. ./android-env.sh >/dev/null 2>&1
|
|
. ./bench-lib.sh
|
|
|
|
keep=""
|
|
while getopts k flag; do
|
|
case $flag in
|
|
k) keep=1 ;;
|
|
*) exit 2 ;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
file=${1:?usage: stream-bench.sh [-k] FILE}
|
|
|
|
sid=$(./ui-sandbox.sh api /sessions | python3 -c 'import json,sys; print(json.load(sys.stdin)[0]["id"])')
|
|
|
|
if [ -z "$keep" ]; then
|
|
title=$(first_session_title)
|
|
[ -n "$title" ] || { echo "stream-bench: no sessions to open" >&2; exit 1; }
|
|
open_session "$title"
|
|
fi
|
|
|
|
# Pinned to the newest end before anything is sent. The transcript never
|
|
# moves under a reader who is further back (see TranscriptList), so a reply
|
|
# streaming into a session parked at an older row arrives entirely
|
|
# off-screen: nothing recomposes, nothing draws, and the report comes back
|
|
# with two recompositions in it and no streaming counters at all. That
|
|
# reads exactly like a build where the work vanished. The control is the
|
|
# app's own "Jump to latest", which is only there while the newest message
|
|
# is off screen -- so when it is absent the list is already where it needs
|
|
# to be. Looked for before it is pressed rather than pressed blindly: a tap
|
|
# by name fails the whole run when its label is missing, and missing is the
|
|
# ordinary case here.
|
|
ui-trace record -d 1200 -o /tmp/bench-jump.txt >/dev/null 2>&1
|
|
if ui-trace show /tmp/bench-jump.txt -m 'Jump to latest' --field box | grep -q '[0-9],[0-9]'; then
|
|
ui-trace record -d 2500 --do "tap 'Jump to latest'" --do 'wait 1000' \
|
|
-o /tmp/bench-tolatest.txt >/dev/null
|
|
fi
|
|
|
|
# The first copy resets the report's window; see transcript-bench.sh.
|
|
copy_render_report /tmp/bench-reset.txt
|
|
adb logcat -c
|
|
|
|
./ui-sandbox.sh send "$sid" "@$file" >/dev/null
|
|
|
|
# Until the reply has finished, measured by the transcript rather than by
|
|
# the status. A session is idle at both ends of a turn, and polling for
|
|
# "idle" answers on the first poll -- before the turn has even started --
|
|
# so the report then covers the moment between the send and the first
|
|
# delta, and prints a window with nothing in it. The event count only
|
|
# grows, so "it stopped growing" is the one signal that cannot be true
|
|
# before the work begins.
|
|
events() {
|
|
./ui-sandbox.sh api "/sessions/$sid/transcript?limit=1" |
|
|
python3 -c 'import json,sys; d=json.load(sys.stdin); print(d[-1]["seq"] if d else 0)'
|
|
}
|
|
last=""
|
|
still=0
|
|
i=0
|
|
while [ "$i" -lt 180 ]; do
|
|
now=$(events)
|
|
if [ "$now" = "$last" ]; then
|
|
still=$((still + 1))
|
|
[ "$still" -ge 2 ] && break
|
|
else
|
|
still=0
|
|
fi
|
|
last=$now
|
|
sleep 2
|
|
i=$((i + 1))
|
|
done
|
|
sleep 1
|
|
|
|
copy_render_report /tmp/bench-report.txt
|
|
sleep 1
|
|
adb logcat -d -s ai-app:I | sed -n '/ai-app render report/,$p' | sed 's/^.*ai-app : //'
|