# Auto-retry video load with retry-count toast

Status: ACTIVE
Source request: "bug when i click on a video it just stuck on the thumbnail and not proceeding to play or even to the ad count. i need to retry many times (by reopening the window or clicking back-forward) and eventually it loads, but i want it to just retry behind the scenes, and show me the count in the toast how many retries it's on."

## Root cause (confirmed by code read)

`Watch.js getVideoInformationLocal()` calls `getLocalVideoInfo(this.videoId)` (src/renderer/helpers/api/local.js) — a long fetch chain: YouTube HTML page → botguard challenge → poToken generation → /player → format deciphering. Any transient failure (network hiccup, poToken/botguard failure, 429, bot check "Sign in to confirm you're not a bot") lands in the catch and sets `errorMessage`/falls back, leaving the Watch page stuck on the thumbnail. The user's manual workaround (reopen window, back-forward) re-runs this exact fetch until it happens to succeed.

## Fix

Auto-retry the video-info fetch in `getVideoInformationLocal` up to 20 attempts (1.5 s apart), showing a toast with the attempt count (`Retrying video load: 1/20`, ...). Definitive failures (private, members-only, age-restricted, DRM) set `errorMessage` and return early — the retry loop only retries **thrown** errors, so those are untouched. Bot-check errors ("Sign in to confirm you're not a bot") also do NOT retry — with backendFallback OFF they set the error message and return; with backendFallback ON they throw and fall back to Invidious as before (owner decision).

## Acceptance criteria

1. Transient fetch failure → up to 19 automatic retries, each with a toast showing the running attempt count, then normal error/fallback handling.
2. Definitive errors (private video, members-only, age-restricted, DRM, missing formats) and bot checks still fail immediately with no retry toast.
3. Navigating away mid-retry stops the retry loop (guard on videoId change / component unmount).
4. Existing fallback-to-Invidious path still works after retries are exhausted.
5. `pnpm run eslint-lint` passes.
6. Committed locally; release package rebuilt.

## Changes

- `src/renderer/views/Watch/Watch.js` — constants `VIDEO_INFO_MAX_ATTEMPTS = 20`, `VIDEO_INFO_RETRY_DELAY_MS = 1500`; extract current try-body to `fetchVideoInformationLocalOnce()`; rewrite `getVideoInformationLocal()` as retry loop (toast + delay + navigation guard).
- `static/locales/en-US.yaml` — add `Video.Watch.'Retrying video load: {attempt}/{maxAttempts}'`.

## Receipt

- Implemented in Watch.js + local.js + en-US.yaml; full `pnpm run eslint-lint` passes (exit 0); extracted body verified byte-identical to the original except the intended changes; bot-check no-retry reverted per owner; max attempts = 20.
- Committed locally (`0e7792212`); release package rebuilt in `build/` (deb, zip, 7z, AppImage); packed renderer.js + en-US locale verified to contain the retry toast.
- Follow-up fix (committed after `0e7792212`): stalled requests now count as failed attempts — each attempt is capped by a 45 s timeout (`AbortController` threaded through the fetch chain: HTML page, player JS, /player, /next, age-restriction bypass), so the retry toast + count now appears in EVERY failure mode, including hangs.
- Installed system-wide via `apt` (freetube 0.25.2, `dpkg -V` clean, installed bundle verified to contain the retry code).
- Build offload: AGENTS.md documents the buildbox recipe; `_scripts/box-build-release.mjs` committed; full release build proven on debian1 (all 4 targets, artifacts rsynced back to `build/`, verified to contain the fix). Laptop no longer runs package builds.

## Next executable action

None — done. Owner uses the rebuilt package.
