Tell "this window is not running" from "we could not read the time"
The five-hour window's reset time is absent between blocks, because the window is anchored to the block it started in and there is nothing to reset until one is running. Measured against a live response: the reset came back as exactly five hours after work resumed, and the weekly windows in the same response carried the identical microsecond, so both are computed from one now() at request time. The weeklies always have a reset because a week is always running -- which is why only the five-hour row looked wrong. `remainingUntil` returned null for that and for a timestamp it could not parse, so the session bar announced "reset time unknown" about a machine behaving perfectly, on the one row somebody reads before starting something big. The usage dialog, looking at the same field, drew nothing at all and printed a raw ISO string when a parse did fail. One missing value, two rules, and neither of them right. `WindowEnd` names the three answers and both callers go through it. A window that is not running shows its percentage and no countdown, in the bar as well as the dialog; an unreadable timestamp says so in words rather than showing itself. Looked at both on the emulator, the second by making the server drop the field: 15% with "4h 43m left" when a block is running, "13%" alone when none is, and the dialog's five-hour row with no reset line beside weeklies that have one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3da0f2e2f6
commit
4edc96de09
4 files changed
+78
-25
No files matched your search
@@ -20,18 +20,42 @@ fun formatSpan(until: Duration): String =
|
||||
}
|
||||
|
||||
/**
|
||||
* Time from [now] until [resetsAt], or null when that timestamp cannot be read.
|
||||
* What is known about when a usage window ends.
|
||||
*
|
||||
* Null rather than a zero duration, because a string this app failed to parse is not a window that
|
||||
* has just run out: a caller given zero for both would tell the reader to refresh on the strength
|
||||
* of something nobody measured.
|
||||
* Three answers rather than a nullable duration, because two of them shared `null` and they are not
|
||||
* the same thing at all. A window the server sent no reset time for is one that is **not running**:
|
||||
* the five-hour window is anchored to the block it started in, so between sessions there is nothing
|
||||
* counting down and the API says so by omitting the field -- measured against a live response on
|
||||
* 2026-08-31, where the five-hour window's reset was exactly five hours after the moment work
|
||||
* resumed. A timestamp that did arrive and could not be read is the genuinely unknown case, and it
|
||||
* is the only one worth those words.
|
||||
*
|
||||
* Collapsing them put "reset time unknown" on the session bar for a machine behaving perfectly, on
|
||||
* the one row somebody reads before starting something big -- and the usage dialog, looking at the
|
||||
* same field, quietly drew nothing. Two rules for one missing value; this is the rule.
|
||||
*/
|
||||
sealed class WindowEnd {
|
||||
/** No reset time was sent, so nothing is running in this window. Not a failure to find out. */
|
||||
data object NotRunning : WindowEnd()
|
||||
|
||||
/** A timestamp arrived and could not be read. The one case that is actually unknown. */
|
||||
data object Unreadable : WindowEnd()
|
||||
|
||||
/** How long is left. Negative once the window is past, which each caller words for itself. */
|
||||
data class Ends(val until: Duration) : WindowEnd()
|
||||
}
|
||||
|
||||
/**
|
||||
* [resetsAt] as the server sent it -- absent, unreadable, or a moment -- against [now].
|
||||
*
|
||||
* [now] is a parameter rather than read here so a caller can drive it from state and have the
|
||||
* countdown recompute on its own schedule.
|
||||
*/
|
||||
fun remainingUntil(resetsAt: String, now: OffsetDateTime): Duration? =
|
||||
try {
|
||||
Duration.between(now, OffsetDateTime.parse(resetsAt))
|
||||
fun windowEnd(resetsAt: String?, now: OffsetDateTime): WindowEnd {
|
||||
if (resetsAt == null) return WindowEnd.NotRunning
|
||||
return try {
|
||||
WindowEnd.Ends(Duration.between(now, OffsetDateTime.parse(resetsAt)))
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
WindowEnd.Unreadable
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user