From 73f3f7749ac7fecaf529dc476f29544024f4106e Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Wed, 6 May 2026 17:06:18 +0800 Subject: [PATCH] fix: cmdPrev retry-skip + show SongCard actions on touch devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two corner-case fixes for the prev-history + play-next feature: 1. cmdPrev only tried queue.prev() once. instance.playNext's auto- advance retry-skip pushes failed songs into the same history stack, so a single prev frequently lands on an unplayable song — returning "Cannot play previous song" while leaving queue.currentIndex stuck mid-failure (causing next() to skip past the actually-playing song). Retry up to 4 times so prev finds a playable history entry, matching the retry budget already used by playNext for auto-advance. 2. SongCard.song-actions has opacity:0 by default and is revealed via parent :hover. Touch devices have no hover, so all three action buttons (Play / Play Next / Add) were invisible to phone/tablet users. Add @media (pointer: coarse) → opacity:1 to always show on touch. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/bot/instance.ts | 14 +++++++++----- web/src/components/SongCard.vue | 9 +++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/bot/instance.ts b/src/bot/instance.ts index 728ded7..3f6bf50 100755 --- a/src/bot/instance.ts +++ b/src/bot/instance.ts @@ -498,13 +498,17 @@ export class BotInstance extends EventEmitter { } private async cmdPrev(): Promise { - const prev = this.queue.prev(); - if (prev) { + // Retry-skip up to 4 attempts: history can include failed songs + // that playNext's auto-advance retry-skipped past, so a single + // prev would otherwise land on an unplayable song and leave the + // queue's currentIndex stuck mid-failure. + for (let i = 0; i < 4; i++) { + const prev = this.queue.prev(); + if (!prev) return "No previous song"; const ok = await this.resolveAndPlay(prev); - if (!ok) return "Cannot play previous song"; - return `Now playing: ${prev.name} - ${prev.artist}`; + if (ok) return `Now playing: ${prev.name} - ${prev.artist}`; } - return "No previous song"; + return "Cannot play any previous songs (all failed to resolve)"; } private cmdVol(cmd: ParsedCommand): string { diff --git a/web/src/components/SongCard.vue b/web/src/components/SongCard.vue index 8dd2b1a..c2cded1 100644 --- a/web/src/components/SongCard.vue +++ b/web/src/components/SongCard.vue @@ -156,6 +156,15 @@ function formatDuration(seconds: number): string { transition: opacity var(--transition-fast); } +// Touch devices have no :hover, so the parent-hover-reveals-actions +// pattern leaves all action buttons invisible. Always show on coarse- +// pointer (touch) inputs — this is also where bigger tap targets matter. +@media (pointer: coarse) { + .song-actions { + opacity: 1; + } +} + .action-btn { font-size: 18px; padding: 4px;