Files
ai-app/app/transcript-bench.sh
T
irisandClaude Opus 5 db55ed4a8f The file explorer on the phone
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>
2026-09-03 23:58:59 -04:00

66 lines
2.4 KiB
Bash
Executable File

#!/bin/sh
# Scrolls the transcript and prints the app's own render report -- the
# standard measurement for "is the transcript smooth", so a perf session
# starts from a number instead of re-inventing this loop in /tmp.
#
# ./transcript-bench.sh restart the app, open the session the
# server lists first, scroll, report
# ./transcript-bench.sh -s TITLE open that session instead
# ./transcript-bench.sh -k keep whatever screen is open now
# ./transcript-bench.sh -n 4 swipe cycles (default 6; one cycle is
# two swipes back and two forward)
#
# What it prints is the report the in-app copy button produces, plus any
# duplicate-key warnings the flatten logged. Absolute frame times from the
# emulator are worthless (it renders in software); what transfers is the
# report's own accounting -- what was on screen, and where the draw phase
# went. Compare two runs of this with the same gestures, not one run
# against a phone.
#
# The swipes are still coordinates, because a gesture across a scrolling
# area is a distance rather than a control. Every *control* is pressed by
# name -- see bench-lib.sh.
set -eu
cd "$(dirname "$0")"
. ./android-env.sh >/dev/null 2>&1
. ./bench-lib.sh
keep=""
cycles=6
session=""
while getopts kn:s: flag; do
case $flag in
k) keep=1 ;;
n) cycles=$OPTARG ;;
s) session=$OPTARG ;;
*) exit 2 ;;
esac
done
if [ -z "$keep" ]; then
[ -n "$session" ] || session=$(first_session_title)
[ -n "$session" ] || { echo "transcript-bench: no sessions to open" >&2; exit 1; }
open_session "$session"
fi
# The first copy resets the report's "work since last copied" window, so the
# numbers cover exactly the swipes between the two.
copy_render_report /tmp/bench-reset.txt
adb logcat -c
DO=""
i=0
while [ "$i" -lt "$cycles" ]; do
DO="$DO --do 'swipe 540 700 540 1600 200' --do 'wait 500'"
DO="$DO --do 'swipe 540 700 540 1600 200' --do 'wait 500'"
DO="$DO --do 'swipe 540 1600 540 700 200' --do 'wait 500'"
DO="$DO --do 'swipe 540 1600 540 700 200' --do 'wait 500'"
i=$((i + 1))
done
eval ui-trace record -d $((cycles * 4400 + 2000)) $DO -o /tmp/bench-scroll.txt >/dev/null 2>&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 : //'
adb logcat -d 2>/dev/null | grep -i "duplicate unit key" | head -3 || true