Merge remote-tracking branch 'origin/main'

This commit is contained in:
iris committed 2026-08-30 00:14:23 -04:00
commit 49d3439ec4
22 files changed
+1125 -61

No files matched your search

@@ -410,7 +410,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.
@@ -495,7 +498,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.
@@ -839,15 +848,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,
)
@@ -1344,16 +1356,25 @@ private fun SessionStatusRow(
)
Spacer(Modifier.weight(1f))
}
else -> {
if (status == "exited") {
Text(
"exited",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Spacer(Modifier.weight(1f))
}
// Every remaining state says which one it is, including the quiet one. The row used
// to name only `exited` and leave the rest blank, so a session sitting idle and one
// whose status nobody could read looked identical -- and a turn that had just been
// stopped showed nothing at all, which reads as the app having lost the session
// rather than as the stop having worked. The words are the session list's own, so
// one state is not called two things depending which screen you are on.
else ->
Text(
when (status) {
"idle" -> "idle"
"exited" -> "exited"
"awaitingInput" -> "your turn"
"unknown" -> "can't tell"
else -> status
},
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
)
}
// Nothing rather than "0 tok" before anything has been spent: a total of zero is a fact
// about a conversation that has not started, and it is the one reading nobody needs.