aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-29 15:57:06 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-29 15:57:06 +0200
commit4d167e9f958d26c1db920274974ae446426928e3 (patch)
treee056e6b4fceca76ab67586548f0aa6ed80f925bc /packages/meshbay-hub/src/meshbay_hub/static
parentbc40ab2c4486cca4ae63e33976151b5d7f856a84 (diff)
downloadmeshbay-4d167e9f958d26c1db920274974ae446426928e3.tar.gz
feat: V13 — per-card "re-match this one file" (MNP 0.13)
A one-click alternative to the full "Fix match" search-and-pick flow, and reachable without SSH (`meshbay-node video rematch` clears a whole group). - MNP 0.13: tmdb_rematch / tmdb_rematch_ack (additive — an older node logs "unknown type", the button just does nothing). OP_TMDB_REMATCH, signed like tmdb_override (media_cache is shared node-wide). - media_cache.drop_tmdb_match(file_id): forgets the match AND the override marker — deliberately stronger than clear_file_tmdb, since the operator is explicitly asking for a fresh resolution. - webrtc_server: _do_tmdb_rematch / _admin_exec_tmdb_rematch, dispatch + admin-response routing, broadcasts tmdb_rematch_ack. - transport.js: rematchTmdbMatch(fileId, signFn); 'tmdb_rematch' in the admin-op allowlist; tmdb_rematch_ack handled like tmdb_override_ack. - video-app.js: a "Re-match" button beside "Fix match" in the detail modal (isNodeAdmin), then bumpMediaMetaGeneration(). video.rematch_one key in all ten locales. docs/mediacenter.md §10.1: V8–V13 marked done. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js23
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/video-app.js25
13 files changed, 55 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 55cf62e..c4ee388 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -186,6 +186,7 @@ export default {
'video.source_file': 'Datei: {name}',
'video.no_match': 'Keine sichere TMDB-Übereinstimmung — Dateiname wird angezeigt.',
'video.versions': '{n} Versionen',
+ 'video.rematch_one': 'Neu zuordnen',
// Musik
'music.mode_grid': 'Alben',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 4b9a4ee..16ae3ab 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -184,6 +184,7 @@ export default {
'video.source_file': 'File: {name}',
'video.no_match': 'No confident TMDB match — showing the filename.',
'video.versions': '{n} versions',
+ 'video.rematch_one': 'Re-match',
// Music
'music.mode_grid': 'Albums',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index a65a619..88b222d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -184,6 +184,7 @@ export default {
'video.source_file': 'Archivo: {name}',
'video.no_match': 'Sin coincidencia fiable en TMDB — se muestra el nombre del archivo.',
'video.versions': '{n} versiones',
+ 'video.rematch_one': 'Volver a asociar',
// Música
'music.mode_grid': 'Álbumes',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index f026b3e..b08cf28 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -185,6 +185,7 @@ export default {
'video.source_file': 'Fichier : {name}',
'video.no_match': 'Aucune correspondance TMDB fiable — nom de fichier affiché.',
'video.versions': '{n} versions',
+ 'video.rematch_one': 'Relancer la recherche',
// Musique
'music.mode_grid': 'Albums',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 71ee6ab..f1e8467 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -185,6 +185,7 @@ export default {
'video.source_file': 'File: {name}',
'video.no_match': 'Nessuna corrispondenza TMDB affidabile — mostrato il nome del file.',
'video.versions': '{n} versioni',
+ 'video.rematch_one': 'Riassocia',
// Musica
'music.mode_grid': 'Album',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index aa22837..95cc040 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -182,6 +182,7 @@ export default {
'video.source_file': 'ファイル: {name}',
'video.no_match': '確実なTMDB一致なし — ファイル名を表示しています。',
'video.versions': '{n} 個のバージョン',
+ 'video.rematch_one': '再マッチ',
// 音楽
'music.mode_grid': 'アルバム',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 9e5f8fb..076aa15 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -186,6 +186,7 @@ export default {
'video.source_file': 'Bestand: {name}',
'video.no_match': 'Geen betrouwbare TMDB-match — bestandsnaam wordt getoond.',
'video.versions': '{n} versies',
+ 'video.rematch_one': 'Opnieuw koppelen',
// Muziek
'music.mode_grid': 'Albums',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index d8c4846..00ef59e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -193,6 +193,7 @@ export default {
'video.source_file': 'Plik: {name}',
'video.no_match': 'Brak pewnego dopasowania TMDB — pokazano nazwę pliku.',
'video.versions': 'Wersje: {n}',
+ 'video.rematch_one': 'Dopasuj ponownie',
// Muzyka
'music.mode_grid': 'Albumy',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index 53390be..7d9173e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -186,6 +186,7 @@ export default {
'video.source_file': 'Arquivo: {name}',
'video.no_match': 'Sem correspondência confiável no TMDB — exibindo o nome do arquivo.',
'video.versions': '{n} versões',
+ 'video.rematch_one': 'Combinar de novo',
// Música
'music.mode_grid': 'Álbuns',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index d845f6d..74d729e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -179,6 +179,7 @@ export default {
'video.source_file': '文件:{name}',
'video.no_match': '没有可靠的 TMDB 匹配 — 显示文件名。',
'video.versions': '{n} 个版本',
+ 'video.rematch_one': '重新匹配',
// 音乐
'music.mode_grid': '专辑',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index a6934fc..8e714f3 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -2930,6 +2930,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
}
.video-card-unmatched { outline: 1px dashed var(--warn, #d98324); outline-offset: -1px; }
.video-version-list { margin-top: 12px; display: flex; flex-direction: column; gap: 4px; }
+.video-admin-actions { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
.video-card-flag {
display: inline-block;
margin-left: 4px;
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index d6a28d9..5a6e36b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -73,7 +73,7 @@ function _aborted() {
// list is what lets a response two steps later be tied back to the right
// one.
const ADMIN_OP_TYPES = new Set([
- 'tmdb_override', 'tmdb_config', 'tmdb_enabled', 'video_root', 'audio_root',
+ 'tmdb_override', 'tmdb_rematch', 'tmdb_config', 'tmdb_enabled', 'video_root', 'audio_root',
'photo_roots',
'musicbrainz_enabled', 'file_delete', 'dir_delete',
'member_upload', 'apps_enabled', 'set_scan_settings', 'member_revoke',
@@ -916,6 +916,22 @@ class MeshBayTransport {
}
/**
+ * Drop one file's cached TMDB match so it re-resolves with the node's
+ * current matcher (§10.1/V13) — the one-click alternative to the full
+ * search-and-pick flow. Signed for the same reason as overrideTmdbMatch.
+ */
+ async rematchTmdbMatch(fileId, signFn) {
+ const msg = await this._sendAndWait({
+ type: 'tmdb_rematch', v: '0.7', file_id: fileId,
+ });
+ if (msg.type === 'error') throw new Error(msg.detail);
+ if (msg.type === 'admin_challenge') {
+ return this._authorizeAdminOp(msg, 'tmdb_rematch', `file_id=${fileId}`, signFn);
+ }
+ return msg;
+ }
+
+ /**
* Set/clear a custom TMDB API token, and/or set the language TMDB is
* queried in (e.g. "fr-FR") — one for the whole node, since both are one
* operator's shared credential/cache, not a per-group concern (see
@@ -2032,6 +2048,11 @@ class MeshBayTransport {
fileId: msg.file_id || '', tmdbId: msg.tmdb_id || '', mediaType: msg.media_type || '',
});
}
+ // Same shape: the operator dropped one file's match to have it
+ // re-resolved (§10.1/V13). No tmdbId — the node re-derives it.
+ if (msg.type === 'tmdb_rematch_ack' && this._onTmdbOverride) {
+ this._onTmdbOverride({ fileId: msg.file_id || '', tmdbId: '', mediaType: '' });
+ }
// Same shape: the operator changed which folder is the Videos app's
// entry point for this group.
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
index 48709f9..6d13e27 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
@@ -482,8 +482,22 @@ function VideoDetailModal({
}) {
const confident = Boolean(meta && meta.confidence && meta.tmdb_id);
const [searching, setSearching] = useState(false);
+ const [rematching, setRematching] = useState(false);
const mediaType = show ? 'tv' : 'movie';
+ // §10.1/V13: drop this file's cached match on the node and let it
+ // re-resolve with the current matcher — the one-click alternative to the
+ // full search-and-pick flow above.
+ const doRematch = useCallback(async () => {
+ if (rematching) return;
+ setRematching(true);
+ try {
+ await transportRef.current.rematchTmdbMatch(repEntry.id, buildSignFn(transportRef));
+ bumpMediaMetaGeneration();
+ } catch { /* leave the current match in place */ }
+ setRematching(false);
+ }, [rematching, repEntry, transportRef]);
+
// Reset whenever a different file/show is opened in this same modal
// instance — repEntry/show change identity, selectedSeason must not
// silently keep pointing at whatever the previous show's season 4 was.
@@ -535,9 +549,14 @@ function VideoDetailModal({
`}
`}
${isNodeAdmin && html`
- <button class="admin-btn video-fix-match" onClick=${() => setSearching(true)}>
- ${t('video.fix_match')}
- </button>
+ <div class="video-admin-actions">
+ <button class="admin-btn video-fix-match" onClick=${() => setSearching(true)}>
+ ${t('video.fix_match')}
+ </button>
+ <button class="admin-btn" onClick=${doRematch} disabled=${rematching}>
+ ${rematching ? html`<span class="spinner"></span>` : t('video.rematch_one')}
+ </button>
+ </div>
`}
${showMultiSeason && html`
<${SeasonTabs} seasons=${show.seasons} selected=${selectedSeason}