feat(jellyfin): a show's tags follow it whatever it is called

A word met on Jellyfin is tagged with the show and the episode by the
names Jellyfin gives them, so a server switching its metadata language,
another server, or a rename in Manage tags made a second set of tags for
the same show. Such a tag now also keeps what it stands for: the ids the
show (or the artist, or the film) has with the metadata providers and on
the server, and the episode's number or the song's own id. New words
find their tag by those first, keeping its name; a Jellyfin tag from
before, found by its name, takes them, and a rename keeps them.

They stay in tag schema 2, a deliberate exception to ADR 0002's rule 3:
schema 3 would hide every new Jellyfin tag from v0.17, while an app
that rewrites the tag only drops the ids, and the tag is found by its
name again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-16 22:34:28 +02:00
commit 238a1a93ae
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -21,6 +21,7 @@ import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_flutter/src/feature/tag/data/tag_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/tag/model/tag.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_card_gateway.dart';
@ -251,6 +252,38 @@ void main() {
expect(find.text('New'), findsOneWidget);
});
testWidgets('a show the server calls otherwise now keeps its tags: a word '
'added gets those standing for the show and the episode, whatever '
'they are called', (tester) async {
final show = await tags.create(
'旅の記録',
fromJellyfin: true,
jellyfinId: const JellyfinTagId(ids: {'tmdb': '424242'}),
);
final episode = await tags.create(
'旅の記録 1-1',
fromJellyfin: true,
jellyfinId: const JellyfinTagId(ids: {'tmdb': '424242'}, part: 'S1E1'),
);
await watch(tester);
await players.last!.play();
await playTo(tester, const Duration(seconds: 2));
await tester.tap(find.text('いました'));
await tester.pumpAndSettle();
await tester.tap(find.text('Add'));
await tester.pumpAndSettle();
final card = (await cards.cardsOf('japanese')).single;
final tagging = await tags.read();
expect(
[for (final tag in tagging.of(card.id)) tag.id],
containsAll([show, episode]),
);
expect(tagging.named('Sample Journey'), isNull);
expect(tagging.named('Sample Journey S1 E1'), isNull);
});
testWidgets("the video's words are listed, the most frequent first, "
'particles left out; one opens its first line', (tester) async {
await watch(tester);