fix: watch-mode Next/autoplay follow one true ring, honor Fresh toggle

Every forward path through the ring now uses a single selector:
  chooseNextInRing() = freshMode ? getNextUnwatchedItem() : getNextItem()

Next button, autoplay countdown, DJ crossfade target, preload, and
countdown-play-now all route through it. Previously, Next button
hardcoded skipToNextUnwatched() regardless of Fresh toggle — user
would see offset +1 in sidebar but land on a farther unwatched item.

navigateToNext and completeDjFadeout now sync ringPosition via
indexOf(target) instead of blindly advancing by +direction — necessary
when Fresh mode jumps past watched items.
This commit is contained in:
russell@unturf.com 2026-04-21 14:22:59 -04:00
parent 206ff49e68
commit b09d1fc739

View file

@ -1218,15 +1218,20 @@
fetchAndNavigate(ringProductIds[idx]);
}
function skipToNextUnwatched() {
// One true selector — every forward path (Next button, autoplay, preload)
// walks our ring the same way. Fresh toggle controls watched-skip.
function chooseNextInRing() {
return freshMode ? getNextUnwatchedItem() : getNextItem();
}
function skipToNext() {
if (djCrossfadeActive) cancelDjCrossfade();
cancelCountdown();
if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; }
var item = getNextUnwatchedItem();
var item = chooseNextInRing();
if (!item || !item.id) return;
// Advance ring position to the target item
var idx = ringProductIds.indexOf(item.id);
if (idx !== -1) {
ringPosition = idx;
@ -1264,7 +1269,7 @@
if (!autoplayEnabled) return;
if (!activeMedia) return;
var next = getNextItem();
var next = chooseNextInRing();
if (!next || !next.id) return;
// Need preloaded data for the next item
@ -1450,13 +1455,18 @@
djCrossfadeData = null;
cancelCountdown();
// Pop queue or advance ring
// Pop queue or sync ring position to target
if (queue.length > 0 && queue[0].id === data.product_id) {
queue.shift();
saveQueue();
renderQueue();
} else if (ringProductIds.length) {
ringPosition = (ringPosition + ringDirection + ringProductIds.length) % ringProductIds.length;
var idx = ringProductIds.indexOf(data.product_id);
if (idx !== -1) {
ringPosition = idx;
} else {
ringPosition = (ringPosition + ringDirection + ringProductIds.length) % ringProductIds.length;
}
saveRingState();
}
@ -1499,7 +1509,7 @@
if (!autoplayEnabled) return;
if (djCrossfadeActive) return;
var next = getNextItem();
var next = chooseNextInRing();
if (!next) return;
showCountdownOverlay(next);
@ -1565,12 +1575,16 @@
queue.shift();
saveQueue();
renderQueue();
} else {
// Advance ring position
if (ringProductIds.length) {
} else if (ringProductIds.length) {
// Sync ring position to target's actual index — honors Fresh-mode
// jumps past watched items (which can be farther than +direction).
var idx = ringProductIds.indexOf(productId);
if (idx !== -1) {
ringPosition = idx;
} else {
ringPosition = (ringPosition + ringDirection + ringProductIds.length) % ringProductIds.length;
saveRingState();
}
saveRingState();
}
if (preloadedData && preloadedData.product_id === productId) {
@ -1593,7 +1607,7 @@
// --- Phase 1: fetch next item JSON (fast, no media download) ---
function preloadNext() {
var next = getNextItem();
var next = chooseNextInRing();
if (!next || !next.id) return;
// Idempotent: skip if already preloading or preloaded this item
@ -1866,7 +1880,7 @@
if (e.target.id === 'skip-next-btn' || e.target.closest('#skip-next-btn')) {
e.preventDefault();
skipToNextUnwatched();
skipToNext();
return;
}
@ -1888,7 +1902,7 @@
}
} else {
cancelCountdown();
var next = getNextItem();
var next = chooseNextInRing();
if (next) navigateToNext(next);
}
return;