Skip to content

feat(playlists): artist mixes - #846

Merged
InstaZDLL merged 5 commits into
mainfrom
feat/artist-mixes
Oct 12, 2026
Merged

InstaZDLL merged 5 commits into
mainfrom
feat/artist-mixes

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

What

A third generated family next to the genre and decade mixes from #841: one mix per artist the user listens to most. Each mix holds that artist plus its neighbours. Along the way, two fixes apply to every mix, plus a bug in the scanner.

Artist mixes (themed::regenerate_artist_mixes)

  • Seeds. The artists with the most listened_ms over 90 days that hold 4 songs once copies are merged. Nine are read for three mixes, so an artist that falls short leaves its place to the next one. There is no fallback without listening history: the family is "the artists you play".
  • Neighbours (up to 8):
    • First, the similar-artists cache (app.lastfm_similar), in its own order, keeping only artists with an available track.
    • Then, artists the user plays in the same sessions: a play within 30 minutes of one of the artist's over the last year (co_listened_artists). A session neighbour needs 3 distinct plays and a quarter of the closest neighbour's count.
    • Why the floor: measured on a real library, a lo-fi artist's closest session neighbour came up in 45 plays and its eighth, a K-pop act, in 5.
    • Nothing goes to the network. The cache is read, never filled, so a regen offline gives the same mixes, and an artist nobody looked up still gets neighbours from the user's own listening.
  • Tracks. Up to 15 of the artist's own songs and up to 4 per neighbour, scored and shuffled like the other mixes.
  • Floor. A mix needs 2 neighbours bringing tracks and 15 songs, lower than a genre's 25. In a library built of singles, the most played artists held 4 to 9 songs and their neighbours one or two each.
  • Rows. ArtistMix { artist_id }, at positions 10+. Names come from the frontend's artist_name / artist_description templates, in all 17 locales.

Shared similar-artists read

The cache read moves from commands/radio.rs to waveflow_core::similar. The radio and the mixes now share one implementation, and the radio still fills the cache before reading it.

One copy per song, in every mix (dedupe)

Two files of the same song (same artist, same title under canonical_name, for example an MP3 and its Hi-Res re-buy) used to land in a mix twice. Now:

  • The best copy stands for the song (bit depth, then sample rate), carrying the plays of both copies.
  • A track with no artist, or whose title folds to nothing, stays its own song.
  • Genre and decade seeds count songs rather than files, before the per-artist cap.

