fix(series): Date Added respects the direction toggle; bump bundle stamp
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.
This commit is contained in:
1 parent
ffbcc53083
commit
fd9292e6f2
7 files changed
+56
-5
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -72,7 +72,7 @@ templ DocsLayout(nav Navigation, doc Document, user User, currentPath string) {
|
||||
</main>
|
||||
</div>
|
||||
<!-- module bundle; ?v=<stamp> busts stale intermediate builds -->
|
||||
<script type="module" src="/static/main.js?v=7"></script>
|
||||
<script type="module" src="/static/main.js?v=8"></script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
@@ -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, "</div></div></main></div><!-- module bundle; ?v=<stamp> busts stale intermediate builds --><script type=\"module\" src=\"/static/main.js?v=7\"></script></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div></div></main></div><!-- module bundle; ?v=<stamp> busts stale intermediate builds --><script type=\"module\" src=\"/static/main.js?v=8\"></script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ templ Header(user User, currentPath string) {
|
||||
</header>
|
||||
<!-- module bundle; ?v=<commit-ish stamp> busts stale intermediate
|
||||
builds across app restarts (browser caches are keyed by URL) -->
|
||||
<script type="module" src="/static/main.js?v=7"></script>
|
||||
<script type="module" src="/static/main.js?v=8"></script>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "</span> <input type=\"text\" id=\"header-search\" name=\"q\" placeholder=\"Search your library…\" class=\"input pl-10\" autocomplete=\"off\" x-ref=\"input\" role=\"combobox\" :aria-expanded=\"open ? 'true' : 'false'\" aria-label=\"Search your library\" @input=\"onInput()\" @focus=\"onFocus()\" @keydown.arrow-down.prevent=\"moveSelection(1)\" @keydown.arrow-up.prevent=\"moveSelection(-1)\" @keydown.enter.prevent=\"openSelected()\" @keydown.escape=\"close()\" hx-get=\"/api/media-items/search?layout=dropdown\" hx-trigger=\"search delay:300ms\" hx-target=\"#search-results\" hx-swap=\"innerHTML\"><div id=\"search-results\" x-show=\"open\" x-cloak class=\"absolute z-50 w-full max-w-2xl mt-2 rounded-lg shadow-lg border overflow-hidden\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"></div></div></div></header><!-- module bundle; ?v=<commit-ish stamp> busts stale intermediate\n\t\t builds across app restarts (browser caches are keyed by URL) --><script type=\"module\" src=\"/static/main.js?v=7\"></script></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "</span> <input type=\"text\" id=\"header-search\" name=\"q\" placeholder=\"Search your library…\" class=\"input pl-10\" autocomplete=\"off\" x-ref=\"input\" role=\"combobox\" :aria-expanded=\"open ? 'true' : 'false'\" aria-label=\"Search your library\" @input=\"onInput()\" @focus=\"onFocus()\" @keydown.arrow-down.prevent=\"moveSelection(1)\" @keydown.arrow-up.prevent=\"moveSelection(-1)\" @keydown.enter.prevent=\"openSelected()\" @keydown.escape=\"close()\" hx-get=\"/api/media-items/search?layout=dropdown\" hx-trigger=\"search delay:300ms\" hx-target=\"#search-results\" hx-swap=\"innerHTML\"><div id=\"search-results\" x-show=\"open\" x-cloak class=\"absolute z-50 w-full max-w-2xl mt-2 rounded-lg shadow-lg border overflow-hidden\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"></div></div></div></header><!-- module bundle; ?v=<commit-ish stamp> busts stale intermediate\n\t\t builds across app restarts (browser caches are keyed by URL) --><script type=\"module\" src=\"/static/main.js?v=8\"></script></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user