889f0f3f38
Spec #134, all ten tickets. Closes #134. ## What ships The admin surface becomes four bookmarkable addresses behind one nav row, and Lane observability stops dying with the process. - **#138** `/admin` splits into Overview, Lanes, Readers, Series, each a real route with the active tab underlined. - **#139** `poll_passes` and `poll_lanes` land as durable tables with their store surface. - **#140** cross-Series admin read model, with the privacy boundary in the projection: the Reader id that raised a Latest Chapter never leaves the store package. - **#141** the poller records exactly one pass row per exit, with a skip reason and outcome counts. - **#142** Series list: eight hygiene filters, Site and Library narrowing, paging — all of it in the query string, so a filtered list is a bookmark. - **#143** Overview: a three-state verdict line and a stats block where every non-zero figure links to the list that counts it. - **#144** per-Series detail page, keyed by the `site:series_id` composite the rest of the system already uses. - **#145** the Lanes page reads the database; the in-memory Lane state, `web.LaneReporter` and `latest.Status` are deleted. - **#146** Forced Poll: *Check now* stamps `series.force_poll_at` and never commands the poller. - **#147** pause and resume one Site's Lane, with a mandatory 1h/6h/24h expiry. ## Shape of the design Two decisions carry the rest. **Commands go through the database, never at the poller**: both *Check now* and a Lane pause write a row the next pass reads, so they survive a restart and the whole surface stays testable with no poller running. And **pending is derived, never stored** — the request stamp being newer than the check stamp — which self-clears on the check stamp with no second write and no sweeper, because the check stamp is written before the fetch. ADRs: `docs/adr/0012-persisted-lane-state.md`, `docs/adr/0013-commands-through-the-database.md`. ## Verification `go test ./...` green on the merged base (`264839e`), all packages, Docker-backed. `gofmt -l` and `go vet` clean. Every ticket was reviewed on both axes (`cr-spec` + `cr-standards`) before merge. ## Known, non-blocking - **#143** the verdict ignores never-reported Lanes when other Lanes have reported, and the per-Site table lists Sites that have Series rather than the whole registry. The ticket prose asks for eight hygiene figures per Site; the design mock and the landed `.tbl.sites` grid both say six columns, and the mock won. - **#146** two `SeriesPage` scans per press instead of a keyed read — `ponytail:`-commented in-tree with the upgrade path. - **#147** a paused Site with no pass row yet renders no row and so no control, since the Lanes page lists Sites that have passed. - **#141** a sibling browser Lane declining at the top of a pass records as `sidecar-down`. Specified deliberately; the later spec in this series settles it. Reviewed-on: #148 Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com> Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
36 lines
1.6 KiB
HTML
36 lines
1.6 KiB
HTML
{{/* Per-Series page: one address per Series, keyed "<site>:<series_id>" so the
|
|
list row is one hop from it. Everything here is a Series-level fact plus
|
|
the anonymous Reader count. The Check now control lands in its own .dform
|
|
below the (empty) .detail-grid; the pending marker rides the meta line
|
|
with the other marks. */}}
|
|
{{define "series-detail"}}
|
|
<a class="ghost detail-back" href="/admin/series">← Series</a>
|
|
<h1 class="detail-title">{{.Title}}</h1>
|
|
<p class="detail-key">{{.Key}} · {{.Site}} · {{.Kind}}</p>
|
|
{{if .Cover}}<div class="cover"><img src="{{.Cover}}" alt="" loading="lazy"></div>
|
|
{{else}}<div class="cover"></div>{{end}}
|
|
{{template "series-detail-meta" .}}
|
|
<div class="detail-grid"></div>
|
|
{{if .CanPoll}}
|
|
<div class="dform">
|
|
<div class="field"><a class="ghost act" hx-post="/admin/series/{{.Key}}/poll" hx-target="#detail-meta" hx-swap="outerHTML" href="#">Check now</a></div>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* series-detail-meta is the meta line, and the answer a Check now press on
|
|
the detail page swaps into its place: the same marks, re-rendered after
|
|
the stamp so the pending marker shows. */}}
|
|
{{define "series-detail-meta"}}
|
|
<div class="detail-meta" id="detail-meta">
|
|
<span>ch {{.Chapter}}</span>
|
|
<span>checked {{.Checked}}</span>
|
|
<span>{{.Readers}} readers</span>
|
|
{{if .Pending}}<span class="mark">{{.Requested}}</span>{{end}}
|
|
{{if .Unpollable}}<span class="mark">unpollable</span>{{end}}
|
|
{{if .NoCover}}<span class="mark">no cover</span>{{end}}
|
|
{{if .Orphan}}<span class="mark">orphan</span>{{end}}
|
|
{{if .SightingRaised}}<span class="mark">sighting-raised</span>{{end}}
|
|
</div>
|
|
{{end}}
|