Skip to content

Reject aggregator error placeholders and bound the details meta fetch - #1

Merged
Mitsu03 merged 2 commits into
Devfrom
fix/addon-error-meta-sentinel
Sep 8, 2026
Merged

Mitsu03 merged 2 commits into
Devfrom
fix/addon-error-meta-sentinel

Conversation

@Mitsu03

@Mitsu03 Mitsu03 commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

Two defects in the same meta-resolution path, both surfaced by one report
("clicking Naruto or One Piece loads forever on a black screen"):

  1. MetaDetailsParser.parse accepted an aggregator addon's error placeholder as
    real metadata, so it was stored and synced as a series title.
  2. MetaDetailsRepository.load — the details screen path — had no timeout at all,
    while fetch() reaching the same tryFetchMeta has always had one.

PR type

  • Reproducible bug fix
  • UI glitch/bug fix
  • Behavior bug/regression fix
  • Small maintenance only, with no UI or behavior change
  • Docs accuracy fix
  • Translation/localization only
  • Approved larger or directional change

Why

Defect 1 — error placeholder stored as metadata.

A series appeared in Continue Watching titled [X] Anime Kitsu, with
Request for meta for Anime Kitsu timed out after 30000ms where its synopsis
belongs. Not a render glitch — it was persisted on disk:

nuvio_watch_progress.properties                 "title":"[X] Anime Kitsu"
nuvio_continue_watching_enrichment.properties   "name":"[X] Anime Kitsu"
contentId tt0988824 (Naruto Shippuuden), S17E23

Written on a webOS install, it surfaced on desktop as the title in the player
overlay. The aggregator addon answers a failed upstream lookup with a
well-formed meta object rather than an error:

{"meta": {"id":"tt0988824", "type":"series",
          "name":"[X] Anime Kitsu",
          "description":"Request for meta for Anime Kitsu timed out after 30000ms"}}

looksLikeMetaObject asks only whether id, type and name exist. The
placeholder has all three, so presence validation cannot catch it. The 30000ms
is the aggregator's own internal budget for its sub-addon; no client knows that
name. Rejecting the placeholder lets the caller fall through to the next addon,
which is what should have happened when the lookup failed.

Defect 2 — unbounded fetch on the details screen.

load() walked its manifests with no timeout. With a 60s connect + 60s read HTTP
client, an addon that accepts the connection and then stalls leaves the screen on
MetaDetailsUiState(isLoading = true) with no meta to render behind it — a black
screen — for up to a minute per addon in turn. The in-flight guard means tapping
again does nothing but wait.

Desktop scope

Windows (where this was diagnosed), and desktop shared code. Both files are in
commonMain, so the parser guard also benefits the other targets — the corrupted
record here was in fact produced by the webOS client and reached desktop via sync.
The HTTP timeouts that make defect 2 visible are the desktop client's
(AddonPlatform.desktop.kt: 60s connect, 60s read).

Issue or approval

Fixes #2 — filed from this diagnosis, with the persisted corrupted records
quoted verbatim as evidence.

UI / behavior impact

  • No UI change
  • No behavior change
  • UI changed only to fix a documented glitch/bug
  • Behavior changed only to fix a documented bug/regression
  • UI change has explicit maintainer approval
  • Behavior change has explicit maintainer approval

No UI code changed. The visible effect is that a stored title stops being an
addon's error string, and that the details screen stops waiting without bound.

Policy check

  • I have read and understood CONTRIBUTING.md.
  • This PR is small, focused, and limited to one problem.
  • This PR is scoped to the desktop app, desktop packaging, desktop documentation, or shared code required for desktop behavior.
  • This PR is not cosmetic-only.
  • Any UI change fixes a linked glitch/bug and includes visual proof, or this PR has no UI change.
  • Any behavior change fixes a linked bug/regression or has explicit approval, or this PR has no behavior change.
  • This PR does not bundle unrelated refactors, cleanups, formatting, or drive-by changes.
  • This PR does not add dependencies, architecture changes, migrations, or product-direction changes without explicit approval.
  • I listed the testing performed below.

