All checks were successful
Build and Push Docker Image / build (push) Successful in 49s
the previous TimeInput was a bespoke two-field widget. correct in behaviour
but off-policy: we don't roll our own ui primitives when a maintained
library solves it. swap for react-aria-components + @internationalized/date
pinned to hourCycle={24}, granularity=minute, shouldForceLeadingZeros so
the output is always strict HH:MM regardless of browser/OS locale.
wrapper lives at src/shared/components/ui/time-input.tsx and keeps the
existing string-based API (value: "HH:MM", onChange(next)) so callers don't
change.
also updates the stack docs: web-stack.md now pins react-aria-components
as THE required library for every date/time ui; iOS and Android entries
mark their canonical component as TBD and explicitly forbid rolling our
own without user sign-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.7 KiB
4.7 KiB
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 devruns Hono API (:3000) + Vite (:5173) concurrently; Vite proxies /api/* - Prod:
bun run build→ Vite builds todist/; 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_languagesconfig order - Subtitles: ALL removed from container, extracted to sidecar files on disk
subtitle_filestable tracks extracted sidecar files (file manager UI)review_plans.subs_extractedflag tracks extraction statusis_nooponly considers audio changes (subtitle extraction is implicit)
Scan flow
- Jellyfin paginated API → upsert media_items + media_streams
- Cross-check with Radarr (movies) / Sonarr (episodes) for language
- analyzeItem() → upsert review_plans + stream_decisions
- SSE events stream progress to browser (React EventSource in ScanPage)
Running locally
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
Forward-looking rules
- Schema changes need migrations going forward — resurrect the try/catch ALTER TABLE pattern in
server/db/index.tswhenever touching table columns - Never use locale-aware time/number widgets — use
TimeInput+formatThousands, never<input type="time">ortoLocaleString()
Workflow rules
- Always bump version in
package.jsonbefore committing/pushing. CalVer with dot-suffix per global AGENTS.md (YYYY.MM.DD.N)..gitea/workflows/build.ymltags a Docker image with this version, so the+Nform breaks CI withinvalid reference format.
Docker deploy (Unraid)
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.