fix: an asleep browser Lane is not a stalled one on the admin page

laneRow.Stalled was Due > 0 && Checked == 0, which is exactly the shape
of the on-demand wake gate declining to start Chrome (ADR-0005): under
five due Series and none overdue by 15m, a browser Lane reads nothing on
purpose. kagane and novelfull each have one bookmarked Series, so that is
their state for most of every cycle and the page marked both "not
checking" — spending the one mark that means "go look" on the commonest
healthy state.

LaneState.Asleep is set at the wake gate, excluded from Stalled, kept out
of Attention, and rendered as "browser asleep". Verified in prod: those
Lanes wake on the age rule 15m past due (kagane and novelfull both polled
at 14:55Z for a 14:40Z due moment, novelfull picking up Chapter 4458).
This commit is contained in:
2026-08-16 22:03:57 +07:00
parent 20fff588cc
commit 5f9cfbb27e
12 changed files with 834 additions and 615 deletions
+8 -3
View File
@@ -61,8 +61,11 @@ type laneRow struct {
// is configured, which stops those Series just as completely.
BrowserLost bool
// Stalled marks a Lane with Series waiting that its last pass did not read
// — the difference between a stopped Lane and a quiet one (story 13).
// — the difference between a stopped Lane and a quiet one (story 13). A
// browser Lane holding Chrome asleep under the wake thresholds is neither,
// so it carries Asleep instead and never Stalled.
Stalled bool
Asleep bool
// Attention is the one flag the template colours on, so an unhealthy Lane
// is found at a glance rather than read for.
Attention bool
@@ -152,8 +155,9 @@ func (h *Handler) lanesView() lanesView {
for _, l := range snap.Lanes {
lost := l.Browser && !snap.BrowserReachable
// Series waiting and none read is the shape of a Lane that has stopped
// working, as distinct from one that is quiet for want of work.
stalled := l.Due > 0 && l.Checked == 0
// working, as distinct from one that is quiet for want of work — or one
// deliberately leaving Chrome asleep until its group gathers.
stalled := l.Due > 0 && l.Checked == 0 && !l.Asleep
gap := ""
if l.Gap > 0 {
gap = l.Gap.Truncate(time.Second).String()
@@ -168,6 +172,7 @@ func (h *Handler) lanesView() lanesView {
Refusing: l.Refusing,
BrowserLost: lost,
Stalled: stalled,
Asleep: l.Asleep,
Attention: l.Clamped || l.Refusing || lost || stalled,
})
}
@@ -23,6 +23,7 @@
{{if .Refusing}}<span class="lane-mark">refusing</span>{{end}}
{{if .BrowserLost}}<span class="lane-mark">no browser</span>{{end}}
{{if .Stalled}}<span class="lane-mark">not checking</span>{{end}}
{{if .Asleep}}<span class="lane-mark">browser asleep</span>{{end}}
</li>
{{end}}
</ul>