Skip to content

feat(playlists): genre and decade mixes - #841

Merged
InstaZDLL merged 1 commit into
mainfrom
feat/genre-decade-mixes
Oct 12, 2026
Merged

InstaZDLL merged 1 commit into
mainfrom
feat/genre-decade-mixes

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

What

Two new generated playlist families, next to Daily Mix and On Repeat: one mix per genre and one per decade the user listens to most. Both live in the new core module smart_playlists/themed.rs.

  • Seeds. The three genres (or decades) with the most listened_ms over 90 days; when listening ties, the larger one in the library wins. A library with no history yet still gets mixes. A seed needs 25 available tracks by 4 artists, otherwise the "mix" would be one album on repeat. A year outside 1–9999 belongs to no decade, as in the Years tab.
  • Tracks. Each track scores ln(1 + plays) plus a jitter in [0, 1). The jitter is seeded from the family, the seed and the day:
    • the mix favours what the user plays, while an unplayed track can still beat one played once;
    • it changes from one day to the next, but not between two visits on the same day.
    • Play counts are all-time.
    • At most 3 tracks per artist, 50 per mix, then shuffled with the same seed so the favourites are spread through the mix.
  • Rows.
    • Stored as GenreMix { genre_id } / DecadeMix { decade } and upserted through upsert_smart_playlist.
    • The LIKE needle is the rule JSON minus its opening brace, so genre 1 cannot match genre 12. Neither variant has a slot field, so Daily Mix's "slot":N deletes cannot catch them.
    • Positions are 20+ for genres and 30+ for decades.
    • After a regen, a sweep removes the family's rows that were not rewritten, so a genre that drops out takes its mix with it.
  • Names in the user's language. regenerate_all_smart_playlists now takes MixLabels: the name and description templates, translated by the frontend, with {name} / {decade} left for the backend to fill in. Keys are added to all 17 locales.
  • UI.
    • Each family gets its own eyebrow label and fallback colour on the Home cards and the playlist header (smartFamilyStyle).
    • The user's own rule-based playlists, which are also is_smart, no longer appear in "Made for you".
    • Their header no longer reads "Daily Mix".

A fix found on the way. top_artists_with_bpm still read deezer_artist, renamed metadata_artist in 20260427000000. The query failed, the error was swallowed, and no Daily Mix ever got its artist pictures as a cover.

Docs: smart-playlists.md has a new section and an updated regen flow, and two stale lines are fixed (sidebar ordering, the regen call).

Testing

  • cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check.
  • cargo test -p waveflow-core --features sqlite themed:
    • the per-artist cap;
    • same seed gives the same mix, another seed another mix;
    • played tracks lead without filling the mix;
    • needles are unique per seed and per family, and never match Daily Mix's.
  • New app-crate tests against the real migrator, run by the CI Rust job:
    • a genre and a decade big enough get a mix named from the labels, while smaller ones do not;
    • a seed that no longer qualifies takes its mix with it, and a rewrite keeps the row.
  • bun run typecheck, bun run lint.
  • Manual test on Windows: decade mixes appear after the Daily Mixes with their own eyebrow and colour. No genre mix appeared on that library, as expected: only 54 of its 1,251 tracks carry a genre, and the largest genre holds 19.

Summary by CodeRabbit

  • Nouvelles fonctionnalités
    • Ajout de mixes intelligents par genre et par décennie, générés à partir des écoutes récentes et affichés avec des noms et descriptions localisés.
    • Les mixes apparaissent dans l’interface avec un style et un libellé adaptés à leur catégorie.
  • Améliorations
    • Les mixes sont actualisés quotidiennement, limités à 50 titres et à trois titres par artiste.
    • Les images d’artistes des Daily Mix s’appuient désormais sur les métadonnées de la bibliothèque.

Two generated families beside Daily Mix and On Repeat: one mix per genre and per decade the user listens to most (90-day listening, then size), each 50 tracks scored on plays plus a per-day jitter, three per artist. Names come translated from the frontend; rows that no longer qualify are swept. The user's own rule-based playlists leave Made for you, and the playlist header names each family.
@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: 194ca026-6244-4ef6-9c10-71fb1b104405

📥 Commits

Reviewing files that changed from the base of the PR and between 6ddc9d2 and 5c44880.


⛔ 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 (10)
  • docs/features/smart-playlists.md
  • src-tauri/crates/app/src/commands/smart_playlists.rs
  • src-tauri/crates/app/src/smart_playlists.rs
  • src-tauri/crates/core/src/smart_playlists/generator.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/components/views/PlaylistView.tsx
  • src/lib/tauri/playlist.ts
  • src/lib/tauri/smart_playlists.ts

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



📝 Walkthrough

Walkthrough

Le changement ajoute des mixes intelligents par genre et par décennie. Il étend leur génération et leur régénération côté backend, transmet les libellés localisés depuis l’interface et adapte l’affichage des familles de playlists.