Scanner fix (from #840)

write_counter wrote value_type 'number', which profile_setting's CHECK refuses. As a result:

  • no backfill attempt was ever recorded, for the genre pass or the existing custom-tag pass;
  • the retry cap was never reached;
  • a folder holding one unreadable file was re-read in full on every scan.

It now writes 'int', and a test writes the counter and reads it back.

Testing

  • cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check.
  • cargo test -p waveflow-core --features sqlite themed (6 tests):
    • the per-artist cap, and the artist's larger cap;
    • one song on two files merges into its best copy;
    • seed determinism;
    • needle uniqueness.
  • New app-crate tests (run in CI):
    • an artist mix draws on the artists played beside it, while one played a day apart stays out, and the caps hold;
    • the backfill attempt counter is written and read back.
  • bun run typecheck, bun run lint.
  • Manual test on Windows, on a library built mostly of singles:
    • three artist mixes appear;
    • "Mujo Mix" holds only lo-fi from its cache neighbour and its strong session neighbours;
    • duplicates are gone.

Summary by CodeRabbit

  • Nouvelles fonctionnalités
    • Les mixes intelligents incluent désormais des mixes par artiste, basés sur des artistes proches et des écoutes réalisées au cours d’une même session.
    • Les mixes par genre et par décennie fusionnent les copies d’un même morceau et cumulent leurs écoutes.
    • La régénération inclut les mixes par artiste, dont les noms et descriptions sont affichés dans l’application.
    • La documentation précise les critères de sélection et les positions des mixes par artiste dans la barre latérale.
  • Corrections
    • Les compteurs de progression restent correctement enregistrés et relus après plusieurs mises à jour.

One mix per artist the user listens to most: up to 15 of its tracks among those of up to 8 neighbours, 4 each, from the similar-artists cache then the artists played in the same sessions, never the network. The cache read moves to core, shared with the radio.
A session neighbour now needs three of its plays near the artist's and a quarter of the closest neighbour's, counted as distinct plays rather than pairs. Without that floor eight places reached deep into artists that only shared a shuffled session.
Two files of one song, same artist and title, merge into the best copy with the plays of both, so a library holding a track twice no longer puts it in a mix twice. Genre and decade seeds count songs, not files, before the per-artist cap, and a seed that falls short leaves its place to the next.
An artist mix is mostly the artist, and its neighbours are often in a library by a single or two: eight of them brought 24 songs once copies were merged, one short of the genre floor, and the mix disappeared.
An artist is a seed with four songs once copies are merged, and a mix needs fifteen: in a library of singles the most played artists hold a handful and their neighbours one or two. A seed that falls short leaves its place. The scan's backfill attempt counter wrote a value type the table refuses, so no attempt was recorded and a folder with one unreadable file was re-read on every scan; it writes int now.
@InstaZDLL InstaZDLL added scope: frontend React/Vite frontend (src/) scope: backend Rust/Tauri backend (src-tauri/) scope: i18n Translations (src/i18n/) scope: docs Docs, README, assets type: feat New feature size: xl > 500 lines labels Oct 12, 2026
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: InstaZDLL/WaveFlow/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: eaa78f49-dfd2-4102-8e1d-82ec3207f66a

📥 Commits

Reviewing files that changed from the base of the PR and between 49fc98d and 6cfb523.


⛔ Files ignored due to path filters (17)
  • src/i18n/locales/ar.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/de.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/en.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/es.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/fr.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/hi.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/id.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/it.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/ja.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/ko.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/nl.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/pt-BR.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/pt.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/ru.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/tr.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/zh-CN.json is excluded by !src/i18n/locales/**
  • src/i18n/locales/zh-TW.json is excluded by !src/i18n/locales/**

📒 Files selected for processing (11)
  • docs/features/smart-playlists.md
  • src-tauri/crates/app/src/commands/radio.rs
  • src-tauri/crates/app/src/commands/scan.rs
  • src-tauri/crates/app/src/commands/smart_playlists.rs
  • src-tauri/crates/core/src/lib.rs
  • src-tauri/crates/core/src/similar.rs
  • src-tauri/crates/core/src/smart_playlists/mod.rs
  • src-tauri/crates/core/src/smart_playlists/themed.rs
  • src/components/views/HomeView.tsx
  • src/lib/tauri/playlist.ts
  • src/lib/tauri/smart_playlists.ts

Included review availability: This review used your included allowance. 1 included review remains after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.



📝 Walkthrough

Walkthrough

Ce changement ajoute les mixes intelligents par artiste, la fusion des copies de pistes dans les mixes thématiques et un résolveur partagé d’artistes similaires. Il met aussi à jour les types et l’interface, la documentation et l’écriture des compteurs de backfill.

Changes

Mixes intelligents par artiste et artistes similaires

Couche / Fichier(s) Résumé
Résolution des artistes similaires
src-tauri/crates/core/src/lib.rs, src-tauri/crates/core/src/similar.rs, src-tauri/crates/app/src/commands/radio.rs
Le cœur ajoute la lecture du cache Last.fm et le calcul des artistes coécoutés. La commande radio utilise le résolveur partagé.
Fusion des pistes et régénération thématique
src-tauri/crates/core/src/smart_playlists/themed.rs, docs/features/smart-playlists.md
Les copies d’une piste sont fusionnées selon l’artiste et le titre canonique. Les mixes par genre et décennie parcourent les graines candidates jusqu’à produire trois mixes admissibles.
Génération des mixes par artiste
src-tauri/crates/core/src/smart_playlists/mod.rs, src-tauri/crates/core/src/smart_playlists/themed.rs, src-tauri/crates/app/src/commands/smart_playlists.rs
La nouvelle famille sélectionne des graines et des voisins depuis le cache de similarité et les écoutes en session. La régénération renvoie les identifiants des mixes créés.
Interface et documentation
src/lib/tauri/playlist.ts, src/lib/tauri/smart_playlists.ts, src/components/views/HomeView.tsx, docs/features/smart-playlists.md
Les types et styles reconnaissent artist_mix. L’interface transmet les modèles de nom et de description. La documentation décrit les critères et le tri de cette famille.

Compteurs de backfill

Couche / Fichier(s) Résumé
Écriture et vérification des compteurs
src-tauri/crates/app/src/commands/scan.rs
write_counter utilise le type int. Un test vérifie la valeur initiale et la relecture après plusieurs mises à jour.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant CommandeRegeneration
  participant regenerate_artist_mixes
  participant cached_similar_library_ids
  participant co_listened_artists
  participant SQLite
  CommandeRegeneration->>regenerate_artist_mixes: paramètres de profil et libellés
  regenerate_artist_mixes->>cached_similar_library_ids: artiste graine
  cached_similar_library_ids->>SQLite: cache Last.fm et artistes de la bibliothèque
  regenerate_artist_mixes->>co_listened_artists: artiste graine et limite
  co_listened_artists->>SQLite: écoutes en session sur 365 jours
  regenerate_artist_mixes->>SQLite: pistes sélectionnées et mix enregistré
Loading

Merge Risk | ⚪ Minimal · up to 6cfb5

Merge Risk: ⚪ Minimal · up to 6cfb5

No confirmed issue blocks merging; the artist-mix translation keys could not be verified from the available checkout.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 6cfb5

The new mixes use existing listening data and cached similarities without adding a network request. Individual playlist updates remain transactional. Regeneration can still leave partially refreshed results after interruption, and incomplete coverage prevents a stronger assurance.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The traced new data path operates on one supplied profile pool and reads the shared application similarity cache. Imported metadata, cached names and listening activity can influence recommendations, but the inspected resolution path does not grant them network or credential authority.

Trust Boundaries and Controls

  • observed — Cache names, artist IDs and playlist labels are bound SQL values. Dynamic candidate queries construct only placeholder lists. The command obtains its pool from application state, not a client-supplied database path.

Resilience and Maintainability Implications

  • inferred — Transactions contain each playlist's metadata and track replacement, but invocation-local keep sets and subsequent family sweeps do not establish ownership across concurrent regenerations. This is an inherited persistence pattern, now reused for artist mixes, rather than evidence of expanded attacker privileges.

Hardening Proposals

  • proposed — Consider coordinating regeneration per profile so one invocation owns the family refresh and sweep. This would harden the existing transition pattern against overlapping runs without implying a verified security vulnerability.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 78.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 10 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed Le titre « feat(playlists): artist mixes » est concis, conforme à Conventional Commits et décrit le changement principal.
Description check Passed La description explique le fonctionnement des mixes par artiste, les changements partagés, la correction du scanner et les validations exécutées. Elle est suffisamment complète, même si elle ne repren…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 78.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 10 files. (1 skipped: 1 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@InstaZDLL InstaZDLL self-assigned this Oct 12, 2026
@InstaZDLL
InstaZDLL merged commit df95e7f into main Oct 12, 2026
17 checks passed
@InstaZDLL
InstaZDLL deleted the feat/artist-mixes branch October 12, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: backend Rust/Tauri backend (src-tauri/) scope: docs Docs, README, assets scope: frontend React/Vite frontend (src/) scope: i18n Translations (src/i18n/) size: xl > 500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant