Merge remote-tracking branch 'origin/main'
# Conflicts: # app/androidApp/src/main/kotlin/com/example/aiapp/Sizes.kt
This commit is contained in:
commit
e3e02d55f7
29 files changed
+2737
-479
No files matched your search
@@ -5,11 +5,6 @@ replacing the Claude app for daily use. Rust/Axum backend on the desktop,
|
||||
Kotlin/Compose Android app, WireGuard + pinned self-signed TLS + bearer token
|
||||
between them.
|
||||
|
||||
**`TRANSCRIPT_RENDERING.md` is the record of the transcript work** --
|
||||
measurements, techniques, the harness, and the ordered list of what is
|
||||
next. Read it before touching anything under `Markdown*.kt`,
|
||||
`Transcript*.kt` or `SessionScreen.kt`'s list.
|
||||
|
||||
**`PLAN.md` is the design source of truth.** Read it before building or
|
||||
changing anything structural. It records every decision with its date, its
|
||||
rationale, and the alternatives that were rejected and why — keep that habit
|
||||
@@ -129,6 +124,35 @@ repo is in PLAN.md's "Backend layout" section.
|
||||
what makes two icon buttons the same width without either being given
|
||||
one, and it is why `GLYPH_SIZE` is smaller than it looks like it should
|
||||
be.
|
||||
- **The file explorer** — `FilesScreen.kt` (the navigation stack, the
|
||||
per-directory cache, the create dialog), `FileViewer.kt` (a `LazyColumn`
|
||||
of lines, each with its own colours from `FileLines.kt`, sharing one
|
||||
horizontal scroll so nothing wraps), `FileEditor.kt` (a
|
||||
`BasicTextField` with a `VisualTransformation` carrying the scanner's
|
||||
spans, which is the one Compose API that colours a field's own text).
|
||||
It draws **over** the session in `AppRoot`'s `Screen.Session`, so the
|
||||
session under it stays composed and coming back from a file costs
|
||||
nothing; back steps editor → viewer → directory → parent and only closes
|
||||
from where it opened. `EXPLORER.md` is the design and `server/src/files.rs`
|
||||
is the other half.
|
||||
To exercise it, `./ui-sandbox.sh` builds a fixture tree at the sandbox
|
||||
home's `~/files` holding the states that are otherwise only reachable by
|
||||
finding a real machine in one: an empty directory, a name with a tab in
|
||||
it and one with an apostrophe, a binary file, one over `FILE_LIMIT`, one
|
||||
`chmod 000`, a symlink to a directory and a broken one, a source file per
|
||||
language, and the three sizes the limits were measured against
|
||||
(`edit-32k.rs`, `edit-128k.rs`, `big-source.rs`), so those figures can be
|
||||
taken again rather than re-derived. Point a session at it with
|
||||
`./ui-sandbox.sh api /sessions/<id>/cwd -X POST -H 'content-type: application/json' -d '{"cwd":"~/files"}'`.
|
||||
The 409 is produced by editing the file on the machine (`printf … > file`)
|
||||
between pressing the pencil and pressing save.
|
||||
**Reading is cheap and editing is not**, and the sizes are measured
|
||||
rather than guessed -- see EXPLORER.md's "What the measurements said".
|
||||
The viewer handles a 1 MiB, 28,000-line file because it draws one row per
|
||||
line; the editor is one `BasicTextField`, which costs two seconds a frame
|
||||
at 128 kB and stops the app at 1 MiB, so `EDIT_LIMIT` caps it at 32 kB
|
||||
with the reason said on screen. If you make the editor faster, that
|
||||
number is what to move.
|
||||
- `.dev-updater.ron` — what Dev Updater is asked to do with this checkout:
|
||||
the server (built in `server/`, run as `service: Managed(...)`) and the
|
||||
APK (built in `app/`), built in parallel. The project it serves is the
|
||||
@@ -212,6 +236,23 @@ IQ2_XXS of that model produces fluent nonsense, which reads exactly like a
|
||||
broken driver — `llama-cli` produces the same from the file directly, which
|
||||
is how to tell the two apart in a hurry.
|
||||
|
||||
**No script that drives this app's UI presses a coordinate.** Every control
|
||||
is found by the name it already carries for assistive technology --
|
||||
`ui-trace record --do "tap 'Session settings'"`, which resolves the label
|
||||
against the screen at the moment of the gesture and fails the whole run
|
||||
when it is not there. `app/bench-lib.sh` is what `transcript-bench.sh` and
|
||||
`stream-bench.sh` share for it. A coordinate is a position measured once by
|
||||
hand, and anything that moves the control makes the tap land on whatever
|
||||
now sits there -- the bench then reports a number that was never measured,
|
||||
which reads exactly like a result. Both scripts pressed the render report
|
||||
at `tap 723 205` until that button moved into the session settings dialog
|
||||
on 2026-09-03. The check that none has crept back:
|
||||
|
||||
grep -n "tap [0-9]" app/*.sh
|
||||
|
||||
Swipes are still coordinates, deliberately: a gesture across a scrolling
|
||||
area is a distance rather than a control.
|
||||
|
||||
**How to test SSH here, since there is no second machine:** ssh this VM to
|
||||
itself. Generate a throwaway key, append the public half to
|
||||
`~/.ssh/authorized_keys`, and configure a host of `bob@127.0.0.1` with
|
||||
@@ -324,7 +365,7 @@ first if a remote spawn ever mangles an argument.
|
||||
**An inline code chip is drawn behind the text** rather than as the
|
||||
renderer's span background, because a span background is part of the
|
||||
text's own drawing and hid the selection under it -- see
|
||||
`appendCodeChip` in `MarkdownLinks.kt` and TRANSCRIPT_RENDERING.md.
|
||||
`appendCodeChip` in `MarkdownLinks.kt`.
|
||||
- **A session can be moved to another directory** from the settings dialog
|
||||
(`POST /sessions/{id}/cwd`). It stops the process, because a working
|
||||
directory is settled at spawn; the next message starts it in the new one.
|
||||
@@ -477,7 +518,48 @@ first if a remote spawn ever mangles an argument.
|
||||
the in-app copy button produces, whose `on screen:` line names what the
|
||||
viewport was actually holding. Compare two runs of it with the same
|
||||
gestures; the emulator's absolute frame times transfer nothing, the
|
||||
report's accounting does.
|
||||
report's accounting does. Run it either side of any change under
|
||||
`Markdown*.kt`, `Transcript*.kt` or `SessionScreen.kt`'s list, and put the
|
||||
report in the commit; the numbers that move first are the worst
|
||||
`record: one block`, the reparse mean while streaming, and the draw
|
||||
phase's accounting line.
|
||||
- **`app/stream-bench.sh [-k] FILE` is that measurement for a reply still
|
||||
arriving.** It opens the first session, taps "Jump to latest" so the list
|
||||
is pinned to the newest end, resets the report, sends FILE, waits for the
|
||||
transcript to stop growing, and prints. Both of those are corrections to a
|
||||
first version that measured nothing: a transcript parked further back never
|
||||
redraws while a reply streams into it, and a session is idle at *both* ends
|
||||
of a turn, so polling for idle answers before the turn has started.
|
||||
- **`app/trace-draw.sh` names what a scrolling frame spends inside the
|
||||
framework**, from `atrace` text output with no trace processor needed. It
|
||||
is how the cost of a layout node per link was attributed to the framework
|
||||
rather than guessed at.
|
||||
- **Two traps in the emulator bench loop**, each of which cost a run.
|
||||
`adb shell pm clear` removes the enrolment and the notification permission
|
||||
along with the saved anchors, so the next run measures a permission dialog
|
||||
-- re-enrol with the command `ui-sandbox.sh` prints, and
|
||||
`pm grant ... POST_NOTIFICATIONS`. And a saved scroll anchor is per session
|
||||
id, so the only way two builds start a scroll from the same place is a
|
||||
*fresh session for each*.
|
||||
- **A phone that falls behind the stream is answered with `reset`, and
|
||||
`RUST_LOG=ai_server=debug` says when.** Every SSE subscriber logs the
|
||||
cursor it arrived with and whether it was continued or reset
|
||||
(`stream backlog:` in `send_backlog`), which is the only place that
|
||||
question is answerable: the app sees a window arrive and cannot tell how
|
||||
far it had fallen, and a reset is the one thing that makes its screen jump
|
||||
to the newest end. Measured 2026-09-04 against a session streaming at 20
|
||||
events a second: reopening one with an anchor 1,800 events back connects
|
||||
**87-119 events behind**, well under `CATCH_UP_LIMIT`'s 200, because the
|
||||
restore is two requests -- the opening page, then one span covering the
|
||||
whole distance to the anchor. So the reset path is not reachable by
|
||||
reopening a session, and **to exercise it at all you have to lower
|
||||
`CATCH_UP_LIMIT`** in a throwaway server build; at 5 the app takes the
|
||||
reset on a live connection, clears, refills and carries on without
|
||||
reconnecting. Worth knowing alongside it: **the session screen's stream
|
||||
survives backgrounding here** -- 20 seconds at the launcher while 415
|
||||
events were produced brought no reconnect at all -- which is not what the
|
||||
comment above that loop expects, and is most likely this emulator being
|
||||
headless rather than the phone's behaviour.
|
||||
- **`ai-server --delay MS` holds every response back.** Over the tunnel a
|
||||
phone's requests take tens to hundreds of milliseconds, and several
|
||||
faults live entirely in what the app does *while* one is outstanding. On
|
||||
|
||||
Reference in new issue
Block a user