# netfelix-audio-fix — Project Memory ## What it does Bun + Hono JSON REST API + React 19 SPA to scan a Jellyfin library, compute which audio/subtitle tracks to remove from each file, let you review and edit those decisions, then execute FFmpeg (copy mode) to strip/reorder streams. Remote nodes via SSH. ## Key technical decisions - **Runtime**: Bun + Hono JSON REST API backend; React 19 SPA frontend via Vite - **DB**: bun:sqlite WAL mode → `data/netfelix.db` (server-side; PGlite not applicable) - **Frontend stack**: React 19 + TanStack Router + Zustand + Tailwind v4 + cn() utilities - **Code quality**: Biome (formatting + linting) — no ESLint/Prettier - **Path alias**: `~/` → `src/` (vite.config.ts resolve.alias + tsconfig.json paths) - **Dev**: `bun run dev` runs Hono API (:3000) + Vite (:5173) concurrently; Vite proxies /api/* - **Prod**: `bun run build` → Vite builds to `dist/`; Hono serves dist/ + /api/* routes - **SSH keys**: uploaded via file input, stored as PEM text in `nodes.private_key` - Two tsconfigs: `tsconfig.json` (frontend, DOM lib), `tsconfig.server.json` (backend, bun-types) ## Project structure ``` server/ ← Backend (Bun + Hono, JSON API at /api/*) index.tsx ← entry point, Bun.serve, CORS for dev, static SPA serve types.ts ← server-side interfaces db/index.ts ← getDb(), getConfig(), setConfig(), getEnvLockedKeys() db/schema.ts ← SCHEMA DDL string + DEFAULT_CONFIG services/ jellyfin.ts / radarr.ts / sonarr.ts / analyzer.ts / ffmpeg.ts / ssh.ts api/ dashboard.ts / scan.ts / review.ts / execute.ts / nodes.ts / setup.ts / subtitles.ts src/ ← Frontend (React SPA, built with Vite) main.tsx ← entry, RouterProvider index.css ← Tailwind v4 @import routeTree.gen.ts ← auto-generated by TanStack Router (gitignored) routes/ __root.tsx ← nav layout with Link components index.tsx / scan.tsx / execute.tsx / nodes.tsx / setup.tsx review.tsx ← layout route with Audio/Subtitles tab bar + Outlet review/index.tsx (redirect → /review/audio) review/audio/index.tsx ($filter) / review/audio/$id.tsx review/subtitles/index.tsx ($filter) / review/subtitles/$id.tsx features/ dashboard/DashboardPage.tsx scan/ScanPage.tsx (SSE for live progress) review/AudioListPage.tsx / AudioDetailPage.tsx subtitles/SubtitleListPage.tsx / SubtitleDetailPage.tsx execute/ExecutePage.tsx (SSE for job updates) nodes/NodesPage.tsx setup/SetupPage.tsx shared/ lib/utils.ts (cn()) / api.ts (typed fetch) / types.ts / lang.ts (LANG_NAMES) components/ui/badge.tsx / button.tsx / input.tsx / select.tsx / textarea.tsx / alert.tsx biome.json / vite.config.ts / tsconfig.json / tsconfig.server.json / index.html ``` ## Rules: what gets kept - Video/Data streams: always keep - Audio: keep original_language + configured `audio_languages` (if OG unknown → keep all, flag needs_review) - Audio order: OG first, then additional languages in `audio_languages` config order - Subtitles: ALL removed from container, extracted to sidecar files on disk - `subtitle_files` table tracks extracted sidecar files (file manager UI) - `review_plans.subs_extracted` flag tracks extraction status - `is_noop` only considers audio changes (subtitle extraction is implicit) ## Scan flow 1. Jellyfin paginated API → upsert media_items + media_streams 2. Cross-check with Radarr (movies) / Sonarr (episodes) for language 3. analyzeItem() → upsert review_plans + stream_decisions 4. SSE events stream progress to browser (React EventSource in ScanPage) ## Running locally ```fish mise exec bun -- bun run dev # concurrent: Hono API :3000 + Vite :5173 mise exec bun -- bun run build # build frontend to dist/ mise exec bun -- bun start # production: Hono serves dist/ + API on :3000 ``` ## Workflow rules - **Always bump version** in `package.json` before committing/pushing. CalVer format: `YYYY.MM.DD` (append `.N` suffix for same-day releases). ## Docker deploy (Unraid) ```fish docker compose up -d # port 3000, data volume at ./data/ ``` Note: Docker must serve the built dist/ — run `bun run build` before building the Docker image.