Skip to content

v0.1.0 — genre blacklist and batch genre removal

Choose a tag to compare

@sredevopsorgagents sredevopsorgagents released this 08 Oct 16:24
· 11 commits to main since this release
04a5eb8

Two features, one problem. Both edit genres, and both are destructive when they guess
wrong — so both are built on the same rule.

A genre blacklist you can edit while the app is running

Settings → Blacklisted genres takes the genres this tool must never propose for
writing. Entries live in Postgres and take effect on the very next request, instead of
after a container restart. TAG_BLACKLIST_EXTRA still works and merges in, so an existing
deployment is unchanged.

The screen shows what each entry matches before you save it — "this will match 3 genre
values in your library" — because the input format hides a real ambiguity. A comma
separates entries as you type, and is also a legal character inside a genre value. A line
containing one is reported with both readings rather than guessed at, and splitting is a
separate labelled action.

A batch tool that removes one genre from a whole library

Remove genre finds every item carrying a value, shows exactly what would change, and
applies it as one revertible batch. It reuses the existing bulk write path rather than
adding a second one, so it inherits payload completeness, snapshot-before-write and the
etag check without restating any of them. Failures are isolated per item, and the whole
run reverts through the same endpoint as any other batch.

The comma problem, and why matching is exact

A comma separates entries in rock, reggae — and it is a legal character inside a genre
value. Both are true at once, and both are in this library:

'Rock, Reggae'   'EBM, Synth-pop'   'Reggae; Ska'
'Hip Hop, Rock, Latin, Funk / Soul, Pop, Children's, Folk, World, & Country'

This is not cosmetic. Measured against Jellyfin 12.2.0:

Query Result
Genres=Rock 67 albums
Genres=Rock, Reggae 1 album

A comma-split matcher would delete 67 albums' worth of curation for a request naming a
genre that occurs once. So matching is exact and case-insensitive, an empty target is
refused rather than treated as a wildcard, and removing part of a packed value is an
explicit opt-in that rebuilds using the separator the value already used — Reggae; Ska
does not become Reggae, Ska.

Verified on 100 real artists: removing rock took Rock/rock and left Gothic Rock,
indie rock, punk rock, Rock en Espanol, rock en español and rock mexicano alone.

Blacklisting is not deleting

Blacklisting a genre stops it being added. It does not remove one an item already has:
merge mode carries existing values through by design, and a blacklist that silently pruned
curated data on the next unrelated edit would be a destructive surprise. Deleting a stored
genre is the separate Remove genre tool, which is reviewed and revertible.

Upgrading

One migration, forward-only, adding a single table. alembic upgrade head is all that is
required, and no existing behaviour changes.

Never apply an autogenerated Alembic revision here without reading it. Autogenerate
emits op.drop_table for every lastfm_request_YYYY_MM partition, because those are
created at runtime rather than declared on the model — applying its output verbatim would
delete the archived request log. For the same reason alembic check cannot be used as a
CI gate on this repository. This is recorded in the migration, in docs/operations.md and
in ADR 0016, because I hit it while building this release.

Two pre-existing defects, recorded rather than quietly fixed

  • Jellyfin rounds PremiereDate to midnight on every write. Verified on 12.2.0, and
    documented in ADR 0014. Affects the 9 artists and 5,907 albums/songs that carry a date.
  • Any merge write decomposes a stored comma-bearing genre. An item holding the curated
    genre Rock, Reggae ends up with two genres after an unrelated edit. 8 of 502 albums
    carry such a value. Pinned in a test so a fix is deliberate rather than silent.

Both predate this release. Neither is introduced or worked around by it, and fixing the
second changes existing write behaviour, so it is called out instead of folded in.

Notes

  • 945 backend tests, 17 frontend, 39 end-to-end, all passing. Guarantees were
    mutation-tested rather than trusted to counts.
  • Version 0.1.0: new table, seven endpoints, two screens.
  • ADR 0015
    and 0016
    record the decisions and the alternatives rejected.