diff --git a/backend/AGENTS.md b/backend/AGENTS.md
index f8bb0a3..49d3443 100644
--- a/backend/AGENTS.md
+++ b/backend/AGENTS.md
@@ -134,7 +134,7 @@ unchanged read is exactly the Sighting worth deferring a Poll on.
rest.
**Refusals and browser loss are Lane-local.** Two `errChallengeHeld` in a pass
-stop that Site for `refuseBackoff` while other Lanes continue. An
+stop that Site for `RefuseBackoff` while other Lanes continue. An
`errBrowserInterrupted` (remote Chrome restarted) sets a shared Poller flag so
the *other* browser Lanes skip their passes for the same window — otherwise a
restarting Chrome stamps one Series per Lane per pass, burning rests on
diff --git a/backend/internal/web/admin.go b/backend/internal/web/admin.go
index 81f07dc..4e6c6cc 100644
--- a/backend/internal/web/admin.go
+++ b/backend/internal/web/admin.go
@@ -4,7 +4,6 @@ import (
"log"
"net/http"
"strconv"
- "time"
"bookmarkmanager/backend/internal/store"
)
diff --git a/backend/internal/web/admin_lanes.go b/backend/internal/web/admin_lanes.go
index ae5fe73..7cc5bd1 100644
--- a/backend/internal/web/admin_lanes.go
+++ b/backend/internal/web/admin_lanes.go
@@ -81,6 +81,10 @@ func (h *Handler) lanesView() lanesView {
passes, err := h.store.LatestLanePasses()
if err != nil {
log.Printf("admin lanes: latest passes: %v", err)
+ // No evidence of a lost sidecar reads as reachable, per the same rule
+ // browserReachable applies: a store failure must not condemn the
+ // browser. The empty table already says no Lane has recorded a pass.
+ v.BrowserReachable = true
return v
}
now := time.Now()
diff --git a/backend/internal/web/templates/lanes.html b/backend/internal/web/templates/lanes.html
index a978861..5ac35d4 100644
--- a/backend/internal/web/templates/lanes.html
+++ b/backend/internal/web/templates/lanes.html
@@ -14,9 +14,8 @@
Poll Lanes
- {{if .PollerOff}}Polling is switched off in this deployment: no Lane
- runs, and Latest Chapter comes from the userscripts alone.
- {{else}}Browser: {{if not .BrowserConfigured}}not configured{{else if .BrowserReachable}}reachable{{else}}unreachable{{end}}{{end}}
+ {{if .PollerOff}}Polling: off
+ {{else}}Browser: {{if not .BrowserConfigured}}not configured{{else if .BrowserReachable}}reachable{{else}}unreachable{{end}}{{end}}
{{if .Rows}}
diff --git a/backend/main.go b/backend/main.go
index ca2bd90..629e1cf 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -343,7 +343,8 @@ func newLatestPoller(s *store.Store, cfg LatestPoll, fetch, browser latest.Fetch
// HTTP client cannot be built. Any problem here is logged and skipped: this
// feature going missing degrades the service to userscript-only latest-chapter
// tracking, which is exactly how it behaved before. It returns the running
-// Poller, or nil when there is none — the admin page's Lane status reads it.
+// Poller, or nil when there is none; the caller starts it for its own sake —
+// the Lanes page reads the pass log, so no return value is wired anywhere.
func startLatestPoller(ctx context.Context, s *store.Store, cfg LatestPoll, browser latest.Fetcher) *latest.Poller {
if !cfg.Enabled {
log.Println("latest-chapter poller: disabled by config")
diff --git a/backend/web_test.go b/backend/web_test.go
index 54f4f42..4ff5362 100644
--- a/backend/web_test.go
+++ b/backend/web_test.go
@@ -885,7 +885,7 @@ func TestAdminPageWithoutAPollerSaysSo(t *testing.T) {
cfg Config
want, unwant string
}{
- {"polling switched off", testConfig(), "Polling is switched off", "not configured"},
+ {"polling switched off", testConfig(), `Polling: off`, "not configured"},
{"browser not configured", lanesConfig(), "not configured", "Polling is switched off"},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -986,7 +986,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
router, st := newWebTestServer(t, lanesConfig())
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.UnixMilli(), Skip: latest.SkipNoFetcher})
body := lanesBody(t, router, st)
- if !strings.Contains(body, "not configured") || strings.Contains(body, "unreachable") {
+ if !strings.Contains(body, `mark-faint">not configured`) || strings.Contains(body, "unreachable") {
t.Errorf("unset BROWSER_WS_URL must read as not configured:\n%s", body)
}
})
@@ -997,7 +997,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
router, st := newWebTestServer(t, cfg)
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
body := lanesBody(t, router, st)
- if !strings.Contains(body, "unreachable") {
+ if !strings.Contains(body, `bad">unreachable`) {
t.Errorf("recent sidecar-down passes must read as unreachable:\n%s", body)
}
})
@@ -1008,7 +1008,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
router, st := newWebTestServer(t, cfg)
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-time.Minute).UnixMilli(), Checked: 3})
body := lanesBody(t, router, st)
- if !strings.Contains(body, "reachable") {
+ if !strings.Contains(body, `mark-strong">reachable`) {
t.Errorf("clean browser passes must read as reachable:\n%s", body)
}
})
@@ -1019,7 +1019,7 @@ func TestBrowserConfigAndReachabilityDerived(t *testing.T) {
router, st := newWebTestServer(t, cfg)
seedPass(t, st, store.LanePass{Site: "kagane", RanAt: now.Add(-30 * time.Minute).UnixMilli(), Skip: latest.SkipSidecarDown})
body := lanesBody(t, router, st)
- if !strings.Contains(body, "reachable") {
+ if !strings.Contains(body, `mark-strong">reachable`) {
t.Errorf("a sidecar-down older than the backoff must read as reachable:\n%s", body)
}
})