Commit Graph

222 Commits

Author SHA1 Message Date
felixfoertsch 9d65dd12be pipeline ux: actionable errors, inbox sorting, danger stop buttons, overlapping checkmarks
Build and Push Docker Image / build (push) Successful in 2m10s
- clickable error count in header shows file names + error messages
- inbox sort dropdown (scan time / name, asc / desc)
- inbox movies no longer minimal (show available badges)
- stop buttons use solid danger style, descriptive labels (Stop Scan, Stop Job, Stop Sorting)
- double checkmarks overlap like WhatsApp read receipts
- processInbox logs start/completion to stdout for Docker visibility
- fix byTitle in language-resolver test, bump to 2026.04.21.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 08:58:08 +02:00
felixfoertsch 203c1aa154 reorder settings: languages + schedule left, radarr/sonarr right, danger zone bottom
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 08:30:35 +02:00
felixfoertsch e0fe061b74 drop old jellyfin schema on upgrade, require rescan
Build and Push Docker Image / build (push) Successful in 45s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 07:04:42 +02:00
felixfoertsch 6f4d265385 bump version to 2026.04.21
Build and Push Docker Image / build (push) Successful in 1m32s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 06:42:22 +02:00
felixfoertsch 686434f5c3 remove jellyfin, mqtt, webhook services, fix tests, add schema migrations
- delete server/services/jellyfin.ts, webhook.ts, mqtt.ts and their tests
- strip jellyfin/mqtt imports and startup calls from index.tsx and settings.ts
- remove /jellyfin, /mqtt, /mqtt/status, /mqtt/test, /jellyfin/webhook-plugin endpoints from settings router
- clean ENV_MAP and isEnvConfigured of jellyfin/mqtt keys
- add db/index.ts migrations for series_key, duration_seconds, scan_status, scan_error, last_scanned_at (new columns absent on older dev DBs)
- move idx_media_items_series_key out of SCHEMA into migrate() so it runs after the column is added
- fix all test fixtures: drop jellyfin_id/series_jellyfin_id column refs, update MediaItem/MediaStream object literals to match current types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00
felixfoertsch 6b01de5f30 frontend: drop jellyfin/mqtt, use series_key
Remove Jellyfin web links, MqttBadge, MqttSection, jellyfinUrl prop chain,
and all jellyfin_id/seriesJellyfinId references from frontend types and
components. Replace series_jellyfin_id with series_key, remove jellyfinUrl
from PipelineData, drop language_display from MediaStream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00
felixfoertsch 5bea914b14 review.ts: replace jellyfin_id/series_jellyfin_id with file_path/series_key, remove jellyfin imports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00
felixfoertsch 6022ed09b2 simplify language-resolver: drop jellyfin resolveSeriesTvdb callback
remove resolveSeriesTvdb from LanguageResolverConfig, rename jellyfinFallback
to probeFallback, replace Jellyfin-based TVDB resolution with series_name
title search over Sonarr library, update tests accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:54 +02:00
felixfoertsch 8a95026728 rewrite scan: filesystem walk + ffprobe, no jellyfin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch fbfd492e18 rewrite rescan: upsertScannedItem from probe data, no jellyfin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 9b04ed87d2 new schema: drop jellyfin columns, file_path as unique key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 444d2eb733 extract normalizeLanguage, guessOriginalLanguage to language-utils.ts
Move language normalization out of jellyfin.ts into its own module so
non-Jellyfin services (ffmpeg, radarr, sonarr, analyzer) no longer
depend on the Jellyfin service file. jellyfin.ts re-exports
normalizeLanguage for backward compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 7cb2714793 add discover.ts: recursive video file discovery with Bun.Glob, unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 7d12241ccb add probe.ts: ffprobe JSON parser with probeFile, parseProbeOutput, unit tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 96bf208a16 add path-parser: extract movie/episode metadata from Radarr/Sonarr file paths
Parses title, year, provider IDs (imdb/tmdb/tvdb), season, episode number,
and container from on-disk paths without requiring Jellyfin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 06:31:21 +02:00
felixfoertsch 4fe651f822 add sonarr title fallback for episode language resolution, bump version
Build and Push Docker Image / build (push) Successful in 2m34s
2026-04-20 21:12:14 +02:00
felixfoertsch c11ba795e6 compact icon buttons on cards: ↻ rescan, ✓ approve, ✓✓ approve all, ↑ above
Build and Push Docker Image / build (push) Successful in 1m17s
all with title hover text for discoverability. saves horizontal space
so buttons never overflow their card boundaries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:45:55 +02:00
felixfoertsch e0a43f1a54 approve-above button uses absolute positioning, no overflow
Build and Push Docker Image / build (push) Successful in 1m33s
the button floats top-left on hover without displacing the action row
buttons (rescan, approve series) which stay in fixed positions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:43:03 +02:00
felixfoertsch 089cfe8640 fix series card action row overflow: allow button wrapping
Build and Push Docker Image / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:41:46 +02:00
felixfoertsch abdfa2a790 move inbox stop button to left side (backward slot)
Build and Push Docker Image / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:41:13 +02:00
felixfoertsch 65acc683b9 stop button aborts queue loop, remaining jobs stay pending in queue
Build and Push Docker Image / build (push) Successful in 6m48s
previously stop only killed the running ffmpeg process — the loop
immediately picked up the next pending job. now the abort signal
breaks the loop between jobs, so remaining items stay in the queue
column as pending jobs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 15:46:31 +02:00
felixfoertsch c985bb9f04 move auto-process queue checkbox from Processing to Queue column
Build and Push Docker Image / build (push) Successful in 1m24s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:25:15 +02:00
felixfoertsch df08065aa4 processing bar full width, stop button shows just "Stop"
Build and Push Docker Image / build (push) Successful in 1m35s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:20:59 +02:00
felixfoertsch 1e749e0188 OG quality check compares against all non-OG streams in file, not just kept
Build and Push Docker Image / build (push) Successful in 3m55s
the Dead Zone case: OG is Japanese mono, English 5.1 exists but is removed
by config (audio_languages=[]). the previous check only looked at kept
streams, so it never fired. now compares OG channels against all non-OG
audio in the file — if a superior dub exists, flag for review regardless
of whether it's currently configured to be kept.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 14:00:10 +02:00
felixfoertsch e6d3c179fe bump version to 2026.04.20.9
Build and Push Docker Image / build (push) Successful in 1m19s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:08:30 +02:00
felixfoertsch fd1fb8c77b unify all back-to-inbox actions via shared sendToInbox logic
Build and Push Docker Image / build (push) Successful in 1m18s
every "← Back to inbox" button (review, queue, done, per-item unapprove,
per-item reopen) now does the same thing: reset plan to pending/unsorted,
clear auto_class, delete non-running jobs. previously unapprove left
sorted=1 (sent to review instead of inbox) and each column had its own
SQL. now consistent: back to inbox always means "needs re-processing."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 12:03:39 +02:00
felixfoertsch f00360b128 bump version to 2026.04.20.8
Build and Push Docker Image / build (push) Successful in 3m45s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:47:05 +02:00
felixfoertsch c3ee64974c flag OG quality inferior to non-OG as auto_heuristic (needs review)
Build and Push Docker Image / build (push) Successful in 59s
when the original language track has fewer channels than a kept non-OG
track (e.g. Japanese mono vs English 5.1), classify as auto_heuristic
instead of auto so the user can decide whether to prefer the dub.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:36:35 +02:00
felixfoertsch 3341ceed14 remove analyzer from scan, scan is now pure jellyfin ingest
Build and Push Docker Image / build (push) Successful in 1m13s
upsertJellyfinItem no longer runs analyzeItem or creates stream_decisions.
it inserts a minimal review_plans stub (pending, unsorted). all analysis
happens in processInbox. this means after scan, ALL items land in the
inbox — the "needs action" count equals the inbox count until processing
classifies them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:31:30 +02:00
felixfoertsch fbb8bdf272 inbox cards show title only, no badges or stream details before processing
Build and Push Docker Image / build (push) Successful in 1m12s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:06:29 +02:00
felixfoertsch b7f6144a6a live pipeline updates during scan via SSE pipeline_changed events
Build and Push Docker Image / build (push) Successful in 38s
scan emits pipeline_changed every 25 items, rescan endpoints emit on
completion. pipeline page listens and throttle-reloads all column data
(1s debounce) so inbox fills progressively without manual navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:57:01 +02:00
felixfoertsch ce37aec647 use debian base for runtime stage so ffmpeg layer caches independently
Build and Push Docker Image / build (push) Successful in 8m49s
the oven/bun:1-slim tag resolves to a new digest frequently, busting
the apt-get ffmpeg layer (5+ minutes). switching to debian:bookworm-slim
keeps that layer stable. bun is copied as a single binary from the
build stage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:34:33 +02:00
felixfoertsch b7ee1f387b cancel superseded CI builds on new push
Build and Push Docker Image / build (push) Successful in 48s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:31:12 +02:00
felixfoertsch ebbb305a58 settings: two-column layout, remove auto-process toggle, languages on top right
Build and Push Docker Image / build (push) Successful in 7m8s
jellyfin on the left, languages + radarr + sonarr + schedule on the right.
auto-process toggle removed (available from inbox column). danger zone
stays full width at the bottom.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:15:32 +02:00
felixfoertsch 2701441a1c background processing with stop, simplify pipeline header stats, remove inbox skip
Build and Push Docker Image / build (push) Successful in 1m40s
- processInbox runs in background, returns immediately, items appear in
  columns progressively via SSE-triggered reloads
