Add finished Series filter; clock-driven hygiene predicates exclude it (#159)

This commit is contained in:
2026-08-22 17:25:10 +07:00
parent f85641da61
commit 0029cff27c
5 changed files with 218 additions and 23 deletions
+5 -3
View File
@@ -59,7 +59,7 @@ type siteRow struct {
}
// overviewView assembles the landing page from the store's read model: one
// SeriesShapes pass per filter summed in Go (the shipped surface offers eight
// SeriesShapes pass per filter summed in Go (the shipped surface offers nine
// grouped passes, not a stats query — #140), the pass log's latest pass per
// Site, and the roster. A failure in any read is a 500 with a logged reason,
// never a page of silent zeroes.
@@ -93,8 +93,10 @@ func (h *Handler) overviewView() (overviewView, error) {
view.Unchecked = totals[store.SeriesFilterStale] + totals[store.SeriesFilterNeverChecked]
view.Verdict, view.HasCounts = overviewVerdict(passes, now)
// The seven problem filters, in seriesFilterOrder's permanent-then-fixable
// order; the All filter's count belongs to the Library block, not to a
// The hygiene figures, in seriesFilterOrder's tail: the seven problem
// filters in permanent-then-fixable order, then the finished figure last —
// informational, not a problem, and last because seriesFilterOrder appends
// it there. The All filter's count belongs to the Library block, not to a
// "hygiene" figure.
hygiene := make([]fig, 0, len(seriesFilterOrder)-1)
for _, name := range seriesFilterOrder[1:] {
+12 -8
View File
@@ -20,10 +20,10 @@ import (
// the wrong page. The store does not export it (#140).
const seriesPageSize = 50
// seriesFilterLabels names every hygiene filter for the Series list select,
// keyed by the wire constant the URL carries. The render order is
// seriesFilterOrder; the labels are read by later admin tickets too, so the
// map and the constants cannot drift apart.
// seriesFilterLabels names every Series filter for the list select, keyed by
// the wire constant the URL carries. The render order is seriesFilterOrder;
// the labels are read by later admin tickets too, so the map and the
// constants cannot drift apart.
var seriesFilterLabels = map[string]string{
store.SeriesFilterAll: "All series",
store.SeriesFilterNoURL: "No series URL",
@@ -33,10 +33,13 @@ var seriesFilterLabels = map[string]string{
store.SeriesFilterStale: "Not checked in 12h",
store.SeriesFilterNoCover: "No cover",
store.SeriesFilterReaderReport: "Latest from a Reader",
store.SeriesFilterFinished: "Finished",
}
// seriesFilterOrder is the select's render order: All first, then the
// permanent repairs, then the fixable ones (issue #140).
// permanent repairs, then the fixable ones (issue #140). Finished rides the
// tail, last — deliberate, not a repair — and the Overview's stats block
// renders the same tail, which is what sits the finished figure last there.
var seriesFilterOrder = []string{
store.SeriesFilterAll,
store.SeriesFilterNoURL,
@@ -46,6 +49,7 @@ var seriesFilterOrder = []string{
store.SeriesFilterStale,
store.SeriesFilterNoCover,
store.SeriesFilterReaderReport,
store.SeriesFilterFinished,
}
// seriesListView is the Series list page's data. The template renders strings
@@ -611,9 +615,9 @@ func (h *Handler) seriesListView(r *http.Request) (seriesListView, error) {
return view, nil
}
// seriesFilterOptions renders every hygiene filter with its library-wide
// count, one SeriesShapes pass per filter summed in Go — the shipped surface
// offers eight grouped passes, not a single stats query (#140). The counts
// seriesFilterOptions renders every filter with its library-wide count, one
// SeriesShapes pass per filter summed in Go — the shipped surface offers nine
// grouped passes, not a single stats query (#140). The counts
// are library-wide because the select sits next to the Site narrowing and
// must not shift as the owner narrows the list itself. Cutoff travels with
// the stale filter, or its count would always be zero.