From 0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 24 Aug 2026 14:33:20 +0200 Subject: feat(node,hub): season-specific overviews, manual TMDB match correction, and wizard polish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two operator-facing fixes for a real 3-season show whose automatic TMDB match was wrong at the show level: per-season overview/air_date tabs in the detail modal (falling back to the show-level text when a season's own is empty), and a "Fix match…" search-and-correct affordance that re-resolves every file sharing the corrected show's display_title. New signed op OP_TMDB_OVERRIDE and two read-only pairs (season_meta_req/resp, tmdb_search_req/resp), MNP_VERSION 0.5 -> 0.6. Also: the create-group wizard gets a spinning indexing indicator and an app-selection step, group settings default the TMDB language to the operator's own locale (never as a global default), and a file renamed mid-session now re-triggers title parsing instead of being silently skipped by the enrichment dedup guard. Fixes two bugs found during this work: the search overlay's z-index lost to the base video-overlay class and rendered invisibly, and season_meta's own empty overview didn't fall back to the show-level one. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY --- packages/meshbay-node/src/meshbay_node/daemon.py | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'packages/meshbay-node/src/meshbay_node/daemon.py') diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index 65c7da8..bf6bd64 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -938,6 +938,7 @@ class NodeDaemon: prev = self._last_broadcast_snapshot.get(group_id) delta = None + previous = None if prev is not None: prev_version, prev_entries = prev previous = GroupIndex._snapshot( @@ -953,6 +954,18 @@ class NodeDaemon: new_entries = delta.additions if delta is not None else list(idx.entries) asyncio.ensure_future(self._enrich_new_video_entries(indexer, new_entries)) + # A rename/move changes the very filename (or season folder) that + # §3.3/§3.4's title-parse read display_title/season/episode from, + # but leaves the file's content — and so its id and everything + # ffprobe/thumbnailing already found — untouched. Only entries + # whose name or path actually differ from the last broadcast get a + # fresh pass; an update that is enrichment's own field-fill + # (duration/thumb_hash/... landing via _on_enriched below) leaves + # name/path alone and must not re-trigger itself forever. + if delta is not None and delta.updates and previous is not None: + asyncio.ensure_future( + self._reenrich_renamed_video_entries(indexer, delta.updates, previous)) + # Videos app: a file that leaves the index also loses its thumbnail # and file->tmdb mapping — the "real deletion obligation" docs/ # mediacenter.md §2/§8 calls out explicitly rather than leaving @@ -1060,6 +1073,32 @@ class NodeDaemon: return await self._enrich_new_video_entries(indexer, list(indexer.index.entries)) + async def _reenrich_renamed_video_entries( + self, indexer: DirectoryIndexer, updates: list, previous: GroupIndex, + ) -> None: + """ + Videos app: found live — a French-named episode file, renamed by + the operator to match its English-named siblings, kept showing as + its own separate poster-grid card (and its own row in Flat list) + indefinitely, because `_enriched_attempted` — there specifically to + stop enrichment's own field-fill from re-triggering itself forever + (see the caller) — also silently blocked the *new* filename from + ever being title-parsed at all. `entry.id in self._enriched_attempted` + is the same content, so simply discarding it here and re-running + the ordinary enrichment path is enough: a fresh ffprobe/thumbnail + for an unchanged file is redundant work, not a correctness issue, + and renames are rare enough that the redundancy is not worth a + separate "title-parse only" code path. + """ + for entry in updates: + if entry.type != "video": + continue + old = previous.get_entry(entry.id) + if old is None or (old.name == entry.name and old.path == entry.path): + continue + self._enriched_attempted.discard(entry.id) + await self._enrich_new_video_entries(indexer, updates) + async def _on_enriched(self, indexer: DirectoryIndexer, file_id: str, fields: dict) -> None: """ Merge enrichment fields into the live index and re-trigger a -- cgit v1.2.3