Restructure step 4: local library and media identity

- L1 (core, offline): release names with kinds (episode, movie, extra:
  OP/ED/Opening/Ending/NC/PV/OVA…), folder hints (season and name from
  parentheticals, ranges), one show across folders, gaps and next with
  skipped numbers, junk ignored, stable keys.
- L2 (app, device-owned cache): AniList match with synonyms and
  sequel walking, episode mapping across cours (AnimeFlow runs when
  signed in), art and episode titles, "Wrong show?" pins per show or
  folder, episode offsets; lazy, spaced requests, works offline.
- Files tab as a library: poster cards, show pages with banner, gap
  prompt, extras, movies, Wrong show? and Episode numbers.
- media.ref produced for local files, Jellyfin (provider ids) and links,
  carried in swaps and Up next; receivers name media from it.
- Hosting relays each Jellyfin item through its own server's login.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-15 23:34:39 +03:00
1 parent 4d00b7455e
commit bf9fc193b3
44 files changed
+4350 -891

No files matched your search

+5 -6
View File
@@ -193,9 +193,10 @@ void main() {
expect(plain.name, 'movie.mkv');
});
test('relay credential: the account token with exactly its own device fields', () async {
test('relay credentials: each account token with exactly its own device fields', () async {
final (accounts, _) = await linkedAccounts();
final cred = relayCredentialFor(accounts, serverId: fixtureServerId)!;
final creds = relayCredentialsFor(accounts);
final cred = creds.single;
final info = accounts.clientInfo;
expect(cred.token, 'TEST_ACCESS_TOKEN');
expect(cred.serverId, fixtureServerId);
@@ -203,13 +204,11 @@ void main() {
expect([cred.client, cred.device, cred.deviceId, cred.version],
[info.client, info.device, info.deviceId, info.version]);
expect(cred.device, 'Test PC');
// Another server's item: the default account still relays its own.
expect(relayCredentialFor(accounts, serverId: 'other')!.serverId, fixtureServerId);
accounts.markExpired(fixtureServerId);
expect(relayCredentialFor(accounts, serverId: fixtureServerId), isNull);
expect(relayCredentialsFor(accounts), isEmpty);
final none = JellyfinAccounts(store: MemoryJellyfinStore());
await none.ensureLoaded(validate: false);
expect(relayCredentialFor(none), isNull);
expect(relayCredentialsFor(none), isEmpty);
});
});