Caveat on "limited to one problem": this is strictly two defects, in two
commits, and I would rather flag that than quietly tick the box. They share one
file pair, one code path and one user report, and the timeout is what turns a
failed lookup into an unbounded wait. Happy to split into two PRs if preferred.

Scope boundaries

Not changed, deliberately:

  • tryFetchMetaByAlternateId is also unbounded, but it does not exist on Dev
    it is unmerged work on another branch. Left alone here.
  • No in-flight request de-duplication, though startup issues 155 meta fetches for
    10.5 MB with the same title fetched up to 5 times. That is performance, not this bug.
  • No retroactive repair of already-corrupted stored records. The two on this
    install were corrected by hand; the guard only prevents recurrence.
  • No change to MetaDetailsParser's handling of any other malformed payload.

Testing

MetaDetailsParserTest — 11/11 passing:

tests="11" skipped="0" failures="0" errors="0"

Two new cases: the verbatim placeholder payload is rejected, and
[Dub] Naruto Shippuuden still parses so legitimate bracketed titles are
unaffected.

Not covered, stated plainly: the black screen was never captured under
instrumentation.
Thread dumps every 15s across the session caught only one
transient 30s block that resolved itself. The timeout makes an unbounded wait
impossible, but it bounds the symptom rather than proving the cause — and the
placeholder cannot explain the One Piece case, whose tt0388629 is served by
cinemeta and never touches that addon. Defect 1 is proven; defect 2 is a real
unbounded path that would produce this symptom.

Environment: Windows 11, desktop build from Dev, JDK 21.0.11.

Note: composeApp/src/commonTest/.../PlayerExitOrderingTest.kt does not compile on
a clean Dev checkout. Pre-existing and unrelated — verified by stashing these
changes and rebuilding on untouched Dev, which reproduces the same single error.

Screenshots / Video

Not a UI change.

Breaking changes

None. A meta response that previously parsed into a placeholder-titled entry now
fails parsing, so the caller advances to the next addon — the path already taken
for any other unusable response.

Linked issues

Fixes #2

Aggregator addons answer a failed upstream lookup with a well-formed meta
object rather than an error: the name field carries the failing sub-addon
tagged with a cross mark, and the description carries the error text. It
parses cleanly, so the parser accepted it as real metadata.

The value then travelled well past the request that produced it. On a user's
install a series ended up stored as "[X] Anime Kitsu" with "Request for meta
for Anime Kitsu timed out after 30000ms" as its synopsis, in both watch
progress and the Continue Watching enrichment cache, and surfaced later as
the title in the player overlay -- on a different device from the one where
the lookup had failed.

Presence checks cannot catch this: the placeholder has an id, a type and a
name, so it satisfies looksLikeMetaObject. Reject it explicitly so the caller
falls through to the next addon, which is what should have happened when the
lookup failed.

Tests cover the verbatim placeholder payload and confirm that legitimate
titles containing brackets, such as "[Dub] Naruto Shippuuden", still parse.
load() walked its meta manifests with no timeout at all, while fetch() -- the
prefetch path reaching the same tryFetchMeta -- has wrapped each attempt in
withTimeoutOrNull(FETCH_TIMEOUT_MS) all along. The asymmetry only shows when
an addon accepts the connection and then stalls: the desktop HTTP client
allows 60s to connect and 60s to read, so a details screen could sit on
MetaDetailsUiState(isLoading = true) for a minute per addon in turn, with no
meta to render behind it. That reads as a permanently black screen, and the
in-flight guard means tapping again does nothing but wait.

Use a separate 20s budget rather than reusing FETCH_TIMEOUT_MS: this is the
interactive path and some payloads are genuinely large -- One Piece is about
1.3 MB across 1239 videos -- so 5s would give up on responses that were going
to arrive. The point is an upper bound, not a tight one.
@Mitsu03
Mitsu03 merged commit 000056b into Dev Sep 8, 2026
1 of 4 checks passed
@Mitsu03
Mitsu03 deleted the fix/addon-error-meta-sentinel branch September 8, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Addon error placeholder is stored as a series title ("[X] Anime Kitsu")

1 participant