Commit Graph

41 Commits

Author SHA1 Message Date
felixfoertsch 94a460be9d rename setup → settings throughout; persist arr creds even on test failure
Build and Push Docker Image / build (push) Successful in 36s
Two cleanups:

1. Rename the page from 'Setup' to 'Settings' all the way down. The H1
   already said Settings; the file/component/api path were lying.
   - src/features/setup/ → src/features/settings/
   - SetupPage.tsx → SettingsPage.tsx, SetupPage → SettingsPage,
     SetupData → SettingsData, setupCache → settingsCache
   - server/api/setup.ts → server/api/settings.ts
   - /api/setup → /api/settings (only consumer is our frontend)
   - server/index.tsx import + route mount renamed
   - ScanPage's local setupChecked → configChecked

2. Sonarr (and Radarr) save flow: persist the values BEFORE running the
   connection test. The previous code returned early if the test failed,
   silently dropping what the user typed — explained the user's report
   that Sonarr 'forgets' the input. Now setConfig fires unconditionally
   on a valid (non-empty) URL+key; the test result is returned as
   { ok, saved, testError } so the UI can show 'Saved & connected' on
   success or '⚠ Saved, but connection test failed: …' on failure
   instead of erasing the input.

Note: setup_complete config key kept as-is — it represents 'has the user
configured Jellyfin' which is conceptually setup and not user-visible.
2026-04-13 12:26:30 +02:00
felixfoertsch e8f33c6224 consolidate dashboard into scan page; / now renders Scan
Build and Push Docker Image / build (push) Successful in 45s
Single landing page: stats grid up top, then scan controls + progress,
then recent items log. Drops the 'click → bounce to Scan' indirection.

- ScanPage pulls /api/dashboard for the stats card grid; refetches when
  a scan completes so totals reflect the new state
- Scan page also owns the setup-complete redirect to /settings (was on
  Dashboard) and the empty-library 'click Start Scan' nudge
- / route now renders ScanPage; /scan route deleted
- DashboardPage and its feature dir gone
- Nav: drop 'Dashboard', repoint 'Scan' to /
2026-04-13 12:21:03 +02:00
felixfoertsch 962b5efc6f settings: drop the section-header env-var lock badges
Build and Push Docker Image / build (push) Successful in 34s
The per-input LockedInput already shows a 🔒 inside any field that's
controlled by an env var, with a tooltip pointing at the .env file. The
extra '🔒 JELLYFIN_URL' badge in the section title was duplicate signal —
remove it. Drop EnvBadge entirely; section titles go back to plain text
('Jellyfin', 'Radarr (optional)', etc.).
2026-04-13 12:14:00 +02:00
felixfoertsch 1aafcb4972 apply codex code review: fix useEffect refetch loops, dead routes, subtitle job_type leftovers
Build and Push Docker Image / build (push) Successful in 36s
All ack'd as real bugs:

frontend
- AudioDetailPage / SubtitleDetailPage / PathsPage / ScanPage /
  SubtitleListPage / ExecutePage: load() was a fresh function reference
  every render, so 'useEffect(() => load(), [load])' refetched on every
  render. Wrap each in useCallback with the right deps ([id], [filter],
  or []).
- SetupPage: langsLoaded was useState; setting it inside load() retriggered
  the same effect → infinite loop. Switch to useRef. Also wrap saveJellyfin/
  Radarr/Sonarr in async fns so they return Promise<void> (matches the
  consumer signatures, fixes the latent TS error).
- DashboardPage: redirect target /setup doesn't exist; the route is
  /settings.
- ExecutePage: <>...</> fragment with two <tr> children had keys on the
  rows but not on the fragment → React reconciliation warning. Use
  <Fragment key>. jobTypeLabel + badge variant still branched on the
  removed 'subtitle' job_type — relabel to 'Audio Transcode' / 'Audio
  Remux' and use 'manual'/'noop' variants.

