e3eddab80f8ef2eed143ee9e15a359ab8e36498b
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3eddab80f |
feat: the duplicates review says what each match rests on and plays every copy (#6168)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m25s
release / go (push) Successful in 1m34s
release / android (push) Successful in 4m58s
release / integration (push) Successful in 5m4s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Each group now states its evidence: identical audio data, or the share of audio fingerprints that agree. Copies titled as different songs in one acoustic group say so and ask the reviewer to listen, and each copy has a play button so they can. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
460e78c955 |
fix: an album with missing tracks is wanted back by its release group, not only when it carries a release MBID (#6167)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m32s
release / integration (push) Successful in 5m1s
release / android (push) Successful in 5m7s
release / Build signed APK (releases and dev) (push) Successful in 5m17s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
The release id was only ever used to find the group. 238 albums matched to a group carried none, so their losses, five of the resolver's wrong-slot removals among them, were never fetched again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
39029b5cd9 |
fix: clearing a wrong slot's MBID clears its source too, as tracks_mbid_source_pair requires (M498)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m37s
release / android (push) Successful in 5m1s
release / integration (push) Successful in 5m4s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8daa3d07b5 |
fix: a slot emptied of another song's file drops that file's recording MBID, so missing-pair folding cannot fold it into the copy kept (M498)
release / govulncheck (push) Successful in 24s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m1s
release / android (push) Successful in 5m2s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
On 2026-10-20 twelve of the 24 slots the resolver emptied were folded by MBID into the kept copy, so their albums were never wanted back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
304caaee02 |
fix: the AcoustID rate-limit test bounds arrivals by the service's 3 a second, not the client's send spacing
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m42s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m26s
release / Build signed APK (releases and dev) (push) Successful in 5m31s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
Run 9543 saw two arrivals 389ms apart though the client sent them 400ms apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a3d9effca3 |
feat: when no slot an album gives one recording is its own, every copy is removed and the album fetched again (M498)
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m28s
release / go (push) Failing after 1m31s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m23s
release / Build signed APK (releases and dev) (push) Successful in 5m37s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Shnabubula's whole album, one 45-minute file, was filed as both Fading Light and Looking for Answers. Neither slot fits, so wrongSlots had nothing to keep and held the group. Every copy now goes when each is clearly another song's; a copy near its slot still holds the group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c8e855ae4a |
feat: the duplicate resolver fetches a missing album track list from MusicBrainz to decide a wrong slot, five albums a pass (M498)
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m16s
release / go (push) Successful in 1m33s
release / integration (push) Successful in 5m0s
release / android (push) Successful in 5m7s
release / Build signed APK (releases and dev) (push) Successful in 5m20s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
20 of the 34 groups left for review had no cached track list for their album: the catalogue only reads one when the wanted list or the importer needs it. The resolver now asks catalogue.RefreshReleaseGroup for an album it must decide and the catalogue has never read, through the shared MusicBrainz pacer, at most five a pass (hourly). An unreachable MusicBrainz stops the pass's fetching; a group read and found empty is not asked about again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5868c00cc |
feat: a version named after a dash counts as a version, and a song held twice beside a wrong slot no longer blocks it (M498)
release / govulncheck (push) Successful in 25s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m31s
release / android (push) Successful in 4m51s
release / integration (push) Successful in 4m53s
release / Build signed APK (releases and dev) (push) Successful in 5m0s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 17s
release / Verify release artifacts (tag releases only) (push) Skipped
- songKey drops a dashed version suffix (" - Instrumental Version", " - Radio
Edit", live, remix, mix, acoustic, demo, reprise, rework, VIP), so Andora's
"Goodbye Yesterday - Instrumental Version" is distinct, not a wrong slot.
- wrongSlots accepts several fitting copies when they share one title
(Killjoy twice beside Dreamland): the other slot's copy is removed, and the
doubled song is left for the next sweep to find as a pair the resolver merges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
80c711ac9d |
fix: a wrong-slot removal reads without a counted noun (M498)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m28s
release / go (push) Successful in 1m38s
release / integration (push) Successful in 5m9s
release / android (push) Successful in 5m11s
release / Build signed APK (releases and dev) (push) Successful in 5m21s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 29s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f58ebcd6c |
feat: duplicates settle themselves: retitled copies across releases link, alike versions on one album are dismissed, and a file in another song's slot is removed so the album is fetched again (M498 #6159-#6162)
release / govulncheck (push) Successful in 14s
release / web (push) Failing after 1m26s
release / go (push) Successful in 1m35s
release / integration (push) Successful in 5m8s
release / android (push) Successful in 5m12s
release / Build signed APK (releases and dev) (push) Successful in 5m26s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
The operator's library had 536 groups waiting for review, 2026-10-19. A sample
showed most were one recording under two titles on two releases.
- The headline and the Duplicates count leave out cross-release groups, which
the resolver keeps and links: the headline read 3,479 when 536 needed review.
- Any group spanning albums is cross_release and is linked as one song,
whatever its titles say. Nothing is deleted.
- One album, two versions named as such ("Lovesick (Instrumental)") and not
byte-identical: a new class, distinct, dismissed by the resolver.
- One album, one recording under different song titles (mismatch): the
album's cached MusicBrainz track list decides. When exactly one slot's song
is as long as the file and every other slot's song is clearly not, the other
copies are removed and their tracks marked missing, so the reacquisition
sweeper wants the album back. Otherwise the group stays for review.
- The duplicates_found notice moves from the sweep to the resolver: it names
only groups a pass first left for review.
- Resolved automatically lists dismissals and wrong-slot removals beside
merges.
Migration 0094 adds 'distinct' to the class CHECK (rule 36).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
8538f0dc68 |
fix: the playback-error test matches the prefixed username, and announcementFor keeps detekt's two returns (M542)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m25s
release / go (push) Successful in 1m35s
release / integration (push) Successful in 5m7s
release / android (push) Successful in 5m8s
release / Build signed APK (releases and dev) (push) Successful in 5m20s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
854c307834 |
feat: notifications say what changed and alert once: a grouped notice names the tracks, grows without re-alerting, and a phone pile lists its titles (M542 #6146-#6151)
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m33s
release / android (push) Failing after 1m42s
release / integration (push) Failing after 4m42s
release / Build signed APK (releases and dev) (push) Successful in 4m55s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- An update to a grouped notice keeps its arrival time and email state, so the phone and the email no longer go off again each time it grows. - Notices carry the names of what changed (newest ten), shown as three and "and N more": tracks still missing after the scan's move/replacement fold, duplicate groups new to a sweep, songs merged automatically, and the track and listener a playback error was for. - Duplicates found counts only the groups new to that sweep; playback errors count reports, not the unresolved total. - A scan failure says what stopped and links to Admin > Library. - Android: a pile of more than three alerts lists the newest five titles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2365514e5b |
test: TestPlanRoots looks for the empty-list refusal as the message words it (#6129)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m34s
release / integration (push) Successful in 5m2s
release / android (push) Successful in 5m6s
release / Build signed APK (releases and dev) (push) Successful in 5m16s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m22s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
edcb3a57ef |
feat: the library's folders are set in Admin › Library, and changing a folder moves its tracks with it, keeping their plays, likes and playlists (#6129)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m20s
release / go (push) Failing after 1m37s
release / integration (push) Failing after 4m56s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m13s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
453a977e48 |
refactor: the admin and user Requests pages draw a request with one RequestRow, and the kind chip is one KindPill there and on Hidden tracks (#5651)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m16s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 5m10s
release / android (push) Successful in 5m17s
release / Build signed APK (releases and dev) (push) Successful in 5m33s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
db15713798 |
refactor: the server says a count one way, words.Count, for notifications, a wanted match's reason and the duplicate resolver's activity line (M505 #5643)
release / govulncheck (push) Successful in 22s
release / web (push) Successful in 1m35s
release / go (push) Successful in 1m45s
release / integration (push) Successful in 5m41s
release / android (push) Successful in 6m7s
release / Build signed APK (releases and dev) (push) Successful in 6m26s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 29s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9e24b2b4fc |
refactor: every count in the web app is said by one plural(), with the viewer's thousands separator; a source guard names any hand-written copy (M505 #5642)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
af2d4f9859 |
refactor: a running download is worded the same on the admin Overview as on the wanted list, from one liveDownloadLine (M505 #5641)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m29s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m8s
release / android (push) Successful in 5m21s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
622f248ea2 |
refactor: track lengths and file sizes come from the shared formatters on Missing files, Duplicates, a want's Releases and the app download; an hour-long track reads 1:01:05, not 61:05 (M505 #5639 #5640)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9e7d50b38e |
fix: FLAC, Ogg and Opus tracks carry their MusicBrainz recording id from the tag: one reader in library gives the scanner, the backfills and the importer the same ids, and the next scan re-reads the library (#5637)
release / govulncheck (push) Successful in 20s
release / web (push) Successful in 1m30s
release / go (push) Successful in 1m50s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m50s
release / Build signed APK (releases and dev) (push) Successful in 6m12s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c1305a547d |
feat: the admin Overview shows who is listening, downloads, the library and its disk, and the server's version, uptime and recent errors; a download waiting on you, a client not answering or a full disk count in its verdict (M504 #5627 #5628 #5629 #5630)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m35s
release / go (push) Successful in 2m0s
release / integration (push) Successful in 5m40s
release / android (push) Successful in 6m15s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
The version moves to internal/buildinfo, so /rest/ping reports the build instead of "dev". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ead0abf0cf |
feat: one Missing files page under Collection: folders gone, each with its album's want, last search or download, and Search, Releases and Remove; an album held before its files went is no longer shown as held again (M504 #5626)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m30s
release / go (push) Successful in 1m46s
release / integration (push) Successful in 5m23s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 6m2s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
38b2325d2d |
feat: Reports, Duplicates and Suspect sources live under Collection, with their counts on its rail and a "To look at" line naming the ones waiting; notices link there (M504 #5625)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m39s
release / integration (push) Successful in 5m31s
release / android (push) Successful in 6m5s
release / Build signed APK (releases and dev) (push) Successful in 6m21s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 32s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
12725e71b0 |
fix: the reports test name keeps its apostrophe inside double quotes, and the merge API test sends no unmonitor (M502 #5525)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m35s
release / integration (push) Successful in 5m12s
release / android (push) Successful in 5m18s
release / Build signed APK (releases and dev) (push) Successful in 5m34s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3a16732883 |
feat: Minstrel no longer talks to Lidarr: its connection, Delete via Lidarr and the unmonitor options are gone, and the track reports are plain quarantine (M502 #5525)
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 17s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Migration 0091 drops lidarr_config and renames the quarantine tables, types, indexes and the history column off the Lidarr name. The Integrations Lidarr card, the Reports "Delete via Lidarr" action, the merge and remove-track unmonitor options, the Lidarr audit actions and error codes go, on the server, web and Android. The README describes acquisition as it is now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c54792c51e |
feat: a held album below the quality you settle for, or holding a video rip, stays wanted for a better copy, and the import replaces the old tracks with their likes and plays carried over (M502 #5524)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 5m20s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 27s
release / Verify release artifacts (tag releases only) (push) Skipped
- acquisition_settings.upgrade_until (default High quality lossy, Lidarr's cutoff, note #5527; "" is off), an "Upgrade until" row under Searching. - The wanted worker judges held albums hourly: tier per format from size per second (the scanner records no bitrate), the album's worst. Below the cutoff it is wanted for quality; a video rip among its tracks always. - The schedule searches upgrades weekly, after open wants; a release no better than the copy held is refused ("not_better"). - The import matches each file to the track it would replace (recording, then disc/track) and replaces it when it is a rip or the file is a better tier: over the same path in one rename, or filed beside it and the old track folded in (library.ReplaceTrack: file first, then foldTrackInto). Nothing better: the release is set aside. - Collection › Upgrades (Lidarr's Cutoff Unmet); Suspect sources says when a clean copy was looked for instead of Lidarr's search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a5ad301c94 |
feat: Users, Diagnostics and Tuning sit on the admin layout like the rest: debug mode is a row action on Users, every admin load error has Try again, and figures carry their scale (M503 #5608)
release / android (push) Successful in 5m24s
release / Build signed APK (releases and dev) (push) Successful in 5m26s
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m34s
release / integration (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
- Users: a one-line count of accounts, admins and open invites; Turn on/off debug mode in each row's actions, and a "debug on" badge that opens that account's events - Diagnostics: no title or paragraph of its own; devices named by account, OS, app version and last seen, not client id; opens on ?account= - Tuning: each profile, the taste build and Discover are row groups with one-line hints; the sparkline has a 0–100% axis - Requests, Duplicates, Missing files and Suspect sources: load errors offer Try again - Library: cover art reads "N of M albums have art" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0049b64ff1 |
fix: the AcoustID and Library tests expect the new copy and one folder per line (M503 #5607)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m27s
release / go (push) Successful in 1m43s
release / android (push) Successful in 5m19s
release / integration (push) Successful in 5m22s
release / Build signed APK (releases and dev) (push) Successful in 5m38s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7f913e3e70 |
feat: the admin settings cards read as menus: each a titled group of rows with one short line, status first, failures in words (M503 #5607)
release / govulncheck (push) Successful in 16s
release / web (push) Failing after 1m27s
release / go (push) Successful in 1m44s
release / android (push) Successful in 5m13s
release / integration (push) Successful in 5m18s
release / Build signed APK (releases and dev) (push) Successful in 5m26s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Indexers and clients, searching, missing files, Lidarr's neighbours, AcoustID, fingerprinting, loudness, outgoing email's schedule, public address and proxies move from paragraph cards to SettingsGroup and SettingRow. Switches are role="switch". Also fixes the two type errors run 9023 found: the Lidarr test result is narrowed before its lists are read, and the tag-source save mock carries version_bumped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7e1cfb8d08 |
feat: Integrations splits into Library, Acquisition, Metadata sources and Email & network, each a page on the admin rail; the Overview keeps only what needs you (M503 #5607)
release / govulncheck (push) Successful in 14s
release / web (push) Failing after 16s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m54s
release / android (push) Successful in 5m5s
release / Build signed APK (releases and dev) (push) Successful in 5m18s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- Library: the folders, Run scan and a row per scan job, cover art, fingerprints and loudness (off the Overview and Duplicates) - Acquisition: indexers and clients, searching, re-acquisition (off Missing files), Lidarr - Metadata sources: AcoustID, and one provider block for cover art and tags in place of the pasted pair - Email & network: outgoing email, the notification schedule, public address, proxies - Lidarr, outgoing email and the provider block are rebuilt as SettingsGroup rows; failures read as sentences, not codes - Duplicates and Missing files say what their setting is and link to where it is set Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0cc4bc686a |
fix: a report's details keep the space before a note, and the Reports menu test opens the row it means (M503 #5606)
release / govulncheck (push) Successful in 19s
release / web (push) Successful in 1m38s
release / go (push) Successful in 1m49s
release / android (push) Successful in 5m22s
release / integration (push) Successful in 5m27s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
89a2653995 |
feat: Admin › Reports is one queue of broken tracks, whether a listener flagged it or it failed to play, one row per track with one set of actions (M503 #5606)
release / govulncheck (push) Successful in 16s
release / web (push) Failing after 1m18s
release / go (push) Successful in 1m36s
release / integration (push) Successful in 5m27s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 5m38s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- /admin/reports folds the quarantine queue and the unresolved playback errors into one row per track, newest first: who flagged it and why, what failed and for whom, the file, with Resolve (clears the reports and marks the errors fixed), Play, Copy details, Delete file and, for an album Lidarr holds, Delete via Lidarr. - One delete path: Delete file removes the track, and its reports and errors go with the row. - A Handled tab shows what was done: reports cleared, files deleted, errors marked fixed or ignored. - The summary counts reports by track across both sources, so the rail, the Overview and the page agree. - /admin/quarantine and /admin/playback-errors redirect to Reports: the notifications and the Android app link to them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
864eb45580 |
feat: Settings uses the width of a wide screen: groups sit in two columns, each narrow enough that a row's control stays near its name (M503 #5595)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 5m13s
release / android (push) Successful in 5m15s
release / Build signed APK (releases and dev) (push) Successful in 5m25s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
The content was capped at max-w-3xl, so a wide window showed one narrow strip and a lot of empty space. SettingsColumns lays a page's groups out as one column on a phone or narrow window and two side by side from xl up; the page cap moves to 100rem so the columns don't stretch on a very wide screen. - Account: Android app, Profile and Password, with Signed-in devices beside. - Playback: "Between tracks" (crossfade) beside "Volume leveling". - Connections: ListenBrainz beside Subsonic apps. - Appearance, Notifications, About: one column's width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ace7e22e6a |
feat: Admin is a left rail with how many wait on you in each queue, and the Overview opens with a verdict, the queues that need you and since when, then the clear ones on one line (M503 #5605)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m10s
release / android (push) Successful in 5m16s
release / Build signed APK (releases and dev) (push) Successful in 5m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
- GET /api/admin/summary: every admin queue (requests, reported tracks, playback errors, duplicates, suspect sources, missing files) with its count and the age of its oldest item, in one query. - The admin layout drops the 11-tab strip for SectionRail, as Settings and the Collection have, with each queue's count as its badge; the counts refresh on each admin navigation and every 30s. - The Overview leads with "Nothing needs you." or "N things need you.", lists each waiting queue with "oldest 3d ago" and a link to it, counts a failed library scan as a problem, and names the empty queues on one line. The request and quarantine previews and the stat tiles are gone; those queues are worked on their own pages. A failed summary says so with Try again instead of reading as all-clear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7c89e59536 |
fix: admin bugs from the audit: a playback-error delete no longer reports failure, a failed Overview load no longer reads as all-clear, and email settings can't be saved over with defaults (M503 #5604)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m31s
release / go (push) Successful in 1m49s
release / integration (push) Successful in 5m20s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / android (push) Successful in 6m26s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
- Playback errors: Delete file stops resolving the error row the track deletion already took with it (ON DELETE CASCADE), which 404'd into "Delete failed". - Overview: a queue that fails to load says so with Try again, and its tile shows a dash, not 0. Delete via Lidarr shows only for an album Lidarr holds while Lidarr is connected. An approve refreshes the Collection. - Quarantine: Delete via Lidarr is in the row menu, so a phone reaches it; "1 report", not "1 reports". - Duplicates: the Keep badge follows the copy picked. - Integrations: provider save failures and load errors are said, not swallowed or shown as "No providers registered"; the Lidarr test catches a failed call; SMTP fills once and Save stays off until it has loaded. Lidarr's blurb says what it is still used for. - Requests: release profiles point at Collection › Following. - Library: an empty library sends an admin to Admin to run a scan, and tells anyone else the admin scans it, instead of opening Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c94c09aeae |
feat: Settings is one page per section down a left rail, Account first, every setting a row; the profile form no longer saves blanks over your name and email (M503 #5595 #5596 #5597)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m45s
release / go (push) Successful in 1m58s
release / integration (push) Successful in 5m39s
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
- /settings lands on Account: the Android app, profile, password, signed-in devices. Then Playback, Appearance, Notifications, Connections, About. - Each setting is a row (name, one short line, control at the end) in a capped-width column; one-of-N choices are a shared Segmented control. - Collection and Settings share one SectionRail. - Connections leads ListenBrainz with a status line: not connected, connected and not sending, or sending with the last scrobble's age. - Notifications prints its column labels once; the digest email's settings link points at /settings/notifications. - #5596: the profile form now loads what is stored and sends only the fields that changed. It used to start blank, and the server reads a blank as "clear", so one Save wiped the display name and email. - Recommendation metrics moves to Library › Your listening, and Hidden tracks gets a Library tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c0c0f8a71f |
feat: Now Playing's cover grows to fill the screen, and the page takes the cover's dominant colour as Android does (#5592)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m45s
release / go (push) Successful in 2m9s
release / integration (push) Successful in 5m44s
release / android (push) Successful in 5m49s
release / Build signed APK (releases and dev) (push) Successful in 6m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
The web colour helper now picks Palette's dominant swatch (5-bit quantise, Palette's default filter, median cut to 16, the most-populated box) instead of the 1x1 mean, so the web and Android agree; PlayerBar's accent strip shares it. Now Playing draws Android's gradient (55% at the top, 18% at 45%, then the page) as a masked background colour that fades over 600ms between tracks, holding the old colour until the new resolves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2f2573dd07 |
fix: the re-tag test sets its clock one statement at a time, as a prepared statement takes only one (M502 #5579)
release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m35s
release / integration (push) Successful in 5m9s
release / android (push) Successful in 5m15s
release / Build signed APK (releases and dev) (push) Successful in 5m20s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a8786abace |
feat: Collection › Unmatched lists the library albums no MusicBrainz album matched, each matched by hand from a search or a link, or left off (M502 #5579)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m34s
release / integration (push) Failing after 5m34s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m44s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Like Lidarr's Unmapped Files. A local band's album, which MusicBrainz will never have, is left off: never matched again and not counted. A re-tagged album is matched again on the next listing. The Collection's verdict now says when library albums need matching. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
10be068dcc |
fix: the player bar's heart is Lucide's, outlined then filled as on Android, and its title and artist read at the size of the rest of the page (#5580)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
358849de77 |
feat: the Wanted view becomes Collection, shaped like Lidarr: an artist catalogue first, the state sections down a left rail (M502 #5577)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m39s
release / go (push) Successful in 1m54s
release / integration (push) Successful in 5m33s
release / android (push) Successful in 5m40s
release / Build signed APK (releases and dev) (push) Successful in 5m56s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
The section opens on a catalogue of every artist on the list, A–Z with a letter rail, each card showing how many of their albums are held. An artist opens on their page: every album of theirs, held or not, with its status, search, releases and tracks. Wanted, Missing files, Activity, Following and Blocked sit down a left rail as the sections that show state; Held is gone, the catalogue shows it. The section moves from /wanted to /collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a4fbe44bd5 |
feat: the Wanted view lists artists A–Z with their albums inside, each album's tracks a click away (M502 #5572)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m38s
release / integration (push) Successful in 5m35s
release / android (push) Successful in 5m59s
release / Build signed APK (releases and dev) (push) Successful in 6m10s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
The list runs like the library: artists by the library's sort name, each artist's albums oldest first, grouped under the artist (linked into the library when it holds them). A filter narrows it to an artist or album by name, on the server, so it finds what has not been scrolled to. Tracks opens an album's track list: the library's own for an album it holds, a lost file marked, otherwise MusicBrainz's standard edition, named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ca57133c8a |
fix: a release group whose title names a live, remix or demo version is wanted only when the profile allows that version (M502 #5573)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m31s
release / go (push) Successful in 1m51s
release / android (push) Successful in 5m18s
release / integration (push) Successful in 5m23s
release / Build + push container image (push) Successful in 21s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
MusicBrainz types My Chemical Romance's "Common People (Recorded For BBC Radio 1)" as a plain Single, so a studio-only follow wanted it. Passes now reads the title's qualifier (brackets, or after a dash) for the version it names and treats it as a secondary type; a version no type names, an acoustic or instrumental one, no profile passes. The follow check takes off the list an open follow want the rule now refuses. Judge counts bbc, sessions and unplugged as live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7ef1fca90c |
fix: an album holding every track of an edition, and more besides, is that release group (M502 #5569)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m40s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m23s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Savant's "Void" folder holds all 23 tracks of the VOID (DLC) edition plus 20 more, and was refused at 23 of 43. A whole edition of three or more tracks now matches whatever else the folder holds; singles and two-track releases still need most of the album's tracks, since many albums hold their songs. Matcher version 4 retries the refusals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1936fe77c6 |
fix: the matcher-retry test stamps the listing's cutoff after its albums exist, as a listing done before them would not see them (M502 #5569)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m26s
release / go (push) Successful in 1m43s
release / integration (push) Successful in 5m29s
release / android (push) Successful in 5m35s
release / Build signed APK (releases and dev) (push) Successful in 5m47s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bf409d5f3f |
fix: a second copy of a track counts once, joining words don't part two titles, and a newer matcher retries an older one's refusals (M502 #5569)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m18s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m33s
release / android (push) Successful in 5m47s
release / Build signed APK (releases and dev) (push) Successful in 6m4s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
The second run's refusals were mostly albums holding a track twice (a two-track single with three files), which no edition can pair, and release names that differ only in "+" against "/". Tracks are now read once per disc, number and title, and "and", "&" and "+" count for nothing when titles are compared; an edition's own title also counts as the album's name. library_album_matches records which matcher version decided each album. Refusals by an older version are matched again, even after the listing is done, so an improvement reaches them without a migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
80d2fdc3ab |
fix: under the album's own name, matching track titles are enough to match it; a refusal says which track differed and how (M502 #5569)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m33s
release / android (push) Successful in 4m54s
release / integration (push) Successful in 4m59s
release / Build signed APK (releases and dev) (push) Successful in 5m6s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
The first run on the library refused singles whose one track has the
album's title ("0 of 1 agree") and near-complete albums (11 of 14), because
every track's length had to fit as well. Lengths now only decide between
groups whose names merely overlap. The reason for a refusal names the first
track that differed, with both titles and lengths, so what is left can be
judged. Migration 0087 clears the refusals so those albums are matched
again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
b887ab759b |
fix: every library album is matched to MusicBrainz before it is listed, by its tags, its artist's discography or a search, and checked against its tracks (M502 #5569)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m28s
release / android (push) Successful in 5m44s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Verify release artifacts (tag releases only) (push) Skipped
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
The one-time listing took only albums whose tags already named their release group and artist, and left 340 off the list. The rest are now matched: the release their tags name, else the artist's release groups called what the album is called, else a MusicBrainz search by artist and title. A group found by name is taken only when its tracks agree with the album's (by disc and number, then title; length and title, or recording), so a partial copy still matches and a same-titled other album does not. library_album_matches records each album the listing decided, listed or unmatched with why, so an album taken off the list is never re-listed. The albums no match was found for are named on the Held tab. Album rows with no tracks are no longer counted. The length and title checks move from the importer to release, shared by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2c677b3184 |
feat: the library's albums go on the wanted list as held, once; after that the list is the truth (M502 #5567)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m31s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
A one-time job in the wanted worker lists every album in the library as a held want, one per release group, with a record that it happened (acquisition_settings.library_listed_at). Albums whose tags name only a release are named on MusicBrainz first, 120 a pass; what nothing names is counted and shown on the Held tab. Re-acquisition now wants back only albums on the list. Taking a held album off the list leaves its files, and it is never fetched back; albums put in the library folder by hand afterwards are not added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bc198a9389 |
fix: the Activity test's page carries clients_down as a value, not a literal Page<T> would refuse (M502 #5523)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m38s
release / integration (push) Successful in 5m31s
release / android (push) Successful in 5m46s
release / Build signed APK (releases and dev) (push) Successful in 5m59s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 27s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8245f4406e |
feat: download lifecycle — stalled and failed downloads are given up and searched again; imported ones seed, then go (M502 #5523)
release / govulncheck (push) Successful in 17s
release / web (push) Failing after 17s
release / go (push) Successful in 1m11s
release / integration (push) Successful in 4m50s
release / android (push) Successful in 4m54s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Ported from Fabled Librarian's System 104. A new lifecycle poller, every ten minutes: - a download the client fails is given up at once; - one nobody shares, or still fetching its file list, past the stall setting (6 h) is asked for again three times, then given up; - giving up removes the download with its files, holds the release back for a week (release_blocks.expires_at), ends the grab and searches the album again, with no limit per album; - a running download the client stops listing for half an hour ends and is searched again, not held back; an empty list is not taken as gone; - an imported download seeds to ratio 2.0 or 14 days, then is removed; - a download the client would not remove is asked again every pass. Every condition and act is one open row per (client, download, condition) in download_interventions. Activity shows what is wrong with a download and how often it was retried, why one ended, its seeding, and a banner for a client that does not answer. A running download can be given up by hand (set aside for good). The seed ratio, seed days and stall hours are on the search settings card. The download client lookup moves to acquire.DownloadClientFor, shared by the importer and the lifecycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d9e00d8bc2 |
fix: the integrations test's library folder isn't /music, which a Lidarr root folder there is named (M502 #5522)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m36s
release / integration (push) Successful in 5m5s
release / android (push) Successful in 5m15s
release / Build signed APK (releases and dev) (push) Successful in 5m34s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f268b4df09 |
fix: WantList's prop is list, not state, which Svelte reads as the $state store; the length test's 4-minute case fits within 3% (M502 #5522)
release / govulncheck (push) Successful in 16s
release / web (push) Failing after 1m40s
release / go (push) Successful in 1m53s
release / integration (push) Successful in 5m38s
release / android (push) Successful in 5m39s
release / Build signed APK (releases and dev) (push) Successful in 5m51s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ebbc49b902 |
feat: import with verification, and a Wanted view of its own (M502 #5522)
release / govulncheck (push) Successful in 14s
release / web (push) Failing after 15s
release / go (push) Failing after 1m16s
release / integration (push) Failing after 4m49s
release / android (push) Successful in 4m59s
release / Build signed APK (releases and dev) (push) Successful in 5m14s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
A finished download is checked against MusicBrainz before anything is filed: the files are paired with an edition's tracks by position or path, and AcoustID, tag ids, lengths and titles say whether it is the album. What is the album is copied into the library as Artist/Album (Year)/Artist - Album - NN - Title, tagged with Picard's MusicBrainz ids, and scanned; the download keeps seeding. What is not is refused: set aside, removed from the client, and the album searched for again. A download the check cannot settle waits for a person, who imports it anyway or refuses it. Tracks the library already holds are skipped. The wanted list leaves Admin for a top-level Wanted view, admin-only and web-only, with tabs for Wanted, Activity (downloads and their imports, the ones needing a person first), Missing files, Held, Following (with the release-type profiles, moved off Integrations) and Blocked. The search settings gain "File new music into" when the library has several folders. The search pass logs why each failed search failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
77081f1cef |
fix: the search schedule logs its state when it changes, so an idle pass says why (M502 #5521)
release / govulncheck (push) Successful in 22s
release / web (push) Successful in 1m16s
release / go (push) Successful in 1m36s
release / integration (push) Successful in 4m53s
release / android (push) Successful in 4m54s
release / Build signed APK (releases and dev) (push) Successful in 5m4s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7f1fe57935 |
fix: the search test reads the last pass as JSON, since jsonb respaces it (M502 #5521)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m31s
release / go (push) Successful in 1m42s
release / integration (push) Successful in 5m18s
release / android (push) Successful in 5m18s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 30s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
89b3b44779 |
feat: search and grab — music release names, a quality ladder, a paced search schedule and one live grab per want (M502 #5521)
release / govulncheck (push) Successful in 21s
release / go (push) Failing after 33s
release / web (push) Successful in 56s
release / integration (push) Failing after 4m48s
release / android (push) Successful in 5m6s
release / Build signed APK (releases and dev) (push) Successful in 5m16s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
- internal/release: reads a release name (format, bitrate, bit depth and sample rate, source, year, video, discography) and judges it against the album wanted: the artist and title must be in it; video rips, discographies, other versions (drumless, instrumental, karaoke, remix, live unless the album is live…) and singles or EPs for an album are refused. Quality tiers on Lidarr's ladder; a .torrent's file list is read for format, video files and a track count short of the album. - internal/acquire: fan-out across every search source with a narrowed search kept, a process-wide pacer per source, every candidate judged and kept with its reasons, and the grab: one live grab per want, tie-broken (a person's grab beats the schedule's; a better release replaces a worse one unless it is nearly done). Each download lands in its own folder under the client's downloads folder. - internal/wanted: the search schedule, every ten minutes, never-searched first, then requests, missing files and followed artists, taking the best release when auto-grab is on (the default). - Migration 0082: acquisition_settings (auto-grab, interval, allowed qualities seeded from Lidarr's "Any"), want search columns, want_candidates, grabs, release_blocks. - Web: the Wanted page shows each want's last search, a Search button and every release found with its verdict (Grab, Grab anyway, Never this one, Allow again). Integrations gains "Searching and grabbing". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1a1646a920 |
fix: the wanted-list tests set a track's MBID with its source, and find the missing-files link in its row (M502 #5520)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m32s
release / go (push) Successful in 1m46s
release / integration (push) Successful in 5m7s
release / android (push) Successful in 5m15s
release / Build signed APK (releases and dev) (push) Successful in 5m25s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
04515a977c |
feat: a wanted list — follow artists on release-type profiles; requests and re-acquisition become wants (M502 #5520)
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 4m57s
release / android (push) Successful in 5m11s
release / Build signed APK (releases and dev) (push) Successful in 5m28s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Minstrel keeps its own list of what it is to get, keyed by MusicBrainz release group. A want comes from an approved request, a new release by a followed artist, or an album whose files went missing, and is held once the library has a full edition of it. - Migration 0081: release_type_profiles (Standard seeded: Album, EP, Single, studio only), follows, follow_seen_groups and wants. lidarr_requests becomes requests with plain *_mbid columns; approved album and track requests move onto the wanted list, approved artist requests become follows, and the library's album artists start out followed. missing_reacquisitions and the retry settings go. - internal/wanted: follows are checked against MusicBrainz discographies daily. The first check sets a baseline, and only releases after it are wanted unless the follow asked for what already exists. Profiles filter by primary and secondary type, so live recordings stay out unless a profile ticks Live. - internal/requests (was lidarrrequests): approving puts the request on the wanted list, and an artist request can be approved on a chosen profile. A request completes when its want is held. - The re-acquisition sweeper wants a missing album back instead of asking Lidarr; searching for it is the search schedule's (#5521). - Web: an admin Wanted page (Wanted, Missing files, Held, Following), a follow control on artist pages, a release-type profiles card on Integrations, "Approve on a profile" on Requests, and want state on Missing files. The artist detail API returns the artist's MBID. - Android: the request body sends artist_mbid, album_mbid and track_mbid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
867f52358c |
fix: the result card's state prop no longer shadows the $state rune (M502 #5519)
release / govulncheck (push) Successful in 27s
release / web (push) Successful in 1m0s
release / go (push) Successful in 2m4s
release / integration (push) Successful in 4m7s
release / Build signed APK (releases and dev) (push) Successful in 9m44s
release / android (push) Successful in 9m48s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
svelte-check read `$state(false)` as a subscription to the `state` prop. The prop keeps its name for callers and is bound as cardState inside. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
77389f88bc |
feat: a release catalogue from MusicBrainz; request search asks it directly (M502 #5519)
release / govulncheck (push) Successful in 22s
release / web (push) Failing after 24s
release / go (push) Successful in 1m57s
release / integration (push) Successful in 4m1s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / Build signed APK (releases and dev) (push) Canceled after 4m37s
release / android (push) Canceled after 4m38s
Minstrel keeps its own record of what artists have released, read from MusicBrainz, so the wanted list, search and import no longer need Lidarr. - internal/musicbrainz: searches (artists, release groups, recordings) and browses (an artist's release groups; a group's releases with track lists). One process-wide pacer at 1.1s, shared with tag enrichment, because MusicBrainz's limit is per address. A 503 throttle is retried. - Migration 0080: mb_artists, mb_release_groups, mb_artist_release_groups (a collaboration belongs to each credited artist), mb_releases, mb_release_tracks. - internal/catalogue: Discography and Releases serve the held copy and re-read it when older than 7 or 30 days. An unreachable MusicBrainz serves the stale copy, marked. An hourly pass re-reads due artists. - Art for what isn't held comes through /api/catalogue/art from the enabled cover-art sources (Cover Art Archive by release group, then the name-based sources) and is kept on disk, so the browser never fetches a third-party host. A miss is a 404 and the card shows its icon. - Request search moves from /api/lidarr/search to /api/catalogue/search, with the same response shape. Albums are release groups, and "in library" matches albums.release_group_mbid. A 10-minute cache absorbs search-as-you-type. Discover suggestions point at the art endpoint instead of looking up Lidarr. - Android calls the new path and loads Discover art through ServerImage. Installed builds older than this one lose Discover search until they update (no compatibility path, rule 22). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a2e1890d4c |
feat: indexers and download clients you add and configure (M502 #5518)
release / govulncheck (push) Successful in 34s
release / web (push) Successful in 1m13s
release / go (push) Successful in 2m12s
release / integration (push) Successful in 4m6s
release / Build signed APK (releases and dev) (push) Successful in 8m41s
release / android (push) Successful in 9m47s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 59s
release / Verify release artifacts (tag releases only) (push) Skipped
Admin → Integrations gains "Indexers and download clients": two lists of
connections the operator adds, Fabled Librarian's model. Nothing is seeded
or copied from Lidarr.
- Indexers: Prowlarr (its native /api/v1/search, which asks every indexer
at once; Prowlarr has no aggregate Torznab feed) and any Torznab feed.
- Download clients: qBittorrent (WebUI v2, with re-login on an expired
session) and FabledExchange, one entry with a switch per job:
downloads (/api/downloads), search (/api/search) and release details
(/api/torrents/{infohash}). All three start on; its key is optional.
- Each entry: enable, name, address, a write-only key or password, Test.
A download client's Test lists its torrents and checks the downloads
folder is visible and writable from Minstrel; an Exchange's Test
reports each job switched on.
- Download clients carry a path mapping (Minstrel's /downloads vs the
client's own name for it); search sources carry searches per hour.
Migration 0079 adds acquisition_services with its CHECKs. Ported from
Fabled Librarian: internal/indexer (Torznab, 429 Retry-After, magnet
redirects), internal/prowlarr, internal/exchange, internal/downloader,
and internal/acquire's one builder per kind, which Test already uses
so a passing Test vouches for the path a grab will take.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5306a261c1 |
feat: duplicates resolve themselves without Lidarr (M502 #5517)
release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m46s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 6m48s
release / Verify release artifacts (tag releases only) (push) Skipped
Lidarr is off, and with it the resolver had stopped merging: it only merged when Lidarr had said which copies it mapped. Minstrel is now the only program writing the library, so the rule is about the copies: - A song held twice on one album: the extra copies merge into the one whose tags fit best (then lossless, larger, older). - Across releases or under another title: when every copy but one is a video rip (held back, or named like one and not marked fine), the rips merge into the one that is not. - Clean copies on different releases are linked as one song, as before. - Anything else waits for the operator. Gone with Lidarr: release switches and their settle window, the unmapped-file read, the rip search, the merge guard and its 409/503, lidarr.Client's release methods, and duplicate_group_members.lidarr_state (migration 0078). The duplicates page drops the per-copy Lidarr badge and the blocked-merge notice; past release changes stay in its history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8bd48b4302 |
fix: a merge keeps the song link when the removed copy names the song (M498)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m39s
release / go (push) Successful in 1m56s
release / integration (push) Successful in 5m11s
release / android (push) Successful in 5m58s
release / Build signed APK (releases and dev) (push) Successful in 6m11s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
MergeInheritSongLink took "linked" to mean song_key <> id, but the copy whose id became the song's key has no song_id of its own. Removing that copy dropped the survivor out of the song. It now asks whether another copy shares the key. The test covers both copies as the one removed, so it no longer passes or fails on which random id sorts first. The release-change test counted audit rows other tests left behind (ResetDB keeps audit_log); it now counts what its own pass adds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e1ad1c9ba2 |
fix: a release change Lidarr applied but did not answer is recorded (M498)
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m43s
release / integration (push) Failing after 5m5s
release / android (push) Successful in 5m58s
release / Build signed APK (releases and dev) (push) Successful in 6m17s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
On the first deploy pass Lidarr took longer than the client's 30s timeout to answer the release change for Cracker Island: its album update unlinks the files and queues the rescan before it replies. The change may well have landed, but the resolver logged a failure and wrote no audit row, so the album had no settle window and the report did not show it. An unanswered PUT (ErrUnreachable) is now read back: when the album's monitored release is the chosen one, the change is recorded as made. Any other error, or a read-back that disagrees, stands as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e17556dce9 |
feat: Lidarr searches for a better copy of a rip that has none (M498 #5447)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m26s
release / go (push) Successful in 1m45s
release / integration (push) Successful in 5m6s
release / android (push) Successful in 5m58s
release / Build signed APK (releases and dev) (push) Successful in 6m17s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
The operator chose option 2: an album holding a video rip that is the only copy of its song is searched in Lidarr (AlbumSearch), at most once a week and ten albums a pass. Lidarr grabs only when the quality profile allows an upgrade, so nothing is removed and the rip stays held back either way. - lidarr.Client.SearchAlbums posts the AlbumSearch command. - ListAlbumsWithSoleCopyRips finds the albums: a held-back rip with no present clean copy sharing its song or its pending group, and not searched in the last week (audit action lidarr_rip_search). - Suspect sources shows when Lidarr last searched the track's album. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d59b7fdc2a |
fix(test): the mix-writer test reaches oneCopyPerSong through export_test; suspect fixtures carry the verdict (M498 #5439)
release / govulncheck (push) Successful in 26s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m49s
release / integration (push) Successful in 4m36s
release / android (push) Successful in 6m15s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 27s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
53eb954c86 |
feat: video rips and stray copies handle themselves (M498 #5439)
release / web (push) Failing after 26s
release / govulncheck (push) Successful in 25s
release / go (push) Failing after 58s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 3m25s
release / Build signed APK (releases and dev) (push) Canceled after 3m26s
release / android (push) Canceled after 3m28s
- The resolver also merges cross-release and mismatch groups where Lidarr
maps exactly one copy: the others fulfil nothing, so removing them opens no
hole (D-a rule 1). That covers a rip beside the clean copy on another
release and a wrong-file import Lidarr holds unmapped. With two or more
mapped copies each fulfils its own release and nothing is removed.
- A track whose file name carries a video-rip marker is held back from radio
and the system mixes (tracks.source_verdict, migration 0077). It still plays
when chosen. A renamed file is released; the operator's "fine" sticks.
- Suspect sources shows what was done to each track, with "This one is fine"
and "Hold back again" (PUT /api/admin/library/suspect-sources/{id}).
- The Liked list prefers a copy that is not held back.
Replacing a rip that has no clean copy is left for the operator to decide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5b372f61d7 |
feat: the same song on several releases counts as one song (M498 #5438)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m7s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m11s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 5m58s
release / Build signed APK (releases and dev) (push) Canceled after 5m59s
A single and the album it is on stay two files, since each fulfils its own
release in Lidarr, but they are one song to the listener.
- tracks.song_id links copies; the generated song_key (song_id, else the
track's own id) is what they share (migration 0076).
- The resolver links each cross-release group every pass (idempotent; only
with auto-resolve on) and, when a link is new, shares existing likes across
the song and logs them for sync.
- A like or unlike (web and Subsonic) reaches every copy; each change is
logged and published so clients update every heart.
- The Liked list and its count show the song once; Shuffle and the mix writer
take one copy per song.
- A merge keeps the removed copy's song link; "Not the same song" on the
Across releases tab dismisses the group and undoes the link.
Shared plays ("heard via another copy") are left for a later step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4ecff52f19 |
feat: duplicates resolve themselves where Lidarr says it is safe (M498)
release / govulncheck (push) Successful in 45s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m39s
release / integration (push) Successful in 4m25s
release / android (push) Successful in 6m17s
release / Build signed APK (releases and dev) (push) Successful in 5m57s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m54s
release / Verify release artifacts (tag releases only) (push) Skipped
The duplicate sweep proposed 4,197 groups and every one waited for the operator. Most are safe to settle, and Lidarr defines what safe means: it maps one file to each track of the release it monitors and downloads any mapped file that disappears. Deleting a mapped copy opens exactly the hole the operator saw Lidarr fill. Classify (#5435) - Migration 0075: duplicate_groups.class (same_release, cross_release, mismatch, review), resolve_note, resolved_automatically; duplicate_group_members.lidarr_state (tracked, unmapped); fingerprint_settings.auto_resolve; notification kind duplicates_resolved with both kind CHECKs swapped (rule 36). - library.ClassifyDuplicateGroup, with MatchTitleKey dropping featuring credits, remaster notes and video-rip markers, and keeping live, demo, remix and instrumental. The rip markers move from api to library. Choose the copy to keep (#5436) - ProposeSurvivor ranks the copy Lidarr maps first, then tag fit (a clash-free track number, no rip marker in the name, an MBID), then the quality rules. File size picked the wrong Humanz copy in 6 of 21 groups. Act (#5437) - An hourly resolver pass reads Lidarr's unmapped files, matched by the last three path components, and records each copy's state. - Same album, with at most one copy mapped: merged into the mapped copy. The merge is guarded, so a mapped copy can never be removed (MergeDuplicateGroupGuarded, ErrCopyTrackedByLidarr). - Same album, every copy mapped: the monitored release lists the song twice (Humanz's 14x12" box set). The pass moves Lidarr to the release that lists each song once and best covers what is on disk. It never picks one covering less, and is capped at 10 albums per pass. - Fixed point (lesson #4183): the chosen release no longer repeats. - The album is left alone for 24h while Lidarr rescans, so "every copy unmapped" mid-rescan is never read as licence to merge. - Both actions are audited with no actor and summarised to admins. The operator can switch them off in the Fingerprinting card (rule 25). - Manual merges use the same guard: 409 copy_tracked_by_lidarr, or 503 lidarr_unavailable when Lidarr cannot say. Web - Duplicates gets tabs: Needs review, Across releases, Resolved automatically. Each loads as you scroll (rule 172), replacing the pager. - Each copy says whether Lidarr uses it. - The resolver's note shows on each group. - The merge confirm blocks, before sending, a merge that would remove the copy Lidarr uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
01e2294471 |
ci: probe and relax the integration Postgres over TCP, with retries (#5426)
release / web (push) Successful in 1m34s
release / govulncheck (push) Successful in 24s
release / go (push) Successful in 1m51s
release / integration (push) Successful in 5m21s
release / android (push) Successful in 5m33s
release / Build signed APK (releases and dev) (push) Successful in 5m32s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 13s
release / Verify release artifacts (tag releases only) (push) Skipped
On first boot the postgres image runs initdb under a temporary server that listens on the unix socket only, then stops it. The socket readiness probe could catch that server, and the fsync relax then hit 'the database system is shutting down', leaving the suite ~15x slower on internal/api. Only the real server listens on TCP, so probe and relax via -h 127.0.0.1, retry the relax, and print the settings that took effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bf0bc45c4b |
fix(web): filter a refetched mix inside the self-heal step, not by wrapping the closure (#5418)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m31s
release / go (push) Successful in 1m42s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m16s
release / integration (push) Successful in 17m42s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 22s
release / Verify release artifacts (tag releases only) (push) Skipped
The wrapper added a promise hop, so the self-heal test's single microtask wait saw 'loading' instead of 'error'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8bd2f66416 |
fix(web): missing files show greyed in track lists and never enter the queue (#5418)
release / web (push) Failing after 1m23s
release / govulncheck (push) Successful in 25s
release / go (push) Successful in 1m35s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 4m34s
release / android (push) Canceled after 4m39s
release / Build signed APK (releases and dev) (push) Canceled after 3m21s
TrackRef gains the unavailable flag the server already sends on album detail and search. TrackRow greys such a row with '· File missing' and hides like, radio, add and the menu, as playlist rows already do. Every player-store entry point (playQueue, enqueue, play next, and a refetched mix) drops missing files; playQueue keeps the chosen track as the start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
015fe1d3b2 |
fix(web): admin error lines use text-error-fg, not the untokened text-oxblood (#5417)
release / govulncheck (push) Successful in 36s
release / web (push) Successful in 1m42s
release / go (push) Successful in 1m52s
release / integration (push) Successful in 5m7s
release / android (push) Successful in 5m48s
release / Build signed APK (releases and dev) (push) Successful in 5m22s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m28s
release / Verify release artifacts (tag releases only) (push) Skipped
text-oxblood is not a theme token, so Tailwind emits nothing and the scan error on Admin → Overview and "It stopped early" on Duplicates rendered as body text. Same fix as the album page in #5392; no uses remain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f9ddbca97 |
feat(web): every paged list loads as you scroll; Liked becomes tabs (#5416)
release / go (push) Successful in 1m53s
release / govulncheck (push) Successful in 49s
release / web (push) Successful in 1m50s
release / android (push) Successful in 6m44s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 21m41s
Preference 172: no "Load more" buttons. Eight remained, on Liked (three sections), the three Search overflow pages, Genres and Years. - ListContinuation: the one bottom-of-list block. It loads the next page ahead of the reader, announces "Loading more…" through aria-live, shows an optional end line, and on a failed page shows the error with Try again, dropping the sentinel so a dead endpoint isn't re-hit on every scroll. Used on all ten paged lists, including the four that already autoloaded. - A failed next page no longer replaces the list. TanStack sets isError for it, so the page-level error branches now apply only to a failed first load. Genres and Years did the same with their own loader; a later page's failure now keeps the grid. - Liked: Artists | Albums | Tracks tabs (operator's choice). Stacked, a long Artists list loading as you scroll would bury the other two. It opens on the first tab that has likes. - TabStrip: the in-page tab strip, now shared by Liked, Playback errors and Requests. - test-utils/intersectionObserver: a stand-in so tests can scroll to the end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2205b2927b |
test: fix the Suspect sources tests CI rejected (#5410)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m36s
release / integration (push) Successful in 4m36s
release / android (push) Successful in 5m4s
release / Build signed APK (releases and dev) (push) Successful in 5m13s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m11s
release / Verify release artifacts (tag releases only) (push) Skipped
- staticcheck QF1001: the nil-vs-empty check in TestSourceMarkersFor is now an early continue instead of a negated conjunction. - The page test mocks #lib/api/admin.js whole, so suspectSourcesNextOffset was undefined there. Its test moves to admin.suspect-sources.test.ts, beside admin.scan.test.ts, against the real module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3f0540cb7a |
feat: admin Suspect sources report, for files named like video rips (#5410)
release / govulncheck (push) Successful in 37s
release / go (push) Failing after 1m10s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / web (push) Failing after 1m24s
release / integration (push) Successful in 4m38s
release / android (push) Successful in 5m5s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / Build signed APK (releases and dev) (push) Canceled after 5m18s
Humanz turned out to be YouTube rips: "(Official Video)", "Visualizer", a reaction video filed as a song, junk disc numbers (#5401). The library holds about 200 more files named the same way. This report lists them, grouped by folder like Missing files, with the markers each file name carries. - GET /api/admin/library/suspect-sources: one marker list in Go builds both the Postgres ~* filter and each row's labels, so they cannot drift. Basename only; missing files are left out. - Markers calibrated on the live library: "live in/at" dropped (real live albums), "reaction" narrowed (it caught "Chain Reaction"). - Admin tab "Suspect sources", read-only, loads as you scroll; a folder split across pages is joined back into one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
918db27fec |
feat(web): shared collection header, Play and Shuffle, and the centred page column (M494)
release / govulncheck (push) Successful in 42s
release / web (push) Successful in 1m30s
release / go (push) Successful in 1m48s
release / integration (push) Successful in 5m51s
release / android (push) Successful in 6m53s
release / Build signed APK (releases and dev) (push) Successful in 7m14s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m29s
release / Verify release artifacts (tag releases only) (push) Skipped
- CollectionHeader holds cover, title, facts, then Play and Shuffle, for album and playlist. The playlist page gains Play and Shuffle; it had neither. The cover uses the shared Cover component, so the playlist header also gets the fallback image. - PlayActions is the one Play and Shuffle pair: filled Play, outlined Shuffle, on album, playlist and artist pages (operator, 2026-10-08). The artist page's round icon button said Play but shuffled; Play now keeps the server's order, and Shuffle shuffles. - PageColumn is the playlist page's centred column, now used by album, playlist, Liked, History and Search → Tracks. - The playlist page builds its queue with playlistTrackToRef, the shared converter, in place of its own copy. Its copy skipped only removed tracks, so a missing file (#2527) could still be queued from there. - shuffled() in lib/utils is the one shuffle, replacing three private copies (album page, artist page, ArtistCard). - The album's refetch error now uses text-error-fg. `text-oxblood` is not a colour in the theme, so it never rendered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aaa6fa15d6 |
test(web): import history components after their mocks (M494)
HistoryRow now renders LikeButton, and both history test files imported the component before the likes mock helper. LikeButton then loaded while emptyLikesMock was still uninitialised, and the suites failed at import. Every other suite imports components after its mocks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b7f50c42a9 |
feat(web): one track-list row shape, and drag reorder that snaps (M494, #5393)
release / govulncheck (push) Successful in 34s
release / go (push) Successful in 1m37s
release / web (push) Failing after 1m20s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 4m38s
release / android (push) Canceled after 4m39s
release / Build signed APK (releases and dev) (push) Canceled after 3m49s
Every track list (album, Liked, Search, Search → Tracks, playlist, History) now draws its rows with TrackListRow inside a TrackList box, in place of four row components that had drifted apart: - TrackListRow owns the look: lead column, title over "artist · album", like, radio, add to queue, menu, duration, then a trailing slot. It also owns the stripe, hover, the now-playing band and the unavailable state. The stripe is dropped on now-playing and selected rows, where Tailwind would otherwise let `odd:` win. - TrackRow, PlaylistTrackRow and HistoryRow are wrappers that keep only their behaviour: track number and multi-select; grip, remove and dead-entry states; cover and timestamp. Playlist and History rows gain radio, add and the menu, so every list offers the same actions. - The radio control is Lucide's Radio icon, not the 📻 emoji. - Enter on a control inside a row now belongs to that control. The old TrackRow played the track when Enter was pressed on "add to queue". - TrackList has no fill, so the stripe shows on playlists too. Drag reorder (#5393): neodrag kept the drop offset, so after the list re-rendered the dragged row sat between two slots. reorderDrag, shared by the playlist and queue rows: - snaps the row to whole rows while dragging; - resets it to its slot on drop; - on playlists, gives the touch gesture to the grip only, so the list still scrolls by touch. The playlist page reorders optimistically and rolls back on error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e16d182417 |
fix(web): drop the shelf-header accent rule Tailwind 4 started drawing (#5021)
release / govulncheck (push) Successful in 34s
release / web (push) Successful in 1m27s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 4m47s
release / android (push) Successful in 5m17s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m23s
release / Verify release artifacts (tag releases only) (push) Skipped
The rule after each shelf title used `after:bg-accent/60`. Tailwind 3 cannot put an opacity modifier on a `var()` colour, so it emitted nothing for that class and the rule never showed. Tailwind 4 mixes the opacity in with color-mix, so the rule appeared after the upgrade. Removed, so the headers look as they did before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ca9917d8cd |
chore(web): clear kit 3's alias and Vite's config-loader deprecations (#5021)
release / govulncheck (push) Successful in 23s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 4m49s
release / android (push) Successful in 5m32s
release / Build signed APK (releases and dev) (push) Successful in 5m39s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 28s
release / Verify release artifacts (tag releases only) (push) Skipped
CI run 8789 warned on both: - kit 3 deprecates `config.alias`. The $test-utils alias becomes a `#test-utils/*` subpath import, matching #lib, and its 43 import sites move with it. - Vite's coming native config loader needs the extension on vitest.config.ts's import of vite.config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b1b10b0c77 |
fix(web): run Tailwind 4 through @tailwindcss/vite, not PostCSS (#5021)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 56s
release / go (push) Successful in 2m22s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m14s
release / Verify release artifacts (tag releases only) (push) Skipped
The image build on
|
||
|
|
94a9c8cbe3 |
chore(deps): SvelteKit 3 with adapter-static 4 and TypeScript 6, full-tree npm audit (#5021)
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps, plus the migration they need. The mechanical part is `sv migrate sveltekit-3`, run one task at a time and reviewed: - svelte.config.js is gone. Its options move into sveltekit() in vite.config.ts, exported as kitOptions so vitest.config.ts runs the same kit setup, including the $test-utils alias the tests import. - $lib becomes #lib through package.json "imports". There is no src/lib/index, so only the "#lib/*" entry is kept. - tsconfig extends $app/tsconfig. - Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite ^8.0.12, svelte-check ^4.7.5. By hand, from the codemod's list of non-automated tasks: - goto's replaceState option is now replace; keepFocus becomes reset: false. For the search typeahead, reset: false also stops the scroll-to-top, which is wanted while typing. - The test setup mocks drop pushState/replaceState and $app/paths base/assets, which kit 3 removed, and mock refreshAll in place of invalidateAll. - The other flagged files only read page.url or goto internal routes, so they needed no change. TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to 7 once both accept it. With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0, so the web lane now audits every dependency rather than only what ships to browsers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
beba5b2082 | Merge remote-tracking branch 'origin/renovate/sveltejs-kit-3.x' into dev | ||
|
|
fecd030b68 |
chore(deps): Tailwind 4 (#5021)
release / govulncheck (push) Successful in 42s
release / web (push) Successful in 1m39s
release / go (push) Successful in 1m53s
release / integration (push) Successful in 5m37s
release / android (push) Successful in 6m54s
release / Build signed APK (releases and dev) (push) Successful in 7m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Failing after 34s
release / Verify release artifacts (tag releases only) (push) Skipped
Renovate's tailwindcss bump (PR #150) plus the migration it needs: - Theme moves from tailwind.config.js into app.css as `@theme inline`, mapping the same FabledSword tokens. tailwind.config.js is gone. - PostCSS runs @tailwindcss/postcss; autoprefixer is dropped, since Tailwind 4 prefixes through Lightning CSS. - Class renames from @tailwindcss/upgrade 4.3.3, reviewed: outline-none -> outline-hidden, focus-visible:outline -> outline-solid, shadow -> shadow-sm, shadow-sm -> shadow-xs, flex-shrink-0 -> shrink-0. Bare `rounded` stays: v4 keeps it at 0.25rem, as before. - Three v3 preflight defaults kept in a base layer so nothing changes on screen: gray-200 default border colour, gray-400 placeholder text and the pointer cursor on buttons. - The unused class-based dark variant is not carried over; no template uses `dark:`. Clears the five high and two moderate npm audit findings that came in through Tailwind 3 (braces, chokidar, micromatch, fast-glob, postcss-selector-parser). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
308045d056 |
chore(deps): vite 8, vite-plugin-svelte 7 and vitest 5 together (#5021)
release / govulncheck (push) Successful in 33s
release / web (push) Successful in 1m41s
release / go (push) Successful in 1m50s
release / integration (push) Successful in 4m57s
release / android (push) Successful in 6m25s
release / Build signed APK (releases and dev) (push) Successful in 6m37s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 2m13s
release / Verify release artifacts (tag releases only) (push) Skipped
Merges Renovate's three branches (PRs #145, #146, #147), which fail alone: vite-plugin-svelte 7 requires vite 8, and vitest 5 is the vitest for vite 8. Renovate changed only package.json, so npm ci failed on each. The lockfile is regenerated for just these packages: the stale entries for vite, vitest, @vitest/* and vite-plugin-svelte (with its old inspector) were dropped and re-resolved, leaving everything else locked. SvelteKit stays on 2.70.3, which accepts vite 8 and plugin 7. Vite 8 builds with Rolldown, so esbuild moves to 0.28 and rollup leaves the tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5d5af359b6 | Merge remote-tracking branch 'origin/renovate/vite-8.x' into dev | ||
|
|
9be5bbae3c |
fix(discover): cap the taste-matched arm per album and artist before its LIMIT (#5356)
release / web (push) Successful in 1m37s
release / govulncheck (push) Successful in 53s
release / go (push) Successful in 2m7s
release / integration (push) Successful in 5m2s
release / android (push) Successful in 6m34s
release / Build signed APK (releases and dev) (push) Successful in 6m7s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m36s
release / Verify release artifacts (tag releases only) (push) Skipped
Summed tag weight rewards a track for carrying many of the user's tags, so on the deploy two artists whose every track carries the whole lo-fi profile took all 120 rows of the taste-unheard query. capByAlbumAndArtist ran after the LIMIT and left 6, and the arm with the lowest skip rate (12% against ~23%) handed its slots to dormant and random. The query now ranks within album, then within artist over what the album cap kept, before the LIMIT: the same walk the Go cap makes, so the bucket fills from as many artists as match. The caps come from the Go constants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9940bc375d |
feat(android): notifications when the app is closed, a specialUse delivery service (#5347)
release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m8s
release / go (push) Successful in 1m31s
release / integration (push) Successful in 5m6s
release / android (push) Successful in 6m17s
release / Build signed APK (releases and dev) (push) Successful in 6m13s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m13s
release / Verify release artifacts (tag releases only) (push) Skipped
Roundtable's shape: no FCM. A specialUse foreground service keeps the process alive; EventsStream stays connected; DeliveryLauncher runs the catch-up on every notification.created nudge and every reconnect, and is the single owner of the service's lifetime (signed in AND the device's "Notifications when the app is closed", on by default). - NotificationSync pulls the newest page and announces unread notices past a high-water mark (auth_session.notifiedUpTo, read and written through the DAO), honouring the per-kind phone pref. A first look sets the mark without announcing; more than three collapse to one line; nothing is posted while the app is on screen. - Two channels: "Your requests" and "Library health"; the ongoing notice sits on a MIN "Background connection" channel. - EventsStream: a connected flow, 2s→5min backoff with ±25% jitter, and an immediate reconnect when a network comes up (a hint, not VALIDATED) or the app comes to the foreground. - BootReceiver restarts delivery after a reboot or a self-update. - A tap opens what the notice links to (routeForLink), a pile the inbox. - POST_NOTIFICATIONS is asked for on Android 13+ once delivery is wanted, and again when the toggle is turned on. - Room v12: auth_session.backgroundDelivery and notifiedUpTo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
63709a433d |
feat(notifications): grouped email digest, new music as a daily summary (#5346)
release / govulncheck (push) Successful in 21s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m39s
release / integration (push) Successful in 5m27s
release / android (push) Successful in 5m47s
release / Build signed APK (releases and dev) (push) Successful in 5m34s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
Nothing is emailed per event. New music (request_completed) goes out at most once a day, at the summary hour in each user's own timezone, grouped by artist. Everything else is batched: one email a window after the first un-emailed item, holding whatever accumulated. - Migration 0074: notification_email_settings (summary hour, batch window, admin-configurable) and user_notification_email_state (batch start, last sent, failures and retry_after per user and group). Existing rows are stamped emailed so the upgrade sends no backlog. - The Notifier stamps emailed_at at write time when the recipient's email channel is off, so turning email on later doesn't send old items. - Read rows are never selected. A row is stamped only after the mailer accepts, in one transaction with the state, against the read's clock, so a coalesced row updated mid-send stays pending. - A failed send backs off 5m doubling to 6h; SMTP not configured just waits. - Links come from the public address; without one the email has none. - The mailer now RFC 2047-encodes subjects and strips line breaks from them. - Admin → Integrations gains a Notification emails card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
62f76290fb |
fix(android): split notification kinds out of the replayer's dispatch (detekt)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m16s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 4m39s
release / android (push) Successful in 4m59s
release / Build signed APK (releases and dev) (push) Successful in 5m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
dispatch reached cyclomatic complexity 16 with the three M489 kinds; they now share one entry that hands off to dispatchNotification. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c416157a1b |
feat(android): notifications bell, inbox screen and settings, offline-first (#5343, #5345)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m11s
release / go (push) Successful in 1m29s
release / android (push) Failing after 1m41s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 2m49s
release / Build signed APK (releases and dev) (push) Canceled after 2m50s
- The top bar carries a bell with a badge. The badge is inverse, not
error red, and counts to 9, then shows 9+. It opens the Notifications
screen: one line per notice, how long ago, "Mark all read", pull to
refresh. A tap marks the notice read and opens its screen (albums,
artists, requests, admin requests and quarantine; other admin pages
land on Admin).
- The newest 50 notices live in Room (cached_notifications, v11 with
MIGRATION_10_11), so the badge and the list work offline. A refresh
keeps a read made here that the server hasn't seen yet.
- Reads, read-all and setting toggles go through the MutationQueue
(rule 100): NOTIFICATION_READ, NOTIFICATIONS_READ_ALL and
NOTIFICATION_SETTING_SET.
- Read-all sends the newest notice shown, rounded up a millisecond
(Room keeps ms, the server µs), so a late replay leaves newer notices
unread.
- Settings collapse per kind and channel.
- The `notification.created` live event, a return to the foreground and
reconnecting all refresh the inbox.
- Settings → Notifications: a row per kind with Inbox, Phone and Email.
Admin kinds sit under "Library health". Phone and email ride on the
inbox. One line says why email is off. If the system blocks
notifications, a row opens Minstrel's notification settings; it is
re-checked on resume.
- Signing out clears the cached inbox.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f11dba2336 |
feat(api): read-all takes an optional up_to cutoff (M489)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m40s
release / integration (push) Successful in 4m40s
release / android (push) Successful in 5m1s
release / Build signed APK (releases and dev) (push) Successful in 5m12s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
POST /api/me/notifications/read-all accepts {"up_to": RFC3339}. Android
queues "mark all read" for replay when offline, and a replay landing later
must not mark notices that arrived in between, which the user never saw.
A coalesced notice updated since then has a newer created_at, so it stays
unread. An empty body still marks everything.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
87f8ed6147 |
feat(web): notification settings per kind and channel (#5345, web half)
- A Notifications section on Settings has a row per kind and a toggle each for Inbox, Phone and Email. Labels are short, menu-style. - Admin kinds sit under "Library health", for admins only. - Toggles are optimistic and send only the kind and channel touched. A failed save reverts unless something newer has happened (snippet #5106's generation counter). - With the inbox off, phone and email are disabled: they ride on it. - When email isn't usable, one line says why. With no address it links to the profile. With no SMTP an admin gets a link to Integrations and a listener is simply told. Saving the profile refreshes the settings so the line clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
956058d4a0 |
feat(web): notifications bell, unread badge and inbox panel in the header (#5342)
- The bell sits between search and the user menu. Its badge is parchment on obsidian, not the accent, which the house style keeps off general chrome. It counts up to 9, then shows 9+. - The panel lists the server-rendered title, body and relative time, newest first, with unread rows marked. Clicking a row marks it read and opens its link. "Mark all read" appears while anything is unread, and an empty inbox says "Nothing waiting for you." - createNotificationsQuery and createUnreadCountQuery poll every 60s while the tab is visible. The `notification.created` live event invalidates ['notifications'] so the badge and list refresh promptly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1e9408c835 |
feat(notifications): library health reaches admins, coalesced (#5341)
release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m22s
release / go (push) Successful in 1m43s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m16s
release / Build signed APK (releases and dev) (push) Successful in 5m27s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
- A failed scan run sends scan_failed. Each failure adds to the count and the notice shows the latest error. A scan cut short by shutdown says nothing. - Marking tracks missing sends tracks_missing with a running count. - A duplicate sweep that proposes a group it had not proposed before sends duplicates_found, counting everything awaiting review. A sweep that only re-finds known groups stays quiet, so a read notice isn't repeated every sweep (CountDuplicateGroupsDetectedSince). - A playback-error report sends playback_errors, counting the unresolved errors (CountUnresolvedPlaybackErrors). The library package gets its notifier as a package-level SetNotifier beside SetEventBus, for the same reason the bus is package-level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
069baeb14d |
feat(notifications): requests and flags reach the people who act on them (#5340)
- A new request still pending after any auto-approval notifies the admins (request_pending), but not the requester if they are an admin. A request that dedups into one already in flight is not announced again. - Approving or rejecting a request notifies the requester, and a rejection carries the admin's notes as the reason. An admin deciding their own request gets nothing. - The reconciler notifies the requester when their request arrives (request_completed), linking the matched album or artist. - A request the re-acquisition sweeper files and cannot approve itself notifies the admins. - A quarantine flag notifies every admin except the flagger, naming the track, the flagger and the reason. lidarrrequests.Service.CreateTracked reports whether a request was inserted or deduped; Create wraps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94ef8c1887 |
feat(api): notifications inbox and per-user settings endpoints (#5339)
release / go (push) Successful in 1m35s
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m18s
release / integration (push) Successful in 4m35s
release / android (push) Successful in 4m58s
release / Build signed APK (releases and dev) (push) Successful in 5m14s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
M489 step 2.
- GET /api/me/notifications?limit&before: newest first, keyset-paged on
(created_at, id) with an opaque cursor, plus the unread count.
- GET /api/me/notifications/unread-count: the badge's cheap call.
- POST /api/me/notifications/{id}/read and /read-all. Mark-read is
idempotent; another user's id is a 404, the same as a malformed one.
- GET/PUT /api/me/notification-settings: every kind the caller can receive
(admin kinds only for admins) with inbox/phone/email. PUT is partial, so an
offline replay sends only what was touched, and a batch with any invalid
change applies nothing. The response says whether email can be delivered
at all: no address on file, or SMTP not configured. A failed SMTP config
read is a 500, not "not configured".
notifications.Render turns kind + payload into title, body and link on the
server, so the web inbox, the Android inbox, the phone's shade and the email
digest all say the same thing. mailer.Configured lifts Send's readiness
check out so settings can report it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
8bf333e748 |
feat(notifications): the inbox store, one writer, coalescing and retention (#5338)
release / govulncheck (push) Successful in 42s
release / web (push) Successful in 1m34s
release / go (push) Successful in 1m51s
release / integration (push) Successful in 4m59s
release / android (push) Successful in 5m24s
release / Build signed APK (releases and dev) (push) Successful in 5m32s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m20s
release / Verify release artifacts (tag releases only) (push) Skipped
M489 step 1. The event bus is fire-and-forget, so a client that isn't connected never hears that a request completed or that tracks went missing. user_notifications is the durable record; the bus only nudges. - Migration 0073: user_notifications (kind CHECK-gated, payload jsonb, read_at, coalesce_key, emailed_at) and user_notification_prefs (per user, per kind: inbox, phone, email). A missing pref row means the kind's defaults, so nothing is seeded. - internal/notifications.Notifier is the only writer. It resolves recipients (admin kinds reach admins only, and never the excepted user), honours the inbox pref (phone and email ride on it), writes, and publishes a contentless notification.created nudge per recipient. - Burst-prone admin kinds coalesce into one unread row: tracks_missing and scan_failed add up their counts, duplicates_found and playback_errors take the latest total. Once read, the next event is a new row. - Retention: read rows go after 90 days, anything after a year, on the library_changes compactor's daily shape. Tests: unit (channel rules, kind table) and integration (recipients, nudge, coalescing both ways, prefs, owner-scoped idempotent mark-read, every kind against both schema CHECKs, retention cut-offs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |