feat(deezer): add Deezer fallback to all remaining enrichment pipelines
Audited every metadata enrichment flow that resolves tracks via Spotify /
MusicBrainz and wired the Deezer fallback into the ones that were still missing
it, so no pipeline ends up with incomplete metadata when the other providers
fail.
Rust:
- crates/webscrobbler: new deezer module (client + EnrichedTrack->Track) and a
Deezer step in resolve_track between Spotify and MusicBrainz.
- crates/mirror: enrich_via_deezer fills album_art / isrc / track & disc number
/ duration on NormalizedTrack after a Spotify miss (durationMs already ms).
TypeScript (apps/api):
- New shared lib/deezer.ts helper (enrichWithDeezer, never throws).
- nowplaying.service.ts (live scrobble path): fill album art, ISRC, duration,
track/disc number, label, release date/year, genres, artist picture, deezer
link after MusicBrainz/Spotify.
- subscribers/status.ts: Deezer fallback for album art, and resolve a correct
track number (payload -> DB -> Deezer) that is only published when > 0. Adds
trackNumber (minimum 1) to the actor.defs#trackView lexicon.
- scripts/backfill-isrc-mbid.ts: Deezer ISRC fallback when Spotify has none.
feat(sdk): add notifications + shout GIF across all SDKs; type all TS responses
Wire the new app.rocksky.notification.* XRPC group (getUnreadCount,
listNotifications, updateSeen) and shout GIF/sticker/clip attachments into
every SDK, and type all previously-untyped TypeScript client responses.
Core (crates/rocksky-sdk):
- Regenerate shout + notification lexicon bindings (adopt only those to avoid
codegen churn); shout record gains gif/facets, message now optional
- Typed AppView notification methods (unread_count/notifications/update_seen)
with NotificationView/Actor/List/UnreadCount/UpdateSeenResult wire types +
a POST mutate helper
- ShoutGif input + agent shout_with_gif / reply_shout_with_gif
FFI cores: uniffi (typed Records + methods + ShoutGifInput), capi C ABI
(rocksky_update_seen, gif shout fns), rustler NIF (update_seen, gif shout nifs).
Language SDKs:
- python/kotlin: regenerate uniffi bindings
- ruby/clojure/erlang/elixir/gleam: hand-written wrappers over capi/nif
- typescript: notifications + gif; type every Promise<unknown> read against the
regenerated lexicon views (only the generic get() escape hatch stays unknown)
- go: regenerate types; typed notification methods + gif shout variants
Bump versions: rust sdk 0.6.0, uniffi/nif 0.3.0, ts 0.8.0, python/ruby/elixir
0.8.0, gleam 1.8.0, erlang 0.5.0, kotlin 0.9.0, clojure 0.8.0-SNAPSHOT.
feat(sdk): add notifications + shout GIF across all SDKs; type all TS responses
Wire the new app.rocksky.notification.* XRPC group (getUnreadCount,
listNotifications, updateSeen) and shout GIF/sticker/clip attachments into
every SDK, and type all previously-untyped TypeScript client responses.
Core (crates/rocksky-sdk):
- Regenerate shout + notification lexicon bindings (adopt only those to avoid
codegen churn); shout record gains gif/facets, message now optional
- Typed AppView notification methods (unread_count/notifications/update_seen)
with NotificationView/Actor/List/UnreadCount/UpdateSeenResult wire types +
a POST mutate helper
- ShoutGif input + agent shout_with_gif / reply_shout_with_gif
FFI cores: uniffi (typed Records + methods + ShoutGifInput), capi C ABI
(rocksky_update_seen, gif shout fns), rustler NIF (update_seen, gif shout nifs).
Language SDKs:
- python/kotlin: regenerate uniffi bindings
- ruby/clojure/erlang/elixir/gleam: hand-written wrappers over capi/nif
- typescript: notifications + gif; type every Promise<unknown> read against the
regenerated lexicon views (only the generic get() escape hatch stays unknown)
- go: regenerate types; typed notification methods + gif shout variants
Bump versions: rust sdk 0.6.0, uniffi/nif 0.3.0, ts 0.8.0, python/ruby/elixir
0.8.0, gleam 1.8.0, erlang 0.5.0, kotlin 0.9.0, clojure 0.8.0-SNAPSHOT.