web: a failure names itself on the page (issue #167)

The Series list row's fact line gains the failure word and its age in one
mark before the Notes chips, spelled through outcomeWord — the one
vocabulary shared with the Lanes chips. The detail page adds one sentence
beside the correction control while a Reader's number stands behind a
failure past the owner window, without naming the Reader. Each Lanes Site
name is a plain link to that Site's failing Series; the outcome chips stay
unlinked, and outcomeChips records why the promise is withdrawn.
This commit is contained in:
2026-08-23 00:36:12 +07:00
parent 3d746092ab
commit f85908d3b4
7 changed files with 171 additions and 10 deletions
+22
View File
@@ -123,6 +123,11 @@ type seriesRowView struct {
// page, where a press that retires a Series from the Lane is on purpose
// and confirm-gated (issue #158).
Finished bool
// Failure names the standing failure and how long it has stood — "not
// found · 3d ago", "" while no failure row stands. Its own field, never a
// Notes chip: the chips cap at two plus a tail, so the one fact that
// names the failure would be the most likely to be truncated away.
Failure string
}
// adminSeries renders the filterable, bookmarkable Series list: filter, Site,
@@ -683,6 +688,9 @@ func seriesRow(a store.AdminSeries, i int, now time.Time) seriesRowView {
row.Ch = "—"
}
row.Age = checkedAge(now, a.LatestCheckedAt)
if a.FailureOutcome != "" {
row.Failure = outcomeWord(a.FailureOutcome) + " · " + checkedAge(now, a.FailingSince)
}
notes := seriesNotes(a, now)
if n := len(notes); n > 2 {
row.Notes, row.More = notes[:2], n-2
@@ -693,6 +701,20 @@ func seriesRow(a store.AdminSeries, i int, now time.Time) seriesRowView {
return row
}
// outcomeWord spells the wire failure word as the Lanes chips spell it — a
// space, not the underscore: not_found reads "not found", no_chapter "no
// chapter". The remaining words are their own spelling, so an unknown word
// degrades to itself rather than vanishing from the page.
func outcomeWord(wire string) string {
switch wire {
case "not_found":
return "not found"
case "no_chapter":
return "no chapter"
}
return wire
}
// seriesNotes are a row's hygiene chips in the design's order: no URL, no
// cover, orphan, stale, reader sighting.
func seriesNotes(a store.AdminSeries, now time.Time) []string {