server
- review.ts + scan.ts: parseLanguageList helper catches JSON errors and
  enforces array-of-strings shape with a fallback. A corrupted config
  row would otherwise throw mid-scan.
2026-04-13 12:01:57 +02:00
felixfoertsch e3b241bef3 drop audio list tab, move per-item actions onto pipeline cards
Build and Push Docker Image / build (push) Successful in 39s
The pipeline tab fully replaces the audio list: same items, better
workflow. What the old list contributed (per-item details + skip/approve)
now lives inline on each pipeline card.

- delete src/routes/review/audio/index.tsx + src/features/review/AudioListPage.tsx
- /review/ now redirects to /pipeline (was /review/audio, which no longer exists)
- AudioDetailPage back link goes to /pipeline
- nav: drop the Audio link
- PipelineCard: three buttons on every card — Details (TanStack Link to
  /review/audio/$id — the detail route stays, it's how you drill in),
  Skip (POST /api/review/:id/skip), Approve (POST /api/review/:id/approve).
  Remove the old 'Approve up to here' button (it was computing against
  frontend ordering we don't want to maintain, and it was broken).
- SeriesCard: drop onApproveUpTo, pass new approve/skip handlers through
  to each expanded episode card
- server: remove now-unused POST /api/review/approve-batch (no callers)
2026-04-13 11:20:57 +02:00
felixfoertsch 5fa39aee7c processing card: meaningful progress display
Build and Push Docker Image / build (push) Successful in 1m42s
Two issues with the old bar:
1. progress state was never cleared between jobs — when a job finished,
   its 100% bar lingered on the next job's card until that job emitted
   its first progress event. Clear progress on any job_update where
   status != 'running', and on the column side ignore progress unless
   progress.id matches the current job.id.
2. labels were misleading: the left/right times were ffmpeg's *input*
   timestamp position (how far into the source it had read), not wall-
   clock elapsed/remaining. For -c copy jobs ripping a 90-min file in
   5 wall-seconds, the user saw '0:45 / 90:00' jump straight to
   '90:00 / 90:00' which looks broken.

New display: 'elapsed M:SS  N%  ~M:SS left'. Elapsed is wall-clock
since the job started (re-renders every second), percent comes from
ffmpeg input progress as before, ETA is derived from elapsed × (100-p)/p
once we have at least 1% to avoid wild guesses.
2026-04-13 10:29:49 +02:00
felixfoertsch 37e30e9ade processing column: per-card stop button alongside the column-header one
Build and Push Docker Image / build (push) Successful in 37s
2026-04-13 10:26:20 +02:00
felixfoertsch 2ada728e50 fix approve-up-to: client sends explicit visible plan id list
Build and Push Docker Image / build (push) Successful in 1m12s
The server's old /approve-up-to/:id re-ran its own SQL ORDER BY against
ALL pending plans (no LIMIT) to decide which rows fell 'before' the target.
The pipeline UI uses a different ordering — interleaving movies with
series cards, sorting by confidence tier without a name tiebreaker, and
collapsing every episode of a series into one card. Visible position
therefore did not map to the server's iteration position, and clicking
'Approve up to here' could approve far more (or different) items than
the user expected.

- replace POST /approve-up-to/:id with POST /approve-batch { planIds: [...] }
  — server only approves the plans the client lists, idempotent: skips
  ids that are no longer pending, were already approved, or are noop
- ReviewColumn now builds visiblePlanIds in actual render order
  (each movie's id, then every episode id of each series in series order)
  and 'approve up to here' on any card sends slice(0, idx+1) of that list
- works the same for both PipelineCard (movie) and SeriesCard (whole series
  through its last episode)
2026-04-13 10:16:58 +02:00
felixfoertsch 4a378eb833 pipeline: equal-width columns + per-column clear/stop button
Build and Push Docker Image / build (push) Successful in 39s
Extract a ColumnShell component so all four columns share the same flex-1
basis-0 width (no more 24/16/18/16 rem mix) and the same header layout
(title + count + optional action button on the right).

Per-column actions:
- Review:     'Skip all' → POST /api/review/skip-all (new endpoint, sets all
              pending non-noop plans to skipped in one update)
- Queued:     'Clear'    → POST /api/execute/clear (existing; cancels pending jobs)
- Processing: 'Stop'     → POST /api/execute/stop (new; SIGTERMs the running
              ffmpeg via a tracked Bun.spawn handle, runJob's catch path
              marks the job error and cleans up)
- Done:       'Clear'    → POST /api/execute/clear-completed (existing)

All destructive actions confirm before firing.
2026-04-13 10:08:42 +02:00
felixfoertsch ec28e43484 make pipeline responsive at scale: cap review query, debounce sse reload, indexable done count
Build and Push Docker Image / build (push) Successful in 37s
The pipeline endpoint returned every pending plan (no LIMIT) while the audio
list capped at 500 — that alone was the main lag. SSE compounded it: every
job_update (which fires per-line of running ffmpeg output) re-ran the entire
endpoint and re-rendered every card.

- review query: LIMIT 500 + a separate COUNT for reviewTotal; column header
  shows 'X of Y' and a footer 'Showing first X of Y. Approve some to see
  the rest' when truncated
- doneCount: split the OR-form into two indexable counts (is_noop + done&!noop),
  added together — uses idx_review_plans_is_noop and idx_review_plans_status
  instead of full scan
- pipeline page: 1s debounce on SSE-triggered reload so a burst of
  job_update events collapses into one refetch
2026-04-13 10:00:08 +02:00
felixfoertsch 9ee0dd445f remove standalone subtitle extract, unify done semantics, fix nav active matching
Build and Push Docker Image / build (push) Successful in 49s
Subtitle extraction lives only in the pipeline now; a file is 'done' when it
matches the desired end state — no embedded subs AND audio matches the
language config. The separate Extract page was redundant.

- delete src/routes/review/subtitles/extract.tsx + SubtitleExtractPage
- delete /api/subtitles/extract-all + /:id/extract endpoints
- delete buildExtractOnlyCommand + unused buildExtractionOutputs from ffmpeg.ts
- detail page: drop Extract button + extractCommand textarea, replace with
  'will be extracted via pipeline' note when embedded subs present
- pipeline endpoint: doneCount = is_noop OR status='done' (a file in the
  desired state, however it got there); UI label 'N files in desired state'
- nav: drop the now-defunct 'Extract subs' link, default activeOptions.exact
  to false so detail subpages (e.g. /review/audio/123) highlight their
  parent ('Audio') in the menu — was the cause of the broken-feeling menu
2026-04-13 09:41:46 +02:00
felixfoertsch e4c771d39e fix scan page tdz crash: break flush/stopFlushing useCallback cycle
Build and Push Docker Image / build (push) Has been cancelled
Prod minified bundle crashed with 'can't access lexical declaration 'o'
before initialization' because flush was memoized with stopFlushing in its
deps, and stopFlushing was memoized with flush in its deps — circular.
In dev this still worked (refs paper over TDZ), but Vite's minifier emitted
the declarations in an order that tripped the temporal dead zone.

Extract the interval-clearing into a plain inline helper (clearFlushTimer)
that both flush and stopFlushing call. flush no longer depends on
stopFlushing; the cycle is gone.
2026-04-13 08:17:57 +02:00
felixfoertsch 874f04b7a5 wire scheduler into queue, add retry, dev-reset cleanup, biome 2.4 migrate
- execute: actually call isInScheduleWindow/waitForWindow/sleepBetweenJobs in runSequential (they were dead code); emit queue_status SSE events (running/paused/sleeping/idle) so the pipeline's existing QueueStatus listener lights up
- review: POST /:id/retry resets an errored plan to approved, wipes old done/error jobs, rebuilds command from current decisions, queues fresh job
- scan: dev-mode DELETE now also wipes jobs + subtitle_files (previously orphaned after every dev reset)
- biome: migrate config to 2.4 schema, autoformat 68 files (strings + indentation), relax opinionated a11y/hooks-deps/index-key rules that don't fit this codebase
- routeTree.gen.ts regenerated after /nodes removal
2026-04-13 07:41:19 +02:00
felixfoertsch cdcb1ff706 drop multi-node ssh execution, unify job runner to local + fix job completion atomicity
- remove nodes table, ssh service, nodes api, NodesPage route
- execute.ts: local-only spawn, atomic CAS job claim via UPDATE status
- wrap job done + subtitle_files insert + review_plans status in db transaction
- stream ffmpeg output per line with 500ms throttled flush
- bump version to 2026.04.13
2026-04-13 07:25:19 +02:00
felixfoertsch 1762f070a9 pipeline UI polish: transcode reasons, scroll fix, series card overflow, rounded corners
Build and Push Docker Image / build (push) Successful in 51s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 10:35:51 +01:00
felixfoertsch 9c5a793a47 pipeline UI polish: jellyfin deep-links on titles, hover-to-show approve buttons, series approve-up-to
Build and Push Docker Image / build (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 01:14:19 +01:00
felixfoertsch 7cefd9bf04 wire scan completion to pipeline page
Build and Push Docker Image / build (push) Successful in 8m50s
After a scan completes, show a "Review in Pipeline →" link next to the
status label. Nav already included the Pipeline entry from a prior task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 01:53:29 +01:00
felixfoertsch 8bdfa79215 add pipeline Kanban board: route, layout, review/queue/processing/done columns, schedule controls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 01:51:47 +01:00
felixfoertsch 3f14b19195 remove green tint from action boxes, simplify execute empty state
Build and Push Docker Image / build (push) Successful in 1m14s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 21:51:30 +01:00
felixfoertsch 6363a133dd unify action box across all pages: consistent border/rounded style, green tint for "all good" states
Build and Push Docker Image / build (push) Successful in 35s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:16:57 +01:00
felixfoertsch d422b0a79b split subtitles tab into ST Extract (browse/extract items) and ST Manager (language summary, title harmonization)
Build and Push Docker Image / build (push) Successful in 2m1s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:01:50 +01:00
felixfoertsch 38b0faf55a add job_type column, simplify execute page: remove node/command columns, add type badge, make item title clickable
Build and Push Docker Image / build (push) Successful in 37s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:01:11 +01:00
felixfoertsch 76d3b1acfb remove path mappings, add subtitle summary endpoint, cache setup page, bump version
Build and Push Docker Image / build (push) Successful in 1m50s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:02:26 +01:00
felixfoertsch 99274d3ae8 add execute page filtering + colored FilterTabs component, fix ffmpeg audio-less files
- server-side filter + LIMIT 200 + totalCounts on GET /api/execute
- shared FilterTabs component with status-colored active tabs
- execute page: filter tabs, SSE live count updates, module-level cache
- replace inline tab pills in AudioListPage, SubtitleListPage with FilterTabs
- fix buildExtractOnlyCommand: skip -map 0:a when no audio streams exist
- bump version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:01:27 +01:00
felixfoertsch 511a3c1ace remove path mappings from settings UI, fix clear-scan blocking by deleting children first
Build and Push Docker Image / build (push) Successful in 1m6s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:52:26 +01:00
felixfoertsch 12e60c069e cache page data across tab switches, bump version
Build and Push Docker Image / build (push) Successful in 1m7s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:38:35 +01:00
felixfoertsch da668b2d36 add paths page to check volume accessibility after scan
Build and Push Docker Image / build (push) Successful in 38s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:31:05 +01:00
felixfoertsch 923f9afafc add collapsible series/season/episode view to subtitle list page
Build and Push Docker Image / build (push) Successful in 21s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:22:56 +01:00
felixfoertsch f562cb42d9 show file name in scan log, fix progress total by using Jellyfin page callback
Build and Push Docker Image / build (push) Successful in 33s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:19:45 +01:00
felixfoertsch b117147339 throttle scan SSE updates: buffer events, flush to React every 200ms
Build and Push Docker Image / build (push) Successful in 25s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:07:43 +01:00
felixfoertsch 28ca5679c6 fix scan SSE race: connect before starting scan so no events are missed
Build and Push Docker Image / build (push) Successful in 34s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:06:40 +01:00
felixfoertsch 2b1dda3c7d fix hooks ordering in SetupPage, move language state above conditional return
Build and Push Docker Image / build (push) Successful in 24s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:56:02 +01:00
felixfoertsch a4d5eb59e1 add configurable audio languages, sortable language lists in settings
Build and Push Docker Image / build (push) Successful in 1m9s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:51:03 +01:00
felixfoertsch 588a3d8f1f remove subtitle streams from container after extraction, remove job list limit, fix audio detail display
Build and Push Docker Image / build (push) Successful in 1m9s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:17:39 +01:00
felixfoertsch a393dd280e make app mobile-friendly: responsive nav, scrollable tables, adaptive grids
Build and Push Docker Image / build (push) Successful in 20s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:44:08 +01:00
felixfoertsch aaaa5ff402 rename /setup route to /settings, fix hooks violation crash on settings page
Build and Push Docker Image / build (push) Successful in 16s
useState was called after conditional return (React hooks rules violation),
causing the settings page to crash. moved all hooks to top level.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:31:34 +01:00
felixfoertsch 818b0d1396 add version badge in nav, apply path mappings at execution time, clear done/error jobs
Build and Push Docker Image / build (push) Successful in 58s
- show version (from package.json) in nav bar, warn on frontend/server mismatch
- apply path_mappings to file access check and command string at execution time
  so existing scans with old jellyfin paths work without re-scanning
- add clear done/errors button on execute page
- bump version to 2026.03.04

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:22:14 +01:00
felixfoertsch 37fae33bbc add clear queue button on execute page
Build and Push Docker Image / build (push) Successful in 20s
deletes all pending jobs, reverts their review plans back to pending so they
can be re-reviewed and re-approved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 17:11:18 +01:00
felixfoertsch ef785de955 add path mappings to translate jellyfin library paths to container mount paths
Build and Push Docker Image / build (push) Successful in 20s
jellyfin may use different internal paths (e.g. /tv/) than container mounts
(/series/). path_mappings config (or PATH_MAPPINGS env var) translates at scan
time. configurable via setup ui or env var format: /tv/=/series/,/data/=/movies/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:57:22 +01:00
felixfoertsch d5f4afd26b split audio/subtitle concerns, remove docker-in-docker, add per-node path mapping
Build and Push Docker Image / build (push) Successful in 1m54s
- install ffmpeg in dockerfile (fixes exit code 127)
- buildCommand() now audio-only remux, no subtitle extraction
- add unapprove endpoint + ui button for approved items
- add batch extract-all subtitles endpoint + ui button
- audio detail page shows only video+audio streams
- remove global movies_path/series_path config, add per-node path mapping
- remove docker-in-docker command building (buildDockerCommand, buildDockerExtractOnlyCommand)
- ssh execution translates /movies/ and /series/ to node-specific paths
- remove media paths section from setup page
- add unraid-template.xml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:48:00 +01:00
felixfoertsch 5ac44b7551 restructure to react spa + hono api, fix missing server/ and lib/
rewrite from monolithic hono jsx to react 19 spa with tanstack router
+ hono json api backend. add scan, review, execute, nodes, and setup
pages. multi-stage dockerfile (node for vite build, bun for runtime).

previously, server/ and src/shared/lib/ were silently excluded by
global gitignore patterns (/server/ from emacs, lib/ from python).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:57:40 +01:00