Skip to content

refactor(plugins): load bundled plugins from resource dir (closes #280)#285

Merged
InstaZDLL merged 2 commits into
mainfrom
refactor/bundled-plugins-from-resource
Jun 20, 2026
Merged

refactor(plugins): load bundled plugins from resource dir (closes #280)#285
InstaZDLL merged 2 commits into
mainfrom
refactor/bundled-plugins-from-resource

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Draft pour test manuel — refactor de l'architecture plugins bundled pour éliminer la duplication signalée dans #280.

Closes #280

Le bug

Avant 1.5.1, ensure_bundled_plugins copiait chaque plugin bundled de BaseDirectory::Resource vers `/plugins//` à chaque boot. Résultat : sur Windows on retrouvait `web-radio` dans DEUX dossiers :

  • `%LocalAppData%\WaveFlow\plugins\web-radio\` (resource dir, shippé par l'installer)
  • `%AppData%\Roaming\app.waveflow\waveflow\plugins\web-radio\` (copie writable créée au boot)

Exactement 143 KB dupliqués (manifest.toml 1,6 KB + plugin.wasm 142 KB), pour rien fonctionnellement — le runtime n'a JAMAIS lu depuis Roaming après la première copie.

Le fix

Le runtime résout les plugins bundled directement depuis le resource dir. Plus de copy au boot.

Changes

File Change
core/plugin/mod.rs BUNDLED_PLUGINS + is_bundled_plugin movés depuis app/state.rs. PluginPaths gagne bundled_root: Option<PathBuf> + with_bundled_root builder. Path resolution route bundled ids vers bundled_root, sideloaded vers plugins_root. state_dir reste toujours dans la writable tree.
app/paths.rs AppPaths::from_handle résout BaseDirectory::Resource / plugins au startup, stocke comme bundled_plugins_dir: Option<PathBuf>. Failure logged WARN + recorded comme None (dev builds, broken installs) — fallback safe.
app/state.rs ensure_bundled_plugins supprimé. cleanup_bundled_plugin_leftovers ajouté : drop tout subdir de <app-data>/plugins/ dont le nom est dans BUNDLED_PLUGINS. Idempotent — 1.5.1 fresh install ne trouve rien à remove.
app/commands/plugins.rs list_installed_plugins walke les DEUX roots (bundled + sideloaded) avec bundled wins on collision. Helper walk_install_root extrait. Re-import is_bundled_plugin depuis core.

Compatibility

  • Fresh install 1.5.1+ : web-radio loaded depuis resource dir directement, <app-data>/plugins/ reste vide jusqu'à ce que l'user sideload qqch.
  • Upgrade 1.5.0 → 1.5.1 : la leftover `/plugins/web-radio/` est removée au premier boot (logged INFO), puis comportement fresh install. State plugin (<app-data>/plugin-data/web-radio/) intact.
  • Sideloaded shadowing a bundled id : never expected, mais le bundled wins. La sideloaded est skip + logged WARN.
  • Dev builds : si BaseDirectory::Resource / plugins n'est pas résolvable, fallback vers `/plugins/` — comportement pre-refactor preservé.

Tests

4 nouveaux tests unitaires dans core::plugin::tests :

  • bundled_plugin_routes_to_bundled_root : bundled id → resource path. state_dir reste writable.
  • sideloaded_plugin_stays_in_plugins_root_even_with_bundled_set : ids non-bundled ignorent le resource root.
  • bundled_plugin_falls_back_to_plugins_root_without_bundled_set : fallback dev/test sans bundled_root.
  • is_bundled_plugin_only_matches_known_ids : case-sensitive + empty edge.

40 tests plugin:: + 84 tests desktop unit passent. cargo check --all-targets workspace : zéro warning.

Test plan

  • cargo test workspace passe (plugin + app)
  • cargo check --all-targets workspace clean
  • Build Tauri dev (bun run tauri dev) : Web Radio loading + listing OK
  • Test manuel : Settings → Plugins, web-radio listé une seule fois avec badge "bundled"
  • Test manuel : <app-data>/plugins/ reste vide après boot fresh
  • Test manuel upgrade 1.5.0 → 1.5.1 : leftover removed au premier boot (logged), web-radio fonctionne toujours
  • Test manuel : Web Radio start/play marche
  • Build production Windows NSIS (manuel ou CI) : .wasm chargé depuis resource dir
  • Test linux .deb si possible (path resource diffère : /usr/lib/WaveFlow/plugins/)

Risks

  • Cross-platform path resolution : BaseDirectory::Resource est Tauri-géré donc devrait marcher cross-OS, mais on n'a pas testé en CI sur Linux .deb / .rpm yet. À tester manuellement.
  • Sandbox & permissions : le runtime lit depuis <resource>/plugins/<id>/plugin.wasm au lieu de <app-data>/plugins/.... Le file open + check is_file() est unchanged dans runtime.rs:283-287. Sur Linux /usr/lib/ est world-readable donc OK ; sur macOS .app bundle Resources/ pareil.

Summary by CodeRabbit

  • Améliorations

    • Meilleure gestion et résolution des plugins intégrés (bundled) et externes (sideloaded).
    • Les plugins intégrés prennent correctement priorité en cas de conflits de noms.
  • Chores

    • Nettoyage automatique des restes de configurations de plugins provenant des versions antérieures.

Pre-1.5.1, `ensure_bundled_plugins` copied every bundled plugin
(`manifest.toml` + `plugin.wasm`) from the installer's resource
tree into `<app-data>/plugins/<id>/` at every boot. That gave us
one uniform install root for both bundled and sideloaded plugins
at the cost of ~150 KB duplicated per id per install, ~10 ms of
boot-time IO, and one bad user-facing surprise: anybody who went
folder spelunking found `web-radio` sitting in two places
(`%LocalAppData%\WaveFlow\plugins\` AND
`%AppData%\Roaming\app.waveflow\waveflow\plugins\`) and rightly
asked "why?" (issue #280).

This change moves bundled plugins to resolve directly from
`BaseDirectory::Resource` and eliminates the copy entirely.

## What moves

- `BUNDLED_PLUGINS` + `is_bundled_plugin` shift from
  `crates/app/src/state.rs` into `waveflow_core::plugin`. They're a
  static fact about which ids are first-party, and core needs to
  know to route path resolution. The desktop's existing callsite
  (`commands::plugins::manifest_to_info` for the `bundled: bool`
  badge + `uninstall_plugin`'s refusal gate) re-imports from core
  unchanged.
- `PluginPaths` gains an optional `bundled_root: Option<PathBuf>`
  field + a `with_bundled_root` builder. Path-resolution methods
  (`plugin_dir`, `manifest_path`, `wasm_path`, `assets_dir`) route
  bundled ids under `bundled_root` when present, sideloaded ids
  under `plugins_root`. `state_dir` always targets `data_root`
  regardless of bundling — user state stays in the writable
  app-data tree so a bundled-plugin upgrade can't lose it.
- `AppPaths::from_handle` resolves `BaseDirectory::Resource /
  plugins` once at startup + stores it as `bundled_plugins_dir`.
  Failure (dev builds, broken installs) is logged at WARN +
  recorded as `None` so bundled resolution falls back to the
  writable tree — pre-1.5.1 behaviour, kept as a safety net.
- `AppPaths::plugin_paths()` injects `bundled_plugins_dir` into the
  `PluginPaths` it returns. Every existing callsite that pulls
  `PluginPaths` (the wasmtime runtime, every command in
  `commands/plugins.rs`) gets the new routing transparently.

## What's removed

- `ensure_bundled_plugins(handle, paths)` — the copy that was the
  whole reason `<app-data>/plugins/web-radio/` ever existed.
- Its `BaseDirectory` + `Manager` imports from `state.rs` (now
  resolved in `paths.rs`).

## What's added

- `cleanup_bundled_plugin_leftovers(paths)` — one-shot cleanup that
  drops any subdirectory of `<app-data>/plugins/` whose name is in
  `BUNDLED_PLUGINS`. Handles the 1.5.0 → 1.5.1 transition for users
  whose disk already carries the pre-refactor copies. Idempotent:
  a 1.5.1 fresh install finds nothing to remove.
- `walk_install_root(root)` helper — pulled out of
  `list_installed_plugins` so the same parse + dir-id-pin logic
  serves both the bundled walk and the sideloaded walk.
- `list_installed_plugins` now walks BOTH the bundled tree (when
  resolvable) AND the sideloaded tree, with bundled winning on any
  id collision (defence-in-depth — post-cleanup collisions
  shouldn't happen, but a stray sideloaded copy of a reserved id
  shouldn't load the wrong .wasm).

## Tests

Four new unit tests in `core::plugin::tests`:

- `bundled_plugin_routes_to_bundled_root` pins the bundled-id ->
  resource-path routing and confirms `state_dir` still hits the
  writable tree.
- `sideloaded_plugin_stays_in_plugins_root_even_with_bundled_set`
  confirms non-bundled ids ignore the resource root.
- `bundled_plugin_falls_back_to_plugins_root_without_bundled_set`
  covers the `bundled_root: None` fallback for tests + dev builds.
- `is_bundled_plugin_only_matches_known_ids` checks case-
  sensitivity + the empty-string edge.

All 40 `plugin::` unit tests + 84 desktop unit tests pass; full
workspace `cargo check --all-targets` is warning-free.

## Disk footprint

`%LocalAppData%\WaveFlow\plugins\web-radio\` (resource dir, 143 KB):
the installer's read-only copy, source of truth, unchanged.

`%AppData%\Roaming\app.waveflow\waveflow\plugins\web-radio\` (was
the writable copy): no longer created. Existing copies from a
prior 1.5.0 install are removed at boot by the cleanup pass.
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0baf2420-b137-4f5e-b9cf-3d1e090b17c2

📥 Commits

Reviewing files that changed from the base of the PR and between fe9095b and f82ceaa.

📒 Files selected for processing (2)
  • src-tauri/crates/app/src/paths.rs
  • src-tauri/crates/app/src/state.rs

📝 Walkthrough

Walkthrough

Déplace BUNDLED_PLUGINS et is_bundled_plugin dans le crate core, étend PluginPaths avec un bundled_root optionnel résolu depuis BaseDirectory::Resource, remplace la copie des plugins bundled à chaque démarrage par un nettoyage one-shot des restes pre-1.5.1, et refactorise list_installed_plugins pour scanner les deux racines avec priorité bundled.

Changes

Routage des plugins bundled et refactor du scan

Layer / File(s) Summary
BUNDLED_PLUGINS, is_bundled_plugin et PluginPaths::bundled_root dans core
src-tauri/crates/core/src/plugin/mod.rs
Ajoute BUNDLED_PLUGINS et is_bundled_plugin dans le crate core, étend PluginPaths avec le champ bundled_root et with_bundled_root, rend install_root_for conditionnel (bundled pointe vers bundled_root, sinon plugins_root), met à jour plugin_dir, et couvre tout par des tests unitaires case-sensitive.
Résolution de bundled_plugins_dir dans AppPaths et plugin_paths()
src-tauri/crates/app/src/paths.rs
Ajoute bundled_plugins_dir: Option<PathBuf> à AppPaths, résout le dossier plugins depuis BaseDirectory::Resource dans from_handle de manière non-fatale (warn + None en cas d'échec), et transmet la valeur via with_bundled_root dans plugin_paths().
Nettoyage one-shot et suppression de ensure_bundled_plugins
src-tauri/crates/app/src/state.rs
Remplace l'appel à ensure_bundled_plugins dans AppState::init par cleanup_bundled_plugin_leftovers, supprime les définitions locales de BUNDLED_PLUGINS/is_bundled_plugin/ensure_bundled_plugins, et implémente la suppression via spawn_blocking en ignorant NotFound.
walk_install_root et scan bundled-wins dans list_installed_plugins
src-tauri/crates/app/src/commands/plugins.rs
Ajoute le helper privé walk_install_root (scan d'un répertoire avec vérification nom-dossier == id manifeste), et refactorise list_installed_plugins pour scanner d'abord bundled_root puis plugins_root avec un HashSet de déduplication (bundled wins, sideloaded shadowing loggé en warn).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • InstaZDLL/WaveFlow#220 : Introduit Manifest::load_from_path utilisé directement dans walk_install_root pour charger et valider les manifest.toml.
  • InstaZDLL/WaveFlow#222 : Restructure PluginPaths avec plugins_root/data_root, base directe sur laquelle ce PR greffe le champ bundled_root.
  • InstaZDLL/WaveFlow#223 : Implémente list_installed_plugins avec scan de manifest.toml et vérification d'ID — même surface fonctionnelle remaniée ici pour le support dual-root.

Suggested labels

type: fix, size: xl

Poem

🎵 Plus de doublons dans les dossiers perdus,
bundled gagne, sideloaded attend son tour,
Le nettoyage one-shot efface le passé reçu,
HashSet veille, les ombres n'ont plus de séjour.
WaveFlow chante propre, sans échos superflus ! 🌊

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Le titre suit Conventional Commits avec scope kebab-case et résume précisément le changement principal : refactoring de l'architecture des plugins bundled pour les charger depuis le répertoire ressource.
Description check ✅ Passed La description est très complète : elle explique le bug, le fix, les changements par fichier, la compatibilité, les tests et les risques identifiés. Elle dépasse largement le minimum requis.
Linked Issues check ✅ Passed Le PR satisfait complètement l'objectif de l'issue #280 : éliminer la duplication du dossier web-radio en chargeant les plugins bundled directement depuis le resource dir sans copie au boot.
Out of Scope Changes check ✅ Passed Tous les changements restent centrés sur l'élimination de la duplication des plugins bundled et la refactorisation de l'architecture de chargement. Aucun changement hors-scope détecté.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 refactor/bundled-plugins-from-resource

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

@InstaZDLL InstaZDLL added scope: backend Rust/Tauri backend (src-tauri/) type: refactor Code refactoring size: l 200-500 lines labels Jun 20, 2026
@InstaZDLL InstaZDLL self-assigned this Jun 20, 2026
@InstaZDLL InstaZDLL marked this pull request as ready for review June 20, 2026 19:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/paths.rs`:
- Around line 64-66: The bundled_plugins_dir assignment in the
handle.path().resolve() match block does not validate that the resolved
directory actually exists on disk. After obtaining the path from resolve(), you
must verify the directory exists using standard file system checks (exists() and
is_dir() methods) before assigning it to Some(path). If the directory does not
exist, set bundled_plugins_dir to None instead so that the documented fallback
mechanism to the root plugins directory can be activated. This ensures that
missing or improperly packaged bundled plugin directories do not break plugin
loading.

In `@src-tauri/crates/app/src/state.rs`:
- Around line 125-126: The cleanup_bundled_plugin_leftovers function is being
called unconditionally at line 125, which can delete the compatibility fallback
directory when no valid bundled root is available. Check if a valid bundled root
path exists before calling cleanup_bundled_plugin_leftovers. Add a condition to
verify that the bundled plugin root is available and valid before proceeding
with the cleanup operation in the deletion loop (around lines 381-389) to
prevent removing the fallback plugin directory like web-radio that should be
preserved for compatibility.
🪄 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: fbe087b4-d58d-4343-b7c3-4ea352ae165c

📥 Commits

Reviewing files that changed from the base of the PR and between 3d1c405 and fe9095b.

📒 Files selected for processing (4)
  • src-tauri/crates/app/src/commands/plugins.rs
  • src-tauri/crates/app/src/paths.rs
  • src-tauri/crates/app/src/state.rs
  • src-tauri/crates/core/src/plugin/mod.rs

Comment thread src-tauri/crates/app/src/paths.rs
Comment thread src-tauri/crates/app/src/state.rs Outdated
@InstaZDLL InstaZDLL merged commit f75b321 into main Jun 20, 2026
14 checks passed
@InstaZDLL InstaZDLL deleted the refactor/bundled-plugins-from-resource branch June 20, 2026 19: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/) size: l 200-500 lines type: refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: web radio plugin folder ,have 2 time folders in side same files .

1 participant