Keep the reader's place: in a group, in a compaction, and in the list
Five things Iris asked for, all about the transcript screen holding still around whoever is reading it. A tool call opened on its own stayed open when a second call in the same run turns it into a group. Watching a Bash call and having the session make another one used to shut the card being read and fold it behind "Called 2 tools" -- the reader lost their place because something else happened. The transition is noticed once, at the moment a run first becomes a group; after that the group's own toggle owns it, so shutting a group whose inner call is still expanded does not re-open it. The compaction clock is taken from the `compacting` status event's own timestamp rather than from this device noticing one, so it survives leaving the session and coming back -- it used to disappear, because the only thing that knew when the compaction started was a screen that had been disposed. The server timestamps every transcript line, so this is still a measurement; it is compared against the phone's wall clock, which is the same comparison a session's "last active" already makes. Session settings are a dialog over the session instead of a screen below it. Two controls did not warrant a page transition and a back stack, and the thing they change was hidden while they were on screen. Captions are gone -- each control is a labelled noun -- and "Notify me" is "Notifications" with a bell beside it (`md-bell`, added to the committed Nerd Fonts subset). Failures keep their words, since those are what a reader cannot work out by looking. Tool groups are rounded like every other card, their foot bar is the same height as their heading (both derived from the heading's own line height, so the pair cannot drift), and the calls inside are a connected stack: square where they face a neighbour, rounded on the outside, with a small gap so the join reads as a join. Scroll position is persistent on the device, per session, keyed by the row rather than by an index -- an index means nothing across a reopen, where the transcript is fetched newest-first. Reopening pages backwards until that row is loaded *and* has something older behind it, because the oldest loaded row is a half-row that grows when the page behind it arrives; anchoring into one landed a screen and a half out. The list draws nothing until the position lands, so there is no frame in which the transcript is somewhere other than where it was left. Two things found on the way. `snapshotFlow`'s first emission is the state before anybody has touched the list, and reading it as a scroll that had just ended at the newest end wiped every saved position on the way in. And backwards pages now ask for 800 events rather than 80: ai-app-2 measured a real transcript at 2,426 events for seven assistant messages, so a page of eighty is a fifth of one row and filling the lookahead took about thirty sequential round trips -- seconds of a list that will not move, over the tunnel. `/tools [n] [gap]` in the echo driver takes seconds between calls, which is what makes a run grow slowly enough for somebody to have opened one of its calls first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b0e654733
commit
61d2c78afe
10 files changed
+626
-323
No files matched your search
@@ -29,7 +29,9 @@ import kotlinx.coroutines.withContext
|
||||
*
|
||||
* Import, models and setups are not here any more. They are tabs inside [MainScreen] -- four views
|
||||
* of the same backend, none of them a step down from another -- and what is left in this `when` is
|
||||
* only what genuinely is a step down: one session, spawning one, and settings.
|
||||
* only what genuinely is a step down: one session, spawning one, and settings. A session's own
|
||||
* settings are not among them: they are a dialog over the session, which is where the thing they
|
||||
* change is.
|
||||
*/
|
||||
private sealed class Screen {
|
||||
data object Main : Screen()
|
||||
@@ -38,13 +40,6 @@ private sealed class Screen {
|
||||
|
||||
data object Spawn : Screen()
|
||||
|
||||
/**
|
||||
* What can be changed about one session. Carries the session back with it so Back returns where
|
||||
* it came from, and carries it *out* renamed, so the session behind it shows the new name
|
||||
* without waiting for a list refresh.
|
||||
*/
|
||||
data class SessionSettings(val from: SessionSummary) : Screen()
|
||||
|
||||
data object Settings : Screen()
|
||||
}
|
||||
|
||||
@@ -174,12 +169,7 @@ fun AppRoot(settingsVersion: Int, openRequest: SessionOpenRequest?) {
|
||||
// row key. Only reachable since a notification can move straight from one session to
|
||||
// another; every other way here passes through [Screen.Main], which disposes it anyway.
|
||||
key(here.summary.id) {
|
||||
SessionScreen(
|
||||
settings = current,
|
||||
summary = here.summary,
|
||||
onBack = goToMain,
|
||||
onSettings = { screen = Screen.SessionSettings(here.summary) },
|
||||
)
|
||||
SessionScreen(settings = current, summary = here.summary, onBack = goToMain)
|
||||
}
|
||||
is Screen.Spawn ->
|
||||
SpawnScreen(
|
||||
@@ -190,18 +180,6 @@ fun AppRoot(settingsVersion: Int, openRequest: SessionOpenRequest?) {
|
||||
},
|
||||
onBack = goToMain,
|
||||
)
|
||||
is Screen.SessionSettings ->
|
||||
SessionSettingsScreen(
|
||||
settings = current,
|
||||
session = here.from,
|
||||
onRenamed = { renamed ->
|
||||
// The list shows the name too, so it has to refetch; and the session
|
||||
// returned to is the renamed one, not the one this was opened from.
|
||||
reloadToken++
|
||||
screen = Screen.Session(renamed)
|
||||
},
|
||||
onBack = { screen = Screen.Session(here.from) },
|
||||
)
|
||||
is Screen.Settings ->
|
||||
SettingsScreen(
|
||||
existing = current,
|
||||
|
||||
Reference in new issue
Block a user