fix(anime): guard playback races and harden install/extract tests

- Tag subtitle cache writes with a generation counter so overlapping playEpisode calls can't clobber the shared cache dir, and don't fail playback when track setup errors
- Ignore stale episode clicks in the detail panel via a LatestRequest guard on playback
- Extract a resetGrid helper in animeui to dedupe grid-clearing logic
- Assert reader cancellation and extracted-file writes actually happen in installer/subsync tests
This commit is contained in:
2026-08-15 21:44:51 -07:00
parent 289c74da35
commit 8aff2ae505
5 changed files with 57 additions and 29 deletions
+7
View File
@@ -25,6 +25,7 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
let selectedAnime: { url: string; title: string; sourceId: string } | null = null;
let resultsScrollTop = 0;
const requests = new LatestRequest();
const playbacks = new LatestRequest();
function formatEpisodeIndex(episode: AnimeBrowserEpisode, fallbackIndex: number): string {
const value = episode.number ?? fallbackIndex;
@@ -38,6 +39,9 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
const anime = selectedAnime;
if (!anime) return;
// Only the newest click owns the button states and the status line; an
// earlier episode resolving late must not overwrite them.
const playback = playbacks.begin();
for (const other of episodes.querySelectorAll<HTMLButtonElement>('.cue')) {
other.removeAttribute('data-state');
}
@@ -55,6 +59,8 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
}),
);
if (!playbacks.isCurrent(playback)) return;
if (!attempt.ok) {
button.removeAttribute('data-state');
setStatus(describe(attempt.error), 'error');
@@ -163,6 +169,7 @@ export function createDetailPanel({ api, setStatus }: DetailPanelOptions) {
function close(): void {
requests.cancel();
playbacks.cancel();
detail.classList.add('hidden');
results.classList.remove('hidden');
results.scrollTop = resultsScrollTop;