v0.3.0
Quality profiles and airing awareness: releases are now scored against a
profile instead of ranked by seeders, and Transpondarr now knows when episodes
air β feeding a calendar, a seasonal discovery page, and a scheduled refresh
that keeps a releasing series growing on its own.
Added
- Quality profiles drive release selection. Releases are scored against a
profile β ranked release groups (dominant by construction), resolution
order, and reward-only preferences over source, subtitle type, codec, and
repack/version β instead of ranked by raw seeders. Hard excludes and an
optional minimum-score floor let automation answer "nothing yet" rather
than grab something unwanted. - Profile management UI and per-series assignment β create, edit,
reorder, and delete profiles under Settings β Quality profiles (deleting a
profile in use prompts to migrate its series first), and assign a profile
per series. - The Releases tab shows each release's score and its breakdown β a
per-axis tooltip explains the ranking, and a release outside the profile is
shown with an amber score and the profile's reason, never hidden. - Profiles inform manual grabs; they gate only automation. A manual grab
always succeeds in one request β no confirm step; when the release falls
outside the profile, the response carriesineligible_reasonand the UI
reports it after the fact. - Per-series pinned release group β "for this show, this group's release
is definitive." The pin is an absolute sort tier above profile scoring, so
no stack of bonuses can outrank it β but it never bypasses eligibility: a
pinned release that trips a hard exclude or the score floor stays refused. - Air dates from AniList β wanted items now carry when they air, synced
in the background off the job runner. Absence is normal, not an error:
AniList's schedule coverage thins out before ~2015 and can skip episodes
even on modern titles. - Calendar view β upcoming episodes for monitored series
(GET /api/v1/calendar), each carrying its acquisition status; monitored
series with no schedule are listed as unscheduled instead of silently
missing. - Discovery page β a browse-and-add seasonal chart with format,
airing-status, and genre filters, backed by a per-season cache; entries
already in the library are marked as tracked. - A releasing series now grows on its own. A scheduled metadata refresh
adds newly-announced episodes as wanted items, and the airing sync creates
the items its schedule names β covering long-runners whose total episode
count AniList never publishes. GET /api/v1/system/jobsreports background job status β each job's
interval, last run, how long it took, its last error, and when it runs next.
"Did the refresh run, and did it fail?" was previously answerable only by
reading server logs.
Fixed
- Dimension-form resolutions now count. A release named with
1920x1080
kept the literal dimension string as its resolution, so it scored zero on
the resolution axis and slipped past a1080phard exclude; the parser now
folds dimension forms to the height form profiles are written in. - Blocking a group in the profile editor no longer yanks the row away from
the cursor. Blocked groups still serialize last on save, but the list no
longer re-sorts mid-edit.
Security
- Upgraded React Router to v8 to clear GHSA-qwww-vcr4-c8h2, a CSRF bypass
in its unstable RSC APIs. The UI's declarative routing was never
exploitable, but the only patched release line is 8.x.
Internal
- Background work runs on a named job runner (
internal/core/jobs) instead
of a baregoper feature. Jobs are registered by name with an interval, a
panicking job is contained to the run that caused it (its own loop and every
other job survive), and shutdown drains in-flight runs before the store
closes. Deliberately not a cron library and not a persistent queue: intervals
only, nothing durable across restarts. - The importer now runs on the job runner instead of its own goroutine, so
it reports a real last run, duration, and error like every other job, and
the entrypoint is down to one background mechanism and one drain. - Session cleanup no longer races the database close on shutdown. It was
started as an unawaited goroutine, so a sweep could be mid-DELETEwhen the
store closed; it now drains with everything else inside the shutdown budget.
The shutdown warning also names which worker overran instead of just
reporting that something did. - Interval loops are now tested with
testing/synctest, so scheduling
behaviour is asserted exactly against a virtual clock rather than polled
against wall-clock deadlines. make testnow runs under the race detector. The job runner's status
fields are written by each job's goroutine and read by the HTTP handler, so a
missing lock would be invisible without it. The existing suite was already
race-clean; the whole run costs about a second more.- A fuzz target guards
parser.Parseagainst panics. Release titles come
from external indexers through an unmaintained parsing dependency, so
"never panics on arbitrary input" is now checked in rather than assumed. - The frontend suite is split into node and happy-dom vitest projects, so
pure-logic suites skip the ~350ms-per-file DOM build. - Local verification is scoped to what changed, with CI named as the
full-suite enforcement point β and the CI gates that stance relies on
hardened.