From cf3ed685110aa27e8a033eb0ba0e1a212f2b08e9 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sat, 29 Aug 2026 20:16:31 -0400 Subject: [PATCH] Tell "nothing to meter" apart from "couldn't find out" Found by looking at the bar on an echo session rather than by reading the diff: it said "5-hour usage unknown -- this machine reports no usage", which is the failure the rest of this file was written to avoid, one level up. A machine with no metered provider is never asked by the backend, so it returns no snapshot for it. The bar read that silence as a failed lookup, because Unavailable was the nearest word it had -- and a session on `echo`, or on a local llama.cpp, has no paid quota at all. That is a fact about how somebody set the machine up, not a question that went unanswered, and reporting it as unknown nags about a deliberate choice on every screen forever. So the state exists now: NotMetered, drawn as nothing, because there is nothing. Unavailable keeps its words and its reason and still covers the three ways an answer can fail -- nobody logged in, machine unreachable, snapshot without the window. Verified on the emulator against the real endpoint: a setup carrying claude-cli draws the bar at 22% of 5h, selected by kind "session"; the no-snapshot path was the one on screen before this change, so it is reached, and this only changes what it draws. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VETa8afmpWaYezLCqJhDB8 --- .../com/example/aiapp/SessionUsageBar.kt | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionUsageBar.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionUsageBar.kt index 9360669..ff566aa 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionUsageBar.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionUsageBar.kt @@ -27,6 +27,17 @@ sealed class FiveHourUsage { data class Known(val percent: Double, val resetsAt: String?) : FiveHourUsage() + /** + * This machine meters nothing, so there is no window to show. + * + * Separate from [Unavailable], and the distinction is the whole point: a session on `echo` or + * on a local llama.cpp has no paid quota at all, which is a fact about how it was set up and + * not a failure to find something out. The backend never asks such a machine, so it returns no + * snapshot for it -- and reading that silence as "couldn't find out" is exactly the mistake of + * answering with the nearest available word. Drawn as nothing, because there is nothing. + */ + data object NotMetered : FiveHourUsage() + /** * The question could not be answered, and why. * @@ -69,11 +80,18 @@ fun SessionUsageBar(settings: ServerSettings, setup: String, modifier: Modifier } } + // Nothing at all for a machine that meters nothing: a row saying "unknown" there would + // report a problem about a setup somebody chose, on every screen, forever. + if (usage is FiveHourUsage.NotMetered) { + return + } + Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 2.dp), ) { when (val state = usage) { + FiveHourUsage.NotMetered -> Unit // Words, not a colour and not an empty bar: "couldn't check" is a different kind of // answer from "this much is used", and only words carry a difference in kind. is FiveHourUsage.Unavailable -> @@ -111,14 +129,18 @@ fun SessionUsageBar(settings: ServerSettings, setup: String, modifier: Modifier * the label is written to be read and would stop matching the day its wording changes, silently * leaving the bar with nothing to show. * - * Every way of not having a number is [FiveHourUsage.Unavailable] with the reason in it: a machine - * nobody logged into, one that could not be reached, a snapshot that came back without the window. - * None of them may look like zero. + * Every way of having *failed* to get a number is [FiveHourUsage.Unavailable] with the reason in + * it: a machine nobody logged into, one that could not be reached, a snapshot that came back + * without the window. None of them may look like zero, and none of them may look like + * [FiveHourUsage.NotMetered], which is the machine having no quota rather than the question going + * unanswered. */ fun fiveHourFor(snapshots: List, setup: String): FiveHourUsage { val mine = snapshots.firstOrNull { it.setup == setup } + // No snapshot at all means the backend never asked, which it only does for a machine with + // nothing metered on it. That is a different answer from having asked and failed. if (mine == null) { - return FiveHourUsage.Unavailable("this machine reports no usage") + return FiveHourUsage.NotMetered } if (mine.state != "ok") { return FiveHourUsage.Unavailable(mine.detail ?: mine.state)