# AGENTS.md — FreeTubeApp

audience: AI coding agents first

## Repo

Electron desktop app (FreeTube), Vue 2.7 renderer, webpack 5, pnpm. YouTube frontend with local/invidious backends. Branch `development` is the working branch; HEAD carries local patches over upstream.

## Build OFFLOAD — MUST NOT run builds on the laptop

`pnpm run pack` + electron-builder freeze the laptop. ALWAYS offload to a buildbox.

- Buildbox registry (single source of host truth): `~/.claude/buildbox-hosts.json`. Read `orders.build` = host spill order: debian1 → debian2 → debian3. Contact ONLY hosts marked `reachable`. NEVER hardcode a host name.
- Access: ssh alias (`ssh debian1`). Non-interactive ssh lacks `~/.local/bin` on PATH — always `export PATH="$HOME/.local/bin:$PATH"` first (corepack pnpm lives there).
- Box needs repo + deps first:
  1. `rsync -a --exclude node_modules --exclude .worktrees --exclude build --exclude dist --exclude .git --exclude .claude --exclude .slopgate <repo>/ debian1:~/Projects/FreeTubeApp/`
  2. `cd ~/Projects/FreeTubeApp && pnpm install --frozen-lockfile` (only after new deps; else cached)
- Build (background + poll — pack ~5 min, ebuilder ~10 min):
  `nohup bash -c "pnpm run pack > /tmp/box-pack.log 2>&1 && node _scripts/box-build-release.mjs > /tmp/box-ebuilder.log 2>&1; echo DONE > /tmp/box-build.done" &`
  `_scripts/box-build-release.mjs` is a committed copy of the release build (deb, zip, 7z, AppImage → `build/`).
- When `/tmp/box-build.done` exists: check both logs for exit 0 + `building target=...` lines, then `rsync -a debian1:~/Projects/FreeTubeApp/build/ build/`.

DO NOT run `pnpm run pack`, `build-release`, or electron-builder on the laptop. Lint and quick tests stay local (fast); full package builds go to the box.

## Commands

- Lint: `pnpm run eslint-lint` (whole project, incl. YAML), `pnpm run lint-yml`
- Pre-commit: lefthook runs eslint (auto).
- Verify release build local vs packaged: extract `dist/renderer.js` from `build/linux-unpacked/resources/app.asar` via `@electron/asar` (in pnpm store: `node_modules/.pnpm/node_modules/@electron/asar/bin/asar.js`), grep for the changed string.

## Video-load retry (local API)

`src/renderer/views/Watch/Watch.js` — `getVideoInformationLocal` retries `fetchVideoInformationLocalOnce` up to 20× (1.5 s apart) with a `Retrying video load: {attempt}/{maxAttempts}` toast; each attempt capped at 45 s via AbortController threaded through the fetch chain in `src/renderer/helpers/api/local.js`. Bot checks and definitive errors (private/members-only/age-restricted/DRM) do NOT retry. Retry toast text lives in `static/locales/en-US.yaml` only (en-US-only matches upstream convention for new keys).
