feat(web): read admin lanes from the durable pass log, not a poller snapshot (#145)

The Lanes page now projects store.LatestLanePasses and the owner-window
outcomes (store.LanePassOutcomes) into per-Site rows instead of reading an
in-memory Poller snapshot, so a deploy answers the instant the store is up.
Browser configuration is a config fact and reachability is derived from
recent browser-Site passes inside latest.RefuseBackoff.

This atomically deletes the in-memory path in the same commit that makes the
page read the DB: latest/status.go (LaneState, Status, LaneStatus,
recordLaneState) and the web.LaneReporter seam plus fakeLanes are gone, and
latest.refuseBackoff is renamed latest.RefuseBackoff at every callsite.
ownerWindow (#142) is referenced, never declared (contract C2)
This commit is contained in:
2026-08-21 20:25:38 +07:00
parent 503fb49d0a
commit cb2b104e63
16 changed files with 757 additions and 517 deletions
+15 -7
View File
@@ -50,9 +50,14 @@ type Handler struct {
// httpClient is the plain stdlib client that talks to Discord. It is not
// an injected interface: tests point APIBase at a stub server instead.
httpClient *http.Client
// lanes is the Poll Lane snapshot source the administrative page reads.
// Nil is a running deployment with no poller, not a bug.
lanes LaneReporter
// pollerEnabled reports whether latest-chapter polling is switched on in
// this deployment (LATEST_CHAPTER_POLL_ENABLED) and browserConfigured
// whether a browser sidecar is configured (BROWSER_WS_URL set). Both are
// deployment facts resolved by the composition root; the Lanes page (issue
// #145) reports them from config and derives reachability from the pass
// log rather than from whether a poller goroutine happened to start.
pollerEnabled bool
browserConfigured bool
}
// listView is what every list-rendering template receives.
@@ -110,9 +115,11 @@ type loginView struct {
// New parses every template up front so a broken one kills the process at
// startup rather than the first request that touches it.
//
// lanes is the administrative page's window onto the running Poller; nil means
// nothing is polling, which the page reports rather than hides.
func New(s *store.Store, discord DiscordConfig, tokenKey []byte, mangaPath, novelPath string, lanes LaneReporter) (*Handler, error) {
// pollerEnabled and browserConfigured are deployment facts the composition
// root resolves from LATEST_CHAPTER_POLL_ENABLED and BROWSER_WS_URL: the Lanes
// page (issue #145) reports them and derives browser reachability from the
// pass log, so no running poller is wired through here at all.
func New(s *store.Store, discord DiscordConfig, tokenKey []byte, mangaPath, novelPath string, pollerEnabled, browserConfigured bool) (*Handler, error) {
tmpl, err := template.ParseFS(templateFS, "templates/*.html")
if err != nil {
return nil, err
@@ -127,7 +134,8 @@ func New(s *store.Store, discord DiscordConfig, tokenKey []byte, mangaPath, nove
states: newOAuthStates(),
limiter: session.NewLoginLimiter(),
httpClient: &http.Client{Timeout: discordTimeout},
lanes: lanes,
pollerEnabled: pollerEnabled,
browserConfigured: browserConfigured,
}, nil
}