From d72c48295da8015c51bd21615a32029a929358a4 Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Fri, 21 Aug 2026 20:41:45 +0700 Subject: [PATCH] fix(web): statusline tokens, unreachable dot, reachability test spans (#145) Second review round (spec + standards): - Polling-off and unreachable render as statusline tokens; the unreachable span drops mark-strong so the patina dot never sits next to red text. - Reachability tests assert on the rendered span, which 'reachable' and 'unreachable' substrings never could. - A pass-log query failure now reads as reachable (no evidence rule) instead of condemning the browser, and admin.go loses its dead time import. - startLatestPoller's doc no longer claims the admin page reads the poller; AGENTS.md carries the RefuseBackoff rename. --- backend/AGENTS.md | 2 +- backend/internal/web/admin.go | 1 - backend/internal/web/admin_lanes.go | 4 ++++ backend/internal/web/templates/lanes.html | 5 ++--- backend/main.go | 3 ++- backend/web_test.go | 10 +++++----- 6 files changed, 14 insertions(+), 11 deletions(-) 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) } })