The committed track kept the server display title, which collapses to the
bare language ("English") and is identical for every same-language row: a
carried signs/songs choice tied with the full dialogue track on the next
episode and latched onto whichever row sorted first. The row's own title
is preferred now, so the carried intent names the exact row again and the
native pass can match the right container track by title instead of
language order.
Reproduced against a live library where both English ASS rows differ only
by Title ("Styled Subtitles" vs "Signs/OP/ED").
close#1785
The cross-item subtitle intent required declared languages on both sides,
and a null on either side counted as a contradiction. Any untagged track -
common when a title like "Swedish" is the only signal - declined on every
episode advance, fell to the server's per-item priority, and turned the
viewer's subtitles off (a 2.11.0 regression from the #1716/#1717 hard
gates).
A unique real title match now vouches for a row when language evidence is
missing on either side. Declared languages that disagree still decline no
matter what the title says, forced-class parity is untouched, codec and
external parity only break ties within the title-matched set, and a
residual tie declines rather than guesses, so the wrong-track class of
#1716 stays closed.
A decline is also no longer laundered into a viewer decision: the resolver
keeps the unserved preference on the selection, the open flow hands it to
the track manager instead of a navigation-priority off (late native tracks
may carry the container tags the server rows lack), the next episode
boundary re-carries it instead of hardening it into an explicit off, and
progress reports withhold the -1 subtitle index that would otherwise come
back as the item's server-side default forever.
A pick the screen could not map to a source row (no subtitle catalog, or
an identity-matcher miss) previously never reached the committed session
selection at all, so the next episode carried the stale off while the
picked track was visibly on screen. Such picks now commit the raw native
track without source ids and demote to a semantic intent at the boundary.
close#1785
Formatting was clean through 53288116 and then drifted across three commits on
2026-07-30: 1bf7aac7 left one source unformatted, f13f5af6 a second, and
daab4f1e four more. CI's Verify formatting job checks the whole tree, so it has
had six files to report ever since. No pre-commit hook is installed in this
checkout, so the aggregate check never ran locally to catch them.
Formatted with the dart_style revision Dart 3.12.0 bundles, which is what the
pinned Flutter 3.44.0 CI toolchain runs, rather than with a newer local SDK; the
two disagree about some argument-list splits. The current stable formatter
accepts this result as well, so both report the tree clean.
Plex treats a subtitle stream as forced when its title says "Forced" even
with the API flag unset. Every forced comparison now uses that effective
forced-ness on both sides: the match scorer, the low-metadata hard gate,
the Jellyfin OnlyForced/Smart profile modes, and stream-index negotiation.
Carrying a track choice into the next episode no longer reuses the
same-item identity matchers. A sealed SubtitlePreference (off / track
reference / semantic intent) replaces the id-'navigation' pseudo-track
through the whole preference channel, and cross-item intents hard-require
language and forced-class parity. When the next episode has no track of
the same class, the intent declines and selection falls through to the
server's own per-episode choice instead of latching onto a full track by
position and persisting that mistake back to the server.
Intents wait for pending native tracks under the same catalog-completeness
rule as source ids, so an early decline cannot retire the selection
listener before the real track arrives.
Ref #1716
Focus chrome was implemented twice, once in the focusable wrapper and once
in the focus builders; both now go through FocusChrome. TvColorPicker's
channel row was a copy of TvNumberSpinner and is now that widget in compact
density.
Also trims unused helpers and fields and simplifies the Jellyfin browse
paths.