web: site-completed hint reaches the owner, decides nothing (#170)

The Site's own completed marker (issue #168) joins the owner's surface as
an informational hint, never a decision:

- store: SeriesFilterSiteCompleted ('site_completed') sits beside Finished
  in the filter vocabulary; its predicate carries the same finished_at
  guard as the clock-driven filters, because the Site's stamp keeps
  standing after the owner retires the row. The projection and SeriesPage
  GROUP BY carry site_completed_at so the detail page can age it.
- web: the filter joins the Series list select as 'Site says completed'
  and rides the tail of seriesFilterOrder, so the landing stats figure
  comes for free and sits after the finished figure.
- detail: one hint line beside the Finish control - 'the site says this
  work is completed (since 3d ago)' - rendered only while the stamp
  stands and no Finish stands; the Finish control itself is untouched.
- tests: filter's finished exclusion and un-finish return to the Poll
  query (store); figure at zero and nonzero, hint present/absent, and the
  Finish control's markup byte-identical with a hint present (web).
This commit is contained in:
2026-08-23 00:31:21 +07:00
parent 3d746092ab
commit a533e4f769
7 changed files with 363 additions and 81 deletions
+17 -14
View File
@@ -25,23 +25,25 @@ const seriesPageSize = 50
// 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",
store.SeriesFilterNoChapter: "Never read a chapter",
store.SeriesFilterNoReaders: "No Readers",
store.SeriesFilterNeverChecked: "Never checked",
store.SeriesFilterStale: "Not checked in 12h",
store.SeriesFilterNoCover: "No cover",
store.SeriesFilterReaderReport: "Latest from a Reader",
store.SeriesFilterFailing: "Failing over 12h",
store.SeriesFilterUnverified: "Unverified Reader number",
store.SeriesFilterFinished: "Finished",
store.SeriesFilterAll: "All series",
store.SeriesFilterNoURL: "No series URL",
store.SeriesFilterNoChapter: "Never read a chapter",
store.SeriesFilterNoReaders: "No Readers",
store.SeriesFilterNeverChecked: "Never checked",
store.SeriesFilterStale: "Not checked in 12h",
store.SeriesFilterNoCover: "No cover",
store.SeriesFilterReaderReport: "Latest from a Reader",
store.SeriesFilterFailing: "Failing over 12h",
store.SeriesFilterUnverified: "Unverified Reader number",
store.SeriesFilterFinished: "Finished",
store.SeriesFilterSiteCompleted: "Site says completed",
}
// seriesFilterOrder is the select's render order: All first, then the
// 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.
// permanent repairs, then the fixable ones (issue #140). Finished and the
// site-completed hint ride the tail — deliberate, not repairs — and the
// Overview's stats block renders the same tail, which is what sits the
// finished and site-completed figures last there.
var seriesFilterOrder = []string{
store.SeriesFilterAll,
store.SeriesFilterNoURL,
@@ -54,6 +56,7 @@ var seriesFilterOrder = []string{
store.SeriesFilterFailing,
store.SeriesFilterUnverified,
store.SeriesFilterFinished,
store.SeriesFilterSiteCompleted,
}
// seriesListView is the Series list page's data. The template renders strings