From daecf9dfae515488e37ad192e1d52e7846e71cda Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sun, 14 Jun 2026 13:50:49 -0700 Subject: [PATCH] video manage-workers modal: TVDB defaults to Shows, not an empty Movies view TVDB enriches shows only, but selectWorker hardcoded state.kind='movie', so picking TVDB queried tvdb+movie (always empty) and rendered a bogus Movies view. Each worker now declares its kinds (tmdb: movie+show, tvdb: show) and the panel defaults to the worker's first kind. Backend was already safe (returns empty for unsupported service+kind); pinned that invariant with a seam test. --- tests/test_video_database.py | 11 +++++++++++ tests/test_video_side_shell.py | 7 +++++++ webui/static/video/video-enrichment-manager.js | 18 +++++++++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/tests/test_video_database.py b/tests/test_video_database.py index 5fd4f5b9..47c6b4e2 100644 --- a/tests/test_video_database.py +++ b/tests/test_video_database.py @@ -394,6 +394,17 @@ def test_enrichment_breakdown_unmatched_retry(db): assert db.enrichment_breakdown("tmdb")["movie"]["pending"] == 1 +def test_tvdb_is_shows_only(db): + # TVDB enriches shows, never movies. The breakdown must not advertise a + # movie bucket, and asking for tvdb movies returns empty (never garbage) — + # this is what keeps the Manage-Workers modal from showing a Movies view + # for TVDB. + assert "movie" not in db.enrichment_breakdown("tvdb") + assert "show" in db.enrichment_breakdown("tvdb") + db.upsert_movie("plex", {"server_id": "m1", "title": "A"}) + assert db.enrichment_unmatched("tvdb", "movie") == {"items": [], "total": 0} + + # ── isolation: the video DB imports nothing from music ─────────────────────── def test_video_db_module_imports_nothing_from_music(): diff --git a/tests/test_video_side_shell.py b/tests/test_video_side_shell.py index 483a3bef..f3e116dd 100644 --- a/tests/test_video_side_shell.py +++ b/tests/test_video_side_shell.py @@ -280,6 +280,13 @@ def test_video_enrichment_manager_isolated(): assert "enrichment-manager-modal" in src and "em-rail" in src # Its own overlay id (not music's) so the two never collide. assert "vem-overlay" in src and "enrichment-manager-overlay" not in src + # TVDB is shows-only: each worker declares its kinds and the panel defaults + # to the worker's first kind — never a hardcoded 'movie' (which would show a + # bogus empty Movies view for TVDB). + assert "kinds: ['show']" in src # tvdb + assert "kinds: ['movie', 'show']" in src # tmdb + assert "state.kind = defaultKind(id)" in src + assert "state.kind = 'movie'" not in src def test_video_settings_module_referenced_and_isolated(): diff --git a/webui/static/video/video-enrichment-manager.js b/webui/static/video/video-enrichment-manager.js index 963d2871..b2dad8fd 100644 --- a/webui/static/video/video-enrichment-manager.js +++ b/webui/static/video/video-enrichment-manager.js @@ -11,10 +11,22 @@ (function () { 'use strict'; + // `kinds` = the entity kinds each service actually enriches (must match the + // backend's _ENRICH map). TVDB is shows-only, so it must NOT default to the + // Movies view — it would query tvdb+movie (always empty) and look broken. var WORKERS = [ - { id: 'tmdb', name: 'TMDB', color: '#38bdf8', rgb: '56, 189, 248' }, - { id: 'tvdb', name: 'TVDB', color: '#a855f7', rgb: '168, 85, 247' }, + { id: 'tmdb', name: 'TMDB', color: '#38bdf8', rgb: '56, 189, 248', kinds: ['movie', 'show'] }, + { id: 'tvdb', name: 'TVDB', color: '#a855f7', rgb: '168, 85, 247', kinds: ['show'] }, ]; + + function workerDef(id) { + for (var i = 0; i < WORKERS.length; i++) { if (WORKERS[i].id === id) return WORKERS[i]; } + return null; + } + function defaultKind(id) { + var w = workerDef(id); + return (w && w.kinds && w.kinds[0]) || 'movie'; + } var LOGOS = { tmdb: 'https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_2-d537fb228cf3ded904ef09b136fe3fec72548ebc1fea3fbbd1ad9e36364db38b.svg', tvdb: 'https://www.svgrepo.com/show/443500/brand-tvdb.svg', @@ -224,7 +236,7 @@ // ── selection / actions ──────────────────────────────────────────────────── function selectWorker(id) { state.selected = id; state.breakdown = null; state.unmatched = null; - state.kind = 'movie'; state.page = 0; + state.kind = defaultKind(id); state.page = 0; renderRail(); renderPanel(); Promise.all([loadBreakdown(id), loadUnmatched()]).then(function () { renderPanel(); }); }