mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
* fix(tmdb): series cast was the latest season only, not the show
TMDB documents a TV id's `credits` as the credits of the LATEST SEASON.
We requested exactly that and rendered it as "the cast", so every
long-running show lost every regular who had left: The Boys showed
whoever appears in the newest season, not the ensemble.
The TV details request now also appends `aggregate_credits`, which spans
the whole run — but per TMDB omits the newest season, so neither payload
alone is the cast. `unifiedTvCast` unions them: whole-run billing order
first, then people who appear only in the newest season, deduplicated by
person id. Characters come from the aggregate `roles[]` shape.
Deliberately NO cache-key bump. Rows cached before this simply lack
`aggregate_credits` and keep the previous behaviour until they expire,
which avoids invalidating every user's details cache twice — the roadmap
schedules one consolidated bump once the remaining append_to_response
additions (images, certifications, alternative_titles) land together.
Movies are untouched: /movie/{id} has no aggregate_credits and its
`credits` is already the full cast.
Tests: departed regulars retained, newest-season arrivals appended after
show billing order, characters read from roles[], no duplicates across
the two payloads, graceful fallback for pre-aggregate cache rows.
Refs docs/architecture/tmdb-roadmap.md A2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tmdb): reserve cast slots so newest-season arrivals survive the cap
The union was appended aggregate-first and then truncated to ten, so on
exactly the shows it was built for — long-running ones, where the
whole-run cast alone exceeds the limit — every newest-season arrival was
sliced back off. The original fixture had two aggregate members and
could not catch it.
unifiedTvCast now holds back up to three slots for the top-billed
arrivals instead of appending them where the cap discards them, and
gives the slots back when nobody is new.
Tests: a 12-member aggregate plus two arrivals keeps both arrivals and
top billing; an aggregate with no arrivals still gets all ten slots.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(tmdb): split the series-cast suite out of the merge spec
The merge conflict resolution put both new describes back into
tmdb-merge.spec.ts, pushing it to 499 lines — past the 400-line
max-lines cap. The aggregate-credits suite moves to its own file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tmdb): stop the cast union from shrinking, and bound what it caches
Three follow-ups from a review pass over the aggregate-credits union:
- The reserved arrival slots were subtracted from the aggregate even when
the aggregate was shorter than the cap, so a show with four regulars and
five newcomers returned seven names instead of nine. The reservation is
a floor for arrivals now, not a quota.
- An aggregate member's character came from the first role with any text,
so a one-episode cameo could outrank the part the actor is known for.
Pick the role with the most episodes.
- aggregate_credits carries a show's whole-run cast AND crew, and details
payloads are cached verbatim — orders of magnitude of JSON for a list
the merge truncates to ten people. Cache the billing-order prefix and
drop the crew nothing reads.
Extracting the people-related helpers into tmdb-credits.ts keeps
tmdb-merge.ts under the line cap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tmdb): keep the aggregate ids the arrival check depends on
Trimming the cached cast to its top 40 broke the property it was supposed
to preserve: `known` is built from the aggregate ids, so a returning actor
billed below the cut read as a new arrival on the cached path and took a
reserved slot. The same show then showed a different top ten on its second
open than on its first.
Keep the whole cast, and cut the two things nothing reads instead: the
aggregate crew, and every `roles[]` entry except the one the merge picks
(most episodes). A merge over the trimmed payload now provably returns
what a merge over the full one does — covered by a test that runs both.
Also points CLAUDE.md and the doc's module table at tmdb-credits.ts, where
the credit helpers now live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(tmdb): add the release note for the series-cast fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(tmdb): let the cache trim reuse the merge's own role choice
The trim picked the role with the most episodes; the merge picks the
NAMED role with the most episodes. TMDB uses unnamed roles for uncredited
appearances, so a member whose blank role outranked their real one lost
their character on every render after the first.
Both now call pickAggregateRole, which is the point — two copies of the
same choice are what let them drift.
Also adds a test pinning the property the earlier truncation defect broke:
the displayed cast is the cap or everyone available, whichever is smaller.
Which people make the cut at the cap is the reservation's job and is
deliberate; the count is not negotiable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(tmdb): state the aggregate-credits contract as TMDB actually words it
TMDB describes the endpoint in one sentence that contradicts itself: "it
does not return the newest season. Instead, it is a view of all the entire
cast & crew for all episodes belonging to a TV show." The doc and the code
comment asserted the first half as settled fact.
The union never depended on that reading — arrivals are a set difference,
so under "whole run" they are simply empty — but the comment implied an
assumption the code does not make. Say what TMDB says, note the ambiguity,
and note why either reading is safe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
import { pickAggregateRole } from './tmdb-credits';
|
|
import { TmdbDetails, TmdbTvDetails } from './tmdb.types';
|
|
|
|
/**
|
|
* Trims a details payload before it is cached.
|
|
*
|
|
* `aggregate_credits` spans a show's whole run: a crew list that can reach
|
|
* into the thousands, plus every cast member carrying one `roles[]` entry
|
|
* per character they ever played. Caching that verbatim grows
|
|
* `tmdb_metadata` (and the PWA's in-memory map) by orders of magnitude for
|
|
* data nothing reads.
|
|
*
|
|
* What goes is chosen so that a merge over the trimmed payload produces
|
|
* exactly what a merge over the full one would:
|
|
*
|
|
* - the whole cast stays, ids included. It is the only record of who was
|
|
* in the show before the newest season, so dropping the tail would make
|
|
* a returning actor read as a new arrival on the cached path — the
|
|
* displayed cast would then differ between the first render and every
|
|
* later one.
|
|
* - `roles[]` keeps only the entry the merge itself would pick — the
|
|
* named character with the most episodes, chosen by the very same
|
|
* function, so the two can never drift apart.
|
|
* - `crew` goes entirely: series credits come from `created_by`, so
|
|
* nothing reads it.
|
|
*
|
|
* Everything else is passed through untouched — payloads still hold
|
|
* whatever a later phase might want without a refetch.
|
|
*/
|
|
export function trimDetailsForCache(details: TmdbDetails): TmdbDetails {
|
|
const aggregate = (details as TmdbTvDetails).aggregate_credits;
|
|
if (!aggregate) {
|
|
return details;
|
|
}
|
|
|
|
const cast = (aggregate.cast ?? []).map((member) => {
|
|
const roles = member.roles ?? [];
|
|
if (roles.length <= 1) {
|
|
return member;
|
|
}
|
|
// The merge's own choice, not a second one that could differ
|
|
const main = pickAggregateRole(member);
|
|
return { ...member, roles: main ? [main] : [] };
|
|
});
|
|
|
|
return { ...details, aggregate_credits: { cast } } as TmdbDetails;
|
|
}
|