- stop button aborts mid-run, processed items stay in their destination
- fix language resolver: resolve series TVDB before sonarrLang HTTP fallback
- remove skip/skip-all from inbox (not meaningful in process-based flow)
- pipeline header shows only: total, needs action, queued, errors
- fix column header height jitter with fixed subtitle slot height

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 10:06:44 +02:00
felixfoertsch 43d934ff68 background processing with stop, remove inbox skip, progressive column updates, fix header height
processing runs in background, items appear in columns progressively via
SSE-triggered reloads. stop button aborts mid-run, remaining items stay in
inbox. remove skip/skip-all from inbox. fix column header height jitter by
giving subtitle slot a fixed height.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:52:58 +02:00
felixfoertsch aafe5b2ec1 fix processInbox slowness: resolve series TVDB before sonarrLang HTTP fallback
Build and Push Docker Image / build (push) Successful in 1m9s
the resolver was calling sonarrLang with episode-level TVDB IDs first,
missing the library, triggering an HTTP round-trip per episode. now checks
the library and resolves the correct series TVDB before calling sonarrLang.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:38:04 +02:00
felixfoertsch c251d22638 pipeline is root page, full viewport width, remove pipeline nav link
Build and Push Docker Image / build (push) Successful in 2m1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:23:55 +02:00
felixfoertsch 5aa8041f52 remove done column 50-item cap, rename Auto Review to Process Inbox, fix duplicate navigate
Build and Push Docker Image / build (push) Successful in 3m18s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:14:48 +02:00
felixfoertsch 56a1aa3b65 add rescan buttons to SeriesCard (series + season), navigate back to pipeline after detail rescan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:13:37 +02:00
felixfoertsch ffa6d95d6f rescan endpoints: reset to inbox, add series/season rescan
- Per-item rescan now resets plan to inbox (sorted=0, auto_class=NULL),
  clears external_raw, deletes pending jobs, then lets processInbox
  handle language resolution + analysis when auto_processing is on