Changes

Mixes intelligents thématiques

Layer / File(s) Summary
Règles et sélection des pistes
src-tauri/crates/core/src/smart_playlists/mod.rs, src-tauri/crates/core/src/smart_playlists/themed.rs
Le type de règles reconnaît les mixes par genre et par décennie. Le générateur classe les graines sur 90 jours, applique les seuils de sélection et limite les résultats à 3 pistes par artiste et 50 pistes par mix.
Écriture et régénération des mixes
src-tauri/crates/core/src/smart_playlists/themed.rs, src-tauri/crates/app/src/commands/smart_playlists.rs, src-tauri/crates/app/src/smart_playlists.rs, src-tauri/crates/core/src/smart_playlists/generator.rs
Le backend écrit et régénère les mixes thématiques, renvoie leurs identifiants et supprime les mixes d’une famille qui ne sont pas recréés. Les tests couvrent les seuils, les libellés, la régénération et les limites de sélection. La recherche des images d’artistes utilise metadata_artist.
Contrat et affichage dans l’interface
src/lib/tauri/smart_playlists.ts, src/lib/tauri/playlist.ts, src/components/views/HomeView.tsx, src/components/views/PlaylistView.tsx, docs/features/smart-playlists.md
L’interface transmet les libellés localisés et reconnaît les nouvelles familles de playlists. Elle affiche leur libellé et leur style, et exclut les playlists personnalisées du carrousel « Pour vous ». La documentation décrit la génération et l’affichage des mixes.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant HomeView
  participant regenerateAllSmartPlaylists
  participant regenerate_all_smart_playlists
  participant themed
  participant SQLite
  HomeView->>regenerateAllSmartPlaylists: transmettre les libellés localisés
  regenerateAllSmartPlaylists->>regenerate_all_smart_playlists: appeler la commande avec MixLabels
  regenerate_all_smart_playlists->>themed: régénérer les mixes par genre et décennie
  themed->>SQLite: lire les candidats et écrire les mixes
  themed-->>regenerate_all_smart_playlists: renvoyer les identifiants des mixes
  regenerate_all_smart_playlists-->>HomeView: renvoyer les identifiants de régénération
Loading

Merge Risk | ⚪ Minimal · up to 5c448

Merge Risk: ⚪ Minimal · up to 5c448

The change adds genre and decade mixes with localized labels and family-aware display. No actionable merge-blocking issue was found in review.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5c448

The new mixes remain within the existing local playlist workflow, and supplied labels are treated as text rather than commands or queries. The main design risk is overlapping regeneration: cleanup from one run could remove another run’s generated results. Individual playlist updates are protected, but the complete regeneration is not.

Retained concerns

  • Low · reliability · inferred: The new family cleanup assumes exclusive regeneration ownership. If overlapping runs select different eligible seeds, one run can delete themed playlists committed by the other because its sweep removes every family row outside its own kept-ID list. This can invalidate returned IDs or discard newer generated results. The impact is contained to generated rows in the acquired profile database; individual replacement transactions and the frontend busy guard reduce exposure but do not establish backend exclusivity.
Security review details

Security Blast Radius

  • inferred — The new database mutations operate through the acquired active-profile pool. Cleanup selects smart playlists carrying the relevant themed kind; deleting those rows cascades to their playlist membership, not to the underlying tracks. This bounds the identified concurrency concern to generated playlist state in that database.

Trust Boundaries and Controls

  • observed — Caller-controlled labels become SQL-bound names and descriptions and are rendered as React text. Family styling comes from a fixed mapping rather than label contents. The inspected label path therefore does not turn those strings into SQL fragments or executable markup.

Resilience and Maintainability Implications

  • observed — The command retains separate pool and profile-ID acquisitions from the base. The state implementation documents that these can straddle a profile switch and provides an atomic snapshot alternative. The lease protects pool lifetime only for a bounded drain interval; it does not establish atomic identity pairing or serialize regeneration.

Hardening Proposals

  • proposed — Protect each themed family’s selection, writes, and cleanup with profile-scoped backend serialization or an equivalent generation-ownership protocol. This would prevent one run’s kept-ID list from authorizing deletion of another run’s results; the frontend busy guard should remain a usability control rather than the ownership guarantee.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 67.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 9 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 respecte Conventional Commits et décrit clairement l’ajout principal des mixes par genre et par décennie.
Description check Passed La description fournit un résumé technique détaillé, les choix de conception, les changements d’interface, la correction associée et des étapes de test concrètes. Elle n’utilise pas exactement les sec…
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 67.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 9 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 9c0bc60 into main Oct 12, 2026
17 checks passed
@InstaZDLL
InstaZDLL deleted the feat/genre-decade-mixes branch October 12, 2026 09:28
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