feat(podcasts): unset listening order defaults to reversed (audiobook-first)

The per-feed listening order — the Order dropdown on the podcast page
and player — resolved its UNSET state to "native": feed document order,
which runs newest-first for classic RSS. This platform is audiobook-
first, so native started every book at its LAST chapter (verified on
live data: 7th Son position 1 = Bonus Episode #2, positions descending
through the chapters).

The unset fallback flips to "reverse" at every resolution site of that
ONE setting — the podcast page's list + dropdown (podcastOrderForFeed),
the player's feed chain (reader.go), and the download queue
(orderOverrideFor) — so the dropdown shows reversed selected, the page
renders in that order, and play/download follow. Stored overrides are
untouched (native stays a valid savable choice); no ordering semantics
changed; collections are untouched.

Pins: the flipped native expectation is changelogged in contract_pins;
new pins cover the sequence config carrying reverse, feed position 1
playing last, and the page dropdown rendering reverse selected for an
unset feed.
This commit is contained in:
John O'Keefe committed 2026-11-01 11:46:14 -05:00
1 parent 65f257f377
commit 0e29eff7b7
5 files changed
+65 -12

No files matched your search

+21 -3
View File
@@ -531,13 +531,31 @@ func TestPodcastPlayerOrderControl(t *testing.T) {
// match the escaped form.
esc := func(s string) string { return strings.ReplaceAll(s, `"`, """) }
t.Run("context_carries_feed_source", func(t *testing.T) {
t.Run("unset_default_is_reversed", func(t *testing.T) {
// 2026-11-01: the UNSET per-feed order resolves to "reverse"
// (audiobook-first — feed document order runs newest-first, so
// native started a book at its last chapter). The player's
// sequence config carries it; the podcast page's dropdown shows
// it selected; ep1 (feed position 1 of 2) plays LAST.
code, body := podcastAuthed(t, setup, setup.Token, "GET", ep1URL, "")
require.Equal(t, http.StatusOK, code)
assert.Contains(t, string(body), esc(`"source":"feed"`), "queue context must carry its owner (§10 R2)")
assert.Contains(t, string(body), esc(`"feed_id":"`+feedID+`"`))
assert.Contains(t, string(body), esc(`"order":"native"`))
assert.Contains(t, string(body), esc(`"episode_index":1`))
assert.Contains(t, string(body), esc(`"order":"reverse"`),
"unset order defaults to reversed (audiobook-first)")
assert.Contains(t, string(body), esc(`"episode_index":2`),
"feed position 1 plays last under the reversed default")
})
t.Run("podcast_page_dropdown_shows_reversed_when_unset", func(t *testing.T) {
code, body := podcastAuthed(t, setup, setup.Token, "GET", "/podcasts/"+feedID, "")
require.Equal(t, http.StatusOK, code)
html := string(body)
assert.Contains(t, html, `id="podcast-order-select"`, "the feed page renders its Order dropdown")
assert.Contains(t, html, `<option value="reverse" selected`,
"the unset default renders reversed as the selected option")
assert.NotContains(t, html, `<option value="native" selected`,
"native is no longer the unset default")
})
t.Run("order_dropdown_renders_feed_mode", func(t *testing.T) {