docs(specs): Jellyfin server version compatibility, and what research found

Adds the spec for running one build against two Jellyfin generations, plus the
research report that establishes what actually differs — with a source URL per
claim, and an explicit section for what could NOT be established.

The framing the spec started from was wrong, which is the most useful thing here:

  Jellyfin 11.0 does not exist and never did. With 12.0 the project dropped the
  leading "10" from its scheme, so what would have been 10.12.0 shipped as 12.0
  and the server reports Version: "12.0.0". The two live generations are 10.11.x
  and 12.x — one release-branch step apart, not two majors. 12.0 became stable
  on 2026-09-08.

The delta turned out far smaller than assumed, and almost none of it is a
version branch:

  - X-Emby-Authorization and the api_key query parameter are disabled by default
    in 12.0, including on upgraded servers via a migration. This is the one
    genuinely breaking change, and the fix is a rename: Authorization and ApiKey
    are ungated on both generations.
  - GetItems now defaults recursive to true for a library parent with
    IncludeItemTypes, so the same request returns a different result set. Fixed
    by stating Recursive explicitly.
  - The /Users/{userId}/... family survives. Six routes were removed in total;
    none are ones this client calls.
  - BaseItemDto is purely additive. DeviceProfile, PlaybackInfo and
    PublicSystemInfo are byte-identical between the two tags.

The generalisable lesson, recorded in the spec: most of a version delta is fixed
by writing the request correctly for both generations rather than by branching
on the version. A flag is a silent branch that outlives the reason it was added.

Allocates UR-085, IR-035, JA-037, DR-279..DR-288 and IT-019..IT-026. DR-287 and
DR-288 did not exist when the spec was written — they are what the research
turned up.

Also corrects docs/specs/README.md, whose "next free requirement ids" line was
stale by five, two and forty-seven.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 20:09:11 +02:00
co-authored by Claude Opus 5
parent 4bce81a800
commit b1844f673e
4 changed files with 1054 additions and 3 deletions
+6 -3
View File
@@ -27,15 +27,18 @@ know how something *works*, read
| Design authority | No code of its own — it records a decision later specs act on. |
**Next free requirement ids** (always re-check
[requirements.md](../requirements.md) before allocating): **UR-079**,
**IR-033**, **DR-232**. Three specs below suggested ids that have since been
taken by other work; each carries a ⚠️ note at the top.
[requirements.md](../requirements.md) before allocating): **UR-086**,
**IR-036**, **JA-038**, **DR-289**. Three specs below suggested ids that have
since been taken by other work; each carries a ⚠️ note at the top — this line
was itself stale by five, two and forty-seven until 2026-09-08, which is why the
re-check is not optional.
## Partially implemented
| Spec | What landed | What is left |
|---|---|---|
| [frontend-domain-model.md](frontend-domain-model.md) | Catalog surface: `MediaKind`, `from_jellyfin` isolated, ticks → ms | `primaryImageTag``imageId` (~30 sites); player/session/reporting tick math; `stream.type` |
| [jellyfin-server-version-compatibility.md](jellyfin-server-version-compatibility.md) | Route table, `ServerCapabilities`, the auth-spelling fix (the one thing 12.0 actually breaks), explicit `Recursive`, cache generation stamping, the frontend route leak, the unsupported-server state, and an HTTP-level harness that runs the repository against both generations | DR-283: two resolved flags are not consumed yet, and `honours_directplay_audio_codec` is unestablished for 12.x — both need a running 12.x server. Nothing has been tested against a real server of either generation |
| [libmpv2-migration.md](libmpv2-migration.md) | `LICENSE` | The `libmpv``libmpv2` crate swap |
| [read-through-media-cache.md](read-through-media-cache.md) | DR-126…128, DR-133…138 — cache entries *are* download rows; local playback of downloads | DR-122/124/125 — the read-through capture. DR-121 shipped as backend-owned stream selection and left this spec |
| [scoped-search-boundary-implementation.md](scoped-search-boundary-implementation.md) | Stage 1: `SearchScope` owned by Rust (DR-063…067) | Stage 2: result-side grouping (`GROUP_ITEM_TYPES` still in `searchScope.ts`) |