Scope the strip to series with a chapter waiting

"Continue reading" was the head of the same updated_at DESC list rendered
directly beneath it, so on a small library it said nothing new for ~240px of
the first phone screen. Scoping it to series with an unread chapter gives it
the one question the list does not already answer, since the list's order is
reading recency and its head *is* the old strip.

Consequence, accepted deliberately: with nothing new anywhere the strip does
not render at all, and the list starts at the top of the page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 22:17:56 +07:00
parent 43d719c62c
commit d16e82abf4
2 changed files with 64 additions and 31 deletions
+14 -7
View File
@@ -200,14 +200,21 @@ func (h *webHandler) buildListView(tab string) (listView, error) {
tab = "all"
items = reading
}
// The strip is the head of the same updated_at DESC reading list the All tab
// renders, so it only earns its ~230px of the first phone screen where it is
// not a pure duplicate of what sits right below it: on All, with more series
// than it shows. On the other tabs it either repeats the list or, worse,
// puts reading series above the archived and finished buckets.
// The strip is scoped to series with a chapter waiting, which is the one
// question the list below it does not already answer: the list is ordered by
// reading recency, so the head of it *is* the strip whenever the strip is
// just "the most recent rows". Only on All — on Updated it would render the
// same set twice, and on the other tabs it would contradict the bucket.
//
// It therefore disappears entirely on a library with nothing new. That is
// the intended reading: an empty strip has nothing to say, and the ~240px it
// costs on a phone belongs to the list.
var recent []Bookmark
if tab == "all" && len(reading) > recentCount {
recent = reading[:recentCount]
if tab == "all" {
recent = withNew
if len(recent) > recentCount {
recent = recent[:recentCount]
}
}
return listView{Tab: tab, Recent: recent, Items: items, NewCount: len(withNew)}, nil
}
+50 -24
View File
@@ -594,43 +594,69 @@ func stripOf(t *testing.T, srv http.Handler, cfg Config, tab string) string {
return body
}
// The strip is the head of the same list the All tab renders, so it only shows
// where it is not a pure duplicate: on All, with more reading series than it
// holds.
func TestRecentStripOnlyOnAllAndOnlyWhenItAddsSomething(t *testing.T) {
// The strip carries the series with a chapter waiting — the one thing the
// updated_at-ordered list below it does not already say — and only on All.
func TestRecentStripCarriesUnreadOnlyAndOnlyOnAll(t *testing.T) {
cfg := webConfig()
srv, store := newWebTestServer(t, cfg)
seedStatusRows(t, store)
seedStatusRows(t, store) // ReadingOne is at 10 with 11 out; the rest are not reading
for _, tab := range []string{"all", "new", "fav", "archived", "finished"} {
if strings.Contains(stripOf(t, srv, cfg, tab), `class="recent"`) {
t.Fatalf("tab %s rendered the strip for a library of one reading series", tab)
// A reading series that is caught up has nothing waiting, so it stays out.
caught := Bookmark{
Key: "asura:caught", Site: "asura", SeriesID: "caught", Title: "CaughtUpOne",
Status: statusReading, LastChapterNum: 40, LatestChapter: "40",
LatestChapterNum: floatPtr(40), UpdatedAt: time.Now().UnixMilli(),
}
if _, err := store.Upsert(caught); err != nil {
t.Fatalf("seed %s: %v", caught.Key, err)
}
strip := stripOf(t, srv, cfg, "all")
if !strings.Contains(strip, "ReadingOne") {
t.Fatal("strip dropped the series with an unread chapter")
}
for _, unwanted := range []string{"CaughtUpOne", "ArchivedOne", "FinishedOne"} {
if strings.Contains(strip, unwanted) {
t.Fatalf("strip included %s", unwanted)
}
}
for _, tab := range []string{"new", "fav", "archived", "finished"} {
if strings.Contains(stripOf(t, srv, cfg, tab), "ReadingOne") {
t.Fatalf("tab %s rendered the strip", tab)
}
}
// Past recentCount the strip stops repeating the whole list.
// Nothing new anywhere: the strip has nothing to say and does not render.
reading, _, err := store.Get("asura:reading")
if err != nil {
t.Fatalf("Get: %v", err)
}
reading.LatestChapterNum = floatPtr(reading.LastChapterNum)
if _, err := store.Upsert(reading); err != nil {
t.Fatalf("Upsert: %v", err)
}
if strings.Contains(stripOf(t, srv, cfg, "all"), `class="recent"`) {
t.Fatal("strip rendered with no unread chapters anywhere")
}
}
// The strip never grows past recentCount, however many series are waiting.
func TestRecentStripCapped(t *testing.T) {
cfg := webConfig()
srv, store := newWebTestServer(t, cfg)
for i := 0; i <= recentCount; i++ {
b := Bookmark{
Key: fmt.Sprintf("asura:more%d", i), Site: "asura",
SeriesID: fmt.Sprintf("more%d", i), Title: fmt.Sprintf("MoreReading%d", i),
Status: statusReading, LastChapterNum: 1, UpdatedAt: time.Now().UnixMilli(),
Key: fmt.Sprintf("asura:new%d", i), Site: "asura",
SeriesID: fmt.Sprintf("new%d", i), Title: fmt.Sprintf("Waiting%d", i),
Status: statusReading, LastChapterNum: 1, LatestChapter: "2",
LatestChapterNum: floatPtr(2), UpdatedAt: time.Now().UnixMilli() + int64(i),
}
if _, err := store.Upsert(b); err != nil {
t.Fatalf("seed %s: %v", b.Key, err)
}
}
strip := stripOf(t, srv, cfg, "all")
if !strings.Contains(strip, `class="recent"`) {
t.Fatal("strip missing on All with more reading series than it shows")
}
if strings.Contains(strip, "ArchivedOne") || strings.Contains(strip, "FinishedOne") {
t.Fatal("recent strip included an archived or finished series")
}
for _, tab := range []string{"new", "fav", "archived", "finished"} {
if strings.Contains(stripOf(t, srv, cfg, tab), `class="recent"`) {
t.Fatalf("tab %s rendered the strip", tab)
}
if got := strings.Count(stripOf(t, srv, cfg, "all"), "recent-card"); got != recentCount {
t.Fatalf("strip rendered %d cards, want %d", got, recentCount)
}
}