diff --git a/cmd/server/tests/series_sort_filter_test.go b/cmd/server/tests/series_sort_filter_test.go index 3c385f87..859151ea 100644 --- a/cmd/server/tests/series_sort_filter_test.go +++ b/cmd/server/tests/series_sort_filter_test.go @@ -176,6 +176,9 @@ func TestSeriesSortAndFilter(t *testing.T) { {"sort=books&dir=asc", []string{"Apple", "Zebra", "Mango"}}, // books desc default, flipped {"sort=progress&dir=asc", []string{"Apple", "Zebra", "Mango"}}, // progress desc default, flipped {"sort=author", []string{"Mango", "Apple", "Zebra"}}, // author asc default + // THE toggle regression (2026-10-27): "recent" is both a base + // key and a registry key — dir=asc used to be swallowed. + {"sort=recent&dir=asc", []string{"Apple", "Mango", "Zebra"}}, } for _, tc := range cases { got := renderedNames("&" + tc.query) diff --git a/internal/services/series_parsing_test.go b/internal/services/series_parsing_test.go index 3b4ba009..e829f003 100644 --- a/internal/services/series_parsing_test.go +++ b/internal/services/series_parsing_test.go @@ -320,3 +320,40 @@ func TestSeriesNumberFloatOrderingValues(t *testing.T) { t.Fatal("1 must sort before 1.5") } } + +// ResolveSeriesSort composes the toolbar's base key + direction into +// the registry key the SQL cascade implements. "recent" is both a base +// key AND a full registry key (meaning desc) — the composition must +// run BEFORE the full-key passthrough, or Date Added can never sort +// ascending (2026-10-27 regression the direction toggle exposed). +func TestResolveSeriesSortComposition(t *testing.T) { + cases := []struct { + sort, dir string + wantKey, wantD string + }{ + // THE regression: Date Added + toggle up. + {"recent", "asc", "recent_asc", "asc"}, + {"recent", "", "recent", "desc"}, + {"recent", "desc", "recent", "desc"}, + {"recent", "bogus", "recent", "desc"}, + // Full keys (back-compat URLs / JSON API) win as-is. + {"name_desc", "", "name_desc", "desc"}, + {"name_asc", "desc", "name_asc", "asc"}, + // Base keys compose; default direction applies when dir absent. + {"name", "", "name_asc", "asc"}, + {"name", "desc", "name_desc", "desc"}, + {"books", "", "books_desc", "desc"}, + {"books", "asc", "books_asc", "asc"}, + {"last_read", "asc", "last_read_asc", "asc"}, + // Unknown/empty → default. + {"", "", "recent", "desc"}, + {"bogus", "asc", "recent", "desc"}, + } + for _, tc := range cases { + key, dir := ResolveSeriesSort(tc.sort, tc.dir) + if key != tc.wantKey || dir != tc.wantD { + t.Errorf("ResolveSeriesSort(%q, %q) = (%q, %q), want (%q, %q)", + tc.sort, tc.dir, key, dir, tc.wantKey, tc.wantD) + } + } +} diff --git a/internal/services/series_service.go b/internal/services/series_service.go index 0181bf7a..88a4df11 100644 --- a/internal/services/series_service.go +++ b/internal/services/series_service.go @@ -91,6 +91,15 @@ func ResolveSeriesSort(sort, dir string) (string, string) { if dir != "asc" && dir != "desc" { dir = "" } + // "recent" is BOTH a base toolbar key and a registry key (it means + // desc). Handle it before the full-key check or dir=asc is silently + // swallowed and the direction toggle can never flip Date Added. + if sort == "recent" { + if dir == "asc" { + return "recent_asc", "asc" + } + return "recent", "desc" + } // Full registry key straight from the URL wins as-is. if seriesSortKeys[sort] { return sort, seriesDirOf(sort) @@ -101,7 +110,9 @@ func ResolveSeriesSort(sort, dir string) (string, string) { } defaultDir, known := seriesBaseSorts[base] if !known { - base, defaultDir = "recent", "desc" + // Unknown base: the whole thing falls to the default — an + // unhandled sort must not sneak a direction past the registry. + return "recent", "desc" } if dir == "" { dir = defaultDir diff --git a/templates/docs.templ b/templates/docs.templ index 089366ef..112db597 100644 --- a/templates/docs.templ +++ b/templates/docs.templ @@ -72,7 +72,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) { - + } diff --git a/templates/docs_templ.go b/templates/docs_templ.go index c7d763c4..b17a4be9 100644 --- a/templates/docs_templ.go +++ b/templates/docs_templ.go @@ -264,7 +264,7 @@ func DocsLayout(nav Navigation, doc Document, user User, currentPath string) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/header.templ b/templates/header.templ index 61397a36..f00c9880 100644 --- a/templates/header.templ +++ b/templates/header.templ @@ -262,7 +262,7 @@ templ Header(user User, currentPath string) { - + } diff --git a/templates/header_templ.go b/templates/header_templ.go index dfaabf24..7dc4dcb9 100644 --- a/templates/header_templ.go +++ b/templates/header_templ.go @@ -725,7 +725,7 @@ func Header(user User, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }