Report what a session last did and what it cost, not what this page holds
Three readings that were each a part presented as the whole.
**"just now", everywhere, after a restart.** A relaunched session took its
last-activity from the clock, so every session the backend brought back
claimed to have been active that instant. On the phone that is every row
reading "just now" and the list -- which sorts by it -- coming back in an
order that means nothing, with the conversation somebody was in the middle
of buried among sessions untouched for days. It comes from the transcript
now, in the pass `Transcript::open` already makes, which is the same
correction `last_status` got and for the same reason: a server that has just
started has been told nothing, and the file is the only thing it knows. The
test backdates a transcript by a day, so it cannot pass by the test being
fast; it fails on the old code with the clock's answer in the message.
**The token total was the newest page's.** The phone added up the
`UsageDelta`s it had received, and it opens a session on the newest page of
the transcript -- so a long conversation reported its last few turns as the
total, and a page with no turn in it reported nothing at all, since zero is
drawn as blank. That is the reading Bryan saw: no tokens, on sessions that
had certainly spent some.
The count belongs to the server, which is the only side that sees every
turn. `UsageDelta` now carries the running total beside the delta, filled in
by the pump rather than by each driver -- a driver knows what its own turn
cost and nothing else does, so a new one cannot get this wrong by leaving it
out -- and the session row reports it for a screen that has not opened the
stream yet. The phone takes the largest total it has seen instead of
accumulating, which also means paging older history cannot move it, and
leaves the seeded figure alone for transcripts recorded before the field
existed. Seeded by summing deltas at startup for exactly that reason.
**The header said the model twice and the machine backwards.** A session's
subtitle now reads `machine · provider`, in that order and with no "on"
joining them, matching the list and the usage dialog -- the "on" made it a
phrase, which works in one order and stops working the moment the same pair
is shown somewhere else. The model is gone from it: the footer's picker
already shows what the session is set to, and two places showing it meant
two things to keep in step, which disagreed for a moment on every switch
since one follows the request and the other the session's own answer.
Checked on the emulator against a twelve-turn session whose visible page
held the last six: the header reads "this machine · echo", the status row
reads "idle", and the total reads 42 tok, which is what `GET
/sessions/{id}` says rather than what the page adds up to.
This commit is contained in:
1 parent
694535badc
commit
47d6b84265
10 files changed
+253
-23
No files matched your search
@@ -366,7 +366,10 @@ fun SessionScreen(
|
||||
val scope = rememberCoroutineScope()
|
||||
var items by remember { mutableStateOf(listOf<TranscriptItem>()) }
|
||||
var status by remember { mutableStateOf(summary.status) }
|
||||
var totalTokens by remember { mutableLongStateOf(0L) }
|
||||
// Seeded from the row this screen was opened from, so a conversation that has spent
|
||||
// something says so before any turn happens here. Zero used to mean "nothing yet" and
|
||||
// "nothing in the page I loaded" at once, and the second one is most of the long sessions.
|
||||
var totalTokens by remember(summary.id) { mutableLongStateOf(summary.totalTokens) }
|
||||
// When this screen saw the current compaction start, on this device's own clock, and how long
|
||||
// ago that is. See `compactingLabel`: null is the honest answer whenever the start was not
|
||||
// witnessed here, which is what opening a session that is already compacting looks like.
|
||||
@@ -451,7 +454,13 @@ fun SessionScreen(
|
||||
moreHistory = entry.seq > 1L
|
||||
}
|
||||
when (val event = entry.event) {
|
||||
is SessionEvent.UsageDelta -> totalTokens += event.tokens
|
||||
// Taken, not accumulated: the server's running total is on the event, and adding
|
||||
// up the deltas this screen happened to receive counted one page of a conversation
|
||||
// and called it the whole. `max` because pages arrive in no guaranteed order and an
|
||||
// older event's total is a smaller true answer, never a correction downwards; it
|
||||
// also leaves the seeded figure alone for transcripts recorded before the backend
|
||||
// sent a total at all.
|
||||
is SessionEvent.UsageDelta -> totalTokens = maxOf(totalTokens, event.total)
|
||||
else -> {
|
||||
// What the session says it is set to now, which is the only thing that
|
||||
// says it: picking from either menu asks, and the answer comes back here.
|
||||
@@ -795,15 +804,18 @@ fun SessionScreen(
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(title, style = MaterialTheme.typography.titleMedium)
|
||||
// Machine first, then what runs on it -- the same order and the same wording
|
||||
// everywhere this pair appears, so it reads as one fact rather than as two
|
||||
// sentences with different grammar. The "on" that used to sit in the middle
|
||||
// made it a phrase, which only works in one order and stops working the moment
|
||||
// the pair is shown anywhere else.
|
||||
//
|
||||
// No model. The picker in the footer already shows what this session is set to,
|
||||
// and showing it twice means two things to keep in step -- they disagreed for a
|
||||
// moment on every model change, since one follows the request and the other the
|
||||
// session's own answer.
|
||||
Text(
|
||||
listOfNotNull(
|
||||
summary.provider,
|
||||
"on ${summary.setupName}",
|
||||
// What the session says it is set to now, which is the same fact
|
||||
// the picker below shows and has to be the same answer.
|
||||
model?.let { modelLabel(it) },
|
||||
)
|
||||
.joinToString(" · "),
|
||||
"${summary.setupName} · ${summary.provider}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user