- Add getSeriesEpisodes() to jellyfin service for bulk episode discovery
- Add POST /api/review/rescan-series endpoint accepting seriesJellyfinId
  + optional seasonNumber — discovers new episodes, resets all matching
  items to inbox, refreshes streams from Jellyfin, auto-processes if on

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:09:26 +02:00
felixfoertsch babc6414e5 processInbox auto-loads sonarr/radarr libraries when no external config provided
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:06:47 +02:00
felixfoertsch 648a17b763 rename sortInbox to processInbox, integrate language resolution into process step
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 09:04:18 +02:00
felixfoertsch ed085a5e17 add resolveLanguage module for standalone Sonarr/Radarr language resolution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 08:58:25 +02:00
felixfoertsch 94610d05b7 strip Sonarr/Radarr lookups from scan path, make upsertJellyfinItem Jellyfin-only
RescanConfig now only carries audioLanguages. Radarr/Sonarr library
loading, language resolution, and resolveSeriesTvdb callback removed
from rescan.ts, scan.ts, and webhook.ts. RescanResult no longer tracks
radarrHit/sonarrHit/missingProviderId counters. Tests updated: removed
authoritative-source and resolved-TVDB-enables-Sonarr tests (moving to
processInbox in a later task), added assertion that scan never sets
sonarr/radarr as language source.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 08:54:43 +02:00
felixfoertsch a5847d0219 add pipeline refactor implementation plan
9 tasks: strip scan, language resolver, processInbox, rescan endpoints,
done column uncap, frontend renames, rescan buttons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 08:30:18 +02:00
felixfoertsch 7509bb40fd add pipeline refactor design spec
scan/process separation, per-item/series/season rescan, done column uncap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 08:23:54 +02:00
felixfoertsch a1da644aa3 fix sonarr OG-language miss when Jellyfin omits SeriesProviderIds
Build and Push Docker Image / build (push) Successful in 3m49s
episodes without SeriesProviderIds.Tvdb (older Jellyfin, un-refreshed items)
fell back to the episode-level TVDB ID, which never matched Sonarr's
series-keyed library. add resolveSeriesTvdb callback that fetches the series
item from Jellyfin to get the correct series TVDB ID, with per-scan caching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 07:39:24 +02:00
felixfoertsch c1baf3e476 audio detail: delete-file + delete-and-refetch via Radarr/Sonarr
Build and Push Docker Image / build (push) Successful in 3m24s
escape hatch for items the audio pipeline can't usefully fix — e.g. a
release whose only audio track is English commentary and needs to be
purged so *arr can find a better one. two buttons on the audio detail
page:

  🗑 Delete file            — unlinks the file and drops our DB rows
                              (cascades streams, plans, decisions, jobs)
  🗑 Delete & refetch       — same, then asks Radarr/Sonarr to rescan
                              (to notice the deleted file) and trigger
                              an indexer search for a replacement

