fix(portals): close the player we launched, not the one we now own

Three follow-ups, two of them to last round's own fixes.

The external-session close was defeated in exactly the case it was written
for: `switchToSource` marks the DESTINATION active before handing playback
over, so by the time the service ran, the process still playing no longer
looked like ours and was left running beside its replacement. The service now
remembers the ids it launched with, independently of what is active.

The ASCII/Unicode branch was decided from the NORMALIZED token, which folds
diacritics — "Ça" arrived as "ca", looked like plain ASCII, and took the GLOB
path while the stored title still read "Ça". Decided from the raw token now.

Backup restore upserted archived pins but never removed the playlist's
existing ones, so a present-but-empty collection left stale preferences alive
— unlike the playback positions cleared beside it. An absent collection (an
older archive) still means "no opinion" and is left alone.

Four files crossed the size cap on the way; the split ones now share
`title-sources.spec-data.ts` and `playlist-backup.xtream-fixtures.ts`, and the
external-session ownership moved to its own module.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 committed 2026-07-29 01:42:22 +02:00
1 parent 270e54a0b5
commit 481d368019
13 files changed
+808 -508

No files matched your search

+7 -3
View File
@@ -473,8 +473,9 @@ object carrying `codec_name`/`width`/`height`. `readStreamInfo` accepts both —
reading only the object silently lost the codec on every array response, and
with it the "dub may differ" warning, which compares stated audio tracks.
Switching sources through `startResolvedPlayback` closes a matched external
session first. It REPLACES what is playing — with MPV or VLC and instance
Switching sources through `startResolvedPlayback` closes the external session
it LAUNCHED first — tracked separately from the controller's active source,
which a switch has already moved to the destination by then. It REPLACES what is playing — with MPV or VLC and instance
reuse off, the backend would otherwise spawn a second detached player, leaving
both sources running and Stop owning only the newer one.
@@ -482,7 +483,10 @@ both sources running and Stop owning only the newer one.
SQLite's `LOWER()` and GLOB character classes are ASCII-only, so a short
non-ASCII title could not be folded or word-bounded and simply never matched —
the film stayed absent from the chip. ASCII tokens keep the word-boundary GLOB
the film stayed absent from the chip. The ASCII/Unicode branch is decided from the RAW token, not the normalized
one: normalization folds diacritics, so "Ça" arrives as "ca" and looks like
plain ASCII while the stored title still reads "Ça". ASCII tokens keep the
word-boundary GLOB
(what stops "it" matching "Titanic"); a non-ASCII token falls back to a
substring test against both the folded and the as-typed form. That covers a
title stored in the same case as the request or in lower case, and deliberately