summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-30 17:05:15 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-30 17:05:15 +0200
commitd5fc45ff907e618d884a8d2d91c1f2b45e6898d1 (patch)
treeb368ee90e99254e6d66f0a22324d9d25c5dd0a4a
parentb206dc86221d453c699074749f59f1d7cb3d47dc (diff)
downloadmeshbay-d5fc45ff907e618d884a8d2d91c1f2b45e6898d1.tar.gz
chore: scrub copyrighted names from tests, comments and docs
Real franchise / show / release-group names had crept back into test fixtures, code comments, a docstring and docs/mediacenter.md while fixing the saga-match and misclassification bugs. Replace them all with invented placeholders ("Some Saga", "A Different Show") and shape descriptions ("a franchise-origin film", "a 3-season show"). Behaviour and assertions unchanged; 738 node tests still pass. Record the rule in CLAUDE.md so it stops recurring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018BMLQjqFGCize2KtNBT79v
-rw-r--r--CLAUDE.md10
-rw-r--r--docs/mediacenter.md27
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/video-app.js4
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/title_parse.py11
-rw-r--r--packages/meshbay-node/tests/test_media_meta_request.py4
-rw-r--r--packages/meshbay-node/tests/test_season_and_search_requests.py10
-rw-r--r--packages/meshbay-node/tests/test_title_parse.py12
-rw-r--r--packages/meshbay-node/tests/test_tmdb_language_fallback.py10
-rw-r--r--packages/meshbay-node/tests/test_tmdb_override_policy.py12
9 files changed, 55 insertions, 45 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 91e791b..3d345c3 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -780,3 +780,13 @@ SFR residential Fedora 44 → meshbay.org OVH VPS:
## new rules, from now
Documents and demo/comments are written in english unless requested in french.
+No copyrighted names. Never put real brand names, trademarks, artist names, or
+copyrighted titles (film titles, show titles, song/artist names, character
+names, release-group handles) into code, comments, docstrings, test fixtures,
+documentation, or commit messages — even when the bug being fixed or documented
+was genuinely found against real content with real names. Describe the *shape*
+instead ("a franchise-origin film", "a two-part saga", "a 3-season show") and
+use invented placeholders in fixtures ("Some Saga", "A Different Show"). This
+holds for every file, including throwaway test data and one-line commit
+subjects.
+
diff --git a/docs/mediacenter.md b/docs/mediacenter.md
index 3a6b733..7a712d9 100644
--- a/docs/mediacenter.md
+++ b/docs/mediacenter.md
@@ -317,7 +317,7 @@ real, currently-readable directory, or the request is refused immediately
signing prompt.
The client-side filter is a plain path-prefix test against `IndexEntry.path`
-(already root-relative, e.g. `"Séries/OVNI/S1/ep01.mkv"`) — no new index
+(already root-relative, e.g. `"Series/SomeShow/S1/ep01.mkv"`) — no new index
field, no server-side filtering; the node still sends the whole index, and
narrowing to the configured folder is exactly the same kind of view-over-
the-index work Files' own current-folder filter already does.
@@ -510,7 +510,7 @@ used *at all* used to live here too — moved per-group, below.)
**Added 2026-08-24**: a third field, `tmdb_language` (e.g. `"fr-FR"`),
travels the same way — one node-wide setting, not a per-viewer request.
Live against a real French-language show, the node's TMDB matches came back
-in English by default (`"UFOs"` for a show whose real title and audience
+in English by default (an English title for a show whose real title and audience
are French) purely because no language was ever requested; TMDB honors a
`language` parameter on every search/details call, so `tmdb.py`'s `_get()`
now attaches whichever language is configured (or none, TMDB's own English
@@ -740,8 +740,8 @@ centralization was for (one computation, reused by every member).
| V3 | TMDB metadata cache TTL (30 days proposed) | Needs no more than a config constant; not worth deciding without seeing real staleness complaints |
| V4 | Multi-audio-track / subtitle-track surfacing in the detail view | Out of scope for this pass — `video-player.js`'s existing track handling is unchanged; Videos only adds discovery and metadata |
| V5 | `Music`/`Photos` apps | Explicitly out of scope, per `apps.md` — this document only builds `video-app.js` and the shared node-side machinery (TMDB client, thumbnail cache, title parser) that a future audio/photo app could also reuse |
-| ~~V6~~ | ~~Two folders of the same show, named by different release groups, can produce two separate poster-grid cards~~ | **Closed, 2026-08-24.** Rather than fuzzy title matching (real design decision, still deferred), `PosterGrid` now merges raw show-groups client-side once each group's TMDB lookup resolves to the same confident `tmdb_id` (`onMetaResolved` reports each `PosterCard`'s resolved meta upward; a `useMemo` groups by id, combining episodes/seasons into one card). This only merges what TMDB already agrees is one show — a genuinely unmatched show still gets its own card, which is correct. Confirmed live: `OVNIs.S01...`/`Ovnis-S01...` now render as a single "OVNI(s)" card with both seasons |
-| ~~V7~~ | ~~A show's automatic TMDB match can be wrong at the show level (not just mis-ranked locally), and a wrong match's `overview` can read as scoped to one season~~ | **Closed, 2026-08-24 — §5.7.** Per-season tabs (own `overview`/`air_date`, falling back to the show-level text when empty) plus an operator-only "Fix match…" search-and-correct affordance, applied to every file sharing the resolved `display_title`. Confirmed live on the operator's real "War of the Worlds" 3-season show, itself matched to a wrong season-3-specific 1988 promotional TMDB entry: season tabs correctly filtered episodes and swapped in each season's own air_date, and the search overlay returned real TMDB candidates for a manual correction |
+| ~~V6~~ | ~~Two folders of the same show, named by different release groups, can produce two separate poster-grid cards~~ | **Closed, 2026-08-24.** Rather than fuzzy title matching (real design decision, still deferred), `PosterGrid` now merges raw show-groups client-side once each group's TMDB lookup resolves to the same confident `tmdb_id` (`onMetaResolved` reports each `PosterCard`'s resolved meta upward; a `useMemo` groups by id, combining episodes/seasons into one card). This only merges what TMDB already agrees is one show — a genuinely unmatched show still gets its own card, which is correct. Confirmed live: two release-group spellings of one show's season folders now render as a single card with both seasons |
+| ~~V7~~ | ~~A show's automatic TMDB match can be wrong at the show level (not just mis-ranked locally), and a wrong match's `overview` can read as scoped to one season~~ | **Closed, 2026-08-24 — §5.7.** Per-season tabs (own `overview`/`air_date`, falling back to the show-level text when empty) plus an operator-only "Fix match…" search-and-correct affordance, applied to every file sharing the resolved `display_title`. Confirmed live on a real 3-season show, itself matched to a wrong season-specific promotional TMDB entry: season tabs correctly filtered episodes and swapped in each season's own air_date, and the search overlay returned real TMDB candidates for a manual correction |
### 10.1 Movie-matching bugs — a batch found live (2026-08-29)
@@ -788,16 +788,15 @@ file. A "this is a movie / a show" toggle would close that.
### 10.3 A whole saga matched to its first film (2026-08-30)
-Every `Star Wars Episode <N> - <subtitle>` file resolved to the 1977 original (TMDB id 11).
-Cause: `sequel_variants` stripped `Episode <N>` and offered the bare `Star Wars` as a
-candidate query — which matches the 1977 film's `original_title` at ratio 1.0, beating
-PASS 1's correct (but lower-ratio) hit. A franchise's bare name is very often a real,
-different film. **Fixed** (`fix/starwars-saga-match`): when a `Part`/`Episode`/… keyword is
-what carried the index, `sequel_variants` no longer emits the bare base — only
-`<base> <digit>` and `<base> <roman>`. Without a keyword (`Jurassic Park 3`) the bare base
-is still offered. Verified live: Star Wars I–VI now each resolve to their own episode;
-Jurassic Park / Kill Bill / Bond regressions all hold. V12's movie merge, reverted the
-same day, would have compounded this into one card for the saga.
+Every `<Saga> Episode <N> - <subtitle>` file in a numbered franchise resolved to the
+series' first entry. Cause: `sequel_variants` stripped `Episode <N>` and offered the bare
+`<Saga>` as a candidate query — which matches the first film's `original_title` at ratio
+1.0, beating PASS 1's correct (but lower-ratio) hit. A franchise's bare name is very often
+a real, different film. **Fixed** (`fix/saga-match`): when a `Part`/`Episode`/…
+keyword is what carried the index, `sequel_variants` no longer emits the bare base — only
+`<base> <digit>` and `<base> <roman>`. Without a keyword (`<Franchise> 3`) the bare base is
+still offered. Verified live against a numbered franchise's episodes plus the earlier
+numbered-sequel / two-part-film / franchise-subtitle regressions.
## 11. Acceptance before shipping
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 5222afb..c0102e5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
@@ -627,7 +627,7 @@ function PosterGrid({ movies, shows, transportRef, gekRef, onPreview, tmdbEnable
// Two raw groups (grouped by parsed display_title, §4.1) resolving to the
// same confident TMDB id are almost certainly one show whose seasons
// were released under differently-named folders — confirmed live: one
- // operator's show had its two seasons parsed as "Ovni" and "OVNIs" by
+ // operator's show had its two seasons parsed as two spellings by
// two different release groups, showing as two identical-looking cards
// once both matched the same real show (§3.4/V6). Merged here once both
// are actually known — never required for the fallback to work: a group
@@ -735,7 +735,7 @@ function PosterGrid({ movies, shows, transportRef, gekRef, onPreview, tmdbEnable
// Within a season group, every episode's own display_title is usually
// just the show name again (guessit rarely finds a per-episode subtitle
-// for this kind of release) — repeating "OVNI" twelve times in a row said
+// for this kind of release) — repeating the show name twelve times in a row said
// nothing an episode number wouldn't say better. Shown only when this row
// is actually inside a season group (`seasonContext` set); a real,
// distinct per-episode title (a show that *does* carry one) still wins
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
index 032dc58..beb0d3f 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/title_parse.py
@@ -154,11 +154,12 @@ def sequel_variants(title: str) -> list[str]:
index re-rendered as digit and as Roman numeral, plus (only when there
is no "Part"/"Episode"/… keyword) the bare base.
- The bare base is withheld for a keyword'd index — "Star Wars Episode
- III" → "Star Wars" — because a franchise's bare name is very often a
- real, *different* film (the 1977 original), and that variant matched
- every episode to it (§10.1/V14). Without the keyword ("Jurassic Park 3")
- the number is decoration and "Jurassic Park" is the right base to try.
+ The bare base is withheld for a keyword'd index — "<Saga> Chapter III"
+ → "<Saga>" — because a franchise's bare name is very often a real,
+ *different* film (the series' first entry), and that variant matched
+ every later entry to it (§10.1/V14). Without the keyword ("<Franchise>
+ 3") the number is decoration and the bare base is the right thing to
+ try.
"""
m = _TRAILING_INDEX_RE.match(title.strip())
if not m:
diff --git a/packages/meshbay-node/tests/test_media_meta_request.py b/packages/meshbay-node/tests/test_media_meta_request.py
index 0a3df12..9a1c5aa 100644
--- a/packages/meshbay-node/tests/test_media_meta_request.py
+++ b/packages/meshbay-node/tests/test_media_meta_request.py
@@ -93,7 +93,7 @@ async def test_two_episodes_in_the_same_season_folder_each_get_their_own_metadat
season folder, and each must resolve against its own entry — not
whichever one the index happens to return first for that folder."""
index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate())
- ep1 = _entry("shows/Show/Season 1", "s01e01.mkv", "id-1", "War of the Worlds")
+ ep1 = _entry("shows/Show/Season 1", "s01e01.mkv", "id-1", "Some Show")
ep2 = _entry("shows/Show/Season 1", "s01e02.mkv", "id-2", "A Different Show")
index.add_entry(ep1)
index.add_entry(ep2)
@@ -105,7 +105,7 @@ async def test_two_episodes_in_the_same_season_folder_each_get_their_own_metadat
resp_1, resp_2 = session.sent
assert resp_1["file_id"] == "id-1"
- assert resp_1["title"] == "War of the Worlds"
+ assert resp_1["title"] == "Some Show"
assert resp_2["file_id"] == "id-2"
assert resp_2["title"] == "A Different Show"
assert resp_1["tmdb_id"] != resp_2["tmdb_id"], (
diff --git a/packages/meshbay-node/tests/test_season_and_search_requests.py b/packages/meshbay-node/tests/test_season_and_search_requests.py
index 856797e..44d7da6 100644
--- a/packages/meshbay-node/tests/test_season_and_search_requests.py
+++ b/packages/meshbay-node/tests/test_season_and_search_requests.py
@@ -161,9 +161,9 @@ async def test_search_movie_calls_movie_search_and_echoes_media_type(media_cache
client = FakeTmdbClient()
session = _session(media_cache=media_cache, tmdb_client=client)
- await session._do_tmdb_search_request({"query": "War of the Worlds", "media_type": "movie"})
+ await session._do_tmdb_search_request({"query": "Some Show", "media_type": "movie"})
- assert client.movie_search_calls == ["War of the Worlds"]
+ assert client.movie_search_calls == ["Some Show"]
assert client.tv_search_calls == []
resp = session.sent[0]
assert resp["type"] == MNP.TMDB_SEARCH_RESP
@@ -172,7 +172,7 @@ async def test_search_movie_calls_movie_search_and_echoes_media_type(media_cache
"search for the same query are indistinguishable to the client's "
"keyed response matching (transport.js tmdb_search_resp handler)")
assert resp["results"] == [{
- "tmdb_id": "111", "title": "War of the Worlds", "year": "2019",
+ "tmdb_id": "111", "title": "Some Show", "year": "2019",
"poster_thumb_hash": resp["results"][0]["poster_thumb_hash"],
}]
@@ -181,8 +181,8 @@ async def test_search_tv_calls_tv_search(media_cache):
client = FakeTmdbClient()
session = _session(media_cache=media_cache, tmdb_client=client)
- await session._do_tmdb_search_request({"query": "War of the Worlds", "media_type": "tv"})
+ await session._do_tmdb_search_request({"query": "Some Show", "media_type": "tv"})
- assert client.tv_search_calls == ["War of the Worlds"]
+ assert client.tv_search_calls == ["Some Show"]
assert client.movie_search_calls == []
assert session.sent[0]["media_type"] == "tv"
diff --git a/packages/meshbay-node/tests/test_title_parse.py b/packages/meshbay-node/tests/test_title_parse.py
index 56f4e20..bbe9afa 100644
--- a/packages/meshbay-node/tests/test_title_parse.py
+++ b/packages/meshbay-node/tests/test_title_parse.py
@@ -95,12 +95,12 @@ def test_sequel_variants_reads_a_spelled_out_index():
assert "Story" not in v # keyword present -> no bare base
-def test_sequel_variants_star_wars_shape_does_not_offer_the_bare_franchise():
- # Every "<Franchise> Episode <N>" was matching the 1977 original
- # because the "Franchise" variant hit it at ratio 1.0 (§10.1/V14).
- v = sequel_variants("Star Wars Episode III")
- assert "Star Wars" not in v
- assert "Star Wars 3" in v
+def test_sequel_variants_saga_shape_does_not_offer_the_bare_franchise():
+ # Every "<Saga> Chapter <N>" was matching the franchise's first entry
+ # because the bare "<Saga>" variant hit it at ratio 1.0 (§10.1/V14).
+ v = sequel_variants("Some Saga Chapter III")
+ assert "Some Saga" not in v
+ assert "Some Saga 3" in v
def test_sequel_variants_ignores_a_trailing_word_that_is_not_an_index():
diff --git a/packages/meshbay-node/tests/test_tmdb_language_fallback.py b/packages/meshbay-node/tests/test_tmdb_language_fallback.py
index 1a85531..52fe4c4 100644
--- a/packages/meshbay-node/tests/test_tmdb_language_fallback.py
+++ b/packages/meshbay-node/tests/test_tmdb_language_fallback.py
@@ -39,14 +39,14 @@ class FakeTmdbClient:
async def test_empty_overview_falls_back_to_english_but_keeps_localized_poster():
localized = {
- "title": "OVNI(s)", "original_title": "OVNI(s)",
+ "title": "Une Série", "original_title": "Une Série",
"overview": "", # no French translation on TMDB for this field
"poster_path": "/fr-poster.jpg", "backdrop_path": "/fr-backdrop.jpg",
"genres": [{"name": "Comédie"}], "vote_average": 7.2,
"first_air_date": "2016-01-01",
}
english = {
- "title": "UFOs", "original_title": "OVNI(s)",
+ "title": "A Series", "original_title": "Une Série",
"overview": "A real English overview.",
"poster_path": "/en-poster.jpg", "backdrop_path": "/en-backdrop.jpg",
"genres": [{"name": "Comedy"}], "vote_average": 7.2,
@@ -67,7 +67,7 @@ async def test_empty_overview_falls_back_to_english_but_keeps_localized_poster()
async def test_fully_populated_localized_response_never_triggers_a_fallback_call():
localized = {
- "title": "OVNI(s)", "overview": "Un résumé complet en français.",
+ "title": "Une Série", "overview": "Un résumé complet en français.",
"poster_path": "/fr-poster.jpg", "genres": [{"name": "Comédie"}],
"vote_average": 7.2,
}
@@ -83,7 +83,7 @@ async def test_fully_populated_localized_response_never_triggers_a_fallback_call
async def test_completely_untranslated_response_falls_back_entirely():
localized = {"overview": "", "poster_path": None, "genres": []}
english = {
- "title": "UFOs", "original_title": "OVNI(s)",
+ "title": "A Series", "original_title": "Une Série",
"overview": "A real English overview.", "poster_path": "/en-poster.jpg",
"genres": [{"name": "Comedy"}], "vote_average": 7.2,
"release_date": "2016-01-01",
@@ -95,4 +95,4 @@ async def test_completely_untranslated_response_falls_back_entirely():
assert meta["overview"] == "A real English overview."
assert meta["poster_path"] == "/en-poster.jpg"
assert meta["genres"] == ["Comedy"]
- assert meta["title"] == "UFOs"
+ assert meta["title"] == "A Series"
diff --git a/packages/meshbay-node/tests/test_tmdb_override_policy.py b/packages/meshbay-node/tests/test_tmdb_override_policy.py
index 0717a0c..d434e1a 100644
--- a/packages/meshbay-node/tests/test_tmdb_override_policy.py
+++ b/packages/meshbay-node/tests/test_tmdb_override_policy.py
@@ -116,7 +116,7 @@ async def test_a_request_with_nobody_to_authorize_it_is_refused(tmp_path):
async def test_a_valid_request_is_signed(tmp_path):
session = _session(tmp_path, "op", operator="op")
- entry = _entry("shared", "ep.mkv", "War of the Worlds")
+ entry = _entry("shared", "ep.mkv", "Some Show")
session._ctx["index"].add_entry(entry)
session._has_admin_authority = lambda: True
issued = []
@@ -138,8 +138,8 @@ async def test_two_files_in_the_same_folder_are_told_apart(tmp_path):
own, distinct entries.
"""
session = _session(tmp_path, "op", operator="op")
- e1 = _entry("shared/Season 1", "s01e01.mkv", "War of the Worlds")
- e2 = _entry("shared/Season 1", "s01e02.mkv", "War of the Worlds")
+ e1 = _entry("shared/Season 1", "s01e01.mkv", "Some Show")
+ e2 = _entry("shared/Season 1", "s01e02.mkv", "Some Show")
session._ctx["index"].add_entry(e1)
session._ctx["index"].add_entry(e2)
session._has_admin_authority = lambda: True
@@ -157,9 +157,9 @@ async def test_two_files_in_the_same_folder_are_told_apart(tmp_path):
async def test_override_updates_every_entry_sharing_the_display_title(tmp_path):
session = _session(tmp_path, "op", operator="op")
index = session._ctx["index"]
- s1 = _entry("shared/S1", "s01e01.mkv", "War of the Worlds")
- s2 = _entry("shared/S2", "s02e01.mkv", "War of the Worlds")
- s3 = _entry("shared/S3", "s03e02.mkv", "War of the Worlds")
+ s1 = _entry("shared/S1", "s01e01.mkv", "Some Show")
+ s2 = _entry("shared/S2", "s02e01.mkv", "Some Show")
+ s3 = _entry("shared/S3", "s03e02.mkv", "Some Show")
other_show = _entry("shared/Other", "ep.mkv", "A Different Show")
for e in (s1, s2, s3, other_show):
index.add_entry(e)