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" tab = "all"
items = reading items = reading
} }
// The strip is the head of the same updated_at DESC reading list the All tab // The strip is scoped to series with a chapter waiting, which is the one
// renders, so it only earns its ~230px of the first phone screen where it is // question the list below it does not already answer: the list is ordered by
// not a pure duplicate of what sits right below it: on All, with more series // reading recency, so the head of it *is* the strip whenever the strip is
// than it shows. On the other tabs it either repeats the list or, worse, // just "the most recent rows". Only on All — on Updated it would render the
// puts reading series above the archived and finished buckets. // 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 var recent []Bookmark
if tab == "all" && len(reading) > recentCount { if tab == "all" {
recent = reading[:recentCount] recent = withNew
if len(recent) > recentCount {
recent = recent[:recentCount]
}
} }
return listView{Tab: tab, Recent: recent, Items: items, NewCount: len(withNew)}, nil 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 return body
} }
// The strip is the head of the same list the All tab renders, so it only shows // The strip carries the series with a chapter waiting — the one thing the
// where it is not a pure duplicate: on All, with more reading series than it // updated_at-ordered list below it does not already say — and only on All.
// holds. func TestRecentStripCarriesUnreadOnlyAndOnlyOnAll(t *testing.T) {
func TestRecentStripOnlyOnAllAndOnlyWhenItAddsSomething(t *testing.T) {
cfg := webConfig() cfg := webConfig()
srv, store := newWebTestServer(t, cfg) 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"} { // A reading series that is caught up has nothing waiting, so it stays out.
if strings.Contains(stripOf(t, srv, cfg, tab), `class="recent"`) { caught := Bookmark{
t.Fatalf("tab %s rendered the strip for a library of one reading series", tab) 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++ { for i := 0; i <= recentCount; i++ {
b := Bookmark{ b := Bookmark{
Key: fmt.Sprintf("asura:more%d", i), Site: "asura", Key: fmt.Sprintf("asura:new%d", i), Site: "asura",
SeriesID: fmt.Sprintf("more%d", i), Title: fmt.Sprintf("MoreReading%d", i), SeriesID: fmt.Sprintf("new%d", i), Title: fmt.Sprintf("Waiting%d", i),
Status: statusReading, LastChapterNum: 1, UpdatedAt: time.Now().UnixMilli(), Status: statusReading, LastChapterNum: 1, LatestChapter: "2",
LatestChapterNum: floatPtr(2), UpdatedAt: time.Now().UnixMilli() + int64(i),
} }
if _, err := store.Upsert(b); err != nil { if _, err := store.Upsert(b); err != nil {
t.Fatalf("seed %s: %v", b.Key, err) t.Fatalf("seed %s: %v", b.Key, err)
} }
} }
if got := strings.Count(stripOf(t, srv, cfg, "all"), "recent-card"); got != recentCount {
strip := stripOf(t, srv, cfg, "all") t.Fatalf("strip rendered %d cards, want %d", got, recentCount)
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)
}
} }
} }