Read a timeout in units, share one usage answer, and let a tilde mean home

The composer's settings row is outlined bubbles opening round menus, and
the message box is a TextFieldValue so anything put into it without being
typed -- a draft, a share, a slash command -- leaves the cursor at the end.

A tap that puts a text selection away no longer also collapses the card the
text was drawn in: every open and close on the session screen goes through
one guard that spends such a press on the selection.

The usage bar and the usage dialog were two polls of one measurement and
disagreed for up to a minute at a time; they are one feed now, and the
countdown rounds up to the minute in the one place both read.

A working directory typed as ~/repos/ai-app was four literal characters on
the local transport and as an argument on both, so the existence check
refused every home-relative path. It is checked by entering the directory
now, expanded for a local spawn the way the remote shell expands it, and
stored short so the phone draws what somebody would write.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-03 19:56:36 -04:00
1 parent 2970a6cf9a
commit 32b2a4871a
16 files changed
+414 -99

No files matched your search

@@ -15,20 +15,11 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import java.time.OffsetDateTime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Window bars for the account's rate limits, with reset times.
@@ -41,23 +32,7 @@ import kotlinx.coroutines.withContext
* handles that itself.
*/
@Composable
fun UsageDialog(settings: ServerSettings, onDismiss: () -> Unit) {
val scope = rememberCoroutineScope()
var state by remember { mutableStateOf<LoadState<List<UsageSnapshot>>>(LoadState.Loading) }
fun refresh() {
state = LoadState.Loading
scope.launch {
state =
try {
withContext(Dispatchers.IO) { LoadState.Loaded(fetchUsage(settings)) }
} catch (e: ApiException) {
LoadState.failed(e)
}
}
}
LaunchedEffect(Unit) { refresh() }
fun UsageDialog(feed: UsageFeed, onDismiss: () -> Unit) {
// A plain Dialog rather than an AlertDialog, for the spacing alone. AlertDialog fixes the
// gaps between its title, its content and its buttons at sizes meant for a sentence of prose
// and a decision; this is a dense read-out, and those gaps left a band of empty dialog above
@@ -84,7 +59,14 @@ fun UsageDialog(settings: ServerSettings, onDismiss: () -> Unit) {
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.weight(1f),
)
GlyphButton(REFRESH_GLYPH, "Refresh usage", { refresh() })
// A spinner in the button's place while the answer is on its way, since the
// numbers under it stay put during a refresh -- without it, pressing refresh
// over an unchanged read-out looks like a button that does nothing.
if (feed.refreshing) {
GlyphSpinner("Refreshing usage")
} else {
GlyphButton(REFRESH_GLYPH, "Refresh usage", feed.refresh)
}
}
Spacer(Modifier.height(8.dp))
// Scrolls rather than being trimmed: a machine can report any number of windows
@@ -92,7 +74,7 @@ fun UsageDialog(settings: ServerSettings, onDismiss: () -> Unit) {
// running out of room is silent. `fill = false` so a short read-out keeps a short
// dialog instead of stretching to the window.
Column(Modifier.weight(1f, fill = false).verticalScroll(rememberScrollState())) {
UsageBody(state)
UsageBody(feed.snapshots)
}
TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) {
Text("Close")