Skip to content

feat(sync): library + liked + rating hooks (Phase 1.f.desktop.5)#200

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/1-f-desktop-5-library-track-hooks
Jun 3, 2026
Merged

feat(sync): library + liked + rating hooks (Phase 1.f.desktop.5)#200
InstaZDLL merged 2 commits into
mainfrom
feat/1-f-desktop-5-library-track-hooks

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented Jun 2, 2026

Summary

Extends the canonical-id sync loop from 4b (#199) to three new entities. Same patterns, no new architecture.

  • library — full CRUD sync, mirror direct du pattern playlist (canonical-id UUID, migration + triggers + sync_id_map seed, hooks outbound dans commands/library.rs, branche apply_library_op).
  • liked_track — toggle ❤️ sync. entity_id = track.file_hash (BLAKE3 already cross-device-stable), pas de canonical column ni mapping table.
  • track_rating — set/clear 0-5 stars. Même hash-routing que liked_track. Apply valide la range, out-of-range = Ignored.

Architecture invariants (rappel)

  • Outbound : tx wrap entity write + canonical lookup + enqueue_op_in_tx. state.drain.notify() après tx.commit().
  • Inbound : apply_remote_op_in_tx bypass enqueue_op_in_tx (anti-ping-pong). Tx per op : observe_remote_conn + apply + cursor advance. Echoes (device_id match) = Skipped, mapping miss / unknown entity / malformed = Ignored.

File hash routing

Pour liked_track et track_rating, le entity_id est le BLAKE3 file_hash directement. Trade-off : si le fichier n'a pas été scanné sur device B, l'op est Ignored (le user verra le like / rating apparaître au prochain rescan du même fichier). Pas de mapping table pour track parce que le contenu du fichier EST le canonical key naturel.

What lands

Migration 20260603000001_sync_library_canonical_id.sqllibrary.canonical_id TEXT + UUIDv4 backfill (avec random() & 3 bitmask, leçon CR de 4b) + UNIQUE index + AFTER INSERT/BEFORE UPDATE triggers + sync_id_map seed.

Corelibrary::{insert_conn, update_conn, delete_conn} extraits (même pattern que playlist atomicity refactor #195). Trait methods délèguent.

sync::canonical — 3 nouvelles ENTITY_* constantes + ensure_local_library / set_canonical_library (mirror playlist) + local_track_for_hash / file_hash_for_local_track pour le hash routing.

Outbound hooks :

  • commands/library.rs::create_library/update_library/delete_library — tx + enqueue
  • commands/track.rs::toggle_like_track — tx + enqueue liked_track (insert/delete)
  • commands/track.rs::set_track_rating — tx + enqueue track_rating (set/delete)

Apply — 3 nouveaux dispatchers (apply_library_op, apply_liked_track_op, apply_track_rating_op) routés depuis apply_remote_op_in_tx.

Tests — 5 nouveaux tests dans sync::apply :

  • applies_remote_library_insert
  • applies_liked_track_via_file_hash
  • liked_track_for_unknown_hash_is_ignored
  • applies_track_rating_via_file_hash
  • track_rating_out_of_range_is_ignored

Out of scope

  • Tag editor sync (commands/edit.rs::update_track_tags) — le filesystem est source of truth, rescan résout. Cross-device tag sync est une autre feature.
  • Settings diagnostic UI pour SubscribeOutcome counts.
  • library_folder + watcher sync — les folders sont path-local, ne traversent pas device boundaries.

Test plan

  • Créer une library "Vinyles" sur device A → apparaît sur device B
  • Rename + recolor → propagent
  • Delete → cascade (track / liked_track / playlist_track) gère côté DB
  • Like un track sur A → ❤️ sur B (à condition que le même fichier soit scanné)
  • Rating 4★ sur A → reflète sur B
  • Rating sur un fichier non-scanné sur B → Ignored, pas d'erreur

Summary by CodeRabbit

Notes de version

  • Nouvelles Fonctionnalités

    • Synchronisation étendue : gestion des bibliothèques, évaluations de piste et marquages favoris entre appareils.
  • Améliorations

    • Écritures atomiques avec file d’outbound pour garantir cohérence et propagation fiable des changements.
    • Application distante renforcée : handlers idempotents et validations (ex. plage de notes).
  • Migration

    • Ajout et backfill d’un identifiant canonique pour les bibliothèques dans la base de données.

Extends the canonical-id sync infrastructure to three new entities,
all mirroring patterns already proven on playlist in 4b.

- library: same UUID-canonical-id pattern as playlist. Migration
  20260603000001 plants the column, backfills existing rows,
  installs the AFTER INSERT / BEFORE UPDATE triggers and seeds
  sync_id_map. Outbound hooks in commands/library.rs wrap each
  CRUD command in a tx with enqueue_op_in_tx. Apply branch in
  sync::apply::apply_library_op mirrors apply_playlist_op.
- liked_track: no canonical column needed — track.file_hash
  (BLAKE3) is already a stable cross-device key. toggle_like_track
  enqueues with entity_id = file_hash; apply_liked_track_op
  resolves hash → local track id via canonical::local_track_for_hash.
  A hash that hasn't been scanned locally lands Ignored.
- track_rating: same hash-keyed routing. set_track_rating wraps
  the rating UPDATE + outbox enqueue in one tx. Apply validates the
  0-5 range and rejects out-of-range as Ignored.

Core: extracted insert_conn / update_conn / delete_conn for library
following the same pattern as playlist atomicity refactor (#195).
Trait methods now delegate to the *_conn helpers.

5 new unit tests in sync::apply (library insert, liked apply, liked
miss, rating apply, rating out-of-range).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Ce PR étend l'outbox transactionnel aux entités library, liked_track et track_rating : migration canonical_id, helpers repo transactionnels, handlers d'application d'op distantes, et commandes mises à jour pour écrire dans des transactions atomiques qui enfilent des ops outbox puis notifient le drain.

Changes

Synchronisation transactionnelle pour Library et Track

Layer / File(s) Summary
Schéma et infrastructure de synchronisation canonique
src-tauri/migrations/profile/20260603000001_sync_library_canonical_id.sql, src-tauri/crates/app/src/sync/canonical.rs
Migration ajoutant library.canonical_id (backfill UUIDv4, index UNIQUE, triggers INSERT/UPDATE) et constantes ENTITY_LIBRARY, ENTITY_LIKED_TRACK, ENTITY_TRACK_RATING ; helpers ensure_local_library, set_canonical_library, local_track_for_hash, file_hash_for_local_track.
Helpers de repository pour transactions
src-tauri/crates/core/src/repository/sqlite/library.rs, src-tauri/crates/core/src/repository/sqlite/playlist.rs
Ajout de insert_conn, update_conn, delete_conn acceptant &mut SqliteConnection pour permettre des tx composées ; LibraryRepository délègue désormais à ces helpers via pool.acquire(). Petit reformattage dans playlist.rs.
Transactions de commandes library (create/update/delete)
src-tauri/crates/app/src/commands/library.rs
Refactor de create_library/update_library/delete_library : opérations SQL via *_conn en transaction, résolution/gestion canonical, enfilage d'op outbox (insert/set/delete) par champ selon le cas, commit puis state.drain.notify().
Transactions de commandes track (rating/likes)
src-tauri/crates/app/src/commands/track.rs
set_track_rating et toggle_like_track réécrites pour faire update/insert/delete atomiques, résoudre file_hash en tx, enfilement outbox (track_rating/liked_track) uniquement si DB change, commit, puis state.drain.notify().
Gestionnaires d'opérations distantes entrantes
src-tauri/crates/app/src/sync/apply.rs
Dispatch étendu pour library, liked_track, track_rating ; implémentation d'apply_library_op (insert idempotent via mapping, delete, set sur champs contrôlés), apply_liked_track_op et apply_track_rating_op (résolution par file_hash, validation stricte des payloads). Tests de schéma et cas Ignored ajoutés.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • InstaZDLL/WaveFlow#196: Intègre la tâche sync::drain qui consomme les ops outbox ; étroitement lié à la notification state.drain.notify() ajoutée ici.
  • InstaZDLL/WaveFlow#195: Introduit le pattern transactionnel + outbox pour playlists ; ce PR généralise le pattern à library et track.
  • InstaZDLL/WaveFlow#199: Changements de sync et canonical mapping pour d'autres entités (playlist) partageant la même base architecturale.

Suggested labels

type: feat, size: xl, scope: backend

Poem

📚 Canonicals et outbox en file,
🔁 Transactions claires pour chaque pile,
❤️ Likes et notes signées en tx,
🛠️ Migration, handlers et tests en mix,
✅ Commit, notify — le sync se profile.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Le titre identifie clairement les trois entités ajoutées (library, liked, rating) et leur intégration dans le système de hooks de synchronisation, reflétant le changement principal du PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description check ✅ Passed La description couvre tous les éléments majeurs : résumé clair, patterns réutilisés de #199, architecture invariants rappelés, test plan fourni et scope bien délimité.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1-f-desktop-5-library-track-hooks

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

@InstaZDLL InstaZDLL added scope: backend Rust/Tauri backend (src-tauri/) type: feat New feature size: xl > 500 lines labels Jun 2, 2026
@InstaZDLL InstaZDLL self-assigned this Jun 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/app/src/commands/track.rs`:
- Around line 625-638: The current branch uses INSERT OR IGNORE which can
silently do nothing (FK violation) but still sets now_liked = true; change the
logic to inspect the query result's rows_affected() for both the DELETE and
INSERT executed on &mut *tx (the queries acting on liked_track with
bind(track_id) and bind(now)) and derive now_liked = rows_affected() > 0 instead
of unconditionally true/false; alternatively, validate that the referenced track
exists in track before attempting the INSERT and set now_liked based on the
actual insert outcome (use the execute() result to decide).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3caad7bc-f4b3-4716-8a04-beba2c851813

📥 Commits

Reviewing files that changed from the base of the PR and between 928335e and 90f42fd.

📒 Files selected for processing (7)
  • src-tauri/crates/app/src/commands/library.rs
  • src-tauri/crates/app/src/commands/track.rs
  • src-tauri/crates/app/src/sync/apply.rs
  • src-tauri/crates/app/src/sync/canonical.rs
  • src-tauri/crates/core/src/repository/sqlite/library.rs
  • src-tauri/crates/core/src/repository/sqlite/playlist.rs
  • src-tauri/migrations/profile/20260603000001_sync_library_canonical_id.sql

Comment thread src-tauri/crates/app/src/commands/track.rs Outdated
INSERT OR IGNORE silently no-ops on FK violation (track removed
between the UI render and the IPC call), but the previous code set
now_liked = true unconditionally — the UI would render a heart
against a row that no longer exists in liked_track. Same for a
DELETE that matched 0 rows under a concurrent unlike.

Now both branches read rows_affected and gate the outbox enqueue
on did_change so a phantom op isn't sent for a no-op write.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL InstaZDLL merged commit 6f9dfad into main Jun 3, 2026
14 checks passed
@InstaZDLL InstaZDLL deleted the feat/1-f-desktop-5-library-track-hooks branch June 3, 2026 19:27
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/) size: xl > 500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant