diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:20 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:20 +0200 |
| commit | 0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (patch) | |
| tree | ba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-hub/src/meshbay_hub/static/transport.js | |
| parent | 6af05abf410bbd038ce7fa6915a659defc509071 (diff) | |
| download | meshbay-0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a.tar.gz | |
feat(node,hub): season-specific overviews, manual TMDB match correction, and wizard polish
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 91 |
1 files changed, 90 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index dd39df8..b4bfe87 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -481,6 +481,61 @@ class MeshBayTransport { } /** + * One season's own overview/air_date/poster (docs/mediacenter.md §5.4's + * per-season view) — a show's own tmdb_meta is one static field that does + * not necessarily describe every season alike, found live: a 3-season + * show whose overview read as season-3-specific for every season. + * Keyed like media_meta_req: a season-tab bar can fire a request per tab + * before the previous one lands, and matching by arrival order would hand + * one season's data to a different season's tab whenever two responses + * reordered. + */ + async fetchSeasonMeta(tmdbId, season) { + const msg = await this._sendAndWait({ + type: 'season_meta_req', v: '0.6', tmdb_id: tmdbId, season, + }); + if (msg.type === 'error') throw new Error(msg.detail); + return msg; + } + + /** + * Raw TMDB search candidates for an operator correcting a wrong automatic + * match — unlike fetchMediaMeta, this never collapses to one best guess: + * a human picks from several, so several is the point. Read-only, not an + * admin op: it looks nothing up in this node's own state and changes + * nothing, so it needs no signature (mirrors why media_meta_req isn't + * signed either). + */ + async searchTmdb(mediaType, query) { + const msg = await this._sendAndWait({ + type: 'tmdb_search_req', v: '0.6', media_type: mediaType, query, + }); + if (msg.type === 'error') throw new Error(msg.detail); + return msg; + } + + /** + * Correct a wrong automatic TMDB match. Signed like setVideoRoot/ + * setTmdbConfig: it replaces what every member sees for a show/movie, + * node-wide (media_cache is shared, not per-viewer) — an unsigned + * override would let any member vandalize another show's metadata. + * Applies to every file sharing the representative one's display_title, + * not just the file the operator happened to be looking at (webrtc_ + * server.py's _admin_exec_tmdb_override). + */ + async overrideTmdbMatch(path, tmdbId, mediaType, signFn) { + const msg = await this._sendAndWait({ + type: 'tmdb_override', v: '0.6', path, tmdb_id: tmdbId, media_type: mediaType, + }); + if (msg.type === 'error') throw new Error(msg.detail); + if (msg.type === 'admin_challenge') { + const subject = `path=${path},tmdb_id=${tmdbId},media_type=${mediaType}`; + return this._authorizeAdminOp(msg, 'tmdb_override', subject, signFn); + } + return msg; + } + + /** * Turn TMDB lookups on/off node-wide, optionally set/clear a custom API * token, and optionally set the language TMDB is queried in (e.g. * "fr-FR") — one for the whole node, same reasoning as the token: one @@ -1243,7 +1298,12 @@ class MeshBayTransport { _key: obj.type === 'file_req' ? `chunk:${obj.file_id}:${obj.chunk_index}` : obj.type === 'ping' ? `ping:${obj.token}` - : obj.type === 'media_meta_req' ? `media_meta:${obj.path}` : null, + : obj.type === 'media_meta_req' ? `media_meta:${obj.path}` + // Same reordering hazard as media_meta_req: a season-tab bar or a + // search box can have more than one of these in flight at once. + : obj.type === 'season_meta_req' ? `season_meta:${obj.tmdb_id}:${obj.season}` + : obj.type === 'tmdb_search_req' ? `tmdb_search:${obj.media_type}:${obj.query}` + : null, resolve: (msg) => { clearTimeout(timeout); this._pending.delete(id); resolve(msg); }, reject: (err) => { clearTimeout(timeout); this._pending.delete(id); reject(err); }, }); @@ -1355,6 +1415,17 @@ class MeshBayTransport { }); } + // Same shape: an operator corrected a wrong automatic TMDB match, and + // everyone connected needs to know their poster grid/detail modal for + // this show is now stale — falls through so the operator's own + // admin_response promise resolves on this same message, exactly like + // member_upload_ack/apps_enabled_ack above. + if (msg.type === 'tmdb_override_ack' && this._onTmdbOverride) { + this._onTmdbOverride({ + path: msg.path || '', tmdbId: msg.tmdb_id || '', mediaType: msg.media_type || '', + }); + } + // Same shape: the operator changed which folder is the Videos app's // entry point for this group. if (msg.type === 'video_root_ack' && this._onVideoRoot) { @@ -1461,6 +1532,24 @@ class MeshBayTransport { return; } + // Same reasoning as media_meta_resp: keyed, not arrival-order, and + // "nobody's waiting any more" must not fall through either. + if (msg.type === 'season_meta_resp') { + const key = `season_meta:${msg.tmdb_id}:${msg.season}`; + for (const [, handler] of this._pending) { + if (handler._key === key) { handler.resolve(msg); return; } + } + return; + } + + if (msg.type === 'tmdb_search_resp') { + const key = `tmdb_search:${msg.media_type}:${msg.query}`; + for (const [, handler] of this._pending) { + if (handler._key === key) { handler.resolve(msg); return; } + } + return; + } + // chat_hist_resp answers a `chat_hist` request, but under a different // type string — unlike index_sync, which is asked for and answered under // the same name, so the generic fallback below happens to work for it by |