backend: POST /api/review/:id/delete { refetch }. the refetch step is
best-effort and its result ships under `refetch` on the response so the
UI can surface partial wins — file deleted + db clean even if Radarr
doesn't have the movie. helpers live on the existing *arr service
modules (triggerMovieRefetch, triggerEpisodeRefetch) and do the command
lookups + POST /api/v3/command calls themselves.

UI uses native confirm dialogs showing the file path. on success,
navigates back to /pipeline since the detail page points at a row that
no longer exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:21:46 +02:00
felixfoertsch ee9add076a collapse Library page into a compact Pipeline header
Build and Push Docker Image / build (push) Successful in 1m17s
the separate Library page was mostly duplicating what the pipeline
columns + the item detail page already show. move its two useful bits —
the stats row (total / scanned / needs action / no change / approved /
done / errors) and the scan control bar — into a compact two-row header
above the pipeline columns, drop the library items table entirely, and
redirect "/" to "/pipeline".

the scan SSE buffering logic moves verbatim into PipelineHeader.tsx so
the progress bar and the stats refresh on completion keep working. the
dead /api/scan/items endpoint and its parseScanItemsQuery +
buildScanItemsWhere helpers (plus their tests) go away with the UI;
/api/scan, /api/scan/start, /api/scan/stop, /api/scan/events stay.

nav loses "Library" — Pipeline is the only entry point now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:16:21 +02:00