Work the design critique down: chapter format, colour law, search, strip, a11y #11

Merged
sulthan merged 13 commits from improve-design-health into main 2026-07-30 22:34:24 +07:00
2 changed files with 64 additions and 31 deletions
Showing only changes of commit d16e82abf4 - Show all commits
+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)
}
}