From fd9292e6f2651ce52c3e3816ed222b6986543034 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Tue, 27 Oct 2026 22:55:11 -0400 Subject: [PATCH] fix(series): Date Added respects the direction toggle; bump bundle stamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from live testing: - "recent" is BOTH a base toolbar key and a full registry key (where it means desc). ResolveSeriesSort's full-key passthrough ran first, so sort=recent&dir=asc silently resolved back to desc — the direction chevron flipped but Date Added could never sort ascending. "recent" now composes with the direction before the passthrough, unknown bases fall to the default entirely (a hostile dir can't ride an unknown sort), and the composition table is unit-pinned plus an HTTP-level row in the series filter test. - The header search "still broken" report was a stale-cache artifact: the fixed code shipped but main.js kept its ?v=7 stamp, so browsers with a cached copy never refetched (fresh-profile e2e passed while the user's browser didn't — heuristically cached module). Stamp bumped to ?v=8 in header + docs; the e2e suite re-verified against the rebuilt server. --- cmd/server/tests/series_sort_filter_test.go | 3 ++ internal/services/series_parsing_test.go | 37 +++++++++++++++++++++ internal/services/series_service.go | 13 +++++++- templates/docs.templ | 2 +- templates/docs_templ.go | 2 +- templates/header.templ | 2 +- templates/header_templ.go | 2 +- 7 files changed, 56 insertions(+), 5 deletions(-) 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 }