aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/transport.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:20 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:20 +0200
commit0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (patch)
treeba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-hub/src/meshbay_hub/static/transport.js
parent6af05abf410bbd038ce7fa6915a659defc509071 (diff)
downloadmeshbay-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.js91
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