v0.5.0
Visibility: the release where unattended acquisition stops being silent. v0.4.0
made Transpondarr act on its own; this one tells you what it did β pushed to
Discord, a webhook, or ntfy, and collected in an Activity feed β and lets you
rehearse the whole thing first. Turn automation to notify-only and watch a
week of real decisions across your library without a single byte reaching the
download client.
Added
- Notifications, with Discord, generic-webhook, and ntfy adapters. Once
acquisition runs unattended, silence is the default failure mode: a stuck
import, a failed grab, an episode landing β all of it previously visible only
by opening the UI. A newNotifierseam carries one structured event that
each adapter flattens natively: Discord to a colored embed with per-field
detail, the generic webhook to a documented JSON contract you can script
against, ntfy to title/priority/tags. ntfy is first-class rather than reached
through the generic webhook because its priority mapping is the feature β
a stuck import buzzes at high priority, an episode landing does not. Six
event kinds (grabbed, imported, import stuck, grab failed, series added, and
rehearsal), per-adapter per-event toggles, and a test button per adapter
so a config can be verified without waiting for a real event. Delivery is
fire-and-forget: a failing notifier logs and never blocks or fails the
pipeline that triggered it. A manual grab stays deliberately silent β you
were there. - Notification-only mode: rehearse the sweep without grabbing. The global
automation toggle grew a third state β off / notify-only / on. In notify-only
the sweep and feed poll run for real (search, decide, cadence, pinned-group
holds) and report what they would have taken, without recording a grab or
touching the download client. The negative half reports too, which is the
more useful half: no eligible candidate, held for a pinned group, refused by
the season-pack guard β each with its reason. A rehearsal is a firehose by
design, so ntfy maps it to low priority. Switching toonafterwards resets
every series' search cadence, because a rehearsal settles nothing and would
otherwise leave the library backed off for releases the feed has already
consumed. - Activity page: the global download and import feed. The placeholder is
now the answer to "what did automation do while I wasn't looking?" β a
queue of every in-flight grab across the library, joined with live
client-reported state, and a paginated history feed of grab and import
events, newest first. The queue surfaces paused, stalled, and checking per
row, which the derived status vocabulary could not express: a paused torrent
previously read as "Downloading" forever. With the download client
unconfigured or unreachable it degrades to grab state rather than erroring. - Per-job "Run now" from the Background jobs card. The runner's trigger has
been implemented and tested since v0.3.0 with no caller; it now has a button.
A manual run bypasses the automation kill switch on the same precedent that
governs manual search and grab β an explicit action is intent, not something
to gate β so the card confirms in words when a hand-triggered sweep will grab
for real. Every eligibility rule still applies, and a run in notify-only
rehearses like any other, so the mode's guarantee holds no matter who
triggered it. The trigger only queues, so the button never pretends to wait
for a result. - Delete a series. Anything added previously stayed forever, occupying the
library list and keeping the sweep searching.DELETE /api/v1/series/{id}
removes the series with its episodes, grab history, and blocklist memory in
one transaction, behind a confirmation that names what goes and what stays.
Library files are never touched β a decision, not an omission: deleting
media is a bigger call than deleting a tracking row. An optional flag also
clears the series' torrents from the download client; without it they are
left seeding.
Changed
- Breaking:
PUT /api/v1/settings/automationnow takesmode
(off|notify_only|on) in place of theenabledboolean, and
GET /api/v1/settingsreports it the same way β an old client body is
rejected. Stored values andTRANSPONDARR_AUTOMATION_ENABLEDstill accept
the old booleans, so no configuration needs migrating. - A re-grab no longer erases the attempt before it. The per-series History
tab now reads an append-only event log instead of the single current grab
row, so a completed lifecycle shows both "Grabbed" and "Imported" and a
replaced release keeps its record. "Import blocked" leaves history in the
process β it is live state, owned by the Episodes tab and the Activity
queue, not an event that happened at a point in time.
Fixed
- The Discovery page built 88 hidden year options on every render. Radix
keeps a closedSelect's items mounted so the trigger can resolve its own
text, so the year picker's full 1940-onward range was constructed on every
render β about half the page's mount cost, for a dropdown that is rarely
opened. The items now render only while the menu is open.
Internal
- An append-only
grab_eventstable records the lifecycle thegrabs
table structurally cannot: one row per wanted item, overwritten by the next
attempt. Events are written at the two convergence points β inside the grab
transaction, and as the importer settles β with the importer's writes
best-effort, because history must never wedge the pipeline. The migration
backfills from surviving grab rows so an upgrade does not start empty. - The API's first pagination, keyset-cursor over
(created_at, id)with an
opaque cursor, set as the precedent for the endpoints that follow. - The notification dispatcher fans out under
context.WithoutCancelwith
its own timeout, so neither a request-scoped context nor a shutdown cancels
an in-flight send, and a hung endpoint cannot leak a goroutine.